summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--PKGBUILD2
-rw-r--r--kvmd/apps/__init__.py6
-rw-r--r--kvmd/apps/kvmd/__init__.py2
-rw-r--r--kvmd/apps/kvmd/api/info.py41
-rw-r--r--kvmd/apps/kvmd/info.py103
-rw-r--r--kvmd/apps/kvmd/server.py24
-rw-r--r--kvmd/apps/kvmd/streamer.py8
-rw-r--r--kvmd/apps/vnc/server.py18
-rw-r--r--web/kvm/index.html24
-rw-r--r--web/share/css/kvm/about.css9
-rw-r--r--web/share/js/index/main.js25
-rw-r--r--web/share/js/kvm/session.js50
12 files changed, 214 insertions, 98 deletions
diff --git a/PKGBUILD b/PKGBUILD
index 0345c6eb..ccc610ef 100644
--- a/PKGBUILD
+++ b/PKGBUILD
@@ -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 &copy; 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, "&nbsp;").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;