pve-manager/www/manager6/tree/ResourceMapTree.js
Dominik Csapak 0a571cffc0 ui: resource map tree: make 'ok' status clearer
by changing into 'mapping matches host data' which indicates that the
configured values matches the host information

also for the pci and usb map selectors

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-06-21 10:03:52 +02:00

317 lines
7.6 KiB
JavaScript

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("Mapping matches host data") : 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',
},
});