diff --git a/data/config.db b/data/config.db index 8d9b397..5e7a8d5 100644 --- a/data/config.db +++ b/data/config.db Binary files differ diff --git a/data/lds.yaml b/data/lds.yaml index afa64e3..81b5897 100644 --- a/data/lds.yaml +++ b/data/lds.yaml @@ -1,5 +1,37 @@ resources: - "@type": type.googleapis.com/envoy.config.listener.v3.Listener + name: http_listener + address: + socket_address: { address: 0.0.0.0, port_value: 10000 } + filter_chains: + - filters: + - name: envoy.filters.network.http_connection_manager + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + stat_prefix: ingress_http + codec_type: AUTO + route_config: + name: ingress_generic_insecure + virtual_hosts: + - name: http_to_https + domains: ["*"] + routes: + - match: { prefix : "/.well-known/acme-challenge"} + route: { cluster: _acme_renewer } + - match: { prefix: "/" } + redirect: { https_redirect: true } + - name: video_insecure + domains: ["video.jerxie.com" , "video.local:10000"] + routes: + - match: { prefix : "/.well-known/acme-challenge"} + route: { cluster: _acme_renewer } + - match: { prefix : "/"} + route: { cluster: _nas_video } + http_filters: + - name: envoy.filters.http.router + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router +- "@type": type.googleapis.com/envoy.config.listener.v3.Listener name: https_listener address: socket_address: { address: 0.0.0.0, port_value: 10001 } diff --git a/internal/snapshot/resource_io.go b/internal/snapshot/resource_io.go index 6f4154a..36316f7 100644 --- a/internal/snapshot/resource_io.go +++ b/internal/snapshot/resource_io.go @@ -8,10 +8,13 @@ clusterv3 "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3" listenerv3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3" + _ "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3" + "github.com/envoyproxy/go-control-plane/pkg/cache/types" "github.com/envoyproxy/go-control-plane/pkg/cache/v3" resourcev3 "github.com/envoyproxy/go-control-plane/pkg/resource/v3" "google.golang.org/protobuf/encoding/protojson" + yaml "gopkg.in/yaml.v3" internallog "envoy-control-plane/internal/log" diff --git a/static/data_fetchers.js b/static/data_fetchers.js index 1f66740..18accfe 100644 --- a/static/data_fetchers.js +++ b/static/data_fetchers.js @@ -9,8 +9,8 @@ /** * 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). + * 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. @@ -22,11 +22,12 @@ 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); @@ -37,12 +38,19 @@ 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 + }); - const matchingChain = fullConfig.filterChains.find(chain => { - const serverNames = chain.filter_chain_match?.server_names; - return serverNames && serverNames.includes(domainName); - }); - + } 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; @@ -51,9 +59,9 @@ try { const yaml = (typeof require !== 'undefined') ? require('js-yaml') : jsyaml; const outputYaml = yaml.dump(matchingChain, { - indent: 2, - lineWidth: -1, - flowLevel: -1 + indent: 2, + lineWidth: -1, + flowLevel: -1 }); return outputYaml.trim(); @@ -65,7 +73,7 @@ // ========================================================================= -// CONFIG-SPECIFIC MODAL LAUNCHERS +// CONFIG-SPECIFIC MODAL LAUNCHERS // ========================================================================= /** @@ -76,51 +84,64 @@ 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'); + const chainIndex = parseInt(element.getAttribute('data-chain-index'), 10); - 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); + if (!listenerName || isNaN(chainIndex)) { + console.error("Missing required data attributes or invalid chain index for domain config."); return; } - let yamlData = 'Generating YAML from in-memory JSON...'; - let defaultTab = 'yaml'; + // 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; + } - try { - if (typeof require === 'undefined' && typeof jsyaml === 'undefined') { - throw new Error("YAML parser (e.g., js-yaml) is required but not found."); + // 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}`; } - - 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); + 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 } @@ -132,7 +153,7 @@ } 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`); @@ -159,7 +180,6 @@ } 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`); @@ -193,7 +213,7 @@ alert("Please paste the filter chain YAML configuration."); return; } - + if (!listenerName) { alert("Listener name is missing. Cannot submit."); return; @@ -217,10 +237,10 @@ } alert(`Successfully appended new filter chain to '${listenerName}'.`); - + // Close modal and refresh listener list document.getElementById('addFilterChainModal').style.display = 'none'; - listListeners(); + 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}`); diff --git a/static/filter_chain.html b/static/filter_chain.html deleted file mode 100644 index 46b7c9e..0000000 --- a/static/filter_chain.html +++ /dev/null @@ -1,341 +0,0 @@ - - - - - - Envoy Filter Chain Configurator - - - - - - -
-

Envoy Filter Chain Composer

-

Configure your Envoy listener filter chain using simple inputs or switch to advanced editing mode.

- -
- -
-
- - -
-
- - -
-
- - -
- - - -
- - -
-
- - -
-
- - -
-
- - - -
- - -
-

Advanced Editing Mode

- -
- - -

Composed YAML Section

