Ext.define('PVE.lxc.NetworkInputPanel', { extend: 'Proxmox.panel.InputPanel', alias: 'widget.pveLxcNetworkInputPanel', insideWizard: false, onlineHelp: 'pct_container_network', setNodename: function(nodename) { let me = this; if (!nodename || me.nodename === nodename) { return; } me.nodename = nodename; let bridgeSelector = me.query("[isFormField][name=bridge]")[0]; bridgeSelector.setNodename(nodename); }, onGetValues: function(values) { let me = this; let id; if (me.isCreate) { id = values.id; delete values.id; } else { id = me.ifname; } let newdata = {}; if (id) { if (values.ipv6mode !== 'static') { values.ip6 = values.ipv6mode; } if (values.ipv4mode !== 'static') { values.ip = values.ipv4mode; } newdata[id] = PVE.Parser.printLxcNetwork(values); } return newdata; }, initComponent: function() { let me = this; let cdata = {}; if (me.insideWizard) { me.ifname = 'net0'; cdata.name = 'eth0'; me.dataCache = {}; } cdata.firewall = me.insideWizard || me.isCreate; if (!me.dataCache) { throw "no dataCache specified"; } if (!me.isCreate) { if (!me.ifname) { throw "no interface name specified"; } if (!me.dataCache[me.ifname]) { throw "no such interface '" + me.ifname + "'"; } cdata = PVE.Parser.parseLxcNetwork(me.dataCache[me.ifname]); } for (let i = 0; i < 32; i++) { let ifname = 'net' + i.toString(); if (me.isCreate && !me.dataCache[ifname]) { me.ifname = ifname; break; } } me.column1 = [ { xtype: 'hidden', name: 'id', value: me.ifname, }, { xtype: 'textfield', name: 'name', fieldLabel: gettext('Name'), emptyText: '(e.g., eth0)', allowBlank: false, value: cdata.name, validator: function(value) { for (const [key, netRaw] of Object.entries(me.dataCache)) { if (!key.match(/^net\d+/) || key === me.ifname) { continue; } let net = PVE.Parser.parseLxcNetwork(netRaw); if (net.name === value) { return "interface name already in use"; } } return true; }, }, { xtype: 'textfield', name: 'hwaddr', fieldLabel: gettext('MAC address'), vtype: 'MacAddress', value: cdata.hwaddr, allowBlank: true, emptyText: 'auto', }, { xtype: 'PVE.form.BridgeSelector', name: 'bridge', nodename: me.nodename, fieldLabel: gettext('Bridge'), value: cdata.bridge, allowBlank: false, }, { xtype: 'pveVlanField', name: 'tag', value: cdata.tag, }, { xtype: 'numberfield', name: 'rate', fieldLabel: gettext('Rate limit') + ' (MB/s)', minValue: 0, maxValue: 10*1024, value: cdata.rate, emptyText: 'unlimited', allowBlank: true, }, { xtype: 'proxmoxcheckbox', fieldLabel: gettext('Firewall'), name: 'firewall', value: cdata.firewall, }, ]; let dhcp4 = cdata.ip === 'dhcp'; if (dhcp4) { cdata.ip = ''; cdata.gw = ''; } let auto6 = cdata.ip6 === 'auto'; let dhcp6 = cdata.ip6 === 'dhcp'; if (auto6 || dhcp6) { cdata.ip6 = ''; cdata.gw6 = ''; } me.column2 = [ { layout: { type: 'hbox', align: 'middle', }, border: false, margin: '0 0 5 0', items: [ { xtype: 'label', text: 'IPv4:', // do not localize }, { xtype: 'radiofield', boxLabel: gettext('Static'), name: 'ipv4mode', inputValue: 'static', checked: !dhcp4, margin: '0 0 0 10', listeners: { change: function(cb, value) { me.down('field[name=ip]').setEmptyText( value ? Proxmox.Utils.NoneText : "", ); me.down('field[name=ip]').setDisabled(!value); me.down('field[name=gw]').setDisabled(!value); }, }, }, { xtype: 'radiofield', boxLabel: 'DHCP', // do not localize name: 'ipv4mode', inputValue: 'dhcp', checked: dhcp4, margin: '0 0 0 10', }, ], }, { xtype: 'textfield', name: 'ip', vtype: 'IPCIDRAddress', value: cdata.ip, emptyText: dhcp4 ? '' : Proxmox.Utils.NoneText, disabled: dhcp4, fieldLabel: 'IPv4/CIDR', // do not localize }, { xtype: 'textfield', name: 'gw', value: cdata.gw, vtype: 'IPAddress', disabled: dhcp4, fieldLabel: gettext('Gateway') + ' (IPv4)', margin: '0 0 3 0', // override bottom margin to account for the menuseparator }, { xtype: 'menuseparator', height: '3', margin: '0', }, { layout: { type: 'hbox', align: 'middle', }, border: false, margin: '0 0 5 0', items: [ { xtype: 'label', text: 'IPv6:', // do not localize }, { xtype: 'radiofield', boxLabel: gettext('Static'), name: 'ipv6mode', inputValue: 'static', checked: !(auto6 || dhcp6), margin: '0 0 0 10', listeners: { change: function(cb, value) { me.down('field[name=ip6]').setEmptyText( value ? Proxmox.Utils.NoneText : "", ); me.down('field[name=ip6]').setDisabled(!value); me.down('field[name=gw6]').setDisabled(!value); }, }, }, { xtype: 'radiofield', boxLabel: 'DHCP', // do not localize name: 'ipv6mode', inputValue: 'dhcp', checked: dhcp6, margin: '0 0 0 10', }, { xtype: 'radiofield', boxLabel: 'SLAAC', // do not localize name: 'ipv6mode', inputValue: 'auto', checked: auto6, margin: '0 0 0 10', }, ], }, { xtype: 'textfield', name: 'ip6', value: cdata.ip6, emptyText: dhcp6 || auto6 ? '' : Proxmox.Utils.NoneText, vtype: 'IP6CIDRAddress', disabled: dhcp6 || auto6, fieldLabel: 'IPv6/CIDR', // do not localize }, { xtype: 'textfield', name: 'gw6', vtype: 'IP6Address', value: cdata.gw6, disabled: dhcp6 || auto6, fieldLabel: gettext('Gateway') + ' (IPv6)', }, ]; me.callParent(); }, }); Ext.define('PVE.lxc.NetworkEdit', { extend: 'Proxmox.window.Edit', isAdd: true, initComponent: function() { let me = this; if (!me.dataCache) { throw "no dataCache specified"; } if (!me.nodename) { throw "no node name specified"; } Ext.apply(me, { subject: gettext('Network Device') + ' (veth)', digest: me.dataCache.digest, items: [ { xtype: 'pveLxcNetworkInputPanel', ifname: me.ifname, nodename: me.nodename, dataCache: me.dataCache, isCreate: me.isCreate, }, ], }); me.callParent(); }, }); Ext.define('PVE.lxc.NetworkView', { extend: 'Ext.grid.GridPanel', alias: 'widget.pveLxcNetworkView', onlineHelp: 'pct_container_network', dataCache: {}, // used to store result of last load stateful: true, stateId: 'grid-lxc-network', load: function() { let me = this; Proxmox.Utils.setErrorMask(me, true); Proxmox.Utils.API2Request({ url: me.url, failure: function(response, opts) { Proxmox.Utils.setErrorMask(me, gettext('Error') + ': ' + response.htmlStatus); }, success: function(response, opts) { Proxmox.Utils.setErrorMask(me, false); let result = Ext.decode(response.responseText); me.dataCache = result.data || {}; let records = []; for (const [key, value] of Object.entries(me.dataCache)) { if (key.match(/^net\d+/)) { let net = PVE.Parser.parseLxcNetwork(value); net.id = key; records.push(net); } } me.store.loadData(records); me.down('button[name=addButton]').setDisabled(records.length >= 32); }, }); }, initComponent: function() { let me = this; let nodename = me.pveSelNode.data.node; if (!nodename) { throw "no node name specified"; } let vmid = me.pveSelNode.data.vmid; if (!vmid) { throw "no VM ID specified"; } let caps = Ext.state.Manager.get('GuiCap'); me.url = `/nodes/${nodename}/lxc/${vmid}/config`; let store = new Ext.data.Store({ model: 'pve-lxc-network', sorters: [ { property: 'id', direction: 'ASC', }, ], }); let sm = Ext.create('Ext.selection.RowModel', {}); let run_editor = function() { let rec = sm.getSelection()[0]; if (!rec || !caps.vms['VM.Config.Network']) { return false; // disable default-propagation when triggered by grid dblclick } Ext.create('PVE.lxc.NetworkEdit', { url: me.url, nodename: nodename, dataCache: me.dataCache, ifname: rec.data.id, listeners: { destroy: () => me.load(), }, autoShow: true, }); return undefined; // make eslint happier }; Ext.apply(me, { store: store, selModel: sm, tbar: [ { text: gettext('Add'), name: 'addButton', disabled: !caps.vms['VM.Config.Network'], handler: function() { Ext.create('PVE.lxc.NetworkEdit', { url: me.url, nodename: nodename, isCreate: true, dataCache: me.dataCache, listeners: { destroy: () => me.load(), }, autoShow: true, }); }, }, { xtype: 'proxmoxButton', text: gettext('Remove'), disabled: true, selModel: sm, enableFn: function(rec) { return !!caps.vms['VM.Config.Network']; }, confirmMsg: ({ data }) => Ext.String.format(gettext('Are you sure you want to remove entry {0}'), `'${data.id}'`), handler: function(btn, e, rec) { Proxmox.Utils.API2Request({ url: me.url, waitMsgTarget: me, method: 'PUT', params: { 'delete': rec.data.id, digest: me.dataCache.digest, }, callback: () => me.load(), failure: (response, opts) => Ext.Msg.alert(gettext('Error'), response.htmlStatus), }); }, }, { xtype: 'proxmoxButton', text: gettext('Edit'), selModel: sm, disabled: true, enableFn: rec => !!caps.vms['VM.Config.Network'], handler: run_editor, }, ], columns: [ { header: 'ID', width: 50, dataIndex: 'id', }, { header: gettext('Name'), width: 80, dataIndex: 'name', }, { header: gettext('Bridge'), width: 80, dataIndex: 'bridge', }, { header: gettext('Firewall'), width: 80, dataIndex: 'firewall', renderer: Proxmox.Utils.format_boolean, }, { header: gettext('VLAN Tag'), width: 80, dataIndex: 'tag', }, { header: gettext('MAC address'), width: 110, dataIndex: 'hwaddr', }, { header: gettext('IP address'), width: 150, dataIndex: 'ip', renderer: function(value, metaData, rec) { if (rec.data.ip && rec.data.ip6) { return rec.data.ip + "
" + rec.data.ip6; } else if (rec.data.ip6) { return rec.data.ip6; } else { return rec.data.ip; } }, }, { header: gettext('Gateway'), width: 150, dataIndex: 'gw', renderer: function(value, metaData, rec) { if (rec.data.gw && rec.data.gw6) { return rec.data.gw + "
" + rec.data.gw6; } else if (rec.data.gw6) { return rec.data.gw6; } else { return rec.data.gw; } }, }, ], listeners: { activate: me.load, itemdblclick: run_editor, }, }); me.callParent(); }, }, function() { Ext.define('pve-lxc-network', { extend: "Ext.data.Model", proxy: { type: 'memory' }, fields: [ 'id', 'name', 'hwaddr', 'bridge', 'ip', 'gw', 'ip6', 'gw6', 'tag', 'firewall', ], }); });