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