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

    const matchingChain = fullConfig.filterChains.find(chain => {
        const serverNames = chain.filter_chain_match?.server_names;
        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 = element.getAttribute('data-chain-index'); 

    if (!listenerName || chainIndex === null) {
        console.error("Missing required data attributes for domain config.");
        return;
    }
    
    const listener = configStore.listeners[listenerName];
    const jsonData = listener?.filterChains?.[parseInt(chainIndex)];

    if (!jsonData) {
        const errorMsg = 'Filter Chain configuration not found in memory.';
        console.error(errorMsg);
        showConfigModal(`🚨 Error: ${title}`, { error: errorMsg }, errorMsg);
        return;
    }

    let yamlData = 'Generating YAML from in-memory JSON...';
    let defaultTab = 'yaml';

    try {
        if (typeof require === 'undefined' && typeof jsyaml === 'undefined') {
            throw new Error("YAML parser (e.g., js-yaml) is required but not found.");
        }
        
        const yaml = (typeof require !== 'undefined') ? require('js-yaml') : jsyaml;
        yamlData = yaml.dump(jsonData, { indent: 2, lineWidth: -1, flowLevel: -1 });
        
    } catch (error) {
        console.error("Failed to generate YAML from JSON. Falling back to approximation.", error);
        
        const yamlApproximation = JSON.stringify(jsonData, null, 2)
            .replace(/[{}]/g, '')
            .replace(/"(\w+)":\s*/g, '$1: ')
            .replace(/,\n\s*/g, '\n')
            .replace(/\[\n\s*(\s*)/g, '\n$1  - ')
            .replace(/,\n\s*(\s*)/g, '\n$1- ')
            .replace(/:\s*"/g, ': ')
            .replace(/"/g, '');
            
        yamlData = yamlApproximation + `\n\n--- WARNING: YAML is an approximation because the js-yaml library is missing or failed to parse. ---\n\n`;
        defaultTab = 'json'; // Switch to JSON tab if YAML generation failed
    }

    showConfigModal(title, jsonData, yamlData, defaultTab);
}


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