]> git.proxmox.com Git - ceph.git/blame - ceph/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/hosts.component.ts
update source to Ceph Pacific 16.2.2
[ceph.git] / ceph / src / pybind / mgr / dashboard / frontend / src / app / ceph / cluster / hosts / hosts.component.ts
CommitLineData
11fdf7f2 1import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core';
9f95a23c 2import { Router } from '@angular/router';
11fdf7f2 3
f67539c2
TL
4import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
5import _ from 'lodash';
11fdf7f2 6
f67539c2
TL
7import { HostService } from '~/app/shared/api/host.service';
8import { OrchestratorService } from '~/app/shared/api/orchestrator.service';
9import { ListWithDetails } from '~/app/shared/classes/list-with-details.class';
10import { ConfirmationModalComponent } from '~/app/shared/components/confirmation-modal/confirmation-modal.component';
11import { CriticalConfirmationModalComponent } from '~/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component';
12import { FormModalComponent } from '~/app/shared/components/form-modal/form-modal.component';
13import { SelectMessages } from '~/app/shared/components/select/select-messages.model';
14import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
15import { TableComponent } from '~/app/shared/datatable/table/table.component';
16import { CellTemplate } from '~/app/shared/enum/cell-template.enum';
17import { Icons } from '~/app/shared/enum/icons.enum';
18import { NotificationType } from '~/app/shared/enum/notification-type.enum';
19import { CdTableAction } from '~/app/shared/models/cd-table-action';
20import { CdTableColumn } from '~/app/shared/models/cd-table-column';
21import { CdTableFetchDataContext } from '~/app/shared/models/cd-table-fetch-data-context';
22import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
23import { FinishedTask } from '~/app/shared/models/finished-task';
24import { OrchestratorFeature } from '~/app/shared/models/orchestrator.enum';
25import { OrchestratorStatus } from '~/app/shared/models/orchestrator.interface';
26import { Permissions } from '~/app/shared/models/permissions';
27import { CephShortVersionPipe } from '~/app/shared/pipes/ceph-short-version.pipe';
28import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
29import { ModalService } from '~/app/shared/services/modal.service';
30import { NotificationService } from '~/app/shared/services/notification.service';
31import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service';
32import { URLBuilderService } from '~/app/shared/services/url-builder.service';
9f95a23c
TL
33
34const BASE_URL = 'hosts';
11fdf7f2
TL
35
36@Component({
37 selector: 'cd-hosts',
38 templateUrl: './hosts.component.html',
9f95a23c
TL
39 styleUrls: ['./hosts.component.scss'],
40 providers: [{ provide: URLBuilderService, useValue: new URLBuilderService(BASE_URL) }]
11fdf7f2 41})
e306af50 42export class HostsComponent extends ListWithDetails implements OnInit {
f67539c2 43 @ViewChild(TableComponent)
f6b5b4d7
TL
44 table: TableComponent;
45 @ViewChild('servicesTpl', { static: true })
46 public servicesTpl: TemplateRef<any>;
f67539c2
TL
47 @ViewChild('maintenanceConfirmTpl', { static: true })
48 maintenanceConfirmTpl: TemplateRef<any>;
f6b5b4d7 49
11fdf7f2
TL
50 permissions: Permissions;
51 columns: Array<CdTableColumn> = [];
52 hosts: Array<object> = [];
53 isLoadingHosts = false;
54 cdParams = { fromLink: '/hosts' };
9f95a23c 55 tableActions: CdTableAction[];
11fdf7f2 56 selection = new CdTableSelection();
f67539c2
TL
57 modalRef: NgbModalRef;
58 isExecuting = false;
59 errorMessage: string;
60 enableButton: boolean;
61
62 icons = Icons;
63
64 messages = {
65 nonOrchHost: $localize`The feature is disabled because the selected host is not managed by Orchestrator.`
66 };
67
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],
73 maintenance: [
74 OrchestratorFeature.HOST_MAINTENANCE_ENTER,
75 OrchestratorFeature.HOST_MAINTENANCE_EXIT
76 ]
77 };
11fdf7f2 78
11fdf7f2
TL
79 constructor(
80 private authStorageService: AuthStorageService,
81 private hostService: HostService,
82 private cephShortVersionPipe: CephShortVersionPipe,
9f95a23c
TL
83 private urlBuilder: URLBuilderService,
84 private actionLabels: ActionLabelsI18n,
f67539c2 85 private modalService: ModalService,
9f95a23c
TL
86 private taskWrapper: TaskWrapperService,
87 private router: Router,
f67539c2
TL
88 private notificationService: NotificationService,
89 private orchService: OrchestratorService
11fdf7f2 90 ) {
e306af50 91 super();
11fdf7f2 92 this.permissions = this.authStorageService.getPermissions();
9f95a23c
TL
93 this.tableActions = [
94 {
95 name: this.actionLabels.CREATE,
96 permission: 'create',
97 icon: Icons.add,
f67539c2
TL
98 click: () => this.router.navigate([this.urlBuilder.getCreate()]),
99 disable: (selection: CdTableSelection) => this.getDisable('create', selection)
9f95a23c 100 },
f6b5b4d7
TL
101 {
102 name: this.actionLabels.EDIT,
103 permission: 'update',
104 icon: Icons.edit,
f67539c2
TL
105 click: () => this.editAction(),
106 disable: (selection: CdTableSelection) => this.getDisable('edit', selection)
f6b5b4d7 107 },
9f95a23c
TL
108 {
109 name: this.actionLabels.DELETE,
110 permission: 'delete',
111 icon: Icons.destroy,
f67539c2
TL
112 click: () => this.deleteAction(),
113 disable: (selection: CdTableSelection) => this.getDisable('delete', selection)
114 },
115 {
116 name: this.actionLabels.ENTER_MAINTENANCE,
117 permission: 'update',
118 icon: Icons.enter,
119 click: () => this.hostMaintenance(),
120 disable: (selection: CdTableSelection) =>
121 this.getDisable('maintenance', selection) || this.isExecuting || this.enableButton
122 },
123 {
124 name: this.actionLabels.EXIT_MAINTENANCE,
125 permission: 'update',
126 icon: Icons.exit,
127 click: () => this.hostMaintenance(),
128 disable: (selection: CdTableSelection) =>
129 this.getDisable('maintenance', selection) || this.isExecuting || !this.enableButton
9f95a23c
TL
130 }
131 ];
11fdf7f2
TL
132 }
133
134 ngOnInit() {
135 this.columns = [
136 {
f67539c2 137 name: $localize`Hostname`,
11fdf7f2
TL
138 prop: 'hostname',
139 flexGrow: 1
140 },
141 {
f67539c2 142 name: $localize`Services`,
11fdf7f2
TL
143 prop: 'services',
144 flexGrow: 3,
145 cellTemplate: this.servicesTpl
146 },
e306af50 147 {
f67539c2 148 name: $localize`Labels`,
e306af50
TL
149 prop: 'labels',
150 flexGrow: 1,
f67539c2
TL
151 cellTransformation: CellTemplate.badge,
152 customTemplateConfig: {
153 class: 'badge-dark'
154 }
155 },
156 {
157 name: $localize`Status`,
158 prop: 'status',
159 flexGrow: 1,
160 cellTransformation: CellTemplate.badge,
161 customTemplateConfig: {
162 map: {
163 maintenance: { class: 'badge-warning' }
164 }
165 }
e306af50 166 },
11fdf7f2 167 {
f67539c2 168 name: $localize`Version`,
11fdf7f2
TL
169 prop: 'ceph_version',
170 flexGrow: 1,
171 pipe: this.cephShortVersionPipe
172 }
173 ];
f67539c2
TL
174 this.orchService.status().subscribe((status: OrchestratorStatus) => {
175 this.orchStatus = status;
176 });
11fdf7f2
TL
177 }
178
179 updateSelection(selection: CdTableSelection) {
180 this.selection = selection;
f67539c2
TL
181 this.enableButton = false;
182 if (this.selection.hasSelection) {
183 if (this.selection.first().status === 'maintenance') {
184 this.enableButton = true;
185 }
186 }
11fdf7f2
TL
187 }
188
f6b5b4d7
TL
189 editAction() {
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 };
194 });
195 this.modalService.show(FormModalComponent, {
f67539c2
TL
196 titleText: $localize`Edit Host: ${host.hostname}`,
197 fields: [
198 {
199 type: 'select-badges',
200 name: 'labels',
201 value: host['labels'],
202 label: $localize`Labels`,
203 typeConfig: {
204 customBadges: true,
205 options: allLabels,
206 messages: new SelectMessages({
207 empty: $localize`There are no labels.`,
208 filter: $localize`Filter or add labels`,
209 add: $localize`Add label`
210 })
f6b5b4d7 211 }
f6b5b4d7 212 }
f67539c2
TL
213 ],
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}"`
220 );
221 // Reload the data table content.
222 this.table.refreshBtn();
223 });
f6b5b4d7
TL
224 }
225 });
226 });
227 }
228
f67539c2
TL
229 hostMaintenance() {
230 this.isExecuting = true;
231 const host = this.selection.first();
232 if (host['status'] !== 'maintenance') {
233 this.hostService.update(host['hostname'], false, [], true).subscribe(
234 () => {
235 this.isExecuting = false;
236 this.notificationService.show(
237 NotificationType.success,
238 $localize`"${host.hostname}" moved to maintenance`
239 );
240 this.table.refreshBtn();
241 },
242 (error) => {
243 this.isExecuting = false;
244 this.errorMessage = error.error['detail'].split(/\n/);
245 error.preventDefault();
246 if (
247 error.error['detail'].includes('WARNING') &&
248 !error.error['detail'].includes('It is NOT safe to stop') &&
249 !error.error['detail'].includes('ALERT')
250 ) {
251 const modalVarialbes = {
252 titleText: $localize`Warning`,
253 buttonText: $localize`Continue`,
254 warning: true,
255 bodyTpl: this.maintenanceConfirmTpl,
256 showSubmit: true,
257 onSubmit: () => {
258 this.hostService.update(host['hostname'], false, [], true, true).subscribe(
259 () => {
260 this.modalRef.close();
261 },
262 () => this.modalRef.close()
263 );
264 }
265 };
266 this.modalRef = this.modalService.show(ConfirmationModalComponent, modalVarialbes);
267 } else {
268 this.notificationService.show(
269 NotificationType.error,
270 $localize`"${host.hostname}" cannot be put into maintenance`,
271 $localize`${error.error['detail']}`
272 );
273 }
274 }
275 );
276 } else {
277 this.hostService.update(host['hostname'], false, [], true).subscribe(() => {
278 this.isExecuting = false;
279 this.notificationService.show(
280 NotificationType.success,
281 $localize`"${host.hostname}" has exited maintenance`
f91f0fd5 282 );
f67539c2
TL
283 this.table.refreshBtn();
284 });
f6b5b4d7 285 }
f67539c2 286 }
f91f0fd5 287
f67539c2
TL
288 getDisable(
289 action: 'create' | 'edit' | 'delete' | 'maintenance',
290 selection: CdTableSelection
291 ): boolean | string {
292 if (action === 'delete' || action === 'edit' || action === 'maintenance') {
293 if (!selection?.hasSingleSelection) {
294 return true;
295 }
296 if (!_.every(selection.selected, 'sources.orchestrator')) {
297 return this.messages.nonOrchHost;
298 }
299 }
300 return this.orchService.getTableActionDisableDesc(
301 this.orchStatus,
302 this.actionOrchFeatures[action]
303 );
f6b5b4d7
TL
304 }
305
306 deleteAction() {
9f95a23c
TL
307 const hostname = this.selection.first().hostname;
308 this.modalRef = this.modalService.show(CriticalConfirmationModalComponent, {
f67539c2
TL
309 itemDescription: 'Host',
310 itemNames: [hostname],
311 actionDescription: 'delete',
312 submitActionObservable: () =>
313 this.taskWrapper.wrapTaskAroundCall({
314 task: new FinishedTask('host/delete', { hostname: hostname }),
315 call: this.hostService.delete(hostname)
316 })
9f95a23c
TL
317 });
318 }
319
11fdf7f2
TL
320 getHosts(context: CdTableFetchDataContext) {
321 if (this.isLoadingHosts) {
322 return;
323 }
324 const typeToPermissionKey = {
325 mds: 'cephfs',
326 mon: 'monitor',
327 osd: 'osd',
328 rgw: 'rgw',
329 'rbd-mirror': 'rbdMirroring',
330 mgr: 'manager',
331 'tcmu-runner': 'iscsi'
332 };
333 this.isLoadingHosts = true;
9f95a23c
TL
334 this.hostService.list().subscribe(
335 (resp: any[]) => {
11fdf7f2 336 resp.map((host) => {
9f95a23c 337 host.services.map((service: any) => {
81eedcae 338 service.cdLink = `/perf_counters/${service.type}/${encodeURIComponent(service.id)}`;
11fdf7f2
TL
339 const permission = this.permissions[typeToPermissionKey[service.type]];
340 service.canRead = permission ? permission.read : false;
341 return service;
342 });
343 return host;
344 });
345 this.hosts = resp;
346 this.isLoadingHosts = false;
9f95a23c
TL
347 },
348 () => {
11fdf7f2
TL
349 this.isLoadingHosts = false;
350 context.error();
9f95a23c
TL
351 }
352 );
11fdf7f2
TL
353 }
354}