[pve-devel] [PATCH common v7 1/2] JSONSchema: refactor tag regex

2022-06-21 Thread Dominik Csapak
we'll use that elsewhere too

Signed-off-by: Dominik Csapak 
---
 src/PVE/JSONSchema.pm | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/src/PVE/JSONSchema.pm b/src/PVE/JSONSchema.pm
index ab718f3..54c149d 100644
--- a/src/PVE/JSONSchema.pm
+++ b/src/PVE/JSONSchema.pm
@@ -696,12 +696,14 @@ register_standard_option('proxmox-remote', {
 type => 'string', format => 'proxmox-remote',
 });
 
+our $PVE_TAG_RE = qr/[a-z0-9_][a-z0-9_\-\+\.]*/i;
+
 # used for pve-tag-list in e.g., guest configs
 register_format('pve-tag', \&pve_verify_tag);
 sub pve_verify_tag {
 my ($value, $noerr) = @_;
 
-return $value if $value =~ m/^[a-z0-9_][a-z0-9_\-\+\.]*$/i;
+return $value if $value =~ m/^${PVE_TAG_RE}$/i;
 
 return undef if $noerr;
 
-- 
2.30.2



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



[pve-devel] [PATCH cluster v7 2/3] Cluster: add get_guest_config_properties

2022-06-21 Thread Dominik Csapak
akin to get_guest_config_property, but with a list of properties.
uses the new CFS_IPC_GET_GUEST_CONFIG_PROPERTIES

also adds the same NOTEs regarding parsing/permissions to the comment
of get_guest_config_property

Signed-off-by: Dominik Csapak 
---
 data/PVE/Cluster.pm | 27 +++
 1 file changed, 27 insertions(+)

diff --git a/data/PVE/Cluster.pm b/data/PVE/Cluster.pm
index 05451fd..d0148fc 100644
--- a/data/PVE/Cluster.pm
+++ b/data/PVE/Cluster.pm
@@ -340,10 +340,37 @@ sub get_node_kv {
 return $res;
 }
 
+# properties: an array-ref of config properties you want to get, e.g., this
+# is perfect to get multiple properties of a guest _fast_
+# (>100 faster than manual parsing here)
+# vmid: optional, if a valid is passed we only check that one, else return all
+# NOTE: does *not* searches snapshot and PENDING entries sections!
+# NOTE: returns the guest config lines (excluding trailing whitespace) as is,
+#   so for non-trivial properties, checking the validity must be done
+# NOTE: no permission check is done, that is the responsibilty of the caller
+sub get_guest_config_properties {
+my ($properties, $vmid) = @_;
+
+die "properties required" if !defined($properties);
+
+my $num_props = scalar(@$properties);
+die "only up to 255 properties supported" if $num_props > 255;
+my $bindata = pack "VC", $vmid // 0, $num_props;
+for my $property (@$properties) {
+   $bindata .= pack "Z*", $property;
+}
+my $res = $ipcc_send_rec_json->(CFS_IPC_GET_GUEST_CONFIG_PROPERTIES, 
$bindata);
+
+return $res;
+}
+
 # property: a config property you want to get, e.g., this is perfect to get
 # the 'lock' entry of a guest _fast_ (>100 faster than manual parsing here)
 # vmid: optional, if a valid is passed we only check that one, else return all
 # NOTE: does *not* searches snapshot and PENDING entries sections!
+# NOTE: returns the guest config lines (excluding trailing whitespace) as is,
+#   so for non-trivial properties, checking the validity must be done
+# NOTE: no permission check is done, that is the responsibilty of the caller
 sub get_guest_config_property {
 my ($property, $vmid) = @_;
 
-- 
2.30.2



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



[pve-devel] [PATCH widget-toolkit v7 2/3] add class for 'admin' tags

2022-06-21 Thread Dominik Csapak
when a tag starts with '+', we want to emphasize it, so it's clear that
it's an 'admin' tag, do this by making the font bold

Signed-off-by: Dominik Csapak 
---
 src/Utils.js | 4 
 src/css/ext6-pmx.css | 4 
 2 files changed, 8 insertions(+)

diff --git a/src/Utils.js b/src/Utils.js
index eb13838..7d490c0 100644
--- a/src/Utils.js
+++ b/src/Utils.js
@@ -1360,6 +1360,10 @@ utilities: {
let txtCls = Proxmox.Utils.getTextContrastClass(rgb);
cls = `proxmox-tag-${txtCls}`;
}
+
+   if (string[0] === '+') {
+   cls += ' admin-tag';
+   }
return `${string}`;
 },
 },
diff --git a/src/css/ext6-pmx.css b/src/css/ext6-pmx.css
index 3e0f04f..486de18 100644
--- a/src/css/ext6-pmx.css
+++ b/src/css/ext6-pmx.css
@@ -6,6 +6,10 @@
 background-color: LightYellow;
 }
 
+.admin-tag {
+font-weight: bold;
+}
+
 .proxmox-tags-full .proxmox-tag-light,
 .proxmox-tags-full .proxmox-tag-dark {
 border-radius: 3px;
-- 
2.30.2



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



[pve-devel] [PATCH manager v7 13/14] ui: ResourceGrid: render tags

2022-06-21 Thread Dominik Csapak
with the 'full' styling

Signed-off-by: Dominik Csapak 
---
 www/manager6/data/ResourceStore.js | 3 +++
 www/manager6/grid/ResourceGrid.js  | 1 +
 2 files changed, 4 insertions(+)

diff --git a/www/manager6/data/ResourceStore.js 
b/www/manager6/data/ResourceStore.js
index b18f7dd8..0bfe9259 100644
--- a/www/manager6/data/ResourceStore.js
+++ b/www/manager6/data/ResourceStore.js
@@ -295,6 +295,9 @@ Ext.define('PVE.data.ResourceStore', {
},
tags: {
header: gettext('Tags'),
+   renderer: (value) => {
+   return PVE.Utils.renderTags(value, PVE.Utils.tagOverrides);
+   },
type: 'string',
hidden: true,
sortable: true,
diff --git a/www/manager6/grid/ResourceGrid.js 
b/www/manager6/grid/ResourceGrid.js
index 29906a37..9376bcc2 100644
--- a/www/manager6/grid/ResourceGrid.js
+++ b/www/manager6/grid/ResourceGrid.js
@@ -7,6 +7,7 @@ Ext.define('PVE.grid.ResourceGrid', {
property: 'type',
direction: 'ASC',
 },
+userCls: 'proxmox-tags-full',
 initComponent: function() {
let me = this;
 
-- 
2.30.2



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



[pve-devel] [PATCH manager v7 02/14] api: /version: add 'tag-style'

2022-06-21 Thread Dominik Csapak
to be able to use it in the gui directly after login

Signed-off-by: Dominik Csapak 
---
 PVE/API2.pm | 7 ++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/PVE/API2.pm b/PVE/API2.pm
index a4256160..26fcc02e 100644
--- a/PVE/API2.pm
+++ b/PVE/API2.pm
@@ -111,6 +111,11 @@ __PACKAGE__->register_method ({
optional => 1,
description => 'The default console viewer to use.',
},
+   'tag-style' => {
+   type => 'string',
+   optional => 1,
+   description => 'Cluster wide tag style overrides',
+   },
},
 },
 code => sub {
@@ -119,7 +124,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-style)) {
$res->{$k} = $datacenter_confg->{$k} if exists 
$datacenter_confg->{$k};
}
 
-- 
2.30.2



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



[pve-devel] [PATCH common v7 2/2] JSONSchema: pve-tag: add syntax for 'admin' tags

2022-06-21 Thread Dominik Csapak
by prefixing the tag with '+'

these can be useful to distinguish tags set by a 'normal' user, and an
admin. This patch is only useful with additional patches that
check those permissions while setting the tags though.

i chose a syntax that was invalid before, but does not add
unnecessary overhead. Additionally, the character was allowed (but not
as first), so there should problem arise from that.

Signed-off-by: Dominik Csapak 
---
 src/PVE/JSONSchema.pm | 9 -
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/src/PVE/JSONSchema.pm b/src/PVE/JSONSchema.pm
index 54c149d..bf2c9ca 100644
--- a/src/PVE/JSONSchema.pm
+++ b/src/PVE/JSONSchema.pm
@@ -696,7 +696,7 @@ register_standard_option('proxmox-remote', {
 type => 'string', format => 'proxmox-remote',
 });
 
-our $PVE_TAG_RE = qr/[a-z0-9_][a-z0-9_\-\+\.]*/i;
+our $PVE_TAG_RE = qr/\+?[a-z0-9_][a-z0-9_\-\+\.]*/i;
 
 # used for pve-tag-list in e.g., guest configs
 register_format('pve-tag', \&pve_verify_tag);
@@ -710,6 +710,13 @@ sub pve_verify_tag {
 die "invalid characters in tag\n";
 }
 
+sub is_admin_pve_tag {
+my ($tag) = @_;
+return undef if !defined($tag);
+return 1 if $tag =~ m/^\+/;
+return 0;
+}
+
 sub pve_parse_startup_order {
 my ($value) = @_;
 
-- 
2.30.2



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



[pve-devel] [PATCH manager v7 11/14] ui: form/GlobalSearchField: display tags and allow to search for them

2022-06-21 Thread Dominik Csapak
each tag is treated like a seperate field, so it weighs more if the user
searches for the exact string of a single tag

Signed-off-by: Dominik Csapak 
---
 www/manager6/form/GlobalSearchField.js | 20 +++-
 1 file changed, 15 insertions(+), 5 deletions(-)

diff --git a/www/manager6/form/GlobalSearchField.js 
b/www/manager6/form/GlobalSearchField.js
index 267a480d..8e815d4f 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,11 @@ Ext.define('PVE.form.GlobalSearchField', {
text: gettext('Description'),
flex: 1,
dataIndex: 'text',
+   renderer: function(value, mD, rec) {
+   let overrides = PVE.Utils.tagOverrides;
+   let tags = PVE.Utils.renderTags(rec.data.tags, overrides);
+   return `${value}${tags}`;
+   },
},
{
text: gettext('Node'),
@@ -104,16 +110,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



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



[pve-devel] [PATCH manager v7 10/14] ui: tree/ResourceTree: show Tags in tree

2022-06-21 Thread Dominik Csapak
Signed-off-by: Dominik Csapak 
---
 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 9f1994d3..1b79628e 100644
--- a/www/manager6/lxc/Config.js
+++ b/www/manager6/lxc/Config.js
@@ -206,8 +206,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, '->', startBtn, shutdownBtn, 
migrateBtn, consoleBtn, moreBtn],
diff --git a/www/manager6/qemu/Config.js b/www/manager6/qemu/Config.js
index 2cd6d856..5c8fa620 100644
--- a/www/manager6/qemu/Config.js
+++ b/www/manager6/qemu/Config.js
@@ -242,8 +242,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, '->', resumeBtn, startBtn, 
shutdownBtn, migrateBtn, consoleBtn, moreBtn],
diff --git a/www/manager6/tree/ResourceTree.js 
b/www/manager6/tree/ResourceTree.js
index 139defab..d41721b9 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, PVE.Utils.tagOverrides);
+
info.text = status + info.text;
 },
 
-- 
2.30.2



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



[pve-devel] [PATCH widget-toolkit v7 1/3] add tag related helpers

2022-06-21 Thread Dominik Csapak
helpers to
* generate a color from a string consistently
* generate a html tag for a tag
* related css classes

contrast is calculated according to SAPC draft:
https://github.com/Myndex/SAPC-APCA

which is likely to become a w3c guideline in the future and seems
to be a better algorithm for this

Signed-off-by: Dominik Csapak 
---
 src/Utils.js | 90 
 src/css/ext6-pmx.css | 45 ++
 2 files changed, 135 insertions(+)

diff --git a/src/Utils.js b/src/Utils.js
index 6a03057..eb13838 100644
--- a/src/Utils.js
+++ b/src/Utils.js
@@ -1272,6 +1272,96 @@ 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
+   }
+
+   let alpha = 0.7; // make the color a bit brighter
+   let bg = 255; // assume white background
+
+   return [
+   (hash & 255)*alpha + bg*(1-alpha),
+   ((hash >> 8) & 255)*alpha + bg*(1-alpha),
+   ((hash >> 16) & 255)*alpha + bg*(1-alpha),
+   ];
+},
+
+rgbToCss: function(rgb) {
+   return `rgb(${rgb[0]}, ${rgb[1]}, ${rgb[2]})`;
+},
+
+rgbToHex: function(rgb) {
+   let r = Math.round(rgb[0]).toString(16);
+   let g = Math.round(rgb[1]).toString(16);
+   let b = Math.round(rgb[2]).toString(16);
+   return `${r}${g}${b}`;
+},
+
+hexToRGB: function(hex) {
+   if (!hex) {
+   return undefined;
+   }
+   if (hex.length === 7) {
+   hex = hex.slice(1);
+   }
+   let r = parseInt(hex.slice(0, 2), 16);
+   let g = parseInt(hex.slice(2, 4), 16);
+   let b = parseInt(hex.slice(4, 6), 16);
+   return [r, g, b];
+},
+
+// optimized & simplified SAPC function
+// https://github.com/Myndex/SAPC-APCA
+getTextContrastClass: function(rgb) {
+   const blkThrs = 0.022;
+   const blkClmp = 1.414;
+
+   // linearize & gamma correction
+   let r = (rgb[0]/255)**2.4;
+   let g = (rgb[1]/255)**2.4;
+   let b = (rgb[2]/255)**2.4;
+
+   // relative luminance sRGB
+   let bg = r*0.2126729 + g*0.7151522 + b*0.0721750;
+
+   // black clamp
+   bg = bg > blkThrs ? bg : bg + (blkThrs - bg) ** blkClmp;
+
+   // SAPC with white text
+   let contrastLight = bg ** 0.65 - 1;
+   // SAPC with black text
+   let contrastDark = bg ** 0.56 - 0.046134502;
+
+   if (Math.abs(contrastLight) >= Math.abs(contrastDark)) {
+   return 'light';
+   } else {
+   return 'dark';
+   }
+},
+
+getTagElement: function(string, color_overrides) {
+   let rgb = color_overrides?.[string] || 
Proxmox.Utils.stringToRGB(string);
+   let bgcolor = Proxmox.Utils.rgbToCss(rgb);
+   let style = `background-color: ${bgcolor};`;
+   let cls;
+   if (rgb.length > 3) {
+   let fgcolor = Proxmox.Utils.rgbToCss([rgb[3], rgb[4], rgb[5]]);
+   style += `color: ${fgcolor}`;
+   cls = "proxmox-tag-dark";
+   } else {
+   let txtCls = Proxmox.Utils.getTextContrastClass(rgb);
+   cls = `proxmox-tag-${txtCls}`;
+   }
+   return `${string}`;
+},
 },
 
 singleton: true,
