pve-manager/www/manager6/panel/MultiDiskEdit.js
Dominik Csapak 5f219fd3ba ui: add MultiDiskPanel
this adds a new panel where a user can add multiple disks, intended
for use in the wizard.

Has a simple grid for displaying the already added disks and displays
a warning triangle if the disk is not valid.

this is a base panel for adding multiple disks/mps for vms/ct
respectively.

this combines the shared behavior and layout and defines the functions
that subclasses must define

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
Tested-by: Lorenz Stechauner <l.stechauner@proxmox.com>
Tested-by: Aaron Lauterer <a.lauterer@proxmox.com>
2021-11-05 09:50:36 +01:00

273 lines
6.0 KiB
JavaScript

Ext.define('PVE.panel.MultiDiskPanel', {
extend: 'Ext.panel.Panel',
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);
},
getNextFreeDisk: function(vmconfig) {
throw "implement in subclass";
},
addPanel: function(itemId, vmconfig, nextFreeDisk) {
throw "implement in subclass";
},
// define in subclass
diskSorter: undefined,
addDisk: function() {
let me = this;
let grid = me.lookup('grid');
let store = grid.getStore();
// get free disk id
let vmconfig = me.getVMConfig(true);
let nextFreeDisk = me.getNextFreeDisk(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 = me.addPanel(itemId, vmconfig, nextFreeDisk);
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(me.diskSorter);
// select if the panel added is the only one
if (store.getCount() === 1) {
grid.getSelectionModel().select(0, false);
}
},
getBaseVMConfig: function() {
throw "implement in subclass";
},
getVMConfig: function(all) {
let me = this;
let vmconfig = me.getBaseVMConfig();
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();
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);
return vmconfig;
},
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(me.diskSorter);
// 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.updateVMConfig();
me.checkValidity();
},
onSelectionChange: function(grid, selection) {
let me = this;
if (!selection || selection.length < 1) {
return;
}
me.getView().setActiveItem(selection[0].data.itemId);
},
control: {
'inputpanel': {
diskidchange: 'onChange',
},
'grid[reference=grid]': {
selectionchange: 'onSelectionChange',
},
},
init: function(view) {
let me = this;
me.onAdd();
me.lookup('grid').getSelectionModel().select(0, false);
},
},
dockedItems: [
{
xtype: 'container',
layout: {
type: 'vbox',
align: 'stretch',
},
dock: 'left',
border: false,
width: 130,
items: [
{
xtype: 'grid',
hideHeaders: true,
reference: 'grid',
flex: 1,
emptyText: gettext('No Disks'),
margin: '0 0 5 0',
store: {
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: 1,
},
{
xtype: 'actioncolumn',
width: 30,
align: 'center',
menuDisabled: true,
items: [
{
iconCls: 'x-fa fa-trash critical',
tooltip: 'Delete',
handler: 'onRemove',
isActionDisabled: 'deleteDisabled',
},
],
},
],
},
{
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',
submitValue: false,
value: true,
validator: (val) => !!val,
},
],
},
],
});