From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) by lore.proxmox.com (Postfix) with ESMTPS id 0048B1FF146 for ; Tue, 23 Jun 2026 16:35:01 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id BC89C38E6; Tue, 23 Jun 2026 16:34:43 +0200 (CEST) From: "Max R. Carrara" To: pve-devel@lists.proxmox.com Subject: [PATCH pve-storage 07/13] all plugins: add 'title' to properties, adapt 'description's Date: Tue, 23 Jun 2026 16:33:24 +0200 Message-ID: <20260623143402.772452-8-m.carrara@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260623143402.772452-1-m.carrara@proxmox.com> References: <20260623143402.772452-1-m.carrara@proxmox.com> MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1782225245794 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.079 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Message-ID-Hash: D7PKD2EN62JQQ3QV5NUEV23PTRJUQKLK X-Message-ID-Hash: D7PKD2EN62JQQ3QV5NUEV23PTRJUQKLK X-MailFrom: m.carrara@proxmox.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox VE development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: Add a `title` [1] to almost all plugins' properties. Since this keyword is included when querying the `plugins/storage/plugin` endpoint, it can be used for cosmetic purposes, such as field labels in the UI or similar. The only properties that do not get a `title` are `mkdir` and `authsupported`; the former is deprecated and the latter is currently unused. If a property is exposed in the UI, the label of its corresponding field is used as its `title`, with minor alterations at most (e.g. spelling out "Filesystem" instead of "FS"). Otherwise, something sensible and simple is chosen instead. Additionally, adapt some `description` [1] keywords because they are either very short, not general enough (if used by multiple plugins), or not really descriptive. In certain cases the wording is improved a little. Notably, fix the line-breaking of the descriptions of the fields in the `$prune_backups_format` hash by adding a space at the beginning of the concatenated strings. There is still room for improvement here, especially since some properties are kind of awkward to describe in more general terms, so this is nothing that should be considered set in stone. All of this is done as preparation for supporting custom (third-party) storage plugins in the web UI. Note that altering the `description` keywords affects the descriptions of the flags in the `pvesm` manpage, in particular for the `pvesm add` and `pvesm set` subcommands, and also possibly other parts of our documentation. [1]: https://json-schema.org/draft/2020-12/json-schema-validation#name-title-and-description Signed-off-by: Max R. Carrara --- src/PVE/Storage/BTRFSPlugin.pm | 1 + src/PVE/Storage/CIFSPlugin.pm | 10 ++++--- src/PVE/Storage/CephFSPlugin.pm | 6 +++-- src/PVE/Storage/DirPlugin.pm | 11 +++++--- src/PVE/Storage/ESXiPlugin.pm | 1 + src/PVE/Storage/ISCSIPlugin.pm | 6 +++-- src/PVE/Storage/LVMPlugin.pm | 10 +++++-- src/PVE/Storage/LvmThinPlugin.pm | 3 ++- src/PVE/Storage/NFSPlugin.pm | 6 +++-- src/PVE/Storage/PBSPlugin.pm | 5 +++- src/PVE/Storage/Plugin.pm | 46 +++++++++++++++++++++++++------- src/PVE/Storage/RBDPlugin.pm | 16 ++++++++--- src/PVE/Storage/ZFSPlugin.pm | 16 +++++++---- src/PVE/Storage/ZFSPoolPlugin.pm | 9 ++++--- 14 files changed, 109 insertions(+), 37 deletions(-) diff --git a/src/PVE/Storage/BTRFSPlugin.pm b/src/PVE/Storage/BTRFSPlugin.pm index fb47aa0b..6b2e09ad 100644 --- a/src/PVE/Storage/BTRFSPlugin.pm +++ b/src/PVE/Storage/BTRFSPlugin.pm @@ -52,6 +52,7 @@ sub plugindata { sub properties { return { nocow => { + title => "Set NOCOW Flag", description => "Set the NOCOW flag on files." . " Disables data checksumming and causes data errors to be unrecoverable from" . " while allowing direct I/O. Only use this if data does not need to be any more" diff --git a/src/PVE/Storage/CIFSPlugin.pm b/src/PVE/Storage/CIFSPlugin.pm index 54f0f4ec..23b505cd 100644 --- a/src/PVE/Storage/CIFSPlugin.pm +++ b/src/PVE/Storage/CIFSPlugin.pm @@ -132,21 +132,25 @@ sub plugindata { sub properties { return { share => { - description => "CIFS share.", + title => "Share", + description => "The name of the share to use.", type => 'string', }, password => { - description => "Password for accessing the share/datastore.", + title => "Password", + description => "The password used for authentication.", type => 'string', maxLength => 256, }, domain => { - description => "CIFS domain.", + title => "Domain", + description => "The name of the user domain (workgroup) to use.", type => 'string', optional => 1, maxLength => 256, }, smbversion => { + title => "SMB Version", description => "SMB protocol version. 'default' if not set, negotiates the highest SMB2+" . " version supported by both the client and server.", diff --git a/src/PVE/Storage/CephFSPlugin.pm b/src/PVE/Storage/CephFSPlugin.pm index fbc97113..fcb8a9e6 100644 --- a/src/PVE/Storage/CephFSPlugin.pm +++ b/src/PVE/Storage/CephFSPlugin.pm @@ -125,11 +125,13 @@ sub plugindata { sub properties { return { fuse => { - description => "Mount CephFS through FUSE.", + title => "Mount with FUSE", + description => "Mount the filesystem through FUSE.", type => 'boolean', }, 'fs-name' => { - description => "The Ceph filesystem name.", + title => "Filesystem Name", + description => "The name or identifier of the filesystem.", type => 'string', format => 'pve-configid', }, diff --git a/src/PVE/Storage/DirPlugin.pm b/src/PVE/Storage/DirPlugin.pm index 80c4a031..cc52feae 100644 --- a/src/PVE/Storage/DirPlugin.pm +++ b/src/PVE/Storage/DirPlugin.pm @@ -45,7 +45,9 @@ sub plugindata { sub properties { return { path => { - description => "File system path.", + title => "Path", + description => "The path on the filesystem that leads to the" + . " mountpoint or base directory of the storage.", type => 'string', format => 'pve-storage-path', }, @@ -57,23 +59,26 @@ sub properties { default => 'yes', }, 'create-base-path' => { + title => "Create Base Directory", description => "Create the base directory if it doesn't exist.", type => 'boolean', default => 'yes', }, 'create-subdirs' => { - description => "Populate the directory with the default structure.", + title => "Populate Base Directory", + description => "Populate the base directory with the default structure.", type => 'boolean', default => 'yes', }, is_mountpoint => { + title => "Is Mountpoint", description => "Assume the given path is an externally managed mountpoint " . "and consider the storage offline if it is not mounted. " . "Using a boolean (yes/no) value serves as a shortcut to using the target path in this field.", type => 'string', default => 'no', }, - bwlimit => get_standard_option('bwlimit'), + bwlimit => get_standard_option('bwlimit', { title => "Bandwidth Limit" }), }; } diff --git a/src/PVE/Storage/ESXiPlugin.pm b/src/PVE/Storage/ESXiPlugin.pm index 19f23bb9..b5e4135e 100644 --- a/src/PVE/Storage/ESXiPlugin.pm +++ b/src/PVE/Storage/ESXiPlugin.pm @@ -38,6 +38,7 @@ sub plugindata { sub properties { return { 'skip-cert-verification' => { + title => "Skip Certificate Verification", description => 'Disable TLS certificate verification, only enable on fully trusted networks!', type => 'boolean', diff --git a/src/PVE/Storage/ISCSIPlugin.pm b/src/PVE/Storage/ISCSIPlugin.pm index 801b5d1b..a35a9882 100644 --- a/src/PVE/Storage/ISCSIPlugin.pm +++ b/src/PVE/Storage/ISCSIPlugin.pm @@ -341,11 +341,13 @@ sub plugindata { sub properties { return { target => { - description => "iSCSI target.", + title => "Target", + description => "iSCSI target name (an iSCSI Qualified Name - IQN).", type => 'string', }, portal => { - description => "iSCSI portal (IP or DNS name with optional port).", + title => "Portal", + description => "iSCSI portal (IP or hostname, optionally with port).", type => 'string', format => 'pve-storage-portal-dns', }, diff --git a/src/PVE/Storage/LVMPlugin.pm b/src/PVE/Storage/LVMPlugin.pm index a313ecc8..903eda3a 100644 --- a/src/PVE/Storage/LVMPlugin.pm +++ b/src/PVE/Storage/LVMPlugin.pm @@ -415,20 +415,24 @@ sub plugindata { sub properties { return { vgname => { - description => "Volume group name.", + title => "Volume Group", + description => "LVM volume group name.", type => 'string', format => 'pve-storage-vgname', }, base => { - description => "Base volume. This volume is automatically activated.", + title => "Base Storage", + description => "Name of the base volume (automatically activated).", type => 'string', format => 'pve-volume-id', }, saferemove => { + title => "Wipe Removed Volumes", description => "Zero-out data when removing LVs.", type => 'boolean', }, 'saferemove-stepsize' => { + title => "Wipe Step Size", description => "Wipe step size in MiB." . " It will be capped to the maximum supported by the storage.", default => 32, @@ -436,10 +440,12 @@ sub properties { type => 'integer', }, saferemove_throughput => { + title => "Wipe Throughput", description => "Wipe throughput (cstream -t parameter value).", type => 'string', }, tagged_only => { + title => "Tagged Volumes Only", description => "Only list logical volumes tagged with 'pve-vm-ID'.", type => 'boolean', }, diff --git a/src/PVE/Storage/LvmThinPlugin.pm b/src/PVE/Storage/LvmThinPlugin.pm index cadf343f..0f3b21d4 100644 --- a/src/PVE/Storage/LvmThinPlugin.pm +++ b/src/PVE/Storage/LvmThinPlugin.pm @@ -39,7 +39,8 @@ sub plugindata { sub properties { return { thinpool => { - description => "LVM thin pool LV name.", + title => "Thin Pool", + description => "Name of the LVM thin pool logical volume.", type => 'string', format => 'pve-storage-vgname', }, diff --git a/src/PVE/Storage/NFSPlugin.pm b/src/PVE/Storage/NFSPlugin.pm index 4cc02c9e..e54c4a4a 100644 --- a/src/PVE/Storage/NFSPlugin.pm +++ b/src/PVE/Storage/NFSPlugin.pm @@ -73,12 +73,14 @@ sub plugindata { sub properties { return { export => { - description => "NFS export path.", + title => "Export Path", + description => "Path exported by the NFS server (e.g., /srv/nfs).", type => 'string', format => 'pve-storage-path', }, server => { - description => "Server IP or DNS name.", + title => "Server", + description => "IP address or hostname of the server.", type => 'string', format => 'pve-storage-server', }, diff --git a/src/PVE/Storage/PBSPlugin.pm b/src/PVE/Storage/PBSPlugin.pm index 6b049b47..eb907aa3 100644 --- a/src/PVE/Storage/PBSPlugin.pm +++ b/src/PVE/Storage/PBSPlugin.pm @@ -42,17 +42,20 @@ sub plugindata { sub properties { return { datastore => { + title => "Datastore", description => "Proxmox Backup Server datastore name.", type => 'string', }, # openssl s_client -connect :8007 2>&1 |openssl x509 -fingerprint -sha256 - fingerprint => get_standard_option('fingerprint-sha256'), + fingerprint => get_standard_option('fingerprint-sha256', { title => "Fingerprint" }), 'encryption-key' => { + title => "Encryption Key", description => "Encryption key. Use 'autogen' to generate one automatically without passphrase.", type => 'string', }, 'master-pubkey' => { + title => "Master Public Key", description => "Base64-encoded, PEM-formatted public RSA key. Used to encrypt a copy of the encryption-key which will be added to each encrypted backup.", type => 'string', diff --git a/src/PVE/Storage/Plugin.pm b/src/PVE/Storage/Plugin.pm index 4f69f9b5..d8f6987a 100644 --- a/src/PVE/Storage/Plugin.pm +++ b/src/PVE/Storage/Plugin.pm @@ -88,36 +88,44 @@ my %prune_option = ( our $prune_backups_format = { 'keep-all' => { type => 'boolean', + title => "Keep All Backups", description => 'Keep all backups. Conflicts with the other options when true.', optional => 1, }, 'keep-last' => { - %prune_option, description => 'Keep the last backups.', + %prune_option, + title => "Keep Last", + description => 'Keep the last backups.', }, 'keep-hourly' => { %prune_option, + title => "Keep Hourly", description => 'Keep backups for the last different hours. If there is more' - . 'than one backup for a single hour, only the latest one is kept.', + . ' than one backup for a single hour, only the latest one is kept.', }, 'keep-daily' => { %prune_option, + title => "Keep Daily", description => 'Keep backups for the last different days. If there is more' - . 'than one backup for a single day, only the latest one is kept.', + . ' than one backup for a single day, only the latest one is kept.', }, 'keep-weekly' => { %prune_option, + title => "Keep Weekly", description => 'Keep backups for the last different weeks. If there is more' - . 'than one backup for a single week, only the latest one is kept.', + . ' than one backup for a single week, only the latest one is kept.', }, 'keep-monthly' => { %prune_option, + title => "Keep Monthly", description => 'Keep backups for the last different months. If there is more' - . 'than one backup for a single month, only the latest one is kept.', + . ' than one backup for a single month, only the latest one is kept.', }, 'keep-yearly' => { %prune_option, + title => "Keep Yearly", description => 'Keep backups for the last different years. If there is more' - . 'than one backup for a single year, only the latest one is kept.', + . ' than one backup for a single year, only the latest one is kept.', }, }; PVE::JSONSchema::register_format('prune-backups', $prune_backups_format, \&validate_prune_backups); @@ -140,6 +148,7 @@ sub validate_prune_backups { register_standard_option( 'prune-backups', { + title => "Backup Retention", description => "The retention options with shorter intervals are processed first " . "with --keep-last being the very first one. Each option covers a " . "specific period of time. We say that backups within this period " @@ -153,19 +162,27 @@ register_standard_option( my $defaultData = { propertyList => { - type => { description => "Storage type." }, + type => { + title => "Type", + description => "Storage type.", + }, storage => get_standard_option( 'pve-storage-id', - { completion => \&PVE::Storage::complete_storage }, + { + title => "ID", + completion => \&PVE::Storage::complete_storage, + }, ), nodes => get_standard_option( 'pve-node-list', { + title => "Nodes", description => "List of nodes for which the storage configuration applies.", optional => 1, }, ), content => { + title => "Content", description => "Allowed content types.\n\nNOTE: the value " . "'rootdir' is used for Containers, and value 'images' for VMs.\n", type => 'string', @@ -174,12 +191,14 @@ my $defaultData = { completion => \&PVE::Storage::complete_content_type, }, disable => { + title => "Disable", description => "Flag to disable the storage.", type => 'boolean', optional => 1, }, 'prune-backups' => get_standard_option('prune-backups'), 'max-protected-backups' => { + title => "Maximum Protected", description => "Maximal number of protected backups per guest. Use '-1' for unlimited.", type => 'integer', @@ -189,6 +208,7 @@ my $defaultData = { "Unlimited for users with Datastore.Allocate privilege, 5 for other users", }, shared => { + title => "Shared", description => "Indicate that this is a single storage with the same contents on all " . "nodes (or all listed in the 'nodes' option). It will not make the contents of a " @@ -198,6 +218,7 @@ my $defaultData = { optional => 1, }, subdir => { + title => "Subdirectory", description => "Subdir to mount.", type => 'string', format => 'pve-storage-path', @@ -206,11 +227,13 @@ my $defaultData = { format => get_standard_option( 'pve-storage-image-format', { + title => "Default Format", description => "Default image format.", optional => 1, }, ), preallocation => { + title => "Preallocation", description => "Preallocation mode for raw and qcow2 images. " . "Using 'metadata' on raw images results in preallocation=off.", type => 'string', @@ -219,18 +242,22 @@ my $defaultData = { optional => 1, }, 'content-dirs' => { + title => "Content Directories", description => "Overrides for default content type directories.", type => "string", format => "pve-dir-override-list", optional => 1, }, options => { - description => "NFS/CIFS mount options (see 'man nfs' or 'man mount.cifs')", + title => "Mount Options", + description => "A list of comma-separated mount options for the filesystem." + . " See 'man 5 fstab' and 'man 8 mount' for more information.", type => 'string', format => 'pve-storage-options', optional => 1, }, port => { + title => "Port", description => "Use this port to connect to the storage instead of the default one (for" . " example, with PBS or ESXi). For NFS and CIFS, use the 'options' option to" @@ -242,6 +269,7 @@ my $defaultData = { }, 'snapshot-as-volume-chain' => { type => 'boolean', + title => "Allow Snapshots as Volume-Chain", description => 'Enable support for creating storage-vendor agnostic snapshot' . ' through volume backing-chains.', default => 0, diff --git a/src/PVE/Storage/RBDPlugin.pm b/src/PVE/Storage/RBDPlugin.pm index b5374251..bf05f739 100644 --- a/src/PVE/Storage/RBDPlugin.pm +++ b/src/PVE/Storage/RBDPlugin.pm @@ -419,24 +419,30 @@ sub plugindata { sub properties { return { monhost => { + title => "Monitor(s)", description => "IP addresses of monitors (for external clusters).", type => 'string', format => 'pve-storage-portal-dns-list', }, pool => { - description => "Pool.", + title => "Pool", + description => "Name of the pool that stores images.", type => 'string', }, 'data-pool' => { - description => "Data Pool (for erasure coding only)", + title => "Data Pool", + description => "Data pool name for erasure-coded images.", type => 'string', }, namespace => { - description => "Namespace.", + title => "Namespace", + description => + "The namespace used for grouping, isolation, or partitioning purposes.", type => 'string', }, username => { - description => "RBD Id.", + title => "Username", + description => "The username used for authentication.", type => 'string', }, authsupported => { @@ -444,11 +450,13 @@ sub properties { type => 'string', }, krbd => { + title => "KRBD", description => "Always access rbd through krbd kernel module.", type => 'boolean', default => 0, }, keyring => { + title => "Keyring", description => "Client keyring contents (for external clusters).", type => 'string', }, diff --git a/src/PVE/Storage/ZFSPlugin.pm b/src/PVE/Storage/ZFSPlugin.pm index 74e0a084..7fef9303 100644 --- a/src/PVE/Storage/ZFSPlugin.pm +++ b/src/PVE/Storage/ZFSPlugin.pm @@ -183,29 +183,35 @@ sub plugindata { sub properties { return { iscsiprovider => { - description => "iscsi provider", + title => "iSCSI Provider", + description => "The iSCSI target implementation used on the remote machine.", type => 'string', }, # this will disable write caching on comstar and istgt. # it is not implemented for iet. iet blockio always operates with # writethrough caching when not in readonly mode nowritecache => { - description => "disable write caching on the target", + title => "Disable Write Cache", + description => "Disable write caching on the iSCSI target.", type => 'boolean', }, comstar_tg => { - description => "target group for comstar views", + title => "Target Group", + description => "Target group for Comstar views.", type => 'string', }, comstar_hg => { - description => "host group for comstar views", + title => "Host Group", + description => "Host group for Comstar views.", type => 'string', }, lio_tpg => { - description => "target portal group for Linux LIO targets", + title => "Target Portal Group", + description => "Target portal group for Linux LIO targets.", type => 'string', }, 'zfs-base-path' => { + title => "Base Path", description => "Base path where to look for the created ZFS block devices. Set" . " automatically during creation if not specified. Usually '/dev/zvol'.", type => 'string', diff --git a/src/PVE/Storage/ZFSPoolPlugin.pm b/src/PVE/Storage/ZFSPoolPlugin.pm index 86307449..0612c25d 100644 --- a/src/PVE/Storage/ZFSPoolPlugin.pm +++ b/src/PVE/Storage/ZFSPoolPlugin.pm @@ -63,17 +63,20 @@ sub plugindata { sub properties { return { blocksize => { - description => "ZFS block size", + title => "Block Size", + description => "ZFS block size.", type => 'string', format => 'pve-storage-zfs-blocksize', format_description => 'a power of 2 with optional k or m suffix', }, sparse => { - description => "use sparse volumes", + title => "Thin Provision", + description => "Use sparse (thin‑provisioned) volumes.", type => 'boolean', }, mountpoint => { - description => "mount point", + title => "Mountpoint", + description => "Path to the directory where the storage is mounted.", type => 'string', format => 'pve-storage-path', }, -- 2.47.3