]>
Commit | Line | Data |
---|---|---|
11fdf7f2 | 1 | import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core'; |
9f95a23c | 2 | import { Router } from '@angular/router'; |
11fdf7f2 | 3 | |
f67539c2 TL |
4 | import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; |
5 | import _ from 'lodash'; | |
11fdf7f2 | 6 | |
f67539c2 TL |
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'; | |
9f95a23c TL |
33 | |
34 | const 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 | 42 | export 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 | } |