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


/**
 * 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');

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

        // ADD: Append validity status for TLS Certificates
        if (typeName === 'TlsCertificate' && secret.validityStatus) {
            const statusText = secret.validityStatus; // 'Valid' or 'Invalid'
            const statusClass = statusText.toLowerCase(); // 'valid' or 'invalid'
            typeDetails += ` <span class="status-label ${statusClass}">${statusText}</span>`;
        }

// NEW: Append rotation status for TLS Certificates with HOVER DETAILS
        if (typeName === 'TlsCertificate' && secret.rotationStatus === 'Enabled') {
            const rotationInfo = secret.rotationDetails; 
            let tooltipContent = 'Auto Rotation Enabled.'; // Renamed from tooltipText to tooltipContent

            if (rotationInfo) {
                // 1. Convert renew_before to display format
                let renewBeforeDisplay = rotationInfo.renew_before || 'N/A';
                const hoursMatch = renewBeforeDisplay.match(/(\d+)h/);
                if (hoursMatch) {
                    const hours = parseInt(hoursMatch[1], 10);
                    const days = Math.round(hours / 24);
                    renewBeforeDisplay = `${days}d (${hours}h)`;
                }

                const rotationStatusText = rotationInfo.rotation_enabled ? 'Active' : 'Configured (Disabled)';
                const expiresAtText = rotationInfo.expires_at || 'N/A';

                // 2. Assemble the full tooltip content with actual newlines
                //    This content will be used by our CSS tooltip.
                tooltipContent = `
Rotation Status: ${rotationStatusText}
Domain: ${rotationInfo.domain}
Secret Name: ${rotationInfo.secret_name}
Renew Before: ${renewBeforeDisplay}
Expires At: ${expiresAtText}
                `.trim(); 
            }

            // --- IMPORTANT CHANGE HERE ---
            // Remove the 'title' attribute and use 'data-tooltip' instead.
            // The 'title' attribute can be kept as a fallback for accessibility,
            // but the CSS tooltip will take precedence.
            typeDetails += `<span class="status-label rotation-enabled has-tooltip" data-tooltip="${tooltipContent}">Auto Rotation Enabled</span>`;
        }
        return typeDetails;

    } catch {
        return '<span style="color: gray;">(Config Error)';
    }
}

/**
 * Checks if a secret is a TLS Certificate.
 * @param {object} secret - The secret configuration object.
 * @returns {boolean} True if the secret is a TLS Certificate.
 */
function isTlsCertificate(secret) {
    try {
        const typeKeys = Object.keys(secret.Type);
        return typeKeys.some(key => key === 'TlsCertificate');
    } catch {
        return false;
    }
}


// =========================================================================
// CERTIFICATE LOGIC (API & Modal) (Modified/New)
// =========================================================================

/**
 * Calls the backend API to parse and retrieve certificate details.
 * @param {string} certificatePem - The PEM encoded certificate string.
 * @returns {Promise<object>} The parsed certificate details as a JSON object.
 */
async function getCertificateDetails(certificatePem) {
    const url = `${API_BASE_URL}/parse-certificate`;
    const payload = { certificate_pem: certificatePem };

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

    return response.json();
}

/**
 * Calls the backend API to check the validity of a certificate.
 * @param {string} certificatePem - The PEM encoded certificate string.
 * @returns {Promise<boolean>} True if the certificate is valid, false otherwise.
 */
async function checkCertificateValidity(certificatePem) {
    const url = `${API_BASE_URL}/check-certificate-validity`;
    const payload = { certificate_pem: certificatePem };

    try {
        const response = await fetch(url, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(payload)
        });

        if (!response.ok) {
            // Treat an un-ok response as invalid or uncheckable, but don't crash the UI.
            console.warn(`Validity check failed with status ${response.status} for a certificate.`);
            return false;
        }

        const result = await response.json();
        // The API response structure is { "valid": true/false }
        return result.valid === true;

    } catch (error) {
        console.error("Error during certificate validity check:", error);
        return false; // Assume invalid on API failure
    }
}

/**
 * Shows a modal with the detailed certificate information.
 * @param {string} secretName - The name of the secret.
 */
