diff --git a/main.py b/main.py index 5c3ea7e..c9879d2 100644 --- a/main.py +++ b/main.py @@ -3,12 +3,12 @@ __version__ = "0.2.2" __author__ = "M. Heuwes " -import os import json import logging +import os from tornado.web import authenticated -from tornado import web, ioloop +from tornado import ioloop, web from nojava_ipmi_kvm.config import config, DEFAULT_CONFIG_FILEPATH from nojava_ipmi_kvm import utils @@ -26,6 +26,13 @@ config.read_config(CONFIG_PATH) +def server_labels(): + labels = {} + for server_id in config.get_servers(): + labels[server_id] = config[server_id].full_hostname + return labels + + class MainHandler(BaseHandler): @authenticated @authorized @@ -35,8 +42,10 @@ def get(self): title="Remote KVM", user=self.get_current_user(), servers=config.get_servers(), + server_labels=server_labels(), base_uri=WEBAPP_BASE, websocket_uri="ws" + WEBAPP_BASE[4:], + version=__version__, ) @authenticated @@ -47,18 +56,17 @@ def post(self): title="Remote KVM", user=self.get_current_user(), servers=config.get_servers(), + server_labels=server_labels(), base_uri=WEBAPP_BASE, websocket_uri="ws" + WEBAPP_BASE[4:], server_name=json.dumps(self.get_body_argument("server_name")), password=json.dumps(self.get_body_argument("password")), resolution=json.dumps(self.get_body_argument("resolution")), + version=__version__, ) def make_app(): - """ - returns a tornado.web.Application - """ settings = { "template_path": "templates", "static_path": "static", @@ -71,7 +79,7 @@ def make_app(): } return web.Application( [web.url(r"/oauth/login", OAuth2LoginHandler), web.url(r"/", MainHandler), web.url(r"/kvm", KVMHandler)], - **settings + **settings, ) diff --git a/static/app.core.css b/static/app.core.css new file mode 100644 index 0000000..90718b0 --- /dev/null +++ b/static/app.core.css @@ -0,0 +1,195 @@ +*, +*::before, +*::after { + box-sizing: border-box; +} + +html, +body { + margin: 0; + min-height: 100%; +} + +body { + font-family: Helvetica, Arial, sans-serif; + color: #222222; + background: #ffffff; + line-height: 1.5; +} + +body.session-active { + overflow: hidden; +} + +body.session-active .app-shell { + display: none; +} + +.app-shell { + max-width: 1000px; + margin: 0 auto; + padding: 1.5rem 1.25rem; +} + +.app-header h1 { + margin: 0 0 0.25rem; + font-size: 1.25rem; +} + +.user-meta { + margin: 0 0 1rem; + color: #555555; +} + +.app-footer { + margin-top: 1.5rem; +} + +.status.is-busy { + display: flex; + align-items: center; + gap: 0.5rem; +} + +.status-spinner { + width: 1rem; + height: 1rem; + border: 2px solid #104070; + border-right-color: transparent; + border-radius: 50%; + animation: kvm-spin 0.8s linear infinite; +} + +@keyframes kvm-spin { + to { + transform: rotate(360deg); + } +} + +@media (prefers-reduced-motion: reduce) { + .status-spinner { + animation: none; + } +} + +.form-grid { + display: grid; + grid-template-columns: 7.5rem 1fr; + gap: 0.5rem 1rem; + align-items: center; +} + +.form-grid label { + font-size: 0.95rem; +} + +.form-grid input, +.form-grid select { + width: 100%; + max-width: 20em; + padding: 0.4rem 0.5rem; + border: 1px solid #cccccc; + font: inherit; +} + +.form-actions { + grid-column: 1 / -1; + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + margin-top: 0.25rem; +} + +.btn { + display: inline-flex; + align-items: center; + justify-content: center; + min-height: 2.25rem; + padding: 0 1rem; + border-radius: 4px; + border: 1px solid transparent; + font: inherit; + font-weight: 600; + cursor: pointer; +} + +.btn:disabled { + opacity: 0.55; + cursor: not-allowed; +} + +.btn-primary { + background: #1a5fb4; + color: #ffffff; +} + +.btn-secondary { + background: #ffffff; + color: #222222; + border-color: #cccccc; +} + +.status { + display: none; + margin-top: 0.75rem; + padding: 0.5rem 0.75rem; + border-radius: 4px; + font-size: 0.9rem; +} + +.status.is-visible { + display: block; +} + +.status.is-info, +.status.is-busy { + background: #e8f2ff; + color: #104070; +} + +.status.is-error { + background: #fde8ea; + color: #c01c28; +} + +.logs-panel { + margin-top: 1rem; +} + +.logs-panel.is-empty { + display: none; +} + +#logsul { + margin: 0; + padding: 0; + list-style: none; + font-family: ui-monospace, Menlo, Consolas, monospace; + font-size: 0.8rem; + color: #555555; +} + +#logsul li.error-log { + color: #c01c28; +} + +#container { + position: relative; + z-index: 100; +} + +.kvm-iframe { + position: fixed; + inset: 0; + width: 100%; + height: 100%; + border: none; + margin: 0; + padding: 0; + z-index: 999999; + background: #000000; +} + +.hidden { + display: none !important; +} diff --git a/static/app.core.js b/static/app.core.js new file mode 100644 index 0000000..7c63cf2 --- /dev/null +++ b/static/app.core.js @@ -0,0 +1,438 @@ +(function () { + "use strict"; + + var config = window.KVM_UI_CONFIG; + if (!config) { + return; + } + + var pageTitle = document.title; + var ws = null; + var hostName = ""; + var isConnecting = false; + + window.KvmUi = { _connectTimer: -1 }; + + var els = { + card: document.getElementById("kvm-card"), + form: document.getElementById("kvm-form"), + server: document.getElementById("kvm-server"), + password: document.getElementById("kvm-password"), + resolution: document.getElementById("kvm-resolution"), + connectBtn: document.getElementById("connect-btn"), + newTabBtn: document.getElementById("new-tab-btn"), + status: document.getElementById("status"), + logsPanel: document.getElementById("logs"), + logsUl: document.getElementById("logsul"), + container: document.getElementById("container"), + }; + + function isServerSelectable() { + return els.server && els.server.tagName === "SELECT"; + } + + function restorePageTitle() { + document.title = pageTitle; + } + + function formatLogTimestamp() { + return new Date().toLocaleString("de-DE"); + } + + function replaceContainerChildren(container, child) { + if (!container) { + return; + } + if (typeof container.replaceChildren === "function") { + container.replaceChildren(child); + return; + } + while (container.firstChild) { + container.removeChild(container.firstChild); + } + container.appendChild(child); + } + + function setStatus(message, kind) { + if (!els.status) { + return; + } + var visible = Boolean(message) || kind === "busy"; + els.status.className = "status" + (visible ? " is-visible" : "") + (kind ? " is-" + kind : ""); + if (kind === "busy") { + els.status.innerHTML = + '' + + ''; + els.status.querySelector(".status-text").textContent = message; + return; + } + els.status.textContent = message || ""; + } + + function setFormDisabled(disabled) { + isConnecting = disabled; + if (els.card) { + els.card.classList.toggle("is-connecting", disabled); + els.card.setAttribute("aria-busy", disabled ? "true" : "false"); + } + ["connectBtn", "newTabBtn", "server", "password", "resolution"].forEach(function (key) { + if (els[key]) { + els[key].disabled = disabled; + } + }); + updateLogsVisibility(); + } + + function unlockForm(restoreTitle) { + setFormDisabled(false); + if (restoreTitle !== false) { + restorePageTitle(); + } + } + + function updateLogsVisibility() { + if (!els.logsPanel) { + return; + } + var hasEntries = els.logsUl && els.logsUl.children.length > 0; + els.logsPanel.classList.toggle("is-empty", !hasEntries && !isConnecting); + } + + function appendLog(message, isError) { + if (!els.logsUl) { + return; + } + var item = document.createElement("li"); + if (isError) { + item.className = "error-log"; + } + item.textContent = formatLogTimestamp() + ": " + message; + els.logsUl.appendChild(item); + els.logsUl.scrollTop = els.logsUl.scrollHeight; + updateLogsVisibility(); + } + + function clearLogs() { + if (!els.logsUl) { + return; + } + els.logsUl.innerHTML = ""; + updateLogsVisibility(); + } + + function stopConnectingAnimation() { + if (window.KvmUi._connectTimer !== -1) { + clearInterval(window.KvmUi._connectTimer); + window.KvmUi._connectTimer = -1; + } + } + + function startConnectingAnimation() { + stopConnectingAnimation(); + var text = "Connecting to " + hostName + "…"; + setStatus(text, "busy"); + document.title = text; + } + + function getServerCount() { + if (typeof config.serverCount === "number") { + return config.serverCount; + } + if (els.server && els.server.tagName === "SELECT") { + return els.server.options.length; + } + return 1; + } + + function readServerName() { + if (config.autoConnect) { + return config.autoServer || ""; + } + return els.server ? els.server.value.trim() : ""; + } + + function readPassword() { + if (config.autoConnect) { + return config.autoPassword || ""; + } + return els.password ? els.password.value : ""; + } + + function readResolution() { + if (config.autoConnect) { + return config.autoResolution || ""; + } + return els.resolution ? els.resolution.value : ""; + } + + function focusField(field) { + if (field && typeof field.focus === "function" && field.type !== "hidden") { + field.focus(); + } + } + + function applyInitialFocus() { + if (config.autoConnect || !els.password) { + return; + } + if (getServerCount() > 1) { + focusField(els.server); + return; + } + focusField(els.password); + } + + function focusAfterFailure() { + if (config.autoConnect) { + return; + } + focusField(els.password); + } + + function focusAfterNotice(message) { + if (/hostname is not valid/i.test(message) && isServerSelectable()) { + focusField(els.server); + return; + } + if (/already connected/i.test(message) || /no unused port/i.test(message)) { + return; + } + focusAfterFailure(); + } + + function getXsrfToken() { + var input = document.querySelector("input[name='_xsrf']"); + return input ? input.value : ""; + } + + function deleteAllCookies() { + var paths = ["/", "/oauth"]; + document.cookie.split(";").forEach(function (cookie) { + var eqPos = cookie.indexOf("="); + var name = eqPos > -1 ? cookie.substr(0, eqPos).trim() : cookie.trim(); + if (!name) { + return; + } + paths.forEach(function (path) { + document.cookie = name + "=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=" + path; + }); + document.cookie = name + "=;expires=Thu, 01 Jan 1970 00:00:00 GMT"; + }); + } + + function enterSession(url) { + document.body.classList.add("session-active"); + var iframe = document.createElement("iframe"); + iframe.className = "kvm-iframe"; + iframe.src = url; + iframe.title = "KVM console"; + iframe.textContent = "Your browser does not support iframes."; + replaceContainerChildren(els.container, iframe); + } + + function handleConnectFailure(statusMessage, focusFn, options) { + options = options || {}; + stopConnectingAnimation(); + unlockForm(); + setStatus(statusMessage, "error"); + document.title = options.title || statusMessage; + if (options.logMessage !== false) { + appendLog(options.logMessage || statusMessage, true); + } + if (focusFn) { + focusFn(); + } + } + + function connectWebSocket() { + if (ws && (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING)) { + return ws; + } + ws = new WebSocket(config.websocketUri + "/kvm"); + ws.onopen = function () { + if (config.autoConnect) { + startKvm(); + } + }; + ws.onmessage = function (evt) { + var data; + try { + data = JSON.parse(evt.data); + } catch (e) { + return; + } + if (data.action === "notice") { + stopConnectingAnimation(); + unlockForm(); + setStatus(data.message, "info"); + appendLog(data.message, false); + focusAfterNotice(data.message); + if (data.refresh) { + window.setTimeout(function () { + window.location.href = "/"; + }, 1500); + } + return; + } + if (data.action === "connected") { + stopConnectingAnimation(); + setFormDisabled(false); + setStatus("", ""); + document.title = "Connected to " + hostName; + enterSession(data.url); + return; + } + if (data.action === "log" || data.action === "error") { + var isError = data.action === "error"; + appendLog(data.message, isError); + if (isError) { + handleConnectFailure("Failed to connect.", focusAfterFailure, { + title: "Failed to connect", + logMessage: false, + }); + } + } + }; + ws.onclose = function () { + if (!isConnecting) { + return; + } + handleConnectFailure("Connection to server lost.", focusAfterFailure); + }; + ws.onerror = function () { + if (!isConnecting) { + setStatus("WebSocket error.", "error"); + return; + } + handleConnectFailure("WebSocket error.", focusAfterFailure); + }; + return ws; + } + + function validateConnectForm() { + hostName = readServerName(); + if (!hostName) { + setStatus("Select a server.", "error"); + if (isServerSelectable()) { + focusField(els.server); + } + return false; + } + if (!readPassword()) { + setStatus("Enter the KVM password.", "error"); + focusField(els.password); + return false; + } + return true; + } + + function saveResolution() {} + + function startKvm() { + if (isConnecting) { + return; + } + if (!validateConnectForm()) { + return; + } + saveResolution(); + clearLogs(); + setFormDisabled(true); + startConnectingAnimation(); + var socket = connectWebSocket(); + var payload = JSON.stringify({ + action: "connect", + server: hostName, + password: readPassword(), + resolution: readResolution(), + }); + function send() { + socket.send(payload); + } + if (socket.readyState === WebSocket.OPEN) { + send(); + } else { + socket.addEventListener("open", send, { once: true }); + } + } + + function openInNewTab() { + if (!validateConnectForm()) { + return; + } + saveResolution(); + var form = document.createElement("form"); + form.method = "POST"; + form.action = "/"; + form.target = "_blank"; + [ + { name: "_xsrf", value: getXsrfToken() }, + { name: "server_name", value: hostName }, + { name: "password", value: readPassword() }, + { name: "resolution", value: readResolution() }, + ].forEach(function (field) { + var input = document.createElement("input"); + input.type = "hidden"; + input.name = field.name; + input.value = field.value; + form.appendChild(input); + }); + document.body.appendChild(form); + form.submit(); + form.remove(); + } + + window.KvmUi = Object.assign(window.KvmUi, { + config: config, + els: els, + getHostName: function () { + return hostName; + }, + setHostName: function (value) { + hostName = value; + }, + setStatus: setStatus, + stopConnectingAnimation: stopConnectingAnimation, + startConnectingAnimation: startConnectingAnimation, + saveResolution: saveResolution, + restoreResolution: function () {}, + applyInitialFocus: applyInitialFocus, + focusField: focusField, + isServerSelectable: isServerSelectable, + getServerCount: getServerCount, + readResolution: readResolution, + stopConnectingAnimation: stopConnectingAnimation, + startConnectingAnimation: startConnectingAnimation, + }); + + var logoutLink = document.getElementById("logout-link"); + if (logoutLink) { + logoutLink.addEventListener("click", deleteAllCookies); + } + if (els.card) { + els.card.setAttribute("aria-busy", "false"); + } + updateLogsVisibility(); + connectWebSocket(); + if (els.form) { + els.form.addEventListener("submit", function (evt) { + evt.preventDefault(); + startKvm(); + }); + } + if (els.newTabBtn) { + els.newTabBtn.addEventListener("click", openInNewTab); + } + if (isServerSelectable()) { + els.server.addEventListener("change", function () { + if (!config.autoConnect && els.password && !els.password.value) { + focusField(els.password); + } + }); + } + if (config.autoConnect) { + hostName = config.autoServer || ""; + } else { + applyInitialFocus(); + } +})(); diff --git a/static/app.ui.css b/static/app.ui.css new file mode 100644 index 0000000..cd0c99d --- /dev/null +++ b/static/app.ui.css @@ -0,0 +1,224 @@ +:root { + --font: "Helvetica Neue", Helvetica, Arial, system-ui, sans-serif; + --text: #222222; + --text-muted: #5c6773; + --bg: #eef1f6; + --surface: #ffffff; + --border: #d8dee9; + --border-focus: #1a5fb4; + --accent: #1a5fb4; + --accent-hover: #1c71d8; + --accent-soft: #e8f2ff; + --danger: #c01c28; + --danger-soft: #fde8ea; + --radius: 8px; + --shadow: 0 1px 2px rgba(0, 0, 0, 0.06), 0 8px 24px rgba(0, 0, 0, 0.07); + --label-width: 7.5rem; + --select-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='none' stroke='%235c6773' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round' d='M2.5 4.5 6 8l3.5-3.5'/%3E%3C/svg%3E"); +} + +body { + font-family: var(--font); + color: var(--text); + background: var(--bg); +} + +.app-page { + min-height: 100vh; + display: flex; + flex-direction: column; + align-items: center; + padding: 2rem 1.25rem 3rem; +} + +.app-header { + width: min(100%, 32rem); + margin-bottom: 1rem; +} + +.app-header h1 { + margin: 0; + font-size: 1.5rem; + font-weight: 600; + letter-spacing: -0.02em; +} + +.app-header .user-meta { + margin: 0.35rem 0 0; + color: var(--text-muted); + font-size: 0.9rem; +} + +.card { + width: min(100%, 32rem); + background: var(--surface); + border: 1px solid var(--border); + border-radius: var(--radius); + box-shadow: var(--shadow); + padding: 1.5rem; +} + +.card.is-connecting .form-grid { + opacity: 0.72; + transition: opacity 0.15s ease; +} + +.form-grid { + grid-template-columns: var(--label-width) 1fr; + gap: 0.85rem 1rem; +} + +.server-name { + margin: 0; + padding: 0.55rem 0; + font-size: 0.95rem; + font-weight: 600; + color: var(--text); +} + +.form-grid input, +.form-grid select { + max-width: none; + padding: 0.55rem 0.65rem; + border: 1px solid var(--border); + border-radius: 6px; + background-color: #ffffff; + transition: border-color 0.15s ease, box-shadow 0.15s ease; +} + +.form-grid select { + appearance: none; + -webkit-appearance: none; + padding-right: 2.25rem; + background-image: var(--select-chevron); + background-repeat: no-repeat; + background-position: right 0.7rem center; + background-size: 0.75rem; +} + +.form-grid input:focus, +.form-grid select:focus { + outline: none; + border-color: var(--border-focus); + box-shadow: 0 0 0 3px rgba(26, 95, 180, 0.15); +} + +.form-grid input:disabled, +.form-grid select:disabled { + background-color: #f6f7f9; + color: var(--text-muted); +} + +.btn { + min-height: 2.5rem; + padding: 0 1.1rem; + border-radius: 6px; + transition: background 0.15s ease, border-color 0.15s ease, opacity 0.15s ease; +} + +.btn-primary:hover:not(:disabled) { + background: var(--accent-hover); +} + +.btn-secondary:hover:not(:disabled) { + background: #f6f7f9; + border-color: #bcc4d0; +} + +.status { + display: none; + align-items: center; + gap: 0.65rem; +} + +.status.is-visible { + display: flex; +} + +.status-spinner { + flex-shrink: 0; + width: 1rem; + height: 1rem; + border: 2px solid rgba(26, 95, 180, 0.2); + border-top-color: var(--accent); + border-radius: 50%; + animation: spin 0.75s linear infinite; +} + +@keyframes spin { + to { + transform: rotate(360deg); + } +} + +.logs-panel { + padding-top: 1.25rem; + border-top: 1px solid var(--border); +} + +.logs-panel h2 { + margin: 0 0 0.5rem; + font-size: 0.75rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.06em; + color: var(--text-muted); +} + +#logsul { + max-height: 11rem; + overflow-y: auto; + font-size: 0.78rem; + color: var(--text-muted); +} + +#logsul li { + padding: 0.2rem 0; + border-bottom: 1px solid #f0f2f5; +} + +#logsul li:last-child { + border-bottom: none; +} + +.app-footer { + width: min(100%, 32rem); + margin-top: 1rem; + font-size: 0.9rem; +} + +.app-footer a { + color: var(--accent); + text-decoration: none; +} + +.app-footer a:hover { + text-decoration: underline; +} + +@media (prefers-reduced-motion: reduce) { + .status-spinner { + animation: none; + border-color: rgba(26, 95, 180, 0.35); + border-top-color: var(--accent); + } + + .card.is-connecting .form-grid { + transition: none; + } +} + +@media (max-width: 520px) { + .form-grid { + grid-template-columns: 1fr; + gap: 0.35rem; + } + + .form-actions { + flex-direction: column; + } + + .btn { + width: 100%; + } +} diff --git a/static/app.ui.js b/static/app.ui.js new file mode 100644 index 0000000..c9dffb8 --- /dev/null +++ b/static/app.ui.js @@ -0,0 +1,89 @@ +(function () { + "use strict"; + + var k = window.KvmUi; + if (!k) { + return; + } + + var RESOLUTION_STORAGE_KEY = "nojava-kvm-resolution"; + var LEGACY_RESOLUTION_STORAGE_KEY = "kvm-resolution"; + + function repeatChar(ch, count) { + var out = ""; + for (var i = 0; i < count; i += 1) { + out += ch; + } + return out; + } + + function getResolutionOptions() { + if (!k.els.resolution) { + return []; + } + return Array.prototype.map.call(k.els.resolution.options, function (option) { + return option.value; + }); + } + + function migrateResolutionStorage() { + try { + var legacy = localStorage.getItem(LEGACY_RESOLUTION_STORAGE_KEY); + if (legacy && !localStorage.getItem(RESOLUTION_STORAGE_KEY)) { + localStorage.setItem(RESOLUTION_STORAGE_KEY, legacy); + localStorage.removeItem(LEGACY_RESOLUTION_STORAGE_KEY); + } + } catch (e) { + /* ignore */ + } + } + + k.saveResolution = function () { + var value = k.readResolution(); + if (!value) { + return; + } + try { + localStorage.setItem(RESOLUTION_STORAGE_KEY, value); + } catch (e) { + /* ignore */ + } + }; + + k.restoreResolution = function () { + if (!k.els.resolution) { + return; + } + try { + var saved = localStorage.getItem(RESOLUTION_STORAGE_KEY); + if (saved && getResolutionOptions().indexOf(saved) !== -1) { + k.els.resolution.value = saved; + } + } catch (e) { + /* ignore */ + } + }; + + k.startConnectingAnimation = function () { + var dots = 0; + k.stopConnectingAnimation(); + var hostName = k.getHostName(); + var text = "Connecting to " + hostName + "…"; + k.setStatus(text, "busy"); + document.title = text; + k._connectTimer = window.setInterval(function () { + dots = (dots + 1) % 4; + var animated = "Connecting to " + hostName + repeatChar(".", dots); + var textNode = k.els.status && k.els.status.querySelector(".status-text"); + if (textNode) { + textNode.textContent = animated; + } else { + k.setStatus(animated, "busy"); + } + document.title = animated; + }, 500); + }; + + migrateResolutionStorage(); + k.restoreResolution(); +})(); diff --git a/templates/_resolution_select.tpl b/templates/_resolution_select.tpl new file mode 100644 index 0000000..cf04d7c --- /dev/null +++ b/templates/_resolution_select.tpl @@ -0,0 +1,6 @@ + diff --git a/templates/_server_field.tpl b/templates/_server_field.tpl new file mode 100644 index 0000000..94b67a2 --- /dev/null +++ b/templates/_server_field.tpl @@ -0,0 +1,19 @@ +{# Multi-host +{% end %} +{% else %} + + +{% end %} diff --git a/templates/_session_log.tpl b/templates/_session_log.tpl new file mode 100644 index 0000000..64d31d8 --- /dev/null +++ b/templates/_session_log.tpl @@ -0,0 +1,4 @@ +
+

Session log

+ +
diff --git a/templates/base.tpl b/templates/base.tpl new file mode 100644 index 0000000..f2dd5d6 --- /dev/null +++ b/templates/base.tpl @@ -0,0 +1,42 @@ +{% import os %} +{% if version %}{% set ui_cache = version %}{% else %}{% set ui_cache = os.environ.get("UI_CACHE_VERSION", "1") %}{% end %} + + + + + + {{ title }} + + + + +
+
+

{{ title }}

+ {% if user['name'] != 'anonymous' %} +

Hello {{ user['name'] }} ({{ user['email'] }})

+ {% end %} +
+ + {% block main %}{% end %} + + {% block footer %} + {% if 'OAUTH_HOST' in os.environ %} + + {% end %} + {% end %} +
+ +
+ + {% block after_container %}{% end %} + + + + + + diff --git a/templates/index.tpl b/templates/index.tpl index 98f3b19..9a0543a 100644 --- a/templates/index.tpl +++ b/templates/index.tpl @@ -1,180 +1,37 @@ - - - {{ title }} - - - -
-

Hello {{ user['name'] }} ({{ user['email'] }})

- -
- {% module xsrf_form_html() %} - - - {% for server in servers %} - - {% end %} - - -
- - - -
- - - -
- - - - {% import os %} - {% if 'OAUTH_HOST' in os.environ %} - - {% end %} -
-
-
- -
- - - - +{% extends "base.tpl" %} + +{% block main %} +
+
+ {% module xsrf_form_html() %} + +
+ {% include "_server_field.tpl" %} + + + + + + {% include "_resolution_select.tpl" %} + +
+ + +
+
+
+ +
+ + {% set logs_empty = True %} + {% include "_session_log.tpl" %} +
+{% end %} + +{% block kvm_config %} +{ + websocketUri: "{{ websocket_uri }}", + autoConnect: false, + serverCount: {{ len(servers) }} +} +{% end %} diff --git a/templates/index_instant.tpl b/templates/index_instant.tpl index d37bc7f..2d6d608 100644 --- a/templates/index_instant.tpl +++ b/templates/index_instant.tpl @@ -1,10 +1,23 @@ -{% extends 'index.tpl' %} -{% block ws_onopen %} +{% extends "base.tpl" %} -document.getElementById('kvm-server').value = {% raw server_name %}; -document.getElementById('kvm-password').value = {% raw password %}; -document.getElementById('kvm-resolution').value = {% raw resolution %}; - -start_kvm(); +{% block main %} +
+
+ + Connecting… +
+ {% set logs_empty = False %} + {% include "_session_log.tpl" %} +
+{% end %} +{% block kvm_config %} +{ + websocketUri: "{{ websocket_uri }}", + autoConnect: true, + autoServer: {{ server_name }}, + autoPassword: {{ password }}, + autoResolution: {{ resolution }}, + serverCount: 1 +} {% end %}