From 01980f7509a67f5834f896dc33d2eaf608087f1d Mon Sep 17 00:00:00 2001 From: Matteo Di Lorenzi <52289654+m-dilorenzi@users.noreply.github.com> Date: Wed, 13 May 2026 14:24:34 +0200 Subject: [PATCH 01/11] feat(firewall-logging): unify traffic logging option visualization (#756) --- .../standalone/firewall/PortForwardTable.vue | 45 ++++++++++- .../firewall/rules/FirewallRulesContent.vue | 2 +- .../firewall/rules/FirewallRulesTable.vue | 57 ++++++++----- src/i18n/en.json | 8 +- src/i18n/it.json | 8 +- .../standalone/firewall/ZonesAndPolicies.vue | 79 +++++++++++++------ 6 files changed, 147 insertions(+), 52 deletions(-) diff --git a/src/components/standalone/firewall/PortForwardTable.vue b/src/components/standalone/firewall/PortForwardTable.vue index ec3d0f717..796c1cf5d 100644 --- a/src/components/standalone/firewall/PortForwardTable.vue +++ b/src/components/standalone/firewall/PortForwardTable.vue @@ -15,14 +15,17 @@ import { NeTableBody, NeTableRow, NeTableCell, - NeButton + NeButton, + NeTooltip } from '@nethesis/vue-components' import ObjectTooltip from '@/components/standalone/users_objects/ObjectTooltip.vue' import { faCircleCheck, faCircleXmark, faClone, + faList, faPenToSquare, + faThumbTack, faTrash } from '@fortawesome/free-solid-svg-icons' import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome' @@ -124,7 +127,45 @@ function getCellClasses(item: PortForward) { -

{{ item.name }}

+
+
+

{{ item.name }}

+
+
+ + + + + + + + + + +
+

diff --git a/src/components/standalone/firewall/rules/FirewallRulesContent.vue b/src/components/standalone/firewall/rules/FirewallRulesContent.vue index c386ae91e..3fa302283 100644 --- a/src/components/standalone/firewall/rules/FirewallRulesContent.vue +++ b/src/components/standalone/firewall/rules/FirewallRulesContent.vue @@ -282,7 +282,7 @@ function searchStringInRule(rule: FirewallRule, queryText: string) {

-
+
{{ rule.name }}
@@ -393,24 +395,39 @@ function searchStringInRule(rule: FirewallRule, queryText: string) {
- - - - - +
+ + + + + + + + + + +
diff --git a/src/i18n/en.json b/src/i18n/en.json index 0f68f40e5..b6684ceda 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -1506,7 +1506,8 @@ "zone_dmz_color": "Orange", "preset_active_title": "Preset selected", "preset_active_description": "When you create this zone, the system will automatically add firewall rules to allow access to essential services. You can view or modify them anytime under Firewall > Rules > Input Rules.", - "choose_one_or_more_zones": "Choose one or more zones" + "choose_one_or_more_zones": "Choose one or more zones", + "logging_enabled": "Logging enabled" }, "port_forward": { "title": "Port forward", @@ -1538,6 +1539,8 @@ "log": "Log", "hairpin_nat": "Hairpin NAT", "hairpin_nat_zones": "Hairpin NAT enabled on following zones", + "hairpin_nat_enabled": "Hairpin NAT enabled on {zones}", + "logging_enabled": "Logging enabled", "choose_zone": "Choose zone", "choose_protocol": "Choose protocol", "protocol_helper": "Leave unselected for any source protocol", @@ -1643,7 +1646,8 @@ "destination_object": "Destination object", "inactive": "Inactive", "zone_no_longer_exists": "This rule is inactive because it uses a zone that no longer exists", - "disabled_rule": "This rule has been manually disabled, but can be re-enabled" + "disabled_rule": "This rule has been manually disabled, but can be re-enabled", + "logging_enabled": "Logging enabled" }, "firewall": { "title": "Firewall" diff --git a/src/i18n/it.json b/src/i18n/it.json index ba07f7ebe..8a0c11412 100644 --- a/src/i18n/it.json +++ b/src/i18n/it.json @@ -1052,7 +1052,8 @@ "zone_dmz_color": "Orange", "preset_active_title": "Preset selezionato", "preset_active_description": "Quando si crea questa zona, il sistema aggiungerà automaticamente le regole firewall per consentire l'accesso ai servizi essenziali. È possibile visualizzarli o modificarli in qualsiasi momento sotto Firewall > Regole > Regole di input.", - "choose_one_or_more_zones": "Scegliere una o più zone" + "choose_one_or_more_zones": "Scegliere una o più zone", + "logging_enabled": "Logging abilitato" }, "port_forward": { "disable": "Disabilita", @@ -1079,6 +1080,8 @@ "status": "Stato", "title": "Port forward", "hairpin_nat_zones": "Hairpin NAT abilitato sulle seguenti zone", + "hairpin_nat_enabled": "Hairpin NAT abilitato su {zones}", + "logging_enabled": "Logging abilitato", "log": "Log", "wan_ip": "IP WAN", "destination_port": "Porta di destinazione", @@ -2002,7 +2005,8 @@ "destination_object": "Oggetto di destinazione", "inactive": "Inattiva", "zone_no_longer_exists": "Questa regola è stata disattivata perché utilizza una zona che non esiste più", - "disabled_rule": "Questa regola è stata disabilitata manualmente, ma può essere riattivata" + "disabled_rule": "Questa regola è stata disabilitata manualmente, ma può essere riattivata", + "logging_enabled": "Logging abilitato" }, "certificates": { "title": "Certificati", diff --git a/src/views/standalone/firewall/ZonesAndPolicies.vue b/src/views/standalone/firewall/ZonesAndPolicies.vue index 67c294aaa..b68ea91af 100644 --- a/src/views/standalone/firewall/ZonesAndPolicies.vue +++ b/src/views/standalone/firewall/ZonesAndPolicies.vue @@ -18,7 +18,8 @@ import { NeTableCell, NePaginator, NeEmptyState, - useItemPagination + useItemPagination, + NeTooltip } from '@nethesis/vue-components' import { useI18n } from 'vue-i18n' import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome' @@ -33,7 +34,13 @@ import { getZoneColorClasses, getIconFromZone } from '@/lib/standalone/network' -import { faPenToSquare, faTrash } from '@fortawesome/free-solid-svg-icons' +import { + faArrowRight, + faBan, + faList, + faPenToSquare, + faTrash +} from '@fortawesome/free-solid-svg-icons' const { t } = useI18n() const firewallConfig = useFirewallStore() @@ -89,7 +96,7 @@ function editZone(zone: Zone) {

- + @@ -123,7 +130,6 @@ function editZone(zone: Zone) { t('standalone.zones_and_policies.traffic_to_same_zone') }} {{ t('standalone.zones_and_policies.interfaces') }} - {{ t('standalone.zones_and_policies.logging') }} @@ -140,14 +146,33 @@ function editZone(zone: Zone) { -
-
- +
+
+
+
+ +
+ {{ item.name }} +
+
+
+ + + +
- {{ item.name }}
@@ -171,24 +196,38 @@ function editZone(zone: Zone) { -
- + {{ item.input.toUpperCase() }}
- + {{ item.forward.toUpperCase() }}
@@ -198,16 +237,6 @@ function editZone(zone: Zone) { - -
-
-
Date: Fri, 15 May 2026 10:36:41 +0200 Subject: [PATCH 02/11] feat(threat shield dns): use staged commits (#766) --- src/stores/standalone/threatShield.ts | 44 +++------------------------ 1 file changed, 4 insertions(+), 40 deletions(-) diff --git a/src/stores/standalone/threatShield.ts b/src/stores/standalone/threatShield.ts index fe8a9e574..f717cf32c 100644 --- a/src/stores/standalone/threatShield.ts +++ b/src/stores/standalone/threatShield.ts @@ -7,7 +7,6 @@ import { ubusCall, ValidationError } from '@/lib/standalone/ubus' import { getAxiosErrorMessage, sortByProperty } from '@nethesis/vue-components' import { defineStore } from 'pinia' import { useUciPendingChangesStore } from './uciPendingChanges' -import { useNotificationsStore } from '../notifications' export type Blocklist = { name: string @@ -36,7 +35,6 @@ export type DnsAllowedDomain = { export const useThreatShieldStore = defineStore('threatShield', () => { const { t, te } = useI18n() const uciChangesStore = useUciPendingChangesStore() - const notificationsStore = useNotificationsStore() const dnsBlocklists = ref([]) const dnsSettings = ref() const dnsZones = ref([]) @@ -190,17 +188,7 @@ export const useThreatShieldStore = defineStore('threatShield', () => { try { await ubusCall('ns.threatshield', method, domain) - - if (!isEditing) { - // applied instantly, show notification (only if creating) - notificationsStore.createNotification({ - kind: 'success', - title: t('standalone.threat_shield_dns.blocked_domain_added_title'), - description: t('standalone.threat_shield_dns.blocked_domain_added_description', { - domain: domain.address - }) - }) - } + uciChangesStore.getChanges() } catch (err: any) { console.error(err) @@ -223,17 +211,7 @@ export const useThreatShieldStore = defineStore('threatShield', () => { try { await ubusCall('ns.threatshield', method, domain) - - if (!isEditing) { - // applied instantly, show notification (only if creating) - notificationsStore.createNotification({ - kind: 'success', - title: t('standalone.threat_shield_dns.allowed_domain_added_title'), - description: t('standalone.threat_shield_dns.allowed_domain_added_description', { - domain: domain.address - }) - }) - } + uciChangesStore.getChanges() } catch (err: any) { console.error(err) @@ -278,14 +256,7 @@ export const useThreatShieldStore = defineStore('threatShield', () => { await ubusCall('ns.threatshield', 'dns-delete-blocked', { address: domain }) - // applied instantly, show notification - notificationsStore.createNotification({ - kind: 'success', - title: t('standalone.threat_shield_dns.blocked_domain_deleted_title'), - description: t('standalone.threat_shield_dns.blocked_domain_deleted_description', { - domain - }) - }) + uciChangesStore.getChanges() } catch (err: any) { console.error(err) errorDeleteDnsBlockedDomain.value = t(getAxiosErrorMessage(err)) @@ -310,14 +281,7 @@ export const useThreatShieldStore = defineStore('threatShield', () => { await ubusCall('ns.threatshield', 'dns-delete-allowed', { address: domain }) - // applied instantly, show notification - notificationsStore.createNotification({ - kind: 'success', - title: t('standalone.threat_shield_dns.allowed_domain_deleted_title'), - description: t('standalone.threat_shield_dns.allowed_domain_deleted_description', { - domain - }) - }) + uciChangesStore.getChanges() } catch (err: any) { console.error(err) errorDeleteDnsAllowedDomain.value = t(getAxiosErrorMessage(err)) From b4e74688e255265bfc235111e5e055dfcb2fa366 Mon Sep 17 00:00:00 2001 From: Giacomo Sanchietti Date: Fri, 15 May 2026 11:18:07 +0200 Subject: [PATCH 03/11] feat: expose metrics from Victoria (#754) Co-authored-by: Tommaso Bailetti --- eslint-suppressions.json | 5 - package.json | 2 +- src/assets/main.css | 4 +- src/components/charts/TimeLineChart.vue | 9 +- src/components/standalone/SideMenu.vue | 4 + .../standalone/firewall/PortForwardTable.vue | 4 +- .../firewall/rules/FirewallRulesTable.vue | 4 +- .../monitoring/metrics/MetricChartCard.vue | 46 +++ .../metrics/MetricsAlertsSection.vue | 232 +++++++++++++ .../metrics/MetricsChartsSection.vue | 298 +++++++++++++++++ src/i18n/en.json | 51 +++ src/i18n/it.json | 54 +++ src/i18n/ta.json | 1 + src/lib/standalone/metricsCharts.ts | 308 ++++++++++++++++++ src/router/index.ts | 5 + .../standalone/firewall/ZonesAndPolicies.vue | 2 +- .../standalone/monitoring/MetricsView.vue | 104 ++++++ .../monitoring/PingLatencyMonitorView.vue | 175 ++++------ 18 files changed, 1183 insertions(+), 125 deletions(-) create mode 100644 src/components/standalone/monitoring/metrics/MetricChartCard.vue create mode 100644 src/components/standalone/monitoring/metrics/MetricsAlertsSection.vue create mode 100644 src/components/standalone/monitoring/metrics/MetricsChartsSection.vue create mode 100644 src/lib/standalone/metricsCharts.ts create mode 100644 src/views/standalone/monitoring/MetricsView.vue diff --git a/eslint-suppressions.json b/eslint-suppressions.json index 78b270677..91fd2af7f 100644 --- a/eslint-suppressions.json +++ b/eslint-suppressions.json @@ -849,11 +849,6 @@ "count": 4 } }, - "src/views/standalone/monitoring/PingLatencyMonitorView.vue": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, "src/views/standalone/monitoring/RealTimeMonitoringView.vue": { "@typescript-eslint/no-explicit-any": { "count": 1 diff --git a/package.json b/package.json index 4034d44d0..b44494d69 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "preview": "vite preview", "build-only": "vite build", "type-check": "vue-tsc --build", - "lint": "eslint . --max-warnings 1229", + "lint": "eslint . --max-warnings 60", "lint-fix": "npm run lint -- --fix", "format": "prettier --list-different src/", "format-fix": "prettier --write src/", diff --git a/src/assets/main.css b/src/assets/main.css index d0f376184..77901e083 100644 --- a/src/assets/main.css +++ b/src/assets/main.css @@ -146,7 +146,7 @@ } @utility text-primary-neutral { - @apply text-gray-600 dark:text-gray-50; + @apply text-gray-900 dark:text-gray-50; } @utility text-secondary { @@ -154,7 +154,7 @@ } @utility text-secondary-neutral { - @apply dark:text-gray-200; + @apply text-gray-700 dark:text-gray-200; } @utility text-tertiary-neutral { diff --git a/src/components/charts/TimeLineChart.vue b/src/components/charts/TimeLineChart.vue index 3a7fefd54..b5f52e0c1 100644 --- a/src/components/charts/TimeLineChart.vue +++ b/src/components/charts/TimeLineChart.vue @@ -125,11 +125,16 @@ const defaultOptions: any = { } const allOptions = computed(() => { - return merge(typeof props.options === 'object' ? props.options : {}, defaultOptions) + const customOptions = props.options && typeof props.options === 'object' ? props.options : {} + return merge({}, defaultOptions, customOptions) }) const chartData: any = computed(() => { - return { labels: props.labels, datasets: props.datasets } + // Deep-clone to strip Vue readonly proxies before handing data to Chart.js. + // Chart.js internally mutates dataset objects (attaches _meta, controllers, etc.) + // and Vue will block those mutations with "target is readonly" warnings if the + // objects are still wrapped in a shallowReadonly prop proxy. + return JSON.parse(JSON.stringify({ labels: props.labels, datasets: props.datasets })) }) const chartStyle = computed(() => { diff --git a/src/components/standalone/SideMenu.vue b/src/components/standalone/SideMenu.vue index 98584f1b7..0f05a14fb 100644 --- a/src/components/standalone/SideMenu.vue +++ b/src/components/standalone/SideMenu.vue @@ -51,6 +51,10 @@ const navigation: Ref = ref([ { name: 'standalone.ping_latency_monitor.title', to: 'monitoring/ping-latency-monitor' + }, + { + name: 'standalone.metrics.title', + to: 'monitoring/metrics' } ] }, diff --git a/src/components/standalone/firewall/PortForwardTable.vue b/src/components/standalone/firewall/PortForwardTable.vue index 796c1cf5d..1851d0b71 100644 --- a/src/components/standalone/firewall/PortForwardTable.vue +++ b/src/components/standalone/firewall/PortForwardTable.vue @@ -133,7 +133,7 @@ function getCellClasses(item: PortForward) {
- +