export async function showCertificateDetailsModal(secretName) {
    const modal = document.getElementById('certificateDetailsModal');
    const content = document.getElementById('certificate-details-content');
    if (!modal || !content) {
        console.error("Certificate modal elements not found. Ensure 'certificateDetailsModal' and 'certificate-details-content' exist in your HTML.");
        alert("Certificate details modal is not properly configured in HTML.");
        return;
    }
    
    // Get the full secret config, which should contain the 'certificate_pem'
    const secretConfig = configStore.secrets[secretName];
    // Drill down to the PEM string
    const certPem = secretConfig?.Type?.TlsCertificate?.certificate_chain?.Specifier?.InlineString

    if (!certPem) {
        alert(`Could not find certificate PEM in the configuration for secret: ${secretName}`);
        return;
    }

    content.innerHTML = `<p style="text-align: center;">Parsing certificate for <strong>${secretName}</strong>...</p>`;
    modal.style.display = 'block';

    try {
        const details = await getCertificateDetails(certPem);
        
        // Display the pretty-printed JSON response
        content.innerHTML = `
            <h3>TLS Certificate Details: ${secretName}</h3>
            <pre style="white-space: pre-wrap; word-wrap: break-word; background: var(--input-bg); padding: 10px; border-radius: 5px;">${JSON.stringify(details, null, 2)}</pre>
        `;
    } catch (error) {
        content.innerHTML = `
            <h3>Error Parsing Certificate: ${secretName}</h3>
            <p class="error">🚨 Failed to parse certificate. Error: ${error.message}</p>
        `;
        console.error("Certificate Parsing Error:", error);
    }
}

export function hideCertificateDetailsModal() {
    const modal = document.getElementById('certificateDetailsModal');
    if (modal) {
        modal.style.display = 'none';
        // Clear the input when closing
        // NOTE: Changed .value to .innerHTML as 'certificate-details-content' is a container, not an input.
        const content = document.getElementById('certificate-details-content');
        if (content) content.innerHTML = '';
    }
}

// Expose the new function globally for inline HTML onclick handlers
window.showCertificateDetailsModal = showCertificateDetailsModal;
window.hideCertificateDetailsModal = hideCertificateDetailsModal;


// -------------------------------------------------------------------------
// NEW ROTATION SETTINGS LOGIC
// -------------------------------------------------------------------------

/**
 * Calls the backend API to fetch the current certificate rotation status.
 * @param {string} secretName - The name of the secret/certificate.
 * @returns {Promise<object>} The rotation status as a JSON object.
 */
async function getCertificateRotationStatus(secretName) {
    // NOTE: Using a GET request with query parameter, similar to the user's curl example
    const url = `${API_BASE_URL}/get-certificate?secret_name=${encodeURIComponent(secretName)}`;
    
    try {
        const response = await fetch(url, { method: 'GET' });

        if (!response.ok) {
            const errorText = await response.text();
            let errorMessage = `HTTP Error ${response.status}: ${response.statusText}`;
            try {
                errorMessage = JSON.parse(errorText).error || JSON.parse(errorText).message || errorText;
            } catch {
                errorMessage = errorText || errorMessage;
            }
            throw new Error(errorMessage);
        }

        return response.json();

    } catch (error) {
        console.error(`Failed to fetch rotation status for '${secretName}':`, error);
        throw new Error(`Failed to load rotation status. Error: ${error.message}`);
    }
}

/**
 * Calls the backend API to enable certificate rotation.
 * @param {string} secretName - The name of the secret/certificate.
 * @param {string} domain - The domain name associated with the certificate.
 * @param {string} renewBefore - The Go duration string (e.g., "720h").
 */
