]>
Commit | Line | Data |
---|---|---|
5a691a50 | 1 | Ext.define('PVE.lxc.NetworkInputPanel', { |
ef4ef788 | 2 | extend: 'Proxmox.panel.InputPanel', |
5a691a50 DM |
3 | alias: 'widget.pveLxcNetworkInputPanel', |
4 | ||
5 | insideWizard: false, | |
6 | ||
c8802a60 | 7 | onlineHelp: 'pct_container_network', |
7f1ac74c | 8 | |
5a691a50 | 9 | setNodename: function(nodename) { |
c511ca9c | 10 | let me = this; |
99541dcc | 11 | |
53e3ea84 | 12 | if (!nodename || me.nodename === nodename) { |
5a691a50 DM |
13 | return; |
14 | } | |
5a691a50 DM |
15 | me.nodename = nodename; |
16 | ||
c511ca9c TL |
17 | let bridgeSelector = me.query("[isFormField][name=bridge]")[0]; |
18 | bridgeSelector.setNodename(nodename); | |
5a691a50 | 19 | }, |
99541dcc | 20 | |
5a691a50 | 21 | onGetValues: function(values) { |
c511ca9c | 22 | let me = this; |
5a691a50 | 23 | |
c511ca9c | 24 | let id; |
d5e771ce | 25 | if (me.isCreate) { |
5a691a50 DM |
26 | id = values.id; |
27 | delete values.id; | |
28 | } else { | |
29 | id = me.ifname; | |
30 | } | |
c511ca9c TL |
31 | let newdata = {}; |
32 | if (id) { | |
33 | if (values.ipv6mode !== 'static') { | |
34 | values.ip6 = values.ipv6mode; | |
35 | } | |
36 | if (values.ipv4mode !== 'static') { | |
37 | values.ip = values.ipv4mode; | |
38 | } | |
39 | newdata[id] = PVE.Parser.printLxcNetwork(values); | |
84de645d | 40 | } |
5a691a50 DM |
41 | return newdata; |
42 | }, | |
43 | ||
8058410f | 44 | initComponent: function() { |
c511ca9c | 45 | let me = this; |
5a691a50 | 46 | |
c511ca9c | 47 | let cdata = {}; |
5a691a50 DM |
48 | if (me.insideWizard) { |
49 | me.ifname = 'net0'; | |
50 | cdata.name = 'eth0'; | |
d8e3d77c | 51 | me.dataCache = {}; |
5a691a50 | 52 | } |
53e3ea84 | 53 | cdata.firewall = me.insideWizard || me.isCreate; |
d8e3d77c TL |
54 | |
55 | if (!me.dataCache) { | |
56 | throw "no dataCache specified"; | |
57 | } | |
58 | ||
d5e771ce | 59 | if (!me.isCreate) { |
5a691a50 DM |
60 | if (!me.ifname) { |
61 | throw "no interface name specified"; | |
62 | } | |
63 | if (!me.dataCache[me.ifname]) { | |
64 | throw "no such interface '" + me.ifname + "'"; | |
65 | } | |
5a691a50 DM |
66 | cdata = PVE.Parser.parseLxcNetwork(me.dataCache[me.ifname]); |
67 | } | |
68 | ||
c511ca9c TL |
69 | for (let i = 0; i < 32; i++) { |
70 | let ifname = 'net' + i.toString(); | |
71 | if (me.isCreate && !me.dataCache[ifname]) { | |
72 | me.ifname = ifname; | |
2517c76b DC |
73 | break; |
74 | } | |
5a691a50 | 75 | } |
5a691a50 | 76 | |
7c7ae44f | 77 | me.column1 = [ |
c511ca9c TL |
78 | { |
79 | xtype: 'hidden', | |
80 | name: 'id', | |
81 | value: me.ifname, | |
82 | }, | |
5a691a50 DM |
83 | { |
84 | xtype: 'textfield', | |
85 | name: 'name', | |
160673c1 TL |
86 | fieldLabel: gettext('Name'), |
87 | emptyText: '(e.g., eth0)', | |
5a691a50 DM |
88 | allowBlank: false, |
89 | value: cdata.name, | |
90 | validator: function(value) { | |
c511ca9c | 91 | for (const [key, netRaw] of Object.entries(me.dataCache)) { |
5a691a50 | 92 | if (!key.match(/^net\d+/) || key === me.ifname) { |
c511ca9c | 93 | continue; |
5a691a50 | 94 | } |
c511ca9c | 95 | let net = PVE.Parser.parseLxcNetwork(netRaw); |
5a691a50 | 96 | if (net.name === value) { |
c511ca9c | 97 | return "interface name already in use"; |
5a691a50 | 98 | } |
ec0bd652 | 99 | } |
ec0bd652 | 100 | return true; |
f6710aac | 101 | }, |
5a691a50 DM |
102 | }, |
103 | { | |
104 | xtype: 'textfield', | |
105 | name: 'hwaddr', | |
106 | fieldLabel: gettext('MAC address'), | |
107 | vtype: 'MacAddress', | |
108 | value: cdata.hwaddr, | |
41bfbd0a | 109 | allowBlank: true, |
f6710aac | 110 | emptyText: 'auto', |
5a691a50 DM |
111 | }, |
112 | { | |
113 | xtype: 'PVE.form.BridgeSelector', | |
114 | name: 'bridge', | |
115 | nodename: me.nodename, | |
116 | fieldLabel: gettext('Bridge'), | |
117 | value: cdata.bridge, | |
f6710aac | 118 | allowBlank: false, |
5a691a50 DM |
119 | }, |
120 | { | |
121 | xtype: 'pveVlanField', | |
122 | name: 'tag', | |
f6710aac | 123 | value: cdata.tag, |
5a691a50 DM |
124 | }, |
125 | { | |
519ca7fa DM |
126 | xtype: 'numberfield', |
127 | name: 'rate', | |
128 | fieldLabel: gettext('Rate limit') + ' (MB/s)', | |
129 | minValue: 0, | |
130 | maxValue: 10*1024, | |
131 | value: cdata.rate, | |
132 | emptyText: 'unlimited', | |
f6710aac | 133 | allowBlank: true, |
519ca7fa DM |
134 | }, |
135 | { | |
896c0d50 | 136 | xtype: 'proxmoxcheckbox', |
5a691a50 DM |
137 | fieldLabel: gettext('Firewall'), |
138 | name: 'firewall', | |
f6710aac TL |
139 | value: cdata.firewall, |
140 | }, | |
5a691a50 DM |
141 | ]; |
142 | ||
c511ca9c | 143 | let dhcp4 = cdata.ip === 'dhcp'; |
5a691a50 DM |
144 | if (dhcp4) { |
145 | cdata.ip = ''; | |
146 | cdata.gw = ''; | |
147 | } | |
148 | ||
c511ca9c TL |
149 | let auto6 = cdata.ip6 === 'auto'; |
150 | let dhcp6 = cdata.ip6 === 'dhcp'; | |
5a691a50 DM |
151 | if (auto6 || dhcp6) { |
152 | cdata.ip6 = ''; | |
153 | cdata.gw6 = ''; | |
154 | } | |
2a4971d8 | 155 | |
5a691a50 DM |
156 | me.column2 = [ |
157 | { | |
158 | layout: { | |
159 | type: 'hbox', | |
f6710aac | 160 | align: 'middle', |
5a691a50 DM |
161 | }, |
162 | border: false, | |
163 | margin: '0 0 5 0', | |
5a691a50 DM |
164 | items: [ |
165 | { | |
166 | xtype: 'label', | |
f6710aac | 167 | text: 'IPv4:', // do not localize |
5a691a50 DM |
168 | }, |
169 | { | |
170 | xtype: 'radiofield', | |
171 | boxLabel: gettext('Static'), | |
172 | name: 'ipv4mode', | |
173 | inputValue: 'static', | |
174 | checked: !dhcp4, | |
175 | margin: '0 0 0 10', | |
176 | listeners: { | |
177 | change: function(cb, value) { | |
a2163454 | 178 | me.down('field[name=ip]').setEmptyText( |
d2021707 | 179 | value ? Proxmox.Utils.NoneText : "", |
a2163454 | 180 | ); |
5a691a50 DM |
181 | me.down('field[name=ip]').setDisabled(!value); |
182 | me.down('field[name=gw]').setDisabled(!value); | |
f6710aac TL |
183 | }, |
184 | }, | |
5a691a50 DM |
185 | }, |
186 | { | |
187 | xtype: 'radiofield', | |
2ce6111f | 188 | boxLabel: 'DHCP', // do not localize |
5a691a50 DM |
189 | name: 'ipv4mode', |
190 | inputValue: 'dhcp', | |
191 | checked: dhcp4, | |
f6710aac TL |
192 | margin: '0 0 0 10', |
193 | }, | |
194 | ], | |
5a691a50 DM |
195 | }, |
196 | { | |
197 | xtype: 'textfield', | |
198 | name: 'ip', | |
199 | vtype: 'IPCIDRAddress', | |
200 | value: cdata.ip, | |
b552db18 | 201 | emptyText: dhcp4 ? '' : Proxmox.Utils.NoneText, |
5a691a50 | 202 | disabled: dhcp4, |
f6710aac | 203 | fieldLabel: 'IPv4/CIDR', // do not localize |
5a691a50 DM |
204 | }, |
205 | { | |
206 | xtype: 'textfield', | |
207 | name: 'gw', | |
208 | value: cdata.gw, | |
209 | vtype: 'IPAddress', | |
210 | disabled: dhcp4, | |
2ce6111f | 211 | fieldLabel: gettext('Gateway') + ' (IPv4)', |
f6710aac | 212 | margin: '0 0 3 0', // override bottom margin to account for the menuseparator |
5a691a50 DM |
213 | }, |
214 | { | |
215 | xtype: 'menuseparator', | |
216 | height: '3', | |
f6710aac | 217 | margin: '0', |
5a691a50 DM |
218 | }, |
219 | { | |
220 | layout: { | |
221 | type: 'hbox', | |
f6710aac | 222 | align: 'middle', |
5a691a50 DM |
223 | }, |
224 | border: false, | |
225 | margin: '0 0 5 0', | |
5a691a50 DM |
226 | items: [ |
227 | { | |
228 | xtype: 'label', | |
f6710aac | 229 | text: 'IPv6:', // do not localize |
5a691a50 DM |
230 | }, |
231 | { | |
232 | xtype: 'radiofield', | |
233 | boxLabel: gettext('Static'), | |
234 | name: 'ipv6mode', | |
235 | inputValue: 'static', | |
236 | checked: !(auto6 || dhcp6), | |
237 | margin: '0 0 0 10', | |
238 | listeners: { | |
239 | change: function(cb, value) { | |
a2163454 | 240 | me.down('field[name=ip6]').setEmptyText( |
d2021707 | 241 | value ? Proxmox.Utils.NoneText : "", |
a2163454 | 242 | ); |
5a691a50 DM |
243 | me.down('field[name=ip6]').setDisabled(!value); |
244 | me.down('field[name=gw6]').setDisabled(!value); | |
f6710aac TL |
245 | }, |
246 | }, | |
5a691a50 DM |
247 | }, |
248 | { | |
249 | xtype: 'radiofield', | |
2ce6111f | 250 | boxLabel: 'DHCP', // do not localize |
5a691a50 DM |
251 | name: 'ipv6mode', |
252 | inputValue: 'dhcp', | |
253 | checked: dhcp6, | |
f6710aac | 254 | margin: '0 0 0 10', |
5a691a50 DM |
255 | }, |
256 | { | |
257 | xtype: 'radiofield', | |
2ce6111f | 258 | boxLabel: 'SLAAC', // do not localize |
5a691a50 DM |
259 | name: 'ipv6mode', |
260 | inputValue: 'auto', | |
261 | checked: auto6, | |
f6710aac TL |
262 | margin: '0 0 0 10', |
263 | }, | |
264 | ], | |
5a691a50 DM |
265 | }, |
266 | { | |
267 | xtype: 'textfield', | |
268 | name: 'ip6', | |
269 | value: cdata.ip6, | |
b552db18 | 270 | emptyText: dhcp6 || auto6 ? '' : Proxmox.Utils.NoneText, |
5a691a50 | 271 | vtype: 'IP6CIDRAddress', |
53e3ea84 | 272 | disabled: dhcp6 || auto6, |
f6710aac | 273 | fieldLabel: 'IPv6/CIDR', // do not localize |
5a691a50 DM |
274 | }, |
275 | { | |
276 | xtype: 'textfield', | |
277 | name: 'gw6', | |
278 | vtype: 'IP6Address', | |
279 | value: cdata.gw6, | |
53e3ea84 | 280 | disabled: dhcp6 || auto6, |
f6710aac TL |
281 | fieldLabel: gettext('Gateway') + ' (IPv6)', |
282 | }, | |
5a691a50 DM |
283 | ]; |
284 | ||
4e192f9d DT |
285 | me.advancedColumn1 = [ |
286 | { | |
287 | xtype: 'proxmoxintegerfield', | |
288 | fieldLabel: 'MTU', | |
289 | emptyText: gettext('Same as bridge'), | |
290 | name: 'mtu', | |
291 | value: cdata.mtu, | |
292 | minValue: 576, | |
293 | maxValue: 65535, | |
294 | }, | |
295 | ]; | |
296 | ||
5a691a50 | 297 | me.callParent(); |
f6710aac | 298 | }, |
5a691a50 | 299 | }); |
d5e771ce | 300 | |
5a691a50 | 301 | Ext.define('PVE.lxc.NetworkEdit', { |
9fccc702 | 302 | extend: 'Proxmox.window.Edit', |
5a691a50 DM |
303 | |
304 | isAdd: true, | |
305 | ||
8058410f | 306 | initComponent: function() { |
c511ca9c | 307 | let me = this; |
5a691a50 DM |
308 | |
309 | if (!me.dataCache) { | |
310 | throw "no dataCache specified"; | |
311 | } | |
5a691a50 DM |
312 | if (!me.nodename) { |
313 | throw "no node name specified"; | |
314 | } | |
315 | ||
5a691a50 DM |
316 | Ext.apply(me, { |
317 | subject: gettext('Network Device') + ' (veth)', | |
318 | digest: me.dataCache.digest, | |
c511ca9c TL |
319 | items: [ |
320 | { | |
321 | xtype: 'pveLxcNetworkInputPanel', | |
322 | ifname: me.ifname, | |
323 | nodename: me.nodename, | |
324 | dataCache: me.dataCache, | |
325 | isCreate: me.isCreate, | |
326 | }, | |
327 | ], | |
5a691a50 DM |
328 | }); |
329 | ||
330 | me.callParent(); | |
f6710aac | 331 | }, |
5a691a50 DM |
332 | }); |
333 | ||
334 | Ext.define('PVE.lxc.NetworkView', { | |
335 | extend: 'Ext.grid.GridPanel', | |
d5e771ce | 336 | alias: 'widget.pveLxcNetworkView', |
5a691a50 | 337 | |
ba93a9c6 DC |
338 | onlineHelp: 'pct_container_network', |
339 | ||
5a691a50 DM |
340 | dataCache: {}, // used to store result of last load |
341 | ||
af603c15 DC |
342 | stateful: true, |
343 | stateId: 'grid-lxc-network', | |
344 | ||
5a691a50 | 345 | load: function() { |
c511ca9c | 346 | let me = this; |
5a691a50 | 347 | |
e7ade592 | 348 | Proxmox.Utils.setErrorMask(me, true); |
5a691a50 | 349 | |
e7ade592 | 350 | Proxmox.Utils.API2Request({ |
5a691a50 DM |
351 | url: me.url, |
352 | failure: function(response, opts) { | |
e7ade592 | 353 | Proxmox.Utils.setErrorMask(me, gettext('Error') + ': ' + response.htmlStatus); |
5a691a50 DM |
354 | }, |
355 | success: function(response, opts) { | |
e7ade592 | 356 | Proxmox.Utils.setErrorMask(me, false); |
c511ca9c TL |
357 | let result = Ext.decode(response.responseText); |
358 | me.dataCache = result.data || {}; | |
359 | let records = []; | |
360 | for (const [key, value] of Object.entries(me.dataCache)) { | |
361 | if (key.match(/^net\d+/)) { | |
362 | let net = PVE.Parser.parseLxcNetwork(value); | |
363 | net.id = key; | |
364 | records.push(net); | |
5a691a50 | 365 | } |
c511ca9c | 366 | } |
5a691a50 | 367 | me.store.loadData(records); |
53e3ea84 | 368 | me.down('button[name=addButton]').setDisabled(records.length >= 32); |
f6710aac | 369 | }, |
5a691a50 DM |
370 | }); |
371 | }, | |
372 | ||
8058410f | 373 | initComponent: function() { |
c511ca9c | 374 | let me = this; |
5a691a50 | 375 | |
c511ca9c | 376 | let nodename = me.pveSelNode.data.node; |
5a691a50 DM |
377 | if (!nodename) { |
378 | throw "no node name specified"; | |
379 | } | |
380 | ||
c511ca9c | 381 | let vmid = me.pveSelNode.data.vmid; |
5a691a50 DM |
382 | if (!vmid) { |
383 | throw "no VM ID specified"; | |
384 | } | |
385 | ||
c511ca9c | 386 | let caps = Ext.state.Manager.get('GuiCap'); |
5a691a50 | 387 | |
c511ca9c | 388 | me.url = `/nodes/${nodename}/lxc/${vmid}/config`; |
5a691a50 | 389 | |
c511ca9c | 390 | let store = new Ext.data.Store({ |
5a691a50 DM |
391 | model: 'pve-lxc-network', |
392 | sorters: [ | |
393 | { | |
8058410f | 394 | property: 'id', |
f6710aac TL |
395 | direction: 'ASC', |
396 | }, | |
397 | ], | |
5a691a50 DM |
398 | }); |
399 | ||
c511ca9c | 400 | let sm = Ext.create('Ext.selection.RowModel', {}); |
5a691a50 | 401 | |
c511ca9c TL |
402 | let run_editor = function() { |
403 | let rec = sm.getSelection()[0]; | |
404 | if (!rec || !caps.vms['VM.Config.Network']) { | |
405 | return false; // disable default-propagation when triggered by grid dblclick | |
5a691a50 | 406 | } |
c511ca9c | 407 | Ext.create('PVE.lxc.NetworkEdit', { |
5a691a50 DM |
408 | url: me.url, |
409 | nodename: nodename, | |
410 | dataCache: me.dataCache, | |
f6710aac | 411 | ifname: rec.data.id, |
c511ca9c TL |
412 | listeners: { |
413 | destroy: () => me.load(), | |
414 | }, | |
415 | autoShow: true, | |
5a691a50 | 416 | }); |
c511ca9c | 417 | return undefined; // make eslint happier |
5a691a50 DM |
418 | }; |
419 | ||
f7993618 | 420 | Ext.apply(me, { |
5a691a50 DM |
421 | store: store, |
422 | selModel: sm, | |
5a691a50 DM |
423 | tbar: [ |
424 | { | |
425 | text: gettext('Add'), | |
2517c76b | 426 | name: 'addButton', |
5a691a50 DM |
427 | disabled: !caps.vms['VM.Config.Network'], |
428 | handler: function() { | |
c511ca9c | 429 | Ext.create('PVE.lxc.NetworkEdit', { |
5a691a50 DM |
430 | url: me.url, |
431 | nodename: nodename, | |
d5e771ce | 432 | isCreate: true, |
f6710aac | 433 | dataCache: me.dataCache, |
c511ca9c TL |
434 | listeners: { |
435 | destroy: () => me.load(), | |
436 | }, | |
437 | autoShow: true, | |
438 | }); | |
439 | }, | |
440 | }, | |
441 | { | |
442 | xtype: 'proxmoxButton', | |
443 | text: gettext('Remove'), | |
444 | disabled: true, | |
445 | selModel: sm, | |
446 | enableFn: function(rec) { | |
447 | return !!caps.vms['VM.Config.Network']; | |
448 | }, | |
449 | confirmMsg: ({ data }) => | |
450 | Ext.String.format(gettext('Are you sure you want to remove entry {0}'), `'${data.id}'`), | |
451 | handler: function(btn, e, rec) { | |
452 | Proxmox.Utils.API2Request({ | |
453 | url: me.url, | |
454 | waitMsgTarget: me, | |
455 | method: 'PUT', | |
456 | params: { | |
457 | 'delete': rec.data.id, | |
458 | digest: me.dataCache.digest, | |
459 | }, | |
460 | callback: () => me.load(), | |
461 | failure: (response, opts) => Ext.Msg.alert(gettext('Error'), response.htmlStatus), | |
5a691a50 | 462 | }); |
f6710aac | 463 | }, |
5a691a50 | 464 | }, |
c511ca9c TL |
465 | { |
466 | xtype: 'proxmoxButton', | |
467 | text: gettext('Edit'), | |
468 | selModel: sm, | |
469 | disabled: true, | |
470 | enableFn: rec => !!caps.vms['VM.Config.Network'], | |
471 | handler: run_editor, | |
472 | }, | |
5a691a50 DM |
473 | ], |
474 | columns: [ | |
475 | { | |
a29ff1bc | 476 | header: 'ID', |
5a691a50 | 477 | width: 50, |
f6710aac | 478 | dataIndex: 'id', |
5a691a50 DM |
479 | }, |
480 | { | |
481 | header: gettext('Name'), | |
482 | width: 80, | |
f6710aac | 483 | dataIndex: 'name', |
5a691a50 DM |
484 | }, |
485 | { | |
486 | header: gettext('Bridge'), | |
487 | width: 80, | |
f6710aac | 488 | dataIndex: 'bridge', |
5a691a50 DM |
489 | }, |
490 | { | |
491 | header: gettext('Firewall'), | |
492 | width: 80, | |
493 | dataIndex: 'firewall', | |
f6710aac | 494 | renderer: Proxmox.Utils.format_boolean, |
5a691a50 DM |
495 | }, |
496 | { | |
497 | header: gettext('VLAN Tag'), | |
498 | width: 80, | |
f6710aac | 499 | dataIndex: 'tag', |
5a691a50 DM |
500 | }, |
501 | { | |
502 | header: gettext('MAC address'), | |
503 | width: 110, | |
f6710aac | 504 | dataIndex: 'hwaddr', |
5a691a50 DM |
505 | }, |
506 | { | |
507 | header: gettext('IP address'), | |
508 | width: 150, | |
509 | dataIndex: 'ip', | |
510 | renderer: function(value, metaData, rec) { | |
511 | if (rec.data.ip && rec.data.ip6) { | |
512 | return rec.data.ip + "<br>" + rec.data.ip6; | |
513 | } else if (rec.data.ip6) { | |
514 | return rec.data.ip6; | |
515 | } else { | |
516 | return rec.data.ip; | |
517 | } | |
f6710aac | 518 | }, |
5a691a50 DM |
519 | }, |
520 | { | |
521 | header: gettext('Gateway'), | |
522 | width: 150, | |
523 | dataIndex: 'gw', | |
524 | renderer: function(value, metaData, rec) { | |
525 | if (rec.data.gw && rec.data.gw6) { | |
526 | return rec.data.gw + "<br>" + rec.data.gw6; | |
527 | } else if (rec.data.gw6) { | |
528 | return rec.data.gw6; | |
529 | } else { | |
530 | return rec.data.gw; | |
531 | } | |
f6710aac TL |
532 | }, |
533 | }, | |
4e192f9d DT |
534 | { |
535 | header: gettext('MTU'), | |
536 | width: 80, | |
537 | dataIndex: 'mtu', | |
538 | }, | |
5a691a50 DM |
539 | ], |
540 | listeners: { | |
4b488565 | 541 | activate: me.load, |
f6710aac TL |
542 | itemdblclick: run_editor, |
543 | }, | |
5a691a50 DM |
544 | }); |
545 | ||
546 | me.callParent(); | |
f6710aac | 547 | }, |
5a691a50 | 548 | }, function() { |
5a691a50 DM |
549 | Ext.define('pve-lxc-network', { |
550 | extend: "Ext.data.Model", | |
551 | proxy: { type: 'memory' }, | |
c511ca9c TL |
552 | fields: [ |
553 | 'id', | |
554 | 'name', | |
555 | 'hwaddr', | |
556 | 'bridge', | |
557 | 'ip', | |
558 | 'gw', | |
559 | 'ip6', | |
560 | 'gw6', | |
561 | 'tag', | |
562 | 'firewall', | |
4e192f9d | 563 | 'mtu', |
c511ca9c | 564 | ], |
5a691a50 | 565 | }); |
5a691a50 DM |
566 | }); |
567 |