[pve-devel] [PATCH 7/8] add novnc console to workspace

Stefan Priebe s.priebe at profihost.ag
Sun Jun 1 22:49:24 CEST 2014


Signed-off-by: Stefan Priebe <s.priebe at profihost.ag>
---
 www/manager/Workspace.js            |   12 +
 www/manager/button/ConsoleButton.js |  717 ++++++++++++++++++++++++++++++++++-
 2 files changed, 728 insertions(+), 1 deletion(-)

diff --git a/www/manager/Workspace.js b/www/manager/Workspace.js
index e1f4405..a890e99 100644
--- a/www/manager/Workspace.js
+++ b/www/manager/Workspace.js
@@ -129,6 +129,18 @@ Ext.define('PVE.ConsoleWorkspace', {
 		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) {
diff --git a/www/manager/button/ConsoleButton.js b/www/manager/button/ConsoleButton.js
index 6dd45d8..5e763f0 100644
--- a/www/manager/button/ConsoleButton.js
+++ b/www/manager/button/ConsoleButton.js
@@ -1,3 +1,706 @@
+PVE_vnc_console_event = function(appletid, action, err) {
+    //console.log("TESTINIT param1 " + appletid + " action " + action);
+
+    if (action === "error") {
+	var compid = appletid.replace("-vncapp", "");
+	var comp = Ext.getCmp(compid);
+
+	if (!comp || !comp.vmid || !comp.toplevel) {
+	    return;
+	}
+
+	// try to detect migrated VM
+	PVE.Utils.API2Request({
+	    url: '/cluster/resources',
+	    method: 'GET',
+	    success: function(response) {
+		var list = response.result.data;
+		Ext.Array.each(list, function(item) {
+		    if (item.type === 'qemu' && item.vmid == comp.vmid) {
+			if (item.node !== comp.nodename) {
+			    //console.log("MOVED VM to node " + item.node);
+			    comp.nodename = item.node;
+			    comp.url = "/nodes/" + comp.nodename + "/" + item.type + "/" + comp.vmid + "/vncproxy";
+			    //console.log("NEW URL " + comp.url);
+			    comp.reloadApplet();
+			}
+			return false; // break
+		    }
+		});
+	    }
+	});
+    }
+
+    return;
+    /*
+      var el = Ext.get(appletid);
+      if (!el)
+      return;
+
+      if (action === "close") {
+      //	el.remove();
+      } else if (action === "error") {
+      //	console.log("TESTERROR: " + err);
+      //	var compid = appletid.replace("-vncapp", "");
+      //	var comp = Ext.getCmp(compid);
+      }
+
+      //Ext.get('mytestid').remove();
+      */
+
+};
+
+Ext.define('PVE.VNCConsole', {
+    extend: 'Ext.panel.Panel',
+    alias: ['widget.pveVNCConsole'],
+
+    initComponent : function() {
+	var me = this;
+
+	if (!me.url) {
+	    throw "no url specified";
+	}
+
+	var myid = me.id + "-vncapp";
+
+	me.appletID = myid;
+
+	var 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);
+	    
+	    // 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;
+
+	    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";
+	    }
+
+	    Ext.fly(applet).setSize(aw, ah + tbh);
+
+	    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 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);
+	    }
+
+	    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.toplevel) {
+		Ext.Function.defer(resize_window, 1000);
+            } else {
+		Ext.Function.defer(resize_box, 1000);
+            }
+	};
+
+	Ext.apply(me, {
+	    layout: 'fit',
+	    border: false,
+	    autoScroll: me.toplevel ? false : true,
+	    items: box,
+	    reloadApplet: function() {
+		PVE.Utils.API2Request({
+		    url: me.url,
+		    params: me.params,
+		    method: me.method || 'POST',
+		    failure: function(response, opts) {
+			box.update(gettext('Error') + ' ' + response.htmlStatus);
+		    },
+		    success: function(response, opts) {
+			start_vnc_viewer(response.result.data);
+		    }
+		});
+	    }
+	});
+
+	me.callParent();
+
+	if (me.toplevel) {
+	    me.on("render", function() { me.reloadApplet();});
+	} else {
+	    me.on("show", function() { me.reloadApplet();});
+	    me.on("hide", function() { box.update(""); });
+	}
+    }
+});
+
+Ext.define('PVE.KVMConsole', {
+    extend: 'PVE.VNCConsole',
+    alias: ['widget.pveKVMConsole'],
+
+    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_applet) {
+	    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_applet) {
+			Ext.Function.defer(me.reloadApplet, 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('Refresh'),
+		handler: function() { 
+		    var applet = Ext.getDom(me.appletID);
+		    applet.sendRefreshRequest();
+		}
+	    },
+	    {
+                text: gettext('Reload'),
+                handler: function () { 
+		    me.reloadApplet(); 
+		}
+	    }
+	];
+
+	Ext.apply(me, {
+	    tbar: tbar,
+	    url: "/nodes/" + me.nodename + "/qemu/" + me.vmid + "/vncproxy"
+	});
+
+	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,
+		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: me.params,
+		    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",
+	    params: { unsecure: 1, websocket: 1 }
+	});
+
+	me.callParent();
+    }
+});
+
+Ext.define('PVE.OpenVZConsole', {
+    extend: 'PVE.VNCConsole',
+    alias: ['widget.pveOpenVZConsole'],
+
+    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_applet) {
+	    PVE.Utils.API2Request({
+		params: params,
+		url: '/nodes/' + me.nodename + '/openvz/' + me.vmid + "/status/" + cmd,
+		waitMsgTarget: me,
+		method: 'POST',
+		failure: function(response, opts) {
+		    Ext.Msg.alert('Error', response.htmlStatus);
+		},
+		success: function() {
+		    if (reload_applet) {
+			Ext.Function.defer(me.reloadApplet, 1000, me);
+		    }
+		}
+	    });
+	};
+
+	var tbar = [ 
+	    { 
+		text: gettext('Start'),
+		handler: function() { 
+		    vm_command("start");
+		}
+	    },
+	    { 
+		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('Stop'),
+		handler: function() {
+		    var msg = Ext.String.format(gettext("Do you really want to stop VM {0}?"), me.vmid);
+		    Ext.Msg.confirm(gettext('Confirm'), msg, function(btn) {
+			if (btn !== 'yes') {
+			    return;
+			}
+			vm_command("stop");
+		    }); 
+		}
+	    },
+	    // Note: no migrate here, because we can't display migrate log
+            '->',
+	    {
+                text: gettext('Refresh'),
+		handler: function() { 
+		    var applet = Ext.getDom(me.appletID);
+		    applet.sendRefreshRequest();
+		}
+	    },
+	    {
+                text: gettext('Reload'),
+                handler: function () { 
+		    me.reloadApplet(); 
+		}
+	    }
+	];
+
+	Ext.apply(me, {
+	    tbar: tbar,
+	    url: "/nodes/" + me.nodename + "/openvz/" + me.vmid + "/vncproxy"
+	});
+
+	me.callParent();
+    }
+});
+
+Ext.define('PVE.Shell', {
+    extend: 'PVE.VNCConsole',
+    alias: ['widget.pveShell'],
+
+    ugradeSystem: false, // set to true to run "apt-get dist-upgrade"
+
+    initComponent : function() {
+	var me = this;
+ 
+	if (!me.nodename) { 
+	    throw "no node name specified";
+	}
+
+	var tbar = [ 
+           '->',
+	    {
+                text: gettext('Refresh'),
+		handler: function() { 
+		    var applet = Ext.getDom(me.appletID);
+		    applet.sendRefreshRequest();
+		}
+	    }
+	];
+
+	if (!me.ugradeSystem) {
+	    // we dont want to restart the upgrade script
+	    tbar.push([
+		{
+                    text: gettext('Reload'),
+                    handler: function () { me.reloadApplet(); }
+		}]);
+	}
+
+	tbar.push([
+	    { 
+		text: gettext('Shell'),
+		handler: function() {
+		    PVE.Utils.openConsoleWindow('shell', undefined, me.nodename);
+		}
+	    }
+	]);
+
+
+	Ext.apply(me, {
+	    tbar: tbar,
+	    url: "/nodes/" + me.nodename + "/vncshell"
+	});
+
+	if (me.ugradeSystem) {
+	    me.params = { upgrade: 1 };	    
+	}
+
+	me.callParent();
+    }
+});
 Ext.define('PVE.button.ConsoleButton', {
     extend: 'Ext.button.Split',
     alias: 'widget.pveConsoleButton',
@@ -71,6 +774,12 @@ Ext.define('PVE.button.ConsoleButton', {
 	    }
 	};
 
+	var create_novnc_console = function() {
+	    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);
@@ -97,6 +806,12 @@ Ext.define('PVE.button.ConsoleButton', {
 	    handler: create_vnc_console
 	});
 
+	var novncMenu = Ext.create('Ext.menu.Item', {
+	    text: 'noVNC',
+	    iconCls: 'pve-itype-icon-novnc',
+	    handler: create_novnc_console
+	});
+
 	Ext.applyIf(me, { text: gettext('Console') });
 
 	Ext.apply(me, {
@@ -109,7 +824,7 @@ Ext.define('PVE.button.ConsoleButton', {
 		}
 	    },
 	    menu: new Ext.menu.Menu({
-		items: [ vncMenu, me.spiceMenu ]
+		items: [ novncMenu, vncMenu, me.spiceMenu ]
 	    })
 	});
 
-- 
1.7.10.4




More information about the pve-devel mailing list