]> git.proxmox.com Git - ceph.git/blob - ceph/src/pybind/mgr/dashboard/frontend/src/app/shared/services/notification.service.ts
c05dbce0f571fb7ea173f64e095c84407a632d8c
[ceph.git] / ceph / src / pybind / mgr / dashboard / frontend / src / app / shared / services / notification.service.ts
1 import { Injectable } from '@angular/core';
2
3 import _ from 'lodash';
4 import { IndividualConfig, ToastrService } from 'ngx-toastr';
5 import { BehaviorSubject, Subject } from 'rxjs';
6
7 import { NotificationType } from '../enum/notification-type.enum';
8 import { CdNotification, CdNotificationConfig } from '../models/cd-notification';
9 import { FinishedTask } from '../models/finished-task';
10 import { CdDatePipe } from '../pipes/cd-date.pipe';
11 import { TaskMessageService } from './task-message.service';
12
13 @Injectable({
14 providedIn: 'root'
15 })
16 export class NotificationService {
17 private hideToasties = false;
18
19 // Data observable
20 private dataSource = new BehaviorSubject<CdNotification[]>([]);
21 data$ = this.dataSource.asObservable();
22
23 // Sidebar observable
24 sidebarSubject = new Subject();
25
26 private queued: CdNotificationConfig[] = [];
27 private queuedTimeoutId: number;
28 KEY = 'cdNotifications';
29
30 constructor(
31 public toastr: ToastrService,
32 private taskMessageService: TaskMessageService,
33 private cdDatePipe: CdDatePipe
34 ) {
35 const stringNotifications = localStorage.getItem(this.KEY);
36 let notifications: CdNotification[] = [];
37
38 if (_.isString(stringNotifications)) {
39 notifications = JSON.parse(stringNotifications, (_key, value) => {
40 if (_.isPlainObject(value)) {
41 return _.assign(new CdNotification(), value);
42 }
43 return value;
44 });
45 }
46
47 this.dataSource.next(notifications);
48 }
49
50 /**
51 * Removes all current saved notifications
52 */
53 removeAll() {
54 localStorage.removeItem(this.KEY);
55 this.dataSource.next([]);
56 }
57
58 /**
59 * Removes a single saved notifications
60 */
61 remove(index: number) {
62 const recent = this.dataSource.getValue();
63 recent.splice(index, 1);
64 this.dataSource.next(recent);
65 localStorage.setItem(this.KEY, JSON.stringify(recent));
66 }
67
68 /**
69 * Method used for saving a shown notification (check show() method).
70 */
71 save(notification: CdNotification) {
72 const recent = this.dataSource.getValue();
73 recent.push(notification);
74 recent.sort((a, b) => (a.timestamp > b.timestamp ? -1 : 1));
75 while (recent.length > 10) {
76 recent.pop();
77 }
78 this.dataSource.next(recent);
79 localStorage.setItem(this.KEY, JSON.stringify(recent));
80 }
81
82 /**
83 * Method for showing a notification.
84 * @param {NotificationType} type toastr type
85 * @param {string} title
86 * @param {string} [message] The message to be displayed. Note, use this field
87 * for error notifications only.
88 * @param {*} [options] toastr compatible options, used when creating a toastr
89 * @param {string} [application] Only needed if notification comes from an external application
90 * @returns The timeout ID that is set to be able to cancel the notification.
91 */
92 show(
93 type: NotificationType,
94 title: string,
95 message?: string,
96 options?: any | IndividualConfig,
97 application?: string
98 ): number;
99 show(config: CdNotificationConfig | (() => CdNotificationConfig)): number;
100 show(
101 arg: NotificationType | CdNotificationConfig | (() => CdNotificationConfig),
102 title?: string,
103 message?: string,
104 options?: any | IndividualConfig,
105 application?: string
106 ): number {
107 return window.setTimeout(() => {
108 let config: CdNotificationConfig;
109 if (_.isFunction(arg)) {
110 config = arg() as CdNotificationConfig;
111 } else if (_.isObject(arg)) {
112 config = arg as CdNotificationConfig;
113 } else {
114 config = new CdNotificationConfig(
115 arg as NotificationType,
116 title,
117 message,
118 options,
119 application
120 );
121 }
122 this.queueToShow(config);
123 }, 10);
124 }
125
126 private queueToShow(config: CdNotificationConfig) {
127 this.cancel(this.queuedTimeoutId);
128 if (!this.queued.find((c) => _.isEqual(c, config))) {
129 this.queued.push(config);
130 }
131 this.queuedTimeoutId = window.setTimeout(() => {
132 this.showQueued();
133 }, 500);
134 }
135
136 private showQueued() {
137 this.getUnifiedTitleQueue().forEach((config) => {
138 const notification = new CdNotification(config);
139
140 if (!notification.isFinishedTask) {
141 this.save(notification);
142 }
143 this.showToasty(notification);
144 });
145 }
146
147 private getUnifiedTitleQueue(): CdNotificationConfig[] {
148 return Object.values(this.queueShiftByTitle()).map((configs) => {
149 const config = configs[0];
150 if (configs.length > 1) {
151 config.message = '<ul>' + configs.map((c) => `<li>${c.message}</li>`).join('') + '</ul>';
152 }
153 return config;
154 });
155 }
156
157 private queueShiftByTitle(): { [key: string]: CdNotificationConfig[] } {
158 const byTitle: { [key: string]: CdNotificationConfig[] } = {};
159 let config: CdNotificationConfig;
160 while ((config = this.queued.shift())) {
161 if (!byTitle[config.title]) {
162 byTitle[config.title] = [];
163 }
164 byTitle[config.title].push(config);
165 }
166 return byTitle;
167 }
168
169 private showToasty(notification: CdNotification) {
170 // Exit immediately if no toasty should be displayed.
171 if (this.hideToasties) {
172 return;
173 }
174 this.toastr[['error', 'info', 'success'][notification.type]](
175 (notification.message ? notification.message + '<br>' : '') +
176 this.renderTimeAndApplicationHtml(notification),
177 notification.title,
178 notification.options
179 );
180 }
181
182 renderTimeAndApplicationHtml(notification: CdNotification): string {
183 return `<small class="date">${this.cdDatePipe.transform(
184 notification.timestamp
185 )}</small><i class="float-right custom-icon ${notification.applicationClass}" title="${
186 notification.application
187 }"></i>`;
188 }
189
190 notifyTask(finishedTask: FinishedTask, success: boolean = true): number {
191 const notification = this.finishedTaskToNotification(finishedTask, success);
192 notification.isFinishedTask = true;
193 return this.show(notification);
194 }
195
196 finishedTaskToNotification(
197 finishedTask: FinishedTask,
198 success: boolean = true
199 ): CdNotificationConfig {
200 let notification: CdNotificationConfig;
201 if (finishedTask.success && success) {
202 notification = new CdNotificationConfig(
203 NotificationType.success,
204 this.taskMessageService.getSuccessTitle(finishedTask)
205 );
206 } else {
207 notification = new CdNotificationConfig(
208 NotificationType.error,
209 this.taskMessageService.getErrorTitle(finishedTask),
210 this.taskMessageService.getErrorMessage(finishedTask)
211 );
212 }
213 notification.isFinishedTask = true;
214
215 return notification;
216 }
217
218 /**
219 * Prevent the notification from being shown.
220 * @param {number} timeoutId A number representing the ID of the timeout to be canceled.
221 */
222 cancel(timeoutId: number) {
223 window.clearTimeout(timeoutId);
224 }
225
226 /**
227 * Suspend showing the notification toasties.
228 * @param {boolean} suspend Set to ``true`` to disable/hide toasties.
229 */
230 suspendToasties(suspend: boolean) {
231 this.hideToasties = suspend;
232 }
233
234 toggleSidebar(forceClose = false) {
235 this.sidebarSubject.next(forceClose);
236 }
237 }