diff --git a/README.md b/README.md index be24e3f..334c10d 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ A web-based administration interface for [pyobs](https://github.com/pyobs/pyobs- the robotic telescope framework. It lets you start, stop, and restart modules, tail and filter their logs, and view and edit their configuration files — all from a browser. +![Dashboard showing modules grouped under Stopped and Deactivated headings, with summary tiles and per-row quick-action buttons](docs/source/_static/screenshots/dashboard.jpg) + ## Features - **Dashboard** — sortable list view of all modules with: @@ -18,7 +20,7 @@ filter their logs, and view and edit their configuration files — all from a br - Responsive: on small screens the table collapses to status dot + name + log counts + actions - **Module detail** — per-module view with four tabs: - *Overview* — current status, PID, uptime, CPU and memory usage, running `pyobs-*` package versions (flagged when they lag the installed set), per-level log message counts (last 24 h), XMPP connection state (if enabled), start/restart/stop/activate/deactivate control - - *Logs* — live log tail with text filter, time-range filter (set a start date to load all logs since that instant, or click a line to set it), colour-coded by severity, auto-refresh; scrolling to the top auto-loads older entries (journald-backed modules, or file-backed modules once a start date is set, see [journald-logs.md](specs/design/journald-logs.md)) + - *Logs* — live log tail with text filter, time-range filter (set a start date to load all logs since that instant, or click a line to set it), colour-coded by severity, auto-refresh; scrolling to the top auto-loads older entries (journald-backed modules, or file-backed modules once a start date is set) - *Config* — YAML editor with syntax highlighting and colour-coded `{include}` lines; included shared configs are shown as clickable links - *ACL* — point-and-click editor for the module's `acl:` block: click to allow/deny known modules, add other callers, toggle enforce/log mode - **New module** — a "+" next to the sidebar's Modules section creates a brand-new `.yaml` config (a minimal starter with just a `class:` key) and takes you straight to its Config tab to fill in the rest @@ -26,7 +28,7 @@ filter their logs, and view and edit their configuration files — all from a br - **Packages** (`/packages/`) — every installed `pyobs-*` package (plus anything else listed in `PYOBS_MANAGED_PACKAGES`) with its installed and latest-PyPI version, and a one-click Update button; git/URL-installed packages get a Reinstall action instead (see [Package management](#package-management)) - **Overview** (`/overview/`) — fleet-wide summary, one row per configured host: reachable or not, running/stopped/total counts, aggregate CPU/RAM, linking into that host's own Dashboard, plus a package-version matrix (one row per `pyobs-*` package, one column per host) so version drift across the fleet is visible at a glance. Deliberately no bulk or per-module actions — those stay on the per-host Dashboard, since a fleet-wide "Stop All" from one button is a real footgun - **All Logs** (`/logs/`) — fleet-wide live log tail across every module on every configured host, same filtering and scroll-to-load-older behaviour as a module's own Logs tab -- **ACL Matrix** (`/acl/`) — fleet-wide read-only matrix of which module can call which, merged across every configured host (see [acl-matrix.md](specs/design/acl-matrix.md)) +- **ACL Matrix** (`/acl/`) — fleet-wide read-only matrix of which module can call which, merged across every configured host - **Hub mode** — control multiple remote pyobs hosts from a single browser tab; remote hosts are listed in the sidebar and all actions are proxied transparently - **ejabberd / XMPP status** (optional) — dashboard summary tile and per-module connected/not-connected indicator, plus a session/last-seen/registered-account block on each module's own page, for modules with a `comm.user` in their config — closes the gap between "the process is running" and "the module is actually reachable over XMPP" (see [ejabberd integration](#ejabberd-integration)) - **ejabberd / XMPP user management** (optional, builds on the above) — register, reset password, ban/unban, unregister, and kick XMPP accounts, either from a module's own Overview tab or from a fleet-wide **Users** page (`/xmpp-users/`) listing every registered account across every host, cross-referenced against which module(s) use it and which one is actually running. Safe by design for an identity shared across more than one module's `comm.user` — a password reset writes back to every module sharing it, and destructive actions name which other modules are affected before you confirm (see [ejabberd user management](#ejabberd-user-management)) @@ -193,13 +195,12 @@ PYOBS_LOG_DIR = "/opt/pyobs/log" # directory containing *.log files PYOBS_RUN_DIR = "/opt/pyobs/run" # directory for PID files PYOBS_LOG_LEVEL = "info" # log level passed to pyobs on start PYOBS_LOG_BACKEND = None # None (default): auto-detect from pyobsd's own - # config; "file" or "journald" to override -- - # see specs/design/journald-logs.md. If "journald": - # the account running pyobs-web-admin needs journal - # read access — `usermod -aG systemd-journal - # ` (preferred over `adm`, see the doc's - # "Which group to grant") — otherwise logs come - # back silently empty, no error. + # config; "file" or "journald" to override. If + # "journald": the account running pyobs-web-admin + # needs journal read access — `usermod -aG + # systemd-journal ` (preferred over `adm`, + # which grants broader log access than needed) — + # otherwise logs come back silently empty, no error. # Packages page (optional — see Package management section) PYOBS_MANAGED_PACKAGES = [] # e.g. ["pyobs-core[full]", "my-custom-driver", @@ -389,7 +390,7 @@ it as-is; `mod_http_api` can also expose account-management commands trusted, no password or token is involved. This blocks the network (a request from outside the host is rejected), but **not** other processes on the same machine, which get the same access pyobs-web-admin does. That's an accepted tradeoff for a dedicated, single-purpose -observatory control host — see `specs/design/ejabberd-integration.md` if your threat model is different. +observatory control host, not a shared one — reassess if that's not your deployment. --- @@ -539,12 +540,12 @@ end up as a literal `pip install` command-line argument, visible to any other lo - **Discovery** — all `*.yaml` files in `PYOBS_CONFIG_DIR` (excluding `*.shared.yaml`) are treated as modules. `*.shared.yaml` files are listed separately as shared configs. - **Creating a module** — the "New module" button writes a fresh `.yaml` with a minimal starter (`class:` key only); `PYOBS_CONFIG_DIR` is created automatically if it doesn't exist yet. - **Activate / Deactivate** — deactivating a module renames its config from `name.yaml` to `_name.yaml` (stopping it first if running); activating renames it back. Deactivated modules are excluded from *Start All* and *Restart All*. -- **Start** — runs `pyobs --pid-file /.pid --log-file /.log --log-level `. pyobs daemonises itself via `python-daemon`. If the effective log backend is `"journald"` (see below), `--syslog` is passed instead of `--log-file` — pyobs then logs directly to the systemd journal, tagged `SYSLOG_IDENTIFIER=pyobs` and `PYOBS_MODULE=` (see [journald-logs.md](specs/design/journald-logs.md)). +- **Start** — runs `pyobs --pid-file /.pid --log-file /.log --log-level `. pyobs daemonises itself via `python-daemon`. If the effective log backend is `"journald"` (see below), `--syslog` is passed instead of `--log-file` — pyobs then logs directly to the systemd journal, tagged `SYSLOG_IDENTIFIER=pyobs` and `PYOBS_MODULE=`. - **Stop** — sends `SIGTERM` to the PID in the PID file; falls back to `SIGKILL` after 5 s. - **Restart** — stop followed by start. - **Status** — checks whether the process with the stored PID is alive (`os.kill(pid, 0)`). - **Resource usage** — uptime, CPU %, and RSS memory read via `psutil` on every status poll. -- **Logs** — read from `PYOBS_LOG_DIR`'s flat files by default, or from the systemd journal via `journalctl` if the effective log backend is `"journald"`; the log viewer and per-level counts work identically either way. The effective backend is `PYOBS_LOG_BACKEND` if set explicitly, otherwise auto-detected from `pyobsd`'s own config file (`~/.config/pyobs.yaml`, `/etc/pyobs.yaml`, or `/opt/pyobs/storage/pyobs.yaml`, first found wins) — the same file `pyobsd` (`pyobs-core`'s daemon manager) reads to decide whether *it* starts modules with `--syslog`, so this can't silently drift out of sync with it. Scrolling a log window to the top auto-loads older entries via journalctl's `--until`, for journald-backed modules only — the file backend's plain `tail -n` has no seek/offset to page further back with, so it reports nothing older available instead (see [journald-logs.md](specs/design/journald-logs.md)). +- **Logs** — read from `PYOBS_LOG_DIR`'s flat files by default, or from the systemd journal via `journalctl` if the effective log backend is `"journald"`; the log viewer and per-level counts work identically either way. The effective backend is `PYOBS_LOG_BACKEND` if set explicitly, otherwise auto-detected from `pyobsd`'s own config file (`~/.config/pyobs.yaml`, `/etc/pyobs.yaml`, or `/opt/pyobs/storage/pyobs.yaml`, first found wins) — the same file `pyobsd` (`pyobs-core`'s daemon manager) reads to decide whether *it* starts modules with `--syslog`, so this can't silently drift out of sync with it. Scrolling a log window to the top auto-loads older entries via journalctl's `--until`, for journald-backed modules only — the file backend's plain `tail -n` has no seek/offset to page further back with, so it reports nothing older available instead. - **Log counts** — per-level message counts (DEBUG / INFO / WARNING / ERROR / CRITICAL) for the last 24 h, using binary search on the log file to avoid reading the whole file. --- diff --git a/docs/source/_static/screenshots/config-editor.jpg b/docs/source/_static/screenshots/config-editor.jpg new file mode 100644 index 0000000..411da7d Binary files /dev/null and b/docs/source/_static/screenshots/config-editor.jpg differ diff --git a/docs/source/_static/screenshots/dashboard.jpg b/docs/source/_static/screenshots/dashboard.jpg new file mode 100644 index 0000000..6e7616b Binary files /dev/null and b/docs/source/_static/screenshots/dashboard.jpg differ diff --git a/docs/source/features/dashboard.rst b/docs/source/features/dashboard.rst index 5cc0060..62a9d63 100644 --- a/docs/source/features/dashboard.rst +++ b/docs/source/features/dashboard.rst @@ -1,6 +1,16 @@ Dashboard and module detail ############################ +.. image:: ../_static/screenshots/dashboard.jpg + :alt: Dashboard showing six modules grouped under Stopped and Deactivated headings, with + Total/Running/Stopped/RAM/CPU summary tiles and per-row quick-action buttons. + :width: 100% + +.. image:: ../_static/screenshots/config-editor.jpg + :alt: Module detail Config tab showing a syntax-highlighted YAML editor for a module's + config file. + :width: 100% + Dashboard ********* diff --git a/pyproject.toml b/pyproject.toml index 99faf48..fd92ae6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "pyobs-web-admin" -version = "2.0.0.dev14" +version = "2.0.0" description = "Web-based administration interface for pyobs robotic telescope modules" authors = [ { name = "Tim-Oliver Husser", email = "thusser@uni-goettingen.de" }, @@ -11,7 +11,7 @@ dependencies = [ "gunicorn>=26.0.0", "packaging>=24.0", "psutil>=6.0", - "pyobs-auth>=2.0.0.dev9", + "pyobs-auth>=2.0.0", "pyyaml>=6.0", "requests>=2.32", "ruamel.yaml>=0.18", diff --git a/uv.lock b/uv.lock index 355920c..2f5fa0c 100644 --- a/uv.lock +++ b/uv.lock @@ -325,7 +325,7 @@ crypto = [ [[package]] name = "pyobs-auth" -version = "2.0.0.dev9" +version = "2.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "django" }, @@ -333,14 +333,14 @@ dependencies = [ { name = "pyjwt", extra = ["crypto"] }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7d/ff/a64a7994969277066c095497a70751fbef8111ce732123744cf4fc7880bf/pyobs_auth-2.0.0.dev9.tar.gz", hash = "sha256:1c6b9cc5bb46598cf5b11f070aaaa8975b5133b52c2960588b3b5a5c42041288", size = 10731, upload-time = "2026-08-23T11:09:30.172Z" } +sdist = { url = "https://files.pythonhosted.org/packages/03/d8/e48c678696cd9fe01107b447b34121c8353fac79750eae3c0cd7717fe948/pyobs_auth-2.0.0.tar.gz", hash = "sha256:09111ea0c40834230fb07f77a257c88843d9058da8ca2d49ed8f6823129ce18d", size = 10734, upload-time = "2026-08-26T11:48:35.842Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ad/ea/c292cb6eb57247a64747b186cb0f0ff0b8190235d190d74bab9e9499d426/pyobs_auth-2.0.0.dev9-py3-none-any.whl", hash = "sha256:7122ac35d604d1d3f251c5c9f0d7180696ecdfefd7860a09585fd88e06dfdf72", size = 12827, upload-time = "2026-08-23T11:09:29.062Z" }, + { url = "https://files.pythonhosted.org/packages/05/56/38a65add71f41b80b6d3105b85083e785855f6159228880b710a6eddc163/pyobs_auth-2.0.0-py3-none-any.whl", hash = "sha256:22831044a997ec7ec5ea58ac233f12adda14683ab7f18b3685b272cf8026d743", size = 12770, upload-time = "2026-08-26T11:48:34.908Z" }, ] [[package]] name = "pyobs-web-admin" -version = "2.0.0.dev14" +version = "2.0.0" source = { virtual = "." } dependencies = [ { name = "django" }, @@ -366,7 +366,7 @@ requires-dist = [ { name = "gunicorn", specifier = ">=26.0.0" }, { name = "packaging", specifier = ">=24.0" }, { name = "psutil", specifier = ">=6.0" }, - { name = "pyobs-auth", specifier = ">=2.0.0.dev9" }, + { name = "pyobs-auth", specifier = ">=2.0.0" }, { name = "pyyaml", specifier = ">=6.0" }, { name = "requests", specifier = ">=2.32" }, { name = "ruamel-yaml", specifier = ">=0.18" },