Newer
Older
EnvoyControlPlane / static / modals.js
// modals.js
import { setInconsistencyData, inconsistencyData } from './global.js';
import { loadAllData } from './data_loader.js'; // Will be imported later
import { configStore, API_BASE_URL } from './global.js';

// =========================================================================
// MAIN CONFIGURATION MODAL HANDLERS
// =========================================================================

/**
 * Switches between the JSON and YAML tabs in a given modal content area.
 * This function MUST be exported as it's used directly in HTML/inline handlers.
 * @param {HTMLElement} modalContent - The parent container (modal-content) for tabs.
 * @param {string} tabName - 'json' or 'yaml'.
 */
export function switchTab(modalContent, tabName) {
    // Deactivate all buttons and hide all content
    modalContent.querySelectorAll('.tab-button').forEach(btn => btn.classList.remove('active'));
    modalContent.querySelectorAll('.code-block').forEach(content => content.style.display = 'none');

    // Activate the selected button and show the corresponding content
    const activeBtn = modalContent.querySelector(`.tab-button[data-tab="${tabName}"]`);
    const activeContent = document.getElementById(`modal-${tabName}-content`);

    if (activeBtn) activeBtn.classList.add('active');
    if (activeContent) activeContent.style.display = 'block';
}

/**
 * Displays the main configuration modal.
 * @param {string} title - The modal title.
 * @param {object} jsonData - The configuration data object (for JSON tab).
 * @param {string} yamlData - The configuration data as a YAML string.
 * @param {string} [defaultTab='yaml'] - The tab to show by default.
 */
export function showModal(title, jsonData, yamlData, defaultTab = 'yaml') {
    document.getElementById('modal-title').textContent = title;
    
    // Populate JSON content
    document.getElementById('modal-json-content').textContent =
        JSON.stringify(jsonData, null, 2);

    // Populate YAML content
    document.getElementById('modal-yaml-content').textContent = yamlData;
    
    // Default to the specified tab
    const modalContent = document.getElementById('configModal')?.querySelector('.modal-content');
    if (modalContent) {
        switchTab(modalContent, defaultTab); 
    }

    document.getElementById('configModal').style.display = 'block';
}

export function hideModal() {
    document.getElementById('configModal').style.display = 'none';
}

/**
 * Sets up click handlers for the tab buttons in the main modal.
 */
export function setupModalTabs() {
    const modalContent = document.getElementById('configModal')?.querySelector('.modal-content');
    if (!modalContent) return;

    modalContent.querySelectorAll('.tab-button').forEach(button => {
        button.addEventListener('click', (event) => {
            const tabName = event.target.getAttribute('data-tab');
            switchTab(modalContent, tabName);
        });
    });
}

// =========================================================================
// ADD FILTER CHAIN MODAL HANDLERS
// =========================================================================

/**
 * Shows the modal for adding a new filter chain to a listener.
 * @param {string} listenerName - The name of the listener to modify.
 */
export function showAddFilterChainModal(listenerName) {
    // 1. Set the listener name in the hidden input for form submission
    document.getElementById('add-fc-listener-name').value = listenerName;
    
    // 2. Set the title
    document.getElementById('add-fc-modal-title').textContent = 
        `Add New Filter Chain to: ${listenerName}`;
        
    // 3. Clear any previous YAML content
    const yamlInput = document.getElementById('add-fc-yaml-input');
    yamlInput.value = '';
    
    // 4. Show the modal
    document.getElementById('addFilterChainModal').style.display = 'block';
    
    // 5. Provide a template to guide the user (optional)
    yamlInput.placeholder = 
`# Paste your new Filter Chain YAML here.
# NOTE: The root key should be the filter chain object itself.
filter_chain_match:
  server_names: ["new.example.com"]
...`;
}

/**
 * Closes the Add Filter Chain modal.
 */
export function hideAddFilterChainModal() {
    document.getElementById('addFilterChainModal').style.display = 'none';
}


// =========================================================================
// CONSISTENCY MODAL HANDLERS
// =========================================================================

export function showConsistencyModal() {
    if (!inconsistencyData || inconsistencyData.inconsistent === false) return;

    // Populate modal content
    const cacheOnly = inconsistencyData['cache-only'] || {};
    const dbOnly = inconsistencyData['db-only'] || {};
    
    document.getElementById('cache-only-count').textContent = 
        Object.keys(cacheOnly).length;
    document.getElementById('cache-only-data').textContent = 
        JSON.stringify(cacheOnly, null, 2);

    document.getElementById('db-only-count').textContent = 
        Object.keys(dbOnly).length;
    document.getElementById('db-only-data').textContent = 
        JSON.stringify(dbOnly, null, 2);
    
    document.getElementById('consistencyModal').style.display = 'block';
}

export function hideConsistencyModal() {
    document.getElementById('consistencyModal').style.display = 'none';
}


// =========================================================================
// WINDOW EVENT LISTENERS
// =========================================================================

window.addEventListener('keydown', (event) => {
    // Check for Escape key to close all modals
    if (event.key === 'Escape') {
        hideModal();
        hideAddFilterChainModal();
        hideConsistencyModal();
    }
});

// Close modal when clicking outside of the content (on the backdrop)
window.addEventListener('click', (event) => {
    const modal = document.getElementById('configModal');
    const addModal = document.getElementById('addFilterChainModal'); 
    const consistencyModal = document.getElementById('consistencyModal');
    
    if (event.target === modal) {
        hideModal();
    }
    if (event.target === addModal) {
        hideAddFilterChainModal();
    }
    if (event.target === consistencyModal) {
        hideConsistencyModal();
    }
});

// =========================================================================
// UTILITY HANDLERS (Download)
// =========================================================================

export function downloadYaml() {
    const yamlContent = document.getElementById('modal-yaml-content').textContent;
    if (!yamlContent || yamlContent.trim() === '') {
        alert("No YAML content available to download.");
        return;
    }

    // Use modal title as filename fallback
    const title = document.getElementById('modal-title').textContent
        .replace(/\s+/g, '_')
        .replace(/[^\w\-]/g, '');
    
    const blob = new Blob([yamlContent], { type: 'text/yaml' });
    const url = URL.createObjectURL(blob);

    const a = document.createElement('a');
    a.href = url;
    a.download = title ? `${title}.yaml` : 'config.yaml';
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
    URL.revokeObjectURL(url);
}

export async function showListenerConfigModal(listenerName) {
    const config = configStore.listeners[listenerName];
    if (!config) {
        showModal(`🚨 Error: Listener Not Found`, { name: listenerName, error: 'Configuration data missing from memory.' }, 'Error: Listener not in memory.');
        return;
    }

    let yamlData = configStore.listeners[listenerName]?.yaml || 'Loading YAML...';
    
    if (yamlData === 'Loading YAML...') {
        try {
            const response = await fetch(`${API_BASE_URL}/get-listener?name=${listenerName}&format=yaml`);
            if (!response.ok) {
                yamlData = `Error fetching YAML: ${response.status} ${response.statusText}`;
            } else {
                yamlData = await response.text();
                configStore.listeners[listenerName].yaml = yamlData; // Store YAML
            }
        } catch (error) {
            console.error("Failed to fetch YAML listener config:", error);
            yamlData = `Network Error fetching YAML: ${error.message}`;
        }
    }

    showModal(`Full Config for Listener: ${listenerName}`, config, yamlData);
}