]> git.proxmox.com Git - pve-manager.git/blob - www/manager6/lxc/Resources.js
ui: lxc/qemu: add disk reassign and action submenu
[pve-manager.git] / www / manager6 / lxc / Resources.js
1 Ext.define('PVE.lxc.RessourceView', {
2 extend: 'Proxmox.grid.PendingObjectGrid',
3 alias: ['widget.pveLxcRessourceView'],
4
5 onlineHelp: 'pct_configuration',
6
7 renderKey: function(key, metaData, rec, rowIndex, colIndex, store) {
8 let me = this;
9 let rowdef = me.rows[key] || {};
10
11 let txt = rowdef.header || key;
12 let icon = '';
13
14 metaData.tdAttr = "valign=middle";
15 if (rowdef.tdCls) {
16 metaData.tdCls = rowdef.tdCls;
17 } else if (rowdef.iconCls) {
18 icon = `<i class='pve-grid-fa fa fa-fw fa-${rowdef.iconCls}'></i>`;
19 metaData.tdCls += " pve-itype-fa";
20 }
21 // only return icons in grid but not remove dialog
22 if (rowIndex !== undefined) {
23 return icon + txt;
24 } else {
25 return txt;
26 }
27 },
28
29 initComponent: function() {
30 var me = this;
31 let confid;
32
33 var nodename = me.pveSelNode.data.node;
34 if (!nodename) {
35 throw "no node name specified";
36 }
37
38 var vmid = me.pveSelNode.data.vmid;
39 if (!vmid) {
40 throw "no VM ID specified";
41 }
42
43 var caps = Ext.state.Manager.get('GuiCap');
44 var diskCap = caps.vms['VM.Config.Disk'];
45
46 var mpeditor = caps.vms['VM.Config.Disk'] ? 'PVE.lxc.MountPointEdit' : undefined;
47
48 var rows = {
49 memory: {
50 header: gettext('Memory'),
51 editor: caps.vms['VM.Config.Memory'] ? 'PVE.lxc.MemoryEdit' : undefined,
52 defaultValue: 512,
53 tdCls: 'pmx-itype-icon-memory',
54 group: 1,
55 renderer: function(value) {
56 return Proxmox.Utils.format_size(value*1024*1024);
57 },
58 },
59 swap: {
60 header: gettext('Swap'),
61 editor: caps.vms['VM.Config.Memory'] ? 'PVE.lxc.MemoryEdit' : undefined,
62 defaultValue: 512,
63 iconCls: 'refresh',
64 group: 2,
65 renderer: function(value) {
66 return Proxmox.Utils.format_size(value*1024*1024);
67 },
68 },
69 cores: {
70 header: gettext('Cores'),
71 editor: caps.vms['VM.Config.CPU'] ? 'PVE.lxc.CPUEdit' : undefined,
72 defaultValue: '',
73 tdCls: 'pmx-itype-icon-processor',
74 group: 3,
75 renderer: function(value) {
76 var cpulimit = me.getObjectValue('cpulimit');
77 var cpuunits = me.getObjectValue('cpuunits');
78 var res;
79 if (value) {
80 res = value;
81 } else {
82 res = gettext('unlimited');
83 }
84
85 if (cpulimit) {
86 res += ' [cpulimit=' + cpulimit + ']';
87 }
88
89 if (cpuunits) {
90 res += ' [cpuunits=' + cpuunits + ']';
91 }
92 return res;
93 },
94 },
95 rootfs: {
96 header: gettext('Root Disk'),
97 defaultValue: Proxmox.Utils.noneText,
98 editor: mpeditor,
99 iconCls: 'hdd-o',
100 group: 4,
101 },
102 cpulimit: {
103 visible: false,
104 },
105 cpuunits: {
106 visible: false,
107 },
108 unprivileged: {
109 visible: false,
110 },
111 };
112
113 PVE.Utils.forEachMP(function(bus, i) {
114 confid = bus + i;
115 var group = 5;
116 var header;
117 if (bus === 'mp') {
118 header = gettext('Mount Point') + ' (' + confid + ')';
119 } else {
120 header = gettext('Unused Disk') + ' ' + i;
121 group += 1;
122 }
123 rows[confid] = {
124 group: group,
125 order: i,
126 tdCls: 'pve-itype-icon-storage',
127 editor: mpeditor,
128 header: header,
129 };
130 }, true);
131
132 var baseurl = 'nodes/' + nodename + '/lxc/' + vmid + '/config';
133
134 me.selModel = Ext.create('Ext.selection.RowModel', {});
135
136 var run_resize = function() {
137 var rec = me.selModel.getSelection()[0];
138 if (!rec) {
139 return;
140 }
141
142 var win = Ext.create('PVE.window.MPResize', {
143 disk: rec.data.key,
144 nodename: nodename,
145 vmid: vmid,
146 });
147
148 win.show();
149 };
150
151 var run_remove = function(b, e, rec) {
152 Proxmox.Utils.API2Request({
153 url: '/api2/extjs/' + baseurl,
154 waitMsgTarget: me,
155 method: 'PUT',
156 params: {
157 'delete': rec.data.key,
158 },
159 failure: function(response, opts) {
160 Ext.Msg.alert('Error', response.htmlStatus);
161 },
162 });
163 };
164
165 let run_move = function() {
166 let rec = me.selModel.getSelection()[0];
167 if (!rec) {
168 return;
169 }
170
171 var win = Ext.create('PVE.window.HDMove', {
172 disk: rec.data.key,
173 nodename: nodename,
174 vmid: vmid,
175 type: 'lxc',
176 });
177
178 win.show();
179
180 win.on('destroy', me.reload, me);
181 };
182
183 let run_reassign = function() {
184 let rec = me.selModel.getSelection()[0];
185 if (!rec) {
186 return;
187 }
188
189 Ext.create('PVE.window.HDReassign', {
190 disk: rec.data.key,
191 nodename: nodename,
192 autoShow: true,
193 vmid: vmid,
194 type: 'lxc',
195 listeners: {
196 destroy: () => me.reload(),
197 },
198 });
199 };
200
201 var edit_btn = new Proxmox.button.Button({
202 text: gettext('Edit'),
203 selModel: me.selModel,
204 disabled: true,
205 enableFn: function(rec) {
206 if (!rec) {
207 return false;
208 }
209 var rowdef = rows[rec.data.key];
210 return !!rowdef.editor;
211 },
212 handler: function() { me.run_editor(); },
213 });
214
215 var remove_btn = new Proxmox.button.Button({
216 text: gettext('Remove'),
217 defaultText: gettext('Remove'),
218 altText: gettext('Detach'),
219 selModel: me.selModel,
220 disabled: true,
221 dangerous: true,
222 confirmMsg: function(rec) {
223 let warn = Ext.String.format(gettext('Are you sure you want to remove entry {0}'));
224 if (this.text === this.altText) {
225 warn = gettext('Are you sure you want to detach entry {0}');
226 }
227 let rendered = me.renderKey(rec.data.key, {}, rec);
228 let msg = Ext.String.format(warn, `'${rendered}'`);
229
230 if (rec.data.key.match(/^unused\d+$/)) {
231 msg += " " + gettext('This will permanently erase all data.');
232 }
233 return msg;
234 },
235 handler: run_remove,
236 listeners: {
237 render: function(btn) {
238 // hack: calculate the max button width on first display to prevent the whole
239 // toolbar to move when we switch between the "Remove" and "Detach" labels
240 let def = btn.getSize().width;
241
242 btn.setText(btn.altText);
243 let alt = btn.getSize().width;
244
245 btn.setText(btn.defaultText);
246
247 let optimal = alt > def ? alt : def;
248 btn.setSize({ width: optimal });
249 },
250 },
251 });
252
253 let move_menuitem = new Ext.menu.Item({
254 text: gettext('Move Storage'),
255 tooltip: gettext('Move volume to another storage'),
256 iconCls: 'fa fa-database',
257 selModel: me.selModel,
258 handler: run_move,
259 });
260
261 let reassign_menuitem = new Ext.menu.Item({
262 text: gettext('Reassign Owner'),
263 tooltip: gettext('Reassign volume to another CT'),
264 iconCls: 'fa fa-cube',
265 handler: run_reassign,
266 reference: 'reassing_item',
267 });
268
269 let resize_menuitem = new Ext.menu.Item({
270 text: gettext('Resize'),
271 iconCls: 'fa fa-plus',
272 selModel: me.selModel,
273 handler: run_resize,
274 });
275
276 let volumeaction_btn = new Proxmox.button.Button({
277 text: gettext('Volume Action'),
278 disabled: true,
279 menu: {
280 items: [
281 move_menuitem,
282 reassign_menuitem,
283 resize_menuitem,
284 ],
285 },
286 });
287
288 var revert_btn = new PVE.button.PendingRevert();
289
290 var set_button_status = function() {
291 var rec = me.selModel.getSelection()[0];
292
293 if (!rec) {
294 edit_btn.disable();
295 remove_btn.disable();
296 volumeaction_btn.disable();
297 revert_btn.disable();
298 return;
299 }
300 var key = rec.data.key;
301 var value = rec.data.value;
302 var rowdef = rows[key];
303
304 var pending = rec.data.delete || me.hasPendingChanges(key);
305 let isRootFS = key === 'rootfs';
306 let isDisk = isRootFS || key.match(/^(mp|unused)\d+/);
307 var isUnusedDisk = key.match(/^unused\d+/);
308 var isUsedDisk = isDisk && !isUnusedDisk;
309
310 var noedit = rec.data.delete || !rowdef.editor;
311 if (!noedit && Proxmox.UserName !== 'root@pam' && key.match(/^mp\d+$/)) {
312 var mp = PVE.Parser.parseLxcMountPoint(value);
313 if (mp.type !== 'volume') {
314 noedit = true;
315 }
316 }
317 edit_btn.setDisabled(noedit);
318
319 volumeaction_btn.setDisabled(!isDisk || !diskCap);
320 move_menuitem.setDisabled(isUnusedDisk);
321 reassign_menuitem.setDisabled(isRootFS);
322 resize_menuitem.setDisabled(isUnusedDisk);
323
324 remove_btn.setDisabled(!isDisk || isRootFS || !diskCap || pending);
325 revert_btn.setDisabled(!pending);
326
327 remove_btn.setText(isUsedDisk ? remove_btn.altText : remove_btn.defaultText);
328 };
329
330 var sorterFn = function(rec1, rec2) {
331 var v1 = rec1.data.key;
332 var v2 = rec2.data.key;
333 var g1 = rows[v1].group || 0;
334 var g2 = rows[v2].group || 0;
335 var order1 = rows[v1].order || 0;
336 var order2 = rows[v2].order || 0;
337
338 if (g1 - g2 !== 0) {
339 return g1 - g2;
340 }
341
342 if (order1 - order2 !== 0) {
343 return order1 - order2;
344 }
345
346 if (v1 > v2) {
347 return 1;
348 } else if (v1 < v2) {
349 return -1;
350 } else {
351 return 0;
352 }
353 };
354
355 Ext.apply(me, {
356 url: "/api2/json/nodes/" + nodename + "/lxc/" + vmid + "/pending",
357 selModel: me.selModel,
358 interval: 2000,
359 cwidth1: 170,
360 tbar: [
361 {
362 text: gettext('Add'),
363 menu: new Ext.menu.Menu({
364 items: [
365 {
366 text: gettext('Mount Point'),
367 iconCls: 'fa fa-fw fa-hdd-o black',
368 disabled: !caps.vms['VM.Config.Disk'],
369 handler: function() {
370 Ext.create('PVE.lxc.MountPointEdit', {
371 autoShow: true,
372 url: `/api2/extjs/${baseurl}`,
373 unprivileged: me.getObjectValue('unprivileged'),
374 pveSelNode: me.pveSelNode,
375 listeners: {
376 destroy: () => me.reload(),
377 },
378 });
379 },
380 },
381 ],
382 }),
383 },
384 edit_btn,
385 remove_btn,
386 volumeaction_btn,
387 revert_btn,
388 ],
389 rows: rows,
390 sorterFn: sorterFn,
391 editorConfig: {
392 pveSelNode: me.pveSelNode,
393 url: '/api2/extjs/' + baseurl,
394 },
395 listeners: {
396 itemdblclick: me.run_editor,
397 selectionchange: set_button_status,
398 },
399 });
400
401 me.callParent();
402
403 me.on('activate', me.rstore.startUpdate);
404 me.on('destroy', me.rstore.stopUpdate);
405 me.on('deactivate', me.rstore.stopUpdate);
406
407 me.mon(me.getStore(), 'datachanged', function() {
408 set_button_status();
409 });
410
411 Ext.apply(me.editorConfig, { unprivileged: me.getObjectValue('unprivileged') });
412 },
413 });