]> git.proxmox.com Git - ceph.git/blob - ceph/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/inventory/inventory-devices/inventory-devices.component.ts
import ceph quincy 17.2.6
[ceph.git] / ceph / src / pybind / mgr / dashboard / frontend / src / app / ceph / cluster / inventory / inventory-devices / inventory-devices.component.ts
1 import {
2 Component,
3 EventEmitter,
4 Input,
5 OnDestroy,
6 OnInit,
7 Output,
8 ViewChild
9 } from '@angular/core';
10
11 import _ from 'lodash';
12 import { Subscription } from 'rxjs';
13
14 import { HostService } from '~/app/shared/api/host.service';
15 import { OrchestratorService } from '~/app/shared/api/orchestrator.service';
16 import { FormModalComponent } from '~/app/shared/components/form-modal/form-modal.component';
17 import { TableComponent } from '~/app/shared/datatable/table/table.component';
18 import { CellTemplate } from '~/app/shared/enum/cell-template.enum';
19 import { Icons } from '~/app/shared/enum/icons.enum';
20 import { NotificationType } from '~/app/shared/enum/notification-type.enum';
21 import { CdTableAction } from '~/app/shared/models/cd-table-action';
22 import { CdTableColumn } from '~/app/shared/models/cd-table-column';
23 import { CdTableColumnFiltersChange } from '~/app/shared/models/cd-table-column-filters-change';
24 import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
25 import { OrchestratorFeature } from '~/app/shared/models/orchestrator.enum';
26 import { OrchestratorStatus } from '~/app/shared/models/orchestrator.interface';
27 import { Permission } from '~/app/shared/models/permissions';
28 import { DimlessBinaryPipe } from '~/app/shared/pipes/dimless-binary.pipe';
29 import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
30 import { ModalService } from '~/app/shared/services/modal.service';
31 import { NotificationService } from '~/app/shared/services/notification.service';
32 import { InventoryDevice } from './inventory-device.model';
33
34 @Component({
35 selector: 'cd-inventory-devices',
36 templateUrl: './inventory-devices.component.html',
37 styleUrls: ['./inventory-devices.component.scss']
38 })
39 export class InventoryDevicesComponent implements OnInit, OnDestroy {
40 @ViewChild(TableComponent, { static: true })
41 table: TableComponent;
42
43 // Devices
44 @Input() devices: InventoryDevice[] = [];
45
46 @Input() showAvailDeviceOnly = false;
47 // Do not display these columns
48 @Input() hiddenColumns: string[] = [];
49
50 @Input() hostname = '';
51
52 @Input() diskType = '';
53
54 // Show filters for these columns, specify empty array to disable
55 @Input() filterColumns = [
56 'hostname',
57 'human_readable_type',
58 'available',
59 'sys_api.vendor',
60 'sys_api.model',
61 'sys_api.size'
62 ];
63
64 // Device table row selection type
65 @Input() selectionType: string = undefined;
66
67 @Output() filterChange = new EventEmitter<CdTableColumnFiltersChange>();
68
69 @Output() fetchInventory = new EventEmitter();
70
71 icons = Icons;
72 columns: Array<CdTableColumn> = [];
73 selection: CdTableSelection = new CdTableSelection();
74 permission: Permission;
75 tableActions: CdTableAction[];
76 fetchInventorySub: Subscription;
77
78 @Input() orchStatus: OrchestratorStatus = undefined;
79
80 actionOrchFeatures = {
81 identify: [OrchestratorFeature.DEVICE_BLINK_LIGHT]
82 };
83
84 constructor(
85 private authStorageService: AuthStorageService,
86 private dimlessBinary: DimlessBinaryPipe,
87 private modalService: ModalService,
88 private notificationService: NotificationService,
89 private orchService: OrchestratorService,
90 private hostService: HostService
91 ) {}
92
93 ngOnInit() {
94 this.permission = this.authStorageService.getPermissions().osd;
95 this.tableActions = [
96 {
97 permission: 'update',
98 icon: Icons.show,
99 click: () => this.identifyDevice(),
100 name: $localize`Identify`,
101 disable: (selection: CdTableSelection) => this.getDisable('identify', selection),
102 canBePrimary: (selection: CdTableSelection) => !selection.hasSingleSelection,
103 visible: () => _.isString(this.selectionType)
104 }
105 ];
106 const columns = [
107 {
108 name: $localize`Hostname`,
109 prop: 'hostname',
110 flexGrow: 1
111 },
112 {
113 name: $localize`Device path`,
114 prop: 'path',
115 flexGrow: 1
116 },
117 {
118 name: $localize`Type`,
119 prop: 'human_readable_type',
120 flexGrow: 1,
121 cellTransformation: CellTemplate.badge,
122 customTemplateConfig: {
123 map: {
124 hdd: { value: 'HDD', class: 'badge-hdd' },
125 ssd: { value: 'SSD', class: 'badge-ssd' }
126 }
127 }
128 },
129 {
130 name: $localize`Available`,
131 prop: 'available',
132 flexGrow: 1,
133 cellClass: 'text-center',
134 cellTransformation: CellTemplate.checkIcon
135 },
136 {
137 name: $localize`Vendor`,
138 prop: 'sys_api.vendor',
139 flexGrow: 1
140 },
141 {
142 name: $localize`Model`,
143 prop: 'sys_api.model',
144 flexGrow: 1
145 },
146 {
147 name: $localize`Size`,
148 prop: 'sys_api.size',
149 flexGrow: 1,
150 pipe: this.dimlessBinary
151 },
152 {
153 name: $localize`OSDs`,
154 prop: 'osd_ids',
155 flexGrow: 1,
156 cellTransformation: CellTemplate.badge,
157 customTemplateConfig: {
158 class: 'badge-dark',
159 prefix: 'osd.'
160 }
161 }
162 ];
163
164 this.columns = columns.filter((col: any) => {
165 return !this.hiddenColumns.includes(col.prop);
166 });
167
168 // init column filters
169 _.forEach(this.filterColumns, (prop) => {
170 const col = _.find(this.columns, { prop: prop });
171 if (col) {
172 col.filterable = true;
173 }
174
175 if (col?.prop === 'human_readable_type' && this.diskType === 'ssd') {
176 col.filterInitValue = this.diskType;
177 }
178
179 if (col?.prop === 'hostname' && this.hostname) {
180 col.filterInitValue = this.hostname;
181 }
182 });
183
184 if (this.fetchInventory.observers.length > 0) {
185 this.fetchInventorySub = this.table.fetchData.subscribe(() => {
186 this.fetchInventory.emit();
187 });
188 }
189 }
190
191 getDevices() {
192 if (this.showAvailDeviceOnly) {
193 this.hostService.inventoryDeviceList().subscribe(
194 (devices: InventoryDevice[]) => {
195 this.devices = _.filter(devices, 'available');
196 this.devices = [...this.devices];
197 },
198 () => {
199 this.devices = [];
200 }
201 );
202 } else {
203 this.devices = [...this.devices];
204 }
205 }
206
207 ngOnDestroy() {
208 if (this.fetchInventorySub) {
209 this.fetchInventorySub.unsubscribe();
210 }
211 }
212
213 onColumnFiltersChanged(event: CdTableColumnFiltersChange) {
214 this.filterChange.emit(event);
215 }
216
217 getDisable(action: 'identify', selection: CdTableSelection): boolean | string {
218 if (!selection.hasSingleSelection) {
219 return true;
220 }
221 return this.orchService.getTableActionDisableDesc(
222 this.orchStatus,
223 this.actionOrchFeatures[action]
224 );
225 }
226
227 updateSelection(selection: CdTableSelection) {
228 this.selection = selection;
229 }
230
231 identifyDevice() {
232 const selected = this.selection.first();
233 const hostname = selected.hostname;
234 const device = selected.path || selected.device_id;
235 this.modalService.show(FormModalComponent, {
236 titleText: $localize`Identify device ${device}`,
237 message: $localize`Please enter the duration how long to blink the LED.`,
238 fields: [
239 {
240 type: 'select',
241 name: 'duration',
242 value: 300,
243 required: true,
244 typeConfig: {
245 options: [
246 { text: $localize`1 minute`, value: 60 },
247 { text: $localize`2 minutes`, value: 120 },
248 { text: $localize`5 minutes`, value: 300 },
249 { text: $localize`10 minutes`, value: 600 },
250 { text: $localize`15 minutes`, value: 900 }
251 ]
252 }
253 }
254 ],
255 submitButtonText: $localize`Execute`,
256 onSubmit: (values: any) => {
257 this.hostService.identifyDevice(hostname, device, values.duration).subscribe(() => {
258 this.notificationService.show(
259 NotificationType.success,
260 $localize`Identifying '${device}' started on host '${hostname}'`
261 );
262 });
263 }
264 });
265 }
266 }