]> git.proxmox.com Git - ceph.git/blob - ceph/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/services.component.ts
82a975c9df47ed58f6de09ec861a47b4e5a8ee0e
[ceph.git] / ceph / src / pybind / mgr / dashboard / frontend / src / app / ceph / cluster / services / services.component.ts
1 import { Component, Input, OnChanges, OnInit, TemplateRef, ViewChild } from '@angular/core';
2 import { Router } from '@angular/router';
3
4 import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
5 import { delay } from 'rxjs/operators';
6
7 import { CephServiceService } from '~/app/shared/api/ceph-service.service';
8 import { OrchestratorService } from '~/app/shared/api/orchestrator.service';
9 import { ListWithDetails } from '~/app/shared/classes/list-with-details.class';
10 import { CriticalConfirmationModalComponent } from '~/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component';
11 import { ActionLabelsI18n, URLVerbs } from '~/app/shared/constants/app.constants';
12 import { TableComponent } from '~/app/shared/datatable/table/table.component';
13 import { Icons } from '~/app/shared/enum/icons.enum';
14 import { CdTableAction } from '~/app/shared/models/cd-table-action';
15 import { CdTableColumn } from '~/app/shared/models/cd-table-column';
16 import { CdTableFetchDataContext } from '~/app/shared/models/cd-table-fetch-data-context';
17 import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
18 import { FinishedTask } from '~/app/shared/models/finished-task';
19 import { OrchestratorFeature } from '~/app/shared/models/orchestrator.enum';
20 import { OrchestratorStatus } from '~/app/shared/models/orchestrator.interface';
21 import { Permissions } from '~/app/shared/models/permissions';
22 import { CephServiceSpec } from '~/app/shared/models/service.interface';
23 import { RelativeDatePipe } from '~/app/shared/pipes/relative-date.pipe';
24 import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
25 import { ModalService } from '~/app/shared/services/modal.service';
26 import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service';
27 import { URLBuilderService } from '~/app/shared/services/url-builder.service';
28 import { PlacementPipe } from './placement.pipe';
29 import { ServiceFormComponent } from './service-form/service-form.component';
30
31 const BASE_URL = 'services';
32
33 @Component({
34 selector: 'cd-services',
35 templateUrl: './services.component.html',
36 styleUrls: ['./services.component.scss'],
37 providers: [{ provide: URLBuilderService, useValue: new URLBuilderService(BASE_URL) }]
38 })
39 export class ServicesComponent extends ListWithDetails implements OnChanges, OnInit {
40 @ViewChild(TableComponent, { static: true })
41 table: TableComponent;
42 @ViewChild('runningTpl', { static: true })
43 public runningTpl: TemplateRef<any>;
44
45 @Input() hostname: string;
46
47 // Do not display these columns
48 @Input() hiddenColumns: string[] = [];
49
50 @Input() hiddenServices: string[] = [];
51
52 @Input() hasDetails = true;
53
54 @Input() routedModal = true;
55
56 permissions: Permissions;
57 tableActions: CdTableAction[];
58 showDocPanel = false;
59 count = 0;
60 bsModalRef: NgbModalRef;
61
62 orchStatus: OrchestratorStatus;
63 actionOrchFeatures = {
64 create: [OrchestratorFeature.SERVICE_CREATE],
65 update: [OrchestratorFeature.SERVICE_EDIT],
66 delete: [OrchestratorFeature.SERVICE_DELETE]
67 };
68
69 columns: Array<CdTableColumn> = [];
70 services: Array<CephServiceSpec> = [];
71 isLoadingServices = false;
72 selection: CdTableSelection = new CdTableSelection();
73 icons = Icons;
74
75 constructor(
76 private actionLabels: ActionLabelsI18n,
77 private authStorageService: AuthStorageService,
78 private modalService: ModalService,
79 private orchService: OrchestratorService,
80 private cephServiceService: CephServiceService,
81 private relativeDatePipe: RelativeDatePipe,
82 private taskWrapperService: TaskWrapperService,
83 private router: Router
84 ) {
85 super();
86 this.permissions = this.authStorageService.getPermissions();
87 this.tableActions = [
88 {
89 permission: 'create',
90 icon: Icons.add,
91 click: () => this.openModal(),
92 name: this.actionLabels.CREATE,
93 canBePrimary: (selection: CdTableSelection) => !selection.hasSelection
94 // disable: (selection: CdTableSelection) => this.getDisable('create', selection)
95 },
96 {
97 permission: 'update',
98 icon: Icons.edit,
99 click: () => this.openModal(true),
100 name: this.actionLabels.EDIT,
101 disable: (selection: CdTableSelection) => this.getDisable('update', selection)
102 },
103 {
104 permission: 'delete',
105 icon: Icons.destroy,
106 click: () => this.deleteAction(),
107 name: this.actionLabels.DELETE,
108 disable: (selection: CdTableSelection) => this.getDisable('delete', selection)
109 }
110 ];
111 }
112
113 openModal(edit = false) {
114 if (this.routedModal) {
115 edit
116 ? this.router.navigate([
117 BASE_URL,
118 {
119 outlets: {
120 modal: [
121 URLVerbs.EDIT,
122 this.selection.first().service_type,
123 this.selection.first().service_name
124 ]
125 }
126 }
127 ])
128 : this.router.navigate([BASE_URL, { outlets: { modal: [URLVerbs.CREATE] } }]);
129 } else {
130 let initialState = {};
131 edit
132 ? (initialState = {
133 serviceName: this.selection.first()?.service_name,
134 serviceType: this.selection?.first()?.service_type,
135 hiddenServices: this.hiddenServices,
136 editing: edit
137 })
138 : (initialState = {
139 hiddenServices: this.hiddenServices,
140 editing: edit
141 });
142 this.bsModalRef = this.modalService.show(ServiceFormComponent, initialState, { size: 'lg' });
143 }
144 }
145
146 ngOnInit() {
147 const columns = [
148 {
149 name: $localize`Service`,
150 prop: 'service_name',
151 flexGrow: 1
152 },
153 {
154 name: $localize`Placement`,
155 prop: '',
156 pipe: new PlacementPipe(),
157 flexGrow: 2
158 },
159 {
160 name: $localize`Running`,
161 prop: 'status',
162 flexGrow: 1,
163 cellTemplate: this.runningTpl
164 },
165 {
166 name: $localize`Last Refreshed`,
167 prop: 'status.last_refresh',
168 pipe: this.relativeDatePipe,
169 flexGrow: 1
170 }
171 ];
172
173 this.columns = columns.filter((col: any) => {
174 return !this.hiddenColumns.includes(col.prop);
175 });
176
177 this.orchService.status().subscribe((status: OrchestratorStatus) => {
178 this.orchStatus = status;
179 this.showDocPanel = !status.available;
180 });
181 }
182
183 ngOnChanges() {
184 if (this.orchStatus?.available) {
185 this.services = [];
186 this.table.reloadData();
187 }
188 }
189
190 getDisable(
191 action: 'create' | 'update' | 'delete',
192 selection: CdTableSelection
193 ): boolean | string {
194 if (action === 'delete') {
195 if (!selection?.hasSingleSelection) {
196 return true;
197 }
198 }
199 if (action === 'update') {
200 const disableEditServices = ['osd', 'container'];
201 if (disableEditServices.indexOf(this.selection.first()?.service_type) >= 0) {
202 return true;
203 }
204 }
205 return this.orchService.getTableActionDisableDesc(
206 this.orchStatus,
207 this.actionOrchFeatures[action]
208 );
209 }
210
211 getServices(context: CdTableFetchDataContext) {
212 if (this.isLoadingServices) {
213 return;
214 }
215 this.isLoadingServices = true;
216 const pagination_obs = this.cephServiceService.list(context.toParams());
217 pagination_obs.observable.subscribe(
218 (services: CephServiceSpec[]) => {
219 this.services = services;
220 this.count = pagination_obs.count;
221 this.services = this.services.filter((col: any) => {
222 return !this.hiddenServices.includes(col.service_name);
223 });
224 this.isLoadingServices = false;
225 },
226 () => {
227 this.isLoadingServices = false;
228 this.services = [];
229 context.error();
230 }
231 );
232 }
233
234 updateSelection(selection: CdTableSelection) {
235 this.selection = selection;
236 }
237
238 deleteAction() {
239 const service = this.selection.first();
240 this.modalService.show(CriticalConfirmationModalComponent, {
241 itemDescription: $localize`Service`,
242 itemNames: [service.service_name],
243 actionDescription: 'delete',
244 submitActionObservable: () =>
245 this.taskWrapperService
246 .wrapTaskAroundCall({
247 task: new FinishedTask(`service/${URLVerbs.DELETE}`, {
248 service_name: service.service_name
249 }),
250 call: this.cephServiceService.delete(service.service_name)
251 })
252 .pipe(
253 // Delay closing the dialog, otherwise the datatable still
254 // shows the deleted service after an auto-reload.
255 // Showing the dialog while delaying is done to increase
256 // the user experience.
257 delay(5000)
258 )
259 });
260 }
261 }