From: Aashish Sharma Date: Fri, 9 Jan 2026 08:11:26 +0000 (+0530) Subject: mgr/dashboard: Include executing tasks in notification panel X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=13dab758429a6400f69e4ba78df3ea7354903306;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 7236662e7cc..a9791bf2179 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 e9814713b45..ca261b8db3a 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 c7ddebbd465..ec1d8ec7e46 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 095524df5af..a7ef567b89f 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 7ea97ea5898..7cf0e76dd9b 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 a3ea809897c..902be1b1481 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); +}