diff --git a/dashboard/src/Index.vue b/dashboard/src/Index.vue index ae1c80ef8..f7af936c5 100644 --- a/dashboard/src/Index.vue +++ b/dashboard/src/Index.vue @@ -34,6 +34,7 @@ import EmailSettingsView from './views/EmailSettingsView.vue'; import EmailEventlogView from './views/EmailEventlogView.vue'; import EventlogView from './views/EventlogView.vue'; import NetworkView from './views/NetworkView.vue'; +import NotificationsView from './views/NotificationsView.vue'; import ProfileView from './views/ProfileView.vue'; import ServicesView from './views/ServicesView.vue'; import SystemSettingsView from './views/SystemSettingsView.vue'; @@ -64,6 +65,7 @@ const VIEWS = Object.freeze({ EMAIL_EVENTLOG: '#/email-eventlog', SERVER: '#/server', NETWORK: '#/network', + NOTIFICATIONS: '#/notifications', PROFILE: '#/profile', SERVICES: '#/services', SYSTEM_SETTINGS: '#/system-settings', @@ -319,6 +321,8 @@ function onHashChange() { view.value = VIEWS.EMAIL_EVENTLOG; } else if (v === VIEWS.SERVER && profile.value.isAtLeastAdmin) { view.value = VIEWS.SERVER; + } else if (v === VIEWS.NOTIFICATIONS && profile.value.isAtLeastAdmin) { + view.value = VIEWS.NOTIFICATIONS; } else if (v === VIEWS.NETWORK && profile.value.isAtLeastAdmin) { view.value = VIEWS.NETWORK; } else if (v === VIEWS.PROFILE) { @@ -481,6 +485,7 @@ onUnmounted(() => { + diff --git a/dashboard/src/components/Headerbar.vue b/dashboard/src/components/Headerbar.vue index 4928954b9..6bbe37a38 100644 --- a/dashboard/src/components/Headerbar.vue +++ b/dashboard/src/components/Headerbar.vue @@ -6,9 +6,7 @@ const t = i18n.t; import { onMounted, onUnmounted, ref, useTemplateRef, inject } from 'vue'; import { marked } from 'marked'; -import { eachLimit } from 'async'; -import { Menu, Button, Popover, Icon, InputDialog, Spinner } from '@cloudron/pankow'; -import { prettyDate, prettyLongDate } from '@cloudron/pankow/utils'; +import { Menu, Popover, Icon, InputDialog, Spinner } from '@cloudron/pankow'; import NotificationsModel from '../models/NotificationsModel.js'; import ServicesModel from '../models/ServicesModel.js'; import ProfileModel from '../models/ProfileModel.js'; @@ -28,47 +26,10 @@ const servicesModel = ServicesModel.create(); const profileModel = ProfileModel.create(); const notificationModel = NotificationsModel.create(); -const notificationButton = useTemplateRef('notificationButton'); -const notificationPopover = useTemplateRef('notificationPopover'); const notifications = ref([]); -const notificationsAllBusy = ref(false); - -function onOpenNotifications(popover, event, elem) { - popover.open(event, elem); - - // close after 2 seconds if there is nothing to show - if (notifications.value.length === 0) setTimeout(popover.close, 2000); -} - -async function onMarkNotificationRead(notification) { - notification.busy = true; - const [error] = await notificationModel.update(notification.id, true); - if (error) return console.error(error); - - await refresh(); - - // close after 2 seconds if there is nothing to show - if (notifications.value.length === 0) setTimeout(notificationPopover.value.close, 2000); -} - -async function onMarkAllNotificationRead() { - notificationsAllBusy.value = true; - - await eachLimit(notifications.value, 5, async (notification) => { - notification.busy = true; - const [error] = await notificationModel.update(notification.id, true); - if (error) return console.error(error); - }); - - await refresh(); - - notificationsAllBusy.value = false; - - if (notifications.value.length === 0) setTimeout(notificationPopover.value.close, 2000); -} async function refresh() { - const [error, result] = await notificationModel.list(); + const [error, result] = await notificationModel.list(false); if (error) return console.error(error); result.forEach(n => { @@ -150,30 +111,6 @@ onUnmounted(() => { - -
-
-
-
-
- {{ notification.title }}
- {{ prettyDate(notification.creationTime) }} -
- -
-
-
{{ JSON.stringify(notification.messageJson, null, 4) }}
-
-
-
-
- -
- {{ $t('notifications.allCaughtUp') }} -
-
-
-

{{ $t('support.help.title') }}

@@ -199,7 +136,7 @@ onUnmounted(() => {
Subscription Expired
-
{{ notifications.length > 99 ? '99+' : notifications.length }}
+ {{ notifications.length > 99 ? '99+' : notifications.length }}
{{ profile.username }} @@ -249,40 +186,6 @@ onUnmounted(() => { border-bottom: 1px solid var(--pankow-input-border-color); } -.notification-item { - margin-bottom: 10px; - padding-bottom: 10px; - cursor: pointer; - border-bottom: 1px solid var(--pankow-input-border-color); - position: relative; -} - -.notification-item-title { - display: flex; - justify-content: space-between; - align-items: center; - gap: 6px; -} - -.notification-item-date { - font-size: 10px; -} - -.notification-read-button { - visibility: hidden; - margin-right: 10px; -} - -.notification-item:hover .notification-read-button { - visibility: visible; -} - -@media (hover: none) { - .notification-item .notification-read-button { - visibility: visible; - } -} - .subscription-expired { background-color: var(--pankow-color-danger); color: white; diff --git a/dashboard/src/models/NotificationsModel.js b/dashboard/src/models/NotificationsModel.js index b9a01050e..e33480e31 100644 --- a/dashboard/src/models/NotificationsModel.js +++ b/dashboard/src/models/NotificationsModel.js @@ -6,10 +6,17 @@ function create() { const accessToken = localStorage.token; return { - async list(acknowledged = false) { + async list(acknowledged = null) { + const query = { + access_token: accessToken, + per_page: 1000 + }; + + if (acknowledged !== null) query.acknowledged = !!acknowledged; + let result; try { - result = await fetcher.get(`${API_ORIGIN}/api/v1/notifications`, { acknowledged, access_token: accessToken, per_page: 1000 }); + result = await fetcher.get(`${API_ORIGIN}/api/v1/notifications`, query); } catch (e) { return [e]; } diff --git a/dashboard/src/views/NotificationsView.vue b/dashboard/src/views/NotificationsView.vue new file mode 100644 index 000000000..758808a2c --- /dev/null +++ b/dashboard/src/views/NotificationsView.vue @@ -0,0 +1,160 @@ + + + + +