Newer
Older
EnvoyControlPlane / static / js / components / clustersTable.js
// static/js/components/clustersTable.js
import { configStore, cleanupConfigStore } from '../store/configStore.js';
import { fetchClusters, toggleClusterStatus, fetchClusterYaml } from '../api/clustersService.js';
import { setupConfigModal } from './modals.js';

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>';

        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>';
    }
}

export async function renderClustersTable() {
    const tableBody = document.getElementById('cluster-table-body');
    if (!tableBody) return;

    tableBody.innerHTML = '<tr><td colspan="5" style="text-align: center; padding: 20px;">Loading...</td></tr>';

    try {
        const clusterResponse = await fetchClusters();
        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();
        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');

            const clusterNameCell = row.insertCell();
            const clusterLink = document.createElement('a');
            clusterLink.href = '#';
            clusterLink.innerHTML = `<span class="cluster-name">${cluster.name}</span>`;
            clusterLink.addEventListener('click', (e) => {
                e.preventDefault();
                showClusterConfig(cluster.name);
            });
            clusterNameCell.appendChild(clusterLink);

            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`;

            const actionsCell = row.insertCell();
            if (cluster.status === 'Enabled') {
                const disableBtn = document.createElement('button');
                disableBtn.className = 'action-button disable';
                disableBtn.textContent = 'Disable';
                disableBtn.onclick = (e) => {
                    e.stopPropagation();
                    if (confirm(`Are you sure you want to DISABLE cluster: ${cluster.name}?`)) {
                        handleStatusToggle(cluster.name, 'disable');
                    }
                };
                actionsCell.appendChild(disableBtn);
            } else {
                const enableBtn = document.createElement('button');
                enableBtn.className = 'action-button enable';
                enableBtn.textContent = 'Enable';
                enableBtn.onclick = (e) => {
                    e.stopPropagation();
                    if (confirm(`Are you sure you want to ENABLE cluster: ${cluster.name}?`)) {
                        handleStatusToggle(cluster.name, 'enable');
                    }
                };
                actionsCell.appendChild(enableBtn);

                const removeBtn = document.createElement('button');
                removeBtn.className = 'action-button remove';
                removeBtn.textContent = 'Remove';
                removeBtn.onclick = (e) => {
                    e.stopPropagation();
                    if (confirm(`⚠️ WARNING: Are you absolutely sure you want to PERMANENTLY REMOVE cluster: ${cluster.name}? This action cannot be undone.`)) {
                        handleStatusToggle(cluster.name, 'remove');
                    }
                };
                actionsCell.appendChild(removeBtn);
            }
        });
    } catch (error) {
        tableBody.innerHTML = `<tr><td colspan="5" class="error" style="text-align: center;">🚨 Cluster Error: ${error.message}</td></tr>`;
    }
}

async function handleStatusToggle(name, action) {
    try {
        await toggleClusterStatus(name, action);
        cleanupConfigStore();
        renderClustersTable();
    } catch (error) {
        alert(`Failed to ${action} cluster '${name}'. Error: ${error.message}`);
    }
}

async function showClusterConfig(name) {
    const config = configStore.clusters[name];
    if (!config) return;

    let yamlData = config.yaml || 'Loading YAML...';
    if (yamlData === 'Loading YAML...') {
        try {
            yamlData = await fetchClusterYaml(name);
            configStore.clusters[name].yaml = yamlData;
        } catch (error) {
            yamlData = `Error fetching YAML: ${error.message}`;
        }
    }
    setupConfigModal(`Full Config for Cluster: ${name}`, config, yamlData);
}