Re: [pve-devel] [PATCH v2 storage] rbd: alloc image: fix #3970 avoid ambiguous rbd path

2022-04-12 Thread Thomas Lamprecht
On 11.04.22 16:49, Aaron Lauterer wrote:
> On 4/11/22 14:17, Thomas Lamprecht wrote:
>> FWIW, with storage getting the following patch the symlinks get created (may 
>> need
>> an trigger for reloading udev (or manually `udevadm control -R`).
>>
>> We'd only need to check to prioritize /deb/rbd-pve/$fsid/... paths first;
>> do you have time to give that a go?
> 
> 
> The final `fi` in the renamer script was missing from the diff. Once I fixed 
> that, it seems to work fine. Situation with a local hyperconverged cluster 
> and an external one:

Oh sorry, seems my copy-paste has gone slightly wrong.

> -
> root@cephtest1:/dev/rbd-pve# tree
> .
> ├── ce99d398-91ab-4667-b4f2-307ba0bec358
> │   └── ecpool-metadata
> │   ├── vm-103-disk-0 -> ../../../rbd0
> │   ├── vm-103-disk-0-part1 -> ../../../rbd0p1
> │   ├── vm-103-disk-0-part2 -> ../../../rbd0p2
> │   ├── vm-103-disk-0-part5 -> ../../../rbd0p5
> │   ├── vm-103-disk-1 -> ../../../rbd1
> │   └── vm-103-disk-2 -> ../../../rbd2
> └── e78d9b15-d5a1-4660-a4a5-d2c1208119e9
>     └── rbd
>     └── vm-103-disk-0 -> ../../../rbd3

Glad that it works for you too, but what I actually meant is if you had time to
give the remaining perl-side of the fix a go (sorry if I did not made that clear
enough).
___
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel


[pve-devel] [PATCH docs 1/2] zfs: fix #3479 rephrase boot-loader description.

2022-04-12 Thread Stoiko Ivanov
since using proxmox-boot-tool also for legacy systems the distinction
of p-b-t vs grub is not accurate anymore.

Signed-off-by: Stoiko Ivanov 
---
 local-zfs.adoc | 8 ++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/local-zfs.adoc b/local-zfs.adoc
index e77595f..ab0f6ad 100644
--- a/local-zfs.adoc
+++ b/local-zfs.adoc
@@ -387,7 +387,8 @@ Changing a failed device
 
 .Changing a failed bootable device
 
-Depending on how {pve} was installed it is either using `proxmox-boot-tool`
+Depending on how {pve} was installed it is either using `systemd-boot` or 
`grub`
+through `proxmox-boot-tool`
 footnote:[Systems installed with {pve} 6.4 or later, EFI systems installed with
 {pve} 5.4 or later] or plain `grub` as bootloader (see
 xref:sysboot[Host Bootloader]). You can check by running:
@@ -420,11 +421,14 @@ NOTE: `ESP` stands for EFI System Partition, which is 
setup as partition #2 on
 bootable disks setup by the {pve} installer since version 5.4. For details, see
 xref:sysboot_proxmox_boot_setup[Setting up a new partition for use as synced 
ESP].
 
-.With `grub`:
+.With plain `grub`:
 
 
 # grub-install 
 
+NOTE: plain `grub` is only used on systems installed with {pve} 6.3 or earlier,
+which have not been manually migrated to using `proxmox-boot-tool` yet.
+
 
 Configure E-Mail Notification
 ~
-- 
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 docs 2/2] sys: boot: minimally document kernel version pinning

2022-04-12 Thread Stoiko Ivanov
Signed-off-by: Stoiko Ivanov 
---
 system-booting.adoc | 48 +
 1 file changed, 48 insertions(+)

diff --git a/system-booting.adoc b/system-booting.adoc
index 2b96409..7d8ff47 100644
--- a/system-booting.adoc
+++ b/system-booting.adoc
@@ -68,6 +68,7 @@ device fails.
 * copying and configuring new kernel images and initrd images to all listed 
ESPs
 * synchronizing the configuration on kernel upgrades and other maintenance 
tasks
 * managing the list of kernel versions which are synchronized
+* configuring the boot-loader to boot a particular kernel version (pinning)
 
 
 You can view the currently configured ESPs and their state by running:
@@ -286,3 +287,50 @@ The kernel commandline needs to be placed in the variable
 The kernel commandline needs to be placed as one line in `/etc/kernel/cmdline`.
 To apply your changes, run `proxmox-boot-tool refresh`, which sets it as the
 `option` line for all config files in `loader/entries/proxmox-*.conf`.
+
+
+[[sysboot_kernel_pin]]
+Selecting the kernel-version for booting
+
+
+In addition to actively watching the boot process to select an older kernel
+version to boot into, you can also use `proxmox-boot-tool` to `pin` the kernel
+version the system should use. This should help you to work around
+incompatibilities between a newer kernel version and the hardware. Such a
+`pin` should be removed as soon as possible in order to have all the latest
+security patches in place.
+
+NOTE: The pinning functionality works for all {pve} systems, not only those
+using `proxmox-boot-tool` to synchronize the contents of the ESPs, if your
+system does not use `proxmox-boot-tool` for synchronizing you can also skip the
+`proxmox-boot-tool refresh` call in the end.
+
+To permanently select the version `5.15.30-1-pve` for booting run:
+
+
+# proxmox-boot-tool kernel pin 5.15.30-1-pve
+
+
+You can also set a kernel version to be booted on the next system boot only
+(e.g. to test if an updated kernel has resolved an issue, which caused you
+to `pin` a version in the first place):
+
+
+# proxmox-boot-tool kernel pin 5.15.31-1-pve --next-boot
+
+
+To remove any pinned version configuration use the `unpin` subcommand:
+
+
+# proxmox-boot-tool kernel unpin
+
+
+While `unpin` has a `--next-boot` option as well, it is used to clear a pinned
+version set with `--next-boot` and manually invoking is of little use.
+
+After setting or clearing pinned versions you also need to synchronize the
+content and configuration on the ESPs by running the `refresh` subcommand:
+
+
+# proxmox-boot-tool refresh
+
-- 
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 docs 0/2] update proxmox-boot-tool docs

2022-04-12 Thread Stoiko Ivanov
the 2 patches are independent of each other (I had overlooked 3479 for a while)

Stoiko Ivanov (2):
  zfs: fix #3479 rephrase boot-loader description.
  sys: boot: minimally document kernel version pinning

 local-zfs.adoc  |  8 ++--
 system-booting.adoc | 48 +
 2 files changed, 54 insertions(+), 2 deletions(-)

-- 
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 proxmox-backup v5 5/5] fix #3067: ui: add a separate notes view for longer markdown notes

2022-04-12 Thread Stefan Sterz
since markdown notes might be rather long, this commit adds a tab
similar to pve's datacenter or node notes. requires a bump of the
widget toolkit in order to use the `pmxNotesView`.

