]> git.proxmox.com Git - proxmox-widget-toolkit.git/blob - src/panel/DiskList.js
b1551394e40a371c281a6f6170f4482031ae663f
[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: function(response, options) {
103 Ext.Msg.alert(gettext('Error'), response.htmlStatus);
104 },
105 success: function(response, options) {
106 var upid = response.result.data;
107 var win = Ext.create('Proxmox.window.TaskProgress', {
108 upid: upid,
109 taskDone: function() {
110 me.reload();
111 },
112 });
113 win.show();
114 },
115 });
116 },
117
118 wipeDisk: function() {
119 let me = this;
120 let view = me.getView();
121 let selection = view.getSelection();
122 if (!selection || selection.length < 1) return;
123
124 let rec = selection[0];
125 Proxmox.Utils.API2Request({
126 url: `${view.exturl}/wipedisk`,
127 waitMsgTarget: view,
128 method: 'PUT',
129 params: { disk: rec.data.name },
130 failure: function(response, options) {
131 Ext.Msg.alert(gettext('Error'), response.htmlStatus);
132 },
133 success: function(response, options) {
134 var upid = response.result.data;
135 var win = Ext.create('Proxmox.window.TaskProgress', {
136 upid: upid,
137 taskDone: function() {
138 me.reload();
139 },
140 });
141 win.show();
142 },
143 });
144 },
145
146 init: function(view) {
147 let nodename = view.nodename || 'localhost';
148 view.baseurl = `/api2/json/nodes/${nodename}/disks`;
149 view.exturl = `/api2/extjs/nodes/${nodename}/disks`;
150
151 this.store = Ext.create('Ext.data.Store', {
152 model: 'pmx-disk-list',
153 });
154 this.store.on('load', this.onLoad, this);
155
156 Proxmox.Utils.monStoreErrors(view, this.store);
157 this.reload();
158 },
159
160 onLoad: function(store, records, success, operation) {
161 let me = this;
162 let view = this.getView();
163
164 if (!success) {
165 Proxmox.Utils.setErrorMask(
166 view,
167 Proxmox.Utils.getResponseErrorMessage(operation.getError()),
168 );
169 return;
170 }
171
172 let disks = {};
173
174 for (const item of records) {
175 let data = item.data;
176 data.leaf = true;
177 data.expanded = true;
178 data.children = [];
179 data.iconCls = 'fa fa-fw fa-hdd-o x-fa-tree';
180 if (!data.parent) {
181 disks[data.devpath] = data;
182 }
183 }
184 for (const item of records) {
185 let data = item.data;
186 if (data.parent) {
187 disks[data.parent].leaf = false;
188 disks[data.parent].children.push(data);
189 }
190 }
191
192 let children = [];
193 for (const [_, device] of Object.entries(disks)) {
194 children.push(device);
195 }
196
197 view.setRootNode({
198 expanded: true,
199 children: children,
200 });
201
202 Proxmox.Utils.setErrorMask(view, false);
203 },
204 },
205
206 renderDiskType: function(v) {
207 if (v === undefined) return Proxmox.Utils.unknownText;
208 switch (v) {
209 case 'ssd': return 'SSD';
210 case 'hdd': return 'Hard Disk';
211 case 'usb': return 'USB';
212 default: return v;
213 }
214 },
215
216 renderDiskUsage: function(v, metaData, rec) {
217 let extendedInfo = '';
218 if (rec) {
219 let types = [];
220 if (rec.data.osdid !== undefined && rec.data.osdid >= 0) {
221 types.push(`OSD.${rec.data.osdid.toString()}`);
222 }
223 if (rec.data.journals > 0) {
224 types.push('Journal');
225 }
226 if (rec.data.db > 0) {
227 types.push('DB');
228 }
229 if (rec.data.wal > 0) {
230 types.push('WAL');
231 }
232 if (types.length > 0) {
233 extendedInfo = `, Ceph (${types.join(', ')})`;
234 }
235 }
236 return v ? `${v}${extendedInfo}` : Proxmox.Utils.noText;
237 },
238
239 columns: [
240 {
241 xtype: 'treecolumn',
242 header: gettext('Device'),
243 width: 150,
244 sortable: true,
245 dataIndex: 'devpath',
246 },
247 {
248 header: gettext('Type'),
249 width: 80,
250 sortable: true,
251 dataIndex: 'disk-type',
252 renderer: function(v) {
253 let me = this;
254 return me.renderDiskType(v);
255 },
256 },
257 {
258 header: gettext('Usage'),
259 width: 150,
260 sortable: false,
261 renderer: function(v, metaData, rec) {
262 let me = this;
263 return me.renderDiskUsage(v, metaData, rec);
264 },
265 dataIndex: 'used',
266 },
267 {
268 header: gettext('Size'),
269 width: 100,
270 align: 'right',
271 sortable: true,
272 renderer: Proxmox.Utils.format_size,
273 dataIndex: 'size',
274 },
275 {
276 header: 'GPT',
277 width: 60,
278 align: 'right',
279 renderer: Proxmox.Utils.format_boolean,
280 dataIndex: 'gpt',
281 },
282 {
283 header: gettext('Vendor'),
284 width: 100,
285 sortable: true,
286 hidden: true,
287 renderer: Ext.String.htmlEncode,
288 dataIndex: 'vendor',
289 },
290 {
291 header: gettext('Model'),
292 width: 200,
293 sortable: true,
294 renderer: Ext.String.htmlEncode,
295 dataIndex: 'model',
296 },
297 {
298 header: gettext('Serial'),
299 width: 200,
300 sortable: true,
301 renderer: Ext.String.htmlEncode,
302 dataIndex: 'serial',
303 },
304 {
305 header: 'S.M.A.R.T.',
306 width: 100,
307 sortable: true,
308 renderer: Ext.String.htmlEncode,
309 dataIndex: 'status',
310 },
311 {
312 header: 'Wearout',
313 width: 90,
314 sortable: true,
315 align: 'right',
316 dataIndex: 'wearout',
317 renderer: function(value) {
318 if (Ext.isNumeric(value)) {
319 return (100 - value).toString() + '%';
320 }
321 return 'N/A';
322 },
323 },
324 ],
325
326 listeners: {
327 itemdblclick: 'openSmartWindow',
328 },
329
330 initComponent: function() {
331 let me = this;
332
333 let tbar = [
334 {
335 text: gettext('Reload'),
336 handler: 'reload',
337 },
338 {
339 xtype: 'proxmoxButton',
340 text: gettext('Show S.M.A.R.T. values'),
341 parentXType: 'treepanel',
342 disabled: true,
343 enableFn: function(rec) {
344 if (!rec || rec.data.parent) {
345 return false;
346 } else {
347 return true;
348 }
349 },
350 handler: 'openSmartWindow',
351 },
352 {
353 xtype: 'proxmoxButton',
354 text: gettext('Initialize Disk with GPT'),
355 parentXType: 'treepanel',
356 disabled: true,
357 enableFn: function(rec) {
358 if (!rec || rec.data.parent ||
359 (rec.data.used && rec.data.used !== 'unused')) {
360 return false;
361 } else {
362 return true;
363 }
364 },
365 handler: 'initGPT',
366 },
367 ];
368
369 if (me.supportsWipeDisk) {
370 tbar.push('-');
371 tbar.push({
372 xtype: 'proxmoxButton',
373 text: gettext('Wipe Disk'),
374 parentXType: 'treepanel',
375 dangerous: true,
376 confirmMsg: function(rec) {
377 const data = rec.data;
378
379 let mainMessage = Ext.String.format(
380 gettext('Are you sure you want to wipe {0}?'),
381 data.devpath,
382 );
383 mainMessage += `<br> ${gettext('All data on the device will be lost!')}`;
384
385 const type = me.renderDiskType(data["disk-type"]);
386
387 let usage;
388 if (data.children.length > 0) {
389 const partitionUsage = data.children.map(
390 partition => me.renderDiskUsage(partition.used),
391 ).join(', ');
392 usage = `${gettext('Partitions')} (${partitionUsage})`;
393 } else {
394 usage = me.renderDiskUsage(data.used, undefined, rec);
395 }
396
397 const size = Proxmox.Utils.format_size(data.size);
398 const serial = Ext.String.htmlEncode(data.serial);
399
400 let additionalInfo = `${gettext('Type')}: ${type}<br>`;
401 additionalInfo += `${gettext('Usage')}: ${usage}<br>`;
402 additionalInfo += `${gettext('Size')}: ${size}<br>`;
403 additionalInfo += `${gettext('Serial')}: ${serial}`;
404
405 return `${mainMessage}<br><br>${additionalInfo}`;
406 },
407 disabled: true,
408 enableFn: function(rec) {
409 // TODO enable for partitions once they can be selected for ZFS,LVM,etc. creation
410 if (!rec || rec.data.parent) {
411 return false;
412 } else {
413 return true;
414 }
415 },
416 handler: 'wipeDisk',
417 });
418 }
419
420 me.tbar = tbar;
421
422 me.callParent();
423 },
424 });