async function enableCertificateRotation(secretName, domain, renewBefore) {
    const url = `${API_BASE_URL}/enable-certificate-rotation`;
    const payload = { 
        domain: domain.trim(),
        secret_name: secretName,
        renew_before: renewBefore.trim()
    };

    console.log(`Attempting to enable rotation for secret: ${secretName}`, payload);

    try {
        const response = await fetch(url, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(payload)
        });

        if (!response.ok) {
            const errorText = await response.text();
            let errorMessage = `HTTP Error ${response.status}: ${response.statusText}`;
            try {
                // Try to extract a clean error message from the JSON response
                errorMessage = JSON.parse(errorText).error || JSON.parse(errorText).message || errorText;
            } catch {
                errorMessage = errorText || errorMessage;
            }
            throw new Error(errorMessage);
        }

        const responseBody = await response.json(); 
        console.log(`Rotation successfully enabled for '${domain}':`, responseBody);
        alert(`Certificate rotation successfully enabled for ${domain}! The list will now refresh.`);

        hideRotationSettingsModal();
        cleanupConfigStore(); 
        listSecrets(); 

    } catch (error) {
        console.error(`Failed to enable rotation for '${domain}':`, error);
        alert(`🚨 Failed to enable rotation for ${domain}. Error: ${error.message}`);
    }
}

/**
 * Calls the backend API to disable certificate rotation.
 * @param {string} secretName - The name of the secret/certificate.
 */
async function disableCertificateRotation(secretName, domain) {
    const url = `${API_BASE_URL}/disable-certificate-rotation`;
    const payload = { secret_name: secretName , domain: domain.trim()};

    console.log(`Attempting to disable rotation for secret: ${secretName}`);

    try {
        const response = await fetch(url, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(payload)
        });

        if (!response.ok) {
            const errorText = await response.text();
            let errorMessage = `HTTP Error ${response.status}: ${response.statusText}`;
            try {
                errorMessage = JSON.parse(errorText).error || JSON.parse(errorText).message || errorText;
            } catch {
                errorMessage = errorText || errorMessage;
            }
            throw new Error(errorMessage);
        }

        console.log(`Rotation successfully disabled for secret: ${secretName}`);
        alert(`Certificate rotation successfully disabled for ${secretName}! The list will now refresh.`);

        hideRotationSettingsModal();
        cleanupConfigStore(); 
        listSecrets(); 

    } catch (error) {
        console.error(`Failed to disable rotation for secret: ${secretName}`, error);
        alert(`🚨 Failed to disable rotation for ${secretName}. Error: ${error.message}`);
    }
}

/**
 * Shows the modal for certificate rotation settings.
 * @param {string} secretName - The name of the secret.
 */
export async function showRotationSettingsModal(secretName) {
    const modal = document.getElementById('rotationSettingsModal');
    const secretNameEl = document.getElementById('rotation-secret-name');
    const statusContainer = document.getElementById('rotation-current-status-container');
    const domainInput = document.getElementById('rotation-domain-input');
    const renewBeforeInput = document.getElementById('rotation-renew-before-input');
    const rotationToggle = document.getElementById('rotation-enable-toggle');
    const submitBtn = document.getElementById('rotation-submit-btn');
    const manualRenewBtn = document.getElementById('rotation-manual-renew-btn');

    if (!modal || !secretNameEl || !domainInput || !renewBeforeInput || !rotationToggle || !submitBtn || !manualRenewBtn) {
        console.error("Rotation settings modal elements not found.");
        alert("Rotation settings modal is not properly configured in HTML.");
        return;
    }

    // Set secret name and show loading state
    secretNameEl.textContent = secretName;
    const loadingHtml = '<p style="text-align: center;">⏳ Loading current rotation status...</p>';
    
    if (!statusContainer) {
        alert("HTML is missing the required 'rotation-current-status-container' element.");
        return;
    }
    
    statusContainer.innerHTML = loadingHtml;
    // Hide the form elements until data is loaded
    document.getElementById('rotation-settings-form').style.display = 'none';
    modal.style.display = 'block';

    try {
        const status = await getCertificateRotationStatus(secretName);
        
        // --- Display Current Status ---
        const isEnabled = status.EnableRotation;
        const statusClass = isEnabled ? 'valid' : 'invalid';
        const statusText = isEnabled ? 'ENABLED' : 'DISABLED';
        // Get RenewBefore, which is now an integer nanosecond timestamp
        const renewBeforeNanos = status.RenewBefore || 0;

        let days = 0;
        let renewBeforeDisplay = 'N/A';
        
        // CORRECTED LOGIC: Convert nanoseconds to days for display and pre-fill
        if (typeof renewBeforeNanos === 'number' && renewBeforeNanos > 0) {
            // 1 day = 24 hours * 60 minutes * 60 seconds * 1,000,000,000 nanoseconds
            const NANOS_PER_DAY = 86400000000000; 
            
            // Calculate days (use Math.round for setting the input value)
            days = Math.round(renewBeforeNanos / NANOS_PER_DAY);
            
            // For display, format it nicely
            const hours = Math.round(renewBeforeNanos / 3600000000000); // 1 hour = 3.6e12 nanos
            renewBeforeDisplay = `${days} days (${hours}h)`;
        } 
        
        // Fallback for pre-fill if conversion results in 0 or N/A, use default 30 days
        const renewBeforeDaysForInput = days > 0 ? days : 30;

        // --- Build Status HTML ---
        statusContainer.innerHTML = `
            <div class="status-block ${statusClass}" style="margin-bottom: 20px;">
                <p><strong>Rotation Status:</strong> <span class="status-label ${statusClass}">${statusText}</span></p>
                ${isEnabled ? 
                    `<p><strong>Domain:</strong> ${status.Domain}</p>
                     <p><strong>Renew Before:</strong> ${renewBeforeDisplay}</p>
                     <p><strong>Issuer:</strong> ${status.IssuerType}</p>`
                    : '<p style="color: var(--secondary-color);">Rotation is not currently configured for this secret.</p>'
                }
            </div>
            <hr style="margin-top: 20px; margin-bottom: 20px;" />
            <h3>Change Rotation Settings</h3>
        `;
        
        // --- Pre-fill & Configure Form ---
        
        // Domain: Use fetched domain if available, otherwise suggest a domain
        domainInput.value = status.Domain || secretName.replace(/_/g, '.');
        
        // ADDED: Disable the domain input if rotation is already enabled (unchangeable)
        domainInput.disabled = true; // Always disable domain input to prevent changes after initial setup
        
        // Renew Before: Use calculated days or default
        renewBeforeInput.value = renewBeforeDaysForInput;
        
        // Toggle: Set based on fetched status
        rotationToggle.checked = isEnabled;
        
        // Initial visibility of renew-before group
        document.getElementById('renew-before-group').style.display = rotationToggle.checked ? 'block' : 'none';

        // Re-show form
        document.getElementById('rotation-settings-form').style.display = 'block';

        // Update submit button action to call the correct enable/disable function
        submitBtn.onclick = () => {
            if (rotationToggle.checked) {
                // --- ENABLE LOGIC ---
                // Domain validation (simple check)
                if (!domainInput.value.trim()) {
                    alert("Please enter the associated Domain Name.");
                    domainInput.focus();
                    return;
                }

                const inputDays = parseInt(renewBeforeInput.value, 10);
                
                // Validation: Check if renewBefore is within the 1 to 80 day range
                if (isNaN(inputDays) || inputDays < 1 || inputDays > 80) {
                    alert("Please enter a 'Renew Before' value between 1 and 80 days.");
                    renewBeforeInput.focus();
                    return;
                }
                // Convert input days back to Go duration string (e.g., 30 days -> 720h)
                const hours = inputDays * 24;
                const renewBeforeDuration = `${hours}h`; 

                // Call the API function
                enableCertificateRotation(secretName, domainInput.value, renewBeforeDuration);

            } else {
                // --- DISABLE LOGIC ---
                disableCertificateRotation(secretName ,domainInput.value,);
            }
        };
        
        // Ensure manual renew button uses the secretName from the closure
        manualRenewBtn.onclick = () => manualRenewCertificate(secretName);


    } catch (error) {
        statusContainer.innerHTML = `
            <p class="error" style="text-align: center;">🚨 Could not load rotation status. Error: ${error.message}</p>
        `;
        // Hide form on failure
        document.getElementById('rotation-settings-form').style.display = 'none';
        console.error("Rotation Status Load Error:", error);
    }
}

/**
 * Hides the rotation settings modal.
 */
export function hideRotationSettingsModal() {
    const modal = document.getElementById('rotationSettingsModal');
    if (modal) {
        modal.style.display = 'none';
        // Clear status and hide form when closing
        const statusContainer = document.getElementById('rotation-current-status-container');
        if (statusContainer) statusContainer.innerHTML = '';
        document.getElementById('rotation-settings-form').style.display = 'none';
    }
}

