summaryGrid.getStore().setData(Object.entries(values).map(([key, value]) => ({ key, value })));
},
+ // assume assigned sata disks indices are continuous, so without holes
+ getMaxSata: function() {
+ let me = this;
+ let view = me.getView();
+ if (view.maxSata !== undefined) {
+ return view.maxSata;
+ }
+
+ view.maxSata = -1;
+ for (const key of Object.keys(me.getView().vmConfig)) {
+ if (!key.toLowerCase().startsWith('sata')) {
+ continue;
+ }
+ let idx = parseInt(key.slice(4), 10);
+ if (idx > view.maxSata) {
+ view.maxSata = idx;
+ }
+ }
+ me.lookup('diskGrid').getStore().each(rec => {
+ if (!rec.data.id.toLowerCase().startsWith('sata')) {
+ return;
+ }
+ let idx = parseInt(rec.data.id.slice(4), 10);
+ if (idx > view.maxSata) {
+ view.maxSata = idx;
+ }
+ });
+ me.lookup('cdGrid').getStore().each(rec => {
+ if (!rec.data.id.toLowerCase().startsWith('sata')) {
+ return;
+ }
+ let idx = parseInt(rec.data.id.slice(4), 10);
+ if (idx > view.maxSata) {
+ view.maxSata = idx;
+ }
+ });
+
+ return view.maxSata;
+ },
+
+ mapDisk: function(value, metaData) {
+ let me = this;
+ let mapSata = me.lookup('mapSata');
+ if (mapSata.isDisabled() || !mapSata.getValue()) {
+ return value;
+ }
+ if (!value.toLowerCase().startsWith('scsi')) {
+ return value;
+ }
+ let offset = parseInt(value.slice(4), 10);
+ let newIdx = offset + me.getMaxSata() + 1;
+ if (newIdx > PVE.Utils.diskControllerMaxIDs.sata) {
+ let prefix = '';
+ if (metaData !== undefined) {
+ // we're in the renderer so put a warning here
+ let warning = gettext('Too many disks, could not map to SATA.');
+ prefix = `<i data-qtip="${warning}" class="fa fa-exclamation-triangle warning"></i> `;
+ }
+ return `${prefix}${value}`;
+ }
+ return `sata${newIdx}`;
+ },
+
+ refreshDiskGrid: function() {
+ this.lookup('diskGrid').reconfigure();
+ },
+
+ toggleIsoSelector: function(_cb, value) {
+ let me = this;
+ me.lookup('isoSelector').setDisabled(!value);
+ me.lookup('isoSelector').setHidden(!value);
+ },
+
control: {
'grid field': {
// update records from widgetcolumns
'panel[reference=summaryTab]': {
activate: 'calculateConfig',
},
+ 'proxmoxcheckbox[reference=mapSata]': {
+ change: 'refreshDiskGrid',
+ },
+ 'combobox[name=ostype]': {
+ change: 'refreshDiskGrid',
+ },
+ 'proxmoxcheckbox[reference=enableSecondCD]': {
+ change: 'toggleIsoSelector',
+ },
},
},
data: {
coreCount: 1,
socketCount: 1,
+ liveImport: false,
+ os: '',
warnings: [],
},
hideWarnings: get => get('warnings').length === 0,
warningsText: get => '<ul style="margin: 0; padding-left: 20px;">'
+ get('warnings').map(w => `<li>${w}</li>`).join('') + '</ul>',
+ liveImportNote: get => !get('liveImport') ? ''
+ : gettext('Note: If anything goes wrong during the live-import, new data written by the VM may be lost.'),
+ isWindows: get => (get('os') ?? '').startsWith('win'),
},
},
config.scsi0 = config.scsi0.replace('local:0,', 'local:0,format=qcow2,');
}
+ let parsedBoot = PVE.Parser.parsePropertyString(config.boot ?? '');
+ if (parsedBoot.order) {
+ parsedBoot.order = parsedBoot.order.split(';');
+ }
+
grid.lookup('diskGrid').getStore().each((rec) => {
if (!rec.data.enable) {
return;
}
- let id = rec.data.id;
+ let id = grid.getController().mapDisk(rec.data.id);
+ if (id !== rec.data.id && parsedBoot?.order) {
+ let idx = parsedBoot.order.indexOf(rec.data.id);
+ if (idx !== -1) {
+ parsedBoot.order[idx] = id;
+ }
+ }
let data = {
...rec.data,
};
config[id] = PVE.Parser.printQemuDrive(data);
});
+ if (parsedBoot.order) {
+ parsedBoot.order = parsedBoot.order.join(';');
+ }
+ config.boot = PVE.Parser.printPropertyString(parsedBoot);
+
grid.lookup('netGrid').getStore().each((rec) => {
if (!rec.data.enable) {
return;
config[id] = PVE.Parser.printPropertyString(cd);
});
+ config.scsihw = grid.lookup('scsihw').getValue();
+
if (grid.lookup('liveimport').getValue()) {
config['live-restore'] = 1;
}
+ if (grid.lookup('enableSecondCD')) {
+ let idsToTry = ['ide0', 'ide2'];
+ for (let i = 0; i <=PVE.Utils.diskControllerMaxIDs.sata; i++) {
+ idsToTry.push(`sata{$i}`);
+ }
+ let found = false;
+ for (const id of idsToTry) {
+ if (!config[id]) {
+ config[id] = PVE.Parser.printQemuDrive({
+ media: 'cdrom',
+ file: grid.lookup('isoSelector').getValue(),
+ });
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ console.warn('could not insert cd drive for virtio');
+ }
+ }
+
+ // remove __default__ values
+ for (const [key, value] of Object.entries(config)) {
+ if (value === '__default__') {
+ delete config[key];
+ }
+ }
+
return config;
},
queryMode: 'local',
valueField: 'val',
displayField: 'desc',
+ bind: {
+ value: '{os}',
+ },
store: {
fields: ['desc', 'val'],
data: PVE.Utils.kvm_ostypes.Linux,
fieldLabel: gettext('Live Import'),
reference: 'liveimport',
isFormField: false,
- boxLabel: gettext('Experimental'),
+ boxLabelCls: 'pmx-hint black x-form-cb-label',
+ bind: {
+ value: '{liveImport}',
+ boxLabel: '{liveImportNote}',
+ },
},
{
xtype: 'displayfield',
{
title: gettext('Advanced'),
xtype: 'inputpanel',
- items: [
+
+ // the first inputpanel handles the values, prevent
+ // accidental values from this inputpanel here
+ onGetValues: () => ({}),
+ column1: [
+ {
+ xtype: 'pveScsiHwSelector',
+ reference: 'scsihw',
+ name: 'scsihw',
+ submitValue: false,
+ fieldLabel: gettext('SCSI Controller'),
+ },
+ {
+ xtype: 'proxmoxcheckbox',
+ reference: 'enableSecondCD',
+ isFormField: false,
+ hidden: true,
+ checked: false,
+ boxLabel: gettext('Add additional drive for VirtIO drivers'),
+ bind: {
+ hidden: '{!isWindows}',
+ disabled: '{!isWindows}',
+ },
+ },
+ ],
+
+ column2: [
+ {
+ xtype: 'proxmoxcheckbox',
+ fieldLabel: gettext('Map SCSI to SATA'),
+ labelWidth: 120,
+ reference: 'mapSata',
+ isFormField: false,
+ hidden: true,
+ disabled: true,
+ bind: {
+ hidden: '{!isWindows}',
+ disabled: '{!isWindows}',
+ },
+ autoEl: {
+ tag: 'div',
+ 'data-qtip': gettext('Useful when wanting to use VirtIO-SCSI'),
+ },
+ },
+ {
+ xtype: 'pveIsoSelector',
+ reference: 'isoSelector',
+ submitValue: false,
+ labelWidth: 120,
+ labelAlign: 'left',
+ insideWizard: true,
+ hidden: true,
+ disabled: true,
+ },
+ ],
+
+ columnB: [
{
xtype: 'displayfield',
fieldLabel: gettext('Disks'),
{
xtype: 'grid',
reference: 'diskGrid',
- minHeight: 58,
+ minHeight: 60,
maxHeight: 150,
store: {
data: [],
{
text: gettext('Disk'),
dataIndex: 'id',
+ renderer: 'mapDisk',
},
{
text: gettext('Source'),
xtype: 'displayfield',
fieldLabel: gettext('CD/DVD Drives'),
labelWidth: 200,
+ style: {
+ paddingTop: '10px',
+ },
},
{
xtype: 'grid',
reference: 'cdGrid',
- minHeight: 58,
+ minHeight: 60,
maxHeight: 150,
store: {
data: [],
xtype: 'displayfield',
fieldLabel: gettext('Network Interfaces'),
labelWidth: 200,
+ style: {
+ paddingTop: '10px',
+ },
},
{
xtype: 'grid',
me.lookup('defaultStorage').setNodename(me.nodename);
me.lookup('defaultBridge').setNodename(me.nodename);
+ me.lookup('isoSelector').setNodename(me.nodename);
let renderWarning = w => {
const warningsCatalogue = {
- 'cdrom-image-ignored': gettext("CD-ROM images cannot get imported, please reconfigure the '{0}' drive after the import"),
+ 'cdrom-image-ignored': gettext("CD-ROM images cannot get imported, if required you can reconfigure the '{0}' drive in the 'Advanced' tab."),
'nvme-unsupported': gettext("NVMe disks are currently not supported, '{0}' will get attaced as SCSI"),
'ovmf-with-lsi-unsupported': gettext("OVMF is built without LSI drivers, scsi hardware was set to '{1}'"),
'serial-port-socket-only': gettext("Serial socket '{0}' will be mapped to a socket"),
+ 'guest-is-running': gettext('Virtual guest seems to be running on source host. Import might fail or have inconsistent state!'),
};
let message = warningsCatalogue[w.type];
if (!w.type || !message) {