From: Aashish Sharma Date: Fri, 9 Jan 2026 08:11:26 +0000 (+0530) Subject: mgr/dashboard: Include executing tasks in notification panel X-Git-Tag: v21.0.0~480^2 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=refs%2Fpull%2F66855%2Fhead;p=ceph.git mgr/dashboard: Include executing tasks in notification panel The new notification panel only includes the notifications currently and not the executing tasks. We need to include it in the panel as well Fixes: https://tracker.ceph.com/issues/74364 Signed-off-by: Aashish Sharma --- diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation.module.ts b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation.module.ts index 7236662e7cc1..a9791bf2179f 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation.module.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation.module.ts @@ -16,7 +16,8 @@ import { ToggleModule, ButtonModule, PlaceholderModule, - TagModule + TagModule, + ProgressBarModule } from 'carbon-components-angular'; import { AppRoutingModule } from '~/app/app-routing.module'; @@ -72,7 +73,8 @@ import { ModalCdsService } from '~/app/shared/services/modal-cds.service'; ToggleModule, ButtonModule, PlaceholderModule, - TagModule + TagModule, + ProgressBarModule ], declarations: [ AboutComponent, diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/notification-panel/notification-area/notification-area.component.html b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/notification-panel/notification-area/notification-area.component.html index e9814713b45a..ca261b8db3ac 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/notification-panel/notification-area/notification-area.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/notification-panel/notification-area/notification-area.component.html @@ -1,48 +1,116 @@ - +@if (executingTasks.length > 0) { +
+ Running tasks +
+ +@for (task of executingTasks; track task.begin_time) { +
+
+ + +
+
+ {{ task.description }} +
+ + + + +
+ + {{ task.begin_time | relativeDate }} + + + + {{ task.progress || 0 }} % + +
+
+
+ +
+
+} +} + +
- + +
-
{{ notification.timestamp | relativeDate }}
-
{{ notification.title }}
-
+
+ {{ notification.timestamp | relativeDate }} +
+
+ {{ notification.title }} +
+
+
-
- @if (!last) { + + @if (!last) {
- } + }
@if (todayNotifications.length > 0) { -
Today
- @for (notification of todayNotifications; track notification.timestamp; let last = $last) { - - } +
+ Today +
+ +@for (notification of todayNotifications; track notification.timestamp; let last = $last) { + + +} } @if (previousNotifications.length > 0) { -
Previous
- @for (notification of previousNotifications; track notification.timestamp; let last = $last) { - - } +
+ Previous +
+ +@for (notification of previousNotifications; track notification.timestamp; let last = $last) { + + +} } @if (todayNotifications.length === 0 && previousNotifications.length === 0) { -
-
No notifications
-
+
+
No notifications
+
} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/notification-panel/notification-area/notification-area.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/notification-panel/notification-area/notification-area.component.scss index c7ddebbd465f..ec1d8ec7e468 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/notification-panel/notification-area/notification-area.component.scss +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/notification-panel/notification-area/notification-area.component.scss @@ -1,3 +1,4 @@ +@use './src/styles/defaults' as *; @use '@carbon/styles/scss/theme' as *; @use '@carbon/styles/scss/spacing' as *; @use '@carbon/type'; @@ -15,6 +16,20 @@ display: block; } +.task-row { + display: flex; + align-items: center; + justify-content: space-between; + margin-top: var(--cds-spacing-02); +} + +.task-progress { + @include type.type-style('label-01'); + + color: var(--cds-text-secondary); + text-align: right; +} + .notification-timestamp { @include type.type-style('label-01'); @@ -111,3 +126,7 @@ overflow-y: auto; background-color: $layer-01; } + +:host ::ng-deep .infoCircle-icon { + fill: $primary !important; +} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/notification-panel/notification-area/notification-area.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/notification-panel/notification-area/notification-area.component.spec.ts index 095524df5af9..a7ef567b89fe 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/notification-panel/notification-area/notification-area.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/notification-panel/notification-area/notification-area.component.spec.ts @@ -9,6 +9,7 @@ import { CdNotification, CdNotificationConfig } from '../../../../shared/models/ import { NotificationType } from '../../../../shared/enum/notification-type.enum'; import { SharedModule } from '../../../../shared/shared.module'; import { configureTestBed } from '~/testing/unit-test-helper'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; describe('NotificationAreaComponent', () => { let component: NotificationAreaComponent; @@ -20,6 +21,10 @@ describe('NotificationAreaComponent', () => { const yesterday = new Date(today); yesterday.setDate(yesterday.getDate() - 1); + configureTestBed({ + imports: [HttpClientTestingModule] + }); + const createNotification = ( type: NotificationType, title: string, @@ -90,7 +95,7 @@ describe('NotificationAreaComponent', () => { }); it('should unsubscribe from notification service on destroy', () => { - const subSpy = spyOn(component['sub'], 'unsubscribe'); + const subSpy = spyOn(component['subs'], 'unsubscribe'); component.ngOnDestroy(); expect(subSpy).toHaveBeenCalled(); }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/notification-panel/notification-area/notification-area.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/notification-panel/notification-area/notification-area.component.ts index 7ea97ea5898d..7cf0e76dd9b8 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/notification-panel/notification-area/notification-area.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/notification-panel/notification-area/notification-area.component.ts @@ -1,8 +1,16 @@ -import { Component, OnInit, OnDestroy } from '@angular/core'; +import { Component, OnInit, OnDestroy, ChangeDetectorRef } from '@angular/core'; import { Subscription } from 'rxjs'; import { NotificationService } from '../../../../shared/services/notification.service'; import { CdNotification } from '../../../../shared/models/cd-notification'; import { NotificationType } from '../../../../shared/enum/notification-type.enum'; +import { SummaryService } from '~/app/shared/services/summary.service'; +import { Mutex } from 'async-mutex'; +import _ from 'lodash'; +import { FinishedTask } from '~/app/shared/models/finished-task'; +import moment from 'moment'; +import { ExecutingTask } from '~/app/shared/models/executing-task'; +import { TaskMessageService } from '~/app/shared/services/task-message.service'; +import { Icons } from '~/app/shared/enum/icons.enum'; @Component({ selector: 'cd-notification-area', @@ -13,7 +21,11 @@ import { NotificationType } from '../../../../shared/enum/notification-type.enum export class NotificationAreaComponent implements OnInit, OnDestroy { todayNotifications: CdNotification[] = []; previousNotifications: CdNotification[] = []; - private sub: Subscription; + private subs = new Subscription(); + last_task = ''; + mutex = new Mutex(); + icons = Icons; + executingTasks: ExecutingTask[] = []; readonly notificationIconMap = { [NotificationType.success]: 'success', @@ -23,32 +35,73 @@ export class NotificationAreaComponent implements OnInit, OnDestroy { default: 'infoCircle' } as const; - constructor(private notificationService: NotificationService) {} + constructor( + private notificationService: NotificationService, + private summaryService: SummaryService, + private cdRef: ChangeDetectorRef, + private taskMessageService: TaskMessageService + ) {} ngOnInit(): void { - this.sub = this.notificationService.data$.subscribe((notifications: CdNotification[]) => { - const today: Date = new Date(); - this.todayNotifications = []; - this.previousNotifications = []; - notifications.forEach((n: CdNotification) => { - const notifDate = new Date(n.timestamp); - if ( - notifDate.getDate() === today.getDate() && - notifDate.getMonth() === today.getMonth() && - notifDate.getFullYear() === today.getFullYear() - ) { - this.todayNotifications.push(n); - } else { - this.previousNotifications.push(n); - } - }); - }); + this.subs.add( + this.notificationService.data$.subscribe((notifications: CdNotification[]) => { + const today: Date = new Date(); + this.todayNotifications = []; + this.previousNotifications = []; + notifications.forEach((n: CdNotification) => { + const notifDate = new Date(n.timestamp); + if ( + notifDate.getDate() === today.getDate() && + notifDate.getMonth() === today.getMonth() && + notifDate.getFullYear() === today.getFullYear() + ) { + this.todayNotifications.push(n); + } else { + this.previousNotifications.push(n); + } + }); + }) + ); + + this.subs.add( + this.summaryService.subscribe((summary) => { + this._handleTasks(summary.executing_tasks); + + this.mutex.acquire().then((release) => { + _.filter( + summary.finished_tasks, + (task: FinishedTask) => !this.last_task || moment(task.end_time).isAfter(this.last_task) + ).forEach((task) => { + const config = this.notificationService.finishedTaskToNotification(task, task.success); + const notification = new CdNotification(config); + notification.timestamp = task.end_time; + notification.duration = task.duration; + + if (!this.last_task || moment(task.end_time).isAfter(this.last_task)) { + this.last_task = task.end_time; + window.localStorage.setItem('last_task', this.last_task); + } + + this.notificationService.save(notification); + }); + + this.cdRef.detectChanges(); + + release(); + }); + }) + ); } - ngOnDestroy(): void { - if (this.sub) { - this.sub.unsubscribe(); + _handleTasks(executingTasks: ExecutingTask[]) { + for (const executingTask of executingTasks) { + executingTask.description = this.taskMessageService.getRunningTitle(executingTask); } + this.executingTasks = executingTasks; + } + + ngOnDestroy(): void { + this.subs.unsubscribe(); } removeNotification(notification: CdNotification, event: MouseEvent) { diff --git a/src/pybind/mgr/dashboard/frontend/src/styles.scss b/src/pybind/mgr/dashboard/frontend/src/styles.scss index a3ea809897c1..902be1b14810 100644 --- a/src/pybind/mgr/dashboard/frontend/src/styles.scss +++ b/src/pybind/mgr/dashboard/frontend/src/styles.scss @@ -211,3 +211,11 @@ input:-webkit-autofill:active { -webkit-text-fill-color: inherit; transition: background-color 5000s ease-in-out 0s; } + +.cds--progress-bar__track { + background-color: colors.$gray-30; +} + +.cds--progress-bar__bar { + background-color: var(--cds-primary); +}