diff --git a/public/i18n/en/translation.json b/public/i18n/en/translation.json index afedcb5b3..f54d5c667 100644 --- a/public/i18n/en/translation.json +++ b/public/i18n/en/translation.json @@ -46,7 +46,11 @@ "more_info": "More info", "data_updated_every_seconds": "Data updated every {seconds} seconds", "actions": "Actions", - "go_to_page": "Go to {page}" + "go_to_page": "Go to {page}", + "download": "Download", + "upload": "Upload", + "no_data_available": "No data available", + "type": "Type" }, "error": { "generic_error": "Something went wrong", @@ -200,6 +204,7 @@ "cannot_delete_rule": "Cannot delete rule", "upload_file_migration": "Uploaded file is not a valid migration archive", "cannot_migrate": "Can't execute migration", + "cannot_retrieve_ovpn_tunnels": "Cannot retrieve OpenVPN tunnels", "cannot_retrieve_server_tunnels": "Cannot retrieve server tunnels", "cannot_retrieve_client_tunnels": "Cannot retrieve client tunnels", "cannot_duplicate_tunnel": "Cannot duplicate tunnel", @@ -357,7 +362,15 @@ "start_reserved": "First IP of network is reserved", "cannot_retrieve_object_suggestions": "Cannot retrieve object suggestions", "cannot_retrieve_nat_helpers": "Cannot retrieve NAT helpers", - "cannot_save_nat_helper": "Cannot save NAT helper" + "cannot_save_nat_helper": "Cannot save NAT helper", + "cannot_retrieve_mwan_report": "Cannot retrieve MultiWAN report", + "cannot_retrieve_malware_report": "Cannot retrieve malware report", + "cannot_retrieve_attack_report": "Cannot retrieve attack report", + "cannot_retrieve_ovpn_traffic_by_hour": "Cannot retrieve OpenVPN traffic by hour", + "cannot_retrieve_vpn_client_sessions": "Cannot retrieve VPN client sessions", + "cannot_retrieve_connected_client_by_hour": "Cannot retrieve connected clients by hour", + "cannot_retrieve_client_traffic_by_hour": "Cannot retrieve client traffic by hour", + "cannot_retrieve_talkers_list": "Cannot retrieve talkers list" }, "ne_text_input": { "show_password": "Show password", @@ -430,15 +443,6 @@ "hotspot": "Hotspot", "thread_shield_ip": "Threat Shield IP", "known_hosts": "Known hosts", - "real_time_traffic": "Real time traffic", - "host": "Host", - "traffic": "Traffic", - "today_top_hosts": "Top hosts", - "today_top_applications": "Top applications", - "today_top_protocols": "Top protocols", - "today_top_hosts_description": "Hosts that generated most traffic today", - "today_top_applications_description": "Applications that generated most traffic today", - "today_top_protocols_description": "Protocols that generated most traffic today", "wan_traffic": "WAN traffic", "wan_interface": "WAN interface", "download": "Download", @@ -448,9 +452,6 @@ "root_usage": "Root", "storage_usage": "Data", "tmpfs_usage": "Tmpfs", - "no_hosts": "No hosts", - "no_applications": "No applications", - "no_protocols": "No protocols", "openvpn_rw": "OpenVPN RW", "usage_free_of_total": "{free} free of {total}", "default_hostname_warning": "Using the default hostname 'NethSec' is not recommended. Change it in {systemSettingsLink} and apply the change.", @@ -1443,7 +1444,7 @@ "blocklist_description": "Threat shield keeps you safe by blocking attacks from known malicious IP addresses. These addresses are compiled into blocklists, each with a clear name that tells you its purpose and who maintains it. The confidence score is a value from 1 to 10 that indicates the quality of the list. A higher number means a lower chance of false positives. This score is not available for 'Community' lists.", "blocklist_subscription_description": "Nethesis and Yoroi blocklists are available only if the unit has a valid subscription that includes Threat shield service.", "threat_shield_enabled": "Threat shield enabled", - "threat_shield_disabled": "Threat shield disabled", + "threat_shield_disabled": "Threat shield is disabled", "name": "Name", "type": "Type", "confidence": "Confidence", @@ -1783,22 +1784,79 @@ "search_error": "Invalid regular expression.", "search_tooltip": "The search uses regular expression format." }, - "report": { - "title": "Report", - "tabs": { - "real_time_report": "Real time report", - "ping_latency_monitor": "Ping latency monitor" - }, - "real_time_report": { - "description": "The monitoring tool offers in-depth analysis of system and application performance, presenting detailed metrics and visualizations. These metrics are stored in RAM and reset upon machine reboot, except when the unit is linked to a remote controller. In such cases, metrics are saved in the controller and persist through reboots.", - "open_report": "Open report", - "cannot_open_report_from_controller": "Cannot open report from a controlled unit. You can open the report by accessing the unit UI directly, or by accessing the controller UI, going to 'Unit manager' page, clicking the three dots menu of the unit and selecting 'Open metrics'." - }, - "ping_latency_monitor": { - "description": "Set up the monitoring tool to assess round-trip time and packet loss by sending ping messages to network hosts. This tool is used for monitoring network connectivity quality. You can add one or more hosts to monitor. It's also possible to add IP addresses in a VPN to assess tunnel quality.", - "add_host": "Add host", - "host_to_monitor": "Hosts to monitor" - } + "monitoring": { + "title": "Monitoring" + }, + "real_time_monitor": { + "title": "Real time monitor", + "description": "In-depth analysis of system and application performance, detailed metrics and visualizations. Monitoring data are stored in RAM and reset upon unit reboot. If the unit is linked to a remote controller, metrics are saved on the controller too and persist through unit reboots.", + "open_report": "Open Netdata", + "cannot_open_grafana_message": "Grafana monitoring is available only after connecting the unit to a controller", + "cannot_open_report_from_controller": "Cannot open report from a controlled unit. You can open the report by accessing the unit UI directly, or by accessing the controller UI, going to 'Unit manager' page, clicking the three dots menu of the unit and selecting 'Open metrics'.", + "traffic": "Traffic", + "connectivity": "Connectivity", + "vpn": "VPN", + "security": "Security", + "today_top_local_hosts": "Local hosts", + "no_hosts": "No hosts", + "today_top_applications": "Applications", + "today_top_remote_hosts": "Remote hosts", + "no_applications": "No applications", + "today_top_protocols": "Protocols", + "no_protocols": "No protocols", + "today_total_traffic": "Today total traffic", + "instant_traffic": "Instant traffic", + "today_traffic": "Today traffic", + "recent_traffic": "Recent traffic", + "host": "Host", + "hosts": "Hosts", + "application": "Application", + "applications": "Applications", + "protocol": "Protocol", + "protocols": "Protocols", + "connections": "Connections", + "wan_events": "WAN events", + "wan_name_events": "{name} events", + "interface_name_traffic": "{name} traffic", + "blocked_threats": "Blocked threats", + "blocked_packets_by_hour": "Blocked packets by hour", + "malware_by_direction": "Malware by direction", + "malware_by_category": "Malware by category", + "most_blocked_ip_addresses": "Most blocked IP addresses", + "blocked_ip_addresses_by_hour": "Blocked IP addresses by hour", + "times_blocked": "Times blocked", + "blocked_ip_addresses": "Blocked IP addresses", + "timestamp": "Timestamp", + "event": "Event", + "remote_access_vpn": "Remote access VPN", + "site_to_site_vpn": "Site-to-Site VPN", + "connected_clients": "Connected clients", + "total_clients_traffic_by_hour": "Total clients traffic by hour", + "client_sessions": "Client sessions", + "account": "Account", + "connected_since": "Connected since", + "no_sessions": "No sessions", + "connected_clients_by_hour": "Connected clients by hour", + "client_traffic_by_hour": "Client traffic by hour", + "connected_tunnels": "Connected tunnels", + "configured_tunnels": "Configured tunnels", + "tunnel_ovpn_client": "OpenVPN client tunnel", + "tunnel_ovpn_server": "OpenVPN server tunnel", + "tunnel_ipsec": "IPsec tunnel", + "device": "Device", + "interface": "Interface", + "threat_shield_disabled_message": "No statistics are available because Threat shield is disabled.", + "online": "Online", + "offline": "Offline", + "no_events_message": "No events, all good", + "view_all_on_grafana": "View all on Grafana", + "no_vpn_network_configured": "No VPN network configured" + }, + "ping_latency_monitor": { + "title": "Ping latency monitor", + "description": "Set up the monitoring tool to assess round-trip time and packet loss by sending ping messages to network hosts. This tool is used for monitoring network connectivity quality. You can add one or more hosts to monitor. It's also possible to add IP addresses in a VPN to assess tunnel quality.", + "add_host": "Add host", + "host_to_monitor": "Hosts to monitor" }, "account_management": { "title": "Account settings for '{name}'", diff --git a/src/components/charts/BasicBarChart.vue b/src/components/charts/BasicBarChart.vue new file mode 100644 index 000000000..422449acc --- /dev/null +++ b/src/components/charts/BasicBarChart.vue @@ -0,0 +1,103 @@ + + + + + diff --git a/src/components/charts/BasicPieChart.vue b/src/components/charts/BasicPieChart.vue new file mode 100644 index 000000000..8d54ce136 --- /dev/null +++ b/src/components/charts/BasicPieChart.vue @@ -0,0 +1,85 @@ + + + + + diff --git a/src/components/charts/SimpleStat.vue b/src/components/charts/SimpleStat.vue new file mode 100644 index 000000000..7666b72aa --- /dev/null +++ b/src/components/charts/SimpleStat.vue @@ -0,0 +1,12 @@ + + + diff --git a/src/components/standalone/SideMenu.vue b/src/components/standalone/SideMenu.vue index c3fc9662d..f3bdf9d46 100644 --- a/src/components/standalone/SideMenu.vue +++ b/src/components/standalone/SideMenu.vue @@ -29,7 +29,8 @@ const menuExpanded: any = ref({ 'users-objects': false, firewall: false, security: false, - vpn: false + vpn: false, + monitoring: false }) const navigation: Ref = ref([ @@ -191,8 +192,22 @@ const navigation: Ref = ref([ } ] }, - { name: t('standalone.logs.title'), to: 'logs', icon: 'list' }, - { name: t('standalone.report.title'), to: 'report', icon: 'chart-line' } + { + name: t('standalone.monitoring.title'), + to: 'monitoring', + icon: 'chart-line', + children: [ + { + name: t('standalone.real_time_monitor.title'), + to: 'monitoring/real-time-monitoring' + }, + { + name: t('standalone.ping_latency_monitor.title'), + to: 'monitoring/ping-latency-monitor' + } + ] + }, + { name: t('standalone.logs.title'), to: 'logs', icon: 'list' } ]) onMounted(() => { diff --git a/src/components/standalone/dashboard/RealTimeTrafficCard.vue b/src/components/standalone/dashboard/RealTimeTrafficCard.vue deleted file mode 100644 index 9e57be0e8..000000000 --- a/src/components/standalone/dashboard/RealTimeTrafficCard.vue +++ /dev/null @@ -1,99 +0,0 @@ - - - - - diff --git a/src/components/standalone/dashboard/SystemInfoCard.vue b/src/components/standalone/dashboard/SystemInfoCard.vue index 957ad7cde..ff8ab9523 100644 --- a/src/components/standalone/dashboard/SystemInfoCard.vue +++ b/src/components/standalone/dashboard/SystemInfoCard.vue @@ -169,7 +169,7 @@ async function getUpdatesStatus() {
{{ systemInfo?.hardware || '-' }}
-
+
{{ t('standalone.dashboard.hostname') }}
{{ systemInfo?.hostname || '-' }} @@ -198,7 +198,7 @@ async function getUpdatesStatus() {
-
+
{{ t('standalone.dashboard.operating_system') }}
{{ systemInfo?.version?.release || '-' }} @@ -208,11 +208,11 @@ async function getUpdatesStatus() {
-
+
{{ t('standalone.dashboard.uptime') }} {{ systemInfo?.uptime ? formatDurationLoc(systemInfo.uptime) : '-' }}
-
+
{{ t('standalone.dashboard.load_minutes') }} {{ systemInfo?.load @@ -223,7 +223,7 @@ async function getUpdatesStatus() { }}
-
+
{{ t('standalone.dashboard.memory_usage') }} {{ diff --git a/src/components/standalone/dashboard/WanTrafficCard.vue b/src/components/standalone/dashboard/WanTrafficCard.vue deleted file mode 100644 index 222e9e770..000000000 --- a/src/components/standalone/dashboard/WanTrafficCard.vue +++ /dev/null @@ -1,182 +0,0 @@ - - - - - diff --git a/src/components/standalone/monitoring/ConnectivityMonitor.vue b/src/components/standalone/monitoring/ConnectivityMonitor.vue new file mode 100644 index 000000000..2ede431c9 --- /dev/null +++ b/src/components/standalone/monitoring/ConnectivityMonitor.vue @@ -0,0 +1,244 @@ + + + + + diff --git a/src/components/standalone/monitoring/SecurityMonitor.vue b/src/components/standalone/monitoring/SecurityMonitor.vue new file mode 100644 index 000000000..57d9380d9 --- /dev/null +++ b/src/components/standalone/monitoring/SecurityMonitor.vue @@ -0,0 +1,472 @@ + + + + + diff --git a/src/components/standalone/monitoring/TrafficByHourChart.vue b/src/components/standalone/monitoring/TrafficByHourChart.vue new file mode 100644 index 000000000..c9d22a339 --- /dev/null +++ b/src/components/standalone/monitoring/TrafficByHourChart.vue @@ -0,0 +1,112 @@ + + + + + diff --git a/src/components/standalone/monitoring/TrafficMonitor.vue b/src/components/standalone/monitoring/TrafficMonitor.vue new file mode 100644 index 000000000..0875ea9e6 --- /dev/null +++ b/src/components/standalone/monitoring/TrafficMonitor.vue @@ -0,0 +1,180 @@ + + + + + diff --git a/src/components/standalone/monitoring/VpnMonitor.vue b/src/components/standalone/monitoring/VpnMonitor.vue new file mode 100644 index 000000000..b83f6c7c3 --- /dev/null +++ b/src/components/standalone/monitoring/VpnMonitor.vue @@ -0,0 +1,428 @@ + + + + diff --git a/src/components/standalone/monitoring/connectivity/InterfaceTrafficCard.vue b/src/components/standalone/monitoring/connectivity/InterfaceTrafficCard.vue new file mode 100644 index 000000000..42fac3c79 --- /dev/null +++ b/src/components/standalone/monitoring/connectivity/InterfaceTrafficCard.vue @@ -0,0 +1,110 @@ + + + + + diff --git a/src/components/standalone/monitoring/connectivity/WanConnectionsCard.vue b/src/components/standalone/monitoring/connectivity/WanConnectionsCard.vue new file mode 100644 index 000000000..749737b14 --- /dev/null +++ b/src/components/standalone/monitoring/connectivity/WanConnectionsCard.vue @@ -0,0 +1,98 @@ + + + + + diff --git a/src/components/standalone/monitoring/connectivity/WanEventsCard.vue b/src/components/standalone/monitoring/connectivity/WanEventsCard.vue new file mode 100644 index 000000000..11bc095ad --- /dev/null +++ b/src/components/standalone/monitoring/connectivity/WanEventsCard.vue @@ -0,0 +1,106 @@ + + + + + diff --git a/src/components/standalone/monitoring/security/BlockedIpsByHourChart.vue b/src/components/standalone/monitoring/security/BlockedIpsByHourChart.vue new file mode 100644 index 000000000..d45b7faed --- /dev/null +++ b/src/components/standalone/monitoring/security/BlockedIpsByHourChart.vue @@ -0,0 +1,95 @@ + + + + + diff --git a/src/components/standalone/monitoring/security/BlockedPacketsByHourChart.vue b/src/components/standalone/monitoring/security/BlockedPacketsByHourChart.vue new file mode 100644 index 000000000..e98209b09 --- /dev/null +++ b/src/components/standalone/monitoring/security/BlockedPacketsByHourChart.vue @@ -0,0 +1,106 @@ + + + + + diff --git a/src/components/standalone/monitoring/traffic/InstantAppTrafficCard.vue b/src/components/standalone/monitoring/traffic/InstantAppTrafficCard.vue new file mode 100644 index 000000000..90511f6f8 --- /dev/null +++ b/src/components/standalone/monitoring/traffic/InstantAppTrafficCard.vue @@ -0,0 +1,101 @@ + + + + + diff --git a/src/components/standalone/monitoring/traffic/InstantHostTrafficCard.vue b/src/components/standalone/monitoring/traffic/InstantHostTrafficCard.vue new file mode 100644 index 000000000..6b640f8bd --- /dev/null +++ b/src/components/standalone/monitoring/traffic/InstantHostTrafficCard.vue @@ -0,0 +1,97 @@ + + + + + diff --git a/src/components/standalone/monitoring/traffic/InstantProtocolTrafficCard.vue b/src/components/standalone/monitoring/traffic/InstantProtocolTrafficCard.vue new file mode 100644 index 000000000..60eccee2f --- /dev/null +++ b/src/components/standalone/monitoring/traffic/InstantProtocolTrafficCard.vue @@ -0,0 +1,101 @@ + + + + + diff --git a/src/components/standalone/dashboard/WanTrafficChart.vue b/src/components/standalone/monitoring/traffic/InterfaceTrafficChart.vue similarity index 89% rename from src/components/standalone/dashboard/WanTrafficChart.vue rename to src/components/standalone/monitoring/traffic/InterfaceTrafficChart.vue index ba3dc390c..09bc77b34 100644 --- a/src/components/standalone/dashboard/WanTrafficChart.vue +++ b/src/components/standalone/monitoring/traffic/InterfaceTrafficChart.vue @@ -24,16 +24,11 @@ import { GRAY_200, GRAY_700, GRAY_800 } from '@/lib/color' const themeStore = useThemeStore() -const props = defineProps({ - labels: { - type: Array, - required: true - }, - datasets: { - type: Array, - required: true - } -}) +const props = defineProps<{ + labels: string[] + datasets: any[] + height?: string +}>() const options: any = { // turn off animations and data parsing for performance @@ -104,13 +99,22 @@ const options: any = { color: themeStore.isLight ? GRAY_700 : GRAY_200 } } - } + }, + responsive: true } const chartData: any = computed(() => { return { labels: props.labels, datasets: props.datasets } }) +const chartStyle = computed(() => { + return { + height: props.height || '', + width: '100%', + position: 'relative' + } +}) + ChartJS.register( CategoryScale, LinearScale, @@ -124,5 +128,5 @@ ChartJS.register( diff --git a/src/components/standalone/dashboard/TrafficSummaryChart.vue b/src/components/standalone/monitoring/traffic/TrafficSummaryChart.vue similarity index 75% rename from src/components/standalone/dashboard/TrafficSummaryChart.vue rename to src/components/standalone/monitoring/traffic/TrafficSummaryChart.vue index 327322f56..a26ad0329 100644 --- a/src/components/standalone/dashboard/TrafficSummaryChart.vue +++ b/src/components/standalone/monitoring/traffic/TrafficSummaryChart.vue @@ -78,6 +78,25 @@ const options: any = { legend: { display: false } + }, + animation: { + duration: 1, + onComplete: function ({ chart }: any) { + const ctx = chart.ctx + + chart.config.data.datasets.forEach(function (dataset: any, i: number) { + const meta = chart.getDatasetMeta(i) + + meta.data.forEach(function (bar: any, index: number) { + const data = dataset.data[index] + + if (data) { + ctx.fillStyle = themeStore.isLight ? GRAY_700 : GRAY_200 + ctx.fillText(byteFormat1024(Number(data)), bar.x + 5, bar.y + 5) + } + }) + }) + } } } @@ -89,5 +108,7 @@ ChartJS.register(CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend) diff --git a/src/components/standalone/monitoring/vpn/ClientSessionsCard.vue b/src/components/standalone/monitoring/vpn/ClientSessionsCard.vue new file mode 100644 index 000000000..33266ccf8 --- /dev/null +++ b/src/components/standalone/monitoring/vpn/ClientSessionsCard.vue @@ -0,0 +1,131 @@ + + + + + diff --git a/src/components/standalone/monitoring/vpn/ClientsTrafficByHourCard.vue b/src/components/standalone/monitoring/vpn/ClientsTrafficByHourCard.vue new file mode 100644 index 000000000..de63f4a22 --- /dev/null +++ b/src/components/standalone/monitoring/vpn/ClientsTrafficByHourCard.vue @@ -0,0 +1,75 @@ + + + + + diff --git a/src/components/standalone/monitoring/vpn/ConfiguredTunnelsCard.vue b/src/components/standalone/monitoring/vpn/ConfiguredTunnelsCard.vue new file mode 100644 index 000000000..98f3fadae --- /dev/null +++ b/src/components/standalone/monitoring/vpn/ConfiguredTunnelsCard.vue @@ -0,0 +1,109 @@ + + + + + diff --git a/src/components/standalone/monitoring/vpn/ConnectedClientsByHourCard.vue b/src/components/standalone/monitoring/vpn/ConnectedClientsByHourCard.vue new file mode 100644 index 000000000..6a7d5bf6f --- /dev/null +++ b/src/components/standalone/monitoring/vpn/ConnectedClientsByHourCard.vue @@ -0,0 +1,71 @@ + + + + + diff --git a/src/components/standalone/monitoring/vpn/ConnectedClientsByHourChart.vue b/src/components/standalone/monitoring/vpn/ConnectedClientsByHourChart.vue new file mode 100644 index 000000000..d45b7faed --- /dev/null +++ b/src/components/standalone/monitoring/vpn/ConnectedClientsByHourChart.vue @@ -0,0 +1,95 @@ + + + + + diff --git a/src/components/standalone/monitoring/vpn/CurrentlyConnectedClientsCard.vue b/src/components/standalone/monitoring/vpn/CurrentlyConnectedClientsCard.vue new file mode 100644 index 000000000..af968f1d4 --- /dev/null +++ b/src/components/standalone/monitoring/vpn/CurrentlyConnectedClientsCard.vue @@ -0,0 +1,39 @@ + + + + + diff --git a/src/components/standalone/monitoring/vpn/ServerStatusCard.vue b/src/components/standalone/monitoring/vpn/ServerStatusCard.vue new file mode 100644 index 000000000..549ca9a3b --- /dev/null +++ b/src/components/standalone/monitoring/vpn/ServerStatusCard.vue @@ -0,0 +1,31 @@ + + + + + diff --git a/src/components/standalone/monitoring/vpn/TrafficByClientByHourCard.vue b/src/components/standalone/monitoring/vpn/TrafficByClientByHourCard.vue new file mode 100644 index 000000000..a89d274be --- /dev/null +++ b/src/components/standalone/monitoring/vpn/TrafficByClientByHourCard.vue @@ -0,0 +1,124 @@ + + + + + diff --git a/src/components/standalone/report/ReportContent.vue b/src/components/standalone/report/ReportContent.vue deleted file mode 100644 index 828dadb0f..000000000 --- a/src/components/standalone/report/ReportContent.vue +++ /dev/null @@ -1,50 +0,0 @@ - - - - - diff --git a/src/composables/useTopTalkers.ts b/src/composables/useTopTalkers.ts new file mode 100644 index 000000000..4926c71d3 --- /dev/null +++ b/src/composables/useTopTalkers.ts @@ -0,0 +1,86 @@ +// Copyright (C) 2024 Nethesis S.r.l. +// SPDX-License-Identifier: GPL-3.0-or-later + +import { onMounted, onUnmounted, ref, toValue, type MaybeRefOrGetter } from 'vue' +import { ubusCall } from '@/lib/standalone/ubus' +import { useI18n } from 'vue-i18n' +import { getAxiosErrorMessage } from '@nethesis/vue-components' + +export type TopHost = { + host: string + ip: string + mac: string + totals: TopHostTotal +} + +type TopHostTotal = { + bandwidth: number + download: number + packets: number + upload: number +} + +export type TopItem = { + name: string + value: number +} + +export function useTopTalkers(limit: MaybeRefOrGetter = 20) { + // random refresh interval between 20 and 30 seconds + const REFRESH_INTERVAL = 20000 + Math.random() * 10 * 1000 + const { t } = useI18n() + const intervalId = ref(0) + const topHosts = ref([]) + const topApps = ref([]) + const topProtocols = ref([]) + const loadingTopTalkers = ref(false) + const errorTopTalkers = ref('') + const errorTopTalkersDescription = ref('') + + onMounted(() => { + getTopTalkers() + + // periodically reload data + intervalId.value = setInterval(getTopTalkers, REFRESH_INTERVAL) + }) + + onUnmounted(() => { + if (intervalId.value) { + clearInterval(intervalId.value) + } + }) + + async function getTopTalkers() { + errorTopTalkers.value = '' + errorTopTalkersDescription.value = '' + + // show skeleton only the first time + if (!intervalId.value) { + loadingTopTalkers.value = true + } + + try { + const res = await ubusCall('ns.talkers', 'list', { + limit: toValue(limit) + }) + topApps.value = res.data.talkers.top_apps + topHosts.value = res.data.talkers.top_hosts + topProtocols.value = res.data.talkers.top_protocols + } catch (err: any) { + console.error(err) + errorTopTalkers.value = t('error.cannot_retrieve_talkers_list') + errorTopTalkersDescription.value = t(getAxiosErrorMessage(err)) + } finally { + loadingTopTalkers.value = false + } + } + + return { + topApps, + topHosts, + topProtocols, + loadingTopTalkers, + errorTopTalkers, + errorTopTalkersDescription + } +} diff --git a/src/composables/useTrafficSummary.ts b/src/composables/useTrafficSummary.ts index 957e10fd7..ab1743698 100644 --- a/src/composables/useTrafficSummary.ts +++ b/src/composables/useTrafficSummary.ts @@ -1,7 +1,7 @@ // Copyright (C) 2024 Nethesis S.r.l. // SPDX-License-Identifier: GPL-3.0-or-later -import { onMounted, onUnmounted, ref } from 'vue' +import { onMounted, ref } from 'vue' import { ubusCall } from '@/lib/standalone/ubus' import { padStart, upperFirst } from 'lodash-es' import { useI18n } from 'vue-i18n' @@ -10,54 +10,46 @@ import { useThemeStore } from '@/stores/theme' import { CYAN_500, CYAN_600 } from '@/lib/color' export function useTrafficSummary() { - // random refresh interval between 20 and 30 seconds - const REFRESH_INTERVAL = 20000 + Math.random() * 10 * 1000 const CHART_NUM_ITEMS = 5 const { t } = useI18n() const themeStore = useThemeStore() - const intervalId = ref(0) - const clientsLabels = ref([]) + const clientsLabels = ref([]) const clientsDatasets = ref([]) - const protocolsLabels = ref([]) + const protocolsLabels = ref([]) const protocolsDatasets = ref([]) - const appsLabels = ref([]) + const remoteHostsLabels = ref([]) + const remoteHostsDatasets = ref([]) + const appsLabels = ref([]) const appsDatasets = ref([]) + const hoursLabels = ref([]) + const hoursDatasets = ref([]) + const totalTraffic = ref(0) const loadingTrafficSummary = ref(false) - const errorTitle = ref('') - const errorDescription = ref('') + const trafficSummaryError = ref('') + const trafficSummaryErrorDescription = ref('') const datasetProps = { - label: t('standalone.dashboard.traffic'), + label: t('standalone.real_time_monitor.traffic'), backgroundColor: themeStore.isLight ? CYAN_600 : CYAN_500, + borderColor: themeStore.isLight ? CYAN_600 : CYAN_500, borderRadius: 6, - maxBarThickness: 25 + maxBarThickness: 25, + borderWidth: 1, + radius: 0 } onMounted(() => { getTrafficSummary() - - // periodically reload data - intervalId.value = setInterval(getTrafficSummary, REFRESH_INTERVAL) - }) - - onUnmounted(() => { - if (intervalId.value) { - clearInterval(intervalId.value) - } }) async function getTrafficSummary() { - errorTitle.value = '' - errorDescription.value = '' + trafficSummaryError.value = '' + trafficSummaryErrorDescription.value = '' const today = new Date() const year = today.getFullYear() const month = today.getMonth() + 1 const day = today.getDate() - - // show skeleton only the first time - if (!intervalId.value) { - loadingTrafficSummary.value = true - } + loadingTrafficSummary.value = true try { const res = await ubusCall('ns.dpireport', 'summary', { @@ -65,6 +57,7 @@ export function useTrafficSummary() { month: padStart(month.toString(), 2, '0'), day: day.toString() }) + totalTraffic.value = res.data.total // protocols chart @@ -125,10 +118,36 @@ export function useTrafficSummary() { data: clientsChartData } ] + + // hosts chart + + remoteHostsLabels.value = res.data.host + .slice(0, CHART_NUM_ITEMS) + .map((host: any[]) => host[0]) + + const hostsChartData = res.data.host.slice(0, CHART_NUM_ITEMS).map((host: any[]) => host[1]) + + remoteHostsDatasets.value = [ + { + ...datasetProps, + data: hostsChartData + } + ] + + // hours chart + + hoursLabels.value = res.data.hours.map((host: any[]) => host[0]) + const hoursChartData = res.data.hours.map((host: any[]) => host[1]) + hoursDatasets.value = [ + { + ...datasetProps, + data: hoursChartData + } + ] } catch (err: any) { console.error(err) - errorTitle.value = t('error.cannot_retrieve_traffic_summary') - errorDescription.value = t(getAxiosErrorMessage(err)) + trafficSummaryError.value = t('error.cannot_retrieve_traffic_summary') + trafficSummaryErrorDescription.value = t(getAxiosErrorMessage(err)) } finally { loadingTrafficSummary.value = false } @@ -141,8 +160,13 @@ export function useTrafficSummary() { protocolsDatasets, appsLabels, appsDatasets, + remoteHostsLabels, + remoteHostsDatasets, + hoursLabels, + hoursDatasets, + totalTraffic, loadingTrafficSummary, - errorTitle, - errorDescription + trafficSummaryError, + trafficSummaryErrorDescription } } diff --git a/src/lib/color.ts b/src/lib/color.ts index 7723d8e42..598d71a7b 100644 --- a/src/lib/color.ts +++ b/src/lib/color.ts @@ -2,10 +2,30 @@ // SPDX-License-Identifier: GPL-3.0-or-later // Use these color constants only where Tailwind classes cannot be used +// TODO use Tailwind color variables instead of these constants +export const CYAN_300 = '#67e8f9' export const CYAN_500 = '#06b6d4' export const CYAN_600 = '#0891b2' +export const CYAN_800 = '#155e75' export const INDIGO_400 = '#818cf8' +export const INDIGO_500 = '#6366f1' export const INDIGO_600 = '#4f46e5' +export const GRAY_50 = '#f9fafb' export const GRAY_200 = '#e5e7eb' +export const GRAY_400 = '#9ca3af' +export const GRAY_500 = '#6b7280' export const GRAY_700 = '#374151' export const GRAY_800 = '#1f2937' +export const GRAY_900 = '#111827' +export const EMERALD_500 = '#10b981' +export const EMERALD_600 = '#059669' +export const VIOLET_500 = '#8b5cf6' +export const VIOLET_600 = '#7c3aed' +export const AMBER_500 = '#f59e0b' +export const AMBER_600 = '#d97706' +export const ROSE_500 = '#f43f5e' +export const ROSE_600 = '#e11d48' +export const LIME_500 = '#84cc16' +export const LIME_600 = '#65a30d' +export const FUCHSIA_500 = '#d946ef' +export const FUCHSIA_600 = '#c026d3' diff --git a/src/router/index.ts b/src/router/index.ts index c8c5ba3d2..5e0ead40e 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -166,19 +166,19 @@ const standaloneRoutes = [ component: () => import('../views/standalone/LogsView.vue') }, { - path: 'report', - name: 'Report', - component: () => import('../views/standalone/ReportView.vue') + path: 'monitoring/real-time-monitoring', + name: 'RealTimeMonitoring', + component: () => import('../views/standalone/monitoring/RealTimeMonitoringView.vue') + }, + { + path: 'monitoring/ping-latency-monitor', + name: 'PingLatencyMonitor', + component: () => import('../views/standalone/monitoring/PingLatencyMonitorView.vue') }, { path: 'account', name: 'Account', component: () => import('../views/standalone/AccountView.vue') - }, - { - path: 'report', - name: 'Report', - component: () => import('../views/standalone/ReportView.vue') } ] diff --git a/src/views/standalone/ReportView.vue b/src/views/standalone/ReportView.vue deleted file mode 100644 index 1f80d5799..000000000 --- a/src/views/standalone/ReportView.vue +++ /dev/null @@ -1,52 +0,0 @@ - - - - - diff --git a/src/views/standalone/StandaloneDashboardView.vue b/src/views/standalone/StandaloneDashboardView.vue index 70fd21bb1..9cb2c8466 100644 --- a/src/views/standalone/StandaloneDashboardView.vue +++ b/src/views/standalone/StandaloneDashboardView.vue @@ -4,14 +4,10 @@ --> +