Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 1 addition & 6 deletions eslint-suppressions.json
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -916,7 +911,7 @@
},
"src/views/standalone/users_objects/UsersDatabaseView.vue": {
"@typescript-eslint/no-explicit-any": {
"count": 2
"count": 1
}
},
"src/views/standalone/vpn/IPsecTunnelView.vue": {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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 645",
"lint-fix": "npm run lint -- --fix",
"format": "prettier --list-different src/",
"format-fix": "prettier --write src/",
Expand Down
16 changes: 14 additions & 2 deletions src/assets/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -146,15 +146,15 @@
}

@utility text-primary-neutral {
@apply text-gray-600 dark:text-gray-50;
@apply text-gray-900 dark:text-gray-50;
}

@utility text-secondary {
@apply text-indigo-700 dark:text-indigo-500;
}

@utility text-secondary-neutral {
@apply dark:text-gray-200;
@apply text-gray-700 dark:text-gray-200;
}

@utility text-tertiary-neutral {
Expand All @@ -165,6 +165,18 @@
@apply text-green-700 dark:text-green-500;
}

@utility text-danger {
@apply text-rose-700 dark:text-rose-500;
}

@utility text-disabled {
@apply text-gray-400 dark:text-gray-600;
}

@utility bg-icon-danger {
@apply bg-rose-700 dark:bg-rose-500;
}

@utility border-elevation-0 {
@apply border-white dark:border-gray-950;
}
69 changes: 49 additions & 20 deletions src/components/NotificationDrawer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,35 @@
-->

<script setup lang="ts">
import { NeSideDrawer, NeEmptyState } from '@nethesis/vue-components'
import { NeToastNotification } from '@nethesis/vue-components'
import {
NeSideDrawer,
NeEmptyState,
NeToastNotificationV2,
NeToastNotification
} from '@nethesis/vue-components'
import { useI18n } from 'vue-i18n'
import { useNotificationsStore } from '@/stores/notifications'
import { isEmpty } from 'lodash-es'
import { faBell } from '@fortawesome/free-solid-svg-icons'
import { useAlerts } from '@/composables/useAlerts'
import { useRoute, useRouter } from 'vue-router'
import { getStandaloneRoutePrefix } from '@/lib/router'

const { t } = useI18n()
const notificationsStore = useNotificationsStore()
const { notifications: alertNotifications } = useAlerts()

function closeDrawer() {
notificationsStore.setNotificationDrawerOpen(false)
}

const router = useRouter()
const route = useRoute()

function goToAlerts() {
closeDrawer()
router.push(`${getStandaloneRoutePrefix(route)}/monitoring/metrics?tab=alerts`)
}
</script>