Signed-off-by: Stefan Sterz 
---
i chose the maxLength of a note in pbs to be 1022*64 because the
server allows at most 1024*64 bytes of form data per request, which
are distributed as follows:

equal signs: 2
the word "digest":   7
digest:  64
the word "description":  12
description: 65451

so by setting the limit to 1024*64 some users might encounter an
error telling them that the message body was too large even though the
front-end said the description was fine. i wanted to approximate
the 1024*64 limit here as close as possible so i chose:
65451 / 64 ~ 1022.

note that afaict due to the way maxLength is implemented the frontend
might not report an error when it should. this is due to character
encodings and how javascript's string length property works.

 www/Makefile  |  1 +
 www/NavigationTree.js |  6 ++
 www/NodeNotes.js  | 23 +++
 3 files changed, 30 insertions(+)
 create mode 100644 www/NodeNotes.js

diff --git a/www/Makefile b/www/Makefile
index 455fbeec..922d8de9 100644
--- a/www/Makefile
+++ b/www/Makefile
@@ -98,6 +98,7 @@ JSSRC=
\
datastore/DataStoreList.js  \
ServerStatus.js \
ServerAdministration.js \
+   NodeNotes.js\
Dashboard.js\
${TAPE_UI_FILES}\
NavigationTree.js   \
diff --git a/www/NavigationTree.js b/www/NavigationTree.js
index 576d05ab..916582ef 100644
--- a/www/NavigationTree.js
+++ b/www/NavigationTree.js
@@ -32,6 +32,12 @@ Ext.define('PBS.store.NavigationStore', {
path: 'pbsDashboard',
leaf: true,
},
+   {
+   text: gettext('Notes'),
+   iconCls: 'fa fa-sticky-note-o',
+   path: 'pbsNodeNotes',
+   leaf: true,
+   },
{
text: gettext('Configuration'),
iconCls: 'fa fa-gears',
diff --git a/www/NodeNotes.js b/www/NodeNotes.js
new file mode 100644
index ..f8b253c4
--- /dev/null
+++ b/www/NodeNotes.js
@@ -0,0 +1,23 @@
+// Needs to be its own xtype for `path` to work in `NavigationTree`
+Ext.define('PBS.NodeNotes', {
+extend: 'Ext.panel.Panel',
+xtype: 'pbsNodeNotes',
+
+scrollable: true,
+layout: 'fit',
+
+items: [
+   {
+   xtype: 'container',
+   layout: 'fit',
+   items: [{
+   xtype: 'pmxNotesView',
+   tools: false,
+   border: false,
+   node: 'localhost',
+   enableTBar: true,
+   maxLength: 1022*64,
+   }],
+   },
+],
+});
-- 
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 proxmox-backup v5 0/5] fix 3067: add notes functionality in webui

2022-04-12 Thread Stefan Sterz
adds support for markdown-based notes to pbs. It also refactors the
pve `NotesView` panel and `NotesEdit` window so that we can move it
to the widget toolkit and maintain a single version of the two.
hence, the last commits for proxmox-backup and pve-manager need to be
applied and bumped with or after the toolkit.

changes v5:

* split commit adding NotesEdit and NotesView to widget kit into two
  commits
* several minort improvements to NotesEdit and NotesView

changes v4 (thanks @ Thomas Lamprecht):

* several improvements to NotesView
* makes onlineHelp of the NotesEdit window used by NotesView
  configurable

changes v3 (thanks @ Dominik Csapak):

* refactored NotesView and NotesEdit
* removed notes from dashboard
* several javascrpt improvements

changes v2 (thanks @ Wolfgang Bumiller):

* performance improvements when parsing/writing a node configuration
* adjusted multi-line regex to remove superfluous "\s*"
* better formatting of rust code

Stefan Sterz (2):
  fix #3067: docs: add markdown primer from pve to pbs
  fix #3067: ui: add a separate notes view for longer markdown notes

 docs/index.rst   |   1 +
 docs/markdown-primer.rst | 178 +++
 www/Makefile |   1 +
 www/NavigationTree.js|   6 ++
 www/NodeNotes.js |  23 +
 5 files changed, 209 insertions(+)
 create mode 100644 docs/markdown-primer.rst
 create mode 100644 www/NodeNotes.js

Stefan Sterz (2):
  toolkit: add NotesView panel and NotesEdit window
  toolkit: refactor markdown based NotesView and NotesEdit

 src/Makefile|   2 +
 src/panel/NotesView.js  | 152 
 src/window/NotesEdit.js |  38 ++
 3 files changed, 192 insertions(+)
 create mode 100644 src/panel/NotesView.js
 create mode 100644 src/window/NotesEdit.js

Stefan Sterz (1):
  ui: move NotesView panel and NotesEdit window to widget kit

 www/manager6/Makefile  |   2 -
 www/manager6/dc/Config.js  |   2 +-
 www/manager6/node/Config.js|   2 +-
 www/manager6/panel/GuestSummary.js |   2 +-
 www/manager6/panel/NotesView.js| 129 -
 www/manager6/window/NotesEdit.js   |  38 -
 6 files changed, 3 insertions(+), 172 deletions(-)
 delete mode 100644 www/manager6/panel/NotesView.js
 delete mode 100644 www/manager6/window/NotesEdit.js

-- 
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 v5 3/5] toolkit: refactor markdown based NotesView and NotesEdit

2022-04-12 Thread Stefan Sterz
refactor them to make them more flexible and, thus, usable in pbs.
adds parameters for enabling the TBar, setting the help section in the
editing dialog and cleans up the code in some places

Signed-off-by: Stefan Sterz 
---
 src/panel/NotesView.js  | 135 +++-
 src/window/NotesEdit.js |   4 +-
 2 files changed, 81 insertions(+), 58 deletions(-)

