mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-01-29 00:51:53 +08:00
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
@@ -24,28 +24,6 @@ div#text-menu {
|
||||
width: 340px;
|
||||
}
|
||||
|
||||
textarea#hid-pak-text {
|
||||
display: block;
|
||||
resize: none;
|
||||
height: 120px;
|
||||
width: 100%;
|
||||
border: var(--border-default-thin);
|
||||
border-radius: 4px;
|
||||
color: var(--cs-code-default-fg);
|
||||
background-color: var(--cs-code-default-bg);
|
||||
-webkit-appearance:none;
|
||||
}
|
||||
|
||||
textarea#hid-pak-text::-moz-placeholder {
|
||||
line-height: 60px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
textarea#hid-pak-text::-webkit-input-placeholder {
|
||||
line-height: 60px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
input#hid-recorder-new-script-file {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -123,6 +123,26 @@ select {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding-left: 5px;
|
||||
}
|
||||
select[size] {
|
||||
height: auto;
|
||||
padding: 5px;
|
||||
}
|
||||
select[size]::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
select[size]::-webkit-scrollbar-thumb {
|
||||
border-radius: 4px;
|
||||
background: var(--cs-scroll-default-bg);
|
||||
}
|
||||
@-moz-document url-prefix() {
|
||||
select[size] {
|
||||
scrollbar-width: 8px;
|
||||
scrollbar-color: var(--cs-scroll-default-bg) var(--cs-code-default-bg);
|
||||
}
|
||||
}
|
||||
select:not([size]) {
|
||||
padding-right: 25px;
|
||||
}
|
||||
button.small {
|
||||
@@ -149,22 +169,24 @@ select {
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
select:not([size]) {
|
||||
background-image: url("../svg/select-arrow-normal.svg");
|
||||
background-position: center right;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
select:disabled {
|
||||
select:not([size]):disabled {
|
||||
background-image: url("../svg/select-arrow-inactive.svg") !important;
|
||||
}
|
||||
select:active {
|
||||
select:not([size]):active {
|
||||
color: var(--cs-control-intensive-fg) !important;
|
||||
background-image: url("../svg/select-arrow-intensive.svg") !important;
|
||||
}
|
||||
select option {
|
||||
select:not([size]) option {
|
||||
color: var(--cs-control-default-fg);
|
||||
background-color: var(--cs-control-default-bg);
|
||||
}
|
||||
select option.comment {
|
||||
select:not([size]) option.comment {
|
||||
color: var(--cs-control-disabled-fg);
|
||||
font-style: italic;
|
||||
}
|
||||
@@ -180,6 +202,26 @@ input[type=text], input[type=password] {
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
textarea {
|
||||
display: block;
|
||||
resize: none;
|
||||
height: 120px;
|
||||
width: 100%;
|
||||
border: var(--border-default-thin);
|
||||
border-radius: 4px;
|
||||
color: var(--cs-code-default-fg);
|
||||
background-color: var(--cs-code-default-bg);
|
||||
-webkit-appearance:none;
|
||||
}
|
||||
textarea::-moz-placeholder {
|
||||
line-height: 60px;
|
||||
text-align: center;
|
||||
}
|
||||
textarea::-webkit-input-placeholder {
|
||||
line-height: 60px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
div.buttons-row {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
@@ -218,6 +260,30 @@ div.buttons-row {
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
table.kv {
|
||||
border-spacing: 5px;
|
||||
margin: 0 10px 0 10px;
|
||||
font-size: 12px;
|
||||
}
|
||||
table.kv td {
|
||||
text-align: left;
|
||||
}
|
||||
table.kv td.value {
|
||||
font-weight: bold;
|
||||
max-width: 310px;
|
||||
overflow: hidden;
|
||||
}
|
||||
table.kv td.value-slider {
|
||||
width: 100%;
|
||||
}
|
||||
table.kv td.value-number {
|
||||
font-weight: bold;
|
||||
max-width: 310px;
|
||||
overflow: hidden;
|
||||
min-width: 40px;
|
||||
max-width: 40px;
|
||||
}
|
||||
|
||||
ul.footer {
|
||||
list-style-type: none;
|
||||
bottom: 0;
|
||||
@@ -231,10 +297,10 @@ ul.footer {
|
||||
ul.footer li {
|
||||
padding: 0 10px;
|
||||
}
|
||||
ul.footer li.footer-left {
|
||||
ul.footer li.left {
|
||||
float: left;
|
||||
}
|
||||
ul.footer li.footer-right {
|
||||
ul.footer li.right {
|
||||
float: right;
|
||||
}
|
||||
ul.footer li a {
|
||||
|
||||
@@ -167,30 +167,6 @@ ul#navbar li div.menu div.text {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
ul#navbar li div.menu table.kv {
|
||||
border-spacing: 5px;
|
||||
margin: 0 10px 0 10px;
|
||||
font-size: 12px;
|
||||
}
|
||||
ul#navbar li div.menu table.kv td {
|
||||
text-align: left;
|
||||
}
|
||||
ul#navbar li div.menu table.kv td.value {
|
||||
font-weight: bold;
|
||||
max-width: 310px;
|
||||
overflow: hidden;
|
||||
}
|
||||
ul#navbar li div.menu table.kv td.value-slider {
|
||||
width: 100%;
|
||||
}
|
||||
ul#navbar li div.menu table.kv td.value-number {
|
||||
font-weight: bold;
|
||||
max-width: 310px;
|
||||
overflow: hidden;
|
||||
min-width: 40px;
|
||||
max-width: 40px;
|
||||
}
|
||||
|
||||
ul#navbar li div.menu div.buttons button,
|
||||
ul#navbar li div.menu div.buttons select {
|
||||
border-radius: 0;
|
||||
@@ -222,3 +198,33 @@ ul#navbar li div.menu img.sign {
|
||||
margin-right: 10px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
ul.navbar-bg-tips {
|
||||
list-style-type: none;
|
||||
top: 50px;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
font-size: 0.7em;
|
||||
line-height: 1.5em;
|
||||
color: var(--cs-page-obscure-fg);
|
||||
z-index: -10;
|
||||
}
|
||||
ul.navbar-bg-tips li {
|
||||
padding: 0 10px;
|
||||
max-width: 20%;
|
||||
}
|
||||
ul.navbar-bg-tips li pre {
|
||||
word-break: break-word;
|
||||
white-space: break-spaces;
|
||||
text-align: justify;
|
||||
}
|
||||
ul.navbar-bg-tips li.left {
|
||||
float: left;
|
||||
}
|
||||
ul.navbar-bg-tips li.right {
|
||||
float: right;
|
||||
}
|
||||
ul.navbar-bg-tips li a {
|
||||
color: var(--cs-page-obscure-fg);
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
/* ===== main.css ===== */
|
||||
|
||||
button:enabled:hover,
|
||||
select:enabled:hover,
|
||||
select:not([size]):enabled:hover,
|
||||
input[type=file]:enabled:hover::-webkit-file-selector-button,
|
||||
input[type=file]:enabled:hover::file-selector-button {
|
||||
color: var(--cs-control-hovered-fg);
|
||||
@@ -31,7 +31,7 @@ input[type=file]:enabled:hover::file-selector-button {
|
||||
}
|
||||
|
||||
button:active,
|
||||
select:active,
|
||||
select:not([size]):active,
|
||||
input[type=file]:active::-webkit-file-selector-button,
|
||||
input[type=file]:active::file-selector-button {
|
||||
color: var(--cs-control-pressed-fg) !important;
|
||||
@@ -42,7 +42,7 @@ button.key:active,
|
||||
select.key:active {
|
||||
box-shadow: none;
|
||||
}
|
||||
select:enabled:hover {
|
||||
select:not([size]):enabled:hover {
|
||||
background-image: url("../svg/select-arrow-intensive.svg") !important;
|
||||
}
|
||||
|
||||
|
||||
@@ -113,6 +113,7 @@
|
||||
"kvm_text80":"Record video using the browser API, and will be downloaded automatically",
|
||||
"kvm_text81":"Start recording",
|
||||
"kvm_text82":"End recording",
|
||||
"kvm_text83":"Web UI settings",
|
||||
|
||||
"atx-ask-switch":"Ask click confirmation",
|
||||
"hid-recorder-loop-switch":"Infinite loop playback",
|
||||
@@ -140,5 +141,6 @@
|
||||
"msd-message-out-of-storage":"Current image is out of storage",
|
||||
"msd-message-rw-enabled":"Read-write mode is enabled",
|
||||
"msd-message-downloads":"The image is being downloaded from One-KVM",
|
||||
"msd-message-another-user-uploads":"Another user uploads an image"
|
||||
"msd-message-another-user-uploads":"Another user uploads an image",
|
||||
"page-full-tab-stream-switch":"Expand for the entire tab by default"
|
||||
}
|
||||
@@ -113,6 +113,7 @@
|
||||
"kvm_text80":"使用浏览器 API 录制视频,结束录制后视频文件会自动下载",
|
||||
"kvm_text81":"开始录制",
|
||||
"kvm_text82":"结束录制",
|
||||
"kvm_text83":"网页界面设置",
|
||||
|
||||
"atx-ask-switch":"点击二次确认",
|
||||
"hid-recorder-loop-switch":"无限循环重放",
|
||||
@@ -140,5 +141,6 @@
|
||||
"msd-message-out-of-storage":"当前镜像大小超出存储空间",
|
||||
"msd-message-rw-enabled":"读写模式以启用",
|
||||
"msd-message-downloads":"正在从 One-KVM 下载镜像",
|
||||
"msd-message-another-user-uploads":"另一个用户正在上传镜像"
|
||||
"msd-message-another-user-uploads":"另一个用户正在上传镜像",
|
||||
"page-full-tab-stream-switch":"自动全屏视频窗口"
|
||||
}
|
||||
@@ -38,13 +38,13 @@ export function main() {
|
||||
|
||||
|
||||
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;
|
||||
|
||||
let apps = [];
|
||||
if (info.extras === null) {
|
||||
wm.error("Not all applications in the menu can be displayed<br>due an error. See KVMD logs for details.");
|
||||
wm.error("Not all applications in the menu can be displayed due an error.<br>See KVMD logs for details.");
|
||||
} else {
|
||||
apps = Object.values(info.extras).sort(function(a, b) {
|
||||
if (a.place < b.place) {
|
||||
@@ -100,7 +100,7 @@ function __makeApp(id, path, icon, name) {
|
||||
<a href="${path}">
|
||||
<div>
|
||||
<img class="svg-gray" src="${icon}">
|
||||
${name}
|
||||
${tools.escape(name)}
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
@@ -108,11 +108,11 @@ function __makeApp(id, path, icon, name) {
|
||||
}
|
||||
|
||||
function __logout() {
|
||||
tools.httpPost("/api/auth/logout", function(http) {
|
||||
tools.httpPost("/api/auth/logout", null, function(http) {
|
||||
if (http.status === 200 || http.status === 401 || http.status === 403) {
|
||||
document.location.href = "/login";
|
||||
} else {
|
||||
wm.error("Logout error:<br>", http.responseText);
|
||||
wm.error("Logout error", http.responseText);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ export function main() {
|
||||
}
|
||||
|
||||
function __loadKvmdInfo() {
|
||||
tools.httpGet("/api/info", 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) => `
|
||||
|
||||
@@ -32,59 +32,77 @@ export function Atx(__recorder) {
|
||||
|
||||
/************************************************************************/
|
||||
|
||||
var __state = null;
|
||||
|
||||
var __init__ = function() {
|
||||
$("atx-power-led").title = "Power Led";
|
||||
$("atx-hdd-led").title = "Disk Activity Led";
|
||||
|
||||
tools.storage.bindSimpleSwitch($("atx-ask-switch"), "atx.ask", true);
|
||||
|
||||
for (let args of [
|
||||
["atx-power-button", "power", "Are you sure you want to press the power button?"],
|
||||
["atx-power-button-long", "power_long", `
|
||||
Are you sure you want to long press the power button?<br>
|
||||
Warning! This could cause data loss on the server.
|
||||
`],
|
||||
["atx-reset-button", "reset", `
|
||||
Are you sure you want to press the reset button?<br>
|
||||
Warning! This could case data loss on the server.
|
||||
`],
|
||||
]) {
|
||||
tools.el.setOnClick($(args[0]), () => __clickButton(args[1], args[2]));
|
||||
}
|
||||
tools.el.setOnClick($("atx-power-button"), () => __clickAtx("power"));
|
||||
tools.el.setOnClick($("atx-power-button-long"), () => __clickAtx("power_long"));
|
||||
tools.el.setOnClick($("atx-reset-button"), () => __clickAtx("reset"));
|
||||
};
|
||||
|
||||
/************************************************************************/
|
||||
|
||||
self.setState = function(state) {
|
||||
let buttons_enabled = false;
|
||||
if (state) {
|
||||
tools.feature.setEnabled($("atx-dropdown"), state.enabled);
|
||||
$("atx-power-led").className = (state.busy ? "led-yellow" : (state.leds.power ? "led-green" : "led-gray"));
|
||||
$("atx-hdd-led").className = (state.leds.hdd ? "led-red" : "led-gray");
|
||||
buttons_enabled = !state.busy;
|
||||
if (!__state) {
|
||||
__state = {"leds": {}};
|
||||
}
|
||||
if (state.enabled !== undefined) {
|
||||
__state.enabled = state.enabled;
|
||||
tools.feature.setEnabled($("atx-dropdown"), __state.enabled);
|
||||
}
|
||||
if (__state.enabled !== undefined) {
|
||||
if (state.busy !== undefined) {
|
||||
__state.busy = state.busy;
|
||||
__updateButtons(!__state.busy);
|
||||
}
|
||||
if (state.leds !== undefined) {
|
||||
__state.leds = state.leds;
|
||||
}
|
||||
if (state.busy !== undefined || state.leds !== undefined) {
|
||||
__updateLeds(__state.leds.power, __state.leds.hdd, __state.busy);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$("atx-power-led").className = "led-gray";
|
||||
$("atx-hdd-led").className = "led-gray";
|
||||
}
|
||||
for (let id of ["atx-power-button", "atx-power-button-long", "atx-reset-button"]) {
|
||||
tools.el.setEnabled($(id), buttons_enabled);
|
||||
__state = null;
|
||||
__updateLeds(false, false, false);
|
||||
__updateButtons(false);
|
||||
}
|
||||
};
|
||||
|
||||
var __clickButton = function(button, confirm_msg) {
|
||||
var __updateLeds = function(power, hdd, busy) {
|
||||
$("atx-power-led").className = (busy ? "led-yellow" : (power ? "led-green" : "led-gray"));
|
||||
$("atx-hdd-led").className = (hdd ? "led-red" : "led-gray");
|
||||
};
|
||||
|
||||
var __updateButtons = function(enabled) {
|
||||
for (let id of ["atx-power-button", "atx-power-button-long", "atx-reset-button"]) {
|
||||
tools.el.setEnabled($(id), enabled);
|
||||
}
|
||||
};
|
||||
|
||||
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");
|
||||
wm.error("Performing another ATX operation for other client.<br>Please try again later.");
|
||||
} else if (http.status !== 200) {
|
||||
wm.error("Click error:<br>", http.responseText);
|
||||
wm.error("Click error", http.responseText);
|
||||
}
|
||||
});
|
||||
__recorder.recordAtxButtonEvent(button);
|
||||
};
|
||||
|
||||
if ($("atx-ask-switch").checked) {
|
||||
wm.confirm(confirm_msg).then(function(ok) {
|
||||
wm.confirm(`
|
||||
Are you sure you want to press the <b>${button}</b> button?<br>
|
||||
Warning! This could case data loss on the server.
|
||||
`).then(function(ok) {
|
||||
if (ok) {
|
||||
click_button();
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
"use strict";
|
||||
|
||||
|
||||
import {tools, $, $$$} from "../tools.js";
|
||||
import {tools, $, $$} from "../tools.js";
|
||||
import {wm} from "../wm.js";
|
||||
|
||||
|
||||
@@ -32,44 +32,59 @@ export function Gpio(__recorder) {
|
||||
|
||||
/************************************************************************/
|
||||
|
||||
var __state = null;
|
||||
var __has_model = false;
|
||||
|
||||
/************************************************************************/
|
||||
|
||||
self.setState = function(state) {
|
||||
if (state) {
|
||||
for (let channel in state.inputs) {
|
||||
let el = $(`gpio-led-${channel}`);
|
||||
if (el) {
|
||||
__setLedState(el, state.inputs[channel].state);
|
||||
}
|
||||
if (state.model !== undefined) {
|
||||
__has_model = true;
|
||||
__updateModel(state.model);
|
||||
}
|
||||
for (let channel in state.outputs) {
|
||||
for (let type of ["switch", "button"]) {
|
||||
let el = $(`gpio-${type}-${channel}`);
|
||||
if (el) {
|
||||
tools.el.setEnabled(el, state.outputs[channel].online && !state.outputs[channel].busy);
|
||||
}
|
||||
if (__has_model && state.state !== undefined) {
|
||||
if (state.state.inputs !== undefined) {
|
||||
__updateInputs(state.state.inputs);
|
||||
}
|
||||
let el = $(`gpio-switch-${channel}`);
|
||||
if (el) {
|
||||
el.checked = state.outputs[channel].state;
|
||||
if (state.state.outputs !== undefined) {
|
||||
__updateOutputs(state.state.outputs);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (let el of $$$(".gpio-led")) {
|
||||
__has_model = false;
|
||||
for (let el of $$("__gpio-led")) {
|
||||
__setLedState(el, false);
|
||||
}
|
||||
for (let selector of [".gpio-switch", ".gpio-button"]) {
|
||||
for (let el of $$$(selector)) {
|
||||
for (let selector of ["__gpio-switch", "__gpio-button"]) {
|
||||
for (let el of $$(selector)) {
|
||||
tools.el.setEnabled(el, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
__state = state;
|
||||
};
|
||||
|
||||
self.setModel = function(model) {
|
||||
var __updateInputs = function(inputs) {
|
||||
for (let ch in inputs) {
|
||||
for (let el of $$(`__gpio-led-${ch}`)) {
|
||||
__setLedState(el, inputs[ch].state);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var __updateOutputs = function(outputs) {
|
||||
for (let ch in outputs) {
|
||||
for (let type of ["switch", "button"]) {
|
||||
for (let el of $$(`__gpio-${type}-${ch}`)) {
|
||||
tools.el.setEnabled(el, (outputs[ch].online && !outputs[ch].busy));
|
||||
}
|
||||
}
|
||||
for (let el of $$(`__gpio-switch-${ch}`)) {
|
||||
el.checked = outputs[ch].state;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var __updateModel = function(model) {
|
||||
tools.feature.setEnabled($("gpio-dropdown"), model.view.table.length);
|
||||
if (model.view.table.length) {
|
||||
let title = [];
|
||||
@@ -84,44 +99,36 @@ export function Gpio(__recorder) {
|
||||
$("gpio-menu-button").innerHTML = title.join(" ");
|
||||
}
|
||||
|
||||
let content = "<table class=\"kv\">";
|
||||
let html = "<table class=\"kv\">";
|
||||
for (let row of model.view.table) {
|
||||
if (row === null) {
|
||||
content += "</table><hr><table class=\"kv\">";
|
||||
html += "</table><hr><table class=\"kv\">";
|
||||
} else {
|
||||
content += "<tr>";
|
||||
html += "<tr>";
|
||||
for (let item of row) {
|
||||
if (item.type === "output") {
|
||||
item.scheme = model.scheme.outputs[item.channel];
|
||||
}
|
||||
content += `<td align="center">${__createItem(item)}</td>`;
|
||||
html += `<td align="center">${__createItem(item)}</td>`;
|
||||
}
|
||||
content += "</tr>";
|
||||
html += "</tr>";
|
||||
}
|
||||
}
|
||||
content += "</table>";
|
||||
$("gpio-menu").innerHTML = content;
|
||||
html += "</table>";
|
||||
$("gpio-menu").innerHTML = html;
|
||||
|
||||
for (let channel in model.scheme.outputs) {
|
||||
let el = $(`gpio-switch-${channel}`);
|
||||
if (el) {
|
||||
tools.el.setOnClick(el, __createAction(el, __switchChannel));
|
||||
for (let ch in model.scheme.outputs) {
|
||||
for (let el of $$(`__gpio-switch-${ch}`)) {
|
||||
tools.el.setOnClick(el, tools.partial(__switchChannel, el));
|
||||
}
|
||||
el = $(`gpio-button-${channel}`);
|
||||
if (el) {
|
||||
tools.el.setOnClick(el, __createAction(el, __pulseChannel));
|
||||
for (let el of $$(`__gpio-button-${ch}`)) {
|
||||
tools.el.setOnClick(el, tools.partial(__pulseChannel, el));
|
||||
}
|
||||
}
|
||||
|
||||
tools.feature.setEnabled($("v3-usb-breaker"), ("__v3_usb_breaker__" in model.scheme.outputs));
|
||||
tools.feature.setEnabled($("v4-locator"), ("__v4_locator__" in model.scheme.outputs));
|
||||
tools.feature.setEnabled($("system-tool-wol"), ("__wol__" in model.scheme.outputs));
|
||||
|
||||
self.setState(__state);
|
||||
};
|
||||
|
||||
var __createAction = function(el, action) {
|
||||
return () => action(el);
|
||||
};
|
||||
|
||||
var __createItem = function(item) {
|
||||
@@ -129,18 +136,28 @@ export function Gpio(__recorder) {
|
||||
return item.text;
|
||||
} else if (item.type === "input") {
|
||||
return `
|
||||
<img id="gpio-led-${item.channel}" class="gpio-led inline-lamp-big led-gray"
|
||||
src="/share/svg/led-circle.svg" data-color="${item.color}" />
|
||||
<img
|
||||
class="__gpio-led __gpio-led-${item.channel} inline-lamp-big led-gray"
|
||||
src="/share/svg/led-circle.svg"
|
||||
data-color="${item.color}"
|
||||
/>
|
||||
`;
|
||||
} else if (item.type === "output") {
|
||||
let controls = [];
|
||||
let confirm = (item.confirm ? "Are you sure you want to perform this action?" : "");
|
||||
if (item.scheme["switch"]) {
|
||||
let id = tools.makeId();
|
||||
controls.push(`
|
||||
<td><div class="switch-box">
|
||||
<input disabled type="checkbox" id="gpio-switch-${item.channel}" class="gpio-switch"
|
||||
data-channel="${item.channel}" data-confirm="${confirm}" />
|
||||
<label for="gpio-switch-${item.channel}">
|
||||
<input
|
||||
disabled
|
||||
type="checkbox"
|
||||
id="__gpio-switch-${id}"
|
||||
class="__gpio-switch __gpio-switch-${item.channel}"
|
||||
data-channel="${item.channel}"
|
||||
data-confirm="${confirm}"
|
||||
/>
|
||||
<label for="__gpio-switch-${id}">
|
||||
<span class="switch-inner"></span>
|
||||
<span class="switch"></span>
|
||||
</label>
|
||||
@@ -149,10 +166,14 @@ export function Gpio(__recorder) {
|
||||
}
|
||||
if (item.scheme.pulse.delay) {
|
||||
controls.push(`
|
||||
<td><button disabled id="gpio-button-${item.channel}" class="gpio-button"
|
||||
${item.hide ? "data-force-hide-menu" : ""}
|
||||
data-channel="${item.channel}" data-confirm="${confirm}">
|
||||
${(item.hide ? "• " : "") + item.text}
|
||||
<td><button
|
||||
disabled
|
||||
class="__gpio-button __gpio-button-${item.channel}"
|
||||
${item.hide ? "data-force-hide-menu" : ""}
|
||||
data-channel="${item.channel}"
|
||||
data-confirm="${confirm}"
|
||||
>
|
||||
${(item.hide ? "• " : "") + item.text}
|
||||
</button></td>
|
||||
`);
|
||||
}
|
||||
@@ -162,9 +183,9 @@ export function Gpio(__recorder) {
|
||||
}
|
||||
};
|
||||
|
||||
var __setLedState = function(el, state) {
|
||||
var __setLedState = function(el, on) {
|
||||
let color = el.getAttribute("data-color");
|
||||
if (state) {
|
||||
if (on) {
|
||||
el.classList.add(`led-${color}`);
|
||||
el.classList.remove("led-gray");
|
||||
} else {
|
||||
@@ -174,22 +195,20 @@ export function Gpio(__recorder) {
|
||||
};
|
||||
|
||||
var __switchChannel = function(el) {
|
||||
let channel = el.getAttribute("data-channel");
|
||||
let ch = el.getAttribute("data-channel");
|
||||
let confirm = el.getAttribute("data-confirm");
|
||||
let to = ($(`gpio-switch-${channel}`).checked ? "1" : "0");
|
||||
let to = (el.checked ? "1" : "0");
|
||||
if (to === "0" && el.hasAttribute("data-confirm-off")) {
|
||||
confirm = el.getAttribute("data-confirm-off");
|
||||
}
|
||||
let act = () => {
|
||||
__sendPost(`/api/gpio/switch?channel=${channel}&state=${to}`);
|
||||
__recorder.recordGpioSwitchEvent(channel, to);
|
||||
__sendPost("/api/gpio/switch", {"channel": ch, "state": to});
|
||||
__recorder.recordGpioSwitchEvent(ch, to);
|
||||
};
|
||||
if (confirm) {
|
||||
wm.confirm(confirm).then(function(ok) {
|
||||
wm.confirm(tools.escape(confirm)).then(function(ok) {
|
||||
if (ok) {
|
||||
act();
|
||||
} else {
|
||||
self.setState(__state); // Switch back
|
||||
}
|
||||
});
|
||||
} else {
|
||||
@@ -198,25 +217,29 @@ export function Gpio(__recorder) {
|
||||
};
|
||||
|
||||
var __pulseChannel = function(el) {
|
||||
let channel = el.getAttribute("data-channel");
|
||||
let ch = el.getAttribute("data-channel");
|
||||
let confirm = el.getAttribute("data-confirm");
|
||||
let act = () => {
|
||||
__sendPost(`/api/gpio/pulse?channel=${channel}`);
|
||||
__recorder.recordGpioPulseEvent(channel);
|
||||
__sendPost("/api/gpio/pulse", {"channel": ch});
|
||||
__recorder.recordGpioPulseEvent(ch);
|
||||
};
|
||||
if (confirm) {
|
||||
wm.confirm(confirm).then(function(ok) { if (ok) act(); });
|
||||
wm.confirm(tools.escape(confirm)).then(function(ok) {
|
||||
if (ok) {
|
||||
act();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
act();
|
||||
}
|
||||
};
|
||||
|
||||
var __sendPost = function(url) {
|
||||
tools.httpPost(url, function(http) {
|
||||
var __sendPost = function(url, params) {
|
||||
tools.httpPost(url, params, function(http) {
|
||||
if (http.status === 409) {
|
||||
wm.error("Performing another operation for this GPIO channel.<br>Please try again later");
|
||||
wm.error("Performing another operation for this GPIO channel.<br>Please try again later.");
|
||||
} else if (http.status !== 200) {
|
||||
wm.error("GPIO error:<br>", http.responseText);
|
||||
wm.error("GPIO error", http.responseText);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -35,6 +35,7 @@ export function Hid(__getGeometry, __recorder) {
|
||||
|
||||
/************************************************************************/
|
||||
|
||||
var __state = null;
|
||||
var __keyboard = null;
|
||||
var __mouse = null;
|
||||
|
||||
@@ -71,21 +72,6 @@ export function Hid(__getGeometry, __recorder) {
|
||||
window.addEventListener("pagehide", __releaseAll);
|
||||
window.addEventListener("blur", __releaseAll);
|
||||
|
||||
tools.storage.bindSimpleSwitch($("hid-pak-ask-switch"), "hid.pak.ask", true);
|
||||
tools.storage.bindSimpleSwitch($("hid-pak-secure-switch"), "hid.pak.secure", false, function(value) {
|
||||
$("hid-pak-text").style.setProperty("-webkit-text-security", (value ? "disc" : "none"));
|
||||
});
|
||||
tools.feature.setEnabled($("hid-pak-secure"), (
|
||||
tools.browser.is_chrome
|
||||
|| tools.browser.is_safari
|
||||
|| tools.browser.is_opera
|
||||
));
|
||||
|
||||
$("hid-pak-keymap-selector").addEventListener("change", function() {
|
||||
tools.storage.set("hid.pak.keymap", $("hid-pak-keymap-selector").value);
|
||||
});
|
||||
|
||||
tools.el.setOnClick($("hid-pak-button"), __clickPasteAsKeysButton);
|
||||
tools.el.setOnClick($("hid-connect-switch"), __clickConnectSwitch);
|
||||
tools.el.setOnClick($("hid-reset-button"), __clickResetButton);
|
||||
|
||||
@@ -98,8 +84,7 @@ export function Hid(__getGeometry, __recorder) {
|
||||
}
|
||||
let codes = el_shortcut.getAttribute("data-shortcut").split(" ");
|
||||
if (ask) {
|
||||
let confirm_msg = `Do you want to press <b>${codes.join(" + ")}</b>?`;
|
||||
wm.confirm(confirm_msg).then(function(ok) {
|
||||
wm.confirm("Do you want to press this hotkey?", codes.join(" + ")).then(function(ok) {
|
||||
if (ok) {
|
||||
__emitShortcut(codes);
|
||||
}
|
||||
@@ -118,10 +103,6 @@ export function Hid(__getGeometry, __recorder) {
|
||||
/************************************************************************/
|
||||
|
||||
self.setSocket = function(ws) {
|
||||
tools.el.setEnabled($("hid-pak-text"), ws);
|
||||
tools.el.setEnabled($("hid-pak-button"), ws);
|
||||
tools.el.setEnabled($("hid-reset-button"), ws);
|
||||
tools.el.setEnabled($("hid-jiggler-switch"), ws);
|
||||
if (!ws) {
|
||||
self.setState(null);
|
||||
}
|
||||
@@ -130,84 +111,135 @@ export function Hid(__getGeometry, __recorder) {
|
||||
};
|
||||
|
||||
self.setState = function(state) {
|
||||
let has_relative_squash = false;
|
||||
|
||||
if (state) {
|
||||
tools.feature.setEnabled($("hid-jiggler"), state.jiggler.enabled);
|
||||
$("hid-jiggler-switch").checked = state.jiggler.active;
|
||||
}
|
||||
if (state && state.online) {
|
||||
let keyboard_outputs = state.keyboard.outputs.available;
|
||||
let mouse_outputs = state.mouse.outputs.available;
|
||||
if (keyboard_outputs.length) {
|
||||
if ($("hid-outputs-keyboard-box").outputs !== keyboard_outputs) {
|
||||
let html = "";
|
||||
for (let args of [
|
||||
["USB", "usb"],
|
||||
["PS/2", "ps2"],
|
||||
["Off", "disabled"],
|
||||
]) {
|
||||
if (keyboard_outputs.includes(args[1])) {
|
||||
html += tools.radio.makeItem("hid-outputs-keyboard-radio", args[0], args[1]);
|
||||
}
|
||||
}
|
||||
$("hid-outputs-keyboard-box").innerHTML = html;
|
||||
$("hid-outputs-keyboard-box").outputs = keyboard_outputs;
|
||||
tools.radio.setOnClick("hid-outputs-keyboard-radio", () => __clickOutputsRadio("keyboard"));
|
||||
}
|
||||
tools.radio.setValue("hid-outputs-keyboard-radio", state.keyboard.outputs.active);
|
||||
if (!__state) {
|
||||
__state = {"keyboard": {}, "mouse": {}};
|
||||
}
|
||||
let has_relative = false;
|
||||
if (mouse_outputs.length) {
|
||||
if ($("hid-outputs-mouse-box").outputs !== mouse_outputs) {
|
||||
let html = "";
|
||||
for (let args of [
|
||||
["Absolute", "usb", false],
|
||||
["Abs-Win98", "usb_win98", false],
|
||||
["Relative", "usb_rel", true],
|
||||
["PS/2", "ps2", true],
|
||||
["Off", "disabled"],
|
||||
]) {
|
||||
if (mouse_outputs.includes(args[1])) {
|
||||
html += tools.radio.makeItem("hid-outputs-mouse-radio", args[0], args[1]);
|
||||
has_relative = (has_relative || args[2]);
|
||||
}
|
||||
}
|
||||
$("hid-outputs-mouse-box").innerHTML = html;
|
||||
$("hid-outputs-mouse-box").outputs = mouse_outputs;
|
||||
tools.radio.setOnClick("hid-outputs-mouse-radio", () => __clickOutputsRadio("mouse"));
|
||||
}
|
||||
tools.radio.setValue("hid-outputs-mouse-radio", state.mouse.outputs.active);
|
||||
has_relative_squash = ["usb_rel", "ps2"].includes(state.mouse.outputs.active);
|
||||
} else {
|
||||
has_relative = !state.mouse.absolute;
|
||||
has_relative_squash = has_relative;
|
||||
if (state.enabled !== undefined) {
|
||||
__state.enabled = state.enabled; // Currently unused, always true
|
||||
}
|
||||
tools.feature.setEnabled($("hid-outputs"), (keyboard_outputs.length || mouse_outputs.length));
|
||||
tools.feature.setEnabled($("hid-outputs-keyboard"), keyboard_outputs.length);
|
||||
tools.feature.setEnabled($("hid-outputs-mouse"), mouse_outputs.length);
|
||||
tools.feature.setEnabled($("hid-mouse-squash"), has_relative);
|
||||
tools.feature.setEnabled($("hid-mouse-sens"), has_relative);
|
||||
tools.feature.setEnabled($("hid-connect"), (state.connected !== null));
|
||||
$("hid-connect-switch").checked = !!state.connected;
|
||||
}
|
||||
|
||||
tools.radio.setEnabled("hid-outputs-keyboard-radio", (state && state.online && !state.busy));
|
||||
tools.radio.setEnabled("hid-outputs-mouse-radio", (state && state.online && !state.busy));
|
||||
tools.el.setEnabled($("hid-mouse-squash-switch"), (has_relative_squash && !state.busy));
|
||||
tools.el.setEnabled($("hid-mouse-sens-slider"), (has_relative_squash && !state.busy));
|
||||
tools.el.setEnabled($("hid-connect-switch"), (state && state.online && !state.busy));
|
||||
|
||||
if (state) {
|
||||
__keyboard.setState(state.keyboard, state.online, state.busy);
|
||||
__mouse.setState(state.mouse, state.online, state.busy);
|
||||
if (__state.enabled !== undefined) {
|
||||
for (let key of ["online", "busy", "connected", "jiggler"]) {
|
||||
if (state[key] !== undefined) {
|
||||
__state[key] = state[key];
|
||||
}
|
||||
}
|
||||
for (let hid of ["keyboard", "mouse"]) {
|
||||
if (state[hid] === undefined) {
|
||||
state[hid] = {}; // Add some stubs for processing
|
||||
}
|
||||
for (let key of ["online", "outputs", (hid === "keyboard" ? "leds" : "absolute")]) {
|
||||
__state[hid][key] = state[hid][key];
|
||||
}
|
||||
}
|
||||
if (state.connected !== undefined) {
|
||||
tools.feature.setEnabled($("hid-connect"), (__state.connected !== null));
|
||||
$("hid-connect-switch").checked = !!__state.connected;
|
||||
}
|
||||
if (state.jiggler !== undefined) {
|
||||
tools.feature.setEnabled($("hid-jiggler"), __state.jiggler.enabled);
|
||||
$("hid-jiggler-switch").checked = __state.jiggler.active;
|
||||
}
|
||||
if (state.keyboard.outputs !== undefined) {
|
||||
__updateKeyboardOutputs(__state.keyboard.outputs);
|
||||
}
|
||||
if (state.mouse.outputs !== undefined) {
|
||||
__updateMouseOutputs(__state.mouse.outputs, __state.mouse.absolute); // Follows together
|
||||
}
|
||||
if (
|
||||
state.keyboard.online !== undefined || state.keyboard.leds !== undefined
|
||||
|| state.online !== undefined || state.busy !== undefined
|
||||
) {
|
||||
__keyboard.setState(__state.keyboard.online, __state.keyboard.leds, __state.online, __state.busy);
|
||||
}
|
||||
if (
|
||||
state.mouse.online !== undefined || state.mouse.absolute !== undefined
|
||||
|| state.online !== undefined || state.busy !== undefined
|
||||
) {
|
||||
__mouse.setState(__state.mouse.online, __state.mouse.absolute, __state.online, __state.busy);
|
||||
}
|
||||
if (state.online !== undefined || state.busy !== undefined) {
|
||||
tools.radio.setEnabled("hid-outputs-keyboard-radio", (__state.online && !__state.busy));
|
||||
tools.radio.setEnabled("hid-outputs-mouse-radio", (__state.online && !__state.busy));
|
||||
tools.el.setEnabled($("hid-connect-switch"), (__state.online && !__state.busy));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
__state = null;
|
||||
tools.radio.setEnabled("hid-outputs-keyboard-radio", false);
|
||||
tools.radio.setEnabled("hid-outputs-mouse-radio", false);
|
||||
tools.el.setEnabled($("hid-connect-switch"), false);
|
||||
tools.el.setEnabled($("hid-mouse-squash-switch"), false);
|
||||
tools.el.setEnabled($("hid-mouse-sens-slider"), false);
|
||||
}
|
||||
tools.el.setEnabled($("hid-reset-button"), __state);
|
||||
tools.el.setEnabled($("hid-jiggler-switch"), __state);
|
||||
};
|
||||
|
||||
self.setKeymaps = function(state) {
|
||||
let el = $("hid-pak-keymap-selector");
|
||||
tools.selector.setValues(el, state.keymaps.available);
|
||||
tools.selector.setSelectedValue(el, tools.storage.get("hid.pak.keymap", state.keymaps["default"]));
|
||||
var __updateKeyboardOutputs = function(outputs) {
|
||||
let avail = outputs.available;
|
||||
if (avail.length > 0) {
|
||||
let el = $("hid-outputs-keyboard-box");
|
||||
let avail_json = JSON.stringify(avail);
|
||||
if (el.__avail_json !== avail_json) {
|
||||
let html = "";
|
||||
for (let pair of [
|
||||
["USB", "usb"],
|
||||
["PS/2", "ps2"],
|
||||
["Off", "disabled"],
|
||||
]) {
|
||||
if (avail.includes(pair[1])) {
|
||||
html += tools.radio.makeItem("hid-outputs-keyboard-radio", pair[0], pair[1]);
|
||||
}
|
||||
}
|
||||
el.innerHTML = html;
|
||||
tools.radio.setOnClick("hid-outputs-keyboard-radio", () => __clickOutputsRadio("keyboard"));
|
||||
el.__avail_json = avail_json;
|
||||
}
|
||||
tools.radio.setValue("hid-outputs-keyboard-radio", outputs.active);
|
||||
}
|
||||
tools.feature.setEnabled($("hid-outputs-keyboard"), (avail.length > 0));
|
||||
};
|
||||
|
||||
var __updateMouseOutputs = function(outputs, absolute) {
|
||||
let has_relative = null;
|
||||
let has_relative_squash = null;
|
||||
let avail = outputs.available;
|
||||
if (avail.length > 0) {
|
||||
let el = $("hid-outputs-mouse-box");
|
||||
let avail_json = JSON.stringify(avail);
|
||||
if (el.__avail_json !== avail_json) {
|
||||
has_relative = false;
|
||||
let html = "";
|
||||
for (let pair of [
|
||||
["Absolute", "usb", false],
|
||||
["Abs-Win98", "usb_win98", false],
|
||||
["Relative", "usb_rel", true],
|
||||
["PS/2", "ps2", true],
|
||||
["Off", "disabled", false],
|
||||
]) {
|
||||
if (avail.includes(pair[1])) {
|
||||
html += tools.radio.makeItem("hid-outputs-mouse-radio", pair[0], pair[1]);
|
||||
has_relative = (has_relative || pair[2]);
|
||||
}
|
||||
}
|
||||
el.innerHTML = html;
|
||||
tools.radio.setOnClick("hid-outputs-mouse-radio", () => __clickOutputsRadio("mouse"));
|
||||
el.__avail_json = avail_json;
|
||||
}
|
||||
tools.radio.setValue("hid-outputs-mouse-radio", outputs.active);
|
||||
has_relative_squash = (["usb_rel", "ps2"].includes(outputs.active));
|
||||
} else {
|
||||
has_relative = !absolute;
|
||||
has_relative_squash = has_relative;
|
||||
}
|
||||
if (has_relative !== null) {
|
||||
tools.feature.setEnabled($("hid-mouse-squash"), has_relative);
|
||||
tools.feature.setEnabled($("hid-mouse-sens"), has_relative);
|
||||
}
|
||||
tools.feature.setEnabled($("hid-outputs-mouse"), (avail.length > 0));
|
||||
tools.el.setEnabled($("hid-mouse-squash-switch"), has_relative_squash);
|
||||
tools.el.setEnabled($("hid-mouse-sens-slider"), has_relative_squash);
|
||||
};
|
||||
|
||||
var __releaseAll = function() {
|
||||
@@ -241,72 +273,29 @@ export function Hid(__getGeometry, __recorder) {
|
||||
});
|
||||
};
|
||||
|
||||
var __clickPasteAsKeysButton = function() {
|
||||
let text = $("hid-pak-text").value;
|
||||
if (text) {
|
||||
let paste_as_keys = function() {
|
||||
tools.el.setEnabled($("hid-pak-text"), false);
|
||||
tools.el.setEnabled($("hid-pak-button"), false);
|
||||
tools.el.setEnabled($("hid-pak-keymap-selector"), false);
|
||||
|
||||
let keymap = $("hid-pak-keymap-selector").value;
|
||||
|
||||
tools.debug(`HID: paste-as-keys ${keymap}: ${text}`);
|
||||
|
||||
tools.httpPost(`/api/hid/print?limit=0&keymap=${keymap}`, function(http) {
|
||||
tools.el.setEnabled($("hid-pak-text"), true);
|
||||
tools.el.setEnabled($("hid-pak-button"), true);
|
||||
tools.el.setEnabled($("hid-pak-keymap-selector"), true);
|
||||
$("hid-pak-text").value = "";
|
||||
if (http.status === 413) {
|
||||
wm.error("Too many text for paste!");
|
||||
} else if (http.status !== 200) {
|
||||
wm.error("HID paste error:<br>", http.responseText);
|
||||
} else if (http.status === 200) {
|
||||
__recorder.recordPrintEvent(text);
|
||||
}
|
||||
}, text, "text/plain");
|
||||
};
|
||||
|
||||
if ($("hid-pak-ask-switch").checked) {
|
||||
let confirm_msg = `You're going to paste ${text.length} character${text.length ? "s" : ""}.<br>`;
|
||||
confirm_msg += "Are you sure you want to continue?";
|
||||
wm.confirm(confirm_msg).then(function(ok) {
|
||||
if (ok) {
|
||||
paste_as_keys();
|
||||
} else {
|
||||
$("hid-pak-text").value = "";
|
||||
}
|
||||
});
|
||||
} else {
|
||||
paste_as_keys();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
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:<br>", http.responseText);
|
||||
wm.error("Can't configure HID", http.responseText);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
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 juggler:<br>`, http.responseText);
|
||||
wm.error(`Can't ${enabled ? "enabled" : "disable"} mouse jiggler`, http.responseText);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
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:<br>`, http.responseText);
|
||||
wm.error(`Can't ${connected ? "connect" : "disconnect"} HID`, http.responseText);
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -314,9 +303,9 @@ 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", function(http) {
|
||||
tools.httpPost("/api/hid/reset", null, function(http) {
|
||||
if (http.status !== 200) {
|
||||
wm.error("HID reset error:<br>", http.responseText);
|
||||
wm.error("HID reset error", http.responseText);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -65,17 +65,17 @@ export function Keyboard(__recordWsEvent) {
|
||||
__updateOnlineLeds();
|
||||
};
|
||||
|
||||
self.setState = function(state, hid_online, hid_busy) {
|
||||
self.setState = function(online, leds, hid_online, hid_busy) {
|
||||
if (!hid_online) {
|
||||
__online = null;
|
||||
} else {
|
||||
__online = (state.online && !hid_busy);
|
||||
__online = (online && !hid_busy);
|
||||
}
|
||||
__updateOnlineLeds();
|
||||
|
||||
for (let led of ["caps", "scroll", "num"]) {
|
||||
for (let el of $$$(`.hid-keyboard-${led}-led`)) {
|
||||
if (state.leds[led]) {
|
||||
if (leds[led]) {
|
||||
el.classList.add("led-green");
|
||||
el.classList.remove("led-gray");
|
||||
} else {
|
||||
|
||||
@@ -50,9 +50,14 @@ export function main() {
|
||||
|
||||
tools.el.setOnClick($("open-log-button"), () => window.open("/api/log?seek=3600&follow=1", "_blank"));
|
||||
|
||||
if (tools.config.getBool("kvm--full-tab-stream", false)) {
|
||||
wm.toggleFullTabWindow($("stream-window"), true);
|
||||
tools.storage.bindSimpleSwitch(
|
||||
$("page-full-tab-stream-switch"),
|
||||
"page.full_tab_stream",
|
||||
tools.config.getBool("kvm--full-tab-stream", false));
|
||||
if ($("page-full-tab-stream-switch").checked) {
|
||||
wm.setFullTabWindow($("stream-window"), true);
|
||||
}
|
||||
|
||||
wm.showWindow($("stream-window"));
|
||||
|
||||
new Session();
|
||||
|
||||
@@ -90,20 +90,20 @@ export function Mouse(__getGeometry, __recordWsEvent) {
|
||||
__updateOnlineLeds();
|
||||
};
|
||||
|
||||
self.setState = function(state, hid_online, hid_busy) {
|
||||
self.setState = function(online, absolute, hid_online, hid_busy) {
|
||||
if (!hid_online) {
|
||||
__online = null;
|
||||
} else {
|
||||
__online = (state.online && !hid_busy);
|
||||
__online = (online && !hid_busy);
|
||||
}
|
||||
if (!__absolute && state.absolute && __isRelativeCaptured()) {
|
||||
if (!__absolute && absolute && __isRelativeCaptured()) {
|
||||
document.exitPointerLock();
|
||||
}
|
||||
if (__absolute && !state.absolute) {
|
||||
if (__absolute && !absolute) {
|
||||
__relative_deltas = [];
|
||||
__relative_touch_pos = null;
|
||||
}
|
||||
__absolute = state.absolute;
|
||||
__absolute = absolute;
|
||||
__updateOnlineLeds();
|
||||
};
|
||||
|
||||
|
||||
@@ -35,10 +35,6 @@ export function Msd() {
|
||||
var __state = null;
|
||||
var __http = null;
|
||||
|
||||
var __parts_names_json = "";
|
||||
var __parts_names_len = 0;
|
||||
var __parts = {};
|
||||
|
||||
var __init__ = function() {
|
||||
$("msd-led").title = "Unknown state";
|
||||
|
||||
@@ -68,8 +64,201 @@ export function Msd() {
|
||||
/************************************************************************/
|
||||
|
||||
self.setState = function(state) {
|
||||
__state = state;
|
||||
__applyState();
|
||||
if (state) {
|
||||
if (!__state) {
|
||||
__state = {"storage": {}};
|
||||
}
|
||||
if (state.enabled !== undefined) {
|
||||
__state.enabled = state.enabled;
|
||||
tools.feature.setEnabled($("msd-dropdown"), __state.enabled);
|
||||
}
|
||||
if (__state.enabled !== undefined) {
|
||||
if (state.online !== undefined) {
|
||||
__state.online = state.online;
|
||||
}
|
||||
if (state.busy !== undefined) {
|
||||
__state.busy = state.busy;
|
||||
}
|
||||
if (state.drive) { // Null on offline, ignore
|
||||
__state.drive = state.drive;
|
||||
}
|
||||
if (state.storage) { // Null on offline, ignore
|
||||
if (state.storage.parts !== undefined) {
|
||||
__state.storage.parts = state.storage.parts;
|
||||
__updateParts(__state.storage.parts);
|
||||
}
|
||||
if (state.storage.uploading !== undefined) {
|
||||
__state.storage.uploading = state.storage.uploading;
|
||||
__updateUploading(__state.storage.uploading);
|
||||
}
|
||||
if (state.storage.downloading !== undefined) {
|
||||
__state.storage.downloading = state.storage.downloading;
|
||||
}
|
||||
if (state.storage.images !== undefined) {
|
||||
__state.storage.images = state.storage.images;
|
||||
}
|
||||
}
|
||||
if (state.drive || (state.storage && state.storage.images !== undefined)) {
|
||||
__updateImageSelector(__state.drive, __state.storage.images);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
__state = null;
|
||||
}
|
||||
__refreshControls();
|
||||
};
|
||||
|
||||
var __refreshControls = function() {
|
||||
__updateControls(__state && (__state.online !== undefined) ? __state : null);
|
||||
};
|
||||
|
||||
var __updateControls = function(state) {
|
||||
let o = (state && state.online);
|
||||
let d = (state ? state.drive : null);
|
||||
let s = (state ? state.storage : null);
|
||||
let busy = !!(state && state.busy);
|
||||
|
||||
tools.hidden.setVisible($("msd-message-offline"), (state && !state.online));
|
||||
tools.hidden.setVisible($("msd-message-image-broken"), (o && d.image && !d.image.complete && !s.uploading));
|
||||
tools.hidden.setVisible($("msd-message-too-big-for-cdrom"), (o && d.cdrom && d.image && d.image.size >= 2359296000));
|
||||
tools.hidden.setVisible($("msd-message-out-of-storage"), (o && d.image && !d.image.in_storage));
|
||||
tools.hidden.setVisible($("msd-message-rw-enabled"), (o && d.rw));
|
||||
tools.hidden.setVisible($("msd-message-another-user-uploads"), (o && s.uploading && !__http));
|
||||
tools.hidden.setVisible($("msd-message-downloads"), (o && s.downloading));
|
||||
|
||||
tools.el.setEnabled($("msd-image-selector"), (o && !d.connected && !busy));
|
||||
tools.el.setEnabled($("msd-download-button"), (o && d.image && !d.connected && !busy));
|
||||
tools.el.setEnabled($("msd-remove-button"), (o && d.image && d.image.removable && !d.connected && !busy));
|
||||
|
||||
tools.radio.setEnabled("msd-mode-radio", (o && !d.connected && !busy));
|
||||
tools.radio.setValue("msd-mode-radio", `${Number(o && d.cdrom)}`);
|
||||
|
||||
tools.el.setEnabled($("msd-rw-switch"), (o && !d.connected && !busy));
|
||||
$("msd-rw-switch").checked = (o && d.rw);
|
||||
|
||||
tools.el.setEnabled($("msd-connect-button"), (o && d.image && !d.connected && !busy));
|
||||
tools.el.setEnabled($("msd-disconnect-button"), (o && d.connected && !busy));
|
||||
|
||||
tools.el.setEnabled($("msd-select-new-button"), (o && !d.connected && !__http && !busy));
|
||||
tools.el.setEnabled($("msd-upload-new-button"),
|
||||
(o && !d.connected && (tools.input.getFile($("msd-new-file")) || $("msd-new-url").value.length > 0) && !busy));
|
||||
tools.el.setEnabled($("msd-abort-new-button"), (o && __http));
|
||||
|
||||
tools.el.setEnabled($("msd-reset-button"), (state && state.enabled && !busy));
|
||||
|
||||
tools.el.setEnabled($("msd-new-file"), (o && !d.connected && !__http && !busy));
|
||||
tools.el.setEnabled($("msd-new-url"), (o && !d.connected && !__http && !busy));
|
||||
tools.el.setEnabled($("msd-new-part-selector"), (o && !d.connected && !__http && !busy));
|
||||
|
||||
if (o && s.uploading) {
|
||||
tools.hidden.setVisible($("msd-new-sub"), false);
|
||||
$("msd-new-file").value = "";
|
||||
$("msd-new-url").value = "";
|
||||
}
|
||||
tools.hidden.setVisible($("msd-uploading-sub"), (o && s.uploading));
|
||||
tools.hidden.setVisible($("msd-new-tips"), (o && s.uploading && __http));
|
||||
|
||||
let led_cls = "led-gray";
|
||||
let msg = "Unavailable";
|
||||
if (o && d.connected) {
|
||||
led_cls = "led-green";
|
||||
msg = "Connected to Server";
|
||||
} else if (o && s.uploading) {
|
||||
led_cls = "led-yellow-rotating-fast";
|
||||
msg = "Uploading new image";
|
||||
} else if (o && s.downloading) {
|
||||
led_cls = "led-yellow-rotating-fast";
|
||||
msg = "Serving the image to download";
|
||||
} else if (o) { // Sic!
|
||||
msg = "Disconnected";
|
||||
}
|
||||
$("msd-led").className = led_cls;
|
||||
$("msd-status").innerText = $("msd-led").title = msg;
|
||||
};
|
||||
|
||||
var __updateUploading = function(uploading) {
|
||||
$("msd-uploading-name").innerText = (uploading ? uploading.name : "");
|
||||
$("msd-uploading-size").innerText = (uploading ? tools.formatSize(uploading.size) : "");
|
||||
if (uploading) {
|
||||
tools.progress.setPercentOf($("msd-uploading-progress"), uploading.size, uploading.written);
|
||||
}
|
||||
};
|
||||
|
||||
var __updateParts = function(parts) {
|
||||
let names = Object.keys(parts).sort();
|
||||
{
|
||||
let writable = names.filter(name => (name === "" || parts[name].writable));
|
||||
let writable_json = JSON.stringify(writable);
|
||||
let el = $("msd-new-part-selector");
|
||||
if (el.__writable_json !== writable_json) {
|
||||
let sel = (el.value || "");
|
||||
el.options.length = 0;
|
||||
for (let name of writable) {
|
||||
let title = (name || "\u2500 Internal \u2500");
|
||||
tools.selector.addOption(el, title, name, (name === sel));
|
||||
}
|
||||
tools.hidden.setVisible($("msd-new-part"), (writable.length > 1));
|
||||
el.__writable_json = writable_json;
|
||||
}
|
||||
}
|
||||
{
|
||||
let names_json = JSON.stringify(names);
|
||||
let el = $("msd-storages");
|
||||
if (el.__names_json !== names_json) {
|
||||
el.innerHTML = names.map(name => `
|
||||
<div class="text">
|
||||
<div id="__msd-storage-${tools.makeIdByText(name)}-progress" class="progress">
|
||||
<span class="progress-value"></span>
|
||||
</div>
|
||||
</div>
|
||||
`).join("<hr>");
|
||||
el.__names_json = names_json;
|
||||
}
|
||||
}
|
||||
for (let name of names) {
|
||||
let part = parts[name];
|
||||
let title = (
|
||||
name === ""
|
||||
? `${names.length === 1 ? "Storage: %s" : "Internal storage: %s"}` // eslint-disable-line
|
||||
: `Storage [${name}${part.writable ? "]" : ", read-only]"}: %s` // eslint-disable-line
|
||||
);
|
||||
let id = `__msd-storage-${tools.makeIdByText(name)}-progress`;
|
||||
tools.progress.setSizeOf($(id), title, part.size, part.free);
|
||||
}
|
||||
};
|
||||
|
||||
var __updateImageSelector = function(drive, images) {
|
||||
let sel = "";
|
||||
let el = $("msd-image-selector");
|
||||
el.options.length = 1;
|
||||
for (let name of Object.keys(images).sort()) {
|
||||
tools.selector.addSeparator(el);
|
||||
tools.selector.addOption(el, name, name);
|
||||
tools.selector.addComment(el, __makeImageSelectorInfo(images[name]));
|
||||
if (drive.image && drive.image.name === name && drive.image.in_storage) {
|
||||
sel = name;
|
||||
}
|
||||
}
|
||||
if (drive.image && !drive.image.in_storage) {
|
||||
sel = ".__external__"; // Just some magic name
|
||||
tools.selector.addOption(el, drive.image.name, sel);
|
||||
tools.selector.addComment(el, __makeImageSelectorInfo(drive.image));
|
||||
}
|
||||
el.value = sel;
|
||||
};
|
||||
|
||||
var __makeImageSelectorInfo = function(image) {
|
||||
let text = `\xA0\xA0\xA0\xA0\xA0\u2570 ${tools.formatSize(image.size)}`;
|
||||
if (!image.complete) {
|
||||
text += ", broken";
|
||||
}
|
||||
if (image.in_storage !== undefined && !image.in_storage) {
|
||||
text += ", out of storage";
|
||||
}
|
||||
let ts = new Date(image.mod_ts * 1000);
|
||||
ts = new Date(ts.getTime() - (ts.getTimezoneOffset() * 60000));
|
||||
ts = ts.toISOString().slice(0, -8).replaceAll("-", ".").replace("T", "-");
|
||||
return `${text} \u2500 ${ts}`;
|
||||
};
|
||||
|
||||
var __selectImage = function() {
|
||||
@@ -80,17 +269,17 @@ export function Msd() {
|
||||
};
|
||||
|
||||
var __clickDownloadButton = function() {
|
||||
let name = $("msd-image-selector").value;
|
||||
window.open(`/api/msd/read?image=${name}`);
|
||||
let image = encodeURIComponent($("msd-image-selector").value);
|
||||
window.open(`/api/msd/read?image=${image}`);
|
||||
};
|
||||
|
||||
var __clickRemoveButton = function() {
|
||||
let name = $("msd-image-selector").value;
|
||||
wm.confirm(`Are you sure you want to remove the image<br><b>${name}</b> from PiKVM?`).then(function(ok) {
|
||||
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:<br>", http.responseText);
|
||||
wm.error("Can't remove image", http.responseText);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -98,10 +287,11 @@ export function Msd() {
|
||||
};
|
||||
|
||||
var __sendParam = function(name, value) {
|
||||
tools.httpPost(`/api/msd/set_params?${name}=${encodeURIComponent(value)}`, function(http) {
|
||||
tools.httpPost("/api/msd/set_params", {[name]: value}, function(http) {
|
||||
if (http.status !== 200) {
|
||||
wm.error("Can't configure MSD:<br>", http.responseText);
|
||||
wm.error("Can't configure Mass Storage", http.responseText);
|
||||
}
|
||||
__refreshControls();
|
||||
});
|
||||
};
|
||||
|
||||
@@ -110,80 +300,84 @@ export function Msd() {
|
||||
__http = new XMLHttpRequest();
|
||||
let prefix = encodeURIComponent($("msd-new-part-selector").value);
|
||||
if (file) {
|
||||
__http.open("POST", `/api/msd/write?prefix=${prefix}&image=${encodeURIComponent(file.name)}&remove_incomplete=1`, true);
|
||||
let image = encodeURIComponent(file.name);
|
||||
__http.open("POST", `/api/msd/write?prefix=${prefix}&image=${image}&remove_incomplete=1`, true);
|
||||
} else {
|
||||
let url = $("msd-new-url").value;
|
||||
__http.open("POST", `/api/msd/write_remote?prefix=${prefix}&url=${encodeURIComponent(url)}&remove_incomplete=1`, true);
|
||||
let url = encodeURIComponent($("msd-new-url").value);
|
||||
__http.open("POST", `/api/msd/write_remote?prefix=${prefix}&url=${url}&remove_incomplete=1`, true);
|
||||
}
|
||||
__http.upload.timeout = 7 * 24 * 3600;
|
||||
__http.onreadystatechange = __httpStateChange;
|
||||
__http.onreadystatechange = __uploadStateChange;
|
||||
__http.send(file);
|
||||
__applyState();
|
||||
__refreshControls();
|
||||
};
|
||||
|
||||
var __httpStateChange = function() {
|
||||
if (__http.readyState === 4) {
|
||||
if (__http.status !== 200) {
|
||||
wm.error("Can't upload image to the Mass Storage Drive:<br>", __http.responseText);
|
||||
} else if ($("msd-new-url").value.length > 0) {
|
||||
let msg = "";
|
||||
try {
|
||||
let end = __http.responseText.lastIndexOf("\r\n");
|
||||
if (end < 0) {
|
||||
end = __http.responseText.length;
|
||||
}
|
||||
let begin = __http.responseText.lastIndexOf("\r\n", end - 2);
|
||||
if (begin < 0) {
|
||||
end = 0;
|
||||
}
|
||||
let result_str = __http.responseText.slice(begin, end);
|
||||
let result = JSON.parse(result_str);
|
||||
if (!result.ok) {
|
||||
msg = `Can't upload image to the Mass Storage Drive:<br>${result_str}`;
|
||||
}
|
||||
} catch (err) {
|
||||
msg = `Can't parse upload result:<br>${err}`;
|
||||
}
|
||||
if (msg.length > 0) {
|
||||
wm.error(msg);
|
||||
}
|
||||
}
|
||||
tools.hidden.setVisible($("msd-new-sub"), false);
|
||||
$("msd-new-file").value = "";
|
||||
$("msd-new-url").value = "";
|
||||
__http = null;
|
||||
__applyState();
|
||||
var __uploadStateChange = function() {
|
||||
if (__http.readyState !== 4) {
|
||||
return;
|
||||
}
|
||||
if (__http.status !== 200) {
|
||||
wm.error("Can't upload image", __http.responseText);
|
||||
} else if ($("msd-new-url").value.length > 0) {
|
||||
let html = "";
|
||||
let msg = "";
|
||||
try {
|
||||
let end = __http.responseText.lastIndexOf("\r\n");
|
||||
if (end < 0) {
|
||||
end = __http.responseText.length;
|
||||
}
|
||||
let begin = __http.responseText.lastIndexOf("\r\n", end - 2);
|
||||
if (begin < 0) {
|
||||
end = 0;
|
||||
}
|
||||
let result_str = __http.responseText.slice(begin, end);
|
||||
let result = JSON.parse(result_str);
|
||||
if (!result.ok) {
|
||||
html = "Can't upload image";
|
||||
msg = result_str;
|
||||
}
|
||||
} catch (ex) {
|
||||
html = "Can't parse upload result";
|
||||
msg = `${ex}`;
|
||||
}
|
||||
if (html.length > 0) {
|
||||
wm.error(html, msg);
|
||||
}
|
||||
}
|
||||
tools.hidden.setVisible($("msd-new-sub"), false);
|
||||
$("msd-new-file").value = "";
|
||||
$("msd-new-url").value = "";
|
||||
__http = null;
|
||||
__refreshControls();
|
||||
};
|
||||
|
||||
var __clickAbortNewButton = function() {
|
||||
__http.onreadystatechange = null;
|
||||
__http.abort();
|
||||
__http = null;
|
||||
tools.progress.setValue($("msd-uploading-progress"), "Aborted", 0);
|
||||
__refreshControls();
|
||||
tools.hidden.setVisible($("msd-new-sub"), true);
|
||||
};
|
||||
|
||||
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("Switch error:<br>", http.responseText);
|
||||
wm.error("Can't switch Mass Storage", http.responseText);
|
||||
}
|
||||
__applyState();
|
||||
__refreshControls();
|
||||
});
|
||||
__applyState();
|
||||
__refreshControls();
|
||||
tools.el.setEnabled($(`msd-${connected ? "connect" : "disconnect"}-button`), false);
|
||||
};
|
||||
|
||||
var __clickResetButton = function() {
|
||||
wm.confirm("Are you sure you want to reset Mass Storage Drive?").then(function(ok) {
|
||||
wm.confirm("Are you sure you want to reset Mass Storage?").then(function(ok) {
|
||||
if (ok) {
|
||||
tools.httpPost("/api/msd/reset", function(http) {
|
||||
tools.httpPost("/api/msd/reset", null, function(http) {
|
||||
if (http.status !== 200) {
|
||||
wm.error("MSD reset error:<br>", http.responseText);
|
||||
wm.error("Mass Storage reset error", http.responseText);
|
||||
}
|
||||
__applyState();
|
||||
});
|
||||
__applyState();
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -191,193 +385,35 @@ export function Msd() {
|
||||
var __toggleSelectSub = function() {
|
||||
let el_sub = $("msd-new-sub");
|
||||
let visible = tools.hidden.isVisible(el_sub);
|
||||
tools.hidden.setVisible(el_sub, !visible);
|
||||
if (visible) {
|
||||
$("msd-new-file").value = "";
|
||||
$("msd-new-url").value = "";
|
||||
}
|
||||
tools.hidden.setVisible(el_sub, !visible);
|
||||
__applyState();
|
||||
__refreshControls();
|
||||
};
|
||||
|
||||
var __selectNewFile = function() {
|
||||
let el_input = $("msd-new-file");
|
||||
let file = tools.input.getFile($("msd-new-file"));
|
||||
let el = $("msd-new-file");
|
||||
let file = tools.input.getFile(el);
|
||||
if (file) {
|
||||
$("msd-new-url").value = "";
|
||||
let part = __state.storage.parts[$("msd-new-part-selector").value];
|
||||
if (file.size > part.size) {
|
||||
wm.error("New image is too big for the MSD partition.<br>Maximum:", tools.formatSize(part.size));
|
||||
el_input.value = "";
|
||||
if (__state && __state.storage && __state.storage.parts) {
|
||||
let part = __state.storage.parts[$("msd-new-part-selector").value];
|
||||
if (part && (file.size > part.size)) {
|
||||
wm.error(`The new image is too big for the Mass Storage partition.<br>Maximum: ${tools.formatSize(part.size)}`);
|
||||
el.value = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
__applyState();
|
||||
__refreshControls();
|
||||
};
|
||||
|
||||
var __selectNewUrl = function() {
|
||||
if ($("msd-new-url").value.length > 0) {
|
||||
$("msd-new-file").value = "";
|
||||
}
|
||||
__applyState();
|
||||
};
|
||||
|
||||
var __applyState = function() {
|
||||
__applyStateStatus();
|
||||
|
||||
let s = __state;
|
||||
let online = (s && s.online);
|
||||
|
||||
if (s) {
|
||||
tools.feature.setEnabled($("msd-dropdown"), s.enabled);
|
||||
tools.feature.setEnabled($("msd-reset-button"), s.enabled);
|
||||
}
|
||||
tools.hidden.setVisible($("msd-message-offline"), (s && !s.online));
|
||||
tools.hidden.setVisible($("msd-message-image-broken"), (online && s.drive.image && !s.drive.image.complete && !s.storage.uploading));
|
||||
tools.hidden.setVisible($("msd-message-too-big-for-cdrom"), (online && s.drive.cdrom && s.drive.image && s.drive.image.size >= 2359296000));
|
||||
tools.hidden.setVisible($("msd-message-out-of-storage"), (online && s.drive.image && !s.drive.image.in_storage));
|
||||
tools.hidden.setVisible($("msd-message-rw-enabled"), (online && s.drive.rw));
|
||||
tools.hidden.setVisible($("msd-message-another-user-uploads"), (online && s.storage.uploading && !__http));
|
||||
tools.hidden.setVisible($("msd-message-downloads"), (online && s.storage.downloading));
|
||||
|
||||
if (online) {
|
||||
let names = Object.keys(s.storage.parts).sort();
|
||||
let parts_names_json = JSON.stringify(names);
|
||||
if (__parts_names_json !== parts_names_json) {
|
||||
$("msd-storages").innerHTML = names.map(name => `
|
||||
<div class="text">
|
||||
<div id="msd-storage-${tools.makeIdByText(name)}-progress" class="progress">
|
||||
<span class="progress-value"></span>
|
||||
</div>
|
||||
</div>
|
||||
`).join("<hr>");
|
||||
__parts_names_json = parts_names_json;
|
||||
__parts_names_len = names.length;
|
||||
}
|
||||
__parts = s.storage.parts;
|
||||
}
|
||||
for (let name in __parts) {
|
||||
let part = __parts[name];
|
||||
let title = (
|
||||
name.length === 0
|
||||
? `${__parts_names_len === 1 ? "Storage: %s" : "Internal storage: %s"}` // eslint-disable-line
|
||||
: `Storage [${name}${part.writable ? "]" : ", read-only]"}: %s` // eslint-disable-line
|
||||
);
|
||||
let id = `msd-storage-${tools.makeIdByText(name)}-progress`;
|
||||
if (online) {
|
||||
tools.progress.setSizeOf($(id), title, part.size, part.free);
|
||||
} else {
|
||||
tools.progress.setValue($(id), title.replace("%s", "unavailable"), 0);
|
||||
}
|
||||
}
|
||||
|
||||
tools.el.setEnabled($("msd-image-selector"), (online && !s.drive.connected && !s.busy));
|
||||
__applyStateImageSelector();
|
||||
tools.el.setEnabled($("msd-download-button"), (online && s.drive.image && !s.drive.connected && !s.busy));
|
||||
tools.el.setEnabled($("msd-remove-button"), (online && s.drive.image && s.drive.image.removable && !s.drive.connected && !s.busy));
|
||||
|
||||
tools.radio.setEnabled("msd-mode-radio", (online && !s.drive.connected && !s.busy));
|
||||
tools.radio.setValue("msd-mode-radio", `${Number(online && s.drive.cdrom)}`);
|
||||
|
||||
tools.el.setEnabled($("msd-rw-switch"), (online && !s.drive.connected && !s.busy));
|
||||
$("msd-rw-switch").checked = (online && s.drive.rw);
|
||||
|
||||
tools.el.setEnabled($("msd-connect-button"), (online && s.drive.image && !s.drive.connected && !s.busy));
|
||||
tools.el.setEnabled($("msd-disconnect-button"), (online && s.drive.connected && !s.busy));
|
||||
|
||||
tools.el.setEnabled($("msd-select-new-button"), (online && !s.drive.connected && !__http && !s.busy));
|
||||
tools.el.setEnabled($("msd-upload-new-button"),
|
||||
(online && !s.drive.connected && (tools.input.getFile($("msd-new-file")) || $("msd-new-url").value.length > 0) && !s.busy));
|
||||
tools.el.setEnabled($("msd-abort-new-button"), (online && __http));
|
||||
|
||||
tools.el.setEnabled($("msd-reset-button"), (s && s.enabled && !s.busy));
|
||||
|
||||
tools.el.setEnabled($("msd-new-file"), (online && !s.drive.connected && !__http && !s.busy));
|
||||
tools.el.setEnabled($("msd-new-url"), (online && !s.drive.connected && !__http && !s.busy));
|
||||
tools.el.setEnabled($("msd-new-part-selector"), (online && !s.drive.connected && !__http && !s.busy));
|
||||
if (online && !s.storage.uploading && !s.storage.downloading) {
|
||||
let parts = Object.keys(s.storage.parts).sort().filter(name => (name === "" || s.storage.parts[name].writable));
|
||||
tools.selector.setValues($("msd-new-part-selector"), parts, "\u2500 Internal \u2500");
|
||||
tools.hidden.setVisible($("msd-new-part"), (parts.length > 1));
|
||||
}
|
||||
|
||||
tools.hidden.setVisible($("msd-uploading-sub"), (online && s.storage.uploading));
|
||||
$("msd-uploading-name").innerHTML = ((online && s.storage.uploading) ? s.storage.uploading.name : "");
|
||||
$("msd-uploading-size").innerHTML = ((online && s.storage.uploading) ? tools.formatSize(s.storage.uploading.size) : "");
|
||||
if (online) {
|
||||
if (s.storage.uploading) {
|
||||
tools.progress.setPercentOf($("msd-uploading-progress"), s.storage.uploading.size, s.storage.uploading.written);
|
||||
} else if (!__http) {
|
||||
tools.progress.setValue($("msd-uploading-progress"), "Waiting for upload (press UPLOAD button) ...", 0);
|
||||
}
|
||||
} else {
|
||||
$("msd-new-file").value = "";
|
||||
$("msd-new-url").value = "";
|
||||
tools.progress.setValue($("msd-uploading-progress"), "", 0);
|
||||
}
|
||||
};
|
||||
|
||||
var __applyStateStatus = function() {
|
||||
let s = __state;
|
||||
let online = (s && s.online);
|
||||
|
||||
let led_cls = "led-gray";
|
||||
let msg = "Unavailable";
|
||||
|
||||
if (online && s.drive.connected) {
|
||||
led_cls = "led-green";
|
||||
msg = "Connected to Server";
|
||||
} else if (online && s.storage.uploading) {
|
||||
led_cls = "led-yellow-rotating-fast";
|
||||
msg = "Uploading new image";
|
||||
} else if (online && s.storage.downloading) {
|
||||
led_cls = "led-yellow-rotating-fast";
|
||||
msg = "Serving the image to download";
|
||||
} else if (online) { // Sic!
|
||||
msg = "Disconnected";
|
||||
}
|
||||
|
||||
$("msd-led").className = led_cls;
|
||||
$("msd-status").innerHTML = $("msd-led").title = msg;
|
||||
};
|
||||
|
||||
var __applyStateImageSelector = function() {
|
||||
let s = __state;
|
||||
if (!(s && s.online) || s.storage.uploading || s.storage.downloading) {
|
||||
return;
|
||||
}
|
||||
|
||||
let el = $("msd-image-selector");
|
||||
el.options.length = 1;
|
||||
|
||||
let selected = "";
|
||||
|
||||
for (let name of Object.keys(s.storage.images).sort()) {
|
||||
tools.selector.addSeparator(el);
|
||||
tools.selector.addOption(el, name, name);
|
||||
tools.selector.addComment(el, __makeImageSelectorInfo(s.storage.images[name]));
|
||||
if (s.drive.image && s.drive.image.name === name && s.drive.image.in_storage) {
|
||||
selected = name;
|
||||
}
|
||||
}
|
||||
|
||||
if (s.drive.image && !s.drive.image.in_storage) {
|
||||
selected = ".__external";
|
||||
tools.selector.addOption(el, s.drive.image.name, selected);
|
||||
tools.selector.addComment(el, __makeImageSelectorInfo(s.drive.image));
|
||||
}
|
||||
|
||||
el.value = selected;
|
||||
};
|
||||
|
||||
var __makeImageSelectorInfo = function(image) {
|
||||
let info = `\xA0\xA0\xA0\xA0\xA0\u2570 ${tools.formatSize(image.size)}`;
|
||||
info += (image.complete ? "" : ", broken");
|
||||
if (image.in_storage !== undefined && !image.in_storage) {
|
||||
info += ", out of storage";
|
||||
}
|
||||
let dt = new Date(image.mod_ts * 1000);
|
||||
dt = new Date(dt.getTime() - (dt.getTimezoneOffset() * 60000));
|
||||
info += " \u2500 " + dt.toISOString().slice(0, -8).replaceAll("-", ".").replace("T", "-");
|
||||
return info;
|
||||
__refreshControls();
|
||||
};
|
||||
|
||||
__init__();
|
||||
|
||||
@@ -32,9 +32,11 @@ export function Ocr(__getGeometry) {
|
||||
|
||||
/************************************************************************/
|
||||
|
||||
var __enabled = null;
|
||||
|
||||
var __start_pos = null;
|
||||
var __end_pos = null;
|
||||
var __selection = null;
|
||||
var __sel = null;
|
||||
|
||||
var __init__ = function() {
|
||||
tools.el.setOnClick($("stream-ocr-button"), function() {
|
||||
@@ -54,7 +56,7 @@ export function Ocr(__getGeometry) {
|
||||
$("stream-ocr-window").onkeyup = function(event) {
|
||||
event.preventDefault();
|
||||
if (event.code === "Enter") {
|
||||
if (__selection) {
|
||||
if (__sel) {
|
||||
__recognizeSelection();
|
||||
wm.closeWindow($("stream-ocr-window"));
|
||||
}
|
||||
@@ -71,14 +73,29 @@ export function Ocr(__getGeometry) {
|
||||
/************************************************************************/
|
||||
|
||||
self.setState = function(state) {
|
||||
let enabled = (state && state.ocr.enabled && !tools.browser.is_mobile);
|
||||
if (enabled) {
|
||||
let el = $("stream-ocr-lang-selector");
|
||||
tools.selector.setValues(el, state.ocr.langs.available);
|
||||
tools.selector.setSelectedValue(el, tools.storage.get("stream.ocr.lang", state.ocr.langs["default"]));
|
||||
if (state) {
|
||||
if (state.enabled !== undefined) {
|
||||
__enabled = (state.enabled && !tools.browser.is_mobile);
|
||||
tools.feature.setEnabled($("stream-ocr"), __enabled);
|
||||
$("stream-ocr-led").className = (__enabled ? "led-gray" : "hidden");
|
||||
}
|
||||
if (__enabled && state.langs !== undefined) {
|
||||
__updateLangs(state.langs);
|
||||
}
|
||||
} else {
|
||||
__enabled = false;
|
||||
tools.feature.setEnabled($("stream-ocr"), false);
|
||||
$("stream-ocr-led").className = "hidden";
|
||||
}
|
||||
tools.feature.setEnabled($("stream-ocr"), enabled);
|
||||
$("stream-ocr-led").className = (enabled ? "led-gray" : "hidden");
|
||||
};
|
||||
|
||||
var __updateLangs = function(langs) {
|
||||
let el = $("stream-ocr-lang-selector");
|
||||
el.options.length = 0;
|
||||
for (let lang of langs.available) {
|
||||
tools.selector.addOption(el, lang, lang);
|
||||
}
|
||||
el.value = tools.storage.get("stream.ocr.lang", langs["default"]);
|
||||
};
|
||||
|
||||
var __startSelection = function(event) {
|
||||
@@ -94,23 +111,23 @@ export function Ocr(__getGeometry) {
|
||||
__end_pos = __getGlobalPosition(event);
|
||||
let width = Math.abs(__start_pos.x - __end_pos.x);
|
||||
let height = Math.abs(__start_pos.y - __end_pos.y);
|
||||
let el_selection = $("stream-ocr-selection");
|
||||
el_selection.style.left = Math.min(__start_pos.x, __end_pos.x) + "px";
|
||||
el_selection.style.top = Math.min(__start_pos.y, __end_pos.y) + "px";
|
||||
el_selection.style.width = width + "px";
|
||||
el_selection.style.height = height + "px";
|
||||
tools.hidden.setVisible(el_selection, (width > 1 || height > 1));
|
||||
let el = $("stream-ocr-selection");
|
||||
el.style.left = Math.min(__start_pos.x, __end_pos.x) + "px";
|
||||
el.style.top = Math.min(__start_pos.y, __end_pos.y) + "px";
|
||||
el.style.width = width + "px";
|
||||
el.style.height = height + "px";
|
||||
tools.hidden.setVisible(el, (width > 1 || height > 1));
|
||||
}
|
||||
};
|
||||
|
||||
var __endSelection = function(event) {
|
||||
__changeSelection(event);
|
||||
let el_selection = $("stream-ocr-selection");
|
||||
let el = $("stream-ocr-selection");
|
||||
let ok = (
|
||||
el_selection.offsetWidth > 1 && el_selection.offsetHeight > 1
|
||||
el.offsetWidth > 1 && el.offsetHeight > 1
|
||||
&& __start_pos !== null && __end_pos !== null
|
||||
);
|
||||
tools.hidden.setVisible(el_selection, ok);
|
||||
tools.hidden.setVisible(el, ok);
|
||||
if (ok) {
|
||||
let rect = $("stream-box").getBoundingClientRect();
|
||||
let rel_left = Math.min(__start_pos.x, __end_pos.x) - rect.left;
|
||||
@@ -119,14 +136,14 @@ export function Ocr(__getGeometry) {
|
||||
let rel_top = Math.min(__start_pos.y, __end_pos.y) - rect.top + offset;
|
||||
let rel_bottom = Math.max(__start_pos.y, __end_pos.y) - rect.top + offset;
|
||||
let geo = __getGeometry();
|
||||
__selection = {
|
||||
__sel = {
|
||||
"left": tools.remap(rel_left, geo.x, geo.width, 0, geo.real_width),
|
||||
"right": tools.remap(rel_right, geo.x, geo.width, 0, geo.real_width),
|
||||
"top": tools.remap(rel_top, geo.y, geo.height, 0, geo.real_height),
|
||||
"bottom": tools.remap(rel_bottom, geo.y, geo.height, 0, geo.real_height),
|
||||
};
|
||||
} else {
|
||||
__selection = null;
|
||||
__sel = null;
|
||||
}
|
||||
__start_pos = null;
|
||||
__end_pos = null;
|
||||
@@ -154,20 +171,22 @@ export function Ocr(__getGeometry) {
|
||||
tools.hidden.setVisible($("stream-ocr-selection"), false);
|
||||
__start_pos = null;
|
||||
__end_pos = null;
|
||||
__selection = null;
|
||||
__sel = null;
|
||||
};
|
||||
|
||||
var __recognizeSelection = function() {
|
||||
tools.el.setEnabled($("stream-ocr-button"), false);
|
||||
tools.el.setEnabled($("stream-ocr-lang-selector"), false);
|
||||
$("stream-ocr-led").className = "led-yellow-rotating-fast";
|
||||
|
||||
let lang = $("stream-ocr-lang-selector").value;
|
||||
let url = `/api/streamer/snapshot?ocr=1&ocr_langs=${lang}`;
|
||||
url += `&ocr_left=${__selection.left}&ocr_top=${__selection.top}`;
|
||||
url += `&ocr_right=${__selection.right}&ocr_bottom=${__selection.bottom}`;
|
||||
|
||||
tools.httpGet(url, function(http) {
|
||||
let params = {
|
||||
"ocr": 1,
|
||||
"ocr_langs": $("stream-ocr-lang-selector").value,
|
||||
"ocr_left": __sel.left,
|
||||
"ocr_top": __sel.top,
|
||||
"ocr_right": __sel.right,
|
||||
"orc_bottom": __sel.bottom,
|
||||
};
|
||||
tools.httpGet("/api/streamer/snapshot", params, function(http) {
|
||||
if (http.status === 200) {
|
||||
wm.copyTextToClipboard(http.responseText);
|
||||
} else {
|
||||
|
||||
112
web/share/js/kvm/paste.js
Normal file
112
web/share/js/kvm/paste.js
Normal file
@@ -0,0 +1,112 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# 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";
|
||||
|
||||
|
||||
import {tools, $} from "../tools.js";
|
||||
import {wm} from "../wm.js";
|
||||
|
||||
|
||||
export function Paste(__recorder) {
|
||||
var self = this;
|
||||
|
||||
/************************************************************************/
|
||||
|
||||
var __init__ = function() {
|
||||
tools.storage.bindSimpleSwitch($("hid-pak-ask-switch"), "hid.pak.ask", true);
|
||||
tools.storage.bindSimpleSwitch($("hid-pak-secure-switch"), "hid.pak.secure", false, function(value) {
|
||||
$("hid-pak-text").style.setProperty("-webkit-text-security", (value ? "disc" : "none"));
|
||||
});
|
||||
tools.feature.setEnabled($("hid-pak-secure"), (
|
||||
tools.browser.is_chrome
|
||||
|| tools.browser.is_safari
|
||||
|| tools.browser.is_opera
|
||||
));
|
||||
|
||||
$("hid-pak-keymap-selector").addEventListener("change", function() {
|
||||
tools.storage.set("hid.pak.keymap", $("hid-pak-keymap-selector").value);
|
||||
});
|
||||
tools.el.setOnClick($("hid-pak-button"), __clickPasteAsKeysButton);
|
||||
};
|
||||
|
||||
/************************************************************************/
|
||||
|
||||
self.setState = function(state) {
|
||||
tools.el.setEnabled($("hid-pak-text"), state);
|
||||
tools.el.setEnabled($("hid-pak-button"), state);
|
||||
if (state) {
|
||||
let el = $("hid-pak-keymap-selector");
|
||||
let sel = tools.storage.get("hid.pak.keymap", state.keymaps["default"]);
|
||||
el.options.length = 0;
|
||||
for (let keymap of state.keymaps.available) {
|
||||
tools.selector.addOption(el, keymap, keymap, (keymap === sel));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var __clickPasteAsKeysButton = function() {
|
||||
let text = $("hid-pak-text").value;
|
||||
if (text) {
|
||||
let paste_as_keys = function() {
|
||||
tools.el.setEnabled($("hid-pak-text"), false);
|
||||
tools.el.setEnabled($("hid-pak-button"), false);
|
||||
tools.el.setEnabled($("hid-pak-keymap-selector"), false);
|
||||
|
||||
let keymap = $("hid-pak-keymap-selector").value;
|
||||
|
||||
tools.debug(`HID: paste-as-keys ${keymap}: ${text}`);
|
||||
|
||||
tools.httpPost("/api/hid/print", {"limit": 0, "keymap": keymap}, function(http) {
|
||||
tools.el.setEnabled($("hid-pak-text"), true);
|
||||
tools.el.setEnabled($("hid-pak-button"), true);
|
||||
tools.el.setEnabled($("hid-pak-keymap-selector"), true);
|
||||
$("hid-pak-text").value = "";
|
||||
if (http.status === 413) {
|
||||
wm.error("Too many text for paste!");
|
||||
} else if (http.status !== 200) {
|
||||
wm.error("HID paste error", http.responseText);
|
||||
} else if (http.status === 200) {
|
||||
__recorder.recordPrintEvent(text, keymap);
|
||||
}
|
||||
}, text, "text/plain");
|
||||
};
|
||||
|
||||
if ($("hid-pak-ask-switch").checked) {
|
||||
wm.confirm(`
|
||||
You're going to paste ${text.length} character${text.length ? "s" : ""}.<br>
|
||||
Are you sure you want to continue?
|
||||
`).then(function(ok) {
|
||||
if (ok) {
|
||||
paste_as_keys();
|
||||
} else {
|
||||
$("hid-pak-text").value = "";
|
||||
}
|
||||
});
|
||||
} else {
|
||||
paste_as_keys();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
__init__();
|
||||
}
|
||||
@@ -67,8 +67,8 @@ export function Recorder() {
|
||||
__recordEvent(event);
|
||||
};
|
||||
|
||||
self.recordPrintEvent = function(text) {
|
||||
__recordEvent({"event_type": "print", "event": {"text": text}});
|
||||
self.recordPrintEvent = function(text, keymap) {
|
||||
__recordEvent({"event_type": "print", "event": {"text": text, "keymap": keymap}});
|
||||
};
|
||||
|
||||
self.recordAtxButtonEvent = function(button) {
|
||||
@@ -159,6 +159,9 @@ export function Recorder() {
|
||||
|
||||
} else if (event.event_type === "print") {
|
||||
__checkType(event.event.text, "string", "Non-string print text");
|
||||
if (event.event.keymap) {
|
||||
__checkType(event.event.keymap, "string", "Non-string keymap");
|
||||
}
|
||||
|
||||
} else if (event.event_type === "key") {
|
||||
__checkType(event.event.key, "string", "Non-string key code");
|
||||
@@ -214,8 +217,8 @@ export function Recorder() {
|
||||
|
||||
__events = events;
|
||||
__events_time = events_time;
|
||||
} catch (err) {
|
||||
wm.error(`Invalid script: ${err}`);
|
||||
} catch (ex) {
|
||||
wm.error("Invalid script", `${ex}`);
|
||||
}
|
||||
|
||||
el_input.value = "";
|
||||
@@ -280,12 +283,16 @@ export function Recorder() {
|
||||
return;
|
||||
|
||||
} else if (event.event_type === "print") {
|
||||
tools.httpPost("/api/hid/print?limit=0", function(http) {
|
||||
let params = {"limit": 0};
|
||||
if (event.event.keymap) {
|
||||
params["keymap"] = event.event.keymap;
|
||||
}
|
||||
tools.httpPost("/api/hid/print", params, function(http) {
|
||||
if (http.status === 413) {
|
||||
wm.error("Too many text for paste!");
|
||||
__stopProcess();
|
||||
} else if (http.status !== 200) {
|
||||
wm.error("HID paste error:<br>", http.responseText);
|
||||
wm.error("HID paste error", http.responseText);
|
||||
__stopProcess();
|
||||
} else if (http.status === 200) {
|
||||
__play_timer = setTimeout(() => __runEvents(index + 1, time), 0);
|
||||
@@ -294,9 +301,9 @@ 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:<br>", http.responseText);
|
||||
wm.error("ATX error", http.responseText);
|
||||
__stopProcess();
|
||||
} else if (http.status === 200) {
|
||||
__play_timer = setTimeout(() => __runEvents(index + 1, time), 0);
|
||||
@@ -306,14 +313,16 @@ export function Recorder() {
|
||||
|
||||
} else if (["gpio_switch", "gpio_pulse"].includes(event.event_type)) {
|
||||
let path = "/api/gpio";
|
||||
let params = {"channel": event.event.channel};
|
||||
if (event.event_type === "gpio_switch") {
|
||||
path += `/switch?channel=${event.event.channel}&state=${event.event.to}`;
|
||||
path += "/switch";
|
||||
params["state"] = event.event.to;
|
||||
} else { // gpio_pulse
|
||||
path += `/pulse?channel=${event.event.channel}`;
|
||||
path += "/pulse";
|
||||
}
|
||||
tools.httpPost(path, function(http) {
|
||||
tools.httpPost(path, params, function(http) {
|
||||
if (http.status !== 200) {
|
||||
wm.error("GPIO error:<br>", http.responseText);
|
||||
wm.error("GPIO error", http.responseText);
|
||||
__stopProcess();
|
||||
} else if (http.status === 200) {
|
||||
__play_timer = setTimeout(() => __runEvents(index + 1, time), 0);
|
||||
|
||||
@@ -28,6 +28,7 @@ import {wm} from "../wm.js";
|
||||
|
||||
import {Recorder} from "./recorder.js";
|
||||
import {Hid} from "./hid.js";
|
||||
import {Paste} from "./paste.js";
|
||||
import {Atx} from "./atx.js";
|
||||
import {Msd} from "./msd.js";
|
||||
import {Streamer} from "./stream.js";
|
||||
@@ -48,6 +49,7 @@ export function Session() {
|
||||
var __streamer = new Streamer();
|
||||
var __recorder = new Recorder();
|
||||
var __hid = new Hid(__streamer.getGeometry, __recorder);
|
||||
var __paste = new Paste(__recorder);
|
||||
var __atx = new Atx(__recorder);
|
||||
var __msd = new Msd();
|
||||
var __gpio = new Gpio(__recorder);
|
||||
@@ -57,30 +59,42 @@ export function Session() {
|
||||
var __info_fan_state = null;
|
||||
|
||||
var __init__ = function() {
|
||||
__startSession();
|
||||
__streamer.ensureDeps(() => __startSession());
|
||||
};
|
||||
|
||||
/************************************************************************/
|
||||
|
||||
var __setAboutInfoMeta = function(state) {
|
||||
var __setInfoState = function(state) {
|
||||
for (let key of Object.keys(state)) {
|
||||
switch (key) {
|
||||
case "meta": __setInfoStateMeta(state.meta); break;
|
||||
case "hw": __setInfoStateHw(state.hw); break;
|
||||
case "fan": __setInfoStateFan(state.fan); break;
|
||||
case "system": __setInfoStateSystem(state.system); break;
|
||||
case "extras": __setInfoStateExtras(state.extras); break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var __setInfoStateMeta = function(state) {
|
||||
if (state !== null) {
|
||||
let text = JSON.stringify(state, undefined, 4).replace(/ /g, " ").replace(/\n/g, "<br>");
|
||||
$("about-meta").innerHTML = `
|
||||
<span class="code-comment">// The PiKVM metadata.<br>
|
||||
// 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>
|
||||
<br>
|
||||
${text}
|
||||
`;
|
||||
$("kvmd-meta-json").innerText = JSON.stringify(state, undefined, 4);
|
||||
|
||||
if (state.server && state.server.host) {
|
||||
$("kvmd-meta-server-host").innerHTML = `Server: ${state.server.host}`;
|
||||
$("kvmd-meta-server-host").innerText = `Server: ${state.server.host}`;
|
||||
document.title = `PiKVM Session: ${state.server.host}`;
|
||||
} else {
|
||||
$("kvmd-meta-server-host").innerHTML = "";
|
||||
$("kvmd-meta-server-host").innerText = "";
|
||||
document.title = "PiKVM Session";
|
||||
}
|
||||
|
||||
if (state.tips && state.tips.left) {
|
||||
$("kvmd-meta-tips-left").innerText = `${state.tips.left}`;
|
||||
}
|
||||
if (state.tips && state.tips.right) {
|
||||
$("kvmd-meta-tips-right").innerText = `${state.tips.right}`;
|
||||
}
|
||||
|
||||
// Don't use this option, it may be removed in any time
|
||||
if (state.web && state.web.confirm_session_exit === false) {
|
||||
window.onbeforeunload = null; // See main.js
|
||||
@@ -88,7 +102,7 @@ export function Session() {
|
||||
}
|
||||
};
|
||||
|
||||
var __setAboutInfoHw = function(state) {
|
||||
var __setInfoStateHw = function(state) {
|
||||
if (state.health.throttling !== null) {
|
||||
let flags = state.health.throttling.parsed_flags;
|
||||
let ignore_past = state.health.throttling.ignore_past;
|
||||
@@ -105,7 +119,7 @@ export function Session() {
|
||||
__renderAboutInfoHardware();
|
||||
};
|
||||
|
||||
var __setAboutInfoFan = function(state) {
|
||||
var __setInfoStateFan = function(state) {
|
||||
let failed = false;
|
||||
let failed_past = false;
|
||||
if (state.monitored) {
|
||||
@@ -207,11 +221,11 @@ export function Session() {
|
||||
}
|
||||
};
|
||||
|
||||
var __colored = function(color, text) {
|
||||
return `<font color="${color}">${text}</font>`;
|
||||
var __colored = function(color, html) {
|
||||
return `<font color="${color}">${html}</font>`;
|
||||
};
|
||||
|
||||
var __setAboutInfoSystem = function(state) {
|
||||
var __setInfoStateSystem = function(state) {
|
||||
$("about-version").innerHTML = `
|
||||
KVMD: <span class="code-comment">${state.kvmd.version}</span><br>
|
||||
<hr>
|
||||
@@ -221,8 +235,8 @@ export function Session() {
|
||||
${state.kernel.system} kernel:
|
||||
${__formatUname(state.kernel)}
|
||||
`;
|
||||
$("kvmd-version-kvmd").innerHTML = state.kvmd.version;
|
||||
$("kvmd-version-streamer").innerHTML = state.streamer.version;
|
||||
$("kvmd-version-kvmd").innerText = state.kvmd.version;
|
||||
$("kvmd-version-streamer").innerText = state.streamer.version;
|
||||
};
|
||||
|
||||
var __formatStreamerFeatures = function(features) {
|
||||
@@ -244,14 +258,14 @@ export function Session() {
|
||||
};
|
||||
|
||||
var __formatUl = function(pairs) {
|
||||
let text = "<ul>";
|
||||
let html = "";
|
||||
for (let pair of pairs) {
|
||||
text += `<li>${pair[0]}: <span class="code-comment">${pair[1]}</span></li>`;
|
||||
html += `<li>${pair[0]}: <span class="code-comment">${pair[1]}</span></li>`;
|
||||
}
|
||||
return text + "</ul>";
|
||||
return `<ul>${html}</ul>`;
|
||||
};
|
||||
|
||||
var __setExtras = function(state) {
|
||||
var __setInfoStateExtras = function(state) {
|
||||
let show_hook = null;
|
||||
let close_hook = null;
|
||||
let has_webterm = (state.webterm && (state.webterm.enabled || state.webterm.started));
|
||||
@@ -269,20 +283,15 @@ export function Session() {
|
||||
tools.feature.setEnabled($("system-tool-webterm"), has_webterm);
|
||||
$("webterm-window").show_hook = show_hook;
|
||||
$("webterm-window").close_hook = close_hook;
|
||||
|
||||
__streamer.setJanusEnabled(
|
||||
(state.janus && (state.janus.enabled || state.janus.started))
|
||||
|| (state.janus_static && (state.janus_static.enabled || state.janus_static.started))
|
||||
);
|
||||
};
|
||||
|
||||
var __startSession = function() {
|
||||
$("link-led").className = "led-yellow";
|
||||
$("link-led").title = "Connecting...";
|
||||
|
||||
tools.httpGet("/api/auth/check", 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.is_https ? "wss" : "ws"}://${location.host}/api/ws?legacy=0`);
|
||||
__ws.sendHidEvent = (event) => __sendHidEvent(__ws, event.event_type, event.event);
|
||||
__ws.onopen = __wsOpenHandler;
|
||||
__ws.onmessage = __wsMessageHandler;
|
||||
@@ -354,19 +363,14 @@ export function Session() {
|
||||
let data = JSON.parse(event.data);
|
||||
switch (data.event_type) {
|
||||
case "pong": __missed_heartbeats = 0; break;
|
||||
case "info_meta_state": __setAboutInfoMeta(data.event); break;
|
||||
case "info_hw_state": __setAboutInfoHw(data.event); break;
|
||||
case "info_fan_state": __setAboutInfoFan(data.event); break;
|
||||
case "info_system_state": __setAboutInfoSystem(data.event); break;
|
||||
case "info_extras_state": __setExtras(data.event); break;
|
||||
case "gpio_model_state": __gpio.setModel(data.event); break;
|
||||
case "info_state": __setInfoState(data.event); break;
|
||||
case "gpio_state": __gpio.setState(data.event); break;
|
||||
case "hid_keymaps_state": __hid.setKeymaps(data.event); break;
|
||||
case "hid_state": __hid.setState(data.event); break;
|
||||
case "hid_keymaps_state": __paste.setState(data.event); break;
|
||||
case "atx_state": __atx.setState(data.event); break;
|
||||
case "msd_state": __msd.setState(data.event); break;
|
||||
case "streamer_state": __streamer.setState(data.event); break;
|
||||
case "streamer_ocr_state": __ocr.setState(data.event); break;
|
||||
case "ocr_state": __ocr.setState(data.event); break;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -389,13 +393,14 @@ export function Session() {
|
||||
__ping_timer = null;
|
||||
}
|
||||
|
||||
__ocr.setState(null);
|
||||
__gpio.setState(null);
|
||||
__hid.setSocket(null);
|
||||
__recorder.setSocket(null);
|
||||
__hid.setSocket(null); // auto setState(null);
|
||||
__paste.setState(null);
|
||||
__atx.setState(null);
|
||||
__msd.setState(null);
|
||||
__streamer.setState(null);
|
||||
__ocr.setState(null);
|
||||
__recorder.setSocket(null);
|
||||
__ws = null;
|
||||
|
||||
setTimeout(function() {
|
||||
@@ -411,8 +416,8 @@ export function Session() {
|
||||
throw new Error("Too many missed heartbeats");
|
||||
}
|
||||
__ws.send("{\"event_type\": \"ping\", \"event\": {}}");
|
||||
} catch (err) {
|
||||
__wsErrorHandler(err.message);
|
||||
} catch (ex) {
|
||||
__wsErrorHandler(ex.message);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -35,11 +35,11 @@ export function Streamer() {
|
||||
|
||||
/************************************************************************/
|
||||
|
||||
var __janus_enabled = null;
|
||||
var __janus_imported = null;
|
||||
var __streamer = null;
|
||||
|
||||
var __state = null;
|
||||
var __resolution = {"width": 640, "height": 480};
|
||||
var __res = {"width": 640, "height": 480};
|
||||
|
||||
var __init__ = function() {
|
||||
__streamer = new MjpegStreamer(__setActive, __setInactive, __setInfo);
|
||||
@@ -47,22 +47,22 @@ export function Streamer() {
|
||||
$("stream-led").title = "Stream inactive";
|
||||
|
||||
tools.slider.setParams($("stream-quality-slider"), 5, 100, 5, 80, function(value) {
|
||||
$("stream-quality-value").innerHTML = `${value}%`;
|
||||
$("stream-quality-value").innerText = `${value}%`;
|
||||
});
|
||||
tools.slider.setOnUpDelayed($("stream-quality-slider"), 1000, (value) => __sendParam("quality", value));
|
||||
|
||||
tools.slider.setParams($("stream-h264-bitrate-slider"), 25, 20000, 25, 5000, function(value) {
|
||||
$("stream-h264-bitrate-value").innerHTML = value;
|
||||
$("stream-h264-bitrate-value").innerText = value;
|
||||
});
|
||||
tools.slider.setOnUpDelayed($("stream-h264-bitrate-slider"), 1000, (value) => __sendParam("h264_bitrate", value));
|
||||
|
||||
tools.slider.setParams($("stream-h264-gop-slider"), 0, 60, 1, 30, function(value) {
|
||||
$("stream-h264-gop-value").innerHTML = value;
|
||||
$("stream-h264-gop-value").innerText = value;
|
||||
});
|
||||
tools.slider.setOnUpDelayed($("stream-h264-gop-slider"), 1000, (value) => __sendParam("h264_gop", value));
|
||||
|
||||
tools.slider.setParams($("stream-desired-fps-slider"), 0, 120, 1, 0, function(value) {
|
||||
$("stream-desired-fps-value").innerHTML = (value === 0 ? "Unlimited" : value);
|
||||
$("stream-desired-fps-value").innerText = (value === 0 ? "Unlimited" : value);
|
||||
});
|
||||
tools.slider.setOnUpDelayed($("stream-desired-fps-slider"), 1000, (value) => __sendParam("desired_fps", value));
|
||||
|
||||
@@ -86,7 +86,7 @@ export function Streamer() {
|
||||
tools.slider.setParams($("stream-audio-volume-slider"), 0, 100, 1, 0, function(value) {
|
||||
$("stream-video").muted = !value;
|
||||
$("stream-video").volume = value / 100;
|
||||
$("stream-audio-volume-value").innerHTML = value + "%";
|
||||
$("stream-audio-volume-value").innerText = value + "%";
|
||||
if (__streamer.getMode() === "janus") {
|
||||
let allow_audio = !$("stream-video").muted;
|
||||
if (__streamer.isAudioAllowed() !== allow_audio) {
|
||||
@@ -110,6 +110,13 @@ export function Streamer() {
|
||||
|
||||
/************************************************************************/
|
||||
|
||||
self.ensureDeps = function(callback) {
|
||||
JanusStreamer.ensure_janus(function(avail) {
|
||||
__janus_imported = avail;
|
||||
callback();
|
||||
});
|
||||
};
|
||||
|
||||
self.getGeometry = function() {
|
||||
// Первоначально обновление геометрии считалось через ResizeObserver.
|
||||
// Но оно не ловило некоторые события, например в последовательности:
|
||||
@@ -132,90 +139,103 @@ export function Streamer() {
|
||||
};
|
||||
};
|
||||
|
||||
self.setJanusEnabled = function(enabled) {
|
||||
let has_webrtc = JanusStreamer.is_webrtc_available();
|
||||
let has_h264 = JanusStreamer.is_h264_available();
|
||||
|
||||
let set_enabled = function(imported) {
|
||||
tools.hidden.setVisible($("stream-message-no-webrtc"), enabled && !has_webrtc);
|
||||
tools.hidden.setVisible($("stream-message-no-h264"), enabled && !has_h264);
|
||||
__janus_enabled = (enabled && has_webrtc && imported); // Don't check has_h264 for sure
|
||||
tools.feature.setEnabled($("stream-mode"), __janus_enabled);
|
||||
tools.info(
|
||||
`Stream: Janus WebRTC state: enabled=${enabled},`
|
||||
+ ` webrtc=${has_webrtc}, h264=${has_h264}, imported=${imported}`
|
||||
);
|
||||
let mode = (__janus_enabled ? tools.storage.get("stream.mode", "janus") : "mjpeg");
|
||||
tools.radio.clickValue("stream-mode-radio", mode);
|
||||
if (!__janus_enabled) {
|
||||
tools.feature.setEnabled($("stream-audio"), false); // Enabling in stream_janus.js
|
||||
}
|
||||
self.setState(__state);
|
||||
};
|
||||
|
||||
if (enabled && has_webrtc) {
|
||||
JanusStreamer.ensure_janus(set_enabled);
|
||||
} else {
|
||||
set_enabled(false);
|
||||
}
|
||||
};
|
||||
|
||||
self.setState = function(state) {
|
||||
__state = state;
|
||||
if (__janus_enabled !== null) {
|
||||
__applyState(wm.isWindowVisible($("stream-window")) ? __state : null);
|
||||
if (state) {
|
||||
if (!__state) {
|
||||
__state = {};
|
||||
}
|
||||
if (state.features !== undefined) {
|
||||
__state.features = state.features;
|
||||
__state.limits = state.limits; // Following together with features
|
||||
}
|
||||
if (__state.features !== undefined && state.streamer !== undefined) {
|
||||
__state.streamer = state.streamer;
|
||||
__setControlsEnabled(!!state.streamer);
|
||||
}
|
||||
} else {
|
||||
__state = null;
|
||||
__setControlsEnabled(false);
|
||||
}
|
||||
let visible = wm.isWindowVisible($("stream-window"));
|
||||
__applyState((visible && __state && __state.features) ? state : null);
|
||||
};
|
||||
|
||||
var __applyState = function(state) {
|
||||
if (state) {
|
||||
tools.feature.setEnabled($("stream-quality"), state.features.quality && (state.streamer === null || state.streamer.encoder.quality > 0));
|
||||
tools.feature.setEnabled($("stream-h264-bitrate"), state.features.h264 && __janus_enabled);
|
||||
tools.feature.setEnabled($("stream-h264-gop"), state.features.h264 && __janus_enabled);
|
||||
tools.feature.setEnabled($("stream-resolution"), state.features.resolution);
|
||||
if (__janus_imported === null) {
|
||||
alert("__janus_imported is null, please report");
|
||||
return;
|
||||
}
|
||||
|
||||
if (state.streamer) {
|
||||
tools.el.setEnabled($("stream-quality-slider"), true);
|
||||
tools.slider.setValue($("stream-quality-slider"), state.streamer.encoder.quality);
|
||||
if (!state) {
|
||||
__streamer.stopStream();
|
||||
return;
|
||||
}
|
||||
|
||||
if (state.features.h264 && __janus_enabled) {
|
||||
__setLimitsAndValue($("stream-h264-bitrate-slider"), state.limits.h264_bitrate, state.streamer.h264.bitrate);
|
||||
tools.el.setEnabled($("stream-h264-bitrate-slider"), true);
|
||||
if (state.features) {
|
||||
let f = state.features;
|
||||
let l = state.limits;
|
||||
let has_webrtc = JanusStreamer.is_webrtc_available();
|
||||
let has_h264 = JanusStreamer.is_h264_available();
|
||||
let has_janus = (__janus_imported && f.h264 && has_webrtc); // Don't check has_h264 for sure
|
||||
|
||||
__setLimitsAndValue($("stream-h264-gop-slider"), state.limits.h264_gop, state.streamer.h264.gop);
|
||||
tools.el.setEnabled($("stream-h264-gop-slider"), true);
|
||||
tools.info(
|
||||
`Stream: Janus WebRTC state: features.h264=${f.h264},`
|
||||
+ ` webrtc=${has_webrtc}, h264=${has_h264}, janus_imported=${__janus_imported}`
|
||||
);
|
||||
|
||||
tools.hidden.setVisible($("stream-message-no-webrtc"), __janus_imported && f.h264 && !has_webrtc);
|
||||
tools.hidden.setVisible($("stream-message-no-h264"), __janus_imported && f.h264 && !has_h264);
|
||||
|
||||
tools.slider.setRange($("stream-desired-fps-slider"), l.desired_fps.min, l.desired_fps.max);
|
||||
if (f.resolution) {
|
||||
let el = $("stream-resolution-selector");
|
||||
el.options.length = 0;
|
||||
for (let res of l.available_resolutions) {
|
||||
tools.selector.addOption(el, res, res);
|
||||
}
|
||||
|
||||
__setLimitsAndValue($("stream-desired-fps-slider"), state.limits.desired_fps, state.streamer.source.desired_fps);
|
||||
tools.el.setEnabled($("stream-desired-fps-slider"), true);
|
||||
|
||||
let resolution_str = __makeStringResolution(state.streamer.source.resolution);
|
||||
if (__makeStringResolution(__resolution) !== resolution_str) {
|
||||
__resolution = state.streamer.source.resolution;
|
||||
}
|
||||
|
||||
if (state.features.resolution) {
|
||||
let el = $("stream-resolution-selector");
|
||||
if (!state.limits.available_resolutions.includes(resolution_str)) {
|
||||
state.limits.available_resolutions.push(resolution_str);
|
||||
}
|
||||
tools.selector.setValues(el, state.limits.available_resolutions);
|
||||
tools.selector.setSelectedValue(el, resolution_str);
|
||||
tools.el.setEnabled(el, true);
|
||||
}
|
||||
|
||||
} else {
|
||||
tools.el.setEnabled($("stream-quality-slider"), false);
|
||||
tools.el.setEnabled($("stream-h264-bitrate-slider"), false);
|
||||
tools.el.setEnabled($("stream-h264-gop-slider"), false);
|
||||
tools.el.setEnabled($("stream-desired-fps-slider"), false);
|
||||
tools.el.setEnabled($("stream-resolution-selector"), false);
|
||||
$("stream-resolution-selector").options.length = 0;
|
||||
}
|
||||
if (has_janus) {
|
||||
tools.slider.setRange($("stream-h264-bitrate-slider"), l.h264_bitrate.min, l.h264_bitrate.max);
|
||||
tools.slider.setRange($("stream-h264-gop-slider"), l.h264_gop.min, l.h264_gop.max);
|
||||
}
|
||||
|
||||
__streamer.ensureStream(state.streamer);
|
||||
// tools.feature.setEnabled($("stream-quality"), f.quality); // Only on s.encoder.quality
|
||||
tools.feature.setEnabled($("stream-resolution"), f.resolution);
|
||||
tools.feature.setEnabled($("stream-h264-bitrate"), has_janus);
|
||||
tools.feature.setEnabled($("stream-h264-gop"), has_janus);
|
||||
tools.feature.setEnabled($("stream-mode"), has_janus);
|
||||
if (!has_janus) {
|
||||
tools.feature.setEnabled($("stream-audio"), false);
|
||||
}
|
||||
|
||||
} else {
|
||||
__streamer.stopStream();
|
||||
let mode = (has_janus ? tools.storage.get("stream.mode", "janus") : "mjpeg");
|
||||
tools.radio.clickValue("stream-mode-radio", mode);
|
||||
}
|
||||
|
||||
if (state.streamer) {
|
||||
let s = state.streamer;
|
||||
__res = s.source.resolution;
|
||||
|
||||
{
|
||||
let res = `${__res.width}x${__res.height}`;
|
||||
let el = $("stream-resolution-selector");
|
||||
if (!tools.selector.hasValue(el, res)) {
|
||||
tools.selector.addOption(el, res, res);
|
||||
}
|
||||
el.value = res;
|
||||
}
|
||||
tools.slider.setValue($("stream-quality-slider"), Math.max(s.encoder.quality, 1));
|
||||
tools.slider.setValue($("stream-desired-fps-slider"), s.source.desired_fps);
|
||||
if (s.h264 && s.h264.bitrate) {
|
||||
tools.slider.setValue($("stream-h264-bitrate-slider"), s.h264.bitrate);
|
||||
tools.slider.setValue($("stream-h264-gop-slider"), s.h264.gop); // Following together with gop
|
||||
}
|
||||
|
||||
tools.feature.setEnabled($("stream-quality"), (s.encoder.quality > 0));
|
||||
|
||||
__streamer.ensureStream(s);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -229,16 +249,24 @@ export function Streamer() {
|
||||
$("stream-led").title = "Stream inactive";
|
||||
};
|
||||
|
||||
var __setControlsEnabled = function(enabled) {
|
||||
tools.el.setEnabled($("stream-quality-slider"), enabled);
|
||||
tools.el.setEnabled($("stream-desired-fps-slider"), enabled);
|
||||
tools.el.setEnabled($("stream-resolution-selector"), enabled);
|
||||
tools.el.setEnabled($("stream-h264-bitrate-slider"), enabled);
|
||||
tools.el.setEnabled($("stream-h264-gop-slider"), enabled);
|
||||
};
|
||||
|
||||
var __setInfo = function(is_active, online, text) {
|
||||
$("stream-box").classList.toggle("stream-box-offline", !online);
|
||||
let el_grab = document.querySelector("#stream-window-header .window-grab");
|
||||
let el_info = $("stream-info");
|
||||
let title = `${__streamer.getName()} – `;
|
||||
let title = `${__streamer.getName()} - `;
|
||||
if (is_active) {
|
||||
if (!online) {
|
||||
title += "No signal / ";
|
||||
}
|
||||
title += __makeStringResolution(__resolution);
|
||||
title += `${__res.width}x${__res.height}`;
|
||||
if (text.length > 0) {
|
||||
title += " / " + text;
|
||||
}
|
||||
@@ -249,12 +277,7 @@ export function Streamer() {
|
||||
title += "Inactive";
|
||||
}
|
||||
}
|
||||
el_grab.innerHTML = el_info.innerHTML = title;
|
||||
};
|
||||
|
||||
var __setLimitsAndValue = function(el, limits, value) {
|
||||
tools.slider.setRange(el, limits.min, limits.max);
|
||||
tools.slider.setValue(el, value);
|
||||
el_grab.innerText = el_info.innerText = title;
|
||||
};
|
||||
|
||||
var __resetStream = function(mode=null) {
|
||||
@@ -274,7 +297,7 @@ export function Streamer() {
|
||||
tools.feature.setEnabled($("stream-audio"), false); // Enabling in stream_janus.js
|
||||
}
|
||||
if (wm.isWindowVisible($("stream-window"))) {
|
||||
__streamer.ensureStream(__state ? __state.streamer : null);
|
||||
__streamer.ensureStream((__state && __state.streamer !== undefined) ? __state.streamer : null);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -298,12 +321,12 @@ export function Streamer() {
|
||||
};
|
||||
|
||||
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) {
|
||||
__resetStream();
|
||||
tools.httpPost("/api/streamer/reset", function(http) {
|
||||
tools.httpPost("/api/streamer/reset", null, function(http) {
|
||||
if (http.status !== 200) {
|
||||
wm.error("Can't reset stream:<br>", http.responseText);
|
||||
wm.error("Can't reset stream", http.responseText);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -382,16 +405,12 @@ 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:<br>", http.responseText);
|
||||
wm.error("Can't configure stream", http.responseText);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var __makeStringResolution = function(resolution) {
|
||||
return `${resolution.width}x${resolution.height}`;
|
||||
};
|
||||
|
||||
__init__();
|
||||
}
|
||||
|
||||
@@ -248,6 +248,13 @@ export function JanusStreamer(__setActive, __setInactive, __setInfo, __orient, _
|
||||
// Janus 0.x
|
||||
"media": {"audioSend": false, "videoSend": false, "data": false},
|
||||
|
||||
// Chrome is playing OPUS as mono without this hack
|
||||
// - https://issues.webrtc.org/issues/41481053 - IT'S NOT FIXED!
|
||||
// - https://github.com/ossrs/srs/pull/2683/files
|
||||
"customizeSdp": function(jsep) {
|
||||
jsep.sdp = jsep.sdp.replace("useinbandfec=1", "useinbandfec=1;stereo=1");
|
||||
},
|
||||
|
||||
"success": function(jsep) {
|
||||
__logInfo("Got SDP:", jsep);
|
||||
__sendStart(jsep);
|
||||
@@ -376,7 +383,7 @@ export function JanusStreamer(__setActive, __setInactive, __setInfo, __orient, _
|
||||
};
|
||||
|
||||
var __isOnline = function() {
|
||||
return !!(__state && __state.source && __state.source.online);
|
||||
return !!(__state && __state.source.online);
|
||||
};
|
||||
|
||||
var __sendWatch = function() {
|
||||
@@ -428,8 +435,8 @@ JanusStreamer.ensure_janus = function(callback) {
|
||||
callback(true);
|
||||
},
|
||||
});
|
||||
}).catch((err) => {
|
||||
tools.error("Stream: Can't import Janus module:", err);
|
||||
}).catch((ex) => {
|
||||
tools.error("Stream: Can't import Janus module:", ex);
|
||||
callback(false);
|
||||
});
|
||||
} else {
|
||||
|
||||
@@ -117,10 +117,10 @@ export function MjpegStreamer(__setActive, __setInactive, __setInfo) {
|
||||
};
|
||||
|
||||
var __findId = function() {
|
||||
let stream_client = tools.cookies.get("stream_client");
|
||||
if (__id.length === 0 && stream_client && stream_client.startsWith(__key + "/")) {
|
||||
__logInfo("Found acceptable stream_client cookie:", stream_client);
|
||||
__id = stream_client.slice(stream_client.indexOf("/") + 1);
|
||||
let sc = tools.cookies.get("stream_client");
|
||||
if (__id.length === 0 && sc && sc.startsWith(__key + "/")) {
|
||||
__logInfo("Found acceptable stream_client cookie:", sc);
|
||||
__id = sc.slice(sc.indexOf("/") + 1);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ function __login() {
|
||||
} else {
|
||||
let passwd = $("passwd-input").value + $("code-input").value;
|
||||
let body = `user=${encodeURIComponent(user)}&passwd=${encodeURIComponent(passwd)}`;
|
||||
tools.httpPost("/api/auth/login", function(http) {
|
||||
tools.httpPost("/api/auth/login", null, function(http) {
|
||||
if (http.status === 200) {
|
||||
document.location.href = "/";
|
||||
} else if (http.status === 403) {
|
||||
@@ -59,12 +59,12 @@ function __login() {
|
||||
} else {
|
||||
let error = "";
|
||||
if (http.status === 400) {
|
||||
try { error = JSON.parse(http.responseText)["result"]["error"]; } catch (_) { /* Nah */ }
|
||||
try { error = JSON.parse(http.responseText)["result"]["error"]; } catch { /* Nah */ }
|
||||
}
|
||||
if (error === "ValidatorError") {
|
||||
wm.error("Invalid characters in credentials").then(__tryAgain);
|
||||
} else {
|
||||
wm.error("Login error:<br>", http.responseText).then(__tryAgain);
|
||||
wm.error("Login error", http.responseText).then(__tryAgain);
|
||||
}
|
||||
}
|
||||
}, body, "application/x-www-form-urlencoded");
|
||||
|
||||
@@ -39,7 +39,13 @@ export var tools = new function() {
|
||||
|
||||
/************************************************************************/
|
||||
|
||||
self.httpRequest = function(method, url, callback, body=null, content_type=null, timeout=15000) {
|
||||
self.httpRequest = function(method, url, params, callback, body=null, content_type=null, timeout=15000) {
|
||||
if (params) {
|
||||
params = new URLSearchParams(params);
|
||||
if (params) {
|
||||
url += "?" + params;
|
||||
}
|
||||
}
|
||||
let http = new XMLHttpRequest();
|
||||
http.open(method, url, true);
|
||||
if (content_type) {
|
||||
@@ -54,16 +60,27 @@ export var tools = new function() {
|
||||
http.send(body);
|
||||
};
|
||||
|
||||
self.httpGet = function(url, callback, body=null, content_type=null, timeout=15000) {
|
||||
self.httpRequest("GET", url, callback, body, content_type, timeout);
|
||||
self.httpGet = function(url, params, callback, body=null, content_type=null, timeout=15000) {
|
||||
self.httpRequest("GET", url, params, callback, body, content_type, timeout);
|
||||
};
|
||||
|
||||
self.httpPost = function(url, callback, body=null, content_type=null, timeout=15000) {
|
||||
self.httpRequest("POST", url, callback, body, content_type, timeout);
|
||||
self.httpPost = function(url, params, callback, body=null, content_type=null, timeout=15000) {
|
||||
self.httpRequest("POST", url, params, callback, body, content_type, timeout);
|
||||
};
|
||||
|
||||
/************************************************************************/
|
||||
|
||||
self.escape = function(text) {
|
||||
return text.replace(
|
||||
/[^0-9A-Za-z ]/g,
|
||||
ch => "&#" + ch.charCodeAt(0) + ";"
|
||||
);
|
||||
};
|
||||
|
||||
self.partial = function(func, ...args) {
|
||||
return () => func(...args);
|
||||
};
|
||||
|
||||
self.upperFirst = function(text) {
|
||||
return text[0].toUpperCase() + text.slice(1);
|
||||
};
|
||||
@@ -87,6 +104,10 @@ export var tools = new function() {
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
};
|
||||
|
||||
self.formatHex = function(value) {
|
||||
return `0x${value.toString(16).toUpperCase()}`;
|
||||
};
|
||||
|
||||
self.formatSize = function(size) {
|
||||
if (size > 0) {
|
||||
let index = Math.floor( Math.log(size) / Math.log(1024) );
|
||||
@@ -283,34 +304,18 @@ export var tools = new function() {
|
||||
option.className = "comment";
|
||||
el.add(option);
|
||||
},
|
||||
"addSeparator": function(el) {
|
||||
"addSeparator": function(el, repeat=30) {
|
||||
if (!self.browser.is_mobile) {
|
||||
self.selector.addComment(el, "\u2500".repeat(30));
|
||||
self.selector.addComment(el, "\u2500".repeat(repeat));
|
||||
}
|
||||
},
|
||||
|
||||
"setValues": function(el, values, empty_title=null) {
|
||||
if (values.constructor == Object) {
|
||||
values = Object.keys(values).sort();
|
||||
}
|
||||
let values_json = JSON.stringify(values);
|
||||
if (el.__values_json !== values_json) {
|
||||
el.options.length = 0;
|
||||
for (let value of values) {
|
||||
let title = value;
|
||||
if (title.length === 0 && empty_title !== null) {
|
||||
title = empty_title;
|
||||
}
|
||||
self.selector.addOption(el, title, value);
|
||||
"hasValue": function(el, value) {
|
||||
for (let el_op of el.options) {
|
||||
if (el_op.value === value) {
|
||||
return true;
|
||||
}
|
||||
el.__values_json = values_json;
|
||||
el.__values = values;
|
||||
}
|
||||
},
|
||||
"setSelectedValue": function(el, value) {
|
||||
if (el.__values && el.__values.includes(value)) {
|
||||
el.value = value;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -31,7 +31,7 @@ export function main() {
|
||||
}
|
||||
|
||||
function __loadKvmdInfo() {
|
||||
tools.httpGet("/api/info", 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 = `
|
||||
|
||||
@@ -111,8 +111,8 @@ function __WindowManager() {
|
||||
let el_exit_full_tab_button = el_window.querySelector(".window-button-exit-full-tab");
|
||||
if (el_enter_full_tab_button && el_exit_full_tab_button) {
|
||||
el_enter_full_tab_button.title = "Stretch to the entire tab";
|
||||
tools.el.setOnClick(el_enter_full_tab_button, () => self.toggleFullTabWindow(el_window, true));
|
||||
tools.el.setOnClick(el_exit_full_tab_button, () => self.toggleFullTabWindow(el_window, false));
|
||||
tools.el.setOnClick(el_enter_full_tab_button, () => self.setFullTabWindow(el_window, true));
|
||||
tools.el.setOnClick(el_exit_full_tab_button, () => self.setFullTabWindow(el_window, false));
|
||||
}
|
||||
|
||||
let el_full_screen_button = el_window.querySelector(".window-header .window-button-full-screen");
|
||||
@@ -145,10 +145,10 @@ function __WindowManager() {
|
||||
/************************************************************************/
|
||||
|
||||
self.copyTextToClipboard = function(text) {
|
||||
let workaround = function(err) {
|
||||
let workaround = function(ex) {
|
||||
// https://stackoverflow.com/questions/60317969/document-execcommandcopy-not-working-even-though-the-dom-element-is-created
|
||||
let callback = function() {
|
||||
tools.error("copyTextToClipboard(): navigator.clipboard.writeText() is not working:", err);
|
||||
__modalDialog("Info", "Press OK to copy the text to the clipboard", true, false).then(function() {
|
||||
tools.error("copyTextToClipboard(): navigator.clipboard.writeText() is not working:", ex);
|
||||
tools.info("copyTextToClipboard(): Trying a workaround...");
|
||||
|
||||
let el = document.createElement("textarea");
|
||||
@@ -164,36 +164,46 @@ function __WindowManager() {
|
||||
el.setSelectionRange(0, el.value.length); // iOS
|
||||
|
||||
try {
|
||||
err = (document.execCommand("copy") ? null : "Unknown error");
|
||||
} catch (err) { // eslint-disable-line no-empty
|
||||
ex = (document.execCommand("copy") ? null : "Unknown error");
|
||||
} catch (ex) { // eslint-disable-line no-unused-vars
|
||||
}
|
||||
|
||||
// Remove the added textarea again:
|
||||
document.body.removeChild(el);
|
||||
|
||||
if (err) {
|
||||
tools.error("copyTextToClipboard(): Workaround failed:", err);
|
||||
wm.error("Can't copy text to the clipboard:<br>", err);
|
||||
if (ex) {
|
||||
tools.error("copyTextToClipboard(): Workaround failed:", ex);
|
||||
self.error("Can't copy text to the clipboard", `${ex}`);
|
||||
}
|
||||
};
|
||||
__modalDialog("Info", "Press OK to copy the text to the clipboard", true, false, callback);
|
||||
});
|
||||
};
|
||||
if (navigator.clipboard) {
|
||||
navigator.clipboard.writeText(text).then(function() {
|
||||
wm.info("The text has been copied to the clipboard");
|
||||
}, function(err) {
|
||||
workaround(err);
|
||||
self.info("The text has been copied to the clipboard");
|
||||
}, function(ex) {
|
||||
workaround(ex);
|
||||
});
|
||||
} else {
|
||||
workaround("navigator.clipboard is not available");
|
||||
}
|
||||
};
|
||||
|
||||
self.info = (...args) => __modalDialog("Info", args.join(" "), true, false);
|
||||
self.error = (...args) => __modalDialog("Error", args.join(" "), true, false);
|
||||
self.confirm = (...args) => __modalDialog("Question", args.join(" "), true, true);
|
||||
self.info = (html, ...args) => __modalCodeDialog("Info", html, args.join("\n"), true, false);
|
||||
self.error = (html, ...args) => __modalCodeDialog("Error", html, args.join("\n"), true, false);
|
||||
self.confirm = (html, ...args) => __modalCodeDialog("Question", html, args.join("\n"), true, true);
|
||||
self.modal = (header, html, ok, cancel) => __modalDialog(header, html, ok, cancel);
|
||||
|
||||
var __modalDialog = function(header, text, ok, cancel, callback=null, parent=null) {
|
||||
var __modalCodeDialog = function(header, html, code, ok, cancel) {
|
||||
let create_content = function(el_content) {
|
||||
if (code) {
|
||||
html += `<br><br><div class="code"><pre style="margin:0px">${tools.escape(code)}</pre></div>`;
|
||||
}
|
||||
el_content.innerHTML = html;
|
||||
};
|
||||
return __modalDialog(header, create_content, ok, cancel);
|
||||
};
|
||||
|
||||
var __modalDialog = function(header, html, ok, cancel, parent=null) {
|
||||
let el_active_menu = (document.activeElement && document.activeElement.closest(".menu"));
|
||||
|
||||
let el_modal = document.createElement("div");
|
||||
@@ -207,27 +217,50 @@ function __WindowManager() {
|
||||
|
||||
let el_header = document.createElement("div");
|
||||
el_header.className = "modal-header";
|
||||
el_header.innerHTML = header;
|
||||
el_header.innerText = header;
|
||||
el_window.appendChild(el_header);
|
||||
|
||||
let el_content = document.createElement("div");
|
||||
el_content.className = "modal-content";
|
||||
el_content.innerHTML = text;
|
||||
el_window.appendChild(el_content);
|
||||
|
||||
let el_buttons = document.createElement("div");
|
||||
el_buttons.classList.add("modal-buttons", "buttons-row");
|
||||
el_window.appendChild(el_buttons);
|
||||
|
||||
let el_cancel_button = null;
|
||||
let el_ok_button = null;
|
||||
if (cancel) {
|
||||
el_cancel_button = document.createElement("button");
|
||||
el_cancel_button.className = "row100";
|
||||
el_cancel_button.innerText = "Cancel";
|
||||
el_buttons.appendChild(el_cancel_button);
|
||||
}
|
||||
if (ok) {
|
||||
el_ok_button = document.createElement("button");
|
||||
el_ok_button.className = "row100";
|
||||
el_ok_button.innerText = "OK";
|
||||
el_buttons.appendChild(el_ok_button);
|
||||
}
|
||||
if (ok && cancel) {
|
||||
el_ok_button.className = "row50";
|
||||
el_cancel_button.className = "row50";
|
||||
}
|
||||
|
||||
el_window.onkeyup = function(event) {
|
||||
event.preventDefault();
|
||||
if (ok && event.code === "Enter") {
|
||||
el_ok_button.click();
|
||||
} else if (cancel && event.code === "Escape") {
|
||||
el_cancel_button.click();
|
||||
}
|
||||
};
|
||||
|
||||
let promise = null;
|
||||
if (ok || cancel) {
|
||||
promise = new Promise(function(resolve) {
|
||||
let el_buttons = document.createElement("div");
|
||||
el_buttons.className = "modal-buttons";
|
||||
el_window.appendChild(el_buttons);
|
||||
|
||||
function close(retval) {
|
||||
if (callback) {
|
||||
callback(retval);
|
||||
}
|
||||
__closeWindow(el_window);
|
||||
el_modal.outerHTML = "";
|
||||
let index = __windows.indexOf(el_modal);
|
||||
if (index !== -1) {
|
||||
__windows.splice(index, 1);
|
||||
@@ -238,38 +271,27 @@ function __WindowManager() {
|
||||
__activateLastWindow(el_modal);
|
||||
}
|
||||
resolve(retval);
|
||||
// Так как resolve() асинхронный, надо выполнить в эвентлупе после него
|
||||
setTimeout(function() { el_modal.outerHTML = ""; }, 0);
|
||||
}
|
||||
|
||||
if (cancel) {
|
||||
var el_cancel_button = document.createElement("button");
|
||||
el_cancel_button.innerHTML = "Cancel";
|
||||
tools.el.setOnClick(el_cancel_button, () => close(false));
|
||||
el_buttons.appendChild(el_cancel_button);
|
||||
}
|
||||
if (ok) {
|
||||
var el_ok_button = document.createElement("button");
|
||||
el_ok_button.innerHTML = "OK";
|
||||
tools.el.setOnClick(el_ok_button, () => close(true));
|
||||
el_buttons.appendChild(el_ok_button);
|
||||
}
|
||||
if (ok && cancel) {
|
||||
el_ok_button.className = "row50";
|
||||
el_cancel_button.className = "row50";
|
||||
}
|
||||
|
||||
el_window.onkeyup = function(event) {
|
||||
event.preventDefault();
|
||||
if (ok && event.code === "Enter") {
|
||||
el_ok_button.click();
|
||||
} else if (cancel && event.code === "Escape") {
|
||||
el_cancel_button.click();
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
__windows.push(el_modal);
|
||||
(parent || document.fullscreenElement || document.body).appendChild(el_modal);
|
||||
if (typeof html === "function") {
|
||||
// Это должно быть здесь, потому что элемент должен иметь родителя чтобы существовать
|
||||
html(el_content, el_ok_button);
|
||||
} else {
|
||||
el_content.innerHTML = html;
|
||||
}
|
||||
__activateWindow(el_modal);
|
||||
|
||||
return promise;
|
||||
@@ -312,7 +334,7 @@ function __WindowManager() {
|
||||
__activateLastWindow(el_window);
|
||||
};
|
||||
|
||||
self.toggleFullTabWindow = function(el_window, enabled) {
|
||||
self.setFullTabWindow = function(el_window, enabled) {
|
||||
el_window.classList.toggle("window-full-tab", enabled);
|
||||
__activateLastWindow(el_window);
|
||||
let el_navbar = $("navbar");
|
||||
@@ -625,7 +647,7 @@ function __WindowManager() {
|
||||
+ "In Chrome use HTTPS and enable <i>system-keyboard-lock</i><br>"
|
||||
+ "by putting at URL <i>chrome://flags/#system-keyboard-lock</i>"
|
||||
);
|
||||
__modalDialog("Keyboard lock is unsupported", msg, true, false, null, el_window);
|
||||
__modalDialog("Keyboard lock is unsupported", msg, true, false, el_window);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user