mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2025-12-12 01:00:29 +08:00
pikvm/pikvm#803: Ability to use LEDs in GPIO title
This commit is contained in:
parent
16d9c3815f
commit
a39d3dffbe
@ -93,6 +93,7 @@ from ..validators.kvm import valid_stream_h264_gop
|
||||
from ..validators.ugpio import valid_ugpio_driver
|
||||
from ..validators.ugpio import valid_ugpio_channel
|
||||
from ..validators.ugpio import valid_ugpio_mode
|
||||
from ..validators.ugpio import valid_ugpio_view_title
|
||||
from ..validators.ugpio import valid_ugpio_view_table
|
||||
|
||||
from ..validators.hw import valid_tty_speed
|
||||
@ -482,7 +483,7 @@ def _get_config_scheme() -> dict:
|
||||
"scheme": {}, # Dymanic content
|
||||
"view": {
|
||||
"header": {
|
||||
"title": Option("GPIO"),
|
||||
"title": Option("GPIO", type=valid_ugpio_view_title),
|
||||
},
|
||||
"table": Option([], type=valid_ugpio_view_table),
|
||||
},
|
||||
|
||||
@ -311,6 +311,27 @@ class UserGpio:
|
||||
# =====
|
||||
|
||||
def __make_view(self) -> dict:
|
||||
return {
|
||||
"header": {"title": self.__make_view_title()},
|
||||
"table": self.__make_view_table(),
|
||||
}
|
||||
|
||||
def __make_view_title(self) -> list[dict]:
|
||||
raw_title = self.__view["header"]["title"]
|
||||
title: list[dict] = []
|
||||
if isinstance(raw_title, list):
|
||||
for item in raw_title:
|
||||
if item.startswith("#") or len(item) == 0:
|
||||
title.append(self.__make_item_label(item))
|
||||
else:
|
||||
parts = list(map(str.strip, item.split("|", 2)))
|
||||
if parts and parts[0] in self.__inputs:
|
||||
title.append(self.__make_item_input(parts))
|
||||
else:
|
||||
title.append(self.__make_item_label(f"#{raw_title}"))
|
||||
return title
|
||||
|
||||
def __make_view_table(self) -> list[list[dict] | None]:
|
||||
table: list[list[dict] | None] = []
|
||||
for row in self.__view["table"]:
|
||||
if len(row) == 0:
|
||||
@ -320,29 +341,24 @@ class UserGpio:
|
||||
items: list[dict] = []
|
||||
for item in map(str.strip, row):
|
||||
if item.startswith("#") or len(item) == 0:
|
||||
items.append(self.__make_view_label(item))
|
||||
items.append(self.__make_item_label(item))
|
||||
else:
|
||||
parts = list(map(str.strip, item.split("|", 2)))
|
||||
if parts:
|
||||
if parts[0] in self.__inputs:
|
||||
items.append(self.__make_view_input(parts))
|
||||
items.append(self.__make_item_input(parts))
|
||||
elif parts[0] in self.__outputs:
|
||||
items.append(self.__make_view_output(parts))
|
||||
items.append(self.__make_item_output(parts))
|
||||
table.append(items)
|
||||
return table
|
||||
|
||||
return {
|
||||
"header": self.__view["header"],
|
||||
"table": table,
|
||||
}
|
||||
|
||||
def __make_view_label(self, item: str) -> dict:
|
||||
assert item.startswith("#")
|
||||
def __make_item_label(self, item: str) -> dict:
|
||||
return {
|
||||
"type": "label",
|
||||
"text": item[1:].strip(),
|
||||
}
|
||||
|
||||
def __make_view_input(self, parts: list[str]) -> dict:
|
||||
def __make_item_input(self, parts: list[str]) -> dict:
|
||||
assert len(parts) >= 1
|
||||
color = (parts[1] if len(parts) > 1 else None)
|
||||
if color not in ["green", "yellow", "red"]:
|
||||
@ -353,7 +369,7 @@ class UserGpio:
|
||||
"color": color,
|
||||
}
|
||||
|
||||
def __make_view_output(self, parts: list[str]) -> dict:
|
||||
def __make_item_output(self, parts: list[str]) -> dict:
|
||||
assert len(parts) >= 1
|
||||
confirm = False
|
||||
text = "Click"
|
||||
|
||||
@ -46,6 +46,10 @@ def valid_ugpio_mode(arg: Any, variants: set[str]) -> str:
|
||||
return check_string_in_list(arg, "GPIO driver's pin mode", variants)
|
||||
|
||||
|
||||
def valid_ugpio_view_title(arg: Any) -> (str | list[str]):
|
||||
return (list(map(str, arg)) if isinstance(arg, list) else str(arg))
|
||||
|
||||
|
||||
def valid_ugpio_view_table(arg: Any) -> list[list[str]]: # pylint: disable=inconsistent-return-statements
|
||||
try:
|
||||
return [list(map(str, row)) for row in list(arg)]
|
||||
|
||||
@ -29,6 +29,7 @@ from kvmd.validators import ValidatorError
|
||||
from kvmd.validators.ugpio import valid_ugpio_driver
|
||||
from kvmd.validators.ugpio import valid_ugpio_channel
|
||||
from kvmd.validators.ugpio import valid_ugpio_mode
|
||||
from kvmd.validators.ugpio import valid_ugpio_view_title
|
||||
from kvmd.validators.ugpio import valid_ugpio_view_table
|
||||
|
||||
from kvmd.plugins.ugpio import UserGpioModes
|
||||
@ -93,6 +94,19 @@ def test_fail__valid_ugpio_mode(arg: Any) -> None:
|
||||
print(valid_ugpio_mode(arg, UserGpioModes.ALL))
|
||||
|
||||
|
||||
# =====
|
||||
@pytest.mark.parametrize("arg,retval", [
|
||||
([], []),
|
||||
("", ""),
|
||||
("ab", "ab"),
|
||||
([""], [""]),
|
||||
([[]], ["[]"]),
|
||||
(["a", None], ["a", "None"]),
|
||||
])
|
||||
def test_ok__valid_ugpio_view_title(arg: Any, retval: Any) -> None:
|
||||
assert valid_ugpio_view_title(arg) == retval
|
||||
|
||||
|
||||
# =====
|
||||
@pytest.mark.parametrize("arg,retval", [
|
||||
([], []),
|
||||
|
||||
@ -60,7 +60,7 @@
|
||||
<ul id="navbar">
|
||||
<li class="left"><a id="logo" href="/">← <img class="svg-gray" src="/share/svg/logo.svg" alt="&pi;-kvm"></a></li>
|
||||
<div class="hidden" id="hw-health-dropdown">
|
||||
<li class="left"><a class="menu-button" href="#"><img class="hidden" data-dont-hide-menu id="hw-health-undervoltage-led" src="/share/svg/led-undervoltage.svg"><img class="hidden" data-dont-hide-menu id="hw-health-overheating-led" src="/share/svg/led-overheating.svg"></a>
|
||||
<li class="left"><a class="menu-button" href="#"><img class="hidden" id="hw-health-undervoltage-led" src="/share/svg/led-undervoltage.svg"><img class="hidden" id="hw-health-overheating-led" src="/share/svg/led-overheating.svg"></a>
|
||||
<div class="menu" data-dont-hide-menu>
|
||||
<div class="text">
|
||||
<table>
|
||||
@ -108,7 +108,7 @@
|
||||
</li>
|
||||
</div>
|
||||
<div class="hidden" id="fan-health-dropdown">
|
||||
<li class="left"><a class="menu-button" href="#"><img class="hidden" data-dont-hide-menu id="fan-health-led" src="/share/svg/led-fan.svg"></a>
|
||||
<li class="left"><a class="menu-button" href="#"><img class="hidden" id="fan-health-led" src="/share/svg/led-fan.svg"></a>
|
||||
<div class="menu" data-dont-hide-menu>
|
||||
<div class="text">
|
||||
<table>
|
||||
@ -139,7 +139,7 @@
|
||||
</div>
|
||||
</li>
|
||||
</div>
|
||||
<li class="right" id="system-dropdown"><a class="menu-button" href="#"><img class="led-gray" data-dont-hide-menu id="link-led" src="/share/svg/led-link.svg"><img class="led-gray" data-dont-hide-menu id="stream-led" src="/share/svg/led-stream.svg"><img class="led-gray" data-dont-hide-menu id="hid-keyboard-led" src="/share/svg/led-hid-keyboard.svg"><img class="led-gray" data-dont-hide-menu id="hid-mouse-led" src="/share/svg/led-hid-mouse.svg">System</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-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>
|
||||
<div class="menu" data-dont-hide-menu id="system-menu">
|
||||
<table class="kv" style="width: calc(100% - 20px)">
|
||||
<tr>
|
||||
@ -335,7 +335,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li class="right feature-disabled" id="atx-dropdown"><a class="menu-button" href="#"><img class="led-gray" data-dont-hide-menu id="atx-power-led" src="/share/svg/led-atx-power.svg"><img class="led-gray" data-dont-hide-menu id="atx-hdd-led" src="/share/svg/led-atx-hdd.svg">ATX</a>
|
||||
<li class="right feature-disabled" id="atx-dropdown"><a class="menu-button" href="#"><img class="led-gray" id="atx-power-led" src="/share/svg/led-atx-power.svg"><img class="led-gray" id="atx-hdd-led" src="/share/svg/led-atx-hdd.svg"><span>ATX</span></a>
|
||||
<div class="menu" data-dont-hide-menu>
|
||||
<div class="text"><b>Control the server's power<br></b><sub>Use the short click for ACPI shutdown</sub></div>
|
||||
<hr>
|
||||
@ -359,7 +359,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li class="right feature-disabled" id="msd-dropdown"><a class="menu-button" href="#"><img class="led-gray" data-dont-hide-menu id="msd-led" src="/share/svg/led-msd.svg">Drive</a>
|
||||
<li class="right feature-disabled" id="msd-dropdown"><a class="menu-button" href="#"><img class="led-gray" id="msd-led" src="/share/svg/led-msd.svg"><span>Drive</span></a>
|
||||
<div class="menu" data-dont-hide-menu id="msd-menu">
|
||||
<div class="text"><b>Mass Storage Drive: </b><span id="msd-status"></span><br></div>
|
||||
<hr>
|
||||
@ -570,7 +570,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li class="right" id="macro-dropdown"><a class="menu-button" href="#"><img class="led-gray" data-dont-hide-menu id="hid-recorder-led" src="/share/svg/led-gear.svg">Macro</a>
|
||||
<li class="right" id="macro-dropdown"><a class="menu-button" href="#"><img class="led-gray" id="hid-recorder-led" src="/share/svg/led-gear.svg"><span>Macro</span></a>
|
||||
<div class="menu" data-dont-hide-menu>
|
||||
<div class="text"><b>Record and play HID/ATX/GPIO actions<br></b><sub>For security reasons, the record will not be saved on the PiKVM</sub></div>
|
||||
<hr>
|
||||
@ -612,7 +612,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li class="right" id="text-dropdown"><a class="menu-button" href="#"><img class="feature-disabled" data-dont-hide-menu id="stream-ocr-led" src="/share/svg/led-gear.svg">Text</a>
|
||||
<li class="right" id="text-dropdown"><a class="menu-button" href="#"><img class="feature-disabled" id="stream-ocr-led" src="/share/svg/led-gear.svg"><span>Text</span></a>
|
||||
<div class="menu" data-dont-hide-menu>
|
||||
<div class="text"><b>Paste text as keypress sequence<br></b><sub>Please note that PiKVM cannot switch the keyboard layout</sub></div>
|
||||
<hr>
|
||||
@ -747,7 +747,7 @@
|
||||
</table>
|
||||
</div>
|
||||
</li>
|
||||
<li class="right feature-disabled" id="gpio-dropdown"><a class="menu-button" id="gpio-menu-button" href="#">GPIO</a>
|
||||
<li class="right feature-disabled" id="gpio-dropdown"><a class="menu-button" id="gpio-menu-button" href="#"><span>GPIO</span></a>
|
||||
<div class="menu" data-dont-hide-menu id="gpio-menu"></div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@ -2,7 +2,7 @@ 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")
|
||||
| ATX
|
||||
span ATX
|
||||
div(data-dont-hide-menu class="menu")
|
||||
div(class="text")
|
||||
b Control the server's power#[br]
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
li(id="gpio-dropdown" class="right feature-disabled")
|
||||
a(class="menu-button" id="gpio-menu-button" href="#")
|
||||
| GPIO
|
||||
span GPIO
|
||||
div(data-dont-hide-menu id="gpio-menu" class="menu")
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
li(id="macro-dropdown" class="right")
|
||||
a(class="menu-button" href="#")
|
||||
+navbar_led("hid-recorder-led", "led-gear")
|
||||
| Macro
|
||||
span Macro
|
||||
div(data-dont-hide-menu class="menu")
|
||||
div(class="text")
|
||||
b Record and play HID/ATX/GPIO actions#[br]
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
li(id="msd-dropdown" class="right feature-disabled")
|
||||
a(class="menu-button" href="#")
|
||||
+navbar_led("msd-led", "led-msd")
|
||||
| Drive
|
||||
span Drive
|
||||
div(data-dont-hide-menu id="msd-menu" class="menu")
|
||||
div(class="text")
|
||||
b Mass Storage Drive:
|
||||
|
||||
@ -4,7 +4,7 @@ li(id="system-dropdown" class="right")
|
||||
+navbar_led("stream-led", "led-stream")
|
||||
+navbar_led("hid-keyboard-led", "led-hid-keyboard")
|
||||
+navbar_led("hid-mouse-led", "led-hid-mouse")
|
||||
| System
|
||||
span System
|
||||
div(data-dont-hide-menu id="system-menu" class="menu")
|
||||
table(class="kv" style="width: calc(100% - 20px)")
|
||||
tr
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
li(id="text-dropdown" class="right")
|
||||
a(class="menu-button" href="#")
|
||||
+navbar_led("stream-ocr-led", "led-gear", "feature-disabled")
|
||||
| Text
|
||||
span Text
|
||||
div(data-dont-hide-menu class="menu")
|
||||
div(class="text")
|
||||
b Paste text as keypress sequence#[br]
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
mixin navbar_led(id, icon, cls="led-gray")
|
||||
img(data-dont-hide-menu id=id, class=cls src=`${svg_dir}/${icon}.svg`)
|
||||
img(id=id, class=cls src=`${svg_dir}/${icon}.svg`)
|
||||
|
||||
mixin menu_message(icon, short, classes="")
|
||||
div(class="text")
|
||||
|
||||
@ -44,6 +44,7 @@ ul#navbar li.left {
|
||||
}
|
||||
|
||||
ul#navbar li a#logo {
|
||||
height: 50px; /* Чтобы вертикальные разделители не вылезали за пределы навбара */
|
||||
line-height: 50px;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
@ -55,6 +56,7 @@ ul#navbar li a#logo {
|
||||
}
|
||||
|
||||
ul#navbar li a.menu-button {
|
||||
height: 50px; /* То же самое */
|
||||
line-height: 50px;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
@ -83,7 +85,7 @@ ul#navbar li a.menu-button:hover:not(.active) {
|
||||
@media only screen and (pointer: coarse) {
|
||||
ul#navbar li a#logo:hover:not(.active),
|
||||
ul#navbar li a.menu-button:hover:not(.active) {
|
||||
background-color: var(--cs-navbar-default-bg) !important;
|
||||
background-color: var(--cs-navbar-default-bg);
|
||||
}
|
||||
}
|
||||
|
||||
@ -92,11 +94,15 @@ ul#navbar li a#logo img {
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
ul#navbar li a.menu-button span,
|
||||
ul#navbar li a.menu-button img {
|
||||
vertical-align: middle;
|
||||
margin-right: 10px;
|
||||
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);
|
||||
|
||||
@ -72,7 +72,16 @@ export function Gpio(__recorder) {
|
||||
self.setModel = function(model) {
|
||||
tools.feature.setEnabled($("gpio-dropdown"), model.view.table.length);
|
||||
if (model.view.table.length) {
|
||||
$("gpio-menu-button").innerHTML = `${model.view.header.title}`;
|
||||
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 content = "<table class=\"kv\">";
|
||||
|
||||
@ -344,8 +344,9 @@ function __WindowManager() {
|
||||
|
||||
var __globalMouseButtonHandler = function(event) {
|
||||
if (
|
||||
event.target.matches && !event.target.matches(".menu-button")
|
||||
&& event.target.closest && !event.target.closest(".modal")
|
||||
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.hasAttribute("data-force-hide-menu")) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user