// Expose the new functions globally for inline HTML onclick handlers
window.showRotationSettingsModal = showRotationSettingsModal;
window.hideRotationSettingsModal = hideRotationSettingsModal;

/**
 * Calls the backend API to trigger an immediate manual certificate renewal.
 * NOTE: Updated to accept secretName as an argument or fallback to the element text.
 * @param {string} [secretNameArg] - The name of the secret/certificate to renew (optional).
 */
export async function manualRenewCertificate(secretNameArg) {
    // Prefer argument, fallback to element text if available
    const secretName = secretNameArg || document.getElementById("rotation-secret-name").textContent;
    if (!secretName) {
        alert("Error: Could not determine the secret name for manual renewal.");
        return;
    }
    
    const url = `${API_BASE_URL}/manual-renew-certificate`;
    const payload = { 
        secret_name: secretName
    };
    if (!confirm(`⚠️ WARNING: Are you sure you want to manually renew certificate for secret: ${secretName}? This will attempt to refresh the certificate immediately.`)) {
        return;
    }

    // Hide the modal while processing
    hideRotationSettingsModal();

    try {
        const response = await fetch(url, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(payload)
        });

        if (!response.ok) {
            const errorText = await response.text();
            let errorMessage = `HTTP Error ${response.status}: ${response.statusText}`;
            try {
                errorMessage = JSON.parse(errorText).error || JSON.parse(errorText).message || errorText;
            } catch {
                errorMessage = errorText || errorMessage;
            }
            throw new Error(errorMessage);
        }

        console.log(`Manual renewal successfully triggered for secret: ${secretName}`);
        alert(`✅ Manual certificate renewal triggered for ${secretName}. Refreshing list to check status...`);

        // Refresh the secrets list to show the new status
        // NOTE: listSecrets is imported from global.js now
        listSecrets(); 

    } catch (error) {
        console.error(`Failed to trigger manual renewal for '${secretName}':`, error);
        alert(`🚨 Failed to trigger manual renewal for ${secretName}. Error: ${error.message}`);
    }
}


// =========================================================================
// NEW: Rotation List Logic (Updated for full details)
// =========================================================================

/**
 * Calls the backend API to get a list of all secrets with rotation enabled,
 * and returns them as a Map of secret names to rotation details for quick lookup.
 * @returns {Promise<Map<string, object>>} A Map containing the rotation details for enabled secrets.
 */
async function getRotatingCertificatesMap() {
    const url = `${API_BASE_URL}/list-rotating-certificates`;
    
    try {
        const response = await fetch(url, { method: 'GET' });

        if (!response.ok) {
            console.warn(`Failed to fetch rotating certificate list with status ${response.status}. Assuming none are rotating.`);
            return new Map();
        }

        const rotationList = await response.json();
        
        if (!Array.isArray(rotationList)) {
             console.error("API returned an unexpected format for rotation list:", rotationList);
             return new Map();
        }

        // Create a Map of secret_name -> { domain, renew_before, ... } for O(1) lookups
        return rotationList.reduce((map, cert) => {
            if (cert.rotation_enabled && cert.secret_name) {
                map.set(cert.secret_name, cert);
            }
            return map;
        }, new Map());

    } catch (error) {
        console.error("Error fetching rotating certificate list:", error);
        return new Map(); // Assume no rotation enabled on failure
    }
}


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

