diff --git a/CHANGELOG.md b/CHANGELOG.md
index 14ece31cf..73de49911 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,7 @@
- [VM & Host] "Pool > Host" breadcrumb at the top of the page (PR [#3898](https://github.com/vatesfr/xen-orchestra/pull/3898))
- [Hosts] Ability to enable/disable host multipathing [#3659](https://github.com/vatesfr/xen-orchestra/issues/3659) (PR [#3865](https://github.com/vatesfr/xen-orchestra/pull/3865))
- [Login] Add OTP authentication [#2044](https://github.com/vatesfr/xen-orchestra/issues/2044) (PR [#3879](https://github.com/vatesfr/xen-orchestra/pull/3879))
+- [Notifications] New notification page to provide important information about XOA (PR [#3904](https://github.com/vatesfr/xen-orchestra/pull/3904))
### Bug fixes
@@ -28,6 +29,7 @@
### Released packages
+- xoa-updater v0.15.0
- xen-api v0.24.1
- xo-vmdk-to-vhd v0.1.6
- xo-server v5.34.0
diff --git a/packages/xo-web/src/common/intl/messages.js b/packages/xo-web/src/common/intl/messages.js
index 7531a1691..8468aaa62 100644
--- a/packages/xo-web/src/common/intl/messages.js
+++ b/packages/xo-web/src/common/intl/messages.js
@@ -14,6 +14,13 @@ const messages = {
errorNoSuchItem: 'no such item',
errorUnknownItem: 'unknown {type}',
memoryFree: '{memoryFree} RAM free',
+ date: 'Date',
+ notifications: 'Notifications',
+ noNotifications: 'No notifications so far.',
+ notificationNew: 'NEW!',
+ messageSubject: 'Subject',
+ messageFrom: 'From',
+ messageReply: 'Reply',
editableLongClickPlaceholder: 'Long click to edit',
editableClickPlaceholder: 'Click to edit',
@@ -74,6 +81,7 @@ const messages = {
xoaPage: 'XOA',
updatePage: 'Updates',
licensesPage: 'Licenses',
+ notificationsPage: 'Notifications',
settingsPage: 'Settings',
settingsServersPage: 'Servers',
settingsUsersPage: 'Users',
diff --git a/packages/xo-web/src/common/report-bug-button.js b/packages/xo-web/src/common/report-bug-button.js
index 36370a5fb..88f200109 100644
--- a/packages/xo-web/src/common/report-bug-button.js
+++ b/packages/xo-web/src/common/report-bug-button.js
@@ -8,9 +8,13 @@ import ActionRowButton from './action-row-button'
export const CAN_REPORT_BUG = __DEV__ && process.env.XOA_PLAN > 1
export const reportBug = ({ formatMessage, message, title }) => {
- const encodedTitle = encodeURIComponent(title)
+ const encodedTitle = encodeURIComponent(title == null ? '' : title)
const encodedMessage = encodeURIComponent(
- formatMessage !== undefined ? formatMessage(message) : message
+ message == null
+ ? ''
+ : formatMessage === undefined
+ ? message
+ : formatMessage(message)
)
window.open(
diff --git a/packages/xo-web/src/common/xo/index.js b/packages/xo-web/src/common/xo/index.js
index 7659933a9..190a65006 100644
--- a/packages/xo-web/src/common/xo/index.js
+++ b/packages/xo-web/src/common/xo/index.js
@@ -5,6 +5,7 @@ import pFinally from 'promise-toolbox/finally'
import React from 'react'
import reflect from 'promise-toolbox/reflect'
import tap from 'promise-toolbox/tap'
+import updater from 'xoa-updater'
import URL from 'url-parse'
import Xo from 'xo-lib'
import { createBackoff } from 'jsonrpc-websocket-client'
@@ -358,6 +359,55 @@ export const subscribeResourceCatalog = createSubscription(() =>
_call('cloud.getResourceCatalog')
)
+const getNotificationCookie = () => {
+ const notificationCookie = cookies.get(
+ `notifications:${store.getState().user.id}`
+ )
+ return notificationCookie === undefined ? {} : JSON.parse(notificationCookie)
+}
+
+const setNotificationCookie = (id, changes) => {
+ const notifications = getNotificationCookie()
+ notifications[id] = { ...(notifications[id] || {}), ...changes }
+ forEach(notifications[id], (value, key) => {
+ if (value === null) {
+ delete notifications[id][key]
+ }
+ })
+ cookies.set(
+ `notifications:${store.getState().user.id}`,
+ JSON.stringify(notifications)
+ )
+}
+
+export const dismissNotification = id => {
+ setNotificationCookie(id, { read: true, date: Date.now() })
+ subscribeNotifications.forceRefresh()
+}
+
+export const subscribeNotifications = createSubscription(async () => {
+ const { user, xoaUpdaterState } = store.getState()
+ if (
+ process.env.XOA_PLAN === 5 ||
+ xoaUpdaterState === 'disconnected' ||
+ xoaUpdaterState === 'error'
+ ) {
+ return []
+ }
+
+ const notifications = await updater._call('getMessages')
+ const notificationCookie = getNotificationCookie()
+ return map(
+ user != null && user.permission === 'admin'
+ ? notifications
+ : filter(notifications, { level: 'warning' }),
+ notification => ({
+ ...notification,
+ read: !!get(notificationCookie, `${notification.id}.read`),
+ })
+ )
+})
+
const checkSrCurrentStateSubscriptions = {}
export const subscribeCheckSrCurrentState = (pool, cb) => {
const poolId = resolveId(pool)
diff --git a/packages/xo-web/src/icons.scss b/packages/xo-web/src/icons.scss
index aba25b973..d564512a5 100644
--- a/packages/xo-web/src/icons.scss
+++ b/packages/xo-web/src/icons.scss
@@ -803,6 +803,10 @@
@extend .fa;
@extend .fa-file-text-o;
}
+ &-menu-notification {
+ @extend .fa;
+ @extend .fa-bell;
+ }
&-menu-settings {
@extend .fa;
@extend .fa-cog;
@@ -1048,6 +1052,14 @@
@extend .fa;
@extend .fa-support;
}
+ &-notification {
+ @extend .fa;
+ @extend .fa-bell;
+ }
+ &-reply {
+ @extend .fa;
+ @extend .fa-share;
+ }
// XOSAN related
diff --git a/packages/xo-web/src/xo-app/menu/index.js b/packages/xo-web/src/xo-app/menu/index.js
index 20bc7862d..fa6e28b84 100644
--- a/packages/xo-web/src/xo-app/menu/index.js
+++ b/packages/xo-web/src/xo-app/menu/index.js
@@ -8,6 +8,7 @@ import map from 'lodash/map'
import React from 'react'
import Tooltip from 'tooltip'
import { UpdateTag } from '../xoa/update'
+import { NotificationTag } from '../xoa/notifications'
import { addSubscriptions, connectStore, getXoaPlan, noop } from 'utils'
import {
connect,
@@ -242,14 +243,38 @@ export default class Menu extends Component {
},
],
},
- isAdmin && {
- to: 'xoa/update',
+ {
+ to: isAdmin ? 'xoa/update' : 'xoa/notifications',
icon: 'menu-xoa',
label: 'xoa',
- extra: