diff --git a/backend/atlas_portal/routes/account.py b/backend/atlas_portal/routes/account.py index 58dd1d8..18a880f 100644 --- a/backend/atlas_portal/routes/account.py +++ b/backend/atlas_portal/routes/account.py @@ -1,5 +1,6 @@ from __future__ import annotations +import socket import time from typing import Any @@ -11,6 +12,16 @@ from ..keycloak import admin_client, require_auth, require_account_access from ..utils import random_password +def _tcp_check(host: str, port: int, timeout_sec: float) -> bool: + if not host or port <= 0: + return False + try: + with socket.create_connection((host, port), timeout=timeout_sec): + return True + except OSError: + return False + + def register(app) -> None: @app.route("/api/account/overview", methods=["GET"]) @require_auth @@ -24,10 +35,14 @@ def register(app) -> None: mailu_app_password = "" mailu_status = "ready" jellyfin_status = "ready" + jellyfin_sync_status = "unknown" + jellyfin_sync_detail = "" if not admin_client().ready(): mailu_status = "server not configured" jellyfin_status = "server not configured" + jellyfin_sync_status = "unknown" + jellyfin_sync_detail = "keycloak admin not configured" elif username: try: user = admin_client().find_user(username) or {} @@ -58,6 +73,8 @@ def register(app) -> None: except Exception: mailu_status = "keycloak admin error" jellyfin_status = "keycloak admin error" + jellyfin_sync_status = "unknown" + jellyfin_sync_detail = "keycloak admin error" mailu_username = "" if keycloak_email and keycloak_email.lower().endswith(f"@{settings.MAILU_DOMAIN.lower()}"): @@ -65,11 +82,28 @@ def register(app) -> None: elif username: mailu_username = f"{username}@{settings.MAILU_DOMAIN}" + if jellyfin_status == "ready": + if _tcp_check( + settings.JELLYFIN_LDAP_HOST, + settings.JELLYFIN_LDAP_PORT, + settings.JELLYFIN_LDAP_CHECK_TIMEOUT_SEC, + ): + jellyfin_sync_status = "ok" + jellyfin_sync_detail = "LDAP reachable" + else: + jellyfin_sync_status = "degraded" + jellyfin_sync_detail = "LDAP unreachable" + return jsonify( { "user": {"username": username, "email": keycloak_email, "groups": g.keycloak_groups}, "mailu": {"status": mailu_status, "username": mailu_username, "app_password": mailu_app_password}, - "jellyfin": {"status": jellyfin_status, "username": username}, + "jellyfin": { + "status": jellyfin_status, + "username": username, + "sync_status": jellyfin_sync_status, + "sync_detail": jellyfin_sync_detail, + }, } ) diff --git a/backend/atlas_portal/settings.py b/backend/atlas_portal/settings.py index 8105bde..394b56a 100644 --- a/backend/atlas_portal/settings.py +++ b/backend/atlas_portal/settings.py @@ -77,3 +77,6 @@ MAILU_SYNC_URL = os.getenv( ).rstrip("/") JELLYFIN_SYNC_URL = os.getenv("JELLYFIN_SYNC_URL", "").rstrip("/") +JELLYFIN_LDAP_HOST = os.getenv("JELLYFIN_LDAP_HOST", "openldap.sso.svc.cluster.local").strip() +JELLYFIN_LDAP_PORT = int(os.getenv("JELLYFIN_LDAP_PORT", "389")) +JELLYFIN_LDAP_CHECK_TIMEOUT_SEC = float(os.getenv("JELLYFIN_LDAP_CHECK_TIMEOUT_SEC", "1")) diff --git a/frontend/src/assets/theme.css b/frontend/src/assets/theme.css index 11e1ee8..160b37e 100644 --- a/frontend/src/assets/theme.css +++ b/frontend/src/assets/theme.css @@ -29,6 +29,16 @@ font-size: 13px; } +.pill-ok { + border-color: rgba(120, 255, 160, 0.35); + color: rgba(170, 255, 215, 0.92); +} + +.pill-warn { + border-color: rgba(255, 220, 120, 0.35); + color: rgba(255, 230, 170, 0.92); +} + .card { background: var(--bg-panel); border: 1px solid var(--border); diff --git a/frontend/src/views/AccountView.vue b/frontend/src/views/AccountView.vue index d05d6ed..1e771ba 100644 --- a/frontend/src/views/AccountView.vue +++ b/frontend/src/views/AccountView.vue @@ -85,7 +85,18 @@
Jellyfin authentication is backed by LDAP (Keycloak is the source of truth). Use your Keycloak username and @@ -101,6 +112,7 @@ {{ jellyfin.username }}