diff --git a/accounts/templatetags/tags_fingerprint.py b/accounts/templatetags/tags_fingerprint.py index d15c473..5688039 100644 --- a/accounts/templatetags/tags_fingerprint.py +++ b/accounts/templatetags/tags_fingerprint.py @@ -9,4 +9,4 @@ register = template.Library() def ssh_to_fingerprint(line): key = base64.b64decode(line.strip().split()[1].encode('ascii')) fp_plain = hashlib.md5(key).hexdigest() - return ':'.join(a+b for a, b in zip(fp_plain[::2], fp_plain[1::2])) + return ':'.join(a + b for a, b in zip(fp_plain[::2], fp_plain[1::2])) diff --git a/conf/requirements.txt b/conf/requirements.txt index 63f4677..e01113b 100644 --- a/conf/requirements.txt +++ b/conf/requirements.txt @@ -1,5 +1,6 @@ -Django==1.8.2 -websockify==0.7.0 +Django==1.8.11 +websockify==0.8.0 gunicorn==19.3.0 -libvirt-python==1.2.16 -http://github.com/retspen/retspen.github.io/raw/master/libxml2-python-2.9.1.tar.gz +libvirt-python==1.3.2 +#http://github.com/retspen/retspen.github.io/raw/master/libxml2-python-2.9.1.tar.gz +http://git.gnome.org/browse/libxml2/snapshot/libxml2-2.9.1.tar.gz#egg=libxml2-python&subdirectory=python diff --git a/console/templates/console-base.html b/console/templates/console-base.html new file mode 100644 index 0000000..d0e0e7a --- /dev/null +++ b/console/templates/console-base.html @@ -0,0 +1,127 @@ +{% load i18n %} +<html> +<head> + <meta charset="utf-8"> + <link rel="shortcut icon" href="{{ STATIC_URL }}img/favicon.ico"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <link rel="stylesheet" href="{{ STATIC_URL }}css/bootstrap.min.css"> + <link href="{{ STATIC_URL }}css/webvirtcloud.css" rel="stylesheet"> + + <style> + body { + margin: 0; + padding: 0; + background-color: #313131; + } + + .navbar { + margin-bottom: 2px; + } + + #main_container { + padding: 0; + width: 100%; + max-width: none; + height: 100%; + background-color:#494949; + border-bottom-right-radius: 800px 600px; + + } + + #main_container canvas { + padding-left: 0; + padding-right: 0; + margin-left: auto; + margin-right: auto; + display: block; + margin: auto; + } + + #status { + z-index: 10000; + width: 80%; + position: absolute; + top: 5px; + left: 10%; + text-align: center; + } + </style> + +{% block head %}{% endblock %} + +</head> + +<body> +<nav class="navbar navbar-inverse navbar-static-top" role="navigation"> + <div class="container"> + <div class="navbar-header"> + <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse"> + <span class="icon-bar"></span> + <span class="icon-bar"></span> + <span class="icon-bar"></span> + </button> + <a class="navbar-brand">{{ instance.name }}</a> + </div> + <div class="collapse navbar-collapse"> + <ul class="nav navbar-nav"> + <li class="dropdown"> + <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">Send key(s) <span class="caret"></span></a> + <ul class="dropdown-menu" role="menu"> + <li onclick='sendCtrlAltDel();'><a href='#'>Ctrl+Alt+Del</a></li> + <li class="divider"></li> + <li onclick='sendCtrlAltFN(0);'><a href='#'>Ctrl+Alt+F1</a></li> + <li onclick='sendCtrlAltFN(1);'><a href='#'>Ctrl+Alt+F2</a></li> + <li onclick='sendCtrlAltFN(2);'><a href='#'>Ctrl+Alt+F3</a></li> + <li onclick='sendCtrlAltFN(3);'><a href='#'>Ctrl+Alt+F4</a></li> + <li onclick='sendCtrlAltFN(4);'><a href='#'>Ctrl+Alt+F5</a></li> + <li onclick='sendCtrlAltFN(5);'><a href='#'>Ctrl+Alt+F6</a></li> + <li onclick='sendCtrlAltFN(6);'><a href='#'>Ctrl+Alt+F7</a></li> + <li onclick='sendCtrlAltFN(7);'><a href='#'>Ctrl+Alt+F8</a></li> + <li onclick='sendCtrlAltFN(8);'><a href='#'>Ctrl+Alt+F9</a></li> + <li onclick='sendCtrlAltFN(9);'><a href='#'>Ctrl+Alt+F10</a></li> + <li onclick='sendCtrlAltFN(10);'><a href='#'>Ctrl+Alt+F11</a></li> + <li onclick='sendCtrlAltFN(11);'><a href='#'>Ctrl+Alt+F12</a></li> + </ul> + </li> + <li onclick='fullscreen()'><a href='#'>{% trans "Fullscreen" %}</a></li> +{% block navbarmenu %}{% endblock %} + </ul> + </div> + </div> +</nav> +<div id='main_container' class="container"> +{% block content %}{% endblock %} +</div> +<script src="{{ STATIC_URL }}js/jquery.js"></script> +<script src="{{ STATIC_URL }}js/bootstrap.min.js"></script> + +<script> + function log_message(msg,type) { + var exist=$('#status').is('div'); + status_div=$('<div id="status" class="alert alert-'+type+' role="alert"><button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>'+msg+'</div>'); + if (exist) { + $('#status').remove(); + $('body').prepend(status_div); + } + else { + status_div.hide(); + $('body').prepend(status_div); + status_div.fadeIn(200); + } + if (type!='danger') { + status_div.delay(3000).fadeOut(200); + } + } + + function log_error(msg) { + log_message(msg,'danger'); + } + + function log_info(msg) { + log_message(msg,'info'); + } +</script> + +{% block foot %}{% endblock %} +</body> +</html> diff --git a/console/templates/console-spice.html b/console/templates/console-spice.html index 4704ef3..1a1cbf8 100644 --- a/console/templates/console-spice.html +++ b/console/templates/console-spice.html @@ -1,333 +1,194 @@ +{% extends "console-base.html" %} {% load i18n %} -<html> -<head> - <link rel="shortcut icon" href="{{ STATIC_URL }}img/favicon.ico"> - <script src="{{ STATIC_URL }}js/spice-html5/spicearraybuffer.js"></script> - <script src="{{ STATIC_URL }}js/spice-html5/enums.js"></script> - <script src="{{ STATIC_URL }}js/spice-html5/atKeynames.js"></script> - <script src="{{ STATIC_URL }}js/spice-html5/utils.js"></script> - <script src="{{ STATIC_URL }}js/spice-html5/png.js"></script> - <script src="{{ STATIC_URL }}js/spice-html5/lz.js"></script> - <script src="{{ STATIC_URL }}js/spice-html5/quic.js"></script> - <script src="{{ STATIC_URL }}js/spice-html5/bitmap.js"></script> - <script src="{{ STATIC_URL }}js/spice-html5/spicedataview.js"></script> - <script src="{{ STATIC_URL }}js/spice-html5/spicetype.js"></script> - <script src="{{ STATIC_URL }}js/spice-html5/spicemsg.js"></script> - <script src="{{ STATIC_URL }}js/spice-html5/wire.js"></script> - <script src="{{ STATIC_URL }}js/spice-html5/spiceconn.js"></script> - <script src="{{ STATIC_URL }}js/spice-html5/display.js"></script> - <script src="{{ STATIC_URL }}js/spice-html5/main.js"></script> - <script src="{{ STATIC_URL }}js/spice-html5/inputs.js"></script> - <script src="{{ STATIC_URL }}js/spice-html5/webm.js"></script> - <script src="{{ STATIC_URL }}js/spice-html5/playback.js"></script> - <script src="{{ STATIC_URL }}js/spice-html5/simulatecursor.js"></script> - <script src="{{ STATIC_URL }}js/spice-html5/cursor.js"></script> - <script src="{{ STATIC_URL }}js/spice-html5/thirdparty/jsbn.js"></script> - <script src="{{ STATIC_URL }}js/spice-html5/thirdparty/rsa.js"></script> - <script src="{{ STATIC_URL }}js/spice-html5/thirdparty/prng4.js"></script> - <script src="{{ STATIC_URL }}js/spice-html5/thirdparty/rng.js"></script> - <script src="{{ STATIC_URL }}js/spice-html5/thirdparty/sha1.js"></script> - <script src="{{ STATIC_URL }}js/spice-html5/ticket.js"></script> - <script src="{{ STATIC_URL }}js/spice-html5/resize.js"></script> - <script src="{{ STATIC_URL }}js/spice-html5/filexfer.js"></script> +{% load staticfiles %} +{% block head %} + <script src="{% static "js/spice-html5/spicearraybuffer.js" %}"></script> + <script src="{% static "js/spice-html5/enums.js" %}"></script> + <script src="{% static "js/spice-html5/atKeynames.js" %}"></script> + <script src="{% static "js/spice-html5/utils.js" %}"></script> + <script src="{% static "js/spice-html5/png.js" %}"></script> + <script src="{% static "js/spice-html5/lz.js" %}"></script> + <script src="{% static "js/spice-html5/quic.js" %}"></script> + <script src="{% static "js/spice-html5/bitmap.js" %}"></script> + <script src="{% static "js/spice-html5/spicedataview.js" %}"></script> + <script src="{% static "js/spice-html5/spicetype.js" %}"></script> + <script src="{% static "js/spice-html5/spicemsg.js" %}"></script> + <script src="{% static "js/spice-html5/wire.js" %}"></script> + <script src="{% static "js/spice-html5/spiceconn.js" %}"></script> + <script src="{% static "js/spice-html5/display.js" %}"></script> + <script src="{% static "js/spice-html5/main.js" %}"></script> + <script src="{% static "js/spice-html5/inputs.js" %}"></script> + <script src="{% static "js/spice-html5/webm.js" %}"></script> + <script src="{% static "js/spice-html5/playback.js" %}"></script> + <script src="{% static "js/spice-html5/simulatecursor.js" %}"></script> + <script src="{% static "js/spice-html5/cursor.js" %}"></script> + <script src="{% static "js/spice-html5/thirdparty/jsbn.js" %}"></script> + <script src="{% static "js/spice-html5/thirdparty/rsa.js" %}"></script> + <script src="{% static "js/spice-html5/thirdparty/prng4.js" %}"></script> + <script src="{% static "js/spice-html5/thirdparty/rng.js" %}"></script> + <script src="{% static "js/spice-html5/thirdparty/sha1.js" %}"></script> + <script src="{% static "js/spice-html5/ticket.js" %}"></script> + <script src="{% static "js/spice-html5/resize.js" %}"></script> + <script src="{% static "js/spice-html5/filexfer.js" %}"></script> - <style> - body { - margin: 0; - padding: 0; - font-family: Helvetica; - background-color:#494949; +{% endblock %} + +{% block content %} +<div id='spice_container'></div> +{% endblock %} + +{% block foot %} +<script> + var sc; + + function spice_set_cookie(name, value, days) { + var date, expires; + date = new Date(); + date.setTime(date.getTime() + (days*24*60*60*1000)); + expires = "; expires=" + date.toGMTString(); + document.cookie = name + "=" + value + expires + "; path=/"; + }; + + function spice_query_var(name, defvalue) { + var match = RegExp('[?&]' + name + '=([^&]*)') + .exec(window.location.search); + return match ? + decodeURIComponent(match[1].replace(/\+/g, ' ')) + : defvalue; + } + + function spice_error(e) + { + console.log(e); + disconnect(); + if (e.message != undefined) { + log_error(e.message); } - - #status { - text-align: center; - width: 100%; - margin: 0; - font-size: 1em; - height: 1.6em; - padding-top: 0.3em; - background-color: #222; + else { + log_error('Unknown error'); } - - #spice-area { - border-bottom-right-radius: 800px 600px; - background-color: #313131; - height: 100%; + } + + function spice_success(msg) { + log_info(msg); + } + + function connect(uri,password) + { + // If a token variable is passed in, set the parameter in a cookie. + // This is used by nova-spiceproxy. + token = spice_query_var('token', null); + if (token) { + spice_set_cookie('token', token, 1) } - #spice-screen canvas { - padding-left: 0; - padding-right: 0; - margin-left: auto; - margin-right: auto; - display: block; + if (sc) { + sc.stop(); } - #spice-menu { - list-style-type: none; - margin: 0px; - padding: 0px; - border: 0px none; - position: absolute; - top: 0px; - z-index: 100; + try + { + sc = new SpiceMainConn({uri: uri, password: password, screen_id: "spice_container", + onsuccess: spice_success, onerror: spice_error, onagent: agent_connected }); } - - #spice-menu li { - width: auto; - float: left; - margin: 2px; - padding: 0px; - border: 0px none; - position: relative; - height: 18px; - cursor: pointer; - padding: 0.2em; - background: #eee; - border-radius: 3px; + catch (e) + { + console.log(e); + log_error(e.toString()); + disconnect(); } - - #spice-menu .spice-submenu { - display: none; - opacity: 0; - list-style-type: none; - margin: 0; - padding: 0; - border: 0; - overflow: hidden; - position: absolute; + + } + + function disconnect() + { + console.log(">> disconnect"); + if (sc) { + sc.stop(); } - - #spice-menu .spice-submenu li { - float: none; - margin: 0; - padding: 0; - border: 0; - border-top: 1px solid white; - border-right: 1px solid white; - font: normal 14px sans-serif; - z-index: 100; - white-space: nowrap; - min-width: 100px; - padding: 0.2em; - border-radius: 0; + if (window.File && window.FileReader && window.FileList && window.Blob) + { + console.log(" -> Disable drag/drop transfer"); + var spice_xfer_area = document.getElementById('spice-xfer-area'); + try { + document.getElementById('spice-area').removeChild(spice_xfer_area); + document.getElementById('spice-area').removeEventListener('dragover', handle_file_dragover, false); + document.getElementById('spice-area').removeEventListener('drop', handle_file_drop, false); + } + catch(e) { + console.log(' -> Error disabling drag/drop transfer'); + } } - - #spice-menu li:hover > .spice-submenu { - opacity: 1; - display: block; - margin: 2px 0 0 -2px; + console.log("<< disconnect"); + } + + function agent_connected(sc) { + window.addEventListener('resize', handle_resize); + window.spice_connection = this; + + resize_helper(this); + + if (window.File && window.FileReader && window.FileList && window.Blob) + { + var spice_xfer_area = document.createElement("div"); + spice_xfer_area.setAttribute('id', 'spice-xfer-area'); + document.getElementById('spice-area').addEventListener('dragover', handle_file_dragover, false); + document.getElementById('spice-area').addEventListener('drop', handle_file_drop, false); + log_info('Drag and drop transfer enabled.'); } - - #spice-menu li:hover > .spice-submenu li { - height: auto; + else + { + console.log("File API is not supported"); + log_info('Drag and drop transfer not supported.'); } - </style> -</head> -<body> - <div id="header"> - <ul id='spice-menu'> - <li>Send key(s) - <ul class='spice-submenu'> - <li onclick='sendCtrlAltDel();'>Ctrl+Alt+Del</li> - <li onclick='sendCtrlAltFN(0);'>Ctrl+Alt+F1</li> - <li onclick='sendCtrlAltFN(1);'>Ctrl+Alt+F2</li> - <li onclick='sendCtrlAltFN(2);'>Ctrl+Alt+F3</li> - <li onclick='sendCtrlAltFN(3);'>Ctrl+Alt+F4</li> - <li onclick='sendCtrlAltFN(4);'>Ctrl+Alt+F5</li> - <li onclick='sendCtrlAltFN(5);'>Ctrl+Alt+F6</li> - <li onclick='sendCtrlAltFN(6);'>Ctrl+Alt+F7</li> - <li onclick='sendCtrlAltFN(7);'>Ctrl+Alt+F8</li> - <li onclick='sendCtrlAltFN(8);'>Ctrl+Alt+F9</li> - <li onclick='sendCtrlAltFN(9);'>Ctrl+Alt+F10</li> - <li onclick='sendCtrlAltFN(10);'>Ctrl+Alt+F11</li> - <li onclick='sendCtrlAltFN(11);'>Ctrl+Alt+F12</li> - </ul> - </li> - <li onclick='fullscreen()'>Fullscreen</li> - </ul> - <div id="status"></div> - </div> - <div id="spice-area"> - <div id="spice-screen" class="spice-screen"></div> - </div> + } - <script> - var sc; + function sendCtrlAltFN(f) { + if (sc && sc.inputs && sc.inputs.state === "ready"){ + var keys_code=[KEY_F1,KEY_F2,KEY_F3,KEY_F4,KEY_F5,KEY_F6,KEY_F7,KEY_F8,KEY_F9,KEY_F10,KEY_F11,KEY_F12]; - function spice_set_cookie(name, value, days) { - var date, expires; - date = new Date(); - date.setTime(date.getTime() + (days*24*60*60*1000)); - expires = "; expires=" + date.toGMTString(); - document.cookie = name + "=" + value + expires + "; path=/"; - }; - - function spice_query_var(name, defvalue) { - var match = RegExp('[?&]' + name + '=([^&]*)') - .exec(window.location.search); - return match ? - decodeURIComponent(match[1].replace(/\+/g, ' ')) - : defvalue; + if (keys_code[f]==undefined) { + return; } + var key = new SpiceMsgcKeyDown(); + var msg = new SpiceMiniData(); - var status_div=false; - function log_message(msg,color,bgcolor) { - if (!status_div) { - status_div=document.getElementById('status'); - } - status_div.innerHTML=msg; - if (color) { - status_div.style.color=color; - } - if (bgcolor) { - status_div.style.backgroundColor=bgcolor; - } - } + update_modifier(true, KEY_LCtrl, sc); + update_modifier(true, KEY_Alt, sc); - function log_error(msg) { - log_message(msg,'#000','#f44'); - } + key.code = keys_code[f]; + msg.build_msg(SPICE_MSGC_INPUTS_KEY_DOWN, key); + sc.inputs.send_msg(msg); + msg.build_msg(SPICE_MSGC_INPUTS_KEY_UP, key); + sc.inputs.send_msg(msg); - function log_info(msg) { - log_message(msg,'#000','#eee'); - } + if(Ctrl_state == false) update_modifier(false, KEY_LCtrl, sc); + if(Alt_state == false) update_modifier(false, KEY_Alt, sc); + } + } - function spice_error(e) - { - console.log(e); - disconnect(); - if (e.message != undefined) { - log_error(e.message); - } - else { - log_error('Unknown error'); - } - } + function fullscreen() { + var screen=document.getElementById('spice_container'); + if(screen.requestFullscreen) { + screen.requestFullscreen(); + } else if(screen.mozRequestFullScreen) { + screen.mozRequestFullScreen(); + } else if(screen.webkitRequestFullscreen) { + screen.webkitRequestFullscreen(); + } else if(screen.msRequestFullscreen) { + screen.msRequestFullscreen(); + } + } - function spice_success(msg) { - log_info(msg); - } + var uri; + if (window.location.protocol === "https:") { + uri = 'wss://{{ ws_host }}:{{ ws_port }}'; + } else { + uri = 'ws://{{ ws_host }}:{{ ws_port }}'; + } - function connect(uri,password) - { - // If a token variable is passed in, set the parameter in a cookie. - // This is used by nova-spiceproxy. - token = spice_query_var('token', null); - if (token) { - spice_set_cookie('token', token, 1) - } - - if (sc) { - sc.stop(); - } - - try - { - sc = new SpiceMainConn({uri: uri, password: password, screen_id: "spice-screen", - onsuccess: spice_success, onerror: spice_error, onagent: agent_connected }); - } - catch (e) - { - console.log(e); - log_error(e.toString()); - disconnect(); - } - - } - - function disconnect() - { - console.log(">> disconnect"); - if (sc) { - sc.stop(); - } - if (window.File && window.FileReader && window.FileList && window.Blob) - { - console.log(" -> Disable drag/drop transfer"); - var spice_xfer_area = document.getElementById('spice-xfer-area'); - try { - document.getElementById('spice-area').removeChild(spice_xfer_area); - document.getElementById('spice-area').removeEventListener('dragover', handle_file_dragover, false); - document.getElementById('spice-area').removeEventListener('drop', handle_file_drop, false); - } - catch(e) { - console.log(' -> Error disabling drag/drop transfer'); - } - } - console.log("<< disconnect"); - } - - function agent_connected(sc) { - console.log('Connected'); - window.addEventListener('resize', handle_resize); - window.spice_connection = this; - - resize_helper(this); - - if (window.File && window.FileReader && window.FileList && window.Blob) - { - var spice_xfer_area = document.createElement("div"); - spice_xfer_area.setAttribute('id', 'spice-xfer-area'); - document.getElementById('spice-area').addEventListener('dragover', handle_file_dragover, false); - document.getElementById('spice-area').addEventListener('drop', handle_file_drop, false); - log_info('Drag and drop transfer enabled.'); - } - else - { - console.log("File API is not supported"); - log_info('Drag and drop transfer not supported.'); - } - log_info('Connected'); - } - - function sendCtrlAltFN(f) { - if (sc && sc.inputs && sc.inputs.state === "ready"){ - var keys_code=[KEY_F1,KEY_F2,KEY_F3,KEY_F4,KEY_F5,KEY_F6,KEY_F7,KEY_F8,KEY_F9,KEY_F10,KEY_F11,KEY_F12]; - - if (keys_code[f]==undefined) { - return; - } - var key = new SpiceMsgcKeyDown(); - var msg = new SpiceMiniData(); - - update_modifier(true, KEY_LCtrl, sc); - update_modifier(true, KEY_Alt, sc); - - key.code = keys_code[f]; - msg.build_msg(SPICE_MSGC_INPUTS_KEY_DOWN, key); - sc.inputs.send_msg(msg); - msg.build_msg(SPICE_MSGC_INPUTS_KEY_UP, key); - sc.inputs.send_msg(msg); - - if(Ctrl_state == false) update_modifier(false, KEY_LCtrl, sc); - if(Alt_state == false) update_modifier(false, KEY_Alt, sc); - } - } - - function fullscreen() { - var screen=document.getElementById('spice-screen'); - if(screen.requestFullscreen) { - screen.requestFullscreen(); - } else if(screen.mozRequestFullScreen) { - screen.mozRequestFullScreen(); - } else if(screen.webkitRequestFullscreen) { - screen.webkitRequestFullscreen(); - } else if(screen.msRequestFullscreen) { - screen.msRequestFullscreen(); - } - } - - var uri; - if (window.location.protocol === "https:") { - uri = 'wss://{{ ws_host }}:{{ ws_port }}'; - } else { - uri = 'ws://{{ ws_host }}:{{ ws_port }}'; - } - - var password = '{{ console_passwd }}'; - log_info('Connecting ...'); - connect(uri,password); - </script> -</body> -</html> + var password = '{{ console_passwd }}'; + log_info('Connecting ...'); + connect(uri,password); +</script> +{% endblock %} diff --git a/console/templates/console-vnc.html b/console/templates/console-vnc.html index 59d0afa..b0a3334 100644 --- a/console/templates/console-vnc.html +++ b/console/templates/console-vnc.html @@ -1,48 +1,37 @@ +{% extends "console-base.html" %} {% load i18n %} -<html> -<head> - <link rel="shortcut icon" href="{{ STATIC_URL }}img/favicon.ico"> - <link rel="stylesheet" href="{{ STATIC_URL }}js/novnc/base.css" title="plain"> - <!-- - <script type='text/javascript' - src='http://getfirebug.com/releases/lite/1.2/firebug-lite-compressed.js'></script> - --> - <script src="{{ STATIC_URL }}js/novnc/util.js"></script> -</head> -<body style="margin: 0px;"> -<div id="noVNC_screen"> - <div id="noVNC_status_bar" class="noVNC_status_bar" style="margin-top: 0px;"> - <table border=0 width="100%"> - <tr> - <td> - <div id="noVNC_status">{% trans "Loading..." %}</div> - </td> - <td width="32%" style="text-align:right;"> - <div id="noVNC_buttons"> - <!-- dirty fix for keyboard on iOS devices --> - <input type="button" id="showKeyboard" value="Keyboard" title="Show Keyboard"/> - <!-- Note that Google Chrome on Android doesn't respect any of these, - html attributes which attempt to disable text suggestions on the - on-screen keyboard. Let's hope Chrome implements the ime-mode - style for example --> - <!-- TODO: check if this is needed on iOS --> - <textarea id="keyboardinput" autocapitalize="off" - autocorrect="off" autocomplete="off" spellcheck="false" - mozactionhint="Enter" onsubmit="return false;" - style="ime-mode: disabled;"> - </textarea> +{% load staticfiles %} +{% block head %} + <script src="{% static "js/novnc/util.js" %}"></script> - <input type=button value="Ctrl+Alt+Del" id="sendCtrlAltDelButton"> - <input type=button value="Fullscreen" id="askFullscreen"> - </div> - </td> - </tr> - </table> - </div> +{% endblock %} + +{% block navbarmenu %} + <!-- dirty fix for keyboard on iOS devices --> + <li id="showKeyboard"><a href='#'>{% trans "Show Keyboad" %}</a></li> +{% endblock %} + +{% block content %} + <div id='noVNC_area'> <canvas id="noVNC_canvas" width="640px" height="20px"> {% trans "Canvas not supported." %} </canvas> -</div> + </div> + + <!-- Note that Google Chrome on Android doesn't respect any of these, + html attributes which attempt to disable text suggestions on the + on-screen keyboard. Let's hope Chrome implements the ime-mode + style for example --> + <!-- TODO: check if this is needed on iOS --> + <textarea id="keyboardinput" autocapitalize="off" + autocorrect="off" autocomplete="off" spellcheck="false" + mozactionhint="Enter" onsubmit="return false;" + style="display: none;"> + </textarea> + +{% endblock %} + +{% block foot %} <script> /*jslint white: false */ /*global window, $, Util, RFB, */ @@ -60,18 +49,32 @@ var rfb; function passwordRequired(rfb) { - var msg; - msg = '<form onsubmit="return setPassword();"'; - msg += 'role="form"'; - msg += ' style="margin-bottom: 0px">'; - msg += 'Password Required: '; - msg += '<input type=password size=10 id="password_input" class="noVNC_status">'; - msg += '<\/form>'; - $D('noVNC_status_bar').setAttribute("class", "noVNC_status_warn"); - $D('noVNC_status').innerHTML = msg; + var modal; + modal = '<div class="modal fade">'; + modal += ' <div class="modal-dialog">'; + modal += ' <div class="modal-content">'; + modal += ' <div class="modal-header">'; + modal += ' <h4 class="modal-title">{% trans "Password required" %}</h4>'; + modal += ' </div>'; + modal += ' <div class="modal-body">'; + modal += ' <form id="password_form" onsubmit="return setPassword();">'; + modal += ' <div class="form-group">'; + modal += ' <label for="password_input">Password</label>'; + modal += ' <input type="password" class="form-control" id="password_input" placeholder="Password"/>'; + modal += ' </div>'; + modal += ' </form>'; + modal += ' </div>'; + modal += ' <div class="modal-footer">'; + modal += ' <button type="button" class="btn btn-primary" data-dismiss="modal" onclick="return setPassword();">{% trans "OK" %}</button>'; + modal += ' </div>'; + modal += ' </div>'; + modal += ' </div>'; + modal += '</div>'; + $('body').append(modal); + $('div.modal').modal(); } function setPassword() { - rfb.sendPassword($D('password_input').value); + rfb.sendPassword($('#password_input').val()); return false; } function sendCtrlAltDel() { @@ -79,6 +82,19 @@ return false; } + function sendCtrlAltFN(f) { + var keys_code=[0xFFBE,0xFFBF,0xFFC0,0xFFC1,0xFFC2,0xFFC3,0xFFC4,0xFFC5,0xFFC6,0xFFC7,0xFFC8,0xFFC9]; + if (keys_code[f]==undefined) { + return; + } + rfb.sendKey(0xFFE3, 'down'); + rfb.sendKey(0xFFE9, 'down'); + rfb.sendKey(keys_code[f], 'down'); + rfb.sendKey(keys_code[f], 'up'); + rfb.sendKey(0xFFE9, 'up'); + rfb.sendKey(0xFFE3, 'up'); + }; + // dirty fix for keyboard on iOS devices function showKeyboard() { var kbi, skb, l; @@ -103,48 +119,36 @@ function updateState(rfb, state, oldstate, msg) { var s, sb, cad, af, level; - s = $D('noVNC_status'); - sb = $D('noVNC_status_bar'); cad = $D('sendCtrlAltDelButton'); af = $D('askFullscreen'); switch (state) { case 'failed': - level = "error"; + level = "danger"; break; case 'fatal': - level = "error"; + level = "danger"; break; case 'normal': - level = "normal"; + level = "info"; break; case 'disconnected': - level = "normal"; + level = "info"; break; case 'loaded': - level = "normal"; + level = "info"; break; default: - level = "warn"; + level = "warning"; break; } - if (state === "normal") { - cad.disabled = false; - af.disabled = false; - } - else { - cad.disabled = true; - af.disabled = true; - } - if (typeof(msg) !== 'undefined') { - sb.setAttribute("class", "noVNC_status_" + level); - s.innerHTML = msg; + log_message(msg,level); } } function fullscreen() { - var screen=document.getElementById('noVNC_canvas'); + var screen=document.getElementById('main_container'); if(screen.requestFullscreen) { screen.requestFullscreen(); } else if(screen.mozRequestFullScreen) { @@ -159,11 +163,6 @@ window.onscriptsload = function () { var host, port, password, path, token; - $D('sendCtrlAltDelButton').style.display = "inline"; - $D('sendCtrlAltDelButton').onclick = sendCtrlAltDel; - $D('askFullscreen').style.display = "inline"; - $D('askFullscreen').onclick = fullscreen; - // dirty fix for keyboard on iOS devices if (isTouchDevice) { $D('showKeyboard').onclick = showKeyboard; @@ -188,7 +187,7 @@ return; } - rfb = new RFB({'target': $D('noVNC_canvas'), + rfb = new RFB({'target': document.getElementById('noVNC_canvas'), 'encrypt': WebUtil.getQueryVar('encrypt', (window.location.protocol === "https:")), 'repeaterID': WebUtil.getQueryVar('repeaterID', ''), @@ -201,5 +200,4 @@ rfb.connect(host, port, password, path); }; </script> -</body> -</html> +{% endblock %} diff --git a/create/forms.py b/create/forms.py index 5b9fab5..fccf944 100644 --- a/create/forms.py +++ b/create/forms.py @@ -40,6 +40,7 @@ class NewVMForm(forms.Form): storage = forms.CharField(max_length=20, required=False) template = forms.CharField(required=False) images = forms.CharField(required=False) + cache_mode = forms.CharField(error_messages={'required': _('Please select HDD cache mode')}) hdd_size = forms.IntegerField(required=False) meta_prealloc = forms.BooleanField(required=False) virtio = forms.BooleanField(required=False) diff --git a/create/templates/create_instance.html b/create/templates/create_instance.html index 37189b7..9df7326 100644 --- a/create/templates/create_instance.html +++ b/create/templates/create_instance.html @@ -77,6 +77,23 @@ </select> </div> </div> + <div class="form-group meta-prealloc"> + <label class="col-sm-3 control-label">{% trans "Metadata" %}</label> + <div class="col-sm-6"> + <input type="checkbox" name="meta_prealloc" title="Metadata preallocation" value="true"> + </div> + <label class="col-lg-1 control-label">{% trans "Image" %}</label> + </div> + <div class="form-group"> + <label class="col-sm-3 control-label">{% trans "HDD cache mode" %}</label> + <div class="col-sm-6"> + <select id="cache_mode" name="cache_mode" class="form-control"> + {% for mode, name in cache_modes %} + <option value="{{ mode }}">{% trans name %}</option> + {% endfor %} + </select> + </div> + </div> <div class="form-group"> <label class="col-sm-3 control-label">{% trans "Network" %}</label> <div class="col-sm-6"> @@ -159,6 +176,16 @@ </div> <label class="col-lg-1 control-label">{% trans "Image" %}</label> </div> + <div class="form-group"> + <label class="col-sm-3 control-label">{% trans "HDD cache mode" %}</label> + <div class="col-sm-6"> + <select id="cache_mode" name="cache_mode" class="form-control"> + {% for mode, name in cache_modes %} + <option value="{{ mode }}">{% trans name %}</option> + {% endfor %} + </select> + </div> + </div> <div class="form-group"> <label class="col-sm-3 control-label">{% trans "Network" %}</label> <div class="col-sm-6"> @@ -258,6 +285,7 @@ <label class="col-sm-3 control-label">{% trans "Storage" %}</label> <div class="col-sm-6"> + <input type="hidden" name="cache_mode" value="default"> <select name="storage" class="form-control"> {% if storages %} {% for storage in storages %} @@ -269,13 +297,6 @@ </select> </div> </div> - <div class="form-group meta-prealloc"> - <label class="col-sm-3 control-label">{% trans "Metadata" %}</label> - <div class="col-sm-6"> - <input type="checkbox" name="meta_prealloc" title="Metadata preallocation" value="true"> - </div> - <label class="col-lg-1 control-label">{% trans "Image" %}</label> - </div> <div class="form-group"> <label class="col-sm-3 control-label">{% trans "Network" %}</label> <div class="col-sm-6"> @@ -415,4 +436,4 @@ var editor = ace.edit("editor"); editor.getSession().setMode("ace/mode/xml"); </script> -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/create/views.py b/create/views.py index 3f97606..6c223da 100644 --- a/create/views.py +++ b/create/views.py @@ -41,6 +41,7 @@ def create_instance(request, compute_id): networks = sorted(conn.get_networks()) instances = conn.get_instances() get_images = sorted(conn.get_storages_images()) + cache_modes = sorted(conn.get_cache_modes().items()) mac_auto = util.randomMAC() except libvirtError as lib_err: error_messages.append(lib_err) @@ -122,11 +123,15 @@ def create_instance(request, compute_id): volumes[path] = conn.get_volume_type(path) except libvirtError as lib_err: error_messages.append(lib_err.message) + if data['cache_mode'] not in conn.get_cache_modes(): + error_msg = _("Invalid cache mode") + error_messages.append(error_msg) if not error_messages: uuid = util.randomUUID() try: conn.create_instance(data['name'], data['memory'], data['vcpu'], data['host_model'], - uuid, volumes, data['networks'], data['virtio'], data['mac']) + uuid, volumes, data['cache_mode'], data['networks'], data['virtio'], + data['mac']) create_instance = Instance(compute_id=compute_id, name=data['name'], uuid=uuid) create_instance.save() return HttpResponseRedirect(reverse('instance', args=[compute_id, data['name']])) diff --git a/instances/views.py b/instances/views.py index 83c80e8..5fcce4a 100644 --- a/instances/views.py +++ b/instances/views.py @@ -103,7 +103,7 @@ def instances(request): conn.start(name) addlogmsg(request.user.username, instance.name, msg) return HttpResponseRedirect(request.get_full_path()) - + if 'getvvfile' in request.POST: msg = _("Send console.vv file") addlogmsg(request.user.username, instance.name, msg) diff --git a/vrtManager/create.py b/vrtManager/create.py index fc32177..473b9d2 100644 --- a/vrtManager/create.py +++ b/vrtManager/create.py @@ -7,9 +7,17 @@ from webvirtcloud.settings import QEMU_CONSOLE_DEFAULT_TYPE def get_rbd_storage_data(stg): xml = stg.XMLDesc(0) ceph_user = util.get_xml_path(xml, "/pool/source/auth/@username") - ceph_host = util.get_xml_path(xml, "/pool/source/host/@name") - secrt_uuid = util.get_xml_path(xml, "/pool/source/auth/secret/@uuid") - return ceph_user, secrt_uuid, ceph_host + + def get_ceph_hosts(ctx): + hosts = [] + for host in ctx.xpathEval("/pool/source/host"): + name = host.prop("name") + if name: + hosts.append({'name': name, 'port': host.prop("port")}) + return hosts + ceph_hosts = util.get_xml_path(xml, func=get_ceph_hosts) + secret_uuid = util.get_xml_path(xml, "/pool/source/auth/secret/@uuid") + return ceph_user, secret_uuid, ceph_hosts class wvmCreate(wvmConnect): @@ -40,6 +48,17 @@ class wvmCreate(wvmConnect): """Get guest capabilities""" return util.get_xml_path(self.get_cap_xml(), "/capabilities/host/cpu/arch") + def get_cache_modes(self): + """Get cache available modes""" + return { + 'default': 'Default', + 'none': 'Disabled', + 'writethrough': 'Write through', + 'writeback': 'Write back', + 'directsync': 'Direct sync', # since libvirt 0.9.5 + 'unsafe': 'Unsafe', # since libvirt 0.9.7 + } + def create_volume(self, storage, name, size, format='qcow2', metadata=False): size = int(size) * 1073741824 stg = self.get_storage(storage) @@ -121,7 +140,7 @@ class wvmCreate(wvmConnect): vol = self.get_volume_by_path(path) vol.delete() - def create_instance(self, name, memory, vcpu, host_model, uuid, images, networks, virtio, mac=None): + def create_instance(self, name, memory, vcpu, host_model, uuid, images, cache_mode, networks, virtio, mac=None): """ Create VM function """ @@ -162,19 +181,27 @@ class wvmCreate(wvmConnect): stg_type = util.get_xml_path(stg.XMLDesc(0), "/pool/@type") if stg_type == 'rbd': - ceph_user, secrt_uuid, ceph_host = get_rbd_storage_data(stg) + ceph_user, secret_uuid, ceph_hosts = get_rbd_storage_data(stg) xml += """<disk type='network' device='disk'> - <driver name='qemu' type='%s'/> + <driver name='qemu' type='%s' cache='%s'/> <auth username='%s'> <secret type='ceph' uuid='%s'/> </auth> - <source protocol='rbd' name='%s'> - <host name='%s' port='6789'/> - </source>""" % (img_type, ceph_user, secrt_uuid, image, ceph_host) + <source protocol='rbd' name='%s'>""" % (img_type, cache_mode, ceph_user, secret_uuid, image) + if isinstance(ceph_hosts, list): + for host in ceph_hosts: + if host.get('port'): + xml += """ + <host name='%s' port='%s'/>""" % (host.get('name'), host.get('port')) + else: + xml += """ + <host name='%s'/>""" % host.get('name') + xml += """ + </source>""" else: xml += """<disk type='file' device='disk'> - <driver name='qemu' type='%s'/> - <source file='%s'/>""" % (img_type, image) + <driver name='qemu' type='%s' cache='%s'/> + <source file='%s'/>""" % (img_type, cache_mode, image) if virtio: xml += """<target dev='vd%s' bus='virtio'/>""" % (disk_letters.pop(0),) diff --git a/vrtManager/instance.py b/vrtManager/instance.py index 08e0c39..e792e13 100644 --- a/vrtManager/instance.py +++ b/vrtManager/instance.py @@ -82,7 +82,7 @@ class wvmInstances(wvmConnect): dom = self.get_instance(name) xml = dom.XMLDesc(VIR_DOMAIN_XML_SECURE) self.wvm.defineXML(xml) - + def graphics_type(self, name): inst = self.get_instance(name) console_type = util.get_xml_path(inst.XMLDesc(0), "/domain/devices/graphics/@type")