- -
- - - - diff --git a/static/global.js b/static/global.js index 79a1772..a7df594 100644 --- a/static/global.js +++ b/static/global.js @@ -25,8 +25,9 @@ * @param {string} title - The modal title. * @param {object} jsonData - The configuration data object (used for JSON tab). * @param {string} yamlData - The configuration data as a YAML string. + * @param {string} defaultTab - The tab to show by default ('json' or 'yaml'). */ -function showConfigModal(title, jsonData, yamlData) { +function showConfigModal(title, jsonData, yamlData, defaultTab = 'yaml') { document.getElementById('modal-title').textContent = title; // Populate JSON content @@ -36,10 +37,10 @@ // Populate YAML content document.getElementById('modal-yaml-content').textContent = yamlData; - // Default to YAML tab + // Default to the specified tab const modalContent = document.getElementById('configModal')?.querySelector('.modal-content'); if (modalContent) { - switchTab(modalContent, 'yaml'); + switchTab(modalContent, defaultTab); } document.getElementById('configModal').style.display = 'block'; @@ -104,23 +105,15 @@ /** * Handles showing the configuration for an individual FilterChain/Domain. - * This function now loads the JSON from memory and fetches YAML via API. - */ -/** - * Handles showing the configuration for an individual FilterChain/Domain. * This function now loads the JSON from memory and generates the YAML from it. */ async function showDomainConfig(element) { const title = element.getAttribute('data-title'); const listenerName = element.getAttribute('data-listener-name'); - // We now use domainName for the API call (UNUSED IN FIX) - // const domainName = element.getAttribute('data-domain-name'); // We now use index to retrieve the JSON from memory const chainIndex = element.getAttribute('data-chain-index'); if (!listenerName || chainIndex === null) { - // domainName is no longer strictly required for the fix, but check if needed. - // if (domainName === null) { ... } console.error("Missing required data attributes for domain config."); return; } @@ -137,6 +130,7 @@ } let yamlData = 'Generating YAML from in-memory JSON...'; + let defaultTab = 'json'; // 2. Generate YAML from the specific Filter Chain JSON object try { @@ -153,6 +147,7 @@ lineWidth: -1, flowLevel: -1 }); + defaultTab = 'yaml'; // Switch to YAML tab if generation was successful } catch (error) { // Fallback to a JSON-to-YAML approximation if jsyaml is missing or fails @@ -168,19 +163,10 @@ .replace(/"/g, ''); yamlData = yamlApproximation + `\n\n--- WARNING: YAML is an approximation because the js-yaml library is missing or failed to parse. ---\n\n`; - - // Ensure the JSON tab is active by default when the YAML is a failed approximation - const modalContent = document.getElementById('configModal')?.querySelector('.modal-content'); - if (modalContent) { - switchTab(modalContent, 'json'); - } + defaultTab = 'json'; // Ensure JSON is the default if YAML generation fails } - // 3. REMOVE the call to extractFilterChainByDomain, as we now have the correct YAML. - // The previous code: yamlData = extractFilterChainByDomain(yamlData, domainName) || yamlData; - // is no longer needed. - - showConfigModal(title, jsonData, yamlData); + showConfigModal(title, jsonData, yamlData, defaultTab); } @@ -395,8 +381,13 @@ alert(`Successfully appended new filter chain to '${listenerName}'.`); - // Close modal and refresh listener list + // Close modal document.getElementById('addFilterChainModal').style.display = 'none'; + + // ⭐ NEW: Clean up cached YAML to force a fresh fetch + cleanupConfigStore(); + + // Refresh listener list listListeners(); } catch (error) { console.error(`Failed to append filter chain to '${listenerName}':`, error); @@ -440,6 +431,10 @@ } console.log(`Cluster '${clusterName}' successfully ${action}d.`); + + // ⭐ NEW: Clean up cached YAML to force a fresh fetch + cleanupConfigStore(); + listClusters(); } catch (error) { console.error(`Failed to ${action} cluster '${clusterName}':`, error); @@ -497,6 +492,10 @@ } console.log(`Listener '${listenerName}' successfully ${action}d.`); + + // ⭐ NEW: Clean up cached YAML to force a fresh fetch + cleanupConfigStore(); + listListeners(); } catch (error) { console.error(`Failed to ${action} listener '${listenerName}':`, error); @@ -731,7 +730,7 @@ let actionButtons = ''; // NEW: Add Filter Chain button - actionButtons += ``; + actionButtons += ``; // Existing logic for disable/enable/remove if (rowData.status === 'Enabled') { @@ -764,6 +763,28 @@ // CONSISTENCY LOGIC // ========================================================================= +/** + * Cleans up the cached YAML data in configStore for all clusters and listeners. + * This should be called after any successful configuration mutation to force + * a fresh YAML fetch the next time a config modal is opened. + */ +function cleanupConfigStore() { + // Clear YAML cache for clusters + for (const name in configStore.clusters) { + if (configStore.clusters.hasOwnProperty(name)) { + configStore.clusters[name].yaml = 'Loading YAML...'; + } + } + + // Clear YAML cache for listeners + for (const name in configStore.listeners) { + if (configStore.listeners.hasOwnProperty(name)) { + configStore.listeners[name].yaml = 'Loading YAML...'; + } + } +} + + export const CONSISTENCY_POLL_INTERVAL = 5000; // This was already correct // Must use 'export let' to allow importing modules to see the variable. @@ -902,6 +923,10 @@ } alert(`Sync successful via ${action.toUpperCase()}. Reloading data.`); + + // ⭐ NEW: Clean up cached YAML to force a fresh fetch after sync + cleanupConfigStore(); + loadAllData(); checkConsistency(); // Rerun check immediately to update status } catch (error) { diff --git a/static/index.html b/static/index.html index 689adc3..f9dc765 100644 --- a/static/index.html +++ b/static/index.html @@ -1,23 +1,24 @@ + Envoy Configuration Dashboard - + +

Envoy Configuration Dashboard ⚙️

-
@@ -26,8 +27,9 @@ - -
+ +

Existing Clusters (Click a row for full JSON)

@@ -41,7 +43,10 @@ - + + +
Loading cluster data...
Loading cluster + data...
@@ -57,82 +62,107 @@ - Loading listener data... + + Loading listener + data... + -