Ext.define('proxmox-networks', { extend: 'Ext.data.Model', fields: [ { name: 'active', type: 'boolean' }, 'address', 'address6', 'altnames', { name: 'autostart', type: 'boolean' }, 'bridge_ports', { name: 'bridge_vlan_aware', type: 'boolean' }, 'cidr', 'cidr6', 'comments', 'gateway', 'gateway6', 'iface', 'netmask', 'netmask6', 'slaves', 'type', 'vlan-id', 'vlan-raw-device', ], idProperty: 'iface', }); Ext.define('Proxmox.node.NetworkView', { extend: 'Ext.panel.Panel', alias: ['widget.proxmoxNodeNetworkView'], // defines what types of network devices we want to create // order is always the same types: ['bridge', 'bond', 'vlan', 'ovs'], showApplyBtn: false, // if true, the altnames column will be shown by default showAltNames: false, // for options passed down to the network edit window editOptions: {}, initComponent: function () { let me = this; if (!me.nodename) { throw 'no node name specified'; } let baseUrl = `/nodes/${me.nodename}/network`; // sort numbers in names by value, so that 'eno2' comes before 'eno10' let naturalCompare = (a, b) => (a ?? '').localeCompare(b ?? '', undefined, { numeric: true }); let getPortsOrSlaves = (data) => { if (data.type === 'bridge') { return data.bridge_ports; } else if (data.type === 'bond') { return data.slaves; } else if (data.type === 'OVSBridge') { return data.ovs_ports; } else if (data.type === 'OVSBond') { return data.ovs_bonds; } return ''; }; // shared with the name column, so that its header shows the initial sort order let ifaceSorter = Ext.create('Ext.util.Sorter', { id: 'iface', sorterFn: (a, b) => naturalCompare(a.data.iface, b.data.iface), }); let store = Ext.create('Ext.data.Store', { model: 'proxmox-networks', proxy: { type: 'proxmox', url: '/api2/json' + baseUrl, }, sorters: [ifaceSorter], }); let reload = function () { let changeitem = me.down('#changes'); let apply_btn = me.down('#apply'); let revert_btn = me.down('#revert'); Proxmox.Utils.API2Request({ url: baseUrl, failure: function (response, opts) { store.loadData({}); Proxmox.Utils.setErrorMask(me, response.htmlStatus); changeitem.update(''); changeitem.setHidden(true); }, success: function (response, opts) { let result = Ext.decode(response.responseText); store.loadData(result.data); let changes = result.changes; if (changes === undefined || changes === '') { changes = gettext('No changes'); changeitem.setHidden(true); apply_btn.setDisabled(true); revert_btn.setDisabled(true); } else { changeitem.update('
' + Ext.htmlEncode(changes) + ''); changeitem.setHidden(false); apply_btn.setDisabled(false); revert_btn.setDisabled(false); } }, }); }; let run_editor = function () { let grid = me.down('gridpanel'); let sm = grid.getSelectionModel(); let rec = sm.getSelection()[0]; if (!rec) { return; } Ext.create('Proxmox.node.NetworkEdit', { autoShow: true, nodename: me.nodename, iface: rec.data.iface, iftype: rec.data.type, showAltNames: me.showAltNames, ...me.editOptions, listeners: { destroy: () => reload(), }, }); }; let edit_btn = new Ext.Button({ text: gettext('Edit'), disabled: true, handler: run_editor, }); let sm = Ext.create('Ext.selection.RowModel', {}); let del_btn = new Proxmox.button.StdRemoveButton({ selModel: sm, getUrl: ({ data }) => `${baseUrl}/${data.iface}`, callback: () => reload(), }); let apply_btn = Ext.create('Proxmox.button.Button', { text: gettext('Apply Configuration'), itemId: 'apply', disabled: true, confirmMsg: 'Do you want to apply pending network changes?', hidden: !me.showApplyBtn, handler: function () { Proxmox.Utils.API2Request({ url: baseUrl, method: 'PUT', waitMsgTarget: me, success: function ({ result }, opts) { Ext.create('Proxmox.window.TaskProgress', { autoShow: true, taskDone: reload, upid: result.data, }); }, failure: (response) => Ext.Msg.alert(gettext('Error'), response.htmlStatus), }); }, }); let set_button_status = function () { let rec = sm.getSelection()[0]; edit_btn.setDisabled(!rec); del_btn.setDisabled(!rec); }; let findNextFreeInterfaceId = function (prefix) { for (let next = 0; next <= 9999; next++) { let id = `${prefix}${next.toString()}`; if (!store.getById(id)) { return id; } } Ext.Msg.alert('Error', `No free ID for ${prefix} found!`); return ''; }; let menu_items = []; let addEditWindowToMenu = (iType, iDefault) => { menu_items.push({ text: Proxmox.Utils.render_network_iface_type(iType), handler: () => Ext.create('Proxmox.node.NetworkEdit', { autoShow: true, nodename: me.nodename, iftype: iType, iface_default: findNextFreeInterfaceId(iDefault ?? iType), ...me.editOptions, onlineHelp: 'sysadmin_network_configuration', listeners: { destroy: () => reload(), }, }), }); }; if (me.types.indexOf('bridge') !== -1) { addEditWindowToMenu('bridge', 'vmbr'); } if (me.types.indexOf('bond') !== -1) { addEditWindowToMenu('bond'); } if (me.types.indexOf('vlan') !== -1) { addEditWindowToMenu('vlan'); } if (me.types.indexOf('ovs') !== -1) { if (menu_items.length > 0) { menu_items.push({ xtype: 'menuseparator' }); } addEditWindowToMenu('OVSBridge', 'vmbr'); addEditWindowToMenu('OVSBond', 'bond'); menu_items.push({ text: Proxmox.Utils.render_network_iface_type('OVSIntPort'), handler: () => Ext.create('Proxmox.node.NetworkEdit', { autoShow: true, nodename: me.nodename, iftype: 'OVSIntPort', listeners: { destroy: () => reload(), }, }), }); } let renderer_generator = function (fieldname) { return function (val, metaData, rec) { let tmp = []; if (rec.data[fieldname]) { tmp.push(rec.data[fieldname]); } if (rec.data[fieldname + '6']) { tmp.push(rec.data[fieldname + '6']); } return tmp.join('