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

View File

@ -40,7 +40,9 @@
<link rel="stylesheet" href="./share/css/modal.css">
<link rel="stylesheet" href="./share/css/index/index.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();
</script>
</head>
@ -66,7 +68,7 @@
<table>
<td class="server">
<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>
</table>
<hr>

View File

@ -25,7 +25,7 @@ block start
table
td(class="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
#apps-box

View File

@ -37,13 +37,15 @@
<link rel="stylesheet" href="../share/css/main.css">
<link rel="stylesheet" href="../share/css/start.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();
</script>
</head>
<body>
<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>
<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.

View File

@ -3,7 +3,7 @@ extends ../start.pug
append vars
-
prefix = "../"
root_prefix = "../"
title = "PiKVM IPMI Info"
main_js = "ipmi/main"
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/about.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();
</script>
</head>
<body class="body-no-select">
<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">
<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">
@ -131,7 +133,7 @@
<td style="line-height:1.5"><b>Fan failed</b></td>
</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>
</table>
</div>
@ -2283,7 +2285,9 @@
<label for="about-tab-meta-button">Meta</label>
<div class="tab">
<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>
// is specified in the file /etc/kvmd/meta.yaml</span><br>
<pre id="kvmd-meta-json">No data</pre>

View File

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

View File

@ -31,4 +31,4 @@
#fan-health-message-fail
hr
+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
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")
include navbar-health.pug

View File

@ -34,7 +34,8 @@ mixin about_tab(name, title, checked=false)
+about_tab("meta", "Meta", true)
div
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]
| // is specified in the file /etc/kvmd/meta.yaml
br

View File

@ -39,7 +39,9 @@
<link rel="stylesheet" href="../share/css/modal.css">
<link rel="stylesheet" href="../share/css/login/login.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();
</script>
</head>

View File

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

View File

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

View File

@ -23,6 +23,7 @@
"use strict";
import {ROOT_PREFIX} from "../vars.js";
import {tools, $} from "../tools.js";
import {checkBrowser} from "../bb.js";
import {wm, initWindowManager} from "../wm.js";
@ -51,7 +52,7 @@ function __setAppText() {
}
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) {
let info = JSON.parse(http.responseText).result;
@ -100,7 +101,7 @@ function __loadKvmdInfo() {
document.title = "PiKVM Index";
}
} else if (http.status === 401 || http.status === 403) {
document.location.href = "/login";
tools.currentOpen("login");
} else {
setTimeout(__loadKvmdInfo, 1000);
}
@ -108,11 +109,14 @@ function __loadKvmdInfo() {
}
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>
<div ${id ? "id=\"" + id + "\"" : ""} class="app">
<a href="${path}">
<a href="${ROOT_PREFIX}${path}/">
<div>
<img class="svg-gray" src="${icon}">
<img class="svg-gray" src="${ROOT_PREFIX}${icon}">
${tools.escape(name)}
</div>
</a>
@ -121,9 +125,9 @@ function __makeApp(id, path, icon, name) {
}
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) {
document.location.href = "/login";
tools.currentOpen("login");
} else {
wm.error("Logout error", http.responseText);
}

View File

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

View File

