1 import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core';
2 import { Router } from '@angular/router';
4 import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
5 import _ from 'lodash';
7 import { HostService } from '~/app/shared/api/host.service';
8 import { OrchestratorService } from '~/app/shared/api/orchestrator.service';
9 import { ListWithDetails } from '~/app/shared/classes/list-with-details.class';
10 import { ConfirmationModalComponent } from '~/app/shared/components/confirmation-modal/confirmation-modal.component';
11 import { CriticalConfirmationModalComponent } from '~/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component';
12 import { FormModalComponent } from '~/app/shared/components/form-modal/form-modal.component';
13 import { SelectMessages } from '~/app/shared/components/select/select-messages.model';
14 import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
15 import { TableComponent } from '~/app/shared/datatable/table/table.component';
16 import { CellTemplate } from '~/app/shared/enum/cell-template.enum';
17 import { Icons } from '~/app/shared/enum/icons.enum';
18 import { NotificationType } from '~/app/shared/enum/notification-type.enum';
19 import { CdTableAction } from '~/app/shared/models/cd-table-action';
20 import { CdTableColumn } from '~/app/shared/models/cd-table-column';
21 import { CdTableFetchDataContext } from '~/app/shared/models/cd-table-fetch-data-context';
22 import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
23 import { FinishedTask } from '~/app/shared/models/finished-task';
24 import { OrchestratorFeature } from '~/app/shared/models/orchestrator.enum';
25 import { OrchestratorStatus } from '~/app/shared/models/orchestrator.interface';
26 import { Permissions } from '~/app/shared/models/permissions';
27 import { CephShortVersionPipe } from '~/app/shared/pipes/ceph-short-version.pipe';
28 import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
29 import { ModalService } from '~/app/shared/services/modal.service';
30 import { NotificationService } from '~/app/shared/services/notification.service';
31 import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service';
32 import { URLBuilderService } from '~/app/shared/services/url-builder.service';
34 const BASE_URL = 'hosts';
38 templateUrl: './hosts.component.html',
39 styleUrls: ['./hosts.component.scss'],
40 providers: [{ provide: URLBuilderService, useValue: new URLBuilderService(BASE_URL) }]
42 export class HostsComponent extends ListWithDetails implements OnInit {
43 @ViewChild(TableComponent)
44 table: TableComponent;
45 @ViewChild('servicesTpl', { static: true })
46 public servicesTpl: TemplateRef<any>;
47 @ViewChild('maintenanceConfirmTpl', { static: true })
48 maintenanceConfirmTpl: TemplateRef<any>;
50 permissions: Permissions;
51 columns: Array<CdTableColumn> = [];
52 hosts: Array<object> = [];
53 isLoadingHosts = false;
54 cdParams = { fromLink: '/hosts' };
55 tableActions: CdTableAction[];
56 selection = new CdTableSelection();
57 modalRef: NgbModalRef;
60 enableButton: boolean;
65 nonOrchHost: $localize`The feature is disabled because the selected host is not managed by Orchestrator.`
68 orchStatus: OrchestratorStatus;
69 actionOrchFeatures = {
70 create: [OrchestratorFeature.HOST_CREATE],
71 edit: [OrchestratorFeature.HOST_LABEL_ADD, OrchestratorFeature.HOST_LABEL_REMOVE],
72 delete: [OrchestratorFeature.HOST_DELETE],
74 OrchestratorFeature.HOST_MAINTENANCE_ENTER,
75 OrchestratorFeature.HOST_MAINTENANCE_EXIT
80 private authStorageService: AuthStorageService,
81 private hostService: HostService,
82 private cephShortVersionPipe: CephShortVersionPipe,
83 private urlBuilder: URLBuilderService,
84 private actionLabels: ActionLabelsI18n,
85 private modalService: ModalService,
86 private taskWrapper: TaskWrapperService,
87 private router: Router,
88 private notificationService: NotificationService,
89 private orchService: OrchestratorService
92 this.permissions = this.authStorageService.getPermissions();
95 name: this.actionLabels.CREATE,
98 click: () => this.router.navigate([this.urlBuilder.getCreate()]),
99 disable: (selection: CdTableSelection) => this.getDisable('create', selection)
102 name: this.actionLabels.EDIT,
103 permission: 'update',
105 click: () => this.editAction(),
106 disable: (selection: CdTableSelection) => this.getDisable('edit', selection)
109 name: this.actionLabels.DELETE,
110 permission: 'delete',
112 click: () => this.deleteAction(),
113 disable: (selection: CdTableSelection) => this.getDisable('delete', selection)
116 name: this.actionLabels.ENTER_MAINTENANCE,
117 permission: 'update',
119 click: () => this.hostMaintenance(),
120 disable: (selection: CdTableSelection) =>
121 this.getDisable('maintenance', selection) || this.isExecuting || this.enableButton
124 name: this.actionLabels.EXIT_MAINTENANCE,
125 permission: 'update',
127 click: () => this.hostMaintenance(),
128 disable: (selection: CdTableSelection) =>
129 this.getDisable('maintenance', selection) || this.isExecuting || !this.enableButton
137 name: $localize`Hostname`,
142 name: $localize`Services`,
145 cellTemplate: this.servicesTpl
148 name: $localize`Labels`,
151 cellTransformation: CellTemplate.badge,
152 customTemplateConfig: {
157 name: $localize`Status`,
160 cellTransformation: CellTemplate.badge,
161 customTemplateConfig: {
163 maintenance: { class: 'badge-warning' }
168 name: $localize`Version`,
169 prop: 'ceph_version',
171 pipe: this.cephShortVersionPipe
174 this.orchService.status().subscribe((status: OrchestratorStatus) => {
175 this.orchStatus = status;
179 updateSelection(selection: CdTableSelection) {
180 this.selection = selection;
181 this.enableButton = false;
182 if (this.selection.hasSelection) {
183 if (this.selection.first().status === 'maintenance') {
184 this.enableButton = true;
190 this.hostService.getLabels().subscribe((resp: string[]) => {
191 const host = this.selection.first();
192 const allLabels = resp.map((label) => {
193 return { enabled: true, name: label };
195 this.modalService.show(FormModalComponent, {
196 titleText: $localize`Edit Host: ${host.hostname}`,
199 type: 'select-badges',
201 value: host['labels'],
202 label: $localize`Labels`,
206 messages: new SelectMessages({
207 empty: $localize`There are no labels.`,
208 filter: $localize`Filter or add labels`,
209 add: $localize`Add label`
214 submitButtonText: $localize`Edit Host`,
215 onSubmit: (values: any) => {
216 this.hostService.update(host['hostname'], true, values.labels).subscribe(() => {
217 this.notificationService.show(
218 NotificationType.success,
219 $localize`Updated Host "${host.hostname}"`
221 // Reload the data table content.
222 this.table.refreshBtn();
230 this.isExecuting = true;
231 const host = this.selection.first();
232 if (host['status'] !== 'maintenance') {
233 this.hostService.update(host['hostname'], false, [], true).subscribe(
235 this.isExecuting = false;
236 this.notificationService.show(
237 NotificationType.success,
238 $localize`"${host.hostname}" moved to maintenance`
240 this.table.refreshBtn();
243 this.isExecuting = false;
244 this.errorMessage = error.error['detail'].split(/\n/);
245 error.preventDefault();
247 error.error['detail'].includes('WARNING') &&
248 !error.error['detail'].includes('It is NOT safe to stop') &&
249 !error.error['detail'].includes('ALERT') &&
250 !error.error['detail'].includes('unable to stop')
252 const modalVarialbes = {
253 titleText: $localize`Warning`,
254 buttonText: $localize`Continue`,
256 bodyTpl: this.maintenanceConfirmTpl,
259 this.hostService.update(host['hostname'], false, [], true, true).subscribe(
261 this.modalRef.close();
263 () => this.modalRef.close()
267 this.modalRef = this.modalService.show(ConfirmationModalComponent, modalVarialbes);
269 this.notificationService.show(
270 NotificationType.error,
271 $localize`"${host.hostname}" cannot be put into maintenance`,
272 $localize`${error.error['detail']}`
278 this.hostService.update(host['hostname'], false, [], true).subscribe(() => {
279 this.isExecuting = false;
280 this.notificationService.show(
281 NotificationType.success,
282 $localize`"${host.hostname}" has exited maintenance`
284 this.table.refreshBtn();
290 action: 'create' | 'edit' | 'delete' | 'maintenance',
291 selection: CdTableSelection
292 ): boolean | string {
293 if (action === 'delete' || action === 'edit' || action === 'maintenance') {
294 if (!selection?.hasSingleSelection) {
297 if (!_.every(selection.selected, 'sources.orchestrator')) {
298 return this.messages.nonOrchHost;
301 return this.orchService.getTableActionDisableDesc(
303 this.actionOrchFeatures[action]
308 const hostname = this.selection.first().hostname;
309 this.modalRef = this.modalService.show(CriticalConfirmationModalComponent, {
310 itemDescription: 'Host',
311 itemNames: [hostname],
312 actionDescription: 'delete',
313 submitActionObservable: () =>
314 this.taskWrapper.wrapTaskAroundCall({
315 task: new FinishedTask('host/delete', { hostname: hostname }),
316 call: this.hostService.delete(hostname)
321 getHosts(context: CdTableFetchDataContext) {
322 if (this.isLoadingHosts) {
325 const typeToPermissionKey = {
330 'rbd-mirror': 'rbdMirroring',
332 'tcmu-runner': 'iscsi'
334 this.isLoadingHosts = true;
335 this.hostService.list().subscribe(
338 host.services.map((service: any) => {
339 service.cdLink = `/perf_counters/${service.type}/${encodeURIComponent(service.id)}`;
340 const permission = this.permissions[typeToPermissionKey[service.type]];
341 service.canRead = permission ? permission.read : false;
347 this.isLoadingHosts = false;
350 this.isLoadingHosts = false;