From 2b479e7b484ed400e4c4d15231e77fdaa0087faf Mon Sep 17 00:00:00 2001 From: Tiago Melo Date: Thu, 9 Jul 2020 17:36:59 +0000 Subject: [PATCH] mgr/dashboard: Display cluster status in favicon Fixes: https://tracker.ceph.com/issues/44542 Signed-off-by: Tiago Melo --- .../workbench-layout.component.ts | 8 +- .../src/app/shared/enum/color.enum.ts | 6 ++ .../src/app/shared/pipes/health-color.pipe.ts | 12 +-- .../shared/services/favicon.service.spec.ts | 22 ++++++ .../app/shared/services/favicon.service.ts | 74 +++++++++++++++++++ .../mgr/dashboard/frontend/src/index.html | 2 +- 6 files changed, 112 insertions(+), 12 deletions(-) create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/enum/color.enum.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/favicon.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/favicon.service.ts diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/layouts/workbench-layout/workbench-layout.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/core/layouts/workbench-layout/workbench-layout.component.ts index 5ad4535474a79..5305d2abae47d 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/core/layouts/workbench-layout/workbench-layout.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/layouts/workbench-layout/workbench-layout.component.ts @@ -3,13 +3,15 @@ import { Router } from '@angular/router'; import { Subscription } from 'rxjs'; +import { FaviconService } from '../../../shared/services/favicon.service'; import { SummaryService } from '../../../shared/services/summary.service'; import { TaskManagerService } from '../../../shared/services/task-manager.service'; @Component({ selector: 'cd-workbench-layout', templateUrl: './workbench-layout.component.html', - styleUrls: ['./workbench-layout.component.scss'] + styleUrls: ['./workbench-layout.component.scss'], + providers: [FaviconService] }) export class WorkbenchLayoutComponent implements OnInit, OnDestroy { private subs = new Subscription(); @@ -17,12 +19,14 @@ export class WorkbenchLayoutComponent implements OnInit, OnDestroy { constructor( private router: Router, private summaryService: SummaryService, - private taskManagerService: TaskManagerService + private taskManagerService: TaskManagerService, + private faviconService: FaviconService ) {} ngOnInit() { this.subs.add(this.summaryService.startPolling()); this.subs.add(this.taskManagerService.init(this.summaryService)); + this.faviconService.init(); } ngOnDestroy() { diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/enum/color.enum.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/enum/color.enum.ts new file mode 100644 index 0000000000000..0378827b2863b --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/enum/color.enum.ts @@ -0,0 +1,6 @@ +export enum Color { + // HEALTH + HEALTH_ERR = '#ff0000', + HEALTH_WARN = '#ffa500', + HEALTH_OK = '#00bb00' +} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/health-color.pipe.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/health-color.pipe.ts index 4b95022668c34..7da99de4b297b 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/health-color.pipe.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/health-color.pipe.ts @@ -1,18 +1,12 @@ import { Pipe, PipeTransform } from '@angular/core'; +import { Color } from '../enum/color.enum'; + @Pipe({ name: 'healthColor' }) export class HealthColorPipe implements PipeTransform { transform(value: any): any { - if (value === 'HEALTH_OK') { - return { color: '#00bb00' }; - } else if (value === 'HEALTH_WARN') { - return { color: '#ffa500' }; - } else if (value === 'HEALTH_ERR') { - return { color: '#ff0000' }; - } else { - return null; - } + return Color[value] ? { color: Color[value] } : null; } } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/services/favicon.service.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/favicon.service.spec.ts new file mode 100644 index 0000000000000..7c7184e5abbad --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/favicon.service.spec.ts @@ -0,0 +1,22 @@ +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { TestBed } from '@angular/core/testing'; + +import { configureTestBed } from '../../../testing/unit-test-helper'; +import { FaviconService } from './favicon.service'; + +describe('FaviconService', () => { + let service: FaviconService; + + configureTestBed({ + imports: [HttpClientTestingModule], + providers: [FaviconService] + }); + + beforeEach(() => { + service = TestBed.inject(FaviconService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/services/favicon.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/favicon.service.ts new file mode 100644 index 0000000000000..3ed43f690db02 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/favicon.service.ts @@ -0,0 +1,74 @@ +import { DOCUMENT } from '@angular/common'; +import { Inject, Injectable, OnDestroy } from '@angular/core'; + +import { Subscription } from 'rxjs'; + +import { Color } from '../enum/color.enum'; +import { SummaryService } from './summary.service'; + +@Injectable() +export class FaviconService implements OnDestroy { + sub: Subscription; + oldStatus: string; + url: string; + + constructor( + @Inject(DOCUMENT) private document: HTMLDocument, + private summaryService: SummaryService + ) {} + + init() { + this.url = this.document.getElementById('cdFavicon')?.getAttribute('href'); + + this.sub = this.summaryService.subscribe((summary) => { + this.changeIcon(summary.health_status); + }); + } + + changeIcon(status?: string) { + if (status === this.oldStatus) { + return; + } + + this.oldStatus = status; + + const favicon = this.document.getElementById('cdFavicon'); + const faviconSize = 16; + const radius = faviconSize / 4; + + const canvas = this.document.createElement('canvas'); + canvas.width = faviconSize; + canvas.height = faviconSize; + + const context = canvas.getContext('2d'); + const img = this.document.createElement('img'); + img.src = this.url; + + img.onload = () => { + // Draw Original Favicon as Background + context.drawImage(img, 0, 0, faviconSize, faviconSize); + + // Cut notification circle area + context.save(); + context.globalCompositeOperation = 'destination-out'; + context.beginPath(); + context.arc(canvas.width - radius, radius, radius + 1, 0, 2 * Math.PI); + context.fill(); + context.restore(); + + // Draw Notification Circle + context.beginPath(); + context.arc(canvas.width - radius, radius, radius, 0, 2 * Math.PI); + context.fillStyle = Color[status] || 'transparent'; + context.fill(); + + // Replace favicon + favicon.setAttribute('href', canvas.toDataURL('image/png')); + }; + } + + ngOnDestroy() { + this.changeIcon(); + this.sub?.unsubscribe(); + } +} diff --git a/src/pybind/mgr/dashboard/frontend/src/index.html b/src/pybind/mgr/dashboard/frontend/src/index.html index f314e2c53a8f5..bfd6d3ecd8373 100644 --- a/src/pybind/mgr/dashboard/frontend/src/index.html +++ b/src/pybind/mgr/dashboard/frontend/src/index.html @@ -9,7 +9,7 @@ - +