]> git.proxmox.com Git - proxmox-widget-toolkit.git/blob - src/panel/DiskList.js
panel: disks: more static task window creation
[proxmox-widget-toolkit.git] / src / panel / DiskList.js
1 Ext.define('pmx-disk-list', {
2 extend: 'Ext.data.Model',
3 fields: [
4 'devpath', 'used',
5 { name: 'size', type: 'number' },
6 { name: 'osdid', type: 'number', defaultValue: -1 },
7 {
8 name: 'status',
9 convert: function(value, rec) {
10 if (value) return value;
11 if (rec.data.health) {
12 return rec.data.health;
13 }
14
15 if (rec.data.type === 'partition') {
16 return "";
17 }
18
19 return Proxmox.Utils.unknownText;
20 },
21 },
22 {
23 name: 'name',
24 convert: function(value, rec) {
25 if (value) return value;
26 if (rec.data.devpath) return rec.data.devpath;
27 return undefined;
28 },
29 },
30 {
31 name: 'disk-type',
32 convert: function(value, rec) {
33 if (value) return value;
34 if (rec.data.type) return rec.data.type;
35 return undefined;
36 },
37 },
38 'vendor', 'model', 'serial', 'rpm', 'type', 'wearout', 'health',
39 ],
40 idProperty: 'devpath',
41 });
42
43 Ext.define('Proxmox.DiskList', {
44 extend: 'Ext.tree.Panel',
45 alias: 'widget.pmxDiskList',
46
47 supportsWipeDisk: false,
48
49 rootVisible: false,
50
51 emptyText: gettext('No Disks found'),
52
53 stateful: true,
54 stateId: 'tree-node-disks',
55
56 controller: {
57 xclass: 'Ext.app.ViewController',
58
59 reload: function() {
60 let me = this;
61 let view = me.getView();
62
63 let extraParams = {};
64 if (view.includePartitions) {
65 extraParams['include-partitions'] = 1;
66 }
67
68 let url = `${view.baseurl}/list`;
69 me.store.setProxy({
70 type: 'proxmox',
71 extraParams: extraParams,
72 url: url,
73 });
74 me.store.load();
75 },
76
77 openSmartWindow: function() {
78 let me = this;
79 let view = me.getView();
80 let selection = view.getSelection();
81 if (!selection || selection.length < 1) return;
82
83 let rec = selection[0];
84 Ext.create('Proxmox.window.DiskSmart', {
85 baseurl: view.baseurl,
86 dev: rec.data.name,
87 }).show();
88 },
89
90 initGPT: function() {
91 let me = this;
92 let view = me.getView();
93 let selection = view.getSelection();
94 if (!selection || selection.length < 1) return;
95
96 let rec = selection[0];
97 Proxmox.Utils.API2Request({
98 url: `${view.exturl}/initgpt`,
99 waitMsgTarget: view,
100 method: 'POST',
101 params: { disk: rec.data.name },
102 failure: response => Ext.Msg.alert(gettext('Error'), response.htmlStatus),
103 success: function(response, options) {
104 Ext.create('Proxmox.window.TaskProgress', {
105 upid: response.result.data,
106 taskDone: function() {
107 me.reload();
108 },
109 autoShow: true,
110 });
111 },
112 });
113 },
114
115 wipeDisk: function() {
116 let me = this;
117 let view = me.getView();
118 let selection = view.getSelection();
119 if (!selection || selection.length < 1) return;
120
121 let rec = selection[0];
122 Proxmox.Utils.API2Request({
123 url: `${view.exturl}/wipedisk`,
124 waitMsgTarget: view,
125 method: 'PUT',
126 params: { disk: rec.data.name },
127 failure: response => Ext.Msg.alert(gettext('Error'), response.htmlStatus),
128 success: function(response, options) {
129 Ext.create('Proxmox.window.TaskProgress', {
130 upid: response.result.data,
131 taskDone: function() {
132 me.reload();
133 },
134 autoShow: true,
135 });
136 },
137 });
138 },
139
140 init: function(view) {
141 let nodename = view.nodename || 'localhost';
142 view.baseurl = `/api2/json/nodes/${nodename}/disks`;
143 view.exturl = `/api2/extjs/nodes/${nodename}/disks`;
144
145 this.store = Ext.create('Ext.data.Store', {
146 model: 'pmx-disk-list',
147 });
148 this.store.on('load', this.onLoad, this);
149
150 Proxmox.Utils.monStoreErrors(view, this.store);
151 this.reload();
152 },
153
154 onLoad: function(store, records, success, operation) {
155 let me = this;
156 let view = this.getView();
157
158 if (!success) {
159 Proxmox.Utils.setErrorMask(
160 view,
161 Proxmox.Utils.getResponseErrorMessage(operation.getError()),
162 );
163 return;
164 }
165
166 let disks = {};
167
168 for (const item of records) {
169 let data = item.data;
170 data.leaf = true;
171 data.expanded = true;
172 data.children = [];
173 data.iconCls = 'fa fa-fw fa-hdd-o x-fa-tree';
174 if (!data.parent) {
175 disks[data.devpath] = data;
176 }
177 }
178 for (const item of records) {
179 let data = item.data;
180 if (data.parent) {
181 disks[data.parent].leaf = false;
182 disks[data.parent].children.push(data);
183 }
184 }
185
186 let children = [];
187 for (const [_, device] of Object.entries(disks)) {
188 children.push(device);
189 }
190
191 view.setRootNode({
192 expanded: true,
193 children: children,
194 });
195
196 Proxmox.Utils.setErrorMask(view, false);
197 },
198 },
199
200 renderDiskType: function(v) {
201 if (v === undefined) return Proxmox.Utils.unknownText;
202 switch (v) {
203 case 'ssd': return 'SSD';
204 case 'hdd': return 'Hard Disk';
205 case 'usb': return 'USB';
206 default: return v;
207 }
208 },
209
210 renderDiskUsage: function(v, metaData, rec) {
211 let extendedInfo = '';
212 if (rec) {
213 let types = [];
214 if (rec.data.osdid !== undefined && rec.data.osdid >= 0) {
215 types.push(`OSD.${rec.data.osdid.toString()}`);
216 }
217 if (rec.data.journals > 0) {
218 types.push('Journal');
219 }
220 if (rec.data.db > 0) {
221 types.push('DB');
222 }
223 if (rec.data.wal > 0) {
224 types.push('WAL');
225 }
226 if (types.length > 0) {
227 extendedInfo = `, Ceph (${types.join(', ')})`;
228 }
229 }
230 return v ? `${v}${extendedInfo}` : Proxmox.Utils.noText;
231 },
232
233 columns: [
234 {
235 xtype: 'treecolumn',
236 header: gettext('Device'),
237 width: 150,
238 sortable: true,
239 dataIndex: 'devpath',
240 },
241 {
242 header: gettext('Type'),
243 width: 80,
244 sortable: true,
245 dataIndex: 'disk-type',
246 renderer: function(v) {
247 let me = this;
248 return me.renderDiskType(v);
249 },
250 },
251 {
252 header: gettext('Usage'),
253 width: 150,
254 sortable: false,
255 renderer: function(v, metaData, rec) {
256 let me = this;
257 return me.renderDiskUsage(v, metaData, rec);
258 },
259 dataIndex: 'used',
260 },
261 {
262 header: gettext('Size'),
263 width: 100,
264 align: 'right',
265 sortable: true,
266 renderer: Proxmox.Utils.format_size,
267 dataIndex: 'size',
268 },
269 {
270 header: 'GPT',
271 width: 60,
272 align: 'right',
273 renderer: Proxmox.Utils.format_boolean,
274 dataIndex: 'gpt',
275 },
276 {
277 header: gettext('Vendor'),
278 width: 100,
279 sortable: true,
280 hidden: true,
281 renderer: Ext.String.htmlEncode,
282 dataIndex: 'vendor',
283 },
284 {
285 header: gettext('Model'),
286 width: 200,
287 sortable: true,
288 renderer: Ext.String.htmlEncode,
289 dataIndex: 'model',
290 },
291 {
292 header: gettext('Serial'),
293 width: 200,
294 sortable: true,
295 renderer: Ext.String.htmlEncode,
296 dataIndex: 'serial',
297 },
298 {
299 header: 'S.M.A.R.T.',
300 width: 100,
301 sortable: true,
302 renderer: Ext.String.htmlEncode,
303 dataIndex: 'status',
304 },
305 {
306 header: 'Wearout',
307 width: 90,
308 sortable: true,
309 align: 'right',
310 dataIndex: 'wearout',
311 renderer: function(value) {
312 if (Ext.isNumeric(value)) {
313 return (100 - value).toString() + '%';
314 }
315 return 'N/A';
316 },
317 },
318 ],
319
320 listeners: {
321 itemdblclick: 'openSmartWindow',
322 },
323
324 initComponent: function() {
325 let me = this;
326
327 let tbar = [
328 {
329 text: gettext('Reload'),
330 handler: 'reload',
331 },
332 {
333 xtype: 'proxmoxButton',
334 text: gettext('Show S.M.A.R.T. values'),
335 parentXType: 'treepanel',
336 disabled: true,
337 enableFn: function(rec) {
338 if (!rec || rec.data.parent) {
339 return false;
340 } else {
341 return true;
342 }
343 },
344 handler: 'openSmartWindow',
345 },
346 {
347 xtype: 'proxmoxButton',
348 text: gettext('Initialize Disk with GPT'),
349 parentXType: 'treepanel',
350 disabled: true,
351 enableFn: function(rec) {
352 if (!rec || rec.data.parent ||
353 (rec.data.used && rec.data.used !== 'unused')) {
354 return false;
355 } else {
356 return true;
357 }
358 },
359 handler: 'initGPT',
360 },
361 ];
362
363 if (me.supportsWipeDisk) {
364 tbar.push('-');
365 tbar.push({
366 xtype: 'proxmoxButton',
367 text: gettext('Wipe Disk'),
368 parentXType: 'treepanel',
369 dangerous: true,
370 confirmMsg: function(rec) {
371 const data = rec.data;
372
373 let mainMessage = Ext.String.format(
374 gettext('Are you sure you want to wipe {0}?'),
375 data.devpath,
376 );
377 mainMessage += `<br> ${gettext('All data on the device will be lost!')}`;
378
379 const type = me.renderDiskType(data["disk-type"]);
380
381 let usage;
382 if (data.children.length > 0) {
383 const partitionUsage = data.children.map(
384 partition => me.renderDiskUsage(partition.used),
385 ).join(', ');
386 usage = `${gettext('Partitions')} (${partitionUsage})`;
387 } else {
388 usage = me.renderDiskUsage(data.used, undefined, rec);
389 }
390
391 const size = Proxmox.Utils.format_size(data.size);
392 const serial = Ext.String.htmlEncode(data.serial);
393
394 let additionalInfo = `${gettext('Type')}: ${type}<br>`;
395 additionalInfo += `${gettext('Usage')}: ${usage}<br>`;
396 additionalInfo += `${gettext('Size')}: ${size}<br>`;
397 additionalInfo += `${gettext('Serial')}: ${serial}`;
398
399 return `${mainMessage}<br><br>${additionalInfo}`;
400 },
401 disabled: true,
402 enableFn: function(rec) {
403 // TODO enable for partitions once they can be selected for ZFS,LVM,etc. creation
404 if (!rec || rec.data.parent) {
405 return false;
406 } else {
407 return true;
408 }
409 },
410 handler: 'wipeDisk',
411 });
412 }
413
414 me.tbar = tbar;
415
416 me.callParent();
417 },
418 });