Commit 240ae6fa authored by Dietmar Maurer's avatar Dietmar Maurer

cleanup novnc console code

And implement novnc console for openvz and shell (needs vncterm update).
parent ab70e2ab
......@@ -682,6 +682,11 @@ __PACKAGE__->register_method ({
optional => 1,
default => 0,
},
websocket => {
optional => 1,
type => 'boolean',
description => "use websocket instead of standard vnc.",
},
},
},
returns => {
......@@ -745,7 +750,14 @@ __PACKAGE__->register_method ({
my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
'-timeout', $timeout, '-authpath', $authpath,
'-perm', 'Sys.Console', '-c', @$remcmd, @$shcmd];
'-perm', 'Sys.Console'];
if ($param->{websocket}) {
$ENV{PVE_VNC_TICKET} = $ticket; # pass ticket to vncterm
push @$cmd, '-notls', '-listen', 'localhost';
}
push @$cmd, '-c', @$remcmd, @$shcmd;
my $realcmd = sub {
my $upid = shift;
......@@ -757,6 +769,7 @@ __PACKAGE__->register_method ({
eval {
foreach my $k (keys %ENV) {
next if $k eq 'PVE_VNC_TICKET';
next if $k eq 'PATH' || $k eq 'TERM' || $k eq 'USER' || $k eq 'HOME';
delete $ENV{$k};
}
......@@ -784,6 +797,56 @@ __PACKAGE__->register_method ({
};
}});
__PACKAGE__->register_method({
name => 'vncwebsocket',
path => 'vncwebsocket',
method => 'GET',
permissions => {
description => "Restricted to users on realm 'pam'. You also need to pass a valid ticket (vncticket).",
check => ['perm', '/nodes/{node}', [ 'Sys.Console' ]],
},
description => "Opens a weksocket for VNC traffic.",
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
vncticket => {
description => "Ticket from previous call to vncproxy.",
type => 'string',
maxLength => 512,
},
port => {
description => "Port number returned by previous vncproxy call.",
type => 'integer',
minimum => 5900,
maximum => 5999,
},
},
},
returns => {
type => "object",
properties => {
port => { type => 'string' },
},
},
code => sub {
my ($param) = @_;
my $rpcenv = PVE::RPCEnvironment::get();
my ($user, undef, $realm) = PVE::AccessControl::verify_username($rpcenv->get_user());
raise_perm_exc("realm != pam") if $realm ne 'pam';
my $authpath = "/nodes/$param->{node}";
PVE::AccessControl::verify_vnc_ticket($param->{vncticket}, $user, $authpath);
my $port = $param->{port};
return { port => $port };
}});
__PACKAGE__->register_method ({
name => 'spiceshell',
path => 'spiceshell',
......
......@@ -833,6 +833,11 @@ __PACKAGE__->register_method ({
properties => {
node => get_standard_option('pve-node'),
vmid => get_standard_option('pve-vmid'),
websocket => {
optional => 1,
type => 'boolean',
description => "use websocket instead of standard VNC.",
},
},
},
returns => {
......@@ -889,7 +894,14 @@ __PACKAGE__->register_method ({
my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
'-timeout', $timeout, '-authpath', $authpath,
'-perm', 'VM.Console', '-c', @$remcmd, @$shcmd];
'-perm', 'VM.Console'];
if ($param->{websocket}) {
$ENV{PVE_VNC_TICKET} = $ticket; # pass ticket to vncterm
push @$cmd, '-notls', '-listen', 'localhost';
}
push @$cmd, '-c', @$remcmd, @$shcmd;
run_command($cmd);
......@@ -909,6 +921,55 @@ __PACKAGE__->register_method ({
};
}});
__PACKAGE__->register_method({
name => 'vncwebsocket',
path => '{vmid}/vncwebsocket',
method => 'GET',
permissions => {
description => "You also need to pass a valid ticket (vncticket).",
check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
},
description => "Opens a weksocket for VNC traffic.",
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
vmid => get_standard_option('pve-vmid'),
vncticket => {
description => "Ticket from previous call to vncproxy.",
type => 'string',
maxLength => 512,
},
port => {
description => "Port number returned by previous vncproxy call.",
type => 'integer',
minimum => 5900,
maximum => 5999,
},
},
},
returns => {
type => "object",
properties => {
port => { type => 'string' },
},
},
code => sub {
my ($param) = @_;
my $rpcenv = PVE::RPCEnvironment::get();
my $authuser = $rpcenv->get_user();
my $authpath = "/vms/$param->{vmid}";
PVE::AccessControl::verify_vnc_ticket($param->{vncticket}, $authuser, $authpath);
my $port = $param->{port};
return { port => $port };
}});
__PACKAGE__->register_method ({
name => 'spiceproxy',
path => '{vmid}/spiceproxy',
......
......@@ -1088,9 +1088,10 @@ Ext.define('PVE.Utils', { statics: {
}
},
openConsoleWindow: function(vmtype, vmid, nodename, vmname) {
openConsoleWindow: function(vmtype, vmid, nodename, vmname, novnc) {
var url = Ext.urlEncode({
console: vmtype, // kvm, openvz or shell
novnc: novnc ? 1 : 0,
vmid: vmid,
vmname: vmname,
node: nodename
......
......@@ -54,6 +54,8 @@ Ext.define('PVE.VNCConsole', {
extend: 'Ext.panel.Panel',
alias: ['widget.pveVNCConsole'],
novnc: false,
initComponent : function() {
var me = this;
......@@ -65,31 +67,49 @@ Ext.define('PVE.VNCConsole', {
me.appletID = myid;
var box = Ext.create('Ext.Component', {
border: false,
html: ""
});
var box;
if (me.novnc) {
if (!me.wsurl) {
throw "no web socket url specified";
}
box = Ext.create('widget.uxiframe', { id: myid });
} else {
box = Ext.create('Ext.Component', { border: false, html: "" });
}
var resize_window = function() {
//console.log("resize");
var applet = Ext.getDom(myid);
//console.log("resize " + myid + " " + applet);
var aw;
var ah;
var applet;
if (me.novnc) {
var novnciframe = box.getFrame();
// noVNC_canvas
var innerDoc = novnciframe.contentDocument || novnciframe.contentWindow.document;
aw = innerDoc.getElementById('noVNC_canvas').width + 8;
ah = innerDoc.getElementById('noVNC_canvas').height + 8;
} else {
applet = Ext.getDom(myid);
// try again when dom element is available
if (!(applet && Ext.isFunction(applet.getPreferredSize))) {
return Ext.Function.defer(resize_window, 1000);
}
// try again when dom element is available
if (!(applet && Ext.isFunction(applet.getPreferredSize))) {
return Ext.Function.defer(resize_window, 1000);
}
var tbar = me.getDockedItems("[dock=top]")[0];
var tbh = tbar ? tbar.getHeight() : 0;
var ps = applet.getPreferredSize();
var aw = ps.width;
var ah = ps.height;
var ps = applet.getPreferredSize();
aw = ps.width;
ah = ps.height;
}
if (aw < 640) { aw = 640; }
if (ah < 400) { ah = 400; }
var tbar = me.getDockedItems("[dock=top]")[0];
var tbh = tbar ? tbar.getHeight() : 0;
var oh;
var ow;
......@@ -109,7 +129,9 @@ Ext.define('PVE.VNCConsole', {
throw "can't get window size";
}
Ext.fly(applet).setSize(aw, ah + tbh);
if (!me.novnc) {
Ext.fly(applet).setSize(aw, ah + tbh);
}
var offsetw = aw - ow;
var offseth = ah + tbh - oh;
......@@ -123,39 +145,62 @@ Ext.define('PVE.VNCConsole', {
};
var resize_box = function() {
var applet = Ext.getDom(myid);
if ((applet && Ext.isFunction(applet.getPreferredSize))) {
var ps = applet.getPreferredSize();
Ext.fly(applet).setSize(ps.width, ps.height);
if (me.novnc) {
throw "implement me";
} else {
var applet = Ext.getDom(myid);
if ((applet && Ext.isFunction(applet.getPreferredSize))) {
var ps = applet.getPreferredSize();
Ext.fly(applet).setSize(ps.width, ps.height);
}
}
Ext.Function.defer(resize_box, 1000);
};
var start_vnc_viewer = function(param) {
var cert = param.cert;
cert = cert.replace(/\n/g, "|");
box.update({
id: myid,
border: false,
tag: 'applet',
code: 'com.tigervnc.vncviewer.VncViewer',
archive: '/vncterm/VncViewer.jar',
// NOTE: set size to '100%' - else resize does not work
width: "100%",
height: "100%",
cn: [
{tag: 'param', name: 'id', value: myid},
{tag: 'param', name: 'PORT', value: param.port},
{tag: 'param', name: 'PASSWORD', value: param.ticket},
{tag: 'param', name: 'USERNAME', value: param.user},
{tag: 'param', name: 'Show Controls', value: 'No'},
{tag: 'param', name: 'Offer Relogin', value: 'No'},
{tag: 'param', name: 'PVECert', value: cert}
]
});
if (me.novnc) {
var pveparams = Ext.urlEncode({
port: param.port,
vncticket: param.ticket
});
var urlparams = Ext.urlEncode({
encrypt: 1,
path: "api2/json" + me.wsurl + "?" + pveparams,
password: param.ticket
});
box.load('/novnc/vnc_pve.html?' + urlparams);
} else {
var cert = param.cert;
cert = cert.replace(/\n/g, "|");
box.update({
id: myid,
border: false,
tag: 'applet',
code: 'com.tigervnc.vncviewer.VncViewer',
archive: '/vncterm/VncViewer.jar',
// NOTE: set size to '100%' - else resize does not work
width: "100%",
height: "100%",
cn: [
{tag: 'param', name: 'id', value: myid},
{tag: 'param', name: 'PORT', value: param.port},
{tag: 'param', name: 'PASSWORD', value: param.ticket},
{tag: 'param', name: 'USERNAME', value: param.user},
{tag: 'param', name: 'Show Controls', value: 'No'},
{tag: 'param', name: 'Offer Relogin', value: 'No'},
{tag: 'param', name: 'PVECert', value: cert}
]
});
}
if (me.toplevel) {
Ext.Function.defer(resize_window, 1000);
} else {
......@@ -169,9 +214,13 @@ Ext.define('PVE.VNCConsole', {
autoScroll: me.toplevel ? false : true,
items: box,
reloadApplet: function() {
var params = Ext.apply({}, me.params);
if (me.novnc) {
params.websocket = 1;
}
PVE.Utils.API2Request({
url: me.url,
params: me.params,
params: params,
method: me.method || 'POST',
failure: function(response, opts) {
box.update(gettext('Error') + ' ' + response.htmlStatus);
......@@ -186,9 +235,9 @@ Ext.define('PVE.VNCConsole', {
me.callParent();
if (me.toplevel) {
me.on("render", function() { me.reloadApplet();});
me.on("render", me.reloadApplet);
} else {
me.on("show", function() { me.reloadApplet();});
me.on("show", me.reloadApplet);
me.on("hide", function() { box.update(""); });
}
}
......@@ -209,10 +258,12 @@ Ext.define('PVE.KVMConsole', {
throw "no VM ID specified";
}
var baseUrl = "/nodes/" + me.nodename + "/qemu/" + me.vmid;
var vm_command = function(cmd, params, reload_applet) {
PVE.Utils.API2Request({
params: params,
url: '/nodes/' + me.nodename + '/qemu/' + me.vmid + "/status/" + cmd,
url: baseUrl + "/status/" + cmd,
method: 'POST',
waitMsgTarget: me,
failure: function(response, opts) {
......@@ -296,7 +347,7 @@ Ext.define('PVE.KVMConsole', {
{
text: gettext('Console'),
handler: function() {
PVE.Utils.openConsoleWindow('kvm', me.vmid, me.nodename, me.vmname);
PVE.Utils.openConsoleWindow('kvm', me.vmid, me.nodename, me.vmname, me.novnc);
}
},
'->',
......@@ -315,9 +366,11 @@ Ext.define('PVE.KVMConsole', {
}
];
Ext.apply(me, {
tbar: tbar,
url: "/nodes/" + me.nodename + "/qemu/" + me.vmid + "/vncproxy"
url: baseUrl + "/vncproxy",
wsurl: baseUrl + "/vncwebsocket"
});
me.callParent();
......@@ -339,10 +392,12 @@ Ext.define('PVE.OpenVZConsole', {
throw "no VM ID specified";
}
var baseUrl = "/nodes/" + me.nodename + "/openvz/" + me.vmid;
var vm_command = function(cmd, params, reload_applet) {
PVE.Utils.API2Request({
params: params,
url: '/nodes/' + me.nodename + '/openvz/' + me.vmid + "/status/" + cmd,
url: baseUrl + "/status/" + cmd,
waitMsgTarget: me,
method: 'POST',
failure: function(response, opts) {
......@@ -406,7 +461,8 @@ Ext.define('PVE.OpenVZConsole', {
Ext.apply(me, {
tbar: tbar,
url: "/nodes/" + me.nodename + "/openvz/" + me.vmid + "/vncproxy"
url: baseUrl + "/vncproxy",
wsurl: baseUrl + "/vncwebsocket"
});
me.callParent();
......@@ -450,15 +506,17 @@ Ext.define('PVE.Shell', {
{
text: gettext('Shell'),
handler: function() {
PVE.Utils.openConsoleWindow('shell', undefined, me.nodename);
PVE.Utils.openConsoleWindow('shell', undefined, me.nodename, undefined, me.novnc);
}
}
]);
var baseUrl = "/nodes/" + me.nodename;
Ext.apply(me, {
tbar: tbar,
url: "/nodes/" + me.nodename + "/vncshell"
url: baseUrl + "/vncshell",
wsurl: baseUrl + "/vncwebsocket"
});
if (me.ugradeSystem) {
......@@ -468,233 +526,3 @@ Ext.define('PVE.Shell', {
me.callParent();
}
});
Ext.define('PVE.noVNCConsole', {
extend: 'Ext.panel.Panel',
alias: ['widget.pvenoVNCConsole'],
initComponent : function() {
var me = this;
if (!me.url) {
throw "no url specified";
}
var myid = me.id + "-vncapp";
var box = Ext.create('widget.uxiframe', { id: myid });
var resize_window = function() {
//console.log("resize");
var novnciframe = box.getFrame();
// console.log("resize " + myid + " " + novnciframe);
// noVNC_canvas
var tbar = me.getDockedItems("[dock=top]")[0];
var tbh = tbar ? tbar.getHeight() : 0;
// does not work as ExtJS modifies our iframe id from below
var innerDoc = novnciframe.contentDocument || novnciframe.contentWindow.document;
var aw = innerDoc.getElementById('noVNC_canvas').width + 8;
var ah = innerDoc.getElementById('noVNC_canvas').height + 8;
if (aw < 640) { aw = 640; }
if (ah < 400) { ah = 400; }
var oh;
var ow;
//console.log("size0 " + aw + " " + ah + " tbh " + tbh);
if (window.innerHeight) {
oh = window.innerHeight;
ow = window.innerWidth;
} else if (document.documentElement &&
document.documentElement.clientHeight) {
oh = document.documentElement.clientHeight;
ow = document.documentElement.clientWidth;
} else if (document.body) {
oh = document.body.clientHeight;
ow = document.body.clientWidth;
} else {
throw "can't get window size";
}
var offsetw = aw - ow;
var offseth = ah + tbh - oh;
if (offsetw !== 0 || offseth !== 0) {
//console.log("try resize by " + offsetw + " " + offseth);
try { window.resizeBy(offsetw, offseth); } catch (e) {}
}
Ext.Function.defer(resize_window, 1000);
};
var start_novnc_viewer = function(param) {
var urlparams = Ext.urlEncode({
encrypt: 1,
path: "api2/json/nodes/" + me.nodename + "/qemu/" + me.vmid + "/vncwebsocket?port=" + param.port,
password: param.ticket
});
box.load('/novnc/vnc_pve.html?' + urlparams);
if (me.toplevel) {
Ext.Function.defer(resize_window, 1000);
}
};
Ext.apply(me, {
layout: 'fit',
border: false,
autoScroll: me.toplevel ? false : true,
items: box,
reloadnoVNC: function() {
PVE.Utils.API2Request({
url: me.url,
params: { websocket: 1 },
method: me.method || 'POST',
failure: function(response, opts) {
box.update(gettext('Error') + ' ' + response.htmlStatus);
},
success: function(response, opts) {
start_novnc_viewer(response.result.data);
}
});
}
});
me.callParent();
if (me.toplevel) {
me.on("render", function() { me.reloadnoVNC();});
} else {
me.on("show", function() { me.reloadnoVNC();});
me.on("hide", function() { box.update(""); });
}
}
});
Ext.define('PVE.novncConsole', {
extend: 'PVE.noVNCConsole',
alias: ['widget.pvenovncConsole'],
initComponent : function() {
var me = this;
if (!me.nodename) {
throw "no node name specified";
}
if (!me.vmid) {
throw "no VM ID specified";
}
var vm_command = function(cmd, params, reload) {
PVE.Utils.API2Request({
params: params,
url: '/nodes/' + me.nodename + '/qemu/' + me.vmid + "/status/" + cmd,
method: 'POST',
waitMsgTarget: me,
failure: function(response, opts) {
Ext.Msg.alert('Error', response.htmlStatus);
},
success: function() {
if (reload) {
Ext.Function.defer(me.reloadnoVNC, 1000, me);
}
}
});
};
var tbar = [
{
text: gettext('Start'),
handler: function() {
vm_command("start", {}, 1);
}
},
{
text: gettext('Shutdown'),
handler: function() {
var msg = Ext.String.format(gettext("Do you really want to shutdown VM {0}?"), me.vmid);
Ext.Msg.confirm(gettext('Confirm'), msg, function(btn) {
if (btn !== 'yes') {
return;
}
vm_command('shutdown');
});
}
},
{
text: gettext('Kill VM'),
handler: function() {
var msg = Ext.String.format(gettext("Do you really want to KILL VM {0}? This can cause data loss!"), me.vmid);
Ext.Msg.confirm(gettext('Confirm'), msg, function(btn) {
if (btn !== 'yes') {
return;
}
vm_command("stop");
});
}
},
{
xtype: 'pveQemuSendKeyMenu',
nodename: me.nodename,
vmid: me.vmid
},
{
text: gettext('Reset'),
handler: function() {
var msg = Ext.String.format(gettext("Do you really want to reset VM {0}?"), me.vmid);
Ext.Msg.confirm(gettext('Confirm'), msg, function(btn) {
if (btn !== 'yes') {
return;
}
vm_command("reset");
});
}
},
{
text: gettext('Suspend'),
handler: function() {
var msg = Ext.String.format(gettext("Do you really want to suspend VM {0}?"), me.vmid);
Ext.Msg.confirm(gettext('Confirm'), msg, function(btn) {
if (btn !== 'yes') {
return;
}
vm_command("suspend");
});
}
},
{
text: gettext('Resume'),
handler: function() {
vm_command("resume");
}
},
// Note: no migrate here, because we can't display migrate log
{
text: gettext('Console'),
handler: function() {
PVE.Utils.openConsoleWindow('kvm', me.vmid, me.nodename, me.vmname);
}
},
'->',
{
text: gettext('Reload'),
handler: function () {
me.reloadnoVNC();
}
}
];
Ext.apply(me, {
tbar: tbar,
url: "/nodes/" + me.nodename + "/qemu/" + me.vmid + "/vncproxy"
});
me.callParent();
}
});
......@@ -116,6 +116,8 @@ Ext.define('PVE.ConsoleWorkspace', {
var param = Ext.Object.fromQueryString(window.location.search);
var consoleType = me.consoleType || param.console;
param.novnc = (param.novnc === '1') ? true : false;
var content;
if (consoleType === 'kvm') {
me.title = "VM " + param.vmid;
......@@ -124,23 +126,12 @@ Ext.define('PVE.ConsoleWorkspace', {
}
content = {
xtype: 'pveKVMConsole',
novnc: param.novnc,
vmid: param.vmid,
nodename: param.node,
vmname: param.vmname,
toplevel: true
};
} else if (consoleType === 'novnc') {
me.title = "VM " + param.vmid;
if (param.vmname) {
me.title += " ('" + param.vmname + "')";
}
content = {
xtype: 'pvenovncConsole',
vmid: param.vmid,
nodename: param.node,
vmname: param.vmname,
toplevel: true
};
} else if (consoleType === 'openvz') {
me.title = "CT " + param.vmid;
if (param.vmname) {
......@@ -148,6 +139,7 @@ Ext.define('PVE.ConsoleWorkspace', {
}
content = {
xtype: 'pveOpenVZConsole',
novnc: param.novnc,
vmid: param.vmid,
nodename: param.node,
vmname: param.vmname,
......@@ -157,6 +149,7 @@ Ext.define('PVE.ConsoleWorkspace', {
me.title = "node '" + param.node + "'";
content = {
xtype: 'pveShell',
novnc: param.novnc,
nodename: param.node,
toplevel: true
};
......@@ -164,6 +157,7 @@ Ext.define('PVE.ConsoleWorkspace', {
me.title = Ext.String.format(gettext('System upgrade on node {0}'), "'" + param.node + "'");
content = {
xtype: 'pveShell',
novnc: param.novnc,
nodename: param.node,
ugradeSystem: true,
toplevel: true
......
......@@ -71,21 +71,15 @@ Ext.define('PVE.button.ConsoleButton', {
}
};
var create_novnc_console = function() {
var create_vnc_console = function(novnc) {
if (me.consoleType === 'kvm') {
PVE.Utils.openConsoleWindow('novnc', me.vmid, me.nodename, me.consoleName);
}
};
var create_vnc_console = function() {
if (me.consoleType === 'kvm') {
PVE.Utils.openConsoleWindow('kvm', me.vmid, me.nodename, me.consoleName);
PVE.Utils.openConsoleWindow('kvm', me.vmid, me.nodename, me.consoleName, novnc);
} else if (me.consoleType === 'openvz') {
PVE.Utils.openConsoleWindow('openvz', me.vmid, me.nodename, me.consoleName);
PVE.Utils.openConsoleWindow('openvz', me.vmid, me.nodename, me.consoleName, novnc);
} else if (me.consoleType === 'shell') {
PVE.Utils.openConsoleWindow('shell', undefined, me.nodename);
PVE.Utils.openConsoleWindow('shell', undefined, me.nodename, undefined, novnc);
} else if (me.consoleType === 'upgrade') {
var url = Ext.urlEncode({ console: 'upgrade', node: me.nodename });
var url = Ext.urlEncode({ console: 'upgrade', node: me.nodename, novnc: novnc });
var nw = window.open("?" + url, '_blank', "innerWidth=745,innerheight=427");
nw.focus();
}
......@@ -100,13 +94,13 @@ Ext.define('PVE.button.ConsoleButton', {
var vncMenu = Ext.create('Ext.menu.Item', {
text: 'VNC',
iconCls: 'pve-itype-icon-tigervnc',
handler: create_vnc_console
handler: function() { create_vnc_console(0); }
});
var novncMenu = Ext.create('Ext.menu.Item', {
var noVncMenu = Ext.create('Ext.menu.Item', {
text: 'noVNC',
iconCls: 'pve-itype-icon-novnc',
handler: create_novnc_console
handler: function() { create_vnc_console(1); }
});
Ext.applyIf(me, { text: gettext('Console') });
......@@ -121,7 +115,7 @@ Ext.define('PVE.button.ConsoleButton', {
}
},
menu: new Ext.menu.Menu({
items: [ novncMenu, vncMenu, me.spiceMenu ]
items: [ noVncMenu, vncMenu, me.spiceMenu ]
})
});
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment