初步整合:
1. python 内置服务器 2. 将配置文件统一目录
66
kvmd_data/usr/share/kvmd/web/base.pug
Normal file
@@ -0,0 +1,66 @@
|
||||
doctype html
|
||||
|
||||
//
|
||||
==============================================================================
|
||||
# #
|
||||
# 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/>. #
|
||||
# #
|
||||
==============================================================================
|
||||
|
||||
- var css_dir = "/share/css"
|
||||
- var js_dir = "/share/js"
|
||||
- var svg_dir = "/share/svg"
|
||||
- var png_dir = "/share/png"
|
||||
|
||||
- var title = ""
|
||||
- var main_js = ""
|
||||
- var body_class = ""
|
||||
- var css_list = ["vars", "main"]
|
||||
|
||||
block vars
|
||||
|
||||
html(lang="en")
|
||||
head
|
||||
meta(charset="utf-8")
|
||||
title #{title}
|
||||
|
||||
link(rel="apple-touch-icon" sizes="180x180" href="/share/apple-touch-icon.png")
|
||||
link(rel="icon" type="image/png" sizes="32x32" href="/share/favicon-32x32.png")
|
||||
link(rel="icon" type="image/png" sizes="16x16" href="/share/favicon-16x16.png")
|
||||
link(rel="manifest" href="/share/site.webmanifest")
|
||||
link(rel="mask-icon" href="/share/safari-pinned-tab.svg" color="#5bbad5")
|
||||
meta(name="msapplication-TileColor" content="#2b5797")
|
||||
meta(name="theme-color" content="#ffffff")
|
||||
|
||||
each name in css_list
|
||||
link(rel="stylesheet" href=`${css_dir}/${name}.css`)
|
||||
link(rel="stylesheet" href=`${css_dir}/user.css`)
|
||||
|
||||
script(src=`${js_dir}/i18n/jquery-3.7.1.min.js`)
|
||||
script(src=`${js_dir}/i18n/jquery.i18n.min.js`)
|
||||
script(src=`${js_dir}/i18n/i18n.js`)
|
||||
|
||||
if main_js
|
||||
script(type="module")
|
||||
| import {main} from "#{js_dir}/#{main_js}.js";
|
||||
| main();
|
||||
|
||||
|
||||
body(class=body_class)
|
||||
block body
|
||||
|
||||
0
kvmd_data/usr/share/kvmd/web/extras/.gitignore
vendored
Normal file
74
kvmd_data/usr/share/kvmd/web/extras/webterm/terminal.svg
Normal file
@@ -0,0 +1,74 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Generator: Adobe Illustrator 18.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 31.586 31.586" style="enable-background:new 0 0 31.586 31.586;" xml:space="preserve">
|
||||
<g>
|
||||
<path d="M29.331,2.256H2.259C1.01,2.256,0,3.265,0,4.511v22.565c0,1.244,1.01,2.255,2.259,2.255h27.072
|
||||
c1.242,0,2.255-1.011,2.255-2.255V4.511C31.586,3.265,30.573,2.256,29.331,2.256z M10.788,3.95c0.623,0,1.126,0.502,1.126,1.128
|
||||
c0,0.623-0.503,1.125-1.126,1.125S9.659,5.701,9.659,5.078C9.659,4.452,10.165,3.95,10.788,3.95z M7.334,3.95
|
||||
c0.623,0,1.129,0.502,1.129,1.128c0,0.623-0.506,1.125-1.129,1.125c-0.625,0-1.131-0.503-1.131-1.125
|
||||
C6.203,4.452,6.709,3.95,7.334,3.95z M3.947,3.95c0.623,0,1.129,0.502,1.129,1.128c0,0.623-0.506,1.125-1.129,1.125
|
||||
c-0.621,0-1.126-0.503-1.126-1.125C2.821,4.452,3.326,3.95,3.947,3.95z M29.331,27.076H2.259V7.922h27.072
|
||||
C29.331,7.922,29.331,27.076,29.331,27.076z M29.331,5.665H13.536V4.537h15.795C29.331,4.537,29.331,5.665,29.331,5.665z"/>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
BIN
kvmd_data/usr/share/kvmd/web/favicon.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
93
kvmd_data/usr/share/kvmd/web/index.html
Normal file
@@ -0,0 +1,93 @@
|
||||
<!DOCTYPE html>
|
||||
<!--
|
||||
==============================================================================
|
||||
# #
|
||||
# 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/>. #
|
||||
# #
|
||||
==============================================================================
|
||||
|
||||
-->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>One-KVM Index</title>
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/share/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/share/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/share/favicon-16x16.png">
|
||||
<link rel="manifest" href="/share/site.webmanifest">
|
||||
<link rel="mask-icon" href="/share/safari-pinned-tab.svg" color="#5bbad5">
|
||||
<meta name="msapplication-TileColor" content="#2b5797">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
<link rel="stylesheet" href="/share/css/vars.css">
|
||||
<link rel="stylesheet" href="/share/css/main.css">
|
||||
<link rel="stylesheet" href="/share/css/start.css">
|
||||
<link rel="stylesheet" href="/share/css/window.css">
|
||||
<link rel="stylesheet" href="/share/css/modal.css">
|
||||
<link rel="stylesheet" href="/share/css/index/index.css">
|
||||
<link rel="stylesheet" href="/share/css/user.css">
|
||||
<script src="/share/js/i18n/jquery-3.7.1.min.js"></script>
|
||||
<script src="/share/js/i18n/jquery.i18n.min.js"></script>
|
||||
<script src="/share/js/i18n/i18n.js"></script>
|
||||
<script type="module">import {main} from "/share/js/index/main.js";
|
||||
main();
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="start-box">
|
||||
<div class="start">
|
||||
<table>
|
||||
<tr>
|
||||
<td class="logo"><a href="https://pikvm.org" target="_blank"><img class="svg-gray" src="/share/svg/logo.svg" alt="PiKVM" height="40"></a></td>
|
||||
<td>
|
||||
<table>
|
||||
<tr>
|
||||
<td class="title" colspan="2" i18n="index_title">The Open Source KVM over IP</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="copyright" colspan="2" i18n="copyright">Copyright © 2018-2024 Maxim Devaev | Modified by SilentWind</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<hr>
|
||||
<div id="apps-box">
|
||||
<h4>Loading ...</h4>
|
||||
</div>
|
||||
<hr>
|
||||
<table>
|
||||
<td class="server">
|
||||
<td i18n="serve_name">Server:</td>
|
||||
<td><a id="kvmd-meta-server-host" target="_blank" href="/api/info"></a></td>
|
||||
</td>
|
||||
</table>
|
||||
<div id="app-keyboard-warning">
|
||||
<p class="text" i18n="index_text_1">
|
||||
Please note that when you are working with a KVM session or another application that captures the keyboard,
|
||||
you can't use some keyboard shortcuts such as Ctrl+Alt+Del (which will be caught by your OS) or Ctrl+W (caught by your browser).
|
||||
</p>
|
||||
<p class="text" i18n="index_text_2">To override this limitation you can use <a target="_blank" href="https://google.com/chrome">Google Chrome</a>
|
||||
or <a target="_blank" href="https://chromium.org/Home">Chromium</a> in application mode.
|
||||
</p>
|
||||
</div>
|
||||
<hr>
|
||||
<p class="text credits"><a target="_blank" href="https://pikvm.org" i18n="index_text_10">PiKVM Project</a> | <a target="_blank" href="https://docs.pikvm.org" i18n="index_text_11">PiKVM Documentation</a> | <a target="_blank" href="https://github.com/mofeng-git/One-KVM" i18n="index_text_12">One-KVM Project</a> | <a target="_blank" href="https://one-kvm.mofeng.run" i18n="index_text_13">One-KVM Documentation</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
48
kvmd_data/usr/share/kvmd/web/index.pug
Normal file
@@ -0,0 +1,48 @@
|
||||
extends start.pug
|
||||
|
||||
append vars
|
||||
- title = "One-KVM Index"
|
||||
- main_js = "index/main"
|
||||
- css_list = css_list.concat(["window", "modal", "index/index"])
|
||||
|
||||
block start
|
||||
table
|
||||
tr
|
||||
td(class="logo")
|
||||
a(href="https://pikvm.org" target="_blank")
|
||||
img(class="svg-gray" src=`${svg_dir}/logo.svg` alt="PiKVM" height="40")
|
||||
td
|
||||
table
|
||||
tr #[td(colspan="2" class="title" i18n="index_title") The Open Source KVM over IP]
|
||||
tr
|
||||
td(colspan="2" class="copyright" i18n="copyright")
|
||||
| Copyright © 2018-2024 Maxim Devaev | Modified by SilentWind
|
||||
|
||||
hr
|
||||
|
||||
div(id="apps-box")
|
||||
h4 Loading ...
|
||||
|
||||
hr
|
||||
table
|
||||
td(class="server")
|
||||
td(i18n="serve_name") Server:
|
||||
td #[a(id="kvmd-meta-server-host" target="_blank" href="/api/info")]
|
||||
|
||||
div(id="app-keyboard-warning")
|
||||
p(class="text" i18n="index_text_1")
|
||||
| Please note that when you are working with a KVM session or another application that captures the keyboard,
|
||||
| you can't use some keyboard shortcuts such as Ctrl+Alt+Del (which will be caught by your OS) or Ctrl+W (caught by your browser).
|
||||
p(class="text" i18n="index_text_2")
|
||||
| To override this limitation you can use #[a(target="_blank" href="https://google.com/chrome") Google Chrome]
|
||||
| or #[a(target="_blank" href="https://chromium.org/Home") Chromium] in application mode.
|
||||
|
||||
hr
|
||||
p(class="text credits")
|
||||
a(target="_blank" href="https://pikvm.org" i18n="index_text_10") PiKVM Project
|
||||
| |
|
||||
a(target="_blank" href="https://docs.pikvm.org" i18n="index_text_11") PiKVM Documentation
|
||||
| |
|
||||
a(target="_blank" href="https://github.com/mofeng-git/One-KVM" i18n="index_text_12") One-KVM Project
|
||||
| |
|
||||
a(target="_blank" href="https://one-kvm.mofeng.run" i18n="index_text_13") One-KVM Documentation
|
||||
66
kvmd_data/usr/share/kvmd/web/ipmi/index.html
Normal file
@@ -0,0 +1,66 @@
|
||||
<!DOCTYPE html>
|
||||
<!--
|
||||
==============================================================================
|
||||
# #
|
||||
# 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/>. #
|
||||
# #
|
||||
==============================================================================
|
||||
|
||||
-->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>One-KVM IPMI Info</title>
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/share/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/share/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/share/favicon-16x16.png">
|
||||
<link rel="manifest" href="/share/site.webmanifest">
|
||||
<link rel="mask-icon" href="/share/safari-pinned-tab.svg" color="#5bbad5">
|
||||
<meta name="msapplication-TileColor" content="#2b5797">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
<link rel="stylesheet" href="/share/css/vars.css">
|
||||
<link rel="stylesheet" href="/share/css/main.css">
|
||||
<link rel="stylesheet" href="/share/css/start.css">
|
||||
<link rel="stylesheet" href="/share/css/user.css">
|
||||
<script src="/share/js/i18n/jquery-3.7.1.min.js"></script>
|
||||
<script src="/share/js/i18n/jquery.i18n.min.js"></script>
|
||||
<script src="/share/js/i18n/i18n.js"></script>
|
||||
<script type="module">import {main} from "/share/js/ipmi/main.js";
|
||||
main();
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="start-box">
|
||||
<div class="start"><a style="display:inline-block; margin-top:4px; color:#5c90bc; text-decoration:none" href="/" i18n="index"> ← [ One-KVM Index ]</a>
|
||||
<hr>
|
||||
<p class="text" i18n="ipmi_text1">This PiKVM device has running <b>kvmd-ipmi</b> daemon and provides IPMI 2.0 interface for some basic
|
||||
BMC operations like on/off/reset the server.
|
||||
</p>
|
||||
<p class="text" i18n="ipmi_text2"><b>WARNING!</b> We strongly don't recommend you to use IPMI in untrusted networks because
|
||||
this protocol is completely unsafe by design. In short, the authentication process for IPMI mandates
|
||||
that the server send a salted SHA1 or MD5 hash of the requested user's password to the client,
|
||||
prior to the client authenticating.
|
||||
</p>
|
||||
<p class="text" i18n="ipmi_text3"><b>NEVER</b> use the same passwords for KVMD and IPMI users. And even better not to use IPMI.
|
||||
Instead, you can directly use KVMD API via curl. Here some examples:
|
||||
</p>
|
||||
<div class="code" id="ipmi-text" style="max-height:200px"></div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
20
kvmd_data/usr/share/kvmd/web/ipmi/index.pug
Normal file
@@ -0,0 +1,20 @@
|
||||
extends ../start.pug
|
||||
|
||||
append vars
|
||||
- title = "One-KVM IPMI Info"
|
||||
- main_js = "ipmi/main"
|
||||
- index_link = true
|
||||
|
||||
block start
|
||||
p(class="text" i18n="ipmi_text1")
|
||||
| This PiKVM device has running #[b kvmd-ipmi] daemon and provides IPMI 2.0 interface for some basic
|
||||
| BMC operations like on/off/reset the server.
|
||||
p(class="text" i18n="ipmi_text2")
|
||||
| #[b WARNING!] We strongly don't recommend you to use IPMI in untrusted networks because
|
||||
| this protocol is completely unsafe by design. In short, the authentication process for IPMI mandates
|
||||
| that the server send a salted SHA1 or MD5 hash of the requested user's password to the client,
|
||||
| prior to the client authenticating.
|
||||
p(class="text" i18n="ipmi_text3")
|
||||
| #[b NEVER] use the same passwords for KVMD and IPMI users. And even better not to use IPMI.
|
||||
| Instead, you can directly use KVMD API via curl. Here some examples:
|
||||
div(id="ipmi-text" class="code" style="max-height:200px")
|
||||
2737
kvmd_data/usr/share/kvmd/web/kvm/index.html
Normal file
34
kvmd_data/usr/share/kvmd/web/kvm/index.pug
Normal file
@@ -0,0 +1,34 @@
|
||||
extends ../base.pug
|
||||
|
||||
append vars
|
||||
- title = "One-KVM Session"
|
||||
- main_js = "kvm/main"
|
||||
- body_class = "body-no-select"
|
||||
- css_list = css_list.concat(["navbar", "window", "modal", "led", "slider", "switch", "radio", "progress", "keypad", "tabs"])
|
||||
- css_list = css_list.concat(["kvm/stream", "kvm/hid", "kvm/msd", "kvm/system", "kvm/keyboard", "kvm/about"])
|
||||
|
||||
block body
|
||||
include navbar.pug
|
||||
include windows.pug
|
||||
|
||||
ul(class="navbar-bg-tips")
|
||||
li(class="left")
|
||||
pre(id="kvmd-meta-tips-left")
|
||||
li(class="right")
|
||||
pre(id="kvmd-meta-tips-right")
|
||||
|
||||
ul(class="footer")
|
||||
li(class="left")
|
||||
span(id="kvmd-meta-server-host" title="Server name (see System/About)")
|
||||
| |
|
||||
span(id="kvmd-version-kvmd" title="KVMD version")
|
||||
| |
|
||||
span(id="kvmd-version-streamer" title="Streamer version")
|
||||
li(class="right")
|
||||
a(target="_blank" href="https://pikvm.org" i18n="index_text_10") PiKVM Project
|
||||
| |
|
||||
a(target="_blank" href="https://docs.pikvm.org" i18n="index_text_11") Documentation
|
||||
| |
|
||||
a(target="_blank" href="https://github.com/mofeng-git/One-KVM" i18n="index_text_12") One-KVM Project
|
||||
| |
|
||||
a(target="_blank" href="https://one-kvm.mofeng.run" i18n="index_text_13") One-KVM Documentation
|
||||
17
kvmd_data/usr/share/kvmd/web/kvm/navbar-atx.pug
Normal file
@@ -0,0 +1,17 @@
|
||||
li(id="atx-dropdown" class="right feature-disabled")
|
||||
a(class="menu-button" href="#")
|
||||
+navbar_led("atx-power-led", "led-atx-power")
|
||||
+navbar_led("atx-hdd-led", "led-atx-hdd")
|
||||
span ATX
|
||||
div(class="menu")
|
||||
div(class="text")
|
||||
b Control the server's power#[br]
|
||||
sub Use the short click for ACPI shutdown
|
||||
hr
|
||||
+menu_switch("atx-ask-switch", "Ask click confirmation", true, true,"atx-ask-switch")
|
||||
hr
|
||||
div(class="buttons")
|
||||
button(disabled data-force-hide-menu id="atx-power-button") • Click Power #[sup #[i short]]
|
||||
button(disabled data-force-hide-menu id="atx-power-button-long") • Click Power #[sup #[i long]]
|
||||
hr
|
||||
button(disabled data-force-hide-menu id="atx-reset-button") • Click Reset
|
||||
4
kvmd_data/usr/share/kvmd/web/kvm/navbar-gpio.pug
Normal file
@@ -0,0 +1,4 @@
|
||||
li(id="gpio-dropdown" class="right feature-disabled")
|
||||
a(class="menu-button" id="gpio-menu-button" href="#")
|
||||
span GPIO
|
||||
div(id="gpio-menu" class="menu")
|
||||
32
kvmd_data/usr/share/kvmd/web/kvm/navbar-health.pug
Normal file
@@ -0,0 +1,32 @@
|
||||
div(id="hw-health-dropdown" class="hidden")
|
||||
li(class="left")
|
||||
a(class="menu-button" href="#")
|
||||
+navbar_led("hw-health-undervoltage-led", "led-undervoltage", "hidden")
|
||||
+navbar_led("hw-health-overheating-led", "led-overheating", "hidden")
|
||||
div(class="menu")
|
||||
+menu_message("warning", "Raspberry Pi's health is at risk")
|
||||
| This is not a drill! A red icon indicates a current issue,#[br]
|
||||
| a yellow one that was observed since the device booted up
|
||||
div(id="hw-health-message-undervoltage" class="hidden")
|
||||
hr
|
||||
+menu_message("led-undervoltage", "Undervoltage detected", "led-gray")
|
||||
| Make sure your power supply and cabling are providing#[br]
|
||||
| enough power to the Raspberry Pi (3A minimum)
|
||||
div(id="hw-health-message-overheating" class="hidden")
|
||||
hr
|
||||
+menu_message("led-overheating", "Overheating detected", "led-gray")
|
||||
| Frequency capping due to overheating,#[br]
|
||||
| please improve cooling of the Raspberry Pi
|
||||
|
||||
div(id="fan-health-dropdown" class="hidden")
|
||||
li(class="left")
|
||||
a(class="menu-button" href="#")
|
||||
+navbar_led("fan-health-led", "led-fan", "hidden")
|
||||
div(class="menu")
|
||||
+menu_message("warning", "Raspberry Pi's health is at risk")
|
||||
| This is not a drill! A red icon indicates a current issue,#[br]
|
||||
| a yellow one that was observed in the past
|
||||
div(id="fan-health-message-fail")
|
||||
hr
|
||||
+menu_message("led-fan", "Fan failed", "led-gray")
|
||||
| A fan error occured, please #[a(href="/api/log?seek=3600&follow=1" target="_blank") check the log]
|
||||
30
kvmd_data/usr/share/kvmd/web/kvm/navbar-macro.pug
Normal file
@@ -0,0 +1,30 @@
|
||||
li(id="macro-dropdown" class="right")
|
||||
a(class="menu-button" href="#")
|
||||
+navbar_led("hid-recorder-led", "led-gear")
|
||||
span(i18n="kvm_text32") Macro
|
||||
div(class="menu")
|
||||
div(class="text")
|
||||
b(i18n="kvm_text33") Record and play HID/ATX/GPIO actions#[br]
|
||||
sub(i18n="kvm_text34") For security reasons, the record will not be saved on the PiKVM
|
||||
hr
|
||||
div(class="buttons buttons-row")
|
||||
button(disabled data-force-hide-menu id="hid-recorder-record" class="row25" i18n="kvm_text35") • Rec
|
||||
button(disabled id="hid-recorder-stop" class="row25" i18n="kvm_text36") Stop
|
||||
button(disabled id="hid-recorder-play" class="row25" i18n="kvm_text37") Play
|
||||
button(disabled id="hid-recorder-clear" class="row25" i18n="kvm_text38") Clear
|
||||
hr
|
||||
table(class="kv")
|
||||
tr
|
||||
td(i18n="kvm_text39") Script time:
|
||||
td(colspan="2" id="hid-recorder-time" class="value") 00:00:00.0
|
||||
tr
|
||||
td(i18n="kvm_text40") Scripted events:
|
||||
td(id="hid-recorder-events-count" class="value") 0
|
||||
td #[sup #[i(i18n="kvm_text41") include delays]]
|
||||
hr
|
||||
+menu_switch("hid-recorder-loop-switch", "Infinite loop playback", false, false, "hid-recorder-loop-switch")
|
||||
hr
|
||||
input(type="file" id="hid-recorder-new-script-file")
|
||||
div(class="buttons buttons-row")
|
||||
button(disabled id="hid-recorder-upload" class="row50" i18n="kvm_text42") Upload script
|
||||
button(disabled id="hid-recorder-download" class="row50" i18n="kvm_text43") Download script
|
||||
124
kvmd_data/usr/share/kvmd/web/kvm/navbar-msd.pug
Normal file
@@ -0,0 +1,124 @@
|
||||
li(id="msd-dropdown" class="right feature-disabled")
|
||||
a(class="menu-button" href="#")
|
||||
+navbar_led("msd-led", "led-msd")
|
||||
span(i18n="kvm_text60") Drive
|
||||
div(id="msd-menu" class="menu")
|
||||
div(class="text")
|
||||
b(i18n="kvm_text61") Mass Storage Drive:
|
||||
span(id="msd-status")
|
||||
br
|
||||
hr
|
||||
div(id="msd-message-offline" class="hidden")
|
||||
+menu_message("warning", "Mass Storage Drive is offline", "msd-message-offline")
|
||||
hr
|
||||
div(id="msd-message-image-broken" class="hidden")
|
||||
+menu_message("warning", "Current image is broken!", "msd-message-image-broken")
|
||||
| Perhaps uploading was interrupted#[br]
|
||||
hr
|
||||
div(id="msd-message-too-big-for-cdrom" class="hidden")
|
||||
+menu_message("warning", "Current image is too big for CD-ROM!", "msd-message-too-big-for-cdrom")
|
||||
| The device filesystem will be truncated to 2.2GiB
|
||||
hr
|
||||
div(id="msd-message-out-of-storage" class="hidden")
|
||||
+menu_message("warning", "Current image is out of storage", "msd-message-out-of-storage")
|
||||
| This image was connected manually using #[b kvmd-otgmsd]
|
||||
hr
|
||||
div(id="msd-message-rw-enabled" class="hidden")
|
||||
+menu_message("warning", "Read-write mode is enabled", "msd-message-rw-enabled")
|
||||
| Do not turn off PiKVM while this is active to prevent#[br]
|
||||
| filesystem corruption. Use read-only mode where possible,#[br]
|
||||
| as writing to SD card often can reduce its lifespan.
|
||||
hr
|
||||
div(id="msd-message-downloads" class="hidden")
|
||||
+menu_message("info", "The image is being downloaded from PiKVM", "msd-message-downloads")
|
||||
| Please wait
|
||||
hr
|
||||
table(class="kv")
|
||||
tr
|
||||
td(i18n="kvm_text62") Image:
|
||||
td(width="100%") #[select(disabled id="msd-image-selector")]
|
||||
td #[button(disabled id="msd-download-button" title="Download image") #[b ⇩ ]]
|
||||
td #[button(disabled id="msd-remove-button" title="Remove image") #[b × ]]
|
||||
table(class="kv")
|
||||
tr
|
||||
td(i18n="kvm_text63") Drive #[a(target="_blank" href="https://docs.pikvm.org/msd") mode]:
|
||||
td
|
||||
div(class="radio-box")
|
||||
input(checked type="radio" id="msd-mode-radio-cdrom" name="msd-mode-radio" value="1")
|
||||
label(for="msd-mode-radio-cdrom") CD-ROM
|
||||
input(type="radio" id="msd-mode-radio-flash" name="msd-mode-radio" value="0")
|
||||
label(for="msd-mode-radio-flash") Flash
|
||||
td
|
||||
+menu_switch_notable("msd-rw-switch", "Writable", false, false, "msd-rw-switch")
|
||||
tr
|
||||
td(i18n="kvm_text84") 文件内容:
|
||||
td
|
||||
div(class="radio-box")
|
||||
input(checked type="radio" id="msd-mode-radio-image" name="file-mode-radio" value="1")
|
||||
label(for="msd-mode-radio-image" i18n="kvm_text90") ImageFiles
|
||||
input(type="radio" id="msd-mode-radio-file" name="file-mode-radio" value="0")
|
||||
label(for="msd-mode-radio-file" i18n="kvm_text91") NormalFiles
|
||||
div(id="msd-storages")
|
||||
hr
|
||||
div(class="buttons buttons-row")
|
||||
button(disabled id="msd-select-new-button" class="row50" i18n="kvm_text64") Select image to upload
|
||||
button(disabled id="msd-upload-new-button" class="row25" i18n="kvm_text65") Upload
|
||||
button(disabled id="msd-abort-new-button" class="row25" i18n="kvm_text66") Abort
|
||||
div(id="msd-message-another-user-uploads" class="hidden")
|
||||
hr
|
||||
+menu_message("info", "Another user uploads an image", "msd-message-another-user-uploads")
|
||||
div(id="msd-new-sub" class="hidden")
|
||||
hr
|
||||
table(class="kv")
|
||||
tr
|
||||
td(i18n="kvm_text68") Specify a local file:
|
||||
td #[input(type="file" id="msd-new-file")]
|
||||
tr
|
||||
td(i18n="kvm_text69") #[b Or] paste a URL:
|
||||
td #[input(type="text" id="msd-new-url" style="width: 100%")]
|
||||
tr(id="msd-new-part" class="hidden")
|
||||
td(i18n="kvm_text70") Upload partition:
|
||||
td(width="100%") #[select(id="msd-new-part-selector")]
|
||||
div(id="msd-uploading-sub" class="hidden")
|
||||
hr
|
||||
table(class="kv")
|
||||
tr
|
||||
td(i18n="kvm_text74") New image:
|
||||
td(id="msd-uploading-name" class="value")
|
||||
tr
|
||||
td(i18n="kvm_text75") Upload size:
|
||||
td(id="msd-uploading-size" class="value")
|
||||
div(class="text")
|
||||
div(id="msd-uploading-progress" class="progress")
|
||||
span(id="msd-uploading-progress-value" class="progress-value")
|
||||
div(id="msd-new-tips" class="hidden")
|
||||
hr
|
||||
table(class="kv")
|
||||
tr
|
||||
td(class="value" i18n="kvm_text71") Note:
|
||||
td(i18n="kvm_text72") • Don't close the browser page until the upload is complete.
|
||||
tr
|
||||
td
|
||||
td(i18n="kvm_text73") • To speed up the upload, close the stream window.
|
||||
hr
|
||||
div(class="buttons buttons-row")
|
||||
button(disabled id="msd-connect-button" class="row50" i18n="kvm_text76") Connect drive to Server
|
||||
button(disabled id="msd-disconnect-button" class="row25" i18n="kvm_text77") Disconnect
|
||||
button(disabled id="msd-reset-button" class="row25" i18n="kvm_text78") Reset
|
||||
|
||||
|
||||
|
||||
hr
|
||||
div(class="text")
|
||||
b(i18n="kvm_text85") Quick file transfer:
|
||||
br
|
||||
sub(i18n="kvm_text86") • Select NormalFiles tab to upload, package them and mount image
|
||||
br
|
||||
sub(i18n="kvm_text87") • Disconnect MSD, unpackage it, select tab to download
|
||||
br
|
||||
hr
|
||||
div(class="buttons buttons-row")
|
||||
button(id="msd-file-image-update-button" class="row50" i18n="kvm_text88") Package files into image
|
||||
button(id="msd-file-image-unzip-button" class="row50" i18n="kvm_text89") Unpackage files from image
|
||||
hr
|
||||
|
||||
68
kvmd_data/usr/share/kvmd/web/kvm/navbar-shortcuts.pug
Normal file
@@ -0,0 +1,68 @@
|
||||
li(id="shortcuts-dropdown" class="right")
|
||||
a(class="menu-button" href="#" i18n="kvm_text56") Shortcuts
|
||||
div(id="shortcuts-menu" class="menu")
|
||||
div(class="text")
|
||||
b(i18n="kvm_text57") Quick keyboard shortcuts#[br]
|
||||
sub(i18n="kvm_text58") Also see #[i System → Show keyboard]
|
||||
hr
|
||||
div(class="buttons")
|
||||
div(class="buttons-row")
|
||||
button(data-force-hide-menu data-shortcut="CapsLock" class="row50")
|
||||
| • Caps Lock
|
||||
img(class="inline-lamp hid-keyboard-caps-led led-gray" src=`${svg_dir}/led-square.svg`)
|
||||
button(data-force-hide-menu data-shortcut="MetaLeft" class="row50") • Left Win
|
||||
hr
|
||||
div(class="buttons-row")
|
||||
button(data-force-hide-menu data-shortcut="AltLeft ShiftLeft" class="row50") • Alt+Shift
|
||||
button(data-force-hide-menu data-shortcut="ControlLeft KeyW" class="row50") • Ctrl+W
|
||||
div(class="buttons-row")
|
||||
button(data-force-hide-menu data-shortcut="ControlLeft ShiftLeft" class="row50") • Ctrl+Shift
|
||||
button(data-force-hide-menu data-shortcut="AltLeft Tab" class="row50") • Alt+Tab
|
||||
div(class="buttons-row")
|
||||
button(data-force-hide-menu data-shortcut="ShiftLeft ShiftRight" class="row50") • Shift+Shift
|
||||
button(data-force-hide-menu data-shortcut="AltLeft Enter" class="row50") • Alt+Enter
|
||||
div(class="buttons-row")
|
||||
button(data-force-hide-menu data-shortcut="MetaLeft Space" class="row50") • Win+Space
|
||||
button(data-force-hide-menu data-shortcut="AltLeft F4" class="row50") • Alt+F4
|
||||
hr
|
||||
div(class="buttons-row")
|
||||
button(data-force-hide-menu data-shortcut="ControlLeft AltLeft F1" class="row50") • Ctrl+Alt+F1
|
||||
button(data-force-hide-menu data-shortcut="MetaLeft KeyL" class="row50") • Win+L
|
||||
div(class="buttons-row")
|
||||
button(data-force-hide-menu data-shortcut="ControlLeft AltLeft F2" class="row50") • Ctrl+Alt+F2
|
||||
button(data-force-hide-menu data-shortcut="PrintScreen" class="row50") • Print Screen
|
||||
hr
|
||||
div(class="buttons-row")
|
||||
button(data-force-hide-menu data-shortcut="ControlLeft AltLeft Delete" class="row50") • Ctrl+Alt+Del
|
||||
button(data-force-hide-menu data-shortcut="Power" class="row50") • Power
|
||||
hr
|
||||
div(class="text")
|
||||
| ↓ • Alt+SysRq+... <sup><i>linux magic
|
||||
| #[a(target="_blank" href="https://www.kernel.org/doc/html/latest/admin-guide/sysrq.html" i18n="kvm_text59") help]</i></sup>
|
||||
hr
|
||||
div(class="buttons")
|
||||
div(class="buttons-row")
|
||||
-
|
||||
let sysrq = {
|
||||
"F": "Call the OOM killer to kill a memory hog process",
|
||||
"M": "Dump current memory info to the console",
|
||||
"D": "Show all locks that are held",
|
||||
"T": "Dump a list of current tasks and their information to the console",
|
||||
}
|
||||
each title, key in sysrq
|
||||
button(data-shortcut=`AltLeft PrintScreen Key${key}` data-shortcut-confirm="hid-sysrq-ask-switch" class="row25" style="text-align: center;" title=`${title}`) #{key}
|
||||
hr
|
||||
div(class="buttons-row")
|
||||
-
|
||||
sysrq = {
|
||||
"R": "Turn off keyboard raw mode, set it to XLATE",
|
||||
"E": "Send a SIGTERM to all processes, except for init",
|
||||
"I": "Send a SIGKILL to all processes, except for init",
|
||||
"S": "Attempt to sync all mounted filesystems",
|
||||
"U": "Attempt to remount all mounted filesystems read-only",
|
||||
"B": "Immediately reboot the system without syncing or unmounting disks",
|
||||
}
|
||||
each title, key in sysrq
|
||||
button(data-shortcut=`AltLeft PrintScreen Key${key}` data-shortcut-confirm="hid-sysrq-ask-switch" class="row16" style="text-align: center;" title=`${title}`) #{key}
|
||||
hr
|
||||
+menu_switch("hid-sysrq-ask-switch", "Ask the magic confirmation", true, true,"hid-sysrq-ask-switch")
|
||||
145
kvmd_data/usr/share/kvmd/web/kvm/navbar-system.pug
Normal file
@@ -0,0 +1,145 @@
|
||||
li(id="system-dropdown" class="right")
|
||||
a(class="menu-button" href="#")
|
||||
+navbar_led("link-led", "led-link")
|
||||
+navbar_led("stream-led", "led-stream")
|
||||
+navbar_led("hid-keyboard-led", "led-hid-keyboard")
|
||||
+navbar_led("hid-mouse-led", "led-hid-mouse")
|
||||
span(i18n="kvm_text3") System
|
||||
div(id="system-menu" class="menu")
|
||||
table(class="kv")
|
||||
tr
|
||||
td(class="value" i18n="kvm_text4") Runtime settings & tools
|
||||
td(id="system-tool-webterm" class="feature-disabled") #[button(data-force-hide-menu data-show-window="webterm-window" class="small" i18n="kvm_text5") • Term]
|
||||
td(id="system-tool-about") #[button(data-force-hide-menu data-show-window="about-window" class="small" i18n="kvm_text6") • About]
|
||||
td(id="system-tool-log") #[button(data-force-hide-menu id="open-log-button" class="small" i18n="kvm_text7") • Log]
|
||||
td(id="system-tool-wol" class="feature-disabled")
|
||||
button(data-force-hide-menu class="__gpio-button-__wol__ __gpio-button small" data-channel="__wol__"
|
||||
data-confirm="Are you sure to send Wake-on-LAN packet to the server?" i18n="kvm_text8") • WoL
|
||||
hr
|
||||
div(id="stream-message-no-webrtc" class="hidden")
|
||||
+menu_message("warning", "WebRTC is not supported by this browser", "stream-message-no-webrtc")
|
||||
hr
|
||||
div(id="stream-message-no-h264" class="hidden")
|
||||
+menu_message("warning", "H.264 is not supported by this browser", "stream-message-no-h264")
|
||||
hr
|
||||
table(class="kv")
|
||||
tr(id="stream-resolution" class="feature-disabled")
|
||||
td(i18n="kvm_text9") Resolution:
|
||||
td #[select(disabled id="stream-resolution-selector")]
|
||||
tr(id="stream-quality" class="feature-disabled")
|
||||
td(i18n="kvm_text10") JPEG quality:
|
||||
td(class="value-slider") #[input(disabled type="range" id="stream-quality-slider" class="slider")]
|
||||
td(id="stream-quality-value" class="value-number")
|
||||
tr
|
||||
td(i18n="kvm_text11") JPEG max fps:
|
||||
td(class="value-slider") #[input(disabled type="range" id="stream-desired-fps-slider" class="slider")]
|
||||
td(id="stream-desired-fps-value" class="value-number")
|
||||
tr(id="stream-h264-bitrate" class="feature-disabled")
|
||||
td(i18n="kvm_text12") H.264 kbps:
|
||||
td(class="value-slider") #[input(disabled type="range" id="stream-h264-bitrate-slider" class="slider")]
|
||||
td(id="stream-h264-bitrate-value" class="value-number")
|
||||
tr(id="stream-h264-gop" class="feature-disabled")
|
||||
td(i18n="kvm_text13") H.264 #[a(target="_blank" href="https://docs.pikvm.org/webrtc") gop]:
|
||||
td(class="value-slider") #[input(disabled type="range" id="stream-h264-gop-slider" class="slider")]
|
||||
td(id="stream-h264-gop-value" class="value-number")
|
||||
tr(id="stream-mode" class="feature-disabled")
|
||||
td(i18n="kvm_text14") Video #[a(target="_blank" href="https://docs.pikvm.org/webrtc") mode]:
|
||||
td
|
||||
div(class="radio-box")
|
||||
input(checked type="radio" id="stream-mode-radio-mjpeg" name="stream-mode-radio" value="mjpeg")
|
||||
label(for="stream-mode-radio-mjpeg") MJPEG / HTTP
|
||||
input(type="radio" id="stream-mode-radio-janus" name="stream-mode-radio" value="janus")
|
||||
label(for="stream-mode-radio-janus") H.264 / WebRTC
|
||||
tr(id="stream-orient" class="feature-disabled")
|
||||
td(i18n="kvm_text17") Orientation:
|
||||
td
|
||||
div(class="radio-box")
|
||||
input(checked type="radio" id="stream-orient-radio-0" name="stream-orient-radio" value="0")
|
||||
label(for="stream-orient-radio-0" i18n="kvm_text18") Default
|
||||
input(type="radio" id="stream-orient-radio-90" name="stream-orient-radio" value="90")
|
||||
label(for="stream-orient-radio-90") 90°
|
||||
input(type="radio" id="stream-orient-radio-180" name="stream-orient-radio" value="180")
|
||||
label(for="stream-orient-radio-180") 180°
|
||||
input(type="radio" id="stream-orient-radio-270" name="stream-orient-radio" value="270")
|
||||
label(for="stream-orient-radio-270") 270°
|
||||
tr(id="stream-audio", class="feature-disabled")
|
||||
td(i18n="kvm_text19") Audio volume:
|
||||
td(class="value-slider") #[input(type="range" id="stream-audio-volume-slider" class="slider")]
|
||||
td(id="stream-audio-volume-value" class="value-number")
|
||||
hr
|
||||
div(class="buttons buttons-row")
|
||||
button(data-force-hide-menu data-show-window="stream-window" class="row33" i18n="kvm_text20") • Show stream
|
||||
button(data-force-hide-menu id="stream-screenshot-button" class="row33" i18n="kvm_text21") • Screenshot
|
||||
button(id="stream-reset-button" class="row33" i18n="kvm_text22") Reset stream
|
||||
div(class="text")
|
||||
b(i18n="kvm_text79") Video Record#[br]
|
||||
sub(i18n="kvm_text80") Record video using the browser API, and will be downloaded automatically
|
||||
div(class="buttons buttons-row")
|
||||
button(data-force-hide-menu id="stream-record-start-button" class="row50" i18n="kvm_text81") • Start recording
|
||||
button(data-force-hide-menu id="stream-record-stop-button" class="row50" i18n="kvm_text82") • End recording
|
||||
hr
|
||||
table(class="kv")
|
||||
tr(id="hid-outputs-keyboard", class="feature-disabled")
|
||||
td(i18n="kvm_text23") Keyboard mode:
|
||||
td #[div(id="hid-outputs-keyboard-box" class="radio-box")]
|
||||
tr(id="hid-outputs-mouse", class="feature-disabled")
|
||||
td(i18n="kvm_text24") Mouse #[a(target="_blank" href="https://docs.pikvm.org/mouse") mode]:
|
||||
td #[div(id="hid-outputs-mouse-box" class="radio-box")]
|
||||
details
|
||||
summary(i18n="kvm_text25") Keyboard & Mouse (HID) settings
|
||||
div(class="spoiler")
|
||||
table(class="kv")
|
||||
tr
|
||||
+menu_switch_notable("hid-keyboard-swap-cc-switch", "Swap Left Ctrl and Caps keys", true, false, "hid-keyboard-swap-cc-switch")
|
||||
hr
|
||||
table(class="kv")
|
||||
tr
|
||||
td(i18n="kvm_text26") Mouse polling:
|
||||
td(class="value-slider") #[input(type="range" id="hid-mouse-rate-slider" class="slider")]
|
||||
td(id="hid-mouse-rate-value" class="value-number")
|
||||
tr(id="hid-mouse-sens" class="feature-disabled")
|
||||
td(i18n="kvm_text27") Relative sensitivity:
|
||||
td(class="value-slider") #[input(disabled type="range" id="hid-mouse-sens-slider" class="slider")]
|
||||
td(id="hid-mouse-sens-value" class="value-number")
|
||||
tr(id="hid-mouse-squash" class="feature-disabled")
|
||||
+menu_switch_notable("hid-mouse-squash-switch", "Squash relative moves", true, true, "hid-mouse-squash-switch")
|
||||
tr
|
||||
td(i18n="kvm_text28") Reverse scrolling:
|
||||
td
|
||||
table
|
||||
tr
|
||||
+menu_switch_notable("hid-mouse-reverse-scrolling-switch", "Y", true, false)
|
||||
td
|
||||
+menu_switch_notable("hid-mouse-reverse-panning-switch", "X", true, false)
|
||||
tr
|
||||
+menu_switch_notable("hid-mouse-cumulative-scrolling-switch", "Cumulative scrolling", true, false,"hid-mouse-cumulative-scrolling-switch")
|
||||
tr
|
||||
td(i18n="kvm_text29") Scroll rate:
|
||||
td(class="value-slider") #[input(type="range" id="hid-mouse-scroll-slider" class="slider")]
|
||||
td(id="hid-mouse-scroll-value" class="value-number")
|
||||
tr
|
||||
+menu_switch_notable("hid-mouse-dot-switch", "Show the blue dot", true, true, "hid-mouse-dot-switch")
|
||||
details
|
||||
summary(i18n="kvm_text83") Web UI settings
|
||||
div(class="spoiler")
|
||||
table(class="kv")
|
||||
tr
|
||||
+menu_switch_notable("page-close-ask-switch", "Ask page close confirmation", true, true, "page-close-ask-switch")
|
||||
tr
|
||||
+menu_switch_notable("page-full-tab-stream-switch", "Expand for the entire tab by default", true, false,"page-full-tab-stream-switch")
|
||||
table(class="kv")
|
||||
tr(id="hid-connect" class="feature-disabled")
|
||||
+menu_switch_notable("hid-connect-switch", "Connect HID to Server", true, true, "hid-connect-switch")
|
||||
tr(id="hid-jiggler" class="feature-disabled")
|
||||
+menu_switch_notable("hid-jiggler-switch", "<a href=\"https://docs.pikvm.org/mouse_jiggler\" target=\"_blank\">Mouse jiggler</a>", false, false,"hid-jiggler-switch")
|
||||
tr
|
||||
+menu_switch_notable("hid-mute-switch", "Mute HID input events", true, false, "hid-mute-switch")
|
||||
tr(id="v3-usb-breaker" class="feature-disabled")
|
||||
+menu_switch_notable_gpio("__v3_usb_breaker__", "Connect main USB to Server",
|
||||
"Turning off this switch will disconnect the main USB<br>from the server. Are you sure you want to continue?")
|
||||
tr(id="v4-locator" class="feature-disabled")
|
||||
+menu_switch_notable_gpio("__v4_locator__", "Enable locator LED")
|
||||
hr
|
||||
div(class="buttons buttons-row")
|
||||
button(data-force-hide-menu data-show-window="keyboard-window" class="row50" i18n="kvm_text30") • Show keyboard
|
||||
button(disabled id="hid-reset-button" class="row50" i18n="kvm_text31") Reset HID
|
||||
46
kvmd_data/usr/share/kvmd/web/kvm/navbar-text.pug
Normal file
@@ -0,0 +1,46 @@
|
||||
li(id="text-dropdown" class="right")
|
||||
a(class="menu-button" href="#")
|
||||
+navbar_led("stream-ocr-led", "led-gear", "feature-disabled")
|
||||
span(i18n="kvm_text44") Text
|
||||
div(id="text-menu" class="menu")
|
||||
div(class="text")
|
||||
b(i18n="kvm_text45") Paste text as keypress sequence#[br]
|
||||
sub(i18n="kvm_text46") Please note that PiKVM cannot switch the keyboard layout
|
||||
hr
|
||||
div(class="text" style="margin-right: 20px")
|
||||
textarea(id="hid-pak-text" data-focus placeholder="Enter your text here")
|
||||
table(class="kv")
|
||||
tr
|
||||
td
|
||||
button(disabled data-force-hide-menu id="hid-pak-button" i18n="kvm_text47") • Paste
|
||||
td(i18n="kvm_text48") using host keymap
|
||||
td
|
||||
select(id="hid-pak-keymap-selector")
|
||||
table(class="kv")
|
||||
tr
|
||||
+menu_switch_notable("hid-pak-ask-switch", "Ask paste confirmation", true, true, "hid-pak-ask-switch")
|
||||
tr(id="hid-pak-secure" class="feature-disabled")
|
||||
+menu_switch_notable("hid-pak-secure-switch", "Hide input text", true, false,"hid-pak-secure-switch")
|
||||
div(id="stream-ocr" class="feature-disabled")
|
||||
hr
|
||||
br
|
||||
hr
|
||||
div(class="text")
|
||||
b(i18n="kvm_text49") Text recognition <sup><i>β</i></sup>#[br]
|
||||
sub(i18n="kvm_text50") #[a(target="_blank" href="https://docs.pikvm.org/ocr") OCR] works locally on PiKVM
|
||||
hr
|
||||
table(class="kv")
|
||||
tr
|
||||
td
|
||||
button(data-force-hide-menu id="stream-ocr-button" i18n="kvm_text51") • Select area
|
||||
td(i18n="kvm_text52") for
|
||||
td
|
||||
select(id="stream-ocr-lang-selector")
|
||||
td(i18n="kvm_text53") text recognition
|
||||
table(class="kv")
|
||||
tr
|
||||
td(colspan="4" i18n="kvm_text54") • Press #[b Enter] to recognize and copy text to clipboard
|
||||
tr
|
||||
td(colspan="4" i18n="kvm_text55") • Press #[b Esc] to cancel selection
|
||||
tr
|
||||
td
|
||||
53
kvmd_data/usr/share/kvmd/web/kvm/navbar.pug
Normal file
@@ -0,0 +1,53 @@
|
||||
mixin navbar_led(id, icon, cls="led-gray")
|
||||
img(id=id, class=cls src=`${svg_dir}/${icon}.svg`)
|
||||
|
||||
mixin menu_message(icon, short, classes="", i18nid)
|
||||
div(class="text")
|
||||
table
|
||||
tr
|
||||
td(rowspan="2") #[img(class=`sign ${classes}` src=`${svg_dir}/${icon}.svg`)]
|
||||
td(style="line-height:1.5") #[b(i18n=i18nid) #{short}]
|
||||
if block
|
||||
tr
|
||||
td
|
||||
sup(style="line-height:1")
|
||||
block
|
||||
|
||||
mixin menu_switch_notable_gpio(channel, title, confirm_off="")
|
||||
td !{title}:
|
||||
td(align="right")
|
||||
div(class="switch-box")
|
||||
input(disabled type="checkbox" id=`__gpio-switch-${channel}` class=`__gpio-switch-${channel} gpio-switch`
|
||||
data-channel=channel data-confirm-off=confirm_off)
|
||||
label(for=`__gpio-switch-${channel}`)
|
||||
span(class="switch-inner")
|
||||
span(class="switch")
|
||||
|
||||
mixin menu_switch_notable(id, title, enabled, checked, i18nid)
|
||||
td(i18n=i18nid) !{title}:
|
||||
td(align="right")
|
||||
div(class="switch-box")
|
||||
input(checked=checked disabled=!enabled type="checkbox" id=id)
|
||||
label(for=id)
|
||||
span(class="switch-inner")
|
||||
span(class="switch")
|
||||
|
||||
mixin menu_switch(id, title, enabled, checked, i18nid)
|
||||
table(class="kv")
|
||||
tr
|
||||
+menu_switch_notable(id, title, enabled, checked, i18nid)
|
||||
|
||||
ul(id="navbar")
|
||||
li(class="left")
|
||||
a(id="logo" href="/") ←
|
||||
img(class="svg-gray" src=`${svg_dir}/logo.svg` alt="π-kvm")
|
||||
|
||||
include navbar-health.pug
|
||||
|
||||
include navbar-system.pug
|
||||
include navbar-atx.pug
|
||||
include navbar-msd.pug
|
||||
include navbar-macro.pug
|
||||
include navbar-text.pug
|
||||
include navbar-shortcuts.pug
|
||||
include navbar-gpio.pug
|
||||
692
kvmd_data/usr/share/kvmd/web/kvm/window-about.pug
Normal file
@@ -0,0 +1,692 @@
|
||||
mixin about_tab(name, title, i18nid, checked=false)
|
||||
- let button_id = `about-tab-${name}-button`
|
||||
input(checked=checked type="radio" name="about-tab-button", id=button_id)
|
||||
label(for=button_id i18n=i18nid) #{title}
|
||||
div(class="tab")
|
||||
div(id=`about-${name}` class="code")
|
||||
if block
|
||||
block
|
||||
else
|
||||
span(class="code-comment") No data
|
||||
|
||||
div(id="about-window" class="window")
|
||||
div(class="window-header")
|
||||
div(class="window-grab" i18n="kvm_text1") About
|
||||
button(class="window-button-close") #[b ×]
|
||||
div(id="about")
|
||||
table
|
||||
tr
|
||||
td(class="logo")
|
||||
a(href="https://pikvm.org" target="_blank")
|
||||
img(class="svg-gray" src=`${svg_dir}/logo.svg` alt="PiKVM" height="40")
|
||||
td
|
||||
table
|
||||
tr #[td(colspan="2" class="title" i18n="index_title") The Open Source KVM over IP]
|
||||
tr
|
||||
td(colspan="2" class="copyright" i18n="copyright")
|
||||
| Copyright © 2018-2024 #[a(target="_blank" href="mailto:mdevaev@gmail.com") Maxim Devaev]
|
||||
br
|
||||
div(class="tabs-box")
|
||||
+about_tab("meta", "Meta", "meta", true)
|
||||
div
|
||||
span(class="code-comment")
|
||||
| // You can get this JSON using handle #[a(target="_blank" href="/api/info?fields=meta") /api/info?fields=meta]#[br]
|
||||
| // In the standard configuration this data#[br]
|
||||
| // is specified in the file /etc/kvmd/meta.yaml
|
||||
br
|
||||
pre(id="kvmd-meta-json")
|
||||
| No data
|
||||
|
||||
+about_tab("hardware", "Hardware", "hardware")
|
||||
+about_tab("version", "Version", "version")
|
||||
|
||||
+about_tab("thanks", "Thanks", "thanks")
|
||||
span(class="code-comment" i18n="kvm_text2")
|
||||
| // These kind people donated money to the PiKVM project#[br]
|
||||
| // and supported the work on it. We are very grateful#[br]
|
||||
| // for their help, and memorializing their names#[br]
|
||||
| // is the least we can do in gratitude.#[br]
|
||||
| // If you also want to support this project,#[br]
|
||||
| // you can donate on #[a(target="_blank" href="https://www.patreon.com/pikvm") Patreon]
|
||||
| or #[a(target="_blank" href="https://paypal.me/pikvm") Paypal].
|
||||
ul
|
||||
li A. Isenring
|
||||
li Aaron Graubert
|
||||
li Aaron Heise
|
||||
li Aaron Stein
|
||||
li Accalia
|
||||
li Adam Goodbar
|
||||
li Adam S
|
||||
li Adam Stuart
|
||||
li AdamBomb
|
||||
li adipisicing
|
||||
li Adrian Basham
|
||||
li Adrian Popescu
|
||||
li Ahmed Syed
|
||||
li Alberto Bassi
|
||||
li alejandro
|
||||
li Aleksei Brusianskii
|
||||
li Alessio Curri
|
||||
li Alex T
|
||||
li Alex Z
|
||||
li Alexander Karmanov
|
||||
li Alexander Lahuerta
|
||||
li Alexander Martin
|
||||
li Alexander Pankov
|
||||
li Alexandre Jablonski
|
||||
li Alexey Kamenskiy
|
||||
li alm0241
|
||||
li Alok Anand
|
||||
li Alucard
|
||||
li Ananthaneshan Elampoornan
|
||||
li Andreas Marufke
|
||||
li Andreas Schmid
|
||||
li Andrew Brant
|
||||
li Andrew Melton
|
||||
li Andrew Reusch
|
||||
li Andrew Ruan
|
||||
li Andrzej V
|
||||
li Andy
|
||||
li Andy Keys
|
||||
li Anish Patel
|
||||
li Anix
|
||||
li Anonymous
|
||||
li Anthony Junk
|
||||
li Anton Kovalenko
|
||||
li Armen
|
||||
li Aron Green
|
||||
li Aron Perelman
|
||||
li Artem Simonov
|
||||
li Arthur Mayer
|
||||
li Arthur Woimbée
|
||||
li Ashlesh Chaudhari
|
||||
li Asim Shakour
|
||||
li Augusto Becciu
|
||||
li AVS Computer
|
||||
li awkspace
|
||||
li Badal Patel
|
||||
li baddog
|
||||
li Bao Tin Hoang
|
||||
li Bean Co.
|
||||
li Bela Bargel
|
||||
li Belf Igor
|
||||
li Ben Gordon
|
||||
li Ben Scott
|
||||
li Benedikt Heine
|
||||
li Benedikt Meier
|
||||
li Benjamin Frewert
|
||||
li Benjamin Melancon
|
||||
li Benjamin Schwartz
|
||||
li Benjamin Stegmann
|
||||
li Benni Stauder
|
||||
li Bernhard Fitzke
|
||||
li Beu
|
||||
li bikmaek
|
||||
li bitjoe
|
||||
li Bits and Bytes Computers LLC
|
||||
li Bjoern Petsch
|
||||
li Blair Hasler
|
||||
li Blindside
|
||||
li Blue Frog LLC
|
||||
li Bootstrapper - Programmierung erklärt
|
||||
li Bosco
|
||||
li Bradford King
|
||||
li Brainspore Networks
|
||||
li Branden Shaulis
|
||||
li Brandon Daniels
|
||||
li Brian
|
||||
li Brian Moses
|
||||
li Brian T Mulcahy
|
||||
li Brian Vecchiarelli
|
||||
li Brian White
|
||||
li Bruno Gomes
|
||||
li Bryan Adams
|
||||
li Bryan Montgomery
|
||||
li Buzzer
|
||||
li C P ELSE
|
||||
li Calanish
|
||||
li Cameron Hatcher
|
||||
li Cameron Tacklind
|
||||
li Carl Mercier
|
||||
li Carl-Fredrik Johansson
|
||||
li Carlos Eduardo Porter Herrera
|
||||
li Carlos Garcia
|
||||
li Carlos Manuel Torres
|
||||
li cbad536
|
||||
li César Nascimento
|
||||
li CHINATERA LIMITED
|
||||
li Chris Blackmon
|
||||
li Chris Burton
|
||||
li Chris Campbell
|
||||
li Chris Jackson
|
||||
li Chris Lewis
|
||||
li Chris Rizio
|
||||
li Christi King
|
||||
li Christian Schlögl
|
||||
li Christian Svensson
|
||||
li Christof Maluck
|
||||
li Christoph Dette
|
||||
li Christoffer Lund
|
||||
li Christopher Bulla
|
||||
li Christopher Gelatt
|
||||
li Christopher Hearn
|
||||
li Christopher Mandlbaur
|
||||
li Christopher Mendoza
|
||||
li Christopher Simms
|
||||
li Chucktastic
|
||||
li Cihan VURAL
|
||||
li clauskj3r
|
||||
li Clifford Coleman
|
||||
li Clinton Lee Taylor
|
||||
li Cole Imhoff
|
||||
li Colin Goodman
|
||||
li Corey Layton
|
||||
li Corey Lista
|
||||
li Craig Keenan
|
||||
li Crossfactor
|
||||
li Cruzzer
|
||||
li ctag
|
||||
li Curt Sammer
|
||||
li CyB0rgg
|
||||
li DeMentor
|
||||
li Desmond Whitt
|
||||
li Daegara
|
||||
li DailyAneurism
|
||||
li Damon Meledones
|
||||
li Dan Berkowitz
|
||||
li Dan Brakeley
|
||||
li Daniel Bowder
|
||||
li Daniel Cabrera
|
||||
li Daniel Davila
|
||||
li Danilo Saft
|
||||
li Danne
|
||||
li Dariusz Techmański
|
||||
li David
|
||||
li David Brausewetter
|
||||
li David Davis
|
||||
li David Godibadze
|
||||
li David Howell
|
||||
li David Irvine
|
||||
li David Klinkman
|
||||
li David Niemann
|
||||
li David Shay
|
||||
li David Ye
|
||||
li David York
|
||||
li Denis
|
||||
li Denis Andreev
|
||||
li Denis Yatsenko
|
||||
li Dennis Becker
|
||||
li Dennis Joslin
|
||||
li Dennis Lomet
|
||||
li Derek Jarvis
|
||||
li Derek Yap
|
||||
li Didrik
|
||||
li digitalbaconbits
|
||||
li Dimitrij Jedich
|
||||
li dixon wong
|
||||
li dizztrukshin
|
||||
li Dmitry Shilov
|
||||
li DogeLabs
|
||||
li Dominic Phoon
|
||||
li Dominik Klonowski
|
||||
li Donald Hays
|
||||
li Edmon Abdul Nur
|
||||
li Edward Wang
|
||||
li Egan Ford
|
||||
li Elani Ferri
|
||||
li Elliot Woo
|
||||
li Entt
|
||||
li Eric Phenix
|
||||
li Ethan Shold
|
||||
li Eugene Sukhodolin
|
||||
li ewook
|
||||
li eye-catcher.com
|
||||
li Fabian Druschke
|
||||
li Fabiano Sidler
|
||||
li Far Pin Solutions, LLC
|
||||
li Felyx Gabryel
|
||||
li Fergus McKay
|
||||
li Finn Ebenritter
|
||||
li floppy
|
||||
li fo0bar
|
||||
li Foad Yousef
|
||||
li Foamy
|
||||
li Foli Ayivoh
|
||||
li Folkert Weistra
|
||||
li Francisco Pavon
|
||||
li Frank
|
||||
li Frank Sander
|
||||
li Frederick Czajka
|
||||
li Fredrik Idréus
|
||||
li Garrett Dangerfield
|
||||
li Ge Men
|
||||
li Geekworm
|
||||
li Genkinger Andreas
|
||||
li Geijer
|
||||
li Geoffrey Wright
|
||||
li George Becker
|
||||
li Georgy Brodsky
|
||||
li Gerald
|
||||
li Gerardus Vernimmen
|
||||
li Gernot Neuschröer
|
||||
li Giovanni Fulco
|
||||
li GK
|
||||
li Glen Dragon
|
||||
li Greg Winterstein
|
||||
li Gregory Smith
|
||||
li Gregory Treantos
|
||||
li grewil
|
||||
li Grey Cynic
|
||||
li Guenter Honisch
|
||||
li Guido Bernacchi
|
||||
li Gustin Johnson
|
||||
li György Tamás Vizi
|
||||
li Haiberg GmbH
|
||||
li Haven Zheng
|
||||
li Heibunny
|
||||
li Heikki Tiittanen
|
||||
li Helio Leonardo Pinheiro e Mota
|
||||
li Henrik Ählström
|
||||
li Henry Hood
|
||||
li HimKo
|
||||
li HouseFPV
|
||||
li Howard Simons
|
||||
li HyunohRyu
|
||||
li Icculus
|
||||
li iks
|
||||
li INFO TRX INC
|
||||
li Invader Monks
|
||||
li Ioannis Karageorgos
|
||||
li Isaac
|
||||
li IT Lifesaver
|
||||
li Ivan Ganev
|
||||
li Ivan Josiah Lapis
|
||||
li Ivan Shapovalov
|
||||
li iwbjhbweriuhf
|
||||
li J
|
||||
li J L
|
||||
li Jaanus
|
||||
li Jackson Wyatt
|
||||
li Jacob Karaffa
|
||||
li Jacob Morgan
|
||||
li James Cadd
|
||||
li James Cobb
|
||||
li James Edwards
|
||||
li James Kocher
|
||||
li James Mayhugh
|
||||
li James Noonan
|
||||
li James Ye
|
||||
li Jamie Murphy
|
||||
li Jamie Scott
|
||||
li Jan Niehusmann
|
||||
li Jannick Oursin
|
||||
li Jari Hiltunen
|
||||
li Jason Crossley
|
||||
li Jason Downey
|
||||
li Jason Toland
|
||||
li Jasper Backer
|
||||
li Jay Davis
|
||||
li Jay Isaacs
|
||||
li Jazereel Goh
|
||||
li Jean-Daniel Croteau
|
||||
li Jean-Philippe Guilbault
|
||||
li Jeff
|
||||
li Jeff Bowman
|
||||
li Jeff Urlwin
|
||||
li Jennifer Herting
|
||||
li Jennifer Rowlett
|
||||
li Jeremy Abel
|
||||
li Jeremy Combs
|
||||
li Jeremy Hines
|
||||
li Jerremy Holland
|
||||
li Jerry Nall
|
||||
li Jerry Y. Chen
|
||||
li Jim Bailey
|
||||
li Jim Harbin
|
||||
li Jimmy Burgett
|
||||
li Jimmy Stanley
|
||||
li Joachim Bruening
|
||||
li Joe Hanson
|
||||
li Joe Hinteregger
|
||||
li Joe Ventura
|
||||
li Joel Jacobs
|
||||
li Johannes Heigermose
|
||||
li John Andersen
|
||||
li John Copeland
|
||||
li John F Glenn
|
||||
li John Holmes
|
||||
li John Kelley
|
||||
li John McGovern
|
||||
li Johnny Henson
|
||||
li Jon Ferguy
|
||||
li Jon-Eric
|
||||
li Joni Ruuskanen
|
||||
li Jonas Fischer
|
||||
li Jonathan Slenders
|
||||
li Jonathan Vaughn
|
||||
li Joost Backer
|
||||
li Jordan Blake
|
||||
li Jordi Pakey-Rodriguez
|
||||
li Joris van Embden
|
||||
li Joseph Swift
|
||||
li Josh Nethery
|
||||
li Josh Ricker
|
||||
li Josh VanDeraa
|
||||
li Joshua Futterer
|
||||
li Jozef Riha
|
||||
li Jörgen Fredriksson
|
||||
li Julian Forero
|
||||
li Julian Schneider
|
||||
li Julien Angelier
|
||||
li Justin
|
||||
li Justin Waters
|
||||
li Kai Hadler
|
||||
li Kamil Chyba
|
||||
li Kari Matti Korpi
|
||||
li Karl Dunne
|
||||
li Karl Moos
|
||||
li Keith Muggleton
|
||||
li Ken Lee
|
||||
li Kenneth Younger III
|
||||
li Kenny Hui
|
||||
li KeonWoo PARK
|
||||
li Kevin Bajohr
|
||||
li Kevin Schwartz
|
||||
li Kevin Sherwood
|
||||
li Kiera Kujisawa
|
||||
li Kiran Schuler
|
||||
li Koloman
|
||||
li Konrad Neitzel
|
||||
li Krzysztof Żelaśkiewicz
|
||||
li Lance Ward
|
||||
li Larry Meaney
|
||||
li Lars
|
||||
li Lars Reinhardt
|
||||
li Lee Wilkinson
|
||||
li LeeNX
|
||||
li Leon Siegl
|
||||
li Leonard Feineis
|
||||
li Lewis Wild
|
||||
li Liran
|
||||
li Liviu Dimitriu
|
||||
li Lizardo Hernandez
|
||||
li LoCascio
|
||||
li Lordbob75
|
||||
li Lothar Schweikle-Droll
|
||||
li Louis Müller
|
||||
li LSDTripp
|
||||
li Ľubor Slušný
|
||||
li Luca Di Diomede
|
||||
li Lucio De Carli
|
||||
li Luiz Bizzio
|
||||
li Lukas Bischof
|
||||
li Lukas Kammerer
|
||||
li Lukas Söder
|
||||
li Maksim Terehin
|
||||
li Malcolm Cameron
|
||||
li Manfred Radeschnig
|
||||
li Marc Khouri
|
||||
li Marcin Wilk
|
||||
li Marcio Zimbres
|
||||
li Marco Rossi
|
||||
li Marcos Wolf
|
||||
li Marek Marczykowski-Górecki
|
||||
li Marius
|
||||
li Mar. Balske
|
||||
li Mark Farrell
|
||||
li Mark Gilbert
|
||||
li Mark Knam
|
||||
li Mark Robinson
|
||||
li Markrosoft
|
||||
li Markus Halm
|
||||
li Markus Schicker
|
||||
li Markus Sobczack
|
||||
li Marshall Bjerke
|
||||
li Marten Hermans
|
||||
li Martin Gasser
|
||||
li Martin Hofbauer
|
||||
li Martin Raine
|
||||
li Martin Suelmann
|
||||
li Martin Wilhelmi
|
||||
li Marvin Honderboom
|
||||
li Mateusz Grabowski
|
||||
li Mathias Uhl
|
||||
li Matt Kane
|
||||
li Matthew Cameron
|
||||
li Mauricio Allende
|
||||
li Max Evans
|
||||
li Mecky
|
||||
li Mehmet Aydoğdu
|
||||
li Michael Bartholomew
|
||||
li Michael Bell
|
||||
li Michael Bombe
|
||||
li Michael Collins
|
||||
li Michael Copeland
|
||||
li Michael Ho
|
||||
li Michael Kovacs
|
||||
li Michael Lynch
|
||||
li Michael Pennington
|
||||
li Michael Sage
|
||||
li Michael Stella
|
||||
li Michael Thalmann
|
||||
li Michael Wu
|
||||
li MichaelZ
|
||||
li Michel Bissonnette
|
||||
li Mikael Wikström
|
||||
li Mike Mason
|
||||
li Mikhael Mariano
|
||||
li Milan Múčka
|
||||
li Miles Davis
|
||||
li Minh Tang
|
||||
li Moez Tharani
|
||||
li Morgan Helton
|
||||
li Myron Weber
|
||||
li Murad Khasawneh
|
||||
li N Patel
|
||||
li Nathaniel Griswold
|
||||
li Nelson Lee
|
||||
li nezu
|
||||
li Nicholas Jeppson
|
||||
li Nicholas Kopas
|
||||
li Nicholas Walczak
|
||||
li Nick Leffler
|
||||
li Nick Roethemeier
|
||||
li Nico Baumgartner
|
||||
li Nicolai Kragh-Hansen
|
||||
li Nigel Smith
|
||||
li Nihal Fernando
|
||||
li Nils Orbat
|
||||
li Nis Wechselberg
|
||||
li Nithin Philips
|
||||
li Nod Swal
|
||||
li Nolan Haynes
|
||||
li nubbn
|
||||
li nybble
|
||||
li Oh Be
|
||||
li Oliver Schwarz
|
||||
li Oliver Zimmer
|
||||
li Omar El-Domeiri
|
||||
li Omar Siam
|
||||
li Oscar
|
||||
li Patrick
|
||||
li Patrick Fortin-Ducharme
|
||||
li Patrick McDowell
|
||||
li Patrick Wagstrom
|
||||
li Paul Bishop
|
||||
li Paul De La Rosa
|
||||
li Paul Pietkiewicz
|
||||
li Paul Tan
|
||||
li Pawel Trofimiuk
|
||||
li Peder Madsen
|
||||
li Peter
|
||||
li Peter Drayton
|
||||
li Peter Farrelly
|
||||
li Peter Okelmann
|
||||
li Petra Lohmann
|
||||
li Petri Heiskanen
|
||||
li Phil Wu
|
||||
li Philip Edwards
|
||||
li Philip Merricks
|
||||
li Pierre Brassart
|
||||
li Pierre Peine
|
||||
li posicat
|
||||
li pozitron03
|
||||
li Przemysław Szypowicz
|
||||
li P_Dmitrij
|
||||
li Qteal
|
||||
li Quattro Uno
|
||||
li Quentin Peten
|
||||
li Ralph Borchers
|
||||
li Ranc1d
|
||||
li Randall D Bilbrey
|
||||
li RandomJerk
|
||||
li Raphael Schitz
|
||||
li Ref Chowdhury
|
||||
li René Rathenau
|
||||
li ReysDad
|
||||
li Ricardo Marques
|
||||
li Richard
|
||||
li Richard Bernarts
|
||||
li Richard Fancher
|
||||
li Richard Freemantle
|
||||
li Richard Michael
|
||||
li Rico Cantrell
|
||||
li Rob
|
||||
li Rob Holden
|
||||
li Rob Tongue
|
||||
li Robert Klauco
|
||||
li Robert Weemhoff
|
||||
li Robin Gfatter
|
||||
li Rodion DENISYUK
|
||||
li Rohit Priyadarshi
|
||||
li Rolfs 3D UG
|
||||
li Ronald LeBaron
|
||||
li Ronald Wells
|
||||
li Ronny Haldorsen
|
||||
li rotx
|
||||
li Rufo Sanchez
|
||||
li Russell Scott
|
||||
li Ryan
|
||||
li Ryan Peacock
|
||||
li Samed Ozoglu
|
||||
li Sameul Davies
|
||||
li Samuel Cote
|
||||
li Samuel Vetsch
|
||||
li Samuel Walker
|
||||
li Sarah Foster
|
||||
li Sarten X
|
||||
li Satish Alwani
|
||||
li Scott
|
||||
li Scott Gagon
|
||||
li Scott Spicola
|
||||
li Scott Tusing
|
||||
li Scott Worthington
|
||||
li Scuba
|
||||
li Sean
|
||||
li Sean Akers
|
||||
li SEAT
|
||||
li Sebastian
|
||||
li Seonwoo Lee
|
||||
li Sergey Lukjanov
|
||||
li Seth Jennings
|
||||
li Shane Selling
|
||||
li Shawn Butts
|
||||
li Sheran Gunasekera
|
||||
li Shichun Chen
|
||||
li Shin Guey Wong
|
||||
li Simon Evans
|
||||
li Simon Sundgaard
|
||||
li Simplistic Realities
|
||||
li Sirmo
|
||||
li Snowy Maslov
|
||||
li Solve Technology
|
||||
li srepac
|
||||
li Stefan Bautz
|
||||
li Stefan Müller
|
||||
li Stefan Stemmer
|
||||
li Stefan Vaillant
|
||||
li Stephan Schmidt
|
||||
li Stephen
|
||||
li Stephen Hocking
|
||||
li Steve Jones
|
||||
li Steve Kerr
|
||||
li Steve Ovens
|
||||
li Steve Stringham
|
||||
li Steven Richter
|
||||
li Stratagem Solutions Ltd
|
||||
li Sven Breckler
|
||||
li sudo34
|
||||
li SuperHiTech
|
||||
li Tango_Echo_Alpha
|
||||
li Tarlak Desaydrone
|
||||
li TechBear
|
||||
li techlobo
|
||||
li Ted
|
||||
li Tejun Heo
|
||||
li TheSnowedOne
|
||||
li TheTechGiant
|
||||
li Thomas Charisoulis
|
||||
li Thomas Gitlin
|
||||
li Thomas Hagenmaier
|
||||
li Thomas Hedberg Jensen
|
||||
li Thomas Price
|
||||
li Thomas Søfteland
|
||||
li Tim Lenz
|
||||
li Tim Wilkinson
|
||||
li Timo Brinkmann
|
||||
li Timothee Besset
|
||||
li TitomusPrime
|
||||
li Tobias Schafferhans
|
||||
li Tom Lawson
|
||||
li Tom York
|
||||
li Tomas Kuchta
|
||||
li Tomáš hrubý
|
||||
li Torsten Droste
|
||||
li Torsten Knoll
|
||||
li Tracy Fitch
|
||||
li Tristan Schoening
|
||||
li Truman Kilen
|
||||
li turbochris
|
||||
li tutanak
|
||||
li Tyler
|
||||
li Udo Schroeter
|
||||
li Uli Fahrer
|
||||
li Vasily Lazarev
|
||||
li Vidru Eduard
|
||||
li Vicente Salvador Cubedo
|
||||
li Viktor Aschenbrenner
|
||||
li Viktor Ekmark
|
||||
li Vincent Chov
|
||||
li Vlad Sterescu
|
||||
li Volker Gropp
|
||||
li Walli
|
||||
li Walter_Ego
|
||||
li William Wenzel
|
||||
li Will Froning
|
||||
li William Hooper
|
||||
li William Perrin
|
||||
li William Stearns
|
||||
li Woojin Son
|
||||
li xMdb
|
||||
li Yanko Kaneti
|
||||
li Yaroslav Kulikovskikh
|
||||
li Yethal
|
||||
li Yevgeniy Kuksenko
|
||||
li Yew Kay Yan
|
||||
li Yigal Dar
|
||||
li Yogi
|
||||
li YURI LEE
|
||||
li Yurii Ostapchuk
|
||||
li Zeljko
|
||||
li zgen
|
||||
li Zoltan Magyari
|
||||
li Zsombor Vari
|
||||
br
|
||||
p(class="text credits")
|
||||
a(target="_blank" href="https://pikvm.org" i18n="index_text_10") PiKVM Project
|
||||
| |
|
||||
a(target="_blank" href="https://docs.pikvm.org" i18n="index_text_11") Documentation
|
||||
| |
|
||||
a(target="_blank" href="https://github.com/mofeng-git/One-KVM" i18n="index_text_12") One-KVM Project
|
||||
| |
|
||||
a(target="_blank" href="https://one-kvm.mofeng.run" i18n="index_text_13") One-KVM Documentation
|
||||
232
kvmd_data/usr/share/kvmd/web/kvm/window-keyboard.pug
Normal file
@@ -0,0 +1,232 @@
|
||||
mixin key(spacer, code, classes="", width=0)
|
||||
div(data-code=code, class=`key ${classes}`, style=(width ? `width:${width}px` : ""))
|
||||
div(class="label")
|
||||
block
|
||||
if spacer == 1
|
||||
div(class="spacer")
|
||||
else if spacer == 2
|
||||
div(class="spacer-fixed")
|
||||
|
||||
mixin modifier(spacer, code, classes="", width=0)
|
||||
div(data-code=code class=`modifier ${classes}` style=(width ? `width:${width}px` : ""))
|
||||
div(class="label")
|
||||
| #[b •]#[br]
|
||||
block
|
||||
if spacer == 1
|
||||
div(class="spacer")
|
||||
else if spacer == 2
|
||||
div(class="spacer-fixed")
|
||||
|
||||
mixin empty(spacer, classes="", width=0)
|
||||
div(class=`empty ${classes}` style=(width ? `width:${width}px` : ""))
|
||||
div(class="label")
|
||||
if spacer == 1
|
||||
div(class="spacer")
|
||||
else if spacer == 2
|
||||
div(class="spacer-fixed")
|
||||
|
||||
mixin lamp(cls)
|
||||
img(class=`inline-lamp ${cls} led-gray` src=`${svg_dir}/led-square.svg`)
|
||||
|
||||
div(id="keyboard-window" class="window")
|
||||
div(id="keyboard-window-header" class="window-header")
|
||||
div(class="window-grab" i18n="kvm_text15") Virtual Keyboard
|
||||
button(class="window-button-close") #[b ×]
|
||||
|
||||
div(id="keyboard-desktop" class="keypad" align="center")
|
||||
div(class="keypad-block")
|
||||
div(class="keypad-row")
|
||||
+key(2, "Escape", "small") Esc
|
||||
+empty(1, "", 24)
|
||||
each key in ["F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12"]
|
||||
+key((key != "F12" ? 1 : 0), key, "small") #{key}
|
||||
if key == "F4" || key == "F8"
|
||||
+empty(1, "", 10)
|
||||
hr
|
||||
div(class="keypad-row")
|
||||
+key(1, "Backquote") ~#[br]`
|
||||
each key, index in ["!", "@", "#", "$", "%", "^", "&", "*", "("]
|
||||
+key(1, `Digit${index + 1}`) #{key}#[br]#{index + 1}
|
||||
+key(1, "Digit0") )#[br]0
|
||||
+key(1, "Minus") _#[br]-
|
||||
+key(1, "Equal") +#[br]=
|
||||
+key(0, "Backspace", "wide-1 right") ↤
|
||||
div(class="keypad-row")
|
||||
+key(1, "Tab", "wide-1 left") ⇤#[br]⇥
|
||||
each key in ["Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P"]
|
||||
+key(1, `Key${key}`, "single") #{key}
|
||||
+key(1, "BracketLeft") {#[br][
|
||||
+key(1, "BracketRight") }#[br]]
|
||||
+key(0, "Backslash") |#[br]\
|
||||
div(class="keypad-row")
|
||||
+key(1, "CapsLock", "wide-2 left small")
|
||||
+lamp("hid-keyboard-caps-led")
|
||||
| #[br] Caps Lock
|
||||
each key in ["A", "S", "D", "F", "G", "H", "J", "K", "L"]
|
||||
+key(1, `Key${key}`, "single") #{key}
|
||||
+key(1, "Semicolon") :#[br];
|
||||
+key(1, "Quote") "#[br]'
|
||||
+key(0, "Enter", "wide-2 right small") Enter#[br]↵
|
||||
div(class="keypad-row")
|
||||
+modifier(1, "ShiftLeft", "wide-3 left small") Shift
|
||||
each key in ["Z", "X", "C", "V", "B", "N", "M"]
|
||||
+key(1, `Key${key}`, "single") #{key}
|
||||
+key(1, "Comma") <#[br],
|
||||
+key(1, "Period") >#[br].
|
||||
+key(1, "Slash") ?#[br]/
|
||||
+modifier(0, "ShiftRight", "wide-3 right small") Shift
|
||||
div(class="keypad-row")
|
||||
+modifier(2, "ControlLeft", "wide-1 left small") Ctrl
|
||||
+modifier(2, "MetaLeft", "wide-1 left small") Win
|
||||
+modifier(2, "AltLeft", "wide-1 left small") Alt
|
||||
+key(2, "Space", "wide-4")
|
||||
+modifier(2, "AltRight", "wide-1 right small") Alt
|
||||
+modifier(2, "MetaRight", "wide-1 right small") Win
|
||||
+key(2, "ContextMenu", "small") #[br]Menu
|
||||
+modifier(0, "ControlRight", "wide-1 right small") Ctrl
|
||||
div(class="keypad-block")
|
||||
div(class="keypad-row")
|
||||
+modifier(2, "PrintScreen", "small") Pt/Sq
|
||||
+key(2, "ScrollLock", "small")
|
||||
+lamp("hid-keyboard-scroll-led")
|
||||
| #[br] ScrLk
|
||||
+key(0, "Pause", "small") P/Brk
|
||||
hr
|
||||
div(class="keypad-row")
|
||||
+key(2, "Insert", "small") Ins
|
||||
+key(2, "Home", "small") Home
|
||||
+key(0, "PageUp", "small") PgUp
|
||||
div(class="keypad-row")
|
||||
+key(2, "Delete", "small") Del
|
||||
+key(2, "End", "small") End
|
||||
+key(0, "PageDown", "small") PgDn
|
||||
div(class="keypad-row")
|
||||
div(class="keypad-row")
|
||||
+empty(1, "")
|
||||
+key(2, "ArrowUp") ↑
|
||||
+empty(0, "")
|
||||
div(class="keypad-row")
|
||||
+key(2, "ArrowLeft") ←
|
||||
+key(2, "ArrowDown") ↓
|
||||
+key(0, "ArrowRight") →
|
||||
div(class="keypad-block")
|
||||
div(class="keypad-row")
|
||||
+empty(2, "small")
|
||||
+empty(2, "small")
|
||||
+empty(2, "small")
|
||||
+key(0, "Power", "small") PWR
|
||||
hr
|
||||
div(class="keypad-row")
|
||||
+key(2, "NumLock", "small")
|
||||
+lamp("hid-keyboard-num-led")
|
||||
| #[br] NmLk
|
||||
+key(2, "NumpadDivide") /
|
||||
+key(2, "NumpadMultiply") *
|
||||
+key(0, "NumpadSubtract") -
|
||||
div(class="keypad-row")
|
||||
+key(2, "Numpad7", "small") 7#[br]Home
|
||||
+key(2, "Numpad8", "small") 8#[br]↑
|
||||
+key(2, "Numpad9", "small") 9#[br]PgUp
|
||||
+empty(0, "")
|
||||
div(class="keypad-row")
|
||||
+key(2, "Numpad4", "small") 4#[br]←
|
||||
+key(2, "Numpad5", "small") 5#[br]#[br]
|
||||
+key(2, "Numpad6", "small") 6#[br]→
|
||||
+key(0, "NumpadAdd") +
|
||||
div(class="keypad-row")
|
||||
+key(2, "Numpad1", "small") 1#[br]End
|
||||
+key(2, "Numpad2", "small") 2#[br]↓
|
||||
+key(2, "Numpad3", "small") 3#[br]PgDn
|
||||
+empty(0, "")
|
||||
div(class="keypad-row")
|
||||
+key(2, "Numpad0", "small") 0#[br]Ins
|
||||
+empty(2, "")
|
||||
+key(2, "NumpadDecimal", "small") .#[br]Del
|
||||
+key(0, "NumpadEnter", "small") Ent
|
||||
div(class="keypad-block")
|
||||
div(class="keypad-row")
|
||||
+key(0, "IntlBackslash", "small") \#[br]|
|
||||
hr
|
||||
div(class="keypad-row")
|
||||
+key(0, "IntlYen", "small") ¥#[br]_
|
||||
div(class="keypad-row")
|
||||
+key(0, "IntlRo", "small") \#[br]ろ
|
||||
div(class="keypad-row")
|
||||
+modifier(0, "KanaMode", "small") Kana
|
||||
div(class="keypad-row")
|
||||
+modifier(0, "NonConvert", "small") N/Cnv
|
||||
div(class="keypad-row")
|
||||
+modifier(0, "Convert", "small") Cnv
|
||||
|
||||
div(id="keyboard-mobile" class="keypad" align="center")
|
||||
div(class="keypad-block")
|
||||
div(class="keypad-row")
|
||||
+key(1, "Escape", "small") Esc
|
||||
+key(0, "F1", "wide-0 small rounded-left") F1
|
||||
+key(0, "F2", "wide-0 small rounded-none") F2
|
||||
+key(0, "F3", "wide-0 small rounded-none") F3
|
||||
+key(2, "F4", "wide-0 small rounded-right") F4
|
||||
+key(0, "F5", "wide-0 small rounded-left") F5
|
||||
+key(0, "F6", "wide-0 small rounded-none") F6
|
||||
+key(0, "F7", "wide-0 small rounded-none") F7
|
||||
+key(2, "F8", "wide-0 small rounded-right") F8
|
||||
+key(0, "F9", "wide-0 small rounded-left") F9
|
||||
+key(0, "F10", "wide-0 small rounded-none") F10
|
||||
+key(0, "F11", "wide-0 small rounded-none") F11
|
||||
+key(2, "F12", "wide-0 small rounded-right") F12
|
||||
+modifier(1, "PrintScreen", "small") Pt/Sq
|
||||
+key(1, "ScrollLock", "small")
|
||||
+lamp("hid-keyboard-scroll-led")
|
||||
| #[br] ScrLk
|
||||
+key(1, "Pause", "small") P/Brk
|
||||
+key(1, "Insert", "small") Ins
|
||||
+key(1, "Home", "small") Home
|
||||
+key(1, "End", "small") End
|
||||
+key(0, "Delete", "small") Del
|
||||
div(class="keypad-row")
|
||||
+key(1, "Backquote") ~#[br]`
|
||||
each key, index in ["!", "@", "#", "$", "%", "^", "&", "*", "("]
|
||||
+key(1, `Digit${index + 1}`) #{key}#[br]#{index + 1}
|
||||
+key(1, "Digit0") )#[br]0
|
||||
+key(1, "Minus") _#[br]-
|
||||
+key(1, "Equal") +#[br]=
|
||||
+key(0, "Backspace", "wide-2 right", 101) ↤
|
||||
div(class="keypad-row")
|
||||
+key(1, "Tab", "wide-1 left") ⇤<br>⇥
|
||||
each key in ["Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P"]
|
||||
+key(1, `Key${key}`, "single") #{key}
|
||||
+key(1, "BracketLeft") {#[br][
|
||||
+key(1, "BracketRight") }#[br]]
|
||||
+key(0, "Backslash", "wide-1 left", 78) |#[br]\
|
||||
div(class="keypad-row")
|
||||
+key(1, "CapsLock", "wide-2 left small")
|
||||
+lamp("hid-keyboard-caps-led")
|
||||
| #[br] Caps Lock
|
||||
each key in ["A", "S", "D", "F", "G", "H", "J", "K", "L"]
|
||||
+key(1, `Key${key}`, "single") #{key}
|
||||
+key(1, "Semicolon") :#[br];
|
||||
+key(1, "Quote") `#[br]'
|
||||
+key(0, "Enter", "wide-3 right small", 116) Enter#[br]↵
|
||||
div(class="keypad-row")
|
||||
+modifier(1, "ShiftLeft", "wide-3 left small") Shift
|
||||
each key in ["Z", "X", "C", "V", "B", "N", "M"]
|
||||
+key(1, `Key${key}`, "single") #{key}
|
||||
+key(1, "Comma") <#[br],
|
||||
+key(1, "Period") >#[br].
|
||||
+key(1, "Slash") ?#[br]/
|
||||
+key(2, "PageUp", "small") PgUp
|
||||
+key(2, "ArrowUp") ↑
|
||||
+key(0, "PageDown", "small") PgDn
|
||||
div(class="keypad-row")
|
||||
+modifier(1, "ControlLeft", "wide-1 left small") Ctrl
|
||||
+modifier(1, "MetaLeft", "wide-1 left small") Win
|
||||
+modifier(1, "AltLeft", "wide-1 left small") Alt
|
||||
+key(1, "Space", "", 190)
|
||||
+modifier(1, "AltRight", "right small") Alt
|
||||
+modifier(1, "MetaRight", "right small") Win
|
||||
+key(1, "ContextMenu", "small") #[br]Menu
|
||||
+modifier(1, "ShiftRight", "right small") Shift
|
||||
+modifier(1, "ControlRight", "right small") Ctrl
|
||||
+key(2, "ArrowLeft") ←
|
||||
+key(2, "ArrowDown") ↓
|
||||
+key(0, "ArrowRight") →
|
||||
49
kvmd_data/usr/share/kvmd/web/kvm/window-stream.pug
Normal file
@@ -0,0 +1,49 @@
|
||||
div(id="stream-ocr-window" class="window")
|
||||
div(id="stream-ocr-selection" class="hidden")
|
||||
|
||||
div(id="stream-window" class="window window-resizable")
|
||||
div(id="stream-window-header" class="window-header")
|
||||
div(class="window-grab") MJPEG
|
||||
button(class="window-button-close") #[b ×]
|
||||
button(class="window-button-maximize") ☐
|
||||
button(class="window-button-original") •
|
||||
button(class="window-button-enter-full-tab") ▲
|
||||
button(class="window-button-full-screen") ⤢
|
||||
|
||||
div(id="stream-info")
|
||||
|
||||
button(class="window-button-exit-full-tab") ▼
|
||||
div(id="stream-box" class="stream-box-offline")
|
||||
img(id="stream-image" src=`${png_dir}/blank-stream.png`)
|
||||
video(id="stream-video" class="hidden" disablePictureInPicture="true" autoplay playsinline muted)
|
||||
div(id="stream-fullscreen-active")
|
||||
|
||||
div(id="stream-mouse-buttons" class="keypad" align="center")
|
||||
div(class="keypad-block")
|
||||
div(class="keypad-row")
|
||||
div(data-code="left" class="key wide-3 left rounded-left")
|
||||
div(class="label") Left
|
||||
div(data-code="left" class="modifier left small rounded-right")
|
||||
div(class="label") #[b •]#[br]Hold
|
||||
|
||||
div(class="empty" style="width:15px")
|
||||
|
||||
div(data-code="middle" class="key wide-1 left rounded-left")
|
||||
div(class="label") Mid
|
||||
div(data-code="middle" class="modifier left small rounded-right")
|
||||
div(class="label") #[b •]#[br]Hold
|
||||
|
||||
div(class="empty" style="width:15px")
|
||||
|
||||
div(data-code="right" class="modifier right small rounded-left")
|
||||
div(class="label") #[b •]#[br]Hold
|
||||
div(data-code="right" class="key wide-3 right rounded-right")
|
||||
div(class="label") Right
|
||||
|
||||
div(class="empty" style="width:30px")
|
||||
|
||||
div(data-code="up" class="key small rounded-left")
|
||||
div(class="label") Up
|
||||
div(data-code="down" class="key small rounded-right")
|
||||
div(class="label") Down
|
||||
canvas(id="stream-mjpeg-canvas" class="hidden")
|
||||
8
kvmd_data/usr/share/kvmd/web/kvm/window-webterm.pug
Normal file
@@ -0,0 +1,8 @@
|
||||
div(id="webterm-window" class="window window-resizable" style="width: 640px; height: 480px")
|
||||
div(class="window-header")
|
||||
div(class="window-grab" i18n="kvm_text16") Terminal
|
||||
button(class="window-button-close") #[b ×]
|
||||
button(class="window-button-maximize") ☐
|
||||
// Терминал глючит из-за зажимаемой клавиши ESC для выхода
|
||||
// button(class="window-button-full-screen") ⤢
|
||||
iframe(id="webterm-iframe" src="" style="width: 100%; height: 100%")
|
||||
4
kvmd_data/usr/share/kvmd/web/kvm/windows.pug
Normal file
@@ -0,0 +1,4 @@
|
||||
include window-stream.pug
|
||||
include window-keyboard.pug
|
||||
include window-about.pug
|
||||
include window-webterm.pug
|
||||
103
kvmd_data/usr/share/kvmd/web/login/index.html
Normal file
@@ -0,0 +1,103 @@
|
||||
<!DOCTYPE html>
|
||||
<!--
|
||||
==============================================================================
|
||||
# #
|
||||
# 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/>. #
|
||||
# #
|
||||
==============================================================================
|
||||
|
||||
-->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>One-KVM Login</title>
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/share/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/share/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/share/favicon-16x16.png">
|
||||
<link rel="manifest" href="/share/site.webmanifest">
|
||||
<link rel="mask-icon" href="/share/safari-pinned-tab.svg" color="#5bbad5">
|
||||
<meta name="msapplication-TileColor" content="#2b5797">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
<link rel="stylesheet" href="/share/css/vars.css">
|
||||
<link rel="stylesheet" href="/share/css/main.css">
|
||||
<link rel="stylesheet" href="/share/css/window.css">
|
||||
<link rel="stylesheet" href="/share/css/modal.css">
|
||||
<link rel="stylesheet" href="/share/css/login/login.css">
|
||||
<link rel="stylesheet" href="/share/css/user.css">
|
||||
<script src="/share/js/i18n/jquery-3.7.1.min.js"></script>
|
||||
<script src="/share/js/i18n/jquery.i18n.min.js"></script>
|
||||
<script src="/share/js/i18n/i18n.js"></script>
|
||||
<script type="module">import {main} from "/share/js/login/main.js";
|
||||
main();
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<form action="javascript:void(0)">
|
||||
<div id="login-box">
|
||||
<div id="login">
|
||||
<table>
|
||||
<tr>
|
||||
<td i18n="username">Username: </td>
|
||||
<td>
|
||||
<input type="text" id="user-input" autocapitalize="off">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td i18n="password">Password: </td>
|
||||
<td>
|
||||
<input type="password" id="passwd-input" autocapitalize="off">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td i18n="2fa_code">2FA code: </td>
|
||||
<td>
|
||||
<input type="text" id="code-input" placeholder="if enabled" i18n="if_enabled">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<hr>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td i18n="select_language">Select language: </td>
|
||||
<td>
|
||||
<select id="selectLanguage">
|
||||
<option id="zh" selected="selected" i18n="chinese">Simplified Chinese</option>
|
||||
<option id="en" i18n="english">English</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>
|
||||
<button class="key" id="login-button" i18n="login">Login</button>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<ul class="footer">
|
||||
<li class="left" i18n="footer-left">This site is actively using JavaScript.<br>
|
||||
It doesn't contain ads, but is blocked by some ad filters.<br>
|
||||
Please turn it off to continue and reload the page.
|
||||
</li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
39
kvmd_data/usr/share/kvmd/web/login/index.pug
Normal file
@@ -0,0 +1,39 @@
|
||||
extends ../base.pug
|
||||
|
||||
append vars
|
||||
- title = "One-KVM Login"
|
||||
- main_js = "login/main"
|
||||
- css_list = css_list.concat(["window", "modal", "login/login"])
|
||||
|
||||
block body
|
||||
form(action="javascript:void(0)")
|
||||
div(id="login-box")
|
||||
div(id="login")
|
||||
table
|
||||
tr
|
||||
td(i18n="username") Username:
|
||||
td #[input(type="text" id="user-input" autocapitalize="off")]
|
||||
tr
|
||||
td(i18n="password") Password:
|
||||
td #[input(type="password" id="passwd-input" autocapitalize="off")]
|
||||
tr
|
||||
td(i18n="2fa_code") 2FA code:
|
||||
td #[input(type="text" id="code-input" placeholder="if enabled" i18n="if_enabled")]
|
||||
tr
|
||||
td(colspan=2)
|
||||
hr
|
||||
tr
|
||||
td(i18n="select_language") Select language:
|
||||
td
|
||||
select(id="selectLanguage")
|
||||
option(id='zh', selected="selected" i18n="chinese") Simplified Chinese
|
||||
option(id='en' i18n="english") English
|
||||
tr
|
||||
td
|
||||
td #[button(id="login-button" class="key" i18n="login") Login]
|
||||
|
||||
ul(class="footer")
|
||||
li(class="left" i18n="footer-left")
|
||||
| This site is actively using JavaScript.#[br]
|
||||
| It doesn't contain ads, but is blocked by some ad filters.#[br]
|
||||
| Please turn it off to continue and reload the page.
|
||||
2
kvmd_data/usr/share/kvmd/web/robots.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
User-Agent: *
|
||||
Disallow: /
|
||||
BIN
kvmd_data/usr/share/kvmd/web/share/android-chrome-192x192.png
Normal file
|
After Width: | Height: | Size: 6.1 KiB |
BIN
kvmd_data/usr/share/kvmd/web/share/apple-touch-icon.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
97
kvmd_data/usr/share/kvmd/web/share/css/index/index.css
Normal file
@@ -0,0 +1,97 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# 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/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
div#apps-box {
|
||||
display: table;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
div#apps-box ul#apps {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
div#apps-box ul#apps li {
|
||||
float: left;
|
||||
margin-left: 5px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
div#apps-box ul#apps li div.app {
|
||||
height: 100px;
|
||||
width: 100px;
|
||||
text-align: center;
|
||||
background-color: var(--cs-control-default-bg);
|
||||
box-shadow: var(--shadow-micro);
|
||||
border: var(--border-key-thin);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
div#apps-box ul#apps li div:hover.app {
|
||||
border: var(--border-intensive-thin);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
div#apps-box ul#apps li div:active.app {
|
||||
color: var(--cs-control-pressed-fg) !important;
|
||||
background-color: var(--cs-control-pressed-bg) !important;
|
||||
border: var(--border-key-thin);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
div#apps-box ul#apps li div.app img {
|
||||
display: block;
|
||||
margin: auto;
|
||||
height: 50px;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
div#apps-box ul#apps li div.app a {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
td.logo {
|
||||
padding-right: 25px;
|
||||
}
|
||||
td.title {
|
||||
font-size: 1.2em;
|
||||
}
|
||||
td.copyright {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
tr.server {
|
||||
font-size: 1.4em;
|
||||
font-weight: bold;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
@media only screen and (display-mode: standalone) {
|
||||
div#app-keyboard-warning {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
144
kvmd_data/usr/share/kvmd/web/share/css/keypad.css
Normal file
@@ -0,0 +1,144 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# 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/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
div.keypad {
|
||||
zoom: 0.8;
|
||||
}
|
||||
|
||||
div.keypad div.keypad-block {
|
||||
display: table-cell;
|
||||
padding-right: 0;
|
||||
}
|
||||
div.keypad div.keypad-block:not(:first-child) {
|
||||
padding-left: 15px;
|
||||
}
|
||||
|
||||
div.keypad div.keypad-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
white-space: nowrap;
|
||||
height: 40px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
div.keypad div.keypad-row:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
div.keypad div.keypad-row div.spacer {
|
||||
margin: 2px;
|
||||
flex-grow: 1;
|
||||
}
|
||||
div.keypad div.keypad-row div.spacer-fixed {
|
||||
margin: 3px;
|
||||
}
|
||||
|
||||
div.keypad div.key,
|
||||
div.keypad div.modifier,
|
||||
div.keypad div.empty {
|
||||
vertical-align: top;
|
||||
font-size: 0.9em;
|
||||
text-align: center;
|
||||
box-sizing: border-box;
|
||||
padding: 0;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
div.keypad div.empty {
|
||||
border: thin solid transparent;
|
||||
}
|
||||
div.keypad div.key,
|
||||
div.keypad div.modifier {
|
||||
box-shadow: var(--shadow-micro);
|
||||
border: var(--border-key-thin);
|
||||
border-radius: 6px;
|
||||
color: var(--cs-key-default-fg);
|
||||
background-color: var(--cs-key-default-bg);
|
||||
cursor: pointer;
|
||||
}
|
||||
div.keypad div.key:hover,
|
||||
div.keypad div.modifier:hover {
|
||||
color: var(--cs-key-hovered-fg);
|
||||
background-color: var(--cs-key-hovered-bg);
|
||||
}
|
||||
div.keypad div.rounded-left {
|
||||
border-radius: 6px 0px 0px 6px !important;
|
||||
}
|
||||
div.keypad div.rounded-right {
|
||||
border-radius: 0px 6px 6px 0px !important;
|
||||
}
|
||||
div.keypad div.rounded-none {
|
||||
border-radius: 0px !important;
|
||||
}
|
||||
div.keypad div.pressed {
|
||||
box-shadow: none;
|
||||
color: var(--cs-key-pressed-fg) !important;
|
||||
background-color: var(--cs-key-pressed-bg) !important;
|
||||
}
|
||||
div.keypad div.holded {
|
||||
box-shadow: none;
|
||||
color: var(--cs-key-default-fg) !important;
|
||||
background-color: var(--cs-key-holded-bg) !important;
|
||||
}
|
||||
div.keypad div.key:last-child,
|
||||
div.keypad div.empty:last-child,
|
||||
div.keypad div.modifier:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
div.keypad div.wide-0 {
|
||||
width: 28px;
|
||||
}
|
||||
div.keypad div.wide-1 {
|
||||
width: 61px;
|
||||
}
|
||||
div.keypad div.wide-2 {
|
||||
width: 77px;
|
||||
}
|
||||
div.keypad div.wide-3 {
|
||||
width: 102px;
|
||||
}
|
||||
div.keypad div.wide-4 {
|
||||
flex-grow: 1;
|
||||
width: 288px;
|
||||
}
|
||||
div.keypad div.left {
|
||||
text-align: left !important;
|
||||
padding-left: 6px !important;
|
||||
}
|
||||
div.keypad div.right {
|
||||
text-align: right !important;
|
||||
padding-right: 6px !important;
|
||||
}
|
||||
div.keypad div.small {
|
||||
font-size: 0.7em;
|
||||
}
|
||||
|
||||
div.keypad div.label {
|
||||
margin: 0;
|
||||
position: relative;
|
||||
top: 50%;
|
||||
-webkit-transform: translateY(-50%);
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
div.keypad b {
|
||||
color: var(--cs-key-holded-bg);
|
||||
}
|
||||
46
kvmd_data/usr/share/kvmd/web/share/css/kvm/about.css
Normal file
@@ -0,0 +1,46 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# 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/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
div#about {
|
||||
max-width: 600px;
|
||||
white-space: normal;
|
||||
padding: 5px 5px 5px 5px;
|
||||
}
|
||||
|
||||
div#about td.logo {
|
||||
padding-right: 25px;
|
||||
}
|
||||
|
||||
div#about td.title {
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
div#about td.copyright {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
div#about div.tabs-box div.tab div.code {
|
||||
-webkit-user-select: text;
|
||||
-moz-user-select: text;
|
||||
user-select: text;
|
||||
height: 250px;
|
||||
}
|
||||
29
kvmd_data/usr/share/kvmd/web/share/css/kvm/hid.css
Normal file
@@ -0,0 +1,29 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# 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/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
div#text-menu {
|
||||
width: 340px;
|
||||
}
|
||||
|
||||
input#hid-recorder-new-script-file {
|
||||
display: none;
|
||||
}
|
||||
28
kvmd_data/usr/share/kvmd/web/share/css/kvm/keyboard.css
Normal file
@@ -0,0 +1,28 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# 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/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
div#keyboard-desktop {
|
||||
display: block;
|
||||
}
|
||||
div#keyboard-mobile {
|
||||
display: none;
|
||||
}
|
||||
30
kvmd_data/usr/share/kvmd/web/share/css/kvm/msd.css
Normal file
@@ -0,0 +1,30 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# 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/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
div#msd-menu {
|
||||
width: 450px;
|
||||
}
|
||||
|
||||
div#msd-menu div.msd-message,
|
||||
div#msd-menu input.msd-message {
|
||||
display: none;
|
||||
}
|
||||
111
kvmd_data/usr/share/kvmd/web/share/css/kvm/stream.css
Normal file
@@ -0,0 +1,111 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# 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/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
div#stream-window {
|
||||
min-width: 400px;
|
||||
min-height: 200px;
|
||||
/*padding-top: 23px;
|
||||
padding-bottom: 2px;
|
||||
padding-left: 2px;
|
||||
padding-right: 2px;*/
|
||||
}
|
||||
|
||||
div#stream-info {
|
||||
display: none;
|
||||
}
|
||||
|
||||
div#stream-ocr-window {
|
||||
cursor: crosshair;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 1;
|
||||
background-color: unset !important;
|
||||
border-radius: unset !important;
|
||||
border: unset !important;
|
||||
padding: 0px !important;
|
||||
background: radial-gradient(transparent 15%, black);
|
||||
}
|
||||
div#stream-ocr-selection {
|
||||
position: relative;
|
||||
background-color: #5b90bb50;
|
||||
box-shadow: inset 0 0 0px 1px #e8e8e8cd;
|
||||
}
|
||||
|
||||
div#stream-box {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
border: var(--border-window-default-thin);
|
||||
margin: -1px -1px -1px -1px; /* See pikvm/kvm#86, pikvm/pikvm#599 */
|
||||
}
|
||||
div.stream-box-offline {
|
||||
-webkit-filter: grayscale(100%) brightness(75%) sepia(75%);
|
||||
filter: grayscale(100%) brightness(75%) sepia(75%);
|
||||
}
|
||||
div.stream-box-offline::after {
|
||||
cursor: wait;
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: inline-block;
|
||||
background: radial-gradient(transparent 20%, black);
|
||||
}
|
||||
div.stream-box-mouse-dot {
|
||||
cursor: url("../../svg/stream-mouse-cursor.svg") 5 5, pointer;
|
||||
}
|
||||
div.stream-box-mouse-none {
|
||||
cursor: none;
|
||||
}
|
||||
|
||||
img#stream-image,
|
||||
video#stream-video {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
background-color: black;
|
||||
}
|
||||
div#stream-window.window-active:fullscreen {
|
||||
border: 0px !important;
|
||||
border-radius: 0px !important;
|
||||
}
|
||||
div#stream-window.window-active:fullscreen div#stream-box div#stream-fullscreen-active {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
border: 0;
|
||||
box-shadow: var(--shadow-window-fullscreen-active);
|
||||
}
|
||||
|
||||
div#stream-mouse-buttons {
|
||||
display: none;
|
||||
}
|
||||
26
kvmd_data/usr/share/kvmd/web/share/css/kvm/system.css
Normal file
@@ -0,0 +1,26 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# 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/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
div#system-menu {
|
||||
min-width: 400px;
|
||||
max-width: 400px;
|
||||
}
|
||||
89
kvmd_data/usr/share/kvmd/web/share/css/kvm/x-mobile.css
Normal file
@@ -0,0 +1,89 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# 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/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
/* ===== stream.css ===== */
|
||||
|
||||
div#stream-window {
|
||||
padding-top: 3px !important;
|
||||
padding-bottom: 80px !important;
|
||||
border-top: 0 !important;
|
||||
border-left: 0 !important;
|
||||
border-right: 0 !important;
|
||||
border-radius: 0 !important;
|
||||
top: 50px !important;
|
||||
left: 50% !important;
|
||||
width: 100% !important;
|
||||
-webkit-transform: translateX(-50%) !important;
|
||||
transform: translateX(-50%) !important;
|
||||
}
|
||||
|
||||
div#stream-window::after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
div#stream-window-header {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
div#stream-info {
|
||||
display: block !important;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
padding-bottom: 3px;
|
||||
font-size: 0.8em;
|
||||
color: var(--cs-window-header-default-fg);
|
||||
}
|
||||
|
||||
div#stream-mouse-buttons {
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
|
||||
/* ===== keyboard.css ===== */
|
||||
|
||||
div#keyboard-window {
|
||||
visibility: visible !important;
|
||||
padding-top: 9px !important;
|
||||
padding-bottom: 30px !important;
|
||||
border-bottom: 0 !important;
|
||||
border-left: 0 !important;
|
||||
border-right: 0 !important;
|
||||
border-radius: 0 !important;
|
||||
top: unset !important;
|
||||
bottom: 0 !important;
|
||||
width: 100% !important;
|
||||
left: 50% !important;
|
||||
-webkit-transform: translateX(-50%) !important;
|
||||
transform: translateX(-50%) !important;
|
||||
}
|
||||
|
||||
div#keyboard-window-header {
|
||||
display: none !important
|
||||
}
|
||||
|
||||
div#keyboard-desktop {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
div#keyboard-mobile {
|
||||
display: block !important;
|
||||
}
|
||||
82
kvmd_data/usr/share/kvmd/web/share/css/led.css
Normal file
@@ -0,0 +1,82 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# 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/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
@-webkit-keyframes spin {
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@keyframes spin {
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
:root {
|
||||
--led-filter-gray: invert(0.5);
|
||||
--led-filter-green: invert(0.5) sepia(1) saturate(5) hue-rotate(100deg);
|
||||
--led-filter-red: invert(0.5) sepia(1) saturate(15) hue-rotate(320deg);
|
||||
--led-filter-yellow: invert(0.5) sepia(1) saturate(5) hue-rotate(0deg);
|
||||
|
||||
--led-spin-slow: spin 6s linear infinite;
|
||||
--led-spin-medium: spin 3s linear infinite;
|
||||
--led-spin-fast: spin 2s linear infinite;
|
||||
}
|
||||
|
||||
img.led-gray {
|
||||
-webkit-transform: translateZ(0); /* Без этого новый сафари не переключает иконки */
|
||||
-webkit-filter: var(--led-filter-gray);
|
||||
filter: var(--led-filter-gray);
|
||||
}
|
||||
|
||||
img.led-green {
|
||||
-webkit-transform: translateZ(0);
|
||||
-webkit-filter: var(--led-filter-green);
|
||||
filter: var(--led-filter-green);
|
||||
}
|
||||
|
||||
img.led-red {
|
||||
-webkit-transform: translateZ(0);
|
||||
-webkit-filter: var(--led-filter-red);
|
||||
filter: var(--led-filter-red);
|
||||
}
|
||||
|
||||
img.led-yellow {
|
||||
-webkit-transform: translateZ(0);
|
||||
-webkit-filter: var(--led-filter-yellow);
|
||||
filter: var(--led-filter-yellow);
|
||||
}
|
||||
|
||||
img.led-red-rotating-fast {
|
||||
-webkit-filter: var(--led-filter-red);
|
||||
filter: var(--led-filter-red);
|
||||
-webkit-animation: var(--led-spin-fast);
|
||||
animation: var(--led-spin-fast);
|
||||
}
|
||||
|
||||
img.led-yellow-rotating-fast {
|
||||
-webkit-filter: var(--led-filter-yellow);
|
||||
filter: var(--led-filter-yellow);
|
||||
-webkit-animation: var(--led-spin-fast);
|
||||
animation: var(--led-spin-fast);
|
||||
}
|
||||
50
kvmd_data/usr/share/kvmd/web/share/css/login/login.css
Normal file
@@ -0,0 +1,50 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# 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/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
div#login-box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
div#login {
|
||||
text-align: left;
|
||||
outline: none;
|
||||
word-wrap: break-word;
|
||||
max-width: 400px;
|
||||
border: var(--border-window-default-thin);
|
||||
border-radius: 8px;
|
||||
box-sizing: border-box;
|
||||
box-shadow: var(--shadow-big);
|
||||
background-color: var(--cs-window-default-bg);
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
input[type="text"]#user-input,
|
||||
input[type="password"]#passwd-input,
|
||||
input[type="text"]#code-input {
|
||||
text-align: center;
|
||||
border: thin;
|
||||
}
|
||||
320
kvmd_data/usr/share/kvmd/web/share/css/main.css
Normal file
@@ -0,0 +1,320 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# 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/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
color: var(--cs-page-default-fg);
|
||||
background-color: var(--cs-page-default-bg);
|
||||
font-family: arial, sans-serif !important;
|
||||
}
|
||||
body.body-no-select {
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
user-select: none;
|
||||
touch-action: manipulation;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--cs-page-default-fg);
|
||||
}
|
||||
|
||||
hr {
|
||||
border: none;
|
||||
border-top: var(--border-default-thin);
|
||||
}
|
||||
|
||||
p.text {
|
||||
text-align: justify;
|
||||
}
|
||||
p:not(:first-child).text {
|
||||
margin-top: 0;
|
||||
}
|
||||
p:last-child.text {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
div.code {
|
||||
white-space: nowrap;
|
||||
overflow-x: auto;
|
||||
font-family: monospace;
|
||||
border-radius: 4px;
|
||||
color: var(--cs-code-default-fg);
|
||||
background-color: var(--cs-code-default-bg);
|
||||
padding: 10px;
|
||||
}
|
||||
div.code::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
div.code::-webkit-scrollbar-thumb {
|
||||
border-radius: 4px;
|
||||
background: var(--cs-scroll-default-bg);
|
||||
}
|
||||
@-moz-document url-prefix() {
|
||||
div.code {
|
||||
scrollbar-width: 8px;
|
||||
scrollbar-color: var(--cs-scroll-default-bg) var(--cs-code-default-bg);
|
||||
}
|
||||
}
|
||||
|
||||
div.code span.code-comment {
|
||||
color: var(--cs-code-comment-fg);
|
||||
}
|
||||
|
||||
img.svg-gray {
|
||||
-webkit-filter: invert(0.7);
|
||||
filter: invert(0.7);
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
img.inline-lamp {
|
||||
vertical-align: middle;
|
||||
height: 8px;
|
||||
margin-left: 2px;
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
img.inline-lamp-big {
|
||||
vertical-align: middle;
|
||||
height: 20px;
|
||||
margin-left: 2px;
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
button,
|
||||
select,
|
||||
input[type=file]::-webkit-file-selector-button,
|
||||
input[type=file]::file-selector-button {
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
color: var(--cs-control-default-fg);
|
||||
background-color: var(--cs-control-default-bg);
|
||||
height: 30px;
|
||||
font-family: inherit;
|
||||
font-size: 16px;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
button {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
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 {
|
||||
font-size: 12px;
|
||||
height: 20px;
|
||||
}
|
||||
button.key,
|
||||
select.key {
|
||||
border: var(--border-key-thin);
|
||||
box-shadow: var(--shadow-micro);
|
||||
}
|
||||
button:disabled,
|
||||
select:disabled,
|
||||
input[type=file]:disabled::-webkit-file-selector-button,
|
||||
input[type=file]:disabled::file-selector-button {
|
||||
color: var(--cs-control-disabled-fg);
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
select {
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
-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:not([size]):disabled {
|
||||
background-image: url("../svg/select-arrow-inactive.svg") !important;
|
||||
}
|
||||
select:not([size]):active {
|
||||
color: var(--cs-control-intensive-fg) !important;
|
||||
background-image: url("../svg/select-arrow-intensive.svg") !important;
|
||||
}
|
||||
select:not([size]) option {
|
||||
color: var(--cs-control-default-fg);
|
||||
background-color: var(--cs-control-default-bg);
|
||||
}
|
||||
select:not([size]) option.comment {
|
||||
color: var(--cs-control-disabled-fg);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
input[type=text], input[type=password] {
|
||||
overflow-x: auto;
|
||||
font-family: monospace;
|
||||
border-radius: 4px;
|
||||
border: var(--border-default-thin);
|
||||
color: var(--cs-code-default-fg);
|
||||
background-color: var(--cs-code-default-bg);
|
||||
padding: 2px;
|
||||
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;
|
||||
font-size: 0;
|
||||
}
|
||||
|
||||
.row50 {
|
||||
display: inline-block;
|
||||
width: 50%;
|
||||
}
|
||||
.row33 {
|
||||
display: inline-block;
|
||||
width: 33.33%;
|
||||
}
|
||||
.row25 {
|
||||
display: inline-block;
|
||||
width: 25%;
|
||||
}
|
||||
.row16 {
|
||||
display: inline-block;
|
||||
width: 16.66%;
|
||||
}
|
||||
.row50:not(:first-child),
|
||||
.row33:not(:first-child),
|
||||
.row25:not(:first-child),
|
||||
.row16:not(:first-child) {
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
border-left: var(--border-control-thin) !important;
|
||||
}
|
||||
.row50:not(:last-child),
|
||||
.row33:not(:last-child),
|
||||
.row25:not(:last-child),
|
||||
.row16:not(:last-child) {
|
||||
border-top-right-radius: 0;
|
||||
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;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
font-size: 0.7em;
|
||||
color: var(--cs-page-obscure-fg);
|
||||
z-index: -10;
|
||||
}
|
||||
ul.footer li {
|
||||
padding: 0 10px;
|
||||
}
|
||||
ul.footer li.left {
|
||||
float: left;
|
||||
}
|
||||
ul.footer li.right {
|
||||
float: right;
|
||||
}
|
||||
ul.footer li a {
|
||||
color: var(--cs-page-obscure-fg);
|
||||
}
|
||||
|
||||
.credits {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
.feature-disabled {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
76
kvmd_data/usr/share/kvmd/web/share/css/modal.css
Normal file
@@ -0,0 +1,76 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# 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/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
div.modal {
|
||||
visibility: hidden;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgb(0, 0, 0);
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
z-index: 2147483647;
|
||||
}
|
||||
|
||||
div.modal div.modal-window {
|
||||
display: table;
|
||||
outline: none;
|
||||
margin: 15% auto;
|
||||
overflow: hidden;
|
||||
border: var(--border-window-default-2px);
|
||||
border-radius: 8px;
|
||||
box-sizing: border-box;
|
||||
box-shadow: var(--shadow-big);
|
||||
background-color: var(--cs-window-default-bg);
|
||||
padding: 0;
|
||||
}
|
||||
div.modal div.modal-window-active {
|
||||
border: var(--border-window-active-2px) !important;
|
||||
}
|
||||
|
||||
div.modal div.modal-window div.modal-header {
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
padding: 3px 9px 3px 9px;
|
||||
border-bottom: var(--border-default-thin);
|
||||
}
|
||||
|
||||
div.modal div.modal-window div.modal-content {
|
||||
max-width: 500px;
|
||||
max-height: 500px;
|
||||
padding: 16px 9px 16px 9px;
|
||||
}
|
||||
|
||||
div.modal div.modal-window div.modal-buttons {
|
||||
border-top: var(--border-control-thin);
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-size: 0;
|
||||
}
|
||||
|
||||
div.modal div.modal-window div.modal-buttons button {
|
||||
box-shadow: none;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
height: 40px;
|
||||
}
|
||||
230
kvmd_data/usr/share/kvmd/web/share/css/navbar.css
Normal file
@@ -0,0 +1,230 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# 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/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
ul#navbar {
|
||||
box-shadow: var(--shadow-small);
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: var(--cs-navbar-default-bg);
|
||||
position: fixed;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
z-index: 2147483646;
|
||||
}
|
||||
|
||||
ul#navbar li.right {
|
||||
border-left: var(--border-navbar-item-thin);
|
||||
float: right;
|
||||
}
|
||||
|
||||
ul#navbar li.left {
|
||||
border-right: var(--border-navbar-item-thin);
|
||||
float: left;
|
||||
}
|
||||
|
||||
ul#navbar li a#logo {
|
||||
height: 50px; /* Чтобы вертикальные разделители не вылезали за пределы навбара */
|
||||
line-height: 50px;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
color: var(--cs-navbar-default-fg);
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
ul#navbar li a.menu-button {
|
||||
height: 50px; /* То же самое */
|
||||
line-height: 50px;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
color: var(--cs-navbar-default-fg);
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
text-decoration: none;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/*ul#navbar li a.menu-button:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: calc(100% - 10px);
|
||||
right: 0;
|
||||
width: 0;
|
||||
border-bottom: 10px solid var(--cs-navbar-item-pressed-bg);
|
||||
border-left: 10px solid transparent;
|
||||
}*/
|
||||
|
||||
ul#navbar li a#logo:hover:not(.active),
|
||||
ul#navbar li a.menu-button:hover:not(.active) {
|
||||
background-color: var(--cs-navbar-item-hovered-bg);
|
||||
}
|
||||
|
||||
ul#navbar li a#logo img {
|
||||
margin-top: -2px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
ul#navbar li a.menu-button span,
|
||||
ul#navbar li a.menu-button img {
|
||||
vertical-align: middle;
|
||||
height: 20px;
|
||||
}
|
||||
ul#navbar li a.menu-button span:not(:last-child),
|
||||
ul#navbar li a.menu-button img:not(:last-child) {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
ul#navbar li a.menu-button-pressed {
|
||||
box-shadow: var(--shadow-navbar-item-pressed);
|
||||
background-color: var(--cs-navbar-item-pressed-bg) !important;
|
||||
}
|
||||
|
||||
ul#navbar li div.menu {
|
||||
visibility: hidden;
|
||||
outline: none;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
max-height: calc(100vh - 55px);
|
||||
white-space: nowrap;
|
||||
border: var(--border-navbar-menu-default-2px);
|
||||
border-top: var(--border-navbar-menu-top-thin);
|
||||
border-radius: 0 0 8px 8px;
|
||||
position: absolute;
|
||||
background-color: var(--cs-navbar-default-bg);
|
||||
box-shadow: var(--shadow-big);
|
||||
z-index: 2147483645;
|
||||
}
|
||||
ul#navbar li div.menu-active {
|
||||
border: var(--border-navbar-menu-active-2px) !important;
|
||||
border-top: var(--border-navbar-menu-top-thin) !important;
|
||||
}
|
||||
|
||||
ul#navbar li div.menu::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
ul#navbar li div.menu::-webkit-scrollbar-thumb {
|
||||
border-radius: 4px;
|
||||
background: var(--cs-scroll-default-bg);
|
||||
}
|
||||
@-moz-document url-prefix() {
|
||||
ul#navbar li div.menu {
|
||||
scrollbar-width: 8px;
|
||||
scrollbar-color: var(--cs-scroll-default-bg) var(--cs-code-default-bg);
|
||||
}
|
||||
}
|
||||
|
||||
ul#navbar li div.menu details summary {
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
text-decoration: underline;
|
||||
margin: 8px 15px 8px 15px;
|
||||
font-size: 12px;
|
||||
}
|
||||
ul#navbar li div.menu details div.spoiler {
|
||||
margin-left: 20px !important;
|
||||
border-left: var(--border-default-thin);
|
||||
border-bottom: var(--border-default-thin);
|
||||
}
|
||||
ul#navbar li div.menu details div.spoiler hr {
|
||||
border: none;
|
||||
border-top: var(--border-default-thin);
|
||||
}
|
||||
ul#navbar li div.menu details summary::marker {
|
||||
color: var(--cs-marker-fg);
|
||||
}
|
||||
|
||||
ul#navbar li div.menu div.buttons {
|
||||
background-color: var(--cs-control-default-bg);
|
||||
}
|
||||
|
||||
ul#navbar li div.menu div.text {
|
||||
margin: 10px 15px 10px 15px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
ul#navbar li div.menu div.buttons button,
|
||||
ul#navbar li div.menu div.buttons select {
|
||||
border-radius: 0;
|
||||
text-align: left;
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
ul#navbar li div.menu input[type=text] {
|
||||
height: 1.5em;
|
||||
}
|
||||
ul#navbar li div.menu input[type=text]::-moz-placeholder {
|
||||
text-align: center;
|
||||
}
|
||||
ul#navbar li div.menu input[type=text]::-webkit-input-placeholder {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
ul#navbar li div.menu hr {
|
||||
margin: 0;
|
||||
display: block;
|
||||
height: 0px;
|
||||
padding: 0;
|
||||
border: none;
|
||||
border-top: var(--border-control-thin);
|
||||
}
|
||||
|
||||
ul#navbar li div.menu img.sign {
|
||||
vertical-align: middle;
|
||||
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);
|
||||
}
|
||||
45
kvmd_data/usr/share/kvmd/web/share/css/progress.css
Normal file
@@ -0,0 +1,45 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# 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/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
div.progress {
|
||||
background-color: var(--cs-progress-default-bg);
|
||||
height: 1.5em;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
div.progress:before {
|
||||
color: var(--cs-progress-default-fg);
|
||||
content: attr(data-label);
|
||||
font-size: 0.8em;
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
top: 4px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
div.progress span.progress-value {
|
||||
background-color: var(--cs-progress-bar-bg);
|
||||
display: inline-block;
|
||||
height: 100%;
|
||||
}
|
||||
79
kvmd_data/usr/share/kvmd/web/share/css/radio.css
Normal file
@@ -0,0 +1,79 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# 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/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
div.radio-box {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
div.radio-box input[type=radio] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
div.radio-box label {
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
user-select: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
padding: 4px 8px;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
}
|
||||
div.radio-box label:not(:last-of-type) {
|
||||
margin-right: 1px;
|
||||
box-shadow: 1px 0 0 0 var(--cs-control-pressed-bg);
|
||||
}
|
||||
div.radio-box label:first-of-type {
|
||||
border-top-left-radius: 4px;
|
||||
border-bottom-left-radius: 4px;
|
||||
}
|
||||
div.radio-box label:last-of-type {
|
||||
border-top-right-radius: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
}
|
||||
|
||||
div.radio-box input[type=radio]:checked + label {
|
||||
/*font-weight: bold;*/
|
||||
text-shadow: 0.5px 0 0 currentColor;
|
||||
border: var(--border-intensive-2px);
|
||||
color: var(--cs-control-intensive-fg);
|
||||
background-color: var(--cs-thumb-default-bg);
|
||||
}
|
||||
div.radio-box input[type=radio] + label {
|
||||
border: var(--border-default-2px);
|
||||
color: var(--cs-control-default-fg);
|
||||
background-color: var(--cs-control-default-bg);
|
||||
}
|
||||
div.radio-box input[type=radio]:checked:disabled + label {
|
||||
cursor: default;
|
||||
border: var(--border-default-2px);
|
||||
color: var(--cs-control-default-fg);
|
||||
background-color: var(--cs-control-default-bg);
|
||||
}
|
||||
div.radio-box input[type=radio]:not(:checked):disabled + label {
|
||||
cursor: default;
|
||||
color: var(--cs-control-disabled-fg);
|
||||
background-color: var(--cs-thumb-disabled-bg);
|
||||
}
|
||||
93
kvmd_data/usr/share/kvmd/web/share/css/slider.css
Normal file
@@ -0,0 +1,93 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# 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/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
@supports (-webkit-appearance:none) {
|
||||
input[type=range].slider {
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
width: 100%;
|
||||
box-shadow: none;
|
||||
background: transparent;
|
||||
margin: 8px 0 8px 0;
|
||||
-webkit-appearance: none;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
}
|
||||
@supports not (-webkit-appearance:none) {
|
||||
input[type=range].slider {
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
width: 100%;
|
||||
box-shadow: none;
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
input[type=range].slider:disabled {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
input[type=range].slider::-webkit-slider-runnable-track {
|
||||
height: 5px;
|
||||
background: var(--cs-control-default-bg);
|
||||
border-radius: 3px;
|
||||
}
|
||||
input[type=range].slider:disabled::-webkit-slider-runnable-track {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
input[type=range].slider::-webkit-slider-thumb {
|
||||
border: var(--border-intensive-2px);
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
border-radius: 25px;
|
||||
background: var(--cs-thumb-default-bg);
|
||||
-webkit-appearance: none;
|
||||
margin-top: -7px;
|
||||
}
|
||||
input[type=range].slider:disabled::-webkit-slider-thumb {
|
||||
cursor: default;
|
||||
border: var(--border-default-2px);
|
||||
background: var(--cs-thumb-disabled-bg);
|
||||
}
|
||||
|
||||
input[type=range].slider::-moz-range-track {
|
||||
height: 5px;
|
||||
background: var(--cs-control-default-bg);
|
||||
border-radius: 3px;
|
||||
}
|
||||
input[type=range].slider:disabled::-moz-range-track {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
input[type=range].slider::-moz-range-thumb {
|
||||
border: var(--border-intensive-2px);
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
border-radius: 25px;
|
||||
background: var(--cs-thumb-default-bg);
|
||||
}
|
||||
input[type=range].slider:disabled::-moz-range-thumb {
|
||||
cursor: default;
|
||||
border: var(--border-default-2px);
|
||||
background: var(--cs-thumb-disabled-bg);
|
||||
}
|
||||
43
kvmd_data/usr/share/kvmd/web/share/css/start.css
Normal file
@@ -0,0 +1,43 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# 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/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
div.start-box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
div.start-box div.start {
|
||||
text-align: left;
|
||||
outline: none;
|
||||
word-wrap: break-word;
|
||||
max-width: 800px;
|
||||
border: var(--border-window-default-thin);
|
||||
border-radius: 8px;
|
||||
box-sizing: border-box;
|
||||
box-shadow: var(--shadow-big);
|
||||
background-color: var(--cs-window-default-bg);
|
||||
padding: 15px;
|
||||
}
|
||||
105
kvmd_data/usr/share/kvmd/web/share/css/switch.css
Normal file
@@ -0,0 +1,105 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# 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/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
div.switch-box {
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
user-select: none;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
position: relative;
|
||||
width: 50px;
|
||||
margin-top: 2px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
div.switch-box input[type=checkbox] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
div.switch-box label {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
border-radius: 13px;
|
||||
}
|
||||
|
||||
div.switch-box label span.switch-inner {
|
||||
display: block;
|
||||
width: 200%;
|
||||
margin-left: -100%;
|
||||
}
|
||||
|
||||
div.switch-box label span.switch-inner:before,
|
||||
div.switch-box label span.switch-inner:after {
|
||||
display: block;
|
||||
float: left;
|
||||
width: 50%;
|
||||
height: 17px;
|
||||
padding: 0;
|
||||
line-height: 18px;
|
||||
font-size: 10px;
|
||||
font-family: sans-serif !important;
|
||||
font-weight: bold;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
div.switch-box label span.switch-inner:before {
|
||||
content: "ON";
|
||||
padding-left: 5px;
|
||||
background-color: var(--cs-control-default-bg);
|
||||
color: var(--cs-control-default-fg);
|
||||
text-align: left;
|
||||
}
|
||||
div.switch-box label span.switch-inner:after {
|
||||
content: "OFF";
|
||||
padding-right: 5px;
|
||||
background-color: var(--cs-control-default-bg);
|
||||
color: var(--cs-control-disabled-fg);
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
div.switch-box label span.switch {
|
||||
display: block;
|
||||
width: 13px;
|
||||
margin: 0px;
|
||||
background: var(--cs-thumb-default-bg);
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
border: var(--border-intensive-2px);
|
||||
border-radius: 13px;
|
||||
}
|
||||
|
||||
div.switch-box input[type=checkbox]:checked + label span.switch-inner {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
div.switch-box input[type=checkbox]:checked + label span.switch {
|
||||
right: 0px;
|
||||
}
|
||||
|
||||
div.switch-box input[type=checkbox]:disabled + label span.switch {
|
||||
background: var(--cs-thumb-disabled-bg);
|
||||
border: var(--border-default-2px);
|
||||
}
|
||||
67
kvmd_data/usr/share/kvmd/web/share/css/tabs.css
Normal file
@@ -0,0 +1,67 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# 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/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
div.tabs-box {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
div.tabs-box input[type="radio"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
div.tabs-box div.tab {
|
||||
order: 99;
|
||||
display: none;
|
||||
border: var(--border-default-thin);
|
||||
padding: 10px 10px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
div.tabs-box label {
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
user-select: none;
|
||||
order: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
border-top: var(--border-default-thin);
|
||||
border-left: var(--border-default-thin);
|
||||
padding: 4px 8px;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
top: 1px;
|
||||
}
|
||||
|
||||
div.tabs-box label:last-of-type {
|
||||
border-right: var(--border-default-thin);
|
||||
}
|
||||
|
||||
div.tabs-box input[type="radio"]:checked + label {
|
||||
background-color: var(--cs-control-default-bg);
|
||||
}
|
||||
|
||||
div.tabs-box input[type="radio"]:checked + label + .tab {
|
||||
display: block;
|
||||
}
|
||||
0
kvmd_data/usr/share/kvmd/web/share/css/user.css
Normal file
94
kvmd_data/usr/share/kvmd/web/share/css/vars.css
Normal file
@@ -0,0 +1,94 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# 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/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
:root {
|
||||
--cs-page-default-bg: #36393f;
|
||||
--cs-page-default-fg: #c3c3c3;
|
||||
--cs-page-obscure-fg: #6c7481;
|
||||
|
||||
--cs-control-default-bg: #36393f;
|
||||
--cs-control-default-fg: #c3c3c3;
|
||||
--cs-control-intensive-fg: white;
|
||||
--cs-control-hovered-bg: #2a2d31;
|
||||
--cs-control-hovered-fg: white;
|
||||
--cs-control-pressed-bg: #17191d;
|
||||
--cs-control-pressed-fg: #6c7481;
|
||||
--cs-control-disabled-fg: #6c7481;
|
||||
|
||||
--cs-navbar-default-bg: #202225;
|
||||
--cs-navbar-default-fg: #c3c3c3;
|
||||
--cs-navbar-item-hovered-bg: #1a1c1f;
|
||||
--cs-navbar-item-pressed-bg: #171717;
|
||||
|
||||
--cs-window-default-bg: #484b51;
|
||||
--cs-window-default-fg: #c3c3c3;
|
||||
--cs-window-header-default-fg: #aaaaaa;
|
||||
--cs-window-header-grabbed-bg: #436a8a;
|
||||
--cs-window-header-grabbed-fg: white;
|
||||
--cs-window-closer-default-fg: #6c7481;
|
||||
|
||||
--cs-code-default-bg: #17191d;
|
||||
--cs-code-default-fg: #aaaaaa;
|
||||
--cs-code-comment-fg: #6c7481;
|
||||
|
||||
--cs-scroll-default-bg: #6c7481;
|
||||
--cs-thumb-default-bg: #436a8a;
|
||||
--cs-thumb-disabled-bg: #202225;
|
||||
|
||||
--cs-progress-default-bg: #171717;
|
||||
--cs-progress-default-fg: white;
|
||||
--cs-progress-bar-bg: #436a8a;
|
||||
|
||||
--cs-key-default-bg: #3b3e43;
|
||||
--cs-key-default-fg: #c3c3c3;
|
||||
--cs-key-hovered-bg: #2a2d31;
|
||||
--cs-key-hovered-fg: white;
|
||||
--cs-key-pressed-bg: #17191d;
|
||||
--cs-key-pressed-fg: #6c7481;
|
||||
--cs-key-holded-bg: #436a8a;
|
||||
|
||||
--cs-marker-fg: #5b90bb;
|
||||
--cs-corner-bg: #5b90bb;
|
||||
|
||||
--shadow-micro: 1px 2px 4px 0 rgba(0, 0, 0, 0.4);
|
||||
--shadow-small: 0 2px 4px 0 rgba(0, 0, 0, 0.2);
|
||||
--shadow-big: 0 8px 16px 0 rgba(0, 0, 0, 0.4);
|
||||
--shadow-navbar-item-pressed: 0 5px 0 #5b90bb inset;
|
||||
--shadow-window-fullscreen-active: 0 0 0 2px #5b90bb inset;
|
||||
|
||||
--border-default-thin: thin solid #36393f;
|
||||
--border-default-2px: 2px solid #36393f;
|
||||
--border-hovered-2px: 2px solid #2a2d31;
|
||||
--border-navbar-item-thin: thin solid black;
|
||||
--border-control-thin: thin solid #17191d;
|
||||
--border-key-thin: thin solid #202225;
|
||||
--border-intensive-2px: 2px solid #5b90bb;
|
||||
--border-intensive-thin: thin solid #5b90bb;
|
||||
|
||||
--border-window-default-2px: 2px solid #282a2e;
|
||||
--border-window-active-2px: 2px solid #5b90bb;
|
||||
--border-window-default-thin: thin solid #17191d;
|
||||
|
||||
--border-navbar-menu-default-2px: 2px solid black;
|
||||
--border-navbar-menu-active-2px: 2px solid #5b90bb;
|
||||
--border-menu-item-content-top-thin: thin solid #17191d;
|
||||
}
|
||||
176
kvmd_data/usr/share/kvmd/web/share/css/window.css
Normal file
@@ -0,0 +1,176 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# 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/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
div.window {
|
||||
visibility: hidden;
|
||||
outline: none;
|
||||
overflow: hidden;
|
||||
position: fixed;
|
||||
border: var(--border-window-default-2px);
|
||||
border-radius: 8px;
|
||||
box-sizing: border-box;
|
||||
box-shadow: var(--shadow-big);
|
||||
white-space: nowrap;
|
||||
color: var(--cs-window-default-fg);
|
||||
background-color: var(--cs-window-default-bg);
|
||||
padding: 30px 9px 9px 9px;
|
||||
}
|
||||
div.window-resizable {
|
||||
resize: both;
|
||||
}
|
||||
div.window-active {
|
||||
border: var(--border-window-active-2px) !important;
|
||||
}
|
||||
div.window-resizable.window-active::after {
|
||||
content: "";
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-style: solid;
|
||||
border-width: 0 0 15px 15px;
|
||||
border-color: transparent transparent var(--cs-corner-bg) transparent;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
position: absolute;
|
||||
}
|
||||
div.window:fullscreen {
|
||||
border: 0px;
|
||||
border-radius: 0px;
|
||||
resize: none !important;
|
||||
position: absolute !important;
|
||||
top: 0px !important;
|
||||
left: 0px !important;
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
padding: 0px !important;
|
||||
}
|
||||
div.window:fullscreen::after {
|
||||
display: none;
|
||||
}
|
||||
div.window:-webkit-full-screen {
|
||||
border: 0px;
|
||||
border-radius: 0px;
|
||||
resize: none !important;
|
||||
position: absolute !important;
|
||||
top: 0px !important;
|
||||
left: 0px !important;
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
padding: 0px !important;
|
||||
}
|
||||
div.window:-webkit-full-screen::after {
|
||||
display: none;
|
||||
}
|
||||
div.window.window-full-tab {
|
||||
border: 0px;
|
||||
border-radius: 0px;
|
||||
resize: none !important;
|
||||
top: 0px !important;
|
||||
left: 0px !important;
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
padding: 0px !important;
|
||||
}
|
||||
|
||||
div.window div.window-header {
|
||||
overflow: hidden;
|
||||
top: 0;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
height: 20px;
|
||||
font-size: 0.8em;
|
||||
color: var(--cs-window-header-default-fg);
|
||||
border-bottom: var(--border-default-thin);
|
||||
}
|
||||
div.window:fullscreen div.window-header {
|
||||
display: none !important;
|
||||
}
|
||||
div.window:-webkit-full-screen div.window-header {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
div.window div.window-header div.window-grab {
|
||||
overflow: hidden;
|
||||
top: 0;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 20px;
|
||||
cursor: move;
|
||||
padding: 3px 0 2px 20px;
|
||||
}
|
||||
|
||||
div.window div.window-header-grabbed {
|
||||
color: var(--cs-window-header-grabbed-fg);
|
||||
background-color: var(--cs-window-header-grabbed-bg);
|
||||
border-bottom: var(--border-intensive-thin);
|
||||
}
|
||||
|
||||
div.window div.window-header button.window-button-full-screen,
|
||||
div.window div.window-header button.window-button-enter-full-tab,
|
||||
div.window div.window-header button.window-button-original,
|
||||
div.window div.window-header button.window-button-maximize,
|
||||
div.window div.window-header button.window-button-close {
|
||||
border: none;
|
||||
position: absolute;
|
||||
top: -2px;
|
||||
width: 44px;
|
||||
height: 24px;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
color: var(--cs-window-closer-default-fg);
|
||||
display: inline-block;
|
||||
}
|
||||
div.window div.window-header button.window-button-full-screen {
|
||||
right: 180px;
|
||||
}
|
||||
div.window div.window-header button.window-button-enter-full-tab {
|
||||
right: 135px;
|
||||
}
|
||||
div.window div.window-header button.window-button-original {
|
||||
right: 90px;
|
||||
}
|
||||
div.window div.window-header button.window-button-maximize {
|
||||
right: 45px;
|
||||
}
|
||||
div.window div.window-header button.window-button-close {
|
||||
right: 0px;
|
||||
}
|
||||
|
||||
div.window button.window-button-exit-full-tab {
|
||||
width: 50px;
|
||||
height: 10px;
|
||||
left: calc(50% - 25px);
|
||||
font-size: 8px;
|
||||
position: absolute;
|
||||
opacity: 0.5;
|
||||
border-radius: 0px;
|
||||
visibility: hidden;
|
||||
z-index: 1;
|
||||
}
|
||||
div.window button.window-button-exit-full-tab:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
div.window.window-full-tab button.window-button-exit-full-tab {
|
||||
visibility: visible !important;
|
||||
}
|
||||
88
kvmd_data/usr/share/kvmd/web/share/css/x-desktop.css
Normal file
@@ -0,0 +1,88 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# 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/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
/* ===== main.css ===== */
|
||||
|
||||
button: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);
|
||||
background-color: var(--cs-control-hovered-bg);
|
||||
}
|
||||
|
||||
button: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;
|
||||
background-color: var(--cs-control-pressed-bg) !important;
|
||||
}
|
||||
|
||||
button.key:active,
|
||||
select.key:active {
|
||||
box-shadow: none;
|
||||
}
|
||||
select:not([size]):enabled:hover {
|
||||
background-image: url("../svg/select-arrow-intensive.svg") !important;
|
||||
}
|
||||
|
||||
|
||||
/* ===== radio.css ===== */
|
||||
|
||||
/* If we have a mouse cursor */
|
||||
div.radio-box input[type=radio]:not(:checked):not(:disabled) + label:hover {
|
||||
border: var(--border-hovered-2px);
|
||||
color: var(--cs-control-hovered-fg) !important;
|
||||
background-color: var(--cs-control-hovered-bg) !important;
|
||||
}
|
||||
|
||||
|
||||
/* ===== slider.css ===== */
|
||||
|
||||
/*div.switch-box label span.switch-inner:not(:disabled):hover::before {*/
|
||||
input[type=range].slider:not(:disabled):hover::-webkit-slider-runnable-track {
|
||||
background-color: var(--cs-control-hovered-bg);
|
||||
}
|
||||
|
||||
/*div.switch-box label span.switch-inner:not(:disabled):hover::before {*/
|
||||
input[type=range].slider:not(:disabled):hover::-moz-range-track {
|
||||
background-color: var(--cs-control-hovered-bg);
|
||||
}
|
||||
|
||||
|
||||
/* ===== switch.css ===== */
|
||||
|
||||
div.switch-box label span.switch-inner:not(:disabled):hover::before {
|
||||
background-color: var(--cs-control-hovered-bg);
|
||||
}
|
||||
|
||||
div.switch-box label span.switch-inner:not(:disabled):hover::after {
|
||||
background-color: var(--cs-control-hovered-bg);
|
||||
}
|
||||
|
||||
|
||||
/* ===== tabs.css ===== */
|
||||
|
||||
div.tabs-box input[type="radio"]:not(:checked):hover + label {
|
||||
color: var(--cs-control-hovered-fg);
|
||||
}
|
||||
121
kvmd_data/usr/share/kvmd/web/share/css/x-mobile.css
Normal file
@@ -0,0 +1,121 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# 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/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
/* ===== main.css ===== */
|
||||
|
||||
button:active,
|
||||
select:active,
|
||||
input[type=file]:active::-webkit-file-selector-button,
|
||||
input[type=file]:active::file-selector-button {
|
||||
color: var(--cs-control-hovered-fg);
|
||||
background-color: var(--cs-control-hovered-bg);
|
||||
}
|
||||
|
||||
@media only screen and (orientation: portrait) {
|
||||
button,
|
||||
select,
|
||||
input[type=text],
|
||||
input[type=password],
|
||||
input[type=file]::-webkit-file-selector-button,
|
||||
input[type=file]::file-selector-button {
|
||||
height: 45px !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* ===== window.css ===== */
|
||||
|
||||
div.window {
|
||||
padding-top: 45px !important;
|
||||
}
|
||||
div.window div.window-header {
|
||||
height: 35px !important;
|
||||
}
|
||||
div.window div.window-header div.window-grab {
|
||||
height: 35px !important;
|
||||
}
|
||||
div.window div.window-header button.window-button-full-screen,
|
||||
div.window div.window-header button.window-button-enter-full-tab,
|
||||
div.window div.window-header button.window-button-original,
|
||||
div.window div.window-header button.window-button-maximize,
|
||||
div.window div.window-header button.window-button-close {
|
||||
height: 40px !important;
|
||||
}
|
||||
|
||||
|
||||
/* ===== modal.css ===== */
|
||||
|
||||
@media only screen and (orientation: portrait) {
|
||||
div.modal-buttons button {
|
||||
height: 50px !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* ===== navbar.css ===== */
|
||||
|
||||
ul#navbar li a#logo:hover:not(.active),
|
||||
ul#navbar li a.menu-button:hover:not(.active) {
|
||||
background-color: var(--cs-navbar-default-bg);
|
||||
}
|
||||
|
||||
|
||||
/* ===== radio.css ===== */
|
||||
|
||||
/*@media only screen and (orientation: portrait) {
|
||||
div.radio-box input[type=radio] + label {
|
||||
height: 30px !important;
|
||||
}
|
||||
}*/
|
||||
|
||||
|
||||
/* ===== slider.css ===== */
|
||||
|
||||
/*@media only screen and (orientation: portrait) {
|
||||
@supports (-webkit-appearance: none) {
|
||||
input[type=range].slider {
|
||||
margin: 20px 0 20px 0 !important;
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
|
||||
/* ===== tabs.css ===== */
|
||||
|
||||
@media only screen and (orientation: portrait) {
|
||||
div.tabs-box label {
|
||||
height: 30px !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* ===== keypad.css ===== */
|
||||
|
||||
div.keypad {
|
||||
zoom: 1.28 !important;
|
||||
}
|
||||
|
||||
div.keypad div.key:hover,
|
||||
div.keypad div.modifier:hover {
|
||||
color: var(--cs-key-default-fg);
|
||||
background-color: var(--cs-key-default-bg);
|
||||
}
|
||||
BIN
kvmd_data/usr/share/kvmd/web/share/favicon-16x16.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
kvmd_data/usr/share/kvmd/web/share/favicon-32x32.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
154
kvmd_data/usr/share/kvmd/web/share/i18n/i18n_en.json
Normal file
@@ -0,0 +1,154 @@
|
||||
{
|
||||
"username": "Username: ",
|
||||
"password": "Password: ",
|
||||
"2fa_code": "2FA code: ",
|
||||
"if_enabled": "if enabled",
|
||||
"login": "Login",
|
||||
"select_language": "Select language: ",
|
||||
"chinese": "Simplified Chinese",
|
||||
"english": "English",
|
||||
"footer-left": "This site is actively using JavaScript.<br>It doesn't contain ads, but is blocked by some ad filters.<br>Please turn it off to continue and reload the page.",
|
||||
|
||||
"index": " One-KVM Index ",
|
||||
"copyright": "Copyright © 2018-2024 Maxim Devaev | Modified by SilentWind",
|
||||
"index_text_1": "Please note that when you are working with a KVM session or another application that captures the keyboard,you can't use some keyboard shortcuts such as Ctrl+Alt+Del (which will be caught by your OS) or Ctrl+W (caught by your browser).",
|
||||
"index_text_2": "To override this limitation you can use Google Chrome in application mode.",
|
||||
"serve_name": "Server:",
|
||||
"index_text_10":"PiKVM Project",
|
||||
"index_text_11":"PiKVM Documentation",
|
||||
"index_text_12":"One-KVM Project",
|
||||
"index_text_13":"One-KVM Documentation",
|
||||
"index_title":"The Open Source KVM over IP",
|
||||
|
||||
"vnc_text1": "This One-KVM device has running <b>kvmd-vnc</b> daemon and provides VNC access to the server.",
|
||||
"vnc_text2": "<b>WARNING!</b> We strongly don't recommend you to use VNC in untrusted networks without enabled X.509 or TLS encryption. Otherwise your passwords are transmitted in a plain text over the network.",
|
||||
"vnc_text3": "Your VNC client must support Tight JPEG compression and password authentication. <a href=\"https://tigervnc.org\">TigerVNC</a> is a good choice.On Linux, this client will most likely be available for installation from the repository. It can also be called vncviewer.",
|
||||
|
||||
"ipmi_text1":"This One-KVM device has running <b>kvmd-ipmi</b> daemon and provides IPMI 2.0 interface for some basic BMC operations like on/off/reset the server.",
|
||||
"ipmi_text2":"<b>WARNING!</b> We strongly don't recommend you to use IPMI in untrusted networks because this protocol is completely unsafe by design. In short, the authentication process for IPMI mandates that the server send a salted SHA1 or MD5 hash of the requested user's password to the client, prior to the client authenticating.",
|
||||
"ipmi_text3":"<b>NEVER</b> use the same passwords for KVMD and IPMI users. And even better not to use IPMI. Instead, you can directly use KVMD API via curl. Here some examples:",
|
||||
|
||||
"kvm_text1":"About",
|
||||
"kvm_text2":"// These <a href=\"https://github.com/pikvm/pikvm?tab=readme-ov-file#special-thanks\">kind people</a> donated money to the PiKVM project and supported the work on it. <br>// If you also want to support this project, you can donate on <a target=\"_blank\" href=\"https://www.patreon.com/pikvm\"> Patreon</a> or <a target=\"_blank\" href=\"https://paypal.me/pikvm\"> Paypal</a>.<br><br>// These <a href=\"https://one-kvm.mofeng.run/thanks/#_2\">kind people</a> donated money to the One-KVM project and supported the work on it. <br>// If you also want to support this project, you can donate on <a target=\"_blank\" href=\"https://afdian.com/a/silentwind\"> afdian </a>.",
|
||||
"kvm_text3":"System",
|
||||
"kvm_text4":"Runtime settings & tools",
|
||||
"kvm_text5":"Term",
|
||||
"kvm_text6":"About",
|
||||
"kvm_text7":"Log",
|
||||
"kvm_text8":"WoL",
|
||||
"kvm_text9":"Resolution:",
|
||||
"kvm_text10":"JPEG quality:",
|
||||
"kvm_text11":"JPEG max fps:",
|
||||
"kvm_text12":"H.264 kbps:",
|
||||
"kvm_text13":"H.264 gop",
|
||||
"kvm_text14":"Video mode",
|
||||
"kvm_text15":"Virtual Keyboard",
|
||||
"kvm_text16":"Terminal",
|
||||
"kvm_text17":"Orientation:",
|
||||
"kvm_text18":"Default",
|
||||
"kvm_text19":"Audio volume:",
|
||||
"kvm_text20":"• Show stream",
|
||||
"kvm_text21":"• Screenshot",
|
||||
"kvm_text22":"Reset stream",
|
||||
"kvm_text23":"Keyboard mode:",
|
||||
"kvm_text24":"Mouse mode:",
|
||||
"kvm_text25":"Keyboard & Mouse (HID) settings",
|
||||
"kvm_text26":"Mouse polling:",
|
||||
"kvm_text27":"Relative sensitivity:",
|
||||
"kvm_text28":"Reverse scrolling:",
|
||||
"kvm_text29":"Scroll rate:",
|
||||
"kvm_text30":"• Show keyboard",
|
||||
"kvm_text31":"Reset HID",
|
||||
|
||||
"kvm_text32":"Macro",
|
||||
"kvm_text33":"Record and play HID/ATX/GPIO actions<br>",
|
||||
"kvm_text34":"For security reasons, the record will not be saved on the PiKVM",
|
||||
"kvm_text35":"Rec",
|
||||
"kvm_text36":"Stop",
|
||||
"kvm_text37":"Play",
|
||||
"kvm_text38":"Clear",
|
||||
"kvm_text39":"Script time:",
|
||||
"kvm_text40":"Scripted events:",
|
||||
"kvm_text41":"include delays",
|
||||
"kvm_text42":"Upload script",
|
||||
"kvm_text43":"Download script",
|
||||
|
||||
"kvm_text44":"Text",
|
||||
"kvm_text45":"Paste text as keypress sequence<br>",
|
||||
"kvm_text46":"Please note that One-KVM cannot switch the keyboard layout",
|
||||
"kvm_text47":"• Paste",
|
||||
"kvm_text48":"using host keymap",
|
||||
"kvm_text49":"Text recognition <sup><i>β</i></sup><br>",
|
||||
"kvm_text50":"OCR works locally on One-KVM",
|
||||
"kvm_text51":"• Select area",
|
||||
"kvm_text52":"for",
|
||||
"kvm_text53":"text recognition",
|
||||
"kvm_text54":"Press <b>Enter</b> to recognize and copy text to clipboard",
|
||||
"kvm_text55":"Press <b>Esc</b> to cancel selection",
|
||||
|
||||
"kvm_text56":"Shortcuts",
|
||||
"kvm_text57":"Quick keyboard shortcuts<br>",
|
||||
"kvm_text58":"Also see <i>System → Show keyboard<i>",
|
||||
"kvm_text59":"help",
|
||||
|
||||
"kvm_text60":"Drive",
|
||||
"kvm_text61":"Mass Storage Drive:",
|
||||
"kvm_text62":"Image:",
|
||||
"kvm_text63":"Drive <a target=\"_blank\" href=\"https://docs.pikvm.org/msd\">mode</a>:",
|
||||
"kvm_text64":"Select image to upload",
|
||||
"kvm_text65":"Upload",
|
||||
"kvm_text66":"Abort",
|
||||
"kvm_text68":"Specify a local file:",
|
||||
"kvm_text69":"<b>Or</b> paste a URL:",
|
||||
"kvm_text70":"Upload partition:",
|
||||
"kvm_text71":"Note:",
|
||||
"kvm_text72":"• Don't close the browser page until the upload is complete.",
|
||||
"kvm_text73":"• To speed up the upload, close the stream window.",
|
||||
"kvm_text74":"New image:",
|
||||
"kvm_text75":"Upload size:",
|
||||
"kvm_text76":"Connect drive to Server",
|
||||
"kvm_text77":"Disconnect",
|
||||
"kvm_text78":"Reset",
|
||||
"kvm_text79":"Video Record<br>",
|
||||
"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",
|
||||
"kvm_text84":"File display:",
|
||||
"kvm_text85":"Quick file transfer:",
|
||||
"kvm_text86":"• Select NormalFiles tab to upload, package them and mount image",
|
||||
"kvm_text87":"• Disconnect MSD, unpackage it, select tab to download",
|
||||
"kvm_text88":"Package files into image",
|
||||
"kvm_text89":"Unpackage files from image",
|
||||
"kvm_text90":"ImageFiles",
|
||||
"kvm_text91":"NormalFiles",
|
||||
|
||||
"atx-ask-switch":"Ask click confirmation",
|
||||
"hid-recorder-loop-switch":"Infinite loop playback",
|
||||
"msd-rw-switch":"Writable",
|
||||
"hid-sysrq-ask-switch":"Ask the magic confirmation",
|
||||
"hid-keyboard-swap-cc-switch":"Swap Left Ctrl and Caps keys",
|
||||
"hid-mouse-squash-switch":"Squash relative moves",
|
||||
"hid-mouse-cumulative-scrolling-switch":"Cumulative scrolling",
|
||||
"hid-mouse-dot-switch":"Show the blue dot",
|
||||
"hid-connect-switch":"Connect HID to Server",
|
||||
"hid-jiggler-switch":"<a href=\"https://docs.pikvm.org/mouse_jiggler\" target=\"_blank\">Mouse jiggler</a>",
|
||||
"hid-mute-switch":"Mute HID input events",
|
||||
"page-close-ask-switch":"Ask page close confirmation",
|
||||
"hid-pak-ask-switch":"Ask paste confirmation",
|
||||
"hid-pak-secure-switch":"Hide input text",
|
||||
"meta":"Meta",
|
||||
"hardware":"Hardware",
|
||||
"version":"Version",
|
||||
"thanks":"Thanks",
|
||||
"stream-message-no-webrtc":"WebRTC is not supported by this browser",
|
||||
"stream-message-no-h264":"H.264 is not supported by this browser",
|
||||
"msd-message-offline":"Mass Storage Drive is offline",
|
||||
"msd-message-image-broken":"Current image is broken!",
|
||||
"msd-message-too-big-for-cdrom":"Current image is too big for CD-ROM!",
|
||||
"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",
|
||||
"page-full-tab-stream-switch":"Expand for the entire tab by default"
|
||||
}
|
||||
156
kvmd_data/usr/share/kvmd/web/share/i18n/i18n_zh.json
Normal file
@@ -0,0 +1,156 @@
|
||||
{
|
||||
"username": "用户名: ",
|
||||
"password": "密码: ",
|
||||
"2fa_code": "2FA 验证: ",
|
||||
"if_enabled": "如果有",
|
||||
"login": "登录",
|
||||
"select_language": "选择语言: ",
|
||||
"chinese": "简体中文",
|
||||
"english": "英语",
|
||||
"footer-left": "本网站需要使用 JavaScript 功能。<br>本网站不含广告,但仍有可能会被某些广告过滤器屏蔽。<br>如果网页响应异常请关闭它并重新加载页面以继续浏览。",
|
||||
|
||||
"index": " One-KVM 导航 ",
|
||||
"copyright": "版权所有 © 2018-2024 Maxim Devaev | 由 SilentWind 二次开发",
|
||||
"index_text_1": "请注意,在使用 KVM 会话或其他可捕获键盘的应用程序时,您可能无法使用某些快捷键,如 Ctrl+Alt+Del(这会被操作系统捕获)或 Ctrl+W(这会被浏览器捕获)。",
|
||||
"index_text_2": "要突破这一限制,您可以使用 Google Chrome 浏览器。",
|
||||
"serve_name": "主机名:",
|
||||
"index_text_10":"PiKVM 项目",
|
||||
"index_text_11":"PiKVM 文档",
|
||||
"index_text_12":"One-KVM 项目",
|
||||
"index_text_13":"One-KVM 文档",
|
||||
"index_title":"开源 KVM over IP",
|
||||
|
||||
"vnc_text1": "该 One-KVM 设备 <b>kvmd-vnc</b> 守护进程已运行,提供对服务器的 VNC 访问。",
|
||||
"vnc_text2": "<b>不建议</b>在未启用 X.509 或 TLS 加密的网络中使用 VNC。否则,您的密码将以纯文本形式在网络上传输。",
|
||||
"vnc_text3": "VNC 客户端必须支持 Tight JPEG 压缩和密码验证。",
|
||||
|
||||
"ipmi_text1":"该 One-KVM 设备 <b>kvmd-ipmi</b> 守护进程已运行,并为一些基本的 BMC 操作(如打开/关闭/重置服务器)提供了 IPMI 2.0 接口。",
|
||||
"ipmi_text2":"<b>不建议</b>在不受信任的网络中使用 IPMI,因为该协议的设计完全不安全。<br>原因如下,IPMI 的身份验证过程要求服务器在客户端进行身份验证之前,向客户端发送请求用户密码的加盐 SHA1 或 MD5 哈希值。",
|
||||
"ipmi_text3":"<b>不建议</b>为 KVMD 和 IPMI 用户使用相同的密码,或者直接关闭 IPMI。如有需要可以通过 curl 直接使用 KVMD API。下面是一些示例:",
|
||||
|
||||
"kvm_text1":"关于",
|
||||
"kvm_text2":"//<a href=\"https://github.com/pikvm/pikvm?tab=readme-ov-file#special-thanks\">这些人</a>向 PiKVM 项目捐款并支持其工作,非常感谢他们的帮助。<br>//如果您也想支持 PiKVM ,可以在 <a target=\"_blank\" href=\"https://www.patreon.com/pikvm\"> Patreon</a> 或 <a target=\"_blank\" href=\"https://paypal.me/pikvm\"> PayPal 上捐款</a>。<br><br>//<a href=\"https://one-kvm.mofeng.run/thanks/#_2\">这些人</a>向 One-KVM 项目捐款并支持其工作,非常感谢他们的帮助。<br>//如果您也想支持 One-KVM ,可以在 <a target=\"_blank\" href=\"https://afdian.com/a/silentwind\"> 爱发电 </a>上捐款</a>。",
|
||||
"kvm_text3":"系统",
|
||||
"kvm_text4":"运行设置 & 工具",
|
||||
"kvm_text5":"终端",
|
||||
"kvm_text6":"关于",
|
||||
"kvm_text7":"日志",
|
||||
"kvm_text8":"网络唤醒",
|
||||
"kvm_text9":"分辨率:",
|
||||
"kvm_text10":"JPEG 质量:",
|
||||
"kvm_text11":"JPEG 最大FPS:",
|
||||
"kvm_text12":"H.264 比特率:",
|
||||
"kvm_text13":"H.264 帧间隔:",
|
||||
"kvm_text14":"视频模式",
|
||||
"kvm_text15":"虚拟键盘",
|
||||
"kvm_text16":"终端",
|
||||
"kvm_text17":"方向:",
|
||||
"kvm_text18":"默认",
|
||||
"kvm_text19":"音量:",
|
||||
"kvm_text20":"• 显示视频流",
|
||||
"kvm_text21":"• 截屏",
|
||||
"kvm_text22":"重置视频流",
|
||||
"kvm_text23":"键盘模式:",
|
||||
"kvm_text24":"鼠标模式:",
|
||||
"kvm_text25":"键盘 & 鼠标 (HID) 设置",
|
||||
"kvm_text26":"鼠标轮询:",
|
||||
"kvm_text27":"相对灵敏度:",
|
||||
"kvm_text28":"反向滚动:",
|
||||
"kvm_text29":"滚动速率:",
|
||||
"kvm_text30":"显示虚拟键盘",
|
||||
"kvm_text31":"重置 HID",
|
||||
|
||||
"kvm_text32":"宏",
|
||||
"kvm_text33":"录制或重放 HID/ATX/GPIO 操作<br>",
|
||||
"kvm_text34":"出于安全原因,录制的脚本默认不会保存到 One-KVM 主机上",
|
||||
"kvm_text35":"录制",
|
||||
"kvm_text36":"停止",
|
||||
"kvm_text37":"重放",
|
||||
"kvm_text38":"清除",
|
||||
"kvm_text39":"脚本时间:",
|
||||
"kvm_text40":"脚本事件:",
|
||||
"kvm_text41":"包括延迟",
|
||||
"kvm_text42":"上传脚本文件",
|
||||
"kvm_text43":"下载脚本文件",
|
||||
|
||||
"kvm_text44":"文本",
|
||||
"kvm_text45":"以按键序列方式粘贴文本<br>",
|
||||
"kvm_text46":"请注意 One-KVM 无法自动切换被控机键盘布局",
|
||||
"kvm_text47":"• 粘贴",
|
||||
"kvm_text48":"使用的键盘布局",
|
||||
"kvm_text49":"文本识别<br>",
|
||||
"kvm_text50":"文本识别仅在 One-KVM 主机上本地运行",
|
||||
"kvm_text51":"• 框选识别区域",
|
||||
"kvm_text52":"进行",
|
||||
"kvm_text53":"文本识别",
|
||||
"kvm_text54":"按下 <b>Enter</b> 确认所选区域并将识别结果复制到粘贴板",
|
||||
"kvm_text55":"按下 <b>Esc</b> 取消框选",
|
||||
|
||||
"kvm_text56":"快捷键",
|
||||
"kvm_text57":"键盘快捷键<br>",
|
||||
"kvm_text58":"也可以查看 <i>系统 → 显示键盘<i>",
|
||||
"kvm_text59":"帮助",
|
||||
|
||||
"kvm_text60":"驱动器",
|
||||
"kvm_text61":"虚拟存储驱动器",
|
||||
"kvm_text62":"文件:",
|
||||
"kvm_text63":"驱动<a target=\"_blank\" href=\"https://docs.pikvm.org/msd\">模式</a>:",
|
||||
"kvm_text64":"选择要上传的文件",
|
||||
"kvm_text65":"上传",
|
||||
"kvm_text66":"中止",
|
||||
"kvm_text68":"指定本地文件:",
|
||||
"kvm_text69":"<b>或者</b>粘贴 URL:",
|
||||
"kvm_text70":"上传分区:",
|
||||
"kvm_text71":"注意:",
|
||||
"kvm_text72":"• 在上传完成之前,不要关闭浏览器页面。",
|
||||
"kvm_text73":"• 要加快上传速度,请关闭视频流窗口。",
|
||||
"kvm_text74":"新镜像:",
|
||||
"kvm_text75":"上传大小:",
|
||||
"kvm_text76":"连接 MSD 到主机",
|
||||
"kvm_text77":"断开连接",
|
||||
"kvm_text78":"重置",
|
||||
"kvm_text79":"视频录制<br>",
|
||||
"kvm_text80":"使用浏览器 API 录制视频,结束录制后视频文件会自动下载",
|
||||
"kvm_text81":"开始录制",
|
||||
"kvm_text82":"结束录制",
|
||||
"kvm_text83":"网页界面设置",
|
||||
"kvm_text84":"文件显示:",
|
||||
"kvm_text85":"快速文件互传:",
|
||||
"kvm_text86":"• 切换互传文件上传文件,打包生成镜像文件,挂载 NormalFiles 镜像",
|
||||
"kvm_text87":"• 断开 MSD 连接,选择从镜像文件解压,切换互传文件下载所需文件",
|
||||
"kvm_text88":"从互传文件打包镜像文件",
|
||||
"kvm_text89":"从镜像文件解压互传文件",
|
||||
"kvm_text90":"镜像文件",
|
||||
"kvm_text91":"互传文件",
|
||||
|
||||
|
||||
|
||||
"atx-ask-switch":"点击二次确认",
|
||||
"hid-recorder-loop-switch":"无限循环重放",
|
||||
"msd-rw-switch":"读写",
|
||||
"hid-sysrq-ask-switch":"魔法键二次确认",
|
||||
"hid-keyboard-swap-cc-switch":"交换左 Ctrl 键和大写字母键",
|
||||
"hid-mouse-squash-switch":"减少相对移动",
|
||||
"hid-mouse-cumulative-scrolling-switch":"累加滚动",
|
||||
"hid-mouse-dot-switch":"显示蓝色圆点",
|
||||
"hid-connect-switch":"连接 HID 到主机",
|
||||
"hid-jiggler-switch":"<a href=\"https://docs.pikvm.org/mouse_jiggler\" target=\"_blank\">鼠标抖动器</a>",
|
||||
"hid-mute-switch":"屏蔽 HID 输入事件",
|
||||
"page-close-ask-switch":"页面关闭二次确认",
|
||||
"hid-pak-ask-switch":"粘贴二次确认",
|
||||
"hid-pak-secure-switch":"隐藏输入文本",
|
||||
"meta":"元数据",
|
||||
"hardware":"硬件",
|
||||
"version":"版本",
|
||||
"thanks":"鸣谢",
|
||||
"stream-message-no-webrtc":"当前浏览器不支持 WebRTC",
|
||||
"stream-message-no-h264":"当前浏览器不支持 H.264",
|
||||
"msd-message-offline":"虚拟存储驱动器已离线",
|
||||
"msd-message-image-broken":"当前镜像已损坏!",
|
||||
"msd-message-too-big-for-cdrom":"当前 CD-ROM 格式镜像大小超出限制!",
|
||||
"msd-message-out-of-storage":"当前镜像大小超出存储空间",
|
||||
"msd-message-rw-enabled":"读写模式已启用",
|
||||
"msd-message-downloads":"正在从 One-KVM 下载镜像",
|
||||
"msd-message-another-user-uploads":"另一个用户正在上传镜像",
|
||||
"page-full-tab-stream-switch":"自动全屏视频窗口"
|
||||
}
|
||||
159
kvmd_data/usr/share/kvmd/web/share/js/bb.js
Normal file
@@ -0,0 +1,159 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# KVMD - The main PiKVM daemon. #
|
||||
# #
|
||||
# Copyright (C) 2018-2024 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the GNU General Public License as published by #
|
||||
# the Free Software Foundation, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, #
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||
# GNU General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License #
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
"use strict";
|
||||
|
||||
|
||||
export var browser = new function() {
|
||||
// https://stackoverflow.com/questions/9847580/how-to-detect-safari-chrome-ie-firefox-and-opera-browser/9851769
|
||||
// https://github.com/fingerprintjs/fingerprintjs/discussions/641
|
||||
|
||||
// Opera 8.0+
|
||||
let is_opera = (
|
||||
(!!window.opr && !!opr.addons) // eslint-disable-line no-undef
|
||||
|| !!window.opera
|
||||
|| (navigator.userAgent.indexOf(" OPR/") >= 0)
|
||||
);
|
||||
|
||||
// Firefox 1.0+
|
||||
let is_firefox = (typeof mozInnerScreenX !== "undefined");
|
||||
|
||||
// Safari 3.0+ "[object HTMLElementConstructor]"
|
||||
let is_safari = (function() {
|
||||
if (/constructor/i.test(String(window["HTMLElement"]))) {
|
||||
return true;
|
||||
}
|
||||
let push = null;
|
||||
try {
|
||||
push = window.top["safari"].pushNotification;
|
||||
} catch {
|
||||
try {
|
||||
push = window["safari"].pushNotification;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return String(push) === "[object SafariRemoteNotification]";
|
||||
})();
|
||||
|
||||
// Chrome 1+
|
||||
let is_chrome = !!window.chrome;
|
||||
|
||||
// Blink engine detection
|
||||
let is_blink = ((is_chrome || is_opera) && !!window.CSS);
|
||||
|
||||
// Any browser on Mac
|
||||
let is_mac = ((
|
||||
window.navigator.oscpu
|
||||
|| window.navigator.platform
|
||||
|| window.navigator.appVersion
|
||||
|| "Unknown"
|
||||
).indexOf("Mac") !== -1);
|
||||
|
||||
// Any Windows
|
||||
let is_win = (navigator && !!(/win/i).exec(navigator.platform));
|
||||
|
||||
// iOS browsers
|
||||
// https://stackoverflow.com/questions/9038625/detect-if-device-is-ios
|
||||
// https://github.com/lancedikson/bowser/issues/329
|
||||
let is_ios = (!!navigator.platform && (
|
||||
/iPad|iPhone|iPod/.test(navigator.platform)
|
||||
|| (navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1 && !window["MSStream"])
|
||||
));
|
||||
|
||||
let is_android = /android/i.test(navigator.userAgent);
|
||||
|
||||
let flags = {
|
||||
"is_opera": is_opera,
|
||||
"is_firefox": is_firefox,
|
||||
"is_safari": is_safari,
|
||||
"is_chrome": is_chrome,
|
||||
"is_blink": is_blink,
|
||||
"is_mac": is_mac,
|
||||
"is_win": is_win,
|
||||
"is_ios": is_ios,
|
||||
"is_android": is_android,
|
||||
"is_mobile": (is_ios || is_android),
|
||||
};
|
||||
|
||||
console.log("===== BB flags:", flags);
|
||||
return flags;
|
||||
};
|
||||
|
||||
export function checkBrowser(desktop_css, mobile_css) {
|
||||
if (
|
||||
!window.navigator
|
||||
|| window.navigator.userAgent.indexOf("MSIE ") > 0
|
||||
|| window.navigator.userAgent.indexOf("Trident/") > 0
|
||||
|| window.navigator.userAgent.indexOf("Edge/") > 0
|
||||
) {
|
||||
let el_modal = document.createElement("div");
|
||||
el_modal.className = "modal";
|
||||
el_modal.style.visibility = "visible";
|
||||
el_modal.innerHTML = `
|
||||
<div class="modal-window">
|
||||
<div class="modal-content">
|
||||
Hello. You are using an incompatible or legacy browser.<br>
|
||||
Please use one of the following browsers:
|
||||
<hr>
|
||||
<ul>
|
||||
<li><a target="_blank" href="https://google.com/chrome">Google Chrome</a> <sup><i>recommended</i></sup></li>
|
||||
<li><a target="_blank" href="https://chromium.org/Home">Chromium</a> <sup><i>recommended</i></sup></li>
|
||||
<li><a target="_blank" href="https://mozilla.org/firefox">Mozilla Firefox</a></li>
|
||||
<li><a target="_blank" href="https://apple.com/safari">Apple Safari</a></li>
|
||||
<li><a target="_blank" href="https://opera.com">Opera</a></li>
|
||||
<li><a target="_blank" href="https://vivaldi.com">Vivaldi</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
document.body.appendChild(el_modal);
|
||||
return false;
|
||||
|
||||
} else {
|
||||
let force_desktop = (new URL(window.location.href)).searchParams.get("force_desktop");
|
||||
let force_mobile = (new URL(window.location.href)).searchParams.get("force_mobile");
|
||||
if ((force_desktop || !browser.is_mobile) && !force_mobile) {
|
||||
__addCssLink("/share/css/x-desktop.css");
|
||||
if (desktop_css) {
|
||||
__addCssLink(desktop_css);
|
||||
}
|
||||
} else {
|
||||
__addCssLink("/share/css/x-mobile.css");
|
||||
if (mobile_css) {
|
||||
__addCssLink(mobile_css);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function __addCssLink(path) {
|
||||
console.log("===== Adding CSS:", path);
|
||||
let el_head = document.getElementsByTagName("head")[0];
|
||||
let el_link = document.createElement("link");
|
||||
el_link.rel = "stylesheet";
|
||||
el_link.type = "text/css";
|
||||
el_link.href = path;
|
||||
el_link.media = "all";
|
||||
el_head.appendChild(el_link);
|
||||
}
|
||||
61
kvmd_data/usr/share/kvmd/web/share/js/i18n/i18n.js
Normal file
@@ -0,0 +1,61 @@
|
||||
function setCookie (name, value)
|
||||
{
|
||||
var expdate = new Date();
|
||||
expdate.setTime(expdate.getTime() + 30 * 24 * 60 * 60 * 1000);
|
||||
document.cookie = name + "=" + value + "; expires=" + expdate.toGMTString() + "; path=/" + ";SameSite=Lax";
|
||||
}
|
||||
|
||||
function getCookie(name)
|
||||
{
|
||||
if (document.cookie.length > 0)
|
||||
{
|
||||
start = document.cookie.indexOf(name + "=")
|
||||
if (start != -1)
|
||||
{
|
||||
start = start + name.length + 1
|
||||
end = document.cookie.indexOf(";", start)
|
||||
if (end == -1) end = document.cookie.length
|
||||
return unescape(document.cookie.substring(start, end))
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
var i18nLanguage = "zh";
|
||||
|
||||
$(document).ready(function() {
|
||||
if (getCookie('userLanguage')) {
|
||||
i18nLanguage = getCookie('userLanguage');
|
||||
if (i18nLanguage == "zh") {
|
||||
no = 0;
|
||||
}else if (i18nLanguage == "en") {
|
||||
no = 1;
|
||||
}
|
||||
$("#selectLanguage").each(function(){
|
||||
$(this).find("option").eq(no).prop("selected",true)
|
||||
});
|
||||
}
|
||||
|
||||
$("[i18n]").i18n({
|
||||
defaultLang: i18nLanguage,
|
||||
filePath: "/share/i18n/",
|
||||
filePrefix: "i18n_",
|
||||
fileSuffix: "",
|
||||
forever: true,
|
||||
callback: function() {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
$("#selectLanguage").change(function() {
|
||||
var selectOptionId = $(this).children("option:selected").attr("id");
|
||||
console.log(selectOptionId);
|
||||
$("[i18n]").i18n({
|
||||
defaultLang: selectOptionId,
|
||||
filePath: "/share/i18n/"
|
||||
});
|
||||
setCookie('userLanguage', selectOptionId)
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
2
kvmd_data/usr/share/kvmd/web/share/js/i18n/jquery-3.7.1.min.js
vendored
Normal file
1
kvmd_data/usr/share/kvmd/web/share/js/i18n/jquery.i18n.min.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
(function($){$.fn.extend({i18n:function(options){var defaults={lang:"",defaultLang:"",filePath:"/i18n/",filePrefix:"i18n_",fileSuffix:"",forever:true,callback:function(){}};function getCookie(name){var arr=document.cookie.split('; ');for(var i=0;i<arr.length;i++){var arr1=arr[i].split('=');if(arr1[0]==name){return arr1[1]}}return''};function setCookie(name,value,myDay){var oDate=new Date();oDate.setDate(oDate.getDate()+myDay);document.cookie=name+'='+value+'; expires='+oDate};var options=$.extend(defaults,options);if(getCookie('i18n_lang')!=""&&getCookie('i18n_lang')!="undefined"&&getCookie('i18n_lang')!=null){defaults.defaultLang=getCookie('i18n_lang')}else if(options.lang==""&&defaults.defaultLang==""){throw"defaultLang must not be null !"};if(options.lang!=null&&options.lang!=""){if(options.forever){setCookie('i18n_lang',options.lang)}else{$.removeCookie("i18n_lang")}}else{options.lang=defaults.defaultLang};var i=this;$.getJSON(options.filePath+options.filePrefix+options.lang+options.fileSuffix+".json",function(data){var i18nLang={};if(data!=null){i18nLang=data}$(i).each(function(i){var i18nOnly=$(this).attr("i18n-only");if($(this).val()!=null&&$(this).val()!=""){if(i18nOnly==null||i18nOnly==undefined||i18nOnly==""||i18nOnly=="value"){$(this).val(i18nLang[$(this).attr("i18n")])}}if($(this).html()!=null&&$(this).html()!=""){if(i18nOnly==null||i18nOnly==undefined||i18nOnly==""||i18nOnly=="html"){$(this).html(i18nLang[$(this).attr("i18n")])}}if($(this).attr('placeholder')!=null&&$(this).attr('placeholder')!=""){if(i18nOnly==null||i18nOnly==undefined||i18nOnly==""||i18nOnly=="placeholder"){$(this).attr('placeholder',i18nLang[$(this).attr("i18n")])}}});options.callback()})}})})(jQuery);
|
||||
118
kvmd_data/usr/share/kvmd/web/share/js/index/main.js
Normal file
@@ -0,0 +1,118 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# 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 {checkBrowser} from "../bb.js";
|
||||
import {wm, initWindowManager} from "../wm.js";
|
||||
|
||||
|
||||
export function main() {
|
||||
initWindowManager();
|
||||
|
||||
if (checkBrowser(null, null)) {
|
||||
__loadKvmdInfo();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function __loadKvmdInfo() {
|
||||
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 due an error.<br>See KVMD logs for details.");
|
||||
} else {
|
||||
apps = Object.values(info.extras).sort(function(a, b) {
|
||||
if (a.place < b.place) {
|
||||
return -1;
|
||||
} else if (a.place > b.place) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$("apps-box").innerHTML = "<ul id=\"apps\"></ul>";
|
||||
|
||||
// Don't use this option, it may be removed in any time
|
||||
let hide_kvm_button = (
|
||||
(info.meta !== null && info.meta.web && info.meta.web.hide_kvm_button)
|
||||
|| tools.config.getBool("index--hide-kvm-button", false)
|
||||
);
|
||||
if (!hide_kvm_button) {
|
||||
$("apps").innerHTML += __makeApp(null, "kvm", "share/svg/kvm.svg", "KVM");
|
||||
}
|
||||
|
||||
for (let app of apps) {
|
||||
if (app.place >= 0 && (app.enabled || app.started)) {
|
||||
$("apps").innerHTML += __makeApp(null, app.path, app.icon, app.name);
|
||||
}
|
||||
}
|
||||
|
||||
if (info.auth.enabled) {
|
||||
$("apps").innerHTML += __makeApp("logout-button", "#", "share/svg/logout.svg", "Logout");
|
||||
tools.el.setOnClick($("logout-button"), __logout);
|
||||
}
|
||||
|
||||
if (info.meta !== null && info.meta.server && info.meta.server.host) {
|
||||
$("kvmd-meta-server-host").innerHTML = info.meta.server.host;
|
||||
document.title = `PiKVM Index: ${info.meta.server.host}`;
|
||||
} else {
|
||||
$("kvmd-meta-server-host").innerHTML = "";
|
||||
document.title = "PiKVM Index";
|
||||
}
|
||||
} else if (http.status === 401 || http.status === 403) {
|
||||
document.location.href = "/login";
|
||||
} else {
|
||||
setTimeout(__loadKvmdInfo, 1000);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function __makeApp(id, path, icon, name) {
|
||||
return `<li>
|
||||
<div ${id ? "id=\"" + id + "\"" : ""} class="app">
|
||||
<a href="${path}">
|
||||
<div>
|
||||
<img class="svg-gray" src="${icon}">
|
||||
${tools.escape(name)}
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</li>`;
|
||||
}
|
||||
|
||||
function __logout() {
|
||||
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", http.responseText);
|
||||
}
|
||||
});
|
||||
}
|
||||
60
kvmd_data/usr/share/kvmd/web/share/js/ipmi/main.js
Normal file
@@ -0,0 +1,60 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# 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";
|
||||
|
||||
|
||||
export function main() {
|
||||
__loadKvmdInfo();
|
||||
}
|
||||
|
||||
function __loadKvmdInfo() {
|
||||
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) => `
|
||||
<span class="code-comment"># ${comment}:<br>$</span>
|
||||
ipmitool -I lanplus -U admin -P admin -H ${window.location.hostname} -p ${ipmi_port} ${ipmi}<br>
|
||||
<span class="code-comment">$</span> curl -XPOST -HX-KVMD-User:admin -HX-KVMD-Passwd:admin -k \\<br>
|
||||
${window.location.protocol}//${window.location.host}/api/atx${api}<br>
|
||||
`;
|
||||
$("ipmi-text").innerHTML = `
|
||||
${make_item("Power on the server if it's off", "power on", "/power?action=on")}
|
||||
<br>
|
||||
${make_item("Soft power off the server if it's on", "power soft", "/power?action=off")}
|
||||
<br>
|
||||
${make_item("Hard power off the server if it's on", "power off", "/power?action=off_hard")}
|
||||
<br>
|
||||
${make_item("Hard reset the server if it's on", "power reset", "/power?action=reset_hard")}
|
||||
<br>
|
||||
${make_item("Check the power status", "power status", "")}
|
||||
`;
|
||||
} else if (http.status === 401 || http.status === 403) {
|
||||
document.location.href = "/login";
|
||||
} else {
|
||||
setTimeout(__loadKvmdInfo, 1000);
|
||||
}
|
||||
});
|
||||
}
|
||||
251
kvmd_data/usr/share/kvmd/web/share/js/keypad.js
Normal file
@@ -0,0 +1,251 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# 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";
|
||||
|
||||
|
||||
export function Keypad(__keys_parent, __sendKey, __apply_fixes) {
|
||||
var self = this;
|
||||
|
||||
/************************************************************************/
|
||||
|
||||
var __merged = {};
|
||||
var __keys = {};
|
||||
var __modifiers = {};
|
||||
|
||||
var __fix_mac_cmd = false;
|
||||
var __fix_win_altgr = false;
|
||||
var __altgr_ctrl_timer = null;
|
||||
|
||||
var __init__ = function() {
|
||||
if (__apply_fixes) {
|
||||
__fix_mac_cmd = tools.browser.is_mac;
|
||||
if (__fix_mac_cmd) {
|
||||
tools.info(`Keymap at ${__keys_parent}: enabled Fix-Mac-CMD`);
|
||||
}
|
||||
__fix_win_altgr = tools.browser.is_win;
|
||||
if (__fix_win_altgr) {
|
||||
tools.info(`Keymap at ${__keys_parent}: enabled Fix-Win-AltGr`);
|
||||
}
|
||||
}
|
||||
|
||||
for (let el_key of $$$(`${__keys_parent} div.key`)) {
|
||||
let code = el_key.getAttribute("data-code");
|
||||
|
||||
tools.setDefault(__keys, code, []);
|
||||
__keys[code].push(el_key);
|
||||
|
||||
tools.setDefault(__merged, code, []);
|
||||
__merged[code].push(el_key);
|
||||
|
||||
tools.el.setOnDown(el_key, () => __clickHandler(el_key, true));
|
||||
tools.el.setOnUp(el_key, () => __clickHandler(el_key, false));
|
||||
el_key.onmouseout = function() {
|
||||
if (__isPressed(el_key)) {
|
||||
__clickHandler(el_key, false);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
for (let el_key of $$$(`${__keys_parent} div.modifier`)) {
|
||||
let code = el_key.getAttribute("data-code");
|
||||
|
||||
tools.setDefault(__modifiers, code, []);
|
||||
__modifiers[code].push(el_key);
|
||||
|
||||
tools.setDefault(__merged, code, []);
|
||||
__merged[code].push(el_key);
|
||||
|
||||
tools.el.setOnDown(el_key, () => __toggleModifierHandler(el_key));
|
||||
}
|
||||
};
|
||||
|
||||
/************************************************************************/
|
||||
|
||||
self.releaseAll = function() {
|
||||
for (let dict of [__keys, __modifiers]) {
|
||||
for (let code in dict) {
|
||||
if (__isActive(dict[code][0])) {
|
||||
self.emitByCode(code, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
self.emitByKeyEvent = function(event, state) {
|
||||
if (event.repeat) {
|
||||
return;
|
||||
}
|
||||
|
||||
let code = event.code;
|
||||
if (__apply_fixes) {
|
||||
// https://github.com/pikvm/pikvm/issues/819
|
||||
if (code == "IntlBackslash" && ["`", "~"].includes(event.key)) {
|
||||
code = "Backquote";
|
||||
} else if (code == "Backquote" && ["§", "±"].includes(event.key)) {
|
||||
code = "IntlBackslash";
|
||||
}
|
||||
}
|
||||
|
||||
self.emitByCode(code, state);
|
||||
};
|
||||
|
||||
self.emitByCode = function(code, state, apply_fixes=true) {
|
||||
if (code in __merged) {
|
||||
if (__fix_win_altgr && apply_fixes) {
|
||||
if (!__fixWinAltgr(code, state)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (__fix_mac_cmd && apply_fixes) {
|
||||
__fixMacCmd(code, state);
|
||||
}
|
||||
__commonHandler(__merged[code][0], state, false);
|
||||
__unholdModifiers();
|
||||
}
|
||||
};
|
||||
|
||||
var __fixMacCmd = function(code, state) {
|
||||
if ((code == "MetaLeft" || code == "MetaRight") && !state) {
|
||||
for (code in __keys) {
|
||||
if (__isActive(__keys[code][0])) {
|
||||
self.emitByCode(code, false, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var __fixWinAltgr = function(code, state) {
|
||||
// https://github.com/pikvm/pikvm/issues/375
|
||||
// https://github.com/novnc/noVNC/blob/84f102d6/core/input/keyboard.js
|
||||
if (state) {
|
||||
if (__altgr_ctrl_timer) {
|
||||
clearTimeout(__altgr_ctrl_timer);
|
||||
__altgr_ctrl_timer = null;
|
||||
if (code !== "AltRight") {
|
||||
self.emitByCode("ControlLeft", true, false);
|
||||
}
|
||||
}
|
||||
if (code === "ControlLeft" && !__isActive(__modifiers["ControlLeft"][0])) {
|
||||
__altgr_ctrl_timer = setTimeout(function() {
|
||||
__altgr_ctrl_timer = null;
|
||||
self.emitByCode("ControlLeft", true, false);
|
||||
}, 50);
|
||||
return false; // Stop handling
|
||||
}
|
||||
} else {
|
||||
if (__altgr_ctrl_timer) {
|
||||
clearTimeout(__altgr_ctrl_timer);
|
||||
__altgr_ctrl_timer = null;
|
||||
self.emitByCode("ControlLeft", true, false);
|
||||
}
|
||||
}
|
||||
return true; // Continue handling
|
||||
};
|
||||
|
||||
var __clickHandler = function(el_key, state) {
|
||||
__commonHandler(el_key, state, false);
|
||||
__unholdModifiers();
|
||||
};
|
||||
|
||||
var __toggleModifierHandler = function(el_key) {
|
||||
__commonHandler(el_key, !__isActive(el_key), true);
|
||||
};
|
||||
|
||||
var __commonHandler = function(el_key, state, hold) {
|
||||
if (state && !__isActive(el_key)) {
|
||||
__deactivate(el_key);
|
||||
__activate(el_key, (hold ? "holded" : "pressed"));
|
||||
__process(el_key, true);
|
||||
} else {
|
||||
__deactivate(el_key);
|
||||
__process(el_key, false);
|
||||
}
|
||||
};
|
||||
|
||||
var __unholdModifiers = function() {
|
||||
for (let code in __modifiers) {
|
||||
let el_key = __modifiers[code][0];
|
||||
if (__isHolded(el_key)) {
|
||||
__deactivate(el_key);
|
||||
__process(el_key, false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var __isPressed = function(el_key) {
|
||||
let is_pressed = false;
|
||||
let el_keys = __resolveKeys(el_key);
|
||||
for (el_key of el_keys) {
|
||||
is_pressed = (is_pressed || el_key.classList.contains("pressed"));
|
||||
}
|
||||
return is_pressed;
|
||||
};
|
||||
|
||||
var __isHolded = function(el_key) {
|
||||
let is_holded = false;
|
||||
let el_keys = __resolveKeys(el_key);
|
||||
for (el_key of el_keys) {
|
||||
is_holded = (is_holded || el_key.classList.contains("holded"));
|
||||
}
|
||||
return is_holded;
|
||||
};
|
||||
|
||||
var __isActive = function(el_key) {
|
||||
let is_active = false;
|
||||
let el_keys = __resolveKeys(el_key);
|
||||
for (el_key of el_keys) {
|
||||
is_active = (is_active || el_key.classList.contains("pressed") || el_key.classList.contains("holded"));
|
||||
}
|
||||
return is_active;
|
||||
};
|
||||
|
||||
var __activate = function(el_key, cls) {
|
||||
let el_keys = __resolveKeys(el_key);
|
||||
for (el_key of el_keys) {
|
||||
el_key.classList.add(cls);
|
||||
}
|
||||
};
|
||||
|
||||
var __deactivate = function(el_key) {
|
||||
let el_keys = __resolveKeys(el_key);
|
||||
for (el_key of el_keys) {
|
||||
el_key.classList.remove("pressed");
|
||||
el_key.classList.remove("holded");
|
||||
}
|
||||
};
|
||||
|
||||
var __resolveKeys = function(el_key) {
|
||||
let code = el_key.getAttribute("data-code");
|
||||
return __merged[code];
|
||||
};
|
||||
|
||||
var __process = function(el_key, state) {
|
||||
let code = el_key.getAttribute("data-code");
|
||||
__sendKey(code, state);
|
||||
};
|
||||
|
||||
__init__();
|
||||
}
|
||||
3364
kvmd_data/usr/share/kvmd/web/share/js/kvm/adapter.js
Normal file
116
kvmd_data/usr/share/kvmd/web/share/js/kvm/atx.js
Normal file
@@ -0,0 +1,116 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# 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 Atx(__recorder) {
|
||||
var self = this;
|
||||
|
||||
/************************************************************************/
|
||||
|
||||
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);
|
||||
|
||||
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) {
|
||||
if (state) {
|
||||
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 {
|
||||
__state = null;
|
||||
__updateLeds(false, false, false);
|
||||
__updateButtons(false);
|
||||
}
|
||||
};
|
||||
|
||||
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) {
|
||||
if (http.status === 409) {
|
||||
wm.error("Performing another ATX operation for other client.<br>Please try again later.");
|
||||
} else if (http.status !== 200) {
|
||||
wm.error("Click error", http.responseText);
|
||||
}
|
||||
});
|
||||
__recorder.recordAtxButtonEvent(button);
|
||||
};
|
||||
|
||||
if ($("atx-ask-switch").checked) {
|
||||
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();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
click_button();
|
||||
}
|
||||
};
|
||||
|
||||
__init__();
|
||||
}
|
||||
246
kvmd_data/usr/share/kvmd/web/share/js/kvm/gpio.js
Normal file
@@ -0,0 +1,246 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# 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 Gpio(__recorder) {
|
||||
var self = this;
|
||||
|
||||
/************************************************************************/
|
||||
|
||||
var __has_model = false;
|
||||
|
||||
/************************************************************************/
|
||||
|
||||
self.setState = function(state) {
|
||||
if (state) {
|
||||
if (state.model !== undefined) {
|
||||
__has_model = true;
|
||||
__updateModel(state.model);
|
||||
}
|
||||
if (__has_model && state.state !== undefined) {
|
||||
if (state.state.inputs !== undefined) {
|
||||
__updateInputs(state.state.inputs);
|
||||
}
|
||||
if (state.state.outputs !== undefined) {
|
||||
__updateOutputs(state.state.outputs);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
__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)) {
|
||||
tools.el.setEnabled(el, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
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 = [];
|
||||
let last_is_label = false;
|
||||
for (let item of model.view.header.title) {
|
||||
if (last_is_label && item.type === "label") {
|
||||
title.push("<span></span>");
|
||||
}
|
||||
last_is_label = (item.type === "label");
|
||||
title.push(__createItem(item));
|
||||
}
|
||||
$("gpio-menu-button").innerHTML = title.join(" ");
|
||||
}
|
||||
|
||||
let html = "<table class=\"kv\">";
|
||||
for (let row of model.view.table) {
|
||||
if (row === null) {
|
||||
html += "</table><hr><table class=\"kv\">";
|
||||
} else {
|
||||
html += "<tr>";
|
||||
for (let item of row) {
|
||||
if (item.type === "output") {
|
||||
item.scheme = model.scheme.outputs[item.channel];
|
||||
}
|
||||
html += `<td align="center">${__createItem(item)}</td>`;
|
||||
}
|
||||
html += "</tr>";
|
||||
}
|
||||
}
|
||||
html += "</table>";
|
||||
$("gpio-menu").innerHTML = html;
|
||||
|
||||
for (let ch in model.scheme.outputs) {
|
||||
for (let el of $$(`__gpio-switch-${ch}`)) {
|
||||
tools.el.setOnClick(el, tools.partial(__switchChannel, el));
|
||||
}
|
||||
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));
|
||||
};
|
||||
|
||||
var __createItem = function(item) {
|
||||
if (item.type === "label") {
|
||||
return item.text;
|
||||
} else if (item.type === "input") {
|
||||
return `
|
||||
<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-${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>
|
||||
</div></td>
|
||||
`);
|
||||
}
|
||||
if (item.scheme.pulse.delay) {
|
||||
controls.push(`
|
||||
<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>
|
||||
`);
|
||||
}
|
||||
return `<table><tr>${controls.join("<td> </td>")}</tr></table>`;
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
var __setLedState = function(el, on) {
|
||||
let color = el.getAttribute("data-color");
|
||||
if (on) {
|
||||
el.classList.add(`led-${color}`);
|
||||
el.classList.remove("led-gray");
|
||||
} else {
|
||||
el.classList.add("led-gray");
|
||||
el.classList.remove(`led-${color}`);
|
||||
}
|
||||
};
|
||||
|
||||
var __switchChannel = function(el) {
|
||||
let ch = el.getAttribute("data-channel");
|
||||
let confirm = el.getAttribute("data-confirm");
|
||||
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": ch, "state": to});
|
||||
__recorder.recordGpioSwitchEvent(ch, to);
|
||||
};
|
||||
if (confirm) {
|
||||
wm.confirm(tools.escape(confirm)).then(function(ok) {
|
||||
if (ok) {
|
||||
act();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
act();
|
||||
}
|
||||
};
|
||||
|
||||
var __pulseChannel = function(el) {
|
||||
let ch = el.getAttribute("data-channel");
|
||||
let confirm = el.getAttribute("data-confirm");
|
||||
let act = () => {
|
||||
__sendPost("/api/gpio/pulse", {"channel": ch});
|
||||
__recorder.recordGpioPulseEvent(ch);
|
||||
};
|
||||
if (confirm) {
|
||||
wm.confirm(tools.escape(confirm)).then(function(ok) {
|
||||
if (ok) {
|
||||
act();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
act();
|
||||
}
|
||||
};
|
||||
|
||||
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.");
|
||||
} else if (http.status !== 200) {
|
||||
wm.error("GPIO error", http.responseText);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
316
kvmd_data/usr/share/kvmd/web/share/js/kvm/hid.js
Normal file
@@ -0,0 +1,316 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# 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";
|
||||
|
||||
import {Keyboard} from "./keyboard.js";
|
||||
import {Mouse} from "./mouse.js";
|
||||
|
||||
|
||||
export function Hid(__getGeometry, __recorder) {
|
||||
var self = this;
|
||||
|
||||
/************************************************************************/
|
||||
|
||||
var __state = null;
|
||||
var __keyboard = null;
|
||||
var __mouse = null;
|
||||
|
||||
var __init__ = function() {
|
||||
__keyboard = new Keyboard(__recorder.recordWsEvent);
|
||||
__mouse = new Mouse(__getGeometry, __recorder.recordWsEvent);
|
||||
|
||||
let hidden_attr = null;
|
||||
let visibility_change_attr = null;
|
||||
|
||||
if (typeof document.hidden !== "undefined") {
|
||||
hidden_attr = "hidden";
|
||||
visibility_change_attr = "visibilitychange";
|
||||
} else if (typeof document.webkitHidden !== "undefined") {
|
||||
hidden_attr = "webkitHidden";
|
||||
visibility_change_attr = "webkitvisibilitychange";
|
||||
} else if (typeof document.mozHidden !== "undefined") {
|
||||
hidden_attr = "mozHidden";
|
||||
visibility_change_attr = "mozvisibilitychange";
|
||||
}
|
||||
|
||||
if (visibility_change_attr) {
|
||||
document.addEventListener(
|
||||
visibility_change_attr,
|
||||
function() {
|
||||
if (document[hidden_attr]) {
|
||||
__releaseAll();
|
||||
}
|
||||
},
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
window.addEventListener("pagehide", __releaseAll);
|
||||
window.addEventListener("blur", __releaseAll);
|
||||
|
||||
tools.el.setOnClick($("hid-connect-switch"), __clickConnectSwitch);
|
||||
tools.el.setOnClick($("hid-reset-button"), __clickResetButton);
|
||||
|
||||
for (let el_shortcut of $$$("[data-shortcut]")) {
|
||||
tools.el.setOnClick(el_shortcut, function() {
|
||||
let ask = false;
|
||||
let confirm_id = el_shortcut.getAttribute("data-shortcut-confirm");
|
||||
if (confirm_id) {
|
||||
ask = $(confirm_id).checked;
|
||||
}
|
||||
let codes = el_shortcut.getAttribute("data-shortcut").split(" ");
|
||||
if (ask) {
|
||||
wm.confirm("Do you want to press this hotkey?", codes.join(" + ")).then(function(ok) {
|
||||
if (ok) {
|
||||
__emitShortcut(codes);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
__emitShortcut(codes);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
tools.storage.bindSimpleSwitch($("hid-sysrq-ask-switch"), "hid.sysrq.ask", true);
|
||||
|
||||
tools.el.setOnClick($("hid-jiggler-switch"), __clickJigglerSwitch);
|
||||
};
|
||||
|
||||
/************************************************************************/
|
||||
|
||||
self.setSocket = function(ws) {
|
||||
if (!ws) {
|
||||
self.setState(null);
|
||||
}
|
||||
__keyboard.setSocket(ws);
|
||||
__mouse.setSocket(ws);
|
||||
};
|
||||
|
||||
self.setState = function(state) {
|
||||
if (state) {
|
||||
if (!__state) {
|
||||
__state = {"keyboard": {}, "mouse": {}};
|
||||
}
|
||||
if (state.enabled !== undefined) {
|
||||
__state.enabled = state.enabled; // Currently unused, always true
|
||||
}
|
||||
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);
|
||||
};
|
||||
|
||||
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() {
|
||||
__keyboard.releaseAll();
|
||||
__mouse.releaseAll();
|
||||
};
|
||||
|
||||
var __emitShortcut = function(codes) {
|
||||
return new Promise(function(resolve) {
|
||||
tools.debug("HID: emitting keys:", codes);
|
||||
|
||||
let raw_events = [];
|
||||
[[codes, true], [codes.slice().reverse(), false]].forEach(function(op) {
|
||||
let [op_codes, state] = op;
|
||||
for (let code of op_codes) {
|
||||
raw_events.push({"code": code, "state": state});
|
||||
}
|
||||
});
|
||||
|
||||
let index = 0;
|
||||
let iterate = () => setTimeout(function() {
|
||||
__keyboard.emit(raw_events[index].code, raw_events[index].state);
|
||||
++index;
|
||||
if (index < raw_events.length) {
|
||||
iterate();
|
||||
} else {
|
||||
resolve(null);
|
||||
}
|
||||
}, 100);
|
||||
iterate();
|
||||
});
|
||||
};
|
||||
|
||||
var __clickOutputsRadio = function(hid) {
|
||||
let output = tools.radio.getValue(`hid-outputs-${hid}-radio`);
|
||||
tools.httpPost("/api/hid/set_params", {[`${hid}_output`]: output}, function(http) {
|
||||
if (http.status !== 200) {
|
||||
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) {
|
||||
if (http.status !== 200) {
|
||||
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) {
|
||||
if (http.status !== 200) {
|
||||
wm.error(`Can't ${connected ? "connect" : "disconnect"} HID`, http.responseText);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var __clickResetButton = function() {
|
||||
wm.confirm("Are you sure you want to reset HID (keyboard & mouse)?").then(function(ok) {
|
||||
if (ok) {
|
||||
tools.httpPost("/api/hid/reset", null, function(http) {
|
||||
if (http.status !== 200) {
|
||||
wm.error("HID reset error", http.responseText);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
__init__();
|
||||
}
|
||||
3651
kvmd_data/usr/share/kvmd/web/share/js/kvm/janus.js
Normal file
152
kvmd_data/usr/share/kvmd/web/share/js/kvm/keyboard.js
Normal file
@@ -0,0 +1,152 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# 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/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
import {tools, $, $$$} from "../tools.js";
|
||||
import {Keypad} from "../keypad.js";
|
||||
|
||||
|
||||
export function Keyboard(__recordWsEvent) {
|
||||
var self = this;
|
||||
|
||||
/************************************************************************/
|
||||
|
||||
var __ws = null;
|
||||
var __online = true;
|
||||
|
||||
var __keypad = null;
|
||||
|
||||
var __init__ = function() {
|
||||
__keypad = new Keypad("div#keyboard-window", __sendKey, true);
|
||||
|
||||
$("hid-keyboard-led").title = "Keyboard free";
|
||||
|
||||
$("keyboard-window").onkeydown = (event) => __keyboardHandler(event, true);
|
||||
$("keyboard-window").onkeyup = (event) => __keyboardHandler(event, false);
|
||||
$("keyboard-window").onfocus = __updateOnlineLeds;
|
||||
$("keyboard-window").onblur = __updateOnlineLeds;
|
||||
|
||||
$("stream-window").onkeydown = (event) => __keyboardHandler(event, true);
|
||||
$("stream-window").onkeyup = (event) => __keyboardHandler(event, false);
|
||||
$("stream-window").onfocus = __updateOnlineLeds;
|
||||
$("stream-window").onblur = __updateOnlineLeds;
|
||||
|
||||
window.addEventListener("focusin", __updateOnlineLeds);
|
||||
window.addEventListener("focusout", __updateOnlineLeds);
|
||||
|
||||
tools.storage.bindSimpleSwitch($("hid-keyboard-swap-cc-switch"), "hid.keyboard.swap_cc", false);
|
||||
};
|
||||
|
||||
/************************************************************************/
|
||||
|
||||
self.setSocket = function(ws) {
|
||||
if (ws !== __ws) {
|
||||
self.releaseAll();
|
||||
__ws = ws;
|
||||
}
|
||||
__updateOnlineLeds();
|
||||
};
|
||||
|
||||
self.setState = function(online, leds, hid_online, hid_busy) {
|
||||
if (!hid_online) {
|
||||
__online = null;
|
||||
} else {
|
||||
__online = (online && !hid_busy);
|
||||
}
|
||||
__updateOnlineLeds();
|
||||
|
||||
for (let led of ["caps", "scroll", "num"]) {
|
||||
for (let el of $$$(`.hid-keyboard-${led}-led`)) {
|
||||
if (leds[led]) {
|
||||
el.classList.add("led-green");
|
||||
el.classList.remove("led-gray");
|
||||
} else {
|
||||
el.classList.add("led-gray");
|
||||
el.classList.remove("led-green");
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
self.releaseAll = function() {
|
||||
__keypad.releaseAll();
|
||||
};
|
||||
|
||||
self.emit = function(code, state) {
|
||||
__keypad.emitByCode(code, state);
|
||||
};
|
||||
|
||||
var __updateOnlineLeds = function() {
|
||||
let is_captured = (
|
||||
$("stream-window").classList.contains("window-active")
|
||||
|| $("keyboard-window").classList.contains("window-active")
|
||||
);
|
||||
let led = "led-gray";
|
||||
let title = "Keyboard free";
|
||||
|
||||
if (__ws) {
|
||||
if (__online === null) {
|
||||
led = "led-red";
|
||||
title = (is_captured ? "Keyboard captured, HID offline" : "Keyboard free, HID offline");
|
||||
} else if (__online) {
|
||||
if (is_captured) {
|
||||
led = "led-green";
|
||||
title = "Keyboard captured";
|
||||
}
|
||||
} else {
|
||||
led = "led-yellow";
|
||||
title = (is_captured ? "Keyboard captured, inactive/busy" : "Keyboard free, inactive/busy");
|
||||
}
|
||||
} else {
|
||||
if (is_captured) {
|
||||
title = "Keyboard captured, PiKVM offline";
|
||||
}
|
||||
}
|
||||
$("hid-keyboard-led").className = led;
|
||||
$("hid-keyboard-led").title = title;
|
||||
};
|
||||
|
||||
var __keyboardHandler = function(event, state) {
|
||||
event.preventDefault();
|
||||
__keypad.emitByKeyEvent(event, state);
|
||||
};
|
||||
|
||||
var __sendKey = function(code, state) {
|
||||
tools.debug("Keyboard: key", (state ? "pressed:" : "released:"), code);
|
||||
if ($("hid-keyboard-swap-cc-switch").checked) {
|
||||
if (code === "ControlLeft") {
|
||||
code = "CapsLock";
|
||||
} else if (code === "CapsLock") {
|
||||
code = "ControlLeft";
|
||||
}
|
||||
}
|
||||
let event = {
|
||||
"event_type": "key",
|
||||
"event": {"key": code, "state": state},
|
||||
};
|
||||
if (__ws && !$("hid-mute-switch").checked) {
|
||||
__ws.sendHidEvent(event);
|
||||
}
|
||||
__recordWsEvent(event);
|
||||
};
|
||||
|
||||
__init__();
|
||||
}
|
||||
65
kvmd_data/usr/share/kvmd/web/share/js/kvm/main.js
Normal file
@@ -0,0 +1,65 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# 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 {checkBrowser} from "../bb.js";
|
||||
import {wm, initWindowManager} from "../wm.js";
|
||||
|
||||
import {Session} from "./session.js";
|
||||
|
||||
|
||||
export function main() {
|
||||
if (checkBrowser(null, "/share/css/kvm/x-mobile.css")) {
|
||||
tools.storage.bindSimpleSwitch($("page-close-ask-switch"), "page.close.ask", true, function(value) {
|
||||
if (value) {
|
||||
window.onbeforeunload = function(event) {
|
||||
let text = "Are you sure you want to close PiKVM session?";
|
||||
if (event) {
|
||||
event.returnValue = text;
|
||||
}
|
||||
return text;
|
||||
};
|
||||
} else {
|
||||
window.onbeforeunload = null;
|
||||
}
|
||||
});
|
||||
|
||||
initWindowManager();
|
||||
|
||||
tools.el.setOnClick($("open-log-button"), () => window.open("/api/log?seek=3600&follow=1", "_blank"));
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
369
kvmd_data/usr/share/kvmd/web/share/js/kvm/mouse.js
Normal file
@@ -0,0 +1,369 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# 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 {Keypad} from "../keypad.js";
|
||||
|
||||
|
||||
export function Mouse(__getGeometry, __recordWsEvent) {
|
||||
var self = this;
|
||||
|
||||
/************************************************************************/
|
||||
|
||||
var __ws = null;
|
||||
var __online = true;
|
||||
var __absolute = true;
|
||||
|
||||
var __keypad = null;
|
||||
|
||||
var __timer = null;
|
||||
var __planned_pos = {"x": 0, "y": 0};
|
||||
var __sent_pos = {"x": 0, "y": 0};
|
||||
var __relative_deltas = [];
|
||||
var __relative_touch_pos = null;
|
||||
var __relative_sens = 1.0;
|
||||
var __scroll_rate = 5;
|
||||
var __scroll_delta = {"x": 0, "y": 0};
|
||||
|
||||
var __stream_hovered = false;
|
||||
|
||||
var __init__ = function() {
|
||||
__keypad = new Keypad("div#stream-mouse-buttons", __sendButton, false);
|
||||
|
||||
$("hid-mouse-led").title = "Mouse free";
|
||||
|
||||
document.onpointerlockchange = __relativeCapturedHandler; // Only for relative
|
||||
document.onpointerlockerror = __relativeCapturedHandler;
|
||||
$("stream-box").onmouseenter = () => __streamHoveredHandler(true);
|
||||
$("stream-box").onmouseleave = () => __streamHoveredHandler(false);
|
||||
$("stream-box").onmousedown = (event) => __streamButtonHandler(event, true);
|
||||
$("stream-box").onmouseup = (event) => __streamButtonHandler(event, false);
|
||||
$("stream-box").oncontextmenu = (event) => event.preventDefault();
|
||||
$("stream-box").onmousemove = __streamMoveHandler;
|
||||
$("stream-box").onwheel = __streamScrollHandler;
|
||||
$("stream-box").ontouchstart = (event) => __streamTouchStartHandler(event);
|
||||
$("stream-box").ontouchmove = (event) => __streamTouchMoveHandler(event);
|
||||
$("stream-box").ontouchend = (event) => __streamTouchEndHandler(event);
|
||||
|
||||
tools.storage.bindSimpleSwitch($("hid-mouse-squash-switch"), "hid.mouse.squash", true);
|
||||
tools.slider.setParams($("hid-mouse-sens-slider"), 0.1, 1.9, 0.1, tools.storage.get("hid.mouse.sens", 1.0), __updateRelativeSens);
|
||||
tools.slider.setParams($("hid-mouse-rate-slider"), 10, 100, 10, tools.storage.get("hid.mouse.rate", 10), __updateRate); // set __timer
|
||||
|
||||
tools.storage.bindSimpleSwitch($("hid-mouse-reverse-scrolling-switch"), "hid.mouse.reverse_scrolling", false);
|
||||
tools.storage.bindSimpleSwitch($("hid-mouse-reverse-panning-switch"), "hid.mouse.reverse_panning", false);
|
||||
let cumulative_scrolling = !(tools.browser.is_firefox && !tools.browser.is_mac);
|
||||
tools.storage.bindSimpleSwitch($("hid-mouse-cumulative-scrolling-switch"), "hid.mouse.cumulative_scrolling", cumulative_scrolling);
|
||||
tools.slider.setParams($("hid-mouse-scroll-slider"), 1, 25, 1, tools.storage.get("hid.mouse.scroll_rate", 5), __updateScrollRate);
|
||||
|
||||
tools.storage.bindSimpleSwitch($("hid-mouse-dot-switch"), "hid.mouse.dot", true, __updateOnlineLeds);
|
||||
};
|
||||
|
||||
/************************************************************************/
|
||||
|
||||
self.setSocket = function(ws) {
|
||||
__ws = ws;
|
||||
if (!__absolute && __isRelativeCaptured()) {
|
||||
document.exitPointerLock();
|
||||
}
|
||||
__updateOnlineLeds();
|
||||
};
|
||||
|
||||
self.setState = function(online, absolute, hid_online, hid_busy) {
|
||||
if (!hid_online) {
|
||||
__online = null;
|
||||
} else {
|
||||
__online = (online && !hid_busy);
|
||||
}
|
||||
if (!__absolute && absolute && __isRelativeCaptured()) {
|
||||
document.exitPointerLock();
|
||||
}
|
||||
if (__absolute && !absolute) {
|
||||
__relative_deltas = [];
|
||||
__relative_touch_pos = null;
|
||||
}
|
||||
__absolute = absolute;
|
||||
__updateOnlineLeds();
|
||||
};
|
||||
|
||||
self.releaseAll = function() {
|
||||
__keypad.releaseAll();
|
||||
};
|
||||
|
||||
var __updateRate = function(value) {
|
||||
$("hid-mouse-rate-value").innerHTML = value + " ms";
|
||||
tools.storage.set("hid.mouse.rate", value);
|
||||
if (__timer) {
|
||||
clearInterval(__timer);
|
||||
}
|
||||
__timer = setInterval(__sendPlannedMove, value);
|
||||
};
|
||||
|
||||
var __updateScrollRate = function(value) {
|
||||
$("hid-mouse-scroll-value").innerHTML = value;
|
||||
tools.storage.set("hid.mouse.scroll_rate", value);
|
||||
__scroll_rate = value;
|
||||
};
|
||||
|
||||
var __updateRelativeSens = function(value) {
|
||||
$("hid-mouse-sens-value").innerHTML = value.toFixed(1);
|
||||
tools.storage.set("hid.mouse.sens", value);
|
||||
__relative_sens = value;
|
||||
};
|
||||
|
||||
var __streamHoveredHandler = function(hovered) {
|
||||
if (__absolute) {
|
||||
__stream_hovered = hovered;
|
||||
__updateOnlineLeds();
|
||||
}
|
||||
};
|
||||
|
||||
var __updateOnlineLeds = function() {
|
||||
let is_captured;
|
||||
if (__absolute) {
|
||||
is_captured = (__stream_hovered || tools.browser.is_mobile);
|
||||
} else {
|
||||
is_captured = __isRelativeCaptured();
|
||||
}
|
||||
let led = "led-gray";
|
||||
let title = "Mouse free";
|
||||
|
||||
if (__ws) {
|
||||
if (__online === null) {
|
||||
led = "led-red";
|
||||
title = (is_captured ? "Mouse captured, HID offline" : "Mouse free, HID offline");
|
||||
} else if (__online) {
|
||||
if (is_captured) {
|
||||
led = "led-green";
|
||||
title = "Mouse captured";
|
||||
}
|
||||
} else {
|
||||
led = "led-yellow";
|
||||
title = (is_captured ? "Mouse captured, inactive/busy" : "Mouse free, inactive/busy");
|
||||
}
|
||||
} else {
|
||||
if (is_captured) {
|
||||
title = "Mouse captured, PiKVM offline";
|
||||
}
|
||||
}
|
||||
$("hid-mouse-led").className = led;
|
||||
$("hid-mouse-led").title = title;
|
||||
|
||||
if (__absolute && is_captured) {
|
||||
let dot = $("hid-mouse-dot-switch").checked;
|
||||
$("stream-box").classList.toggle("stream-box-mouse-dot", (dot && __ws));
|
||||
$("stream-box").classList.toggle("stream-box-mouse-none", (!dot && __ws));
|
||||
} else {
|
||||
$("stream-box").classList.toggle("stream-box-mouse-dot", false);
|
||||
$("stream-box").classList.toggle("stream-box-mouse-none", false);
|
||||
}
|
||||
};
|
||||
|
||||
var __isRelativeCaptured = function() {
|
||||
return (document.pointerLockElement === $("stream-box"));
|
||||
};
|
||||
|
||||
var __relativeCapturedHandler = function() {
|
||||
tools.info("Relative mouse", (__isRelativeCaptured() ? "captured" : "released"), "by pointer lock");
|
||||
__updateOnlineLeds();
|
||||
};
|
||||
|
||||
var __streamButtonHandler = function(event, state) {
|
||||
// https://www.w3schools.com/jsref/event_button.asp
|
||||
event.preventDefault();
|
||||
if (__absolute || __isRelativeCaptured()) {
|
||||
switch (event.button) {
|
||||
case 0: __keypad.emitByCode("left", state); break;
|
||||
case 2: __keypad.emitByCode("right", state); break;
|
||||
case 1: __keypad.emitByCode("middle", state); break;
|
||||
case 3: __keypad.emitByCode("up", state); break;
|
||||
case 4: __keypad.emitByCode("down", state); break;
|
||||
}
|
||||
} else if (!__absolute && !__isRelativeCaptured() && !state) {
|
||||
$("stream-box").requestPointerLock();
|
||||
}
|
||||
};
|
||||
|
||||
var __streamTouchStartHandler = function(event) {
|
||||
event.preventDefault();
|
||||
if (event.touches.length === 1) {
|
||||
if (__absolute) {
|
||||
__planned_pos = __getTouchPosition(event, 0);
|
||||
__sendPlannedMove();
|
||||
} else {
|
||||
__relative_touch_pos = __getTouchPosition(event, 0);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var __streamTouchMoveHandler = function(event) {
|
||||
event.preventDefault();
|
||||
if (event.touches.length === 1) {
|
||||
if (__absolute) {
|
||||
__planned_pos = __getTouchPosition(event, 0);
|
||||
} else if (__relative_touch_pos === null) {
|
||||
__relative_touch_pos = __getTouchPosition(event, 0);
|
||||
} else {
|
||||
let pos = __getTouchPosition(event, 0);
|
||||
__sendOrPlanRelativeMove({
|
||||
"x": (pos.x - __relative_touch_pos.x),
|
||||
"y": (pos.y - __relative_touch_pos.y),
|
||||
});
|
||||
__relative_touch_pos = pos;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var __streamTouchEndHandler = function(event) {
|
||||
event.preventDefault();
|
||||
__sendPlannedMove();
|
||||
};
|
||||
|
||||
var __getTouchPosition = function(event, index) {
|
||||
if (event.touches[index].target && event.touches[index].target.getBoundingClientRect) {
|
||||
let rect = event.touches[index].target.getBoundingClientRect();
|
||||
return {
|
||||
"x": Math.round(event.touches[index].clientX - rect.left),
|
||||
"y": Math.round(event.touches[index].clientY - rect.top),
|
||||
};
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
var __streamMoveHandler = function(event) {
|
||||
if (__absolute) {
|
||||
let rect = event.target.getBoundingClientRect();
|
||||
__planned_pos = {
|
||||
"x": Math.max(Math.round(event.clientX - rect.left), 0),
|
||||
"y": Math.max(Math.round(event.clientY - rect.top), 0),
|
||||
};
|
||||
} else if (__isRelativeCaptured()) {
|
||||
__sendOrPlanRelativeMove({
|
||||
"x": event.movementX,
|
||||
"y": event.movementY,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
var __streamScrollHandler = function(event) {
|
||||
// https://learn.javascript.ru/mousewheel
|
||||
// https://stackoverflow.com/a/24595588
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
if (!__absolute && !__isRelativeCaptured()) {
|
||||
return;
|
||||
}
|
||||
|
||||
let delta = {"x": 0, "y": 0};
|
||||
if ($("hid-mouse-cumulative-scrolling-switch").checked) {
|
||||
let factor = (tools.browser.is_mac ? 5 : 1);
|
||||
|
||||
__scroll_delta.x += event.deltaX * factor; // Horizontal scrolling
|
||||
if (Math.abs(__scroll_delta.x) >= 100) {
|
||||
delta.x = __scroll_delta.x / Math.abs(__scroll_delta.x) * (-__scroll_rate);
|
||||
__scroll_delta.x = 0;
|
||||
}
|
||||
|
||||
__scroll_delta.y += event.deltaY * factor; // Vertical scrolling
|
||||
if (Math.abs(__scroll_delta.y) >= 100) {
|
||||
delta.y = __scroll_delta.y / Math.abs(__scroll_delta.y) * (-__scroll_rate);
|
||||
__scroll_delta.y = 0;
|
||||
}
|
||||
} else {
|
||||
if (event.deltaX !== 0) {
|
||||
delta.x = event.deltaX / Math.abs(event.deltaX) * (-__scroll_rate);
|
||||
}
|
||||
if (event.deltaY !== 0) {
|
||||
delta.y = event.deltaY / Math.abs(event.deltaY) * (-__scroll_rate);
|
||||
}
|
||||
}
|
||||
|
||||
__sendScroll(delta);
|
||||
};
|
||||
|
||||
var __sendOrPlanRelativeMove = function(delta) {
|
||||
delta = {
|
||||
"x": Math.min(Math.max(-127, Math.floor(delta.x * __relative_sens)), 127),
|
||||
"y": Math.min(Math.max(-127, Math.floor(delta.y * __relative_sens)), 127),
|
||||
};
|
||||
if (delta.x || delta.y) {
|
||||
if ($("hid-mouse-squash-switch").checked) {
|
||||
__relative_deltas.push(delta);
|
||||
} else {
|
||||
tools.debug("Mouse: relative:", delta);
|
||||
__sendEvent("mouse_relative", {"delta": delta});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var __sendScroll = function(delta) {
|
||||
if (delta.x || delta.y) {
|
||||
if ($("hid-mouse-reverse-scrolling-switch").checked) {
|
||||
delta.y *= -1;
|
||||
}
|
||||
if ($("hid-mouse-reverse-panning-switch").checked) {
|
||||
delta.x *= -1;
|
||||
}
|
||||
tools.debug("Mouse: scrolled:", delta);
|
||||
__sendEvent("mouse_wheel", {"delta": delta});
|
||||
}
|
||||
};
|
||||
|
||||
var __sendPlannedMove = function() {
|
||||
if (__absolute) {
|
||||
let pos = __planned_pos;
|
||||
if (pos.x !== __sent_pos.x || pos.y !== __sent_pos.y) {
|
||||
let geo = __getGeometry();
|
||||
let to = {
|
||||
"x": tools.remap(pos.x, geo.x, geo.width, -32768, 32767),
|
||||
"y": tools.remap(pos.y, geo.y, geo.height, -32768, 32767),
|
||||
};
|
||||
tools.debug("Mouse: moved:", to);
|
||||
__sendEvent("mouse_move", {"to": to});
|
||||
__sent_pos = pos;
|
||||
}
|
||||
} else if (__relative_deltas.length) {
|
||||
tools.debug("Mouse: relative:", __relative_deltas);
|
||||
__sendEvent("mouse_relative", {"delta": __relative_deltas, "squash": true});
|
||||
__relative_deltas = [];
|
||||
}
|
||||
};
|
||||
|
||||
var __sendButton = function(button, state) {
|
||||
tools.debug("Mouse: button", (state ? "pressed:" : "released:"), button);
|
||||
__sendPlannedMove();
|
||||
__sendEvent("mouse_button", {"button": button, "state": state});
|
||||
};
|
||||
|
||||
var __sendEvent = function(event_type, event) {
|
||||
event = {"event_type": event_type, "event": event};
|
||||
if (__ws && !$("hid-mute-switch").checked) {
|
||||
__ws.sendHidEvent(event);
|
||||
}
|
||||
__recordWsEvent(event);
|
||||
};
|
||||
|
||||
__init__();
|
||||
}
|
||||
469
kvmd_data/usr/share/kvmd/web/share/js/kvm/msd.js
Normal file
@@ -0,0 +1,469 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# 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 Msd() {
|
||||
var self = this;
|
||||
|
||||
/************************************************************************/
|
||||
|
||||
var __state = null;
|
||||
var __http = null;
|
||||
|
||||
var __init__ = function() {
|
||||
$("msd-led").title = "Unknown state";
|
||||
|
||||
tools.selector.addOption($("msd-image-selector"), "\u2500 Not selected \u2500", "");
|
||||
$("msd-image-selector").onchange = __selectImage;
|
||||
|
||||
tools.el.setOnClick($("msd-download-button"), __clickDownloadButton);
|
||||
tools.el.setOnClick($("msd-remove-button"), __clickRemoveButton);
|
||||
|
||||
tools.radio.setOnClick("msd-mode-radio", () => __sendParam("cdrom", tools.radio.getValue("msd-mode-radio")));
|
||||
tools.el.setOnClick($("msd-rw-switch"), () => __sendParam("rw", $("msd-rw-switch").checked));
|
||||
tools.radio.setOnClick("file-mode-radio", __refreshFileMode, false);
|
||||
|
||||
tools.el.setOnClick($("msd-select-new-button"), __toggleSelectSub);
|
||||
$("msd-new-file").onchange = __selectNewFile;
|
||||
$("msd-new-url").oninput = __selectNewUrl;
|
||||
$("msd-new-part-selector").onchange = __selectNewFile;
|
||||
|
||||
tools.el.setOnClick($("msd-upload-new-button"), __clickUploadNewButton);
|
||||
tools.el.setOnClick($("msd-abort-new-button"), __clickAbortNewButton);
|
||||
|
||||
tools.el.setOnClick($("msd-connect-button"), () => __clickConnectButton(true));
|
||||
tools.el.setOnClick($("msd-disconnect-button"), () => __clickConnectButton(false));
|
||||
|
||||
tools.el.setOnClick($("msd-file-image-update-button"), () => __clickMakeImageButton(true));
|
||||
tools.el.setOnClick($("msd-file-image-unzip-button"), () => __clickMakeImageButton(false));
|
||||
|
||||
tools.el.setOnClick($("msd-reset-button"), __clickResetButton);
|
||||
};
|
||||
|
||||
/************************************************************************/
|
||||
|
||||
self.setState = function(state) {
|
||||
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.storage.filespath !== undefined) {
|
||||
__state.storage.filespath = state.storage.filespath;
|
||||
}
|
||||
}
|
||||
if (state.drive || (state.storage && state.storage.images !== undefined)) {
|
||||
__updateImageSelector(__state.drive, __state.storage.images, __state.storage.filespath);
|
||||
}
|
||||
}
|
||||
} 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.radio.setEnabled("file-mode-radio", (o && !d.connected && !busy));
|
||||
|
||||
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;
|
||||
|
||||
if (tools.radio.getValue("file-mode-radio") == "0"){
|
||||
tools.el.setEnabled($("msd-connect-button"), false);
|
||||
tools.el.setEnabled($("msd-file-image-update-button"), false);
|
||||
tools.el.setEnabled($("msd-file-image-unzip-button"), false);
|
||||
} else {
|
||||
tools.el.setEnabled($("msd-connect-button"), (o && d.image && !d.connected && !busy));
|
||||
tools.el.setEnabled($("msd-file-image-update-button"), (o && !d.connected && !busy));
|
||||
tools.el.setEnabled($("msd-file-image-unzip-button"), (o && !d.connected && !busy));
|
||||
}
|
||||
};
|
||||
|
||||
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, filespath) {
|
||||
let sel = "";
|
||||
let el = $("msd-image-selector");
|
||||
let fm = tools.radio.getValue("file-mode-radio");
|
||||
el.options.length = 1;
|
||||
for (let name of Object.keys(images).sort()) {
|
||||
if ((fm == "0" && name.startsWith(filespath + "/")) || (fm == "1" && !name.startsWith(filespath + "/"))) {
|
||||
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() {
|
||||
tools.el.setEnabled($("msd-image-selector"), false);
|
||||
tools.el.setEnabled($("msd-download-button"), false);
|
||||
tools.el.setEnabled($("msd-remove-button"), false);
|
||||
__sendParam("image", $("msd-image-selector").value);
|
||||
};
|
||||
|
||||
var __clickDownloadButton = function() {
|
||||
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 this image?", name).then(function(ok) {
|
||||
if (ok) {
|
||||
tools.httpPost("/api/msd/remove", {"image": name}, function(http) {
|
||||
if (http.status !== 200) {
|
||||
wm.error("Can't remove image", http.responseText);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var __sendParam = function(name, value) {
|
||||
tools.httpPost("/api/msd/set_params", {[name]: value}, function(http) {
|
||||
if (http.status !== 200) {
|
||||
wm.error("Can't configure Mass Storage", http.responseText);
|
||||
}
|
||||
__refreshControls();
|
||||
});
|
||||
};
|
||||
|
||||
var __refreshFileMode = function() {
|
||||
if (__state.storage.images !== undefined) {
|
||||
__updateImageSelector(__state.drive, __state.storage.images, __state.storage.filespath);
|
||||
}
|
||||
__refreshControls();
|
||||
};
|
||||
|
||||
var __clickUploadNewButton = function() {
|
||||
let file = tools.input.getFile($("msd-new-file"));
|
||||
__http = new XMLHttpRequest();
|
||||
let prefix = ""
|
||||
|
||||
if (tools.radio.getValue("file-mode-radio") == "1"){
|
||||
prefix = encodeURIComponent($("msd-new-part-selector").value);
|
||||
}else{
|
||||
prefix = __state.storage.filespath;
|
||||
}
|
||||
if (file) {
|
||||
let image = encodeURIComponent(file.name);
|
||||
__http.open("POST", `/api/msd/write?prefix=${prefix}&image=${image}&remove_incomplete=1`, true);
|
||||
} else {
|
||||
let url = encodeURIComponent($("msd-new-url").value);
|
||||
__http.open("POST", `/api/msd/write_remote?prefix=${prefix}&url=${url}&remove_incomplete=1`, true);
|
||||
}
|
||||
__http.upload.timeout = 7 * 24 * 3600;
|
||||
__http.onreadystatechange = __uploadStateChange;
|
||||
__http.send(file);
|
||||
__refreshControls();
|
||||
};
|
||||
|
||||
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;
|
||||
__refreshControls();
|
||||
tools.hidden.setVisible($("msd-new-sub"), true);
|
||||
};
|
||||
|
||||
var __clickConnectButton = function(connected) {
|
||||
tools.httpPost("/api/msd/set_connected", {"connected": connected}, function(http) {
|
||||
if (http.status !== 200) {
|
||||
wm.error("Can't switch Mass Storage", http.responseText);
|
||||
}
|
||||
__refreshControls();
|
||||
});
|
||||
__refreshControls();
|
||||
tools.el.setEnabled($(`msd-${connected ? "connect" : "disconnect"}-button`), false);
|
||||
};
|
||||
|
||||
var __clickMakeImageButton = function(zipped) {
|
||||
tools.el.setEnabled($("msd-file-image-update-button"), false);
|
||||
tools.el.setEnabled($("msd-file-image-unzip-button"), false);
|
||||
tools.httpPost("/api/msd/make_image", {"zipped": zipped}, function(http) {
|
||||
if (http.status !== 200) {
|
||||
wm.error("Can't make File Image", http.responseText);
|
||||
}
|
||||
__refreshControls();
|
||||
});
|
||||
__refreshControls();
|
||||
if (__state.storage.images !== undefined) {
|
||||
__updateImageSelector(__state.drive, __state.storage.images, __state.storage.filespath);
|
||||
}
|
||||
};
|
||||
|
||||
var __clickResetButton = function() {
|
||||
wm.confirm("Are you sure you want to reset Mass Storage?").then(function(ok) {
|
||||
if (ok) {
|
||||
tools.httpPost("/api/msd/reset", null, function(http) {
|
||||
if (http.status !== 200) {
|
||||
wm.error("Mass Storage reset error", http.responseText);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
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 = "";
|
||||
}
|
||||
__refreshControls();
|
||||
};
|
||||
|
||||
var __selectNewFile = function() {
|
||||
let el = $("msd-new-file");
|
||||
let file = tools.input.getFile(el);
|
||||
if (file) {
|
||||
$("msd-new-url").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 = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
__refreshControls();
|
||||
};
|
||||
|
||||
var __selectNewUrl = function() {
|
||||
if ($("msd-new-url").value.length > 0) {
|
||||
$("msd-new-file").value = "";
|
||||
}
|
||||
__refreshControls();
|
||||
};
|
||||
|
||||
__init__();
|
||||
}
|
||||
202
kvmd_data/usr/share/kvmd/web/share/js/kvm/ocr.js
Normal file
@@ -0,0 +1,202 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# 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 Ocr(__getGeometry) {
|
||||
var self = this;
|
||||
|
||||
/************************************************************************/
|
||||
|
||||
var __enabled = null;
|
||||
|
||||
var __start_pos = null;
|
||||
var __end_pos = null;
|
||||
var __sel = null;
|
||||
|
||||
var __init__ = function() {
|
||||
tools.el.setOnClick($("stream-ocr-button"), function() {
|
||||
__resetSelection();
|
||||
wm.showWindow($("stream-window"));
|
||||
wm.showWindow($("stream-ocr-window"));
|
||||
});
|
||||
|
||||
$("stream-ocr-lang-selector").addEventListener("change", function() {
|
||||
tools.storage.set("stream.ocr.lang", $("stream-ocr-lang-selector").value);
|
||||
});
|
||||
|
||||
$("stream-ocr-window").addEventListener("blur", __resetSelection);
|
||||
$("stream-ocr-window").addEventListener("resize", __resetSelection);
|
||||
$("stream-ocr-window").close_hook = __resetSelection;
|
||||
|
||||
$("stream-ocr-window").onkeyup = function(event) {
|
||||
event.preventDefault();
|
||||
if (event.code === "Enter") {
|
||||
if (__sel) {
|
||||
__recognizeSelection();
|
||||
wm.closeWindow($("stream-ocr-window"));
|
||||
}
|
||||
} else if (event.code === "Escape") {
|
||||
wm.closeWindow($("stream-ocr-window"));
|
||||
}
|
||||
};
|
||||
|
||||
$("stream-ocr-window").onmousedown = __startSelection;
|
||||
$("stream-ocr-window").onmousemove = __changeSelection;
|
||||
$("stream-ocr-window").onmouseup = __endSelection;
|
||||
};
|
||||
|
||||
/************************************************************************/
|
||||
|
||||
self.setState = function(state) {
|
||||
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";
|
||||
}
|
||||
};
|
||||
|
||||
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) {
|
||||
if (__start_pos === null) {
|
||||
tools.hidden.setVisible($("stream-ocr-selection"), false);
|
||||
__start_pos = __getGlobalPosition(event);
|
||||
__end_pos = null;
|
||||
}
|
||||
};
|
||||
|
||||
var __changeSelection = function(event) {
|
||||
if (__start_pos !== null) {
|
||||
__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 = $("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 = $("stream-ocr-selection");
|
||||
let ok = (
|
||||
el.offsetWidth > 1 && el.offsetHeight > 1
|
||||
&& __start_pos !== null && __end_pos !== null
|
||||
);
|
||||
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;
|
||||
let rel_right = Math.max(__start_pos.x, __end_pos.x) - rect.left;
|
||||
let offset = __getNavbarOffset();
|
||||
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();
|
||||
__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 {
|
||||
__sel = null;
|
||||
}
|
||||
__start_pos = null;
|
||||
__end_pos = null;
|
||||
};
|
||||
|
||||
var __getGlobalPosition = function(event) {
|
||||
let rect = $("stream-box").getBoundingClientRect();
|
||||
let geo = __getGeometry();
|
||||
let offset = __getNavbarOffset();
|
||||
return {
|
||||
"x": Math.min(Math.max(event.clientX, rect.left + geo.x), rect.right - geo.x),
|
||||
"y": Math.min(Math.max(event.clientY - offset, rect.top + geo.y - offset), rect.bottom - geo.y - offset),
|
||||
};
|
||||
};
|
||||
|
||||
var __getNavbarOffset = function() {
|
||||
if (tools.browser.is_firefox) {
|
||||
// На лисе наблюдается оффсет из-за навбара, хз почему
|
||||
return wm.getViewGeometry().top;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
var __resetSelection = function() {
|
||||
tools.hidden.setVisible($("stream-ocr-selection"), false);
|
||||
__start_pos = null;
|
||||
__end_pos = 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 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 {
|
||||
wm.error("OCR error:<br>", http.responseText);
|
||||
}
|
||||
tools.el.setEnabled($("stream-ocr-button"), true);
|
||||
tools.el.setEnabled($("stream-ocr-lang-selector"), true);
|
||||
$("stream-ocr-led").className = "led-gray";
|
||||
}, null, null, 30000);
|
||||
};
|
||||
|
||||
__init__();
|
||||
}
|
||||
112
kvmd_data/usr/share/kvmd/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__();
|
||||
}
|
||||
385
kvmd_data/usr/share/kvmd/web/share/js/kvm/recorder.js
Normal file
@@ -0,0 +1,385 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# 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 Recorder() {
|
||||
var self = this;
|
||||
|
||||
/************************************************************************/
|
||||
|
||||
var __ws = null;
|
||||
|
||||
var __play_timer = null;
|
||||
var __recording = false;
|
||||
var __events = [];
|
||||
var __events_time = 0;
|
||||
var __last_event_ts = 0;
|
||||
|
||||
var __init__ = function() {
|
||||
tools.el.setOnClick($("hid-recorder-record"), __startRecord);
|
||||
tools.el.setOnClick($("hid-recorder-stop"), __stopProcess);
|
||||
tools.el.setOnClick($("hid-recorder-play"), __playRecord);
|
||||
tools.el.setOnClick($("hid-recorder-clear"), __clearRecord);
|
||||
|
||||
$("hid-recorder-new-script-file").onchange = __uploadScript;
|
||||
tools.el.setOnClick($("hid-recorder-upload"), () => $("hid-recorder-new-script-file").click());
|
||||
tools.el.setOnClick($("hid-recorder-download"), __downloadScript);
|
||||
};
|
||||
|
||||
/************************************************************************/
|
||||
|
||||
self.setSocket = function(ws) {
|
||||
if (ws !== __ws) {
|
||||
__ws = ws;
|
||||
}
|
||||
if (__ws === null) {
|
||||
__stopProcess();
|
||||
}
|
||||
__refresh();
|
||||
};
|
||||
|
||||
self.recordWsEvent = function(event) {
|
||||
__recordEvent(event);
|
||||
};
|
||||
|
||||
self.recordPrintEvent = function(text, keymap) {
|
||||
__recordEvent({"event_type": "print", "event": {"text": text, "keymap": keymap}});
|
||||
};
|
||||
|
||||
self.recordAtxButtonEvent = function(button) {
|
||||
__recordEvent({"event_type": "atx_button", "event": {"button": button}});
|
||||
};
|
||||
|
||||
self.recordGpioSwitchEvent = function(channel, to) {
|
||||
__recordEvent({"event_type": "gpio_switch", "event": {"channel": channel, "state": to}});
|
||||
};
|
||||
|
||||
self.recordGpioPulseEvent = function(channel) {
|
||||
__recordEvent({"event_type": "gpio_pulse", "event": {"channel": channel}});
|
||||
};
|
||||
|
||||
var __recordEvent = function(event) {
|
||||
if (__recording) {
|
||||
let now = new Date().getTime();
|
||||
if (__last_event_ts) {
|
||||
let delay = now - __last_event_ts;
|
||||
__events.push({"event_type": "delay", "event": {"millis": delay}});
|
||||
__events_time += delay;
|
||||
}
|
||||
__last_event_ts = now;
|
||||
__events.push(event);
|
||||
__setCounters(__events.length, __events_time);
|
||||
}
|
||||
};
|
||||
|
||||
var __startRecord = function() {
|
||||
__clearRecord();
|
||||
__recording = true;
|
||||
__refresh();
|
||||
};
|
||||
|
||||
var __stopProcess = function() {
|
||||
if (__play_timer) {
|
||||
clearTimeout(__play_timer);
|
||||
__play_timer = null;
|
||||
}
|
||||
if (__recording) {
|
||||
__recording = false;
|
||||
}
|
||||
__refresh();
|
||||
};
|
||||
|
||||
var __playRecord = function() {
|
||||
__play_timer = setTimeout(() => __runEvents(0), 0);
|
||||
__refresh();
|
||||
};
|
||||
|
||||
var __clearRecord = function() {
|
||||
__events = [];
|
||||
__events_time = 0;
|
||||
__last_event_ts = 0;
|
||||
__refresh();
|
||||
};
|
||||
|
||||
var __downloadScript = function() {
|
||||
let blob = new Blob([JSON.stringify(__events, undefined, 4)], {"type": "application/json"});
|
||||
let url = window.URL.createObjectURL(blob);
|
||||
let el_anchor = document.createElement("a");
|
||||
el_anchor.href = url;
|
||||
el_anchor.download = "script.json";
|
||||
el_anchor.click();
|
||||
window.URL.revokeObjectURL(url);
|
||||
};
|
||||
|
||||
var __uploadScript = function() {
|
||||
let el_input = $("hid-recorder-new-script-file");
|
||||
let script_file = (el_input.files.length ? el_input.files[0] : null);
|
||||
if (script_file) {
|
||||
let reader = new FileReader();
|
||||
reader.onload = function () {
|
||||
let events = [];
|
||||
let events_time = 0;
|
||||
|
||||
try {
|
||||
let raw_events = JSON.parse(reader.result);
|
||||
__checkType(raw_events, "object", "Base of script is not an objects list");
|
||||
|
||||
for (let event of raw_events) {
|
||||
__checkType(event, "object", "Non-dict event");
|
||||
__checkType(event.event, "object", "Non-dict event");
|
||||
|
||||
if (event.event_type === "delay") {
|
||||
__checkUnsigned(event.event.millis, "Non-unsigned delay");
|
||||
events_time += event.event.millis;
|
||||
|
||||
} 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");
|
||||
__checkType(event.event.state, "boolean", "Non-bool key state");
|
||||
|
||||
} else if (event.event_type === "mouse_button") {
|
||||
__checkType(event.event.button, "string", "Non-string mouse button code");
|
||||
__checkType(event.event.state, "boolean", "Non-bool mouse button state");
|
||||
|
||||
} else if (event.event_type === "mouse_move") {
|
||||
__checkType(event.event.to, "object", "Non-object mouse move target");
|
||||
__checkInt(event.event.to.x, "Non-int mouse move X");
|
||||
__checkInt(event.event.to.y, "Non-int mouse move Y");
|
||||
|
||||
} else if (event.event_type === "mouse_relative") {
|
||||
__checkMouseRelativeDelta(event.event.delta);
|
||||
__checkType(event.event.squash, "boolean", "Non-boolean squash");
|
||||
|
||||
} else if (event.event_type === "mouse_wheel") {
|
||||
__checkType(event.event.delta, "object", "Non-object mouse wheel delta");
|
||||
__checkInt(event.event.delta.x, "Non-int mouse delta X");
|
||||
__checkInt(event.event.delta.y, "Non-int mouse delta Y");
|
||||
|
||||
} else if (event.event_type === "atx_button") {
|
||||
__checkType(event.event.button, "string", "Non-string ATX button");
|
||||
|
||||
} else if (event.event_type === "gpio_switch") {
|
||||
__checkType(event.event.channel, "string", "Non-string GPIO channel");
|
||||
__checkType(event.event.state, "boolean", "Non-bool GPIO state");
|
||||
|
||||
} else if (event.event_type === "gpio_pulse") {
|
||||
__checkType(event.event.channel, "string", "Non-string GPIO channel");
|
||||
|
||||
} else if (event.event_type === "delay_random") {
|
||||
__checkType(event.event.range, "object", "Non-object random delay range");
|
||||
__checkUnsigned(event.event.range.min, "Non-unsigned random delay range min");
|
||||
__checkUnsigned(event.event.range.max, "Non-unsigned random delay range max");
|
||||
__checkRangeMinMax(event.event.range, "Invalid random delay range");
|
||||
events_time += event.event.range.max;
|
||||
|
||||
} else if (event.event_type === "mouse_move_random") { // Hack for pikvm/pikvm#1041
|
||||
__checkType(event.event.range, "object", "Non-object random mouse move range");
|
||||
__checkInt(event.event.range.min, "Non-int random mouse move range min");
|
||||
__checkInt(event.event.range.max, "Non-int random mouse move range max");
|
||||
__checkRangeMinMax(event.event.range, "Invalid random mouse move range");
|
||||
|
||||
} else {
|
||||
throw `Unknown event type: ${event.event_type}`;
|
||||
}
|
||||
|
||||
events.push(event);
|
||||
}
|
||||
|
||||
__events = events;
|
||||
__events_time = events_time;
|
||||
} catch (ex) {
|
||||
wm.error("Invalid script", `${ex}`);
|
||||
}
|
||||
|
||||
el_input.value = "";
|
||||
__refresh();
|
||||
};
|
||||
reader.readAsText(script_file, "UTF-8");
|
||||
}
|
||||
};
|
||||
|
||||
var __checkType = function(obj, type, msg) {
|
||||
if (typeof obj !== type) {
|
||||
throw msg;
|
||||
}
|
||||
};
|
||||
|
||||
var __checkInt = function(obj, msg) {
|
||||
if (!Number.isInteger(obj)) {
|
||||
throw msg;
|
||||
}
|
||||
};
|
||||
|
||||
var __checkUnsigned = function(obj, msg) {
|
||||
__checkInt(obj, msg);
|
||||
if (obj < 0) {
|
||||
throw msg;
|
||||
}
|
||||
};
|
||||
|
||||
var __checkRangeMinMax = function(obj, msg) {
|
||||
if (obj.min > obj.max) {
|
||||
throw msg;
|
||||
}
|
||||
};
|
||||
|
||||
var __checkArray = function (obj, msg) {
|
||||
if (!Array.isArray(obj)) {
|
||||
throw msg;
|
||||
}
|
||||
};
|
||||
|
||||
var __checkMouseRelativeDelta = function(delta) {
|
||||
__checkArray(delta, "Non-array relative mouse delta");
|
||||
delta.forEach(element => {
|
||||
__checkType(element, "object", "Non-object relative mouse delta element");
|
||||
__checkInt(element.x, "Non-int mouse delta X");
|
||||
__checkInt(element.y, "Non-int mouse delta Y");
|
||||
});
|
||||
};
|
||||
|
||||
var __runEvents = function(index, time=0) {
|
||||
while (index < __events.length) {
|
||||
__setCounters(__events.length - index + 1, __events_time - time);
|
||||
let event = __events[index];
|
||||
|
||||
if (["delay", "delay_random"].includes(event.event_type)) {
|
||||
let millis = (
|
||||
event.event_type === "delay"
|
||||
? event.event.millis
|
||||
: tools.getRandomInt(event.event.range.min, event.event.range.max)
|
||||
);
|
||||
__play_timer = setTimeout(() => __runEvents(index + 1, time + millis), millis);
|
||||
return;
|
||||
|
||||
} else if (event.event_type === "print") {
|
||||
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", http.responseText);
|
||||
__stopProcess();
|
||||
} else if (http.status === 200) {
|
||||
__play_timer = setTimeout(() => __runEvents(index + 1, time), 0);
|
||||
}
|
||||
}, event.event.text, "text/plain");
|
||||
return;
|
||||
|
||||
} else if (event.event_type === "atx_button") {
|
||||
tools.httpPost("/api/atx/click", {"button": event.event.button}, function(http) {
|
||||
if (http.status !== 200) {
|
||||
wm.error("ATX error", http.responseText);
|
||||
__stopProcess();
|
||||
} else if (http.status === 200) {
|
||||
__play_timer = setTimeout(() => __runEvents(index + 1, time), 0);
|
||||
}
|
||||
});
|
||||
return;
|
||||
|
||||
} 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";
|
||||
params["state"] = event.event.to;
|
||||
} else { // gpio_pulse
|
||||
path += "/pulse";
|
||||
}
|
||||
tools.httpPost(path, params, function(http) {
|
||||
if (http.status !== 200) {
|
||||
wm.error("GPIO error", http.responseText);
|
||||
__stopProcess();
|
||||
} else if (http.status === 200) {
|
||||
__play_timer = setTimeout(() => __runEvents(index + 1, time), 0);
|
||||
}
|
||||
});
|
||||
return;
|
||||
|
||||
} else if (["key", "mouse_button", "mouse_move", "mouse_wheel", "mouse_relative"].includes(event.event_type)) {
|
||||
__ws.sendHidEvent(event);
|
||||
|
||||
} else if (event.event_type === "mouse_move_random") {
|
||||
__ws.sendHidEvent({
|
||||
"event_type": "mouse_move",
|
||||
"event": {"to": {
|
||||
"x": tools.getRandomInt(event.event.range.min, event.event.range.max),
|
||||
"y": tools.getRandomInt(event.event.range.min, event.event.range.max),
|
||||
}},
|
||||
});
|
||||
}
|
||||
|
||||
index += 1;
|
||||
}
|
||||
if ($("hid-recorder-loop-switch").checked) {
|
||||
setTimeout(() => __runEvents(0));
|
||||
} else {
|
||||
__stopProcess();
|
||||
}
|
||||
};
|
||||
|
||||
var __refresh = function() {
|
||||
if (__play_timer) {
|
||||
$("hid-recorder-led").className = "led-yellow-rotating-fast";
|
||||
$("hid-recorder-led").title = "Playing...";
|
||||
} else if (__recording) {
|
||||
$("hid-recorder-led").className = "led-red-rotating-fast";
|
||||
$("hid-recorder-led").title = "Recording...";
|
||||
} else {
|
||||
$("hid-recorder-led").className = "led-gray";
|
||||
$("hid-recorder-led").title = "";
|
||||
}
|
||||
|
||||
tools.el.setEnabled($("hid-recorder-record"), (__ws && !__play_timer && !__recording));
|
||||
tools.el.setEnabled($("hid-recorder-stop"), (__ws && (__play_timer || __recording)));
|
||||
tools.el.setEnabled($("hid-recorder-play"), (__ws && !__recording && __events.length));
|
||||
tools.el.setEnabled($("hid-recorder-clear"), (!__play_timer && !__recording && __events.length));
|
||||
tools.el.setEnabled($("hid-recorder-loop-switch"), (__ws && !__recording));
|
||||
|
||||
tools.el.setEnabled($("hid-recorder-upload"), (!__play_timer && !__recording));
|
||||
tools.el.setEnabled($("hid-recorder-download"), (!__play_timer && !__recording && __events.length));
|
||||
|
||||
__setCounters(__events.length, __events_time);
|
||||
};
|
||||
|
||||
var __setCounters = function(events_count, events_time) {
|
||||
$("hid-recorder-time").innerHTML = tools.formatDuration(events_time);
|
||||
$("hid-recorder-events-count").innerHTML = events_count;
|
||||
};
|
||||
|
||||
__init__();
|
||||
}
|
||||
425
kvmd_data/usr/share/kvmd/web/share/js/kvm/session.js
Normal file
@@ -0,0 +1,425 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# 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";
|
||||
|
||||
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";
|
||||
import {Gpio} from "./gpio.js";
|
||||
import {Ocr} from "./ocr.js";
|
||||
|
||||
|
||||
export function Session() {
|
||||
// var self = this;
|
||||
|
||||
/************************************************************************/
|
||||
|
||||
var __ws = null;
|
||||
|
||||
var __ping_timer = null;
|
||||
var __missed_heartbeats = 0;
|
||||
|
||||
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);
|
||||
var __ocr = new Ocr(__streamer.getGeometry);
|
||||
|
||||
var __info_hw_state = null;
|
||||
var __info_fan_state = null;
|
||||
|
||||
var __init__ = function() {
|
||||
__streamer.ensureDeps(() => __startSession());
|
||||
};
|
||||
|
||||
/************************************************************************/
|
||||
|
||||
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) {
|
||||
$("kvmd-meta-json").innerText = JSON.stringify(state, undefined, 4);
|
||||
|
||||
if (state.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").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
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var __setInfoStateHw = function(state) {
|
||||
if (state.health.throttling !== null) {
|
||||
let flags = state.health.throttling.parsed_flags;
|
||||
let ignore_past = state.health.throttling.ignore_past;
|
||||
let undervoltage = (flags.undervoltage.now || (flags.undervoltage.past && !ignore_past));
|
||||
let freq_capped = (flags.freq_capped.now || (flags.freq_capped.past && !ignore_past));
|
||||
|
||||
tools.hidden.setVisible($("hw-health-dropdown"), (undervoltage || freq_capped));
|
||||
$("hw-health-undervoltage-led").className = (undervoltage ? (flags.undervoltage.now ? "led-red" : "led-yellow") : "hidden");
|
||||
$("hw-health-overheating-led").className = (freq_capped ? (flags.freq_capped.now ? "led-red" : "led-yellow") : "hidden");
|
||||
tools.hidden.setVisible($("hw-health-message-undervoltage"), undervoltage);
|
||||
tools.hidden.setVisible($("hw-health-message-overheating"), freq_capped);
|
||||
}
|
||||
__info_hw_state = state;
|
||||
__renderAboutInfoHardware();
|
||||
};
|
||||
|
||||
var __setInfoStateFan = function(state) {
|
||||
let failed = false;
|
||||
let failed_past = false;
|
||||
if (state.monitored) {
|
||||
if (state.state === null) {
|
||||
failed = true;
|
||||
} else {
|
||||
if (!state.state.fan.ok) {
|
||||
failed = true;
|
||||
} else if (state.state.fan.last_fail_ts >= 0) {
|
||||
failed = true;
|
||||
failed_past = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
tools.hidden.setVisible($("fan-health-dropdown"), failed);
|
||||
$("fan-health-led").className = (failed ? (failed_past ? "led-yellow" : "led-red") : "hidden");
|
||||
|
||||
__info_fan_state = state;
|
||||
__renderAboutInfoHardware();
|
||||
};
|
||||
|
||||
var __renderAboutInfoHardware = function() {
|
||||
let html = "";
|
||||
if (__info_hw_state !== null) {
|
||||
html += `
|
||||
Platform:
|
||||
${__formatMisc(__info_hw_state)}
|
||||
<hr>
|
||||
Temperature:
|
||||
${__formatTemp(__info_hw_state.health.temp)}
|
||||
<hr>
|
||||
Throttling:
|
||||
${__formatThrottling(__info_hw_state.health.throttling)}
|
||||
`;
|
||||
}
|
||||
if (__info_fan_state !== null) {
|
||||
if (html.length > 0) {
|
||||
html += "<hr>";
|
||||
}
|
||||
html += `
|
||||
Fan:
|
||||
${__formatFan(__info_fan_state)}
|
||||
`;
|
||||
}
|
||||
$("about-hardware").innerHTML = html;
|
||||
};
|
||||
|
||||
var __formatMisc = function(state) {
|
||||
return __formatUl([
|
||||
["Base", state.platform.base],
|
||||
["Serial", state.platform.serial],
|
||||
["CPU", `${state.health.cpu.percent}%`],
|
||||
["MEM", `${state.health.mem.percent}%`],
|
||||
]);
|
||||
};
|
||||
|
||||
var __formatFan = function(state) {
|
||||
if (!state.monitored) {
|
||||
return __formatUl([["Status", "Not monitored"]]);
|
||||
} else if (state.state === null) {
|
||||
return __formatUl([["Status", __colored("red", "Not available")]]);
|
||||
} else {
|
||||
state = state.state;
|
||||
let pairs = [
|
||||
["Status", (state.fan.ok ? __colored("green", "Ok") : __colored("red", "Failed"))],
|
||||
["Desired speed", `${state.fan.speed}%`],
|
||||
["PWM", `${state.fan.pwm}`],
|
||||
];
|
||||
if (state.hall.available) {
|
||||
pairs.push(["RPM", __colored((state.fan.ok ? "green" : "red"), state.hall.rpm)]);
|
||||
}
|
||||
return __formatUl(pairs);
|
||||
}
|
||||
};
|
||||
|
||||
var __formatTemp = function(temp) {
|
||||
let pairs = [];
|
||||
for (let field of Object.keys(temp).sort()) {
|
||||
pairs.push([field.toUpperCase(), `${temp[field]}°C`]);
|
||||
}
|
||||
return __formatUl(pairs);
|
||||
};
|
||||
|
||||
var __formatThrottling = function(throttling) {
|
||||
if (throttling !== null) {
|
||||
let pairs = [];
|
||||
for (let field of Object.keys(throttling.parsed_flags).sort()) {
|
||||
let flags = throttling.parsed_flags[field];
|
||||
let key = tools.upperFirst(field).replace("_", " ");
|
||||
let value = (flags["now"] ? __colored("red", "RIGHT NOW") : __colored("green", "No"));
|
||||
if (!throttling.ignore_past) {
|
||||
value += "; " + (flags["past"] ? __colored("red", "In the past") : __colored("green", "Never"));
|
||||
}
|
||||
pairs.push([key, value]);
|
||||
}
|
||||
return __formatUl(pairs);
|
||||
} else {
|
||||
return "NO DATA";
|
||||
}
|
||||
};
|
||||
|
||||
var __colored = function(color, html) {
|
||||
return `<font color="${color}">${html}</font>`;
|
||||
};
|
||||
|
||||
var __setInfoStateSystem = function(state) {
|
||||
$("about-version").innerHTML = `
|
||||
KVMD: <span class="code-comment">${state.kvmd.version}</span><br>
|
||||
<hr>
|
||||
Streamer: <span class="code-comment">${state.streamer.version} (${state.streamer.app})</span>
|
||||
${__formatStreamerFeatures(state.streamer.features)}
|
||||
<hr>
|
||||
${state.kernel.system} kernel:
|
||||
${__formatUname(state.kernel)}
|
||||
`;
|
||||
$("kvmd-version-kvmd").innerText = state.kvmd.version;
|
||||
$("kvmd-version-streamer").innerText = state.streamer.version;
|
||||
};
|
||||
|
||||
var __formatStreamerFeatures = function(features) {
|
||||
let pairs = [];
|
||||
for (let field of Object.keys(features).sort()) {
|
||||
pairs.push([field, (features[field] ? "Yes" : "No")]);
|
||||
}
|
||||
return __formatUl(pairs);
|
||||
};
|
||||
|
||||
var __formatUname = function(kernel) {
|
||||
let pairs = [];
|
||||
for (let field of Object.keys(kernel).sort()) {
|
||||
if (field !== "system") {
|
||||
pairs.push([tools.upperFirst(field), kernel[field]]);
|
||||
}
|
||||
}
|
||||
return __formatUl(pairs);
|
||||
};
|
||||
|
||||
var __formatUl = function(pairs) {
|
||||
let html = "";
|
||||
for (let pair of pairs) {
|
||||
html += `<li>${pair[0]}: <span class="code-comment">${pair[1]}</span></li>`;
|
||||
}
|
||||
return `<ul>${html}</ul>`;
|
||||
};
|
||||
|
||||
var __setInfoStateExtras = function(state) {
|
||||
let show_hook = null;
|
||||
let close_hook = null;
|
||||
let has_webterm = (state.webterm && (state.webterm.enabled || state.webterm.started));
|
||||
if (has_webterm) {
|
||||
let path = "/" + state.webterm.path + "?disableLeaveAlert=true";
|
||||
show_hook = function() {
|
||||
tools.info("Terminal opened: ", path);
|
||||
$("webterm-iframe").src = path;
|
||||
};
|
||||
close_hook = function() {
|
||||
tools.info("Terminal closed");
|
||||
$("webterm-iframe").src = "";
|
||||
};
|
||||
}
|
||||
tools.feature.setEnabled($("system-tool-webterm"), has_webterm);
|
||||
$("webterm-window").show_hook = show_hook;
|
||||
$("webterm-window").close_hook = close_hook;
|
||||
};
|
||||
|
||||
var __startSession = function() {
|
||||
$("link-led").className = "led-yellow";
|
||||
$("link-led").title = "Connecting...";
|
||||
|
||||
tools.httpGet("/api/auth/check", null, function(http) {
|
||||
if (http.status === 200) {
|
||||
__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;
|
||||
__ws.onerror = __wsErrorHandler;
|
||||
__ws.onclose = __wsCloseHandler;
|
||||
} else if (http.status === 401 || http.status === 403) {
|
||||
window.onbeforeunload = () => null;
|
||||
wm.error("Unexpected logout occured, please login again").then(function() {
|
||||
document.location.href = "/login";
|
||||
});
|
||||
} else {
|
||||
__wsCloseHandler(null);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var __ascii_encoder = new TextEncoder("ascii");
|
||||
|
||||
var __sendHidEvent = function(ws, event_type, event) {
|
||||
if (event_type == "key") {
|
||||
let data = __ascii_encoder.encode("\x01\x00" + event.key);
|
||||
data[1] = (event.state ? 1 : 0);
|
||||
ws.send(data);
|
||||
|
||||
} else if (event_type == "mouse_button") {
|
||||
let data = __ascii_encoder.encode("\x02\x00" + event.button);
|
||||
data[1] = (event.state ? 1 : 0);
|
||||
ws.send(data);
|
||||
|
||||
} else if (event_type == "mouse_move") {
|
||||
let data = new Uint8Array([
|
||||
3,
|
||||
(event.to.x >> 8) & 0xFF, event.to.x & 0xFF,
|
||||
(event.to.y >> 8) & 0xFF, event.to.y & 0xFF,
|
||||
]);
|
||||
ws.send(data);
|
||||
|
||||
} else if (event_type == "mouse_relative" || event_type == "mouse_wheel") {
|
||||
let data;
|
||||
if (Array.isArray(event.delta)) {
|
||||
data = new Int8Array(2 + event.delta.length * 2);
|
||||
let index = 0;
|
||||
for (let delta of event.delta) {
|
||||
data[index + 2] = delta["x"];
|
||||
data[index + 3] = delta["y"];
|
||||
index += 2;
|
||||
}
|
||||
} else {
|
||||
data = new Int8Array([0, 0, event.delta.x, event.delta.y]);
|
||||
}
|
||||
data[0] = (event_type == "mouse_relative" ? 4 : 5);
|
||||
data[1] = (event.squash ? 1 : 0);
|
||||
ws.send(data);
|
||||
}
|
||||
};
|
||||
|
||||
var __wsOpenHandler = function(event) {
|
||||
tools.debug("Session: socket opened:", event);
|
||||
$("link-led").className = "led-green";
|
||||
$("link-led").title = "Connected";
|
||||
__recorder.setSocket(__ws);
|
||||
__hid.setSocket(__ws);
|
||||
__missed_heartbeats = 0;
|
||||
__ping_timer = setInterval(__pingServer, 1000);
|
||||
};
|
||||
|
||||
var __wsMessageHandler = function(event) {
|
||||
// tools.debug("Session: received socket data:", event.data);
|
||||
let data = JSON.parse(event.data);
|
||||
switch (data.event_type) {
|
||||
case "pong": __missed_heartbeats = 0; break;
|
||||
case "info_state": __setInfoState(data.event); break;
|
||||
case "gpio_state": __gpio.setState(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 "ocr_state": __ocr.setState(data.event); break;
|
||||
}
|
||||
};
|
||||
|
||||
var __wsErrorHandler = function(event) {
|
||||
tools.error("Session: socket error:", event);
|
||||
if (__ws) {
|
||||
__ws.onclose = null;
|
||||
__ws.close();
|
||||
__wsCloseHandler(null);
|
||||
}
|
||||
};
|
||||
|
||||
var __wsCloseHandler = function(event) {
|
||||
tools.debug("Session: socket closed:", event);
|
||||
|
||||
$("link-led").className = "led-gray";
|
||||
|
||||
if (__ping_timer) {
|
||||
clearInterval(__ping_timer);
|
||||
__ping_timer = null;
|
||||
}
|
||||
|
||||
__gpio.setState(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() {
|
||||
$("link-led").className = "led-yellow";
|
||||
setTimeout(__startSession, 500);
|
||||
}, 500);
|
||||
};
|
||||
|
||||
var __pingServer = function() {
|
||||
try {
|
||||
__missed_heartbeats += 1;
|
||||
if (__missed_heartbeats >= 15) {
|
||||
throw new Error("Too many missed heartbeats");
|
||||
}
|
||||
__ws.send("{\"event_type\": \"ping\", \"event\": {}}");
|
||||
} catch (ex) {
|
||||
__wsErrorHandler(ex.message);
|
||||
}
|
||||
};
|
||||
|
||||
__init__();
|
||||
}
|
||||
416
kvmd_data/usr/share/kvmd/web/share/js/kvm/stream.js
Normal file
@@ -0,0 +1,416 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# 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";
|
||||
|
||||
import {JanusStreamer} from "./stream_janus.js";
|
||||
import {MjpegStreamer} from "./stream_mjpeg.js";
|
||||
|
||||
|
||||
export function Streamer() {
|
||||
var self = this;
|
||||
|
||||
/************************************************************************/
|
||||
|
||||
var __janus_imported = null;
|
||||
var __streamer = null;
|
||||
|
||||
var __state = null;
|
||||
var __res = {"width": 640, "height": 480};
|
||||
|
||||
var __init__ = function() {
|
||||
__streamer = new MjpegStreamer(__setActive, __setInactive, __setInfo);
|
||||
|
||||
$("stream-led").title = "Stream inactive";
|
||||
|
||||
tools.slider.setParams($("stream-quality-slider"), 5, 100, 5, 80, function(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").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").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").innerText = (value === 0 ? "Unlimited" : value);
|
||||
});
|
||||
tools.slider.setOnUpDelayed($("stream-desired-fps-slider"), 1000, (value) => __sendParam("desired_fps", value));
|
||||
|
||||
$("stream-resolution-selector").onchange = (() => __sendParam("resolution", $("stream-resolution-selector").value));
|
||||
|
||||
tools.radio.setOnClick("stream-mode-radio", __clickModeRadio, false);
|
||||
|
||||
// Not getInt() because of radio is a string container.
|
||||
// Also don't reset Janus at class init.
|
||||
tools.radio.clickValue("stream-orient-radio", tools.storage.get("stream.orient", 0));
|
||||
tools.radio.setOnClick("stream-orient-radio", function() {
|
||||
if (__streamer.getMode() === "janus") { // Right now it's working only for H.264
|
||||
let orient = parseInt(tools.radio.getValue("stream-orient-radio"));
|
||||
tools.storage.setInt("stream.orient", orient);
|
||||
if (__streamer.getOrientation() != orient) {
|
||||
__resetStream();
|
||||
}
|
||||
}
|
||||
}, false);
|
||||
|
||||
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").innerText = value + "%";
|
||||
if (__streamer.getMode() === "janus") {
|
||||
let allow_audio = !$("stream-video").muted;
|
||||
if (__streamer.isAudioAllowed() !== allow_audio) {
|
||||
__resetStream();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
tools.el.setOnClick($("stream-screenshot-button"), __clickScreenshotButton);
|
||||
tools.el.setOnClick($("stream-reset-button"), __clickResetButton);
|
||||
tools.el.setOnClick($("stream-record-start-button"), __clickRecordStartButton);
|
||||
tools.el.setOnClick($("stream-record-stop-button"), __clickRecordStopButton);
|
||||
|
||||
|
||||
$("stream-window").show_hook = () => __applyState(__state);
|
||||
$("stream-window").close_hook = () => __applyState(null);
|
||||
|
||||
//hidden stream-record-stop-button
|
||||
document.getElementById('stream-record-stop-button').disabled = true;
|
||||
};
|
||||
|
||||
/************************************************************************/
|
||||
|
||||
self.ensureDeps = function(callback) {
|
||||
JanusStreamer.ensure_janus(function(avail) {
|
||||
__janus_imported = avail;
|
||||
callback();
|
||||
});
|
||||
};
|
||||
|
||||
self.getGeometry = function() {
|
||||
// Первоначально обновление геометрии считалось через ResizeObserver.
|
||||
// Но оно не ловило некоторые события, например в последовательности:
|
||||
// - Находять в HD переходим в фулскрин
|
||||
// - Меняем разрешение на маленькое
|
||||
// - Убираем фулскрин
|
||||
// - Переходим в HD
|
||||
// - Видим нарушение пропорций
|
||||
// Так что теперь используются быстре рассчеты через offset*
|
||||
// вместо getBoundingClientRect().
|
||||
let res = __streamer.getResolution();
|
||||
let ratio = Math.min(res.view_width / res.real_width, res.view_height / res.real_height);
|
||||
return {
|
||||
"x": Math.round((res.view_width - ratio * res.real_width) / 2),
|
||||
"y": Math.round((res.view_height - ratio * res.real_height) / 2),
|
||||
"width": Math.round(ratio * res.real_width),
|
||||
"height": Math.round(ratio * res.real_height),
|
||||
"real_width": res.real_width,
|
||||
"real_height": res.real_height,
|
||||
};
|
||||
};
|
||||
|
||||
self.setState = function(state) {
|
||||
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 (__janus_imported === null) {
|
||||
alert("__janus_imported is null, please report");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!state) {
|
||||
__streamer.stopStream();
|
||||
return;
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
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);
|
||||
}
|
||||
} else {
|
||||
$("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);
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
var __setActive = function() {
|
||||
$("stream-led").className = "led-green";
|
||||
$("stream-led").title = "Stream is active";
|
||||
};
|
||||
|
||||
var __setInactive = function() {
|
||||
$("stream-led").className = "led-gray";
|
||||
$("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()} - `;
|
||||
if (is_active) {
|
||||
if (!online) {
|
||||
title += "No signal / ";
|
||||
}
|
||||
title += `${__res.width}x${__res.height}`;
|
||||
if (text.length > 0) {
|
||||
title += " / " + text;
|
||||
}
|
||||
} else {
|
||||
if (text.length > 0) {
|
||||
title += text;
|
||||
} else {
|
||||
title += "Inactive";
|
||||
}
|
||||
}
|
||||
el_grab.innerText = el_info.innerText = title;
|
||||
};
|
||||
|
||||
var __resetStream = function(mode=null) {
|
||||
if (mode === null) {
|
||||
mode = __streamer.getMode();
|
||||
}
|
||||
__streamer.stopStream();
|
||||
if (mode === "janus") {
|
||||
__streamer = new JanusStreamer(__setActive, __setInactive, __setInfo,
|
||||
tools.storage.getInt("stream.orient", 0), !$("stream-video").muted);
|
||||
// Firefox doesn't support RTP orientation:
|
||||
// - https://bugzilla.mozilla.org/show_bug.cgi?id=1316448
|
||||
tools.feature.setEnabled($("stream-orient"), !tools.browser.is_firefox);
|
||||
} else { // mjpeg
|
||||
__streamer = new MjpegStreamer(__setActive, __setInactive, __setInfo);
|
||||
tools.feature.setEnabled($("stream-orient"), false);
|
||||
tools.feature.setEnabled($("stream-audio"), false); // Enabling in stream_janus.js
|
||||
}
|
||||
if (wm.isWindowVisible($("stream-window"))) {
|
||||
__streamer.ensureStream((__state && __state.streamer !== undefined) ? __state.streamer : null);
|
||||
}
|
||||
};
|
||||
|
||||
var __clickModeRadio = function() {
|
||||
let mode = tools.radio.getValue("stream-mode-radio");
|
||||
tools.storage.set("stream.mode", mode);
|
||||
if (mode !== __streamer.getMode()) {
|
||||
tools.hidden.setVisible($("stream-image"), (mode !== "janus"));
|
||||
tools.hidden.setVisible($("stream-video"), (mode === "janus"));
|
||||
__resetStream(mode);
|
||||
}
|
||||
};
|
||||
|
||||
var __clickScreenshotButton = function() {
|
||||
let el = document.createElement("a");
|
||||
el.href = "/api/streamer/snapshot";
|
||||
el.target = "_blank";
|
||||
document.body.appendChild(el);
|
||||
el.click();
|
||||
setTimeout(() => document.body.removeChild(el), 0);
|
||||
};
|
||||
|
||||
var __clickResetButton = function() {
|
||||
wm.confirm("Are you sure you want to reset stream?").then(function(ok) {
|
||||
if (ok) {
|
||||
__resetStream();
|
||||
tools.httpPost("/api/streamer/reset", null, function(http) {
|
||||
if (http.status !== 200) {
|
||||
wm.error("Can't reset stream", http.responseText);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
var stream_mjpeg_refresh_img;
|
||||
var stream_now_fps
|
||||
let mediaRecorder;
|
||||
var __clickRecordStartButton = function() {
|
||||
wm.confirm("Are you sure you want to record stream?").then(function (ok) {
|
||||
if (ok) {
|
||||
stream_now_fps = tools.slider.getValue($("stream-desired-fps-slider"));
|
||||
let recordedBlobs = [];
|
||||
//"mjpeg" or "janus"
|
||||
let stream_type = document.querySelector('input[name="stream-mode-radio"]:checked').value;
|
||||
if ( stream_type == "mjpeg"){
|
||||
|
||||
var stream_mjpeg_img = document.getElementById('stream-image');
|
||||
var stream_mjpeg_canvas = document.getElementById('stream-mjpeg-canvas');
|
||||
var ctx = stream_mjpeg_canvas.getContext('2d');
|
||||
stream_mjpeg_canvas.width = stream_mjpeg_img.width;
|
||||
stream_mjpeg_canvas.height = stream_mjpeg_img.height;
|
||||
const stream = stream_mjpeg_canvas.captureStream(stream_now_fps); // Capture FPS
|
||||
mediaRecorder = new MediaRecorder(stream);
|
||||
}else{
|
||||
const stream = document.getElementById("stream-video")
|
||||
stream.captureStream = stream.captureStream || stream.mozCaptureStream;
|
||||
mediaRecorder = new MediaRecorder(stream.captureStream());
|
||||
}
|
||||
|
||||
|
||||
mediaRecorder.ondataavailable = function(event) {
|
||||
if (event.data && event.data.size > 0) {
|
||||
recordedBlobs.push(event.data);
|
||||
}
|
||||
};
|
||||
|
||||
mediaRecorder.onstop = function() {
|
||||
const blob = new Blob(recordedBlobs, {type: 'video/webm'});
|
||||
var url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
document.body.appendChild(a);
|
||||
const now = new Date();
|
||||
const year = now.getFullYear();
|
||||
const month = ('0' + (now.getMonth() + 1)).slice(-2);
|
||||
const day = ('0' + now.getDate()).slice(-2);
|
||||
const hours = ('0' + now.getHours()).slice(-2);
|
||||
const minutes = ('0' + now.getMinutes()).slice(-2);
|
||||
const seconds = ('0' + now.getSeconds()).slice(-2);//Get now time
|
||||
a.style = "display: none";
|
||||
a.href = url;
|
||||
a.download = stream_type +"_"+ year + month + day + hours + minutes + seconds + ".webm";
|
||||
a.click();
|
||||
window.URL.revokeObjectURL(url);
|
||||
};
|
||||
|
||||
mediaRecorder.start();
|
||||
document.getElementById('stream-record-start-button').disabled = true;
|
||||
document.getElementById('stream-record-stop-button').disabled = false;
|
||||
if (stream_type == "mjpeg"){
|
||||
stream_mjpeg_refresh_img = setInterval(function() {
|
||||
ctx.drawImage(stream_mjpeg_img, 0, 0, stream_mjpeg_img.width, stream_mjpeg_img.height);
|
||||
}, 1000 / stream_now_fps);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var __clickRecordStopButton = function() {
|
||||
mediaRecorder.stop();
|
||||
clearInterval(stream_mjpeg_refresh_img);
|
||||
document.getElementById('stream-record-start-button').disabled = false;
|
||||
document.getElementById('stream-record-stop-button').disabled = true;
|
||||
};
|
||||
|
||||
var __sendParam = function(name, value) {
|
||||
tools.httpPost("/api/streamer/set_params", {[name]: value}, function(http) {
|
||||
if (http.status !== 200) {
|
||||
wm.error("Can't configure stream", http.responseText);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
__init__();
|
||||
}
|
||||
457
kvmd_data/usr/share/kvmd/web/share/js/kvm/stream_janus.js
Normal file
@@ -0,0 +1,457 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# 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";
|
||||
|
||||
|
||||
var _Janus = null;
|
||||
|
||||
|
||||
export function JanusStreamer(__setActive, __setInactive, __setInfo, __orient, __allow_audio) {
|
||||
var self = this;
|
||||
|
||||
var __stop = false;
|
||||
var __ensuring = false;
|
||||
|
||||
var __janus = null;
|
||||
var __handle = null;
|
||||
|
||||
var __retry_ensure_timeout = null;
|
||||
var __retry_emsg_timeout = null;
|
||||
var __info_interval = null;
|
||||
|
||||
var __state = null;
|
||||
var __frames = 0;
|
||||
|
||||
self.getOrientation = () => __orient;
|
||||
self.isAudioAllowed = () => __allow_audio;
|
||||
|
||||
self.getName = () => (__allow_audio ? "H.264 + Audio" : "H.264");
|
||||
self.getMode = () => "janus";
|
||||
|
||||
self.getResolution = function() {
|
||||
let el = $("stream-video");
|
||||
return {
|
||||
// Разрешение видео или элемента
|
||||
"real_width": (el.videoWidth || el.offsetWidth),
|
||||
"real_height": (el.videoHeight || el.offsetHeight),
|
||||
"view_width": el.offsetWidth,
|
||||
"view_height": el.offsetHeight,
|
||||
};
|
||||
};
|
||||
|
||||
self.ensureStream = function(state) {
|
||||
__state = state;
|
||||
__stop = false;
|
||||
__ensureJanus(false);
|
||||
};
|
||||
|
||||
self.stopStream = function() {
|
||||
__stop = true;
|
||||
__destroyJanus();
|
||||
};
|
||||
|
||||
var __ensureJanus = function(internal) {
|
||||
if (__janus === null && !__stop && (!__ensuring || internal)) {
|
||||
__setInactive();
|
||||
__setInfo(false, false, "");
|
||||
__ensuring = true;
|
||||
__logInfo("Starting Janus ...");
|
||||
__janus = new _Janus({
|
||||
"server": `${tools.is_https ? "wss" : "ws"}://${location.host}/janus/ws`,
|
||||
"ipv6": true,
|
||||
"destroyOnUnload": false,
|
||||
"success": __attachJanus,
|
||||
"error": function(error) {
|
||||
__logError(error);
|
||||
__setInfo(false, false, error);
|
||||
__finishJanus();
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
var __finishJanus = function() {
|
||||
if (__stop) {
|
||||
if (__retry_ensure_timeout !== null) {
|
||||
clearTimeout(__retry_ensure_timeout);
|
||||
__retry_ensure_timeout = null;
|
||||
}
|
||||
__ensuring = false;
|
||||
} else {
|
||||
if (__retry_ensure_timeout === null) {
|
||||
__retry_ensure_timeout = setTimeout(function() {
|
||||
__retry_ensure_timeout = null;
|
||||
__ensureJanus(true);
|
||||
}, 5000);
|
||||
}
|
||||
}
|
||||
__stopRetryEmsgInterval();
|
||||
__stopInfoInterval();
|
||||
if (__handle) {
|
||||
__logInfo("uStreamer detaching ...:", __handle.getPlugin(), __handle.getId());
|
||||
__handle.detach();
|
||||
__handle = null;
|
||||
}
|
||||
__janus = null;
|
||||
__setInactive();
|
||||
if (__stop) {
|
||||
__setInfo(false, false, "");
|
||||
}
|
||||
};
|
||||
|
||||
var __destroyJanus = function() {
|
||||
if (__janus !== null) {
|
||||
__janus.destroy();
|
||||
}
|
||||
__finishJanus();
|
||||
let stream = $("stream-video").srcObject;
|
||||
if (stream) {
|
||||
for (let track of stream.getTracks()) {
|
||||
__removeTrack(track);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var __addTrack = function(track) {
|
||||
let el = $("stream-video");
|
||||
if (el.srcObject) {
|
||||
for (let tr of el.srcObject.getTracks()) {
|
||||
if (tr.kind === track.kind && tr.id !== track.id) {
|
||||
__removeTrack(tr);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!el.srcObject) {
|
||||
el.srcObject = new MediaStream();
|
||||
}
|
||||
el.srcObject.addTrack(track);
|
||||
};
|
||||
|
||||
var __removeTrack = function(track) {
|
||||
let el = $("stream-video");
|
||||
if (!el.srcObject) {
|
||||
return;
|
||||
}
|
||||
track.stop();
|
||||
el.srcObject.removeTrack(track);
|
||||
if (el.srcObject.getTracks().length === 0) {
|
||||
// MediaStream should be destroyed to prevent old picture freezing
|
||||
// on Janus reconnecting.
|
||||
el.srcObject = null;
|
||||
}
|
||||
};
|
||||
|
||||
var __attachJanus = function() {
|
||||
if (__janus === null) {
|
||||
return;
|
||||
}
|
||||
__janus.attach({
|
||||
"plugin": "janus.plugin.ustreamer",
|
||||
"opaqueId": "oid-" + _Janus.randomString(12),
|
||||
|
||||
"success": function(handle) {
|
||||
__handle = handle;
|
||||
__logInfo("uStreamer attached:", handle.getPlugin(), handle.getId());
|
||||
__sendWatch();
|
||||
},
|
||||
|
||||
"error": function(error) {
|
||||
__logError("Can't attach uStreamer: ", error);
|
||||
__setInfo(false, false, error);
|
||||
__destroyJanus();
|
||||
},
|
||||
|
||||
"connectionState": function(state) {
|
||||
__logInfo("Peer connection state changed to", state);
|
||||
if (state === "failed") {
|
||||
__destroyJanus();
|
||||
}
|
||||
},
|
||||
|
||||
"iceState": function(state) {
|
||||
__logInfo("ICE state changed to", state);
|
||||
},
|
||||
|
||||
"webrtcState": function(up) {
|
||||
__logInfo("Janus says our WebRTC PeerConnection is", (up ? "up" : "down"), "now");
|
||||
if (up) {
|
||||
__sendKeyRequired();
|
||||
}
|
||||
},
|
||||
|
||||
"onmessage": function(msg, jsep) {
|
||||
__stopRetryEmsgInterval();
|
||||
|
||||
if (msg.result) {
|
||||
__logInfo("Got uStreamer result message:", msg.result.status); // starting, started, stopped
|
||||
if (msg.result.status === "started") {
|
||||
__setActive();
|
||||
__setInfo(false, false, "");
|
||||
} else if (msg.result.status === "stopped") {
|
||||
__setInactive();
|
||||
__setInfo(false, false, "");
|
||||
} else if (msg.result.status === "features") {
|
||||
tools.feature.setEnabled($("stream-audio"), msg.result.features.audio);
|
||||
}
|
||||
} else if (msg.error_code || msg.error) {
|
||||
__logError("Got uStreamer error message:", msg.error_code, "-", msg.error);
|
||||
__setInfo(false, false, msg.error);
|
||||
if (__retry_emsg_timeout === null) {
|
||||
__retry_emsg_timeout = setTimeout(function() {
|
||||
if (!__stop) {
|
||||
__sendStop();
|
||||
__sendWatch();
|
||||
}
|
||||
__retry_emsg_timeout = null;
|
||||
}, 2000);
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
__logInfo("Got uStreamer other message:", msg);
|
||||
}
|
||||
|
||||
if (jsep) {
|
||||
__logInfo("Handling SDP:", jsep);
|
||||
let tracks = [{"type": "video", "capture": false, "recv": true, "add": true}];
|
||||
if (__allow_audio) {
|
||||
tracks.push({"type": "audio", "capture": false, "recv": true, "add": true});
|
||||
}
|
||||
__handle.createAnswer({
|
||||
"jsep": jsep,
|
||||
|
||||
// Janus 1.x
|
||||
"tracks": tracks,
|
||||
|
||||
// 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);
|
||||
},
|
||||
|
||||
"error": function(error) {
|
||||
__logInfo("Error on SDP handling:", error);
|
||||
__setInfo(false, false, error);
|
||||
//__destroyJanus();
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// Janus 1.x
|
||||
"onremotetrack": function(track, id, added, meta) {
|
||||
// Chrome sends `muted` notifiation for tracks in `disconnected` ICE state
|
||||
// and Janus.js just removes muted track from list of available tracks.
|
||||
// But track still exists actually so it's safe to just ignore
|
||||
// reason == "mute" and "unmute".
|
||||
let reason = (meta || {}).reason;
|
||||
__logInfo("Got onremotetrack:", id, added, reason, track, meta);
|
||||
if (added && reason === "created") {
|
||||
__addTrack(track);
|
||||
if (track.kind === "video") {
|
||||
__sendKeyRequired();
|
||||
__startInfoInterval();
|
||||
}
|
||||
} else if (!added && reason === "ended") {
|
||||
__removeTrack(track);
|
||||
}
|
||||
},
|
||||
|
||||
// Janus 0.x
|
||||
"onremotestream": function(stream) {
|
||||
if (stream === null) {
|
||||
// https://github.com/pikvm/pikvm/issues/1084
|
||||
// Этого вообще не должно происходить, но почему-то янусу в unmute
|
||||
// может прилететь null-эвент. Костыляем, наблюдаем.
|
||||
__logError("Got invalid onremotestream(null). Restarting Janus...");
|
||||
__destroyJanus();
|
||||
return;
|
||||
}
|
||||
|
||||
let tracks = stream.getTracks();
|
||||
__logInfo("Got a remote stream changes:", stream, tracks);
|
||||
|
||||
let has_video = false;
|
||||
for (let track of tracks) {
|
||||
if (track.kind == "video") {
|
||||
has_video = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!has_video && __isOnline()) {
|
||||
// Chrome sends `muted` notifiation for tracks in `disconnected` ICE state
|
||||
// and Janus.js just removes muted track from list of available tracks.
|
||||
// But track still exists actually so it's safe to just ignore that case.
|
||||
return;
|
||||
}
|
||||
|
||||
_Janus.attachMediaStream($("stream-video"), stream);
|
||||
__sendKeyRequired();
|
||||
__startInfoInterval();
|
||||
|
||||
// FIXME: Задержка уменьшается, но начинаются заикания на кейфреймах.
|
||||
// - https://github.com/Glimesh/janus-ftl-plugin/issues/101
|
||||
/*if (__handle && __handle.webrtcStuff && __handle.webrtcStuff.pc) {
|
||||
for (let receiver of __handle.webrtcStuff.pc.getReceivers()) {
|
||||
if (receiver.track && receiver.track.kind === "video" && receiver.playoutDelayHint !== undefined) {
|
||||
receiver.playoutDelayHint = 0;
|
||||
}
|
||||
}
|
||||
}*/
|
||||
},
|
||||
|
||||
"oncleanup": function() {
|
||||
__logInfo("Got a cleanup notification");
|
||||
__stopInfoInterval();
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
var __startInfoInterval = function() {
|
||||
__stopInfoInterval();
|
||||
__setActive();
|
||||
__updateInfo();
|
||||
__info_interval = setInterval(__updateInfo, 1000);
|
||||
};
|
||||
|
||||
var __stopInfoInterval = function() {
|
||||
if (__info_interval !== null) {
|
||||
clearInterval(__info_interval);
|
||||
}
|
||||
__info_interval = null;
|
||||
};
|
||||
|
||||
var __stopRetryEmsgInterval = function() {
|
||||
if (__retry_emsg_timeout !== null) {
|
||||
clearTimeout(__retry_emsg_timeout);
|
||||
__retry_emsg_timeout = null;
|
||||
}
|
||||
};
|
||||
|
||||
var __updateInfo = function() {
|
||||
if (__handle !== null) {
|
||||
let info = "";
|
||||
if (__handle !== null) {
|
||||
// https://wiki.whatwg.org/wiki/Video_Metrics
|
||||
let frames = null;
|
||||
let el = $("stream-video");
|
||||
if (el.webkitDecodedFrameCount !== undefined) {
|
||||
frames = el.webkitDecodedFrameCount;
|
||||
} else if (el.mozPaintedFrames !== undefined) {
|
||||
frames = el.mozPaintedFrames;
|
||||
}
|
||||
info = `${__handle.getBitrate()}`.replace("kbits/sec", "kbps");
|
||||
if (frames !== null) {
|
||||
info += ` / ${Math.max(0, frames - __frames)} fps dynamic`;
|
||||
__frames = frames;
|
||||
}
|
||||
}
|
||||
__setInfo(true, __isOnline(), info);
|
||||
}
|
||||
};
|
||||
|
||||
var __isOnline = function() {
|
||||
return !!(__state && __state.source.online);
|
||||
};
|
||||
|
||||
var __sendWatch = function() {
|
||||
if (__handle) {
|
||||
__logInfo(`Sending WATCH(orient=${__orient}, audio=${__allow_audio}) + FEATURES ...`);
|
||||
__handle.send({"message": {"request": "features"}});
|
||||
__handle.send({"message": {"request": "watch", "params": {
|
||||
"orientation": __orient,
|
||||
"audio": __allow_audio,
|
||||
}}});
|
||||
}
|
||||
};
|
||||
|
||||
var __sendStart = function(jsep) {
|
||||
if (__handle) {
|
||||
__logInfo("Sending START ...");
|
||||
__handle.send({"message": {"request": "start"}, "jsep": jsep});
|
||||
}
|
||||
};
|
||||
|
||||
var __sendKeyRequired = function() {
|
||||
/*if (__handle) {
|
||||
// На этом шаге мы говорим что стрим пошел и надо запросить кейфрейм
|
||||
__logInfo("Sending KEY_REQUIRED ...");
|
||||
__handle.send({message: {request: "key_required"}});
|
||||
}*/
|
||||
};
|
||||
|
||||
var __sendStop = function() {
|
||||
__stopInfoInterval();
|
||||
if (__handle) {
|
||||
__logInfo("Sending STOP ...");
|
||||
__handle.send({"message": {"request": "stop"}});
|
||||
__handle.hangup();
|
||||
}
|
||||
};
|
||||
|
||||
var __logInfo = (...args) => tools.info("Stream [Janus]:", ...args);
|
||||
var __logError = (...args) => tools.error("Stream [Janus]:", ...args);
|
||||
}
|
||||
|
||||
JanusStreamer.ensure_janus = function(callback) {
|
||||
if (_Janus === null) {
|
||||
import("./janus.js").then((module) => {
|
||||
module.Janus.init({
|
||||
"debug": "all",
|
||||
"callback": function() {
|
||||
_Janus = module.Janus;
|
||||
callback(true);
|
||||
},
|
||||
});
|
||||
}).catch((ex) => {
|
||||
tools.error("Stream: Can't import Janus module:", ex);
|
||||
callback(false);
|
||||
});
|
||||
} else {
|
||||
callback(true);
|
||||
}
|
||||
};
|
||||
|
||||
JanusStreamer.is_webrtc_available = function() {
|
||||
return !!window.RTCPeerConnection;
|
||||
};
|
||||
|
||||
JanusStreamer.is_h264_available = function() {
|
||||
let ok = true;
|
||||
if ($("stream-video").canPlayType) {
|
||||
ok = $("stream-video").canPlayType("video/mp4; codecs=\"avc1.42E01F\"");
|
||||
}
|
||||
return ok;
|
||||
};
|
||||
159
kvmd_data/usr/share/kvmd/web/share/js/kvm/stream_mjpeg.js
Normal file
@@ -0,0 +1,159 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# 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";
|
||||
|
||||
|
||||
export function MjpegStreamer(__setActive, __setInactive, __setInfo) {
|
||||
var self = this;
|
||||
|
||||
/************************************************************************/
|
||||
|
||||
var __key = tools.makeId();
|
||||
var __id = "";
|
||||
var __fps = -1;
|
||||
var __state = null;
|
||||
|
||||
var __timer = null;
|
||||
var __timer_retries = 0;
|
||||
|
||||
/************************************************************************/
|
||||
|
||||
self.getName = () => "MJPEG";
|
||||
self.getMode = () => "mjpeg";
|
||||
|
||||
self.getResolution = function() {
|
||||
let el = $("stream-image");
|
||||
return {
|
||||
"real_width": el.naturalWidth,
|
||||
"real_height": el.naturalHeight,
|
||||
"view_width": el.offsetWidth,
|
||||
"view_height": el.offsetHeight,
|
||||
};
|
||||
};
|
||||
|
||||
self.ensureStream = function(state) {
|
||||
if (state) {
|
||||
__state = state;
|
||||
__findId();
|
||||
if (__id.length > 0 && __id in __state.stream.clients_stat) {
|
||||
__setStreamActive();
|
||||
__stopChecking();
|
||||
} else {
|
||||
__ensureChecking();
|
||||
}
|
||||
} else {
|
||||
__stopChecking();
|
||||
__setStreamInactive();
|
||||
}
|
||||
};
|
||||
|
||||
self.stopStream = function() {
|
||||
self.ensureStream(null);
|
||||
let blank = "/share/png/blank-stream.png";
|
||||
if (!String.prototype.endsWith.call($("stream-image").src, blank)) {
|
||||
$("stream-image").src = blank;
|
||||
}
|
||||
};
|
||||
|
||||
var __setStreamActive = function() {
|
||||
let old_fps = __fps;
|
||||
__fps = __state.stream.clients_stat[__id].fps;
|
||||
if (old_fps < 0) {
|
||||
__logInfo("Active");
|
||||
__setActive();
|
||||
}
|
||||
__setInfo(true, __state.source.online, `${__fps} fps dynamic`);
|
||||
};
|
||||
|
||||
var __setStreamInactive = function() {
|
||||
let old_fps = __fps;
|
||||
__key = tools.makeId();
|
||||
__id = "";
|
||||
__fps = -1;
|
||||
__state = null;
|
||||
if (old_fps >= 0) {
|
||||
__logInfo("Inactive");
|
||||
__setInactive();
|
||||
__setInfo(false, false, "");
|
||||
}
|
||||
};
|
||||
|
||||
var __ensureChecking = function() {
|
||||
if (!__timer) {
|
||||
__timer_retries = 10;
|
||||
__timer = setInterval(__checkStream, 100);
|
||||
}
|
||||
};
|
||||
|
||||
var __stopChecking = function() {
|
||||
if (__timer) {
|
||||
clearInterval(__timer);
|
||||
}
|
||||
__timer = null;
|
||||
__timer_retries = 0;
|
||||
};
|
||||
|
||||
var __findId = function() {
|
||||
let sc = tools.cookies.get("stream_client");
|
||||
console.log("stream_client", sc);
|
||||
if (__id.length === 0 && sc && sc.startsWith(__key + "/")) {
|
||||
__logInfo("Found acceptable stream_client cookie:", sc);
|
||||
__id = sc.slice(sc.indexOf("/") + 1);
|
||||
}
|
||||
};
|
||||
|
||||
var __checkStream = function() {
|
||||
__findId();
|
||||
|
||||
if (__id.legnth > 0 && __id in __state.stream.clients_stat) {
|
||||
__setStreamActive();
|
||||
__stopChecking();
|
||||
|
||||
} else if (__id.length > 0 && __timer_retries >= 0) {
|
||||
__timer_retries -= 1;
|
||||
|
||||
} else {
|
||||
__setStreamInactive();
|
||||
__stopChecking();
|
||||
|
||||
let path = `/streamer/stream?key=${__key}`;
|
||||
if (tools.browser.is_safari || tools.browser.is_ios) {
|
||||
// uStreamer fix for WebKit
|
||||
__logInfo("Using dual_final_frames=1 to fix WebKit bugs");
|
||||
path += "&dual_final_frames=1";
|
||||
} else if (tools.browser.is_chrome || tools.browser.is_blink) {
|
||||
// uStreamer fix for Blink https://bugs.chromium.org/p/chromium/issues/detail?id=527446
|
||||
__logInfo("Using advance_headers=1 to fix Blink bugs");
|
||||
path += "&advance_headers=1";
|
||||
}
|
||||
|
||||
__logInfo("Refreshing ...");
|
||||
$("stream-image").src = path;
|
||||
}
|
||||
};
|
||||
|
||||
var __logInfo = (...args) => tools.info("Stream [MJPEG]:", ...args);
|
||||
}
|
||||
87
kvmd_data/usr/share/kvmd/web/share/js/login/main.js
Normal file
@@ -0,0 +1,87 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# 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 {checkBrowser} from "../bb.js";
|
||||
import {wm, initWindowManager} from "../wm.js";
|
||||
|
||||
|
||||
export function main() {
|
||||
if (checkBrowser(null, null)) {
|
||||
initWindowManager();
|
||||
|
||||
tools.el.setOnClick($("login-button"), __login);
|
||||
$("user-input").onkeyup = $("passwd-input").onkeyup = $("code-input").onkeyup = function(event) {
|
||||
if (event.code === "Enter") {
|
||||
event.preventDefault();
|
||||
$("login-button").click();
|
||||
}
|
||||
};
|
||||
|
||||
$("user-input").focus();
|
||||
}
|
||||
}
|
||||
|
||||
function __login() {
|
||||
let user = $("user-input").value;
|
||||
if (user.length === 0) {
|
||||
$("user-input").focus();
|
||||
} else {
|
||||
let passwd = $("passwd-input").value + $("code-input").value;
|
||||
let body = `user=${encodeURIComponent(user)}&passwd=${encodeURIComponent(passwd)}`;
|
||||
tools.httpPost("/api/auth/login", null, function(http) {
|
||||
if (http.status === 200) {
|
||||
document.location.href = "/";
|
||||
} else if (http.status === 403) {
|
||||
wm.error("Invalid credentials").then(__tryAgain);
|
||||
} else {
|
||||
let error = "";
|
||||
if (http.status === 400) {
|
||||
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", http.responseText).then(__tryAgain);
|
||||
}
|
||||
}
|
||||
}, body, "application/x-www-form-urlencoded");
|
||||
__setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
function __setEnabled(enabled) {
|
||||
tools.el.setEnabled($("user-input"), enabled);
|
||||
tools.el.setEnabled($("passwd-input"), enabled);
|
||||
tools.el.setEnabled($("code-input"), enabled);
|
||||
tools.el.setEnabled($("login-button"), enabled);
|
||||
}
|
||||
|
||||
function __tryAgain() {
|
||||
__setEnabled(true);
|
||||
let el = ($("code-input").value.length ? $("code-input") : $("passwd-input"));
|
||||
el.focus();
|
||||
el.select();
|
||||
}
|
||||
448
kvmd_data/usr/share/kvmd/web/share/js/tools.js
Normal file
@@ -0,0 +1,448 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# 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 {browser} from "./bb.js";
|
||||
|
||||
|
||||
export var tools = new function() {
|
||||
var self = this;
|
||||
|
||||
/************************************************************************/
|
||||
|
||||
self.setDefault = function(dict, key, value) {
|
||||
if (!(key in dict)) {
|
||||
dict[key] = value;
|
||||
}
|
||||
};
|
||||
|
||||
/************************************************************************/
|
||||
|
||||
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) {
|
||||
http.setRequestHeader("Content-Type", content_type);
|
||||
}
|
||||
http.onreadystatechange = function() {
|
||||
if (http.readyState === 4) {
|
||||
callback(http);
|
||||
}
|
||||
};
|
||||
http.timeout = timeout;
|
||||
http.send(body);
|
||||
};
|
||||
|
||||
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, 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);
|
||||
};
|
||||
|
||||
self.makeId = function() {
|
||||
let chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
let id = "";
|
||||
for (let count = 0; count < 16; ++count) {
|
||||
id += chars.charAt(Math.floor(Math.random() * chars.length));
|
||||
}
|
||||
return id;
|
||||
};
|
||||
|
||||
self.makeIdByText = function(text) {
|
||||
return btoa(text).replace("=", "_");
|
||||
};
|
||||
|
||||
self.getRandomInt = function(min, max) {
|
||||
min = Math.ceil(min);
|
||||
max = Math.floor(max);
|
||||
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) );
|
||||
return (size / Math.pow(1024, index)).toFixed(2) * 1 + " " + ["B", "KiB", "MiB", "GiB", "TiB"][index];
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
self.formatDuration = function(duration) {
|
||||
let millis = parseInt((duration % 1000) / 100);
|
||||
let secs = Math.floor((duration / 1000) % 60);
|
||||
let mins = Math.floor((duration / (1000 * 60)) % 60);
|
||||
let hours = Math.floor((duration / (1000 * 60 * 60)) % 24);
|
||||
hours = (hours < 10 ? "0" + hours : hours);
|
||||
mins = (mins < 10 ? "0" + mins : mins);
|
||||
secs = (secs < 10 ? "0" + secs : secs);
|
||||
return `${hours}:${mins}:${secs}.${millis}`;
|
||||
};
|
||||
|
||||
self.remap = function(x, a1, b1, a2, b2) {
|
||||
let remapped = Math.round((x - a1) / b1 * (b2 - a2) + a2);
|
||||
if (remapped < a2) {
|
||||
return a2;
|
||||
} else if (remapped > b2) {
|
||||
return b2;
|
||||
}
|
||||
return remapped;
|
||||
};
|
||||
|
||||
/************************************************************************/
|
||||
|
||||
self.el = new function() {
|
||||
return {
|
||||
"setOnClick": function(el, callback, prevent_default=true) {
|
||||
el.onclick = el.ontouchend = function(event) {
|
||||
if (prevent_default) {
|
||||
event.preventDefault();
|
||||
}
|
||||
callback();
|
||||
};
|
||||
},
|
||||
"setOnDown": function(el, callback, prevent_default=true) {
|
||||
el.onmousedown = el.ontouchstart = function(event) {
|
||||
if (prevent_default) {
|
||||
event.preventDefault();
|
||||
}
|
||||
callback();
|
||||
};
|
||||
},
|
||||
"setOnUp": function(el, callback, prevent_default=true) {
|
||||
el.onmouseup = el.ontouchend = function(event) {
|
||||
if (prevent_default) {
|
||||
event.preventDefault();
|
||||
}
|
||||
callback();
|
||||
};
|
||||
},
|
||||
"setEnabled": function(el, enabled) {
|
||||
if (!enabled && document.activeElement === el) {
|
||||
let el_to_focus = (
|
||||
el.closest(".modal-window")
|
||||
|| el.closest(".window")
|
||||
|| el.closest(".menu")
|
||||
);
|
||||
if (el_to_focus) {
|
||||
el_to_focus.focus();
|
||||
}
|
||||
}
|
||||
el.disabled = !enabled;
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
self.slider = new function() {
|
||||
return {
|
||||
"setOnUpDelayed": function(el, delay, execute_callback) {
|
||||
el.__execution_timer = null;
|
||||
el.__pressed = false;
|
||||
el.__postponed = null;
|
||||
|
||||
let clear_timer = function() {
|
||||
if (el.__execution_timer) {
|
||||
clearTimeout(el.__execution_timer);
|
||||
el.__execution_timer = null;
|
||||
}
|
||||
};
|
||||
|
||||
el.onmousedown = el.ontouchstart = function() {
|
||||
clear_timer();
|
||||
el.__pressed = true;
|
||||
};
|
||||
|
||||
el.onmouseup = el.ontouchend = function(event) {
|
||||
let value = self.slider.getValue(el);
|
||||
event.preventDefault();
|
||||
clear_timer();
|
||||
el.__execution_timer = setTimeout(function() {
|
||||
el.__pressed = false;
|
||||
if (el.__postponed !== null) {
|
||||
self.slider.setValue(el, el.__postponed);
|
||||
el.__postponed = null;
|
||||
}
|
||||
execute_callback(value);
|
||||
}, delay);
|
||||
};
|
||||
},
|
||||
"setParams": function(el, min, max, step, value, display_callback=null) {
|
||||
el.min = min;
|
||||
el.max = max;
|
||||
el.step = step;
|
||||
el.value = value;
|
||||
if (display_callback) {
|
||||
el.oninput = el.onchange = () => display_callback(self.slider.getValue(el));
|
||||
display_callback(self.slider.getValue(el));
|
||||
el.__display_callback = display_callback;
|
||||
}
|
||||
},
|
||||
"setRange": function(el, min, max) {
|
||||
let value = el.value;
|
||||
el.min = min;
|
||||
el.max = max;
|
||||
if (el.value != value) {
|
||||
self.slider.setValue(el, el.value, true);
|
||||
}
|
||||
},
|
||||
"setValue": function(el, value, force=false) {
|
||||
if (el.value != value || force) {
|
||||
if (el.__pressed) {
|
||||
el.__postponed = value;
|
||||
} else {
|
||||
el.value = value;
|
||||
if (el.__display_callback) {
|
||||
el.__display_callback(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"getValue": function(el) {
|
||||
if (el.step % 1 === 0) {
|
||||
return parseInt(el.value);
|
||||
} else {
|
||||
return parseFloat(el.value);
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
self.radio = new function() {
|
||||
return {
|
||||
"makeItem": function(name, title, value) {
|
||||
return `
|
||||
<input type="radio" id="${name}-${value}" name="${name}" value="${value}" />
|
||||
<label for="${name}-${value}">${title}</label>
|
||||
`;
|
||||
},
|
||||
"setOnClick": function(name, callback, prevent_default=true) {
|
||||
for (let el of $$$(`input[type="radio"][name="${name}"]`)) {
|
||||
self.el.setOnClick(el, callback, prevent_default);
|
||||
}
|
||||
},
|
||||
"getValue": function(name) {
|
||||
return document.querySelector(`input[type="radio"][name="${name}"]:checked`).value;
|
||||
},
|
||||
"setValue": function(name, value) {
|
||||
for (let el of $$$(`input[type="radio"][name="${name}"]`)) {
|
||||
el.checked = (el.value === value);
|
||||
}
|
||||
},
|
||||
"clickValue": function(name, value) {
|
||||
for (let el of $$$(`input[type="radio"][name="${name}"]`)) {
|
||||
if (el.value === value) {
|
||||
el.click();
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
"setEnabled": function(name, enabled) {
|
||||
for (let el of $$$(`input[type="radio"][name="${name}"]`)) {
|
||||
self.el.setEnabled(el, enabled);
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
self.selector = new function() {
|
||||
return {
|
||||
"addOption": function(el, title, value, selected=false) {
|
||||
el.add(new Option(title, value, selected, selected));
|
||||
},
|
||||
"addComment": function(el, title) {
|
||||
let option = new Option(title, ".".repeat(30), false, false); // Kinda magic value
|
||||
option.disabled = true;
|
||||
option.className = "comment";
|
||||
el.add(option);
|
||||
},
|
||||
"addSeparator": function(el, repeat=30) {
|
||||
if (!self.browser.is_mobile) {
|
||||
self.selector.addComment(el, "\u2500".repeat(repeat));
|
||||
}
|
||||
},
|
||||
"hasValue": function(el, value) {
|
||||
for (let el_op of el.options) {
|
||||
if (el_op.value === value) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
self.progress = new function() {
|
||||
return {
|
||||
"setValue": function(el, title, percent) {
|
||||
el.setAttribute("data-label", title);
|
||||
el.querySelector(".progress-value").style.width = `${percent}%`;
|
||||
},
|
||||
"setPercentOf": function(el, max, value) {
|
||||
let percent = Math.round(value * 100 / max);
|
||||
self.progress.setValue(el, `${percent}%`, percent);
|
||||
},
|
||||
"setSizeOf": function(el, title, size, free) {
|
||||
let size_str = self.formatSize(size);
|
||||
let used = size - free;
|
||||
let used_str = self.formatSize(used);
|
||||
let percent = used / size * 100;
|
||||
title = title.replace("%s", `${used_str} of ${size_str}`);
|
||||
self.progress.setValue(el, title, percent);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
self.input = new function() {
|
||||
return {
|
||||
"getFile": function(el) {
|
||||
return (el.files.length ? el.files[0] : null);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
self.hidden = new function() {
|
||||
return {
|
||||
"setVisible": function(el, visible) {
|
||||
el.classList.toggle("hidden", !visible);
|
||||
},
|
||||
"isVisible": function(el) {
|
||||
return !el.classList.contains("hidden");
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
self.feature = new function() {
|
||||
return {
|
||||
"setEnabled": function(el, enabled) {
|
||||
el.classList.toggle("feature-disabled", !enabled);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
/************************************************************************/
|
||||
|
||||
let __debug = (new URL(window.location.href)).searchParams.get("debug");
|
||||
|
||||
self.debug = function(...args) {
|
||||
if (__debug) {
|
||||
__log("DEBUG", ...args);
|
||||
}
|
||||
};
|
||||
self.info = (...args) => __log("INFO", ...args);
|
||||
self.error = (...args) => __log("ERROR", ...args);
|
||||
|
||||
let __log = function(label, ...args) {
|
||||
let now = (new Date()).toISOString().split("T")[1].replace("Z", "");
|
||||
console.log(`[${now}] LOG/${label} --`, ...args);
|
||||
};
|
||||
|
||||
/************************************************************************/
|
||||
|
||||
self.is_https = (location.protocol === "https:");
|
||||
|
||||
self.cookies = new function() {
|
||||
return {
|
||||
"get": function(name) {
|
||||
let matches = document.cookie.match(new RegExp(
|
||||
"(?:^|; )" + name.replace(/([\.$?*|{}\(\)\[\]\\\/\+^])/g, "\\$1") + "=([^;]*)" // eslint-disable-line no-useless-escape
|
||||
));
|
||||
return (matches ? decodeURIComponent(matches[1]) : "");
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
self.storage = new function() {
|
||||
return {
|
||||
"get": function(key, default_value) {
|
||||
let value = window.localStorage.getItem(key);
|
||||
return (value !== null ? value : `${default_value}`);
|
||||
},
|
||||
"set": (key, value) => window.localStorage.setItem(key, value),
|
||||
|
||||
"getInt": (key, default_value) => parseInt(self.storage.get(key, default_value)),
|
||||
"setInt": (key, value) => self.storage.set(key, value),
|
||||
|
||||
"getBool": (key, default_value) => !!parseInt(self.storage.get(key, (default_value ? "1" : "0"))),
|
||||
"setBool": (key, value) => self.storage.set(key, (value ? "1" : "0")),
|
||||
|
||||
"bindSimpleSwitch": function(el, key, default_value, callback=null) {
|
||||
let value = self.storage.getBool(key, default_value);
|
||||
el.checked = value;
|
||||
if (callback) {
|
||||
callback(value);
|
||||
}
|
||||
self.el.setOnClick(el, function() {
|
||||
if (callback) {
|
||||
callback(el.checked);
|
||||
}
|
||||
self.storage.setBool(key, el.checked);
|
||||
}, false);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
self.config = new function() {
|
||||
return {
|
||||
"get": function(key, default_value) {
|
||||
let value = window.getComputedStyle(document.documentElement).getPropertyValue(`--config-ui--${key}`);
|
||||
return (value || default_value);
|
||||
},
|
||||
"getBool": (key, default_value) => !!parseInt(self.config.get(key, (default_value ? "1" : "0"))),
|
||||
};
|
||||
};
|
||||
|
||||
self.browser = browser;
|
||||
};
|
||||
|
||||
export var $ = (id) => document.getElementById(id);
|
||||
export var $$ = (cls) => [].slice.call(document.getElementsByClassName(cls));
|
||||
export var $$$ = (selector) => document.querySelectorAll(selector);
|
||||
47
kvmd_data/usr/share/kvmd/web/share/js/vnc/main.js
Normal file
@@ -0,0 +1,47 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# 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";
|
||||
|
||||
|
||||
export function main() {
|
||||
__loadKvmdInfo();
|
||||
}
|
||||
|
||||
function __loadKvmdInfo() {
|
||||
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 = `
|
||||
<span class="code-comment"># How to connect using the Linux terminal:<br>
|
||||
$</span> vncviewer ${window.location.hostname}::${vnc_port}
|
||||
`;
|
||||
} else if (http.status === 401 || http.status === 403) {
|
||||
document.location.href = "/login";
|
||||
} else {
|
||||
setTimeout(__loadKvmdInfo, 1000);
|
||||
}
|
||||
});
|
||||
}
|
||||
675
kvmd_data/usr/share/kvmd/web/share/js/wm.js
Normal file
@@ -0,0 +1,675 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# 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";
|
||||
|
||||
|
||||
export var wm;
|
||||
|
||||
export function initWindowManager() {
|
||||
wm = new __WindowManager();
|
||||
}
|
||||
|
||||
function __WindowManager() {
|
||||
var self = this;
|
||||
|
||||
/************************************************************************/
|
||||
|
||||
var __top_z_index = 0;
|
||||
var __windows = [];
|
||||
var __menu_buttons = [];
|
||||
|
||||
var __init__ = function() {
|
||||
for (let el_button of $$$("button")) {
|
||||
// XXX: Workaround for iOS Safari:
|
||||
// https://stackoverflow.com/questions/3885018/active-pseudo-class-doesnt-work-in-mobile-safari
|
||||
el_button.ontouchstart = function() {};
|
||||
}
|
||||
|
||||
for (let el_button of $$("menu-button")) {
|
||||
el_button.parentElement.querySelector(".menu").setAttribute("tabindex", "-1");
|
||||
tools.el.setOnDown(el_button, () => __toggleMenu(el_button));
|
||||
__menu_buttons.push(el_button);
|
||||
}
|
||||
|
||||
if (!window.ResizeObserver) {
|
||||
tools.error("ResizeObserver not supported");
|
||||
}
|
||||
|
||||
for (let el_window of $$("window")) {
|
||||
el_window.setAttribute("tabindex", "-1");
|
||||
__makeWindowMovable(el_window);
|
||||
__windows.push(el_window);
|
||||
|
||||
if (el_window.classList.contains("window-resizable") && window.ResizeObserver) {
|
||||
new ResizeObserver(function() {
|
||||
// При переполнении рабочей области сократить размер окна по высоте.
|
||||
// По ширине оно настраивается само в CSS.
|
||||
let view = self.getViewGeometry();
|
||||
let rect = el_window.getBoundingClientRect();
|
||||
if ((rect.bottom - rect.top) > (view.bottom - view.top)) {
|
||||
let ratio = (rect.bottom - rect.top) / (view.bottom - view.top);
|
||||
el_window.style.height = view.bottom - view.top + "px";
|
||||
el_window.style.width = Math.round((rect.right - rect.left) / ratio) + "px";
|
||||
}
|
||||
|
||||
if (el_window.hasAttribute("data-centered")) {
|
||||
__centerWindow(el_window);
|
||||
}
|
||||
}).observe(el_window);
|
||||
}
|
||||
|
||||
let el_close_button = el_window.querySelector(".window-header .window-button-close");
|
||||
if (el_close_button) {
|
||||
el_close_button.title = "Close window";
|
||||
tools.el.setOnClick(el_close_button, () => self.closeWindow(el_window));
|
||||
}
|
||||
|
||||
let el_maximize_button = el_window.querySelector(".window-header .window-button-maximize");
|
||||
if (el_maximize_button) {
|
||||
el_maximize_button.title = "Maximize window";
|
||||
tools.el.setOnClick(el_maximize_button, function() {
|
||||
__maximizeWindow(el_window);
|
||||
__activateLastWindow(el_window);
|
||||
});
|
||||
}
|
||||
|
||||
let el_orig_button = el_window.querySelector(".window-header .window-button-original");
|
||||
if (el_orig_button) {
|
||||
el_orig_button.title = "Reduce window to its original size and center it";
|
||||
tools.el.setOnClick(el_orig_button, function() {
|
||||
el_window.style.width = "";
|
||||
el_window.style.height = "";
|
||||
__centerWindow(el_window);
|
||||
__activateLastWindow(el_window);
|
||||
});
|
||||
}
|
||||
|
||||
let el_enter_full_tab_button = el_window.querySelector(".window-header .window-button-enter-full-tab");
|
||||
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.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");
|
||||
if (el_full_screen_button && __getFullScreenFunction(el_window)) {
|
||||
el_full_screen_button.title = "Go to full-screen mode";
|
||||
tools.el.setOnClick(el_full_screen_button, function() {
|
||||
__fullScreenWindow(el_window);
|
||||
el_window.focus(el_window); // Почему-то теряется фокус
|
||||
__activateLastWindow(el_window);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for (let el_button of $$$("button[data-show-window]")) {
|
||||
tools.el.setOnClick(el_button, () => self.showWindow($(el_button.getAttribute("data-show-window"))));
|
||||
}
|
||||
|
||||
window.onmouseup = __globalMouseButtonHandler;
|
||||
window.ontouchend = __globalMouseButtonHandler;
|
||||
|
||||
window.addEventListener("focusin", (event) => __focusInOut(event, true));
|
||||
window.addEventListener("focusout", (event) => __focusInOut(event, false));
|
||||
|
||||
window.addEventListener("resize", __organizeWindowsOnBrowserResize);
|
||||
window.addEventListener("orientationchange", __organizeWindowsOnBrowserResize);
|
||||
|
||||
document.onfullscreenchange = __onFullScreenChange;
|
||||
};
|
||||
|
||||
/************************************************************************/
|
||||
|
||||
self.copyTextToClipboard = function(text) {
|
||||
let workaround = function(ex) {
|
||||
// https://stackoverflow.com/questions/60317969/document-execcommandcopy-not-working-even-though-the-dom-element-is-created
|
||||
__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");
|
||||
el.readonly = true;
|
||||
el.contentEditable = true;
|
||||
el.style.position = "absolute";
|
||||
el.style.top = "-1000px";
|
||||
el.value = text;
|
||||
document.body.appendChild(el);
|
||||
|
||||
// Select the content of the textarea
|
||||
el.select(); // Ordinary browsers
|
||||
el.setSelectionRange(0, el.value.length); // iOS
|
||||
|
||||
try {
|
||||
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 (ex) {
|
||||
tools.error("copyTextToClipboard(): Workaround failed:", ex);
|
||||
self.error("Can't copy text to the clipboard", `${ex}`);
|
||||
}
|
||||
});
|
||||
};
|
||||
if (navigator.clipboard) {
|
||||
navigator.clipboard.writeText(text).then(function() {
|
||||
self.info("The text has been copied to the clipboard");
|
||||
}, function(ex) {
|
||||
workaround(ex);
|
||||
});
|
||||
} else {
|
||||
workaround("navigator.clipboard is not available");
|
||||
}
|
||||
};
|
||||
|
||||
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 __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");
|
||||
el_modal.className = "modal";
|
||||
el_modal.style.visibility = "visible";
|
||||
|
||||
let el_window = document.createElement("div");
|
||||
el_window.className = "modal-window";
|
||||
el_window.setAttribute("tabindex", "-1");
|
||||
el_modal.appendChild(el_window);
|
||||
|
||||
let el_header = document.createElement("div");
|
||||
el_header.className = "modal-header";
|
||||
el_header.innerText = header;
|
||||
el_window.appendChild(el_header);
|
||||
|
||||
let el_content = document.createElement("div");
|
||||
el_content.className = "modal-content";
|
||||
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) {
|
||||
function close(retval) {
|
||||
__closeWindow(el_window);
|
||||
let index = __windows.indexOf(el_modal);
|
||||
if (index !== -1) {
|
||||
__windows.splice(index, 1);
|
||||
}
|
||||
if (el_active_menu && el_active_menu.style.visibility === "visible") {
|
||||
el_active_menu.focus();
|
||||
} else {
|
||||
__activateLastWindow(el_modal);
|
||||
}
|
||||
resolve(retval);
|
||||
// Так как resolve() асинхронный, надо выполнить в эвентлупе после него
|
||||
setTimeout(function() { el_modal.outerHTML = ""; }, 0);
|
||||
}
|
||||
|
||||
if (cancel) {
|
||||
tools.el.setOnClick(el_cancel_button, () => close(false));
|
||||
}
|
||||
if (ok) {
|
||||
tools.el.setOnClick(el_ok_button, () => close(true));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
__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;
|
||||
};
|
||||
|
||||
self.showWindow = function(el_window, activate=true, center=false) {
|
||||
let showed = false;
|
||||
if (!self.isWindowVisible(el_window)) {
|
||||
center = true;
|
||||
showed = true;
|
||||
}
|
||||
__organizeWindow(el_window, center);
|
||||
el_window.style.visibility = "visible";
|
||||
if (activate) {
|
||||
__activateWindow(el_window);
|
||||
}
|
||||
if (el_window.show_hook) {
|
||||
if (showed) {
|
||||
el_window.show_hook();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
self.isWindowVisible = function(el_window) {
|
||||
return (window.getComputedStyle(el_window, null).visibility !== "hidden");
|
||||
};
|
||||
|
||||
self.getViewGeometry = function() {
|
||||
let el_navbar = $("navbar");
|
||||
return {
|
||||
"top": (el_navbar ? el_navbar.clientHeight : 0), // Navbar height
|
||||
"bottom": Math.max(document.documentElement.clientHeight, window.innerHeight || 0),
|
||||
"left": 0,
|
||||
"right": Math.max(document.documentElement.clientWidth, window.innerWidth || 0),
|
||||
};
|
||||
};
|
||||
|
||||
self.closeWindow = function(el_window) {
|
||||
__closeWindow(el_window);
|
||||
__activateLastWindow(el_window);
|
||||
};
|
||||
|
||||
self.setFullTabWindow = function(el_window, enabled) {
|
||||
el_window.classList.toggle("window-full-tab", enabled);
|
||||
__activateLastWindow(el_window);
|
||||
let el_navbar = $("navbar");
|
||||
if (el_navbar) {
|
||||
tools.hidden.setVisible(el_navbar, !enabled);
|
||||
}
|
||||
};
|
||||
|
||||
var __closeWindow = function(el_window) {
|
||||
el_window.focus();
|
||||
el_window.blur();
|
||||
el_window.style.visibility = "hidden";
|
||||
if (el_window.close_hook) {
|
||||
el_window.close_hook();
|
||||
}
|
||||
};
|
||||
|
||||
var __toggleMenu = function(el_a) {
|
||||
let all_hidden = true;
|
||||
|
||||
for (let el_button of __menu_buttons) {
|
||||
let el_menu = el_button.parentElement.querySelector(".menu");
|
||||
if (el_button === el_a && window.getComputedStyle(el_menu, null).visibility === "hidden") {
|
||||
let rect = el_menu.getBoundingClientRect();
|
||||
let offset = self.getViewGeometry().right - (rect.left + el_menu.clientWidth + 2); // + 2 is ugly hack
|
||||
if (offset < 0) {
|
||||
el_menu.style.right = "0px";
|
||||
} else {
|
||||
el_menu.style.removeProperty("right");
|
||||
}
|
||||
|
||||
el_button.classList.add("menu-button-pressed");
|
||||
el_menu.style.visibility = "visible";
|
||||
let el_focus = el_menu.querySelector("[data-focus]");
|
||||
(el_focus !== null ? el_focus : el_menu).focus();
|
||||
all_hidden &= false;
|
||||
} else {
|
||||
el_button.classList.remove("menu-button-pressed");
|
||||
el_menu.style.visibility = "hidden";
|
||||
el_menu.style.removeProperty("right");
|
||||
}
|
||||
}
|
||||
|
||||
if (all_hidden) {
|
||||
document.onkeyup = null;
|
||||
__activateLastWindow();
|
||||
} else {
|
||||
document.onkeyup = function(event) {
|
||||
if (event.code === "Escape") {
|
||||
event.preventDefault();
|
||||
__closeAllMenues();
|
||||
__activateLastWindow();
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
var __closeAllMenues = function() {
|
||||
document.onkeyup = null;
|
||||
for (let el_button of __menu_buttons) {
|
||||
let el_menu = el_button.parentElement.querySelector(".menu");
|
||||
el_button.classList.remove("menu-button-pressed");
|
||||
el_menu.style.visibility = "hidden";
|
||||
el_menu.style.removeProperty("right");
|
||||
}
|
||||
};
|
||||
|
||||
var __focusInOut = function(event, focus_in) {
|
||||
let el_parent;
|
||||
if ((el_parent = event.target.closest(".modal-window")) !== null) {
|
||||
el_parent.classList.toggle("window-active", focus_in);
|
||||
} else if ((el_parent = event.target.closest(".window")) !== null) {
|
||||
el_parent.classList.toggle("window-active", focus_in);
|
||||
} else if ((el_parent = event.target.closest(".menu")) !== null) {
|
||||
el_parent.classList.toggle("menu-active", focus_in);
|
||||
}
|
||||
tools.debug(`UI: Focus ${focus_in ? "IN" : "OUT"}:`, el_parent);
|
||||
};
|
||||
|
||||
var __globalMouseButtonHandler = function(event) {
|
||||
if (
|
||||
event.target.closest
|
||||
&& !event.target.closest(".menu-button")
|
||||
&& !event.target.closest(".modal")
|
||||
) {
|
||||
for (let el_item = event.target; el_item && el_item !== document; el_item = el_item.parentNode) {
|
||||
if (el_item.classList.contains("menu")) {
|
||||
return;
|
||||
} else if (el_item.hasAttribute("data-force-hide-menu")) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
__closeAllMenues();
|
||||
__activateLastWindow();
|
||||
}
|
||||
};
|
||||
|
||||
var __organizeWindowsOnBrowserResize = function() {
|
||||
for (let el_window of $$("window")) {
|
||||
if (el_window.style.visibility === "visible") {
|
||||
if (tools.browser.is_mobile && el_window.classList.contains("window-resizable")) {
|
||||
// FIXME: При смене ориентации на мобильном браузере надо сбрасывать
|
||||
// настройки окна стрима, поэтому тут стоит вот этот костыль
|
||||
el_window.style.width = "";
|
||||
el_window.style.height = "";
|
||||
}
|
||||
__organizeWindow(el_window);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var __organizeWindow = function(el_window, center=false) {
|
||||
let view = self.getViewGeometry();
|
||||
let rect = el_window.getBoundingClientRect();
|
||||
|
||||
if (el_window.classList.contains("window-resizable")) {
|
||||
// При переполнении рабочей области сократить размер окна
|
||||
if ((rect.bottom - rect.top) > (view.bottom - view.top)) {
|
||||
let ratio = (rect.bottom - rect.top) / (view.bottom - view.top);
|
||||
el_window.style.height = view.bottom - view.top + "px";
|
||||
el_window.style.width = Math.round((rect.right - rect.left) / ratio) + "px";
|
||||
}
|
||||
if ((rect.right - rect.left) > (view.right - view.left)) {
|
||||
el_window.style.width = view.right - view.left + "px";
|
||||
}
|
||||
rect = el_window.getBoundingClientRect();
|
||||
}
|
||||
|
||||
if (el_window.hasAttribute("data-centered") || center) {
|
||||
__centerWindow(el_window);
|
||||
} else {
|
||||
if (rect.top <= view.top) {
|
||||
el_window.style.top = view.top + "px";
|
||||
} else if (rect.bottom > view.bottom) {
|
||||
el_window.style.top = view.bottom - rect.height + "px";
|
||||
}
|
||||
|
||||
if (rect.left <= view.left) {
|
||||
el_window.style.left = view.left + "px";
|
||||
} else if (rect.right > view.right) {
|
||||
el_window.style.left = view.right - rect.width + "px";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var __centerWindow = function(el_window) {
|
||||
let view = self.getViewGeometry();
|
||||
let rect = el_window.getBoundingClientRect();
|
||||
el_window.style.top = Math.max(view.top, Math.round((view.bottom - rect.height) / 2)) + "px";
|
||||
el_window.style.left = Math.round((view.right - rect.width) / 2) + "px";
|
||||
el_window.setAttribute("data-centered", "");
|
||||
};
|
||||
|
||||
var __activateLastWindow = function(el_except_window=null) {
|
||||
let el_last_window = null;
|
||||
|
||||
if (document.activeElement) {
|
||||
el_last_window = (document.activeElement.closest(".modal-window") || document.activeElement.closest(".window"));
|
||||
if (el_last_window && window.getComputedStyle(el_last_window, null).visibility === "hidden") {
|
||||
el_last_window = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (!el_last_window || el_last_window === el_except_window) {
|
||||
let max_z_index = 0;
|
||||
|
||||
for (let el_window of __windows) {
|
||||
let z_index = parseInt(window.getComputedStyle(el_window, null).zIndex) || 0;
|
||||
let visibility = window.getComputedStyle(el_window, null).visibility;
|
||||
|
||||
if (max_z_index < z_index && visibility !== "hidden" && el_window !== el_except_window) {
|
||||
el_last_window = el_window;
|
||||
max_z_index = z_index;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (el_last_window) {
|
||||
tools.debug("UI: Activating last window:", el_last_window);
|
||||
__activateWindow(el_last_window);
|
||||
} else {
|
||||
tools.debug("UI: No last window to activation");
|
||||
}
|
||||
};
|
||||
|
||||
var __activateWindow = function(el_window) {
|
||||
if (window.getComputedStyle(el_window, null).visibility !== "hidden") {
|
||||
let el_to_focus;
|
||||
let el_window_contains_focus;
|
||||
|
||||
if (el_window.className === "modal") {
|
||||
el_to_focus = el_window.querySelector(".modal-window");
|
||||
el_window_contains_focus = (document.activeElement && document.activeElement.closest(".modal-window"));
|
||||
} else { // .window
|
||||
el_to_focus = el_window;
|
||||
el_window_contains_focus = (document.activeElement && document.activeElement.closest(".window"));
|
||||
}
|
||||
|
||||
if (el_window.className !== "modal" && parseInt(el_window.style.zIndex) !== __top_z_index) {
|
||||
__top_z_index += 1;
|
||||
el_window.style.zIndex = __top_z_index;
|
||||
tools.debug("UI: Activated window:", el_window);
|
||||
}
|
||||
|
||||
if (el_window !== el_window_contains_focus) {
|
||||
el_to_focus.focus();
|
||||
tools.debug("UI: Focused window:", el_window);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var __makeWindowMovable = function(el_window) {
|
||||
let el_header = el_window.querySelector(".window-header");
|
||||
let el_grab = el_window.querySelector(".window-header .window-grab");
|
||||
if (el_header === null || el_grab === null) {
|
||||
// Для псевдоокна OCR
|
||||
return;
|
||||
}
|
||||
|
||||
let prev_pos = {"x": 0, "y": 0};
|
||||
|
||||
function startMoving(event) {
|
||||
// При перетаскивании resizable-окна за правый кран экрана оно ужимается.
|
||||
// Этот костыль фиксит это.
|
||||
el_window.style.width = el_window.offsetWidth + "px";
|
||||
|
||||
__closeAllMenues();
|
||||
__activateWindow(el_window);
|
||||
event = (event || window.event);
|
||||
event.preventDefault();
|
||||
|
||||
if (!event.touches || event.touches.length === 1) {
|
||||
el_header.classList.add("window-header-grabbed");
|
||||
|
||||
prev_pos = getEventPosition(event);
|
||||
|
||||
document.onmousemove = doMoving;
|
||||
document.onmouseup = stopMoving;
|
||||
|
||||
document.ontouchmove = doMoving;
|
||||
document.ontouchend = stopMoving;
|
||||
}
|
||||
}
|
||||
|
||||
function doMoving(event) {
|
||||
el_window.removeAttribute("data-centered");
|
||||
|
||||
event = (event || window.event);
|
||||
event.preventDefault();
|
||||
|
||||
let event_pos = getEventPosition(event);
|
||||
let x = prev_pos.x - event_pos.x;
|
||||
let y = prev_pos.y - event_pos.y;
|
||||
|
||||
el_window.style.top = (el_window.offsetTop - y) + "px";
|
||||
el_window.style.left = (el_window.offsetLeft - x) + "px";
|
||||
|
||||
prev_pos = event_pos;
|
||||
}
|
||||
|
||||
function stopMoving() {
|
||||
el_header.classList.remove("window-header-grabbed");
|
||||
|
||||
document.onmousemove = null;
|
||||
document.onmouseup = null;
|
||||
|
||||
document.ontouchmove = null;
|
||||
document.ontouchend = null;
|
||||
}
|
||||
|
||||
function getEventPosition(event) {
|
||||
if (event.touches) {
|
||||
return {"x": event.touches[0].clientX, "y": event.touches[0].clientY};
|
||||
} else {
|
||||
return {"x": event.clientX, "y": event.clientY};
|
||||
}
|
||||
}
|
||||
|
||||
el_window.setAttribute("data-centered", "");
|
||||
el_window.onmousedown = el_window.ontouchstart = () => __activateWindow(el_window);
|
||||
|
||||
el_grab.onmousedown = startMoving;
|
||||
el_grab.ontouchstart = startMoving;
|
||||
};
|
||||
|
||||
var __onFullScreenChange = function(event) {
|
||||
let el_window = event.target;
|
||||
if (!document.fullscreenElement) {
|
||||
let rect = el_window.before_full_screen;
|
||||
if (rect) {
|
||||
el_window.style.width = rect.width + "px";
|
||||
el_window.style.height = rect.height + "px";
|
||||
el_window.style.top = rect.top + "px";
|
||||
el_window.style.left = rect.left + "px";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var __fullScreenWindow = function(el_window) {
|
||||
el_window.before_full_screen = el_window.getBoundingClientRect();
|
||||
__getFullScreenFunction(el_window).call(el_window);
|
||||
if (navigator.keyboard && navigator.keyboard.lock) {
|
||||
navigator.keyboard.lock();
|
||||
} else {
|
||||
let msg = (
|
||||
"Shortcuts like Alt+Tab, Ctrl+W, Ctrl+N might not be captured.<br>"
|
||||
+ "For best keyboard handling use any browser with<br><a target=\"_blank\""
|
||||
+ " href=\"https://developer.mozilla.org/en-US/docs/Web"
|
||||
+ "/API/Keyboard_API#Browser_compatibility\">keyboard lock support from this list</a>.<br><br>"
|
||||
+ "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, el_window);
|
||||
}
|
||||
};
|
||||
|
||||
var __maximizeWindow = function(el_window) {
|
||||
let el_navbar = $("navbar");
|
||||
let vertical_offset = (el_navbar ? el_navbar.offsetHeight : 0);
|
||||
el_window.style.left = "0px";
|
||||
el_window.style.top = vertical_offset + "px";
|
||||
el_window.style.width = window.innerWidth + "px";
|
||||
el_window.style.height = window.innerHeight - vertical_offset + "px";
|
||||
};
|
||||
|
||||
var __getFullScreenFunction = function(el_window) {
|
||||
if (el_window.requestFullscreen) {
|
||||
return el_window.requestFullscreen;
|
||||
} else if (el_window.webkitRequestFullscreen) {
|
||||
return el_window.webkitRequestFullscreen;
|
||||
} else if (el_window.mozRequestFullscreen) {
|
||||
return el_window.mozRequestFullscreen;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
__init__();
|
||||
}
|
||||
BIN
kvmd_data/usr/share/kvmd/web/share/png/blank-stream.png
Normal file
|
After Width: | Height: | Size: 339 KiB |
20
kvmd_data/usr/share/kvmd/web/share/safari-pinned-tab.svg
Normal file
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Creator: CorelDRAW 2020 (64-Bit) -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="251.348mm" height="216.566mm" version="1.1" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd"
|
||||
viewBox="0 0 25755.01 22190.97"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:xodm="http://www.corel.com/coreldraw/odm/2003">
|
||||
<defs>
|
||||
<style type="text/css">
|
||||
<![CDATA[
|
||||
.str0 {stroke:white;stroke-width:717.27;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:22.9256}
|
||||
.fil0 {fill:black;fill-rule:nonzero}
|
||||
]]>
|
||||
</style>
|
||||
</defs>
|
||||
<g id="Layer_x0020_1">
|
||||
<metadata id="CorelCorpID_0Corel-Layer"/>
|
||||
<path class="fil0 str0" d="M24632.75 454.2c-173.5,97.6 -607.29,216.89 -889.24,249.43 -97.6,10.84 -238.57,25.3 -307.26,36.15 -896.48,119.29 -1883.32,133.75 -9543.13,148.21 -3867.85,3.62 -7128.42,18.08 -7247.7,28.92 -769.96,61.45 -791.64,65.07 -1247.11,130.13 -404.86,61.45 -394.02,57.83 -780.8,159.05 -1167.59,310.88 -2082.14,874.78 -2815.95,1738.73 -269.82,317.99 -597.52,833.51 -798.88,1229.03 -325.33,647.05 -560.3,1384.48 -614.52,1952 -36.15,365.1 -39.77,994.08 -7.23,1120.59 14.46,57.83 36.15,166.28 46.99,234.97 10.84,68.68 57.83,187.97 101.22,263.88 195.2,350.63 516.92,281.96 842.25,-184.36 224.12,-318.1 733.81,-820.57 1026.61,-1012.15 1105.15,-729.69 2578.47,-899.71 3885.93,-1004.94 473.54,-36.15 1476.2,-29.37 1572.44,-28.92 -21.68,694.56 -807.25,4104.96 -943.47,4623.35 -484.38,1825.49 -961.54,3141.27 -1630.29,4525.75 -596.44,1229.03 -1286.88,2100.21 -2197.81,2754.5 -495.24,357.87 -741.03,639.83 -950.7,1080.84 -253.65,531.06 -121.23,1611.48 133.75,2139.98 289.18,600.06 983.24,1030.23 1865.24,1153.13 916.71,130.97 1836.24,-47.23 2515.91,-712.12 741.03,-730.19 1482.08,-2436.39 1836.33,-4218.5 283.05,-1383.75 629.98,-2871.28 831.4,-4247.41 144.41,-914.48 323.41,-1831.43 469.93,-2747.27 300.64,-1403.49 465.29,-2909.15 704.89,-4337.77 7.24,-50.67 5643.66,-36.15 6430.75,-36.15 -204.41,490.58 -394.1,1023.02 -610.9,1500.15 -260.27,589.22 -1453.16,3799.17 -1670.04,4500.45 -86.76,271.11 -296.42,1026.61 -332.57,1189.27 -424.51,1867.72 -610.09,3620.83 -325.33,5516.22 168.28,1144.23 699.2,2131.08 1633.89,2823.17 886.03,661.31 1565.81,769.67 2631.59,791.64 1651.97,28.92 3058.14,-422.93 4410.08,-1413.39 621.75,-455.47 1449.54,-1398.94 1821.87,-2074.9 281.96,-513.3 534.99,-1044.69 704.89,-1471.24 169.9,-426.55 206.04,-556.68 209.66,-759.11 18.08,-610.9 -227.73,-867.56 -715.73,-751.89 -242.19,57.83 -368.71,140.97 -625.37,430.17 -704.89,788.04 -1290.49,1236.27 -1854.4,1424.24 -397.63,133.75 -509.69,155.43 -831.41,169.9 -332.57,14.46 -542.23,-32.53 -845.87,-180.74 -647.05,-318.1 -1163.97,-1156.74 -1384.48,-2259.26 -119.29,-589.22 -144.59,-1001.3 -130.13,-2223.11 10.84,-675.96 21.69,-1294.09 32.53,-1373.63 7.23,-79.52 25.3,-307.26 39.77,-506.08 10.84,-198.82 28.92,-412.09 36.15,-469.93 122.91,-860.47 183.05,-1649.91 361.49,-2512.3 86.94,-405.88 453.1,-2267.5 538.61,-2338.79 7.23,-7.23 328.95,-21.69 712.12,-28.92 657.9,-14.46 1344.71,-46.99 1474.84,-72.3 32.53,-3.62 130.13,-21.69 220.5,-36.15 892.86,-133.75 1348.33,-459.08 1850.79,-1312.18 347.03,-585.6 722.97,-1662.82 809.72,-2302.64 32.97,-307.88 72.29,-540.13 72.29,-860.33 -3.36,-319.17 -93.73,-720.19 -524.12,-437.39z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.4 KiB |
15
kvmd_data/usr/share/kvmd/web/share/site.webmanifest
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "One-KVM",
|
||||
"short_name": "One-KVM",
|
||||
"start_url": "/",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/share/android-chrome-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"theme_color": "#ffffff",
|
||||
"background_color": "#ffffff",
|
||||
"display": "standalone"
|
||||
}
|
||||
20
kvmd_data/usr/share/kvmd/web/share/svg/favicon.svg
Normal file
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Creator: CorelDRAW 2020 (64-Bit) -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="251.348mm" height="216.566mm" version="1.1" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd"
|
||||
viewBox="0 0 25755.01 22190.97"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:xodm="http://www.corel.com/coreldraw/odm/2003">
|
||||
<defs>
|
||||
<style type="text/css">
|
||||
<![CDATA[
|
||||
.str0 {stroke:white;stroke-width:717.27;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:22.9256}
|
||||
.fil0 {fill:black;fill-rule:nonzero}
|
||||
]]>
|
||||
</style>
|
||||
</defs>
|
||||
<g id="Layer_x0020_1">
|
||||
<metadata id="CorelCorpID_0Corel-Layer"/>
|
||||
<path class="fil0 str0" d="M24632.75 454.2c-173.5,97.6 -607.29,216.89 -889.24,249.43 -97.6,10.84 -238.57,25.3 -307.26,36.15 -896.48,119.29 -1883.32,133.75 -9543.13,148.21 -3867.85,3.62 -7128.42,18.08 -7247.7,28.92 -769.96,61.45 -791.64,65.07 -1247.11,130.13 -404.86,61.45 -394.02,57.83 -780.8,159.05 -1167.59,310.88 -2082.14,874.78 -2815.95,1738.73 -269.82,317.99 -597.52,833.51 -798.88,1229.03 -325.33,647.05 -560.3,1384.48 -614.52,1952 -36.15,365.1 -39.77,994.08 -7.23,1120.59 14.46,57.83 36.15,166.28 46.99,234.97 10.84,68.68 57.83,187.97 101.22,263.88 195.2,350.63 516.92,281.96 842.25,-184.36 224.12,-318.1 733.81,-820.57 1026.61,-1012.15 1105.15,-729.69 2578.47,-899.71 3885.93,-1004.94 473.54,-36.15 1476.2,-29.37 1572.44,-28.92 -21.68,694.56 -807.25,4104.96 -943.47,4623.35 -484.38,1825.49 -961.54,3141.27 -1630.29,4525.75 -596.44,1229.03 -1286.88,2100.21 -2197.81,2754.5 -495.24,357.87 -741.03,639.83 -950.7,1080.84 -253.65,531.06 -121.23,1611.48 133.75,2139.98 289.18,600.06 983.24,1030.23 1865.24,1153.13 916.71,130.97 1836.24,-47.23 2515.91,-712.12 741.03,-730.19 1482.08,-2436.39 1836.33,-4218.5 283.05,-1383.75 629.98,-2871.28 831.4,-4247.41 144.41,-914.48 323.41,-1831.43 469.93,-2747.27 300.64,-1403.49 465.29,-2909.15 704.89,-4337.77 7.24,-50.67 5643.66,-36.15 6430.75,-36.15 -204.41,490.58 -394.1,1023.02 -610.9,1500.15 -260.27,589.22 -1453.16,3799.17 -1670.04,4500.45 -86.76,271.11 -296.42,1026.61 -332.57,1189.27 -424.51,1867.72 -610.09,3620.83 -325.33,5516.22 168.28,1144.23 699.2,2131.08 1633.89,2823.17 886.03,661.31 1565.81,769.67 2631.59,791.64 1651.97,28.92 3058.14,-422.93 4410.08,-1413.39 621.75,-455.47 1449.54,-1398.94 1821.87,-2074.9 281.96,-513.3 534.99,-1044.69 704.89,-1471.24 169.9,-426.55 206.04,-556.68 209.66,-759.11 18.08,-610.9 -227.73,-867.56 -715.73,-751.89 -242.19,57.83 -368.71,140.97 -625.37,430.17 -704.89,788.04 -1290.49,1236.27 -1854.4,1424.24 -397.63,133.75 -509.69,155.43 -831.41,169.9 -332.57,14.46 -542.23,-32.53 -845.87,-180.74 -647.05,-318.1 -1163.97,-1156.74 -1384.48,-2259.26 -119.29,-589.22 -144.59,-1001.3 -130.13,-2223.11 10.84,-675.96 21.69,-1294.09 32.53,-1373.63 7.23,-79.52 25.3,-307.26 39.77,-506.08 10.84,-198.82 28.92,-412.09 36.15,-469.93 122.91,-860.47 183.05,-1649.91 361.49,-2512.3 86.94,-405.88 453.1,-2267.5 538.61,-2338.79 7.23,-7.23 328.95,-21.69 712.12,-28.92 657.9,-14.46 1344.71,-46.99 1474.84,-72.3 32.53,-3.62 130.13,-21.69 220.5,-36.15 892.86,-133.75 1348.33,-459.08 1850.79,-1312.18 347.03,-585.6 722.97,-1662.82 809.72,-2302.64 32.97,-307.88 72.29,-540.13 72.29,-860.33 -3.36,-319.17 -93.73,-720.19 -524.12,-437.39z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.4 KiB |
7
kvmd_data/usr/share/kvmd/web/share/svg/info.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48">
|
||||
<circle cy="24" cx="24" r="24" fill="#36c"/>
|
||||
<g fill="#fff">
|
||||
<circle cx="24" cy="11.6" r="4.7"/>
|
||||
<path d="m17.4 18.8v2.15h1.13c2.26 0 2.26 1.38 2.26 1.38v15.1s0 1.38-2.26 1.38h-1.13v2.08h14.2v-2.08h-1.13c-2.26 0-2.26-1.38-2.26-1.38v-18.6"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 328 B |
227
kvmd_data/usr/share/kvmd/web/share/svg/ipmi.svg
Normal file
@@ -0,0 +1,227 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.1"
|
||||
id="Layer_1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 512.015 512.015"
|
||||
style="enable-background:new 0 0 512.015 512.015;"
|
||||
xml:space="preserve"
|
||||
sodipodi:docname="server-svgrepo-com3.svg"
|
||||
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"><metadata
|
||||
id="metadata3929"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs3927">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</defs><sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1018"
|
||||
id="namedview3925"
|
||||
showgrid="false"
|
||||
inkscape:zoom="1.6620606"
|
||||
inkscape:cx="147.70821"
|
||||
inkscape:cy="256.00751"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="Layer_1" />
|
||||
<g
|
||||
id="g3826">
|
||||
<g
|
||||
id="g3824">
|
||||
<circle
|
||||
cx="69.818"
|
||||
cy="69.826"
|
||||
r="11.636"
|
||||
id="circle3822"
|
||||
style="stroke-width:20;stroke-miterlimit:4;stroke-dasharray:none;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1" />
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
id="g3832"
|
||||
transform="translate(20)">
|
||||
<g
|
||||
id="g3830">
|
||||
<circle
|
||||
cx="116.364"
|
||||
cy="69.825996"
|
||||
r="11.636"
|
||||
id="circle3828"
|
||||
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:20;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
style="fill:#000000;fill-opacity:1"
|
||||
id="g3836">
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#000000;fill-opacity:1"
|
||||
id="path3834"
|
||||
d="m 290.909,465.462 h -2.141 C 283.951,451.952 271.151,442.189 256,442.189 H 232.727 V 407.28 c 0,-6.423 -5.213,-11.636 -11.636,-11.636 -6.423,0 -11.636,5.213 -11.636,11.636 v 34.909 h -23.273 c -15.151,0 -27.951,9.763 -32.768,23.273 H 81.455 c -6.423,0 -11.636,5.213 -11.636,11.636 0,6.423 5.213,11.636 11.636,11.636 h 71.959 c 4.817,13.51 17.617,23.273 32.768,23.273 H 256 c 15.151,0 27.951,-9.763 32.768,-23.273 h 2.153 c 6.423,0 11.625,-5.213 11.625,-11.636 -10e-4,-6.423 -5.214,-11.636 -11.637,-11.636 z"
|
||||
sodipodi:nodetypes="scscssscscssscsscscs" />
|
||||
</g>
|
||||
<g
|
||||
id="g3844">
|
||||
<g
|
||||
id="g3842">
|
||||
<path
|
||||
d="M372.364,58.189H209.455c-6.423,0-11.636,5.213-11.636,11.636s5.213,11.636,11.636,11.636h162.909 c6.435,0,11.636-5.213,11.636-11.636S378.799,58.189,372.364,58.189z"
|
||||
id="path3840" />
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
id="g3850">
|
||||
<g
|
||||
id="g3848">
|
||||
<circle
|
||||
cx="69.818"
|
||||
cy="302.553"
|
||||
r="11.636"
|
||||
id="circle3846"
|
||||
style="stroke-width:20;stroke-miterlimit:4;stroke-dasharray:none;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1" />
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
id="g3856"
|
||||
transform="translate(20)">
|
||||
<g
|
||||
id="g3854">
|
||||
<circle
|
||||
cx="116.364"
|
||||
cy="302.55301"
|
||||
r="11.636"
|
||||
id="circle3852"
|
||||
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:20;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
id="g3862">
|
||||
<g
|
||||
id="g3860">
|
||||
<path
|
||||
d="M325.818,290.917H209.455c-6.423,0-11.636,5.213-11.636,11.636s5.213,11.636,11.636,11.636h116.364 c6.435,0,11.636-5.213,11.636-11.636S332.253,290.917,325.818,290.917z"
|
||||
id="path3858" />
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
id="g3868">
|
||||
<g
|
||||
id="g3866">
|
||||
<circle
|
||||
cx="69.818"
|
||||
cy="186.189"
|
||||
r="11.636"
|
||||
id="circle3864"
|
||||
style="stroke-width:20;stroke-miterlimit:4;stroke-dasharray:none;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1" />
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
id="g3874"
|
||||
style="stroke:#000000;stroke-opacity:1"
|
||||
transform="translate(20)">
|
||||
<g
|
||||
id="g3872"
|
||||
style="stroke:#000000;stroke-opacity:1">
|
||||
<circle
|
||||
cx="116.364"
|
||||
cy="186.189"
|
||||
r="11.636"
|
||||
id="circle3870"
|
||||
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:20;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
id="g3880">
|
||||
<g
|
||||
id="g3878">
|
||||
<path
|
||||
d="M372.364,174.553H209.455c-6.423,0-11.636,5.213-11.636,11.636s5.213,11.636,11.636,11.636h162.909 c6.435,0,11.636-5.213,11.636-11.636S378.799,174.553,372.364,174.553z"
|
||||
id="path3876" />
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
id="g3886">
|
||||
<g
|
||||
id="g3884">
|
||||
<path
|
||||
d="M430.545,93.098c6.435,0,11.636-5.213,11.636-11.636V34.917c0-19.247-15.663-34.909-34.909-34.909H34.909 C15.663,0.007,0,15.67,0,34.917v93.091v0.012v69.807c0,6.423,5.213,11.636,11.636,11.636s11.636-5.213,11.636-11.636v-58.17 l395.636-0.012v93.091l-407.273,0.012C5.213,232.746,0,237.959,0,244.383v93.079c0,19.247,15.663,34.909,34.909,34.909h232.727 c6.423,0,11.636-5.213,11.636-11.636s-5.213-11.636-11.636-11.636H34.909c-6.412,0-11.636-5.225-11.636-11.636v-81.443 l395.636-0.012v46.545c0,6.423,5.201,11.636,11.636,11.636s11.636-5.213,11.636-11.636v-58.182V128.007 c0-3.084-1.222-6.051-3.409-8.227c-2.188-2.176-5.132-3.409-8.227-3.409l-407.273,0.012V34.917 c0-6.412,5.225-11.636,11.636-11.636h372.364c6.423,0,11.636,5.225,11.636,11.636v46.545 C418.909,87.885,424.111,93.098,430.545,93.098z"
|
||||
id="path3882" />
|
||||
</g>
|
||||
</g>
|
||||
<path
|
||||
d="m 459.811,414.553 c 9.367,0 18.723,-3.561 25.856,-10.682 0.012,-0.012 0.023,-0.035 0.035,-0.058 0.012,-0.012 0.012,-0.012 0.023,-0.012 l 22.935,-23.273 c 4.515,-4.573 4.468,-11.939 -0.116,-16.454 -4.596,-4.515 -11.951,-4.468 -16.454,0.128 l -22.842,23.18 c -0.012,0.012 -0.023,0.012 -0.035,0.023 -5.178,5.178 -13.615,5.178 -18.793,0 -2.513,-2.502 -3.898,-5.841 -3.898,-9.391 0,-3.549 1.385,-6.889 3.898,-9.391 l 23.215,-22.877 c 4.585,-4.515 4.62,-11.881 0.116,-16.454 -4.492,-4.585 -11.857,-4.62 -16.454,-0.128 l -23.273,22.935 c -0.012,0.012 -0.012,0.023 -0.023,0.035 -0.012,0.012 -0.023,0.012 -0.035,0.023 -6.912,6.912 -10.717,16.093 -10.717,25.856 0,5.737 1.455,11.194 3.933,16.175 l -15.139,15.139 c -3.584,-1.28 -7.389,-1.827 -11.194,-1.676 l -23.668,-23.645 15.046,-15.046 c 4.55,-4.55 4.55,-11.904 0,-16.454 l -23.273,-23.273 c -4.55,-4.55 -11.904,-4.55 -16.454,0 l -46.545,46.545 c -3.119,3.119 -4.201,7.727 -2.804,11.904 l 11.636,34.909 c 1.268,3.828 4.433,6.714 8.367,7.645 0.884,0.209 1.78,0.314 2.665,0.314 3.049,0 6.028,-1.199 8.227,-3.409 l 26.682,-26.682 18.164,18.164 -44.719,44.719 c -5.388,5.388 -8.355,12.544 -8.355,20.154 0,7.61 2.967,14.778 8.355,20.154 5.388,5.388 12.544,8.355 20.154,8.355 7.61,0 14.778,-2.967 20.143,-8.355 l 44.73,-44.719 49.664,49.664 c 2.269,2.269 5.248,3.409 8.227,3.409 2.979,0 5.958,-1.14 8.227,-3.409 4.55,-4.55 4.55,-11.904 0,-16.454 l -54.912,-54.912 c 0.023,-0.489 0.14,-0.954 0.14,-1.455 0,-3.526 -0.698,-6.935 -1.92,-10.135 l 15.023,-15.023 c 5.096,2.529 10.6,3.937 16.162,3.937 z"
|
||||
id="path3888"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="scccccccccscccccccsccccssssscccscccsscsccsssscsccs" />
|
||||
<g
|
||||
id="g3894">
|
||||
</g>
|
||||
<g
|
||||
id="g3896">
|
||||
</g>
|
||||
<g
|
||||
id="g3898">
|
||||
</g>
|
||||
<g
|
||||
id="g3900">
|
||||
</g>
|
||||
<g
|
||||
id="g3902">
|
||||
</g>
|
||||
<g
|
||||
id="g3904">
|
||||
</g>
|
||||
<g
|
||||
id="g3906">
|
||||
</g>
|
||||
<g
|
||||
id="g3908">
|
||||
</g>
|
||||
<g
|
||||
id="g3910">
|
||||
</g>
|
||||
<g
|
||||
id="g3912">
|
||||
</g>
|
||||
<g
|
||||
id="g3914">
|
||||
</g>
|
||||
<g
|
||||
id="g3916">
|
||||
</g>
|
||||
<g
|
||||
id="g3918">
|
||||
</g>
|
||||
<g
|
||||
id="g3920">
|
||||
</g>
|
||||
<g
|
||||
id="g3922">
|
||||
</g>
|
||||
<path
|
||||
style="fill:#000000;fill-opacity:1;stroke-width:0.60166276"
|
||||
d=""
|
||||
id="path4738"
|
||||
inkscape:connector-curvature="0" /></svg>
|
||||
|
After Width: | Height: | Size: 7.6 KiB |
91
kvmd_data/usr/share/kvmd/web/share/svg/kvm.svg
Normal file
@@ -0,0 +1,91 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 512.004 512.004" style="enable-background:new 0 0 512.004 512.004;" xml:space="preserve">
|
||||
<g>
|
||||
<g>
|
||||
<path d="M444.326,0H67.754C50.346,0,36.19,14.16,36.19,31.56L36.106,284.5c0,17.404,14.156,31.5,31.556,31.5h124.336
|
||||
c-0.008,0,0,0.18,0,0.212V348H168.27c-2.208,0-4.272,2.18-4.272,4.388V368c0,2.208,2.064,4,4.272,4h174.78
|
||||
c2.212,0,4.948-1.792,4.948-4v-15.612c0-2.208-2.736-4.388-4.944-4.388h-23.056v-31.788c0-0.032-0.256-0.212-0.264-0.212h124.508
|
||||
c17.408,0,31.564-14.096,31.564-31.496l0.092-252.972C475.898,14.128,461.734,0,444.326,0z M255.934,292.392
|
||||
c-6.616,0-12-5.38-12-11.996c0-6.616,5.384-11.996,12-11.996c6.608,0,11.992,5.38,11.992,11.996
|
||||
C267.926,287.012,262.546,292.392,255.934,292.392z M447.998,248h-384V28h328.008h38.296h17.696V248z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path d="M323.998,403.884c0-6.564-5.32-11.884-11.884-11.884H83.882c-6.564,0-11.884,5.32-11.884,11.884v96.236
|
||||
c0,6.564,5.32,11.884,11.884,11.884h228.236c6.564,0,11.884-5.32,11.884-11.884v-96.236H323.998z M267.778,408h4.632
|
||||
c4.42,0,8,3.58,8,8c0,4.416-3.58,8-8,8h-4.632c-4.42,0-8-3.584-8-8C259.778,411.58,263.358,408,267.778,408z M195.786,408h4.116
|
||||
c4.42,0,8,3.58,8,8c0,4.416-3.58,8-8,8h-4.116c-4.42,0-8-3.584-8-8C187.786,411.58,191.366,408,195.786,408z M195.786,432h4.116
|
||||
c4.42,0,8,3.584,8,8c0,4.42-3.58,8-8,8h-4.116c-4.42,0-8-3.58-8-8C187.786,435.584,191.366,432,195.786,432z M195.786,456h4.116
|
||||
c4.42,0,8,3.584,8,8c0,4.416-3.58,8-8,8h-4.116c-4.42,0-8-3.584-8-8C187.786,459.584,191.366,456,195.786,456z M171.786,408h4.4
|
||||
c4.416,0,8,3.58,8,8c0,4.416-3.584,8-8,8h-4.4c-4.416,0-8-3.584-8-8C163.786,411.58,167.374,408,171.786,408z M171.786,432h4.4
|
||||
c4.416,0,8,3.584,8,8c0,4.42-3.584,8-8,8h-4.4c-4.416,0-8-3.58-8-8C163.786,435.584,167.374,432,171.786,432z M171.786,456h4.4
|
||||
c4.416,0,8,3.584,8,8c0,4.416-3.584,8-8,8h-4.4c-4.416,0-8-3.584-8-8C163.786,459.584,167.374,456,171.786,456z M99.794,408h4.108
|
||||
c4.416,0,8,3.58,8,8c0,4.416-3.584,8-8,8h-4.108c-4.416,0-8-3.584-8-8C91.794,411.58,95.382,408,99.794,408z M99.794,432h4.108
|
||||
c4.416,0,8,3.584,8,8c0,4.42-3.584,8-8,8h-4.108c-4.416,0-8-3.58-8-8C91.794,435.584,95.382,432,99.794,432z M103.906,496h-4.108
|
||||
c-4.416,0-8-3.584-8-8c0-4.416,3.584-8,8-8h4.108c4.416,0,8,3.584,8,8C111.906,492.416,108.318,496,103.906,496z M127.898,496
|
||||
h-4.1c-4.42,0-8-3.584-8-8c0-4.416,3.58-8,8-8h4.1c4.42,0,8,3.584,8,8C135.898,492.416,132.318,496,127.898,496z M127.898,472
|
||||
h-28.1c-4.416,0-8-3.584-8-8c0-4.416,3.584-8,8-8h28.1c4.42,0,8,3.584,8,8C135.898,468.416,132.318,472,127.898,472z M127.898,448
|
||||
h-4.1c-4.42,0-8-3.58-8-8c0-4.416,3.58-8,8-8h4.1c4.42,0,8,3.584,8,8C135.898,444.42,132.318,448,127.898,448z M127.898,424h-4.1
|
||||
c-4.42,0-8-3.584-8-8c0-4.42,3.58-8,8-8h4.1c4.42,0,8,3.58,8,8C135.898,420.416,132.318,424,127.898,424z M151.898,496h-4.1
|
||||
c-4.42,0-8-3.584-8-8c0-4.416,3.58-8,8-8h4.1c4.416,0,8,3.584,8,8C159.898,492.416,156.31,496,151.898,496z M151.898,472h-4.1
|
||||
c-4.42,0-8-3.584-8-8c0-4.416,3.58-8,8-8h4.1c4.416,0,8,3.584,8,8C159.898,468.416,156.31,472,151.898,472z M151.898,448h-4.1
|
||||
c-4.42,0-8-3.58-8-8c0-4.416,3.58-8,8-8h4.1c4.416,0,8,3.584,8,8C159.898,444.42,156.31,448,151.898,448z M151.898,424h-4.1
|
||||
c-4.42,0-8-3.584-8-8c0-4.42,3.58-8,8-8h4.1c4.416,0,8,3.58,8,8C159.898,420.416,156.31,424,151.898,424z M224.246,496h-52.46
|
||||
c-4.416,0-8-3.584-8-8c0-4.416,3.584-8,8-8h52.46c4.416,0,8,3.584,8,8C232.246,492.416,228.662,496,224.246,496z M224.246,472
|
||||
h-4.46c-4.42,0-8-3.584-8-8c0-4.416,3.58-8,8-8h4.46c4.416,0,8,3.584,8,8C232.246,468.416,228.662,472,224.246,472z M224.246,448
|
||||
h-4.46c-4.42,0-8-3.58-8-8c0-4.416,3.58-8,8-8h4.46c4.416,0,8,3.584,8,8C232.246,444.42,228.662,448,224.246,448z M224.246,424
|
||||
h-4.46c-4.42,0-8-3.584-8-8c0-4.42,3.58-8,8-8h4.46c4.416,0,8,3.58,8,8C232.246,420.416,228.662,424,224.246,424z M248.578,496
|
||||
h-4.796c-4.416,0-8-3.584-8-8c0-4.416,3.584-8,8-8h4.796c4.42,0,8,3.584,8,8C256.578,492.416,252.998,496,248.578,496z
|
||||
M248.578,472h-4.796c-4.416,0-8-3.584-8-8c0-4.416,3.584-8,8-8h4.796c4.42,0,8,3.584,8,8
|
||||
C256.578,468.416,252.998,472,248.578,472z M248.578,448h-4.796c-4.416,0-8-3.58-8-8c0-4.416,3.584-8,8-8h4.796
|
||||
c4.42,0,8,3.584,8,8C256.578,444.42,252.998,448,248.578,448z M248.578,424h-4.796c-4.416,0-8-3.584-8-8c0-4.42,3.584-8,8-8h4.796
|
||||
c4.42,0,8,3.58,8,8C256.578,420.416,252.998,424,248.578,424z M267.778,432h4.632c4.42,0,8,3.584,8,8c0,4.42-3.58,8-8,8h-4.632
|
||||
c-4.42,0-8-3.58-8-8C259.778,435.584,263.358,432,267.778,432z M272.414,496h-4.632c-4.42,0-8-3.584-8-8c0-4.416,3.58-8,8-8h4.632
|
||||
c4.42,0,8,3.584,8,8C280.414,492.416,276.834,496,272.414,496z M295.882,496h-4.1c-4.42,0-8-3.584-8-8c0-4.416,3.58-8,8-8h4.1
|
||||
c4.42,0,8,3.584,8,8C303.882,492.416,300.302,496,295.882,496z M295.882,472h-28.1c-4.42,0-8-3.584-8-8c0-4.42,3.58-8,8-8h28.1
|
||||
c4.42,0,8,3.58,8,8C303.882,468.416,300.302,472,295.882,472z M296.622,448h-4.844c-4.42,0-8-3.58-8-8c0-4.416,3.58-8,8-8h4.844
|
||||
c4.416,0,8,3.584,8,8C304.622,444.42,301.038,448,296.622,448z M296.622,424h-4.844c-4.42,0-8-3.584-8-8c0-4.42,3.58-8,8-8h4.844
|
||||
c4.416,0,8,3.58,8,8C304.622,420.416,301.038,424,296.622,424z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path d="M394.442,400h-6.188c-18.14,0-32.256,15.26-32.256,34.092v44.316c0,18.836,14.116,33.588,32.256,33.588h6.188
|
||||
c18.516,0,33.556-15.068,33.556-33.588v-44.316C427.998,415.576,412.958,400,394.442,400z M399.998,432.336c0,4.416-3.584,8-8,8
|
||||
c-4.42,0-8-3.584-8-8v-8.668c0-4.42,3.58-8,8-8c4.416,0,8,3.58,8,8V432.336z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.8 KiB |
82
kvmd_data/usr/share/kvmd/web/share/svg/led-atx-hdd.svg
Normal file
@@ -0,0 +1,82 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="16.933332mm"
|
||||
height="16.933332mm"
|
||||
viewBox="0 0 16.933332 16.933332"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
sodipodi:docname="hdd.svg"
|
||||
inkscape:version="0.92.2 2405546, 2018-03-11">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="5.6"
|
||||
inkscape:cx="13.28418"
|
||||
inkscape:cy="14.886884"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1020"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="30"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-107.87666,-24.618942)">
|
||||
<flowRoot
|
||||
xml:space="preserve"
|
||||
id="flowRoot5064"
|
||||
style="fill:black;stroke:none;stroke-opacity:1;stroke-width:1px;stroke-linejoin:miter;stroke-linecap:butt;fill-opacity:1;font-family:Adobe Garamond Pro;font-style:normal;font-weight:bold;font-size:10px;line-height:125%;letter-spacing:0px;word-spacing:0px;-inkscape-font-specification:Adobe Garamond Pro Bold;font-stretch:normal;font-variant:normal;text-anchor:start;text-align:start;writing-mode:lr"><flowRegion
|
||||
id="flowRegion5066"><rect
|
||||
id="rect5068"
|
||||
width="50.892857"
|
||||
height="43.57143"
|
||||
x="-72.321426"
|
||||
y="5.4285674" /></flowRegion><flowPara
|
||||
id="flowPara5070"></flowPara></flowRoot> <g
|
||||
aria-label="🖴"
|
||||
transform="matrix(1.8649046,0,0,1.8649046,248.12652,14.45492)"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:10px;line-height:125%;font-family:'Adobe Garamond Pro';-inkscape-font-specification:'Adobe Garamond Pro Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.141875px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
id="flowRoot5072">
|
||||
<path
|
||||
d="m -75.064844,9.6551562 1.16,-2.35 q 0.07,-0.1299999 0.14,-0.24 0.08,-0.12 0.24,-0.12 h 5.72 q 0.16,0 0.23,0.12 0.08,0.1100001 0.15,0.24 l 1.16,2.35 z m 4.4,-0.4 q 0.62,0 1.12,-0.1399999 0.51,-0.14 0.8,-0.37 0.3,-0.23 0.3,-0.5100001 0,-0.28 -0.3,-0.5 -0.29,-0.23 -0.8,-0.36 -0.5,-0.14 -1.12,-0.14 -0.8,0 -1.41,0.23 -0.6,0.23 -0.77,0.5800001 l 1.32,0.01 -0.03,0.21 -1.31,0.11 q 0.07,0.24 0.37,0.44 0.31,0.2 0.78,0.32 0.48,0.1199999 1.05,0.1199999 z m 0,-0.7899999 q -0.24,0 -0.42,-0.08 -0.17,-0.09 -0.17,-0.2 0,-0.1100001 0.17,-0.1900001 0.18,-0.08 0.42,-0.08 0.25,0 0.43,0.08 0.18,0.08 0.18,0.1900001 0,0.11 -0.18,0.2 -0.18,0.08 -0.43,0.08 z m -4.02,4.5699997 q -0.22,0 -0.37,-0.15 -0.15,-0.16 -0.15,-0.37 V 9.9851562 h 9.08 v 2.5299998 q 0,0.21 -0.15,0.37 -0.14,0.15 -0.37,0.15 z m -0.05,-2.05 h 8.14 v -0.47 h -8.14 z m 7.8,1.08 q 0.13,0 0.21,-0.09 0.08,-0.09 0.08,-0.22 0,-0.11 -0.08,-0.2 -0.08,-0.09 -0.21,-0.09 -0.13,0 -0.22,0.09 -0.09,0.09 -0.09,0.2 0,0.13 0.09,0.22 0.09,0.09 0.22,0.09 z"
|
||||
style="stroke-width:0.141875px"
|
||||
id="path5080"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.1 KiB |
71
kvmd_data/usr/share/kvmd/web/share/svg/led-atx-power.svg
Normal file
@@ -0,0 +1,71 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="16.933332mm"
|
||||
height="16.933332mm"
|
||||
viewBox="0 0 16.933332 16.933332"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
sodipodi:docname="power.svg"
|
||||
inkscape:version="0.92.2 2405546, 2018-03-11">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="5.6"
|
||||
inkscape:cx="13.28418"
|
||||
inkscape:cy="14.886884"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1020"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="30"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-107.87666,-24.618942)">
|
||||
<g
|
||||
aria-label="⏻"
|
||||
transform="matrix(1.9608076,0,0,1.9608076,269.69365,18.4972)"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:10px;line-height:125%;font-family:'Adobe Garamond Pro';-inkscape-font-specification:'Adobe Garamond Pro Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.21589744;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="flowRoot4958">
|
||||
<path
|
||||
d="M -78.747734,7.26 V 3.23 h 1.04 v 4.03 z m 0.54,4.39 q -0.87,0 -1.6,-0.3 -0.73,-0.29 -1.27,-0.82 -0.54,-0.53 -0.84,-1.24 -0.3,-0.72 -0.3,-1.57 0,-0.96 0.4,-1.84 0.4,-0.89 1.18,-1.45 l 0.54,0.7 q -0.43,0.29 -0.71,0.72 -0.27,0.43 -0.41,0.92 -0.13,0.49 -0.13,0.95 0,0.64 0.23,1.2 0.23,0.56 0.65,0.98 0.43,0.42 1,0.66 0.57,0.23 1.25,0.23 0.69,0 1.26,-0.23 0.58,-0.24 1,-0.66 0.42,-0.42 0.65,-0.98 0.24,-0.56 0.24,-1.2 0,-0.46 -0.14,-0.95 -0.13,-0.49 -0.41,-0.92 -0.27,-0.43 -0.7,-0.72 l 0.54,-0.7 q 0.78,0.56 1.18,1.45 0.4,0.88 0.4,1.84 0,0.85 -0.3,1.57 -0.3,0.71 -0.84,1.24 -0.54,0.53 -1.28,0.82 -0.73,0.3 -1.59,0.3 z"
|
||||
style="stroke:#000000;stroke-width:0.21589744;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path4998" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.1 KiB |
128
kvmd_data/usr/share/kvmd/web/share/svg/led-circle.svg
Normal file
@@ -0,0 +1,128 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
height="481.1192"
|
||||
width="481.1192"
|
||||
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
|
||||
sodipodi:docname="led-circle.svg"
|
||||
xml:space="preserve"
|
||||
viewBox="0 0 481.02523 481.1192"
|
||||
y="0px"
|
||||
x="0px"
|
||||
id="Layer_1"
|
||||
version="1.1"><metadata
|
||||
id="metadata85"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs83" /><sodipodi:namedview
|
||||
fit-margin-bottom="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-top="0"
|
||||
lock-margins="false"
|
||||
inkscape:document-rotation="0"
|
||||
inkscape:current-layer="Layer_1"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:window-y="28"
|
||||
inkscape:window-x="0"
|
||||
inkscape:cy="226.75611"
|
||||
inkscape:cx="207.75746"
|
||||
inkscape:zoom="0.921875"
|
||||
showgrid="false"
|
||||
id="namedview81"
|
||||
inkscape:window-height="714"
|
||||
inkscape:window-width="1366"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0"
|
||||
guidetolerance="10"
|
||||
gridtolerance="10"
|
||||
objecttolerance="10"
|
||||
borderopacity="1"
|
||||
bordercolor="#666666"
|
||||
pagecolor="#ffffff" />
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<g
|
||||
transform="translate(-15.683731,-12.674362)"
|
||||
id="g50">
|
||||
</g>
|
||||
<g
|
||||
transform="translate(-15.683731,-12.674362)"
|
||||
id="g52">
|
||||
</g>
|
||||
<g
|
||||
transform="translate(-15.683731,-12.674362)"
|
||||
id="g54">
|
||||
</g>
|
||||
<g
|
||||
transform="translate(-15.683731,-12.674362)"
|
||||
id="g56">
|
||||
</g>
|
||||
<g
|
||||
transform="translate(-15.683731,-12.674362)"
|
||||
id="g58">
|
||||
</g>
|
||||
<g
|
||||
transform="translate(-15.683731,-12.674362)"
|
||||
id="g60">
|
||||
</g>
|
||||
<g
|
||||
transform="translate(-15.683731,-12.674362)"
|
||||
id="g62">
|
||||
</g>
|
||||
<g
|
||||
transform="translate(-15.683731,-12.674362)"
|
||||
id="g64">
|
||||
</g>
|
||||
<g
|
||||
transform="translate(-15.683731,-12.674362)"
|
||||
id="g66">
|
||||
</g>
|
||||
<g
|
||||
transform="translate(-15.683731,-12.674362)"
|
||||
id="g68">
|
||||
</g>
|
||||
<g
|
||||
transform="translate(-15.683731,-12.674362)"
|
||||
id="g70">
|
||||
</g>
|
||||
<g
|
||||
transform="translate(-15.683731,-12.674362)"
|
||||
id="g72">
|
||||
</g>
|
||||
<g
|
||||
transform="translate(-15.683731,-12.674362)"
|
||||
id="g74">
|
||||
</g>
|
||||
<g
|
||||
transform="translate(-15.683731,-12.674362)"
|
||||
id="g76">
|
||||
</g>
|
||||
<g
|
||||
transform="translate(-15.683731,-12.674362)"
|
||||
id="g78">
|
||||
</g>
|
||||
<rect
|
||||
y="45.901909"
|
||||
x="10.350166"
|
||||
height="351.45764"
|
||||
width="478.37289"
|
||||
id="rect4830"
|
||||
style="opacity:1;fill:none;fill-opacity:1;paint-order:normal" /><circle
|
||||
r="240.5596"
|
||||
cy="240.5596"
|
||||
cx="240.51262"
|
||||
id="path857"
|
||||
style="fill:#000000" /></svg>
|
||||
|
After Width: | Height: | Size: 2.8 KiB |
119
kvmd_data/usr/share/kvmd/web/share/svg/led-fan.svg
Normal file
@@ -0,0 +1,119 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 18.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.1"
|
||||
id="Capa_1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 63.999999 63.999714"
|
||||
xml:space="preserve"
|
||||
sodipodi:docname="fan.svg"
|
||||
width="64"
|
||||
height="63.999714"
|
||||
inkscape:version="0.92.2 2405546, 2018-03-11"><metadata
|
||||
id="metadata4808"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs4806">
|
||||
|
||||
|
||||
|
||||
|
||||
</defs><sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1020"
|
||||
id="namedview4804"
|
||||
showgrid="false"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
inkscape:zoom="7.3853375"
|
||||
inkscape:cx="69.858273"
|
||||
inkscape:cy="30.334855"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="30"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="Capa_1" />
|
||||
<path
|
||||
d="m 63.334922,25.549682 c -0.177154,-0.86073 -0.546488,-1.4749 -1.224053,-2.03456 -5.414798,-4.47493 -12.924109,-4.17414 -22.52822,0.90223 -0.626766,-0.62676 -1.30519,-1.15651 -2.065465,-1.6127 6.619247,-5.93839 12.653083,-8.43143 18.101079,-7.47955 1.606266,0.28061 2.718561,-1.5599 1.72332,-2.85136 -2.208992,-2.8669502 -4.723066,-5.2011502 -7.745565,-7.1919202 -0.734089,-0.48338 -1.429398,-0.65624 -2.304294,-0.5731 -6.993017,0.66469 -12.090282,6.1872302 -15.291795,16.5677902 -0.886487,0 -1.740778,0.10503 -2.600792,0.32011 0.481522,-8.87947 2.985008,-14.9088702 7.510599,-18.0882002 1.334239,-0.93729 0.819232,-3.02521996 -0.797623,-3.23499996 -3.589307,-0.46521 -7.017486,-0.33814 -10.562433,0.39166 -0.86073,0.17715 -1.474903,0.54648996 -2.034556,1.22404996 -4.474935,5.4148 -4.174145,12.9241102 0.902228,22.5282202 -0.626766,0.62677 -1.156512,1.30519 -1.612706,2.06547 -5.93839,-6.61925 -8.431429,-12.65309 -7.479547,-18.1010802 0.280614,-1.60627 -1.559902,-2.71856 -2.851354,-1.72347 -2.8669526,2.209 -5.201154,4.7230702 -7.1919221,7.7455702 -0.4833823,0.73409 -0.6562437,1.4294 -0.5731043,2.30429 0.6646865,6.99302 6.1872364,12.09029 16.5677934,15.2918 0,0.88649 0.105033,1.74078 0.320108,2.60079 -8.879467,-0.48152 -14.9088672,-2.98501 -18.0882006,-7.5106 -0.9372866,-1.33424 -3.02521772,-0.81923 -3.2349982,0.79762 -0.46520897,3.58931 -0.33813866,7.01749 0.391657,10.56244 0.17715433,0.86073 0.5464882,1.4749 1.2240534,2.03455 5.4147977,4.47494 12.9241094,4.17415 22.5282204,-0.90222 0.626766,0.62676 1.305189,1.15651 2.065465,1.6127 -6.619247,5.93839 -12.653083,8.43143 -18.1010797,7.47955 -1.6062659,-0.28062 -2.7185604,1.5599 -1.7233194,2.85135 2.2089913,2.86695 4.7230661,5.20116 7.7455651,7.19192 0.734088,0.48339 1.429398,0.65625 2.304294,0.57311 6.993016,-0.66469 12.090281,-6.18724 15.291795,-16.56779 0.886487,0 1.740777,-0.10504 2.600791,-0.32026 -0.481522,8.87947 -2.985007,14.90887 -7.510599,18.0882 -1.334238,0.93729 -0.819231,3.02522 0.797624,3.235 3.589307,0.46521 7.017486,0.33814 10.562433,-0.39165 0.860729,-0.17716 1.474903,-0.54649 2.034556,-1.22406 4.474935,-5.4148 4.174145,-12.92411 -0.902228,-22.52822 0.626766,-0.62676 1.156511,-1.30519 1.612705,-2.06546 5.938391,6.61924 8.43143,12.65308 7.479547,18.10108 -0.280613,1.60626 1.559903,2.71856 2.851355,1.72332 2.866952,-2.20899 5.201153,-4.72307 7.191922,-7.74557 0.483382,-0.73409 0.656243,-1.4294 0.573104,-2.30429 -0.664687,-6.99302 -6.187237,-12.09028 -16.567793,-15.2918 0,-0.88648 -0.105033,-1.74077 -0.320108,-2.60079 8.879467,0.48152 14.908867,2.98501 18.0882,7.5106 0.937287,1.33424 3.025218,0.81923 3.234998,-0.79762 0.465209,-3.58917 0.338139,-7.01706 -0.391657,-10.56215 z m -31.33485,15.38653 c -4.935279,0 -8.936277,-4.001 -8.936277,-8.93628 0,-4.93528 4.000998,-8.93628 8.936277,-8.93628 4.935279,0 8.936276,4.001 8.936276,8.93628 0,4.93528 -4.000997,8.93628 -8.936276,8.93628 z"
|
||||
id="path4768"
|
||||
inkscape:connector-curvature="0"
|
||||
style="stroke-width:1" />
|
||||
<g
|
||||
id="g4773"
|
||||
transform="translate(-82.375262,-465.62302)">
|
||||
</g>
|
||||
<g
|
||||
id="g4775"
|
||||
transform="translate(-82.375262,-465.62302)">
|
||||
</g>
|
||||
<g
|
||||
id="g4777"
|
||||
transform="translate(-82.375262,-465.62302)">
|
||||
</g>
|
||||
<g
|
||||
id="g4779"
|
||||
transform="translate(-82.375262,-465.62302)">
|
||||
</g>
|
||||
<g
|
||||
id="g4781"
|
||||
transform="translate(-82.375262,-465.62302)">
|
||||
</g>
|
||||
<g
|
||||
id="g4783"
|
||||
transform="translate(-82.375262,-465.62302)">
|
||||
</g>
|
||||
<g
|
||||
id="g4785"
|
||||
transform="translate(-82.375262,-465.62302)">
|
||||
</g>
|
||||
<g
|
||||
id="g4787"
|
||||
transform="translate(-82.375262,-465.62302)">
|
||||
</g>
|
||||
<g
|
||||
id="g4789"
|
||||
transform="translate(-82.375262,-465.62302)">
|
||||
</g>
|
||||
<g
|
||||
id="g4791"
|
||||
transform="translate(-82.375262,-465.62302)">
|
||||
</g>
|
||||
<g
|
||||
id="g4793"
|
||||
transform="translate(-82.375262,-465.62302)">
|
||||
</g>
|
||||
<g
|
||||
id="g4795"
|
||||
transform="translate(-82.375262,-465.62302)">
|
||||
</g>
|
||||
<g
|
||||
id="g4797"
|
||||
transform="translate(-82.375262,-465.62302)">
|
||||
</g>
|
||||
<g
|
||||
id="g4799"
|
||||
transform="translate(-82.375262,-465.62302)">
|
||||
</g>
|
||||
<g
|
||||
id="g4801"
|
||||
transform="translate(-82.375262,-465.62302)">
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.6 KiB |
151
kvmd_data/usr/share/kvmd/web/share/svg/led-gear.svg
Normal file
@@ -0,0 +1,151 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.1"
|
||||
id="Layer_1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 521.99903 521.99702"
|
||||
xml:space="preserve"
|
||||
sodipodi:docname="gear-led-2.svg"
|
||||
inkscape:version="0.92.2 2405546, 2018-03-11"
|
||||
width="521.99902"
|
||||
height="521.99701"><metadata
|
||||
id="metadata49"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs47">
|
||||
|
||||
</defs><sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1020"
|
||||
id="namedview45"
|
||||
showgrid="false"
|
||||
inkscape:zoom="0.92187681"
|
||||
inkscape:cx="199.23524"
|
||||
inkscape:cy="241.2828"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="30"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="Layer_1"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0" />
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<g
|
||||
id="g4810"><g
|
||||
transform="translate(5,5)"
|
||||
style="stroke:#000000;stroke-width:10;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="g4">
|
||||
<path
|
||||
style="stroke:#000000;stroke-width:10;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 489.175,206.556 c -9.629,-1.442 -19.514,-2.825 -29.379,-4.111 -1.195,-0.155 -2.165,-0.966 -2.467,-2.064 -4.553,-16.523 -11.16,-32.467 -19.636,-47.389 -0.57,-1.002 -0.463,-2.266 0.273,-3.223 6.067,-7.885 12.081,-15.856 17.876,-23.69 7.824,-10.578 6.688,-25.588 -2.64,-34.917 L 420.836,58.796 c -9.329,-9.328 -24.338,-10.464 -34.918,-2.638 -7.817,5.782 -15.787,11.796 -23.689,17.875 -0.954,0.736 -2.221,0.843 -3.223,0.274 -14.921,-8.476 -30.865,-15.083 -47.389,-19.637 -1.099,-0.301 -1.91,-1.271 -2.066,-2.469 -1.289,-9.88 -2.671,-19.764 -4.109,-29.376 C 303.495,9.812 292.079,0 278.886,0 h -45.773 c -13.194,0 -24.61,9.812 -26.554,22.824 -1.439,9.614 -2.821,19.497 -4.11,29.379 -0.157,1.197 -0.967,2.165 -2.067,2.467 -16.524,4.556 -32.469,11.162 -47.387,19.637 -1.003,0.569 -2.269,0.459 -3.225,-0.274 C 141.869,67.954 133.898,61.94 126.08,56.157 115.499,48.332 100.49,49.47 91.163,58.797 L 58.797,91.163 c -9.329,9.33 -10.464,24.341 -2.638,34.918 5.804,7.846 11.818,15.815 17.875,23.688 0.735,0.955 0.843,2.22 0.274,3.223 -8.478,14.925 -15.084,30.869 -19.637,47.389 -0.301,1.097 -1.271,1.908 -2.467,2.065 -9.86,1.287 -19.744,2.669 -29.378,4.111 C 9.812,208.502 0,219.92 0,233.112 v 45.774 c 0,13.193 9.812,24.61 22.824,26.556 9.634,1.442 19.519,2.824 29.379,4.11 1.197,0.157 2.165,0.967 2.467,2.066 4.553,16.521 11.16,32.465 19.637,47.389 0.569,1.003 0.461,2.268 -0.274,3.223 -6.072,7.892 -12.086,15.862 -17.875,23.689 -7.825,10.578 -6.691,25.589 2.638,34.918 l 32.366,32.366 c 9.33,9.329 24.341,10.465 34.918,2.638 7.817,-5.782 15.787,-11.796 23.689,-17.875 0.955,-0.736 2.221,-0.842 3.223,-0.274 14.92,8.476 30.863,15.081 47.389,19.637 1.099,0.302 1.91,1.271 2.066,2.467 1.289,9.88 2.672,19.765 4.11,29.376 1.946,13.013 13.362,22.825 26.556,22.825 h 45.773 c 13.193,0 24.61,-9.812 26.555,-22.827 1.439,-9.623 2.821,-19.507 4.109,-29.376 0.157,-1.197 0.967,-2.166 2.066,-2.469 16.524,-4.556 32.469,-11.162 47.388,-19.637 1.003,-0.567 2.268,-0.459 3.224,0.274 7.901,6.079 15.872,12.093 23.689,17.875 10.578,7.825 25.588,6.691 34.918,-2.638 l 32.366,-32.366 c 9.328,-9.329 10.464,-24.339 2.639,-34.918 -5.795,-7.831 -11.81,-15.802 -17.876,-23.689 -0.735,-0.955 -0.843,-2.22 -0.273,-3.223 8.477,-14.924 15.083,-30.868 19.636,-47.388 0.304,-1.1 1.272,-1.91 2.469,-2.067 9.863,-1.286 19.748,-2.669 29.378,-4.11 13.013,-1.945 22.825,-13.362 22.825,-26.555 v -45.774 c 0,-13.19 -9.812,-24.608 -22.824,-26.553 z m -1.084,72.332 c 0,1.45 -1.054,2.7 -2.453,2.911 -9.482,1.419 -19.216,2.779 -28.932,4.048 -10.758,1.402 -19.56,9.024 -22.426,19.42 -4.029,14.618 -9.875,28.727 -17.375,41.932 -5.333,9.389 -4.504,21.012 2.112,29.612 5.976,7.768 11.899,15.617 17.604,23.329 0.842,1.137 0.702,2.769 -0.323,3.794 L 403.931,436.3 c -1.026,1.026 -2.657,1.163 -3.793,0.324 -7.697,-5.695 -15.548,-11.618 -23.33,-17.605 -8.599,-6.617 -20.221,-7.446 -29.609,-2.114 -13.205,7.5 -27.314,13.347 -41.934,17.377 -10.394,2.865 -18.016,11.667 -19.421,22.426 -1.267,9.722 -2.629,19.456 -4.047,28.932 -0.209,1.399 -1.461,2.453 -2.911,2.453 h -45.773 c -1.45,0 -2.702,-1.054 -2.911,-2.454 -1.415,-9.465 -2.778,-19.199 -4.047,-28.93 -1.403,-10.759 -9.027,-19.561 -19.421,-22.426 -14.621,-4.03 -28.73,-9.877 -41.934,-17.378 -4.117,-2.337 -8.664,-3.491 -13.196,-3.491 -5.804,0 -11.585,1.89 -16.412,5.607 -7.783,5.987 -15.633,11.91 -23.33,17.605 -1.138,0.839 -2.767,0.702 -3.792,-0.324 L 75.703,403.936 c -1.026,-1.026 -1.166,-2.656 -0.324,-3.793 5.701,-7.707 11.623,-15.556 17.604,-23.33 6.615,-8.6 7.445,-20.221 2.114,-29.609 C 87.594,333.995 81.749,319.887 77.72,305.27 74.855,294.876 66.053,287.253 55.295,285.85 c -9.712,-1.267 -19.447,-2.63 -28.934,-4.048 -1.399,-0.21 -2.453,-1.461 -2.453,-2.911 v -45.774 c 0,-1.45 1.054,-2.701 2.453,-2.911 9.487,-1.419 19.221,-2.781 28.932,-4.048 10.759,-1.402 19.561,-9.025 22.426,-19.42 4.027,-14.616 9.874,-28.725 17.377,-41.934 5.332,-9.389 4.502,-21.011 -2.113,-29.609 -5.965,-7.756 -11.888,-15.604 -17.604,-23.33 -0.84,-1.137 -0.701,-2.769 0.324,-3.793 l 32.365,-32.367 c 1.024,-1.026 2.655,-1.163 3.792,-0.324 7.697,5.694 15.547,11.617 23.33,17.605 8.6,6.614 20.221,7.445 29.611,2.112 13.203,-7.5 27.312,-13.347 41.932,-17.377 10.395,-2.865 18.019,-11.667 19.422,-22.426 1.27,-9.731 2.631,-19.465 4.048,-28.933 0.209,-1.397 1.461,-2.452 2.911,-2.452 h 45.773 c 1.45,0 2.702,1.054 2.911,2.453 1.417,9.465 2.778,19.198 4.048,28.932 1.403,10.759 9.027,19.561 19.421,22.426 14.62,4.03 28.728,9.877 41.934,17.377 9.388,5.33 21.01,4.502 29.608,-2.114 7.783,-5.987 15.633,-11.91 23.329,-17.604 1.137,-0.842 2.769,-0.703 3.794,0.324 l 32.366,32.366 c 1.026,1.026 1.164,2.657 0.324,3.793 -5.705,7.714 -11.628,15.562 -17.604,23.33 -6.615,8.601 -7.445,20.223 -2.112,29.612 7.501,13.205 13.347,27.313 17.377,41.933 2.865,10.394 11.669,18.016 22.424,19.418 9.716,1.268 19.451,2.63 28.934,4.048 1.399,0.21 2.453,1.461 2.453,2.911 v 45.773 z"
|
||||
id="path2"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g><g
|
||||
transform="translate(5,5)"
|
||||
style="stroke:#000000;stroke-width:10;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="g12">
|
||||
<g
|
||||
style="stroke:#000000;stroke-width:10;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="g10">
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
style="stroke:#000000;stroke-width:10;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path8"
|
||||
d="m 256,144.866 c -61.28,0 -111.134,49.854 -111.134,111.134 0,61.28 49.854,111.134 111.134,111.134 61.28,0 111.134,-49.854 111.134,-111.134 0,-61.28 -49.854,-111.134 -111.134,-111.134 z m 0,198.359 c -48.097,0 -87.225,-39.129 -87.225,-87.225 0,-48.097 39.13,-87.225 87.225,-87.225 48.096,0 87.225,39.129 87.225,87.225 0,48.096 -39.128,87.225 -87.225,87.225 z" />
|
||||
</g>
|
||||
</g><g
|
||||
transform="translate(5,5)"
|
||||
style="stroke:#000000;stroke-width:10;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="g14">
|
||||
</g><g
|
||||
transform="translate(5,5)"
|
||||
style="stroke:#000000;stroke-width:10;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="g16">
|
||||
</g><g
|
||||
transform="translate(5,5)"
|
||||
style="stroke:#000000;stroke-width:10;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="g18">
|
||||
</g><g
|
||||
transform="translate(5,5)"
|
||||
style="stroke:#000000;stroke-width:10;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="g20">
|
||||
</g><g
|
||||
transform="translate(5,5)"
|
||||
style="stroke:#000000;stroke-width:10;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="g22">
|
||||
</g><g
|
||||
transform="translate(5,5)"
|
||||
style="stroke:#000000;stroke-width:10;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="g24">
|
||||
</g><g
|
||||
transform="translate(5,5)"
|
||||
style="stroke:#000000;stroke-width:10;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="g26">
|
||||
</g><g
|
||||
transform="translate(5,5)"
|
||||
style="stroke:#000000;stroke-width:10;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="g28">
|
||||
</g><g
|
||||
transform="translate(5,5)"
|
||||
style="stroke:#000000;stroke-width:10;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="g30">
|
||||
</g><g
|
||||
transform="translate(5,5)"
|
||||
style="stroke:#000000;stroke-width:10;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="g32">
|
||||
</g><g
|
||||
transform="translate(5,5)"
|
||||
style="stroke:#000000;stroke-width:10;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="g34">
|
||||
</g><g
|
||||
transform="translate(5,5)"
|
||||
style="stroke:#000000;stroke-width:10;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="g36">
|
||||
</g><g
|
||||
transform="translate(5,5)"
|
||||
style="stroke:#000000;stroke-width:10;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="g38">
|
||||
</g><g
|
||||
transform="translate(5,5)"
|
||||
style="stroke:#000000;stroke-width:10;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="g40">
|
||||
</g><g
|
||||
transform="translate(5,5)"
|
||||
style="stroke:#000000;stroke-width:10;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="g42">
|
||||
</g></g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 9.8 KiB |
71
kvmd_data/usr/share/kvmd/web/share/svg/led-hid-keyboard.svg
Normal file
|
After Width: | Height: | Size: 10 KiB |
71
kvmd_data/usr/share/kvmd/web/share/svg/led-hid-mouse.svg
Normal file
@@ -0,0 +1,71 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="16.933332mm"
|
||||
height="16.933332mm"
|
||||
viewBox="0 0 16.933332 16.933332"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
sodipodi:docname="mouse.svg"
|
||||
inkscape:version="0.92.2 2405546, 2018-03-11">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="5.6"
|
||||
inkscape:cx="13.28418"
|
||||
inkscape:cy="14.886884"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1020"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="30"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-107.87666,-24.618942)">
|
||||
<g
|
||||
aria-label="🖰"
|
||||
transform="matrix(2.0925222,0,0,2.0925222,225.46869,4.2347952)"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:10px;line-height:125%;font-family:'Adobe Garamond Pro';-inkscape-font-specification:'Adobe Garamond Pro Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.20230769;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="flowRoot4916">
|
||||
<path
|
||||
d="m -52.145156,17.732578 q -1.41,0 -2.15,-0.75 -0.73,-0.76 -0.73,-2.21 v -0.46 l 0.13,-2.72 q 0.04,-0.91 0.39,-1.33 0.36,-0.4199999 1.08,-0.4199999 0.2,0 0.44,0.03 l 2.48,0.2499999 q 0.55,0.05 0.79,0.35 0.25,0.29 0.28,0.9 l 0.16,3.14 v 0.29 q 0,1.42 -0.74,2.18 -0.73,0.75 -2.13,0.75 z m -2.48,-5.45 q 0.35,-0.04 0.71,-0.05 0.37,-0.02 0.73,-0.02 0.28,0 0.55,0.01 0.28,0.01 0.55,0.03 v -2 l -0.93,-0.09 q -0.11,-0.01 -0.22,-0.02 -0.1,-0.01 -0.19,-0.01 -0.59,0 -0.86,0.34 -0.27,0.33 -0.31,1.13 z m 4.96,0.39 -0.07,-1.29 q -0.03,-0.5 -0.21,-0.71 -0.17,-0.21 -0.6,-0.26 l -1.26,-0.13 v 2 q 0.56,0.05 1.1,0.16 0.55,0.1 1.04,0.23 z m -2.47,4.77 q 2.56,0 2.56,-2.64 v -0.27 l -0.08,-1.55 q -0.89,-0.24 -1.75,-0.35 -0.86,-0.12 -1.73,-0.12 -0.38,0 -0.75,0.02 -0.37,0.02 -0.76,0.06 l -0.08,1.71 v 0.47 q 0,2.67 2.59,2.67 z"
|
||||
style="stroke:#000000;stroke-width:0.20230769;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path5033" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.3 KiB |
87
kvmd_data/usr/share/kvmd/web/share/svg/led-link.svg
Normal file
@@ -0,0 +1,87 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
|
||||
<g>
|
||||
<g>
|
||||
<path d="M168,184.036c-6.632,0-12,5.376-12,12v23.752c0,6.628,5.368,12,12,12c6.624,0,12-5.372,12-12v-23.752
|
||||
C180,189.412,174.624,184.036,168,184.036z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path d="M256,184.036c-6.632,0-12,5.376-12,12v23.752c0,6.628,5.368,12,12,12c6.624,0,12-5.372,12-12v-23.752
|
||||
C268,189.412,262.624,184.036,256,184.036z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path d="M212,184.036c-6.632,0-12,5.376-12,12v23.752c0,6.628,5.368,12,12,12c6.624,0,12-5.372,12-12v-23.752
|
||||
C224,189.412,218.624,184.036,212,184.036z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path d="M460,0H52C23.28,0,0,23.28,0,52v408c0,28.72,23.28,52,52,52h408c28.72,0,52-23.28,52-52V52C512,23.28,488.72,0,460,0z
|
||||
M444,284h-88.024c-2.212,0-3.976,1.64-3.976,3.848V348H160v-60.152c0-2.208-1.616-3.848-3.828-3.848H68V68h44v92.184
|
||||
c0,6.624,5.368,12,12,12c6.624,0,12-5.376,12-12V68h20v92.184c0,6.624,5.368,12,12,12c6.624,0,12-5.376,12-12V68h20v92.184
|
||||
c0,6.624,5.368,12,12,12c6.624,0,12-5.376,12-12V68h20v92.184c0,6.624,5.368,12,12,12c6.624,0,12-5.376,12-12V68h20v92.184
|
||||
c0,6.624,5.368,12,12,12c6.624,0,12-5.376,12-12V68h20v92.184c0,6.624,5.368,12,12,12c6.624,0,12-5.376,12-12V68h20v92.184
|
||||
c0,6.624,5.368,12,12,12c6.624,0,12-5.376,12-12V68h44V284z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path d="M124,184.036c-6.632,0-12,5.376-12,12v23.752c0,6.628,5.368,12,12,12c6.624,0,12-5.372,12-12v-23.752
|
||||
C136,189.412,130.624,184.036,124,184.036z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path d="M388,184.036c-6.632,0-12,5.376-12,12v23.752c0,6.628,5.368,12,12,12c6.624,0,12-5.372,12-12v-23.752
|
||||
C400,189.412,394.624,184.036,388,184.036z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path d="M344,184.036c-6.632,0-12,5.376-12,12v23.752c0,6.628,5.368,12,12,12c6.624,0,12-5.372,12-12v-23.752
|
||||
C356,189.412,350.624,184.036,344,184.036z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path d="M300,184.036c-6.632,0-12,5.376-12,12v23.752c0,6.628,5.368,12,12,12c6.624,0,12-5.372,12-12v-23.752
|
||||
C312,189.412,306.624,184.036,300,184.036z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |