]>
Commit | Line | Data |
---|---|---|
1 | Ext.define('PVE.node.DiskList', { | |
2 | extend: 'Ext.grid.GridPanel', | |
3 | alias: 'widget.pveNodeDiskList', | |
4 | ||
5 | emptyText: gettext('No Disks found'), | |
6 | ||
7 | stateful: true, | |
8 | stateId: 'grid-node-disks', | |
9 | ||
10 | columns: [ | |
11 | { | |
12 | header: gettext('Device'), | |
13 | width: 150, | |
14 | sortable: true, | |
15 | dataIndex: 'devpath' | |
16 | }, | |
17 | { | |
18 | header: gettext('Type'), | |
19 | width: 80, | |
20 | sortable: true, | |
21 | dataIndex: 'type', | |
22 | renderer: function(v) { | |
23 | if (v === 'ssd') { | |
24 | return 'SSD'; | |
25 | } else if (v === 'hdd') { | |
26 | return 'Hard Disk'; | |
27 | } else if (v === 'usb'){ | |
28 | return 'USB'; | |
29 | } else { | |
30 | return gettext('Unknown'); | |
31 | } | |
32 | } | |
33 | }, | |
34 | { | |
35 | header: gettext('Usage'), | |
36 | width: 150, | |
37 | sortable: false, | |
38 | renderer: function(v, metaData, rec) { | |
39 | if (rec) { | |
40 | if (rec.data.osdid >= 0) { | |
41 | var bluestore = ''; | |
42 | if (rec.data.bluestore === 1) { | |
43 | bluestore = ' (Bluestore)'; | |
44 | } | |
45 | return "Ceph osd." + rec.data.osdid.toString() + bluestore; | |
46 | } | |
47 | ||
48 | var types = []; | |
49 | if (rec.data.journals > 0) { | |
50 | types.push('Journal'); | |
51 | } | |
52 | ||
53 | if (rec.data.db > 0) { | |
54 | types.push('DB'); | |
55 | } | |
56 | ||
57 | if (rec.data.wal > 0) { | |
58 | types.push('WAL'); | |
59 | } | |
60 | ||
61 | if (types.length > 0) { | |
62 | return 'Ceph (' + types.join(', ') + ')'; | |
63 | } | |
64 | } | |
65 | ||
66 | return v || Proxmox.Utils.noText; | |
67 | }, | |
68 | dataIndex: 'used' | |
69 | }, | |
70 | { | |
71 | header: gettext('Size'), | |
72 | width: 100, | |
73 | align: 'right', | |
74 | sortable: true, | |
75 | renderer: Proxmox.Utils.format_size, | |
76 | dataIndex: 'size' | |
77 | }, | |
78 | { | |
79 | header: 'GPT', | |
80 | width: 60, | |
81 | align: 'right', | |
82 | renderer: Proxmox.Utils.format_boolean, | |
83 | dataIndex: 'gpt' | |
84 | }, | |
85 | { | |
86 | header: gettext('Vendor'), | |
87 | width: 100, | |
88 | sortable: true, | |
89 | hidden: true, | |
90 | renderer: Ext.String.htmlEncode, | |
91 | dataIndex: 'vendor' | |
92 | }, | |
93 | { | |
94 | header: gettext('Model'), | |
95 | width: 200, | |
96 | sortable: true, | |
97 | renderer: Ext.String.htmlEncode, | |
98 | dataIndex: 'model' | |
99 | }, | |
100 | { | |
101 | header: gettext('Serial'), | |
102 | width: 200, | |
103 | sortable: true, | |
104 | renderer: Ext.String.htmlEncode, | |
105 | dataIndex: 'serial' | |
106 | }, | |
107 | { | |
108 | header: 'S.M.A.R.T.', | |
109 | width: 100, | |
110 | sortable: true, | |
111 | renderer: Ext.String.htmlEncode, | |
112 | dataIndex: 'health' | |
113 | }, | |
114 | { | |
115 | header: 'Wearout', | |
116 | width: 90, | |
117 | sortable: true, | |
118 | align: 'right', | |
119 | dataIndex: 'wearout', | |
120 | renderer: function(value) { | |
121 | if (Ext.isNumeric(value)) { | |
122 | return (100 - value).toString() + '%'; | |
123 | } | |
124 | return 'N/A'; | |
125 | } | |
126 | } | |
127 | ], | |
128 | ||
129 | initComponent: function() { | |
130 | var me = this; | |
131 | ||
132 | var nodename = me.pveSelNode.data.node; | |
133 | if (!nodename) { | |
134 | throw "no node name specified"; | |
135 | } | |
136 | ||
137 | var sm = Ext.create('Ext.selection.RowModel', {}); | |
138 | ||
139 | var store = Ext.create('Ext.data.Store', { | |
140 | storeid: 'node-disk-list' + nodename, | |
141 | model: 'node-disk-list', | |
142 | proxy: { | |
143 | type: 'proxmox', | |
144 | url: "/api2/json/nodes/" + nodename + "/disks/list" | |
145 | }, | |
146 | sorters: [ | |
147 | { | |
148 | property : 'dev', | |
149 | direction: 'ASC' | |
150 | } | |
151 | ] | |
152 | }); | |
153 | ||
154 | var reloadButton = Ext.create('Proxmox.button.Button', { | |
155 | text: gettext('Reload'), | |
156 | handler: function() { | |
157 | me.store.load(); | |
158 | } | |
159 | }); | |
160 | ||
161 | var smartButton = Ext.create('Proxmox.button.Button', { | |
162 | text: gettext('Show S.M.A.R.T. values'), | |
163 | selModel: sm, | |
164 | enableFn: function() { | |
165 | return !!sm.getSelection().length; | |
166 | }, | |
167 | disabled: true, | |
168 | handler: function() { | |
169 | var rec = sm.getSelection()[0]; | |
170 | ||
171 | var win = Ext.create('PVE.DiskSmartWindow', { | |
172 | nodename: nodename, | |
173 | dev: rec.data.devpath | |
174 | }); | |
175 | win.show(); | |
176 | } | |
177 | }); | |
178 | ||
179 | var initButton = Ext.create('Proxmox.button.Button', { | |
180 | text: gettext('Initialize Disk with GPT'), | |
181 | selModel: sm, | |
182 | enableFn: function() { | |
183 | var selection = sm.getSelection(); | |
184 | ||
185 | if (!selection.length || selection[0].data.used) { | |
186 | return false; | |
187 | } else { | |
188 | return true; | |
189 | } | |
190 | }, | |
191 | disabled: true, | |
192 | ||
193 | handler: function() { | |
194 | var rec = sm.getSelection()[0]; | |
195 | Proxmox.Utils.API2Request({ | |
196 | url: '/api2/extjs/nodes/' + nodename + '/disks/initgpt', | |
197 | waitMsgTarget: me, | |
198 | method: 'POST', | |
199 | params: { disk: rec.data.devpath}, | |
200 | failure: function(response, options) { | |
201 | Ext.Msg.alert(gettext('Error'), response.htmlStatus); | |
202 | }, | |
203 | success: function(response, options) { | |
204 | var upid = response.result.data; | |
205 | var win = Ext.create('Proxmox.window.TaskProgress', { | |
206 | upid: upid | |
207 | }); | |
208 | win.show(); | |
209 | } | |
210 | }); | |
211 | } | |
212 | }); | |
213 | ||
214 | me.loadCount = 1; // avoid duplicate loadmask | |
215 | Proxmox.Utils.monStoreErrors(me, store); | |
216 | ||
217 | Ext.apply(me, { | |
218 | store: store, | |
219 | selModel: sm, | |
220 | tbar: [ reloadButton, smartButton, initButton ], | |
221 | listeners: { | |
222 | itemdblclick: function() { | |
223 | var rec = sm.getSelection()[0]; | |
224 | ||
225 | var win = Ext.create('PVE.DiskSmartWindow', { | |
226 | nodename: nodename, | |
227 | dev: rec.data.devpath | |
228 | }); | |
229 | win.show(); | |
230 | } | |
231 | } | |
232 | }); | |
233 | ||
234 | ||
235 | me.callParent(); | |
236 | me.store.load(); | |
237 | } | |
238 | }, function() { | |
239 | ||
240 | Ext.define('node-disk-list', { | |
241 | extend: 'Ext.data.Model', | |
242 | fields: [ 'devpath', 'used', { name: 'size', type: 'number'}, | |
243 | {name: 'osdid', type: 'number'}, | |
244 | 'vendor', 'model', 'serial', 'rpm', 'type', 'health', 'wearout' ], | |
245 | idProperty: 'devpath' | |
246 | }); | |
247 | }); | |
248 | ||
249 | Ext.define('PVE.DiskSmartWindow', { | |
250 | extend: 'Ext.window.Window', | |
251 | alias: 'widget.pveSmartWindow', | |
252 | ||
253 | modal: true, | |
254 | ||
255 | items: [ | |
256 | { | |
257 | xtype: 'gridpanel', | |
258 | layout: { | |
259 | type: 'fit' | |
260 | }, | |
261 | emptyText: gettext('No S.M.A.R.T. Values'), | |
262 | scrollable: true, | |
263 | flex: 1, | |
264 | itemId: 'smarts', | |
265 | reserveScrollbar: true, | |
266 | columns: [ | |
267 | { text: 'ID', dataIndex: 'id', width: 50 }, | |
268 | { text: gettext('Attribute'), flex: 1, dataIndex: 'name', renderer: Ext.String.htmlEncode }, | |
269 | { text: gettext('Value'), dataIndex: 'raw', renderer: Ext.String.htmlEncode }, | |
270 | { text: gettext('Normalized'), dataIndex: 'value', width: 60}, | |
271 | { text: gettext('Threshold'), dataIndex: 'threshold', width: 60}, | |
272 | { text: gettext('Worst'), dataIndex: 'worst', width: 60}, | |
273 | { text: gettext('Flags'), dataIndex: 'flags'}, | |
274 | { text: gettext('Failing'), dataIndex: 'fail', renderer: Ext.String.htmlEncode } | |
275 | ] | |
276 | }, | |
277 | { | |
278 | xtype: 'component', | |
279 | itemId: 'text', | |
280 | layout: { | |
281 | type: 'fit' | |
282 | }, | |
283 | hidden: true, | |
284 | style: { | |
285 | 'background-color': 'white', | |
286 | 'white-space': 'pre', | |
287 | 'font-family': 'monospace' | |
288 | } | |
289 | } | |
290 | ], | |
291 | ||
292 | buttons: [ | |
293 | { | |
294 | text: gettext('Reload'), | |
295 | name: 'reload', | |
296 | handler: function() { | |
297 | var me = this; | |
298 | me.up('window').store.reload(); | |
299 | } | |
300 | }, | |
301 | { | |
302 | text: gettext('Close'), | |
303 | name: 'close', | |
304 | handler: function() { | |
305 | var me = this; | |
306 | me.up('window').close(); | |
307 | } | |
308 | } | |
309 | ], | |
310 | ||
311 | layout: { | |
312 | type: 'vbox', | |
313 | align: 'stretch' | |
314 | }, | |
315 | width: 800, | |
316 | height: 500, | |
317 | minWidth: 600, | |
318 | minHeight: 400, | |
319 | bodyPadding: 5, | |
320 | title: gettext('S.M.A.R.T. Values'), | |
321 | ||
322 | initComponent: function() { | |
323 | var me = this; | |
324 | ||
325 | var nodename = me.nodename; | |
326 | if (!nodename) { | |
327 | throw "no node name specified"; | |
328 | } | |
329 | ||
330 | var dev = me.dev; | |
331 | if (!dev) { | |
332 | throw "no device specified"; | |
333 | } | |
334 | ||
335 | me.store = Ext.create('Ext.data.Store', { | |
336 | model: 'disk-smart', | |
337 | proxy: { | |
338 | type: 'proxmox', | |
339 | url: "/api2/json/nodes/" + nodename + "/disks/smart?disk=" + dev | |
340 | } | |
341 | }); | |
342 | ||
343 | me.callParent(); | |
344 | var grid = me.down('#smarts'); | |
345 | var text = me.down('#text'); | |
346 | ||
347 | Proxmox.Utils.monStoreErrors(grid, me.store); | |
348 | me.mon(me.store, 'load', function(s, records, success) { | |
349 | if (success && records.length > 0) { | |
350 | var rec = records[0]; | |
351 | switch (rec.data.type) { | |
352 | case 'text': | |
353 | grid.setVisible(false); | |
354 | text.setVisible(true); | |
355 | text.setHtml(Ext.String.htmlEncode(rec.data.text)); | |
356 | break; | |
357 | default: | |
358 | // includes 'ata' | |
359 | // cannot use empty case because | |
360 | grid.setVisible(true); | |
361 | text.setVisible(false); | |
362 | grid.setStore(rec.attributes()); | |
363 | break; | |
364 | } | |
365 | } | |
366 | }); | |
367 | ||
368 | me.store.load(); | |
369 | } | |
370 | }, function() { | |
371 | ||
372 | Ext.define('disk-smart', { | |
373 | extend: 'Ext.data.Model', | |
374 | fields: [ | |
375 | { name:'health'}, | |
376 | { name:'type'}, | |
377 | { name:'text'} | |
378 | ], | |
379 | hasMany: {model: 'smart-attribute', name: 'attributes'} | |
380 | }); | |
381 | Ext.define('smart-attribute', { | |
382 | extend: 'Ext.data.Model', | |
383 | fields: [ | |
384 | { name:'id', type:'number' }, 'name', 'value', 'worst', 'threshold', 'flags', 'fail', 'raw' | |
385 | ] | |
386 | }); | |
387 | }); |