diff --git a/README.md b/README.md index 75064adcd..de4c01d1c 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,7 @@ The icons may not be reused in other projects without the proper flaticon licens --> ## Changelog ### **WORK IN PROGRESS** +* (foxriver76) new dialog for notifications introduced for non-system notifications (system notifications are still on hosts tab) * (foxriver76) the new `licenseInformation` icon now changes color correctly with the theme ### 6.14.1 (2024-02-20) diff --git a/packages/admin/src/src/App.jsx b/packages/admin/src/src/App.jsx index a6fa4e06f..0b6bcb2e9 100644 --- a/packages/admin/src/src/App.jsx +++ b/packages/admin/src/src/App.jsx @@ -42,6 +42,7 @@ import { CloudSync as SyncIcon, SyncDisabled as SyncIconDisabled, Close as CancelIcon, + Notifications as NotificationsIcon, } from '@mui/icons-material'; import { AdminConnection as Connection, PROGRESS } from '@iobroker/socket-client'; @@ -54,6 +55,7 @@ import { IconExpert, ToggleThemeMenu, } from '@iobroker/adapter-react-v5'; +import NotificationsDialog from '@/dialogs/NotificationsDialog'; import Utils from './components/Utils'; // adapter-react-v5/Components/Utils'; import CommandDialog from './dialogs/CommandDialog'; @@ -498,6 +500,12 @@ class App extends Router { triggerAdapterUpdate: 0, updating: false, // js controller updating + /** If the notifications dialog should be shown */ + notificationsDialog: false, + /** Notifications, excluding the system ones */ + notifications: {}, + /** Number of new notifications */ + noNotifications: 0, }; this.logsWorker = null; this.instancesWorker = null; @@ -921,6 +929,7 @@ class App extends Router { this.adaptersWorker.registerRepositoryHandler(this.repoChangeHandler); this.adaptersWorker.registerHandler(this.adaptersChangeHandler); this.hostsWorker.registerHandler(this.updateHosts); + this.hostsWorker.registerNotificationHandler(notifications => this.handleNewNotifications(notifications)); this.subscribeOnHostsStatus(); @@ -986,16 +995,17 @@ class App extends Router { ); setTimeout( - () => - this.hostsWorker - .getNotifications(newState.currentHost) - .then(notifications => - this.showAdaptersWarning( - notifications, - this.socket, - newState.currentHost, - )), - 3000, + async () => { + const notifications = await this.hostsWorker.getNotifications(newState.currentHost); + this.showAdaptersWarning( + notifications, + this.socket, + newState.currentHost, + ); + + this.handleNewNotifications(notifications); + }, + 3_000, ); }) .catch(error => { @@ -1250,6 +1260,25 @@ class App extends Router { }); }; + /** + * Render the notifications dialog + * @return {React.ReactNode} + */ + renderNotificationsDialog() { + if (!this.state.notificationsDialog) { + return null; + } + + return this.setState({ notificationsDialog: false })} + ackCallback={(host, name) => this.socket.clearNotifications(host, name)} + dateFormat={this.state.systemConfig.common.dateFormat} + themeType={this.state.themeType} + />; + } + renderHostWarningDialog() { if (!this.state.showHostWarning) { return null; @@ -1260,12 +1289,39 @@ class App extends Router { messages={this.state.showHostWarning.result.system.categories} dateFormat={this.state.systemConfig.common.dateFormat} themeType={this.state.themeType} - themeName={this.state.themeName} ackCallback={name => this.socket.clearNotifications(this.state.showHostWarning.host, name)} onClose={() => this.setState({ showHostWarning: null })} />; } + /** + * Called when notifications detected, updates the notifications indicator + * + * @param {Record} notifications + */ + async handleNewNotifications(notifications) { + // console.log(`new notifications: ${JSON.stringify(notifications)}`); + let noNotifications = 0; + + for (const hostDetails of Object.values(notifications)) { + for (const [scope, scopeDetails] of Object.entries(hostDetails.result)) { + if (scope === 'system') { + continue; + } + + for (const categoryDetails of Object.values(scopeDetails.categories)) { + for (const instanceDetails of Object.values(categoryDetails.instances)) { + noNotifications += instanceDetails.messages.length; + } + } + } + } + + const instances = await this.instancesWorker.getInstances(); + + this.setState({ noNotifications, notifications: { notifications, instances } }); + } + showAdaptersWarning = (notifications, socket, host) => { if (!notifications || !notifications[host] || !notifications[host].result) { return Promise.resolve(); @@ -1273,7 +1329,7 @@ class App extends Router { const result = notifications[host].result; - if (result && result.system && Object.keys(result.system.categories).length) { + if (result?.system && Object.keys(result.system.categories).length) { return this.instancesWorker.getInstances() .then(instances => this.setState({ showHostWarning: { host, instances, result } })); } @@ -2330,6 +2386,19 @@ class App extends Router { )} > + this.setState({ notificationsDialog: true })} + > + + + + + + { + async () => { this.logsWorkerChanged(host); (window._localStorage || window.localStorage).setItem( 'App.currentHost', host, ); - this.readRepoAndInstalledInfo(host, this.state.hosts).then( - () => - // read notifications from host - this.hostsWorker - .getNotifications(host) - .then(notifications => - this.showAdaptersWarning( - notifications, - this.socket, - host, - )), + await this.readRepoAndInstalledInfo(host, this.state.hosts); + // read notifications from host + const notifications = await this.hostsWorker.getNotifications(host); + this.showAdaptersWarning( + notifications, + this.socket, + host, ); }, ); @@ -2642,6 +2707,7 @@ class App extends Router { {this.renderSlowConnectionWarning()} {this.renderNewsDialog()} {this.renderHostWarningDialog()} + {this.renderNotificationsDialog()} {!this.state.connected && !this.state.redirectCountDown && !this.state.updating ? ( ) : null} diff --git a/packages/admin/src/src/Workers/HostsWorker.jsx b/packages/admin/src/src/Workers/HostsWorker.jsx index 3d4ebcc77..165f20167 100644 --- a/packages/admin/src/src/Workers/HostsWorker.jsx +++ b/packages/admin/src/src/Workers/HostsWorker.jsx @@ -174,7 +174,7 @@ class HostsWorker { this.notificationTimer = setTimeout(host_ => { this.notificationTimer = null; - this.notificationPromises[host_] = this._getNotificationsFromHots(host_, true); + this.notificationPromises[host_] = this._getNotificationsFromHosts(host_, true); this.notificationPromises[host_].then(notifications => this.notificationsHandlers.forEach(cb => cb(notifications))); @@ -182,14 +182,14 @@ class HostsWorker { } }; - _getNotificationsFromHots(hostId, update) { + _getNotificationsFromHosts(hostId, update) { if (!update && this.notificationPromises[hostId]) { return this.notificationPromises[hostId]; } this.notificationPromises[hostId] = this.socket.getState(`${hostId}.alive`) .then(state => { - if (state && state.val) { + if (state?.val) { return this.socket.getNotifications(hostId) .then(notifications => ({ [hostId]: notifications })) .catch(e => { @@ -205,12 +205,12 @@ class HostsWorker { getNotifications(hostId, update) { if (hostId) { - return this._getNotificationsFromHots(hostId, update); + return this._getNotificationsFromHosts(hostId, update); } return this.socket.getCompactHosts(update) .then(hosts => { const promises = hosts - .map(host => this._getNotificationsFromHots(host._id, update)); + .map(host => this._getNotificationsFromHosts(host._id, update)); return Promise.all(promises) .then(pResults => { diff --git a/packages/admin/src/src/dialogs/ExpertModeDialog.jsx b/packages/admin/src/src/dialogs/ExpertModeDialog.jsx index a96aed5cd..15f76db35 100644 --- a/packages/admin/src/src/dialogs/ExpertModeDialog.jsx +++ b/packages/admin/src/src/dialogs/ExpertModeDialog.jsx @@ -9,11 +9,11 @@ import { FormControlLabel, Checkbox, Grid, - DialogTitle, IconButton, Typography + DialogTitle, IconButton, Typography, } from '@mui/material'; import { Build as BuildIcon, - Check, + Check as CheckIcon, } from '@mui/icons-material'; import { I18n, IconExpert } from '@iobroker/adapter-react-v5'; diff --git a/packages/admin/src/src/dialogs/NotificationsDialog.tsx b/packages/admin/src/src/dialogs/NotificationsDialog.tsx new file mode 100644 index 000000000..23c9ff1d1 --- /dev/null +++ b/packages/admin/src/src/dialogs/NotificationsDialog.tsx @@ -0,0 +1,463 @@ +import React, { useState } from 'react'; + +import { + Button, + Dialog, + DialogActions, + DialogContent, + Accordion, AccordionDetails, AccordionSummary, + AppBar, Box, CardMedia, + Tab, Tabs, Typography, Tooltip, +} from '@mui/material'; +import { makeStyles } from '@mui/styles'; + +import { + Warning as WarningIcon, + Notifications as BellIcon, + Info as InfoIcon, + ExpandMore as ExpandMoreIcon, + Check as CheckIcon, + Close as CloseIcon, +} from '@mui/icons-material'; + +import { I18n, Utils } from '@iobroker/adapter-react-v5'; + +const useStyles = makeStyles(theme => ({ + root: { + // @ts-expect-error probably needs better types + backgroundColor: theme.palette.background.paper, + width: '100%', + height: 'auto', + display: 'flex', + borderRadius: 4, + flexDirection: 'column', + }, + paper: { + maxWidth: 1000, + width: '100%', + }, + flex: { + display: 'flex', + }, + overflowHidden: { + overflow: 'hidden', + }, + overflowAuto: { + overflowY: 'auto', + }, + pre: { + overflow: 'auto', + margin: 20, + '& p': { + fontSize: 18, + }, + }, + blockInfo: { + right: 20, + top: 10, + position: 'absolute', + display: 'flex', + alignItems: 'center', + color: 'silver', + }, + img: { + marginLeft: 10, + width: 45, + height: 45, + margin: 'auto 0', + position: 'relative', + '&:after': { + content: '""', + position: 'absolute', + zIndex: 2, + top: 0, + left: 0, + width: '100%', + height: '100%', + background: 'url("img/no-image.png") 100% 100% no-repeat', + backgroundSize: 'cover', + backgroundColor: '#fff', + }, + }, + message: { + justifyContent: 'space-between', + display: 'flex', + width: '100%', + alignItems: 'center', + }, + column: { + flexDirection: 'column', + }, + headerText: { + fontWeight: 'bold', + fontSize: 20, + // @ts-expect-error probably needs better types + color: theme.palette.mode === 'dark' ? '#DDD' : '#111', + }, + descriptionHeaderText: { + margin: '18px 0', + // @ts-expect-error probably needs better types + color: theme.palette.mode === 'dark' ? '#CCC' : '#222', + }, + silver: { + color: 'silver', + }, + button: { + paddingTop: 18, + paddingBottom: 5, + position: 'sticky', + bottom: 0, + background: 'white', + zIndex: 3, + }, + terminal: { + fontFamily: 'monospace', + fontSize: 14, + marginLeft: 20, + whiteSpace: 'pre-wrap', + }, + img2: { + width: 25, + height: 25, + marginRight: 10, + margin: 'auto 0', + position: 'relative', + '&:after': { + content: '""', + position: 'absolute', + zIndex: 2, + top: 0, + left: 0, + width: '100%', + height: '100%', + background: 'url("img/no-image.png") 100% 100% no-repeat', + backgroundSize: 'cover', + backgroundColor: '#fff', + }, + }, + heading: { + display: 'flex', + alignItems: 'center', + overflow: 'hidden', + }, + headingTop: { + display: 'flex', + alignItems: 'center', + }, + classNameBox: { + padding: 24, + }, + textStyle: { + whiteSpace: 'nowrap', + overflow: 'hidden', + textOverflow: 'ellipsis', + }, + content: { + overflow: 'hidden', + }, + buttonStyle: { + margin: 3, + }, + '@media screen and (max-width: 550px)': { + classNameBox: { + padding: 10, + }, + message: { + flexWrap: 'wrap', + }, + textStyle: { + fontSize: '2.9vw', + }, + terminal: { + fontSize: '2.9vw', + marginLeft: 0, + }, + silver: { + fontSize: '2.9vw', + }, + buttonStyle: { + fontSize: '2.9vw', + }, + }, +})); + +/** Possible message severities */ +type Severity = 'notify' | 'info' | 'alert'; + +interface StatusOptions { + /** Severity of the message */ + severity?: Severity; + /** If dark mode enabled */ + isDark: boolean; +} + +const Status = ({ severity, isDark, ...props }: StatusOptions) => { + if (severity === 'notify') { + return ; + } + + if (severity === 'info') { + return ; + } + + return ; +}; + +const a11yProps = (index: number) => ({ + id: `scrollable-force-tab-${index}`, + 'aria-controls': `scrollable-force-tabpanel-${index}`, +}); + +interface TabPanelOptions { + value: number; + index: number; + classNameBox: string; + children: React.JSX.Element; +} + +const TabPanel = ({ + children, value, index, classNameBox, ...other +}: TabPanelOptions) => ; + +type Translated = Record; + +interface InstanceMessage { + messages: { + message: string; + ts: number; + }[]; +} + +interface Message { + name: Translated; + severity: Severity; + description: Translated; + instances: Record; +} + +interface NotificationDialogOptions { + notifications: { + [host: string]: { + result: { + [scope: string]: { + categories: { + [category: string]: Message; + };}; + };}; + }; + onClose: () => void; + ackCallback: (host: string, name: string) => void; + dateFormat: string; + themeType: string; + instances: Record; +} + +interface MessagesPerScope { + [scope: string]: Record; +} + +const NotificationsDialog = ({ + notifications, onClose, ackCallback, dateFormat, themeType, instances, +}: NotificationDialogOptions) => { + const classes = useStyles(); + + const notificationManagerInstalled = !!Object.values(instances).find(instance => instance.common.name === 'notification-manager'); + + const messages: MessagesPerScope = {}; + + for (const [host, hostDetails] of Object.entries(notifications)) { + for (const [scope, scopeDetails] of Object.entries(hostDetails.result)) { + if (scope === 'system') { + continue; + } + + for (const [category, categoryDetails] of Object.entries(scopeDetails.categories)) { + messages[scope] = messages[scope] || {}; + messages[scope][category] = { ...categoryDetails, host }; + } + } + } + + const [value, setValue] = useState(0); + const [disabled, setDisabled] = useState([]); + const [expanded, setExpanded] = useState(''); + const [autoCollapse, setAutoCollapse] = useState(true); + + const handleChange = (event: unknown, newValue: number) => { + setAutoCollapse(true); + setValue(newValue); + setExpanded(''); + }; + + const handleChangeAccordion = (panel: string) => (_event: unknown, isExpanded: boolean) => + setExpanded(isExpanded ? panel : ''); + + const black = themeType === 'dark'; + + return onClose()} + open + classes={{ paper: classes.paper }} + > +