@ -94,7 +94,7 @@ export function Atx(__recorder) {
var __clickAtx = function(button) {
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) {
wm.error("Performing another ATX operation for other client.<br>Please try again later.");
} else if (http.status !== 200) {

View File

@ -23,6 +23,7 @@
"use strict";
import {ROOT_PREFIX} from "../vars.js";
import {tools, $, $$} from "../tools.js";
import {wm} from "../wm.js";
@ -138,7 +139,7 @@ export function Gpio(__recorder) {
return `
<img
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}"
/>
`;
@ -202,7 +203,7 @@ export function Gpio(__recorder) {
confirm = el.getAttribute("data-confirm-off");
}
let act = () => {
__sendPost("/api/gpio/switch", {"channel": ch, "state": to});
__sendPost("api/gpio/switch", {"channel": ch, "state": to});
__recorder.recordGpioSwitchEvent(ch, to);
};
if (confirm) {
@ -220,7 +221,7 @@ export function Gpio(__recorder) {
let ch = el.getAttribute("data-channel");
let confirm = el.getAttribute("data-confirm");
let act = () => {
__sendPost("/api/gpio/pulse", {"channel": ch});
__sendPost("api/gpio/pulse", {"channel": ch});
__recorder.recordGpioPulseEvent(ch);
};
if (confirm) {

View File

@ -275,7 +275,7 @@ export function Hid(__getGeometry, __recorder) {
var __clickOutputsRadio = function(hid) {
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) {
wm.error("Can't configure HID", http.responseText);
}
@ -284,7 +284,7 @@ export function Hid(__getGeometry, __recorder) {
var __clickJigglerSwitch = function() {
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) {
wm.error(`Can't ${enabled ? "enabled" : "disable"} mouse jiggler`, http.responseText);
}
@ -293,7 +293,7 @@ export function Hid(__getGeometry, __recorder) {
var __clickConnectSwitch = function() {
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) {
wm.error(`Can't ${connected ? "connect" : "disconnect"} HID`, http.responseText);
}
@ -303,7 +303,7 @@ export function Hid(__getGeometry, __recorder) {
var __clickResetButton = function() {
wm.confirm("Are you sure you want to reset HID (keyboard & mouse)?").then(function(ok) {
if (ok) {
tools.httpPost("/api/hid/reset", null, function(http) {
tools.httpPost("api/hid/reset", null, function(http) {
if (http.status !== 200) {
wm.error("HID reset error", http.responseText);
}

View File

@ -31,7 +31,7 @@ import {Session} from "./session.js";
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) {
if (value) {
window.onbeforeunload = function(event) {
@ -48,7 +48,7 @@ export function main() {
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(
$("page-full-tab-stream-switch"),

View File

@ -23,6 +23,7 @@
"use strict";
import {ROOT_PREFIX} from "../vars.js";
import {tools, $} from "../tools.js";
import {wm} from "../wm.js";
@ -270,14 +271,14 @@ export function Msd() {
var __clickDownloadButton = function() {
let image = encodeURIComponent($("msd-image-selector").value);
window.open(`/api/msd/read?image=${image}`);
tools.windowOpen(`api/msd/read?image=${image}`);
};
var __clickRemoveButton = function() {
let name = $("msd-image-selector").value;
wm.confirm("Are you sure you want to remove this image?", name).then(function(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) {
wm.error("Can't remove image", http.responseText);
}
@ -287,7 +288,7 @@ export function Msd() {
};
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) {
wm.error("Can't configure Mass Storage", http.responseText);
}
@ -301,10 +302,10 @@ export function Msd() {
let prefix = encodeURIComponent($("msd-new-part-selector").value);
if (file) {
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 {
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.onreadystatechange = __uploadStateChange;
@ -360,7 +361,7 @@ export function Msd() {
};
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) {
wm.error("Can't switch Mass Storage", http.responseText);
}
@ -373,7 +374,7 @@ export function Msd() {
var __clickResetButton = function() {
wm.confirm("Are you sure you want to reset Mass Storage?").then(function(ok) {
if (ok) {
tools.httpPost("/api/msd/reset", null, function(http) {
tools.httpPost("api/msd/reset", null, function(http) {
if (http.status !== 200) {
wm.error("Mass Storage reset error", http.responseText);
}

View File

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

View File

@ -72,7 +72,7 @@ export function Paste(__recorder) {
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-button"), true);
tools.el.setEnabled($("hid-pak-keymap-selector"), true);

View File

@ -293,7 +293,7 @@ export function Recorder() {
if (event.event.slow !== undefined) {
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) {
wm.error("Too many text for paste!");
__stopProcess();
@ -307,7 +307,7 @@ export function Recorder() {
return;
} 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) {
wm.error("ATX error", http.responseText);
__stopProcess();
@ -318,7 +318,7 @@ export function Recorder() {
return;
} else if (["gpio_switch", "gpio_pulse"].includes(event.event_type)) {
let path = "/api/gpio";
let path = "api/gpio";
let params = {"channel": event.event.channel};
if (event.event_type === "gpio_switch") {
path += "/switch";

View File

@ -23,6 +23,7 @@
"use strict";
import {ROOT_PREFIX} from "../vars.js";
import {tools, $} from "../tools.js";
import {wm} from "../wm.js";
@ -272,10 +273,15 @@ export function Session() {
let close_hook = null;
let has_webterm = (state.webterm && (state.webterm.enabled || state.webterm.started));
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() {
tools.info("Terminal opened: ", path);
$("webterm-iframe").src = path;
tools.info("Terminal opened: ", url);
$("webterm-iframe").src = url;
};
close_hook = function() {
tools.info("Terminal closed");
@ -291,9 +297,9 @@ export function Session() {
$("link-led").className = "led-yellow";
$("link-led").title = "Connecting...";
tools.httpGet("/api/auth/check", null, function(http) {
tools.httpGet("api/auth/check", null, function(http) {
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.onopen = __wsOpenHandler;
__ws.onmessage = __wsMessageHandler;
@ -302,7 +308,7 @@ export function Session() {
} else if (http.status === 401 || http.status === 403) {
window.onbeforeunload = () => null;
wm.error("Unexpected logout occured, please login again").then(function() {
document.location.href = "/login";
tools.currentOpen("login");
});
} else {
__wsCloseHandler(null);

View File

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

View File

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

View File

@ -79,7 +79,7 @@ export function MediaStreamer(__setActive, __setInactive, __setInfo) {
__setInactive();
__setInfo(false, false, "");
__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.onopen = __wsOpenHandler;
__ws.onerror = __wsErrorHandler;

View File

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

View File

@ -23,6 +23,7 @@
"use strict";
import {ROOT_PREFIX} from "../vars.js";
import {tools, $} from "../tools.js";
import {wm} from "../wm.js";
@ -125,7 +126,7 @@ export function Switch() {
+ ":" + brightness.toString(16).padStart(2, "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 = (
"#"
+ color.red.toString(16).padStart(2, "0")
@ -137,7 +138,7 @@ export function Switch() {
};
var __clickSetDefaultColorButton = function(role) {
__sendPost("/api/switch/set_colors", {[role]: "default"});
__sendPost("api/switch/set_colors", {[role]: "default"});
};
var __applyEdids = function(edids) {
@ -210,7 +211,7 @@ export function Switch() {
if (ok) {
let name = $("__switch-edid-new-name-input").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.";
wm.confirm(html, name).then(function(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>
<div class="buttons-row">
<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
</button>
<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
</button>
</div>
@ -365,10 +366,10 @@ export function Switch() {
<td>
<div class="buttons-row">
<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 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>
</div>
</td>
@ -385,14 +386,14 @@ export function Switch() {
</td>
<td style="font-size:1em">
<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>
</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-usb-led-p${port}" class="inline-lamp led-gray" src="/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-hdd-led-p${port}" class="inline-lamp led-gray" src="/share/svg/led-atx-hdd.svg" title="HDD Led"/>
<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="${ROOT_PREFIX}share/svg/led-usb.svg" title="USB Link"/>
<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="${ROOT_PREFIX}share/svg/led-atx-hdd.svg" title="HDD Led"/>
</td>
<td>
<div class="buttons-row">
@ -524,7 +525,7 @@ export function Switch() {
for (let action of Object.keys(atx_actions)) {
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).
`);
} else {
__sendPost("/api/switch/set_active", {"port": port});
__sendPost("api/switch/set_active", {"port": port});
}
};
var __switchUplinkBeacon = function(unit) {
let state = false;
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) {
let state = false;
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) {
let state = false;
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) {
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) {
wm.confirm(`

View File

@ -51,9 +51,9 @@ function __login() {
} else {
let passwd = $("passwd-input").value + $("code-input").value;
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) {
document.location.href = "/";
tools.currentOpen("");
} else if (http.status === 403) {
wm.error("Invalid credentials").then(__tryAgain);
} else {

View File

@ -23,6 +23,7 @@
"use strict";
import {ROOT_PREFIX} from "./vars.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) {
url = ROOT_PREFIX + url;
if (params) {
params = new URLSearchParams(params);
if (params) {
@ -68,6 +78,11 @@ export var tools = new function() {
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) {
@ -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() {
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() {
tools.httpGet("/api/info", null, function(http) {
tools.httpGet("api/info", null, function(http) {
if (http.status === 200) {
let vnc_port = JSON.parse(http.responseText).result.extras.vnc.port;
$("vnc-text").innerHTML = `
@ -39,7 +39,7 @@ function __loadKvmdInfo() {
$</span> vncviewer ${window.location.hostname}::${vnc_port}
`;
} else if (http.status === 401 || http.status === 403) {
document.location.href = "/login";
tools.currentOpen("login");
} else {
setTimeout(__loadKvmdInfo, 1000);
}

View File

@ -8,7 +8,7 @@ block body
div(class="start-box")
div(class="start")
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 ]
hr
block start

View File

@ -37,13 +37,15 @@
<link rel="stylesheet" href="../share/css/main.css">
<link rel="stylesheet" href="../share/css/start.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();
</script>
</head>
<body>
<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>
<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

View File

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