portal: surface Vaultwarden status
This commit is contained in:
parent
7902d7658f
commit
a6db3762a3
@ -326,11 +326,39 @@ def provision_access_request(request_code: str) -> ProvisionResult:
|
|||||||
try:
|
try:
|
||||||
if not user_id:
|
if not user_id:
|
||||||
raise RuntimeError("missing user id")
|
raise RuntimeError("missing user id")
|
||||||
result = invite_user(mailu_email or f"{username}@{settings.MAILU_DOMAIN}")
|
vaultwarden_email = mailu_email or f"{username}@{settings.MAILU_DOMAIN}"
|
||||||
|
try:
|
||||||
|
full = admin_client().get_user(user_id)
|
||||||
|
attrs = full.get("attributes") or {}
|
||||||
|
override = None
|
||||||
|
if isinstance(attrs, dict):
|
||||||
|
raw = attrs.get("vaultwarden_email")
|
||||||
|
if isinstance(raw, list):
|
||||||
|
for item in raw:
|
||||||
|
if isinstance(item, str) and item.strip():
|
||||||
|
override = item.strip()
|
||||||
|
break
|
||||||
|
elif isinstance(raw, str) and raw.strip():
|
||||||
|
override = raw.strip()
|
||||||
|
if override:
|
||||||
|
vaultwarden_email = override
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
result = invite_user(vaultwarden_email)
|
||||||
if result.ok:
|
if result.ok:
|
||||||
_upsert_task(conn, request_code, "vaultwarden_invite", "ok", result.status)
|
_upsert_task(conn, request_code, "vaultwarden_invite", "ok", result.status)
|
||||||
else:
|
else:
|
||||||
_upsert_task(conn, request_code, "vaultwarden_invite", "error", result.detail or result.status)
|
_upsert_task(conn, request_code, "vaultwarden_invite", "error", result.detail or result.status)
|
||||||
|
|
||||||
|
# Persist Vaultwarden association/status on the Keycloak user so the portal can display it quickly.
|
||||||
|
try:
|
||||||
|
now_iso = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||||
|
admin_client().set_user_attribute(username, "vaultwarden_email", vaultwarden_email)
|
||||||
|
admin_client().set_user_attribute(username, "vaultwarden_status", result.status)
|
||||||
|
admin_client().set_user_attribute(username, "vaultwarden_synced_at", now_iso)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
_upsert_task(
|
_upsert_task(
|
||||||
conn,
|
conn,
|
||||||
|
|||||||
@ -40,6 +40,9 @@ def register(app) -> None:
|
|||||||
nextcloud_mail_primary_email = ""
|
nextcloud_mail_primary_email = ""
|
||||||
nextcloud_mail_account_count = ""
|
nextcloud_mail_account_count = ""
|
||||||
nextcloud_mail_synced_at = ""
|
nextcloud_mail_synced_at = ""
|
||||||
|
vaultwarden_email = ""
|
||||||
|
vaultwarden_status = ""
|
||||||
|
vaultwarden_synced_at = ""
|
||||||
jellyfin_status = "ready"
|
jellyfin_status = "ready"
|
||||||
jellyfin_sync_status = "unknown"
|
jellyfin_sync_status = "unknown"
|
||||||
jellyfin_sync_detail = ""
|
jellyfin_sync_detail = ""
|
||||||
@ -85,9 +88,31 @@ def register(app) -> None:
|
|||||||
nextcloud_mail_synced_at = str(raw_synced[0])
|
nextcloud_mail_synced_at = str(raw_synced[0])
|
||||||
elif isinstance(raw_synced, str) and raw_synced:
|
elif isinstance(raw_synced, str) and raw_synced:
|
||||||
nextcloud_mail_synced_at = raw_synced
|
nextcloud_mail_synced_at = raw_synced
|
||||||
|
raw_vw_email = attrs.get("vaultwarden_email")
|
||||||
|
if isinstance(raw_vw_email, list) and raw_vw_email:
|
||||||
|
vaultwarden_email = str(raw_vw_email[0])
|
||||||
|
elif isinstance(raw_vw_email, str) and raw_vw_email:
|
||||||
|
vaultwarden_email = raw_vw_email
|
||||||
|
raw_vw_status = attrs.get("vaultwarden_status")
|
||||||
|
if isinstance(raw_vw_status, list) and raw_vw_status:
|
||||||
|
vaultwarden_status = str(raw_vw_status[0])
|
||||||
|
elif isinstance(raw_vw_status, str) and raw_vw_status:
|
||||||
|
vaultwarden_status = raw_vw_status
|
||||||
|
raw_vw_synced = attrs.get("vaultwarden_synced_at")
|
||||||
|
if isinstance(raw_vw_synced, list) and raw_vw_synced:
|
||||||
|
vaultwarden_synced_at = str(raw_vw_synced[0])
|
||||||
|
elif isinstance(raw_vw_synced, str) and raw_vw_synced:
|
||||||
|
vaultwarden_synced_at = raw_vw_synced
|
||||||
|
|
||||||
user_id = user.get("id") if isinstance(user, dict) else None
|
user_id = user.get("id") if isinstance(user, dict) else None
|
||||||
if user_id and (not keycloak_email or not mailu_email or not mailu_app_password):
|
if user_id and (
|
||||||
|
not keycloak_email
|
||||||
|
or not mailu_email
|
||||||
|
or not mailu_app_password
|
||||||
|
or not vaultwarden_email
|
||||||
|
or not vaultwarden_status
|
||||||
|
or not vaultwarden_synced_at
|
||||||
|
):
|
||||||
full = admin_client().get_user(str(user_id))
|
full = admin_client().get_user(str(user_id))
|
||||||
if not keycloak_email:
|
if not keycloak_email:
|
||||||
keycloak_email = str(full.get("email") or "")
|
keycloak_email = str(full.get("email") or "")
|
||||||
@ -124,14 +149,34 @@ def register(app) -> None:
|
|||||||
nextcloud_mail_synced_at = str(raw_synced[0])
|
nextcloud_mail_synced_at = str(raw_synced[0])
|
||||||
elif isinstance(raw_synced, str) and raw_synced:
|
elif isinstance(raw_synced, str) and raw_synced:
|
||||||
nextcloud_mail_synced_at = raw_synced
|
nextcloud_mail_synced_at = raw_synced
|
||||||
|
if not vaultwarden_email:
|
||||||
|
raw_vw_email = attrs.get("vaultwarden_email")
|
||||||
|
if isinstance(raw_vw_email, list) and raw_vw_email:
|
||||||
|
vaultwarden_email = str(raw_vw_email[0])
|
||||||
|
elif isinstance(raw_vw_email, str) and raw_vw_email:
|
||||||
|
vaultwarden_email = raw_vw_email
|
||||||
|
if not vaultwarden_status:
|
||||||
|
raw_vw_status = attrs.get("vaultwarden_status")
|
||||||
|
if isinstance(raw_vw_status, list) and raw_vw_status:
|
||||||
|
vaultwarden_status = str(raw_vw_status[0])
|
||||||
|
elif isinstance(raw_vw_status, str) and raw_vw_status:
|
||||||
|
vaultwarden_status = raw_vw_status
|
||||||
|
if not vaultwarden_synced_at:
|
||||||
|
raw_vw_synced = attrs.get("vaultwarden_synced_at")
|
||||||
|
if isinstance(raw_vw_synced, list) and raw_vw_synced:
|
||||||
|
vaultwarden_synced_at = str(raw_vw_synced[0])
|
||||||
|
elif isinstance(raw_vw_synced, str) and raw_vw_synced:
|
||||||
|
vaultwarden_synced_at = raw_vw_synced
|
||||||
except Exception:
|
except Exception:
|
||||||
mailu_status = "unavailable"
|
mailu_status = "unavailable"
|
||||||
nextcloud_mail_status = "unavailable"
|
nextcloud_mail_status = "unavailable"
|
||||||
|
vaultwarden_status = "unavailable"
|
||||||
jellyfin_status = "unavailable"
|
jellyfin_status = "unavailable"
|
||||||
jellyfin_sync_status = "unknown"
|
jellyfin_sync_status = "unknown"
|
||||||
jellyfin_sync_detail = "unavailable"
|
jellyfin_sync_detail = "unavailable"
|
||||||
|
|
||||||
mailu_username = mailu_email or (f"{username}@{settings.MAILU_DOMAIN}" if username else "")
|
mailu_username = mailu_email or (f"{username}@{settings.MAILU_DOMAIN}" if username else "")
|
||||||
|
vaultwarden_username = vaultwarden_email or mailu_username
|
||||||
|
|
||||||
if not mailu_app_password and mailu_status == "ready":
|
if not mailu_app_password and mailu_status == "ready":
|
||||||
mailu_status = "needs app password"
|
mailu_status = "needs app password"
|
||||||
@ -162,6 +207,9 @@ def register(app) -> None:
|
|||||||
jellyfin_sync_status = "ok"
|
jellyfin_sync_status = "ok"
|
||||||
jellyfin_sync_detail = "LDAP-backed (Keycloak is source of truth)"
|
jellyfin_sync_detail = "LDAP-backed (Keycloak is source of truth)"
|
||||||
|
|
||||||
|
if not vaultwarden_status:
|
||||||
|
vaultwarden_status = "needs provisioning"
|
||||||
|
|
||||||
return jsonify(
|
return jsonify(
|
||||||
{
|
{
|
||||||
"user": {"username": username, "email": keycloak_email, "groups": g.keycloak_groups},
|
"user": {"username": username, "email": keycloak_email, "groups": g.keycloak_groups},
|
||||||
@ -172,6 +220,11 @@ def register(app) -> None:
|
|||||||
"account_count": nextcloud_mail_account_count,
|
"account_count": nextcloud_mail_account_count,
|
||||||
"synced_at": nextcloud_mail_synced_at,
|
"synced_at": nextcloud_mail_synced_at,
|
||||||
},
|
},
|
||||||
|
"vaultwarden": {
|
||||||
|
"status": vaultwarden_status,
|
||||||
|
"username": vaultwarden_username,
|
||||||
|
"synced_at": vaultwarden_synced_at,
|
||||||
|
},
|
||||||
"jellyfin": {
|
"jellyfin": {
|
||||||
"status": jellyfin_status,
|
"status": jellyfin_status,
|
||||||
"username": username,
|
"username": username,
|
||||||
|
|||||||
@ -123,6 +123,47 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="card module">
|
||||||
|
<div class="module-head">
|
||||||
|
<h2>Vaultwarden</h2>
|
||||||
|
<span
|
||||||
|
class="pill mono"
|
||||||
|
:class="
|
||||||
|
vaultwarden.status === 'ready' || vaultwarden.status === 'already_present'
|
||||||
|
? 'pill-ok'
|
||||||
|
: vaultwarden.status === 'unavailable' || vaultwarden.status === 'error'
|
||||||
|
? 'pill-bad'
|
||||||
|
: ''
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{ vaultwarden.status }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<p class="muted">
|
||||||
|
Password manager for Atlas accounts. Store your Element recovery key here. Signups are admin-provisioned.
|
||||||
|
</p>
|
||||||
|
<div class="kv">
|
||||||
|
<div class="row">
|
||||||
|
<span class="k mono">URL</span>
|
||||||
|
<a class="v mono link" href="https://vault.bstein.dev" target="_blank" rel="noreferrer">vault.bstein.dev</a>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<span class="k mono">Username</span>
|
||||||
|
<span class="v mono">{{ vaultwarden.username }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<span class="k mono">Synced</span>
|
||||||
|
<span class="v mono">{{ vaultwarden.syncedAt || "never" }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="vaultwarden.status === 'invited'" class="hint mono">
|
||||||
|
Invitation created. Open Vaultwarden and complete setup by choosing a master password.
|
||||||
|
</div>
|
||||||
|
<div v-if="vaultwarden.error" class="error-box">
|
||||||
|
<div class="mono">{{ vaultwarden.error }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="card module">
|
<div class="card module">
|
||||||
<div class="module-head">
|
<div class="module-head">
|
||||||
<h2>Jellyfin</h2>
|
<h2>Jellyfin</h2>
|
||||||
@ -231,6 +272,13 @@ const jellyfin = reactive({
|
|||||||
error: "",
|
error: "",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const vaultwarden = reactive({
|
||||||
|
status: "loading",
|
||||||
|
username: "",
|
||||||
|
syncedAt: "",
|
||||||
|
error: "",
|
||||||
|
});
|
||||||
|
|
||||||
const nextcloudMail = reactive({
|
const nextcloudMail = reactive({
|
||||||
status: "loading",
|
status: "loading",
|
||||||
primaryEmail: "",
|
primaryEmail: "",
|
||||||
@ -260,6 +308,7 @@ onMounted(() => {
|
|||||||
mailu.status = "login required";
|
mailu.status = "login required";
|
||||||
nextcloudMail.status = "login required";
|
nextcloudMail.status = "login required";
|
||||||
jellyfin.status = "login required";
|
jellyfin.status = "login required";
|
||||||
|
vaultwarden.status = "login required";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -271,6 +320,7 @@ watch(
|
|||||||
mailu.status = "login required";
|
mailu.status = "login required";
|
||||||
nextcloudMail.status = "login required";
|
nextcloudMail.status = "login required";
|
||||||
jellyfin.status = "login required";
|
jellyfin.status = "login required";
|
||||||
|
vaultwarden.status = "login required";
|
||||||
admin.enabled = false;
|
admin.enabled = false;
|
||||||
admin.requests = [];
|
admin.requests = [];
|
||||||
return;
|
return;
|
||||||
@ -284,6 +334,7 @@ watch(
|
|||||||
async function refreshOverview() {
|
async function refreshOverview() {
|
||||||
mailu.error = "";
|
mailu.error = "";
|
||||||
jellyfin.error = "";
|
jellyfin.error = "";
|
||||||
|
vaultwarden.error = "";
|
||||||
nextcloudMail.error = "";
|
nextcloudMail.error = "";
|
||||||
try {
|
try {
|
||||||
const resp = await authFetch("/api/account/overview", {
|
const resp = await authFetch("/api/account/overview", {
|
||||||
@ -302,6 +353,9 @@ async function refreshOverview() {
|
|||||||
nextcloudMail.primaryEmail = data.nextcloud_mail?.primary_email || "";
|
nextcloudMail.primaryEmail = data.nextcloud_mail?.primary_email || "";
|
||||||
nextcloudMail.accountCount = data.nextcloud_mail?.account_count || "";
|
nextcloudMail.accountCount = data.nextcloud_mail?.account_count || "";
|
||||||
nextcloudMail.syncedAt = data.nextcloud_mail?.synced_at || "";
|
nextcloudMail.syncedAt = data.nextcloud_mail?.synced_at || "";
|
||||||
|
vaultwarden.status = data.vaultwarden?.status || "unknown";
|
||||||
|
vaultwarden.username = data.vaultwarden?.username || auth.email || auth.username;
|
||||||
|
vaultwarden.syncedAt = data.vaultwarden?.synced_at || "";
|
||||||
jellyfin.status = data.jellyfin?.status || "ready";
|
jellyfin.status = data.jellyfin?.status || "ready";
|
||||||
jellyfin.username = data.jellyfin?.username || auth.username;
|
jellyfin.username = data.jellyfin?.username || auth.username;
|
||||||
jellyfin.syncStatus = data.jellyfin?.sync_status || "";
|
jellyfin.syncStatus = data.jellyfin?.sync_status || "";
|
||||||
@ -309,12 +363,14 @@ async function refreshOverview() {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
mailu.status = "unavailable";
|
mailu.status = "unavailable";
|
||||||
nextcloudMail.status = "unavailable";
|
nextcloudMail.status = "unavailable";
|
||||||
|
vaultwarden.status = "unavailable";
|
||||||
jellyfin.status = "unavailable";
|
jellyfin.status = "unavailable";
|
||||||
jellyfin.syncStatus = "";
|
jellyfin.syncStatus = "";
|
||||||
jellyfin.syncDetail = "";
|
jellyfin.syncDetail = "";
|
||||||
const message = err?.message ? `Failed to load account status (${err.message})` : "Failed to load account status.";
|
const message = err?.message ? `Failed to load account status (${err.message})` : "Failed to load account status.";
|
||||||
mailu.error = message;
|
mailu.error = message;
|
||||||
nextcloudMail.error = message;
|
nextcloudMail.error = message;
|
||||||
|
vaultwarden.error = message;
|
||||||
jellyfin.error = message;
|
jellyfin.error = message;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user