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: , + extra: ( + + {isAdmin && ( + + {' '} + + )} + + + ), subMenu: [ - { to: 'xoa/update', icon: 'menu-update', label: 'updatePage' }, - { to: 'xoa/licenses', icon: 'menu-license', label: 'licensesPage' }, + isAdmin && { + to: 'xoa/update', + icon: 'menu-update', + label: 'updatePage', + extra: , + }, + isAdmin && { + to: 'xoa/licenses', + icon: 'menu-license', + label: 'licensesPage', + }, + { + to: 'xoa/notifications', + icon: 'menu-notification', + label: 'notificationsPage', + extra: , + }, ], }, isAdmin && { @@ -535,7 +560,7 @@ const SubMenu = props => {
  • {' '} - {_(item.label)} + {_(item.label)} {item.extra}
  • ) diff --git a/packages/xo-web/src/xo-app/xoa/index.js b/packages/xo-web/src/xo-app/xoa/index.js index 41298dd2f..29ce400cf 100644 --- a/packages/xo-web/src/xo-app/xoa/index.js +++ b/packages/xo-web/src/xo-app/xoa/index.js @@ -8,6 +8,7 @@ import { NavLink, NavTabs } from 'nav' import Update from './update' import Licenses from './licenses' +import Notifications, { NotificationTag } from './notifications' const HEADER = ( @@ -25,6 +26,10 @@ const HEADER = ( {_('licensesPage')} + + {_('notificationsPage')}{' '} + + @@ -34,6 +39,7 @@ const HEADER = ( const Xoa = routes('xoa', { update: Update, licenses: Licenses, + notifications: Notifications, })(({ children }) => +process.env.XOA_PLAN === 5 ? ( diff --git a/packages/xo-web/src/xo-app/xoa/notifications/index.js b/packages/xo-web/src/xo-app/xoa/notifications/index.js new file mode 100644 index 000000000..9bffb8f1e --- /dev/null +++ b/packages/xo-web/src/xo-app/xoa/notifications/index.js @@ -0,0 +1,139 @@ +import _ from 'intl' +import classNames from 'classnames' +import decorate from 'apply-decorators' +import Icon from 'icon' +import marked from 'marked' +import NoObjects from 'no-objects' +import React from 'react' +import SortedTable from 'sorted-table' +import { addSubscriptions } from 'utils' +import { alert } from 'modal' +import { CAN_REPORT_BUG, reportBug } from 'report-bug-button' +import { filter, some } from 'lodash' +import { FormattedDate } from 'react-intl' +import { injectState, provideState } from 'reaclette' +import { subscribeNotifications, dismissNotification } from 'xo' + +const COLUMNS = [ + { + name: '', + itemRenderer: ({ level }) => + level === 'warning' && , + sortCriteria: 'level', + }, + { + default: true, + name: _('date'), + itemRenderer: ({ created, read }) => { + const Tag = read ? 'span' : 'strong' + return ( + + + + ) + }, + sortCriteria: 'created', + sortOrder: 'desc', + }, + { + name: _('messageFrom'), + itemRenderer: ({ read }) => { + const Tag = read ? 'span' : 'strong' + return XO Team + }, + sortCriteria: '', + }, + { + name: _('messageSubject'), + itemRenderer: ({ read, title }) => { + const Tag = read ? 'span' : 'strong' + return {title} + }, + sortCriteria: 'title', + }, + { + name: '', + itemRenderer: ({ id, read }) => + !read && {_('notificationNew')}, + sortCriteria: 'read', + }, +] + +const ACTIONS = [ + { + disabled: !CAN_REPORT_BUG, + label: _('messageReply'), + handler: notification => + reportBug({ + title: `Re: ${notification.title} (Ref: ${notification.id})`, + }), + icon: 'reply', + }, +] + +const Notifications = decorate([ + addSubscriptions({ + notifications: subscribeNotifications, + }), + provideState({ + effects: { + showMessage: (effects, notification) => () => + alert( + + {notification.title} + , +
    + ).then(() => dismissNotification(notification.id)), + }, + }), + injectState, + ({ notifications, effects }) => ( + + ), +]) +export { Notifications as default } + +export const NotificationTag = decorate([ + addSubscriptions({ + notifications: subscribeNotifications, + }), + provideState({ + computed: { + nNewNotifications: (_, { notifications }) => + filter(notifications, { read: false }).length, + someWarningNotifications: (_, { notifications }) => + some(notifications, { level: 'warning', read: false }), + }, + }), + injectState, + ({ state }) => + state.nNewNotifications > 0 ? ( + + {state.nNewNotifications} + + ) : null, +]) diff --git a/packages/xo-web/src/xo-app/xoa/update/index.js b/packages/xo-web/src/xo-app/xoa/update/index.js index f192ca4c1..a42024b8b 100644 --- a/packages/xo-web/src/xo-app/xoa/update/index.js +++ b/packages/xo-web/src/xo-app/xoa/update/index.js @@ -480,10 +480,10 @@ const COMPONENTS_BY_STATE = { upgradeNeeded: ( - + ), - upToDate: , + upToDate: null, } const TOOLTIPS_BY_STATE = { connected: _('waitingUpdateInfo'),