// 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}`); } }