]> git.proxmox.com Git - proxmox-widget-toolkit.git/commitdiff
add NetworkView.js and NetworkEdit.js
authorDietmar Maurer <dietmar@proxmox.com>
Tue, 31 Jan 2017 16:05:09 +0000 (17:05 +0100)
committerDietmar Maurer <dietmar@proxmox.com>
Tue, 31 Jan 2017 16:05:09 +0000 (17:05 +0100)
Copied from pve-manager

Makefile
Utils.js
node/NetworkEdit.js [new file with mode: 0644]
node/NetworkView.js [new file with mode: 0644]

index 8d1c7f5cc5a022a358f73fbb61af44373ebba92b..0491161653354d79064f1c76cdf92e8cbd25fe8d 100644 (file)
--- 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                \
index 86a5de868d97028bf559fd6963dda9539ecaf344..3e264c5c1feff930368ec0b39e8698d1129242b8 100644 (file)
--- 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 (file)
index 0000000..3df9dce
--- /dev/null
@@ -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 (file)
index 0000000..b80cefe
--- /dev/null
@@ -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("<pre>" + Ext.htmlEncode(changes) + "</pre>");
+               }
+           });
+       };
+
+       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 + "<br>"
+                                          + 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 + "<br>" + 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();
+    }
+});