<template>
Expand All @@ -25,24 +42,36 @@ function closeDrawer() {
:close-aria-label="t('common.shell.close_side_drawer')"
@close="closeDrawer"
>
<!-- empty state -->
<NeEmptyState
v-if="isEmpty(notificationsStore.notifications)"
:title="t('notifications.no_notification')"
:icon="['fas', 'bell']"
/>
<!-- notifications -->
<div v-else class="flex w-full flex-col items-center space-y-4 sm:items-end">
<TransitionGroup name="fade">
<NeToastNotification
v-for="notification in notificationsStore.notifications"
:key="notification.id"
:notification="notification"
full-width
show-timestamp
:sr-close-label="t('common.close')"
/>
</TransitionGroup>
<div class="space-y-6">
<!-- empty state -->
<NeEmptyState
v-if="isEmpty(notificationsStore.notifications) && alertNotifications.length == 0"
:title="t('notifications.no_notification')"
:icon="faBell"
/>
<!-- notifications -->
<div v-else class="flex w-full flex-col items-center space-y-4 sm:items-end">
<TransitionGroup name="fade">
<template v-if="alertNotifications.length > 0">
<NeToastNotificationV2
v-for="alert in alertNotifications"
:key="alert.id"
:notification="alert"
:sr-close-label="t('common.close')"
full-width
@action="goToAlerts"
/>
</template>
<NeToastNotification
v-for="notification in notificationsStore.notifications"
:key="notification.id"
:notification="notification"
full-width
show-timestamp
:sr-close-label="t('common.close')"
/>
</TransitionGroup>
</div>
</div>
</NeSideDrawer>
</template>
9 changes: 7 additions & 2 deletions src/components/charts/TimeLineChart.vue
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
showLegend?: boolean
useKbpsFormat?: boolean
datasetSuffix?: string
options?: unknown

Check warning on line 36 in src/components/charts/TimeLineChart.vue

View workflow job for this annotation

GitHub Actions / Eslint

Prop 'options' requires default value to be set
}>(),
{ height: '', showLegend: true, useKbpsFormat: false, datasetSuffix: '' }
)
Expand Down Expand Up @@ -125,11 +125,16 @@
}

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(() => {
Expand Down
4 changes: 4 additions & 0 deletions src/components/standalone/SideMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ const navigation: Ref<MenuItem[]> = ref([
{
name: 'standalone.ping_latency_monitor.title',
to: 'monitoring/ping-latency-monitor'
},
{
name: 'standalone.metrics.title',
to: 'monitoring/metrics'
}
]
},
Expand Down
31 changes: 28 additions & 3 deletions src/components/standalone/StandaloneTopBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@ import { isStandaloneMode } from '@/lib/config'
import UciChangesModal from './UciChangesModal.vue'
import {
faAward,
faBell,
faCircleUser,
faMoon,
faRightFromBracket,
faSun
} from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
import { useSubscriptionStore } from '@/stores/standalone/subscription.ts'
import { useAlerts } from '@/composables/useAlerts'

const emit = defineEmits(['openSidebar'])

Expand All @@ -34,6 +36,7 @@ const themeStore = useThemeStore()
const uciChangesStore = useUciPendingChangesStore()
const notificationsStore = useNotificationsStore()
const subscriptionStore = useSubscriptionStore()
const { notifications: alertNotifications } = useAlerts()

const showUciChangesModal = ref(false)
const isChangesButtonFlashing = ref(false)
Expand Down Expand Up @@ -106,6 +109,22 @@ watch(
}
)

watch(
() => alertNotifications.value.length,
(newAlerts, oldAlerts) => {
if (newAlerts > oldAlerts) {
// briefly shake notifications icon
setTimeout(() => {
shakeNotificationsIcon.value = true
}, 700)

setTimeout(() => {
shakeNotificationsIcon.value = false
}, 2700)
}
}
)

onMounted(() => {
getSystemBoard()
})
Expand Down Expand Up @@ -223,16 +242,22 @@ function openNotificationsDrawer() {
<template #trigger>
<button
type="button"
:class="['-m-2.5 flex p-2.5', topBarButtonsColorClasses]"
:class="['relative -m-2.5 flex p-2.5', topBarButtonsColorClasses]"
@click="openNotificationsDrawer"
>
<span class="sr-only">{{ t('common.shell.show_notifications') }}</span>
<font-awesome-icon
:icon="['fas', 'bell']"
<FontAwesomeIcon
:icon="faBell"
:class="['h-6 w-6 shrink-0', { 'fa-shake': shakeNotificationsIcon }]"
style="--fa-animation-duration: 2s"
aria-hidden="true"
/>
<!-- notification indicator -->
<span
v-if="notificationsStore.numNotifications > 0 || alertNotifications.length > 0"
class="absolute top-2.5 right-2.5 size-3 rounded-full border border-elevation-0 bg-icon-danger"
aria-hidden="true"
/>
</button>
</template>
<template #content>
Expand Down
44 changes: 44 additions & 0 deletions src/components/standalone/dashboard/AlertsCard.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<script lang="ts" setup>
import { getStandaloneRoutePrefix } from '@/lib/router'
import { useAlerts } from '@/composables/useAlerts'
import { faCheck } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
import { NeBadgeV2, NeCard, NeLink, NeSkeleton } from '@nethesis/vue-components'
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRoute, useRouter } from 'vue-router'

const router = useRouter()
const route = useRoute()
const { data, status: alertsStatus } = useAlerts()
const { t } = useI18n()

const alertCount = computed<number>(() => {
return data.value?.length ?? 0
})

function goToAlerts() {
router.push(`${getStandaloneRoutePrefix(route)}/monitoring/metrics?tab=alerts`)
}
</script>

<template>
<NeCard :icon="['fas', 'triangle-exclamation']">
<template #title>
<NeLink @click="goToAlerts">
{{ t('standalone.metrics.tabs.alerts') }}
</NeLink>
</template>
<NeSkeleton v-if="alertsStatus == 'pending'" />
<div v-else class="space-y-3">
<NeBadgeV2 v-if="!alertCount" kind="green">
<FontAwesomeIcon :icon="faCheck" class="size-4" />
{{ t('standalone.metrics.no_alerts') }}
</NeBadgeV2>
<p :class="{ 'text-danger': alertCount }">
<span v-if="alertCount" class="mr-1 text-xl">{{ alertCount }}</span>
{{ t('standalone.metrics.active_alerts', { count: alertCount }) }}
</p>
</div>
</NeCard>
</template>
45 changes: 43 additions & 2 deletions src/components/standalone/firewall/PortForwardTable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -124,7 +127,45 @@ function getCellClasses(item: PortForward) {
<NeTableBody>
<NeTableRow v-for="item in portForwards" :key="item.id">
<NeTableCell :data-label="t('standalone.port_forward.name')">
<p :class="[...getCellClasses(item)]">{{ item.name }}</p>
<div class="flex w-full items-center justify-between gap-2">
<div class="flex-1">
<p :class="[...getCellClasses(item)]">{{ item.name }}</p>
</div>
<div class="flex flex-wrap items-center justify-end gap-4">
<!-- logging info -->
<NeTooltip v-if="item.enabled && item.log" trigger-event="mouseenter focus">
<template #trigger>
<NeLink>
<FontAwesomeIcon
:icon="faList"
class="h-4 w-4 text-indigo-800 dark:text-indigo-300"
/>
</NeLink>
</template>
<template #content>
<span> {{ t('standalone.port_forward.logging_enabled') }} </span>
</template>
</NeTooltip>
<!-- hairpin NAT -->
<NeTooltip v-if="item.enabled && item.reflection" trigger-event="mouseenter focus">
<template #trigger>
<NeLink>
<FontAwesomeIcon
:icon="faThumbTack"
class="h-4 w-4 text-indigo-800 dark:text-indigo-300"
/>
</NeLink>
</template>
<template #content>
<span>{{
t('standalone.port_forward.hairpin_nat_enabled', {
zones: item.reflection_zone?.map((z: string) => z.toUpperCase()).join(', ')
})
}}</span>
</template>
</NeTooltip>
</div>
</div>
</NeTableCell>
<NeTableCell :data-label="t('standalone.port_forward.source_port')">
<p :class="[...getCellClasses(item)]">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,10 @@ const actionOptions = ref([
{
id: 'ACCEPT',
label: 'ACCEPT'
},
{
id: 'NOTRACK',
label: 'NOTRACK'
}
])

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ function searchStringInRule(rule: FirewallRule, queryText: string) {
</div>
<NeButton
v-if="loading.listRules || rules.length"
kind="secondary"
kind="primary"
size="lg"
class="ml-6 shrink-0"
@click="showCreateRuleDrawer"
Expand Down
Loading
Loading