summaryrefslogtreecommitdiff
path: root/web
diff options
context:
space:
mode:
authorMaxim Devaev <[email protected]>2024-07-08 03:41:29 +0300
committerMaxim Devaev <[email protected]>2024-12-17 18:20:04 +0200
commit630610bc532299f15ff7ee12d40f617de450aae0 (patch)
treeca0a83f1aa5848a4605034c0394f1edfd0bea7ce /web
parente0bbf6968ef8295274793a564e717f95f42983d7 (diff)
switch
Diffstat (limited to 'web')
-rw-r--r--web/kvm/index.html208
-rw-r--r--web/kvm/navbar-shortcuts.pug2
-rw-r--r--web/kvm/navbar-switch.pug19
-rw-r--r--web/kvm/navbar-system.pug2
-rw-r--r--web/kvm/navbar.pug1
-rw-r--r--web/kvm/window-keyboard.pug2
-rw-r--r--web/kvm/window-switch.pug95
-rw-r--r--web/kvm/windows.pug1
-rw-r--r--web/login/index.html2
-rw-r--r--web/login/index.pug2
-rw-r--r--web/share/css/kvm/msd.css4
-rw-r--r--web/share/css/main.css56
-rw-r--r--web/share/css/modal.css2
-rw-r--r--web/share/css/navbar.css1
-rw-r--r--web/share/css/slider.css22
-rw-r--r--web/share/css/x-desktop.css10
-rw-r--r--web/share/css/x-mobile.css2
-rw-r--r--web/share/js/kvm/atx.js10
-rw-r--r--web/share/js/kvm/session.js20
-rw-r--r--web/share/js/kvm/switch.js606
-rw-r--r--web/share/js/tools.js6
-rw-r--r--web/share/svg/led-beacon.svg4
-rw-r--r--web/share/svg/led-usb.svg22
-rw-r--r--web/share/svg/led-video.svg (renamed from web/share/svg/led-stream.svg)0
24 files changed, 1034 insertions, 65 deletions
diff --git a/web/kvm/index.html b/web/kvm/index.html
index 08093288..fbf9c5df 100644
--- a/web/kvm/index.html
+++ b/web/kvm/index.html
@@ -139,7 +139,7 @@
</div>
</li>
</div>
- <li class="right" id="system-dropdown"><a class="menu-button" href="#"><img class="led-gray" id="link-led" src="/share/svg/led-link.svg"><img class="led-gray" id="stream-led" src="/share/svg/led-stream.svg"><img class="led-gray" id="hid-keyboard-led" src="/share/svg/led-hid-keyboard.svg"><img class="led-gray" id="hid-mouse-led" src="/share/svg/led-hid-mouse.svg"><span>System</span></a>
+ <li class="right" id="system-dropdown"><a class="menu-button" href="#"><img class="led-gray" id="link-led" src="/share/svg/led-link.svg"><img class="led-gray" id="stream-led" src="/share/svg/led-video.svg"><img class="led-gray" id="hid-keyboard-led" src="/share/svg/led-hid-keyboard.svg"><img class="led-gray" id="hid-mouse-led" src="/share/svg/led-hid-mouse.svg"><span>System</span></a>
<div class="menu" id="system-menu">
<table class="kv">
<tr>
@@ -792,7 +792,7 @@
<hr>
<div class="buttons">
<div class="buttons-row">
- <button class="row50" data-force-hide-menu data-shortcut="CapsLock">&bull; Caps Lock &nbsp;<img class="inline-lamp hid-keyboard-caps-led led-gray" src="/share/svg/led-square.svg"></button>
+ <button class="row50" data-force-hide-menu data-shortcut="CapsLock">&bull; Caps Lock &nbsp;<img class="inline-lamp-small hid-keyboard-caps-led led-gray" src="/share/svg/led-square.svg"></button>
<button class="row50" data-force-hide-menu data-shortcut="MetaLeft">&bull; Left Win</button>
</div>
<hr>
@@ -867,6 +867,36 @@
<li class="right feature-disabled" id="gpio-dropdown"><a class="menu-button" id="gpio-menu-button" href="#"><span>GPIO</span></a>
<div class="menu" id="gpio-menu"></div>
</li>
+ <li class="right feature-disabled" id="switch-dropdown"><a class="menu-button" id="switch-menu-button" href="#"><img class="led-gray" id="switch-atx-power-led" src="/share/svg/led-atx-power.svg"><img class="led-gray" id="switch-atx-hdd-led" src="/share/svg/led-atx-hdd.svg"><span>Switch <i><sub id="switch-active-port"></sub></i></span></a>
+ <div class="menu" id="switch-menu">
+ <table style="border-spacing: 0px;">
+ <tr>
+ <td>
+ <div class="text"><b><a target="_blank" href="https://docs.pikvm.org/switch">PiKVM Switch</a> is attached<br></b><sub>Select a port or perform any available action like ATX click</sub></div>
+ </td>
+ <td>
+ <div class="text">
+ <button class="small" data-force-hide-menu data-show-window="switch-window">&bull; Settings</button>
+ </div>
+ </td>
+ </tr>
+ </table>
+ <hr>
+ <table class="kv">
+ <tr>
+ <td>Ask ATX click confirmation:</td>
+ <td align="right">
+ <div class="switch-box">
+ <input checked type="checkbox" id="switch-atx-ask-switch">
+ <label for="switch-atx-ask-switch"><span class="switch-inner"></span><span class="switch"></span></label>
+ </div>
+ </td>
+ </tr>
+ </table>
+ <hr>
+ <table class="kv" id="switch-chain"></table>
+ </div>
+ </li>
</ul>
<div class="window" id="stream-ocr-window">
<div class="hidden" id="stream-ocr-selection"></div>
@@ -1150,7 +1180,7 @@
</div>
<div class="keypad-row">
<div class="key wide-2 left small" data-code="CapsLock">
- <div class="label"><img class="inline-lamp hid-keyboard-caps-led led-gray" src="/share/svg/led-square.svg"><br> Caps Lock
+ <div class="label"><img class="inline-lamp-small hid-keyboard-caps-led led-gray" src="/share/svg/led-square.svg"><br> Caps Lock
</div>
</div>
<div class="spacer"></div>
@@ -1325,7 +1355,7 @@
</div>
<div class="spacer-fixed"></div>
<div class="key small" data-code="ScrollLock">
- <div class="label"><img class="inline-lamp hid-keyboard-scroll-led led-gray" src="/share/svg/led-square.svg"><br> ScrLk
+ <div class="label"><img class="inline-lamp-small hid-keyboard-scroll-led led-gray" src="/share/svg/led-square.svg"><br> ScrLk
</div>
</div>
<div class="spacer-fixed"></div>
@@ -1421,7 +1451,7 @@
<hr>
<div class="keypad-row">
<div class="key small" data-code="NumLock">
- <div class="label"><img class="inline-lamp hid-keyboard-num-led led-gray" src="/share/svg/led-square.svg"><br> NmLk
+ <div class="label"><img class="inline-lamp-small hid-keyboard-num-led led-gray" src="/share/svg/led-square.svg"><br> NmLk
</div>
</div>
<div class="spacer-fixed"></div>
@@ -1627,7 +1657,7 @@
</div>
<div class="spacer"></div>
<div class="key small" data-code="ScrollLock">
- <div class="label"><img class="inline-lamp hid-keyboard-scroll-led led-gray" src="/share/svg/led-square.svg"><br> ScrLk
+ <div class="label"><img class="inline-lamp-small hid-keyboard-scroll-led led-gray" src="/share/svg/led-square.svg"><br> ScrLk
</div>
</div>
<div class="spacer"></div>
@@ -1800,7 +1830,7 @@
</div>
<div class="keypad-row">
<div class="key wide-2 left small" data-code="CapsLock">
- <div class="label"><img class="inline-lamp hid-keyboard-caps-led led-gray" src="/share/svg/led-square.svg"><br> Caps Lock
+ <div class="label"><img class="inline-lamp-small hid-keyboard-caps-led led-gray" src="/share/svg/led-square.svg"><br> Caps Lock
</div>
</div>
<div class="spacer"></div>
@@ -1999,6 +2029,170 @@
</div>
</div>
</div>
+ <div class="window" id="switch-window" style="width:min-content">
+ <div class="window-header">
+ <div class="window-grab">Switch settings</div>
+ <button class="window-button-close"><b>&times;</b></button>
+ </div>
+ <div class="tabs-box">
+ <input checked type="radio" name="switch-tab-button" id="switch-tab-edid-button">
+ <label for="switch-tab-edid-button">EDIDs collection</label>
+ <div class="tab">
+ <table>
+ <tr>
+ <td colspan="2">
+ <select id="switch-edid-selector" size="8"></select>
+ </td>
+ <td rowspan="2" style="vertical-align:top">
+ <table class="kv">
+ <tr>
+ <td>Manufacturer:</td>
+ <td class="value" id="switch-edid-info-mfc-id"></td>
+ </tr>
+ <tr>
+ <td>Product ID:</td>
+ <td class="value" id="switch-edid-info-product-id"></td>
+ </tr>
+ <tr>
+ <td>Serial:</td>
+ <td class="value" id="switch-edid-info-serial"></td>
+ </tr>
+ <tr>
+ <td>Monitor name:</td>
+ <td class="value" id="switch-edid-info-monitor-name"></td>
+ </tr>
+ <tr>
+ <td>Extra serial:</td>
+ <td class="value" id="switch-edid-info-monitor-serial"></td>
+ </tr>
+ <tr>
+ <td>Audio enabled:</td>
+ <td class="value" id="switch-edid-info-audio"></td>
+ </tr>
+ <tr>
+ <td>Data:</td>
+ <td>
+ <button class="small" disabled id="switch-edid-copy-data-button">Copy</button>
+ </td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <button id="switch-edid-add-button">Add new</button>
+ </td>
+ <td style="float:right">
+ <button disabled id="switch-edid-remove-button">Remove</button>
+ </td>
+ </tr>
+ </table>
+ </div>
+ <input type="radio" name="switch-tab-button" id="switch-tab-colors-button">
+ <label for="switch-tab-colors-button">Color scheme</label>
+ <div class="tab">
+ <table>
+ <!--tr
+ td Role
+ td Color
+ td Brightness
+ td
+ td Reset
+ -->
+ <!--trtd
+ <hr>
+ td
+ <hr>
+ td
+ <hr>
+ td
+ td
+ <hr>
+ -->
+ <tr>
+ <td style="white-space: nowrap">Selected port:</td>
+ <td>
+ <input type="color" id="switch-color-active-input">
+ </td>
+ <td>
+ <input type="range" id="switch-color-active-brightness-slider" style="min-width:150px">
+ </td>
+ <td>&nbsp;&nbsp;&nbsp;</td>
+ <td>
+ <button class="small" id="switch-color-active-default-button" title="Reset default">&#8635;</button>
+ </td>
+ </tr>
+ <tr>
+ <td style="white-space: nowrap">Inactive port:</td>
+ <td>
+ <input type="color" id="switch-color-inactive-input">
+ </td>
+ <td>
+ <input type="range" id="switch-color-inactive-brightness-slider" style="min-width:150px">
+ </td>
+ <td>&nbsp;&nbsp;&nbsp;</td>
+ <td>
+ <button class="small" id="switch-color-inactive-default-button" title="Reset default">&#8635;</button>
+ </td>
+ </tr>
+ <tr>
+ <td style="white-space: nowrap">Blinking beacon:</td>
+ <td>
+ <input type="color" id="switch-color-beacon-input">
+ </td>
+ <td>
+ <input type="range" id="switch-color-beacon-brightness-slider" style="min-width:150px">
+ </td>
+ <td>&nbsp;&nbsp;&nbsp;</td>
+ <td>
+ <button class="small" id="switch-color-beacon-default-button" title="Reset default">&#8635;</button>
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <hr>
+ </td>
+ <td>
+ <hr>
+ </td>
+ <td>
+ <hr>
+ </td>
+ <td></td>
+ <td>
+ <hr>
+ </td>
+ </tr>
+ <tr>
+ <td style="white-space: nowrap">Flashing downlink:</td>
+ <td>
+ <input type="color" id="switch-color-flashing-input">
+ </td>
+ <td>
+ <input type="range" id="switch-color-flashing-brightness-slider" style="min-width:150px">
+ </td>
+ <td>&nbsp;&nbsp;&nbsp;</td>
+ <td>
+ <button class="small" id="switch-color-flashing-default-button" title="Reset default">&#8635;</button>
+ </td>
+ </tr>
+ <tr>
+ <td style="white-space: nowrap">Bootloader mode:</td>
+ <td>
+ <input type="color" id="switch-color-bootloader-input">
+ </td>
+ <td>
+ <input type="range" id="switch-color-bootloader-brightness-slider" style="min-width:150px">
+ </td>
+ <td>&nbsp;&nbsp;&nbsp;</td>
+ <td>
+ <button class="small" id="switch-color-bootloader-default-button" title="Reset default">&#8635;</button>
+ </td>
+ </tr>
+ </table>
+ </div>
+ </div>
+ </div>
<div class="window" id="about-window">
<div class="window-header">
<div class="window-grab">About</div>
diff --git a/web/kvm/navbar-shortcuts.pug b/web/kvm/navbar-shortcuts.pug
index 378fdc09..d020b415 100644
--- a/web/kvm/navbar-shortcuts.pug
+++ b/web/kvm/navbar-shortcuts.pug
@@ -9,7 +9,7 @@ li(id="shortcuts-dropdown" class="right")
div(class="buttons-row")
button(data-force-hide-menu data-shortcut="CapsLock" class="row50")
| &bull; Caps Lock &nbsp;
- img(class="inline-lamp hid-keyboard-caps-led led-gray" src=`${svg_dir}/led-square.svg`)
+ img(class="inline-lamp-small hid-keyboard-caps-led led-gray" src=`${svg_dir}/led-square.svg`)
button(data-force-hide-menu data-shortcut="MetaLeft" class="row50") &bull; Left Win
hr
div(class="buttons-row")
diff --git a/web/kvm/navbar-switch.pug b/web/kvm/navbar-switch.pug
new file mode 100644
index 00000000..455daa6f
--- /dev/null
+++ b/web/kvm/navbar-switch.pug
@@ -0,0 +1,19 @@
+li(id="switch-dropdown" class="right feature-disabled")
+ a(class="menu-button" id="switch-menu-button" href="#")
+ +navbar_led("switch-atx-power-led", "led-atx-power")
+ +navbar_led("switch-atx-hdd-led", "led-atx-hdd")
+ span Switch #[i #[sub(id="switch-active-port") ]]
+ div(id="switch-menu" class="menu")
+ table(style="border-spacing: 0px;")
+ tr
+ td
+ div(class="text")
+ b #[a(target="_blank" href="https://docs.pikvm.org/switch") PiKVM Switch] is attached#[br]
+ sub Select a port or perform any available action like ATX click
+ td
+ div(class="text")
+ button(data-force-hide-menu data-show-window="switch-window" class="small") &bull; Settings
+ hr
+ +menu_switch("switch-atx-ask-switch", "Ask ATX click confirmation", true, true)
+ hr
+ table(id="switch-chain" class="kv")
diff --git a/web/kvm/navbar-system.pug b/web/kvm/navbar-system.pug
index 8112d441..62cbda25 100644
--- a/web/kvm/navbar-system.pug
+++ b/web/kvm/navbar-system.pug
@@ -1,7 +1,7 @@
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("stream-led", "led-video")
+navbar_led("hid-keyboard-led", "led-hid-keyboard")
+navbar_led("hid-mouse-led", "led-hid-mouse")
span System
diff --git a/web/kvm/navbar.pug b/web/kvm/navbar.pug
index b1c6b5eb..a9189b7d 100644
--- a/web/kvm/navbar.pug
+++ b/web/kvm/navbar.pug
@@ -51,3 +51,4 @@ ul(id="navbar")
include navbar-text.pug
include navbar-shortcuts.pug
include navbar-gpio.pug
+ include navbar-switch.pug
diff --git a/web/kvm/window-keyboard.pug b/web/kvm/window-keyboard.pug
index fdd00b15..ae1a1e1f 100644
--- a/web/kvm/window-keyboard.pug
+++ b/web/kvm/window-keyboard.pug
@@ -26,7 +26,7 @@ mixin empty(spacer, classes="", width=0)
div(class="spacer-fixed")
mixin lamp(cls)
- img(class=`inline-lamp ${cls} led-gray` src=`${svg_dir}/led-square.svg`)
+ img(class=`inline-lamp-small ${cls} led-gray` src=`${svg_dir}/led-square.svg`)
div(id="keyboard-window" class="window")
div(id="keyboard-window-header" class="window-header")
diff --git a/web/kvm/window-switch.pug b/web/kvm/window-switch.pug
new file mode 100644
index 00000000..71c0e152
--- /dev/null
+++ b/web/kvm/window-switch.pug
@@ -0,0 +1,95 @@
+mixin switch_tab(name, title, checked=false)
+ - let button_id = `switch-tab-${name}-button`
+ input(checked=checked type="radio" name="switch-tab-button", id=button_id)
+ label(for=button_id) #{title}
+ div(class="tab")
+ block
+
+div(id="switch-window" class="window" style="width:min-content")
+ div(class="window-header")
+ div(class="window-grab") Switch settings
+ button(class="window-button-close") #[b &times;]
+
+ div(class="tabs-box")
+ +switch_tab("edid", "EDIDs collection", true)
+ table
+ tr
+ td(colspan="2")
+ select(id="switch-edid-selector" size="8")
+ td(rowspan="2" style="vertical-align:top")
+ table(class="kv")
+ tr
+ td Manufacturer:
+ td(id="switch-edid-info-mfc-id" class="value")
+ tr
+ td Product ID:
+ td(id="switch-edid-info-product-id" class="value")
+ tr
+ td Serial:
+ td(id="switch-edid-info-serial" class="value")
+ tr
+ td Monitor name:
+ td(id="switch-edid-info-monitor-name" class="value")
+ tr
+ td Extra serial:
+ td(id="switch-edid-info-monitor-serial" class="value")
+ tr
+ td Audio enabled:
+ td(id="switch-edid-info-audio" class="value")
+ tr
+ td Data:
+ td #[button(disabled id="switch-edid-copy-data-button" class="small") Copy]
+ tr
+ td #[button(id="switch-edid-add-button") Add new]
+ td(style="float:right") #[button(disabled id="switch-edid-remove-button") Remove]
+
+ +switch_tab("colors", "Color scheme")
+ table
+ //tr
+ td Role
+ td Color
+ td Brightness
+ td
+ td Reset
+ //tr
+ td #[hr]
+ td #[hr]
+ td #[hr]
+ td
+ td #[hr]
+ tr
+ td(style="white-space: nowrap") Selected port:
+ td #[input(type="color" id="switch-color-active-input")]
+ td #[input(type="range" id="switch-color-active-brightness-slider" style="min-width:150px")]
+ td &nbsp;&nbsp;&nbsp;
+ td #[button(id="switch-color-active-default-button" class="small" title="Reset default") &#8635;]
+ tr
+ td(style="white-space: nowrap") Inactive port:
+ td #[input(type="color" id="switch-color-inactive-input")]
+ td #[input(type="range" id="switch-color-inactive-brightness-slider" style="min-width:150px")]
+ td &nbsp;&nbsp;&nbsp;
+ td #[button(id="switch-color-inactive-default-button" class="small" title="Reset default") &#8635;]
+ tr
+ td(style="white-space: nowrap") Blinking beacon:
+ td #[input(type="color" id="switch-color-beacon-input")]
+ td #[input(type="range" id="switch-color-beacon-brightness-slider" style="min-width:150px")]
+ td &nbsp;&nbsp;&nbsp;
+ td #[button(id="switch-color-beacon-default-button" class="small" title="Reset default") &#8635;]
+ tr
+ td #[hr]
+ td #[hr]
+ td #[hr]
+ td
+ td #[hr]
+ tr
+ td(style="white-space: nowrap") Flashing downlink:
+ td #[input(type="color" id="switch-color-flashing-input")]
+ td #[input(type="range" id="switch-color-flashing-brightness-slider" style="min-width:150px")]
+ td &nbsp;&nbsp;&nbsp;
+ td #[button(id="switch-color-flashing-default-button" class="small" title="Reset default") &#8635;]
+ tr
+ td(style="white-space: nowrap") Bootloader mode:
+ td #[input(type="color" id="switch-color-bootloader-input")]
+ td #[input(type="range" id="switch-color-bootloader-brightness-slider" style="min-width:150px")]
+ td &nbsp;&nbsp;&nbsp;
+ td #[button(id="switch-color-bootloader-default-button" class="small" title="Reset default") &#8635;]
diff --git a/web/kvm/windows.pug b/web/kvm/windows.pug
index b2d32dad..7b20bc22 100644
--- a/web/kvm/windows.pug
+++ b/web/kvm/windows.pug
@@ -1,4 +1,5 @@
include window-stream.pug
include window-keyboard.pug
+include window-switch.pug
include window-about.pug
include window-webterm.pug
diff --git a/web/login/index.html b/web/login/index.html
index 99fa2aed..90a840dd 100644
--- a/web/login/index.html
+++ b/web/login/index.html
@@ -74,7 +74,7 @@
<tr>
<td></td>
<td>
- <button class="key" id="login-button">Login</button>
+ <button class="key" id="login-button" style="width:100%">Login</button>
</td>
</tr>
</table>
diff --git a/web/login/index.pug b/web/login/index.pug
index 26b955af..aabb47ae 100644
--- a/web/login/index.pug
+++ b/web/login/index.pug
@@ -24,7 +24,7 @@ block body
hr
tr
td
- td #[button(id="login-button" class="key") Login]
+ td #[button(id="login-button" class="key" style="width:100%") Login]
ul(class="footer")
li(class="left")
diff --git a/web/share/css/kvm/msd.css b/web/share/css/kvm/msd.css
index 5d262fb5..e0a24b4c 100644
--- a/web/share/css/kvm/msd.css
+++ b/web/share/css/kvm/msd.css
@@ -28,3 +28,7 @@ div#msd-menu div.msd-message,
div#msd-menu input.msd-message {
display: none;
}
+
+div#msd-menu select#msd-image-selector {
+ width: 100%;
+}
diff --git a/web/share/css/main.css b/web/share/css/main.css
index 8a074aa7..a543b06f 100644
--- a/web/share/css/main.css
+++ b/web/share/css/main.css
@@ -89,11 +89,16 @@ img.svg-gray {
img.inline-lamp {
vertical-align: middle;
+ height: 1em;
+ margin-left: 2px;
+ margin-right: 2px;
+}
+img.inline-lamp-small {
+ vertical-align: middle;
height: 8px;
margin-left: 2px;
margin-right: 2px;
}
-
img.inline-lamp-big {
vertical-align: middle;
height: 20px;
@@ -104,7 +109,8 @@ img.inline-lamp-big {
button,
select,
input[type=file]::-webkit-file-selector-button,
-input[type=file]::file-selector-button {
+input[type=file]::file-selector-button,
+input[type=color] {
border: none;
border-radius: 4px;
color: var(--cs-control-default-fg);
@@ -117,11 +123,9 @@ input[type=file]::file-selector-button {
}
button {
display: block;
- width: 100%;
}
select {
display: block;
- width: 100%;
padding-left: 5px;
}
select[size] {
@@ -194,6 +198,7 @@ select:not([size]) option.comment {
input[type=text], input[type=password] {
overflow-x: auto;
font-family: monospace;
+ box-sizing: border-box;
border-radius: 4px;
border: var(--border-default-thin);
color: var(--cs-code-default-fg);
@@ -223,42 +228,35 @@ textarea::-webkit-input-placeholder {
}
div.buttons-row {
+ display: flex;
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) {
+div.buttons-row button: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) {
+div.buttons-row button:not(:last-child) {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
+button.row100 {
+ width: 100% !important;
+}
+button.row50 {
+ width: 50% !important;
+}
+button.row33 {
+ width: 33.33% !important;
+}
+button.row25 {
+ width: 25% !important;
+}
+button.row16 {
+ width: 16.66% !important;
+}
table.kv {
border-spacing: 5px;
diff --git a/web/share/css/modal.css b/web/share/css/modal.css
index 48010a6c..cf2f350d 100644
--- a/web/share/css/modal.css
+++ b/web/share/css/modal.css
@@ -63,9 +63,11 @@ div.modal div.modal-window div.modal-content {
div.modal div.modal-window div.modal-buttons {
border-top: var(--border-control-thin);
+ display: flex;
margin: 0;
padding: 0;
font-size: 0;
+ width: 100%;
}
div.modal div.modal-window div.modal-buttons button {
diff --git a/web/share/css/navbar.css b/web/share/css/navbar.css
index af704add..f3e7c0cc 100644
--- a/web/share/css/navbar.css
+++ b/web/share/css/navbar.css
@@ -172,6 +172,7 @@ ul#navbar li div.menu div.buttons select {
border-radius: 0;
text-align: left;
padding: 0 16px;
+ width: 100%;
}
ul#navbar li div.menu input[type=text] {
diff --git a/web/share/css/slider.css b/web/share/css/slider.css
index 2669c9a4..db743289 100644
--- a/web/share/css/slider.css
+++ b/web/share/css/slider.css
@@ -21,7 +21,7 @@
@supports (-webkit-appearance:none) {
- input[type=range].slider {
+ input[type=range] {
cursor: pointer;
outline: none;
width: 100%;
@@ -33,7 +33,7 @@
}
}
@supports not (-webkit-appearance:none) {
- input[type=range].slider {
+ input[type=range] {
cursor: pointer;
outline: none;
width: 100%;
@@ -42,20 +42,20 @@
margin-right: 0;
}
}
-input[type=range].slider:disabled {
+input[type=range]:disabled {
cursor: default;
}
-input[type=range].slider::-webkit-slider-runnable-track {
+input[type=range]::-webkit-slider-runnable-track {
height: 5px;
background: var(--cs-control-default-bg);
border-radius: 3px;
}
-input[type=range].slider:disabled::-webkit-slider-runnable-track {
+input[type=range]:disabled::-webkit-slider-runnable-track {
cursor: default;
}
-input[type=range].slider::-webkit-slider-thumb {
+input[type=range]::-webkit-slider-thumb {
border: var(--border-intensive-2px);
height: 18px;
width: 18px;
@@ -64,29 +64,29 @@ input[type=range].slider::-webkit-slider-thumb {
-webkit-appearance: none;
margin-top: -7px;
}
-input[type=range].slider:disabled::-webkit-slider-thumb {
+input[type=range]:disabled::-webkit-slider-thumb {
cursor: default;
border: var(--border-default-2px);
background: var(--cs-thumb-disabled-bg);
}
-input[type=range].slider::-moz-range-track {
+input[type=range]::-moz-range-track {
height: 5px;
background: var(--cs-control-default-bg);
border-radius: 3px;
}
-input[type=range].slider:disabled::-moz-range-track {
+input[type=range]:disabled::-moz-range-track {
cursor: default;
}
-input[type=range].slider::-moz-range-thumb {
+input[type=range]::-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 {
+input[type=range]:disabled::-moz-range-thumb {
cursor: default;
border: var(--border-default-2px);
background: var(--cs-thumb-disabled-bg);
diff --git a/web/share/css/x-desktop.css b/web/share/css/x-desktop.css
index 732f8aa2..56a27fb5 100644
--- a/web/share/css/x-desktop.css
+++ b/web/share/css/x-desktop.css
@@ -25,7 +25,8 @@
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 {
+input[type=file]:enabled:hover::file-selector-button,
+input[type=color]:enabled:hover {
color: var(--cs-control-hovered-fg);
background-color: var(--cs-control-hovered-bg);
}
@@ -33,7 +34,8 @@ input[type=file]:enabled:hover::file-selector-button {
button:active,
select:not([size]):active,
input[type=file]:active::-webkit-file-selector-button,
-input[type=file]:active::file-selector-button {
+input[type=file]:active::file-selector-button,
+input[type=color]:active {
color: var(--cs-control-pressed-fg) !important;
background-color: var(--cs-control-pressed-bg) !important;
}
@@ -60,12 +62,12 @@ div.radio-box input[type=radio]:not(:checked):not(:disabled) + label:hover {
/* ===== slider.css ===== */
/*div.switch-box label span.switch-inner:not(:disabled):hover::before {*/
-input[type=range].slider:not(:disabled):hover::-webkit-slider-runnable-track {
+input[type=range]: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 {
+input[type=range]:not(:disabled):hover::-moz-range-track {
background-color: var(--cs-control-hovered-bg);
}
diff --git a/web/share/css/x-mobile.css b/web/share/css/x-mobile.css
index fef5f4ba..dfb8b1a7 100644
--- a/web/share/css/x-mobile.css
+++ b/web/share/css/x-mobile.css
@@ -92,7 +92,7 @@ ul#navbar li a.menu-button:hover:not(.active) {
/*@media only screen and (orientation: portrait) {
@supports (-webkit-appearance: none) {
- input[type=range].slider {
+ input[type=range] {
margin: 20px 0 20px 0 !important;
}
}
diff --git a/web/share/js/kvm/atx.js b/web/share/js/kvm/atx.js
index 796a4eeb..0065e73c 100644
--- a/web/share/js/kvm/atx.js
+++ b/web/share/js/kvm/atx.js
@@ -32,6 +32,7 @@ export function Atx(__recorder) {
/************************************************************************/
+ var __has_switch = null; // Or true/false
var __state = null;
var __init__ = function() {
@@ -54,12 +55,12 @@ export function Atx(__recorder) {
}
if (state.enabled !== undefined) {
__state.enabled = state.enabled;
- tools.feature.setEnabled($("atx-dropdown"), __state.enabled);
+ tools.feature.setEnabled($("atx-dropdown"), (__state.enabled && !__has_switch));
}
if (__state.enabled !== undefined) {
if (state.busy !== undefined) {
+ __updateButtons(!state.busy);
__state.busy = state.busy;
- __updateButtons(!__state.busy);
}
if (state.leds !== undefined) {
__state.leds = state.leds;
@@ -75,6 +76,11 @@ export function Atx(__recorder) {
}
};
+ self.setHasSwitch = function(has_switch) {
+ __has_switch = has_switch;
+ self.setState(__state);
+ };
+
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");
diff --git a/web/share/js/kvm/session.js b/web/share/js/kvm/session.js
index 27b18b21..c2f13342 100644
--- a/web/share/js/kvm/session.js
+++ b/web/share/js/kvm/session.js
@@ -34,6 +34,7 @@ import {Msd} from "./msd.js";
import {Streamer} from "./stream.js";
import {Gpio} from "./gpio.js";
import {Ocr} from "./ocr.js";
+import {Switch} from "./switch.js";
export function Session() {
@@ -54,6 +55,7 @@ export function Session() {
var __msd = new Msd();
var __gpio = new Gpio(__recorder);
var __ocr = new Ocr(__streamer.getGeometry);
+ var __switch = new Switch();
var __info_hw_state = null;
var __info_fan_state = null;
@@ -368,9 +370,24 @@ export function Session() {
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;
+
+ case "msd_state":
+ if (data.event.online === false) {
+ __switch.setMsdConnected(false);
+ } else if (data.event.drive !== undefined) {
+ __switch.setMsdConnected(data.event.drive.connected);
+ }
+ __msd.setState(data.event);
+ break;
+
+ case "switch_state":
+ if (data.event.model) {
+ __atx.setHasSwitch(data.event.model.ports.length > 0);
+ }
+ __switch.setState(data.event);
+ break;
}
};
@@ -401,6 +418,7 @@ export function Session() {
__streamer.setState(null);
__ocr.setState(null);
__recorder.setSocket(null);
+ __switch.setState(null);
__ws = null;
setTimeout(function() {
diff --git a/web/share/js/kvm/switch.js b/web/share/js/kvm/switch.js
new file mode 100644
index 00000000..112d8f15
--- /dev/null
+++ b/web/share/js/kvm/switch.js
@@ -0,0 +1,606 @@
+/*****************************************************************************
+# #
+# KVMD - The main PiKVM daemon. #
+# #
+# Copyright (C) 2018-2024 Maxim Devaev <[email protected]> #
+# #
+# 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 Switch() {
+ var self = this;
+
+ /************************************************************************/
+
+ var __state = null;
+ var __msd_connected = false;
+
+ var __init__ = function() {
+ tools.selector.addOption($("switch-edid-selector"), "Default", "default");
+ $("switch-edid-selector").onchange = __selectEdid;
+
+ tools.el.setOnClick($("switch-edid-add-button"), __clickAddEdidButton);
+ tools.el.setOnClick($("switch-edid-remove-button"), __clickRemoveEdidButton);
+ tools.el.setOnClick($("switch-edid-copy-data-button"), __clickCopyEdidDataButton);
+
+ tools.storage.bindSimpleSwitch($("switch-atx-ask-switch"), "switch.atx.ask", true);
+
+ for (let role of ["inactive", "active", "flashing", "beacon", "bootloader"]) {
+ let el_brightness = $(`switch-color-${role}-brightness-slider`);
+ tools.slider.setParams(el_brightness, 0, 255, 1, 0);
+ el_brightness.onchange = $(`switch-color-${role}-input`).onchange = tools.partial(__selectColor, role);
+ tools.el.setOnClick($(`switch-color-${role}-default-button`), tools.partial(__clickSetDefaultColorButton, role));
+ }
+ };
+
+ /************************************************************************/
+
+ self.setMsdConnected = function(connected) {
+ __msd_connected = connected;
+ };
+
+ self.setState = function(state) {
+ if (state) {
+ if (!__state) {
+ __state = {};
+ }
+ if (state.model) {
+ __state = {};
+ __applyModel(state.model);
+ }
+ if (__state.model) {
+ if (state.summary) {
+ __applySummary(state.summary);
+ }
+ if (state.beacons) {
+ __applyBeacons(state.beacons);
+ }
+ if (state.usb) {
+ __applyUsb(state.usb);
+ }
+ if (state.video) {
+ __applyVideo(state.video);
+ }
+ if (state.atx) {
+ __applyAtx(state.atx);
+ }
+ if (state.edids) {
+ __applyEdids(state.edids);
+ }
+ if (state.colors) {
+ __applyColors(state.colors);
+ }
+ }
+ } else {
+ tools.feature.setEnabled($("switch-dropdown"), false);
+ $("switch-chain").innerText = "";
+ $("switch-active-port").innerText = "N/A";
+ __setPowerLedState($("switch-atx-power-led"), false, false);
+ __setLedState($("switch-atx-hdd-led"), "red", false);
+ __state = null;
+ }
+ };
+
+ var __applyColors = function(colors) {
+ for (let role in colors) {
+ let color = colors[role];
+ $(`switch-color-${role}-input`).value = (
+ "#"
+ + color.red.toString(16).padStart(2, "0")
+ + color.green.toString(16).padStart(2, "0")
+ + color.blue.toString(16).padStart(2, "0")
+ );
+ $(`switch-color-${role}-brightness-slider`).value = color.brightness;
+ }
+ __state.colors = colors;
+ };
+
+ var __selectColor = function(role) {
+ let el_color = $(`switch-color-${role}-input`);
+ let el_brightness = $(`switch-color-${role}-brightness-slider`);
+ let color = __state.colors[role];
+ let brightness = parseInt(el_brightness.value);
+ let rgbx = (
+ el_color.value.slice(1)
+ + ":" + brightness.toString(16).padStart(2, "0")
+ + ":" + color.blink_ms.toString(16).padStart(4, "0")
+ );
+ __sendPost("/api/switch/set_colors", {[role]: rgbx}, function() {
+ el_color.value = (
+ "#"
+ + color.red.toString(16).padStart(2, "0")
+ + color.green.toString(16).padStart(2, "0")
+ + color.blue.toString(16).padStart(2, "0")
+ );
+ el_brightness.value = color.brightness;
+ });
+ };
+
+ var __clickSetDefaultColorButton = function(role) {
+ __sendPost("/api/switch/set_colors", {[role]: "default"});
+ };
+
+ var __applyEdids = function(edids) {
+ let el = $("switch-edid-selector");
+ let old_edid_id = el.value;
+ el.options.length = 1;
+ for (let kv of Object.entries(edids.all)) {
+ if (kv[0] !== "default") {
+ tools.selector.addOption(el, kv[1].name, kv[0]);
+ }
+ }
+ el.value = (old_edid_id in edids.all ? old_edid_id : "default");
+
+ for (let port in __state.model.ports) {
+ let custom = (edids.used[port] !== "default");
+ $(`__switch-custom-edid-p${port}`).style.visibility = (custom ? "unset" : "hidden");
+ }
+
+ __state.edids = edids;
+ __selectEdid();
+ };
+
+ var __selectEdid = function() {
+ let edid_id = $("switch-edid-selector").value;
+ let edid = null;
+ try { edid = __state.edids.all[edid_id]; } catch { edid_id = ""; }
+ let parsed = (edid ? edid.parsed : null);
+ let na = "<i>&lt;Not Available&gt;</i>";
+ $("switch-edid-info-mfc-id").innerHTML = (parsed ? tools.escape(parsed.mfc_id) : na);
+ $("switch-edid-info-product-id").innerHTML = (parsed ? tools.escape(`0x${parsed.product_id.toString(16).toUpperCase()}`) : na);
+ $("switch-edid-info-serial").innerHTML = (parsed ? tools.escape(`0x${parsed.serial.toString(16).toUpperCase()}`) : na);
+ $("switch-edid-info-monitor-name").innerHTML = ((parsed && parsed.monitor_name) ? tools.escape(parsed.monitor_name) : na);
+ $("switch-edid-info-monitor-serial").innerHTML = ((parsed && parsed.monitor_serial) ? tools.escape(parsed.monitor_serial) : na);
+ $("switch-edid-info-audio").innerHTML = (parsed ? (parsed.audio ? "Yes" : "No") : na);
+ tools.el.setEnabled($("switch-edid-remove-button"), (edid_id && (edid_id !== "default")));
+ tools.el.setEnabled($("switch-edid-copy-data-button"), !!edid_id);
+ };
+
+ var __clickAddEdidButton = function() {
+ let create_content = function(el_parent, el_ok_button) {
+ tools.el.setEnabled(el_ok_button, false);
+ el_parent.innerHTML = `
+ <table>
+ <tr>
+ <td>Name:</td>
+ <td><input
+ type="text" autocomplete="off" id="__switch-edid-new-name-input"
+ placeholder="Enter some meaningful name"
+ style="width:100%"
+ /></td>
+ </tr>
+ <tr><td colspan="2">HEX data:</td></tr>
+ <tr>
+ <td colspan="2"><textarea
+ id="__switch-edid-new-data-text" placeholder="Like 0123ABCD..."
+ style="min-width:350px"
+ ></textarea><td>
+ </table>
+ `;
+ let el_name = $("__switch-edid-new-name-input");
+ let el_data = $("__switch-edid-new-data-text");
+ el_name.oninput = el_data.oninput = function() {
+ let name = el_name.value.replace(/\s+/g, "");
+ let data = el_data.value.replace(/\s+/g, "");
+ tools.el.setEnabled(el_ok_button, ((name.length > 0) && /[0-9a-fA-F]{512}/.test(data)));
+ };
+ };
+
+ wm.modal("Add new EDID", create_content, true, true).then(function(ok) {
+ if (ok) {
+ let name = $("__switch-edid-new-name-input").value;
+ let data = $("__switch-edid-new-data-text").value;
+ __sendPost("/api/switch/edids/create", {"name": name, "data": data});
+ }
+ });
+ };
+
+ var __clickRemoveEdidButton = function() {
+ let edid_id = $("switch-edid-selector").value;
+ if (edid_id && __state && __state.edids) {
+ let name = __state.edids.all[edid_id].name;
+ let html = "Are you sure to remove this EDID?<br>Ports that used it will change it to the default.";
+ wm.confirm(html, name).then(function(ok) {
+ if (ok) {
+ __sendPost("/api/switch/edids/remove", {"id": edid_id});
+ }
+ });
+ }
+ };
+
+ var __clickCopyEdidDataButton = function() {
+ let edid_id = $("switch-edid-selector").value;
+ if (edid_id && __state && __state.edids) {
+ let data = __state.edids.all[edid_id].data;
+ data = data.replace(/(.{32})/g, "$1\n");
+ wm.copyTextToClipboard(data);
+ }
+ };
+
+ var __applyUsb = function(usb) {
+ for (let port = 0; port < __state.model.ports.length; ++port) {
+ if (!__state.usb || __state.usb.links[port] !== usb.links[port]) {
+ __setLedState($(`__switch-usb-led-p${port}`), "green", usb.links[port]);
+ }
+ }
+ __state.usb = usb;
+ };
+
+ var __applyVideo = function(video) {
+ for (let port = 0; port < __state.model.ports.length; ++port) {
+ if (!__state.video || __state.video.links[port] !== video.links[port]) {
+ __setLedState($(`__switch-video-led-p${port}`), "green", video.links[port]);
+ }
+ }
+ __state.video = video;
+ };
+
+ var __applyAtx = function(atx) {
+ for (let port = 0; port < __state.model.ports.length; ++port) {
+ let busy = atx.busy[port];
+ if (!__state.atx || __state.atx.leds.power[port] !== atx.leds.power[port] || __state.atx.busy[port] !== busy) {
+ let power = atx.leds.power[port];
+ __setPowerLedState($(`__switch-atx-power-led-p${port}`), power, busy);
+ if (port === __state.summary.active_port) {
+ // summary есть всегда, если есть model, и atx обновляется последним в setState()
+ __setPowerLedState($("switch-atx-power-led"), power, busy);
+ }
+ }
+ if (!__state.atx || __state.atx.leds.hdd[port] !== atx.leds.hdd[port]) {
+ let hdd = atx.leds.hdd[port];
+ __setLedState($(`__switch-atx-hdd-led-p${port}`), "red", hdd);
+ if (port === __state.summary.active_port) {
+ __setLedState($("switch-atx-hdd-led"), "red", hdd);
+ }
+ }
+ if (!__state.atx || __state.atx.busy[port] !== busy) {
+ tools.el.setEnabled($(`__switch-atx-power-button-p${port}`), !busy);
+ tools.el.setEnabled($(`__switch-atx-power-long-button-p${port}`), !busy);
+ tools.el.setEnabled($(`__switch-atx-reset-button-p${port}`), !busy);
+ }
+ }
+ __state.atx = atx;
+ };
+
+ var __applyBeacons = function(beacons) {
+ for (let unit = 0; unit < __state.model.units.length; ++unit) {
+ if (!__state.beacons || __state.beacons.uplinks[unit] !== beacons.uplinks[unit]) {
+ __setLedState($(`__switch-beacon-led-u${unit}`), "green", beacons.uplinks[unit]);
+ }
+ if (!__state.beacons || __state.beacons.downlinks[unit] !== beacons.downlinks[unit]) {
+ __setLedState($(`__switch-beacon-led-d${unit}`), "green", beacons.downlinks[unit]);
+ }
+ }
+ for (let port = 0; port < __state.model.ports.length; ++port) {
+ if (!__state.beacons || __state.beacons.ports[port] !== beacons.ports[port]) {
+ __setLedState($(`__switch-beacon-led-p${port}`), "green", beacons.ports[port]);
+ }
+ }
+ __state.beacons = beacons;
+ };
+
+ var __applySummary = function(summary) {
+ let active = summary.active_port;
+ if (!__state.summary || __state.summary.active_port !== active) {
+ if (active < 0 || active >= __state.model.ports.length) {
+ $("switch-active-port").innerText = "N/A";
+ } else {
+ $("switch-active-port").innerText = "p" + __formatPort(__state.model, active);
+ }
+ for (let port = 0; port < __state.model.ports.length; ++port) {
+ __setLedState($(`__switch-port-led-p${port}`), "green", (port === active));
+ }
+ }
+ if (__state.atx) {
+ // Синхронизация светодиодов ATX при смене порта
+ let power = false;
+ let busy = false;
+ let hdd = false;
+ if (active >= 0 && active < __state.model.ports.length) {
+ power = __state.atx.leds.power[active];
+ hdd = __state.atx.leds.hdd[active];
+ busy = __state.atx.busy[active];
+ }
+ __setPowerLedState($("switch-atx-power-led"), power, busy);
+ __setLedState($("switch-atx-hdd-led"), "red", hdd);
+ }
+ __state.summary = summary;
+ };
+
+ var __applyModel = function(model) {
+ tools.feature.setEnabled($("switch-dropdown"), model.ports.length);
+
+ let content = "";
+ let unit = -1;
+ for (let port = 0; port < model.ports.length; ++port) {
+ let pa = model.ports[port]; // pa == port attrs
+ if (unit !== pa.unit) {
+ unit = pa.unit;
+ content += `${unit > 0 ? "<tr><td colspan=100><hr></td></tr>" : ""}
+ <tr>
+ <td></td><td></td><td></td>
+ <td class="value">Unit: ${unit + 1}</td>
+ <td></td>
+ <td colspan=100>
+ <div class="buttons-row">
+ <button id="__switch-beacon-button-u${unit}" class="small" title="Toggle uplink Beacon Led">
+ <img id="__switch-beacon-led-u${unit}" class="inline-lamp led-gray" src="/share/svg/led-beacon.svg"/>
+ Uplink
+ </button>
+ <button id="__switch-beacon-button-d${unit}" class="small" title="Toggle downlink Beacon Led">
+ <img id="__switch-beacon-led-d${unit}" class="inline-lamp led-gray" src="/share/svg/led-beacon.svg"/>
+ Downlink
+ </button>
+ </div>
+ </td>
+ </tr>
+ <tr><td colspan=100><hr></td></tr>
+ `;
+ }
+ content += `
+ <tr>
+ <td>Port:</td>
+ <td class="value">${__formatPort(model, port)}</td>
+ <td>&nbsp;&nbsp;</td>
+ <td>
+ <div class="buttons-row">
+ <button id="__switch-port-button-p${port}" title="Activate this port">
+ <img id="__switch-port-led-p${port}" class="inline-lamp led-gray" src="/share/svg/led-circle.svg"/>
+ </button>
+ <button id="__switch-params-button-p${port}" title="Configure this port">
+ <img id="__switch-params-led-p${port}" class="inline-lamp led-gray" src="/share/svg/led-gear.svg"/>
+ </button>
+ </div>
+ </td>
+ <td>
+ <span
+ id="__switch-custom-edid-p${port}" style="visibility:hidden"
+ title="A non-default EDID is used on this port"
+ >
+ &#9913;
+ </span>
+ &nbsp;&nbsp;&nbsp;&nbsp;
+ ${pa.name.length > 0 ? tools.escape(pa.name) : ("Host " + (port + 1))}
+ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+ </td>
+ <td style="font-size:1em">
+ <button id="__switch-beacon-button-p${port}" class="small" title="Toggle Beacon Led on this port">
+ <img id="__switch-beacon-led-p${port}" class="inline-lamp led-gray" src="/share/svg/led-beacon.svg"/>
+ </button>
+ </td>
+ <td>
+ <img id="__switch-video-led-p${port}" class="inline-lamp led-gray" src="/share/svg/led-video.svg" title="Video Link"/>
+ <img id="__switch-usb-led-p${port}" class="inline-lamp led-gray" src="/share/svg/led-usb.svg" title="USB Link"/>
+ <img id="__switch-atx-power-led-p${port}" class="inline-lamp led-gray" src="/share/svg/led-atx-power.svg" title="Power Led"/>
+ <img id="__switch-atx-hdd-led-p${port}" class="inline-lamp led-gray" src="/share/svg/led-atx-hdd.svg" title="HDD Led"/>
+ </td>
+ <td>
+ <div class="buttons-row">
+ <button id="__switch-atx-power-button-p${port}" class="small">Power <sup><i>short</i></sup></button>
+ <button id="__switch-atx-power-long-button-p${port}" class="small"><sup><i>long</i></sup></button>
+ <button id="__switch-atx-reset-button-p${port}" class="small">Reset</button>
+ </div>
+ </td>
+ </tr>
+ `;
+ }
+ $("switch-chain").innerHTML = content;
+
+ for (let unit = 0; unit < model.units.length; ++unit) {
+ tools.el.setOnClick($(`__switch-beacon-button-u${unit}`), tools.partial(__switchUplinkBeacon, unit));
+ tools.el.setOnClick($(`__switch-beacon-button-d${unit}`), tools.partial(__switchDownlinkBeacon, unit));
+ }
+
+ for (let port = 0; port < model.ports.length; ++port) {
+ tools.el.setOnClick($(`__switch-port-button-p${port}`), tools.partial(__switchActivePort, port));
+ tools.el.setOnClick($(`__switch-params-button-p${port}`), tools.partial(__showParamsDialog, port));
+ tools.el.setOnClick($(`__switch-beacon-button-p${port}`), tools.partial(__switchPortBeacon, port));
+ tools.el.setOnClick($(`__switch-atx-power-button-p${port}`), tools.partial(__atxClick, port, "power"));
+ tools.el.setOnClick($(`__switch-atx-power-long-button-p${port}`), tools.partial(__atxClick, port, "power_long"));
+ tools.el.setOnClick($(`__switch-atx-reset-button-p${port}`), tools.partial(__atxClick, port, "reset"));
+ }
+
+ __setPowerLedState($("switch-atx-power-led"), false, false);
+ __setLedState($("switch-atx-hdd-led"), "red", false);
+
+ __state.model = model;
+ };
+
+ var __showParamsDialog = function(port) {
+ if (!__state || !__state.model || !__state.edids) {
+ return;
+ }
+
+ let model = __state.model;
+ let edids = __state.edids;
+
+ let atx_actions = {
+ "power": "ATX power click",
+ "power_long": "Power long",
+ "reset": "Reset click",
+ };
+
+ let add_edid_option = function(el, attrs, id) {
+ tools.selector.addOption(el, attrs.name, id, (edids.used[port] === id));
+ if (attrs.parsed !== null) {
+ let parsed = attrs.parsed;
+ let text = "\xA0\xA0\xA0\xA0\xA0\u2570 ";
+ text += (parsed.monitor_name !== null ? parsed.monitor_name : parsed.mfc_id);
+ text += (parsed.audio ? "; +Audio" : "; -Audio");
+ tools.selector.addComment(el, text);
+ }
+ };
+
+ let create_content = function(el_parent) {
+ let html = `
+ <table>
+ <tr>
+ <td>Port name:</td>
+ <td><input
+ type="text" autocomplete="off" id="__switch-port-name-input"
+ value="${tools.escape(model.ports[port].name)}" placeholder="Host ${port + 1}"
+ style="width:100%"
+ /></td>
+ </tr>
+ <tr>
+ <td>EDID:</td>
+ <td><select id="__switch-port-edid-selector" style="width: 100%"></select></td>
+ </tr>
+ </table>
+ <hr>
+ <table>
+ `;
+ for (let kv of Object.entries(atx_actions)) {
+ html += `
+ <tr>
+ <td style="white-space: nowrap">${tools.escape(kv[1])}:</td>
+ <td style="width: 100%"><input type="range" id="__switch-port-atx-click-${kv[0]}-delay-slider"/></td>
+ <td id="__switch-port-atx-click-${kv[0]}-delay-value"></td>
+ <td>&nbsp;&nbsp;&nbsp;</td>
+ <td><button
+ id="__switch-port-atx-click-${kv[0]}-delay-default-button"
+ class="small" title="Reset default"
+ >&#8635;</button></td>
+ </tr>
+ `;
+ }
+ html += "</table>";
+ el_parent.innerHTML = html;
+
+ let el_selector = $("__switch-port-edid-selector");
+ add_edid_option(el_selector, edids.all["default"], "default");
+ for (let kv of Object.entries(edids.all)) {
+ if (kv[0] !== "default") {
+ tools.selector.addSeparator(el_selector, 20);
+ add_edid_option(el_selector, kv[1], kv[0]);
+ }
+ }
+
+ for (let action of Object.keys(atx_actions)) {
+ let limits = model.limits.atx.click_delays[action];
+ let el_slider = $(`__switch-port-atx-click-${action}-delay-slider`);
+ let display_value = tools.partial(function(action, value) {
+ $(`__switch-port-atx-click-${action}-delay-value`).innerText = `${value.toFixed(1)}`;
+ }, action);
+ let reset_default = tools.partial(function(el_slider, limits) {
+ tools.slider.setValue(el_slider, limits["default"]);
+ }, el_slider, limits);
+ tools.slider.setParams(el_slider, limits.min, limits.max, 0.5, model.ports[port].atx.click_delays[action], display_value);
+ tools.el.setOnClick($(`__switch-port-atx-click-${action}-delay-default-button`), reset_default);
+ }
+ };
+
+ wm.modal(`Port ${__formatPort(__state.model, port)} settings`, create_content, true, true).then(function(ok) {
+ if (ok) {
+ let params = {
+ "port": port,
+ "edid_id": $("__switch-port-edid-selector").value,
+ "name": $("__switch-port-name-input").value,
+ };
+ for (let action of Object.keys(atx_actions)) {
+ params[`atx_click_${action}_delay`] = tools.slider.getValue($(`__switch-port-atx-click-${action}-delay-slider`));
+ };
+ __sendPost("/api/switch/set_port_params", params);
+ }
+ });
+ };
+
+ var __formatPort = function(model, port) {
+ if (model.units.length > 1) {
+ return `${model.ports[port].unit + 1}.${model.ports[port].channel + 1}`;
+ } else {
+ return `${port + 1}`;
+ }
+ };
+
+ var __setLedState = function(el, color, on) {
+ el.classList.toggle(`led-${color}`, on);
+ el.classList.toggle("led-gray", !on);
+ };
+
+ var __setPowerLedState = function(el, power, busy) {
+ el.classList.toggle("led-green", (power && !busy));
+ el.classList.toggle("led-yellow", busy);
+ el.classList.toggle("led-gray", !(power || busy));
+ };
+
+ var __switchActivePort = function(port) {
+ if (__msd_connected) {
+ wm.error(`
+ Oops! Before port switching, please disconnect an active Mass Storage Drive image first.
+ Otherwise, it will break a current USB operation (OS installation, Live CD, or whatever).
+ `);
+ } else {
+ __sendPost("/api/switch/set_active", {"port": port});
+ }
+ };
+
+ var __switchUplinkBeacon = function(unit) {
+ let state = false;
+ try { state = !__state.beacons.uplinks[unit]; } catch {}; // eslint-disable-line no-empty
+ __sendPost("/api/switch/set_beacon", {"uplink": unit, "state": state});
+ };
+
+ var __switchDownlinkBeacon = function(unit) {
+ let state = false;
+ try { state = !__state.beacons.downlinks[unit]; } catch {}; // eslint-disable-line no-empty
+ __sendPost("/api/switch/set_beacon", {"downlink": unit, "state": state});
+ };
+
+ var __switchPortBeacon = function(port) {
+ let state = false;
+ try { state = !__state.beacons.ports[port]; } catch {}; // eslint-disable-line no-empty
+ __sendPost("/api/switch/set_beacon", {"port": port, "state": state});
+ };
+
+ var __atxClick = function(port, button) {
+ let click_button = function() {
+ __sendPost("/api/switch/atx/click", {"port": port, "button": button});
+ };
+ if ($("switch-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();
+ }
+ };
+
+ var __sendPost = function(url, params, error_callback=null) {
+ tools.httpPost(url, params, function(http) {
+ if (http.status !== 200) {
+ if (error_callback) {
+ error_callback();
+ }
+ wm.error("Switch error", http.responseText);
+ }
+ });
+ };
+
+ __init__();
+}
diff --git a/web/share/js/tools.js b/web/share/js/tools.js
index 046813c6..f5ddae8b 100644
--- a/web/share/js/tools.js
+++ b/web/share/js/tools.js
@@ -78,7 +78,7 @@ export var tools = new function() {
};
self.partial = function(func, ...args) {
- return () => func(...args);
+ return (...rest) => func(...args, ...rest);
};
self.upperFirst = function(text) {
@@ -104,10 +104,6 @@ export var tools = new function() {
return Math.floor(Math.random() * (max - min + 1)) + min;
};
- self.formatHex = function(value) {
- return `0x${value.toString(16).toUpperCase()}`;
- };
-
self.formatSize = function(size) {
if (size > 0) {
let index = Math.floor( Math.log(size) / Math.log(1024) );
diff --git a/web/share/svg/led-beacon.svg b/web/share/svg/led-beacon.svg
new file mode 100644
index 00000000..cf266c74
--- /dev/null
+++ b/web/share/svg/led-beacon.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
+<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M3.51472 0.514648C1.34424 2.68513 0 5.6865 0 8.99993C0 12.3134 1.34424 15.3147 3.51472 17.4852L4.92893 16.071C3.11819 14.2603 2 11.7616 2 8.99993C2 6.23823 3.11819 3.7396 4.92893 1.92886L3.51472 0.514648ZM6.34315 3.34308C4.89653 4.7897 4 6.79107 4 8.99993C4 11.2088 4.89653 13.2102 6.34315 14.6568L7.75736 13.2426C6.67048 12.1557 6 10.6571 6 8.99993C6 7.3428 6.67048 5.84417 7.75736 4.75729L6.34315 3.34308ZM12 4.99995C9.79086 4.99995 8 6.79081 8 8.99995C8 10.8638 9.27477 12.4299 11 12.8739V23H13V12.8739C14.7252 12.4299 16 10.8638 16 8.99995C16 6.79081 14.2091 4.99995 12 4.99995ZM10 8.99995C10 7.89538 10.8954 6.99995 12 6.99995C13.1046 6.99995 14 7.89538 14 8.99995C14 10.1045 13.1046 11 12 11C10.8954 11 10 10.1045 10 8.99995ZM17.6568 3.34308C19.1034 4.7897 20 6.79107 20 8.99993C20 11.2088 19.1034 13.2102 17.6568 14.6568L16.2426 13.2426C17.3295 12.1557 18 10.6571 18 8.99993C18 7.3428 17.3295 5.84417 16.2426 4.75729L17.6568 3.34308ZM20.4852 0.514648C22.6557 2.68513 23.9999 5.6865 23.9999 8.99993C23.9999 12.3134 22.6557 15.3147 20.4852 17.4852L19.071 16.071C20.8817 14.2603 21.9999 11.7616 21.9999 8.99993C21.9999 6.23823 20.8817 3.7396 19.071 1.92886L20.4852 0.514648Z" fill="#000000"/>
+</svg> \ No newline at end of file
diff --git a/web/share/svg/led-usb.svg b/web/share/svg/led-usb.svg
new file mode 100644
index 00000000..a38bcbc7
--- /dev/null
+++ b/web/share/svg/led-usb.svg
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
+<svg fill="#000000" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
+ viewBox="0 0 512 512" xml:space="preserve">
+<g>
+ <g>
+ <g>
+ <rect x="191.996" y="68.27" width="17.067" height="17.067"/>
+ <rect x="123.729" y="68.27" width="17.067" height="17.067"/>
+ <path d="M448,0h-34.133c-4.719,0-8.533,3.814-8.533,8.533V409.6c0,28.237-22.972,51.2-51.2,51.2H243.2
+ c-28.237,0-51.2-22.963-51.2-51.2v-17.067c9.412,0,17.067-7.654,17.067-17.067v-34.133h51.2c9.412,0,17.067-7.654,17.067-17.067
+ V153.6c0-9.412-7.654-17.067-17.067-17.067v-128c0-4.719-3.823-8.533-8.533-8.533H81.067c-4.719,0-8.533,3.814-8.533,8.533v128
+ c-9.421,0-17.067,7.654-17.067,17.067v170.667c0,9.412,7.646,17.067,17.067,17.067h51.2v34.133
+ c0,9.412,7.646,17.067,17.067,17.067V409.6c0,56.465,45.935,102.4,102.4,102.4h110.933c56.457,0,102.4-45.935,102.4-102.4V8.533
+ C456.533,3.814,452.71,0,448,0z M174.933,59.733c0-4.719,3.814-8.533,8.533-8.533H217.6c4.71,0,8.533,3.814,8.533,8.533v34.133
+ c0,4.719-3.823,8.533-8.533,8.533h-34.133c-4.719,0-8.533-3.814-8.533-8.533V59.733z M115.2,102.4
+ c-4.719,0-8.533-3.814-8.533-8.533V59.733c0-4.719,3.814-8.533,8.533-8.533h34.133c4.71,0,8.533,3.814,8.533,8.533v34.133
+ c0,4.719-3.823,8.533-8.533,8.533H115.2z M149.333,375.467H140.8v-34.133H192l0.009,34.133h-8.542H149.333z"/>
+ </g>
+ </g>
+</g>
+</svg> \ No newline at end of file
diff --git a/web/share/svg/led-stream.svg b/web/share/svg/led-video.svg
index 8dfbfbe3..8dfbfbe3 100644
--- a/web/share/svg/led-stream.svg
+++ b/web/share/svg/led-video.svg