this will be the base class for trees for the individual mapping types,
e.g. pci and usb mapping.

there are a few things to configure, but the overall code sharing is
still significant, and should work out fine for future mapping types

Signed-off-by: Dominik Csapak <[email protected]>
---
 www/manager6/Makefile                |   1 +
 www/manager6/tree/ResourceMapTree.js | 316 +++++++++++++++++++++++++++
 2 files changed, 317 insertions(+)
 create mode 100644 www/manager6/tree/ResourceMapTree.js

diff --git a/www/manager6/Makefile b/www/manager6/Makefile
index 99ebc4dc..0cb922d6 100644
--- a/www/manager6/Makefile
+++ b/www/manager6/Makefile
@@ -101,6 +101,7 @@ JSSRC=                                                      
\
        panel/MultiDiskEdit.js                          \
        tree/ResourceTree.js                            \
        tree/SnapshotTree.js                            \
+       tree/ResourceMapTree.js                         \
        window/Backup.js                                \
        window/BackupConfig.js                          \
        window/BulkAction.js                            \
diff --git a/www/manager6/tree/ResourceMapTree.js 
b/www/manager6/tree/ResourceMapTree.js
new file mode 100644
index 00000000..df50b63a
--- /dev/null
+++ b/www/manager6/tree/ResourceMapTree.js
@@ -0,0 +1,316 @@
+Ext.define('PVE.tree.ResourceMapTree', {
+    extend: 'Ext.tree.Panel',
+    alias: 'widget.pveResourceMapTree',
+    mixins: ['Proxmox.Mixin.CBind'],
+
+    rootVisible: false,
+
+    emptyText: gettext('No Mapping found'),
+
+    // will be opened on edit
+    editWindowClass: undefined,
+
+    // The base url of the resource
+    baseUrl: undefined,
+
+    // icon class to show on the entries
+    mapIconCls: undefined,
+
+    // if given, should be a function that takes a nodename and returns
+    // the url for getting the data to check the status
+    getStatusCheckUrl: undefined,
+
+    // the result of above api call and the nodename is passed and can set the 
status
+    checkValidity: undefined,
+
+    // the property that denotes a single map entry for a node
+    entryIdProperty: undefined,
+
+    cbindData: function(initialConfig) {
+       let me = this;
+       const caps = Ext.state.Manager.get('GuiCap');
+       me.canConfigure = !!caps.mapping['Mapping.Modify'];
+
+       return {};
+    },
+
+    controller: {
+       xclass: 'Ext.app.ViewController',
+
+       addMapping: function() {
+           let me = this;
+           let view = me.getView();
+           Ext.create(view.editWindowClass, {
+               url: view.baseUrl,
+               autoShow: true,
+               listeners: {
+                   destroy: () => me.load(),
+               },
+           });
+       },
+
+       addHost: function() {
+           let me = this;
+           me.edit(false);
+       },
+
+       edit: function(includeNodename = true) {
+           let me = this;
+           let view = me.getView();
+           let selection = view.getSelection();
+           if (!selection || !selection.length) {
+               return;
+           }
+           let rec = selection[0];
+           if (!view.canConfigure || (rec.data.type === 'entry' && 
includeNodename)) {
+               return;
+           }
+
+           Ext.create(view.editWindowClass, {
+               url: `${view.baseUrl}/${rec.data.name}`,
+               autoShow: true,
+               autoLoad: true,
+               nodename: includeNodename ? rec.data.node : undefined,
+               name: rec.data.name,
+               listeners: {
+                   destroy: () => me.load(),
+               },
+           });
+       },
+
+       remove: function() {
+           let me = this;
+           let view = me.getView();
+           let selection = view.getSelection();
+           if (!selection || !selection.length) {
+               return;
+           }
+
+           let data = selection[0].data;
+           let url = `${view.baseUrl}/${data.name}`;
+           let method = 'PUT';
+           let params = {
+               digest: me.lookup[data.name].digest,
+           };
+           let map = me.lookup[data.name].map;
+           switch (data.type) {
+               case 'entry':
+                   method = 'DELETE';
+                   params = undefined;
+                   break;
+               case 'node':
+                   params.map = PVE.Parser.filterPropertyStringList(map, (e) 
=> e.node !== data.node);
+                   break;
+               case 'map':
+                   params.map = PVE.Parser.filterPropertyStringList(map, (e) =>
+                       Object.entries(e).some(([key, value]) => data[key] !== 
value));
+                   break;
+               default:
+                   throw "invalid type";
+           }
+           if (!params?.map.length) {
+               method = 'DELETE';
+               params = undefined;
+           }
+           Proxmox.Utils.API2Request({
+               url,
+               method,
+               params,
+               success: function() {
+                   me.load();
+               },
+           });
+       },
+
+       load: function() {
+           let me = this;
+           let view = me.getView();
+           Proxmox.Utils.API2Request({
+               url: view.baseUrl,
+               method: 'GET',
+               failure: response => Ext.Msg.alert(gettext('Error'), 
response.htmlStatus),
+               success: function({ result: { data } }) {
+                   let lookup = {};
+                   data.forEach((entry) => {
+                       lookup[entry.id] = Ext.apply({}, entry);
+                       entry.iconCls = 'fa fa-fw fa-folder-o';
+                       entry.name = entry.id;
+                       entry.text = entry.id;
+                       entry.type = 'entry';
+
+                       let nodes = {};
+                       for (const map of entry.map) {
+                           let parsed = PVE.Parser.parsePropertyString(map);
+                           parsed.iconCls = view.mapIconCls;
+                           parsed.leaf = true;
+                           parsed.name = entry.id;
+                           parsed.text = parsed[view.entryIdProperty];
+                           parsed.type = 'map';
+
+                           if (nodes[parsed.node] === undefined) {
+                               nodes[parsed.node] = {
+                                   children: [],
+                                   expanded: true,
+                                   iconCls: 'fa fa-fw fa-building-o',
+                                   leaf: false,
+                                   name: entry.id,
+                                   node: parsed.node,
+                                   text: parsed.node,
+                                   type: 'node',
+                               };
+                           }
+                           nodes[parsed.node].children.push(parsed);
+                       }
+                       delete entry.id;
+                       entry.children = Object.values(nodes);
+                       entry.leaf = entry.children.length === 0;
+                   });
+                   me.lookup = lookup;
+                   if (view.getStatusCheckUrl !== undefined && 
view.checkValidity !== undefined) {
+                       me.loadStatusData();
+                   }
+                   view.setRootNode({
+                       children: data,
+                   });
+                   let root = view.getRootNode();
+                   root.expand();
+                   root.childNodes.forEach(node => node.expand());
+               },
+           });
+       },
+
+       nodeLoadingState: {},
+
+       loadStatusData: function() {
+           let me = this;
+           let view = me.getView();
+           PVE.data.ResourceStore.getNodes().forEach(({ node }) => {
+               me.nodeLoadingState[node] = true;
+               let url = view.getStatusCheckUrl(node);
+               Proxmox.Utils.API2Request({
+                   url,
+                   method: 'GET',
+                   failure: function(response) {
+                       me.nodeLoadingState[node] = false;
+                       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 } }) {
+                       me.nodeLoadingState[node] = false;
+                       view.checkValidity(data, node);
+                   },
+               });
+           });
+       },
+
+       renderStatus: function(value, _metadata, record) {
+           let me = this;
+           if (record.data.type !== 'map') {
+               return '';
+           }
+           let iconCls;
+           let status;
+           if (value === undefined) {
+               if (me.nodeLoadingState[record.data.node]) {
+                   iconCls = 'fa-spinner fa-spin';
+                   status = gettext('Loading...');
+               } else {
+                   iconCls = 'fa-question-circle';
+                   status = gettext('Unknown Node');
+               }
+           } 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}`;
+       },
+
+       init: function(view) {
+           let me = this;
+
+           ['editWindowClass', 'baseUrl', 'mapIconCls', 
'entryIdProperty'].forEach((property) => {
+               if (view[property] === undefined) {
+                   throw `No ${property} defined`;
+               }
+           });
+
+           me.load();
+       },
+    },
+
+    store: {
+       sorters: 'text',
+       data: {},
+    },
+
+
+    tbar: [
+       {
+           text: gettext('Add mapping'),
+           handler: 'addMapping',
+           cbind: {
+               disabled: '{!canConfigure}',
+           },
+       },
+       {
+           xtype: 'proxmoxButton',
+           text: gettext('New Host mapping'),
+           disabled: true,
+           parentXType: 'treepanel',
+           enableFn: function(_rec) {
+               return this.up('treepanel').canConfigure;
+           },
+           handler: 'addHost',
+       },
+       {
+           xtype: 'proxmoxButton',
+           text: gettext('Edit'),
+           disabled: true,
+           parentXType: 'treepanel',
+           enableFn: function(rec) {
+               return rec && rec.data.type !== 'entry' && 
this.up('treepanel').canConfigure;
+           },
+           handler: 'edit',
+       },
+       {
+           xtype: 'proxmoxButton',
+           parentXType: 'treepanel',
+           handler: 'remove',
+           disabled: true,
+           text: gettext('Remove'),
+           enableFn: function(rec) {
+               return rec && this.up('treepanel').canConfigure;
+           },
+           confirmMsg: function(rec) {
+               let msg, id;
+               let view = this.up('treepanel');
+               switch (rec.data.type) {
+                   case 'entry':
+                       msg = gettext("Are you sure you want to remove '{0}'");
+                       return Ext.String.format(msg, rec.data.name);
+                   case 'node':
+                       msg = gettext("Are you sure you want to remove '{0}' 
entries for '{1}'");
+                       return Ext.String.format(msg, rec.data.node, 
rec.data.name);
+                   case 'map':
+                       msg = gettext("Are you sure you want to remove '{0}' on 
'{1}' for '{2}'");
+                       id = rec.data[view.entryIdProperty];
+                       return Ext.String.format(msg, id, rec.data.node, 
rec.data.name);
+                   default:
+                       throw "invalid type";
+               }
+           },
+       },
+    ],
+
+    listeners: {
+       itemdblclick: 'edit',
+    },
+});
-- 
2.30.2



_______________________________________________
pve-devel mailing list
[email protected]
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel

Reply via email to