Newer
Older
EnvoyControlPlane / static / secrets.js
// secrets.js
import { API_BASE_URL, configStore, cleanupConfigStore } from './global.js';

// =========================================================================
// SECRET UTILITIES
// =========================================================================

/**
 * Extracts a concise description of the secret type (e.g., TlsCertificate).
 * @param {object} secret - The secret configuration object.
 * @returns {string} A string describing the secret type.
 */
function getSecretTypeDetails(secret) {
    try {
        const secretType = secret.Type;
        if (!secretType) return '<span style="color: gray;">(Unknown Type)</span>';
        
        // Find the key that is not 'name' or 'Type' itself
        const typeKeys = Object.keys(secretType);
        const typeName = typeKeys.find(key => key !== 'name');

        if (typeName) {
            // Convert 'TlsCertificate' to 'TLS Certificate'
            return typeName.replace(/([A-Z])/g, ' $1').trim();
        }
        return '<span style="color: gray;">(Generic)</span>';
    } catch {
        return '<span style="color: gray;">(Config Error)</span>';
    }
}

// =========================================================================
// SECRET CORE LOGIC (listSecrets)
// =========================================================================

export async function listSecrets() {
    const tableBody = document.getElementById('secret-table-body');
    if (!tableBody) {
        console.error("Could not find element with ID 'secret-table-body'.");
        return; 
    }
    
    tableBody.innerHTML =
        '<tr><td colspan="4" style="text-align: center; padding: 20px;">Loading...</td></tr>';

    try {
        // The API operations show /list-secrets returns enabled and disabled lists
        const response = await fetch(`${API_BASE_URL}/list-secrets`);
        if (!response.ok) throw new Error(response.statusText);

        const secretResponse = await response.json();

        // Combine enabled and disabled secrets for display
        const allSecrets = [
            ...(secretResponse.enabled || []).map(s => ({ ...s, status: 'Enabled', configData: s })),
            ...(secretResponse.disabled || []).map(s => ({ ...s, status: 'Disabled', configData: s }))
        ];

        if (!allSecrets.length) {
            tableBody.innerHTML =
                '<tr><td colspan="4" style="text-align: center; color: var(--secondary-color);">No secrets found.</td></tr>';
            configStore.secrets = {}; 
            return;
        }
        cleanupConfigStore(); 

        // Store full configs in memory by name
        configStore.secrets = allSecrets.reduce((acc, s) => {
            const existingYaml = acc[s.name]?.yaml; 
            acc[s.name] = { ...s.configData, yaml: existingYaml };
            return acc;
        }, configStore.secrets);


        tableBody.innerHTML = '';
        allSecrets.forEach(secret => {
            const row = tableBody.insertRow();
            if (secret.status === 'Disabled') row.classList.add('disabled-row');

            let actionButtons = '';
            // NOTE: Assuming the API supports enable/disable for secrets like clusters
            if (secret.status === 'Enabled') {
                actionButtons = `<button class="action-button disable" onclick="window.disableSecret('${secret.name}', event)">Disable</button>`;
            } else {
                actionButtons = `
                    <button class="action-button enable" onclick="window.enableSecret('${secret.name}', event)">Enable</button>
                    <button class="action-button remove" onclick="window.removeSecret('${secret.name}', event)">Remove</button>
                `;
            }

            // Secret Name Hyperlink (uses showSecretConfigModal, which must be imported from global.js or defined globally)
            const secretNameCell = row.insertCell();
            secretNameCell.innerHTML = 
                `<a href="#" onclick="event.preventDefault(); window.showSecretConfigModal('${secret.name}')"><span class="secret-name">${secret.name}</span></a>`;
            
            row.insertCell().textContent = secret.status;
            row.insertCell().innerHTML = getSecretTypeDetails(secret);
            row.insertCell().innerHTML = actionButtons;
        });
    } catch (error) {
        tableBody.innerHTML = `<tr><td colspan="4" class="error" style="text-align: center;">🚨 Secret Error: ${error.message}</td></tr>`;
        console.error("Secret Fetch/Parse Error:", error);
    }
}


// =========================================================================
// SECRET ENABLE/DISABLE/REMOVE LOGIC (toggleSecretStatus)
// =========================================================================

async function toggleSecretStatus(secretName, action) {
    // API endpoints are assumed to be /remove-secret, /enable-secret, /disable-secret
    let url = (action === 'remove') ? `${API_BASE_URL}/remove-secret` : `${API_BASE_URL}/${action}-secret`; 
    const payload = { name: secretName };

    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(`Secret '${secretName}' successfully ${action}d.`);
        cleanupConfigStore();
        listSecrets();
    } catch (error) {
        console.error(`Failed to ${action} secret '${secretName}':`, error);
        alert(`Failed to ${action} secret '${secretName}'. Check console for details.`);
    }
}

// Expose these functions globally for inline HTML onclick handlers
export function disableSecret(secretName, event) {
    event.stopPropagation();
    if (confirm(`Are you sure you want to DISABLE secret: ${secretName}?`)) {
        toggleSecretStatus(secretName, 'disable');
    }
}

export function enableSecret(secretName, event) {
    event.stopPropagation();
    if (confirm(`Are you sure you want to ENABLE secret: ${secretName}?`)) {
        toggleSecretStatus(secretName, 'enable');
    }
}

export function removeSecret(secretName, event) {
    event.stopPropagation();
    if (confirm(`⚠️ WARNING: Are you absolutely sure you want to PERMANENTLY REMOVE secret: ${secretName}? This action cannot be undone.`)) {
        toggleSecretStatus(secretName, 'remove');
    }
}

// =========================================================================
// ADD SECRET LOGIC (showAddSecretModal, hideAddSecretModal, submitNewSecret)
// =========================================================================

/**
 * Shows the modal for adding a new secret.
 */
export function showAddSecretModal() {
    document.getElementById('add-secret-yaml-input').value = '';
    document.getElementById('addSecretModal').style.display = 'block';
}

/**
 * Hides the modal for adding a new secret.
 */
export function hideAddSecretModal() {
    const modal = document.getElementById('addSecretModal');
    if (modal) {
        modal.style.display = 'none';
        document.getElementById('add-secret-yaml-input').value = ''; 
    }
}


/**
 * Submits the new secret YAML to the /add-secret endpoint.
 */
export async function submitNewSecret() {
    const yamlInput = document.getElementById('add-secret-yaml-input');
    const secretYaml = yamlInput.value.trim();

    if (!secretYaml) {
        alert('Please paste the secret YAML configuration.');
        return;
    }

    try {
        // The /add-secret endpoint expects a JSON body with a 'YAML' key containing the stringified YAML.
        const payload = { YAML: secretYaml };
        const url = `${API_BASE_URL}/add-secret`;
        
        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 secret successfully added.`);
        alert('Secret successfully added! The dashboard will now refresh.');
        
        yamlInput.value = '';
        hideAddSecretModal(); 
        
        cleanupConfigStore();
        listSecrets(); 

    } catch (error) {
        console.error(`Failed to add new secret:`, error);
        alert(`Failed to add new secret. Check console for details. Error: ${error.message}`);
    }
}