From ea826c62516cd93a71a458e6222589f97f1cf8d3 Mon Sep 17 00:00:00 2001 From: Tommaso Bailetti Date: Tue, 9 Dec 2025 10:00:42 +0100 Subject: [PATCH 01/26] added sink for core flows --- packages/netifyd/files/etc/netifyd/netify-proc-core.json | 4 ++++ packages/netifyd/files/etc/netifyd/netify-sink-socket.json | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/packages/netifyd/files/etc/netifyd/netify-proc-core.json b/packages/netifyd/files/etc/netifyd/netify-proc-core.json index 362a1a59b..8ef1fbd15 100644 --- a/packages/netifyd/files/etc/netifyd/netify-proc-core.json +++ b/packages/netifyd/files/etc/netifyd/netify-proc-core.json @@ -6,6 +6,10 @@ "legacy": { "enable": true, "types": [ "legacy-socket" ] + }, + "flows": { + "enable": true, + "types": [ "stream-flows" ] } } } diff --git a/packages/netifyd/files/etc/netifyd/netify-sink-socket.json b/packages/netifyd/files/etc/netifyd/netify-sink-socket.json index 031b89256..9f2c102f1 100644 --- a/packages/netifyd/files/etc/netifyd/netify-sink-socket.json +++ b/packages/netifyd/files/etc/netifyd/netify-sink-socket.json @@ -3,6 +3,10 @@ "legacy": { "enable": true, "bind_address": "unix://${path_state_volatile}/netifyd.sock" + }, + "flows": { + "enable": true, + "bind_address": "unix://${path_state_volatile}/flows.sock" } } } \ No newline at end of file From 2ce9c105d38f0cb953d88ed29151f3787d34fa94 Mon Sep 17 00:00:00 2001 From: Tommaso Bailetti Date: Wed, 21 Jan 2026 14:15:10 +0100 Subject: [PATCH 02/26] now ui compiles directly on openwrt --- packages/ns-ui/Makefile | 29 +++++++++++------------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/packages/ns-ui/Makefile b/packages/ns-ui/Makefile index ebf3bc8cf..2c7324fce 100644 --- a/packages/ns-ui/Makefile +++ b/packages/ns-ui/Makefile @@ -7,29 +7,31 @@ include $(TOPDIR)/rules.mk PKG_NAME:=ns-ui # renovate: datasource=github-releases depName=NethServer/nethsecurity-ui -PKG_VERSION:=2.14.0 +PKG_VERSION:=flows PKG_RELEASE:=1 -PKG_SOURCE:=ui-$(PKG_VERSION).tar.gz -PKG_SOURCE_URL:=https://nethsecurity.ams3.digitaloceanspaces.com/ui-dist/ +PKG_SOURCE:=nethsecurity-ui-$(PKG_VERSION).tar.gz +PKG_BUILD_DIR=$(BUILD_DIR)/nethsecurity-ui-$(PKG_VERSION) +PKG_SOURCE_URL:=https://codeload.github.com/nethserver/nethsecurity-ui/tar.gz/$(PKG_VERSION)? PKG_HASH:=skip PKG_MAINTAINER:=Giacomo Sanchietti PKG_LICENSE:=GPL-3.0-only -include $(INCLUDE_DIR)/package.mk +PKG_BUILD_DEPENDS:=node/host +PKG_BUILD_PARALLEL:=1 -HOST_BUILD_PARALLEL:=1 +include $(INCLUDE_DIR)/package.mk define Package/ns-ui SECTION:=base CATEGORY:=NethSecurity TITLE:=NethSecurity UI - URL:=https://github.com/NethServer/nethsecurity-controller/ + URL:=https://github.com/NethServer/nethsecurity-ui/ DEPENDS:=+nginx-ssl PKGARCH:=all endef - + define Package/ns-ui/description NethSecurity web user interface endef @@ -42,15 +44,8 @@ define Package/ns-ui/conffiles /www-ns/branding.js endef -# custom prepare step to avoid that 'dist' -# directory get filled with multiple versions of the UI -define Build/Prepare - rm -rvf $(PKG_BUILD_DIR)/../dist/* || true - $(PKG_UNPACK) -endef - -# this is required, otherwise compile will fail define Build/Compile + (cd $(PKG_BUILD_DIR) && npm ci && npm run build) endef define Package/ns-ui/postinst @@ -71,9 +66,7 @@ define Package/ns-ui/install $(INSTALL_BIN) ./files/ns-ui $(1)/usr/sbin $(INSTALL_DIR) $(1)/etc/init.d $(INSTALL_BIN) ./files/ns-ui.init $(1)/etc/init.d/ns-ui - $(INSTALL_DIR) $(1)/etc/uci-defaults - $(INSTALL_BIN) ./files/ns-ui.uci-defaults $(1)/etc/uci-defaults - $(CP) $(PKG_BUILD_DIR)/../dist/* $(1)/www-ns + $(CP) $(PKG_BUILD_DIR)/dist/* $(1)/www-ns endef $(eval $(call BuildPackage,ns-ui)) From 691a0fde2a8a76115fb8363d2e3c5c5fe96fc945 Mon Sep 17 00:00:00 2001 From: Tommaso Bailetti Date: Thu, 22 Jan 2026 16:32:21 +0100 Subject: [PATCH 03/26] added ns-monitoring package --- packages/ns-monitoring/Makefile | 47 ++++++++++++++++++++++ packages/ns-monitoring/files/ns-flows.init | 23 +++++++++++ 2 files changed, 70 insertions(+) create mode 100644 packages/ns-monitoring/Makefile create mode 100644 packages/ns-monitoring/files/ns-flows.init diff --git a/packages/ns-monitoring/Makefile b/packages/ns-monitoring/Makefile new file mode 100644 index 000000000..e408c991d --- /dev/null +++ b/packages/ns-monitoring/Makefile @@ -0,0 +1,47 @@ +# +# Copyright (C) 2025 Nethesis S.r.l. +# SPDX-License-Identifier: GPL-2.0-only +# + +include $(TOPDIR)/rules.mk + +PKG_NAME:=ns-monitoring +# renovate: datasource=github-tags depName=nethserver/nethsecurity-monitoring +PKG_VERSION:=main +PKG_RELEASE:=1 + +PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz +PKG_SOURCE_URL:=https://codeload.github.com/nethserver/nethsecurity-monitoring/tar.gz/$(PKG_VERSION)? +PKG_SOURCE_SUBDIR:=nethsecurity-monitoring-$(PKG_VERSION) +PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_SOURCE_SUBDIR) + +PKG_HASH:=skip +PKG_MAINTAINER:=Tommaso Bailetti +PKG_LICENSE:=MIT + +PKG_BUILD_DEPENDS:=golang/host +PKG_BUILD_PARALLEL:=1 +PKG_BUILD_FLAGS:=no-mips16 + +GO_PKG:=github.com/nethserver/nethsecurity-monitoring + +include $(INCLUDE_DIR)/package.mk +include $(TOPDIR)/feeds/packages/lang/golang/golang-package.mk + +define Package/ns-monitoring + SECTION:=base + CATEGORY:=NethServer + TITLE:=Nethsecurity Monitoring + URL:=https://github.com/nethserver/nethsecurity-monitoring + DEPENDS:=$(GO_ARCH_DEPENDS) +endef + +define Package/ns-monitoring/install + $(INSTALL_DIR) $(1)/usr/sbin + $(INSTALL_BIN) $(GO_PKG_BUILD_BIN_DIR)/nethsecurity-monitoring $(1)/usr/sbin/ns-flows + $(INSTALL_DIR) $(1)/etc/init.d + $(INSTALL_BIN) ./files/ns-flows.init $(1)/etc/init.d/ns-flows +endef + +$(eval $(call GoBinPackage,ns-monitoring)) +$(eval $(call BuildPackage,ns-monitoring)) diff --git a/packages/ns-monitoring/files/ns-flows.init b/packages/ns-monitoring/files/ns-flows.init new file mode 100644 index 000000000..330928100 --- /dev/null +++ b/packages/ns-monitoring/files/ns-flows.init @@ -0,0 +1,23 @@ +#!/bin/sh /etc/rc.common + +# +# Copyright (C) 2026 Nethesis S.r.l. +# SPDX-License-Identifier: GPL-2.0-only +# + +USE_PROCD=1 +START=99 + +start_service() { + procd_open_instance + procd_set_param command "/usr/sbin/ns-flows" + procd_set_param stdout 1 + procd_set_param stderr 1 + procd_close_instance +} + +reload_service() +{ + stop + start +} From ca4609bd070f19a1bab9a39326138b72d4265529 Mon Sep 17 00:00:00 2001 From: Tommaso Bailetti Date: Thu, 22 Jan 2026 16:42:26 +0100 Subject: [PATCH 04/26] forgot config --- config/ns-monitoring.conf | 1 + 1 file changed, 1 insertion(+) create mode 100644 config/ns-monitoring.conf diff --git a/config/ns-monitoring.conf b/config/ns-monitoring.conf new file mode 100644 index 000000000..739b2e600 --- /dev/null +++ b/config/ns-monitoring.conf @@ -0,0 +1 @@ +CONFIG_PACKAGE_ns-monitoring=y From 11bc8253f8c3105fee96e2bdd3a7a5cff8b19435 Mon Sep 17 00:00:00 2001 From: Tommaso Bailetti Date: Fri, 23 Jan 2026 09:14:11 +0100 Subject: [PATCH 05/26] added restart and enable --- packages/ns-monitoring/Makefile | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/ns-monitoring/Makefile b/packages/ns-monitoring/Makefile index e408c991d..58c5dbd13 100644 --- a/packages/ns-monitoring/Makefile +++ b/packages/ns-monitoring/Makefile @@ -43,5 +43,16 @@ define Package/ns-monitoring/install $(INSTALL_BIN) ./files/ns-flows.init $(1)/etc/init.d/ns-flows endef +define Package/ns-monitoring/postinst +#!/bin/sh +if [ -z "$${IPKG_INSTROOT}" ]; then + /etc/init.d/ns-flows restart + if /etc/init.d/ns-flows enabled; then + /etc/init.d/ns-flows enable + fi +fi +exit 0 +endef + $(eval $(call GoBinPackage,ns-monitoring)) $(eval $(call BuildPackage,ns-monitoring)) From 1084dd8d939bc2971fa734ce01ed47e0c2fc673f Mon Sep 17 00:00:00 2001 From: Tommaso Bailetti Date: Mon, 26 Jan 2026 10:42:40 +0100 Subject: [PATCH 06/26] added respawn --- packages/ns-monitoring/files/ns-flows.init | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/ns-monitoring/files/ns-flows.init b/packages/ns-monitoring/files/ns-flows.init index 330928100..6ca32a4f2 100644 --- a/packages/ns-monitoring/files/ns-flows.init +++ b/packages/ns-monitoring/files/ns-flows.init @@ -13,6 +13,7 @@ start_service() { procd_set_param command "/usr/sbin/ns-flows" procd_set_param stdout 1 procd_set_param stderr 1 + procd_set_param respawn 3600 5 0 procd_close_instance } From 6129b7a148a8a0767fa3c8f4c17e31d652ae5631 Mon Sep 17 00:00:00 2001 From: Tommaso Bailetti Date: Mon, 26 Jan 2026 10:53:23 +0100 Subject: [PATCH 07/26] forgot flows api --- packages/ns-api/files/ns.flows | 37 +++++++++++++++++++++++++++++ packages/ns-api/files/ns.flows.json | 13 ++++++++++ 2 files changed, 50 insertions(+) create mode 100644 packages/ns-api/files/ns.flows create mode 100644 packages/ns-api/files/ns.flows.json diff --git a/packages/ns-api/files/ns.flows b/packages/ns-api/files/ns.flows new file mode 100644 index 000000000..2398b9596 --- /dev/null +++ b/packages/ns-api/files/ns.flows @@ -0,0 +1,37 @@ +#!/usr/bin/python3 + +# +# Copyright (C) 2025 Nethesis S.r.l. +# SPDX-License-Identifier: GPL-2.0-only +# + +import sys +import json + +from nethsec.utils import generic_error + +INPUT_PATH = "/var/run/netifyd/flows.json" + + +def main() -> None: + match sys.argv[1]: + case 'list': + print(json.dumps({ + 'list': {} + })) + case 'call': + match sys.argv[2]: + case 'list': + with open(INPUT_PATH, 'r') as f: + flows = json.load(f) + print(json.dumps({ + 'data': flows + })) + case _: + print(json.dumps(generic_error("Unknown method"))) + case _: + print(json.dumps(generic_error("Unknown command"))) + + +if __name__ == "__main__": + main() diff --git a/packages/ns-api/files/ns.flows.json b/packages/ns-api/files/ns.flows.json new file mode 100644 index 000000000..c40549bc4 --- /dev/null +++ b/packages/ns-api/files/ns.flows.json @@ -0,0 +1,13 @@ +{ + "flows": { + "description": "Read flow statistics from netifyd", + "write": {}, + "read": { + "ubus": { + "ns.flows": [ + "*" + ] + } + } + } +} From 12e2330adc165a4373e3a2eb025ebd65a5f13ae4 Mon Sep 17 00:00:00 2001 From: Tommaso Bailetti Date: Tue, 27 Jan 2026 09:36:39 +0100 Subject: [PATCH 08/26] updated version --- packages/ns-ui/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ns-ui/Makefile b/packages/ns-ui/Makefile index 2c7324fce..1a6d80a6a 100644 --- a/packages/ns-ui/Makefile +++ b/packages/ns-ui/Makefile @@ -7,7 +7,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=ns-ui # renovate: datasource=github-releases depName=NethServer/nethsecurity-ui -PKG_VERSION:=flows +PKG_VERSION:=7c1bf449 PKG_RELEASE:=1 PKG_SOURCE:=nethsecurity-ui-$(PKG_VERSION).tar.gz From 2fcecc9c1e910ec957e8573579624996251a0da6 Mon Sep 17 00:00:00 2001 From: Tommaso Bailetti Date: Tue, 27 Jan 2026 09:58:17 +0100 Subject: [PATCH 09/26] version bump --- packages/ns-api/Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/ns-api/Makefile b/packages/ns-api/Makefile index ebb590704..dc9e891a0 100644 --- a/packages/ns-api/Makefile +++ b/packages/ns-api/Makefile @@ -164,6 +164,8 @@ define Package/ns-api/install $(INSTALL_DATA) ./files/ns.wizard.json $(1)/usr/share/rpcd/acl.d/ $(INSTALL_BIN) ./files/ns.ha $(1)/usr/libexec/rpcd/ $(INSTALL_DATA) ./files/ns.ha.json $(1)/usr/share/rpcd/acl.d/ + $(INSTALL_BIN) ./files/ns.flows $(1)/usr/libexec/rpcd/ + $(INSTALL_DATA) ./files/ns.flows.json $(1)/usr/share/rpcd/acl.d/ $(INSTALL_DIR) $(1)/lib/upgrade/keep.d $(INSTALL_CONF) files/msmtp.keep $(1)/lib/upgrade/keep.d/msmtp $(INSTALL_CONF) files/nat-helpers.keep $(1)/lib/upgrade/keep.d/nat-helpers From 571987ad7177f4e96e7e7c01ca6e5da1e6465169 Mon Sep 17 00:00:00 2001 From: Tommaso Bailetti Date: Tue, 27 Jan 2026 10:44:29 +0100 Subject: [PATCH 10/26] fixed data --- packages/ns-api/files/ns.flows | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ns-api/files/ns.flows b/packages/ns-api/files/ns.flows index 2398b9596..ad91df6f4 100644 --- a/packages/ns-api/files/ns.flows +++ b/packages/ns-api/files/ns.flows @@ -25,7 +25,7 @@ def main() -> None: with open(INPUT_PATH, 'r') as f: flows = json.load(f) print(json.dumps({ - 'data': flows + 'list': flows })) case _: print(json.dumps(generic_error("Unknown method"))) From 15ef3cb6bdf0e00a555865a9b30d9cf8464ca4bf Mon Sep 17 00:00:00 2001 From: Tommaso Bailetti Date: Tue, 27 Jan 2026 12:23:26 +0100 Subject: [PATCH 11/26] version --- packages/ns-ui/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ns-ui/Makefile b/packages/ns-ui/Makefile index 1a6d80a6a..cf9c7a971 100644 --- a/packages/ns-ui/Makefile +++ b/packages/ns-ui/Makefile @@ -7,7 +7,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=ns-ui # renovate: datasource=github-releases depName=NethServer/nethsecurity-ui -PKG_VERSION:=7c1bf449 +PKG_VERSION:=751a887f6d5bbfe5a8bc4bb7b24b47e1853287c7 PKG_RELEASE:=1 PKG_SOURCE:=nethsecurity-ui-$(PKG_VERSION).tar.gz From 0106b6a5c4e925f78aefc3f7cdf791295541615e Mon Sep 17 00:00:00 2001 From: Tommaso Bailetti Date: Tue, 3 Feb 2026 09:56:57 +0100 Subject: [PATCH 12/26] monitoring version push --- packages/ns-monitoring/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ns-monitoring/Makefile b/packages/ns-monitoring/Makefile index 58c5dbd13..df5277964 100644 --- a/packages/ns-monitoring/Makefile +++ b/packages/ns-monitoring/Makefile @@ -7,7 +7,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=ns-monitoring # renovate: datasource=github-tags depName=nethserver/nethsecurity-monitoring -PKG_VERSION:=main +PKG_VERSION:=a9978744f63568e2ea10e225bae901cb7271c7e5 PKG_RELEASE:=1 PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz From abe7fd185d7efd1ba1b7af94c1c25de9a832490c Mon Sep 17 00:00:00 2001 From: Tommaso Bailetti Date: Thu, 12 Feb 2026 08:55:40 +0100 Subject: [PATCH 13/26] flows configuration --- packages/ns-api/files/ns.flows | 69 ++++++++++++++++++- packages/ns-monitoring/Makefile | 8 +++ .../files/monitoring.uci-defaults | 15 ++++ packages/ns-monitoring/files/ns-flows.conf | 3 + packages/ns-monitoring/files/ns-flows.init | 19 +++++ 5 files changed, 112 insertions(+), 2 deletions(-) create mode 100644 packages/ns-monitoring/files/monitoring.uci-defaults create mode 100644 packages/ns-monitoring/files/ns-flows.conf diff --git a/packages/ns-api/files/ns.flows b/packages/ns-api/files/ns.flows index ad91df6f4..00f20b283 100644 --- a/packages/ns-api/files/ns.flows +++ b/packages/ns-api/files/ns.flows @@ -7,17 +7,75 @@ import sys import json +import re +import subprocess -from nethsec.utils import generic_error +from euci import EUci +from nethsec.utils import generic_error, validation_error INPUT_PATH = "/var/run/netifyd/flows.json" +def get_configuration(): + """Get ns-flows daemon configuration and status.""" + u = EUci() + + # Read configuration from UCI + config = { + 'enabled': u.get('ns-flows', 'daemon', 'enabled', default=False, dtype=bool), + 'expired_persistence': u.get('ns-flows', 'daemon', 'expired_persistence', default='60s') + } + + # Check service status + try: + result = subprocess.run( + ["service", "ns-flows", "running"], + capture_output=True, + check=False + ) + status = result.returncode == 0 + except Exception: + status = False + + return { + 'configuration': config, + 'status': status + } + + +def set_configuration(data): + """Set ns-flows daemon configuration.""" + u = EUci() + + if 'enabled' not in data: + return validation_error('enabled', 'required') + if not isinstance(data['enabled'], bool): + return validation_error('enabled', 'invalid') + if 'expired_persistence' not in data: + return validation_error('expired_persistence', 'required') + + # Validate golang duration format (e.g., "300ms", "1.5h", "2h45m") + duration_pattern = r'^([0-9]+(\.?[0-9]+)?(ns|us|µs|ms|s|m|h))+$' + if not re.match(duration_pattern, data['expired_persistence']): + return validation_error('expired_persistence', 'invalid') + + u.set('ns-flows', 'daemon', 'enabled', data['enabled']) + u.set('ns-flows', 'daemon', 'expired_persistence', data['expired_persistence']) + + u.save('ns-flows') + + return { + "message": "success" + } + + def main() -> None: match sys.argv[1]: case 'list': print(json.dumps({ - 'list': {} + 'list': {}, + 'get-configuration': {}, + 'set-configuration': {"enabled": True, "expired_persistence": "60s"} })) case 'call': match sys.argv[2]: @@ -27,6 +85,13 @@ def main() -> None: print(json.dumps({ 'list': flows })) + case 'get-configuration': + result = get_configuration() + print(json.dumps(result)) + case 'set-configuration': + data = json.load(sys.stdin) + result = set_configuration(data) + print(json.dumps(result)) case _: print(json.dumps(generic_error("Unknown method"))) case _: diff --git a/packages/ns-monitoring/Makefile b/packages/ns-monitoring/Makefile index df5277964..7cd56aa9c 100644 --- a/packages/ns-monitoring/Makefile +++ b/packages/ns-monitoring/Makefile @@ -36,9 +36,17 @@ define Package/ns-monitoring DEPENDS:=$(GO_ARCH_DEPENDS) endef +define Package/ns-monitoring/conffiles +/etc/config/monitoring +endef + define Package/ns-monitoring/install $(INSTALL_DIR) $(1)/usr/sbin $(INSTALL_BIN) $(GO_PKG_BUILD_BIN_DIR)/nethsecurity-monitoring $(1)/usr/sbin/ns-flows + $(INSTALL_DIR) $(1)/etc/config + $(INSTALL_CONF) ./files/ns-flows.conf $(1)/etc/config/ns-flows + $(INSTALL_DIR) $(1)/etc/uci-defaults + $(INSTALL_BIN) ./files/monitoring.uci-defaults $(1)/etc/uci-defaults/99-monitoring $(INSTALL_DIR) $(1)/etc/init.d $(INSTALL_BIN) ./files/ns-flows.init $(1)/etc/init.d/ns-flows endef diff --git a/packages/ns-monitoring/files/monitoring.uci-defaults b/packages/ns-monitoring/files/monitoring.uci-defaults new file mode 100644 index 000000000..9dfc42a50 --- /dev/null +++ b/packages/ns-monitoring/files/monitoring.uci-defaults @@ -0,0 +1,15 @@ +#!/bin/sh + +# +# Copyright (C) 2025 Nethesis S.r.l. +# SPDX-License-Identifier: GPL-2.0-only +# + +if ! uci -q get ns-flows.daemon > /dev/null; then + uci -q import ns-flows << EOI +config config 'daemon' + option enabled '0' + option log_level 'info' + option expired_persistence '60s' +EOI +fi diff --git a/packages/ns-monitoring/files/ns-flows.conf b/packages/ns-monitoring/files/ns-flows.conf new file mode 100644 index 000000000..b1f64c91b --- /dev/null +++ b/packages/ns-monitoring/files/ns-flows.conf @@ -0,0 +1,3 @@ +config config 'daemon' + option enabled '1' + option log_level 'info' diff --git a/packages/ns-monitoring/files/ns-flows.init b/packages/ns-monitoring/files/ns-flows.init index 6ca32a4f2..7d926590f 100644 --- a/packages/ns-monitoring/files/ns-flows.init +++ b/packages/ns-monitoring/files/ns-flows.init @@ -9,14 +9,33 @@ USE_PROCD=1 START=99 start_service() { + config_load ns-flows + + local enabled + config_get_bool enabled daemon enabled 0 + [ "$enabled" -eq 0 ] && return 0 + + local log_level expired_persistence + config_get log_level daemon log_level "info" + config_get expired_persistence daemon expired_persistence "60s" + procd_open_instance procd_set_param command "/usr/sbin/ns-flows" + procd_append_param command "-log-level" + procd_append_param command "$log_level" + procd_append_param command "-expired-persistence" + procd_append_param command "$expired_persistence" procd_set_param stdout 1 procd_set_param stderr 1 procd_set_param respawn 3600 5 0 procd_close_instance } +service_triggers() +{ + procd_add_reload_trigger "ns-flows" +} + reload_service() { stop From afe8721beda1fc8bc853513db2114e0ec819deb0 Mon Sep 17 00:00:00 2001 From: Tommaso Bailetti Date: Wed, 18 Feb 2026 16:03:18 +0100 Subject: [PATCH 14/26] adapted to new behaviour --- packages/ns-api/files/ns.flows | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/packages/ns-api/files/ns.flows b/packages/ns-api/files/ns.flows index 00f20b283..d7ec93136 100644 --- a/packages/ns-api/files/ns.flows +++ b/packages/ns-api/files/ns.flows @@ -10,6 +10,8 @@ import json import re import subprocess +import requests + from euci import EUci from nethsec.utils import generic_error, validation_error @@ -72,30 +74,35 @@ def set_configuration(data): def main() -> None: match sys.argv[1]: case 'list': - print(json.dumps({ - 'list': {}, + result = { + 'list': { + 'params': {} + }, 'get-configuration': {}, 'set-configuration': {"enabled": True, "expired_persistence": "60s"} - })) + } case 'call': match sys.argv[2]: case 'list': - with open(INPUT_PATH, 'r') as f: - flows = json.load(f) - print(json.dumps({ - 'list': flows - })) + data = json.load(sys.stdin) + if 'params' in data and not isinstance(data['params'], dict): + result = validation_error('params', 'invalid') + else: + try: + response = requests.get('http://127.0.0.1:8080/flows', params=data.get('params', {})) + result = response.json() + except requests.ConnectionError: + result = {} case 'get-configuration': result = get_configuration() - print(json.dumps(result)) case 'set-configuration': data = json.load(sys.stdin) result = set_configuration(data) - print(json.dumps(result)) case _: - print(json.dumps(generic_error("Unknown method"))) + result = generic_error("Unknown method") case _: - print(json.dumps(generic_error("Unknown command"))) + result = generic_error("Unknown command") + print(json.dumps(result)) if __name__ == "__main__": From 09af237505361245e2ba9f3ba1ad43bbab9f8dcd Mon Sep 17 00:00:00 2001 From: Tommaso Bailetti Date: Mon, 23 Feb 2026 11:42:33 +0100 Subject: [PATCH 15/26] added sorting and more --- packages/ns-api/files/ns.flows | 255 +++++++++++++++++++++++++++++++-- 1 file changed, 245 insertions(+), 10 deletions(-) diff --git a/packages/ns-api/files/ns.flows b/packages/ns-api/files/ns.flows index d7ec93136..f72a97a27 100644 --- a/packages/ns-api/files/ns.flows +++ b/packages/ns-api/files/ns.flows @@ -45,6 +45,180 @@ def get_configuration(): } +def sort_flows(flows, sort_by, desc): + """Sort flows by the given key. + + Flows that lack the relevant rate/timestamp fields are always placed at the + bottom of the result, regardless of the ``desc`` direction. + """ + + def has_sort_value(flow_item): + flow = flow_item.get('flow', {}) + if sort_by in ('download', 'upload'): + return flow.get('local_rate') is not None and flow.get('other_rate') is not None + elif sort_by == 'last_seen_at': + return flow.get('last_seen_at') is not None + elif sort_by == 'duration': + return flow.get('last_seen_at') is not None and flow.get('first_seen_at') is not None + return True + + def get_sort_key(flow_item): + flow = flow_item.get('flow', {}) + if sort_by == 'download': + if flow.get('local_origin', True): + return flow.get('other_rate', 0) or 0 + return flow.get('local_rate', 0) or 0 + elif sort_by == 'upload': + if flow.get('local_origin', True): + return flow.get('local_rate', 0) or 0 + return flow.get('other_rate', 0) or 0 + elif sort_by == 'last_seen_at': + return flow.get('last_seen_at', 0) or 0 + elif sort_by == 'duration': + return (flow.get('last_seen_at', 0) or 0) - (flow.get('first_seen_at', 0) or 0) + return 0 + + with_value = [f for f in flows if has_sort_value(f)] + without_value = [f for f in flows if not has_sort_value(f)] + + return sorted(with_value, key=get_sort_key, reverse=desc) + without_value + + +def get_source(flow_item): + """Get the source IP based on flow direction. + + Uses local_origin to determine which IP is the source: + - If local_origin is True, source is local_ip + - Otherwise, source is other_ip + """ + flow = flow_item.get('flow', {}) + if flow.get('local_origin', True): + return flow.get('local_ip', '') + return flow.get('other_ip', '') + + +def get_destination(flow_item): + """Get the destination IP based on flow direction. + + Uses local_origin to determine which IP is the destination: + - If local_origin is True, destination is other_ip + - Otherwise, destination is local_ip + """ + flow = flow_item.get('flow', {}) + if flow.get('local_origin', True): + return flow.get('other_ip', '') + return flow.get('local_ip', '') + + +def collect_unique_values(flows): + """Collect unique values for all filterable fields from the complete flow list. + + Returns a dict with sorted lists of unique values: + - applications: unique detected_application_name values + - protocols: unique detected_protocol_name values + - sources: unique source IPs (direction-aware) + - destinations: unique destination IPs (direction-aware) + """ + applications = set() + protocols = set() + sources = set() + destinations = set() + + for flow_item in flows: + flow = flow_item.get('flow', {}) + + # Collect applications + app = flow.get('detected_application_name') + if app: + applications.add(app) + + # Collect protocols + proto = flow.get('detected_protocol_name') + if proto: + protocols.add(proto) + + # Collect source/destination IPs + src = get_source(flow_item) + if src: + sources.add(src) + + dst = get_destination(flow_item) + if dst: + destinations.add(dst) + + return { + 'applications': sorted(list(applications)), + 'protocols': sorted(list(protocols)), + 'sources': sorted(list(sources)), + 'destinations': sorted(list(destinations)) + } + + +def filter_flows(flows, q=None, application=None, protocol=None, source=None, destination=None): + """Filter flows based on multiple criteria. + + Args: + flows: List of flow items + q: General text search across all fields (case-insensitive substring) + application: List of application names to filter by (case-insensitive exact match) + protocol: List of protocol names to filter by (case-insensitive exact match) + source: List of source IPs to filter by (case-insensitive exact match) + destination: List of destination IPs to filter by (case-insensitive exact match) + + Returns: + Filtered list of flows (multiple values within same filter use OR logic, different filters use AND) + """ + result = [] + + for flow_item in flows: + flow = flow_item.get('flow', {}) + + # Apply specific field filters (exact match, case-insensitive) + # Multiple values within a filter use OR logic + if application: + app = (flow.get('detected_application_name') or '').lower() + if not any(app == a.lower() for a in application): + continue + + if protocol: + proto = (flow.get('detected_protocol_name') or '').lower() + if not any(proto == p.lower() for p in protocol): + continue + + if source: + src = get_source(flow_item).lower() + if not any(src == s.lower() for s in source): + continue + + if destination: + dst = get_destination(flow_item).lower() + if not any(dst == d.lower() for d in destination): + continue + + # Apply general text search (substring match, case-insensitive) + if q: + q_lower = q.lower() + search_fields = [ + flow.get('detected_application_name', ''), + flow.get('detected_protocol_name', ''), + get_source(flow_item), + get_destination(flow_item), + flow.get('host_server_name', ''), + flow.get('dns_host_name', ''), + flow.get('local_mac', ''), + flow.get('other_mac', ''), + flow_item.get('interface', ''), + str(flow.get('local_port', '')), + str(flow.get('other_port', '')) + ] + if not any(q_lower in str(field).lower() for field in search_fields): + continue + + result.append(flow_item) + + return result + + def set_configuration(data): """Set ns-flows daemon configuration.""" u = EUci() @@ -76,7 +250,17 @@ def main() -> None: case 'list': result = { 'list': { - 'params': {} + 'params': { + 'per_page': 10, + 'page': 1, + 'sort_by': 'download', + 'desc': True, + 'q': '', + 'application': '', + 'protocol': '', + 'source': '', + 'destination': '' + } }, 'get-configuration': {}, 'set-configuration': {"enabled": True, "expired_persistence": "60s"} @@ -84,15 +268,66 @@ def main() -> None: case 'call': match sys.argv[2]: case 'list': - data = json.load(sys.stdin) - if 'params' in data and not isinstance(data['params'], dict): - result = validation_error('params', 'invalid') - else: - try: - response = requests.get('http://127.0.0.1:8080/flows', params=data.get('params', {})) - result = response.json() - except requests.ConnectionError: - result = {} + try: + response = requests.get('http://127.0.0.1:8080/flows') + if "flows" not in response.json(): + result = generic_error("Invalid response from ns-flows daemon") + else: + data = json.load(sys.stdin) + flows = response.json()['flows'] + per_page = data.get('per_page', 10) + page = data.get('page', 1) + + sort_by = data.get('sort_by', 'download') + if sort_by not in ['download', 'upload', 'last_seen_at', 'duration']: + result = validation_error('sort_by', 'invalid') + else: + desc = data.get('desc', True) + + # Collect unique filter values from unfiltered flows + filter_values = collect_unique_values(flows) + + # Apply filters (normalize to lists) + def to_list(value): + if value is None: + return None + if isinstance(value, list): + return value if value else None + if isinstance(value, str): + return [value] if value else None + return None + + q = data.get('q') + application = to_list(data.get('application')) + protocol = to_list(data.get('protocol')) + source = to_list(data.get('source')) + destination = to_list(data.get('destination')) + + filtered_flows = filter_flows( + flows, + q=q, + application=application, + protocol=protocol, + source=source, + destination=destination + ) + + # Sort filtered flows + sorted_flows = sort_flows(filtered_flows, sort_by, desc) + + # Paginate after sorting + paginated = sorted_flows[(page - 1) * per_page: page * per_page] + + result = { + 'flows': paginated, + 'total': len(filtered_flows), + 'per_page': per_page, + 'current_page': page, + 'last_page': (len(sorted_flows) + per_page - 1) // per_page, + 'filters': filter_values + } + except requests.ConnectionError: + result = {} case 'get-configuration': result = get_configuration() case 'set-configuration': From a3db99a0a4aa839fb9dda72d6a3b63fa94488d76 Mon Sep 17 00:00:00 2001 From: Tommaso Bailetti Date: Mon, 23 Feb 2026 14:59:50 +0100 Subject: [PATCH 16/26] fix: last page min, flows if error --- packages/ns-api/files/ns.flows | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/packages/ns-api/files/ns.flows b/packages/ns-api/files/ns.flows index f72a97a27..e87f3b2af 100644 --- a/packages/ns-api/files/ns.flows +++ b/packages/ns-api/files/ns.flows @@ -318,16 +318,32 @@ def main() -> None: # Paginate after sorting paginated = sorted_flows[(page - 1) * per_page: page * per_page] + # Calculate last_page, ensuring minimum of 1 when no data + last_page = max(1, (len(sorted_flows) + per_page - 1) // per_page) + result = { 'flows': paginated, 'total': len(filtered_flows), 'per_page': per_page, 'current_page': page, - 'last_page': (len(sorted_flows) + per_page - 1) // per_page, + 'last_page': last_page, 'filters': filter_values } except requests.ConnectionError: - result = {} + # flows daemon is not running or starting up + result = { + 'flows': [], + 'total': 0, + 'per_page': 10, + 'current_page': 1, + 'last_page': 1, + 'filters': { + 'applications': [], + 'protocols': [], + 'sources': [], + 'destinations': [] + } + } case 'get-configuration': result = get_configuration() case 'set-configuration': From d7dad11bfd9b41f4ba458c0f45455f7a4e605a77 Mon Sep 17 00:00:00 2001 From: Tommaso Bailetti Date: Mon, 23 Feb 2026 15:06:41 +0100 Subject: [PATCH 17/26] reverting errors --- packages/ns-api/files/ns.flows | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/packages/ns-api/files/ns.flows b/packages/ns-api/files/ns.flows index e87f3b2af..e5d9676da 100644 --- a/packages/ns-api/files/ns.flows +++ b/packages/ns-api/files/ns.flows @@ -330,20 +330,7 @@ def main() -> None: 'filters': filter_values } except requests.ConnectionError: - # flows daemon is not running or starting up - result = { - 'flows': [], - 'total': 0, - 'per_page': 10, - 'current_page': 1, - 'last_page': 1, - 'filters': { - 'applications': [], - 'protocols': [], - 'sources': [], - 'destinations': [] - } - } + result = generic_error("Unable to connect to ns-flows daemon") case 'get-configuration': result = get_configuration() case 'set-configuration': From 3eb79fcdfd3e243013992840a86b221f3518d617 Mon Sep 17 00:00:00 2001 From: Tommaso Bailetti Date: Tue, 24 Feb 2026 08:42:05 +0100 Subject: [PATCH 18/26] refactor: moved flows plugins into ns-monitoring, new versions --- .../netifyd/files/etc/netifyd/netify-proc-core.json | 4 ---- .../files/etc/netifyd/netify-sink-socket.json | 4 ---- packages/ns-api/Makefile | 4 ++-- packages/ns-monitoring/Makefile | 12 +++++++++--- .../files/netifyd/netify-proc-core-flows.json | 12 ++++++++++++ .../files/netifyd/netify-sink-socket-flows.json | 8 ++++++++ .../files/netifyd/plugins.d/10-netify-flows.conf | 13 +++++++++++++ packages/ns-ui/Makefile | 7 ++++--- 8 files changed, 48 insertions(+), 16 deletions(-) create mode 100644 packages/ns-monitoring/files/netifyd/netify-proc-core-flows.json create mode 100644 packages/ns-monitoring/files/netifyd/netify-sink-socket-flows.json create mode 100644 packages/ns-monitoring/files/netifyd/plugins.d/10-netify-flows.conf diff --git a/packages/netifyd/files/etc/netifyd/netify-proc-core.json b/packages/netifyd/files/etc/netifyd/netify-proc-core.json index 8ef1fbd15..362a1a59b 100644 --- a/packages/netifyd/files/etc/netifyd/netify-proc-core.json +++ b/packages/netifyd/files/etc/netifyd/netify-proc-core.json @@ -6,10 +6,6 @@ "legacy": { "enable": true, "types": [ "legacy-socket" ] - }, - "flows": { - "enable": true, - "types": [ "stream-flows" ] } } } diff --git a/packages/netifyd/files/etc/netifyd/netify-sink-socket.json b/packages/netifyd/files/etc/netifyd/netify-sink-socket.json index 9f2c102f1..031b89256 100644 --- a/packages/netifyd/files/etc/netifyd/netify-sink-socket.json +++ b/packages/netifyd/files/etc/netifyd/netify-sink-socket.json @@ -3,10 +3,6 @@ "legacy": { "enable": true, "bind_address": "unix://${path_state_volatile}/netifyd.sock" - }, - "flows": { - "enable": true, - "bind_address": "unix://${path_state_volatile}/flows.sock" } } } \ No newline at end of file diff --git a/packages/ns-api/Makefile b/packages/ns-api/Makefile index dc9e891a0..afbe9e53a 100644 --- a/packages/ns-api/Makefile +++ b/packages/ns-api/Makefile @@ -6,8 +6,8 @@ include $(TOPDIR)/rules.mk PKG_NAME:=ns-api -PKG_VERSION:=3.5.1 -PKG_RELEASE:=2 +PKG_VERSION:=3.5.1-beta +PKG_RELEASE:=1 PKG_BUILD_DIR:=$(BUILD_DIR)/ns-api-$(PKG_VERSION) diff --git a/packages/ns-monitoring/Makefile b/packages/ns-monitoring/Makefile index 7cd56aa9c..c712133e3 100644 --- a/packages/ns-monitoring/Makefile +++ b/packages/ns-monitoring/Makefile @@ -7,12 +7,13 @@ include $(TOPDIR)/rules.mk PKG_NAME:=ns-monitoring # renovate: datasource=github-tags depName=nethserver/nethsecurity-monitoring -PKG_VERSION:=a9978744f63568e2ea10e225bae901cb7271c7e5 +PKG_VERSION:=0.0.1 PKG_RELEASE:=1 PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz -PKG_SOURCE_URL:=https://codeload.github.com/nethserver/nethsecurity-monitoring/tar.gz/$(PKG_VERSION)? -PKG_SOURCE_SUBDIR:=nethsecurity-monitoring-$(PKG_VERSION) +PKG_SOURCE_VERSION:=badb4039bf3fd5f216154777d8e72413185e8f86 +PKG_SOURCE_URL:=https://codeload.github.com/nethserver/nethsecurity-monitoring/tar.gz/$(PKG_SOURCE_VERSION)? +PKG_SOURCE_SUBDIR:=nethsecurity-monitoring-$(PKG_SOURCE_VERSION) PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_SOURCE_SUBDIR) PKG_HASH:=skip @@ -49,12 +50,17 @@ define Package/ns-monitoring/install $(INSTALL_BIN) ./files/monitoring.uci-defaults $(1)/etc/uci-defaults/99-monitoring $(INSTALL_DIR) $(1)/etc/init.d $(INSTALL_BIN) ./files/ns-flows.init $(1)/etc/init.d/ns-flows + $(INSTALL_DIR) $(1)/etc/netifyd + $(INSTALL_CONF) ./files/netifyd/plugins.d/10-netify-flows.conf $(1)/etc/netifyd/plugins.d/10-netify-flows.conf + $(INSTALL_CONF) ./files/netifyd/netify-sink-socket-flows.json $(1)/etc/netifyd/netify-sink-socket-flows.json + $(INSTALL_CONF) ./files/netifyd/netify-proc-core-flows.json $(1)/etc/netifyd/netify-proc-core-flows.json endef define Package/ns-monitoring/postinst #!/bin/sh if [ -z "$${IPKG_INSTROOT}" ]; then /etc/init.d/ns-flows restart + /etc/init.d/netifyd reload if /etc/init.d/ns-flows enabled; then /etc/init.d/ns-flows enable fi diff --git a/packages/ns-monitoring/files/netifyd/netify-proc-core-flows.json b/packages/ns-monitoring/files/netifyd/netify-proc-core-flows.json new file mode 100644 index 000000000..d20af69e5 --- /dev/null +++ b/packages/ns-monitoring/files/netifyd/netify-proc-core-flows.json @@ -0,0 +1,12 @@ +{ + "format": "json", + "compressor": "none", + "sinks": { + "sink-socket-flows": { + "flows": { + "enable": true, + "types": [ "stream-flows" ] + } + } + } +} \ No newline at end of file diff --git a/packages/ns-monitoring/files/netifyd/netify-sink-socket-flows.json b/packages/ns-monitoring/files/netifyd/netify-sink-socket-flows.json new file mode 100644 index 000000000..a456a5bad --- /dev/null +++ b/packages/ns-monitoring/files/netifyd/netify-sink-socket-flows.json @@ -0,0 +1,8 @@ +{ + "channels": { + "flows": { + "enable": true, + "bind_address": "unix://${path_state_volatile}/flows.sock" + } + } +} \ No newline at end of file diff --git a/packages/ns-monitoring/files/netifyd/plugins.d/10-netify-flows.conf b/packages/ns-monitoring/files/netifyd/plugins.d/10-netify-flows.conf new file mode 100644 index 000000000..b974796fe --- /dev/null +++ b/packages/ns-monitoring/files/netifyd/plugins.d/10-netify-flows.conf @@ -0,0 +1,13 @@ +# Flows analysis for ns-monitoring + +[proc-core-flows] +enable = yes +plugin_library = ${path_plugin_libdir}/libnetify-proc-core.so.0.0.0 +conf_filename = ${path_state_persistent}/netify-proc-core-flows.json + +[sink-socket-flows] +enable = yes +plugin_library = ${path_plugin_libdir}/libnetify-sink-socket.so.0.0.0 +conf_filename = ${path_state_persistent}/netify-sink-socket-flows.json + +# vim: set ft=dosini : diff --git a/packages/ns-ui/Makefile b/packages/ns-ui/Makefile index cf9c7a971..3ede0e418 100644 --- a/packages/ns-ui/Makefile +++ b/packages/ns-ui/Makefile @@ -7,12 +7,13 @@ include $(TOPDIR)/rules.mk PKG_NAME:=ns-ui # renovate: datasource=github-releases depName=NethServer/nethsecurity-ui -PKG_VERSION:=751a887f6d5bbfe5a8bc4bb7b24b47e1853287c7 +PKG_VERSION:=2.13.1-beta PKG_RELEASE:=1 PKG_SOURCE:=nethsecurity-ui-$(PKG_VERSION).tar.gz -PKG_BUILD_DIR=$(BUILD_DIR)/nethsecurity-ui-$(PKG_VERSION) -PKG_SOURCE_URL:=https://codeload.github.com/nethserver/nethsecurity-ui/tar.gz/$(PKG_VERSION)? +PKG_SOURCE_VERSION:=588b27e5fff8946f15f225bcb0aaea1fbc0072b2 +PKG_BUILD_DIR=$(BUILD_DIR)/nethsecurity-ui-$(PKG_SOURCE_VERSION) +PKG_SOURCE_URL:=https://codeload.github.com/nethserver/nethsecurity-ui/tar.gz/$(PKG_SOURCE_VERSION)? PKG_HASH:=skip PKG_MAINTAINER:=Giacomo Sanchietti From c2784efb3356863d88a025b33c05c9b59c0ce334 Mon Sep 17 00:00:00 2001 From: Tommaso Bailetti Date: Wed, 25 Feb 2026 10:05:09 +0100 Subject: [PATCH 19/26] fixed missing directory --- packages/ns-monitoring/Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/ns-monitoring/Makefile b/packages/ns-monitoring/Makefile index c712133e3..fd9edfe1b 100644 --- a/packages/ns-monitoring/Makefile +++ b/packages/ns-monitoring/Makefile @@ -51,6 +51,7 @@ define Package/ns-monitoring/install $(INSTALL_DIR) $(1)/etc/init.d $(INSTALL_BIN) ./files/ns-flows.init $(1)/etc/init.d/ns-flows $(INSTALL_DIR) $(1)/etc/netifyd + $(INSTALL_DIR) $(1)/etc/netifyd/plugins.d $(INSTALL_CONF) ./files/netifyd/plugins.d/10-netify-flows.conf $(1)/etc/netifyd/plugins.d/10-netify-flows.conf $(INSTALL_CONF) ./files/netifyd/netify-sink-socket-flows.json $(1)/etc/netifyd/netify-sink-socket-flows.json $(INSTALL_CONF) ./files/netifyd/netify-proc-core-flows.json $(1)/etc/netifyd/netify-proc-core-flows.json From b6cb11b4c0289797069f93e12e43e5db83e4a024 Mon Sep 17 00:00:00 2001 From: Tommaso Bailetti Date: Wed, 25 Feb 2026 11:47:59 +0100 Subject: [PATCH 20/26] updated ui --- packages/ns-ui/Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ns-ui/Makefile b/packages/ns-ui/Makefile index 3ede0e418..7426ace23 100644 --- a/packages/ns-ui/Makefile +++ b/packages/ns-ui/Makefile @@ -7,11 +7,11 @@ include $(TOPDIR)/rules.mk PKG_NAME:=ns-ui # renovate: datasource=github-releases depName=NethServer/nethsecurity-ui -PKG_VERSION:=2.13.1-beta +PKG_VERSION:=2.14.0-beta PKG_RELEASE:=1 PKG_SOURCE:=nethsecurity-ui-$(PKG_VERSION).tar.gz -PKG_SOURCE_VERSION:=588b27e5fff8946f15f225bcb0aaea1fbc0072b2 +PKG_SOURCE_VERSION:=45c5fa2d94cf85f0cf205fd82cf7d22cf050453f PKG_BUILD_DIR=$(BUILD_DIR)/nethsecurity-ui-$(PKG_SOURCE_VERSION) PKG_SOURCE_URL:=https://codeload.github.com/nethserver/nethsecurity-ui/tar.gz/$(PKG_SOURCE_VERSION)? PKG_HASH:=skip From e8a03d57423f7e228a7458f786e799920abb4d84 Mon Sep 17 00:00:00 2001 From: Tommaso Bailetti Date: Wed, 25 Feb 2026 16:22:26 +0100 Subject: [PATCH 21/26] added badge generation from backend --- packages/ns-api/files/ns.flows | 104 ++++++++++++++++++++++++++------- 1 file changed, 83 insertions(+), 21 deletions(-) diff --git a/packages/ns-api/files/ns.flows b/packages/ns-api/files/ns.flows index e5d9676da..b1ff6cf95 100644 --- a/packages/ns-api/files/ns.flows +++ b/packages/ns-api/files/ns.flows @@ -112,49 +112,57 @@ def get_destination(flow_item): def collect_unique_values(flows): """Collect unique values for all filterable fields from the complete flow list. - + Returns a dict with sorted lists of unique values: - applications: unique detected_application_name values - protocols: unique detected_protocol_name values - sources: unique source IPs (direction-aware) - destinations: unique destination IPs (direction-aware) + - tags: unique tag values """ applications = set() protocols = set() sources = set() destinations = set() + tags = set() for flow_item in flows: flow = flow_item.get('flow', {}) - + # Collect applications app = flow.get('detected_application_name') if app: applications.add(app) - + # Collect protocols proto = flow.get('detected_protocol_name') if proto: protocols.add(proto) - + # Collect source/destination IPs src = get_source(flow_item) if src: sources.add(src) - + dst = get_destination(flow_item) if dst: destinations.add(dst) - + + # Collect tags + flow_tags = flow_item.get('tags', []) + if flow_tags: + tags.update(flow_tags) + return { 'applications': sorted(list(applications)), 'protocols': sorted(list(protocols)), 'sources': sorted(list(sources)), - 'destinations': sorted(list(destinations)) + 'destinations': sorted(list(destinations)), + 'tags': sorted(list(tags)) } -def filter_flows(flows, q=None, application=None, protocol=None, source=None, destination=None): +def filter_flows(flows, q=None, application=None, protocol=None, source=None, destination=None, tag=None): """Filter flows based on multiple criteria. Args: @@ -164,37 +172,43 @@ def filter_flows(flows, q=None, application=None, protocol=None, source=None, de protocol: List of protocol names to filter by (case-insensitive exact match) source: List of source IPs to filter by (case-insensitive exact match) destination: List of destination IPs to filter by (case-insensitive exact match) + tag: List of tags to filter by (case-insensitive exact match) Returns: Filtered list of flows (multiple values within same filter use OR logic, different filters use AND) """ result = [] - + for flow_item in flows: flow = flow_item.get('flow', {}) - + # Apply specific field filters (exact match, case-insensitive) # Multiple values within a filter use OR logic if application: app = (flow.get('detected_application_name') or '').lower() if not any(app == a.lower() for a in application): continue - + if protocol: proto = (flow.get('detected_protocol_name') or '').lower() if not any(proto == p.lower() for p in protocol): continue - + if source: src = get_source(flow_item).lower() if not any(src == s.lower() for s in source): continue - + if destination: dst = get_destination(flow_item).lower() if not any(dst == d.lower() for d in destination): continue - + + if tag: + flow_tags = [t.lower() for t in flow_item.get('tags', [])] + if not any(t.lower() in flow_tags for t in tag): + continue + # Apply general text search (substring match, case-insensitive) if q: q_lower = q.lower() @@ -213,9 +227,9 @@ def filter_flows(flows, q=None, application=None, protocol=None, source=None, de ] if not any(q_lower in str(field).lower() for field in search_fields): continue - + result.append(flow_item) - + return result @@ -245,6 +259,46 @@ def set_configuration(data): } +def compute_tags(flow_item): + """Compute tags for a flow based on its type and direction. + + Returns a list of zero or more tags from: 'remote', 'outgoing', 'internal', 'broadcast', 'scanning'. + Rules: + - 'remote': local_origin==False AND other_type=='remote' + - 'outgoing': local_origin==True AND other_type=='remote' + - 'internal': other_type=='local' + - 'broadcast': other_type=='broadcast' + - 'scanning': top-level type=='flow' + """ + tags = [] + top_level_type = flow_item.get('type') + flow = flow_item.get('flow', {}) + + local_origin = flow.get('local_origin', True) + other_type = flow.get('other_type', '') + + # Check for remote/outgoing (mutually exclusive based on local_origin) + if other_type == 'remote': + if not local_origin: + tags.append('remote') + else: + tags.append('outgoing') + + # Check for internal + if other_type == 'local': + tags.append('internal') + + # Check for broadcast + if other_type == 'broadcast': + tags.append('broadcast') + + # Check for scanning + if top_level_type == 'flow': + tags.append('scanning') + + return tags + + def main() -> None: match sys.argv[1]: case 'list': @@ -257,9 +311,10 @@ def main() -> None: 'desc': True, 'q': '', 'application': '', - 'protocol': '', - 'source': '', - 'destination': '' + 'protocol': '', + 'source': '', + 'destination': '', + 'tag': '' } }, 'get-configuration': {}, @@ -275,6 +330,11 @@ def main() -> None: else: data = json.load(sys.stdin) flows = response.json()['flows'] + + # Augment each flow with computed tags + for f in flows: + f['tags'] = compute_tags(f) + per_page = data.get('per_page', 10) page = data.get('page', 1) @@ -302,14 +362,16 @@ def main() -> None: protocol = to_list(data.get('protocol')) source = to_list(data.get('source')) destination = to_list(data.get('destination')) - + tag = to_list(data.get('tag')) + filtered_flows = filter_flows( flows, q=q, application=application, protocol=protocol, source=source, - destination=destination + destination=destination, + tag=tag ) # Sort filtered flows From 3485553e2df608533006099df158a1c70fe9565a Mon Sep 17 00:00:00 2001 From: Tommaso Bailetti Date: Fri, 27 Feb 2026 08:55:48 +0100 Subject: [PATCH 22/26] added additional metadata to flows --- packages/ns-api/files/ns.dpi | 36 +++++- packages/ns-api/files/ns.flows | 47 +++++++- packages/ns-dpi/Makefile | 4 + .../99-dpi-data-update-cron.uci-defaults | 21 ++++ packages/ns-dpi/files/dpi-data-update.init | 27 +++++ packages/ns-dpi/files/dpi-data-update.py | 110 ++++++++++++++++++ 6 files changed, 242 insertions(+), 3 deletions(-) create mode 100644 packages/ns-dpi/files/99-dpi-data-update-cron.uci-defaults create mode 100644 packages/ns-dpi/files/dpi-data-update.init create mode 100644 packages/ns-dpi/files/dpi-data-update.py diff --git a/packages/ns-api/files/ns.dpi b/packages/ns-api/files/ns.dpi index 04cab3a59..84c3dadc5 100755 --- a/packages/ns-api/files/ns.dpi +++ b/packages/ns-api/files/ns.dpi @@ -56,13 +56,45 @@ if cmd == 'list': 'list-popular': { 'limit': 32, 'page': 32 - } + }, + 'list-application-categories': {}, + 'list-application-catalog': {}, + 'list-protocol-categories': {}, + 'list-protocol-catalog': {} })) elif cmd == 'call': action = sys.argv[2] e_uci = EUci() try: - if action == 'list-applications': + if action == 'list-application-categories': + try: + with open('/etc/netifyd/netify-application-categories.json', 'r') as f: + content = json.load(f) + print(json.dumps({'values': content})) + except Exception: + print(json.dumps({})) + elif action == 'list-application-catalog': + try: + with open('/etc/netifyd/netify-application-catalog.json', 'r') as f: + content = json.load(f) + print(json.dumps({'values': content})) + except Exception: + print(json.dumps({})) + elif action == 'list-protocol-categories': + try: + with open('/etc/netifyd/netify-protocol-categories.json', 'r') as f: + content = json.load(f) + print(json.dumps({'values': content})) + except Exception: + print(json.dumps({})) + elif action == 'list-protocol-catalog': + try: + with open('/etc/netifyd/netify-protocol-catalog.json', 'r') as f: + content = json.load(f) + print(json.dumps({'values': content})) + except Exception: + print(json.dumps({})) + elif action == 'list-applications': data = json.JSONDecoder().decode(sys.stdin.read()) result = dpi.list_applications(data.get('search', None), data.get('limit', None), data.get('page', 1)) print(json.dumps({'values': result})) diff --git a/packages/ns-api/files/ns.flows b/packages/ns-api/files/ns.flows index b1ff6cf95..5c8f4e75c 100644 --- a/packages/ns-api/files/ns.flows +++ b/packages/ns-api/files/ns.flows @@ -299,6 +299,34 @@ def compute_tags(flow_item): return tags +def compute_application_name(flow_item, applications_catalog): + """Compute application name for a flow based on detected_application_name and applications catalog. + + If detected_application_name is present, return it. + Otherwise, try to infer application from the catalog based on detected_protocol_name and ports. + If no match is found, return 'unknown'. + """ + for app in applications_catalog: + if app.get('id') == flow_item.get('flow').get('detected_application'): + return app.get('label') + + return flow_item.get('flow').get('detected_application_name') + + +def compute_protocol_name(flow_item, protocols_catalog): + """Compute protocol name for a flow based on detected_protocol_name and protocols catalog. + + If detected_protocol_name is present, return it. + Otherwise, try to infer protocol from the catalog based on local_port/other_port and protocol categories. + If no match is found, return 'unknown'. + """ + for proto in protocols_catalog: + if proto.get('id') == flow_item.get('flow').get('detected_protocol'): + return proto.get('label') + + return flow_item.get('flow').get('detected_protocol_name') + + def main() -> None: match sys.argv[1]: case 'list': @@ -331,9 +359,26 @@ def main() -> None: data = json.load(sys.stdin) flows = response.json()['flows'] - # Augment each flow with computed tags + applications_catalog = [] + # load applications catalog if the file exists + try: + with open('/etc/netifyd/netify-application-catalog.json', 'r') as f: + applications_catalog = json.load(f) + except Exception: + pass + protocols_catalog = [] + # load protocols catalog if the file exists + try: + with open('/etc/netifyd/netify-protocol-catalog.json', 'r') as f: + protocols_catalog = json.load(f) + except Exception: + pass + + # Augment each flow with additional data for f in flows: f['tags'] = compute_tags(f) + f['application_label'] = compute_application_name(f, applications_catalog) + f['protocol_label'] = compute_protocol_name(f, protocols_catalog) per_page = data.get('per_page', 10) page = data.get('page', 1) diff --git a/packages/ns-dpi/Makefile b/packages/ns-dpi/Makefile index 1f030a8f8..7309eec1c 100644 --- a/packages/ns-dpi/Makefile +++ b/packages/ns-dpi/Makefile @@ -37,6 +37,7 @@ define Package/ns-dpi/postinst if [ -z "$${IPKG_INSTROOT}" ]; then /etc/init.d/cron restart /etc/init.d/dpi-license-update start + /etc/init.d/dpi-data-update start fi exit 0 endef @@ -67,13 +68,16 @@ define Package/ns-dpi/install $(LN) /etc/connlabel.conf $(1)/etc/xtables/connlabel.conf $(INSTALL_BIN) ./files/dpi.init $(1)/etc/init.d/dpi $(INSTALL_BIN) ./files/dpi-license-update.init $(1)/etc/init.d/dpi-license-update + $(INSTALL_BIN) ./files/dpi-data-update.init $(1)/etc/init.d/dpi-data-update $(INSTALL_BIN) ./files/dpi $(1)/usr/sbin/ $(INSTALL_BIN) ./files/dpi-nft $(1)/usr/sbin/ $(INSTALL_BIN) ./files/dpi-config $(1)/usr/sbin/ $(INSTALL_BIN) ./files/dpi-update $(1)/usr/sbin/ $(INSTALL_BIN) ./files/dpi-license-update.py $(1)/usr/sbin/dpi-license-update + $(INSTALL_BIN) ./files/dpi-data-update.py $(1)/usr/sbin/dpi-data-update $(INSTALL_CONF) ./files/20_dpi $(1)/etc/uci-defaults $(INSTALL_BIN) ./files/99-dpi-license-update-cron.uci-defaults $(1)/etc/uci-defaults/99-dpi-license-update-cron + $(INSTALL_BIN) ./files/99-dpi-data-update-cron.uci-defaults $(1)/etc/uci-defaults/99-dpi-data-update-cron $(INSTALL_DIR) $(1)/usr/share/ns-plug/hooks/register $(INSTALL_BIN) ./files/70dpi $(1)/usr/share/ns-plug/hooks/unregister/ $(LN) /usr/sbin/dpi-update $(1)/usr/share/ns-plug/hooks/register/80dpi-update diff --git a/packages/ns-dpi/files/99-dpi-data-update-cron.uci-defaults b/packages/ns-dpi/files/99-dpi-data-update-cron.uci-defaults new file mode 100644 index 000000000..de54a6401 --- /dev/null +++ b/packages/ns-dpi/files/99-dpi-data-update-cron.uci-defaults @@ -0,0 +1,21 @@ +#!/bin/sh + +# +# Copyright (C) 2025 Nethesis S.r.l. +# SPDX-License-Identifier: GPL-2.0-only +# + +# +# DPI Data Update: Add cron job if missing +# + +if ! grep -q '/etc/init.d/dpi-data-update' /etc/crontabs/root; then + echo '0 0 * * * sleep $(( RANDOM % 300 )); /etc/init.d/dpi-data-update start' >> /etc/crontabs/root +fi + +# Ensure dpi-data-update service is enabled at boot +if ! /etc/init.d/dpi-data-update enabled; then + /etc/init.d/dpi-data-update enable + /etc/init.d/dpi-data-update start +fi + diff --git a/packages/ns-dpi/files/dpi-data-update.init b/packages/ns-dpi/files/dpi-data-update.init new file mode 100644 index 000000000..5a4e2354b --- /dev/null +++ b/packages/ns-dpi/files/dpi-data-update.init @@ -0,0 +1,27 @@ +#!/bin/sh /etc/rc.common + +# +# Copyright (C) 2026 Nethesis S.r.l. +# SPDX-License-Identifier: GPL-2.0-only +# + +USE_PROCD=1 +START=51 + +start_service() { + procd_open_instance + procd_set_param command "/usr/sbin/dpi-data-update" + procd_set_param stdout 1 + procd_set_param stderr 1 + procd_close_instance +} + +reload_service() +{ + start +} + +service_triggers() { + procd_add_reload_trigger "fstab" "dpi" +} + diff --git a/packages/ns-dpi/files/dpi-data-update.py b/packages/ns-dpi/files/dpi-data-update.py new file mode 100644 index 000000000..57ed56a95 --- /dev/null +++ b/packages/ns-dpi/files/dpi-data-update.py @@ -0,0 +1,110 @@ +#!/usr/bin/python3 + +# +# Copyright (C) 2025 Nethesis S.r.l. +# SPDX-License-Identifier: GPL-2.0-only +# + +from urllib3.util import Retry +from requests import Session +from requests.adapters import HTTPAdapter +import os.path +import json +import logging +from os import environ + + +DATA_SERVER_ENDPOINT = "http://10.0.1.216:8080" +APPLICATIONS_CATEGORIES_ENDPOINT = "/api/netifyd/applications/categories" +APPLICATIONS_CATALOG_ENDPOINT = "/api/netifyd/applications/catalog" +PROTOCOLS_CATEGORIES_ENDPOINT = "/api/netifyd/protocols/categories" +PROTOCOLS_CATALOG_ENDPOINT = "/api/netifyd/protocols/catalog" +DATA_DISK_LOCATION = "/etc/netifyd" +APPLICATIONS_CATEGORIES_NAME = "netify-application-categories.json" +APPLICATIONS_CATALOG_NAME = "netify-application-catalog.json" +PROTOCOLS_CATEGORIES_NAME = "netify-protocol-categories.json" +PROTOCOLS_CATALOG_NAME = "netify-protocol-catalog.json" + + +def save_data(filename: str, content: str) -> None: + filepath = os.path.join(DATA_DISK_LOCATION, filename) + + if os.path.exists(filepath): + logging.debug(f"File {filename} exists, checking if update is needed") + try: + with open(filepath, "r") as f: + if f.read() == content: + logging.debug(f"{filename} is up to date, no action needed") + return + except Exception as e: + logging.warning(f"Failed to read existing {filename}: {e}") + + # save the new data + try: + if not os.path.exists(DATA_DISK_LOCATION): + os.makedirs(DATA_DISK_LOCATION) + with open(filepath, "w") as f: + f.write(content) + logging.info(f"{filename} updated") + except Exception as e: + logging.error(f"Failed to write {filename}: {e}") + raise + + +def download_data(endpoint: str, filename: str) -> None: + s = Session() + retries = Retry( + total=20, backoff_factor=0.1, status_forcelist=range(500, 600), backoff_max=30 + ) + s.mount(DATA_SERVER_ENDPOINT, HTTPAdapter(max_retries=retries)) + s.headers.update({"Accept": "application/json"}) + + try: + logging.info(f"Downloading from {DATA_SERVER_ENDPOINT}{endpoint}") + response = s.get(DATA_SERVER_ENDPOINT + endpoint, timeout=5) + response.raise_for_status() + content = response.text + + # Validate that content is valid JSON + json.loads(content) + + save_data(filename, content) + except Exception as e: + logging.error(f"Failed to download {filename} from {endpoint}: {e}") + raise + + +if __name__ == "__main__": + logger = logging.getLogger() + logger.setLevel(environ.get("LOGLEVEL", "INFO").upper()) + handler = logging.StreamHandler() + logger.addHandler(handler) + + errors = [] + + try: + download_data(APPLICATIONS_CATEGORIES_ENDPOINT, APPLICATIONS_CATEGORIES_NAME) + except Exception as e: + logger.warning(f"Failed to download applications categories: {e}") + errors.append(str(e)) + + try: + download_data(APPLICATIONS_CATALOG_ENDPOINT, APPLICATIONS_CATALOG_NAME) + except Exception as e: + logger.warning(f"Failed to download applications catalog: {e}") + errors.append(str(e)) + + try: + download_data(PROTOCOLS_CATEGORIES_ENDPOINT, PROTOCOLS_CATEGORIES_NAME) + except Exception as e: + logger.warning(f"Failed to download protocols categories: {e}") + errors.append(str(e)) + + try: + download_data(PROTOCOLS_CATALOG_ENDPOINT, PROTOCOLS_CATALOG_NAME) + except Exception as e: + logger.warning(f"Failed to download protocols catalog: {e}") + errors.append(str(e)) + + if errors: + logger.warning(f"Completed with {len(errors)} error(s)") From 840b3b9a47bee6fdaeda576d4ea41a4728cf2be2 Mon Sep 17 00:00:00 2001 From: Tommaso Bailetti Date: Mon, 2 Mar 2026 11:55:47 +0100 Subject: [PATCH 23/26] updated detection, it will be client side --- packages/ns-api/files/ns.flows | 82 ++++++++++------------------------ 1 file changed, 23 insertions(+), 59 deletions(-) diff --git a/packages/ns-api/files/ns.flows b/packages/ns-api/files/ns.flows index 5c8f4e75c..6fedd916d 100644 --- a/packages/ns-api/files/ns.flows +++ b/packages/ns-api/files/ns.flows @@ -114,14 +114,14 @@ def collect_unique_values(flows): """Collect unique values for all filterable fields from the complete flow list. Returns a dict with sorted lists of unique values: - - applications: unique detected_application_name values - - protocols: unique detected_protocol_name values + - applications: unique detected_application values as dicts with 'id' and 'name' + - protocols: unique detected_protocol values as dicts with 'id' and 'name' - sources: unique source IPs (direction-aware) - destinations: unique destination IPs (direction-aware) - tags: unique tag values """ - applications = set() - protocols = set() + applications = {} + protocols = {} sources = set() destinations = set() tags = set() @@ -129,15 +129,17 @@ def collect_unique_values(flows): for flow_item in flows: flow = flow_item.get('flow', {}) - # Collect applications - app = flow.get('detected_application_name') - if app: - applications.add(app) + # Collect applications with both id and name + app_id = flow.get('detected_application') + app_name = flow.get('detected_application_name') + if app_id is not None and app_name: + applications[app_id] = app_name - # Collect protocols - proto = flow.get('detected_protocol_name') - if proto: - protocols.add(proto) + # Collect protocols with both id and name + proto_id = flow.get('detected_protocol') + proto_name = flow.get('detected_protocol_name') + if proto_id is not None and proto_name: + protocols[proto_id] = proto_name # Collect source/destination IPs src = get_source(flow_item) @@ -153,9 +155,16 @@ def collect_unique_values(flows): if flow_tags: tags.update(flow_tags) + # Convert applications and protocols to sorted lists of dicts + applications_list = [{'id': app_id, 'name': name} for app_id, name in applications.items()] + applications_list.sort(key=lambda x: x['name'].lower()) + + protocols_list = [{'id': proto_id, 'name': name} for proto_id, name in protocols.items()] + protocols_list.sort(key=lambda x: x['name'].lower()) + return { - 'applications': sorted(list(applications)), - 'protocols': sorted(list(protocols)), + 'applications': applications_list, + 'protocols': protocols_list, 'sources': sorted(list(sources)), 'destinations': sorted(list(destinations)), 'tags': sorted(list(tags)) @@ -299,34 +308,6 @@ def compute_tags(flow_item): return tags -def compute_application_name(flow_item, applications_catalog): - """Compute application name for a flow based on detected_application_name and applications catalog. - - If detected_application_name is present, return it. - Otherwise, try to infer application from the catalog based on detected_protocol_name and ports. - If no match is found, return 'unknown'. - """ - for app in applications_catalog: - if app.get('id') == flow_item.get('flow').get('detected_application'): - return app.get('label') - - return flow_item.get('flow').get('detected_application_name') - - -def compute_protocol_name(flow_item, protocols_catalog): - """Compute protocol name for a flow based on detected_protocol_name and protocols catalog. - - If detected_protocol_name is present, return it. - Otherwise, try to infer protocol from the catalog based on local_port/other_port and protocol categories. - If no match is found, return 'unknown'. - """ - for proto in protocols_catalog: - if proto.get('id') == flow_item.get('flow').get('detected_protocol'): - return proto.get('label') - - return flow_item.get('flow').get('detected_protocol_name') - - def main() -> None: match sys.argv[1]: case 'list': @@ -359,26 +340,9 @@ def main() -> None: data = json.load(sys.stdin) flows = response.json()['flows'] - applications_catalog = [] - # load applications catalog if the file exists - try: - with open('/etc/netifyd/netify-application-catalog.json', 'r') as f: - applications_catalog = json.load(f) - except Exception: - pass - protocols_catalog = [] - # load protocols catalog if the file exists - try: - with open('/etc/netifyd/netify-protocol-catalog.json', 'r') as f: - protocols_catalog = json.load(f) - except Exception: - pass - # Augment each flow with additional data for f in flows: f['tags'] = compute_tags(f) - f['application_label'] = compute_application_name(f, applications_catalog) - f['protocol_label'] = compute_protocol_name(f, protocols_catalog) per_page = data.get('per_page', 10) page = data.get('page', 1) From b4f38423ee8c89f2493589770a8101f95de9a6b4 Mon Sep 17 00:00:00 2001 From: Tommaso Bailetti Date: Mon, 2 Mar 2026 15:04:40 +0100 Subject: [PATCH 24/26] updated ui --- packages/ns-ui/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ns-ui/Makefile b/packages/ns-ui/Makefile index 7426ace23..cbb356dff 100644 --- a/packages/ns-ui/Makefile +++ b/packages/ns-ui/Makefile @@ -11,7 +11,7 @@ PKG_VERSION:=2.14.0-beta PKG_RELEASE:=1 PKG_SOURCE:=nethsecurity-ui-$(PKG_VERSION).tar.gz -PKG_SOURCE_VERSION:=45c5fa2d94cf85f0cf205fd82cf7d22cf050453f +PKG_SOURCE_VERSION:=2e397bf6fd93272041c6cfd1da060b1f3731ae4a PKG_BUILD_DIR=$(BUILD_DIR)/nethsecurity-ui-$(PKG_SOURCE_VERSION) PKG_SOURCE_URL:=https://codeload.github.com/nethserver/nethsecurity-ui/tar.gz/$(PKG_SOURCE_VERSION)? PKG_HASH:=skip From 30e63ffbfad09f563bea1c3732554ba158d62ca0 Mon Sep 17 00:00:00 2001 From: Tommaso Bailetti Date: Mon, 2 Mar 2026 16:20:35 +0100 Subject: [PATCH 25/26] using upstream --- packages/ns-dpi/files/dpi-data-update.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ns-dpi/files/dpi-data-update.py b/packages/ns-dpi/files/dpi-data-update.py index 57ed56a95..170dd6538 100644 --- a/packages/ns-dpi/files/dpi-data-update.py +++ b/packages/ns-dpi/files/dpi-data-update.py @@ -14,7 +14,7 @@ from os import environ -DATA_SERVER_ENDPOINT = "http://10.0.1.216:8080" +DATA_SERVER_ENDPOINT = "https://distfeed.nethesis.it" APPLICATIONS_CATEGORIES_ENDPOINT = "/api/netifyd/applications/categories" APPLICATIONS_CATALOG_ENDPOINT = "/api/netifyd/applications/catalog" PROTOCOLS_CATEGORIES_ENDPOINT = "/api/netifyd/protocols/categories" From 8b313dce9374ae346d488e1a310ce66176d13501 Mon Sep 17 00:00:00 2001 From: Tommaso Bailetti Date: Mon, 2 Mar 2026 16:24:48 +0100 Subject: [PATCH 26/26] bump --- packages/ns-monitoring/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ns-monitoring/Makefile b/packages/ns-monitoring/Makefile index fd9edfe1b..7f91dce4d 100644 --- a/packages/ns-monitoring/Makefile +++ b/packages/ns-monitoring/Makefile @@ -11,7 +11,7 @@ PKG_VERSION:=0.0.1 PKG_RELEASE:=1 PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz -PKG_SOURCE_VERSION:=badb4039bf3fd5f216154777d8e72413185e8f86 +PKG_SOURCE_VERSION:=f5bc545bb2fcb7a4f417dd82235509e96c92f891 PKG_SOURCE_URL:=https://codeload.github.com/nethserver/nethsecurity-monitoring/tar.gz/$(PKG_SOURCE_VERSION)? PKG_SOURCE_SUBDIR:=nethsecurity-monitoring-$(PKG_SOURCE_VERSION) PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_SOURCE_SUBDIR)