+ + {I18n.t('Notifications')} + + {!notificationManagerInstalled ? + + : null} +

+ +
+ + + {Object.values(messages).map(categoryEntry => Object.entries(categoryEntry).map(([name, entry], idx) => } + {...a11yProps(idx)} + />))} + + + {Object.keys(messages).map(scope => <> + {/** @ts-expect-error seems to work with multiple children */} + {Object.keys(messages[scope]).map((name, idx) => +
+ {messages[scope][name].name[I18n.getLanguage()]} +
+
+ {messages[scope][name].description[I18n.getLanguage()]} +
+
+ {messages[scope][name].instances ? Object.keys(messages[scope][name].instances).map(nameInst => { + const index = Object.keys(messages[scope]).indexOf(name); + + if (autoCollapse && value === index) { + handleChangeAccordion(`${name}-${nameInst}`)('', true); + setAutoCollapse(false); + } + + const currentInstance = instances && instances[nameInst]; + let icon = 'img/no-image.png'; + if (currentInstance?.common?.icon && currentInstance?.common?.name) { + icon = `adapter/${currentInstance.common.name}/${currentInstance.common.icon}`; + } + return + } + classes={{ content: classes.content }} + aria-controls="panel1bh-content" + id="panel1bh-header" + > + + +
+ {nameInst.replace(/^system\.adapter\./, '')} +
+
+
+ + {messages[scope][name].instances[nameInst].messages.map(msg => + +
{msg.message}
+
{Utils.formatDate(new Date(msg.ts), dateFormat)}
+
)} +
+
; + }) : null} +
+
+ + {Object.keys(messages[scope]).length === 1 && } +
+
)} + )} +
+
+ + + +
; +}; + +export default NotificationsDialog; diff --git a/packages/admin/src/src/i18n/de.json b/packages/admin/src/src/i18n/de.json index 2e1fb2062..785d5180f 100644 --- a/packages/admin/src/src/i18n/de.json +++ b/packages/admin/src/src/i18n/de.json @@ -726,6 +726,7 @@ "Not agree": "Nicht zustimmen", "Not exists": "Existiert nicht", "Note:": "Hinweis", + "Notifications": "Benachrichtigungen", "Nov": "Nov", "November": "November", "Now the expert mode will be active only during this browser session.": "Der Expertenmodus ist momentan nur für diese Browsersitzung aktiviert.", @@ -1041,6 +1042,7 @@ "Time From": "Zeit von", "Time To": "Zeit zum", "Time stamp": "Zeitstempel", + "Tip: Use the \"notification-manager\" adapter to receive notifications automatically via messaging adapters.": "Tipp: Verwenden Sie den „notification-manager“-Adapter, um Benachrichtigungen automatisch über Messaging-Adapter zu empfangen.", "Title": "Titel", "To": "Bis", "Today": "Heute", diff --git a/packages/admin/src/src/i18n/en.json b/packages/admin/src/src/i18n/en.json index 20dc86eb0..1e33bce91 100644 --- a/packages/admin/src/src/i18n/en.json +++ b/packages/admin/src/src/i18n/en.json @@ -726,6 +726,7 @@ "Not agree": "Not agree", "Not exists": "Does not exist", "Note:": "Note", + "Notifications": "Notifications", "Nov": "Nov", "November": "November", "Now the expert mode will be active only during this browser session.": "Now the expert mode will be active only during this browser session.", @@ -1041,6 +1042,7 @@ "Time From": "Time From", "Time To": "Time To", "Time stamp": "Time stamp", + "Tip: Use the \"notification-manager\" adapter to receive notifications automatically via messaging adapters.": "Tip: Use the \"notification-manager\" adapter to receive notifications automatically via messaging adapters.", "Title": "Title", "To": "To", "Today": "Today", diff --git a/packages/admin/src/src/i18n/es.json b/packages/admin/src/src/i18n/es.json index 8866f1df0..ab4f41526 100644 --- a/packages/admin/src/src/i18n/es.json +++ b/packages/admin/src/src/i18n/es.json @@ -726,6 +726,7 @@ "Not agree": "En desacuerdo", "Not exists": "No existe", "Note:": "Nota:", + "Notifications": "Notificaciones", "Nov": "Nov", "November": "Noviembre", "Now the expert mode will be active only during this browser session.": "Ahora, el modo experto estará activo solo durante esta sesión de navegador.", @@ -1041,6 +1042,7 @@ "Time From": "Tiempo desde", "Time To": "Hora de", "Time stamp": "Timestamp", + "Tip: Use the \"notification-manager\" adapter to receive notifications automatically via messaging adapters.": "Consejo: utilice el adaptador \"administrador de notificaciones\" para recibir notificaciones automáticamente a través de adaptadores de mensajería.", "Title": "Título", "To": "Para", "Today": "hoy", diff --git a/packages/admin/src/src/i18n/fr.json b/packages/admin/src/src/i18n/fr.json index a563918a5..078f2542c 100644 --- a/packages/admin/src/src/i18n/fr.json +++ b/packages/admin/src/src/i18n/fr.json @@ -726,6 +726,7 @@ "Not agree": "Pas d'accord", "Not exists": "N'existe pas", "Note:": "Remarque", + "Notifications": "Notifications", "Nov": "nov.", "November": "novembre", "Now the expert mode will be active only during this browser session.": "Désormais, le mode expert ne sera actif que pendant cette session du navigateur.", @@ -1041,6 +1042,7 @@ "Time From": "Temps de", "Time To": "Temps de", "Time stamp": "Horodatage", + "Tip: Use the \"notification-manager\" adapter to receive notifications automatically via messaging adapters.": "Astuce : Utilisez l'adaptateur \"notification-manager\" pour recevoir automatiquement des notifications via des adaptateurs de messagerie.", "Title": "Titre", "To": "À", "Today": "aujourd'hui", diff --git a/packages/admin/src/src/i18n/it.json b/packages/admin/src/src/i18n/it.json index b19564320..21cc00cb0 100644 --- a/packages/admin/src/src/i18n/it.json +++ b/packages/admin/src/src/i18n/it.json @@ -726,6 +726,7 @@ "Not agree": "Non sono d'accordo", "Not exists": "Inesistente", "Note:": "Nota:", + "Notifications": "Notifiche", "Nov": "nov", "November": "novembre", "Now the expert mode will be active only during this browser session.": "Ora la modalità esperto sarà attiva solo durante questa sessione del browser.", @@ -1041,6 +1042,7 @@ "Time From": "Orario da", "Time To": "Orario per", "Time stamp": "Marca temporale", + "Tip: Use the \"notification-manager\" adapter to receive notifications automatically via messaging adapters.": "Suggerimento: utilizzare l'adattatore \"notification-manager\" per ricevere automaticamente le notifiche tramite gli adattatori di messaggistica.", "Title": "Titolo", "To": "A", "Today": "Oggi", diff --git a/packages/admin/src/src/i18n/nl.json b/packages/admin/src/src/i18n/nl.json index 618c9387a..833b89315 100644 --- a/packages/admin/src/src/i18n/nl.json +++ b/packages/admin/src/src/i18n/nl.json @@ -726,6 +726,7 @@ "Not agree": "Oneens", "Not exists": "Bestaat niet", "Note:": "Notitie:", + "Notifications": "Meldingen", "Nov": "november", "November": "november", "Now the expert mode will be active only during this browser session.": "Nu is de expertmodus alleen actief tijdens deze browsersessie.", @@ -1041,6 +1042,7 @@ "Time From": "Tijd vanaf", "Time To": "Tijd om", "Time stamp": "Tijdstempel", + "Tip: Use the \"notification-manager\" adapter to receive notifications automatically via messaging adapters.": "Tip: Gebruik de \"notification-manager\"-adapter om automatisch meldingen te ontvangen via berichtenadapters.", "Title": "Titel", "To": "Naar", "Today": "vandaag", diff --git a/packages/admin/src/src/i18n/pl.json b/packages/admin/src/src/i18n/pl.json index 807a455da..6e25b0c74 100644 --- a/packages/admin/src/src/i18n/pl.json +++ b/packages/admin/src/src/i18n/pl.json @@ -726,6 +726,7 @@ "Not agree": "Nie zgadzam się", "Not exists": "Nie istnieje", "Note:": "Uwaga:", + "Notifications": "Powiadomienia", "Nov": "List", "November": "listopad", "Now the expert mode will be active only during this browser session.": "Teraz tryb eksperta będzie aktywny tylko podczas tej sesji przeglądarki.", @@ -1041,6 +1042,7 @@ "Time From": "Czas od", "Time To": "Czas do", "Time stamp": "Znak czasu", + "Tip: Use the \"notification-manager\" adapter to receive notifications automatically via messaging adapters.": "Wskazówka: użyj adaptera „menedżer powiadomień”, aby automatycznie otrzymywać powiadomienia za pośrednictwem adapterów do przesyłania wiadomości.", "Title": "Tytuł", "To": "Do", "Today": "Dzisiaj", diff --git a/packages/admin/src/src/i18n/pt.json b/packages/admin/src/src/i18n/pt.json index 1d1699ca5..8405b8fab 100644 --- a/packages/admin/src/src/i18n/pt.json +++ b/packages/admin/src/src/i18n/pt.json @@ -726,6 +726,7 @@ "Not agree": "Não concordo", "Not exists": "Não existe", "Note:": "Nota:", + "Notifications": "Notificações", "Nov": "nov", "November": "novembro", "Now the expert mode will be active only during this browser session.": "Agora, o modo especialista estará ativo apenas durante esta sessão do navegador.", @@ -1041,6 +1042,7 @@ "Time From": "Tempo de", "Time To": "Hora de", "Time stamp": "Timestamp", + "Tip: Use the \"notification-manager\" adapter to receive notifications automatically via messaging adapters.": "Dica: Use o adaptador \"notification-manager\" para receber notificações automaticamente por meio de adaptadores de mensagens.", "Title": "Título", "To": "Para", "Today": "hoje", diff --git a/packages/admin/src/src/i18n/ru.json b/packages/admin/src/src/i18n/ru.json index 639f90816..98a35dab2 100644 --- a/packages/admin/src/src/i18n/ru.json +++ b/packages/admin/src/src/i18n/ru.json @@ -726,6 +726,7 @@ "Not agree": "Не принимаю", "Not exists": "Не существует", "Note:": "Замечание:", + "Notifications": "Уведомления", "Nov": "ноя", "November": "ноябрь", "Now the expert mode will be active only during this browser session.": "Теперь экспертный режим будет активен только во время этого сеанса браузера.", @@ -1041,6 +1042,7 @@ "Time From": "Время от", "Time To": "Время до", "Time stamp": "Время", + "Tip: Use the \"notification-manager\" adapter to receive notifications automatically via messaging adapters.": "Совет. Используйте адаптер «менеджер уведомлений» для автоматического получения уведомлений через адаптеры обмена сообщениями.", "Title": "Название", "To": "до", "Today": "сегодня", diff --git a/packages/admin/src/src/i18n/uk.json b/packages/admin/src/src/i18n/uk.json index 6401fd0c4..8e1d41032 100644 --- a/packages/admin/src/src/i18n/uk.json +++ b/packages/admin/src/src/i18n/uk.json @@ -726,6 +726,7 @@ "Not agree": "Не згоден", "Not exists": "Не існує", "Note:": "Примітка", + "Notifications": "Сповіщення", "Nov": "Листопад", "November": "Листопад", "Now the expert mode will be active only during this browser session.": "Тепер експертний режим буде активний лише протягом цього сеансу браузера.", @@ -1041,6 +1042,7 @@ "Time From": "Час від", "Time To": "Час до", "Time stamp": "Позначка часу", + "Tip: Use the \"notification-manager\" adapter to receive notifications automatically via messaging adapters.": "Порада. Використовуйте адаптер «notification-manager», щоб автоматично отримувати сповіщення через адаптери обміну повідомленнями.", "Title": "Назва", "To": "до", "Today": "Сьогодні", diff --git a/packages/admin/src/src/i18n/zh-cn.json b/packages/admin/src/src/i18n/zh-cn.json index b6ae8320c..7952489a8 100644 --- a/packages/admin/src/src/i18n/zh-cn.json +++ b/packages/admin/src/src/i18n/zh-cn.json @@ -726,6 +726,7 @@ "Not agree": "不同意", "Not exists": "不存在", "Note:": "注意", + "Notifications": "通知", "Nov": "十一月", "November": "十一月", "Now the expert mode will be active only during this browser session.": "现在,专家模式将仅在此浏览器会话期间处于活动状态。", @@ -1041,6 +1042,7 @@ "Time From": "时间从", "Time To": "时间到", "Time stamp": "时间戳", + "Tip: Use the \"notification-manager\" adapter to receive notifications automatically via messaging adapters.": "提示:使用“通知管理器”适配器通过消息传递适配器自动接收通知。", "Title": "标题", "To": "至", "Today": "今天",