// clusters.js
import { API_BASE_URL, configStore, cleanupConfigStore, showClusterConfigModal } from './global.js';
// =========================================================================
// CLUSTER UTILITIES
// =========================================================================
function getClusterEndpointDetails(cluster) {
try {
const endpoints = cluster.load_assignment?.endpoints;
if (!endpoints?.length) return '<span style="color: gray;">(No Endpoints)</span>';
const lbEndpoints = endpoints[0].lb_endpoints;
if (!lbEndpoints?.length) return '<span style="color: gray;">(No LB Endpoints)</span>';
// Extract address and port details
const endpointObj = lbEndpoints[0].HostIdentifier?.Endpoint || lbEndpoints[0].endpoint;
const address = endpointObj.address.Address.SocketAddress.address;
const port = endpointObj.address.Address.SocketAddress.PortSpecifier.PortValue;
const tls = cluster.transport_socket ? '<span class="tls-badge">TLS/SSL</span>' : '';
return `${address}:${port} ${tls}`;
} catch {
return '<span style="color: gray;">(Config Error)</span>';
}
}
// =========================================================================
// CLUSTER CORE LOGIC
// =========================================================================
export async function listClusters() {
const tableBody = document.getElementById('cluster-table-body');
if (!tableBody) {
console.error("Could not find element with ID 'cluster-table-body'.");
return;
}
tableBody.innerHTML =
'<tr><td colspan="5" style="text-align: center; padding: 20px;">Loading...</td></tr>';
try {
const response = await fetch(`${API_BASE_URL}/list-clusters`);
if (!response.ok) throw new Error(response.statusText);
const clusterResponse = await response.json();
const allClusters = [
...(clusterResponse.enabled || []).map(c => ({ ...c, status: 'Enabled', configData: c })),
...(clusterResponse.disabled || []).map(c => ({ ...c, status: 'Disabled', configData: c }))
];
if (!allClusters.length) {
tableBody.innerHTML =
'<tr><td colspan="5" style="text-align: center; color: var(--secondary-color);">No clusters found.</td></tr>';
configStore.clusters = {};
return;
}
cleanupConfigStore();
// Store full configs in memory by name
configStore.clusters = allClusters.reduce((acc, c) => {
const existingYaml = acc[c.name]?.yaml;
acc[c.name] = { ...c.configData, yaml: existingYaml };
return acc;
}, configStore.clusters);
tableBody.innerHTML = '';
allClusters.forEach(cluster => {
const row = tableBody.insertRow();
if (cluster.status === 'Disabled') row.classList.add('disabled-row');
let actionButtons = '';
if (cluster.status === 'Enabled') {
actionButtons = `<button class="action-button disable" onclick="window.disableCluster('${cluster.name}', event)">Disable</button>`;
} else {
// When disabled, show Enable and Remove buttons
actionButtons = `
<button class="action-button enable" onclick="window.enableCluster('${cluster.name}', event)">Enable</button>
<button class="action-button remove" onclick="window.removeCluster('${cluster.name}', event)">Remove</button>
`;
}
// Cluster Name Hyperlink (uses showClusterConfigModal from global.js)
const clusterNameCell = row.insertCell();
clusterNameCell.innerHTML =
`<a href="#" onclick="event.preventDefault(); window.showClusterConfigModal('${cluster.name}')"><span class="cluster-name">${cluster.name}</span></a>`;
row.insertCell().textContent = cluster.status;
row.insertCell().innerHTML = getClusterEndpointDetails(cluster);
row.insertCell().textContent =
`${cluster.connect_timeout?.seconds || 0}.${(cluster.connect_timeout?.nanos / 1e6 || 0).toFixed(0).padStart(3, '0')}s`;
row.insertCell().innerHTML = actionButtons;
});
} catch (error) {
tableBody.innerHTML = `<tr><td colspan="5" class="error" style="text-align: center;">🚨 Cluster Error: ${error.message}</td></tr>`;
console.error("Cluster Fetch/Parse Error:", error);
}
}
// =========================================================================
// CLUSTER ENABLE/DISABLE/REMOVE LOGIC
// =========================================================================
async function toggleClusterStatus(clusterName, action) {
let url = (action === 'remove') ? `${API_BASE_URL}/remove-cluster` : `${API_BASE_URL}/${action}-cluster`;
const payload = { name: clusterName };
try {
const response = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
if (!response.ok) {
const errorBody = await response.text();
throw new Error(`HTTP Error ${response.status}: ${errorBody}`);
}
console.log(`Cluster '${clusterName}' successfully ${action}d.`);
cleanupConfigStore();
listClusters();
} catch (error) {
console.error(`Failed to ${action} cluster '${clusterName}':`, error);
alert(`Failed to ${action} cluster '${clusterName}'. Check console for details.`);
}
}
export function disableCluster(clusterName, event) {
event.stopPropagation();
if (confirm(`Are you sure you want to DISABLE cluster: ${clusterName}?`)) {
toggleClusterStatus(clusterName, 'disable');
}
}
export function enableCluster(clusterName, event) {
event.stopPropagation();
if (confirm(`Are you sure you want to ENABLE cluster: ${clusterName}?`)) {
toggleClusterStatus(clusterName, 'enable');
}
}
export function removeCluster(clusterName, event) {
event.stopPropagation();
if (confirm(`⚠️ WARNING: Are you absolutely sure you want to PERMANENTLY REMOVE cluster: ${clusterName}? This action cannot be undone.`)) {
toggleClusterStatus(clusterName, 'remove');
}
}
// =========================================================================
// ADD CLUSTER LOGIC
// =========================================================================
/**
* Shows the modal for adding a new cluster.
*/
export function showAddClusterModal() {
// document.getElementById('add-cluster-modal-title').textContent =
// `Add New Cluster`;
document.getElementById('add-cluster-yaml-input').value = '';
document.getElementById('addClusterModal').style.display = 'block';
}
/**
* Hides the modal for adding a new cluster.
*/
export function hideAddClusterModal() {
const modal = document.getElementById('addClusterModal');
if (modal) {
modal.style.display = 'none';
document.getElementById('add-cluster-yaml-input').value = '';
}
}
/**
* Submits the new cluster YAML to the /add-cluster endpoint.
*/
export async function submitNewCluster() {
const yamlInput = document.getElementById('add-cluster-yaml-input');
const clusterYaml = yamlInput.value.trim();
if (!clusterYaml) {
alert('Please paste the cluster YAML configuration.');
return;
}
try {
const payload = { yaml: clusterYaml };
const url = `${API_BASE_URL}/add-cluster`;
const response = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
if (!response.ok) {
const errorBody = await response.text();
throw new Error(`HTTP Error ${response.status}: ${errorBody}`);
}
console.log(`New cluster successfully added.`);
alert('Cluster successfully added! The dashboard will now refresh.');
yamlInput.value = '';
hideAddClusterModal();
cleanupConfigStore();
listClusters();
} catch (error) {
console.error(`Failed to add new cluster:`, error);
alert(`Failed to add new cluster. Check console for details. Error: ${error.message}`);
}
}