Skip to content

Commit 8cf06e0

Browse files
committed
wip
1 parent 8e90f47 commit 8cf06e0

7 files changed

Lines changed: 289 additions & 4 deletions

File tree

package-lock.json

Lines changed: 118 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@
3333
"@nethesis/nethesis-solid-svg-icons": "github:nethesis/Font-Awesome#ns-solid",
3434
"@nethesis/vue-components": "^3.2.0",
3535
"@tailwindcss/vite": "^4.1.8",
36+
"@tanstack/vue-query": "^5.92.9",
37+
"@tanstack/vue-query-devtools": "^6.1.4",
3638
"@types/lodash-es": "^4.17.12",
3739
"@types/semver": "^7.5.8",
3840
"@vuepic/vue-datepicker": "^12.0.5",
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
<script lang="ts" setup>
2+
import { useMutation, useQuery, useQueryClient } from '@tanstack/vue-query'
3+
import { ubusCall, ValidationError } from '@/lib/standalone/ubus.ts'
4+
import FlowsTable from '@/components/standalone/monitoring/FlowsTable.vue'
5+
import {
6+
getAxiosErrorMessage,
7+
NeButton,
8+
NeInlineNotification,
9+
NeSideDrawer,
10+
NeSkeleton,
11+
NeTextInput,
12+
NeToggle
13+
} from '@nethesis/vue-components'
14+
import { useI18n } from 'vue-i18n'
15+
import { ref, watch } from 'vue'
16+
import { useUciPendingChangesStore } from '@/stores/standalone/uciPendingChanges.ts'
17+
import { MessageBag } from '@/lib/validation.ts'
18+
19+
const { t } = useI18n()
20+
const uci = useUciPendingChangesStore()
21+
22+
type FlowDaemonResponse = {
23+
data: {
24+
configuration: {
25+
enabled: boolean
26+
expired_persistence: string
27+
}
28+
status: boolean
29+
}
30+
}
31+
32+
const { data, error, status } = useQuery({
33+
queryKey: ['flow', 'daemon'],
34+
queryFn: async () => ubusCall<FlowDaemonResponse>('ns.flows', 'get-configuration'),
35+
select: (data) => data.data
36+
})
37+
38+
const enabled = ref(false)
39+
const expiredPersistence = ref('')
40+
watch(
41+
data,
42+
(val) => {
43+
if (val != undefined) {
44+
enabled.value = val.configuration.enabled
45+
expiredPersistence.value = val.configuration.expired_persistence
46+
}
47+
},
48+
{ immediate: true }
49+
)
50+
51+
const configuringDaemon = ref(false)
52+
53+
type SetConfigPayload = {
54+
enabled: boolean
55+
expired_persistence: string
56+
}
57+
58+
const validationBag = ref(new MessageBag())
59+
const queryClient = useQueryClient()
60+
const {
61+
mutate,
62+
isPending,
63+
error: mutationError
64+
} = useMutation({
65+
mutationFn: (payload: SetConfigPayload) => ubusCall('ns.flows', 'set-configuration', payload),
66+
onSuccess: async () => {
67+
await Promise.all([
68+
uci.getChanges(),
69+
queryClient.invalidateQueries({ queryKey: ['flow', 'daemon'] })
70+
])
71+
configuringDaemon.value = false
72+
},
73+
onError: (err) => {
74+
if (err instanceof ValidationError) {
75+
validationBag.value = err.errorBag
76+
}
77+
},
78+
onMutate: () => {
79+
validationBag.value = new MessageBag()
80+
}
81+
})
82+
83+
function submit() {
84+
mutate({
85+
enabled: enabled.value,
86+
expired_persistence: expiredPersistence.value
87+
})
88+
}
89+
</script>
90+
91+
<template>
92+
<NeSkeleton v-if="status == 'pending'" :lines="10" />
93+
<NeInlineNotification
94+
v-else-if="status == 'error'"
95+
:description="t(getAxiosErrorMessage(error))"
96+
:title="t('standalone.flows.unable_to_get_flows_configuration')"
97+
kind="error"
98+
/>
99+
<div v-else class="space-y-4">
100+
<div class="flex flex-wrap items-start gap-4">
101+
<p class="mr-auto max-w-xl text-secondary-neutral">{{ t('standalone.flows.subtitle') }}</p>
102+
<NeButton kind="secondary" size="lg" @click="configuringDaemon = true">
103+
{{ t('standalone.flows.configure_flows_daemon') }}
104+
</NeButton>
105+
</div>
106+
<NeInlineNotification
107+
v-if="!data!.configuration.enabled"
108+
:description="t('standalone.flows.daemon_disabled_description')"
109+
:title="t('standalone.flows.daemon_disabled')"
110+
kind="info"
111+
/>
112+
<FlowsTable v-else />
113+
</div>
114+
<NeSideDrawer
115+
:is-shown="configuringDaemon"
116+
:title="t('standalone.flows.configure_flows_daemon')"
117+
@close="configuringDaemon = false"
118+
>
119+
<form v-if="status == 'success'" class="space-y-8" @submit.prevent="submit">
120+
<NeInlineNotification
121+
v-if="mutationError != null && validationBag.size < 1"
122+
:description="t(getAxiosErrorMessage(mutationError))"
123+
:title="t('standalone.flows.unable_to_configure_flows_daemon')"
124+
kind="error"
125+
/>
126+
<NeToggle
127+
v-model="enabled"
128+
:disabled="isPending"
129+
:label="enabled ? t('common.enabled') : t('common.disabled')"
130+
:top-label="t('standalone.flows.daemon_enabled')"
131+
:invalid-message="t(validationBag.getFirstI18nKeyFor('enabled'))"
132+
/>
133+
<NeTextInput
134+
v-model="expiredPersistence"
135+
:disabled="isPending"
136+
:label="t('standalone.flows.persistence_after_expiration')"
137+
:invalid-message="t(validationBag.getFirstI18nKeyFor('expired_persistence'))"
138+
/>
139+
<hr />
140+
<div class="flex flex-wrap justify-end gap-6">
141+
<NeButton :disabled="isPending" kind="tertiary" @click="configuringDaemon = false">
142+
{{ t('common.cancel') }}
143+
</NeButton>
144+
<NeButton :disabled="isPending" :loading="isPending" kind="primary" type="submit">
145+
{{ t('common.configure') }}
146+
</NeButton>
147+
</div>
148+
</form>
149+
</NeSideDrawer>
150+
</template>