diff --git a/src/css/ext6-pmx.css b/src/css/ext6-pmx.css
index 886e24a..3e0f04f 100644
--- a/src/css/ext6-pmx.css
+++ b/src/css/ext6-pmx.css
@@ -6,6 +6,51 @@
 background-color: LightYellow;
 }
 
+.proxmox-tags-full .proxmox-tag-light,
+.proxmox-tags-full .proxmox-tag-dark {
+border-radius: 3px;
+padding: 1px 6px;
+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 !important;
+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: 6px;
+margin-right: 1px;
+display: inline-block;
+color: transparent !important;
+overflow: hidden;
+vertical-align: bottom;
+}
+
+.proxmox-tags-full .proxmox-tag-light {
+color: #fff;
+background-color: #383838;
+}
+
+.proxmox-tags-full .proxmox-tag-dark {
+color: #000;
+background-color: #f0f0f0;
+}
+
 .x-mask-msg-text {
 text-align: center;
 }
-- 
2.30.2



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



[pve-devel] [PATCH manager v7 06/14] ui: dc/OptionView: add editors for tag settings

2022-06-21 Thread Dominik Csapak
namely for 'tag-tree-style' and 'tag-colors'.
display the tag overrides directly as they will appear as tags

Signed-off-by: Dominik Csapak 
---
 www/manager6/Utils.js | 20 +
 www/manager6/dc/OptionView.js | 84 +++
 2 files changed, 104 insertions(+)

diff --git a/www/manager6/Utils.js b/www/manager6/Utils.js
index bd0490a4..4bd8875b 100644
--- a/www/manager6/Utils.js
+++ b/www/manager6/Utils.js
@@ -1851,6 +1851,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, overrides) {
+   let text = '';
+   if (tagstext) {
+   let tags = (tagstext.split(/[,; ]/) || []).filter(t => !!t);
+   text += ' ';
+   tags.forEach((tag) => {
+   text += Proxmox.Utils.getTagElement(tag, overrides);
+   });
+   }
+   return text;
+},
+
 tagCharRegex: /^[a-z0-9+_.-]$/i,
 },
 
