it's possible to add/edit/remove mappings here, with a cluster
wide view on the mappings and validity.

to do that, we have to to an api call for each node, since
we don't have the pci status synced across them.

Signed-off-by: Dominik Csapak <d.csa...@proxmox.com>
---
 www/manager6/Makefile           |   1 +
 www/manager6/dc/Config.js       |  18 +-
 www/manager6/dc/HardwareView.js | 314 ++++++++++++++++++++++++++++++++
 3 files changed, 331 insertions(+), 2 deletions(-)
 create mode 100644 www/manager6/dc/HardwareView.js

diff --git a/www/manager6/Makefile b/www/manager6/Makefile
index f6687ce5..e0f92169 100644
--- a/www/manager6/Makefile
+++ b/www/manager6/Makefile
@@ -162,6 +162,7 @@ JSSRC=                                                      
\
        dc/UserEdit.js                                  \
        dc/UserView.js                                  \
        dc/MetricServerView.js                          \
+       dc/HardwareView.js                              \
        lxc/CmdMenu.js                                  \
        lxc/Config.js                                   \
        lxc/CreateWizard.js                             \
diff --git a/www/manager6/dc/Config.js b/www/manager6/dc/Config.js
index 13ded12e..37148588 100644
--- a/www/manager6/dc/Config.js
+++ b/www/manager6/dc/Config.js
@@ -255,8 +255,22 @@ Ext.define('PVE.dc.Config', {
                iconCls: 'fa fa-bar-chart',
                itemId: 'metricservers',
                onlineHelp: 'external_metric_server',
-           },
-           {
+           });
+       }
+
+       if (caps.hardware['Hardware.Use'] ||
+           caps.hardware['Hardware.Audit'] ||
+           caps.hardware['Hardware.Configure']) {
+           me.items.push({
+               xtype: 'pveDcHardwareView',
+               title: gettext('Hardware'),
+               iconCls: 'fa fa-desktop',
+               itemId: 'hardware',
+           });
+       }
+
+       if (caps.dc['Sys.Audit']) {
+           me.items.push({
                xtype: 'pveDcSupport',
                title: gettext('Support'),
                itemId: 'support',
diff --git a/www/manager6/dc/HardwareView.js b/www/manager6/dc/HardwareView.js
new file mode 100644
index 00000000..f85a1088
--- /dev/null
+++ b/www/manager6/dc/HardwareView.js
@@ -0,0 +1,314 @@
+Ext.define('pve-hardware-tree', {
+    extend: 'Ext.data.Model',
+    fields: ['type', 'text', 'path', 'ntype',
+       {
+           name: 'vendor',
+           type: 'string',
+       },
+       {
+           name: 'device',
+           type: 'string',
+       },
+       {
+           name: 'iconCls',
+           calculate: function(data) {
+               if (data.ntype === 'entry') {
+                   if (data.type === 'usb') {
+                       return 'fa fa-fw fa-usb';
+                   }
+                   if (data.type === 'pci') {
+                       return 'pve-itype-icon-pci';
+                   }
+                   return 'fa fa-fw fa-folder-o';
+               }
+
+               return 'fa fa-fw fa-building';
+           },
+       },
+       {
+           name: 'leaf',
+           calculate: function(data) {
+               return data.ntype && data.ntype !== 'entry';
+           },
+       },
+    ],
+
+});
+
+Ext.define('PVE.dc.HardwareView', {
+    extend: 'Ext.tree.Panel',
+    alias: 'widget.pveDcHardwareView',
+    mixins: ['Proxmox.Mixin.CBind'],
+
+    rootVisible: false,
+
+    cbindData: function(initialConfig) {
+       let me = this;
+       const caps = Ext.state.Manager.get('GuiCap');
+       me.canConfigure = !!caps.nodes['Sys.Modify'] && 
!!caps.hardware['Hardware.Configure'];
+
+       return {};
+    },
+
+    controller: {
+       xclass: 'Ext.app.ViewController',
+
+       addPCI: function() {
+           let me = this;
+           let nodename = Proxmox.NodeName;
+           Ext.create('PVE.node.PCIEditWindow', {
+               url: `/nodes/${nodename}/hardware/mapping/pci`,
+               autoShow: true,
+               listeners: {
+                   destroy: () => me.load(),
+               },
+           });
+       },
+
+       addUSB: function() {
+           let me = this;
+           let nodename = Proxmox.NodeName;
+           Ext.create('PVE.node.USBEditWindow', {
+               url: `/nodes/${nodename}/hardware/mapping/usb`,
+               autoShow: true,
+               listeners: {
+                   destroy: () => me.load(),
+               },
+           });
+       },
+
+       edit: function() {
+           let me = this;
+           let view = me.getView();
+           let selection = view.getSelection();
+           if (!selection || !selection.length) {
+               return;
+           }
+           let rec = selection[0];
+           if (!view.canConfigure) {
+               return;
+           }
+
+           let type = 'PVE.node.' + (rec.data.type === 'pci' ? 'PCIEditWindow' 
: 'USBEditWindow');
+
+           Ext.create(type, {
+               url: 
`/nodes/${rec.data.node}/hardware/mapping/${rec.data.type}/${rec.data.entry}`,
+               autoShow: true,
+               autoLoad: rec.data.ntype !== 'entry',
+               nodename: rec.data.ntype !== 'entry' ? rec.data.node : 
undefined,
+               name: rec.data.entry ?? rec.data.text,
+               listeners: {
+                   destroy: () => me.load(),
+               },
+           });
+       },
+
+       load: function() {
+           let me = this;
+           let view = me.getView();
+           Proxmox.Utils.API2Request({
+               url: '/cluster/hardware/mapping',
+               method: 'GET',
+               failure: response => Ext.Msg.alert(gettext('Error'), 
response.htmlStatus),
+               success: function({ result: { data } }) {
+                   view.setRootNode({
+                       children: data,
+                   });
+                   let root = view.getRootNode();
+                   root.expand();
+                   root.childNodes.forEach(node => node.expand());
+                   me.loadRemainigNodes();
+               },
+           });
+       },
+
+       loadRemainigNodes: function() {
+           let me = this;
+           let view = me.getView();
+           PVE.data.ResourceStore.getNodes().forEach(({ node }) => {
+               if (node === Proxmox.NodeName) {
+                   return;
+               }
+               Proxmox.Utils.API2Request({
+                   url: `/nodes/${node}/hardware/mapping/all`,
+                   method: 'GET',
+                   failure: function(response) {
+                       view.getRootNode()?.cascade(function(rec) {
+                           if (rec.data.node !== node) {
+                               return;
+                           }
+
+                           rec.set('valid', 0);
+                           rec.set('errmsg', response.htmlStatus);
+                           rec.commit();
+                       });
+                   },
+                   success: function({ result: { data } }) {
+                       let entries = {};
+                       data.forEach((entry) => {
+                           entries[entry.name] = entry;
+                       });
+                       view.getRootNode()?.cascade(function(rec) {
+                           if (rec.data.node !== node) {
+                               return;
+                           }
+
+                           let entry = entries[rec.data.entry];
+
+                           rec.set('valid', entry.valid);
+                           rec.set('errmsg', entry.errmsg);
+                           rec.commit();
+                       });
+                   },
+               });
+           });
+       },
+    },
+
+    store: {
+       sorters: 'text',
+       model: 'pve-hardware-tree',
+       data: {},
+    },
+
+
+    tbar: [
+       {
+           text: gettext('Add new'),
+           cbind: {
+               disabled: '{!canConfigure}',
+           },
+           menu: [
+               {
+                   text: gettext('PCI'),
+                   iconCls: 'pve-itype-icon-pci',
+                   handler: 'addPCI',
+               },
+               {
+                   text: gettext('USB'),
+                   iconCls: 'fa fa-fw fa-usb black',
+                   handler: 'addUSB',
+               },
+           ],
+       },
+       {
+           xtype: 'proxmoxButton',
+           text: gettext('Add mapping'),
+           disabled: true,
+           parentXType: 'treepanel',
+           enableFn: function(rec) {
+               return rec.data.ntype === 'entry' && 
this.up('treepanel').canConfigure;
+           },
+           cbind: {
+               disabled: '{!canConfigure}',
+           },
+           handler: 'edit',
+       },
+       {
+           xtype: 'proxmoxButton',
+           text: gettext('Edit'),
+           disabled: true,
+           parentXType: 'treepanel',
+           enableFn: function(rec) {
+               return rec.data.ntype !== 'entry' && 
this.up('treepanel').canConfigure;
+           },
+           cbind: {
+               disabled: '{!canConfigure}',
+           },
+           handler: 'edit',
+       },
+       {
+           xtype: 'proxmoxStdRemoveButton',
+           parentXType: 'treepanel',
+           getUrl: function(rec) {
+               let data = rec.data;
+               return 
`/api2/extjs/nodes/${data.node}/hardware/mapping/${data.type}/${data.entry}`;
+           },
+           confirmMsg: function(rec) {
+               let msg = gettext('Are you sure you want to remove entry {0} 
for {1}');
+               return Ext.String.format(msg, `'${rec.data.entry}'`, 
`'${rec.data.node}'`);
+           },
+           enableFn: function(rec) {
+               return rec.data.ntype !== 'entry' && 
this.up('treepanel').canConfigure;
+           },
+           callback: 'load',
+           disabled: true,
+           text: gettext('Remove'),
+       },
+    ],
+
+    columns: [
+       {
+           xtype: 'treecolumn',
+           text: gettext('Type/ID/Node'),
+           dataIndex: 'text',
+           renderer: function(value, _meta, record) {
+               if (record.data.ntype === 'entry') {
+                   let typeMap = {
+                       usb: gettext('USB'),
+                       pci: gettext('PCI'),
+                   };
+                   let type = typeMap[record.data.type] || 
Proxmox.Utils.unknownText;
+                   return `${value} (${type})`;
+               }
+               return value;
+           },
+           width: 200,
+       },
+       {
+           text: gettext('Vendor'),
+           dataIndex: 'vendor',
+       },
+       {
+           text: gettext('Device'),
+           dataIndex: 'device',
+       },
+       {
+           text: gettext('Subsystem Vendor'),
+           dataIndex: 'subsystem-vendor',
+       },
+       {
+           text: gettext('Subsystem Device'),
+           dataIndex: 'subsystem-device',
+       },
+       {
+           text: gettext('IOMMU group'),
+           dataIndex: 'iommugroup',
+       },
+       {
+           text: gettext('Path'),
+           dataIndex: 'path',
+       },
+       {
+           header: gettext('Status'),
+           dataIndex: 'valid',
+           flex: 1,
+           renderer: function(value, _metadata, record) {
+               if (record.data.ntype !== 'mapping') {
+                   return '';
+               }
+               let iconCls;
+               let status;
+               if (value === undefined) {
+                   iconCls = 'fa-spinner fa-spin';
+                   status = gettext('Loading...');
+               } else {
+                   let state = value ? 'good' : 'critical';
+                   iconCls = PVE.Utils.get_health_icon(state, true);
+                   status = value ? gettext("OK") : record.data.errmsg || 
Proxmox.Utils.unknownText;
+               }
+               return `<i class="fa ${iconCls}"></i> ${status}`;
+           },
+       },
+       {
+           header: gettext('Comment'),
+           dataIndex: 'comment',
+           flex: 1,
+       },
+    ],
+
+    listeners: {
+       activate: 'load',
+       itemdblclick: 'edit',
+    },
+});
-- 
2.30.2



_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel

Reply via email to