From a58001ddfc146cf84ba2933d9d08566a9b57cdfe Mon Sep 17 00:00:00 2001 From: Dietmar Maurer Date: Tue, 31 Jan 2017 17:05:09 +0100 Subject: [PATCH] add NetworkView.js and NetworkEdit.js Copied from pve-manager --- Makefile | 2 + Utils.js | 17 +++ node/NetworkEdit.js | 324 +++++++++++++++++++++++++++++++++++++++++ node/NetworkView.js | 347 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 690 insertions(+) create mode 100644 node/NetworkEdit.js create mode 100644 node/NetworkView.js diff --git a/Makefile b/Makefile index 8d1c7f5..0491161 100644 --- a/Makefile +++ b/Makefile @@ -23,6 +23,8 @@ JSSRC= \ panel/LogView.js \ window/Edit.js \ window/TaskViewer.js \ + node/NetworkEdit.js \ + node/NetworkView.js \ node/Tasks.js \ node/ServiceView.js \ node/TimeEdit.js \ diff --git a/Utils.js b/Utils.js index 86a5de8..3e264c5 100644 --- a/Utils.js +++ b/Utils.js @@ -41,6 +41,8 @@ Ext.define('Proxmox.Utils', { utilities: { // this singleton contains miscellaneous utilities + unknownText: gettext('Unknown'), + authOK: function() { return (Proxmox.UserName !== '') && Ext.util.Cookies.get(Proxmox.Setup.auth_cookie_name); }, @@ -214,6 +216,21 @@ Ext.define('Proxmox.Utils', { utilities: { } }, + network_iface_types: { + eth: gettext("Network Device"), + bridge: 'Linux Bridge', + bond: 'Linux Bond', + OVSBridge: 'OVS Bridge', + OVSBond: 'OVS Bond', + OVSPort: 'OVS Port', + OVSIntPort: 'OVS IntPort' + }, + + render_network_iface_type: function(value) { + return Proxmox.Utils.network_iface_types[value] || + Proxmox.Utils.unknownText; + }, + // you can override this to provide nicer task descriptions format_task_description: function(type, id) { return type + ' ' + id; diff --git a/node/NetworkEdit.js b/node/NetworkEdit.js new file mode 100644 index 0000000..3df9dce --- /dev/null +++ b/node/NetworkEdit.js @@ -0,0 +1,324 @@ +Ext.define('Proxmox.node.NetworkEdit', { + extend: 'Proxmox.window.Edit', + alias: ['widget.proxmoxNodeNetworkEdit'], + + initComponent : function() { + var me = this; + + if (!me.nodename) { + throw "no node name specified"; + } + + if (!me.iftype) { + throw "no network device type specified"; + } + + me.create = !me.iface; + + var iface_vtype; + + if (me.iftype === 'bridge') { + iface_vtype = 'BridgeName'; + } else if (me.iftype === 'bond') { + iface_vtype = 'BondName'; + } else if (me.iftype === 'eth' && !me.create) { + iface_vtype = 'InterfaceName'; + } else if (me.iftype === 'vlan' && !me.create) { + iface_vtype = 'InterfaceName'; + } else if (me.iftype === 'OVSBridge') { + iface_vtype = 'BridgeName'; + } else if (me.iftype === 'OVSBond') { + iface_vtype = 'BondName'; + } else if (me.iftype === 'OVSIntPort') { + iface_vtype = 'InterfaceName'; + } else if (me.iftype === 'OVSPort') { + iface_vtype = 'InterfaceName'; + } else { + console.log(me.iftype); + throw "unknown network device type specified"; + } + + me.subject = Proxmox.Utils.render_network_iface_type(me.iftype); + + var column2 = []; + + if (!(me.iftype === 'OVSIntPort' || me.iftype === 'OVSPort' || + me.iftype === 'OVSBond')) { + column2.push({ + xtype: 'pvecheckbox', + fieldLabel: gettext('Autostart'), + name: 'autostart', + uncheckedValue: 0, + checked: me.create ? true : undefined + }); + } + + if (me.iftype === 'bridge') { + column2.push({ + xtype: 'pvecheckbox', + fieldLabel: gettext('VLAN aware'), + name: 'bridge_vlan_aware', + deleteEmpty: !me.create + }); + column2.push({ + xtype: 'textfield', + fieldLabel: gettext('Bridge ports'), + name: 'bridge_ports' + }); + } else if (me.iftype === 'OVSBridge') { + column2.push({ + xtype: 'textfield', + fieldLabel: gettext('Bridge ports'), + name: 'ovs_ports' + }); + column2.push({ + xtype: 'textfield', + fieldLabel: gettext('OVS options'), + name: 'ovs_options' + }); + } else if (me.iftype === 'OVSPort' || me.iftype === 'OVSIntPort') { + column2.push({ + xtype: me.create ? 'PVE.form.BridgeSelector' : 'displayfield', + fieldLabel: Proxmox.Utils.render_network_iface_type('OVSBridge'), + allowBlank: false, + nodename: me.nodename, + bridgeType: 'OVSBridge', + name: 'ovs_bridge' + }); + column2.push({ + xtype: 'pveVlanField', + deleteEmpty: !me.create, + name: 'ovs_tag', + value: '' + }); + column2.push({ + xtype: 'textfield', + fieldLabel: gettext('OVS options'), + name: 'ovs_options' + }); + } else if (me.iftype === 'bond') { + column2.push({ + xtype: 'textfield', + fieldLabel: gettext('Slaves'), + name: 'slaves' + }); + + var policySelector = Ext.createWidget('bondPolicySelector', { + fieldLabel: gettext('Hash policy'), + name: 'bond_xmit_hash_policy', + deleteEmpty: !me.create, + disabled: true + }); + + column2.push({ + xtype: 'bondModeSelector', + fieldLabel: gettext('Mode'), + name: 'bond_mode', + value: me.create ? 'balance-rr' : undefined, + listeners: { + change: function(f, value) { + if (value === 'balance-xor' || + value === '802.3ad') { + policySelector.setDisabled(false); + } else { + policySelector.setDisabled(true); + policySelector.setValue(''); + } + } + }, + allowBlank: false + }); + + column2.push(policySelector); + + } else if (me.iftype === 'OVSBond') { + column2.push({ + xtype: me.create ? 'PVE.form.BridgeSelector' : 'displayfield', + fieldLabel: Proxmox.Utils.render_network_iface_type('OVSBridge'), + allowBlank: false, + nodename: me.nodename, + bridgeType: 'OVSBridge', + name: 'ovs_bridge' + }); + column2.push({ + xtype: 'pveVlanField', + deleteEmpty: !me.create, + name: 'ovs_tag', + value: '' + }); + column2.push({ + xtype: 'textfield', + fieldLabel: gettext('OVS options'), + name: 'ovs_options' + }); + } + + column2.push({ + xtype: 'textfield', + fieldLabel: gettext('Comment'), + allowBlank: true, + nodename: me.nodename, + name: 'comments' + }); + + var url; + var method; + + if (me.create) { + url = "/api2/extjs/nodes/" + me.nodename + "/network"; + method = 'POST'; + } else { + url = "/api2/extjs/nodes/" + me.nodename + "/network/" + me.iface; + method = 'PUT'; + } + + var column1 = [ + { + xtype: 'hiddenfield', + name: 'type', + value: me.iftype + }, + { + xtype: me.create ? 'textfield' : 'displayfield', + fieldLabel: gettext('Name'), + name: 'iface', + value: me.iface, + vtype: iface_vtype, + allowBlank: false + } + ]; + + if (me.iftype === 'OVSBond') { + column1.push( + { + xtype: 'bondModeSelector', + fieldLabel: gettext('Mode'), + name: 'bond_mode', + openvswitch: true, + value: me.create ? 'active-backup' : undefined, + allowBlank: false + }, + { + xtype: 'textfield', + fieldLabel: gettext('Slaves'), + name: 'ovs_bonds' + } + ); + } else { + + column1.push( + { + xtype: 'pvetextfield', + deleteEmpty: !me.create, + fieldLabel: gettext('IP address'), + vtype: 'IPAddress', + name: 'address' + }, + { + xtype: 'pvetextfield', + deleteEmpty: !me.create, + fieldLabel: gettext('Subnet mask'), + vtype: 'IPAddress', + name: 'netmask', + validator: function(value) { + /*jslint confusion: true */ + if (!me.items) { + return true; + } + var address = me.down('field[name=address]').getValue(); + if (value !== '') { + if (address === '') { + return "Subnet mask requires option 'IP address'"; + } + } else { + if (address !== '') { + return "Option 'IP address' requires a subnet mask"; + } + } + + return true; + } + }, + { + xtype: 'pvetextfield', + deleteEmpty: !me.create, + fieldLabel: gettext('Gateway'), + vtype: 'IPAddress', + name: 'gateway' + }, + { + xtype: 'pvetextfield', + deleteEmpty: !me.create, + fieldLabel: gettext('IPv6 address'), + vtype: 'IP6Address', + name: 'address6' + }, + { + xtype: 'pvetextfield', + deleteEmpty: !me.create, + fieldLabel: gettext('Prefix length'), + vtype: 'IP6PrefixLength', + name: 'netmask6', + value: '', + allowBlank: true, + validator: function(value) { + /*jslint confusion: true */ + if (!me.items) { + return true; + } + var address = me.down('field[name=address6]').getValue(); + if (value !== '') { + if (address === '') { + return "IPv6 prefix length requires option 'IPv6 address'"; + } + } else { + if (address !== '') { + return "Option 'IPv6 address' requires an IPv6 prefix length"; + } + } + + return true; + } + }, + { + xtype: 'pvetextfield', + deleteEmpty: !me.create, + fieldLabel: gettext('Gateway'), + vtype: 'IP6Address', + name: 'gateway6' + } + ); + } + + Ext.applyIf(me, { + url: url, + method: method, + items: { + xtype: 'inputpanel', + column1: column1, + column2: column2 + } + }); + + me.callParent(); + + if (me.create) { + me.down('field[name=iface]').setValue(me.iface_default); + } else { + me.load({ + success: function(response, options) { + var data = response.result.data; + if (data.type !== me.iftype) { + var msg = "Got unexpected device type"; + Ext.Msg.alert(gettext('Error'), msg, function() { + me.close(); + }); + return; + } + me.setValues(data); + me.isValid(); // trigger validation + } + }); + } + } +}); diff --git a/node/NetworkView.js b/node/NetworkView.js new file mode 100644 index 0000000..b80cefe --- /dev/null +++ b/node/NetworkView.js @@ -0,0 +1,347 @@ +Ext.define('proxmox-networks', { + extend: 'Ext.data.Model', + fields: [ + 'iface', 'type', 'active', 'autostart', + 'bridge_ports', 'slaves', + 'address', 'netmask', 'gateway', + 'address6', 'netmask6', 'gateway6', + 'comments' + ], + idProperty: 'iface' +}); + +Ext.define('Proxmox.node.NetworkView', { + extend: 'Ext.panel.Panel', + + alias: ['widget.proxmoxNodeNetworkView'], + + initComponent : function() { + var me = this; + + if (!me.nodename) { + throw "no node name specified"; + } + + var baseUrl = '/nodes/' + me.nodename + '/network'; + + var store = Ext.create('Ext.data.Store', { + model: 'proxmox-networks', + proxy: { + type: 'proxmox', + url: '/api2/json' + baseUrl + }, + sorters: [ + { + property : 'iface', + direction: 'ASC' + } + ] + }); + + var reload = function() { + var changeitem = me.down('#changes'); + Proxmox.Utils.API2Request({ + url: baseUrl, + failure: function(response, opts) { + changeitem.update(gettext('Error') + ': ' + response.htmlStatus); + store.loadData({}); + }, + success: function(response, opts) { + var result = Ext.decode(response.responseText); + store.loadData(result.data); + var changes = result.changes; + if (changes === undefined || changes === '') { + changes = gettext("No changes"); + } + changeitem.update("
" + Ext.htmlEncode(changes) + "
"); + } + }); + }; + + var run_editor = function() { + var grid = me.down('gridpanel'); + var sm = grid.getSelectionModel(); + var rec = sm.getSelection()[0]; + if (!rec) { + return; + } + + var win = Ext.create('Proxmox.node.NetworkEdit', { + nodename: me.nodename, + iface: rec.data.iface, + iftype: rec.data.type + }); + win.show(); + win.on('destroy', reload); + }; + + var edit_btn = new Ext.Button({ + text: gettext('Edit'), + disabled: true, + handler: run_editor + }); + + var del_btn = new Ext.Button({ + text: gettext('Remove'), + disabled: true, + handler: function(){ + var grid = me.down('gridpanel'); + var sm = grid.getSelectionModel(); + var rec = sm.getSelection()[0]; + if (!rec) { + return; + } + + var iface = rec.data.iface; + + Proxmox.Utils.API2Request({ + url: baseUrl + '/' + iface, + method: 'DELETE', + waitMsgTarget: me, + callback: function() { + reload(); + }, + failure: function(response, opts) { + Ext.Msg.alert(gettext('Error'), response.htmlStatus); + } + }); + } + }); + + var set_button_status = function() { + var grid = me.down('gridpanel'); + var sm = grid.getSelectionModel(); + var rec = sm.getSelection()[0]; + + edit_btn.setDisabled(!rec); + del_btn.setDisabled(!rec); + }; + + Proxmox.Utils.monStoreErrors(me, store); + + var render_ports = function(value, metaData, record) { + if (value === 'bridge') { + return record.data.bridge_ports; + } else if (value === 'bond') { + return record.data.slaves; + } else if (value === 'OVSBridge') { + return record.data.ovs_ports; + } else if (value === 'OVSBond') { + return record.data.ovs_bonds; + } + }; + + var find_next_iface_id = function(prefix) { + var next; + for (next = 0; next <= 9999; next++) { + if (!store.getById(prefix + next.toString())) { + break; + } + } + return prefix + next.toString(); + }; + + Ext.apply(me, { + layout: 'border', + tbar: [ + { + text: gettext('Create'), + menu: new Ext.menu.Menu({ + plain: true, + items: [ + { + text: Proxmox.Utils.render_network_iface_type('bridge'), + handler: function() { + var win = Ext.create('Proxmox.node.NetworkEdit', { + nodename: me.nodename, + iftype: 'bridge', + iface_default: find_next_iface_id('vmbr') + }); + win.on('destroy', reload); + win.show(); + } + }, + { + text: Proxmox.Utils.render_network_iface_type('bond'), + handler: function() { + var win = Ext.create('Proxmox.node.NetworkEdit', { + nodename: me.nodename, + iftype: 'bond', + iface_default: find_next_iface_id('bond') + }); + win.on('destroy', reload); + win.show(); + } + }, '-', + { + text: Proxmox.Utils.render_network_iface_type('OVSBridge'), + handler: function() { + var win = Ext.create('Proxmox.node.NetworkEdit', { + nodename: me.nodename, + iftype: 'OVSBridge', + iface_default: find_next_iface_id('vmbr') + }); + win.on('destroy', reload); + win.show(); + } + }, + { + text: Proxmox.Utils.render_network_iface_type('OVSBond'), + handler: function() { + var win = Ext.create('Proxmox.node.NetworkEdit', { + nodename: me.nodename, + iftype: 'OVSBond', + iface_default: find_next_iface_id('bond') + }); + win.on('destroy', reload); + win.show(); + } + }, + { + text: Proxmox.Utils.render_network_iface_type('OVSIntPort'), + handler: function() { + var win = Ext.create('Proxmox.node.NetworkEdit', { + nodename: me.nodename, + iftype: 'OVSIntPort' + }); + win.on('destroy', reload); + win.show(); + } + } + ] + }) + }, ' ', + { + text: gettext('Revert'), + handler: function() { + Proxmox.Utils.API2Request({ + url: baseUrl, + method: 'DELETE', + waitMsgTarget: me, + callback: function() { + reload(); + }, + failure: function(response, opts) { + Ext.Msg.alert(gettext('Error'), response.htmlStatus); + } + }); + } + }, + edit_btn, + del_btn + ], + items: [ + { + xtype: 'gridpanel', + stateful: true, + stateId: 'grid-node-network', + store: store, + region: 'center', + border: false, + columns: [ + { + header: gettext('Name'), + width: 100, + sortable: true, + dataIndex: 'iface' + }, + { + header: gettext('Type'), + width: 100, + sortable: true, + renderer: Proxmox.Utils.render_network_iface_type, + dataIndex: 'type' + }, + { + xtype: 'booleancolumn', + header: gettext('Active'), + width: 80, + sortable: true, + dataIndex: 'active', + trueText: 'Yes', + falseText: 'No', + undefinedText: 'No' + }, + { + xtype: 'booleancolumn', + header: gettext('Autostart'), + width: 80, + sortable: true, + dataIndex: 'autostart', + trueText: 'Yes', + falseText: 'No', + undefinedText: 'No' + }, + { + header: gettext('Ports/Slaves'), + dataIndex: 'type', + renderer: render_ports + }, + { + header: gettext('IP address'), + sortable: true, + dataIndex: 'address', + renderer: function(value, metaData, rec) { + if (rec.data.address && rec.data.address6) { + return rec.data.address + "
" + + rec.data.address6 + '/' + rec.data.netmask6; + } else if (rec.data.address6) { + return rec.data.address6 + '/' + rec.data.netmask6; + } else { + return rec.data.address; + } + } + }, + { + header: gettext('Subnet mask'), + sortable: true, + dataIndex: 'netmask' + }, + { + header: gettext('Gateway'), + sortable: true, + dataIndex: 'gateway', + renderer: function(value, metaData, rec) { + if (rec.data.gateway && rec.data.gateway6) { + return rec.data.gateway + "
" + rec.data.gateway6; + } else if (rec.data.gateway6) { + return rec.data.gateway6; + } else { + return rec.data.gateway; + } + } + }, + { + header: gettext('Comment'), + dataIndex: 'comments', + renderer: Ext.String.htmlEncode + } + ], + listeners: { + selectionchange: set_button_status, + itemdblclick: run_editor + } + }, + { + border: false, + region: 'south', + autoScroll: true, + itemId: 'changes', + tbar: [ + gettext('Pending changes') + ' (' + + gettext('Please reboot to activate changes') + ')' + ], + split: true, + bodyPadding: 5, + flex: 0.6, + html: gettext("No changes") + } + ], + listeners: { + activate: reload + } + }); + + me.callParent(); + } +}); -- 2.39.5