[pve-devel] [RFC pve-manager master v1 11/12] ui: storage: support custom storage plugins in Datacenter > Storage
Max R. Carrara
m.carrara at proxmox.com
Mon Sep 8 20:00:55 CEST 2025
This commit implements support for custom form views for custom
storage plugins. This is achieved by adapting the
`createStorageEditWindow()` function to request the plugin's form view
if it's a custom plugin. Whether a plugin is custom or not is first
determined by fetching the metadata of all plugins.
Furthermore, custom storage plugins now show up in the menu of the
"Add" button. If a custom plugin defines a form view, the user can
create a new storage config entry using the GUI.
The 'short-name' of custom plugins is now also displayed in the
storage config list as well as the dropdown selection list of the
"Add" button, if present. Otherwise, we fall back to just using the
plugin's type, as we were already doing.
The items in the menu of the "Add" button are now added dynamically.
This is because making requests is asynchronous. Anything else has led
to various exceptions being thrown while testing this, due to race
conditions.
Signed-off-by: Max R. Carrara <m.carrara at proxmox.com>
---
www/manager6/dc/StorageView.js | 134 +++++++++++++++++++++++++++------
1 file changed, 113 insertions(+), 21 deletions(-)
diff --git a/www/manager6/dc/StorageView.js b/www/manager6/dc/StorageView.js
index e4c6f07d..f4515c94 100644
--- a/www/manager6/dc/StorageView.js
+++ b/www/manager6/dc/StorageView.js
@@ -11,6 +11,47 @@ Ext.define(
stateId: 'grid-dc-storage',
createStorageEditWindow: function (type, sid) {
+ let me = this;
+
+ let metadataForPlugin = me.pluginMetadata[type];
+
+ if (!metadataForPlugin) {
+ Ext.Msg.alert(gettext('Error'), `Plugin '${type}' has no metadata`);
+ return;
+ }
+
+ // NOTE: zfspool is only hardcoded for demonstration purposes
+ if (metadataForPlugin.kind === 'custom' || type === 'zfspool') {
+ Proxmox.Utils.API2Request({
+ url: `/api2/extjs/plugins/storage/${type}/views/form`,
+ method: 'GET',
+ params: {
+ mode: sid ? 'update' : 'create',
+ },
+ failure: function ({ htmlStatus }) {
+ Ext.Msg.alert(gettext('Error'), htmlStatus);
+ },
+ success: function ({ result: { data } }) {
+ let formView = data;
+
+ Ext.create('PVE.storage.CustomBaseEdit', {
+ paneltype: 'PVE.storage.CustomInputPanel',
+ type: type,
+ storageId: sid,
+ canDoBackups: metadataForPlugin.content.supported.includes('backup'),
+ formView: formView,
+ metadataForPlugin: metadataForPlugin,
+ autoShow: true,
+ listeners: {
+ destroy: me.reloadStore,
+ },
+ });
+ },
+ });
+
+ return;
+ }
+
let schema = PVE.Utils.storageSchema[type];
if (!schema || !schema.ipanel) {
throw 'no editor registered for storage type: ' + type;
@@ -23,7 +64,7 @@ Ext.define(
canDoBackups: schema.backups,
autoShow: true,
listeners: {
- destroy: this.reloadStore,
+ destroy: me.reloadStore,
},
});
},
@@ -45,6 +86,63 @@ Ext.define(
let sm = Ext.create('Ext.selection.RowModel', {});
+ me.pluginMetadata = {};
+
+ let menuButtonAdd = new Ext.menu.Menu({
+ items: [],
+ });
+
+ let pushBuiltinPluginsToMenu = function () {
+ for (const [type, storage] of Object.entries(PVE.Utils.storageSchema)) {
+ console.log(`Adding builtin plugin '${type}' to Add Button`);
+ if (storage.hideAdd) {
+ continue;
+ }
+
+ menuButtonAdd.add({
+ text: PVE.Utils.format_storage_type(type),
+ iconCls: 'fa fa-fw fa-' + storage.faIcon,
+ handler: () => me.createStorageEditWindow(type),
+ });
+ }
+ };
+
+ let pushCustomPluginsToMenu = function () {
+ for (const type in me.pluginMetadata) {
+ if (!Object.hasOwn(me.pluginMetadata, type)) {
+ continue;
+ }
+
+ const metadata = me.pluginMetadata[type];
+
+ if (metadata.kind !== 'custom') {
+ continue;
+ }
+
+ menuButtonAdd.add({
+ text: metadata['short-name'] || PVE.Utils.format_storage_type(type),
+ iconCls: 'fa fa-fw fa-folder',
+ handler: () => me.createStorageEditWindow(type),
+ });
+ }
+ };
+
+ Proxmox.Utils.API2Request({
+ url: `/api2/extjs/plugins/storage`,
+ method: 'GET',
+ success: function ({ result: { data } }) {
+ data.forEach((metadata) => {
+ me.pluginMetadata[metadata.type] = metadata;
+ });
+
+ pushBuiltinPluginsToMenu();
+ pushCustomPluginsToMenu();
+ },
+ failure: function ({ htmlStatus }) {
+ Ext.Msg.alert('Error', htmlStatus);
+ },
+ });
+
let run_editor = function () {
let rec = sm.getSelection()[0];
if (!rec) {
@@ -66,23 +164,19 @@ Ext.define(
callback: () => store.load(),
});
- // else we cannot dynamically generate the add menu handlers
- let addHandleGenerator = function (type) {
- return function () {
- me.createStorageEditWindow(type);
- };
- };
- let addMenuItems = [];
- for (const [type, storage] of Object.entries(PVE.Utils.storageSchema)) {
- if (storage.hideAdd) {
- continue;
+ // value is plugin type here
+ let columnRendererType = function (value, meta, record) {
+ let metadataForPlugin = me.pluginMetadata[value];
+
+ if (metadataForPlugin && metadataForPlugin.kind === 'custom') {
+ return (
+ metadataForPlugin['short-name'] ||
+ PVE.Utils.format_storage_type(value, meta, record)
+ );
}
- addMenuItems.push({
- text: PVE.Utils.format_storage_type(type),
- iconCls: 'fa fa-fw fa-' + storage.faIcon,
- handler: addHandleGenerator(type),
- });
- }
+
+ return PVE.Utils.format_storage_type(value, meta, record);
+ };
Ext.apply(me, {
store: store,
@@ -94,9 +188,7 @@ Ext.define(
tbar: [
{
text: gettext('Add'),
- menu: new Ext.menu.Menu({
- items: addMenuItems,
- }),
+ menu: menuButtonAdd,
},
remove_btn,
edit_btn,
@@ -113,7 +205,7 @@ Ext.define(
flex: 1,
sortable: true,
dataIndex: 'type',
- renderer: PVE.Utils.format_storage_type,
+ renderer: columnRendererType,
},
{
header: gettext('Content'),
--
2.47.2
More information about the pve-devel
mailing list