pikvm/pikvm#1462: relative root location

This commit is contained in:
Maxim Devaev 2025-02-02 07:09:21 +02:00
parent b51ea5e374
commit 73238e18e9
36 changed files with 170 additions and 96 deletions

View File

@ -23,7 +23,7 @@ doctype html
============================================================================== ==============================================================================
- -
var prefix = "./" var root_prefix = "./"
title = "" title = ""
main_js = "" main_js = ""
@ -36,7 +36,7 @@ block vars
block _vars_dynamic block _vars_dynamic
- -
share_dir = `${prefix}share` share_dir = `${root_prefix}share`
css_dir = `${share_dir}/css` css_dir = `${share_dir}/css`
js_dir = `${share_dir}/js` js_dir = `${share_dir}/js`
svg_dir = `${share_dir}/svg` svg_dir = `${share_dir}/svg`
@ -61,6 +61,8 @@ html(lang="en")
if main_js if main_js
script(type="module") script(type="module")
| import {setRootPrefix} from "#{js_dir}/vars.js";
| setRootPrefix("#{root_prefix}");
| import {main} from "#{js_dir}/#{main_js}.js"; | import {main} from "#{js_dir}/#{main_js}.js";
| main(); | main();

View File

@ -40,7 +40,9 @@
<link rel="stylesheet" href="./share/css/modal.css"> <link rel="stylesheet" href="./share/css/modal.css">
<link rel="stylesheet" href="./share/css/index/index.css"> <link rel="stylesheet" href="./share/css/index/index.css">
<link rel="stylesheet" href="./share/css/user.css"> <link rel="stylesheet" href="./share/css/user.css">
<script type="module">import {main} from "./share/js/index/main.js"; <script type="module">import {setRootPrefix} from "./share/js/vars.js";
setRootPrefix("./");
import {main} from "./share/js/index/main.js";
main(); main();
</script> </script>
</head> </head>
@ -66,7 +68,7 @@
<table> <table>
<td class="server"> <td class="server">
<td>Server:</td> <td>Server:</td>
<td><a id="kvmd-meta-server-host" target="_blank" href="/api/info"></a></td> <td><a id="kvmd-meta-server-host" target="_blank" href="./api/info"></a></td>
</td> </td>
</table> </table>
<hr> <hr>

View File

@ -25,7 +25,7 @@ block start
table table
td(class="server") td(class="server")
td Server: td Server:
td #[a#kvmd-meta-server-host(target="_blank" href="/api/info")] td #[a#kvmd-meta-server-host(target="_blank" href=`${root_prefix}api/info`)]
hr hr
#apps-box #apps-box

View File

@ -37,13 +37,15 @@
<link rel="stylesheet" href="../share/css/main.css"> <link rel="stylesheet" href="../share/css/main.css">
<link rel="stylesheet" href="../share/css/start.css"> <link rel="stylesheet" href="../share/css/start.css">
<link rel="stylesheet" href="../share/css/user.css"> <link rel="stylesheet" href="../share/css/user.css">
<script type="module">import {main} from "../share/js/ipmi/main.js"; <script type="module">import {setRootPrefix} from "../share/js/vars.js";
setRootPrefix("../");
import {main} from "../share/js/ipmi/main.js";
main(); main();
</script> </script>
</head> </head>
<body> <body>
<div class="start-box"> <div class="start-box">
<div class="start"><a style="display:inline-block; margin-top:4px; color:#5c90bc; text-decoration:none" href="/">&nbsp;&nbsp;&larr;&nbsp;&nbsp; [ PiKVM Index ]</a> <div class="start"><a style="display:inline-block; margin-top:4px; color:#5c90bc; text-decoration:none" href="../">&nbsp;&nbsp;&larr;&nbsp;&nbsp; [ PiKVM Index ]</a>
<hr> <hr>
<p class="text">This PiKVM device has running <b>kvmd-ipmi</b> daemon and provides IPMI 2.0 interface for some basic <p class="text">This PiKVM device has running <b>kvmd-ipmi</b> daemon and provides IPMI 2.0 interface for some basic
BMC operations like on/off/reset the server. BMC operations like on/off/reset the server.

View File

@ -3,7 +3,7 @@ extends ../start.pug
append vars append vars
- -
prefix = "../" root_prefix = "../"
title = "PiKVM IPMI Info" title = "PiKVM IPMI Info"
main_js = "ipmi/main" main_js = "ipmi/main"
index_link = true index_link = true

View File

@ -52,13 +52,15 @@
<link rel="stylesheet" href="../share/css/kvm/keyboard.css"> <link rel="stylesheet" href="../share/css/kvm/keyboard.css">
<link rel="stylesheet" href="../share/css/kvm/about.css"> <link rel="stylesheet" href="../share/css/kvm/about.css">
<link rel="stylesheet" href="../share/css/user.css"> <link rel="stylesheet" href="../share/css/user.css">
<script type="module">import {main} from "../share/js/kvm/main.js"; <script type="module">import {setRootPrefix} from "../share/js/vars.js";
setRootPrefix("../");
import {main} from "../share/js/kvm/main.js";
main(); main();
</script> </script>
</head> </head>
<body class="body-no-select"> <body class="body-no-select">
<ul id="navbar"> <ul id="navbar">
<li class="left"><a id="logo" href="/">&larr;&nbsp;&nbsp;<img class="svg-gray" src="../share/svg/logo.svg" alt="&amp;pi;-kvm"></a></li> <li class="left"><a id="logo" href="../">&larr;&nbsp;&nbsp;<img class="svg-gray" src="../share/svg/logo.svg" alt="&amp;pi;-kvm"></a></li>
<div class="hidden" id="hw-health-dropdown"> <div class="hidden" id="hw-health-dropdown">
<li class="left"><a class="menu-button" href="#"><img class="hidden" id="hw-health-undervoltage-led" src="../share/svg/led-undervoltage.svg"><img class="hidden" id="hw-health-overheating-led" src="../share/svg/led-overheating.svg"></a> <li class="left"><a class="menu-button" href="#"><img class="hidden" id="hw-health-undervoltage-led" src="../share/svg/led-undervoltage.svg"><img class="hidden" id="hw-health-overheating-led" src="../share/svg/led-overheating.svg"></a>
<div class="menu"> <div class="menu">
@ -131,7 +133,7 @@
<td style="line-height:1.5"><b>Fan failed</b></td> <td style="line-height:1.5"><b>Fan failed</b></td>
</tr> </tr>
<tr> <tr>
<td><sup style="line-height:1">A fan error occured, please <a href="/api/log?seek=3600&amp;follow=1" target="_blank">check the log</a></sup></td> <td><sup style="line-height:1">A fan error occured, please <a href="../api/log?seek=3600&amp;follow=1" target="_blank">check the log</a></sup></td>
</tr> </tr>
</table> </table>
</div> </div>
@ -2283,7 +2285,9 @@
<label for="about-tab-meta-button">Meta</label> <label for="about-tab-meta-button">Meta</label>
<div class="tab"> <div class="tab">
<div class="code" id="about-meta"> <div class="code" id="about-meta">
<div><span class="code-comment">// You can get this JSON using handle <a target="_blank" href="/api/info?fields=meta">/api/info?fields=meta</a><br> <div><span class="code-comment">
// You can get this JSON using handle
<a target="_blank" href="../api/info?fields=meta">/api/info?fields=meta</a><br>
// In the standard configuration this data<br> // In the standard configuration this data<br>
// is specified in the file /etc/kvmd/meta.yaml</span><br> // is specified in the file /etc/kvmd/meta.yaml</span><br>
<pre id="kvmd-meta-json">No data</pre> <pre id="kvmd-meta-json">No data</pre>

View File

@ -3,7 +3,7 @@ extends ../base.pug
append vars append vars
- -
prefix = "../" root_prefix = "../"
title = "PiKVM Session" title = "PiKVM Session"
main_js = "kvm/main" main_js = "kvm/main"
body_class = "body-no-select" body_class = "body-no-select"

View File

@ -31,4 +31,4 @@
#fan-health-message-fail #fan-health-message-fail
hr hr
+menu_message("led-fan", "Fan failed", "led-gray") +menu_message("led-fan", "Fan failed", "led-gray")
| A fan error occured, please #[a(href="/api/log?seek=3600&follow=1" target="_blank") check the log] | A fan error occured, please #[a(href=`${root_prefix}api/log?seek=3600&follow=1` target="_blank") check the log]

View File

@ -83,7 +83,7 @@ mixin menu_spoiler(title)
ul#navbar ul#navbar
li.left li.left
a(id="logo" href="/") &larr;&nbsp;&nbsp; a(id="logo" href=root_prefix) &larr;&nbsp;&nbsp;
img.svg-gray(src=`${svg_dir}/logo.svg` alt="&pi;-kvm") img.svg-gray(src=`${svg_dir}/logo.svg` alt="&pi;-kvm")
include navbar-health.pug include navbar-health.pug

View File

@ -34,7 +34,8 @@ mixin about_tab(name, title, checked=false)
+about_tab("meta", "Meta", true) +about_tab("meta", "Meta", true)
div div
span.code-comment span.code-comment
| // You can get this JSON using handle #[a(target="_blank" href="/api/info?fields=meta") /api/info?fields=meta]#[br] | // You can get this JSON using handle
| #[a(target="_blank" href=`${root_prefix}api/info?fields=meta`) /api/info?fields=meta]#[br]
| // In the standard configuration this data#[br] | // In the standard configuration this data#[br]
| // is specified in the file /etc/kvmd/meta.yaml | // is specified in the file /etc/kvmd/meta.yaml
br br

View File

@ -39,7 +39,9 @@
<link rel="stylesheet" href="../share/css/modal.css"> <link rel="stylesheet" href="../share/css/modal.css">
<link rel="stylesheet" href="../share/css/login/login.css"> <link rel="stylesheet" href="../share/css/login/login.css">
<link rel="stylesheet" href="../share/css/user.css"> <link rel="stylesheet" href="../share/css/user.css">
<script type="module">import {main} from "../share/js/login/main.js"; <script type="module">import {setRootPrefix} from "../share/js/vars.js";
setRootPrefix("../");
import {main} from "../share/js/login/main.js";
main(); main();
</script> </script>
</head> </head>

View File

@ -3,7 +3,7 @@ extends ../base.pug
append vars append vars
- -
prefix = "../" root_prefix = "../"
title = "PiKVM Login" title = "PiKVM Login"
main_js = "login/main" main_js = "login/main"
css_list.push("window", "modal", "login/login") css_list.push("window", "modal", "login/login")

View File

@ -23,6 +23,9 @@
"use strict"; "use strict";
import {ROOT_PREFIX} from "./vars.js";
export var browser = new function() { export var browser = new function() {
// https://stackoverflow.com/questions/9847580/how-to-detect-safari-chrome-ie-firefox-and-opera-browser/9851769 // https://stackoverflow.com/questions/9847580/how-to-detect-safari-chrome-ie-firefox-and-opera-browser/9851769
// https://github.com/fingerprintjs/fingerprintjs/discussions/641 // https://github.com/fingerprintjs/fingerprintjs/discussions/641
@ -133,12 +136,12 @@ export function checkBrowser(desktop_css, mobile_css) {
let force_desktop = (new URL(window.location.href)).searchParams.get("force_desktop"); let force_desktop = (new URL(window.location.href)).searchParams.get("force_desktop");
let force_mobile = (new URL(window.location.href)).searchParams.get("force_mobile"); let force_mobile = (new URL(window.location.href)).searchParams.get("force_mobile");
if ((force_desktop || !browser.is_mobile) && !force_mobile) { if ((force_desktop || !browser.is_mobile) && !force_mobile) {
__addCssLink("/share/css/x-desktop.css"); __addCssLink("x-desktop.css");
if (desktop_css) { if (desktop_css) {
__addCssLink(desktop_css); __addCssLink(desktop_css);
} }
} else { } else {
__addCssLink("/share/css/x-mobile.css"); __addCssLink("x-mobile.css");
if (mobile_css) { if (mobile_css) {
__addCssLink(mobile_css); __addCssLink(mobile_css);
} }
@ -148,6 +151,7 @@ export function checkBrowser(desktop_css, mobile_css) {
} }
function __addCssLink(path) { function __addCssLink(path) {
path = `${ROOT_PREFIX}share/css/${path}`;
console.log("===== Adding CSS:", path); console.log("===== Adding CSS:", path);
let el_head = document.getElementsByTagName("head")[0]; let el_head = document.getElementsByTagName("head")[0];
let el_link = document.createElement("link"); let el_link = document.createElement("link");

View File

@ -23,6 +23,7 @@
"use strict"; "use strict";
import {ROOT_PREFIX} from "../vars.js";
import {tools, $} from "../tools.js"; import {tools, $} from "../tools.js";
import {checkBrowser} from "../bb.js"; import {checkBrowser} from "../bb.js";
import {wm, initWindowManager} from "../wm.js"; import {wm, initWindowManager} from "../wm.js";
@ -51,7 +52,7 @@ function __setAppText() {
} }
function __loadKvmdInfo() { function __loadKvmdInfo() {
tools.httpGet("/api/info", {"fields": "auth,meta,extras"}, function(http) { tools.httpGet("api/info", {"fields": "auth,meta,extras"}, function(http) {
if (http.status === 200) { if (http.status === 200) {
let info = JSON.parse(http.responseText).result; let info = JSON.parse(http.responseText).result;
@ -100,7 +101,7 @@ function __loadKvmdInfo() {
document.title = "PiKVM Index"; document.title = "PiKVM Index";
} }
} else if (http.status === 401 || http.status === 403) { } else if (http.status === 401 || http.status === 403) {
document.location.href = "/login"; tools.currentOpen("login");
} else { } else {
setTimeout(__loadKvmdInfo, 1000); setTimeout(__loadKvmdInfo, 1000);
} }
@ -108,11 +109,14 @@ function __loadKvmdInfo() {
} }
function __makeApp(id, path, icon, name) { function __makeApp(id, path, icon, name) {
// Tailing slash in href is added to avoid Nginx 301 redirect
// when the location doesn't have tailing slash: "foo -> foo/".
// Reverse proxy over PiKVM can be misconfigured to handle this.
return `<li> return `<li>
<div ${id ? "id=\"" + id + "\"" : ""} class="app"> <div ${id ? "id=\"" + id + "\"" : ""} class="app">
<a href="${path}"> <a href="${ROOT_PREFIX}${path}/">
<div> <div>
<img class="svg-gray" src="${icon}"> <img class="svg-gray" src="${ROOT_PREFIX}${icon}">
${tools.escape(name)} ${tools.escape(name)}
</div> </div>
</a> </a>
@ -121,9 +125,9 @@ function __makeApp(id, path, icon, name) {
} }
function __logout() { function __logout() {
tools.httpPost("/api/auth/logout", null, function(http) { tools.httpPost("api/auth/logout", null, function(http) {
if (http.status === 200 || http.status === 401 || http.status === 403) { if (http.status === 200 || http.status === 401 || http.status === 403) {
document.location.href = "/login"; tools.currentOpen("login");
} else { } else {
wm.error("Logout error", http.responseText); wm.error("Logout error", http.responseText);
} }

View File

@ -31,7 +31,7 @@ export function main() {
} }
function __loadKvmdInfo() { function __loadKvmdInfo() {
tools.httpGet("/api/info", null, function(http) { tools.httpGet("api/info", null, function(http) {
if (http.status === 200) { if (http.status === 200) {
let ipmi_port = JSON.parse(http.responseText).result.extras.ipmi.port; let ipmi_port = JSON.parse(http.responseText).result.extras.ipmi.port;
let make_item = (comment, ipmi, api) => ` let make_item = (comment, ipmi, api) => `
@ -52,7 +52,7 @@ function __loadKvmdInfo() {
${make_item("Check the power status", "power status", "")} ${make_item("Check the power status", "power status", "")}
`; `;
} else if (http.status === 401 || http.status === 403) { } else if (http.status === 401 || http.status === 403) {
document.location.href = "/login"; tools.currentOpen("login");
} else { } else {
setTimeout(__loadKvmdInfo, 1000); setTimeout(__loadKvmdInfo, 1000);
} }

View File

@ -94,7 +94,7 @@ export function Atx(__recorder) {
var __clickAtx = function(button) { var __clickAtx = function(button) {
let click_button = function() { let click_button = function() {
tools.httpPost("/api/atx/click", {"button": button}, function(http) { tools.httpPost("api/atx/click", {"button": button}, function(http) {
if (http.status === 409) { if (http.status === 409) {
wm.error("Performing another ATX operation for other client.<br>Please try again later."); wm.error("Performing another ATX operation for other client.<br>Please try again later.");
} else if (http.status !== 200) { } else if (http.status !== 200) {

View File

@ -23,6 +23,7 @@
"use strict"; "use strict";
import {ROOT_PREFIX} from "../vars.js";
import {tools, $, $$} from "../tools.js"; import {tools, $, $$} from "../tools.js";
import {wm} from "../wm.js"; import {wm} from "../wm.js";
@ -138,7 +139,7 @@ export function Gpio(__recorder) {
return ` return `
<img <img
class="__gpio-led __gpio-led-${item.channel} inline-lamp-big led-gray" class="__gpio-led __gpio-led-${item.channel} inline-lamp-big led-gray"
src="/share/svg/led-circle.svg" src="${ROOT_PREFIX}share/svg/led-circle.svg"
data-color="${item.color}" data-color="${item.color}"
/> />
`; `;
@ -202,7 +203,7 @@ export function Gpio(__recorder) {
confirm = el.getAttribute("data-confirm-off"); confirm = el.getAttribute("data-confirm-off");
} }
let act = () => { let act = () => {
__sendPost("/api/gpio/switch", {"channel": ch, "state": to}); __sendPost("api/gpio/switch", {"channel": ch, "state": to});
__recorder.recordGpioSwitchEvent(ch, to); __recorder.recordGpioSwitchEvent(ch, to);
}; };
if (confirm) { if (confirm) {
@ -220,7 +221,7 @@ export function Gpio(__recorder) {
let ch = el.getAttribute("data-channel"); let ch = el.getAttribute("data-channel");
let confirm = el.getAttribute("data-confirm"); let confirm = el.getAttribute("data-confirm");
let act = () => { let act = () => {
__sendPost("/api/gpio/pulse", {"channel": ch}); __sendPost("api/gpio/pulse", {"channel": ch});
__recorder.recordGpioPulseEvent(ch); __recorder.recordGpioPulseEvent(ch);
}; };
if (confirm) { if (confirm) {

View File

@ -275,7 +275,7 @@ export function Hid(__getGeometry, __recorder) {
var __clickOutputsRadio = function(hid) { var __clickOutputsRadio = function(hid) {
let output = tools.radio.getValue(`hid-outputs-${hid}-radio`); let output = tools.radio.getValue(`hid-outputs-${hid}-radio`);
tools.httpPost("/api/hid/set_params", {[`${hid}_output`]: output}, function(http) { tools.httpPost("api/hid/set_params", {[`${hid}_output`]: output}, function(http) {
if (http.status !== 200) { if (http.status !== 200) {
wm.error("Can't configure HID", http.responseText); wm.error("Can't configure HID", http.responseText);
} }
@ -284,7 +284,7 @@ export function Hid(__getGeometry, __recorder) {
var __clickJigglerSwitch = function() { var __clickJigglerSwitch = function() {
let enabled = $("hid-jiggler-switch").checked; let enabled = $("hid-jiggler-switch").checked;
tools.httpPost("/api/hid/set_params", {"jiggler": enabled}, function(http) { tools.httpPost("api/hid/set_params", {"jiggler": enabled}, function(http) {
if (http.status !== 200) { if (http.status !== 200) {
wm.error(`Can't ${enabled ? "enabled" : "disable"} mouse jiggler`, http.responseText); wm.error(`Can't ${enabled ? "enabled" : "disable"} mouse jiggler`, http.responseText);
} }
@ -293,7 +293,7 @@ export function Hid(__getGeometry, __recorder) {
var __clickConnectSwitch = function() { var __clickConnectSwitch = function() {
let connected = $("hid-connect-switch").checked; let connected = $("hid-connect-switch").checked;
tools.httpPost("/api/hid/set_connected", {"connected": connected}, function(http) { tools.httpPost("api/hid/set_connected", {"connected": connected}, function(http) {
if (http.status !== 200) { if (http.status !== 200) {
wm.error(`Can't ${connected ? "connect" : "disconnect"} HID`, http.responseText); wm.error(`Can't ${connected ? "connect" : "disconnect"} HID`, http.responseText);
} }
@ -303,7 +303,7 @@ export function Hid(__getGeometry, __recorder) {
var __clickResetButton = function() { var __clickResetButton = function() {
wm.confirm("Are you sure you want to reset HID (keyboard & mouse)?").then(function(ok) { wm.confirm("Are you sure you want to reset HID (keyboard & mouse)?").then(function(ok) {
if (ok) { if (ok) {
tools.httpPost("/api/hid/reset", null, function(http) { tools.httpPost("api/hid/reset", null, function(http) {
if (http.status !== 200) { if (http.status !== 200) {
wm.error("HID reset error", http.responseText); wm.error("HID reset error", http.responseText);
} }

View File

@ -31,7 +31,7 @@ import {Session} from "./session.js";
export function main() { export function main() {
if (checkBrowser(null, "/share/css/kvm/x-mobile.css")) { if (checkBrowser(null, "kvm/x-mobile.css")) {
tools.storage.bindSimpleSwitch($("page-close-ask-switch"), "page.close.ask", true, function(value) { tools.storage.bindSimpleSwitch($("page-close-ask-switch"), "page.close.ask", true, function(value) {
if (value) { if (value) {
window.onbeforeunload = function(event) { window.onbeforeunload = function(event) {
@ -48,7 +48,7 @@ export function main() {
initWindowManager(); initWindowManager();
tools.el.setOnClick($("open-log-button"), () => window.open("/api/log?seek=3600&follow=1", "_blank")); tools.el.setOnClick($("open-log-button"), () => tools.windowOpen("api/log?seek=3600&follow=1"));
tools.storage.bindSimpleSwitch( tools.storage.bindSimpleSwitch(
$("page-full-tab-stream-switch"), $("page-full-tab-stream-switch"),

View File

@ -23,6 +23,7 @@
"use strict"; "use strict";
import {ROOT_PREFIX} from "../vars.js";
import {tools, $} from "../tools.js"; import {tools, $} from "../tools.js";
import {wm} from "../wm.js"; import {wm} from "../wm.js";
@ -270,14 +271,14 @@ export function Msd() {
var __clickDownloadButton = function() { var __clickDownloadButton = function() {
let image = encodeURIComponent($("msd-image-selector").value); let image = encodeURIComponent($("msd-image-selector").value);
window.open(`/api/msd/read?image=${image}`); tools.windowOpen(`api/msd/read?image=${image}`);
}; };
var __clickRemoveButton = function() { var __clickRemoveButton = function() {
let name = $("msd-image-selector").value; let name = $("msd-image-selector").value;
wm.confirm("Are you sure you want to remove this image?", name).then(function(ok) { wm.confirm("Are you sure you want to remove this image?", name).then(function(ok) {
if (ok) { if (ok) {
tools.httpPost("/api/msd/remove", {"image": name}, function(http) { tools.httpPost("api/msd/remove", {"image": name}, function(http) {
if (http.status !== 200) { if (http.status !== 200) {
wm.error("Can't remove image", http.responseText); wm.error("Can't remove image", http.responseText);
} }
@ -287,7 +288,7 @@ export function Msd() {
}; };
var __sendParam = function(name, value) { var __sendParam = function(name, value) {
tools.httpPost("/api/msd/set_params", {[name]: value}, function(http) { tools.httpPost("api/msd/set_params", {[name]: value}, function(http) {
if (http.status !== 200) { if (http.status !== 200) {
wm.error("Can't configure Mass Storage", http.responseText); wm.error("Can't configure Mass Storage", http.responseText);
} }
@ -301,10 +302,10 @@ export function Msd() {
let prefix = encodeURIComponent($("msd-new-part-selector").value); let prefix = encodeURIComponent($("msd-new-part-selector").value);
if (file) { if (file) {
let image = encodeURIComponent(file.name); let image = encodeURIComponent(file.name);
__http.open("POST", `/api/msd/write?prefix=${prefix}&image=${image}&remove_incomplete=1`, true); __http.open("POST", `${ROOT_PREFIX}api/msd/write?prefix=${prefix}&image=${image}&remove_incomplete=1`, true);
} else { } else {
let url = encodeURIComponent($("msd-new-url").value); let url = encodeURIComponent($("msd-new-url").value);
__http.open("POST", `/api/msd/write_remote?prefix=${prefix}&url=${url}&remove_incomplete=1`, true); __http.open("POST", `${ROOT_PREFIX}api/msd/write_remote?prefix=${prefix}&url=${url}&remove_incomplete=1`, true);
} }
__http.upload.timeout = 7 * 24 * 3600; __http.upload.timeout = 7 * 24 * 3600;
__http.onreadystatechange = __uploadStateChange; __http.onreadystatechange = __uploadStateChange;
@ -360,7 +361,7 @@ export function Msd() {
}; };
var __clickConnectButton = function(connected) { var __clickConnectButton = function(connected) {
tools.httpPost("/api/msd/set_connected", {"connected": connected}, function(http) { tools.httpPost("api/msd/set_connected", {"connected": connected}, function(http) {
if (http.status !== 200) { if (http.status !== 200) {
wm.error("Can't switch Mass Storage", http.responseText); wm.error("Can't switch Mass Storage", http.responseText);
} }
@ -373,7 +374,7 @@ export function Msd() {
var __clickResetButton = function() { var __clickResetButton = function() {
wm.confirm("Are you sure you want to reset Mass Storage?").then(function(ok) { wm.confirm("Are you sure you want to reset Mass Storage?").then(function(ok) {
if (ok) { if (ok) {
tools.httpPost("/api/msd/reset", null, function(http) { tools.httpPost("api/msd/reset", null, function(http) {
if (http.status !== 200) { if (http.status !== 200) {
wm.error("Mass Storage reset error", http.responseText); wm.error("Mass Storage reset error", http.responseText);
} }

View File

@ -186,7 +186,7 @@ export function Ocr(__getGeometry) {
"ocr_right": __sel.right, "ocr_right": __sel.right,
"ocr_bottom": __sel.bottom, "ocr_bottom": __sel.bottom,
}; };
tools.httpGet("/api/streamer/snapshot", params, function(http) { tools.httpGet("api/streamer/snapshot", params, function(http) {
if (http.status === 200) { if (http.status === 200) {
wm.copyTextToClipboard(http.responseText); wm.copyTextToClipboard(http.responseText);
} else { } else {

View File

@ -72,7 +72,7 @@ export function Paste(__recorder) {
tools.debug(`HID: paste-as-keys ${keymap}: ${text}`); tools.debug(`HID: paste-as-keys ${keymap}: ${text}`);
tools.httpPost("/api/hid/print", {"limit": 0, "keymap": keymap, "slow": slow}, function(http) { tools.httpPost("api/hid/print", {"limit": 0, "keymap": keymap, "slow": slow}, function(http) {
tools.el.setEnabled($("hid-pak-text"), true); tools.el.setEnabled($("hid-pak-text"), true);
tools.el.setEnabled($("hid-pak-button"), true); tools.el.setEnabled($("hid-pak-button"), true);
tools.el.setEnabled($("hid-pak-keymap-selector"), true); tools.el.setEnabled($("hid-pak-keymap-selector"), true);

View File

@ -293,7 +293,7 @@ export function Recorder() {
if (event.event.slow !== undefined) { if (event.event.slow !== undefined) {
params["slow"] = event.event.slow; params["slow"] = event.event.slow;
} }
tools.httpPost("/api/hid/print", params, function(http) { tools.httpPost("api/hid/print", params, function(http) {
if (http.status === 413) { if (http.status === 413) {
wm.error("Too many text for paste!"); wm.error("Too many text for paste!");
__stopProcess(); __stopProcess();
@ -307,7 +307,7 @@ export function Recorder() {
return; return;
} else if (event.event_type === "atx_button") { } else if (event.event_type === "atx_button") {
tools.httpPost("/api/atx/click", {"button": event.event.button}, function(http) { tools.httpPost("api/atx/click", {"button": event.event.button}, function(http) {
if (http.status !== 200) { if (http.status !== 200) {
wm.error("ATX error", http.responseText); wm.error("ATX error", http.responseText);
__stopProcess(); __stopProcess();
@ -318,7 +318,7 @@ export function Recorder() {
return; return;
} else if (["gpio_switch", "gpio_pulse"].includes(event.event_type)) { } else if (["gpio_switch", "gpio_pulse"].includes(event.event_type)) {
let path = "/api/gpio"; let path = "api/gpio";
let params = {"channel": event.event.channel}; let params = {"channel": event.event.channel};
if (event.event_type === "gpio_switch") { if (event.event_type === "gpio_switch") {
path += "/switch"; path += "/switch";

View File

@ -23,6 +23,7 @@
"use strict"; "use strict";
import {ROOT_PREFIX} from "../vars.js";
import {tools, $} from "../tools.js"; import {tools, $} from "../tools.js";
import {wm} from "../wm.js"; import {wm} from "../wm.js";
@ -272,10 +273,15 @@ export function Session() {
let close_hook = null; let close_hook = null;
let has_webterm = (state.webterm && (state.webterm.enabled || state.webterm.started)); let has_webterm = (state.webterm && (state.webterm.enabled || state.webterm.started));
if (has_webterm) { if (has_webterm) {
let path = "/" + state.webterm.path + "?disableLeaveAlert=true"; let loc = window.location;
let base = `${loc.protocol}//${loc.host}${loc.pathname}${ROOT_PREFIX}`;
// Tailing slash after state.webterm.path is added to avoid Nginx 301 redirect
// when the location doesn't have tailing slash: "foo -> foo/".
// Reverse proxy over PiKVM can be misconfigured to handle this.
let url = base + state.webterm.path + "/?disableLeaveAlert=true";
show_hook = function() { show_hook = function() {
tools.info("Terminal opened: ", path); tools.info("Terminal opened: ", url);
$("webterm-iframe").src = path; $("webterm-iframe").src = url;
}; };
close_hook = function() { close_hook = function() {
tools.info("Terminal closed"); tools.info("Terminal closed");
@ -291,9 +297,9 @@ export function Session() {
$("link-led").className = "led-yellow"; $("link-led").className = "led-yellow";
$("link-led").title = "Connecting..."; $("link-led").title = "Connecting...";
tools.httpGet("/api/auth/check", null, function(http) { tools.httpGet("api/auth/check", null, function(http) {
if (http.status === 200) { if (http.status === 200) {
__ws = new WebSocket(`${tools.is_https ? "wss" : "ws"}://${location.host}/api/ws`); __ws = new WebSocket(tools.makeWsUrl("api/ws"));
__ws.sendHidEvent = (event) => __sendHidEvent(__ws, event.event_type, event.event); __ws.sendHidEvent = (event) => __sendHidEvent(__ws, event.event_type, event.event);
__ws.onopen = __wsOpenHandler; __ws.onopen = __wsOpenHandler;
__ws.onmessage = __wsMessageHandler; __ws.onmessage = __wsMessageHandler;
@ -302,7 +308,7 @@ export function Session() {
} else if (http.status === 401 || http.status === 403) { } else if (http.status === 401 || http.status === 403) {
window.onbeforeunload = () => null; window.onbeforeunload = () => null;
wm.error("Unexpected logout occured, please login again").then(function() { wm.error("Unexpected logout occured, please login again").then(function() {
document.location.href = "/login"; tools.currentOpen("login");
}); });
} else { } else {
__wsCloseHandler(null); __wsCloseHandler(null);

View File

@ -332,19 +332,14 @@ export function Streamer() {
}; };
var __clickScreenshotButton = function() { var __clickScreenshotButton = function() {
let el = document.createElement("a"); tools.windowOpen("api/streamer/snapshot");
el.href = "/api/streamer/snapshot";
el.target = "_blank";
document.body.appendChild(el);
el.click();
setTimeout(() => document.body.removeChild(el), 0);
}; };
var __clickResetButton = function() { var __clickResetButton = function() {
wm.confirm("Are you sure you want to reset stream?").then(function(ok) { wm.confirm("Are you sure you want to reset stream?").then(function(ok) {
if (ok) { if (ok) {
__resetStream(); __resetStream();
tools.httpPost("/api/streamer/reset", null, function(http) { tools.httpPost("api/streamer/reset", null, function(http) {
if (http.status !== 200) { if (http.status !== 200) {
wm.error("Can't reset stream", http.responseText); wm.error("Can't reset stream", http.responseText);
} }
@ -354,7 +349,7 @@ export function Streamer() {
}; };
var __sendParam = function(name, value) { var __sendParam = function(name, value) {
tools.httpPost("/api/streamer/set_params", {[name]: value}, function(http) { tools.httpPost("api/streamer/set_params", {[name]: value}, function(http) {
if (http.status !== 200) { if (http.status !== 200) {
wm.error("Can't configure stream", http.responseText); wm.error("Can't configure stream", http.responseText);
} }

View File

@ -96,7 +96,7 @@ export function JanusStreamer(__setActive, __setInactive, __setInfo, __orient, _
__setInfo(false, false, ""); __setInfo(false, false, "");
__logInfo("Starting Janus ..."); __logInfo("Starting Janus ...");
__janus = new _Janus({ __janus = new _Janus({
"server": `${tools.is_https ? "wss" : "ws"}://${location.host}/janus/ws`, "server": tools.makeWsUrl("janus/ws"),
"ipv6": true, "ipv6": true,
"destroyOnUnload": false, "destroyOnUnload": false,
"success": __attachJanus, "success": __attachJanus,

View File

@ -79,7 +79,7 @@ export function MediaStreamer(__setActive, __setInactive, __setInfo) {
__setInactive(); __setInactive();
__setInfo(false, false, ""); __setInfo(false, false, "");
__logInfo("Starting Media ..."); __logInfo("Starting Media ...");
__ws = new WebSocket(`${tools.is_https ? "wss" : "ws"}://${location.host}/api/media/ws`); __ws = new WebSocket(tools.makeWsUrl("api/media/ws"));
__ws.binaryType = "arraybuffer"; __ws.binaryType = "arraybuffer";
__ws.onopen = __wsOpenHandler; __ws.onopen = __wsOpenHandler;
__ws.onerror = __wsErrorHandler; __ws.onerror = __wsErrorHandler;

View File

@ -23,6 +23,7 @@
"use strict"; "use strict";
import {ROOT_PREFIX} from "../vars.js";
import {tools, $} from "../tools.js"; import {tools, $} from "../tools.js";
@ -72,7 +73,7 @@ export function MjpegStreamer(__setActive, __setInactive, __setInfo) {
self.stopStream = function() { self.stopStream = function() {
self.ensureStream(null); self.ensureStream(null);
let blank = "/share/png/blank-stream.png"; let blank = `${ROOT_PREFIX}share/png/blank-stream.png`;
if (!String.prototype.endsWith.call($("stream-image").src, blank)) { if (!String.prototype.endsWith.call($("stream-image").src, blank)) {
$("stream-image").src = blank; $("stream-image").src = blank;
} }
@ -138,7 +139,7 @@ export function MjpegStreamer(__setActive, __setInactive, __setInfo) {
__setStreamInactive(); __setStreamInactive();
__stopChecking(); __stopChecking();
let path = `/streamer/stream?key=${__key}`; let path = `${ROOT_PREFIX}streamer/stream?key=${__key}`;
if (tools.browser.is_safari || tools.browser.is_ios) { if (tools.browser.is_safari || tools.browser.is_ios) {
// uStreamer fix for WebKit // uStreamer fix for WebKit
__logInfo("Using dual_final_frames=1 to fix WebKit bugs"); __logInfo("Using dual_final_frames=1 to fix WebKit bugs");

View File

@ -23,6 +23,7 @@
"use strict"; "use strict";
import {ROOT_PREFIX} from "../vars.js";
import {tools, $} from "../tools.js"; import {tools, $} from "../tools.js";
import {wm} from "../wm.js"; import {wm} from "../wm.js";
@ -125,7 +126,7 @@ export function Switch() {
+ ":" + brightness.toString(16).padStart(2, "0") + ":" + brightness.toString(16).padStart(2, "0")
+ ":" + color.blink_ms.toString(16).padStart(4, "0") + ":" + color.blink_ms.toString(16).padStart(4, "0")
); );
__sendPost("/api/switch/set_colors", {[role]: rgbx}, function() { __sendPost("api/switch/set_colors", {[role]: rgbx}, function() {
el_color.value = ( el_color.value = (
"#" "#"
+ color.red.toString(16).padStart(2, "0") + color.red.toString(16).padStart(2, "0")
@ -137,7 +138,7 @@ export function Switch() {
}; };
var __clickSetDefaultColorButton = function(role) { var __clickSetDefaultColorButton = function(role) {
__sendPost("/api/switch/set_colors", {[role]: "default"}); __sendPost("api/switch/set_colors", {[role]: "default"});
}; };
var __applyEdids = function(edids) { var __applyEdids = function(edids) {
@ -210,7 +211,7 @@ export function Switch() {
if (ok) { if (ok) {
let name = $("__switch-edid-new-name-input").value; let name = $("__switch-edid-new-name-input").value;
let data = $("__switch-edid-new-data-text").value; let data = $("__switch-edid-new-data-text").value;
__sendPost("/api/switch/edids/create", {"name": name, "data": data}); __sendPost("api/switch/edids/create", {"name": name, "data": data});
} }
}); });
}; };
@ -222,7 +223,7 @@ export function Switch() {
let html = "Are you sure to remove this EDID?<br>Ports that used it will change it to the default."; let html = "Are you sure to remove this EDID?<br>Ports that used it will change it to the default.";
wm.confirm(html, name).then(function(ok) { wm.confirm(html, name).then(function(ok) {
if (ok) { if (ok) {
__sendPost("/api/switch/edids/remove", {"id": edid_id}); __sendPost("api/switch/edids/remove", {"id": edid_id});
} }
}); });
} }
@ -344,11 +345,11 @@ export function Switch() {
<td colspan=100> <td colspan=100>
<div class="buttons-row"> <div class="buttons-row">
<button id="__switch-beacon-button-u${unit}" class="small" title="Toggle uplink Beacon Led"> <button id="__switch-beacon-button-u${unit}" class="small" title="Toggle uplink Beacon Led">
<img id="__switch-beacon-led-u${unit}" class="inline-lamp led-gray" src="/share/svg/led-beacon.svg"/> <img id="__switch-beacon-led-u${unit}" class="inline-lamp led-gray" src="${ROOT_PREFIX}share/svg/led-beacon.svg"/>
Uplink Uplink
</button> </button>
<button id="__switch-beacon-button-d${unit}" class="small" title="Toggle downlink Beacon Led"> <button id="__switch-beacon-button-d${unit}" class="small" title="Toggle downlink Beacon Led">
<img id="__switch-beacon-led-d${unit}" class="inline-lamp led-gray" src="/share/svg/led-beacon.svg"/> <img id="__switch-beacon-led-d${unit}" class="inline-lamp led-gray" src="${ROOT_PREFIX}share/svg/led-beacon.svg"/>
Downlink Downlink
</button> </button>
</div> </div>
@ -365,10 +366,10 @@ export function Switch() {
<td> <td>
<div class="buttons-row"> <div class="buttons-row">
<button id="__switch-port-button-p${port}" title="Activate this port"> <button id="__switch-port-button-p${port}" title="Activate this port">
<img id="__switch-port-led-p${port}" class="inline-lamp led-gray" src="/share/svg/led-circle.svg"/> <img id="__switch-port-led-p${port}" class="inline-lamp led-gray" src="${ROOT_PREFIX}share/svg/led-circle.svg"/>
</button> </button>
<button id="__switch-params-button-p${port}" title="Configure this port"> <button id="__switch-params-button-p${port}" title="Configure this port">
<img id="__switch-params-led-p${port}" class="inline-lamp led-gray" src="/share/svg/led-gear.svg"/> <img id="__switch-params-led-p${port}" class="inline-lamp led-gray" src="${ROOT_PREFIX}share/svg/led-gear.svg"/>
</button> </button>
</div> </div>
</td> </td>
@ -385,14 +386,14 @@ export function Switch() {
</td> </td>
<td style="font-size:1em"> <td style="font-size:1em">
<button id="__switch-beacon-button-p${port}" class="small" title="Toggle Beacon Led on this port"> <button id="__switch-beacon-button-p${port}" class="small" title="Toggle Beacon Led on this port">
<img id="__switch-beacon-led-p${port}" class="inline-lamp led-gray" src="/share/svg/led-beacon.svg"/> <img id="__switch-beacon-led-p${port}" class="inline-lamp led-gray" src="${ROOT_PREFIX}share/svg/led-beacon.svg"/>
</button> </button>
</td> </td>
<td> <td>
<img id="__switch-video-led-p${port}" class="inline-lamp led-gray" src="/share/svg/led-video.svg" title="Video Link"/> <img id="__switch-video-led-p${port}" class="inline-lamp led-gray" src="${ROOT_PREFIX}share/svg/led-video.svg" title="Video Link"/>
<img id="__switch-usb-led-p${port}" class="inline-lamp led-gray" src="/share/svg/led-usb.svg" title="USB Link"/> <img id="__switch-usb-led-p${port}" class="inline-lamp led-gray" src="${ROOT_PREFIX}share/svg/led-usb.svg" title="USB Link"/>
<img id="__switch-atx-power-led-p${port}" class="inline-lamp led-gray" src="/share/svg/led-atx-power.svg" title="Power Led"/> <img id="__switch-atx-power-led-p${port}" class="inline-lamp led-gray" src="${ROOT_PREFIX}share/svg/led-atx-power.svg" title="Power Led"/>
<img id="__switch-atx-hdd-led-p${port}" class="inline-lamp led-gray" src="/share/svg/led-atx-hdd.svg" title="HDD Led"/> <img id="__switch-atx-hdd-led-p${port}" class="inline-lamp led-gray" src="${ROOT_PREFIX}share/svg/led-atx-hdd.svg" title="HDD Led"/>
</td> </td>
<td> <td>
<div class="buttons-row"> <div class="buttons-row">
@ -524,7 +525,7 @@ export function Switch() {
for (let action of Object.keys(atx_actions)) { for (let action of Object.keys(atx_actions)) {
params[`atx_click_${action}_delay`] = tools.slider.getValue($(`__switch-port-atx-click-${action}-delay-slider`)); params[`atx_click_${action}_delay`] = tools.slider.getValue($(`__switch-port-atx-click-${action}-delay-slider`));
}; };
__sendPost("/api/switch/set_port_params", params); __sendPost("api/switch/set_port_params", params);
} }
}); });
}; };
@ -555,31 +556,31 @@ export function Switch() {
Otherwise, it will break a current USB operation (OS installation, Live CD, or whatever). Otherwise, it will break a current USB operation (OS installation, Live CD, or whatever).
`); `);
} else { } else {
__sendPost("/api/switch/set_active", {"port": port}); __sendPost("api/switch/set_active", {"port": port});
} }
}; };
var __switchUplinkBeacon = function(unit) { var __switchUplinkBeacon = function(unit) {
let state = false; let state = false;
try { state = !__state.beacons.uplinks[unit]; } catch {}; // eslint-disable-line no-empty try { state = !__state.beacons.uplinks[unit]; } catch {}; // eslint-disable-line no-empty
__sendPost("/api/switch/set_beacon", {"uplink": unit, "state": state}); __sendPost("api/switch/set_beacon", {"uplink": unit, "state": state});
}; };
var __switchDownlinkBeacon = function(unit) { var __switchDownlinkBeacon = function(unit) {
let state = false; let state = false;
try { state = !__state.beacons.downlinks[unit]; } catch {}; // eslint-disable-line no-empty try { state = !__state.beacons.downlinks[unit]; } catch {}; // eslint-disable-line no-empty
__sendPost("/api/switch/set_beacon", {"downlink": unit, "state": state}); __sendPost("api/switch/set_beacon", {"downlink": unit, "state": state});
}; };
var __switchPortBeacon = function(port) { var __switchPortBeacon = function(port) {
let state = false; let state = false;
try { state = !__state.beacons.ports[port]; } catch {}; // eslint-disable-line no-empty try { state = !__state.beacons.ports[port]; } catch {}; // eslint-disable-line no-empty
__sendPost("/api/switch/set_beacon", {"port": port, "state": state}); __sendPost("api/switch/set_beacon", {"port": port, "state": state});
}; };
var __atxClick = function(port, button) { var __atxClick = function(port, button) {
let click_button = function() { let click_button = function() {
__sendPost("/api/switch/atx/click", {"port": port, "button": button}); __sendPost("api/switch/atx/click", {"port": port, "button": button});
}; };
if ($("switch-atx-ask-switch").checked) { if ($("switch-atx-ask-switch").checked) {
wm.confirm(` wm.confirm(`

View File

@ -51,9 +51,9 @@ function __login() {
} else { } else {
let passwd = $("passwd-input").value + $("code-input").value; let passwd = $("passwd-input").value + $("code-input").value;
let body = `user=${encodeURIComponent(user)}&passwd=${encodeURIComponent(passwd)}`; let body = `user=${encodeURIComponent(user)}&passwd=${encodeURIComponent(passwd)}`;
tools.httpPost("/api/auth/login", null, function(http) { tools.httpPost("api/auth/login", null, function(http) {
if (http.status === 200) { if (http.status === 200) {
document.location.href = "/"; tools.currentOpen("");
} else if (http.status === 403) { } else if (http.status === 403) {
wm.error("Invalid credentials").then(__tryAgain); wm.error("Invalid credentials").then(__tryAgain);
} else { } else {

View File

@ -23,6 +23,7 @@
"use strict"; "use strict";
import {ROOT_PREFIX} from "./vars.js";
import {browser} from "./bb.js"; import {browser} from "./bb.js";
@ -39,7 +40,16 @@ export var tools = new function() {
/************************************************************************/ /************************************************************************/
self.currentOpen = function(url) {
window.location.href = ROOT_PREFIX + url;
};
self.windowOpen = function(url) {
window.open(ROOT_PREFIX + url, "_blank");
};
self.httpRequest = function(method, url, params, callback, body=null, content_type=null, timeout=15000) { self.httpRequest = function(method, url, params, callback, body=null, content_type=null, timeout=15000) {
url = ROOT_PREFIX + url;
if (params) { if (params) {
params = new URLSearchParams(params); params = new URLSearchParams(params);
if (params) { if (params) {
@ -68,6 +78,11 @@ export var tools = new function() {
self.httpRequest("POST", url, params, callback, body, content_type, timeout); self.httpRequest("POST", url, params, callback, body, content_type, timeout);
}; };
self.makeWsUrl = function(url) {
let proto = (self.is_https ? "wss://" : "ws://");
return proto + window.location.host + window.location.pathname + ROOT_PREFIX + url;
};
/************************************************************************/ /************************************************************************/
self.escape = function(text) { self.escape = function(text) {
@ -383,7 +398,7 @@ export var tools = new function() {
/************************************************************************/ /************************************************************************/
self.is_https = (location.protocol === "https:"); self.is_https = (window.location.protocol === "https:");
self.cookies = new function() { self.cookies = new function() {
return { return {

31
web/share/js/vars.js Normal file
View File

@ -0,0 +1,31 @@
/*****************************************************************************
# #
# KVMD - The main PiKVM daemon. #
# #
# Copyright (C) 2018-2024 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
"use strict";
export var ROOT_PREFIX = "./";
export function setRootPrefix(prefix) {
ROOT_PREFIX = prefix;
}

View File

@ -31,7 +31,7 @@ export function main() {
} }
function __loadKvmdInfo() { function __loadKvmdInfo() {
tools.httpGet("/api/info", null, function(http) { tools.httpGet("api/info", null, function(http) {
if (http.status === 200) { if (http.status === 200) {
let vnc_port = JSON.parse(http.responseText).result.extras.vnc.port; let vnc_port = JSON.parse(http.responseText).result.extras.vnc.port;
$("vnc-text").innerHTML = ` $("vnc-text").innerHTML = `
@ -39,7 +39,7 @@ function __loadKvmdInfo() {
$</span> vncviewer ${window.location.hostname}::${vnc_port} $</span> vncviewer ${window.location.hostname}::${vnc_port}
`; `;
} else if (http.status === 401 || http.status === 403) { } else if (http.status === 401 || http.status === 403) {
document.location.href = "/login"; tools.currentOpen("login");
} else { } else {
setTimeout(__loadKvmdInfo, 1000); setTimeout(__loadKvmdInfo, 1000);
} }

View File

@ -8,7 +8,7 @@ block body
div(class="start-box") div(class="start-box")
div(class="start") div(class="start")
if index_link if index_link
a(style="display:inline-block; margin-top:4px; color:#5c90bc; text-decoration:none" href="/") a(style="display:inline-block; margin-top:4px; color:#5c90bc; text-decoration:none" href=root_prefix)
| &nbsp;&nbsp;&larr;&nbsp;&nbsp; [ PiKVM Index ] | &nbsp;&nbsp;&larr;&nbsp;&nbsp; [ PiKVM Index ]
hr hr
block start block start

View File

@ -37,13 +37,15 @@
<link rel="stylesheet" href="../share/css/main.css"> <link rel="stylesheet" href="../share/css/main.css">
<link rel="stylesheet" href="../share/css/start.css"> <link rel="stylesheet" href="../share/css/start.css">
<link rel="stylesheet" href="../share/css/user.css"> <link rel="stylesheet" href="../share/css/user.css">
<script type="module">import {main} from "../share/js/vnc/main.js"; <script type="module">import {setRootPrefix} from "../share/js/vars.js";
setRootPrefix("../");
import {main} from "../share/js/vnc/main.js";
main(); main();
</script> </script>
</head> </head>
<body> <body>
<div class="start-box"> <div class="start-box">
<div class="start"><a style="display:inline-block; margin-top:4px; color:#5c90bc; text-decoration:none" href="/">&nbsp;&nbsp;&larr;&nbsp;&nbsp; [ PiKVM Index ]</a> <div class="start"><a style="display:inline-block; margin-top:4px; color:#5c90bc; text-decoration:none" href="../">&nbsp;&nbsp;&larr;&nbsp;&nbsp; [ PiKVM Index ]</a>
<hr> <hr>
<p class="text">This PiKVM device has running <b>kvmd-vnc</b> daemon and provides VNC access to the server.</p> <p class="text">This PiKVM device has running <b>kvmd-vnc</b> daemon and provides VNC access to the server.</p>
<p class="text"><b>WARNING!</b> We strongly don't recommend you to use VNC in untrusted networks without <p class="text"><b>WARNING!</b> We strongly don't recommend you to use VNC in untrusted networks without

View File

@ -3,7 +3,7 @@ extends ../start.pug
append vars append vars
- -
prefix = "../" root_prefix = "../"
title = "PiKVM VNC Info" title = "PiKVM VNC Info"
main_js = "vnc/main" main_js = "vnc/main"
index_link = true index_link = true