From: Dominik Csapak <d.csapak@proxmox.com>
To: pve-devel@lists.proxmox.com
Subject: [pve-devel] [PATCH manager 10/10] ui: add qemu/MultiHDEdit and use it in the wizard
Date: Mon, 20 Sep 2021 14:23:38 +0200 [thread overview]
Message-ID: <20210920122338.840436-11-d.csapak@proxmox.com> (raw)
In-Reply-To: <20210920122338.840436-1-d.csapak@proxmox.com>
this adds a new panel where a user can add multiple disks.
Has a simple grid for displaying the already added disks and displays
a warning triangle if the disk is not valid.
This allows also to create a vm without any disk by removing all of them.
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
www/manager6/Makefile | 1 +
www/manager6/qemu/CreateWizard.js | 5 +-
www/manager6/qemu/HDEdit.js | 9 +-
www/manager6/qemu/MultiHDEdit.js | 291 ++++++++++++++++++++++++++++++
4 files changed, 301 insertions(+), 5 deletions(-)
create mode 100644 www/manager6/qemu/MultiHDEdit.js
diff --git a/www/manager6/Makefile b/www/manager6/Makefile
index 7d491f57..d76acf14 100644
--- a/www/manager6/Makefile
+++ b/www/manager6/Makefile
@@ -213,6 +213,7 @@ JSSRC= \
qemu/MachineEdit.js \
qemu/MemoryEdit.js \
qemu/Monitor.js \
+ qemu/MultiHDEdit.js \
qemu/NetworkEdit.js \
qemu/OSDefaults.js \
qemu/OSTypeEdit.js \
diff --git a/www/manager6/qemu/CreateWizard.js b/www/manager6/qemu/CreateWizard.js
index 015a099d..75836aab 100644
--- a/www/manager6/qemu/CreateWizard.js
+++ b/www/manager6/qemu/CreateWizard.js
@@ -154,14 +154,11 @@ Ext.define('PVE.qemu.CreateWizard', {
insideWizard: true,
},
{
- xtype: 'pveQemuHDInputPanel',
- padding: 0,
+ xtype: 'pveMultiHDPanel',
bind: {
nodename: '{nodename}',
},
title: gettext('Hard Disk'),
- isCreate: true,
- insideWizard: true,
},
{
xtype: 'pveQemuProcessorPanel',
diff --git a/www/manager6/qemu/HDEdit.js b/www/manager6/qemu/HDEdit.js
index 2142c746..9c453b2a 100644
--- a/www/manager6/qemu/HDEdit.js
+++ b/www/manager6/qemu/HDEdit.js
@@ -107,6 +107,12 @@ Ext.define('PVE.qemu.HDInputPanel', {
return params;
},
+ updateVMConfig: function(vmconfig) {
+ var me = this;
+ me.vmconfig = vmconfig;
+ me.bussel?.updateVMConfig(vmconfig);
+ },
+
setVMConfig: function(vmconfig) {
var me = this;
@@ -183,7 +189,8 @@ Ext.define('PVE.qemu.HDInputPanel', {
if (!me.confid || me.unused) {
me.bussel = Ext.create('PVE.form.ControllerSelector', {
- vmconfig: me.insideWizard ? { ide2: 'cdrom' } : {},
+ vmconfig: me.vmconfig,
+ selectFree: true,
});
column1.push(me.bussel);
diff --git a/www/manager6/qemu/MultiHDEdit.js b/www/manager6/qemu/MultiHDEdit.js
new file mode 100644
index 00000000..079a6fc6
--- /dev/null
+++ b/www/manager6/qemu/MultiHDEdit.js
@@ -0,0 +1,291 @@
+Ext.define('PVE.qemu.MultiHDPanel', {
+ extend: 'Ext.panel.Panel',
+ alias: 'widget.pveMultiHDPanel',
+
+ onlineHelp: 'qm_hard_disk',
+
+ setNodename: function(nodename) {
+ this.items.each((panel) => panel.setNodename(nodename));
+ },
+
+ border: false,
+ bodyBorder: false,
+
+ layout: 'card',
+
+ controller: {
+ xclass: 'Ext.app.ViewController',
+
+ vmconfig: {},
+
+ onAdd: function() {
+ let me = this;
+ me.lookup('addButton').setDisabled(true);
+ me.addDisk();
+ let count = me.lookup('grid').getStore().getCount() + 1; // +1 is from ide2
+ me.lookup('addButton').setDisabled(count >= me.maxCount);
+ },
+
+ addDisk: function() {
+ let me = this;
+ let view = me.getView();
+ let grid = me.lookup('grid');
+ let store = grid.getStore();
+
+ // get free disk id
+ let vmconfig = me.getVMConfig(true);
+ let clist = PVE.Utils.sortByPreviousUsage(vmconfig);
+ let nextFreeDisk = PVE.Utils.nextFreeDisk(clist, vmconfig);
+ if (!nextFreeDisk) {
+ return;
+ }
+
+ // add store entry + panel
+ let itemId = 'disk-card-' + ++Ext.idSeed;
+ let rec = store.add({
+ name: nextFreeDisk.confid,
+ itemId,
+ })[0];
+
+ let panel = view.add({
+ vmconfig,
+ border: false,
+ showAdvanced: Ext.state.Manager.getProvider().get('proxmox-advanced-cb'),
+ xtype: 'pveQemuHDInputPanel',
+ bind: {
+ nodename: '{nodename}',
+ },
+ padding: '0 0 0 5',
+ itemId,
+ isCreate: true,
+ insideWizard: true,
+ });
+
+ panel.updateVMConfig(vmconfig);
+
+ // we need to setup a validitychange handler, so that we can show
+ // that a disk has invalid fields
+ let fields = panel.query('field');
+ fields.forEach((el) => el.on('validitychange', () => {
+ let valid = fields.every((field) => field.isValid());
+ rec.set('valid', valid);
+ me.checkValidity();
+ }));
+
+ store.sort();
+
+ // select if the panel added is the only one
+ if (store.getCount() === 1) {
+ grid.getSelectionModel().select(0, false);
+ }
+ },
+
+ getVMConfig: function(all) {
+ let me = this;
+
+ let vmconfig = {
+ ide2: 'media=cdrom',
+ scsihw: me.getViewModel().get('current.scsihw'),
+ };
+
+ me.lookup('grid').getStore().each((rec) => {
+ if (all || rec.get('valid')) {
+ vmconfig[rec.get('name')] = rec.get('itemId');
+ }
+ });
+
+ return vmconfig;
+ },
+
+ checkValidity: function() {
+ let me = this;
+ let valid = me.lookup('grid').getStore().findExact('valid', false) !== -1;
+ me.lookup('validationfield').setValue(valid);
+ },
+
+ updateVMConfig: function() {
+ let me = this;
+ let view = me.getView();
+ let grid = me.lookup('grid');
+ let store = grid.getStore();
+
+ let vmconfig = me.getVMConfig();
+
+ me.getViewModel().set('current.scsihw', vmconfig.scsihw);
+
+ let valid = true;
+
+ store.each((rec) => {
+ let itemId = rec.get('itemId');
+ let name = rec.get('name');
+ let panel = view.getComponent(itemId);
+ if (!panel) {
+ throw "unexpected missing panel";
+ }
+
+ // copy config for each panel and remote its own id
+ let panel_vmconfig = Ext.apply({}, vmconfig);
+ if (panel_vmconfig[name] === itemId) {
+ delete panel_vmconfig[name];
+ }
+
+ if (!rec.get('valid')) {
+ valid = false;
+ }
+
+ panel.updateVMConfig(panel_vmconfig);
+ });
+
+ me.lookup('validationfield').setValue(valid);
+ },
+
+ onChange: function(panel, newVal) {
+ let me = this;
+ let store = me.lookup('grid').getStore();
+
+ let el = store.findRecord('itemId', panel.itemId, 0, false, true, true);
+ if (el.get('name') === newVal) {
+ // do not update if there was no change
+ return;
+ }
+
+ el.set('name', newVal);
+ el.commit();
+
+ store.sort();
+
+ // so that it happens after the layouting
+ setTimeout(function() {
+ me.updateVMConfig();
+ }, 10);
+ },
+
+ onRemove: function(tableview, rowIndex, colIndex, item, event, record) {
+ let me = this;
+ let grid = me.lookup('grid');
+ let store = grid.getStore();
+ let removed_idx = store.indexOf(record);
+
+ let selection = grid.getSelection()[0];
+ let selected_idx = store.indexOf(selection);
+
+ if (selected_idx === removed_idx) {
+ let newidx = store.getCount() > removed_idx + 1 ? removed_idx + 1: removed_idx - 1;
+ grid.getSelectionModel().select(newidx, false);
+ }
+
+ store.remove(record);
+ me.getView().remove(record.get('itemId'));
+ me.lookup('addButton').setDisabled(false);
+ me.checkValidity();
+ },
+
+ onSelectionChange: function(grid, selection) {
+ let me = this;
+ if (!selection || selection.length < 1) {
+ return;
+ }
+
+ me.getView().setActiveItem(selection[0].data.itemId);
+ },
+
+ control: {
+ 'pveQemuHDInputPanel': {
+ diskidchange: 'onChange',
+ },
+ 'grid[reference=grid]': {
+ selectionchange: 'onSelectionChange',
+ },
+ },
+
+ init: function(view) {
+ let me = this;
+ me.onAdd();
+ me.lookup('grid').getSelectionModel().select(0, false);
+
+ // only calculate once
+ me.maxCount = Object.values(PVE.Utils.diskControllerMaxIDs)
+ .reduce((previous, current) => previous+current, 0);
+ },
+ },
+
+ dockedItems: [
+ {
+ xtype: 'container',
+ layout: {
+ type: 'vbox',
+ align: 'stretch',
+ },
+ dock: 'left',
+ border: false,
+ width: 150,
+ items: [
+ {
+ xtype: 'grid',
+ hideHeaders: true,
+ reference: 'grid',
+ flex: 1,
+ emptyText: gettext('No Disks'),
+ margin: '0 0 5 0',
+ store: {
+ sorters: [{
+ sorterFn: function(rec1, rec2) {
+ let [, name1, id1] = PVE.Utils.bus_match.exec(rec1.data.name);
+ let [, name2, id2] = PVE.Utils.bus_match.exec(rec2.data.name);
+
+ if (name1 === name2) {
+ return parseInt(id1, 10) - parseInt(id2, 10);
+ }
+
+ return name1 < name2 ? -1 : 1;
+ },
+ }],
+ fields: ['name', 'itemId', 'valid'],
+ data: [],
+ },
+ columns: [
+ {
+ dataIndex: 'name',
+ renderer: function(val, md, rec) {
+ let warn = '';
+ if (!rec.get('valid')) {
+ warn = ' <i class="fa warning fa-warning"></i>';
+ }
+ return val + warn;
+ },
+ flex: 4,
+ },
+ {
+ flex: 1,
+ xtype: 'actioncolumn',
+ align: 'center',
+ menuDisabled: true,
+ items: [
+ {
+ iconCls: 'x-fa fa-trash critical',
+ tooltip: 'Delete',
+ handler: 'onRemove',
+ },
+ ],
+ },
+ ],
+ },
+ {
+ xtype: 'button',
+ reference: 'addButton',
+ text: gettext('Add'),
+ iconCls: 'fa fa-plus-circle',
+ handler: 'onAdd',
+ },
+ {
+ // dummy field to control wizard validation
+ xtype: 'textfield',
+ hidden: true,
+ reference: 'validationfield',
+ value: true,
+ validator: (val) => !!val,
+ },
+ ],
+ },
+ ],
+});
--
2.30.2
next prev parent reply other threads:[~2021-09-20 12:23 UTC|newest]
Thread overview: 12+ messages / expand[flat|nested] mbox.gz Atom feed top
2021-09-20 12:23 [pve-devel] [PATCH manager 00/10] multi tab disk panel & multi disk wizard Dominik Csapak
2021-09-20 12:23 ` [pve-devel] [PATCH manager 01/10] ui: qemu/HDEdit: move Bandwidth options to a different tab Dominik Csapak
2021-09-20 12:23 ` [pve-devel] [PATCH manager 02/10] ui: form/ControllerSelector: set correct max value for the device-id Dominik Csapak
2021-09-20 12:23 ` [pve-devel] [PATCH manager 03/10] ui: refactor sortByPreviousUsage and nextFreeDisk Dominik Csapak
2021-09-20 12:23 ` [pve-devel] [PATCH manager 04/10] ui: form/ControllerSelector: add updateVMConfig and getConfId Dominik Csapak
2021-09-20 12:23 ` [pve-devel] [PATCH manager 05/10] ui: qemu/HDEdit: use me instead of this Dominik Csapak
2021-09-20 12:23 ` [pve-devel] [PATCH manager 06/10] ui: qemu/HDEdit: fire an event when the disk id changes Dominik Csapak
2021-09-20 12:23 ` [pve-devel] [PATCH manager 07/10] ui: qemu/OSTypeEdit: drop throwing an error on multiple widgets Dominik Csapak
2021-09-20 12:23 ` [pve-devel] [PATCH manager 08/10] ui: Utils: add capture group for the id in bus_match Dominik Csapak
2021-09-20 12:23 ` [pve-devel] [PATCH manager 09/10] ui: form/ControllerSelector: add option for selecting free on inital config Dominik Csapak
2021-09-20 12:23 ` Dominik Csapak [this message]
2021-09-21 13:46 ` [pve-devel] [PATCH manager 00/10] multi tab disk panel & multi disk wizard Lorenz Stechauner
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20210920122338.840436-11-d.csapak@proxmox.com \
--to=d.csapak@proxmox.com \
--cc=pve-devel@lists.proxmox.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal