diff --git a/backend/atlas_portal/routes/account.py b/backend/atlas_portal/routes/account.py
index 18a880f..b95fc5a 100644
--- a/backend/atlas_portal/routes/account.py
+++ b/backend/atlas_portal/routes/account.py
@@ -37,6 +37,7 @@ def register(app) -> None:
jellyfin_status = "ready"
jellyfin_sync_status = "unknown"
jellyfin_sync_detail = ""
+ jellyfin_user_is_ldap = False
if not admin_client().ready():
mailu_status = "server not configured"
@@ -46,6 +47,8 @@ def register(app) -> None:
elif username:
try:
user = admin_client().find_user(username) or {}
+ if isinstance(user, dict):
+ jellyfin_user_is_ldap = bool(user.get("federationLink"))
if not keycloak_email:
keycloak_email = str(user.get("email") or "")
@@ -82,17 +85,24 @@ def register(app) -> None:
elif username:
mailu_username = f"{username}@{settings.MAILU_DOMAIN}"
+ if not mailu_app_password and mailu_status == "ready":
+ mailu_status = "needs app password"
+
if jellyfin_status == "ready":
- if _tcp_check(
+ ldap_reachable = _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:
+ )
+ if not ldap_reachable:
jellyfin_sync_status = "degraded"
jellyfin_sync_detail = "LDAP unreachable"
+ elif not jellyfin_user_is_ldap:
+ jellyfin_sync_status = "degraded"
+ jellyfin_sync_detail = "Keycloak user is not LDAP-backed"
+ else:
+ jellyfin_sync_status = "ok"
+ jellyfin_sync_detail = "LDAP-backed (Keycloak is source of truth)"
return jsonify(
{
diff --git a/frontend/src/views/AccountView.vue b/frontend/src/views/AccountView.vue
index 1e771ba..65f702c 100644
--- a/frontend/src/views/AccountView.vue
+++ b/frontend/src/views/AccountView.vue
@@ -35,7 +35,7 @@
{{ mailu.smtp }}
- Username
+ Email
{{ mailu.username }}
@@ -87,13 +87,13 @@
Jellyfin
{{
jellyfin.syncStatus === "ok"
? "in sync"
: jellyfin.syncStatus === "degraded"
- ? "check sync"
+ ? "out of sync"
: jellyfin.status
}}
diff --git a/frontend/src/views/AppsView.vue b/frontend/src/views/AppsView.vue
index 1efb78a..8917e9c 100644
--- a/frontend/src/views/AppsView.vue
+++ b/frontend/src/views/AppsView.vue
@@ -44,7 +44,7 @@
const sections = [
{
title: "Cloud",
- description: "Files, photos, mail, calendars, and documents — the primary hub for Atlas users.",
+ description: "Your personal cloud hub: files, photos, mail, calendars, and collaborative documents.",
groups: [
{
title: "Nextcloud",
@@ -53,7 +53,7 @@ const sections = [
name: "Cloud",
url: "https://cloud.bstein.dev",
target: "_blank",
- description: "Your personal cloud storage and productivity suite.",
+ description: "Storage, mail, photos, and office docs — the main Atlas hub.",
},
],
},
@@ -61,7 +61,7 @@ const sections = [
},
{
title: "Security",
- description: "Password security, secrets, and account hygiene.",
+ description: "Passwords for humans, secrets for infrastructure.",
groups: [
{
title: "Personal",
@@ -89,16 +89,16 @@ const sections = [
},
{
title: "Communications",
- description: "Discord-like chat, calls, and rooms — plus Atlas AI chat bots.",
+ description: "Chat rooms, calls, and bots. Element X (mobile) is the recommended client.",
groups: [
{
title: "Chat",
apps: [
{
- name: "Element",
+ name: "Element X",
url: "https://live.bstein.dev",
target: "_blank",
- description: "Matrix chat rooms with voice/video powered by local infra.",
+ description: "Matrix rooms with calls powered by Atlas infra.",
},
{
name: "AI Chat",
@@ -112,7 +112,7 @@ const sections = [
},
{
title: "Streaming",
- description: "Family media streaming and upload workflows.",
+ description: "Stream media and publish uploads into your library.",
groups: [
{
title: "Media",
@@ -135,7 +135,7 @@ const sections = [
},
{
title: "Dev",
- description: "Source control, CI, registry, GitOps, and observability.",
+ description: "Build and ship: source control, CI, registry, and GitOps.",
groups: [
{
title: "Source & CI",