]> git.proxmox.com Git - proxmox-widget-toolkit.git/blob - src/panel/DiskList.js
bump version to 4.2.3
[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', 'mounted',
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.expanded = true;
171 data.children = data.partitions ?? [];
172 for (let p of data.children) {
173 p['disk-type'] = 'partition';
174 p.iconCls = 'fa fa-fw fa-hdd-o x-fa-tree';
175 p.used = p.used === 'filesystem' ? p.filesystem : p.used;
176 p.parent = data.devpath;
177 p.children = [];
178 p.leaf = true;
179 }
180 data.iconCls = 'fa fa-fw fa-hdd-o x-fa-tree';
181 data.leaf = data.children.length === 0;
182
183 if (!data.parent) {
184 disks[data.devpath] = data;
185 }
186 }
187 for (const item of records) {
188 let data = item.data;
189 if (data.parent) {
190 disks[data.parent].leaf = false;
191 disks[data.parent].children.push(data);
192 }
193 }
194
195 let children = [];
196 for (const [_, device] of Object.entries(disks)) {
197 children.push(device);
198 }
199
200 view.setRootNode({
201 expanded: true,
202 children: children,
203 });
204
205 Proxmox.Utils.setErrorMask(view, false);
206 },
207 },
208
209 renderDiskType: function(v) {
210 if (v === undefined) return Proxmox.Utils.unknownText;
211 switch (v) {
212 case 'ssd': return 'SSD';
213 case 'hdd': return 'Hard Disk';
214 case 'usb': return 'USB';
215 default: return v;
216 }
217 },
218
219 renderDiskUsage: function(v, metaData, rec) {
220 let extendedInfo = '';
221 if (rec) {
222 let types = [];
223 if (rec.data['osdid-list'] && rec.data['osdid-list'].length > 0) {
224 for (const id of rec.data['osdid-list'].sort()) {
225 types.push(`OSD.${id.toString()}`);
226 }
227 } else if (rec.data.osdid !== undefined && rec.data.osdid >= 0) {
228 types.push(`OSD.${rec.data.osdid.toString()}`);
229 }
230 if (rec.data.journals > 0) {
231 types.push('Journal');
232 }
233 if (rec.data.db > 0) {
234 types.push('DB');
235 }
236 if (rec.data.wal > 0) {
237 types.push('WAL');
238 }
239 if (types.length > 0) {
240 extendedInfo = `, Ceph (${types.join(', ')})`;
241 }
242 }
243 const formatMap = {
244 'bios': 'BIOS boot',
245 'zfsreserved': 'ZFS reserved',
246 'efi': 'EFI',
247 'lvm': 'LVM',
248 'zfs': 'ZFS',
249 };
250
251 v = formatMap[v] || v;
252 return v ? `${v}${extendedInfo}` : Proxmox.Utils.noText;
253 },
254
255 columns: [
256 {
257 xtype: 'treecolumn',
258 header: gettext('Device'),
259 width: 150,
260 sortable: true,
261 dataIndex: 'devpath',
262 },
263 {
264 header: gettext('Type'),
265 width: 80,
266 sortable: true,
267 dataIndex: 'disk-type',
268 renderer: function(v) {
269 let me = this;
270 return me.renderDiskType(v);
271 },
272 },
273 {
274 header: gettext('Usage'),
275 width: 150,
276 sortable: false,
277 renderer: function(v, metaData, rec) {
278 let me = this;
279 return me.renderDiskUsage(v, metaData, rec);
280 },
281 dataIndex: 'used',
282 },
283 {
284 header: gettext('Size'),
285 width: 100,
286 align: 'right',
287 sortable: true,
288 renderer: Proxmox.Utils.format_size,
289 dataIndex: 'size',
290 },
291 {
292 header: 'GPT',
293 width: 60,
294 align: 'right',
295 renderer: Proxmox.Utils.format_boolean,
296 dataIndex: 'gpt',
297 },
298 {
299 header: gettext('Vendor'),
300 width: 100,
301 sortable: true,
302 hidden: true,
303 renderer: Ext.String.htmlEncode,
304 dataIndex: 'vendor',
305 },
306 {
307 header: gettext('Model'),
308 width: 200,
309 sortable: true,
310 renderer: Ext.String.htmlEncode,
311 dataIndex: 'model',
312 },
313 {
314 header: gettext('Serial'),
315 width: 200,
316 sortable: true,
317 renderer: Ext.String.htmlEncode,
318 dataIndex: 'serial',
319 },
320 {
321 header: 'S.M.A.R.T.',
322 width: 100,
323 sortable: true,
324 renderer: Ext.String.htmlEncode,
325 dataIndex: 'status',
326 },
327 {
328 header: gettext('Mounted'),
329 width: 60,
330 align: 'right',
331 renderer: Proxmox.Utils.format_boolean,
332 dataIndex: 'mounted',
333 },
334 {
335 header: gettext('Wearout'),
336 width: 90,
337 sortable: true,
338 align: 'right',
339 dataIndex: 'wearout',
340 renderer: function(value) {
341 if (Ext.isNumeric(value)) {
342 return (100 - value).toString() + '%';
343 }
344 return gettext('N/A');
345 },
346 },
347 ],
348
349 listeners: {
350 itemdblclick: 'openSmartWindow',
351 },
352
353 initComponent: function() {
354 let me = this;
355
356 let tbar = [
357 {
358 text: gettext('Reload'),
359 handler: 'reload',
360 },
361 {
362 xtype: 'proxmoxButton',
363 text: gettext('Show S.M.A.R.T. values'),
364 parentXType: 'treepanel',
365 disabled: true,
366 enableFn: function(rec) {
367 if (!rec || rec.data.parent) {
368 return false;
369 } else {
370 return true;
371 }
372 },
373 handler: 'openSmartWindow',
374 },
375 {
376 xtype: 'proxmoxButton',
377 text: gettext('Initialize Disk with GPT'),
378 parentXType: 'treepanel',
379 disabled: true,
380 enableFn: function(rec) {
381 if (!rec || rec.data.parent ||
382 (rec.data.used && rec.data.used !== 'unused')) {
383 return false;
384 } else {
385 return true;
386 }
387 },
388 handler: 'initGPT',
389 },
390 ];
391
392 if (me.supportsWipeDisk) {
393 tbar.push('-');
394 tbar.push({
395 xtype: 'proxmoxButton',
396 text: gettext('Wipe Disk'),
397 parentXType: 'treepanel',
398 dangerous: true,
399 confirmMsg: function(rec) {
400 const data = rec.data;
401
402 let mainMessage = Ext.String.format(
403 gettext('Are you sure you want to wipe {0}?'),
404 data.devpath,
405 );
406 mainMessage += `<br> ${gettext('All data on the device will be lost!')}`;
407
408 const type = me.renderDiskType(data["disk-type"]);
409
410 let usage;
411 if (data.children.length > 0) {
412 const partitionUsage = data.children.map(
413 partition => me.renderDiskUsage(partition.used),
414 ).join(', ');
415 usage = `${gettext('Partitions')} (${partitionUsage})`;
416 } else {
417 usage = me.renderDiskUsage(data.used, undefined, rec);
418 }
419
420 const size = Proxmox.Utils.format_size(data.size);
421 const serial = Ext.String.htmlEncode(data.serial);
422
423 let additionalInfo = `${gettext('Type')}: ${type}<br>`;
424 additionalInfo += `${gettext('Usage')}: ${usage}<br>`;
425 additionalInfo += `${gettext('Size')}: ${size}<br>`;
426 additionalInfo += `${gettext('Serial')}: ${serial}`;
427
428 return `${mainMessage}<br><br>${additionalInfo}`;
429 },
430 disabled: true,
431 handler: 'wipeDisk',
432 });
433 }
434
435 me.tbar = tbar;
436
437 me.callParent();
438 },
439 });