src/components/standalone/monitoring/FlowsTable.vue

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -496,7 +496,6 @@ watch(flowDetails, (value) => {
496496
<template>
497497
<div class="space-y-8">
498498
<div class="space-y-4">
499-
<p class="max-w-xl text-secondary-neutral">{{ t('standalone.flows.subtitle') }}</p>
500499
<NeInlineNotification
501500
v-if="error"
502501
kind="error"

src/i18n/en.json

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2390,7 +2390,17 @@
23902390
"outgoing": "Outgoing",
23912391
"outgoing_description": "The connection has been initiated from local networks to the internet",
23922392
"broadcast": "Broadcast",
2393-
"broadcast_description": "The connection is a broadcast connection"
2393+
"broadcast_description": "The connection is a broadcast connection",
2394+
"unable_to_get_flows_configuration": "Unable to get flows daemon configuration",
2395+
"unable_to_configure_flows_daemon": "Unable to configure flows daemon",
2396+
"daemon_disabled": "Flows daemon is disabled.",
2397+
"daemon_disabled_description": "Please enable it in the settings to view real-time network connections.",
2398+
"configure_flows_daemon": "Configure flows daemon",
2399+
"daemon_enabled": "Flows daemon enabled",
2400+
"persistence_after_expiration": "Flows persistence after expiration",
2401+
"persistence_after_expiration_description": "Keep flow information available for a short period after the flow has ended. This allows to view information about recently ended flows, which can be useful to investigate connections that are established and closed in a short time frame.",
2402+
"daemon_not_running": "Flows daemon is enabled but not running",
2403+
"daemon_not_running_description": "If you just enabled the daemon, commit changes and wait a few seconds for the daemon to start. If the problem persists, check the system logs for errors related to the flows daemon."
23942404
},
23952405
"ping_latency_monitor": {
23962406
"title": "Ping latency monitor",

src/main.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import messages from '@intlify/unplugin-vue-i18n/messages'
1212

1313
import App from './App.vue'
1414
import router from './router'
15+
import { VueQueryPlugin } from '@tanstack/vue-query'
1516

1617
const app = createApp(App)
1718
const pinia = createPinia()
@@ -38,4 +39,9 @@ app.use(
3839
})
3940
)
4041

42+
// tanstack
43+
app.use(VueQueryPlugin, {
44+
enableDevtoolsV6Plugin: true
45+
})
46+
4147
app.mount('#app')

src/views/standalone/monitoring/RealTimeMonitoringView.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import { isStandaloneMode } from '@/lib/config'
2121
import { onMounted, ref } from 'vue'
2222
import { ubusCall } from '@/lib/standalone/ubus'
2323
import InstantTrafficMonitor from '@/components/standalone/monitoring/InstantTrafficMonitor.vue'
24-
import FlowsTable from '@/components/standalone/monitoring/FlowsTable.vue'
24+
import FlowSection from '@/components/standalone/monitoring/FlowSection.vue'
2525
2626
const { t } = useI18n()
2727
@@ -172,6 +172,6 @@ function openGrafana() {
172172
<VpnMonitor v-else-if="selectedTab === 'vpn'" />
173173
<SecurityMonitor v-else-if="selectedTab === 'security'" />
174174
<InstantTrafficMonitor v-else-if="selectedTab == 'instant-traffic'" />
175-
<FlowsTable v-else-if="selectedTab == 'flows'" />
175+
<FlowSection v-else-if="selectedTab == 'flows'" />
176176
</div>
177177
</template>

0 commit comments

Comments
 (0)