diff --git a/src/panel/NotesView.js b/src/panel/NotesView.js
index 7c8299d..21dbdeb 100644
--- a/src/panel/NotesView.js
+++ b/src/panel/NotesView.js
@@ -1,12 +1,15 @@
-Ext.define('PVE.panel.NotesView', {
+Ext.define('Proxmox.panel.NotesView', {
 extend: 'Ext.panel.Panel',
-xtype: 'pveNotesView',
+xtype: 'pmxNotesView',
+mixins: ['Proxmox.Mixin.CBind'],
 
 title: gettext("Notes"),
 bodyPadding: 10,
 scrollable: true,
 animCollapse: false,
 maxLength: 64 * 1024,
+enableTBar: false,
+onlineHelp: 'markdown_basics',
 
 tbar: {
itemId: 'tbar',
@@ -22,11 +25,55 @@ Ext.define('PVE.panel.NotesView', {
],
 },
 
+cbindData: function(initalConfig) {
+   let me = this;
+   let type = '';
+
+   if (me.node) {
+   me.url = `/api2/extjs/nodes/${me.node}/config`;
+   } else if (me.pveSelNode?.data?.id === 'root') {
+   me.url = '/api2/extjs/cluster/options';
+   type = me.pveSelNode?.data?.type;
+   } else {
+   const nodename = me.pveSelNode?.data?.node;
+   type = me.pveSelNode?.data?.type;
+
+   if (!nodename) {
+   throw "no node name specified";
+   }
+
+   if (!Ext.Array.contains(['node', 'qemu', 'lxc'], type)) {
+   throw 'invalid type specified';
+   }
+
+   const vmid = me.pveSelNode?.data?.vmid;
+
+   if (!vmid && type !== 'node') {
+   throw "no VM ID specified";
+   }
+
+   me.url = `/api2/extjs/nodes/${nodename}/`;
+
+   // add the type specific path if qemu/lxc and set the backend's 
maxLen
+   if (type === 'qemu' || type === 'lxc') {
+   me.url += `${type}/${vmid}/`;
+   me.maxLength = 8 * 1024;
+   }
+
+   me.url += 'config';
+   }
+
+   me.pveType = type;
+
+   me.load();
+   return {};
+},
+
 run_editor: function() {
let me = this;
-   Ext.create('PVE.window.NotesEdit', {
-   pveSelNode: me.pveSelNode,
+   Ext.create('Proxmox.window.NotesEdit', {
url: me.url,
+   onlineHelp: me.onlineHelp,
listeners: {
destroy: () => me.load(),
},
@@ -34,32 +81,34 @@ Ext.define('PVE.panel.NotesView', {
}).setMaxLength(me.maxLength);
 },
 
+setNotes: function(value = '') {
+   let me = this;
+
+   let mdHtml = Proxmox.Markdown.parse(value);
+   me.update(mdHtml);
+
+   if (me.collapsible && me.collapseMode === 'auto') {
+   me.setCollapsed(!value);
+   }
+},
+
 load: function() {
-   var me = this;
+   let me = this;
 
Proxmox.Utils.API2Request({
url: me.url,
waitMsgTarget: me,
-   failure: function(response, opts) {
+   failure: (response, opts) => {
me.update(gettext('Error') + " " + response.htmlStatus);
me.setCollapsed(false);
},
-   success: function(response, opts) {
-   var data = response.result.data.description || '';
-
-   let mdHTML = Proxmox.Markdown.parse(data);
-   me.update(mdHTML);
-
-   if (me.collapsible && me.collapseMode === 'auto') {
-   me.setCollapsed(data === '');
-   }
-   },
+   success: ({ result }) => me.setNotes(result.data.description),
});
 },
 
 listeners: {
render: function(c) {
-   var me = this;
+   let me = this;
me.getEl().on('dblclick', me.run_editor, me);
},
afterlayout: function() {
@@ -71,49 +120,24 @@ Ext.define('PVE.panel.NotesView', {
},
 },
 
-tools: [{
-   type: 'gear',
-   handler: function() {
-   let view = this.up('panel');
-   view.run_editor();
+tools: [
+   {
+   type: 'gear',
+   handler: function() {
+   let view = this.up('panel');
+   view.run_editor();
+   },
},
-}],
+],
 
 initComponent: function() {
-   const me = this;
-   const type = me.pveSelNode.data.type;
-
-   if (me.pveSelNode.data.id === 'root') {
-   me.url = '/api2/extjs/cluster/options';
-   } else {
-   const nodename = me.pveSelNode.data.node;
-   if (!nodename) {
-   throw "no node name specified";
-   }
-
-   if (!Ext.Array.contains(['node', 'qemu', 'lxc'], type)) {
-   throw 'invalid type specified';
-   }
-
- 

[pve-devel] [PATCH manager v5 4/5] ui: move NotesView panel and NotesEdit window to widget kit

2022-04-12 Thread Stefan Sterz
this removes the NotesView panel and NotesEdit and replaces them with
with the version from the widget kit. requires a bump of the widget
toolkit.

Signed-off-by: Stefan Sterz 
---
 www/manager6/Makefile  |   2 -
 www/manager6/dc/Config.js  |   2 +-
 www/manager6/node/Config.js|   2 +-
 www/manager6/panel/GuestSummary.js |   2 +-
 www/manager6/panel/NotesView.js| 129 -
 www/manager6/window/NotesEdit.js   |  38 -
 6 files changed, 3 insertions(+), 172 deletions(-)
 delete mode 100644 www/manager6/panel/NotesView.js
 delete mode 100644 www/manager6/window/NotesEdit.js

diff --git a/www/manager6/Makefile b/www/manager6/Makefile
index d488c3a8..2c7b1e70 100644
--- a/www/manager6/Makefile
+++ b/www/manager6/Makefile
@@ -84,7 +84,6 @@ JSSRC=
\
panel/BackupJobPrune.js \
panel/HealthWidget.js   \
panel/IPSet.js  \
-   panel/NotesView.js  \
panel/RunningChart.js   \
panel/StatusPanel.js\
panel/GuestStatusView.js\
@@ -102,7 +101,6 @@ JSSRC=  
\
window/FirewallLograteEdit.js   \
window/LoginWindow.js   \
window/Migrate.js   \
-   window/NotesEdit.js \
window/Prune.js \
window/Restore.js   \
window/SafeDestroyGuest.js  \
diff --git a/www/manager6/dc/Config.js b/www/manager6/dc/Config.js
index 9c54b19d..13ded12e 100644
--- a/www/manager6/dc/Config.js
+++ b/www/manager6/dc/Config.js
@@ -28,7 +28,7 @@ Ext.define('PVE.dc.Config', {
itemId: 'summary',
},
{
-   xtype: 'pveNotesView',
+   xtype: 'pmxNotesView',
title: gettext('Notes'),
iconCls: 'fa fa-sticky-note-o',
itemId: 'notes',
diff --git a/www/manager6/node/Config.js b/www/manager6/node/Config.js
index 68f80391..52357df8 100644
--- a/www/manager6/node/Config.js
+++ b/www/manager6/node/Config.js
@@ -129,7 +129,7 @@ Ext.define('PVE.node.Config', {
itemId: 'summary',
},
{
-   xtype: 'pveNotesView',
+   xtype: 'pmxNotesView',
title: gettext('Notes'),
iconCls: 'fa fa-sticky-note-o',
itemId: 'notes',
diff --git a/www/manager6/panel/GuestSummary.js 
b/www/manager6/panel/GuestSummary.js
index 82cc7a7f..35721419 100644
--- a/www/manager6/panel/GuestSummary.js
+++ b/www/manager6/panel/GuestSummary.js
@@ -40,7 +40,7 @@ Ext.define('PVE.qemu.Summary', {
rstore: rstore,
},
{
-   xtype: 'pveNotesView',
+   xtype: 'pmxNotesView',
flex: 1,
padding: template ? '5' : '0 0 0 5',
itemId: 'notesview',
diff --git a/www/manager6/panel/NotesView.js b/www/manager6/panel/NotesView.js
deleted file mode 100644
index 7c8299d0..
--- a/www/manager6/panel/NotesView.js
+++ /dev/null
@@ -1,129 +0,0 @@
-Ext.define('PVE.panel.NotesView', {
-extend: 'Ext.panel.Panel',
-xtype: 'pveNotesView',
-
-title: gettext("Notes"),
-bodyPadding: 10,
-scrollable: true,
-animCollapse: false,
-maxLength: 64 * 1024,
-
-tbar: {
-   itemId: 'tbar',
-   hidden: true,
-   items: [
-   {
-   text: gettext('Edit'),
-   handler: function() {
-   let view = this.up('panel');
-   view.run_editor();
-   },
-   },
-   ],
-},
-
-run_editor: function() {
-   let me = this;
-   Ext.create('PVE.window.NotesEdit', {
-   pveSelNode: me.pveSelNode,
-   url: me.url,
-   listeners: {
-   destroy: () => me.load(),
-   },
-   autoShow: true,
-   }).setMaxLength(me.maxLength);
-},
-
-load: function() {
-   var me = this;
-
-   Proxmox.Utils.API2Request({
-   url: me.url,
-   waitMsgTarget: me,
-   failure: function(response, opts) {
-   me.update(gettext('Error') + " " + response.htmlStatus);
-   me.setCollapsed(false);
-   },
-   success: function(response, opts) {
-   var data = response.result.data.description || '';
-
-   let mdHTML = Proxmox.Markdown.parse(data);
-   me.update(mdHTML);
-
-   if (me.collapsible && me.collapseMode === 'auto') {
-   me.setCollapsed(data === '');
-   }
-   

[pve-devel] [PATCH widget-toolkit v5 2/5] toolkit: add NotesView panel and NotesEdit window

2022-04-12 Thread Stefan Sterz
move them here from pve so we can maintain them across several
products

Signed-off-by: Stefan Sterz 
---
 src/Makefile|   2 +
 src/panel/NotesView.js  | 129 
 src/window/NotesEdit.js |  38 
 3 files changed, 169 insertions(+)
 create mode 100644 src/panel/NotesView.js
 create mode 100644 src/window/NotesEdit.js

diff --git a/src/Makefile b/src/Makefile
index abafc2c..dd7729e 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -66,6 +66,7 @@ JSSRC=\
panel/ACMEDomains.js\
panel/StatusView.js \
panel/TfaView.js\
+   panel/NotesView.js  \
window/Edit.js  \
window/PasswordEdit.js  \
window/SafeDestroy.js   \
@@ -87,6 +88,7 @@ JSSRC=\
window/AddWebauthn.js   \
window/AddYubico.js \
window/TfaEdit.js   \
+   window/NotesEdit.js \
node/APT.js \
node/APTRepositories.js \
node/NetworkEdit.js \
diff --git a/src/panel/NotesView.js b/src/panel/NotesView.js
new file mode 100644
index 000..7c8299d
--- /dev/null
+++ b/src/panel/NotesView.js
@@ -0,0 +1,129 @@
+Ext.define('PVE.panel.NotesView', {
+extend: 'Ext.panel.Panel',
+xtype: 'pveNotesView',
+
+title: gettext("Notes"),
+bodyPadding: 10,
+scrollable: true,
+animCollapse: false,
+maxLength: 64 * 1024,
+
+tbar: {
+   itemId: 'tbar',
+   hidden: true,
+   items: [
+   {
+   text: gettext('Edit'),
+   handler: function() {
+   let view = this.up('panel');
+   view.run_editor();
+   },
+   },
+   ],
+},
+
+run_editor: function() {
+   let me = this;
+   Ext.create('PVE.window.NotesEdit', {
+   pveSelNode: me.pveSelNode,
+   url: me.url,
+   listeners: {
+   destroy: () => me.load(),
+   },
+   autoShow: true,
+   }).setMaxLength(me.maxLength);
+},
+
+load: function() {
+   var me = this;
+
+   Proxmox.Utils.API2Request({
+   url: me.url,
+   waitMsgTarget: me,
+   failure: function(response, opts) {
+   me.update(gettext('Error') + " " + response.htmlStatus);
+   me.setCollapsed(false);
+   },
+   success: function(response, opts) {
+   var data = response.result.data.description || '';
+
+   let mdHTML = Proxmox.Markdown.parse(data);
+   me.update(mdHTML);
+
+   if (me.collapsible && me.collapseMode === 'auto') {
+   me.setCollapsed(data === '');
+   }
+   },
+   });
+},
+
+listeners: {
+   render: function(c) {
+   var me = this;
+   me.getEl().on('dblclick', me.run_editor, me);
+   },
+   afterlayout: function() {
+   let me = this;
+   if (me.collapsible && !me.getCollapsed() && me.collapseMode === 
'always') {
+   me.setCollapsed(true);
+   me.collapseMode = ''; // only once, on initial load!
+   }
+   },
+},
+
+tools: [{
+   type: 'gear',
+   handler: function() {
+   let view = this.up('panel');
+   view.run_editor();
+   },
+}],
+
+initComponent: function() {
+   const me = this;
+   const type = me.pveSelNode.data.type;
+
+   if (me.pveSelNode.data.id === 'root') {
+   me.url = '/api2/extjs/cluster/options';
+   } else {
+   const nodename = me.pveSelNode.data.node;
+   if (!nodename) {
+   throw "no node name specified";
+   }
+
+   if (!Ext.Array.contains(['node', 'qemu', 'lxc'], type)) {
+   throw 'invalid type specified';
+   }
+
+   const vmid = me.pveSelNode.data.vmid;
+   if (!vmid && type !== 'node') {
+   throw "no VM ID specified";
+   }
+
+   me.url = `/api2/extjs/nodes/${nodename}/`;
+
+   // add the type specific path if qemu/lxc and set the backend's 
maxLen
+   if (type === 'qemu' || type === 'lxc') {
+   me.url += `${type}/${vmid}/`;
+   me.maxLength = 8 * 1024;
+   }
+   me.url += 'config';
+   }
+
+   me.callParent();
+   if (type === 'node' || type === '') { // '' is for datacenter
+   me.down('#tbar').setVisible(true);
+   } else if (me.pveSelNode.data.template !== 1) {
+   me.setCollapsible(true);
+   me.collapseDirection = 'right';
+
+   let sp = Ext.state.Manager.getProvider();
+   me.collapseMode = sp.get('guest-notes-collapse', 'never');
+
+   if (me.collapseMode === 'auto') {
+

[pve-devel] [PATCH proxmox-backup v5 1/5] fix #3067: docs: add markdown primer from pve to pbs

2022-04-12 Thread Stefan Sterz
this copies the markdown primer from the pve docs to allow access to
it via the help buttons in the gui

Signed-off-by: Stefan Sterz 
---
 docs/index.rst   |   1 +
 docs/markdown-primer.rst | 178 +++
 2 files changed, 179 insertions(+)
 create mode 100644 docs/markdown-primer.rst

diff --git a/docs/index.rst b/docs/index.rst
index daa61249..713b09d8 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -50,6 +50,7 @@ in the section entitled "GNU Free Documentation License".
file-formats.rst
backup-protocol.rst
calendarevents.rst
+   markdown-primer.rst
glossary.rst
GFDL.rst
 
diff --git a/docs/markdown-primer.rst b/docs/markdown-primer.rst
new file mode 100644
index ..01ce1d6d
--- /dev/null
+++ b/docs/markdown-primer.rst
@@ -0,0 +1,178 @@
+.. _markdown-primer:
+
+Markdown Primer
+===
+
+  "Markdown is a text-to-HTML conversion tool for web writers. Markdown allows
+  you to write using an easy-to-read, easy-to-write plain text format, then
+  convertit to structurally valid XHTML (or HTML)."
+
+  --  John Gruber, https://daringfireball.net/projects/markdown/
+
+
+The Proxmox Backup Server (PBS) web-interface has support for using Markdown to
+rendering rich text formatting in node and virtual guest notes.
+
+PBS supports CommonMark with most extensions of GFM (GitHub Flavoured 
Markdown),
+like tables or task-lists.
+
+.. _markdown_basics:
+
+Markdown Basics
+---
+
+Note that we only describe the basics here, please search the web for more
+extensive resources, for example on https://www.markdownguide.org/
+
+Headings
+
+
+.. code-block:: md
+
+  # This is a Heading h1
+  ## This is a Heading h2
+  # This is a Heading h5
+
+
+Emphasis
+
+
+Use ``*text*`` or ``_text_`` for emphasis.
+
+Use ``**text**`` or ``__text__`` for bold, heavy-weight text.
+
+Combinations are also possible, for example:
+
+.. code-block:: md
+
+  _You **can** combine them_
+
+
+Links
+~
+
+You can use automatic detection of links, for example,
+``https://forum.proxmox.com/`` would transform it into a clickable link.
+
+You can also control the link text, for example:
+
+.. code-block:: md
+
+  Now, [the part in brackets will be the link 
text](https://forum.proxmox.com/).
+
+Lists
+~
+
+Unordered Lists
+^^^
+
+Use ``*`` or ``-`` for unordered lists, for example:
+
+.. code-block:: md
+
+  * Item 1
+  * Item 2
+  * Item 2a
+  * Item 2b
+
+
+Adding an indentation can be used to created nested lists.
+
+Ordered Lists
+^
+
+.. code-block:: md
+
+  1. Item 1
+  1. Item 2
+  1. Item 3
+1. Item 3a
+1. Item 3b
+
+NOTE: The integer of ordered lists does not need to be correct, they will be 
numbered automatically.
+
+Task Lists
+^^
+
+Task list use a empty box ``[ ]`` for unfinished tasks and a box with an `X` 
for finished tasks.
+
+For example:
+
+
+.. code-block:: md
+
+  - [X] First task already done!
+  - [X] Second one too
+  - [ ] This one is still to-do
+  - [ ] So is this one
+
+Tables
+~~
+
+Tables use the pipe symbol ``|`` to separate columns, and ``-`` to separate the
+table header from the table body, in that separation one can also set the text
+alignment, making one column left-, center-, or right-aligned.
+
+
+.. code-block:: md
+
+  | Left columns  | Right columns |  Some  | More | Cols.| Centering Works Too
+  | - |--:||--|--|:--:|
+  | left foo  | right foo | First  | Row  | Here | >center<   |
+  | left bar  | right bar | Second | Row  | Here | 12345  |
+  | left baz  | right baz | Third  | Row  | Here | Test   |
+  | left zab  | right zab | Fourth | Row  | Here | ☁️☁️☁️  
|
+  | left rab  | right rab | And| Last | Here | The End|
+
+Note that you do not need to align the columns nicely with white space, but 
that makes
+editing tables easier.
+
+Block Quotes
+
+
+You can enter block quotes by prefixing a line with ``>``, similar as in 
plain-text emails.
+
+.. code-block:: md
+
+  > Markdown is a lightweight markup language with plain-text-formatting 
syntax,
+  > created in 2004 by John Gruber with Aaron Swartz.
+  >
+  >> Markdown is often used to format readme files, for writing messages in 
online discussion forums,
+  >> and to create rich text using a plain text editor.
+
+Code and Snippets
+~
+
+You can use backticks to avoid processing for a few word or paragraphs. That 
is useful for
+avoiding that a code or configuration hunk gets mistakenly interpreted as 
markdown.
+
+Inline code
+^^^
+
+Surrounding part of a line with single backticks allows to write code inline,
+for examples:
+
+.. code-block:: md
+
+  This hosts IP address is `10.0.0.1`.
+
+Whole blocks of code
+
+
+For code blocks spanning several lines you can use triple-backticks to

[pve-devel] [PATCH manager v6 02/11] api: /version: add 'tag-colors' and 'tag-tree-style'

2022-04-12 Thread Dominik Csapak
to be able to get them in the gui directly after login

Signed-off-by: Dominik Csapak 
---
 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



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



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

2022-04-12 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 718a8eb9..01a21c9e 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 cluster v6 2/3] Cluster: add get_guest_config_properties

2022-04-12 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 manager v6 10/11] ui: tree/ResourceTree: show Tags in tree

2022-04-12 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 manager v6 04/11] ui: tree/ResourceTree: collect tags on update

2022-04-12 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 61d93b5d..ff86f16b 100644
--- a/www/manager6/Utils.js
+++ b/www/manager6/Utils.js
@@ -1816,6 +1816,13 @@ Ext.define('PVE.Utils', {
return undefined;
 },
 
+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 v6 03/11] ui: parse and save tag color overrides from /version

2022-04-12 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 | 39 +++
 www/manager6/Workspace.js | 13 +
 2 files changed, 52 insertions(+)

diff --git a/www/manager6/Utils.js b/www/manager6/Utils.js
index fe5be283..61d93b5d 100644
--- a/www/manager6/Utils.js
+++ b/www/manager6/Utils.js
@@ -1815,6 +1815,45 @@ 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];
+   if (color_hex.length === 13) {
+   colors[tag].push(parseInt(color_hex.slice(7, 9), 16));
+   colors[tag].push(parseInt(color_hex.slice(9, 11), 16));
+   colors[tag].push(parseInt(color_hex.slice(11, 13), 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);
+},
+
+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..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



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



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

2022-04-12 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 | 43 ++-
 2 files changed, 62 insertions(+), 1 deletion(-)

diff --git a/www/manager6/Utils.js b/www/manager6/Utils.js
index 63687d08..2fe823ff 100644
--- a/www/manager6/Utils.js
+++ b/www/manager6/Utils.js
@@ -1862,6 +1862,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 6b30ede9..aef77964 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 Color Override'),
+   editor: {
+   xtype: 'proxmoxWindowEdit',
+   width: 800,
+   bodyPadding: 0,
+   subject: gettext('Tag Color Override'),
+   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



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



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

2022-04-12 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 v6 09/11] ui: {lxc, qemu}/Config: show Tags and make them editable

2022-04-12 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 cluster v6 3/3] datacenter.cfg: add option for tag-tree-style and tag-colors

2022-04-12 Thread Dominik Csapak
Signed-off-by: Dominik Csapak 
---
 data/PVE/DataCenterConfig.pm | 18 ++
 1 file changed, 18 insertions(+)

diff --git a/data/PVE/DataCenterConfig.pm b/data/PVE/DataCenterConfig.pm
index 6c0fa5b..c21dbb7 100644
--- a/data/PVE/DataCenterConfig.pm
+++ b/data/PVE/DataCenterConfig.pm
@@ -106,6 +106,10 @@ sub pve_verify_mac_prefix {
 return $mac_prefix;
 }
 
+my $TAG_RE = '[a-zA-Z0-9_][a-zA-Z0-9_\-\+\.]*';
+my $COLOR_RE = '[0-9a-fA-F]{6}';
+my $OVERRIDE_RE = "(?:${TAG_RE}=${COLOR_RE}(?:\:${COLOR_RE})?)";
+
 my $datacenter_schema = {
 type => "object",
 additionalProperties => 0,
@@ -222,6 +226,20 @@ 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 => "${OVERRIDE_RE}(?:\,$OVERRIDE_RE)*",
+   typetext => '=[:][,=...]',
+   description => "Manual color mapping for tags (comma separated).",
+   },
 },
 };
 
-- 
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/widget-toolkit/manager v6] add tags to ui

2022-04-12 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
* 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 styles available for the tree
  (see [0])

changes from v5:
* incorporated fabians suggestions for safeguarding c code
* fixed adding/showing 'empty' tags in edit list
* increased hitbox for delete/edit/apply icons in tags
* improved tagcoloredit by prefilling the 'default' values as long as
  the user does not change them manually
* reused the tag char regex
* added some more helpers in proxmox-widget-toolkit
* fixed some minor bugs

changes from v4:
* optimized the global taglist/tagoverrides so that they don't have to
  be copied for everytag, but only once on update
* made the tags less round by default
* to edit, one must first enter 'edit' mode by clicking on an edit icon
  and apply by clicking on an apply button
  (this way one can copy&paste the tags without starting an edit, and
  can edit multiple tags with only one api call)
* improved the 'dense' style a bit (wider + spacing)
* includes all necessary cluster patches

changes from v3:
* show the tags in the tree (with multiple styles)
* they are now inline editable instead of having a pop up with the editor
* able to override colors in datacenter cfg
* show a dropdown on editing with existing tags (from tree+overrides)
* show the tags in the global search grid (and make them searchable)

changes from v2:
* rebase on master (drop applied patch, merge with lxc pending changes)
* move utilities to widget-toolkit
* prefix css classes
* remove tags from options and add edit button to the tags directly
* show 'no tags' when no tags are defined
* improve statusTxt style

changes from v1:
* slightly different format (use [a-z...] instead of \w)
* add comment in JSONSchema
* better commit message
* add the tags to the status api call of guests (for gui)
* show the tags in the gui
* make the tags editable in the gui

0: https://imgur.com/a/0t2fvud

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-tree-style and tag-colors

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

proxmox-widget-toolkit:

Dominik Csapak (1):
  add tag related helpers

 src/Utils.js | 90 
 src/css/ext6-pmx.css | 45 ++
 2 files changed, 135 insertions(+)

pve-manager:

Dominik Csapak (11):
  api: /cluster/resources: add tags to returned properties
  api: /version: add 'tag-colors' and 'tag-tree-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

 PVE/API2.pm|  12 +-
 PVE/API2/Cluster.pm|   9 +-
 www/css/ext6-pve.css   |   5 +
 www/manager6/Makefile  |   3 +
 www/manager6/Utils.js  |  68 +
 www/manager6/Workspace.js  |  13 +
 www/manager6/data/ResourceStore.js |   6 +
 www/manager6/dc/OptionView.js  |  43 ++-
 www/manager6/form/GlobalSearchField.js |  20 +-
 www/manager6/form/Tag.js   | 270 +++
 www/manager6/form/TagColorGrid.js  | 355 +
 www/manager6/form/TagEdit.js   | 132 +
 www/manager6/lxc/Config.js |  36 ++-
 www/manager6/qemu/Config.js|  35 ++-
 www/manager6/tree/ResourceTree.js  |  20 +-
 15 files changed, 1009 insertions(+), 18 deletions(-)
 create mode 100644 www/manager6/form/Tag.js
 create mode 100644 www/manager6/form/TagColorGrid.js
 create mode 100644 www/manager6/form/TagEdit.js

-- 
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 v6 08/11] ui: add form/TagEdit.js

2022-04-12 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 | 132 +++
 2 files changed, 133 insertions(+)
 create mode 100644 www/manager6/form/TagEdit.js

diff --git a/www/manager6/Makefile b/www/manager6/Makefile
index b0729f7a..c95e9988 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 ..d39225fb
--- /dev/null
+++ b/www/manager6/form/TagEdit.js
@@ -0,0 +1,132 @@
+Ext.define('PVE.panel.TagEditContainer', {
+extend: 'Ext.container.Container',
+alias: 'widget.pveTagEditContainer',
+
+tagCount: 0,
+
+layout: {
+   type: 'hbox',
+   align: 'stretch',
+},
+
+loadTags: function(tagstring = '') {
+   let me = this;
+
+   if (me.oldTags === tagstring) {
+   return;
+   }
+
+   let tags = tagstring.split(/[;, ]/).filter((t) => !!t) || [];
+   me.suspendLayout = true;
+   me.tags = {};
+   me.removeAllTags();
+   tags.forEach((tag) => {
+   me.addTag(tag);
+   });
+   me.suspendLayout = false;
+   me.updateLayout();
+   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 ` `;
+},
+
+onEditClick: function() {
+   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);
+
+   if (!me.editMode) {
+   let tags = [];
+   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.editBtn = me.add({
+   xtype: 'box',
+   html: me.getEditBtnHtml(),
+   style: {
+   cursor: 'pointer',
+   },
+   listeners: {
+   click: () => me.onEditClick(),
+   element: 'el',
+   },
+   });
+   if (me.tags) {
+   me.loadTags(me.tags);
+   }
+},
+});
-- 
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 v6 1/1] add tag related helpers

2022-04-12 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 2516578..4448751 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 v6 07/11] ui: add form/Tag

2022-04-12 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 | 270 +++
 2 files changed, 271 insertions(+)
 create mode 100644 www/manager6/form/Tag.js

diff --git a/www/manager6/Makefile b/www/manager6/Makefile
index 8d14f684..b0729f7a 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 ..f6caa412
--- /dev/null
+++ b/www/manager6/form/Tag.js
@@ -0,0 +1,270 @@
+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 v6 1/3] add CFS_IPC_GET_GUEST_CONFIG_PROPERTIES method

2022-04-12 Thread Dominik Csapak
for getting multiple properties from the in memory config of the
guests. I added a new CSF_IPC_ call to maintain backwards compatibility.

It basically behaves the same as
CFS_IPC_GET_GUEST_CONFIG_PROPERTY, but takes a list of properties
instead.

The old way of getting a single property is now also done by
the new function.

Signed-off-by: Dominik Csapak 
---
 data/src/cfs-ipc-ops.h |   2 +
 data/src/server.c  |  64 +++
 data/src/status.c  | 174 -
 data/src/status.h  |   3 +
 4 files changed, 187 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..c42ff69 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 %lu 
properties", rh->num_props);
+   result = -EINVAL;
+   }
+
+   if (result == 0) {
+   cfs_debug("cfs_get_guest_config_properties: basic 
valid checked, do request");
+   result = 
cfs_create_guest_conf_properties_msg(outbuf, memdb, properties, rh->num_props, 
rh->vmid);
+   }
+
+   free(properties);
+   }
} else if (request_id == CFS_IPC_VERIFY_TOKEN) {
 
cfs_verify_token_request_header_t *rh = 
(cfs_verify_token_request_header_t *) data;
diff --git a/data/src/status.c b/data/src/status.c
index 9bceaeb..03454ec 100644
--- a/data/src/status.c
+++ b/data/src/status.c
@@ -804,25 +804,52 @@ cfs_create_vmlist_msg(GString *str)
return 0;
 }
 
-// checks the conf for a line starting with '$prop:' and returns the value
-// afterwards, whitout initial whit

[pve-devel] [PATCH manager v6 05/11] ui: add form/TagColorGrid

2022-04-12 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 | 355 ++
 4 files changed, 363 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 d488c3a8..8d14f684 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 ff86f16b..63687d08 100644
--- a/www/manager6/Utils.js
+++ b/www/manager6/Utils.js
@@ -1861,6 +1861,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 ..f2c248b3
--- /dev/null
+++ b/www/manager6/form/TagColorGrid.js
@@ -0,0 +1,355 @@
+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;
+   if (!value) {
+   me.getStore().removeAll();
+   me.checkChange();
+   return me;
+ 

[pve-devel] [PATCH storage] rbd: fix #3969: add rbd dev paths with cluster info

2022-04-12 Thread Aaron Lauterer
By adding our own customized rbd udev rules and ceph-rbdnamer we can
create device paths that include the cluster fsid and avoid any
ambiguity if the same pool and namespace combination is used in
multiple clusters we connect to.

Additionally to the '/dev/rbd//...' paths we now have
'/dev/rbd-pve///...' paths.

The other half of the patch makes use of the new device paths in the RBD
plugin.

The cluster fsid is read from the 'ceph.conf' file in the case of a
hyperconverged setup. In the case of an external Ceph cluster we need to
fetch it via a rados api call.

Co-authored-by: Thomas Lamprecht 
Signed-off-by: Aaron Lauterer 
---
 Makefile   |  1 +
 PVE/Storage/RBDPlugin.pm   | 64 --
 udev-rbd/50-rbd-pve.rules  |  2 ++
 udev-rbd/Makefile  | 21 +
 udev-rbd/ceph-rbdnamer-pve | 24 ++
 5 files changed, 89 insertions(+), 23 deletions(-)
 create mode 100644 udev-rbd/50-rbd-pve.rules
 create mode 100644 udev-rbd/Makefile
 create mode 100755 udev-rbd/ceph-rbdnamer-pve

diff --git a/Makefile b/Makefile
index 431db16..029b586 100644
--- a/Makefile
+++ b/Makefile
@@ -41,6 +41,7 @@ install: PVE pvesm.1 pvesm.bash-completion 
pvesm.zsh-completion
install -d ${DESTDIR}${SBINDIR}
install -m 0755 pvesm ${DESTDIR}${SBINDIR}
make -C PVE install
+   make -C udev-rbd install
install -d ${DESTDIR}/usr/share/man/man1
install -m 0644 pvesm.1 ${DESTDIR}/usr/share/man/man1/
gzip -9 -n ${DESTDIR}/usr/share/man/man1/pvesm.1
diff --git a/PVE/Storage/RBDPlugin.pm b/PVE/Storage/RBDPlugin.pm
index e287e28..c6f0afb 100644
--- a/PVE/Storage/RBDPlugin.pm
+++ b/PVE/Storage/RBDPlugin.pm
@@ -8,6 +8,7 @@ use JSON;
 use Net::IP;
 
 use PVE::CephConfig;
+use PVE::Cluster qw(cfs_read_file);;
 use PVE::JSONSchema qw(get_standard_option);
 use PVE::ProcFSTools;
 use PVE::RADOS;
@@ -22,9 +23,36 @@ my $get_parent_image_name = sub {
 return $parent->{image} . "@" . $parent->{snapshot};
 };
 
+my $librados_connect = sub {
+my ($scfg, $storeid, $options) = @_;
+
+my $librados_config = PVE::CephConfig::ceph_connect_option($scfg, 
$storeid);
+
+my $rados = PVE::RADOS->new(%$librados_config);
+
+return $rados;
+};
+
 my sub get_rbd_path {
-my ($scfg, $volume) = @_;
-my $path = $scfg->{pool} ? $scfg->{pool} : 'rbd';
+my ($scfg, $storeid, $volume) = @_;
+
+my $cluster_id = '';
+if ($scfg->{monhost}) {
+   my $rados = $librados_connect->($scfg, $storeid);
+   $cluster_id = $rados->mon_command({ prefix => 'fsid', format => 'json' 
})->{fsid};
+} else {
+   $cluster_id = cfs_read_file('ceph.conf')->{global}->{fsid};
+}
+
+my $uuid_pattern = 
"([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})";
+if ($cluster_id =~ qr/^${uuid_pattern}$/is) {
+   $cluster_id = $1; # use untained value
+} else {
+   die "cluster fsid has invalid format\n";
+}
+
+my $path = "${cluster_id}";
+$path .= $scfg->{pool} ?  "/$scfg->{pool}" : '/rbd';
 $path .= "/$scfg->{namespace}" if defined($scfg->{namespace});
 $path .= "/$volume" if defined($volume);
 return $path;
@@ -70,16 +98,6 @@ my $rados_cmd = sub {
 return $build_cmd->('/usr/bin/rados', $scfg, $storeid, $op, @options);
 };
 
-my $librados_connect = sub {
-my ($scfg, $storeid, $options) = @_;
-
-my $librados_config = PVE::CephConfig::ceph_connect_option($scfg, 
$storeid);
-
-my $rados = PVE::RADOS->new(%$librados_config);
-
-return $rados;
-};
-
 # needed for volumes created using ceph jewel (or higher)
 my $krbd_feature_update = sub {
 my ($scfg, $storeid, $name) = @_;
@@ -380,8 +398,8 @@ sub path {
 my ($vtype, $name, $vmid) = $class->parse_volname($volname);
 $name .= '@'.$snapname if $snapname;
 
-my $rbd_path = get_rbd_path($scfg, $name);
-return ("/dev/rbd/${rbd_path}", $vmid, $vtype) if $scfg->{krbd};
+my $rbd_path = get_rbd_path($scfg, $storeid, $name);
+return ("/dev/rbd-pve/${rbd_path}", $vmid, $vtype) if $scfg->{krbd};
 
 my $path = "rbd:${rbd_path}";
 
@@ -449,8 +467,8 @@ sub create_base {
$scfg,
$storeid,
'rename',
-   get_rbd_path($scfg, $name),
-   get_rbd_path($scfg, $newname),
+   get_rbd_path($scfg, $storeid, $name),
+   get_rbd_path($scfg, $storeid, $newname),
 );
 run_rbd_command($cmd, errmsg => "rbd rename '$name' error");
 
@@ -498,12 +516,12 @@ sub clone_image {
 $newvol = $name if length($snapname);
 
 my @options = (
-   get_rbd_path($scfg, $basename),
+   get_rbd_path($scfg, $storeid, $basename),
'--snap', $snap,
 );
 push @options, ('--data-pool', $scfg->{'data-pool'}) if 
$scfg->{'data-pool'};
 
-my $cmd = $rbd_cmd->($scfg, $storeid, 'clone', @options, 
get_rbd_path($scfg, $name));
+my $cmd = $rbd_cmd->($scfg, $storeid, 'clone', @options, 
get_rbd_path($scfg, $storeid, $name));
 run_rbd_command($cmd, errmsg => "r

[pve-devel] [PATCH manager] cli: acme: Allow to get certificates from CAs requiring EAB

2022-04-12 Thread Ember 'n0emis' Keske via pve-devel
--- Begin Message ---
From: Ember 'n0emis' Keske 

to allow to fetch certificates from CA's like sectigo or zerossl.
The CLI checks, if the CA requires EAB credentials and promts
the user to enter them on demand.

Signed-off-by: Ember 'n0emis' Keske 
---
 PVE/API2/ACMEAccount.pm | 74 -
 PVE/CLI/pvenode.pm  | 12 +++
 2 files changed, 85 insertions(+), 1 deletion(-)

diff --git a/PVE/API2/ACMEAccount.pm b/PVE/API2/ACMEAccount.pm
index b790843a..ca285da5 100644
--- a/PVE/API2/ACMEAccount.pm
+++ b/PVE/API2/ACMEAccount.pm
@@ -2,6 +2,7 @@ package PVE::API2::ACMEAccount;
 
 use strict;
 use warnings;
+use Digest::SHA;
 
 use PVE::ACME;
 use PVE::CertHelpers;
@@ -35,6 +36,31 @@ my $account_contact_from_param = sub {
 my @addresses = PVE::Tools::split_list(extract_param($_[0], 'contact'));
 return [ map { "mailto:$_"; } @addresses ];
 };
+my $generate_eab = sub {
+my ($acme, $param) = @_;
+my $payload = PVE::ACME::encode(PVE::ACME::tojs($acme->jwk()));
+
+my $kid = extract_param($param, 'eab_kid');
+my $key = extract_param($param, 'eab_hmac_key');
+
+my $url = $acme->_method('newAccount');
+
+my $protected = {
+   alg => 'HS256',
+   kid => $kid,
+   url => $url,
+};
+
+$protected = PVE::ACME::encode(PVE::ACME::tojs($protected, canonical=>1));
+my $signdata = "$protected.$payload";
+my $signature = PVE::ACME::encode(Digest::SHA::hmac_sha256($signdata, 
MIME::Base64::decode_base64url($key)));
+
+return {
+   protected => $protected,
+   payload => $payload,
+   signature => $signature,
+};
+};
 my $acme_account_dir = PVE::CertHelpers::acme_account_dir();
 
 __PACKAGE__->register_method ({
@@ -115,6 +141,16 @@ __PACKAGE__->register_method ({
default => $acme_default_directory_url,
optional => 1,
}),
+   eab_kid => {
+   type => 'string',
+   description => 'Key Identifier for External Account Binding.',
+   optional => 1,
+   },
+   eab_hmac_key => {
+   type => 'string',
+   description => 'HMAC key for External Account Binding.',
+   optional => 1,
+   },
},
 },
 returns => {
@@ -145,7 +181,12 @@ __PACKAGE__->register_method ({
print "Generating ACME account key..\n";
$acme->init(4096);
print "Registering ACME account..\n";
-   eval { $acme->new_account($param->{tos_url}, contact => 
$contact); };
+   if ($param->{eab_kid}) {
+   my $eab = $generate_eab->($acme, $param);
+   eval { $acme->new_account($param->{tos_url}, contact => 
$contact, externalAccountBinding => $eab); };
+   } else {
+   eval { $acme->new_account($param->{tos_url}, contact => 
$contact); };
+   }
if (my $err = $@) {
unlink $account_file;
die "Registration failed: $err\n";
@@ -339,6 +380,37 @@ __PACKAGE__->register_method ({
return $meta ? $meta->{termsOfService} : undef;
 }});
 
+__PACKAGE__->register_method ({
+name => 'get_eab',
+path => 'eab',
+method => 'GET',
+description => "Retrieve where an external Account is required from CA.",
+permissions => { user => 'all' },
+parameters => {
+   additionalProperties => 0,
+   properties => {
+   directory => get_standard_option('pve-acme-directory-url', {
+   default => $acme_default_directory_url,
+   optional => 1,
+   }),
+   },
+},
+returns => {
+   type => 'boolean',
+   default => 0,
+   description => 'returns 1 if an external Account is required',
+},
+code => sub {
+   my ($param) = @_;
+
+   my $directory = extract_param($param, 'directory') // 
$acme_default_directory_url;
+
+   my $acme = PVE::ACME->new(undef, $directory);
+   my $meta = $acme->get_meta();
+
+   return $meta ? $meta->{externalAccountRequired} : 0;
+}});
+
 __PACKAGE__->register_method ({
 name => 'get_directories',
 path => 'directories',
diff --git a/PVE/CLI/pvenode.pm b/PVE/CLI/pvenode.pm
index acef6c3b..658f5ee8 100644
--- a/PVE/CLI/pvenode.pm
+++ b/PVE/CLI/pvenode.pm
@@ -129,6 +129,18 @@ __PACKAGE__->register_method({
} else {
print "No Terms of Service found, proceeding.\n";
}
+   print "\nAttempting to check wether an external Account is required for 
'$param->{directory}'..\n";
+   my $eab = PVE::API2::ACMEAccount->get_eab({ directory => 
$param->{directory} });
+   if ($eab) {
+   print "External Account Binding information is required.\n";
+   my $term = Term::ReadLine->new('pvenode');
+   my $kid = $term->readline('Enter the Key Identifier for External 
Account Binding (KID): ');
+   my $hmac_key = $term->read