diff options
author | Devaev Maxim <[email protected]> | 2020-06-02 20:59:43 +0300 |
---|---|---|
committer | Devaev Maxim <[email protected]> | 2020-06-02 20:59:43 +0300 |
commit | cb9597679d8c9189e671e166de45c707c650bb2f (patch) | |
tree | e0f3e00bafe4c1674f8497e6832f292233d94821 | |
parent | fe7c275d1a827f95d40d774e38b928aee493a41b (diff) |
improved info handler
-rw-r--r-- | PKGBUILD | 2 | ||||
-rw-r--r-- | kvmd/apps/__init__.py | 6 | ||||
-rw-r--r-- | kvmd/apps/kvmd/__init__.py | 2 | ||||
-rw-r--r-- | kvmd/apps/kvmd/api/info.py | 41 | ||||
-rw-r--r-- | kvmd/apps/kvmd/info.py | 103 | ||||
-rw-r--r-- | kvmd/apps/kvmd/server.py | 24 | ||||
-rw-r--r-- | kvmd/apps/kvmd/streamer.py | 8 | ||||
-rw-r--r-- | kvmd/apps/vnc/server.py | 18 | ||||
-rw-r--r-- | web/kvm/index.html | 24 | ||||
-rw-r--r-- | web/share/css/kvm/about.css | 9 | ||||
-rw-r--r-- | web/share/js/index/main.js | 25 | ||||
-rw-r--r-- | web/share/js/kvm/session.js | 50 |
12 files changed, 214 insertions, 98 deletions
@@ -50,7 +50,7 @@ depends=( patch sudo raspberrypi-io-access - "ustreamer>=1.13" + "ustreamer>=1.17" ) makedepends=(python-setuptools) source=("$url/archive/v$pkgver.tar.gz") diff --git a/kvmd/apps/__init__.py b/kvmd/apps/__init__.py index ad28961b..e81e6656 100644 --- a/kvmd/apps/__init__.py +++ b/kvmd/apps/__init__.py @@ -214,9 +214,9 @@ def _get_config_scheme() -> Dict: }, }, - "info": { - "meta": Option("/etc/kvmd/meta.yaml", type=valid_abs_file, unpack_as="meta_path"), - "extras": Option("/usr/share/kvmd/extras", type=valid_abs_dir, unpack_as="extras_path"), + "info": { # Accessed via global config, see kvmd/info.py for details + "meta": Option("/etc/kvmd/meta.yaml", type=valid_abs_file), + "extras": Option("/usr/share/kvmd/extras", type=valid_abs_dir), }, "wol": { diff --git a/kvmd/apps/kvmd/__init__.py b/kvmd/apps/kvmd/__init__.py index 5d690840..dbad7a90 100644 --- a/kvmd/apps/kvmd/__init__.py +++ b/kvmd/apps/kvmd/__init__.py @@ -72,7 +72,7 @@ def main(argv: Optional[List[str]]=None) -> None: force_internal_users=config.auth.internal.force_users, enabled=config.auth.enabled, ), - info_manager=InfoManager(global_config, **config.info._unpack()), + info_manager=InfoManager(global_config), log_reader=LogReader(), wol=WakeOnLan(**config.wol._unpack()), diff --git a/kvmd/apps/kvmd/api/info.py b/kvmd/apps/kvmd/api/info.py new file mode 100644 index 00000000..3e0220a0 --- /dev/null +++ b/kvmd/apps/kvmd/api/info.py @@ -0,0 +1,41 @@ +# ========================================================================== # +# # +# KVMD - The main Pi-KVM daemon. # +# # +# Copyright (C) 2018 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/>. # +# # +# ========================================================================== # + + +from aiohttp.web import Request +from aiohttp.web import Response + +from ..info import InfoManager + +from ..http import exposed_http +from ..http import make_json_response + + +# ===== +class InfoApi: + def __init__(self, info_manager: InfoManager) -> None: + self.__info_manager = info_manager + + # ===== + + @exposed_http("GET", "/info") + async def __state_handler(self, _: Request) -> Response: + return make_json_response(await self.__info_manager.get_state()) diff --git a/kvmd/apps/kvmd/info.py b/kvmd/apps/kvmd/info.py index 50ea6eb1..f85a7656 100644 --- a/kvmd/apps/kvmd/info.py +++ b/kvmd/apps/kvmd/info.py @@ -21,9 +21,12 @@ import os +import asyncio +import platform import contextlib from typing import Dict +from typing import Optional import dbus # pylint: disable=import-error import dbus.exceptions @@ -34,35 +37,87 @@ from ...yamlconf import Section from ...yamlconf.loader import load_yaml_file from ... import aiotools +from ... import aioproc + +from ... import __version__ # ===== class InfoManager: - def __init__( - self, - global_config: Section, - meta_path: str, - extras_path: str, - ) -> None: - + def __init__(self, global_config: Section) -> None: self.__global_config = global_config - self.__meta_path = meta_path - self.__extras_path = extras_path - - async def get_meta(self) -> Dict: - return (await aiotools.run_async(load_yaml_file, self.__meta_path)) - - async def get_extras(self) -> Dict: - return (await aiotools.run_async(self.__inner_get_extras)) - - def __inner_get_extras(self) -> Dict: - extras: Dict[str, Dict] = {} - for app in os.listdir(self.__extras_path): - if app[0] != "." and os.path.isdir(os.path.join(self.__extras_path, app)): - extras[app] = load_yaml_file(os.path.join(self.__extras_path, app, "manifest.yaml")) - self.__rewrite_app_daemon(extras[app]) - self.__rewrite_app_port(extras[app]) - return extras + + async def get_state(self) -> Dict: + (streamer_info, meta_info, extras_info) = await asyncio.gather( + self.__get_streamer_info(), + self.__get_meta_info(), + self.__get_extras_info(), + ) + uname_info = platform.uname() # Uname using the internal cache + return { + "system": { + "kvmd": {"version": __version__}, + "streamer": streamer_info, + "kernel": { + field: getattr(uname_info, field) + for field in ["system", "release", "version", "machine"] + }, + }, + "meta": meta_info, + "extras": extras_info, + } + + # ===== + + async def __get_streamer_info(self) -> Dict: + version = "" + features: Dict[str, bool] = {} + try: + path = self.__global_config.kvmd.streamer.cmd[0] + ((_, version), (_, features_text)) = await asyncio.gather( + aioproc.read_process([path, "--version"], err_to_null=True), + aioproc.read_process([path, "--features"], err_to_null=True), + ) + except Exception: + get_logger(0).exception("Can't get streamer info") + else: + try: + for line in features_text.split("\n"): + (status, name) = map(str.strip, line.split(" ")) + features[name] = (status == "+") + except Exception: + get_logger(0).exception("Can't parse streamer features") + return { + "app": os.path.basename(path), + "version": version, + "features": features, + } + + async def __get_meta_info(self) -> Optional[Dict]: + try: + return ((await aiotools.run_async(load_yaml_file, self.__global_config.kvmd.info.meta)) or {}) + except Exception: + get_logger(0).exception("Can't parse meta") + return None + + async def __get_extras_info(self) -> Optional[Dict]: + return (await aiotools.run_async(self.__inner_get_extras_info)) + + # ===== + + def __inner_get_extras_info(self) -> Optional[Dict]: + try: + extras_path = self.__global_config.kvmd.info.extras + extras: Dict[str, Dict] = {} + for app in os.listdir(extras_path): + if app[0] != "." and os.path.isdir(os.path.join(extras_path, app)): + extras[app] = load_yaml_file(os.path.join(extras_path, app, "manifest.yaml")) + self.__rewrite_app_daemon(extras[app]) + self.__rewrite_app_port(extras[app]) + return extras + except Exception: + get_logger(0).exception("Can't parse extras") + return None def __rewrite_app_daemon(self, extras: Dict) -> None: daemon = extras.get("daemon", "") diff --git a/kvmd/apps/kvmd/server.py b/kvmd/apps/kvmd/server.py index 73200cd8..423e77dc 100644 --- a/kvmd/apps/kvmd/server.py +++ b/kvmd/apps/kvmd/server.py @@ -57,8 +57,6 @@ from ...validators.kvm import valid_stream_fps from ... import aiotools -from ... import __version__ - from .auth import AuthManager from .info import InfoManager from .logreader import LogReader @@ -78,6 +76,7 @@ from .http import HttpServer from .api.auth import AuthApi from .api.auth import check_request_auth +from .api.info import InfoApi from .api.log import LogApi from .api.wol import WolApi from .api.hid import HidApi @@ -129,6 +128,7 @@ class KvmdServer(HttpServer): # pylint: disable=too-many-arguments,too-many-ins self.__apis: List[object] = [ self, AuthApi(auth_manager), + InfoApi(info_manager), LogApi(log_reader), WolApi(wol), HidApi(hid, keymap_path), @@ -148,24 +148,6 @@ class KvmdServer(HttpServer): # pylint: disable=too-many-arguments,too-many-ins self.__reset_streamer = False self.__new_streamer_params: Dict = {} - async def __make_info(self) -> Dict: - streamer_info = await self.__streamer.get_info() - return { - "version": { - "kvmd": __version__, - "streamer": streamer_info["version"], - }, - "streamer": streamer_info["app"], - "meta": await self.__info_manager.get_meta(), - "extras": await self.__info_manager.get_extras(), - } - - # ===== SYSTEM - - @exposed_http("GET", "/info") - async def __info_handler(self, _: aiohttp.web.Request) -> aiohttp.web.Response: - return make_json_response(await self.__make_info()) - # ===== STREAMER CONTROLLER @exposed_http("POST", "/streamer/set_params") @@ -198,7 +180,7 @@ class KvmdServer(HttpServer): # pylint: disable=too-many-arguments,too-many-ins await self.__register_socket(ws) try: await asyncio.gather(*[ - self.__broadcast_event(_Events.INFO_STATE, (await self.__make_info())), + self.__broadcast_event(_Events.INFO_STATE, (await self.__info_manager.get_state())), self.__broadcast_event(_Events.WOL_STATE, self.__wol.get_state()), self.__broadcast_event(_Events.HID_STATE, (await self.__hid.get_state())), self.__broadcast_event(_Events.ATX_STATE, self.__atx.get_state()), diff --git a/kvmd/apps/kvmd/streamer.py b/kvmd/apps/kvmd/streamer.py index 26d14b62..2ef1f72c 100644 --- a/kvmd/apps/kvmd/streamer.py +++ b/kvmd/apps/kvmd/streamer.py @@ -20,7 +20,6 @@ # ========================================================================== # -import os import signal import asyncio import asyncio.subprocess @@ -246,13 +245,6 @@ class Streamer: # pylint: disable=too-many-instance-attributes # ===== - async def get_info(self) -> Dict: - version = (await aioproc.read_process([self.__cmd[0], "--version"], err_to_null=True))[1] - return { - "app": os.path.basename(self.__cmd[0]), - "version": version, - } - async def make_snapshot(self, save: bool, load: bool, allow_offline: bool) -> Optional[StreamerSnapshot]: if load: return self.__snapshot diff --git a/kvmd/apps/vnc/server.py b/kvmd/apps/vnc/server.py index a5517c11..4d28555f 100644 --- a/kvmd/apps/vnc/server.py +++ b/kvmd/apps/vnc/server.py @@ -150,13 +150,17 @@ class _Client(RfbClient): # pylint: disable=too-many-instance-attributes async def __process_ws_event(self, event: Dict) -> None: if event["event_type"] == "info_state": - host = event["event"]["meta"].get("server", {}).get("host") - if isinstance(host, str): - name = f"Pi-KVM: {host}" - async with self.__lock: - if self._encodings.has_rename: - await self._send_rename(name) - self.__shared_params.name = name + try: + host = event["event"]["meta"]["server"]["host"] + except Exception: + host = None + else: + if isinstance(host, str): + name = f"Pi-KVM: {host}" + async with self.__lock: + if self._encodings.has_rename: + await self._send_rename(name) + self.__shared_params.name = name elif event["event_type"] == "hid_state": async with self.__lock: diff --git a/web/kvm/index.html b/web/kvm/index.html index 52938a12..36459d21 100644 --- a/web/kvm/index.html +++ b/web/kvm/index.html @@ -650,25 +650,18 @@ <tr> <td colspan="2" class="copyright">Copyright © 2018 Pi-KVM Developers Team</td> </tr> - <tr> - <td><br></td> - </tr> - <tr class="version"> - <td>KVMD:</td> - <td id="about-version-kvmd"></td> - </tr> - <tr class="version"> - <td>Streamer:</td> - <td id="about-version-streamer"></td> - </tr> </table> </td> </tr> </table> + <br> <div class="tabs"> <input type="radio" name="about-tab-button" id="about-tab-info-button" value="" checked> <label for="about-tab-info-button">Info</label> + <input type="radio" name="about-tab-button" id="about-tab-version-button" value=""> + <label for="about-tab-version-button">Version</label> + <input type="radio" name="about-tab-button" id="about-tab-thanks-button" value=""> <label for="about-tab-thanks-button">Thanks</label> @@ -677,8 +670,15 @@ <span class="code-comment">No data</span> </div> </div> + + <div id="about-tab-version-content"> + <div id="about-version" class="code"> + <span class="code-comment">No data</span> + </div> + </div> + <div id="about-tab-thanks-content"> - <div class="code" style="max-height: 300px"> + <div id="about-thanks" class="code"> <span class="code-comment"> // These kind people donated money to the Pi-KVM project<br> // and supported the work on it. We are very grateful<br> diff --git a/web/share/css/kvm/about.css b/web/share/css/kvm/about.css index 306f0a73..d7cc3af1 100644 --- a/web/share/css/kvm/about.css +++ b/web/share/css/kvm/about.css @@ -41,15 +41,12 @@ div#about td.copyright { font-size: 0.8em; } -div#about tr.version { - font-family: monospace; -} - -div#about div#about-meta { - height: 200px; +div#about div#about-meta, div#about-version, div#about-thanks { + height: 250px; } #about-tab-info-button:checked~#about-tab-info-content, +#about-tab-version-button:checked~#about-tab-version-content, #about-tab-thanks-button:checked~#about-tab-thanks-content { display: block; } diff --git a/web/share/js/index/main.js b/web/share/js/index/main.js index ba33b0aa..497a4d73 100644 --- a/web/share/js/index/main.js +++ b/web/share/js/index/main.js @@ -56,15 +56,20 @@ function __loadKvmdInfo() { if (http.status === 200) { let info = JSON.parse(http.responseText).result; - let 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; - } - }); + let apps = []; + if (info.extras === null) { + wm.error("Not all applications in the menu can be displayed<br>due an error. See KVMD logs for details."); + } 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>"; @@ -79,7 +84,7 @@ function __loadKvmdInfo() { $("apps").innerHTML += __makeApp("logout-button", "#", "share/svg/logout.svg", "Logout"); tools.setOnClick($("logout-button"), __logout); - if (info.meta && info.meta.server && info.meta.server.host) { + if (info.meta !== null && info.meta.server && info.meta.server.host) { $("kvmd-meta-server-host").innerHTML = info.meta.server.host; document.title = `Pi-KVM Index: ${info.meta.server.host}`; } else { diff --git a/web/share/js/kvm/session.js b/web/share/js/kvm/session.js index 5df1481c..7b68ac88 100644 --- a/web/share/js/kvm/session.js +++ b/web/share/js/kvm/session.js @@ -55,8 +55,8 @@ export function Session() { /************************************************************************/ - var __setKvmdInfo = function(state) { - if (state.meta) { + var __setAboutInfo = function(state) { + if (state.meta != null) { let text = JSON.stringify(state.meta, undefined, 4).replace(/ /g, " ").replace(/\n/g, "<br>"); $("about-meta").innerHTML = ` <span class="code-comment">// The Pi-KVM metadata.<br> @@ -75,8 +75,48 @@ export function Session() { } } - $("about-version-kvmd").innerHTML = state.version.kvmd; - $("about-version-streamer").innerHTML = `${state.version.streamer} (${state.streamer})`; + let sys = state.system; + $("about-version").innerHTML = ` + KVMD: <span class="code-comment">${sys.kvmd.version}</span><br> + <hr> + Streamer: <span class="code-comment">${sys.streamer.version} (${sys.streamer.app})</span> + ${__formatStreamerFeatures(sys.streamer.features)} + <hr> + ${sys.kernel.system} kernel: + ${__formatUname(sys.kernel)} + `; + }; + + 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([ + field[0].toUpperCase() + field.slice(1), + kernel[field], + ]); + } + } + return __formatUl(pairs); + }; + + var __formatUl = function(pairs) { + let text = "<ul>"; + for (let pair of pairs) { + text += `<li>${pair[0]}: <span class="code-comment">${pair[1]}</span></li>`; + } + return text + "</ul>"; }; var __startSession = function() { @@ -117,7 +157,7 @@ export function Session() { let data = JSON.parse(event.data); switch (data.event_type) { case "pong": __missed_heartbeats = 0; break; - case "info_state": __setKvmdInfo(data.event); break; + case "info_state": __setAboutInfo(data.event); break; case "wol_state": __wol.setState(data.event); break; case "hid_state": __hid.setState(data.event); break; case "atx_state": __atx.setState(data.event); break; |