Fix Splynx spelling + legacy spylnx compat (Fixes #895)

This commit is contained in:
Herbert Wolverson committed 2026-02-03 10:49:55 -06:00
1 parent 8302f270eb
commit acb64d1356
22 files changed
+162 -79

No files matched your search

+1 -2
View File
@@ -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.
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+2 -2
View File
@@ -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))
print(json.dumps(oldConfig))
+21 -21
View File
@@ -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):
"""
+2 -2
View File
@@ -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 = ""
+6 -6
View File
@@ -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(())
}
@@ -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 = ""
+1 -1
View File
@@ -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;
@@ -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(),
+70 -4
View File
@@ -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<Self, String> {
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<String, String> {
let mut doc = raw
.parse::<DocumentMut>()
.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"));
}
}
+5 -5
View File
@@ -1261,21 +1261,21 @@ fn uisp_auth_token() -> PyResult<String> {
#[pyfunction]
fn splynx_api_key() -> PyResult<String> {
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<String> {
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<String> {
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<String> {
fn splynx_strategy() -> PyResult<String> {
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<bool> {
#[pyfunction]
fn automatic_import_splynx() -> PyResult<bool> {
let config = lqos_config::load_config().unwrap();
Ok(config.spylnx_integration.enable_spylnx)
Ok(config.splynx_integration.enable_splynx)
}
#[pyfunction]
@@ -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}"
@@ -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" },
@@ -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', () => {
@@ -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 },
+10 -1
View File
@@ -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(
@@ -21,8 +21,8 @@
</div>
<div class="mb-3">
<label for="spylnxUrl" class="form-label">Splynx URL</label>
<input type="text" class="form-control" id="spylnxUrl">
<label for="splynxUrl" class="form-label">Splynx URL</label>
<input type="text" class="form-control" id="splynxUrl">
<div class="form-text">Base URL for Splynx API (e.g. https://your-splynx.com)</div>
</div>
@@ -41,4 +41,4 @@
</form>
</div>
</div>
<script src="config_spylnx.js%CACHEBUSTERS%"></script>
<script src="config_splynx.js%CACHEBUSTERS%"></script>
@@ -54,7 +54,7 @@
data-bs-target="#v-pills-icommon" type="button" role="tab" aria-controls="v-pills-settings"
aria-selected="false"><i class="fa fa-link"></i> Integration - Common
</button>
<button class="nav-link" id="v-pills-spylnx-tab" data-bs-toggle="pill" data-bs-target="#v-pills-spylnx"
<button class="nav-link" id="v-pills-splynx-tab" data-bs-toggle="pill" data-bs-target="#v-pills-splynx"
type="button" role="tab" aria-controls="v-pills-settings" aria-selected="false"><i
class="fa fa-link"></i> Splynx
</button>
@@ -371,8 +371,8 @@
</table>
</div>
<!-- Spylnx Tab -->
<div class="tab-pane fade" id="v-pills-spylnx" role="tabpanel" aria-labelledby="v-pills-spylnx-tab">
<!-- Splynx Tab -->
<div class="tab-pane fade" id="v-pills-splynx" role="tabpanel" aria-labelledby="v-pills-splynx-tab">
<h2><i class="fa fa-link"></i> Splynx Integration Settings</h2>
<table class="table table-bordered">
<tr>
@@ -60,7 +60,7 @@ pub(super) fn static_routes() -> Result<Router> {
"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 = 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))