summaryrefslogtreecommitdiffstats
path: root/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/service-daemon-list/service-daemon-list.component.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/service-daemon-list/service-daemon-list.component.ts')
-rw-r--r--src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/service-daemon-list/service-daemon-list.component.ts347
1 files changed, 347 insertions, 0 deletions
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/service-daemon-list/service-daemon-list.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/service-daemon-list/service-daemon-list.component.ts
new file mode 100644
index 000000000..d1c2f9cc3
--- /dev/null
+++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/service-daemon-list/service-daemon-list.component.ts
@@ -0,0 +1,347 @@
+import {
+ AfterViewInit,
+ Component,
+ Input,
+ OnChanges,
+ OnDestroy,
+ OnInit,
+ QueryList,
+ TemplateRef,
+ ViewChild,
+ ViewChildren
+} from '@angular/core';
+
+import _ from 'lodash';
+import { Observable, Subscription } from 'rxjs';
+import { take } from 'rxjs/operators';
+
+import { CephServiceService } from '~/app/shared/api/ceph-service.service';
+import { DaemonService } from '~/app/shared/api/daemon.service';
+import { HostService } from '~/app/shared/api/host.service';
+import { OrchestratorService } from '~/app/shared/api/orchestrator.service';
+import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
+import { TableComponent } from '~/app/shared/datatable/table/table.component';
+import { Icons } from '~/app/shared/enum/icons.enum';
+import { NotificationType } from '~/app/shared/enum/notification-type.enum';
+import { CdTableAction } from '~/app/shared/models/cd-table-action';
+import { CdTableColumn } from '~/app/shared/models/cd-table-column';
+import { CdTableFetchDataContext } from '~/app/shared/models/cd-table-fetch-data-context';
+import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
+import { Daemon } from '~/app/shared/models/daemon.interface';
+import { Permissions } from '~/app/shared/models/permissions';
+import { CephServiceSpec } from '~/app/shared/models/service.interface';
+import { DimlessBinaryPipe } from '~/app/shared/pipes/dimless-binary.pipe';
+import { RelativeDatePipe } from '~/app/shared/pipes/relative-date.pipe';
+import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
+import { NotificationService } from '~/app/shared/services/notification.service';
+
+@Component({
+ selector: 'cd-service-daemon-list',
+ templateUrl: './service-daemon-list.component.html',
+ styleUrls: ['./service-daemon-list.component.scss']
+})
+export class ServiceDaemonListComponent implements OnInit, OnChanges, AfterViewInit, OnDestroy {
+ @ViewChild('statusTpl', { static: true })
+ statusTpl: TemplateRef<any>;
+
+ @ViewChild('listTpl', { static: true })
+ listTpl: TemplateRef<any>;
+
+ @ViewChild('cpuTpl', { static: true })
+ cpuTpl: TemplateRef<any>;
+
+ @ViewChildren('daemonsTable')
+ daemonsTableTpls: QueryList<TemplateRef<TableComponent>>;
+
+ @Input()
+ serviceName?: string;
+
+ @Input()
+ hostname?: string;
+
+ @Input()
+ hiddenColumns: string[] = [];
+
+ @Input()
+ flag?: string;
+
+ total = 100;
+
+ warningThreshold = 0.8;
+
+ errorThreshold = 0.9;
+
+ icons = Icons;
+
+ daemons: Daemon[] = [];
+ services: Array<CephServiceSpec> = [];
+ columns: CdTableColumn[] = [];
+ serviceColumns: CdTableColumn[] = [];
+ tableActions: CdTableAction[];
+ selection = new CdTableSelection();
+ permissions: Permissions;
+
+ hasOrchestrator = false;
+ showDocPanel = false;
+
+ private daemonsTable: TableComponent;
+ private daemonsTableTplsSub: Subscription;
+ private serviceSub: Subscription;
+
+ constructor(
+ private hostService: HostService,
+ private cephServiceService: CephServiceService,
+ private orchService: OrchestratorService,
+ private relativeDatePipe: RelativeDatePipe,
+ private dimlessBinary: DimlessBinaryPipe,
+ public actionLabels: ActionLabelsI18n,
+ private authStorageService: AuthStorageService,
+ private daemonService: DaemonService,
+ private notificationService: NotificationService
+ ) {}
+
+ ngOnInit() {
+ this.permissions = this.authStorageService.getPermissions();
+ this.tableActions = [
+ {
+ permission: 'update',
+ icon: Icons.start,
+ click: () => this.daemonAction('start'),
+ name: this.actionLabels.START,
+ disable: () => this.actionDisabled('start')
+ },
+ {
+ permission: 'update',
+ icon: Icons.stop,
+ click: () => this.daemonAction('stop'),
+ name: this.actionLabels.STOP,
+ disable: () => this.actionDisabled('stop')
+ },
+ {
+ permission: 'update',
+ icon: Icons.restart,
+ click: () => this.daemonAction('restart'),
+ name: this.actionLabels.RESTART,
+ disable: () => this.actionDisabled('restart')
+ },
+ {
+ permission: 'update',
+ icon: Icons.deploy,
+ click: () => this.daemonAction('redeploy'),
+ name: this.actionLabels.REDEPLOY,
+ disable: () => this.actionDisabled('redeploy')
+ }
+ ];
+ this.columns = [
+ {
+ name: $localize`Hostname`,
+ prop: 'hostname',
+ flexGrow: 2,
+ filterable: true
+ },
+ {
+ name: $localize`Daemon name`,
+ prop: 'daemon_name',
+ flexGrow: 1,
+ filterable: true
+ },
+ {
+ name: $localize`Version`,
+ prop: 'version',
+ flexGrow: 1,
+ filterable: true
+ },
+ {
+ name: $localize`Status`,
+ prop: 'status_desc',
+ flexGrow: 1,
+ filterable: true,
+ cellTemplate: this.statusTpl
+ },
+ {
+ name: $localize`Last Refreshed`,
+ prop: 'last_refresh',
+ pipe: this.relativeDatePipe,
+ flexGrow: 1
+ },
+ {
+ name: $localize`CPU Usage`,
+ prop: 'cpu_percentage',
+ flexGrow: 1,
+ cellTemplate: this.cpuTpl
+ },
+ {
+ name: $localize`Memory Usage`,
+ prop: 'memory_usage',
+ flexGrow: 1,
+ pipe: this.dimlessBinary,
+ cellClass: 'text-right'
+ },
+ {
+ name: $localize`Daemon Events`,
+ prop: 'events',
+ flexGrow: 2,
+ cellTemplate: this.listTpl
+ }
+ ];
+
+ this.serviceColumns = [
+ {
+ name: $localize`Service Name`,
+ prop: 'service_name',
+ flexGrow: 2,
+ filterable: true
+ },
+ {
+ name: $localize`Service Type`,
+ prop: 'service_type',
+ flexGrow: 1,
+ filterable: true
+ },
+ {
+ name: $localize`Service Events`,
+ prop: 'events',
+ flexGrow: 5,
+ cellTemplate: this.listTpl
+ }
+ ];
+
+ this.orchService.status().subscribe((data: { available: boolean }) => {
+ this.hasOrchestrator = data.available;
+ this.showDocPanel = !data.available;
+ });
+
+ this.columns = this.columns.filter((col: any) => {
+ return !this.hiddenColumns.includes(col.prop);
+ });
+ }
+
+ ngOnChanges() {
+ if (!_.isUndefined(this.daemonsTable)) {
+ this.daemonsTable.reloadData();
+ }
+ }
+
+ ngAfterViewInit() {
+ this.daemonsTableTplsSub = this.daemonsTableTpls.changes.subscribe(
+ (tableRefs: QueryList<TableComponent>) => {
+ this.daemonsTable = tableRefs.first;
+ }
+ );
+ }
+
+ ngOnDestroy() {
+ if (this.daemonsTableTplsSub) {
+ this.daemonsTableTplsSub.unsubscribe();
+ }
+ if (this.serviceSub) {
+ this.serviceSub.unsubscribe();
+ }
+ }
+
+ getStatusClass(row: Daemon): string {
+ return _.get(
+ {
+ '-1': 'badge-danger',
+ '0': 'badge-warning',
+ '1': 'badge-success'
+ },
+ row.status,
+ 'badge-dark'
+ );
+ }
+
+ getDaemons(context: CdTableFetchDataContext) {
+ let observable: Observable<Daemon[]>;
+ if (this.hostname) {
+ observable = this.hostService.getDaemons(this.hostname);
+ } else if (this.serviceName) {
+ observable = this.cephServiceService.getDaemons(this.serviceName);
+ } else {
+ this.daemons = [];
+ return;
+ }
+ observable.subscribe(
+ (daemons: Daemon[]) => {
+ this.daemons = daemons;
+ this.sortDaemonEvents();
+ },
+ () => {
+ this.daemons = [];
+ context.error();
+ }
+ );
+ }
+
+ sortDaemonEvents() {
+ this.daemons.forEach((daemon: any) => {
+ daemon.events?.sort((event1: any, event2: any) => {
+ return new Date(event2.created).getTime() - new Date(event1.created).getTime();
+ });
+ });
+ }
+ getServices(context: CdTableFetchDataContext) {
+ this.serviceSub = this.cephServiceService.list(this.serviceName).subscribe(
+ (services: CephServiceSpec[]) => {
+ this.services = services;
+ },
+ () => {
+ this.services = [];
+ context.error();
+ }
+ );
+ }
+
+ trackByFn(_index: any, item: any) {
+ return item.created;
+ }
+
+ updateSelection(selection: CdTableSelection) {
+ this.selection = selection;
+ }
+
+ daemonAction(actionType: string) {
+ this.daemonService
+ .action(this.selection.first()?.daemon_name, actionType)
+ .pipe(take(1))
+ .subscribe({
+ next: (resp) => {
+ this.notificationService.show(
+ NotificationType.success,
+ `Daemon ${actionType} scheduled`,
+ resp.body.toString()
+ );
+ },
+ error: (resp) => {
+ this.notificationService.show(
+ NotificationType.error,
+ 'Daemon action failed',
+ resp.body.toString()
+ );
+ }
+ });
+ }
+
+ actionDisabled(actionType: string) {
+ if (this.selection?.hasSelection) {
+ const daemon = this.selection.selected[0];
+ if (daemon.daemon_type === 'mon' || daemon.daemon_type === 'mgr') {
+ return true; // don't allow actions on mon and mgr, dashboard requires them.
+ }
+ switch (actionType) {
+ case 'start':
+ if (daemon.status_desc === 'running') {
+ return true;
+ }
+ break;
+ case 'stop':
+ if (daemon.status_desc === 'stopped') {
+ return true;
+ }
+ break;
+ }
+ return false;
+ }
+ return true; // if no selection then disable everything
+ }
+}