// Assuming refreshSecretsList is an alias for listSecrets or a similar function in global.js
// If refreshSecretsList is not defined in global.js, replace it with listSecrets() in all calls.
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 {
        // Run three parallel fetches: secrets list, certificate validity checks, and rotation list
        const [secretsResponse, rotationMap] = await Promise.all([
            fetch(`${API_BASE_URL}/list-secrets`),
            getRotatingCertificatesMap() // Fetch the Map of rotating secrets
        ]);
        
        if (!secretsResponse.ok) throw new Error(secretsResponse.statusText);

        const secretResponse = await secretsResponse.json();

        // Combine enabled and disabled secrets for display
        let 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(); 

        // ----------------------------------------------------------------------
        // NEW: Check validity for all TLS certificates concurrently AND set rotation status
        const validityChecks = allSecrets.map(async secret => {
            if (isTlsCertificate(secret)) {
                // 1. Check/Set Rotation Status and Details
                const rotationDetails = rotationMap.get(secret.name);
                if (rotationDetails) {
                    secret.rotationStatus = 'Enabled';
                    secret.rotationDetails = rotationDetails; // Attach the full details
                }
                
                // 2. Check Validity Status
                // Drill down to the PEM string
                const certPem = secret.Type?.TlsCertificate?.certificate_chain?.Specifier?.InlineString;
                
                if (certPem) {
                    const isValid = await checkCertificateValidity(certPem);
                    secret.validityStatus = isValid ? 'Valid' : 'Invalid';
                } else {
                    secret.validityStatus = 'Invalid'; // Treat missing PEM as invalid
                }
            }
            return secret;
        });

        // Wait for all checks to complete
        allSecrets = await Promise.all(validityChecks);
        // ----------------------------------------------------------------------


        // Store full configs in memory by name
        configStore.secrets = allSecrets.reduce((acc, s) => {
            const existingYaml = acc[s.name]?.yaml; 
            // Also store the validityStatus and rotationStatus in the configStore entry for potential future use
            acc[s.name] = { 
                ...s.configData, 
                yaml: existingYaml, 
                validityStatus: s.validityStatus,
                rotationStatus: s.rotationStatus,
                rotationDetails: s.rotationDetails // Store rotation details
            };
            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>
                `;
            }
            
            // Add 'View Details' and 'Rotation Setting' buttons for TLS Certificates
            if (isTlsCertificate(secret)) {
                actionButtons += `
                    <button class="action-button view-cert" onclick="window.showCertificateDetailsModal('${secret.name}')">View Details</button>
                    <button class="action-button renew-cert" onclick="window.showRotationSettingsModal('${secret.name}')">Rotation Setting</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;
            // The validity label and NEW rotation label are included in the result of getSecretTypeDetails
            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) (Unchanged)
// =========================================================================

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) (Unchanged)
// =========================================================================

/**
 * Shows the modal for adding a new secret.
 * MODIFIED: Clears upsert checkbox on show.
 */
export function showAddSecretModal() {
    document.getElementById('add-secret-yaml-input').value = '';
    
    const upsertCheckbox = document.getElementById('add-secret-upsert-flag');
    if (upsertCheckbox) {
        upsertCheckbox.checked = false;
    }
    
    document.getElementById('addSecretModal').style.display = 'block';
}
/**
 * Hides the modal for adding a new secret.
 * MODIFIED: Clears upsert checkbox on hide.
 */
export function hideAddSecretModal() {
    const modal = document.getElementById('addSecretModal');
    if (modal) {
        modal.style.display = 'none';
        document.getElementById('add-secret-yaml-input').value = ''; 
        
        const upsertCheckbox = document.getElementById('add-secret-upsert-flag');
        if (upsertCheckbox) {
            upsertCheckbox.checked = false;
        }
    }
}


/**
 * Submits the new secret YAML to the /add-secret endpoint.
 * MODIFIED: Now checks for an 'allow-upsert' checkbox and adds 'upsert: true' to the payload.
 */
export async function submitNewSecret() {
    const yamlInput = document.getElementById('add-secret-yaml-input');
    // Get the checkbox element and its state
    const upsertCheckbox = document.getElementById('add-secret-upsert-flag');
    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 };
        
        // Add upsert flag to payload if checkbox is checked
        if (upsertCheckbox && upsertCheckbox.checked) {
            // Note: Assuming your API expects the 'upsert' flag as a separate top-level key like the YAML key.
            // If the API expects { yaml: ..., upsert: true } use: payload.upsert = true;
            // Based on your Listener/Cluster pattern, it's safer to use a top-level `upsert` key if the API supports it.
            // Assuming payload: { YAML: "...", upsert: true }
            payload.upsert = true;
        }
        
        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 = '';
        // Uncheck the box upon success/closing
        if (upsertCheckbox) {
            upsertCheckbox.checked = false;
        }
        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}`);
    }
}