* [pve-devel] [PATCH cluster v4 1/1] datacenter.cfg: add option for tag-tree-style and tag-colors
2022-03-23 10:34 [pve-devel] [PATCH cluster/widget-toolkit/manager v4] add tags to ui Dominik Csapak
@ 2022-03-23 10:34 ` Dominik Csapak
2022-03-23 10:34 ` [pve-devel] [PATCH widget-toolkit v4 1/1] add tag related helpers Dominik Csapak
` (10 subsequent siblings)
11 siblings, 0 replies; 13+ messages in thread
From: Dominik Csapak @ 2022-03-23 10:34 UTC (permalink / raw)
To: pve-devel
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
data/PVE/DataCenterConfig.pm | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/data/PVE/DataCenterConfig.pm b/data/PVE/DataCenterConfig.pm
index 6c0fa5b..e890820 100644
--- a/data/PVE/DataCenterConfig.pm
+++ b/data/PVE/DataCenterConfig.pm
@@ -222,6 +222,19 @@ my $datacenter_schema = {
maxLength => 64 * 1024,
optional => 1,
},
+ 'tag-tree-style' => {
+ optional => 1,
+ type => 'string',
+ enum => ['full', 'circle', 'dense', 'none'],
+ default => 'circle',
+ description => "Tag style in tree.",
+ },
+ 'tag-colors' => {
+ optional => 1,
+ type => 'string',
+ pattern => '(?:[a-zA-Z0-9_][a-zA-Z0-9_\-\+\.]*=[0-9a-fA-F]{6},)*(?:[a-zA-Z0-9_][a-zA-Z0-9_\-\+\.]*=[0-9a-fA-F]{6})',
+ description => "Manual color mapping for tags. Format is '<tag>=<hex-color>'",
+ },
},
};
--
2.30.2
^ permalink raw reply [flat|nested] 13+ messages in thread
* [pve-devel] [PATCH widget-toolkit v4 1/1] add tag related helpers
2022-03-23 10:34 [pve-devel] [PATCH cluster/widget-toolkit/manager v4] add tags to ui Dominik Csapak
2022-03-23 10:34 ` [pve-devel] [PATCH cluster v4 1/1] datacenter.cfg: add option for tag-tree-style and tag-colors Dominik Csapak
@ 2022-03-23 10:34 ` Dominik Csapak
2022-03-23 10:34 ` [pve-devel] [PATCH manager v4 01/10] api: /cluster/resources: add tags to returned properties Dominik Csapak
` (9 subsequent siblings)
11 siblings, 0 replies; 13+ messages in thread
From: Dominik Csapak @ 2022-03-23 10:34 UTC (permalink / raw)
To: pve-devel
helpers to
* generate a color from a string consistently
* generate a html tag for a tag
* related css classes
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
src/Utils.js | 35 +++++++++++++++++++++++++++++
src/css/ext6-pmx.css | 52 ++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 87 insertions(+)
diff --git a/src/Utils.js b/src/Utils.js
index 6a03057..3130b2a 100644
--- a/src/Utils.js
+++ b/src/Utils.js
@@ -1272,6 +1272,41 @@ utilities: {
.map(val => val.charCodeAt(0)),
);
},
+
+ stringToRGB: function(string) {
+ let hash = 0;
+ if (!string) {
+ return hash;
+ }
+ string += 'prox'; // give short strings more variance
+ for (let i = 0; i < string.length; i++) {
+ hash = string.charCodeAt(i) + ((hash << 5) - hash);
+ hash = hash & hash; // to int
+ }
+ return [
+ hash & 255,
+ (hash >> 8) & 255,
+ (hash >> 16) & 255,
+ 0.8, // make the colors a little lighter
+ ];
+ },
+
+ rgbToCss: function(rgb) {
+ return `rgb(${rgb[0]}, ${rgb[1]}, ${rgb[2]}, ${rgb[3]})`;
+ },
+
+ rgbToLuminance: function(rgb) {
+ // https://en.wikipedia.org/wiki/Relative_luminance
+ return (0.2126 * rgb[0] + 0.7152*rgb[1] + 0.0722*rgb[2])/rgb[3];
+ },
+
+ getTagElement: function(string, color_overrides) {
+ let rgb = color_overrides?.[string] || Proxmox.Utils.stringToRGB(string);
+ let bgcolor = Proxmox.Utils.rgbToCss(rgb);
+ let txtCls = Proxmox.Utils.rgbToLuminance(rgb) < 160 ? 'light' : 'dark';
+ let cls = `proxmox-tag-${txtCls}`;
+ return `<span class="${cls}" style="background-color: ${bgcolor};">${string}</span>`;
+ },
},
singleton: true,
diff --git a/src/css/ext6-pmx.css b/src/css/ext6-pmx.css
index 1d815c6..7c01d2f 100644
--- a/src/css/ext6-pmx.css
+++ b/src/css/ext6-pmx.css
@@ -6,6 +6,58 @@
background-color: LightYellow;
}
+.proxmox-tags-full .proxmox-tag-light,
+.proxmox-tags-full .proxmox-tag-dark {
+ border-radius: 9px;
+ padding: 1px 8px;
+ margin: 0px 1px;
+}
+
+.proxmox-tags-circle .proxmox-tag-light,
+.proxmox-tags-circle .proxmox-tag-dark {
+ margin: 0px 1px;
+ position: relative;
+ top: 2px;
+ border-radius: 6px;
+ height: 12px;
+ width: 12px;
+ display: inline-block;
+ color: transparent;
+ overflow: hidden;
+}
+
+.proxmox-tags-none .proxmox-tag-light,
+.proxmox-tags-none .proxmox-tag-dark {
+ display: none;
+}
+
+.proxmox-tags-dense .proxmox-tag-light,
+.proxmox-tags-dense .proxmox-tag-dark {
+ width: 5px;
+ display: inline-block;
+ color: transparent;
+ overflow: hidden;
+ vertical-align: bottom;
+}
+
+.proxmox-tags-full .proxmox-tag-light {
+ color: #fff;
+ background-color: #383838;
+}
+
+.proxmox-tags-full .proxmox-tag-light span::selection {
+ background-color: #000;
+}
+
+.proxmox-tags-full .proxmox-tag-dark {
+ color: #000;
+ background-color: #f0f0f0;
+}
+
+.proxmox-tags-full .proxmox-tag-dark span::selection {
+ background-color: #FFF;
+}
+
.x-mask-msg-text {
text-align: center;
}
--
2.30.2
^ permalink raw reply [flat|nested] 13+ messages in thread
* [pve-devel] [PATCH manager v4 01/10] api: /cluster/resources: add tags to returned properties
2022-03-23 10:34 [pve-devel] [PATCH cluster/widget-toolkit/manager v4] add tags to ui Dominik Csapak
2022-03-23 10:34 ` [pve-devel] [PATCH cluster v4 1/1] datacenter.cfg: add option for tag-tree-style and tag-colors Dominik Csapak
2022-03-23 10:34 ` [pve-devel] [PATCH widget-toolkit v4 1/1] add tag related helpers Dominik Csapak
@ 2022-03-23 10:34 ` Dominik Csapak
2022-03-23 10:34 ` [pve-devel] [PATCH manager v4 02/10] api: /version: add 'tag-colors' and 'tag-tree-style' Dominik Csapak
` (8 subsequent siblings)
11 siblings, 0 replies; 13+ messages in thread
From: Dominik Csapak @ 2022-03-23 10:34 UTC (permalink / raw)
To: pve-devel
by querying 'lock' and 'tags' with 'get_guest_config_properties'
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
PVE/API2/Cluster.pm | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/PVE/API2/Cluster.pm b/PVE/API2/Cluster.pm
index 227c6814..69272fc7 100644
--- a/PVE/API2/Cluster.pm
+++ b/PVE/API2/Cluster.pm
@@ -360,7 +360,8 @@ __PACKAGE__->register_method({
# we try to generate 'numbers' by using "$X + 0"
if (!$param->{type} || $param->{type} eq 'vm') {
- my $locked_vms = PVE::Cluster::get_guest_config_property('lock');
+ my $prop_list = [qw(lock tags)];
+ my $props = PVE::Cluster::get_guest_config_properties($prop_list);
for my $vmid (sort keys %$idlist) {
@@ -392,8 +393,10 @@ __PACKAGE__->register_method({
# only skip now to next to ensure that the pool stats above are filled, if eligible
next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
- if (defined(my $lock = $locked_vms->{$vmid}->{lock})) {
- $entry->{lock} = $lock;
+ for my $prop (@$prop_list) {
+ if (defined(my $value = $props->{$vmid}->{$prop})) {
+ $entry->{$prop} = $value;
+ }
}
if (defined($entry->{pool}) &&
--
2.30.2
^ permalink raw reply [flat|nested] 13+ messages in thread
* [pve-devel] [PATCH manager v4 02/10] api: /version: add 'tag-colors' and 'tag-tree-style'
2022-03-23 10:34 [pve-devel] [PATCH cluster/widget-toolkit/manager v4] add tags to ui Dominik Csapak
` (2 preceding siblings ...)
2022-03-23 10:34 ` [pve-devel] [PATCH manager v4 01/10] api: /cluster/resources: add tags to returned properties Dominik Csapak
@ 2022-03-23 10:34 ` Dominik Csapak
2022-03-23 10:34 ` [pve-devel] [PATCH manager v4 03/10] ui: parse and save tag color overrides from /version Dominik Csapak
` (7 subsequent siblings)
11 siblings, 0 replies; 13+ messages in thread
From: Dominik Csapak @ 2022-03-23 10:34 UTC (permalink / raw)
To: pve-devel
to be able to get them in the gui directly after login
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
PVE/API2.pm | 12 +++++++++++-
1 file changed, 11 insertions(+), 1 deletion(-)
diff --git a/PVE/API2.pm b/PVE/API2.pm
index a4256160..0592d4de 100644
--- a/PVE/API2.pm
+++ b/PVE/API2.pm
@@ -111,6 +111,16 @@ __PACKAGE__->register_method ({
optional => 1,
description => 'The default console viewer to use.',
},
+ 'tag-colors' => {
+ type => 'string',
+ optional => 1,
+ description => 'Cluster wide tag color overrides',
+ },
+ 'tag-tree-style' => {
+ type => 'string',
+ optional => 1,
+ description => 'Tag style in tree',
+ },
},
},
code => sub {
@@ -119,7 +129,7 @@ __PACKAGE__->register_method ({
my $res = {};
my $datacenter_confg = eval { PVE::Cluster::cfs_read_file('datacenter.cfg') } // {};
- for my $k (qw(console)) {
+ for my $k (qw(console tag-colors tag-tree-style)) {
$res->{$k} = $datacenter_confg->{$k} if exists $datacenter_confg->{$k};
}
--
2.30.2
^ permalink raw reply [flat|nested] 13+ messages in thread
* [pve-devel] [PATCH manager v4 03/10] ui: parse and save tag color overrides from /version
2022-03-23 10:34 [pve-devel] [PATCH cluster/widget-toolkit/manager v4] add tags to ui Dominik Csapak
` (3 preceding siblings ...)
2022-03-23 10:34 ` [pve-devel] [PATCH manager v4 02/10] api: /version: add 'tag-colors' and 'tag-tree-style' Dominik Csapak
@ 2022-03-23 10:34 ` Dominik Csapak
2022-03-23 10:34 ` [pve-devel] [PATCH manager v4 04/10] ui: tree/ResourceTree: collect tags on update Dominik Csapak
` (6 subsequent siblings)
11 siblings, 0 replies; 13+ messages in thread
From: Dominik Csapak @ 2022-03-23 10:34 UTC (permalink / raw)
To: pve-devel
and implement a convenience function that also parses the 'color'
state from the browser storage for overrides
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
www/manager6/Utils.js | 34 ++++++++++++++++++++++++++++++++++
www/manager6/Workspace.js | 13 +++++++++++++
2 files changed, 47 insertions(+)
diff --git a/www/manager6/Utils.js b/www/manager6/Utils.js
index aafe359a..0ad3a482 100644
--- a/www/manager6/Utils.js
+++ b/www/manager6/Utils.js
@@ -1803,6 +1803,40 @@ Ext.define('PVE.Utils', {
return undefined;
},
+
+ parseTagOverrides: function(overrides) {
+ let colors = {};
+ (overrides || "").split(/[;, ]/).forEach(color => {
+ if (!color) {
+ return;
+ }
+ let [tag, color_hex] = color.split('=', 2);
+ let r = parseInt(color_hex.slice(0, 2), 16);
+ let g = parseInt(color_hex.slice(2, 4), 16);
+ let b = parseInt(color_hex.slice(4, 6), 16);
+ colors[tag] = [r, g, b, 1.0];
+ });
+ return colors;
+ },
+
+ getTagOverrides: function() {
+ let sp = Ext.state.Manager.getProvider();
+ let color_state = sp.get('colors', '');
+ let browser_colors = PVE.Utils.parseTagOverrides(color_state);
+ return Ext.apply({}, browser_colors, PVE.TagColors);
+ },
+
+ updateTagSettings: function(overrides, style) {
+ if (overrides) {
+ PVE.TagColors = PVE.Utils.parseTagOverrides(overrides);
+ }
+
+ if (style === undefined || style === '__default__') {
+ style = 'circle';
+ }
+
+ Ext.ComponentQuery.query('pveResourceTree')[0].setUserCls(`proxmox-tags-${style}`);
+ },
},
singleton: true,
diff --git a/www/manager6/Workspace.js b/www/manager6/Workspace.js
index 37d772b8..d9875c18 100644
--- a/www/manager6/Workspace.js
+++ b/www/manager6/Workspace.js
@@ -155,6 +155,7 @@ Ext.define('PVE.StdWorkspace', {
success: function(response) {
PVE.VersionInfo = response.result.data;
me.updateVersionInfo();
+ me.updateTags();
},
});
@@ -213,6 +214,18 @@ Ext.define('PVE.StdWorkspace', {
ui.updateLayout();
},
+ updateTags: function() {
+ let me = this;
+ let colors = PVE.VersionInfo?.['tag-colors'];
+ let style = PVE.VersionInfo?.['tag-tree-style'];
+
+ PVE.Utils.updateTagSettings(colors, style);
+ if (colors) {
+ // refresh tree once
+ PVE.data.ResourceStore.fireEvent('load');
+ }
+ },
+
initComponent: function() {
let me = this;
--
2.30.2
^ permalink raw reply [flat|nested] 13+ messages in thread
* [pve-devel] [PATCH manager v4 04/10] ui: tree/ResourceTree: collect tags on update
2022-03-23 10:34 [pve-devel] [PATCH cluster/widget-toolkit/manager v4] add tags to ui Dominik Csapak
` (4 preceding siblings ...)
2022-03-23 10:34 ` [pve-devel] [PATCH manager v4 03/10] ui: parse and save tag color overrides from /version Dominik Csapak
@ 2022-03-23 10:34 ` Dominik Csapak
2022-03-23 10:34 ` [pve-devel] [PATCH manager v4 05/10] ui: add form/TagColorGrid Dominik Csapak
` (5 subsequent siblings)
11 siblings, 0 replies; 13+ messages in thread
From: Dominik Csapak @ 2022-03-23 10:34 UTC (permalink / raw)
To: pve-devel
into a global list, so that we have it avaiable anywhere
add a convenience 'getTagList' function, that also gets tags from
the overrides from datacenter.cfg and browser-storage
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
www/manager6/Utils.js | 8 ++++++++
www/manager6/data/ResourceStore.js | 6 ++++++
www/manager6/tree/ResourceTree.js | 16 ++++++++++++++--
3 files changed, 28 insertions(+), 2 deletions(-)
diff --git a/www/manager6/Utils.js b/www/manager6/Utils.js
index 0ad3a482..16a81127 100644
--- a/www/manager6/Utils.js
+++ b/www/manager6/Utils.js
@@ -1804,6 +1804,14 @@ Ext.define('PVE.Utils', {
return undefined;
},
+ tagList: new Set(),
+
+ getTagList: function() {
+ let overrides = PVE.Utils.getTagOverrides();
+ let override_tags = Object.keys(overrides);
+ return [...new Set([...PVE.Utils.tagList, ...override_tags])].sort();
+ },
+
parseTagOverrides: function(overrides) {
let colors = {};
(overrides || "").split(/[;, ]/).forEach(color => {
diff --git a/www/manager6/data/ResourceStore.js b/www/manager6/data/ResourceStore.js
index c7b72306..b18f7dd8 100644
--- a/www/manager6/data/ResourceStore.js
+++ b/www/manager6/data/ResourceStore.js
@@ -293,6 +293,12 @@ Ext.define('PVE.data.ResourceStore', {
sortable: true,
width: 100,
},
+ tags: {
+ header: gettext('Tags'),
+ type: 'string',
+ hidden: true,
+ sortable: true,
+ },
};
let fields = [];
diff --git a/www/manager6/tree/ResourceTree.js b/www/manager6/tree/ResourceTree.js
index be90d4f7..301f7557 100644
--- a/www/manager6/tree/ResourceTree.js
+++ b/www/manager6/tree/ResourceTree.js
@@ -226,6 +226,10 @@ Ext.define('PVE.tree.ResourceTree', {
let stateid = 'rid';
+ const changedFields = [
+ 'text', 'running', 'template', 'status', 'qmpstatus', 'hastate', 'lock', 'tags',
+ ];
+
let updateTree = function() {
store.suspendEvents();
@@ -261,7 +265,7 @@ Ext.define('PVE.tree.ResourceTree', {
}
// tree item has been updated
- for (const field of ['text', 'running', 'template', 'status', 'qmpstatus', 'hastate', 'lock']) {
+ for (const field of changedFields) {
if (item.data[field] !== olditem.data[field]) {
changed = true;
break;
@@ -294,7 +298,14 @@ Ext.define('PVE.tree.ResourceTree', {
}
}
- rstore.each(function(item) { // add new items
+ let tags = new Set();
+
+ rstore.each(function(item) { // add new items and collect tags
+ if (item.data.tags) {
+ item.data.tags.split(/[,; ]/).filter(t => !!t).forEach((tag) => {
+ tags.add(tag);
+ });
+ }
let olditem = index[item.data.id];
if (olditem) {
return;
@@ -310,6 +321,7 @@ Ext.define('PVE.tree.ResourceTree', {
}
});
+ PVE.Utils.tagList = tags;
store.resumeEvents();
store.fireEvent('refresh', store);
--
2.30.2
^ permalink raw reply [flat|nested] 13+ messages in thread
* [pve-devel] [PATCH manager v4 05/10] ui: add form/TagColorGrid
2022-03-23 10:34 [pve-devel] [PATCH cluster/widget-toolkit/manager v4] add tags to ui Dominik Csapak
` (5 preceding siblings ...)
2022-03-23 10:34 ` [pve-devel] [PATCH manager v4 04/10] ui: tree/ResourceTree: collect tags on update Dominik Csapak
@ 2022-03-23 10:34 ` Dominik Csapak
2022-03-23 10:34 ` [pve-devel] [PATCH manager v4 06/10] ui: dc/OptionView: add editors for tag settings Dominik Csapak
` (4 subsequent siblings)
11 siblings, 0 replies; 13+ messages in thread
From: Dominik Csapak @ 2022-03-23 10:34 UTC (permalink / raw)
To: pve-devel
this provides a basic grid to edit a list of tag color overrides.
We'll use this for editing the datacenter.cfg overrides and the
browser storage overrides.
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
www/manager6/Makefile | 1 +
www/manager6/form/TagColorGrid.js | 218 ++++++++++++++++++++++++++++++
2 files changed, 219 insertions(+)
create mode 100644 www/manager6/form/TagColorGrid.js
diff --git a/www/manager6/Makefile b/www/manager6/Makefile
index e6e01bd1..225dffba 100644
--- a/www/manager6/Makefile
+++ b/www/manager6/Makefile
@@ -73,6 +73,7 @@ JSSRC= \
form/VNCKeyboardSelector.js \
form/ViewSelector.js \
form/iScsiProviderSelector.js \
+ form/TagColorGrid.js \
grid/BackupView.js \
grid/FirewallAliases.js \
grid/FirewallOptions.js \
diff --git a/www/manager6/form/TagColorGrid.js b/www/manager6/form/TagColorGrid.js
new file mode 100644
index 00000000..bd8e539e
--- /dev/null
+++ b/www/manager6/form/TagColorGrid.js
@@ -0,0 +1,218 @@
+Ext.define('PVE.form.ColorPicker', {
+ extend: 'Ext.Component',
+ alias: 'widget.pveColorPicker',
+
+ defaultBindProperty: 'value',
+
+ config: {
+ value: null,
+ },
+
+ height: 15,
+ maxHeight: 15,
+
+ getValue: function() {
+ return this.realvalue.slice(1);
+ },
+
+ setValue: function(value = "000000") {
+ let me = this;
+ me.picker.value = value[0] !== '#' ? `#${value}` : value;
+ me.setColor(value);
+ },
+
+ setColor: function(value = "000000") {
+ let me = this;
+ let oldValue = me.realvalue;
+ me.realvalue = value;
+ me.setStyle('background-color', `#${value}`);
+ me.fireEvent('change', me, me.realvalue, oldValue);
+ },
+
+ initComponent: function() {
+ let me = this;
+ me.picker = document.createElement('input');
+ me.picker.type = 'color';
+ me.picker.style = `opacity: 0; border: 0px; width: 100%; height: ${me.height}px`;
+ me.picker.value = `${me.value}`;
+ me.contentEl = me.picker;
+
+ me.callParent();
+ me.picker.oninput = function() {
+ me.setColor(me.picker.value);
+ };
+ },
+});
+
+Ext.define('PVE.form.TagColorGrid', {
+ extend: 'Ext.grid.Panel',
+ alias: 'widget.pveTagColorGrid',
+
+ mixins: [
+ 'Ext.form.field.Field',
+ ],
+
+ allowBlank: true,
+ selectAll: false,
+ isFormField: true,
+ deleteEmpty: false,
+ selModel: 'checkboxmodel',
+
+ config: {
+ deleteEmpty: false,
+ },
+
+ emptyText: gettext('No Overrides'),
+ viewConfig: {
+ deferEmptyText: false,
+ },
+
+ setValue: function(value) {
+ let me = this;
+ if (!value) {
+ me.getStore().removeAll();
+ me.checkChange();
+ return me;
+ }
+ let entries = (value.split(/[;, ]/) || []).map((entry) => {
+ let [tag, color] = entry.split(/[=]/);
+ return {
+ tag,
+ color,
+ };
+ });
+ me.getStore().setData(entries);
+ me.checkChange();
+ return me;
+ },
+
+ getValue: function() {
+ let me = this;
+ let values = [];
+ me.getStore().each((rec) => {
+ if (rec.data.tag) {
+ values.push(`${rec.data.tag}=${rec.data.color}`);
+ }
+ });
+ return values.join(',');
+ },
+
+ getErrors: function(value) {
+ return [];
+ },
+
+ // override framework function to implement deleteEmpty behaviour
+ getSubmitData: function() {
+ let me = this,
+ data = null,
+ val;
+ if (!me.disabled && me.submitValue) {
+ val = me.getValue();
+ if (val !== null && val !== '') {
+ data = {};
+ data[me.getName()] = val;
+ } else if (me.getDeleteEmpty()) {
+ data = {};
+ data.delete = me.getName();
+ }
+ }
+ return data;
+ },
+
+
+ controller: {
+ xclass: 'Ext.app.ViewController',
+
+ addLine: function() {
+ let me = this;
+ me.getView().getStore().add({});
+ },
+
+ removeSelection: function() {
+ let me = this;
+ let view = me.getView();
+ let selection = view.getSelection();
+ if (selection === undefined) {
+ return;
+ }
+
+ selection.forEach((sel) => {
+ view.getStore().remove(sel);
+ });
+ view.checkChange();
+ },
+
+ fieldChange: function(field, newValue, oldValue) {
+ let me = this;
+ let view = me.getView();
+ let rec = field.getWidgetRecord();
+ let column = field.getWidgetColumn();
+ rec.set(column.dataIndex, newValue);
+ view.checkChange();
+ },
+ },
+
+ tbar: [
+ {
+ text: gettext('Add'),
+ handler: 'addLine',
+ },
+ {
+ xtype: 'proxmoxButton',
+ text: gettext('Remove'),
+ handler: 'removeSelection',
+ disabled: true,
+ },
+ ],
+
+ columns: [
+ {
+ header: 'Tag',
+ dataIndex: 'tag',
+ xtype: 'widgetcolumn',
+ onWidgetAttach: function(col, widget, rec) {
+ widget.getStore().setData(PVE.Utils.getTagList().map(v => ({ tag: v })));
+ },
+ widget: {
+ xtype: 'combobox',
+ isFormField: false,
+ maskRe: /[a-zA-Z0-9_.-]/,
+ queryMode: 'local',
+ displayField: 'tag',
+ valueField: 'tag',
+ store: {},
+ listeners: {
+ change: 'fieldChange',
+ },
+ },
+ flex: 1,
+ },
+ {
+ header: "Color",
+ xtype: 'widgetcolumn',
+ flex: 1,
+ dataIndex: 'color',
+ widget: {
+ xtype: 'pveColorPicker',
+ isFormField: false,
+ listeners: {
+ change: 'fieldChange',
+ },
+ },
+ },
+ ],
+
+ store: {
+ listeners: {
+ update: function() {
+ this.commitChanges();
+ },
+ },
+ },
+
+ initComponent: function() {
+ let me = this;
+ me.callParent();
+ me.initField();
+ },
+});
--
2.30.2
^ permalink raw reply [flat|nested] 13+ messages in thread
* [pve-devel] [PATCH manager v4 06/10] ui: dc/OptionView: add editors for tag settings
2022-03-23 10:34 [pve-devel] [PATCH cluster/widget-toolkit/manager v4] add tags to ui Dominik Csapak
` (6 preceding siblings ...)
2022-03-23 10:34 ` [pve-devel] [PATCH manager v4 05/10] ui: add form/TagColorGrid Dominik Csapak
@ 2022-03-23 10:34 ` Dominik Csapak
2022-03-23 10:34 ` [pve-devel] [PATCH manager v4 07/10] ui: add form/Tag Dominik Csapak
` (3 subsequent siblings)
11 siblings, 0 replies; 13+ messages in thread
From: Dominik Csapak @ 2022-03-23 10:34 UTC (permalink / raw)
To: pve-devel
namely for 'tag-tree-style' and 'tag-colors'.
display the tag overrides directly as they will appear as tags
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
www/manager6/Utils.js | 20 ++++++++++++++++
www/manager6/dc/OptionView.js | 43 ++++++++++++++++++++++++++++++++++-
2 files changed, 62 insertions(+), 1 deletion(-)
diff --git a/www/manager6/Utils.js b/www/manager6/Utils.js
index 16a81127..9097af42 100644
--- a/www/manager6/Utils.js
+++ b/www/manager6/Utils.js
@@ -1845,6 +1845,26 @@ Ext.define('PVE.Utils', {
Ext.ComponentQuery.query('pveResourceTree')[0].setUserCls(`proxmox-tags-${style}`);
},
+
+ tagTreeStyles: {
+ '__default__': Proxmox.Utils.defaultText,
+ 'full': gettext('Full'),
+ 'circle': gettext('Circle'),
+ 'dense': gettext('Dense'),
+ 'none': Proxmox.Utils.NoneText,
+ },
+
+ renderTags: function(tagstext) {
+ let text = '';
+ if (tagstext) {
+ let tags = (tagstext.split(/[,; ]/) || []).filter(t => !!t);
+ text += ' ';
+ tags.forEach((tag) => {
+ text += Proxmox.Utils.getTagElement(tag, PVE.Utils.getTagOverrides());
+ });
+ }
+ return text;
+ },
},
singleton: true,
diff --git a/www/manager6/dc/OptionView.js b/www/manager6/dc/OptionView.js
index 6b30ede9..677ffa90 100644
--- a/www/manager6/dc/OptionView.js
+++ b/www/manager6/dc/OptionView.js
@@ -5,6 +5,7 @@ Ext.define('PVE.dc.OptionView', {
onlineHelp: 'datacenter_configuration_file',
monStoreErrors: true,
+ userCls: 'proxmox-tags-full',
add_inputpanel_row: function(name, text, opts) {
var me = this;
@@ -284,7 +285,43 @@ Ext.define('PVE.dc.OptionView', {
minValue: 1,
maxValue: 64, // arbitrary but generous limit as limits are good
});
-
+ me.add_combobox_row('tag-tree-style', gettext('Tag Tree Style'), {
+ renderer: (value) => PVE.Utils.tagTreeStyles[value] ?? value,
+ comboItems: Object.entries(PVE.Utils.tagTreeStyles),
+ defaultValue: '__default__',
+ deleteEmpty: true,
+ });
+ me.rows['tag-colors'] = {
+ required: true,
+ renderer: (value) => {
+ if (value === undefined) {
+ return gettext('No Overrides');
+ }
+ let overrides = PVE.Utils.parseTagOverrides(value);
+ let txt = '';
+ for (const tag of Object.keys(overrides)) {
+ txt += Proxmox.Utils.getTagElement(tag, overrides);
+ }
+ return txt;
+ },
+ header: gettext('Tag Colors'),
+ editor: {
+ xtype: 'proxmoxWindowEdit',
+ width: 450,
+ bodyPadding: 0,
+ subject: gettext('Tag Colors'),
+ fieldDefaults: {
+ labelWidth: 100,
+ },
+ url: '/api2/extjs/cluster/options',
+ items: [{
+ name: 'tag-colors',
+ xtype: 'pveTagColorGrid',
+ deleteEmpty: true,
+ height: 300,
+ }],
+ },
+ };
me.selModel = Ext.create('Ext.selection.RowModel', {});
Ext.apply(me, {
@@ -319,6 +356,10 @@ Ext.define('PVE.dc.OptionView', {
if (rec.data.value === '__default__') {
delete PVE.VersionInfo.console;
}
+
+ let colors = store.getById('tag-colors')?.data?.value;
+ let style = store.getById('tag-tree-style')?.data?.value;
+ PVE.Utils.updateTagSettings(colors, style);
});
me.on('activate', me.rstore.startUpdate);
--
2.30.2
^ permalink raw reply [flat|nested] 13+ messages in thread
* [pve-devel] [PATCH manager v4 07/10] ui: add form/Tag
2022-03-23 10:34 [pve-devel] [PATCH cluster/widget-toolkit/manager v4] add tags to ui Dominik Csapak
` (7 preceding siblings ...)
2022-03-23 10:34 ` [pve-devel] [PATCH manager v4 06/10] ui: dc/OptionView: add editors for tag settings Dominik Csapak
@ 2022-03-23 10:34 ` Dominik Csapak
2022-03-23 10:34 ` [pve-devel] [PATCH manager v4 08/10] ui: {lxc, qemu}/Config: show Tags and make them editable Dominik Csapak
` (2 subsequent siblings)
11 siblings, 0 replies; 13+ messages in thread
From: Dominik Csapak @ 2022-03-23 10:34 UTC (permalink / raw)
To: pve-devel
displays a single tag, with the ability to edit inline on click. This brings
up a list of globally available tags for simple selection.
Also has a mode for adding a new Tag.
This has a 'updateCallback' which is called when the value changes
so that the parent component can update the config, and a
'layoutCallback' which will be called on input, so that the parent
component can update the layout when the content changes.
This is necessary since we circumvent the extjs logic for updating.
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
www/manager6/Makefile | 1 +
www/manager6/form/Tag.js | 207 +++++++++++++++++++++++++++++++++++++++
2 files changed, 208 insertions(+)
create mode 100644 www/manager6/form/Tag.js
diff --git a/www/manager6/Makefile b/www/manager6/Makefile
index 225dffba..45862e71 100644
--- a/www/manager6/Makefile
+++ b/www/manager6/Makefile
@@ -74,6 +74,7 @@ JSSRC= \
form/ViewSelector.js \
form/iScsiProviderSelector.js \
form/TagColorGrid.js \
+ form/Tag.js \
grid/BackupView.js \
grid/FirewallAliases.js \
grid/FirewallOptions.js \
diff --git a/www/manager6/form/Tag.js b/www/manager6/form/Tag.js
new file mode 100644
index 00000000..5c308f51
--- /dev/null
+++ b/www/manager6/form/Tag.js
@@ -0,0 +1,207 @@
+Ext.define('Proxmox.Tag', {
+ extend: 'Ext.Component',
+ alias: 'widget.pmxTag',
+
+ // if set to true, displays 'Add Tag' and a plus symbol
+ addTag: false,
+
+ // callback to update the config
+ updateCallback: Ext.emptyFn,
+
+ // callback to update the layout in the containing element
+ // this is necessary since we circumvent extjs layout with 'contentEditable'
+ layoutCallback: Ext.emptyFn,
+
+ inEdit: false,
+ style: {
+ 'padding-right': '1px',
+ 'white-space': 'nowrap',
+ },
+
+ icons: {
+ addTag: 'plus',
+ normal: 'times',
+ edit: 'check',
+ },
+
+ // we need to do this in mousedown, because that triggers before
+ // focusleave (which triggers before click)
+ onMouseDown: function(event) {
+ let me = this;
+ if (event.target.tagName === "I" && (!me.addTag || me.inEdit)) {
+ if (!me.inEdit) {
+ me.setVisible(false);
+ me.setText('');
+ }
+ me.finishEdit();
+ }
+ },
+
+ onClick: function(event) {
+ let me = this;
+ if ((me.addTag || event.target.tagName === "SPAN") && !me.inEdit) {
+ if (me.addTag) {
+ me.setText('');
+ me.setStyle('cursor');
+ }
+ me.inEdit = true;
+ me.tagEl().contentEditable = true;
+ me.iconEl().classList = `fa fa-fw fa-${me.icons.edit}-circle`;
+
+ // select text in the element
+ let range = document.createRange();
+ range.selectNodeContents(me.tagEl());
+ let sel = window.getSelection();
+ sel.removeAllRanges();
+ sel.addRange(range);
+
+ me.showPicker();
+ }
+ },
+
+ showPicker: function() {
+ let me = this;
+ if (!me.picker) {
+ me.picker = Ext.widget({
+ xtype: 'boundlist',
+ minWidth: 70,
+ scrollable: true,
+ floating: true,
+ hidden: true,
+ userCls: 'proxmox-tags-circle',
+ displayField: 'tag',
+ itemTpl: [
+ '{[Proxmox.Utils.getTagElement(values.tag, PVE.Utils.getTagOverrides())]} {tag}',
+ ],
+ store: [],
+ listeners: {
+ select: function(picker, rec) {
+ me.setText(rec.data.tag);
+ me.finishEdit();
+ },
+ },
+ });
+ }
+ me.picker.getStore().clearFilter();
+ let taglist = PVE.Utils.getTagList().map(v => ({ tag: v }));
+ if (taglist.length < 1) {
+ return;
+ }
+ me.picker.getStore().setData(taglist);
+ me.picker.showBy(me, 'tl-bl');
+ me.picker.setMaxHeight(200);
+ },
+
+ finishEdit: function(update = true) {
+ let me = this;
+ me.picker?.hide();
+
+ let tag = me.tagEl().innerHTML;
+ let mode;
+ if (me.addTag) {
+ me.setText(me.tag);
+ me.setStyle('cursor', 'pointer');
+ mode = 'addTag';
+ } else {
+ me.tag = tag;
+ mode = 'normal';
+ }
+ me.iconEl().classList = `fa fa-fw fa-${me.icons[mode]}-circle`;
+ me.updateColor(me.tag);
+
+
+ if (update) {
+ me.updateCallback(tag);
+ }
+ me.tagEl().contentEditable = false;
+ me.inEdit = false;
+ },
+
+ setText: function(text) {
+ let me = this;
+ me.tagEl().innerHTML = text;
+ me.layoutCallback();
+ },
+
+ cancelEdit: function(list, event) {
+ let me = this;
+ if (me.inEdit) {
+ me.setText(me.tag);
+ me.finishEdit(false);
+ }
+ },
+
+ onKeyPress: function(event) {
+ let me = this;
+ let key = event.browserEvent.key;
+ if (key === "Enter") {
+ me.finishEdit();
+ } else if (!key.match(/^[a-z0-9+\-_.]$/i)) {
+ event.browserEvent.preventDefault();
+ event.browserEvent.stopPropagation();
+ }
+ },
+
+ onInput: function() {
+ let me = this;
+ let tag = me.tagEl().innerHTML;
+ me.layoutCallback();
+ me.picker.getStore().filter({
+ property: 'tag',
+ value: tag,
+ });
+ },
+
+ listeners: {
+ mousedown: 'onMouseDown',
+ click: 'onClick',
+ focusleave: 'cancelEdit',
+ keypress: 'onKeyPress',
+ input: 'onInput',
+ element: 'el',
+ },
+
+ updateColor: function(tag) {
+ let me = this;
+ if (me.addTag) {
+ me.setUserCls(`proxmox-tag-dark`);
+ me.setStyle('background-color');
+ return;
+ }
+ let rgb = PVE.Utils.getTagOverrides()?.[tag] ?? Proxmox.Utils.stringToRGB(tag);
+ let color = Proxmox.Utils.rgbToCss(rgb);
+ let cls = Proxmox.Utils.rgbToLuminance(rgb) < 160 ? 'light' : 'dark';
+ me.setUserCls(`proxmox-tag-${cls}`);
+ me.setStyle('background-color', color);
+ },
+
+ tagEl: function() {
+ return this.el?.dom?.children?.[0];
+ },
+
+ iconEl: function() {
+ return this.el?.dom?.children?.[1];
+ },
+
+ initComponent: function() {
+ let me = this;
+ if (me.tag === undefined && !me.addTag) {
+ throw "no tag given";
+ }
+
+ let mode = 'normal';
+ if (me.addTag) {
+ me.tag = gettext('Add Tag');
+ mode = 'addTag';
+ }
+
+ let icon = `<i style="cursor: pointer;" class="fa fa-fw fa-${me.icons[mode]}-circle"></i>`;
+ me.html = `<span>${me.tag}</span> ${icon}`;
+
+ me.callParent();
+ me.updateColor(me.tag);
+ if (me.addTag) {
+ me.setStyle('cursor', 'pointer');
+ }
+ },
+});
--
2.30.2
^ permalink raw reply [flat|nested] 13+ messages in thread
* [pve-devel] [PATCH manager v4 08/10] ui: {lxc, qemu}/Config: show Tags and make them editable
2022-03-23 10:34 [pve-devel] [PATCH cluster/widget-toolkit/manager v4] add tags to ui Dominik Csapak
` (8 preceding siblings ...)
2022-03-23 10:34 ` [pve-devel] [PATCH manager v4 07/10] ui: add form/Tag Dominik Csapak
@ 2022-03-23 10:34 ` Dominik Csapak
2022-03-23 10:34 ` [pve-devel] [PATCH manager v4 09/10] ui: tree/ResourceTree: show Tags in tree Dominik Csapak
2022-03-23 10:34 ` [pve-devel] [PATCH manager v4 10/10] ui: form/GlobalSearchField: display tags and allow to search for them Dominik Csapak
11 siblings, 0 replies; 13+ messages in thread
From: Dominik Csapak @ 2022-03-23 10:34 UTC (permalink / raw)
To: pve-devel
add the tags in the status line, and add a button for adding new ones
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
www/manager6/lxc/Config.js | 68 +++++++++++++++++++++++++++++++++++--
www/manager6/qemu/Config.js | 66 +++++++++++++++++++++++++++++++++--
2 files changed, 130 insertions(+), 4 deletions(-)
diff --git a/www/manager6/lxc/Config.js b/www/manager6/lxc/Config.js
index 89b59c9b..4e2b4bb8 100644
--- a/www/manager6/lxc/Config.js
+++ b/www/manager6/lxc/Config.js
@@ -4,6 +4,8 @@ Ext.define('PVE.lxc.Config', {
onlineHelp: 'chapter_pct',
+ userCls: 'proxmox-tags-full',
+
initComponent: function() {
var me = this;
var vm = me.pveSelNode.data;
@@ -182,12 +184,43 @@ Ext.define('PVE.lxc.Config', {
],
});
+ let tagsContainer = Ext.create('Ext.container.Container', {
+ userCls: 'proxmox-tags-full',
+ layout: {
+ type: 'hbox',
+ align: 'stretch',
+ },
+ });
+
+ let tagAddBtn = Ext.create('Proxmox.Tag', {
+ xtype: 'pmxTag',
+ addTag: true,
+ layoutCallback: () => me.query('>toolbar[dock=top]')?.[0].updateLayout(),
+ updateCallback: function(newTag) {
+ let rec = me.statusStore.data.get('tags');
+ let tags = rec?.data?.value?.split(/[;, ]/) || [];
+ tags.push(newTag);
+ Proxmox.Utils.API2Request({
+ url: base_url + '/config',
+ method: 'PUT',
+ params: {
+ tags: tags.filter(t => !!t).join(','),
+ },
+ success: function() {
+ me.statusStore.load();
+ },
+ failure: function(response) {
+ Ext.Msg.alert('Error', response.htmlStatus);
+ },
+ });
+ },
+ });
Ext.apply(me, {
title: Ext.String.format(gettext("Container {0} on node '{1}'"), vm.text, nodename),
hstateid: 'lxctab',
tbarSpacing: false,
- tbar: [statusTxt, '->', startBtn, shutdownBtn, migrateBtn, consoleBtn, moreBtn],
+ tbar: [statusTxt, tagsContainer, '-', tagAddBtn, '->', startBtn, shutdownBtn, migrateBtn, consoleBtn, moreBtn],
defaults: { statusStore: me.statusStore },
items: [
{
@@ -344,10 +377,12 @@ Ext.define('PVE.lxc.Config', {
me.mon(me.statusStore, 'load', function(s, records, success) {
var status;
var lock;
+ var rec;
+
if (!success) {
status = 'unknown';
} else {
- var rec = s.data.get('status');
+ rec = s.data.get('status');
status = rec ? rec.data.value : 'unknown';
rec = s.data.get('template');
template = rec ? rec.data.value : false;
@@ -357,6 +392,35 @@ Ext.define('PVE.lxc.Config', {
statusTxt.update({ lock: lock });
+ rec = s.data.get('tags');
+ if (me.oldtags === undefined || me.oldtags !== rec?.data?.value) {
+ let tags = rec?.data?.value.split(/[,; ]/).filter(t => !!t) ?? [];
+ me.oldtags = rec?.data?.value;
+ tagsContainer.removeAll();
+ for (let i = 0; i < tags.length; i++) {
+ let tag = tags[i];
+ tagsContainer.add({
+ xtype: 'pmxTag',
+ tag,
+ layoutCallback: () => tagsContainer.updateLayout(),
+ updateCallback: function(newTag) {
+ tags[i] = newTag;
+ Proxmox.Utils.API2Request({
+ url: base_url + '/config',
+ method: 'PUT',
+ params: {
+ tags: tags.filter(t => !!t).join(','),
+ },
+ failure: function(response) {
+ Ext.Msg.alert('Error', response.htmlStatus);
+ },
+ });
+ me.oldtags = tags.join(',');
+ },
+ });
+ }
+ }
+
startBtn.setDisabled(!caps.vms['VM.PowerMgmt'] || status === 'running' || template);
shutdownBtn.setDisabled(!caps.vms['VM.PowerMgmt'] || status !== 'running');
me.down('#removeBtn').setDisabled(!caps.vms['VM.Allocate'] || status !== 'stopped');
diff --git a/www/manager6/qemu/Config.js b/www/manager6/qemu/Config.js
index 9fe933df..df5f688f 100644
--- a/www/manager6/qemu/Config.js
+++ b/www/manager6/qemu/Config.js
@@ -3,6 +3,7 @@ Ext.define('PVE.qemu.Config', {
alias: 'widget.PVE.qemu.Config',
onlineHelp: 'chapter_virtual_machines',
+ userCls: 'proxmox-tags-full',
initComponent: function() {
var me = this;
@@ -219,11 +220,42 @@ Ext.define('PVE.qemu.Config', {
],
});
+ let tagsContainer = Ext.create('Ext.container.Container', {
+ layout: {
+ type: 'hbox',
+ align: 'stretch',
+ },
+ });
+
+ let tagAddBtn = Ext.create('Proxmox.Tag', {
+ xtype: 'pmxTag',
+ addTag: true,
+ layoutCallback: () => me.query('>toolbar[dock=top]')?.[0].updateLayout(),
+ updateCallback: function(newTag) {
+ let rec = me.statusStore.data.get('tags');
+ let tags = rec?.data?.value?.split(/[;, ]/) || [];
+ tags.push(newTag);
+ Proxmox.Utils.API2Request({
+ url: base_url + '/config',
+ method: 'PUT',
+ params: {
+ tags: tags.filter(t => !!t).join(','),
+ },
+ success: function() {
+ me.statusStore.load();
+ },
+ failure: function(response) {
+ Ext.Msg.alert('Error', response.htmlStatus);
+ },
+ });
+ },
+ });
+
Ext.apply(me, {
title: Ext.String.format(gettext("Virtual Machine {0} on node '{1}'"), vm.text, nodename),
hstateid: 'kvmtab',
tbarSpacing: false,
- tbar: [statusTxt, '->', resumeBtn, startBtn, shutdownBtn, migrateBtn, consoleBtn, moreBtn],
+ tbar: [statusTxt, tagsContainer, '-', tagAddBtn, '->', resumeBtn, startBtn, shutdownBtn, migrateBtn, consoleBtn, moreBtn],
defaults: { statusStore: me.statusStore },
items: [
{
@@ -382,11 +414,12 @@ Ext.define('PVE.qemu.Config', {
var spice = false;
var xtermjs = false;
var lock;
+ var rec;
if (!success) {
status = qmpstatus = 'unknown';
} else {
- var rec = s.data.get('status');
+ rec = s.data.get('status');
status = rec ? rec.data.value : 'unknown';
rec = s.data.get('qmpstatus');
qmpstatus = rec ? rec.data.value : 'unknown';
@@ -399,6 +432,35 @@ Ext.define('PVE.qemu.Config', {
xtermjs = !!s.data.get('serial');
}
+ rec = s.data.get('tags');
+ if (me.oldtags === undefined || me.oldtags !== rec?.data?.value) {
+ let tags = rec?.data?.value.split(/[,; ]/).filter(t => !!t) ?? [];
+ me.oldtags = rec?.data?.value;
+ tagsContainer.removeAll();
+ for (let i = 0; i < tags.length; i++) {
+ let tag = tags[i];
+ tagsContainer.add({
+ xtype: 'pmxTag',
+ tag,
+ layoutCallback: () => tagsContainer.updateLayout(),
+ updateCallback: function(newTag) {
+ tags[i] = newTag;
+ Proxmox.Utils.API2Request({
+ url: base_url + '/config',
+ method: 'PUT',
+ params: {
+ tags: tags.filter(t => !!t).join(','),
+ },
+ failure: function(response) {
+ Ext.Msg.alert('Error', response.htmlStatus);
+ },
+ });
+ me.oldtags = tags.join(',');
+ },
+ });
+ }
+ }
+
if (template) {
return;
}
--
2.30.2
^ permalink raw reply [flat|nested] 13+ messages in thread
* [pve-devel] [PATCH manager v4 09/10] ui: tree/ResourceTree: show Tags in tree
2022-03-23 10:34 [pve-devel] [PATCH cluster/widget-toolkit/manager v4] add tags to ui Dominik Csapak
` (9 preceding siblings ...)
2022-03-23 10:34 ` [pve-devel] [PATCH manager v4 08/10] ui: {lxc, qemu}/Config: show Tags and make them editable Dominik Csapak
@ 2022-03-23 10:34 ` Dominik Csapak
2022-03-23 10:34 ` [pve-devel] [PATCH manager v4 10/10] ui: form/GlobalSearchField: display tags and allow to search for them Dominik Csapak
11 siblings, 0 replies; 13+ messages in thread
From: Dominik Csapak @ 2022-03-23 10:34 UTC (permalink / raw)
To: pve-devel
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
www/manager6/lxc/Config.js | 4 +++-
www/manager6/qemu/Config.js | 4 +++-
www/manager6/tree/ResourceTree.js | 4 ++++
3 files changed, 10 insertions(+), 2 deletions(-)
diff --git a/www/manager6/lxc/Config.js b/www/manager6/lxc/Config.js
index 4e2b4bb8..80c5642b 100644
--- a/www/manager6/lxc/Config.js
+++ b/www/manager6/lxc/Config.js
@@ -216,8 +216,10 @@ Ext.define('PVE.lxc.Config', {
},
});
+ let vm_text = `${vm.vmid} (${vm.name})`;
+
Ext.apply(me, {
- title: Ext.String.format(gettext("Container {0} on node '{1}'"), vm.text, nodename),
+ title: Ext.String.format(gettext("Container {0} on node '{1}'"), vm_text, nodename),
hstateid: 'lxctab',
tbarSpacing: false,
tbar: [statusTxt, tagsContainer, '-', tagAddBtn, '->', startBtn, shutdownBtn, migrateBtn, consoleBtn, moreBtn],
diff --git a/www/manager6/qemu/Config.js b/www/manager6/qemu/Config.js
index df5f688f..c1865b7f 100644
--- a/www/manager6/qemu/Config.js
+++ b/www/manager6/qemu/Config.js
@@ -251,8 +251,10 @@ Ext.define('PVE.qemu.Config', {
},
});
+ let vm_text = `${vm.vmid} (${vm.name})`;
+
Ext.apply(me, {
- title: Ext.String.format(gettext("Virtual Machine {0} on node '{1}'"), vm.text, nodename),
+ title: Ext.String.format(gettext("Virtual Machine {0} on node '{1}'"), vm_text, nodename),
hstateid: 'kvmtab',
tbarSpacing: false,
tbar: [statusTxt, tagsContainer, '-', tagAddBtn, '->', resumeBtn, startBtn, shutdownBtn, migrateBtn, consoleBtn, moreBtn],
diff --git a/www/manager6/tree/ResourceTree.js b/www/manager6/tree/ResourceTree.js
index 301f7557..049194ee 100644
--- a/www/manager6/tree/ResourceTree.js
+++ b/www/manager6/tree/ResourceTree.js
@@ -5,6 +5,8 @@ Ext.define('PVE.tree.ResourceTree', {
extend: 'Ext.tree.TreePanel',
alias: ['widget.pveResourceTree'],
+ userCls: 'proxmox-tags-circle',
+
statics: {
typeDefaults: {
node: {
@@ -114,6 +116,8 @@ Ext.define('PVE.tree.ResourceTree', {
}
}
+ info.text += PVE.Utils.renderTags(info.tags);
+
info.text = status + info.text;
},
--
2.30.2
^ permalink raw reply [flat|nested] 13+ messages in thread
* [pve-devel] [PATCH manager v4 10/10] ui: form/GlobalSearchField: display tags and allow to search for them
2022-03-23 10:34 [pve-devel] [PATCH cluster/widget-toolkit/manager v4] add tags to ui Dominik Csapak
` (10 preceding siblings ...)
2022-03-23 10:34 ` [pve-devel] [PATCH manager v4 09/10] ui: tree/ResourceTree: show Tags in tree Dominik Csapak
@ 2022-03-23 10:34 ` Dominik Csapak
11 siblings, 0 replies; 13+ messages in thread
From: Dominik Csapak @ 2022-03-23 10:34 UTC (permalink / raw)
To: pve-devel
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
www/manager6/form/GlobalSearchField.js | 19 ++++++++++++++-----
1 file changed, 14 insertions(+), 5 deletions(-)
diff --git a/www/manager6/form/GlobalSearchField.js b/www/manager6/form/GlobalSearchField.js
index 267a480d..7a27cb29 100644
--- a/www/manager6/form/GlobalSearchField.js
+++ b/www/manager6/form/GlobalSearchField.js
@@ -15,6 +15,7 @@ Ext.define('PVE.form.GlobalSearchField', {
grid: {
xtype: 'gridpanel',
+ userCls: 'proxmox-tags-full',
focusOnToFront: false,
floating: true,
emptyText: Proxmox.Utils.noneText,
@@ -23,7 +24,7 @@ Ext.define('PVE.form.GlobalSearchField', {
scrollable: {
xtype: 'scroller',
y: true,
- x: false,
+ x: true,
},
store: {
model: 'PVEResources',
@@ -78,6 +79,10 @@ Ext.define('PVE.form.GlobalSearchField', {
text: gettext('Description'),
flex: 1,
dataIndex: 'text',
+ renderer: function(value, mD, rec) {
+ let tags = PVE.Utils.renderTags(rec.data.tags);
+ return `${value}${tags}`;
+ },
},
{
text: gettext('Node'),
@@ -104,16 +109,20 @@ Ext.define('PVE.form.GlobalSearchField', {
'storage': ['type', 'pool', 'node', 'storage'],
'default': ['name', 'type', 'node', 'pool', 'vmid'],
};
- let fieldArr = fieldMap[item.data.type] || fieldMap.default;
+ let fields = fieldMap[item.data.type] || fieldMap.default;
+ let fieldArr = fields.map(field => item.data[field]?.toString().toLowerCase());
+ if (item.data.tags) {
+ let tags = item.data.tags.split(/[;, ]/);
+ fieldArr.push(...tags);
+ }
let filterWords = me.filterVal.split(/\s+/);
// all text is case insensitive and each split-out word is searched for separately.
// a row gets 1 point for every partial match, and and additional point for every exact match
let match = 0;
- for (let field of fieldArr) {
- let fieldValue = item.data[field]?.toString().toLowerCase();
- if (fieldValue === undefined) {
+ for (let fieldValue of fieldArr) {
+ if (fieldValue === undefined || fieldValue === "") {
continue;
}
for (let filterWord of filterWords) {
--
2.30.2
^ permalink raw reply [flat|nested] 13+ messages in thread