diff --git a/backend/atlas_portal/routes/access_requests.py b/backend/atlas_portal/routes/access_requests.py index f79c7c1..231b5c5 100644 --- a/backend/atlas_portal/routes/access_requests.py +++ b/backend/atlas_portal/routes/access_requests.py @@ -192,7 +192,6 @@ ONBOARDING_REQUIRED_STEPS: tuple[str, ...] = ( KEYCLOAK_MANAGED_STEPS: set[str] = { "keycloak_password_rotated", - "vaultwarden_master_password", "nextcloud_mail_integration", "firefly_password_rotated", "wger_password_rotated", diff --git a/frontend/src/views/OnboardingView.vue b/frontend/src/views/OnboardingView.vue index 087ed33..dd1a936 100644 --- a/frontend/src/views/OnboardingView.vue +++ b/frontend/src/views/OnboardingView.vue @@ -79,9 +79,7 @@ comes first because it stores every credential that follows.

- - {{ status === "ready" ? "ready" : "in progress" }} - + active
    @@ -97,7 +95,9 @@
    {{ index + 1 }}. {{ section.title }}
    - {{ sectionStatusLabel(section) }} + + {{ sectionStatusLabel(section) }} + {{ sectionProgress(section) }}
    @@ -109,10 +109,12 @@
    Username - +
    + + +
    @@ -124,16 +126,17 @@ :value="initialPassword || '********'" readonly /> - - + + + + + +
    -

    - This password was already revealed and cannot be shown again. Ask an admin to reset it if you missed it. -

    @@ -148,19 +151,6 @@

    {{ activeSection.title }}

    {{ activeSection.description }}

    -
    - - -
    @@ -197,8 +187,13 @@
    -
    -
    @@ -219,6 +214,14 @@ Start Keycloak update Open Element +
    @@ -243,6 +246,14 @@ > Verify +
    @@ -251,9 +262,9 @@

    {{ group.title }}

    -
    - +
    {{ guideShot(step, group).label }}
    +
    + +
    + +
    + +
    + + +
    @@ -312,6 +348,16 @@
    {{ error }}
    + + @@ -340,9 +386,15 @@ const keycloakPasswordRotationRequested = ref(false); const activeSectionId = ref("vaultwarden"); const guideShots = ref({}); const guidePage = ref({}); +const lightboxShot = ref(null); const showPasswordCard = computed(() => Boolean(initialPassword.value || initialPasswordRevealedAt.value)); const passwordRevealLocked = computed(() => Boolean(!initialPassword.value && initialPasswordRevealedAt.value)); +const passwordRevealHint = computed(() => + passwordRevealLocked.value + ? "This password was already revealed and cannot be shown again. Ask an admin to reset it if you missed it." + : "", +); const STEP_PREREQS = { vaultwarden_master_password: [], @@ -374,7 +426,7 @@ const SECTION_DEFS = [ { id: "vaultwarden_master_password", title: "Set your Vaultwarden master password", - action: "auto", + action: "confirm", description: "Open Nextcloud Mail to find the invite, then visit vault.bstein.dev and create your master password. Use the temporary Keycloak password to sign in to Nextcloud for the first time.", bullets: [ @@ -466,7 +518,7 @@ const SECTION_DEFS = [ description: "Use the IMAP/SMTP details on your Account page to add mail to your phone or desktop client (Thunderbird, Apple Mail, FairEmail).", links: [{ href: "/account", text: "Open Account details" }], - guide: { service: "nextcloud", step: "step2_mail_integration" }, + guide: { service: "mail", step: "step1_mail_app" }, }, ], }, @@ -512,7 +564,7 @@ const SECTION_DEFS = [ }, { id: "budget", - title: "Budget & Encryption", + title: "Budget Encryption", description: "Protect sensitive data and keep the shared storage budget predictable.", steps: [ { @@ -719,17 +771,16 @@ function sectionProgress(section) { const requiredSteps = section.steps.filter((step) => isStepRequired(step.id)); if (!requiredSteps.length) return "optional"; const doneCount = requiredSteps.filter((step) => isStepDone(step.id)).length; - return `${doneCount}/${requiredSteps.length} required`; + return `${doneCount}/${requiredSteps.length} done`; } function sectionStatusLabel(section) { - if (isSectionDone(section)) return "done"; + if (isSectionDone(section)) return ""; if (isSectionLocked(section)) return "locked"; - return "in progress"; + return "active"; } function sectionPillClass(section) { - if (isSectionDone(section)) return "pill-ok"; if (isSectionLocked(section)) return "pill-wait"; return "pill-info"; } @@ -803,6 +854,15 @@ function guideShot(step, group) { return group.shots[guideIndex(step, group)] || {}; } +function openLightbox(shot) { + if (!shot || !shot.url) return; + lightboxShot.value = shot; +} + +function closeLightbox() { + lightboxShot.value = null; +} + function taskPillClass(value) { const key = (value || "").trim(); if (key === "ok") return "pill-ok"; @@ -891,17 +951,18 @@ function copyUsername() { async function toggleStep(stepId, event) { const checked = Boolean(event?.target?.checked); + await setStepCompletion(stepId, checked); +} + +async function setStepCompletion(stepId, completed) { if (!auth.authenticated) { - event?.preventDefault?.(); error.value = "Log in to update onboarding steps."; return; } if (isStepBlocked(stepId)) { - event?.preventDefault?.(); return; } if (stepId === "element_recovery_key") { - event?.preventDefault?.(); return; } loading.value = true; @@ -910,7 +971,7 @@ async function toggleStep(stepId, event) { const resp = await authFetch("/api/access/request/onboarding/attest", { method: "POST", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ request_code: requestCode.value.trim(), step: stepId, completed: checked }), + body: JSON.stringify({ request_code: requestCode.value.trim(), step: stepId, completed }), }); const data = await resp.json().catch(() => ({})); if (!resp.ok) throw new Error(data.error || resp.statusText || `status ${resp.status}`); @@ -923,6 +984,23 @@ async function toggleStep(stepId, event) { } } +async function confirmStep(step) { + if (!step || isStepBlocked(step.id) || isStepDone(step.id)) return; + if (step.action === "auto") { + await check(); + return; + } + if (step.action === "keycloak_rotate") { + await requestKeycloakPasswordRotation(); + return; + } + if (step.action === "element_recovery") { + await verifyElementRecoveryKey(); + return; + } + await setStepCompletion(step.id, true); +} + async function verifyElementRecoveryKey() { if (!auth.authenticated) { error.value = "Log in to verify your recovery key."; @@ -1241,14 +1319,20 @@ button.copy:disabled { display: flex; align-items: center; justify-content: flex-start; - gap: 6px; + gap: 4px; flex-wrap: nowrap; color: var(--text-muted); } +.stepper-meta .pill { + padding: 4px 6px; + font-size: 11px; + white-space: nowrap; +} + .pill-compact { - padding: 6px 8px; - font-size: 12px; + padding: 4px 6px; + font-size: 11px; white-space: nowrap; max-width: 100%; overflow: hidden; @@ -1317,6 +1401,10 @@ button.copy:disabled { color: var(--text-muted); } +.credential-field .input[readonly] { + opacity: 0.8; +} + .password-row { display: flex; gap: 8px; @@ -1343,7 +1431,11 @@ button.copy:disabled { .section-actions { display: flex; + align-items: center; + justify-content: space-between; gap: 8px; + width: 100%; + margin-top: 12px; } .step-grid { @@ -1416,12 +1508,12 @@ button.copy:disabled { } .step-actions { - margin-top: 10px; display: flex; align-items: center; gap: 12px; justify-content: flex-end; margin-top: auto; + padding-top: 10px; } .recovery-verify { @@ -1460,6 +1552,15 @@ button.copy:disabled { border: 1px solid rgba(255, 255, 255, 0.1); background: rgba(0, 0, 0, 0.2); padding: 6px; + display: grid; + gap: 6px; + cursor: zoom-in; +} + +.guide-shot figcaption { + font-size: 12px; + color: var(--text-muted); + margin: 0 4px; } .guide-shot img { @@ -1534,6 +1635,52 @@ button.copy:disabled { background: rgba(255, 70, 70, 0.1); } +.tooltip-wrap { + display: inline-flex; +} + +.lightbox { + position: fixed; + inset: 0; + background: rgba(6, 8, 12, 0.82); + display: flex; + align-items: center; + justify-content: center; + padding: 28px; + z-index: 2000; +} + +.lightbox-card { + width: min(1100px, 92vw); + max-height: 92vh; + background: rgba(10, 14, 24, 0.96); + border: 1px solid rgba(255, 255, 255, 0.12); + border-radius: 16px; + padding: 14px; + display: flex; + flex-direction: column; + gap: 12px; +} + +.lightbox-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; +} + +.lightbox-label { + color: var(--text-muted); +} + +.lightbox-card img { + width: 100%; + max-height: 74vh; + object-fit: contain; + border-radius: 12px; + background: rgba(0, 0, 0, 0.35); +} + @media (max-width: 720px) { .status-form { flex-direction: column; diff --git a/media/onboarding/element/step3_mobile_app_and_qr_code_login/!0_You may get this or not - if you do choose Enter Recover Key.jpg b/media/onboarding/element/step3_mobile_app_and_qr_code_login/10_You may get this or not - if you do choose Enter Recover Key.jpg similarity index 100% rename from media/onboarding/element/step3_mobile_app_and_qr_code_login/!0_You may get this or not - if you do choose Enter Recover Key.jpg rename to media/onboarding/element/step3_mobile_app_and_qr_code_login/10_You may get this or not - if you do choose Enter Recover Key.jpg diff --git a/media/onboarding/vaultwarden/step2_browser_extension/1_Go to your browsers store page and install the BITWARDEN addon or extension.png b/media/onboarding/vaultwarden/step2_browser_extension/1_Go to your browser store page and install the BITWARDEN addon or extension.png similarity index 100% rename from media/onboarding/vaultwarden/step2_browser_extension/1_Go to your browsers store page and install the BITWARDEN addon or extension.png rename to media/onboarding/vaultwarden/step2_browser_extension/1_Go to your browser store page and install the BITWARDEN addon or extension.png diff --git a/media/onboarding/vaultwarden/step3_mobile_app/3_Change the server url to vault.bstein.dev and hit continue.jpg b/media/onboarding/vaultwarden/step3_mobile_app/3_Change the server url to vault.bstein.dev and hit save.jpg similarity index 100% rename from media/onboarding/vaultwarden/step3_mobile_app/3_Change the server url to vault.bstein.dev and hit continue.jpg rename to media/onboarding/vaultwarden/step3_mobile_app/3_Change the server url to vault.bstein.dev and hit save.jpg diff --git a/media/onboarding/vaultwarden/step3_mobile_app/5_Enter settings.jpg b/media/onboarding/vaultwarden/step3_mobile_app/6_Enter settings.jpg similarity index 100% rename from media/onboarding/vaultwarden/step3_mobile_app/5_Enter settings.jpg rename to media/onboarding/vaultwarden/step3_mobile_app/6_Enter settings.jpg diff --git a/media/onboarding/vaultwarden/step3_mobile_app/6_Enter Account Security.jpg b/media/onboarding/vaultwarden/step3_mobile_app/7_Enter Account Security.jpg similarity index 100% rename from media/onboarding/vaultwarden/step3_mobile_app/6_Enter Account Security.jpg rename to media/onboarding/vaultwarden/step3_mobile_app/7_Enter Account Security.jpg diff --git a/media/onboarding/vaultwarden/step3_mobile_app/7_Turn on biometric unlock to make your life easier.jpg b/media/onboarding/vaultwarden/step3_mobile_app/8_Turn on biometric unlock to make your life easier.jpg similarity index 100% rename from media/onboarding/vaultwarden/step3_mobile_app/7_Turn on biometric unlock to make your life easier.jpg rename to media/onboarding/vaultwarden/step3_mobile_app/8_Turn on biometric unlock to make your life easier.jpg