]> git.proxmox.com Git - ceph.git/blob - ceph/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/inventory/inventory-devices/inventory-devices.component.ts
e0d82cb1975db7e749cdb6c9bfe7bfe020b3fcc5
[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 // Show filters for these columns, specify empty array to disable
51 @Input() filterColumns = [
52 'hostname',
53 'human_readable_type',
54 'available',
55 'sys_api.vendor',
56 'sys_api.model',
57 'sys_api.size'
58 ];
59
60 // Device table row selection type
61 @Input() selectionType: string = undefined;
62
63 @Output() filterChange = new EventEmitter<CdTableColumnFiltersChange>();
64
65 @Output() fetchInventory = new EventEmitter();
66
67 icons = Icons;
68 columns: Array<CdTableColumn> = [];
69 selection: CdTableSelection = new CdTableSelection();
70 permission: Permission;
71 tableActions: CdTableAction[];
72 fetchInventorySub: Subscription;
73
74 @Input() orchStatus: OrchestratorStatus = undefined;
75
76 actionOrchFeatures = {
77 identify: [OrchestratorFeature.DEVICE_BLINK_LIGHT]
78 };
79
80 constructor(
81 private authStorageService: AuthStorageService,
82 private dimlessBinary: DimlessBinaryPipe,
83 private modalService: ModalService,
84 private notificationService: NotificationService,
85 private orchService: OrchestratorService,
86 private hostService: HostService
87 ) {}
88
89 ngOnInit() {
90 this.permission = this.authStorageService.getPermissions().osd;
91 this.tableActions = [
92 {
93 permission: 'update',
94 icon: Icons.show,
95 click: () => this.identifyDevice(),
96 name: $localize`Identify`,
97 disable: (selection: CdTableSelection) => this.getDisable('identify', selection),
98 canBePrimary: (selection: CdTableSelection) => !selection.hasSingleSelection,
99 visible: () => _.isString(this.selectionType)
100 }
101 ];
102 const columns = [
103 {
104 name: $localize`Hostname`,
105 prop: 'hostname',
106 flexGrow: 1
107 },
108 {
109 name: $localize`Device path`,
110 prop: 'path',
111 flexGrow: 1
112 },
113 {
114 name: $localize`Type`,
115 prop: 'human_readable_type',
116 flexGrow: 1,
117 cellTransformation: CellTemplate.badge,
118 customTemplateConfig: {
119 map: {
120 hdd: { value: 'HDD', class: 'badge-hdd' },
121 ssd: { value: 'SSD', class: 'badge-ssd' }
122 }
123 }
124 },
125 {
126 name: $localize`Available`,
127 prop: 'available',
128 flexGrow: 1,
129 cellClass: 'text-center',
130 cellTransformation: CellTemplate.checkIcon
131 },
132 {
133 name: $localize`Vendor`,
134 prop: 'sys_api.vendor',
135 flexGrow: 1
136 },
137 {
138 name: $localize`Model`,
139 prop: 'sys_api.model',
140 flexGrow: 1
141 },
142 {
143 name: $localize`Size`,
144 prop: 'sys_api.size',
145 flexGrow: 1,
146 pipe: this.dimlessBinary
147 },
148 {
149 name: $localize`OSDs`,
150 prop: 'osd_ids',
151 flexGrow: 1,
152 cellTransformation: CellTemplate.badge,
153 customTemplateConfig: {
154 class: 'badge-dark',
155 prefix: 'osd.'
156 }
157 }
158 ];
159
160 this.columns = columns.filter((col: any) => {
161 return !this.hiddenColumns.includes(col.prop);
162 });
163
164 // init column filters
165 _.forEach(this.filterColumns, (prop) => {
166 const col = _.find(this.columns, { prop: prop });
167 if (col) {
168 col.filterable = true;
169 }
170 });
171
172 if (this.fetchInventory.observers.length > 0) {
173 this.fetchInventorySub = this.table.fetchData.subscribe(() => {
174 this.fetchInventory.emit();
175 });
176 }
177 }
178
179 getDevices() {
180 if (this.showAvailDeviceOnly) {
181 this.hostService.inventoryDeviceList().subscribe(
182 (devices: InventoryDevice[]) => {
183 this.devices = _.filter(devices, 'available');
184 this.devices = [...this.devices];
185 },
186 () => {
187 this.devices = [];
188 }
189 );
190 } else {
191 this.devices = [...this.devices];
192 }
193 }
194
195 ngOnDestroy() {
196 if (this.fetchInventorySub) {
197 this.fetchInventorySub.unsubscribe();
198 }
199 }
200
201 onColumnFiltersChanged(event: CdTableColumnFiltersChange) {
202 this.filterChange.emit(event);
203 }
204
205 getDisable(action: 'identify', selection: CdTableSelection): boolean | string {
206 if (!selection.hasSingleSelection) {
207 return true;
208 }
209 return this.orchService.getTableActionDisableDesc(
210 this.orchStatus,
211 this.actionOrchFeatures[action]
212 );
213 }
214
215 updateSelection(selection: CdTableSelection) {
216 this.selection = selection;
217 }
218
219 identifyDevice() {
220 const selected = this.selection.first();
221 const hostname = selected.hostname;
222 const device = selected.path || selected.device_id;
223 this.modalService.show(FormModalComponent, {
224 titleText: $localize`Identify device ${device}`,
225 message: $localize`Please enter the duration how long to blink the LED.`,
226 fields: [
227 {
228 type: 'select',
229 name: 'duration',
230 value: 300,
231 required: true,
232 typeConfig: {
233 options: [
234 { text: $localize`1 minute`, value: 60 },
235 { text: $localize`2 minutes`, value: 120 },
236 { text: $localize`5 minutes`, value: 300 },
237 { text: $localize`10 minutes`, value: 600 },
238 { text: $localize`15 minutes`, value: 900 }
239 ]
240 }
241 }
242 ],
243 submitButtonText: $localize`Execute`,
244 onSubmit: (values: any) => {
245 this.hostService.identifyDevice(hostname, device, values.duration).subscribe(() => {
246 this.notificationService.show(
247 NotificationType.success,
248 $localize`Identifying '${device}' started on host '${hostname}'`
249 );
250 });
251 }
252 });
253 }
254 }