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 3DBF31FF187 for ; Mon, 8 Sep 2025 20:02:02 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id ADBDB18AFA; Mon, 8 Sep 2025 20:01:40 +0200 (CEST) From: "Max R. Carrara" To: pve-devel@lists.proxmox.com Date: Mon, 8 Sep 2025 20:00:48 +0200 Message-ID: <20250908180058.530119-5-m.carrara@proxmox.com> X-Mailer: git-send-email 2.47.2 In-Reply-To: <20250908180058.530119-1-m.carrara@proxmox.com> References: <20250908180058.530119-1-m.carrara@proxmox.com> MIME-Version: 1.0 X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1757354444292 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.087 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 Subject: [pve-devel] [RFC pve-storage master v1 04/12] plugin: views: add package PVE::Storage::Plugin::Views X-BeenThere: pve-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox VE development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: Proxmox VE development discussion Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Errors-To: pve-devel-bounces@lists.proxmox.com Sender: "pve-devel" This package defines schemas and utils for storage plugin views. A view in this context is what defines how data should be displayed to users. Views are specified via a nested hash in Perl, which is then serialized into JSON. Right now, the only such view that can be defined is a "form" view. A form view is simply the representation of a single record; in the context of storage plugins here, this would be the form that opens when you create or edit a single storage configuration entry in the UI. This commit adds a versioned JSON schema for storage plugin form views. The first version of this form view supports customizing the "General" tab in the following ways: - Adding various types of columns: - Regular columns - A "bottom" column (the wide column below the regular ones) - Columns in the advanced subsection - A "bottom" column in the advanced subsection - Adding fields to those columns - A field always corresponds to a SectionConfig property - Fields are typed and share common attributes (readonly, required, default) - Specific field types have specialized attributes unique to them, e.g. 'string' supports setting a 'display-mode', which can be 'text', 'textarea' or 'password' ('text' by default) Because each field corresponds to a SectionConfig property, the existing API calls for creating & editing storage config entries can simply be reused. The ultimate goal here is to allow custom storage plugin authors to allow integrating their plugin into our GUI with minimal effort and without ever having to write JavaScript code. In fact, not being able to write JS is a hard requirement for this feature. The form view schema will be used in further commits after this one. Some additional context: Most of this approach is inspired by my past experience wrangling ERP systems [0]. The ERP system I was developing modules for in particular defined *a lot* of data models which could all be represented via several generalized view types (such as form, list, gantt chart, kanban, etc.). This was possible because all of those data models shared a common base model and consequently a common database representation as well. While all the applications within the system were different, the way they were built was the same. Furthermore, the idea expressed in this commit here is a simplification of the somewhat commonly used MVVM architectural pattern [1], in case that helps with understanding. [0]: https://en.wikipedia.org/wiki/Enterprise_resource_planning [1]: https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93viewmodel Signed-off-by: Max R. Carrara --- src/PVE/Storage/Plugin/Makefile | 1 + src/PVE/Storage/Plugin/Views.pm | 242 ++++++++++++++++++++++++++++++++ 2 files changed, 243 insertions(+) create mode 100644 src/PVE/Storage/Plugin/Views.pm diff --git a/src/PVE/Storage/Plugin/Makefile b/src/PVE/Storage/Plugin/Makefile index ca82517..2e9b538 100644 --- a/src/PVE/Storage/Plugin/Makefile +++ b/src/PVE/Storage/Plugin/Makefile @@ -1,4 +1,5 @@ SOURCES = Meta.pm \ + Views.pm \ INSTALL_PATH = ${DESTDIR}${PERLDIR}/PVE/Storage/Plugin diff --git a/src/PVE/Storage/Plugin/Views.pm b/src/PVE/Storage/Plugin/Views.pm new file mode 100644 index 0000000..597c657 --- /dev/null +++ b/src/PVE/Storage/Plugin/Views.pm @@ -0,0 +1,242 @@ +package PVE::Storage::Plugin::Views; + +use v5.36; + +use Storable qw(dclone); + +use PVE::JSONSchema; + +use Exporter qw(import); + +our @EXPORT_OK = qw( + get_form_view_schema +); + +=head1 NAME + +PVE::Storage::Plugin::Views - Schemas and Utils for Storage Plugin Views + +=head1 DESCRIPTION + +=for comment +TODO + +=cut + +package PVE::Storage::Plugin::Views::v1 { + use v5.36; + + use Storable qw(dclone); + + use PVE::JSONSchema; + + my $FIELD_TYPES = [ + 'boolean', 'integer', 'number', 'string', 'selection', + ]; + + my $ATTRIBUTES_COMMON = { + required => { + type => 'boolean', + optional => 1, + }, + readonly => { + type => 'boolean', + optional => 1, + }, + # NOTE: Overridden per field type; specified here to make clear that + # this is a common attribute + default => { + type => 'any', + optional => 1, + }, + }; + + my $ATTRIBUTES_BOOLEAN = { + 'instance-types' => ['boolean'], + $ATTRIBUTES_COMMON->%*, + default => { + type => 'boolean', + optional => 1, + }, + }; + + my $ATTRIBUTES_INTEGER = { + 'instance-types' => ['integer'], + $ATTRIBUTES_COMMON->%*, + default => { + type => 'integer', + optional => 1, + }, + }; + + my $ATTRIBUTES_NUMBER = { + 'instance-types' => ['number'], + $ATTRIBUTES_COMMON->%*, + default => { + type => 'number', + optional => 1, + }, + }; + + my $ATTRIBUTES_STRING = { + 'instance-types' => ['string'], + $ATTRIBUTES_COMMON->%*, + default => { + type => 'string', + optional => 1, + }, + 'display-mode' => { + type => 'string', + enum => ['text', 'textarea', 'password'], + optional => 1, + default => 'text', + }, + }; + + my $ATTRIBUTES_SELECTION = { + 'instance-types' => ['selection'], + $ATTRIBUTES_COMMON->%*, + 'selection-mode' => { + type => 'string', + enum => ['single', 'multi'], + optional => 1, + default => 'single', + }, + # List of "tuples" where the first element is the selection value, + # and the second element is how the selection value should be displayed to the user. + # For example: + # selection_values => [ + # ['gzip', "Compress using GZIP"], + # ['zstd', "Compress using ZSTD"], + # ['none', "No Compression"], + # ]; + 'selection-values' => { + type => 'array', + optional => 0, + items => { + type => 'array', + items => { + type => 'string', + }, + }, + }, + # The values selected by default on creation. + # Must exist in selection_values. + # If selection-mode is 'single', then only the first element is considered. + default => { + type => 'array', + items => { + type => 'string', + }, + optional => 1, + }, + }; + + my $FIELD_ATTRIBUTES_VARIANTS = [ + $ATTRIBUTES_BOOLEAN, + $ATTRIBUTES_INTEGER, + $ATTRIBUTES_NUMBER, + $ATTRIBUTES_STRING, + $ATTRIBUTES_SELECTION, + ]; + + my $FIELD_SCHEMA = { + type => 'object', + properties => { + property => { + type => 'string', + optional => 0, + }, + 'field-type' => { + type => 'string', + enum => $FIELD_TYPES, + optional => 0, + }, + label => { + type => 'string', + optional => 0, + }, + attributes => { + type => 'object', + 'type-property' => 'field-type', + oneOf => $FIELD_ATTRIBUTES_VARIANTS, + optional => 0, + }, + }, + }; + + my $COLUMN_SCHEMA = { + type => 'object', + properties => { + fields => { + type => 'array', + items => $FIELD_SCHEMA, + optional => 1, + }, + }, + }; + + my $FORM_VIEW_SCHEMA = { + type => 'object', + properties => { + general => { + type => 'object', + optional => 1, + properties => { + columns => { + type => 'array', + items => $COLUMN_SCHEMA, + optional => 1, + }, + 'column-bottom' => { + $COLUMN_SCHEMA->%*, optional => 1, + }, + 'columns-advanced' => { + type => 'array', + items => $COLUMN_SCHEMA, + optional => 1, + }, + 'column-advanced-bottom' => { + $COLUMN_SCHEMA->%*, optional => 1, + }, + }, + }, + }, + }; + + PVE::JSONSchema::validate_schema($FORM_VIEW_SCHEMA); + + sub get_form_view_schema() { + return dclone($FORM_VIEW_SCHEMA); + } +}; + +my $API_FORM_VIEW_SCHEMA = { + type => 'object', + properties => { + version => { + type => 'integer', + enum => [1], + optional => 0, + }, + definition => { + type => 'object', + 'type-property' => 'version', + optional => 0, + oneOf => [ + { + 'instance-types' => [1], + PVE::Storage::Plugin::Views::v1::get_form_view_schema()->%*, + }, + ], + }, + }, +}; + +PVE::JSONSchema::validate_schema($API_FORM_VIEW_SCHEMA); + +sub get_form_view_schema() { + return dclone($API_FORM_VIEW_SCHEMA); +} + +1; -- 2.47.2 _______________________________________________ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel