pve-manager/www/manager6/qemu/CloudInit.js
Mira Limbeck 044b08f255 change permissions for non-network cloudinit settings
With the introduction of VM.Config.Cloudinit we can set the user,
password and an SSH key without VM.Config.Network permission.
Keep the fallback for VM.Config.Network so custom roles don't break.

Signed-off-by: Mira Limbeck <m.limbeck@proxmox.com>
2020-06-26 09:43:00 +02:00

328 lines
8.0 KiB
JavaScript

Ext.define('PVE.qemu.CloudInit', {
extend: 'Proxmox.grid.PendingObjectGrid',
xtype: 'pveCiPanel',
onlineHelp: 'qm_cloud_init',
tbar: [
{
xtype: 'proxmoxButton',
disabled: true,
dangerous: true,
confirmMsg: function(rec) {
var me = this.up('grid');
var warn = gettext('Are you sure you want to remove entry {0}');
var entry = rec.data.key;
var msg = Ext.String.format(warn, "'"
+ me.renderKey(entry, {}, rec) + "'");
return msg;
},
enableFn: function(record) {
var me = this.up('grid');
var caps = Ext.state.Manager.get('GuiCap');
if (me.rows[record.data.key].never_delete ||
!caps.vms['VM.Config.Network']) {
return false;
}
if (record.data.key === 'cipassword' && !record.data.value) {
return false;
}
return true;
},
handler: function() {
var me = this.up('grid');
var records = me.getSelection();
if (!records || !records.length) {
return;
}
var id = records[0].data.key;
var match = id.match(/^net(\d+)$/);
if (match) {
id = 'ipconfig' + match[1];
}
var params = {};
params['delete'] = id;
Proxmox.Utils.API2Request({
url: me.baseurl + '/config',
waitMsgTarget: me,
method: 'PUT',
params: params,
failure: function(response, opts) {
Ext.Msg.alert('Error', response.htmlStatus);
},
callback: function() {
me.reload();
}
});
},
text: gettext('Remove')
},
{
xtype: 'proxmoxButton',
disabled: true,
enableFn: function(rec) {
let me = this.up('pveCiPanel');
return !!me.rows[rec.data.key].editor;
},
handler: function() {
var me = this.up('grid');
me.run_editor();
},
text: gettext('Edit')
},
'-',
{
xtype: 'button',
itemId: 'savebtn',
text: gettext('Regenerate Image'),
handler: function() {
var me = this.up('grid');
var eject_params = {};
var insert_params = {};
var disk = PVE.Parser.parseQemuDrive(me.ciDriveId, me.ciDrive);
var storage = '';
var stormatch = disk.file.match(/^([^\:]+)\:/);
if (stormatch) {
storage = stormatch[1];
}
eject_params[me.ciDriveId] = 'none,media=cdrom';
insert_params[me.ciDriveId] = storage + ':cloudinit';
var failure = function(response, opts) {
Ext.Msg.alert('Error', response.htmlStatus);
};
Proxmox.Utils.API2Request({
url: me.baseurl + '/config',
waitMsgTarget: me,
method: 'PUT',
params: eject_params,
failure: failure,
callback: function() {
Proxmox.Utils.API2Request({
url: me.baseurl + '/config',
waitMsgTarget: me,
method: 'PUT',
params: insert_params,
failure: failure,
callback: function() {
me.reload();
}
});
}
});
}
}
],
border: false,
set_button_status: function(rstore, records, success) {
if (!success || records.length < 1) {
return;
}
var me = this;
var found;
records.forEach(function(record) {
if (found) {
return;
}
var id = record.data.key;
var value = record.data.value;
var ciregex = new RegExp("vm-" + me.pveSelNode.data.vmid + "-cloudinit");
if (id.match(/^(ide|scsi|sata)\d+$/) && ciregex.test(value)) {
found = id;
me.ciDriveId = found;
me.ciDrive = value;
}
});
me.down('#savebtn').setDisabled(!found);
me.setDisabled(!found);
if (!found) {
me.getView().mask(gettext('No CloudInit Drive found'), ['pve-static-mask']);
} else {
me.getView().unmask();
}
},
renderKey: function(key, metaData, rec, rowIndex, colIndex, store) {
var me = this;
var rows = me.rows;
var rowdef = rows[key] || {};
var icon = "";
if (rowdef.iconCls) {
icon = '<i class="' + rowdef.iconCls + '"></i> ';
}
return icon + (rowdef.header || key);
},
listeners: {
activate: function () {
var me = this;
me.rstore.startUpdate();
},
itemdblclick: function() {
var me = this;
me.run_editor();
}
},
initComponent: function() {
var me = this;
var nodename = me.pveSelNode.data.node;
if (!nodename) {
throw "no node name specified";
}
var vmid = me.pveSelNode.data.vmid;
if (!vmid) {
throw "no VM ID specified";
}
var caps = Ext.state.Manager.get('GuiCap');
me.baseurl = '/api2/extjs/nodes/' + nodename + '/qemu/' + vmid;
me.url = me.baseurl + '/pending';
me.editorConfig.url = me.baseurl + '/config';
me.editorConfig.pveSelNode = me.pveSelNode;
let caps_ci = caps.vms['VM.Config.Cloudinit'];
|| caps.vms['VM.Config.Network'];
/* editor is string and object */
me.rows = {
ciuser: {
header: gettext('User'),
iconCls: 'fa fa-user',
never_delete: true,
defaultValue: '',
editor: caps_ci ? {
xtype: 'proxmoxWindowEdit',
subject: gettext('User'),
items: [
{
xtype: 'proxmoxtextfield',
deleteEmpty: true,
emptyText: Proxmox.Utils.defaultText,
fieldLabel: gettext('User'),
name: 'ciuser'
}
]
} : undefined,
renderer: function(value) {
return value || Proxmox.Utils.defaultText;
}
},
cipassword: {
header: gettext('Password'),
iconCls: 'fa fa-unlock',
defaultValue: '',
editor: caps_ci ? {
xtype: 'proxmoxWindowEdit',
subject: gettext('Password'),
items: [
{
xtype: 'proxmoxtextfield',
inputType: 'password',
deleteEmpty: true,
emptyText: Proxmox.Utils.noneText,
fieldLabel: gettext('Password'),
name: 'cipassword'
}
]
} : undefined,
renderer: function(value) {
return value || Proxmox.Utils.noneText;
}
},
searchdomain: {
header: gettext('DNS domain'),
iconCls: 'fa fa-globe',
editor: caps.vms['VM.Config.Network'] ? 'PVE.lxc.DNSEdit' : undefined,
never_delete: true,
defaultValue: gettext('use host settings')
},
nameserver: {
header: gettext('DNS servers'),
iconCls: 'fa fa-globe',
editor: caps.vms['VM.Config.Network'] ? 'PVE.lxc.DNSEdit' : undefined,
never_delete: true,
defaultValue: gettext('use host settings')
},
sshkeys: {
header: gettext('SSH public key'),
iconCls: 'fa fa-key',
editor: caps_ci ? 'PVE.qemu.SSHKeyEdit' : undefined,
never_delete: true,
renderer: function(value) {
value = decodeURIComponent(value);
var keys = value.split('\n');
var text = [];
keys.forEach(function(key) {
if (key.length) {
// First erase all quoted strings (eg. command="foo"
var v = key.replace(/"(?:\\.|[^"\\])*"/g, '');
// Now try to detect the comment:
var res = v.match(/^\s*(\S+\s+)?(?:ssh-(?:dss|rsa|ed25519)|ecdsa-sha2-nistp\d+)\s+\S+\s+(.*?)\s*$/, '');
if (res) {
key = Ext.String.htmlEncode(res[2]);
if (res[1]) {
key += ' <span style="color:gray">(' + gettext('with options') + ')</span>';
}
text.push(key);
return;
}
// Most likely invalid at this point, so just stick to
// the old value.
text.push(Ext.String.htmlEncode(key));
}
});
if (text.length) {
return text.join('<br>');
} else {
return Proxmox.Utils.noneText;
}
},
defaultValue: ''
}
};
var i;
var ipconfig_renderer = function(value, md, record, ri, ci, store, pending) {
var id = record.data.key;
var match = id.match(/^net(\d+)$/);
var val = '';
if (match) {
val = me.getObjectValue('ipconfig'+match[1], '', pending);
}
return val;
};
for (i = 0; i < 32; i++) {
// we want to show an entry for every network device
// even if it is empty
me.rows['net' + i.toString()] = {
multiKey: ['ipconfig' + i.toString(), 'net' + i.toString()],
header: gettext('IP Config') + ' (net' + i.toString() +')',
editor: caps.vms['VM.Config.Network'] ? 'PVE.qemu.IPConfigEdit' : undefined,
iconCls: 'fa fa-exchange',
renderer: ipconfig_renderer
};
me.rows['ipconfig' + i.toString()] = {
visible: false
};
}
PVE.Utils.forEachBus(['ide', 'scsi', 'sata'], function(type, id) {
me.rows[type+id] = {
visible: false
};
});
me.callParent();
me.mon(me.rstore, 'load', me.set_button_status, me);
}
});