pikvm/pikvm#803: Ability to use LEDs in GPIO title

This commit is contained in:
Maxim Devaev 2022-10-08 06:09:33 +03:00
parent 16d9c3815f
commit a39d3dffbe
15 changed files with 84 additions and 33 deletions

View File

@ -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),
},

View File

@ -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"

View File

@ -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)]

View File

@ -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", [
([], []),

View File

@ -60,7 +60,7 @@
<ul id="navbar">
<li class="left"><a id="logo" href="/">&larr;&nbsp;&nbsp;<img class="svg-gray" src="/share/svg/logo.svg" alt="&amp;pi;-kvm"></a></li>
<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>

View File

@ -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]

View File

@ -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")

View File

@ -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]

View File

@ -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:

View File

@ -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

View File

@ -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]

View File

@ -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")

View File

@ -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);

View File

@ -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\">";

View File

@ -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")) {