ui: polish onboarding gating
This commit is contained in:
parent
698ed49a9b
commit
16e69541e5
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<header class="hero card glass">
|
||||
<div class="eyebrow">
|
||||
<span class="pill">Portfolio + Titan Lab</span>
|
||||
<span class="pill">Titan Lab</span>
|
||||
<span class="mono accent">atlas · oceanus · nextcloud-ready</span>
|
||||
</div>
|
||||
<h1>{{ title }}</h1>
|
||||
|
||||
@ -91,7 +91,8 @@
|
||||
<h3>Temporary password</h3>
|
||||
<p class="muted">
|
||||
Use this password to log in for the first time. You won't be forced to change it immediately — you'll rotate
|
||||
it later after Vaultwarden is set up. This password is shown once — copy it now.
|
||||
it later after Vaultwarden is set up. This password is shown once — copy it now. If you refresh this page,
|
||||
it may disappear.
|
||||
</p>
|
||||
<div class="request-code-row">
|
||||
<span class="label mono">Password</span>
|
||||
@ -114,7 +115,7 @@
|
||||
</div>
|
||||
|
||||
<ul class="checklist">
|
||||
<li class="check-item">
|
||||
<li class="check-item" :class="checkItemClass('vaultwarden_master_password')">
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
@ -134,7 +135,7 @@
|
||||
</p>
|
||||
</li>
|
||||
|
||||
<li class="check-item">
|
||||
<li class="check-item" :class="checkItemClass('vaultwarden_browser_extension')">
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
@ -153,7 +154,7 @@
|
||||
</p>
|
||||
</li>
|
||||
|
||||
<li class="check-item">
|
||||
<li class="check-item" :class="checkItemClass('vaultwarden_mobile_app')">
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
@ -172,7 +173,7 @@
|
||||
</p>
|
||||
</li>
|
||||
|
||||
<li class="check-item">
|
||||
<li class="check-item" :class="checkItemClass('keycloak_password_rotated')">
|
||||
<label>
|
||||
<input type="checkbox" :checked="isStepDone('keycloak_password_rotated')" disabled />
|
||||
<span>Rotate your Keycloak password</span>
|
||||
@ -203,7 +204,7 @@
|
||||
</p>
|
||||
</li>
|
||||
|
||||
<li class="check-item mfa-optional">
|
||||
<li class="check-item mfa-optional" :class="mfaItemClass()">
|
||||
<div class="mfa-row">
|
||||
<div class="mfa-text">
|
||||
<span class="mfa-title">Optional: enable MFA (TOTP) for Keycloak</span>
|
||||
@ -251,7 +252,7 @@
|
||||
</details>
|
||||
</li>
|
||||
|
||||
<li class="check-item">
|
||||
<li class="check-item" :class="checkItemClass('element_recovery_key')">
|
||||
<label>
|
||||
<input type="checkbox" :checked="isStepDone('element_recovery_key')" disabled />
|
||||
<span>Create an Element recovery key</span>
|
||||
@ -291,7 +292,7 @@
|
||||
</p>
|
||||
</li>
|
||||
|
||||
<li class="check-item">
|
||||
<li class="check-item" :class="checkItemClass('element_recovery_key_stored')">
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
@ -307,7 +308,7 @@
|
||||
<p class="muted">Save the recovery key in Vaultwarden so it doesn't get lost.</p>
|
||||
</li>
|
||||
|
||||
<li v-for="step in extraSteps" :key="step.id" class="check-item">
|
||||
<li v-for="step in extraSteps" :key="step.id" class="check-item" :class="checkItemClass(step.id)">
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
@ -431,6 +432,31 @@ function isStepDone(step) {
|
||||
return Array.isArray(steps) ? steps.includes(step) : false;
|
||||
}
|
||||
|
||||
function requiredStepOrder() {
|
||||
if (Array.isArray(onboarding.value?.required_steps) && onboarding.value.required_steps.length) {
|
||||
return onboarding.value.required_steps;
|
||||
}
|
||||
return [
|
||||
"vaultwarden_master_password",
|
||||
"vaultwarden_browser_extension",
|
||||
"vaultwarden_mobile_app",
|
||||
"keycloak_password_rotated",
|
||||
"element_recovery_key",
|
||||
"element_recovery_key_stored",
|
||||
"elementx_setup",
|
||||
"jellyfin_login",
|
||||
"mail_client_setup",
|
||||
];
|
||||
}
|
||||
|
||||
function activeRequiredStep() {
|
||||
const order = requiredStepOrder();
|
||||
for (const step of order) {
|
||||
if (!isStepDone(step)) return step;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
function mfaOptionalState() {
|
||||
const state = onboarding.value?.optional?.keycloak_mfa_optional?.state;
|
||||
if (state === "done" || state === "skipped") return state;
|
||||
@ -446,6 +472,16 @@ function isMfaBlocked() {
|
||||
return !isStepDone("keycloak_password_rotated");
|
||||
}
|
||||
|
||||
function mfaItemClass() {
|
||||
const state = mfaOptionalState();
|
||||
return {
|
||||
blocked: isMfaBlocked(),
|
||||
done: state === "done",
|
||||
skipped: state === "skipped",
|
||||
optional: true,
|
||||
};
|
||||
}
|
||||
|
||||
function mfaPillLabel() {
|
||||
if (isMfaBlocked()) return "blocked";
|
||||
const state = mfaOptionalState();
|
||||
@ -491,20 +527,7 @@ async function maybeGenerateMfaQrs(event) {
|
||||
}
|
||||
|
||||
function isStepBlocked(step) {
|
||||
const order =
|
||||
Array.isArray(onboarding.value?.required_steps) && onboarding.value.required_steps.length
|
||||
? onboarding.value.required_steps
|
||||
: [
|
||||
"vaultwarden_master_password",
|
||||
"vaultwarden_browser_extension",
|
||||
"vaultwarden_mobile_app",
|
||||
"keycloak_password_rotated",
|
||||
"element_recovery_key",
|
||||
"element_recovery_key_stored",
|
||||
"elementx_setup",
|
||||
"jellyfin_login",
|
||||
"mail_client_setup",
|
||||
];
|
||||
const order = requiredStepOrder();
|
||||
const idx = order.indexOf(step);
|
||||
if (idx <= 0) return false;
|
||||
for (let i = 0; i < idx; i += 1) {
|
||||
@ -513,6 +536,14 @@ function isStepBlocked(step) {
|
||||
return false;
|
||||
}
|
||||
|
||||
function checkItemClass(step) {
|
||||
const activeStep = activeRequiredStep();
|
||||
const done = isStepDone(step);
|
||||
const blockedStep = isStepBlocked(step);
|
||||
const active = !done && !blockedStep && activeStep === step;
|
||||
return { done, blocked: blockedStep, active };
|
||||
}
|
||||
|
||||
function stepPillLabel(step) {
|
||||
if (isStepDone(step)) return "done";
|
||||
if (isStepBlocked(step)) return "blocked";
|
||||
@ -893,6 +924,30 @@ button.primary {
|
||||
background: rgba(255, 255, 255, 0.02);
|
||||
}
|
||||
|
||||
.check-item.blocked {
|
||||
opacity: 0.55;
|
||||
}
|
||||
|
||||
.check-item.active {
|
||||
border-color: rgba(125, 208, 255, 0.45);
|
||||
background: rgba(79, 139, 255, 0.08);
|
||||
box-shadow: 0 0 0 1px rgba(79, 139, 255, 0.2);
|
||||
}
|
||||
|
||||
.check-item.done {
|
||||
border-color: rgba(92, 214, 167, 0.35);
|
||||
background: rgba(92, 214, 167, 0.05);
|
||||
}
|
||||
|
||||
.check-item.skipped {
|
||||
border-color: rgba(146, 158, 182, 0.25);
|
||||
}
|
||||
|
||||
.check-item.done label,
|
||||
.check-item.active label {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.check-item label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user