ui: polish onboarding gating
This commit is contained in:
parent
698ed49a9b
commit
16e69541e5
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<header class="hero card glass">
|
<header class="hero card glass">
|
||||||
<div class="eyebrow">
|
<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>
|
<span class="mono accent">atlas · oceanus · nextcloud-ready</span>
|
||||||
</div>
|
</div>
|
||||||
<h1>{{ title }}</h1>
|
<h1>{{ title }}</h1>
|
||||||
|
|||||||
@ -91,7 +91,8 @@
|
|||||||
<h3>Temporary password</h3>
|
<h3>Temporary password</h3>
|
||||||
<p class="muted">
|
<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
|
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>
|
</p>
|
||||||
<div class="request-code-row">
|
<div class="request-code-row">
|
||||||
<span class="label mono">Password</span>
|
<span class="label mono">Password</span>
|
||||||
@ -114,7 +115,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ul class="checklist">
|
<ul class="checklist">
|
||||||
<li class="check-item">
|
<li class="check-item" :class="checkItemClass('vaultwarden_master_password')">
|
||||||
<label>
|
<label>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
@ -134,7 +135,7 @@
|
|||||||
</p>
|
</p>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li class="check-item">
|
<li class="check-item" :class="checkItemClass('vaultwarden_browser_extension')">
|
||||||
<label>
|
<label>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
@ -153,7 +154,7 @@
|
|||||||
</p>
|
</p>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li class="check-item">
|
<li class="check-item" :class="checkItemClass('vaultwarden_mobile_app')">
|
||||||
<label>
|
<label>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
@ -172,7 +173,7 @@
|
|||||||
</p>
|
</p>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li class="check-item">
|
<li class="check-item" :class="checkItemClass('keycloak_password_rotated')">
|
||||||
<label>
|
<label>
|
||||||
<input type="checkbox" :checked="isStepDone('keycloak_password_rotated')" disabled />
|
<input type="checkbox" :checked="isStepDone('keycloak_password_rotated')" disabled />
|
||||||
<span>Rotate your Keycloak password</span>
|
<span>Rotate your Keycloak password</span>
|
||||||
@ -203,7 +204,7 @@
|
|||||||
</p>
|
</p>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li class="check-item mfa-optional">
|
<li class="check-item mfa-optional" :class="mfaItemClass()">
|
||||||
<div class="mfa-row">
|
<div class="mfa-row">
|
||||||
<div class="mfa-text">
|
<div class="mfa-text">
|
||||||
<span class="mfa-title">Optional: enable MFA (TOTP) for Keycloak</span>
|
<span class="mfa-title">Optional: enable MFA (TOTP) for Keycloak</span>
|
||||||
@ -251,7 +252,7 @@
|
|||||||
</details>
|
</details>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li class="check-item">
|
<li class="check-item" :class="checkItemClass('element_recovery_key')">
|
||||||
<label>
|
<label>
|
||||||
<input type="checkbox" :checked="isStepDone('element_recovery_key')" disabled />
|
<input type="checkbox" :checked="isStepDone('element_recovery_key')" disabled />
|
||||||
<span>Create an Element recovery key</span>
|
<span>Create an Element recovery key</span>
|
||||||
@ -291,7 +292,7 @@
|
|||||||
</p>
|
</p>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li class="check-item">
|
<li class="check-item" :class="checkItemClass('element_recovery_key_stored')">
|
||||||
<label>
|
<label>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
@ -307,7 +308,7 @@
|
|||||||
<p class="muted">Save the recovery key in Vaultwarden so it doesn't get lost.</p>
|
<p class="muted">Save the recovery key in Vaultwarden so it doesn't get lost.</p>
|
||||||
</li>
|
</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>
|
<label>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
@ -431,6 +432,31 @@ function isStepDone(step) {
|
|||||||
return Array.isArray(steps) ? steps.includes(step) : false;
|
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() {
|
function mfaOptionalState() {
|
||||||
const state = onboarding.value?.optional?.keycloak_mfa_optional?.state;
|
const state = onboarding.value?.optional?.keycloak_mfa_optional?.state;
|
||||||
if (state === "done" || state === "skipped") return state;
|
if (state === "done" || state === "skipped") return state;
|
||||||
@ -446,6 +472,16 @@ function isMfaBlocked() {
|
|||||||
return !isStepDone("keycloak_password_rotated");
|
return !isStepDone("keycloak_password_rotated");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function mfaItemClass() {
|
||||||
|
const state = mfaOptionalState();
|
||||||
|
return {
|
||||||
|
blocked: isMfaBlocked(),
|
||||||
|
done: state === "done",
|
||||||
|
skipped: state === "skipped",
|
||||||
|
optional: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function mfaPillLabel() {
|
function mfaPillLabel() {
|
||||||
if (isMfaBlocked()) return "blocked";
|
if (isMfaBlocked()) return "blocked";
|
||||||
const state = mfaOptionalState();
|
const state = mfaOptionalState();
|
||||||
@ -491,20 +527,7 @@ async function maybeGenerateMfaQrs(event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function isStepBlocked(step) {
|
function isStepBlocked(step) {
|
||||||
const order =
|
const order = requiredStepOrder();
|
||||||
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 idx = order.indexOf(step);
|
const idx = order.indexOf(step);
|
||||||
if (idx <= 0) return false;
|
if (idx <= 0) return false;
|
||||||
for (let i = 0; i < idx; i += 1) {
|
for (let i = 0; i < idx; i += 1) {
|
||||||
@ -513,6 +536,14 @@ function isStepBlocked(step) {
|
|||||||
return false;
|
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) {
|
function stepPillLabel(step) {
|
||||||
if (isStepDone(step)) return "done";
|
if (isStepDone(step)) return "done";
|
||||||
if (isStepBlocked(step)) return "blocked";
|
if (isStepBlocked(step)) return "blocked";
|
||||||
@ -893,6 +924,30 @@ button.primary {
|
|||||||
background: rgba(255, 255, 255, 0.02);
|
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 {
|
.check-item label {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user