diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 10f89507..a8f33fa1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -54,7 +54,7 @@ LibreQos is divided into several sections: * **Python: system operation and integration** * `LibreQoS.py` maps all circuits to TC handles and gets the shaper system running. * `ispConfig.py` provides a system-wide configuration. - * `integrationX.py` provide integrations with UISP, Spylnx and other CRM tools. + * `integrationX.py` provide integrations with UISP, Splynx and other CRM tools. * `lqTools.py` provides an interface for gathering statistics. ## What We're Building @@ -152,4 +152,3 @@ This is very much a work in progress. * Use `thiserror` to emit readable error messages from your functions. * It's fine to use `?` and `anyhow` inside function chains; prefer `result.map_err` to transform your errors into your own errors whenever it's possible that an error can be returned from a function accessible beyond the immediate crate. * Issue a `log::error!` or `log::warn!` messaage when an error occurs. Don't trust the callee to do it for you. It's better to have duplicate error messages than none at all. - diff --git a/docs-es/v2.0/TechnicalDocs/integrations.md b/docs-es/v2.0/TechnicalDocs/integrations.md index 68bb28ef..a5e9da7b 100644 --- a/docs-es/v2.0/TechnicalDocs/integrations.md +++ b/docs-es/v2.0/TechnicalDocs/integrations.md @@ -47,7 +47,7 @@ To ensure the network.json is always overwritten with the newest version pulled Edit the file to set the value of `always_overwrite_network_json` to `true`. Then, run `sudo systemctl restart lqosd`. -You have the option to run integrationSplynx.py automatically on boot and every X minutes (set by the parameter `queue_refresh_interval_mins`), which is highly recommended. This can be enabled by setting ```enable_spylnx = true``` in `/etc/lqos.conf`. +You have the option to run integrationSplynx.py automatically on boot and every X minutes (set by the parameter `queue_refresh_interval_mins`), which is highly recommended. This can be enabled by setting ```enable_splynx = true``` under the ```[splynx_integration]``` section in `/etc/lqos.conf`. Once set, run `sudo systemctl restart lqos_scheduler`. ### Splynx Overrides diff --git a/docs-es/v2.0/integrations-es.md b/docs-es/v2.0/integrations-es.md index d739f8ee..ca09cedd 100644 --- a/docs-es/v2.0/integrations-es.md +++ b/docs-es/v2.0/integrations-es.md @@ -113,7 +113,7 @@ ShapedDevices.csv será sobrescrito cada vez que se ejecute la integración con Para asegurarse de que network.json siempre se sobrescriba con la versión más reciente obtenida por la integración, edite `/etc/lqos.conf` con el comando `sudo nano /etc/lqos.conf` y configure el valor `always_overwrite_network_json` a `true`. Luego ejecute `sudo systemctl restart lqosd`. -Tiene la opción de ejecutar integrationSplynx.py automáticamente al iniciar el equipo y cada X minutos (configurado con el parámetro `queue_refresh_interval_mins`), lo cual es altamente recomendado. Esto se habilita estableciendo ```enable_spylnx = true``` en `/etc/lqos.conf`. +Tiene la opción de ejecutar integrationSplynx.py automáticamente al iniciar el equipo y cada X minutos (configurado con el parámetro `queue_refresh_interval_mins`), lo cual es altamente recomendado. Esto se habilita estableciendo ```enable_splynx = true``` bajo la sección ```[splynx_integration]``` en `/etc/lqos.conf`. Una vez configurado, ejecute `sudo systemctl restart lqos_scheduler`. ### Sobrescrituras de Splynx diff --git a/docs/v2.0/integrations.md b/docs/v2.0/integrations.md index 22c7cc3c..530b1fa0 100644 --- a/docs/v2.0/integrations.md +++ b/docs/v2.0/integrations.md @@ -118,7 +118,7 @@ To ensure the network.json is always overwritten with the newest version pulled Edit the file to set the value of `always_overwrite_network_json` to `true`. Then, run `sudo systemctl restart lqosd`. -You have the option to run integrationSplynx.py automatically on boot and every X minutes (set by the parameter `queue_refresh_interval_mins`), which is highly recommended. This can be enabled by setting ```enable_spylnx = true``` in `/etc/lqos.conf`. +You have the option to run integrationSplynx.py automatically on boot and every X minutes (set by the parameter `queue_refresh_interval_mins`), which is highly recommended. This can be enabled by setting ```enable_splynx = true``` under the ```[splynx_integration]``` section in `/etc/lqos.conf`. Once set, run `sudo systemctl restart lqos_scheduler`. ### Splynx Overrides diff --git a/src/INTEGRATION_HOWTO.md b/src/INTEGRATION_HOWTO.md index be1dd45a..61466105 100644 --- a/src/INTEGRATION_HOWTO.md +++ b/src/INTEGRATION_HOWTO.md @@ -1,6 +1,6 @@ # LibreQoS Integrations -If you need to create an integration for your network, we've tried to give you the tools you need. We currently ship integrations for UISP and Spylnx. We'd love to include more. +If you need to create an integration for your network, we've tried to give you the tools you need. We currently ship integrations for UISP and Splynx. We'd love to include more. ### Overall Concept diff --git a/src/configMigrator.py b/src/configMigrator.py index fdb80937..d393400e 100644 --- a/src/configMigrator.py +++ b/src/configMigrator.py @@ -42,7 +42,7 @@ oldConfig = { 'excludeSites': getIspConfigValue('excludeSites', []), 'findIPv6usingMikrotikAPI': getIspConfigValue('findIPv6usingMikrotikAPI', False), - # Spylnx + # Splynx 'automaticImportSplynx': getIspConfigValue('automaticImportSplynx', False), 'splynx_api_key': getIspConfigValue('splynx_api_key', ''), 'splynx_api_secret': getIspConfigValue('splynx_api_secret', ''), @@ -82,4 +82,4 @@ oldConfig = { 'sonar_ltu_ap_model_ids': getIspConfigValue('sonar_ltu_ap_model_ids', []), } -print(json.dumps(oldConfig)) \ No newline at end of file +print(json.dumps(oldConfig)) diff --git a/src/integrationSplynx.py b/src/integrationSplynx.py index 0b48f392..2a7cdfd6 100644 --- a/src/integrationSplynx.py +++ b/src/integrationSplynx.py @@ -155,13 +155,13 @@ def run_splynx_pipeline(strategy_name: str): """ net = NetworkGraph() print(f"Using {strategy_name.upper()} strategy - unified pipeline") - print("Fetching data from Spylnx") + print("Fetching data from Splynx") headers = buildHeaders() - print("Fetching tariffs from Spylnx") + print("Fetching tariffs from Splynx") tariff, downloadForTariffID, uploadForTariffID = getTariffs(headers) - print("Fetching all customers from Spylnx") + print("Fetching all customers from Splynx") customers = getCustomers(headers) - print("Fetching online customers from Spylnx") + print("Fetching online customers from Splynx") customersOnline = getCustomersOnline(headers) ipForRouter = {} @@ -173,19 +173,19 @@ def run_splynx_pipeline(strategy_name: str): allServices = [] if strategy_name in ("ap_only", "ap_site", "full"): - print("Fetching routers from Spylnx") + print("Fetching routers from Splynx") ipForRouter, nameForRouterID, routerIdList = getRouters(headers) - print("Fetching sectors from Spylnx") + print("Fetching sectors from Splynx") sectorForRouter = getSectors(headers) - print("Fetching services from Spylnx") + print("Fetching services from Splynx") allServices = getAllServices(headers) - print("Fetching hardware monitoring from Spylnx") + print("Fetching hardware monitoring from Splynx") monitoring = getMonitoring(headers) else: - print("Fetching services from Spylnx") + print("Fetching services from Splynx") allServices = getAllServices(headers) - print("Successfully fetched data from Spylnx") + print("Successfully fetched data from Splynx") # Build basic customer map cust_id_to_name = {c['id']: c['name'] for c in customers} @@ -298,7 +298,7 @@ def buildHeaders(): credentials = base64.b64encode(credentials.encode()).decode() return {'Authorization': "Basic %s" % credentials} -def spylnxRequest(target, headers): +def splynx_request(target, headers): """ Send a GET request to the Splynx API and return the JSON response. """ @@ -316,7 +316,7 @@ def getTariffs(headers): """ Retrieve tariff data from Splynx API and calculate download/upload speeds for each tariff. """ - data = spylnxRequest("admin/tariffs/internet", headers) + data = splynx_request("admin/tariffs/internet", headers) downloadForTariffID = {} uploadForTariffID = {} try: @@ -358,19 +358,19 @@ def getCustomers(headers): """ Retrieve all customer data from Splynx API. """ - return spylnxRequest("admin/customers/customer", headers) + return splynx_request("admin/customers/customer", headers) def getCustomersOnline(headers): """ Retrieve data of currently online customers from Splynx API. """ - return spylnxRequest("admin/customers/customers-online", headers) + return splynx_request("admin/customers/customers-online", headers) def getRouters(headers): """ Retrieve router data from Splynx API and build dictionaries for router IPs and names. """ - data = spylnxRequest("admin/networking/routers", headers) + data = splynx_request("admin/networking/routers", headers) routerIdList = [] ipForRouter = {} nameForRouterID = {} @@ -387,7 +387,7 @@ def getSectors(headers): """ Retrieve sector data from Splynx API and build a dictionary mapping routers to their sectors. """ - data = spylnxRequest("admin/networking/routers-sectors", headers) + data = splynx_request("admin/networking/routers-sectors", headers) sectorForRouter = {} for sector in data: routerID = sector['router_id'] @@ -419,7 +419,7 @@ def getAllServices(headers): """ Retrieve all active internet services from Splynx API. """ - return spylnxRequest("admin/customers/customer/0/internet-services?main_attributes%5Bstatus%5D=active", headers) + return splynx_request("admin/customers/customer/0/internet-services?main_attributes%5Bstatus%5D=active", headers) def getAllIPs(headers): """ @@ -427,8 +427,8 @@ def getAllIPs(headers): """ ipv4ByCustomerID = {} ipv6ByCustomerID = {} - allIPv4 = spylnxRequest("admin/networking/ipv4-ip?main_attributes%5Bis_used%5D=1", headers) - allIPv6 = spylnxRequest("admin/networking/ipv6-ip", headers) + allIPv4 = splynx_request("admin/networking/ipv4-ip?main_attributes%5Bis_used%5D=1", headers) + allIPv6 = splynx_request("admin/networking/ipv6-ip", headers) for ipv4 in allIPv4: if ipv4['customer_id'] not in ipv4ByCustomerID: ipv4ByCustomerID[ipv4['customer_id']] = [] @@ -445,13 +445,13 @@ def getAllIPs(headers): return (ipv4ByCustomerID, ipv6ByCustomerID) def getMonitoring(headers): - return spylnxRequest("admin/networking/monitoring", headers) + return splynx_request("admin/networking/monitoring", headers) def getNetworkSites(headers): """ Retrieve network sites data from Splynx API for better topology mapping. """ - return spylnxRequest("admin/networking/network-sites", headers) + return splynx_request("admin/networking/network-sites", headers) def extractServiceIPs(serviceItem, cust_id_to_name, allocated_ipv4s, allocated_ipv6s): """ diff --git a/src/lqos.example b/src/lqos.example index 158094fd..44a826b3 100644 --- a/src/lqos.example +++ b/src/lqos.example @@ -72,8 +72,8 @@ always_overwrite_network_json = true queue_refresh_interval_mins = 30 use_mikrotik_ipv6 = false -[spylnx_integration] -enable_spylnx = false +[splynx_integration] +enable_splynx = false api_key = "" api_secret = "" url = "" diff --git a/src/rust/lqos_config/src/etc/migration.rs b/src/rust/lqos_config/src/etc/migration.rs index 2d89ddc2..8ce9d7e8 100644 --- a/src/rust/lqos_config/src/etc/migration.rs +++ b/src/rust/lqos_config/src/etc/migration.rs @@ -105,7 +105,7 @@ fn do_migration_14_to_15( migrate_lts(old_config, &mut new_config)?; migrate_ip_ranges(python_config, &mut new_config)?; migrate_integration_common(python_config, &mut new_config)?; - migrate_spylnx(python_config, &mut new_config)?; + migrate_splynx(python_config, &mut new_config)?; migrate_uisp(python_config, &mut new_config)?; migrate_powercode(python_config, &mut new_config)?; migrate_sonar(python_config, &mut new_config)?; @@ -237,14 +237,14 @@ fn migrate_integration_common( Ok(()) } -fn migrate_spylnx( +fn migrate_splynx( python_config: &PythonMigration, new_config: &mut Config, ) -> Result<(), MigrationError> { - new_config.spylnx_integration.enable_spylnx = python_config.automatic_import_splynx; - new_config.spylnx_integration.api_key = python_config.splynx_api_key.clone(); - new_config.spylnx_integration.api_secret = python_config.splynx_api_secret.clone(); - new_config.spylnx_integration.url = python_config.splynx_api_url.clone(); + new_config.splynx_integration.enable_splynx = python_config.automatic_import_splynx; + new_config.splynx_integration.api_key = python_config.splynx_api_key.clone(); + new_config.splynx_integration.api_secret = python_config.splynx_api_secret.clone(); + new_config.splynx_integration.url = python_config.splynx_api_url.clone(); Ok(()) } diff --git a/src/rust/lqos_config/src/etc/v15/example.toml b/src/rust/lqos_config/src/etc/v15/example.toml index 81cf17a2..3c3adca3 100644 --- a/src/rust/lqos_config/src/etc/v15/example.toml +++ b/src/rust/lqos_config/src/etc/v15/example.toml @@ -58,8 +58,8 @@ always_overwrite_network_json = false queue_refresh_interval_mins = 30 use_mikrotik_ipv6 = false -[spylnx_integration] -enable_spylnx = false +[splynx_integration] +enable_splynx = false api_key = "" api_secret = "" url = "" diff --git a/src/rust/lqos_config/src/etc/v15/mod.rs b/src/rust/lqos_config/src/etc/v15/mod.rs index d7e95139..9511ebcf 100644 --- a/src/rust/lqos_config/src/etc/v15/mod.rs +++ b/src/rust/lqos_config/src/etc/v15/mod.rs @@ -12,7 +12,7 @@ mod netzur_integration; mod powercode_integration; mod queues; mod sonar_integration; -mod spylnx_integration; +mod splynx_integration; mod stormguard; mod tuning; mod uisp_integration; diff --git a/src/rust/lqos_config/src/etc/v15/spylnx_integration.rs b/src/rust/lqos_config/src/etc/v15/splynx_integration.rs similarity index 91% rename from src/rust/lqos_config/src/etc/v15/spylnx_integration.rs rename to src/rust/lqos_config/src/etc/v15/splynx_integration.rs index baaa268a..13bc1a62 100644 --- a/src/rust/lqos_config/src/etc/v15/spylnx_integration.rs +++ b/src/rust/lqos_config/src/etc/v15/splynx_integration.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Allocative)] pub struct SplynxIntegration { - pub enable_spylnx: bool, + pub enable_splynx: bool, pub api_key: String, pub api_secret: String, pub url: String, @@ -18,7 +18,7 @@ fn default_strategy() -> String { impl Default for SplynxIntegration { fn default() -> Self { SplynxIntegration { - enable_spylnx: false, + enable_splynx: false, api_key: "".to_string(), api_secret: "".to_string(), url: "".to_string(), diff --git a/src/rust/lqos_config/src/etc/v15/top_config.rs b/src/rust/lqos_config/src/etc/v15/top_config.rs index 2c8425ae..d622be48 100644 --- a/src/rust/lqos_config/src/etc/v15/top_config.rs +++ b/src/rust/lqos_config/src/etc/v15/top_config.rs @@ -6,6 +6,7 @@ use allocative::Allocative; use serde::{Deserialize, Serialize}; use sha2::Digest; use sha2::digest::Update; +use toml_edit::DocumentMut; use uuid::Uuid; fn default_true() -> bool { @@ -59,8 +60,8 @@ pub struct Config { /// Integration Common Variables pub integration_common: super::integration_common::IntegrationConfig, - /// Spylnx Integration - pub spylnx_integration: super::spylnx_integration::SplynxIntegration, + /// Splynx Integration + pub splynx_integration: super::splynx_integration::SplynxIntegration, /// Netzur Integration configuration. Optional so older configs without this /// section still deserialize cleanly. @@ -145,13 +146,57 @@ impl Config { /// Loads a config file from a string (used for testing only) #[allow(dead_code)] pub fn load_from_string(s: &str) -> Result { + let normalized = normalize_splynx_compat_keys(s)?; let config: Config = - toml::from_str(s).map_err(|e| format!("Error parsing config: {}", e))?; + toml::from_str(&normalized).map_err(|e| format!("Error parsing config: {}", e))?; config.validate()?; Ok(config) } } +/// Normalizes historical misspellings of Splynx keys in the TOML configuration. +/// +/// This operates purely in-memory so existing installations don't have their `/etc/lqos.conf` +/// rewritten just by upgrading. The canonical schema uses: +/// - `[splynx_integration]` +/// - `enable_splynx = true/false` +/// +/// Compatibility shims accepted: +/// - `[spylnx_integration]` +/// - `enable_spylnx = true/false` +fn normalize_splynx_compat_keys(raw: &str) -> Result { + let mut doc = raw + .parse::() + .map_err(|e| format!("Error parsing config: {}", e))?; + + // Section rename: [spylnx_integration] -> [splynx_integration] + if doc.get("splynx_integration").is_none() { + if let Some(item) = doc.remove("spylnx_integration") { + doc.insert("splynx_integration", item); + } + } else if doc.get("spylnx_integration").is_some() { + // If both exist, prefer the canonical section. + doc.remove("spylnx_integration"); + } + + // Key rename inside the section: enable_spylnx -> enable_splynx + if let Some(table) = doc + .get_mut("splynx_integration") + .and_then(|item| item.as_table_mut()) + { + if table.get("enable_splynx").is_none() { + if let Some(item) = table.remove("enable_spylnx") { + table.insert("enable_splynx", item); + } + } else if table.get("enable_spylnx").is_some() { + // If both exist, prefer the canonical key. + table.remove("enable_spylnx"); + } + } + + Ok(doc.to_string()) +} + impl Default for Config { fn default() -> Self { Self { @@ -166,7 +211,7 @@ impl Default for Config { long_term_stats: super::long_term_stats::LongTermStats::default(), ip_ranges: super::ip_ranges::IpRanges::default(), integration_common: super::integration_common::IntegrationConfig::default(), - spylnx_integration: super::spylnx_integration::SplynxIntegration::default(), + splynx_integration: super::splynx_integration::SplynxIntegration::default(), netzur_integration: Some(super::netzur_integration::NetzurIntegration::default()), uisp_integration: super::uisp_integration::UispIntegration::default(), powercode_integration: super::powercode_integration::PowercodeIntegration::default(), @@ -235,4 +280,25 @@ mod test { .expect("Cannot read example toml file"); assert_eq!(config.version, "1.5"); } + + #[test] + fn load_example_legacy_spylnx() { + let legacy = include_str!("example.toml") + .replace("[splynx_integration]", "[spylnx_integration]") + .replace("enable_splynx", "enable_spylnx"); + let config = + Config::load_from_string(&legacy).expect("Cannot read legacy spylnx example toml"); + assert_eq!(config.version, "1.5"); + } + + #[test] + fn serialize_uses_splynx_keys() { + let config = + Config::load_from_string(include_str!("example.toml")).expect("Cannot read example"); + let serialized = toml::to_string_pretty(&config).expect("Cannot serialize config"); + assert!(serialized.contains("splynx_integration")); + assert!(!serialized.contains("spylnx_integration")); + assert!(serialized.contains("enable_splynx")); + assert!(!serialized.contains("enable_spylnx")); + } } diff --git a/src/rust/lqos_python/src/lib.rs b/src/rust/lqos_python/src/lib.rs index 0239b2bc..21351934 100644 --- a/src/rust/lqos_python/src/lib.rs +++ b/src/rust/lqos_python/src/lib.rs @@ -1261,21 +1261,21 @@ fn uisp_auth_token() -> PyResult { #[pyfunction] fn splynx_api_key() -> PyResult { let config = lqos_config::load_config().unwrap(); - let key = config.spylnx_integration.api_key.clone(); + let key = config.splynx_integration.api_key.clone(); Ok(key) } #[pyfunction] fn splynx_api_secret() -> PyResult { let config = lqos_config::load_config().unwrap(); - let secret = config.spylnx_integration.api_secret.clone(); + let secret = config.splynx_integration.api_secret.clone(); Ok(secret) } #[pyfunction] fn splynx_api_url() -> PyResult { let config = lqos_config::load_config().unwrap(); - let url = config.spylnx_integration.url.clone(); + let url = config.splynx_integration.url.clone(); Ok(url) } @@ -1283,7 +1283,7 @@ fn splynx_api_url() -> PyResult { fn splynx_strategy() -> PyResult { let config = lqos_config::load_config(); match config { - Ok(config) => Ok(config.spylnx_integration.strategy.clone()), + Ok(config) => Ok(config.splynx_integration.strategy.clone()), Err(_) => Ok("ap_only".to_string()), // Default value when config can't be loaded } } @@ -1327,7 +1327,7 @@ fn automatic_import_uisp() -> PyResult { #[pyfunction] fn automatic_import_splynx() -> PyResult { let config = lqos_config::load_config().unwrap(); - Ok(config.spylnx_integration.enable_spylnx) + Ok(config.splynx_integration.enable_splynx) } #[pyfunction] diff --git a/src/rust/lqosd/src/node_manager/js_build/esbuild.sh b/src/rust/lqosd/src/node_manager/js_build/esbuild.sh index 3b9766a1..5c81bbd2 100755 --- a/src/rust/lqosd/src/node_manager/js_build/esbuild.sh +++ b/src/rust/lqosd/src/node_manager/js_build/esbuild.sh @@ -9,7 +9,7 @@ set -e chmod a+x /tmp/esbuild/esbuild ESBUILD_BIN="/tmp/esbuild/esbuild" #fi -scripts=( index.js template.js login.js first-run.js shaped-devices.js tree.js help.js unknown-ips.js configuration.js circuit.js flow_map.js all_tree_sankey.js asn_explorer.js lts_trial.js config_general.js config_tuning.js config_queues.js config_stormguard.js config_lts.js config_iprange.js config_flows.js config_integration.js config_spylnx.js config_netzur.js config_uisp.js config_powercode.js config_sonar.js config_interface.js config_network.js config_devices.js config_users.js config_wispgate.js chatbot.js cpu_weights.js executive_worst_sites.js executive_oversubscribed_sites.js executive_sites_due_upgrade.js executive_circuits_due_upgrade.js executive_top_asns.js executive_heatmap_rtt.js executive_heatmap_retransmit.js executive_heatmap_download.js executive_heatmap_upload.js) +scripts=( index.js template.js login.js first-run.js shaped-devices.js tree.js help.js unknown-ips.js configuration.js circuit.js flow_map.js all_tree_sankey.js asn_explorer.js lts_trial.js config_general.js config_tuning.js config_queues.js config_stormguard.js config_lts.js config_iprange.js config_flows.js config_integration.js config_splynx.js config_netzur.js config_uisp.js config_powercode.js config_sonar.js config_interface.js config_network.js config_devices.js config_users.js config_wispgate.js chatbot.js cpu_weights.js executive_worst_sites.js executive_oversubscribed_sites.js executive_sites_due_upgrade.js executive_circuits_due_upgrade.js executive_top_asns.js executive_heatmap_rtt.js executive_heatmap_retransmit.js executive_heatmap_download.js executive_heatmap_upload.js) for script in "${scripts[@]}" do echo "Building {$script}" diff --git a/src/rust/lqosd/src/node_manager/js_build/src/config/config_helper.js b/src/rust/lqosd/src/node_manager/js_build/src/config/config_helper.js index 5f5b341b..61045fc7 100644 --- a/src/rust/lqosd/src/node_manager/js_build/src/config/config_helper.js +++ b/src/rust/lqosd/src/node_manager/js_build/src/config/config_helper.js @@ -250,7 +250,7 @@ export function renderConfigMenu(currentPage) { { href: "config_iprange.html", icon: "fa-address-card", text: "IP Ranges", id: "iprange" }, { href: "config_flows.html", icon: "fa-arrow-circle-down", text: "Flow Tracking", id: "flows" }, { href: "config_integration.html", icon: "fa-link", text: "Integration - Common", id: "integration" }, - { href: "config_spylnx.html", icon: "fa-link", text: "Splynx", id: "spylnx" }, + { href: "config_splynx.html", icon: "fa-link", text: "Splynx", id: "splynx" }, { href: "config_netzur.html", icon: "fa-link", text: "Netzur", id: "netzur" }, { href: "config_uisp.html", icon: "fa-link", text: "UISP", id: "uisp" }, { href: "config_powercode.html", icon: "fa-link", text: "Powercode", id: "powercode" }, diff --git a/src/rust/lqosd/src/node_manager/js_build/src/config_spylnx.js b/src/rust/lqosd/src/node_manager/js_build/src/config_splynx.js similarity index 75% rename from src/rust/lqosd/src/node_manager/js_build/src/config_spylnx.js rename to src/rust/lqosd/src/node_manager/js_build/src/config_splynx.js index d100097a..0b958482 100644 --- a/src/rust/lqosd/src/node_manager/js_build/src/config_spylnx.js +++ b/src/rust/lqosd/src/node_manager/js_build/src/config_splynx.js @@ -15,7 +15,7 @@ function validateConfig() { return false; } - const url = document.getElementById("spylnxUrl").value.trim(); + const url = document.getElementById("splynxUrl").value.trim(); if (!url) { alert("Splynx URL is required when Splynx integration is enabled"); return false; @@ -31,38 +31,38 @@ function validateConfig() { } function updateConfig() { - // Update only the spylnx_integration section - window.config.spylnx_integration = { - enable_spylnx: document.getElementById("enableSplynx").checked, + // Update only the splynx_integration section + window.config.splynx_integration = { + enable_splynx: document.getElementById("enableSplynx").checked, api_key: document.getElementById("apiKey").value.trim(), api_secret: document.getElementById("apiSecret").value.trim(), - url: document.getElementById("spylnxUrl").value.trim(), + url: document.getElementById("splynxUrl").value.trim(), strategy: document.getElementById("topologyStrategy").value }; } // Render the configuration menu -renderConfigMenu('spylnx'); +renderConfigMenu('splynx'); loadConfig(() => { // window.config now contains the configuration. // Populate form fields with config values - if (window.config && window.config.spylnx_integration) { - const spylnx = window.config.spylnx_integration; + if (window.config && window.config.splynx_integration) { + const splynx = window.config.splynx_integration; // Boolean field document.getElementById("enableSplynx").checked = - spylnx.enable_spylnx ?? false; + splynx.enable_splynx ?? false; // String fields document.getElementById("apiKey").value = - spylnx.api_key ?? ""; + splynx.api_key ?? ""; document.getElementById("apiSecret").value = - spylnx.api_secret ?? ""; - document.getElementById("spylnxUrl").value = - spylnx.url ?? ""; + splynx.api_secret ?? ""; + document.getElementById("splynxUrl").value = + splynx.url ?? ""; document.getElementById("topologyStrategy").value = - spylnx.strategy ?? "ap_only"; + splynx.strategy ?? "ap_only"; // Add save button click handler document.getElementById('saveButton').addEventListener('click', () => { diff --git a/src/rust/lqosd/src/node_manager/js_build/src/configuration.js b/src/rust/lqosd/src/node_manager/js_build/src/configuration.js index a72a8b95..1f284439 100644 --- a/src/rust/lqosd/src/node_manager/js_build/src/configuration.js +++ b/src/rust/lqosd/src/node_manager/js_build/src/configuration.js @@ -98,10 +98,10 @@ const bindings = [ { field: "bindQueueRefreshInterval", path: ".integration_common.queue_refresh_interval_mins", data: "integer", editable: true }, // Splynx - { field: "bindSplynxEnable", path: ".spylnx_integration.enable_spylnx", data: "bool", editable: true }, - { field: "bindSplynxApiKey", path: ".spylnx_integration.api_key", data: "string", editable: true }, - { field: "bindSplynxApiSecret", path: ".spylnx_integration.api_secret", data: "string", editable: true }, - { field: "bindSplynxApiUrl", path: ".spylnx_integration.url", data: "string", editable: true }, + { field: "bindSplynxEnable", path: ".splynx_integration.enable_splynx", data: "bool", editable: true }, + { field: "bindSplynxApiKey", path: ".splynx_integration.api_key", data: "string", editable: true }, + { field: "bindSplynxApiSecret", path: ".splynx_integration.api_secret", data: "string", editable: true }, + { field: "bindSplynxApiUrl", path: ".splynx_integration.url", data: "string", editable: true }, // Netzur { field: "bindNetzurEnable", path: ".netzur_integration.enable_netzur", data: "bool", editable: true }, diff --git a/src/rust/lqosd/src/node_manager/run.rs b/src/rust/lqosd/src/node_manager/run.rs index ed4ccb93..9b398351 100644 --- a/src/rust/lqosd/src/node_manager/run.rs +++ b/src/rust/lqosd/src/node_manager/run.rs @@ -18,7 +18,7 @@ use std::path::Path; use tokio::net::TcpListener; use tokio::sync::mpsc::Sender; use tower_http::cors::CorsLayer; -use tower_http::services::ServeDir; +use tower_http::services::{ServeDir, ServeFile}; use tracing::info; /// Launches the Axum webserver to take over node manager duties. @@ -55,6 +55,15 @@ pub async fn spawn_webserver( .route("/doLogin", post(auth::try_login)) .route("/firstLogin", post(auth::first_user)) .route("/health", get(health_check)) + // Backwards compatible aliases for historical misspellings. + .route_service( + "/config_spylnx.js", + ServeFile::new(static_path.join("config_splynx.js")), + ) + .route_service( + "/config_spylnx.js.map", + ServeFile::new(static_path.join("config_splynx.js.map")), + ) .nest( "/websocket/", websocket_router( diff --git a/src/rust/lqosd/src/node_manager/static2/config_spylnx.html b/src/rust/lqosd/src/node_manager/static2/config_splynx.html similarity index 92% rename from src/rust/lqosd/src/node_manager/static2/config_spylnx.html rename to src/rust/lqosd/src/node_manager/static2/config_splynx.html index 6035bbb1..5a7bf959 100644 --- a/src/rust/lqosd/src/node_manager/static2/config_spylnx.html +++ b/src/rust/lqosd/src/node_manager/static2/config_splynx.html @@ -21,8 +21,8 @@
- - + +
Base URL for Splynx API (e.g. https://your-splynx.com)
@@ -41,4 +41,4 @@ - + diff --git a/src/rust/lqosd/src/node_manager/static2/configuration.html b/src/rust/lqosd/src/node_manager/static2/configuration.html index b77ee1e2..5d773002 100644 --- a/src/rust/lqosd/src/node_manager/static2/configuration.html +++ b/src/rust/lqosd/src/node_manager/static2/configuration.html @@ -54,7 +54,7 @@ data-bs-target="#v-pills-icommon" type="button" role="tab" aria-controls="v-pills-settings" aria-selected="false"> Integration - Common - @@ -371,8 +371,8 @@ - -
+ +

Splynx Integration Settings

diff --git a/src/rust/lqosd/src/node_manager/static_pages.rs b/src/rust/lqosd/src/node_manager/static_pages.rs index d87c2294..fa675705 100644 --- a/src/rust/lqosd/src/node_manager/static_pages.rs +++ b/src/rust/lqosd/src/node_manager/static_pages.rs @@ -60,7 +60,7 @@ pub(super) fn static_routes() -> Result { "config_iprange.html", "config_flows.html", "config_integration.html", - "config_spylnx.html", + "config_splynx.html", "config_netzur.html", "config_uisp.html", "config_powercode.html", @@ -90,6 +90,15 @@ pub(super) fn static_routes() -> Result { router = router.route_service(&format!("/{page}"), ServeFile::new(path)); } + + // Backwards compatible alias for historical misspelling. + // This route is included in the templated/authenticated router so bookmarks keep working. + let splynx_path = Path::new(&config.lqos_directory) + .join("bin") + .join("static2") + .join("config_splynx.html"); + router = router.route_service("/config_spylnx.html", ServeFile::new(splynx_path)); + router = router .layer(CorsLayer::very_permissive()) .route_layer(axum::middleware::from_fn(auth_layer))