diff --git a/www/manager6/dc/OptionView.js b/www/manager6/dc/OptionView.js
index 51696982..6dbe1d28 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;
@@ -305,6 +306,86 @@ Ext.define('PVE.dc.OptionView', {
submitValue: true,
}],
});
+   me.rows['tag-style'] = {
+   required: true,
+   renderer: (value) => {
+   if (value === undefined) {
+   return gettext('No Overrides');
+   }
+   let colors = PVE.Utils.parseTagOverrides(value.colors);
+   let shape = value['tree-shape'];
+   let shapeText = PVE.Utils.tagTreeStyles[shape] ?? 
Proxmox.Utils.defaultText;
+   let txt = Ext.String.format(gettext("Tree Shape: {0}"), 
shapeText);
+   if (Object.keys(colors).length > 0) {
+   txt += ', ';
+   }
+   for (const tag of Object.keys(colors)) {
+   txt += Proxmox.Utils.getTagElement(tag, colors);
+   }
+   return txt;
+   },
+   header: gettext('Tag Style Override'),
+   editor: {
+   xtype: 'proxmoxWindowEdit',
+   width: 800,
+   subject: gettext('Tag Color Override'),
+   fieldDefaults: {
+   labelWidth: 100,
+   },
+   url: '/api2/extjs/cluster/options',
+   items: [
+   {
+   xtype: 'inputpanel',
+   setValues: function(values) {
+   if (values === undefined) {
+   return undefined;
+   }
+   values = values?.['tag-style'] ?? {};
+   values['tree-shape'] = values?.['tree-shape'] || 
'__default__';
+   return 
Proxmox.panel.InputPanel.prototype.setValues.call(this, values);
+   },
+   onGetValues: function(values) {
+   let style = {};
+   if (values.colors) {
+   style.colors = values.colors;
+   }
+   if (values['tree-shape']) {
+   style['tree-shape'] = values['tree-shape'];
+   }
+   let value = PVE.Parser.printPropertyString(style);
+   if (value === '') {
+   return {
+   delete: 'tag-style',
+   };
+   }
+   return {
+   'tag-style': value,
+   };
+   },
+   items: [
+   {
+   name: 'tree-shape',
+   xtype: 'proxmoxKVComboBox',
+   fieldLabel: gettext('Tree Shape'),
+   comboItems: 
Object.entries(PVE.Utils.tagTreeStyles),
+   defaultValue: '__default__',
+   deleteEmpty: true,
+   },
+   {
+   xtype: 'displayfield',
+   fieldLabel: gettext('Color Overrides'),
+   },
+ 

[pve-devel] [PATCH manager v7 09/14] ui: {lxc, qemu}/Config: show Tags and make them editable

2022-06-21 Thread Dominik Csapak
add the tags in the status line, and add a button for adding new ones

Signed-off-by: Dominik Csapak 
---
 www/manager6/lxc/Config.js  | 32 ++--
 www/manager6/qemu/Config.js | 31 +--
 2 files changed, 59 insertions(+), 4 deletions(-)

diff --git a/www/manager6/lxc/Config.js b/www/manager6/lxc/Config.js
index 89b59c9b..9f1994d3 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,33 @@ Ext.define('PVE.lxc.Config', {
],
});
 
+   let tagsContainer = Ext.create('PVE.panel.TagEditContainer', {
+   tags: vm.tags,
+   listeners: {
+   change: function(tags) {
+   Proxmox.Utils.API2Request({
+   url: base_url + '/config',
+   method: 'PUT',
+   params: {
+   tags,
+   },
+   success: function() {
+   me.statusStore.load();
+   },
+   failure: function(response) {
+   Ext.Msg.alert('Error', response.htmlStatus);
+   me.statusStore.load();
+   },
+   });
+   },
+   },
+   });
 
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, '->', startBtn, shutdownBtn, 
migrateBtn, consoleBtn, moreBtn],
defaults: { statusStore: me.statusStore },
items: [
{
@@ -344,10 +367,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 +382,9 @@ Ext.define('PVE.lxc.Config', {
 
statusTxt.update({ lock: lock });
 
+   rec = s.data.get('tags');
+   tagsContainer.loadTags(rec?.data?.value);
+
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..2cd6d856 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,33 @@ Ext.define('PVE.qemu.Config', {
],
});
 
+   let tagsContainer = Ext.create('PVE.panel.TagEditContainer', {
+   tags: vm.tags,
+   listeners: {
+   change: function(tags) {
+   Proxmox.Utils.API2Request({
+   url: base_url + '/config',
+   method: 'PUT',
+   params: {
+   tags,
+   },
+   success: function() {
+   me.statusStore.load();
+   },
+   failure: function(response) {
+   Ext.Msg.alert('Error', response.htmlStatus);
+   me.statusStore.load();
+   },
+   });
+   },
+   },
+   });
+
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, '->', resumeBtn, startBtn, 
shutdownBtn, migrateBtn, consoleBtn, moreBtn],
defaults: { statusStore: me.statusStore },
items: [
{
@@ -382,11 +405,12 @@ Ext.define('PVE.qemu.Config', {
var spice = false;
var xtermjs = false;
var lock;
+   var rec;
 

[pve-devel] [PATCH manager v7 08/14] ui: add form/TagEdit.js

2022-06-21 Thread Dominik Csapak
this is a wrapper container for holding a list of (editable) tags
intended to be used in the lxc/qemu status toolbar

Signed-off-by: Dominik Csapak 
---
 www/manager6/Makefile|   1 +
 www/manager6/form/TagEdit.js | 151 +++
 2 files changed, 152 insertions(+)
 create mode 100644 www/manager6/form/TagEdit.js

diff --git a/www/manager6/Makefile b/www/manager6/Makefile
index 9d610f71..eb4be4c5 100644
--- a/www/manager6/Makefile
+++ b/www/manager6/Makefile
@@ -75,6 +75,7 @@ JSSRC=
\
form/iScsiProviderSelector.js   \
form/TagColorGrid.js\
form/Tag.js \
+   form/TagEdit.js \
grid/BackupView.js  \
grid/FirewallAliases.js \
grid/FirewallOptions.js \
diff --git a/www/manager6/form/TagEdit.js b/www/manager6/form/TagEdit.js
new file mode 100644
index ..5a267169
--- /dev/null
+++ b/www/manager6/form/TagEdit.js
@@ -0,0 +1,151 @@
+Ext.define('PVE.panel.TagEditContainer', {
+extend: 'Ext.container.Container',
+alias: 'widget.pveTagEditContainer',
+
+tagCount: 0,
+
+layout: {
+   type: 'hbox',
+   align: 'stretch',
+},
+
+loadTags: function(tagstring = '', inEdit, force = false) {
+   let me = this;
+
+   if (me.oldTags === tagstring && !force) {
+   return;
+   }
+
+   let tags = tagstring.split(/[;, ]/).filter((t) => !!t) || [];
+   me.suspendLayout = true;
+   me.tags = {};
+   me.removeAllTags();
+   tags.forEach((tag) => {
+   me.addTag(tag, inEdit);
+   });
+   me.suspendLayout = false;
+   me.updateLayout();
+   if (!force) {
+   me.oldTags = tagstring;
+   }
+},
+
+getEditBtnHtml: function() {
+   let me = this;
+   let cls = '';
+   let qtip = '';
+   if (me.editMode) {
+   qtip = gettext('Apply Changes');
+   cls = 'check';
+   } else {
+   qtip = gettext('Edit Tags');
+   cls = 'pencil';
+   }
+   return ` `;
+},
+
+toggleEdit: function(cancel) {
+   let me = this;
+   me.editMode = !me.editMode;
+   let tagCount = 0;
+   me.tagFields.forEach((tag) => {
+   tag.setMode(me.editMode ? 'editable' : 'normal');
+   if (tag.isVisible() && !tag.addTag) {
+   tagCount++;
+   }
+   });
+
+   me.addTagBtn.setVisible(me.editMode);
+   me.editBtn.setHtml(me.getEditBtnHtml());
+   me.noTagsField.setVisible(!me.editMode && tagCount === 0);
+   me.cancelBtn.setVisible(me.editMode);
+
+   if (!me.editMode) {
+   let tags = [];
+   if (cancel) {
+   me.loadTags(me.oldTags, false, true);
+   } else {
+   me.tagFields.forEach((tag) => {
+   let tagValue = tag.getTag();
+   if (tag.isVisible() && tagValue) {
+   tags.push(tagValue);
+   }
+   });
+   tags = tags.join(',');
+   if (me.oldTags !== tags) {
+   me.oldTags = tags;
+   me.fireEvent('change', tags);
+   }
+   }
+   }
+   me.updateLayout();
+},
+
+removeAllTags: function() {
+   let me = this;
+   me.tagFields.forEach((tag) => {
+   me.remove(tag);
+   });
+   me.tagFields = [];
+   me.noTagsField.setVisible(true);
+},
+
+addTag: function(tag, inEdit) {
+   let me = this;
+   let tagField = me.insert(me.tagFields.length + 1, {
+   xtype: 'pmxTag',
+   tag,
+   mode: inEdit ? 'editable' : 'normal',
+   layoutCallback: () => me.updateLayout(),
+   });
+   me.tagFields.push(tagField);
+   me.noTagsField.setVisible(false);
+},
+
+initComponent: function() {
+   let me = this;
+   me.tagFields = [];
+   me.callParent();
+   me.noTagsField = me.add({
+   xtype: 'box',
+   html: gettext('No Tags'),
+   });
+   me.addTagBtn = me.add({
+   xtype: 'pmxTag',
+   addTag: true,
+   hidden: true,
+   layoutCallback: () => me.updateLayout(),
+   listeners: {
+   change: function(tag) {
+   me.addTag(tag, true);
+   },
+   },
+   });
+   me.cancelBtn = me.add({
+   xtype: 'box',
+   html: ` `,
+   style: {
+   cursor: 'pointer',
+   },
+   hidden: true,
+   listeners: {
+   click: () => me.toggleEdit(true),
+   element: 'el',
+   },
+   });
+   me.editBtn = me.add({
+   xtype: 'box',
+   html: me.getEditBtnHtml(),
+   style: {
+   

[pve-devel] [PATCH manager v7 14/14] ui: form/Tag(Edit): add drag & drop when editing tags

2022-06-21 Thread Dominik Csapak
Signed-off-by: Dominik Csapak 
---
 www/manager6/form/Tag.js | 22 +++--
 www/manager6/form/TagEdit.js | 96 +++-
 2 files changed, 114 insertions(+), 4 deletions(-)

diff --git a/www/manager6/form/Tag.js b/www/manager6/form/Tag.js
index 91190051..dcbd9597 100644
--- a/www/manager6/form/Tag.js
+++ b/www/manager6/form/Tag.js
@@ -31,6 +31,9 @@ Ext.define('Proxmox.Tag', {
if (event.target.tagName !== 'I') {
return;
}
+   if (event.target.classList.contains('handle')) {
+   return;
+   }
switch (me.mode) {
case 'editable':
if (me.addTag) {
@@ -156,12 +159,14 @@ Ext.define('Proxmox.Tag', {
let text = me.tag;
let cursor = 'pointer';
let padding = '0px';
+   let dragHandleStyle = 'none';
switch (mode) {
case 'normal':
iconStyle += 'display: none;';
padding = undefined;
break;
case 'editable':
+   dragHandleStyle = '';
break;
case 'edit':
me.tagEl().contentEditable = true;
@@ -174,12 +179,14 @@ Ext.define('Proxmox.Tag', {
if (me.addTag) {
me.setText(text);
me.setStyle('cursor', cursor);
+   dragHandleStyle = 'none';
}
 
me.setStyle('padding-right', padding);
 
me.iconEl().classList = `fa fa-${icon}${me.faIconStyle}`;
me.iconEl().style = iconStyle;
+   me.dragEl().style.display = dragHandleStyle;
me.mode = mode;
 },
 
@@ -233,14 +240,18 @@ Ext.define('Proxmox.Tag', {
}
 },
 
-tagEl: function() {
+dragEl: function() {
return this.el?.dom?.children?.[0];
 },
 
-iconEl: function() {
+tagEl: function() {
return this.el?.dom?.children?.[1];
 },
 
+iconEl: function() {
+   return this.el?.dom?.children?.[2];
+},
+
 initComponent: function() {
let me = this;
if (me.tag === undefined && !me.addTag) {
@@ -256,10 +267,15 @@ Ext.define('Proxmox.Tag', {
 
let iconStyle = me.mode !== 'editable' ? 'display: none' : 
'padding-right: 6px;';
let iconCls = me.icons[me.addTag ? 'addTag' : me.mode];
+   let dragHandleStyle = 'cursor: grab; font-size: 14px;';
+   if (me.addTag || me.mode !== 'editable') {
+   dragHandleStyle += 'display: none';
+   }
 
let adminCls = me.tag[0] === '+' ? 'admin-tag' : '';
+   let dragHandle = ` `;
let icon = ` `;
-   me.html = `${me.tag}${icon}`;
+   me.html = `${dragHandle}${me.tag}${icon}`;
 
me.callParent();
if (me.addTag) {
diff --git a/www/manager6/form/TagEdit.js b/www/manager6/form/TagEdit.js
index 5a267169..85f9f63e 100644
--- a/www/manager6/form/TagEdit.js
+++ b/www/manager6/form/TagEdit.js
@@ -18,7 +18,7 @@ Ext.define('PVE.panel.TagEditContainer', {
 
let tags = tagstring.split(/[;, ]/).filter((t) => !!t) || [];
me.suspendLayout = true;
-   me.tags = {};
+   me.tagList = [];
me.removeAllTags();
tags.forEach((tag) => {
me.addTag(tag, inEdit);
@@ -87,6 +87,7 @@ Ext.define('PVE.panel.TagEditContainer', {
me.remove(tag);
});
me.tagFields = [];
+   me.tagList = [];
me.noTagsField.setVisible(true);
 },
 
@@ -94,11 +95,13 @@ Ext.define('PVE.panel.TagEditContainer', {
let me = this;
let tagField = me.insert(me.tagFields.length + 1, {
xtype: 'pmxTag',
+   tagIndex: me.tagList.length,
tag,
mode: inEdit ? 'editable' : 'normal',
layoutCallback: () => me.updateLayout(),
});
me.tagFields.push(tagField);
+   me.tagList.push(tag);
me.noTagsField.setVisible(false);
 },
 
@@ -147,5 +150,96 @@ Ext.define('PVE.panel.TagEditContainer', {
if (me.tags) {
me.loadTags(me.tags);
}
+
+   me.on('render', function(v) {
+   me.dragzone = Ext.create('Ext.dd.DragZone', v.getEl(), {
+   getDragData: function(e) {
+   let source = e.getTarget('.handle');
+   if (source) {
+   let sourceId = source.parentNode.id;
+   let cmp = Ext.getCmp(sourceId);
+   let tag = cmp.getTag();
+   let ddel = document.createElement('div');
+   ddel.classList.add('proxmox-tags-full');
+   ddel.innerHTML = Proxmox.Utils.getTagElement(tag, 
PVE.Utils.tagOverrides);
+   let repairXY = Ext.fly(source).getXY();
+   cmp.setDisabled(true);
+   ddel.id = Ext.id();
+   return {
+   ddel,
+   repairXY,
+   tagIndex: cmp.tagIndex,
+   tag: cmp.getTag(),
+   

[pve-devel] [PATCH manager v7 07/14] ui: add form/Tag

2022-06-21 Thread Dominik Csapak
displays a single tag, with the ability to edit inline on click (when
the mode is set to editable). This brings up a list of globally available tags
for simple selection.

Also has a mode for adding a new Tag.

This has 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 
---
 www/manager6/Makefile|   1 +
 www/manager6/form/Tag.js | 276 +++
 2 files changed, 277 insertions(+)
 create mode 100644 www/manager6/form/Tag.js

diff --git a/www/manager6/Makefile b/www/manager6/Makefile
index 60ae421e..9d610f71 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 ..0a754edb
--- /dev/null
+++ b/www/manager6/form/Tag.js
@@ -0,0 +1,276 @@
+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 layout in the containing element
+// this is necessary since we circumvent extjs layout with 
'contentEditable'
+layoutCallback: Ext.emptyFn,
+
+style: {
+   'white-space': 'nowrap',
+},
+
+icons: {
+   addTag: 'plus',
+   editable: 'minus',
+   normal: '',
+   edit: 'check',
+},
+
+faIconStyle: '-square',
+
+mode: 'normal',
+
+// 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') {
+   return;
+   }
+   switch (me.mode) {
+   case 'editable':
+   if (me.addTag) {
+   break;
+   }
+   me.setText('');
+   me.finishEdit();
+   break;
+   case 'edit':
+   if (me.addTag && me.tagEl().innerHTML === '') {
+   event.browserEvent.preventDefault();
+   event.browserEvent.stopPropagation();
+   break;
+   }
+   me.finishEdit();
+   break;
+   default: break;
+   }
+},
+
+onClick: function(event) {
+   let me = this;
+   if (event.target.tagName !== 'SPAN' && !me.addTag) {
+   return;
+   }
+   if (me.mode === 'editable') {
+   me.startEdit();
+   }
+},
+
+startEdit: function() {
+   let me = this;
+   me.setMode('edit');
+
+   // 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-full',
+   displayField: 'tag',
+   itemTpl: [
+   '{[Proxmox.Utils.getTagElement(values.tag, 
PVE.Utils.tagOverrides)]}',
+   ],
+   store: [],
+   listeners: {
+   select: function(picker, rec) {
+   me.setText(rec.data.tag);
+   me.finishEdit();
+   },
+   },
+   });
+   }
+   me.picker.getStore().clearFilter();
+   let taglist = PVE.Utils.tagList.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;
+   if (!me.addTag) {
+   if (tag === '') {
+   // "delete" ourselves
+   me.setVisible(false);
+   }
+   me.tag = tag;
+   me.updateColor(me.tag);
+   }
+
+   if (update) {
+   me.fireEvent('change', tag);
+   }
+
+   me.tagEl().contentEditable = false;
+   me.se

[pve-devel] [PATCH cluster v7 3/3] datacenter.cfg: add option for tag-style

2022-06-21 Thread Dominik Csapak
its a property string containing 'tree-shape' and 'colors'
the colors are formatted like this:
:[:]

Signed-off-by: Dominik Csapak 
---
 data/PVE/DataCenterConfig.pm | 37 
 1 file changed, 37 insertions(+)

diff --git a/data/PVE/DataCenterConfig.pm b/data/PVE/DataCenterConfig.pm
index bcd77f8..30db37c 100644
--- a/data/PVE/DataCenterConfig.pm
+++ b/data/PVE/DataCenterConfig.pm
@@ -125,6 +125,29 @@ sub pve_verify_mac_prefix {
 return $mac_prefix;
 }
 
+my $COLOR_RE = '[0-9a-fA-F]{6}';
+my $TAG_COLOR_OVERRIDE_RE = 
"(?:${PVE::JSONSchema::PVE_TAG_RE}:${COLOR_RE}(?:\:${COLOR_RE})?)";
+
+my $tag_style_format = {
+'tree-shape' => {
+   optional => 1,
+   type => 'string',
+   enum => ['full', 'circle', 'dense', 'none'],
+   default => 'circle',
+   description => "Tag style in tree. 'full' draws the full tag. 'circle' 
".
+   "draws only a circle with the background color. 'dense' only draws 
".
+   "a small rectancle (useful when many tags are assigned to each 
guest).".
+   "'none' disables showing the tags.",
+},
+'colors' => {
+   optional => 1,
+   type => 'string',
+   pattern => "${TAG_COLOR_OVERRIDE_RE}(?:\;$TAG_COLOR_OVERRIDE_RE)*",
+   typetext => ':[:][;=...]',
+   description => "Manual color mapping for tags (semicolon separated).",
+},
+};
+
 my $datacenter_schema = {
 type => "object",
 additionalProperties => 0,
@@ -250,6 +273,12 @@ my $datacenter_schema = {
maxLength => 64 * 1024,
optional => 1,
},
+   'tag-style' => {
+   optional => 1,
+   type => 'string',
+   description => "Tag style options.",
+   format => $tag_style_format,
+   },
 },
 };
 
@@ -294,6 +323,10 @@ sub parse_datacenter_config {
$res->{webauthn} = parse_property_string($webauthn_format, $webauthn);
 }
 
+if (my $tag_style = $res->{'tag-style'}) {
+   $res->{'tag-style'} = parse_property_string($tag_style_format, 
$tag_style);
+}
+
 # for backwards compatibility only, new migration property has precedence
 if (defined($res->{migration_unsecure})) {
if (defined($res->{migration}->{type})) {
@@ -353,6 +386,10 @@ sub write_datacenter_config {
$cfg->{webauthn} = PVE::JSONSchema::print_property_string($webauthn, 
$webauthn_format);
 }
 
+if (ref(my $tag_style = $cfg->{'tag-style'})) {
+   $cfg->{'tag-style'} = 
PVE::JSONSchema::print_property_string($tag_style, $tag_style_format);
+}
+
 my $comment = '';
 # add description as comment to top of file
 my $description = $cfg->{description} || '';
-- 
2.30.2



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



[pve-devel] [PATCH widget-toolkit v7 3/3] Toolkit: add override for Ext.dd.DragDropManager

2022-06-21 Thread Dominik Csapak
to fix selection behavior for Ext.dd.DragZone.

Signed-off-by: Dominik Csapak 
---
 src/Toolkit.js | 16 
 1 file changed, 16 insertions(+)

diff --git a/src/Toolkit.js b/src/Toolkit.js
index a1d291e..b6eacfb 100644
--- a/src/Toolkit.js
+++ b/src/Toolkit.js
@@ -699,6 +699,22 @@ Ext.define('Proxmox.view.DragZone', {
 },
 });
 
+// Fix text selection on drag when using DragZone,
+// see https://forum.sencha.com/forum/showthread.php?335100
+Ext.define('Proxmox.dd.DragDropManager', {
+override: 'Ext.dd.DragDropManager',
+
+stopEvent: function(e) {
+   if (this.stopPropagation) {
+   e.stopPropagation();
+   }
+
+   if (this.preventDefault) {
+   e.preventDefault();
+   }
+},
+});
+
 // force alert boxes to be rendered with an Error Icon
 // since Ext.Msg is an object and not a prototype, we need to override it
 // after the framework has been initiated
-- 
2.30.2



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



[pve-devel] [PATCH manager v7 01/14] api: /cluster/resources: add tags to returned properties

2022-06-21 Thread Dominik Csapak
by querying 'lock' and 'tags' with 'get_guest_config_properties'

Signed-off-by: Dominik Csapak 
---
 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 525a95a1..c1e96bd7 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



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



[pve-devel] [PATCH container v7 1/1] check_ct_modify_config_perm: check 'admin' tags privileges

2022-06-21 Thread Dominik Csapak
normal tags require 'VM.Config.Options' on the CT, admin tags require
'Sys.Modify' on '/'

a user can set/delete/reorder tags, as long as no admin tags get
added/removed

Signed-off-by: Dominik Csapak 
---
 src/PVE/LXC.pm| 31 +++
 src/PVE/LXC/Config.pm |  3 ++-
 2 files changed, 33 insertions(+), 1 deletion(-)

diff --git a/src/PVE/LXC.pm b/src/PVE/LXC.pm
index fe63087..ce2f8ff 100644
--- a/src/PVE/LXC.pm
+++ b/src/PVE/LXC.pm
@@ -1333,6 +1333,37 @@ sub check_ct_modify_config_perm {
} elsif ($opt eq 'hookscript') {
# For now this is restricted to root@pam
raise_perm_exc("changing the hookscript is only allowed for 
root\@pam");
+   } elsif ($opt eq 'tags') {
+   my $old_tags = {};
+   my $new_tags = {};
+   my $check_admin = 0;
+   my $check_user = 0;
+
+   map { $old_tags->{$_} = 1 } 
PVE::Tools::split_list($oldconf->{$opt});
+   map { $new_tags->{$_} = 1 } 
PVE::Tools::split_list($newconf->{$opt});
+
+   my $check_tags = sub {
+   my ($a, $b) = @_;
+   foreach my $tag (keys %$a) {
+   next if $b->{$tag};
+   if (PVE::JSONSchema::is_admin_pve_tag($tag)) {
+   $check_admin = 1;
+   } else {
+   $check_user = 1;
+   }
+   }
+   };
+
+   $check_tags->($old_tags, $new_tags);
+   $check_tags->($new_tags, $old_tags);
+
+   if ($check_admin) {
+   $rpcenv->check($authuser, "/", ['Sys.Modify']);
+   }
+
+   if ($check_user) {
+   $rpcenv->check_vm_perm($authuser, $vmid, $pool, 
['VM.Config.Options']);
+   }
} else {
$rpcenv->check_vm_perm($authuser, $vmid, $pool, 
['VM.Config.Options']);
}
diff --git a/src/PVE/LXC/Config.pm b/src/PVE/LXC/Config.pm
index b4b0261..2362d74 100644
--- a/src/PVE/LXC/Config.pm
+++ b/src/PVE/LXC/Config.pm
@@ -597,7 +597,8 @@ my $confdesc = {
 },
 tags => {
type => 'string', format => 'pve-tag-list',
-   description => 'Tags of the Container. This is only meta information.',
+   description => 'Tags of the Container. This is only meta information. 
Prefixing a tag with'.
+   "'+' marks it as an admin tag and can only be set with 'Sys.Modify' 
on '/'.",
optional => 1,
 },
 debug => {
-- 
2.30.2



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



[pve-devel] [PATCH common/cluster/qemu/container/wt/manager v7] add tags to ui

2022-06-21 Thread Dominik Csapak
this series brings the already existing 'tags' for ct/vms to the gui:
* tags can be edited in the status toolbar of the guest
* existing tags will be shown in the tree/global search/resource grids
* when editing a tag, a list of existing tags will be shown
* by default, the color is (consistently) autogenerated based on the
  text
* that color can be overriden in datacenter -> options (cluster wide)
  (edit window for browser local storage is TBD)
* by default, the text color is either white or black, depending which
  provides the greater contrast (according to SAPC)
* this text color can also be overridden
* there are multiple shapes available for the tree (see [0])
* adds new 'admin' tags that need higher privliges, these can then be
  used to enable features like 'inlude in backup by tag', etc.

i left the patches of the 'admin' tags seperate, so that we can
decide if this is the right approach, but still apply some other parts.
(pve-common 2/2, wt 2/3, qemu-server, pve-container, pve-manager 12/14)

same with the gui patches for drag&drop, not sure if the ux is good
enough.
(wt 3/3, pve-manager: 14/14)

sorry for the large series

(i omitted the changeslogs up to v6, were getting a bit long ;) )

changes from v6:
* reworded some commit messages
* added small benchmark result to CFS_IPC_GET_GUEST_CONFIG_PROPERTIES commit msg
* reshaped datacenter.cfg format into a property-string
  (also combined the gui edit window for shape+color override)
* refactored the pve-tags regex in pve-common/JSONSchema
* added admin tags ('+tag' syntax) with priv checks in qemu-sever/pve-container
  and subtle highlighting in the gui (bold-text)
* added tag rendering in resource grids
* add patches for drag&drop support when editing

pve-common:

Dominik Csapak (2):
  JSONSchema: refactor tag regex
  JSONSchema: pve-tag: add syntax for 'admin' tags

 src/PVE/JSONSchema.pm | 11 ++-
 1 file changed, 10 insertions(+), 1 deletion(-)

pve-cluster:

Dominik Csapak (3):
  add CFS_IPC_GET_GUEST_CONFIG_PROPERTIES method
  Cluster: add get_guest_config_properties
  datacenter.cfg: add option for tag-style

 data/PVE/Cluster.pm  |  27 ++
 data/PVE/DataCenterConfig.pm |  37 
 data/src/cfs-ipc-ops.h   |   2 +
 data/src/server.c|  64 +
 data/src/status.c| 177 ---
 data/src/status.h|   3 +
 6 files changed, 254 insertions(+), 56 deletions(-)

proxmox-widget-Toolkit:

Dominik Csapak (3):
  add tag related helpers
  add class for 'admin' tags
  Toolkit: add override for Ext.dd.DragDropManager

 src/Toolkit.js   | 16 
 src/Utils.js | 94 
 src/css/ext6-pmx.css | 49 +++
 3 files changed, 159 insertions(+)

qemu-server:

Dominik Csapak (1):
  api: update: check 'admin' tags privileges

 PVE/API2/Qemu.pm  | 56 ++-
 PVE/QemuServer.pm |  3 ++-
 2 files changed, 57 insertions(+), 2 deletions(-)

pve-container:

Dominik Csapak (1):
  check_ct_modify_config_perm: check 'admin' tags privileges

 src/PVE/LXC.pm| 31 +++
 src/PVE/LXC/Config.pm |  3 ++-
 2 files changed, 33 insertions(+), 1 deletion(-)

pve-manager:

Dominik Csapak (14):
  api: /cluster/resources: add tags to returned properties
  api: /version: add 'tag-style'
  ui: parse and save tag color overrides from /version
  ui: tree/ResourceTree: collect tags on update
  ui: add form/TagColorGrid
  ui: dc/OptionView: add editors for tag settings
  ui: add form/Tag
  ui: add form/TagEdit.js
  ui: {lxc,qemu}/Config: show Tags and make them editable
  ui: tree/ResourceTree: show Tags in tree
  ui: form/GlobalSearchField: display tags and allow to search for them
  ui: form/Tag: add 'admin-tag' class to admin tags
  ui: ResourceGrid: render tags
  ui: form/Tag(Edit): add drag & drop when editing tags

 PVE/API2.pm|   7 +-
 PVE/API2/Cluster.pm|   9 +-
 www/css/ext6-pve.css   |   5 +
 www/manager6/Makefile  |   3 +
 www/manager6/Utils.js  |  69 +
 www/manager6/Workspace.js  |  13 +
 www/manager6/data/ResourceStore.js |   9 +
 www/manager6/dc/OptionView.js  |  84 ++
 www/manager6/form/GlobalSearchField.js |  20 +-
 www/manager6/form/Tag.js   | 294 
 www/manager6/form/TagColorGrid.js  | 357 +
 www/manager6/form/TagEdit.js   | 245 +
 www/manager6/grid/ResourceGrid.js  |   1 +
 www/manager6/lxc/Config.js |  36 ++-
 www/manager6/qemu/Config.js|  35 ++-
 www/manager6/tree/ResourceTree.js  |  20 +-
 16 files changed, 1190 insertions(+), 17 deletions(-)
 create mode 100644 www/manager6/form/Tag.js
 create mode 100644 www/manager6/form/TagColorGrid.js
 create mode 100644 www/manager6/form/TagEdit

[pve-devel] [PATCH qemu-server v7 1/1] api: update: check 'admin' tags privileges

2022-06-21 Thread Dominik Csapak
normal tags require 'VM.Config.Options' on the VM, admin tags require
'Sys.Modify' on '/'

a user can set/delete/reorder tags, as long as no admin tags get
added/removed

Signed-off-by: Dominik Csapak 
---
 PVE/API2/Qemu.pm  | 56 ++-
 PVE/QemuServer.pm |  3 ++-
 2 files changed, 57 insertions(+), 2 deletions(-)

diff --git a/PVE/API2/Qemu.pm b/PVE/API2/Qemu.pm
index 99b426e..37b56c8 100644
--- a/PVE/API2/Qemu.pm
+++ b/PVE/API2/Qemu.pm
@@ -519,7 +519,6 @@ my $generaloptions = {
 'startup' => 1,
 'tdf' => 1,
 'template' => 1,
-'tags' => 1,
 };
 
 my $vmpoweroptions = {
@@ -589,6 +588,7 @@ my $check_vm_modify_config_perm = sub {
next if PVE::QemuServer::is_valid_drivename($opt);
next if $opt eq 'cdrom';
next if $opt =~ m/^(?:unused|serial|usb)\d+$/;
+   next if $opt eq 'tags';
 
 
if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
@@ -1559,6 +1559,27 @@ my $update_vm_api  = sub {
}
PVE::QemuConfig->add_to_pending_delete($conf, $opt, $force);
PVE::QemuConfig->write_config($vmid, $conf);
+   } elsif ($opt eq 'tags') {
+   my $check_admin = 0;
+   my $check_options = 0;
+   foreach my $tag (PVE::Tools::split_list($val)) {
+   if (PVE::JSONSchema::is_admin_pve_tag($tag)) {
+   $check_admin = 1;
+   } else {
+   $check_options = 1;
+   }
+   }
+
+   if ($check_admin) {
+   $rpcenv->check($authuser, "/", ['Sys.Modify']);
+   }
+
+   if ($check_options) {
+   $rpcenv->check_vm_perm($authuser, $vmid, undef, 
['VM.Config.Options']);
+   }
+
+   delete $conf->{$opt};
+   PVE::QemuConfig->write_config($vmid, $conf);
} else {
PVE::QemuConfig->add_to_pending_delete($conf, $opt, $force);
PVE::QemuConfig->write_config($vmid, $conf);
@@ -1619,6 +1640,39 @@ my $update_vm_api  = sub {
die "only root can modify '$opt' config for real 
devices\n";
}
$conf->{pending}->{$opt} = $param->{$opt};
+   } elsif ($opt eq 'tags') {
+   my $check_admin = 0;
+   my $check_user = 0;
+
+   my $old_tags = {};
+   my $new_tags = {};
+
+   map { $old_tags->{$_} = 1 } 
PVE::Tools::split_list($conf->{$opt});
+   map { $new_tags->{$_} = 1 } 
PVE::Tools::split_list($param->{$opt});
+
+   my $check_tags = sub {
+   my ($a, $b) = @_;
+   foreach my $tag (keys %$a) {
+   next if $b->{$tag};
+   if (PVE::JSONSchema::is_admin_pve_tag($tag)) {
+   $check_admin = 1;
+   } else {
+   $check_user = 1;
+   }
+   }
+   };
+
+   $check_tags->($old_tags, $new_tags);
+   $check_tags->($new_tags, $old_tags);
+
+   if ($check_admin) {
+   $rpcenv->check($authuser, "/", ['VM.Config.Options']);
+   }
+
+   if ($check_user) {
+   $rpcenv->check_vm_perm($authuser, $vmid, undef, 
['VM.Config.Options']);
+   }
+   $conf->{pending}->{$opt} = $param->{$opt};
} else {
$conf->{pending}->{$opt} = $param->{$opt};
 
diff --git a/PVE/QemuServer.pm b/PVE/QemuServer.pm
index b3964bc..48247cc 100644
--- a/PVE/QemuServer.pm
+++ b/PVE/QemuServer.pm
@@ -698,7 +698,8 @@ EODESCR
 },
 tags => {
type => 'string', format => 'pve-tag-list',
-   description => 'Tags of the VM. This is only meta information.',
+   description => 'Tags of the VM. This is only meta information. 
Prefixing a tag with'.
+   "'+' marks it as an admin tag and can only be set with 'Sys.Modify' 
on '/'.",
optional => 1,
 },
 rng0 => {
-- 
2.30.2



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



[pve-devel] [PATCH manager v7 03/14] ui: parse and save tag color overrides from /version

2022-06-21 Thread Dominik Csapak
into a global list of overrides. on update, also parse the values
from the browser localstore

Signed-off-by: Dominik Csapak 
---
 www/manager6/Utils.js | 40 +++
 www/manager6/Workspace.js | 13 +
 2 files changed, 53 insertions(+)

diff --git a/www/manager6/Utils.js b/www/manager6/Utils.js
index 7ca6a271..8653a4b9 100644
--- a/www/manager6/Utils.js
+++ b/www/manager6/Utils.js
@@ -1803,6 +1803,46 @@ Ext.define('PVE.Utils', {
 },
 
 notesTemplateVars: ['cluster', 'guestname', 'node', 'vmid'],
+
+parseTagOverrides: function(overrides) {
+   let colors = {};
+   (overrides || "").split(';').forEach(color => {
+   if (!color) {
+   return;
+   }
+   let [tag, color_hex, font_hex] = color.split(':');
+   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];
+   if (font_hex) {
+   colors[tag].push(parseInt(font_hex.slice(0, 2), 16));
+   colors[tag].push(parseInt(font_hex.slice(2, 4), 16));
+   colors[tag].push(parseInt(font_hex.slice(4, 6), 16));
+   }
+   });
+   return colors;
+},
+
+tagOverrides: {},
+
+updateTagOverrides: function(colors) {
+   let sp = Ext.state.Manager.getProvider();
+   let color_state = sp.get('colors', '');
+   let browser_colors = PVE.Utils.parseTagOverrides(color_state);
+   PVE.Utils.tagOverrides = Ext.apply({}, browser_colors, colors);
+   Ext.GlobalEvents.fireEvent('tag-color-override');
+},
+
+updateTagSettings: function(overrides, style) {
+   PVE.Utils.updateTagOverrides(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..cd31249e 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-style']?.colors;
+   let shape = PVE.VersionInfo?.['tag-style']?.['tree-shape'];
+
+   PVE.Utils.updateTagSettings(colors, shape);
+   if (colors) {
+   // refresh tree once
+   PVE.data.ResourceStore.fireEvent('load');
+   }
+},
+
 initComponent: function() {
let me = this;
 
-- 
2.30.2



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



[pve-devel] [PATCH manager v7 04/14] ui: tree/ResourceTree: collect tags on update

2022-06-21 Thread Dominik Csapak
into a global list, so that we have it avaiable anywhere
also add the tags from the tagOverrides on update into the list

Signed-off-by: Dominik Csapak 
---
 www/manager6/Utils.js  |  7 +++
 www/manager6/data/ResourceStore.js |  6 ++
 www/manager6/tree/ResourceTree.js  | 16 ++--
 3 files changed, 27 insertions(+), 2 deletions(-)

diff --git a/www/manager6/Utils.js b/www/manager6/Utils.js
index 8653a4b9..5242e778 100644
--- a/www/manager6/Utils.js
+++ b/www/manager6/Utils.js
@@ -1804,6 +1804,13 @@ Ext.define('PVE.Utils', {
 
 notesTemplateVars: ['cluster', 'guestname', 'node', 'vmid'],
 
+tagList: new Set(),
+
+updateTagList: function(tags) {
+   let override_tags = Object.keys(PVE.Utils.tagOverrides);
+   PVE.Utils.tagList = [...new Set([...tags, ...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..139defab 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.updateTagList(tags);
store.resumeEvents();
store.fireEvent('refresh', store);
 
-- 
2.30.2



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



[pve-devel] [PATCH manager v7 12/14] ui: form/Tag: add 'admin-tag' class to admin tags

2022-06-21 Thread Dominik Csapak
so that they are emphasized there too

Signed-off-by: Dominik Csapak 
---
 www/manager6/form/Tag.js | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/www/manager6/form/Tag.js b/www/manager6/form/Tag.js
index 0a754edb..91190051 100644
--- a/www/manager6/form/Tag.js
+++ b/www/manager6/form/Tag.js
@@ -141,6 +141,7 @@ Ext.define('Proxmox.Tag', {
 setText: function(text) {
let me = this;
me.tagEl().innerHTML = text;
+   me.tagEl().classList = text[0] === '+' ? 'admin-tag' : '';
me.layoutCallback();
 },
 
@@ -256,8 +257,9 @@ Ext.define('Proxmox.Tag', {
let iconStyle = me.mode !== 'editable' ? 'display: none' : 
'padding-right: 6px;';
let iconCls = me.icons[me.addTag ? 'addTag' : me.mode];
 
+   let adminCls = me.tag[0] === '+' ? 'admin-tag' : '';
let icon = ` `;
-   me.html = `${me.tag}${icon}`;
+   me.html = `${me.tag}${icon}`;
 
me.callParent();
if (me.addTag) {
-- 
2.30.2



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



[pve-devel] [PATCH manager v7 05/14] ui: add form/TagColorGrid

2022-06-21 Thread Dominik Csapak
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 
---
 www/css/ext6-pve.css  |   5 +
 www/manager6/Makefile |   1 +
 www/manager6/Utils.js |   2 +
 www/manager6/form/TagColorGrid.js | 357 ++
 4 files changed, 365 insertions(+)
 create mode 100644 www/manager6/form/TagColorGrid.js

diff --git a/www/css/ext6-pve.css b/www/css/ext6-pve.css
index dadb84a9..f7d0c420 100644
--- a/www/css/ext6-pve.css
+++ b/www/css/ext6-pve.css
@@ -651,3 +651,8 @@ table.osds td:first-of-type {
 background-color: rgb(245, 245, 245);
 color: #000;
 }
+
+.x-pveColorPicker-default-cell > .x-grid-cell-inner {
+padding-top: 0px;
+padding-bottom: 0px;
+}
diff --git a/www/manager6/Makefile b/www/manager6/Makefile
index d16770b1..60ae421e 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/Utils.js b/www/manager6/Utils.js
index 5242e778..bd0490a4 100644
--- a/www/manager6/Utils.js
+++ b/www/manager6/Utils.js
@@ -1850,6 +1850,8 @@ Ext.define('PVE.Utils', {
 

Ext.ComponentQuery.query('pveResourceTree')[0].setUserCls(`proxmox-tags-${style}`);
 },
+
+tagCharRegex: /^[a-z0-9+_.-]$/i,
 },
 
 singleton: true,
diff --git a/www/manager6/form/TagColorGrid.js 
b/www/manager6/form/TagColorGrid.js
new file mode 100644
index ..3ad8e07f
--- /dev/null
+++ b/www/manager6/form/TagColorGrid.js
@@ -0,0 +1,357 @@
+Ext.define('PVE.form.ColorPicker', {
+extend: 'Ext.form.FieldContainer',
+alias: 'widget.pveColorPicker',
+
+defaultBindProperty: 'value',
+
+config: {
+   value: null,
+},
+
+height: 24,
+
+layout: {
+   type: 'hbox',
+   align: 'stretch',
+},
+
+getValue: function() {
+   return this.realvalue.slice(1);
+},
+
+setValue: function(value) {
+   let me = this;
+   me.setColor(value);
+   if (value && value.length === 6) {
+   me.picker.value = value[0] !== '#' ? `#${value}` : value;
+   }
+},
+
+setColor: function(value) {
+   let me = this;
+   let oldValue = me.realvalue;
+   me.realvalue = value;
+   let color = value.length === 6 ? `#${value}` : undefined;
+   me.down('#picker').setStyle('background-color', color);
+   me.down('#text').setValue(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.items = [
+   {
+   xtype: 'textfield',
+   itemId: 'text',
+   minLength: !me.allowBlank ? 6 : undefined,
+   maxLength: 6,
+   enforceMaxLength: true,
+   allowBlank: me.allowBlank,
+   emptyText: me.allowBlank ? gettext('Automatic') : undefined,
+   maskRe: /[a-f0-9]/i,
+   regex: /^[a-f0-9]{6}$/i,
+   flex: 1,
+   listeners: {
+   change: function(field, value) {
+   me.setValue(value);
+   },
+   },
+   },
+   {
+   xtype: 'box',
+   style: {
+   'margin-left': '1px',
+   border: '1px solid #cfcfcf',
+   },
+   itemId: 'picker',
+   width: 24,
+   contentEl: me.picker,
+   },
+   ];
+
+   me.callParent();
+   me.picker.oninput = function() {
+   me.setColor(me.picker.value.slice(1));
+   };
+},
+});
+
+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;
+   let colors;
+   if (Ext.isObject(value)) {
+   colors = value.colors;
+   } else {
+

[pve-devel] [PATCH cluster v7 1/3] add CFS_IPC_GET_GUEST_CONFIG_PROPERTIES method

2022-06-21 Thread Dominik Csapak
for getting multiple properties from the in memory config of the
guests in one go. Keep the existing IPC call as is for backward
compatibility and add this as separate, new one.

It basically behaves the same as
CFS_IPC_GET_GUEST_CONFIG_PROPERTY, but takes a list of properties
instead and returns multiple properties per guest.

The old way of getting a single property is now also done by
the new function, since it basically performs identically.

Here a short benchmark:

Setup:
PVE in a VM with cpu type host (12700k) and 4 cores
1 typical configs with both 'lock' and 'tags' set at the end
and fairly long tags ('test-tag1,test-tag2,test-tag3')
(normal vm with a snapshot, ~1KiB)

i let it run 100 times each, times in ms

old code:

total time  time per iteration
1054.2  10.2

new code:

num props  total time  time per iter  function
2  1332.2  13.2   get_properties
1  1051.2  10.2   get_properties
2  2082.2  20.2   get_property (2 calls to get_property)
1  1034.2  10.2   get_property

so a call with the new code for one property is the same as with the
old code, and adding a second property only adds a bit of additional
time (in this case ~30%)

Signed-off-by: Dominik Csapak 
---
 data/src/cfs-ipc-ops.h |   2 +
 data/src/server.c  |  64 +++
 data/src/status.c  | 177 -
 data/src/status.h  |   3 +
 4 files changed, 190 insertions(+), 56 deletions(-)

diff --git a/data/src/cfs-ipc-ops.h b/data/src/cfs-ipc-ops.h
index 003e233..249308d 100644
--- a/data/src/cfs-ipc-ops.h
+++ b/data/src/cfs-ipc-ops.h
@@ -43,4 +43,6 @@
 
 #define CFS_IPC_VERIFY_TOKEN 12
 
+#define CFS_IPC_GET_GUEST_CONFIG_PROPERTIES 13
+
 #endif
diff --git a/data/src/server.c b/data/src/server.c
index 549788a..ced9cfc 100644
--- a/data/src/server.c
+++ b/data/src/server.c
@@ -89,6 +89,13 @@ typedef struct {
char property[];
 } cfs_guest_config_propery_get_request_header_t;
 
+typedef struct {
+   struct qb_ipc_request_header req_header;
+   uint32_t vmid;
+   uint8_t num_props;
+   char props[]; /* list of \0 terminated properties */
+} cfs_guest_config_properties_get_request_header_t;
+
 typedef struct {
struct qb_ipc_request_header req_header;
char token[];
@@ -348,6 +355,63 @@ static int32_t s1_msg_process_fn(
 
result = cfs_create_guest_conf_property_msg(outbuf, 
memdb, rh->property, rh->vmid);
}
+   } else if (request_id == CFS_IPC_GET_GUEST_CONFIG_PROPERTIES) {
+
+   cfs_guest_config_properties_get_request_header_t *rh =
+   (cfs_guest_config_properties_get_request_header_t *) 
data;
+
+   size_t remaining = request_size - 
G_STRUCT_OFFSET(cfs_guest_config_properties_get_request_header_t, props);
+
+   result = 0;
+   if (rh->vmid < 100 && rh->vmid != 0) {
+   cfs_debug("vmid out of range %u", rh->vmid);
+   result = -EINVAL;
+   } else if (rh->vmid >= 100 && !vmlist_vm_exists(rh->vmid)) {
+   result = -ENOENT;
+   } else if (rh->num_props == 0) {
+   cfs_debug("num_props == 0");
+   result = -EINVAL;
+   } else if (remaining <= 1) {
+   cfs_debug("property length <= 1, %ld", remaining);
+   result = -EINVAL;
+   } else {
+   const char **properties = malloc(sizeof(char*) * 
rh->num_props);
+   char *current = (rh->props);
+   for (uint8_t i = 0; i < rh->num_props; i++) {
+   size_t proplen = strnlen(current, remaining);
+   if (proplen == 0) {
+   cfs_debug("property length 0");
+   result = -EINVAL;
+   break;
+   }
+   if (proplen == remaining) {
+   cfs_debug("property not \\0 terminated");
+   result = -EINVAL;
+   break;
+   }
+   if (current[0] < 'a' || current[0] > 'z') {
+   cfs_debug("property does not start with [a-z]");
+   result = -EINVAL;
+   break;
+   }
+   properties[i] = current;
+   current[proplen] = '\0'; // ensure property is 0 
terminated
+   remaining -= (proplen + 1);
+   current += proplen + 1;
+   }
+
+   if (remaining != 0) {
+   cfs_debug("leftover data after parsing %u 
properties", rh->num_props);
+  

Re: [pve-devel] [PATCH v6 qemu-server 3/9] migration: test targetnode min version for cloudinit section

2022-06-21 Thread Fabian Ebner
Am 20.06.22 um 12:44 schrieb Alexandre Derumier:
> Signed-off-by: Alexandre Derumier 
> ---
>  PVE/QemuMigrate.pm| 10 +-
>  PVE/QemuServer/Helpers.pm | 26 ++
>  2 files changed, 35 insertions(+), 1 deletion(-)
> 
> diff --git a/PVE/QemuMigrate.pm b/PVE/QemuMigrate.pm
> index d52dc8d..e594564 100644
> --- a/PVE/QemuMigrate.pm
> +++ b/PVE/QemuMigrate.pm
> @@ -5,6 +5,7 @@ use warnings;
>  
>  use IO::File;
>  use IPC::Open2;
> +use JSON;

Not used anymore in v6.

>  use POSIX qw( WNOHANG );
>  use Time::HiRes qw( usleep );
>  
> @@ -23,7 +24,7 @@ use PVE::Tunnel;
>  use PVE::QemuConfig;
>  use PVE::QemuServer::CPUConfig;
>  use PVE::QemuServer::Drive;
> -use PVE::QemuServer::Helpers qw(min_version);
> +use PVE::QemuServer::Helpers qw(min_version version_cmp);

Same.

>  use PVE::QemuServer::Machine;
>  use PVE::QemuServer::Monitor qw(mon_cmd);
>  use PVE::QemuServer;
> @@ -122,6 +123,13 @@ sub prepare {
>  # test if VM exists
>  my $conf = $self->{vmconf} = PVE::QemuConfig->load_config($vmid);
>  
> +my $version = 
> PVE::QemuServer::Helpers::get_node_pvecfg_version($self->{node});
> +my $cloudinit_config = $conf->{cloudinit};
> +
> +if(defined($cloudinit_config) && keys %$cloudinit_config && 
> !PVE::QemuServer::Helpers::pvecfg_min_version($version, 7, 2, 4)) {

Version should be 7, 2, 5 (since currently released pve-manager 7.2-4
would already pass this).

Style nit: missing space after if, line too long

> + die "target node is too old and doesn't support new cloudinit section";

missing newline in error.

> +}
> +
>  my $repl_conf = PVE::ReplicationConfig->new();
>  $self->{replication_jobcfg} = 
> $repl_conf->find_local_replication_job($vmid, $self->{node});
>  $self->{is_replicated} = $repl_conf->check_for_existing_jobs($vmid, 1);
> diff --git a/PVE/QemuServer/Helpers.pm b/PVE/QemuServer/Helpers.pm
> index c10d842..c9d6b64 100644
> --- a/PVE/QemuServer/Helpers.pm
> +++ b/PVE/QemuServer/Helpers.pm
> @@ -4,6 +4,7 @@ use strict;
>  use warnings;
>  
>  use File::stat;
> +use JSON;
>  
>  use PVE::INotify;
>  use PVE::ProcFSTools;
> @@ -12,6 +13,7 @@ use base 'Exporter';
>  our @EXPORT_OK = qw(
>  min_version
>  config_aware_timeout
> +version_cmp

No need for the new export in v6.

>  );
>  
>  my $nodename = PVE::INotify::nodename();
> @@ -161,4 +163,28 @@ sub config_aware_timeout {
>  return $timeout;
>  }
>  
> +sub get_node_pvecfg_version {
> +my ($node) = @_;
> +
> +my $nodes_version_info = PVE::Cluster::get_node_kv('version-info', 
> $node);
> +return if !$nodes_version_info->{$node};
> +
> +my $version_info_json = $nodes_version_info->{$node};

Style nit: no need for this temporary variable.

> +my $version_info = decode_json($version_info_json);
> +return $version_info->{version};
> +}
> +
> +sub pvecfg_min_version {
> +my ($verstr, $major, $minor, $release) = @_;
> +
> +return 0 if !$verstr;
> +
> +if ($verstr =~ m/^(\d+)\.(\d+)-(\d+)/) {
> + return 1 if version_cmp($1, $major, $2, $minor, $3, $release) >= 0;
> + return 0;
> +}
> +
> +die "internal error: cannot check version of invalid string '$verstr'";
> +}
> +
>  1;


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



Re: [pve-devel] [PATCH v6 qemu-server 0/9] cloudinit pending behaviour change

2022-06-21 Thread Fabian Ebner
Am 20.06.22 um 12:44 schrieb Alexandre Derumier:
> Hi,
> 
> This is an attempt to cleanup current behaviour of cloudinit online changes.
> 
> Currently, we setup cloudinit options as pending, until we generate the 
> config drive.
> 
> This is not 100% true, because some option like vm name, nic mac address can 
> be changed,
> without going to pending, so user can't known if it need to regenerated it.
> 
> Also, some can are very difficult to handle, if you hotplug a nic but it's 
> failing,so pending,
> then you defined an ipconfig, and then you revert hotplug.
> or if you delete a nic, the ipconfig is no more displayed in the gui.
> 
> 
> So, instead of setting cloudinit values in pending,
> this patch serie copy the current cloudinit config in a new section 
> [special:cloudinit],
> when the config drive is generated.
> This is only an hint, to allow to display diff between the generated cloudinit
> drive, and the current vm config.
> 
> A new specific cloudinit config api is added too displaying the diff between 
> current and generated config.
> 

Still found a few minor nits, but overall, it looks good to me. Feel
free to add
Reviewed-by: Fabian Ebner 
after addressing them.

> Reminder: This need pve-manager depency bump first to check the version for 
> live migration.
> pve-manager patches series is available here : 
> https://lists.proxmox.com/pipermail/pve-devel/2021-June/048542.html

Haven't looked at it yet, but I noticed that when a mac address change
is pending, it doesn't show up in the UI. It does show with
 qm cloudinit pending 


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



Re: [pve-devel] [PATCH v6 qemu-server 7/9] api2: add cloudinit config api

2022-06-21 Thread Fabian Ebner
Am 20.06.22 um 12:45 schrieb Alexandre Derumier:
> diff --git a/PVE/QemuServer/Cloudinit.pm b/PVE/QemuServer/Cloudinit.pm
> index cdaf4e5..2355953 100644
> --- a/PVE/QemuServer/Cloudinit.pm
> +++ b/PVE/QemuServer/Cloudinit.pm
> @@ -632,4 +633,81 @@ sub dump_cloudinit_config {
>  }
>  }
>  
> +sub get_pending_config {
> +my ($conf, $vmid) = @_;
> +
> +my $newconf = dclone($conf);
> +
> +my $cloudinit_current = $newconf->{cloudinit};
> +my @cloudinit_opts = keys 
> %{PVE::QemuServer::cloudinit_config_properties()};
> +push @cloudinit_opts, 'name';
> +
> +#add cloud-init drive
> +my $drives = {};
> +PVE::QemuConfig->foreach_volume($newconf, sub {
> + my ($ds, $drive) = @_;
> + $drives->{$ds} = 1 if PVE::QemuServer::drive_is_cloudinit($drive);
> +});
> +
> +PVE::QemuConfig->foreach_volume($cloudinit_current, sub {
> + my ($ds, $drive) = @_;
> + $drives->{$ds} = 1 if PVE::QemuServer::drive_is_cloudinit($drive);
> +});
> +foreach my $ds (keys %{$drives}) {

Style nit: please use for

> + push @cloudinit_opts, $ds;
> +}
> +
> +$newconf->{name} = "VM$vmid" if !$newconf->{name};
> +$cloudinit_current->{name} = "VM$vmid" if !$cloudinit_current->{name};
> +
> +#only mac-address is used in cloud-init config. 

Style nit: trailing whitespace

> +#We don't want to display other pending net changes.
> +my $print_cloudinit_net = sub {
> + my ($conf, $opt) = @_;
> +
> + if (defined($conf->{$opt})) {
> + my $net = PVE::QemuServer::parse_net($conf->{$opt});
> + $conf->{$opt} = "macaddr=".$net->{macaddr} if $net->{macaddr};
> + }
> +};
> +
> +my $cloudinit_options = {};
> +for my $opt (@cloudinit_opts) {
> + if ($opt =~ m/^ipconfig(\d+)/) {
> + my $netid = "net$1";
> + 

Style nit: trailing whitespace

> + next if !defined($newconf->{$netid}) && 
> !defined($cloudinit_current->{$netid}) &&
> + !defined($newconf->{$opt}) && 
> !defined($cloudinit_current->{$opt});
> +
> + &$print_cloudinit_net($newconf, $netid);
> + &$print_cloudinit_net($cloudinit_current, $netid);
> + $cloudinit_options->{$netid} = 1;
> + }
> + $cloudinit_options->{$opt} = 1;
> +}
> +
> +my $res = [];
> +
> +for my $opt (keys %{$cloudinit_options}) {
> +
> + my $item = {
> + key => $opt,
> + };
> + if ($cloudinit_current->{$opt}) {
> + $item->{value} = $cloudinit_current->{$opt};
> + if (defined($newconf->{$opt})) {
> + $item->{pending} = $newconf->{$opt} if $newconf->{$opt} ne 
> $cloudinit_current->{$opt};

Style nit: line too long

> + } else {
> + $item->{delete} = 1;
> + }
> + } else {
> + $item->{pending} = $newconf->{$opt} if $newconf->{$opt}
> + }
> +
> + push @$res, $item;
> +   }
> +
> +   return $res;
> +}
> +
>  1;


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



Re: [pve-devel] [PATCH v6 qemu-server 4/9] cloudinit: add cloudinit section for current generated config.

2022-06-21 Thread Fabian Ebner
Am 20.06.22 um 12:44 schrieb Alexandre Derumier:
> Instead using vm pending options for pending cloudinit generated config,
> 
> write current generated cloudinit config in a new [special:cloudinit] SECTION.
> 
> Currently, some options like vm name, nic mac address can be hotplugged,
> so they are not way to know if the cloud-init disk is already updated.
> 
> Signed-off-by: Alexandre Derumier 
> ---
>  PVE/QemuServer.pm   | 20 +---
>  PVE/QemuServer/Cloudinit.pm | 31 +++
>  2 files changed, 48 insertions(+), 3 deletions(-)
> 
> diff --git a/PVE/QemuServer.pm b/PVE/QemuServer.pm
> index b3964bc..9f550d9 100644
> --- a/PVE/QemuServer.pm
> +++ b/PVE/QemuServer.pm
> @@ -2504,7 +2511,7 @@ sub write_vm_config {
>  
>   foreach my $key (keys %$cref) {
>   next if $key eq 'digest' || $key eq 'description' || $key eq 
> 'snapshots' ||
> - $key eq 'snapstate' || $key eq 'pending';
> + $key eq 'snapstate' || $key eq 'pending' || $key eq 'cloudinit';
>   my $value = $cref->{$key};
>   if ($key eq 'delete') {
>   die "propertry 'delete' is only allowed in [PENDING]\n"
> @@ -2528,6 +2535,8 @@ sub write_vm_config {
>  
>  &$cleanup_config($conf->{pending}, 1);
>  
> +&$cleanup_config($conf->{cloudinit}, 1);

As noted in the review of v4 already:
The second parameter should not be 1 here (it's called $pending and used
to check if the key 'delete' is allowed).

> +
>  foreach my $snapname (keys %{$conf->{snapshots}}) {
>   die "internal error: snapshot name '$snapname' is forbidden" if 
> lc($snapname) eq 'pending';
>   &$cleanup_config($conf->{snapshots}->{$snapname}, undef, $snapname);
> @@ -2571,6 +2580,11 @@ sub write_vm_config {
>   $raw .= &$generate_raw_config($conf->{pending}, 1);
>  }
>  
> +if (scalar(keys %{$conf->{cloudinit}})){
> + $raw .= "\n[special:cloudinit]\n";
> + $raw .= &$generate_raw_config($conf->{cloudinit}, 1);

Similar here, setting the second parameter is specific to pending.

> +}
> +
>  foreach my $snapname (sort keys %{$conf->{snapshots}}) {
>   $raw .= "\n[$snapname]\n";
>   $raw .= &$generate_raw_config($conf->{snapshots}->{$snapname});
> @@ -5102,9 +5116,9 @@ sub vmconfig_apply_pending {
>   $conf->{$opt} = delete $conf->{pending}->{$opt};
>   }
>  }
> -
>  # write all changes at once to avoid unnecessary i/o
>  PVE::QemuConfig->write_config($vmid, $conf);
> +

Style nit: unrelated and doesn't make it better IMHO.

>  }
>  
>  sub vmconfig_update_net {


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



[pve-devel] [PATCH qemu-server] fix #3577: prevent vms with pci passthrough to suspend to disk

2022-06-21 Thread Dominik Csapak
while it may work to suspend, resume will most likely not work, because
we do/can not save/restore the hardware state of the devices

instead, prevent the user from suspending the vm at all.

Signed-off-by: Dominik Csapak 
---
 PVE/API2/Qemu.pm | 9 -
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/PVE/API2/Qemu.pm b/PVE/API2/Qemu.pm
index 99b426e..24d0f59 100644
--- a/PVE/API2/Qemu.pm
+++ b/PVE/API2/Qemu.pm
@@ -2977,10 +2977,17 @@ __PACKAGE__->register_method({
# early check for storage permission, for better user feedback
if ($todisk) {
$rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
+   my $conf = PVE::QemuConfig->load_config($vmid);
+
+   # check for hostpci devices (suspend will maybe work, resume won't),
+   # so prevent users from suspending in the first place
+   for my $key (keys %$conf) {
+   next if $key !~ /^hostpci\d+/;
+   die "Cannot suspend VM to disk with assigned PCI devices\n";
+   }
 
if (!$statestorage) {
# get statestorage from config if none is given
-   my $conf = PVE::QemuConfig->load_config($vmid);
my $storecfg = PVE::Storage::config();
$statestorage = PVE::QemuServer::find_vmstate_storage($conf, 
$storecfg);
}
-- 
2.30.2



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



[pve-devel] [PATCH manager 2/2] VM start Timeout "Options" parameter in the GUI

2022-06-21 Thread Daniel Tschlatscher
The qemu config in the backend already allows specifying a timeout
value. This patch introduces a text field in the Options submenu when
a VM is selected, through which the VM start timeout can be set in the
config.

Signed-off-by: Daniel Tschlatscher 
---
 www/manager6/qemu/Options.js | 18 ++
 1 file changed, 18 insertions(+)

diff --git a/www/manager6/qemu/Options.js b/www/manager6/qemu/Options.js
index a1def4bb..c565066a 100644
--- a/www/manager6/qemu/Options.js
+++ b/www/manager6/qemu/Options.js
@@ -338,6 +338,24 @@ Ext.define('PVE.qemu.Options', {
},
} : undefined,
},
+   timeout: {
+   header: gettext('VM start timeout'),
+   defaultValue: Proxmox.Utils.defaultText,
+   renderer: val => val,
+   editor: caps.vms['VM.Config.Options'] ? {
+   xtype: 'proxmoxWindowEdit',
+   subject: gettext('VM start timeout'),
+   items: {
+   xtype: 'proxmoxintegerfield',
+   name: 'timeout',
+   minValue: 0,
+   maxValue: 268,
+   fieldLabel: gettext('Timeout (sec)'),
+   emptyText: Proxmox.Utils.defaultText,
+   deleteEmpty: true,
+   },
+   } : undefined,
+   },
hookscript: {
header: gettext('Hookscript'),
},
-- 
2.30.2



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



[pve-devel] [PATCH qemu-server 1/1] make the timeout value editable when the VM is locked

2022-06-21 Thread Daniel Tschlatscher
In some cases the VM could no longer start when the timeout value was
set to a very low value and afterwards, for example, hibernated. In
this case the VM is somewhat soft locked, because the API would not
allow changing the timeout value anymore. (The only way out here would
be to change the value manually in the config)

Signed-off-by: Daniel Tschlatscher 
---
To make it possible to change the timeout config value when the VM
is locked I created a function to tell whether the config should
be editable or not. This reduces clutter in the already rather long
invoking function. I also tried to keep it extensible for possible
existing values that could be included here/are added in the future.

Nonetheless, I am open to suggestions for a possibly more elegant
solution with this function.
Also, I am not quite sure about storing the "allowed" fields hardcoded
in the function. But I also didn't find where a good place to store 
such a "constant" in this repository would be.

 PVE/API2/Qemu.pm | 27 ++-
 1 file changed, 26 insertions(+), 1 deletion(-)

diff --git a/PVE/API2/Qemu.pm b/PVE/API2/Qemu.pm
index db21fd8..f37baa4 100644
--- a/PVE/API2/Qemu.pm
+++ b/PVE/API2/Qemu.pm
@@ -8,6 +8,7 @@ use POSIX;
 use IO::Socket::IP;
 use URI::Escape;
 use Crypt::OpenSSL::Random;
+use List::Util qw(first);
 
 use PVE::Cluster qw (cfs_read_file cfs_write_file);;
 use PVE::RRD;
@@ -625,6 +626,28 @@ my $check_vm_modify_config_perm = sub {
 return 1;
 };
 
+# Certain fields should still be editable when the VM is locked 
+# For now, this means that only if those certain fields are edited/deleted 
will the result be true
+sub skiplock_for_allowed_fields {
+my ($param, @deleted) = @_;
+
+my @allowed = qw"timeout";
+my $skiplock = 1;
+
+my @to_check = @deleted;
+for (keys %$param) {
+   push(@to_check, $_);
+}
+
+my $idx = 0;
+while ($skiplock && $idx < keys @to_check) {
+   $skiplock = first { $_ eq $to_check[$idx] } @allowed;
+   $idx++;
+}
+
+return $skiplock;
+}
+
 __PACKAGE__->register_method({
 name => 'vmlist',
 path => '',
@@ -1455,7 +1478,9 @@ my $update_vm_api  = sub {
push @delete, 'runningcpu' if $conf->{runningcpu};
}
 
-   PVE::QemuConfig->check_lock($conf) if !$skiplock;
+   if (!$skiplock && !skiplock_for_allowed_fields($param, @delete)) {
+   PVE::QemuConfig->check_lock($conf);
+   }
 
foreach my $opt (keys %$revert) {
if (defined($conf->{$opt})) {
-- 
2.30.2



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



[pve-devel] [PATCH qemu-server 1/2] fix #3502: VM start timeout config parameter

2022-06-21 Thread Daniel Tschlatscher
It was already possible to set the timeout parameter for the VM config
via the API. However, the value was not considered when the function
config_aware_timeout() was called.
Now, if the timeout parameter is set, it will override the heuristic
calculation of the VM start timeout.

During testing I found a problem where really big values (10^20)+
would be converted to scientific notation, which means they no longer
pass the integer type check. To get around this, I set the maximum
value for the timeout to 2,680,000 seconds, which is around 31 days.
This I'd wager, is an upper limit in which nobody should realistically
run into.

Signed-off-by: Daniel Tschlatscher 
---
 PVE/API2/Qemu.pm  | 1 +
 PVE/QemuServer.pm | 7 +++
 PVE/QemuServer/Helpers.pm | 3 +++
 3 files changed, 11 insertions(+)

diff --git a/PVE/API2/Qemu.pm b/PVE/API2/Qemu.pm
index a824657..d0a4eaa 100644
--- a/PVE/API2/Qemu.pm
+++ b/PVE/API2/Qemu.pm
@@ -2494,6 +2494,7 @@ __PACKAGE__->register_method({
description => "Wait maximal timeout seconds.",
type => 'integer',
minimum => 0,
+   maximum => 268,
default => 'max(30, vm memory in GiB)',
optional => 1,
},
diff --git a/PVE/QemuServer.pm b/PVE/QemuServer.pm
index e9aa248..81a7f6d 100644
--- a/PVE/QemuServer.pm
+++ b/PVE/QemuServer.pm
@@ -713,6 +713,13 @@ EODESCR
description => "Some (read-only) meta-information about this guest.",
optional => 1,
 },
+timeout => {
+   optional => 1,
+   type => 'integer',
+   description => 'The maximum timeout to wait for a VM to start',
+   minimum => 0,
+   maximum => 268,
+}
 };
 
 my $cicustom_fmt = {
diff --git a/PVE/QemuServer/Helpers.pm b/PVE/QemuServer/Helpers.pm
index c10d842..c26d0dc 100644
--- a/PVE/QemuServer/Helpers.pm
+++ b/PVE/QemuServer/Helpers.pm
@@ -142,6 +142,9 @@ sub version_cmp {
 
 sub config_aware_timeout {
 my ($config, $is_suspended) = @_;
+
+return $config->{timeout} if defined($config->{timeout});
+
 my $memory = $config->{memory};
 my $timeout = 30;
 
-- 
2.30.2



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



Re: [pve-devel] [PATCH qemu-server 1/2] fix #3502: VM start timeout config parameter

2022-06-21 Thread Thomas Lamprecht
fix #3502: add start timeout config parameter

Am 21/06/2022 um 17:20 schrieb Daniel Tschlatscher:
> It was already possible to set the timeout parameter for the VM config
> via the API. However, the value was not considered when the function
> config_aware_timeout() was called.

but you only add the timeout param now?! IOW. it was never possible to set
a timeout config, but one could set the parameter for a single start.
Please reword it, as of is its confusing.

> Now, if the timeout parameter is set, it will override the heuristic
> calculation of the VM start timeout.

you mean if a timeout config property/option is set, as the API parameter
was already honored in vm_start_nolock:

my $start_timeout = $params->{timeout} // config_aware_timeout($conf, $resume);

> 
> During testing I found a problem where really big values (10^20)+
> would be converted to scientific notation, which means they no longer
> pass the integer type check. To get around this, I set the maximum
> value for the timeout to 2,680,000 seconds, which is around 31 days.
> This I'd wager, is an upper limit in which nobody should realistically
> run into.

24h is already really big enough, so please use 86000, better to go lower
(but still reasonable) first, we can always easily relax it, but not really
make it stricter without breaking existing setups.

Also, did you test HA for the case when a really long timeout is set and
triggers (by faking it with some sleep added somewhere)?

> 
> Signed-off-by: Daniel Tschlatscher 
> ---
>  PVE/API2/Qemu.pm  | 1 +
>  PVE/QemuServer.pm | 7 +++
>  PVE/QemuServer/Helpers.pm | 3 +++
>  3 files changed, 11 insertions(+)
> 
> diff --git a/PVE/API2/Qemu.pm b/PVE/API2/Qemu.pm
> index a824657..d0a4eaa 100644
> --- a/PVE/API2/Qemu.pm
> +++ b/PVE/API2/Qemu.pm
> @@ -2494,6 +2494,7 @@ __PACKAGE__->register_method({
>   description => "Wait maximal timeout seconds.",
>   type => 'integer',
>   minimum => 0,
> + maximum => 268,
>   default => 'max(30, vm memory in GiB)',
>   optional => 1,
>   },
> diff --git a/PVE/QemuServer.pm b/PVE/QemuServer.pm
> index e9aa248..81a7f6d 100644
> --- a/PVE/QemuServer.pm
> +++ b/PVE/QemuServer.pm
> @@ -713,6 +713,13 @@ EODESCR
>   description => "Some (read-only) meta-information about this guest.",
>   optional => 1,
>  },
> +timeout => {

"timeout what"? in the API it's clear as there it's for an specific actions, but
as config options one cannot have any idea about what this timeout actually does
with such an overly generic name.

I'd maybe put it behind a 'start-options' format string, then it could keep the
simple name and would be still telling, it would also allow to put any future
startup related options in there, so not bloating config line amount up too 
much.

start-options: timeout=12345

> + optional => 1,
> + type => 'integer',
> + description => 'The maximum timeout to wait for a VM to start',

Please describe what is done when this isn't passed. FWIW, there's the
"verbose_description" schema property for very long ones, but I think
a few sentences describing config_aware_timeout could still fit in the
normal "description" one.

> + minimum => 0,
> + maximum => 268,
> +}
>  };
>  
>  my $cicustom_fmt = {
> diff --git a/PVE/QemuServer/Helpers.pm b/PVE/QemuServer/Helpers.pm
> index c10d842..c26d0dc 100644
> --- a/PVE/QemuServer/Helpers.pm
> +++ b/PVE/QemuServer/Helpers.pm
> @@ -142,6 +142,9 @@ sub version_cmp {
>  
>  sub config_aware_timeout {
>  my ($config, $is_suspended) = @_;
> +
> +return $config->{timeout} if defined($config->{timeout});
> +
>  my $memory = $config->{memory};
>  my $timeout = 30;
>  



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



Re: [pve-devel] [PATCH qemu-server 1/2] fix #3502: VM start timeout config parameter

2022-06-21 Thread Thomas Lamprecht
Am 21/06/2022 um 17:52 schrieb Thomas Lamprecht:
> 24h is already really big enough, so please use 86000, better to go lower
> (but still reasonable) first, we can always easily relax it, but not really
> make it stricter without breaking existing setups.

Obv. meant 86400 here, sorry.


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