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