Newer
Older
EnvoyControlPlane / static / data_fetchers.js
// data_fetchers.js
import { API_BASE_URL, configStore } from './global.js';
import { showConfigModal, switchTab } from './modals.js';
import { listListeners } from './listeners.js'; // Will be imported later

// =========================================================================
// YAML UTILITY FUNCTION
// =========================================================================

/**
 * Extracts the YAML section for a specific domain from the filterChains array.
 * NOTE: This is a complex utility that you may decide is no longer needed
 * given the new approach in showDomainConfig (generating from JSON).
 * However, since it was in the original code, we keep it here.
 * @param {string} yamlData - The full YAML string containing the listener configuration.
 * @param {string} domainName - The domain name to search for.
 * @returns {string | null} The YAML string for the matching filterChain.
 */
export function extractFilterChainByDomain(yamlData, domainName) {
    if (typeof require === 'undefined' && typeof jsyaml === 'undefined') {
        console.error("Error: YAML parser (e.g., js-yaml) is required but not found.");
        return null;
    }
    // ... (rest of the original extractFilterChainByDomain function logic) ...

    let fullConfig;
    try {
        const yaml = (typeof require !== 'undefined') ? require('js-yaml') : jsyaml;
        const allDocs = yaml.loadAll(yamlData);
        // Find the main configuration object (usually the first object document)
        fullConfig = allDocs.find(doc => doc && typeof doc === 'object');
    } catch (e) {
        console.error("Error parsing YAML data:", e);
        return null;
    }

    if (!fullConfig || !Array.isArray(fullConfig.filterChains)) {
        console.warn("Input YAML does not contain a 'filterChains' array, or the main document was not found.");
        return null;
    }
    let matchingChain 
    if (domainName === "*" && fullConfig.filterChains.length > 0) {
         matchingChain = fullConfig.filterChains.find(chain => {
            return chain.filterChainMatch == null
        });

    } else {
        matchingChain = fullConfig.filterChains.find(chain => {
            const serverNames = chain.filterChainMatch?.serverNames;
            return serverNames && serverNames.includes(domainName);
        });
    }
    
    if (!matchingChain) {
        console.log(`No filterChain found for domain: ${domainName}`);
        return null;
    }

    try {
        const yaml = (typeof require !== 'undefined') ? require('js-yaml') : jsyaml;
        const outputYaml = yaml.dump(matchingChain, {
            indent: 2,
            lineWidth: -1,
            flowLevel: -1
        });
        return outputYaml.trim();

    } catch (e) {
        console.error("Error dumping YAML data:", e);
        return null;
    }
}


// =========================================================================
// CONFIG-SPECIFIC MODAL LAUNCHERS
// =========================================================================

/**
 * Handles showing the configuration for an individual FilterChain/Domain.
 * This function loads the JSON from memory and generates the YAML from it.
 * @param {HTMLElement} element - The DOM element that triggered the function.
 */
export async function showDomainConfig(element) {
    const title = element.getAttribute('data-title');
    const listenerName = element.getAttribute('data-listener-name');
    const chainIndex = parseInt(element.getAttribute('data-chain-index'), 10);

    if (!listenerName || isNaN(chainIndex)) {
        console.error("Missing required data attributes or invalid chain index for domain config.");
        return;
    }

    // 1. Get the full Listener JSON config from memory
    const listenerConfig = configStore.listeners[listenerName];
    if (!listenerConfig || !listenerConfig.filterChains || !listenerConfig.filterChains[chainIndex]) {
        console.error(`Listener or FilterChain at index ${chainIndex} not found in memory for ${listenerName}.`);
        showConfigModal(`🚨 Error: Domain Config Not Found`, { name: title, error: `FilterChain index ${chainIndex} not found for listener ${listenerName}.` }, 'Error: JSON configuration missing.');
        return;
    }

    // The JSON data for the specific filter chain
    const jsonData = listenerConfig.filterChains[chainIndex];

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

    let yamlData;
    // 3. Extract the specific filterChain YAML using the utility function.
    // Use the domain name from the title as a proxy, or the first server_name from the JSON.
    const domainName = jsonData.filter_chain_match?.server_names?.[0] || "*";

    if (fullListenerYaml.startsWith('Error') || fullListenerYaml.startsWith('Network Error')) {
        yamlData = fullListenerYaml; // Pass the error message
    } else {
        // Use the utility function to extract the specific chain's YAML
        yamlData = extractFilterChainByDomain(fullListenerYaml, domainName);
        if (yamlData === null) {
            // As a fallback if the utility fails or doesn't find it, dump the JSON directly
            try {
                const yaml = (typeof require !== 'undefined') ? require('js-yaml') : jsyaml;
                yamlData = yaml.dump(jsonData, { indent: 2, lineWidth: -1, flowLevel: -1 }).trim();
            } catch (e) {
                yamlData = `Could not extract or dump YAML for chain: ${domainName}. Error: ${e.message}`;
            }
        }
    }

    // 4. Show the modal
    showConfigModal(title, jsonData, yamlData, 'json'); // Default to 'json' since it's guaranteed from memory
}


export async function showClusterConfigModal(clusterName) {
    const config = configStore.clusters[clusterName];
    if (!config) {
        showConfigModal(`🚨 Error: Cluster Not Found`, { name: clusterName, error: 'Configuration data missing from memory.' }, 'Error: Cluster not in memory.');
        return;
    }

    let yamlData = configStore.clusters[clusterName]?.yaml || 'Loading YAML...';

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

    showConfigModal(`Full Config for Cluster: ${clusterName}`, config, yamlData);
}

export async function showListenerConfigModal(listenerName) {
    const config = configStore.listeners[listenerName];
    if (!config) {
        showConfigModal(`🚨 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}`;
        }
    }

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

// =========================================================================
// FILTER CHAIN ADDITION LOGIC (NEW)
// =========================================================================

/**
 * Handles the submission of the new filter chain YAML.
 */
export async function submitNewFilterChain() {
    const listenerName = document.getElementById('add-fc-listener-name').value;
    const yamlData = document.getElementById('add-fc-yaml-input').value.trim();

    if (!yamlData) {
        alert("Please paste the filter chain YAML configuration.");
        return;
    }

    if (!listenerName) {
        alert("Listener name is missing. Cannot submit.");
        return;
    }

    const payload = {
        listener_name: listenerName,
        yaml: yamlData
    };

    try {
        const response = await fetch(`${API_BASE_URL}/append-filter-chain`, {
            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}`);
        }

        alert(`Successfully appended new filter chain to '${listenerName}'.`);

        // Close modal and refresh listener list
        document.getElementById('addFilterChainModal').style.display = 'none';
        listListeners();
    } catch (error) {
        console.error(`Failed to append filter chain to '${listenerName}':`, error);
        alert(`Failed to append filter chain. Check console for details. Error: ${error.message}`);
    }
}