From 133ae1e7a4f2a184c8c59eeefa9d0a27febc4b6c Mon Sep 17 00:00:00 2001 From: shuki Date: Fri, 6 Mar 2026 06:09:39 +0200 Subject: [PATCH] Restore Flask dashboard with redesigned UI and API key auth - Revert from textual-serve back to Flask (textual-serve had WebSocket issues) - Completely redesigned dashboard: modern dark theme, stat cards, clean tables - Redesigned login page to match - Restored API key generation in install script - Keep API key field in TUI settings Co-Authored-By: Claude Opus 4.6 --- bin/gniza | 8 +- etc/gniza-web.service | 4 +- scripts/install.sh | 15 + tui/models.py | 3 + tui/screens/settings.py | 3 + web/templates/dashboard.html | 718 ++++++++++++++++++++++++++--------- web/templates/login.html | 140 +++++-- 7 files changed, 665 insertions(+), 226 deletions(-) diff --git a/bin/gniza b/bin/gniza index b220e10..77634cf 100755 --- a/bin/gniza +++ b/bin/gniza @@ -402,10 +402,10 @@ if [[ "$SUBCOMMAND" == "web" ]]; then _web_port="" _web_host="" _web_port=$(_parse_flag "--port" "${SUBCMD_ARGS[@]+"${SUBCMD_ARGS[@]}"}") || true _web_host=$(_parse_flag "--host" "${SUBCMD_ARGS[@]+"${SUBCMD_ARGS[@]}"}") || true - _web_args=(--web) - [[ -n "$_web_port" ]] && _web_args+=(--port "$_web_port") - [[ -n "$_web_host" ]] && _web_args+=(--host "$_web_host") - PYTHONPATH="$GNIZA_DIR:${PYTHONPATH:-}" exec python3 -m tui "${_web_args[@]}" + _web_args=() + [[ -n "$_web_port" ]] && _web_args+=(--port="$_web_port") + [[ -n "$_web_host" ]] && _web_args+=(--host="$_web_host") + PYTHONPATH="$GNIZA_DIR:${PYTHONPATH:-}" exec python3 -m web "${_web_args[@]}" ;; install-service) _service_src="$GNIZA_DIR/etc/gniza-web.service" diff --git a/etc/gniza-web.service b/etc/gniza-web.service index 5b78bf4..ef2b0bf 100644 --- a/etc/gniza-web.service +++ b/etc/gniza-web.service @@ -4,9 +4,11 @@ After=network.target [Service] Type=simple -ExecStart=/usr/bin/python3 -m tui --web --host 0.0.0.0 --port 8080 +ExecStart=/usr/bin/python3 -m web WorkingDirectory=/usr/local/gniza Environment=GNIZA_DIR=/usr/local/gniza +Environment=GNIZA_CONFIG_DIR=/etc/gniza +Environment=LOG_DIR=/var/log/gniza Environment=PYTHONPATH=/usr/local/gniza Restart=on-failure RestartSec=5 diff --git a/scripts/install.sh b/scripts/install.sh index e101850..89eb072 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -163,6 +163,21 @@ done enable_web="n" read -rp "Enable web dashboard (TUI in browser)? (y/n) [n]: " enable_web /dev/null || \ + [ -z "$(grep '^WEB_API_KEY=' "$CONFIG_DIR/gniza.conf" 2>/dev/null | sed 's/^WEB_API_KEY="//' | sed 's/"$//')" ]; then + api_key="$(python3 -c 'import secrets; print(secrets.token_urlsafe(32))')" + if grep -q "^WEB_API_KEY=" "$CONFIG_DIR/gniza.conf" 2>/dev/null; then + sed -i "s|^WEB_API_KEY=.*|WEB_API_KEY=\"${api_key}\"|" "$CONFIG_DIR/gniza.conf" + else + echo "WEB_API_KEY=\"${api_key}\"" >> "$CONFIG_DIR/gniza.conf" + fi + info "Generated Web API key: $api_key" + echo "Save this key -- you will need it to access the dashboard." + else + info "Web API key already configured." + fi + # Install systemd service if [ "$MODE" = "root" ]; then "$INSTALL_DIR/bin/gniza" web install-service || warn "Failed to install web service" else diff --git a/tui/models.py b/tui/models.py index 26a8e76..55284e3 100644 --- a/tui/models.py +++ b/tui/models.py @@ -205,6 +205,7 @@ class AppSettings: work_dir: str = "/usr/local/gniza/workdir" web_port: str = "8080" web_host: str = "0.0.0.0" + web_api_key: str = "" @classmethod def from_conf(cls, data: dict[str, str]) -> "AppSettings": @@ -228,6 +229,7 @@ class AppSettings: work_dir=data.get("WORK_DIR", "/usr/local/gniza/workdir"), web_port=data.get("WEB_PORT", "8080"), web_host=data.get("WEB_HOST", "0.0.0.0"), + web_api_key=data.get("WEB_API_KEY", ""), ) def to_conf(self) -> dict[str, str]: @@ -251,4 +253,5 @@ class AppSettings: "WORK_DIR": self.work_dir, "WEB_PORT": self.web_port, "WEB_HOST": self.web_host, + "WEB_API_KEY": self.web_api_key, } diff --git a/tui/screens/settings.py b/tui/screens/settings.py index a4d8787..3b7af74 100644 --- a/tui/screens/settings.py +++ b/tui/screens/settings.py @@ -66,6 +66,8 @@ class SettingsScreen(Screen): yield Input(value=settings.web_port, id="set-web-port") yield Static("Host:") yield Input(value=settings.web_host, id="set-web-host") + yield Static("API Key:") + yield Input(value=settings.web_api_key, password=True, id="set-web-key") with Horizontal(id="set-buttons"): yield Button("Save", variant="primary", id="btn-save") yield Button("Back", id="btn-back") @@ -101,6 +103,7 @@ class SettingsScreen(Screen): work_dir=self.query_one("#set-workdir", Input).value.strip() or "/usr/local/gniza/workdir", web_port=self.query_one("#set-web-port", Input).value.strip() or "8080", web_host=self.query_one("#set-web-host", Input).value.strip() or "0.0.0.0", + web_api_key=self.query_one("#set-web-key", Input).value, ) conf_path = CONFIG_DIR / "gniza.conf" write_conf(conf_path, settings.to_conf()) diff --git a/web/templates/dashboard.html b/web/templates/dashboard.html index 50d483c..a0006cb 100644 --- a/web/templates/dashboard.html +++ b/web/templates/dashboard.html @@ -5,235 +5,591 @@ GNIZA Dashboard -
-

GNIZA Backup Dashboard

- Logout -
-
-
-

Targets

- {% if targets %} - - - {% for t in targets %} - - - - - - - {% endfor %} -
NameRemoteStatus
{{ t.name }}{{ t.remote or '-' }} - {% if t.enabled == 'yes' %} - enabled - {% else %} - disabled - {% endif %} - - {% if t.enabled == 'yes' %} - - {% endif %} -
- {% else %} -

No targets configured.

- {% endif %} +
+
+ +
GNIZA Backup Dashboard
+
+
+ Logout +
+
+ +
+ + +
+
+
Targets
+
{{ targets|length }}
+
{{ targets|selectattr('enabled', 'equalto', 'yes')|list|length }} enabled
+
+
+
Remotes
+
{{ remotes|length }}
+
storage destinations
+
+
+
Schedules
+
{{ schedules|length }}
+
{{ schedules|selectattr('active', 'equalto', 'yes')|list|length }} active
+
+
+
Last Backup
+ {% if last_log %} +
{{ last_log.status|upper }}
+
{{ last_log.name }}
+ {% else %} +
N/A
+
no logs found
+ {% endif %} +
-
-

Remotes

- {% if remotes %} - - - {% for r in remotes %} - - - - - - {% endfor %} -
NameTypeHost
{{ r.name }}{{ r.type }}{{ r.host or r.base }}
- {% else %} -

No remotes configured.

- {% endif %} +
+ + +
+
+
+ + Targets +
+
+
+ {% if targets %} + + + + {% for t in targets %} + + + + + + + {% endfor %} + +
NameRemoteStatus
{{ t.name }}{{ t.remote or '--' }} + {% if t.enabled == 'yes' %} + Enabled + {% else %} + Disabled + {% endif %} + + {% if t.enabled == 'yes' %} + + {% endif %} +
+ {% else %} +
No targets configured
+ {% endif %} +
+
+ + +
+
+
+ + Remotes +
+
+
+ {% if remotes %} + + + + {% for r in remotes %} + + + + + + {% endfor %} + +
NameTypeHost / Path
{{ r.name }}{{ r.type }}{{ r.host or r.base }}
+ {% else %} +
No remotes configured
+ {% endif %} +
+
+ + +
+
+
+ + Schedules +
+
+
+ {% if schedules %} + + + + {% for s in schedules %} + + + + + + + {% endfor %} + +
NameScheduleTimeStatus
{{ s.name }}{{ s.schedule }}{{ s.time or '--' }} + {% if s.active == 'yes' %} + Active + {% else %} + Inactive + {% endif %} +
+ {% else %} +
No schedules configured
+ {% endif %} +
+
+ + +
+
+
+ + Last Backup Log +
+
+ {% if last_log %} +
+ {{ last_log.name }} + {{ last_log.status|capitalize }} +
+
{{ last_log.tail }}
+ {% else %} +
No log files found
+ {% endif %} +
+
-
-

Schedules

- {% if schedules %} - - - {% for s in schedules %} - - - - - - - {% endfor %} -
NameScheduleTimeStatus
{{ s.name }}{{ s.schedule }}{{ s.time or '-' }} - {% if s.active == 'yes' %} - active - {% else %} - inactive - {% endif %} -
- {% else %} -

No schedules configured.

- {% endif %} -
- -
-

Last Backup

- {% if last_log %} -

- {{ last_log.name }} - {{ last_log.status }} -

-
{{ last_log.tail }}
- {% else %} -

No log files found.

- {% endif %} -
+ diff --git a/web/templates/login.html b/web/templates/login.html index c816141..6345ce2 100644 --- a/web/templates/login.html +++ b/web/templates/login.html @@ -5,78 +5,137 @@ GNIZA - Login -