2026-01-24 22:59:20 +00:00
< template >
< div >
2026-02-17 15:03:34 +00:00
< div class = "mb-8 flex flex-col md:flex-row md:items-center md:justify-between gap-4" >
< div >
2026-03-11 13:45:59 +00:00
< h1 class = "text-3xl font-bold text-white mb-2 drop-shadow-[0_2px_8px_rgba(0,0,0,0.6)]" > { { t ( 'settings.title' ) } } < / h1 >
< p class = "text-white/80" > { { t ( 'settings.subtitle' ) } } < / p >
2026-02-17 15:03:34 +00:00
< / div >
<!-- Controller indicator - Mobile only ( desktop shows in sidebar ) -- >
< div class = "md:hidden" >
< ControllerIndicator / >
< / div >
2026-01-24 22:59:20 +00:00
< / div >
<!-- Account Section -- >
2026-03-10 23:58:33 +00:00
< div class = "glass-card px-6 py-6 mb-6" >
2026-03-11 13:45:59 +00:00
< h2 class = "text-xl font-semibold text-white/96 mb-6" > { { t ( 'settings.account' ) } } < / h2 >
2026-01-24 22:59:20 +00:00
<!-- Info Grid -- >
< div class = "grid grid-cols-1 md:grid-cols-2 gap-4 mb-6" >
<!-- Server Name Card -- >
< div class = "bg-black/20 rounded-xl px-5 py-4 border border-white/10" >
< div class = "flex items-center gap-3 mb-2" >
< svg class = "w-5 h-5 text-white/70" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" >
< path stroke -linecap = " round " stroke -linejoin = " round " stroke -width = " 2 " d = "M5 12h14M5 12a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v4a2 2 0 01-2 2M5 12a2 2 0 00-2 2v4a2 2 0 002 2h14a2 2 0 002-2v-4a2 2 0 00-2-2m-2-4h.01M17 16h.01" / >
< / svg >
2026-03-11 13:45:59 +00:00
< p class = "text-xs font-semibold text-white/60 uppercase tracking-wide" > { { t ( 'settings.serverName' ) } } < / p >
2026-01-24 22:59:20 +00:00
< / div >
< p class = "text-lg font-semibold text-white/95" > { { serverName } } < / p >
< / div >
<!-- Version Card -- >
< div class = "bg-black/20 rounded-xl px-5 py-4 border border-white/10" >
< div class = "flex items-center gap-3 mb-2" >
< svg class = "w-5 h-5 text-white/70" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" >
< path stroke -linecap = " round " stroke -linejoin = " round " stroke -width = " 2 " d = "M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z" / >
< / svg >
2026-03-11 13:45:59 +00:00
< p class = "text-xs font-semibold text-white/60 uppercase tracking-wide" > { { t ( 'common.version' ) } } < / p >
2026-01-24 22:59:20 +00:00
< / div >
< p class = "text-lg font-semibold text-white/95" > { { version } } < / p >
< / div >
<!-- Session Card -- >
< div class = "bg-black/20 rounded-xl px-5 py-4 border border-white/10 md:col-span-2" >
< div class = "flex items-center gap-3 mb-2" >
< svg class = "w-5 h-5 text-green-400" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" >
< path stroke -linecap = " round " stroke -linejoin = " round " stroke -width = " 2 " d = "M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" / >
< / svg >
2026-03-11 13:45:59 +00:00
< p class = "text-xs font-semibold text-white/60 uppercase tracking-wide" > { { t ( 'settings.sessionStatus' ) } } < / p >
2026-01-24 22:59:20 +00:00
< / div >
2026-03-11 13:45:59 +00:00
< p class = "text-base font-medium text-white/90" > { { t ( 'settings.loggedIn' ) } } < / p >
2026-01-24 22:59:20 +00:00
< / div >
2026-02-17 15:03:34 +00:00
<!-- Identity Card : DID + Tor Address ( onion below DID , with copy ) -- >
< div v-if = "userDid || serverTorAddress" class="bg-black/20 rounded-xl px-5 py-4 border border-white/10 md:col-span-2 space-y-4" >
<!-- DID -- >
< div v-if = "userDid" >
2026-03-11 12:46:02 +00:00
< div class = "flex items-center justify-between gap-2 mb-2" >
< div class = "flex items-center gap-3 min-w-0" >
< svg class = "w-5 h-5 text-amber-400 shrink-0" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" >
< path stroke -linecap = " round " stroke -linejoin = " round " stroke -width = " 2 " d = "M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z" / >
< / svg >
2026-03-11 13:45:59 +00:00
< p class = "text-xs font-semibold text-white/60 uppercase tracking-wide" > { { t ( 'settings.yourDid' ) } } < / p >
2026-03-11 12:46:02 +00:00
< / div >
< button
@ click = "copyDid"
class = "shrink-0 px-3 py-1.5 rounded-lg glass-button glass-button-sm text-xs font-medium text-white/90 hover:text-white transition-colors flex items-center gap-1.5"
>
< svg v-if = "!copiedDid" class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" >
< path stroke -linecap = " round " stroke -linejoin = " round " stroke -width = " 2 " d = "M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" / >
< / svg >
2026-03-11 13:45:59 +00:00
< span v -else class = "text-green-400 text-xs" > { { t ( 'common.copied' ) } } < / span >
< span v-if = "!copiedDid" > {{ t ( ' common.copy ' ) }} < / span >
2026-03-11 12:46:02 +00:00
< / button >
2026-02-17 15:03:34 +00:00
< / div >
< p class = "text-sm font-mono text-white/90 break-all" :title = "userDid" > { { userDid } } < / p >
2026-03-11 13:45:59 +00:00
< p class = "text-xs text-white/50 mt-1" > { { t ( 'settings.didHelper' ) } } < / p >
2026-02-17 15:03:34 +00:00
< / div >
<!-- Tor / Onion Address ( below DID , with copy button ) -- >
< div v-if = "serverTorAddress" :class="userDid ? 'pt-4 border-t border-white/10' : ''" >
< div class = "flex items-center justify-between gap-2 mb-2" >
< div class = "flex items-center gap-3 min-w-0" >
< svg class = "w-5 h-5 text-amber-400 shrink-0" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" >
< path stroke -linecap = " round " stroke -linejoin = " round " stroke -width = " 2 " d = "M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 019-9" / >
< / svg >
2026-03-11 13:45:59 +00:00
< p class = "text-xs font-semibold text-white/60 uppercase tracking-wide" > { { t ( 'settings.onionAddress' ) } } < / p >
2026-02-17 15:03:34 +00:00
< / div >
< button
@ click = "copyOnionAddress"
class = "shrink-0 px-3 py-1.5 rounded-lg glass-button glass-button-sm text-xs font-medium text-white/90 hover:text-white transition-colors flex items-center gap-1.5"
>
< svg v-if = "!copiedOnion" class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" >
< path stroke -linecap = " round " stroke -linejoin = " round " stroke -width = " 2 " d = "M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" / >
< / svg >
2026-03-11 13:45:59 +00:00
< span v -else class = "text-green-400 text-xs" > { { t ( 'common.copied' ) } } < / span >
< span v-if = "!copiedOnion" > {{ t ( ' common.copy ' ) }} < / span >
2026-02-17 15:03:34 +00:00
< / button >
< / div >
< p class = "text-sm font-mono text-amber-400/90 break-all" :title = "serverTorAddress" > { { serverTorAddress } } < / p >
2026-03-11 13:45:59 +00:00
< p class = "text-xs text-white/50 mt-1" > { { t ( 'settings.onionHelper' ) } } < / p >
2026-02-17 15:03:34 +00:00
< / div >
< / div >
< / div >
<!-- Change Password -- >
2026-02-18 11:29:05 +00:00
< div data -controller -container tabindex = "0" class = "mb-6" >
2026-02-17 15:03:34 +00:00
< button
@ click = "showChangePasswordModal = true"
class = "w-full flex items-center justify-center gap-2 mb-4 px-4 py-2 rounded-lg border border-orange-500/50 text-orange-400 font-medium hover:bg-orange-500/10 transition-colors"
>
< svg class = "w-5 h-5" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" >
< path stroke -linecap = " round " stroke -linejoin = " round " stroke -width = " 2 " d = "M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z" / >
< / svg >
2026-03-11 13:45:59 +00:00
< span > { { t ( 'settings.changePassword' ) } } < / span >
2026-02-17 15:03:34 +00:00
< / button >
2026-01-24 22:59:20 +00:00
< / div >
2026-02-17 15:03:34 +00:00
<!-- Change Password Modal -- >
< Teleport to = "body" >
< div
v - if = "showChangePasswordModal"
class = "fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/60 backdrop-blur-sm"
2026-02-17 22:10:38 +00:00
@ click . self = "closeChangePasswordModal()"
2026-02-17 15:03:34 +00:00
>
2026-02-17 22:10:38 +00:00
< div ref = "changePasswordModalRef" class = "glass-card p-6 max-w-md w-full" >
2026-03-11 13:45:59 +00:00
< h3 class = "text-lg font-semibold text-white mb-4" > { { t ( 'settings.changePasswordTitle' ) } } < / h3 >
< p class = "text-white/70 text-sm mb-4" > { { t ( 'settings.changePasswordDesc' ) } } < / p >
2026-02-17 15:03:34 +00:00
< form @submit.prevent ="handleChangePassword" class = "space-y-4" >
< div >
2026-03-11 13:45:59 +00:00
< label class = "block text-sm font-medium text-white/80 mb-2" > { { t ( 'settings.currentPassword' ) } } < / label >
2026-02-17 15:03:34 +00:00
< input
v - model = "changePasswordForm.currentPassword"
type = "password"
required
autocomplete = "current-password"
class = "w-full px-3 py-2 rounded-lg bg-white/10 text-white border border-white/20 focus:border-orange-500 focus:ring-1 focus:ring-orange-500"
2026-03-11 13:45:59 +00:00
: placeholder = "t('login.enterPasswordPlaceholder')"
2026-02-17 15:03:34 +00:00
/ >
< / div >
< div >
2026-03-11 13:45:59 +00:00
< label class = "block text-sm font-medium text-white/80 mb-2" > { { t ( 'settings.newPassword' ) } } < / label >
2026-02-17 15:03:34 +00:00
< input
v - model = "changePasswordForm.newPassword"
type = "password"
required
autocomplete = "new-password"
class = "w-full px-3 py-2 rounded-lg bg-white/10 text-white border border-white/20 focus:border-orange-500 focus:ring-1 focus:ring-orange-500"
2026-03-11 13:45:59 +00:00
: placeholder = "t('settings.passwordPlaceholder')"
2026-02-17 15:03:34 +00:00
/ >
< / div >
< div >
2026-03-11 13:45:59 +00:00
< label class = "block text-sm font-medium text-white/80 mb-2" > { { t ( 'settings.confirmNewPassword' ) } } < / label >
2026-02-17 15:03:34 +00:00
< input
v - model = "changePasswordForm.confirmPassword"
type = "password"
required
autocomplete = "new-password"
class = "w-full px-3 py-2 rounded-lg bg-white/10 text-white border border-white/20 focus:border-orange-500 focus:ring-1 focus:ring-orange-500"
2026-03-11 13:45:59 +00:00
: placeholder = "t('settings.confirmNewPassword')"
2026-02-17 15:03:34 +00:00
/ >
< / div >
< label class = "flex items-center gap-2 text-sm text-white/80" >
< input v-model = "changePasswordForm.alsoChangeSsh" type="checkbox" class="rounded border-white/30" / >
2026-03-11 13:45:59 +00:00
{ { t ( 'settings.updateSshCheckbox' ) } }
2026-02-17 15:03:34 +00:00
< / label >
< p v-if = "changePasswordError" class="text-sm text-red-400" > {{ changePasswordError }} < / p >
< p v-if = "changePasswordSuccess" class="text-sm text-green-400" > {{ changePasswordSuccess }} < / p >
< div class = "flex gap-3 pt-2" >
< button
type = "submit"
: disabled = "changingPassword"
class = "flex-1 px-4 py-2 rounded-lg bg-orange-500 text-white font-medium hover:bg-orange-600 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
>
2026-03-11 13:45:59 +00:00
{ { changingPassword ? t ( 'settings.updatingPassword' ) : t ( 'settings.updatePassword' ) } }
2026-02-17 15:03:34 +00:00
< / button >
< button
type = "button"
@ click = "closeChangePasswordModal"
class = "px-4 py-2 rounded-lg bg-white/10 text-white font-medium hover:bg-white/20 transition-colors"
>
2026-03-11 13:45:59 +00:00
{ { t ( 'common.cancel' ) } }
2026-02-17 15:03:34 +00:00
< / button >
< / div >
< / form >
< / div >
< / div >
< / Teleport >
feat: add TOTP 2FA, API key switcher, login progress bar, and alpha hardening plan
- TOTP 2FA: full setup/confirm/disable/login flow with Argon2id + ChaCha20-Poly1305
encrypted secret storage, QR code generation, and bcrypt-hashed backup codes
- API key switcher: OAuth vs personal API key toggle in AIUI chat settings with
status indicator, key validation, and help text
- Login progress bar: server startup detection with health check polling, form
disabled until server is ready
- AI quarantine docs: comprehensive HTML page documenting all 6 security layers
- Settings: AI Data Access permission toggles with per-category control
- Alpha hardening plan: 28-task overnight automation plan across 7 phases
(onboarding, login, app install, AIUI, UI polish, security, ISO build)
- Backlog: node discovery spatial map feature for alpha demo
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 12:23:57 +00:00
<!-- Two - Factor Authentication -- >
< div class = "mb-6" >
< div class = "flex items-center justify-between mb-3" >
< div class = "flex items-center gap-3" >
< svg class = "w-5 h-5 text-white/70" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" >
< path stroke -linecap = " round " stroke -linejoin = " round " stroke -width = " 2 " d = "M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" / >
< / svg >
< div >
2026-03-11 13:45:59 +00:00
< p class = "text-sm font-medium text-white/90" > { { t ( 'settings.twoFactorAuth' ) } } < / p >
< p class = "text-xs text-white/50" > { { t ( 'settings.twoFaProtect' ) } } < / p >
feat: add TOTP 2FA, API key switcher, login progress bar, and alpha hardening plan
- TOTP 2FA: full setup/confirm/disable/login flow with Argon2id + ChaCha20-Poly1305
encrypted secret storage, QR code generation, and bcrypt-hashed backup codes
- API key switcher: OAuth vs personal API key toggle in AIUI chat settings with
status indicator, key validation, and help text
- Login progress bar: server startup detection with health check polling, form
disabled until server is ready
- AI quarantine docs: comprehensive HTML page documenting all 6 security layers
- Settings: AI Data Access permission toggles with per-category control
- Alpha hardening plan: 28-task overnight automation plan across 7 phases
(onboarding, login, app install, AIUI, UI polish, security, ISO build)
- Backlog: node discovery spatial map feature for alpha demo
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 12:23:57 +00:00
< / div >
< / div >
< span
class = "text-xs font-semibold px-2 py-1 rounded-full"
: class = "totpEnabled ? 'bg-green-500/20 text-green-400' : 'bg-white/10 text-white/50'"
>
2026-03-11 13:45:59 +00:00
{ { totpEnabled ? t ( 'common.enabled' ) : t ( 'common.disabled' ) } }
feat: add TOTP 2FA, API key switcher, login progress bar, and alpha hardening plan
- TOTP 2FA: full setup/confirm/disable/login flow with Argon2id + ChaCha20-Poly1305
encrypted secret storage, QR code generation, and bcrypt-hashed backup codes
- API key switcher: OAuth vs personal API key toggle in AIUI chat settings with
status indicator, key validation, and help text
- Login progress bar: server startup detection with health check polling, form
disabled until server is ready
- AI quarantine docs: comprehensive HTML page documenting all 6 security layers
- Settings: AI Data Access permission toggles with per-category control
- Alpha hardening plan: 28-task overnight automation plan across 7 phases
(onboarding, login, app install, AIUI, UI polish, security, ISO build)
- Backlog: node discovery spatial map feature for alpha demo
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 12:23:57 +00:00
< / span >
< / div >
< button
v - if = "!totpEnabled"
@ click = "showTotpSetupModal = true"
class = "w-full flex items-center justify-center gap-2 px-4 py-2 rounded-lg border border-orange-500/50 text-orange-400 font-medium hover:bg-orange-500/10 transition-colors"
>
< svg class = "w-5 h-5" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" >
< path stroke -linecap = " round " stroke -linejoin = " round " stroke -width = " 2 " d = "M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" / >
< / svg >
2026-03-11 13:45:59 +00:00
< span > { { t ( 'settings.enable2fa' ) } } < / span >
feat: add TOTP 2FA, API key switcher, login progress bar, and alpha hardening plan
- TOTP 2FA: full setup/confirm/disable/login flow with Argon2id + ChaCha20-Poly1305
encrypted secret storage, QR code generation, and bcrypt-hashed backup codes
- API key switcher: OAuth vs personal API key toggle in AIUI chat settings with
status indicator, key validation, and help text
- Login progress bar: server startup detection with health check polling, form
disabled until server is ready
- AI quarantine docs: comprehensive HTML page documenting all 6 security layers
- Settings: AI Data Access permission toggles with per-category control
- Alpha hardening plan: 28-task overnight automation plan across 7 phases
(onboarding, login, app install, AIUI, UI polish, security, ISO build)
- Backlog: node discovery spatial map feature for alpha demo
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 12:23:57 +00:00
< / button >
< button
v - else
@ click = "showTotpDisableModal = true"
class = "w-full flex items-center justify-center gap-2 px-4 py-2 rounded-lg border border-red-500/50 text-red-400 font-medium hover:bg-red-500/10 transition-colors"
>
< svg class = "w-5 h-5" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" >
< path stroke -linecap = " round " stroke -linejoin = " round " stroke -width = " 2 " d = "M8 11V7a4 4 0 118 0m-4 8v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2z" / >
< / svg >
2026-03-11 13:45:59 +00:00
< span > { { t ( 'settings.disable2fa' ) } } < / span >
feat: add TOTP 2FA, API key switcher, login progress bar, and alpha hardening plan
- TOTP 2FA: full setup/confirm/disable/login flow with Argon2id + ChaCha20-Poly1305
encrypted secret storage, QR code generation, and bcrypt-hashed backup codes
- API key switcher: OAuth vs personal API key toggle in AIUI chat settings with
status indicator, key validation, and help text
- Login progress bar: server startup detection with health check polling, form
disabled until server is ready
- AI quarantine docs: comprehensive HTML page documenting all 6 security layers
- Settings: AI Data Access permission toggles with per-category control
- Alpha hardening plan: 28-task overnight automation plan across 7 phases
(onboarding, login, app install, AIUI, UI polish, security, ISO build)
- Backlog: node discovery spatial map feature for alpha demo
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 12:23:57 +00:00
< / button >
< / div >
<!-- TOTP Setup Modal -- >
< Teleport to = "body" >
< div
v - if = "showTotpSetupModal"
class = "fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/60 backdrop-blur-sm"
@ click . self = "closeTotpSetup"
2026-03-11 13:11:45 +00:00
@ keydown . escape = "closeTotpSetup"
feat: add TOTP 2FA, API key switcher, login progress bar, and alpha hardening plan
- TOTP 2FA: full setup/confirm/disable/login flow with Argon2id + ChaCha20-Poly1305
encrypted secret storage, QR code generation, and bcrypt-hashed backup codes
- API key switcher: OAuth vs personal API key toggle in AIUI chat settings with
status indicator, key validation, and help text
- Login progress bar: server startup detection with health check polling, form
disabled until server is ready
- AI quarantine docs: comprehensive HTML page documenting all 6 security layers
- Settings: AI Data Access permission toggles with per-category control
- Alpha hardening plan: 28-task overnight automation plan across 7 phases
(onboarding, login, app install, AIUI, UI polish, security, ISO build)
- Backlog: node discovery spatial map feature for alpha demo
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 12:23:57 +00:00
>
2026-03-11 13:11:45 +00:00
< div class = "glass-card p-6 max-w-md w-full" role = "dialog" aria -modal = " true " aria -labelledby = " totp -setup -title " >
feat: add TOTP 2FA, API key switcher, login progress bar, and alpha hardening plan
- TOTP 2FA: full setup/confirm/disable/login flow with Argon2id + ChaCha20-Poly1305
encrypted secret storage, QR code generation, and bcrypt-hashed backup codes
- API key switcher: OAuth vs personal API key toggle in AIUI chat settings with
status indicator, key validation, and help text
- Login progress bar: server startup detection with health check polling, form
disabled until server is ready
- AI quarantine docs: comprehensive HTML page documenting all 6 security layers
- Settings: AI Data Access permission toggles with per-category control
- Alpha hardening plan: 28-task overnight automation plan across 7 phases
(onboarding, login, app install, AIUI, UI polish, security, ISO build)
- Backlog: node discovery spatial map feature for alpha demo
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 12:23:57 +00:00
<!-- Step 1 : Enter password -- >
< template v-if = "totpSetupStep === 1" >
2026-03-11 13:45:59 +00:00
< h3 id = "totp-setup-title" class = "text-lg font-semibold text-white mb-2" > { { t ( 'settings.setup2faTitle' ) } } < / h3 >
< p class = "text-white/60 text-sm mb-4" > { { t ( 'settings.setup2faPasswordPrompt' ) } } < / p >
feat: add TOTP 2FA, API key switcher, login progress bar, and alpha hardening plan
- TOTP 2FA: full setup/confirm/disable/login flow with Argon2id + ChaCha20-Poly1305
encrypted secret storage, QR code generation, and bcrypt-hashed backup codes
- API key switcher: OAuth vs personal API key toggle in AIUI chat settings with
status indicator, key validation, and help text
- Login progress bar: server startup detection with health check polling, form
disabled until server is ready
- AI quarantine docs: comprehensive HTML page documenting all 6 security layers
- Settings: AI Data Access permission toggles with per-category control
- Alpha hardening plan: 28-task overnight automation plan across 7 phases
(onboarding, login, app install, AIUI, UI polish, security, ISO build)
- Backlog: node discovery spatial map feature for alpha demo
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 12:23:57 +00:00
< form @submit.prevent ="beginTotpSetup" class = "space-y-4" >
< input
v - model = "totpSetupPassword"
type = "password"
required
autocomplete = "current-password"
class = "w-full px-3 py-2 rounded-lg bg-white/10 text-white border border-white/20 focus:border-orange-500 focus:ring-1 focus:ring-orange-500"
2026-03-11 13:45:59 +00:00
: placeholder = "t('login.enterPasswordPlaceholder')"
feat: add TOTP 2FA, API key switcher, login progress bar, and alpha hardening plan
- TOTP 2FA: full setup/confirm/disable/login flow with Argon2id + ChaCha20-Poly1305
encrypted secret storage, QR code generation, and bcrypt-hashed backup codes
- API key switcher: OAuth vs personal API key toggle in AIUI chat settings with
status indicator, key validation, and help text
- Login progress bar: server startup detection with health check polling, form
disabled until server is ready
- AI quarantine docs: comprehensive HTML page documenting all 6 security layers
- Settings: AI Data Access permission toggles with per-category control
- Alpha hardening plan: 28-task overnight automation plan across 7 phases
(onboarding, login, app install, AIUI, UI polish, security, ISO build)
- Backlog: node discovery spatial map feature for alpha demo
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 12:23:57 +00:00
/ >
< p v-if = "totpSetupError" class="text-sm text-red-400" > {{ totpSetupError }} < / p >
< div class = "flex gap-3" >
< button
type = "submit"
: disabled = "totpSetupLoading"
class = "flex-1 px-4 py-2 rounded-lg bg-orange-500 text-white font-medium hover:bg-orange-600 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
>
2026-03-11 13:45:59 +00:00
{ { totpSetupLoading ? t ( 'common.loading' ) : t ( 'common.continue' ) } }
feat: add TOTP 2FA, API key switcher, login progress bar, and alpha hardening plan
- TOTP 2FA: full setup/confirm/disable/login flow with Argon2id + ChaCha20-Poly1305
encrypted secret storage, QR code generation, and bcrypt-hashed backup codes
- API key switcher: OAuth vs personal API key toggle in AIUI chat settings with
status indicator, key validation, and help text
- Login progress bar: server startup detection with health check polling, form
disabled until server is ready
- AI quarantine docs: comprehensive HTML page documenting all 6 security layers
- Settings: AI Data Access permission toggles with per-category control
- Alpha hardening plan: 28-task overnight automation plan across 7 phases
(onboarding, login, app install, AIUI, UI polish, security, ISO build)
- Backlog: node discovery spatial map feature for alpha demo
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 12:23:57 +00:00
< / button >
2026-03-11 13:45:59 +00:00
< button type = "button" @click ="closeTotpSetup" class = "px-4 py-2 rounded-lg bg-white/10 text-white font-medium hover:bg-white/20 transition-colors" > { { t ( 'common.cancel' ) } } < / button >
feat: add TOTP 2FA, API key switcher, login progress bar, and alpha hardening plan
- TOTP 2FA: full setup/confirm/disable/login flow with Argon2id + ChaCha20-Poly1305
encrypted secret storage, QR code generation, and bcrypt-hashed backup codes
- API key switcher: OAuth vs personal API key toggle in AIUI chat settings with
status indicator, key validation, and help text
- Login progress bar: server startup detection with health check polling, form
disabled until server is ready
- AI quarantine docs: comprehensive HTML page documenting all 6 security layers
- Settings: AI Data Access permission toggles with per-category control
- Alpha hardening plan: 28-task overnight automation plan across 7 phases
(onboarding, login, app install, AIUI, UI polish, security, ISO build)
- Backlog: node discovery spatial map feature for alpha demo
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 12:23:57 +00:00
< / div >
< / form >
< / template >
<!-- Step 2 : Scan QR + verify code -- >
< template v -else -if = " totpSetupStep = = = 2 " >
2026-03-11 13:45:59 +00:00
< h3 class = "text-lg font-semibold text-white mb-2" > { { t ( 'settings.scanQrCode' ) } } < / h3 >
< p class = "text-white/60 text-sm mb-4" > { { t ( 'settings.scanQrInstruction' ) } } < / p >
feat: add TOTP 2FA, API key switcher, login progress bar, and alpha hardening plan
- TOTP 2FA: full setup/confirm/disable/login flow with Argon2id + ChaCha20-Poly1305
encrypted secret storage, QR code generation, and bcrypt-hashed backup codes
- API key switcher: OAuth vs personal API key toggle in AIUI chat settings with
status indicator, key validation, and help text
- Login progress bar: server startup detection with health check polling, form
disabled until server is ready
- AI quarantine docs: comprehensive HTML page documenting all 6 security layers
- Settings: AI Data Access permission toggles with per-category control
- Alpha hardening plan: 28-task overnight automation plan across 7 phases
(onboarding, login, app install, AIUI, UI polish, security, ISO build)
- Backlog: node discovery spatial map feature for alpha demo
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 12:23:57 +00:00
< div class = "flex justify-center mb-4 bg-white rounded-xl p-4 mx-auto w-fit" v-html = "totpQrSvg" / >
< div class = "bg-black/30 rounded-lg px-3 py-2 mb-4" >
2026-03-11 13:45:59 +00:00
< p class = "text-xs text-white/50 mb-1" > { { t ( 'settings.manualEntryKey' ) } } < / p >
feat: add TOTP 2FA, API key switcher, login progress bar, and alpha hardening plan
- TOTP 2FA: full setup/confirm/disable/login flow with Argon2id + ChaCha20-Poly1305
encrypted secret storage, QR code generation, and bcrypt-hashed backup codes
- API key switcher: OAuth vs personal API key toggle in AIUI chat settings with
status indicator, key validation, and help text
- Login progress bar: server startup detection with health check polling, form
disabled until server is ready
- AI quarantine docs: comprehensive HTML page documenting all 6 security layers
- Settings: AI Data Access permission toggles with per-category control
- Alpha hardening plan: 28-task overnight automation plan across 7 phases
(onboarding, login, app install, AIUI, UI polish, security, ISO build)
- Backlog: node discovery spatial map feature for alpha demo
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 12:23:57 +00:00
< p class = "text-sm font-mono text-orange-400 break-all select-all" > { { totpSecretBase32 } } < / p >
< / div >
< form @submit.prevent ="confirmTotpSetup" class = "space-y-4" >
< input
v - model = "totpSetupCode"
type = "text"
inputmode = "numeric"
pattern = "[0-9]{6}"
maxlength = "6"
required
autocomplete = "one-time-code"
class = "w-full px-3 py-3 rounded-lg bg-white/10 text-white text-center text-2xl tracking-[0.5em] border border-white/20 focus:border-orange-500 focus:ring-1 focus:ring-orange-500 font-mono"
2026-03-11 13:45:59 +00:00
: placeholder = "t('login.totpPlaceholder')"
feat: add TOTP 2FA, API key switcher, login progress bar, and alpha hardening plan
- TOTP 2FA: full setup/confirm/disable/login flow with Argon2id + ChaCha20-Poly1305
encrypted secret storage, QR code generation, and bcrypt-hashed backup codes
- API key switcher: OAuth vs personal API key toggle in AIUI chat settings with
status indicator, key validation, and help text
- Login progress bar: server startup detection with health check polling, form
disabled until server is ready
- AI quarantine docs: comprehensive HTML page documenting all 6 security layers
- Settings: AI Data Access permission toggles with per-category control
- Alpha hardening plan: 28-task overnight automation plan across 7 phases
(onboarding, login, app install, AIUI, UI polish, security, ISO build)
- Backlog: node discovery spatial map feature for alpha demo
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 12:23:57 +00:00
/ >
< p v-if = "totpSetupError" class="text-sm text-red-400" > {{ totpSetupError }} < / p >
< div class = "flex gap-3" >
< button
type = "submit"
: disabled = "totpSetupLoading || totpSetupCode.length !== 6"
class = "flex-1 px-4 py-2 rounded-lg bg-orange-500 text-white font-medium hover:bg-orange-600 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
>
2026-03-11 13:45:59 +00:00
{ { totpSetupLoading ? t ( 'login.verifying' ) : t ( 'settings.verifyAndEnable' ) } }
feat: add TOTP 2FA, API key switcher, login progress bar, and alpha hardening plan
- TOTP 2FA: full setup/confirm/disable/login flow with Argon2id + ChaCha20-Poly1305
encrypted secret storage, QR code generation, and bcrypt-hashed backup codes
- API key switcher: OAuth vs personal API key toggle in AIUI chat settings with
status indicator, key validation, and help text
- Login progress bar: server startup detection with health check polling, form
disabled until server is ready
- AI quarantine docs: comprehensive HTML page documenting all 6 security layers
- Settings: AI Data Access permission toggles with per-category control
- Alpha hardening plan: 28-task overnight automation plan across 7 phases
(onboarding, login, app install, AIUI, UI polish, security, ISO build)
- Backlog: node discovery spatial map feature for alpha demo
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 12:23:57 +00:00
< / button >
2026-03-11 13:45:59 +00:00
< button type = "button" @click ="closeTotpSetup" class = "px-4 py-2 rounded-lg bg-white/10 text-white font-medium hover:bg-white/20 transition-colors" > { { t ( 'common.cancel' ) } } < / button >
feat: add TOTP 2FA, API key switcher, login progress bar, and alpha hardening plan
- TOTP 2FA: full setup/confirm/disable/login flow with Argon2id + ChaCha20-Poly1305
encrypted secret storage, QR code generation, and bcrypt-hashed backup codes
- API key switcher: OAuth vs personal API key toggle in AIUI chat settings with
status indicator, key validation, and help text
- Login progress bar: server startup detection with health check polling, form
disabled until server is ready
- AI quarantine docs: comprehensive HTML page documenting all 6 security layers
- Settings: AI Data Access permission toggles with per-category control
- Alpha hardening plan: 28-task overnight automation plan across 7 phases
(onboarding, login, app install, AIUI, UI polish, security, ISO build)
- Backlog: node discovery spatial map feature for alpha demo
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 12:23:57 +00:00
< / div >
< / form >
< / template >
<!-- Step 3 : Show backup codes -- >
< template v -else -if = " totpSetupStep = = = 3 " >
2026-03-11 13:45:59 +00:00
< h3 class = "text-lg font-semibold text-white mb-2" > { { t ( 'settings.saveBackupCodes' ) } } < / h3 >
< p class = "text-white/60 text-sm mb-4" > { { t ( 'settings.backupCodesInstruction' ) } } < / p >
feat: add TOTP 2FA, API key switcher, login progress bar, and alpha hardening plan
- TOTP 2FA: full setup/confirm/disable/login flow with Argon2id + ChaCha20-Poly1305
encrypted secret storage, QR code generation, and bcrypt-hashed backup codes
- API key switcher: OAuth vs personal API key toggle in AIUI chat settings with
status indicator, key validation, and help text
- Login progress bar: server startup detection with health check polling, form
disabled until server is ready
- AI quarantine docs: comprehensive HTML page documenting all 6 security layers
- Settings: AI Data Access permission toggles with per-category control
- Alpha hardening plan: 28-task overnight automation plan across 7 phases
(onboarding, login, app install, AIUI, UI polish, security, ISO build)
- Backlog: node discovery spatial map feature for alpha demo
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 12:23:57 +00:00
< div class = "bg-black/30 rounded-xl p-4 mb-4" >
< div class = "grid grid-cols-2 gap-2" >
< div
v - for = "(code, i) in totpBackupCodes"
: key = "i"
class = "text-sm font-mono text-white/90 bg-white/5 rounded px-3 py-2 text-center"
>
{ { code } }
< / div >
< / div >
< / div >
< button
@ click = "copyBackupCodes"
class = "w-full mb-3 flex items-center justify-center gap-2 px-4 py-2 rounded-lg border border-white/20 text-white/80 font-medium hover:bg-white/5 transition-colors"
>
< svg v-if = "!backupCodesCopied" class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" >
< path stroke -linecap = " round " stroke -linejoin = " round " stroke -width = " 2 " d = "M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" / >
< / svg >
2026-03-11 13:45:59 +00:00
< span > { { backupCodesCopied ? t ( 'common.copiedBang' ) : t ( 'settings.copyAllCodes' ) } } < / span >
feat: add TOTP 2FA, API key switcher, login progress bar, and alpha hardening plan
- TOTP 2FA: full setup/confirm/disable/login flow with Argon2id + ChaCha20-Poly1305
encrypted secret storage, QR code generation, and bcrypt-hashed backup codes
- API key switcher: OAuth vs personal API key toggle in AIUI chat settings with
status indicator, key validation, and help text
- Login progress bar: server startup detection with health check polling, form
disabled until server is ready
- AI quarantine docs: comprehensive HTML page documenting all 6 security layers
- Settings: AI Data Access permission toggles with per-category control
- Alpha hardening plan: 28-task overnight automation plan across 7 phases
(onboarding, login, app install, AIUI, UI polish, security, ISO build)
- Backlog: node discovery spatial map feature for alpha demo
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 12:23:57 +00:00
< / button >
< button
@ click = "closeTotpSetup"
class = "w-full px-4 py-2 rounded-lg bg-orange-500 text-white font-medium hover:bg-orange-600 transition-colors"
>
2026-03-11 13:45:59 +00:00
{ { t ( 'common.done' ) } }
feat: add TOTP 2FA, API key switcher, login progress bar, and alpha hardening plan
- TOTP 2FA: full setup/confirm/disable/login flow with Argon2id + ChaCha20-Poly1305
encrypted secret storage, QR code generation, and bcrypt-hashed backup codes
- API key switcher: OAuth vs personal API key toggle in AIUI chat settings with
status indicator, key validation, and help text
- Login progress bar: server startup detection with health check polling, form
disabled until server is ready
- AI quarantine docs: comprehensive HTML page documenting all 6 security layers
- Settings: AI Data Access permission toggles with per-category control
- Alpha hardening plan: 28-task overnight automation plan across 7 phases
(onboarding, login, app install, AIUI, UI polish, security, ISO build)
- Backlog: node discovery spatial map feature for alpha demo
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 12:23:57 +00:00
< / button >
< / template >
< / div >
< / div >
< / Teleport >
<!-- TOTP Disable Modal -- >
< Teleport to = "body" >
< div
v - if = "showTotpDisableModal"
class = "fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/60 backdrop-blur-sm"
@ click . self = "closeTotpDisable"
2026-03-11 13:11:45 +00:00
@ keydown . escape = "closeTotpDisable"
feat: add TOTP 2FA, API key switcher, login progress bar, and alpha hardening plan
- TOTP 2FA: full setup/confirm/disable/login flow with Argon2id + ChaCha20-Poly1305
encrypted secret storage, QR code generation, and bcrypt-hashed backup codes
- API key switcher: OAuth vs personal API key toggle in AIUI chat settings with
status indicator, key validation, and help text
- Login progress bar: server startup detection with health check polling, form
disabled until server is ready
- AI quarantine docs: comprehensive HTML page documenting all 6 security layers
- Settings: AI Data Access permission toggles with per-category control
- Alpha hardening plan: 28-task overnight automation plan across 7 phases
(onboarding, login, app install, AIUI, UI polish, security, ISO build)
- Backlog: node discovery spatial map feature for alpha demo
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 12:23:57 +00:00
>
2026-03-11 13:11:45 +00:00
< div class = "glass-card p-6 max-w-md w-full" role = "dialog" aria -modal = " true " aria -labelledby = " totp -disable -title " >
2026-03-11 13:45:59 +00:00
< h3 id = "totp-disable-title" class = "text-lg font-semibold text-white mb-2" > { { t ( 'settings.disable2faTitle' ) } } < / h3 >
< p class = "text-white/60 text-sm mb-4" > { { t ( 'settings.disable2faDesc' ) } } < / p >
feat: add TOTP 2FA, API key switcher, login progress bar, and alpha hardening plan
- TOTP 2FA: full setup/confirm/disable/login flow with Argon2id + ChaCha20-Poly1305
encrypted secret storage, QR code generation, and bcrypt-hashed backup codes
- API key switcher: OAuth vs personal API key toggle in AIUI chat settings with
status indicator, key validation, and help text
- Login progress bar: server startup detection with health check polling, form
disabled until server is ready
- AI quarantine docs: comprehensive HTML page documenting all 6 security layers
- Settings: AI Data Access permission toggles with per-category control
- Alpha hardening plan: 28-task overnight automation plan across 7 phases
(onboarding, login, app install, AIUI, UI polish, security, ISO build)
- Backlog: node discovery spatial map feature for alpha demo
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 12:23:57 +00:00
< form @submit.prevent ="disableTotp" class = "space-y-4" >
< div >
2026-03-11 13:45:59 +00:00
< label class = "block text-sm font-medium text-white/80 mb-2" > { { t ( 'login.password' ) } } < / label >
feat: add TOTP 2FA, API key switcher, login progress bar, and alpha hardening plan
- TOTP 2FA: full setup/confirm/disable/login flow with Argon2id + ChaCha20-Poly1305
encrypted secret storage, QR code generation, and bcrypt-hashed backup codes
- API key switcher: OAuth vs personal API key toggle in AIUI chat settings with
status indicator, key validation, and help text
- Login progress bar: server startup detection with health check polling, form
disabled until server is ready
- AI quarantine docs: comprehensive HTML page documenting all 6 security layers
- Settings: AI Data Access permission toggles with per-category control
- Alpha hardening plan: 28-task overnight automation plan across 7 phases
(onboarding, login, app install, AIUI, UI polish, security, ISO build)
- Backlog: node discovery spatial map feature for alpha demo
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 12:23:57 +00:00
< input
v - model = "totpDisablePassword"
type = "password"
required
autocomplete = "current-password"
class = "w-full px-3 py-2 rounded-lg bg-white/10 text-white border border-white/20 focus:border-orange-500 focus:ring-1 focus:ring-orange-500"
2026-03-11 13:45:59 +00:00
: placeholder = "t('login.enterPasswordPlaceholder')"
feat: add TOTP 2FA, API key switcher, login progress bar, and alpha hardening plan
- TOTP 2FA: full setup/confirm/disable/login flow with Argon2id + ChaCha20-Poly1305
encrypted secret storage, QR code generation, and bcrypt-hashed backup codes
- API key switcher: OAuth vs personal API key toggle in AIUI chat settings with
status indicator, key validation, and help text
- Login progress bar: server startup detection with health check polling, form
disabled until server is ready
- AI quarantine docs: comprehensive HTML page documenting all 6 security layers
- Settings: AI Data Access permission toggles with per-category control
- Alpha hardening plan: 28-task overnight automation plan across 7 phases
(onboarding, login, app install, AIUI, UI polish, security, ISO build)
- Backlog: node discovery spatial map feature for alpha demo
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 12:23:57 +00:00
/ >
< / div >
< div >
2026-03-11 13:45:59 +00:00
< label class = "block text-sm font-medium text-white/80 mb-2" > { { t ( 'settings.authenticatorCode' ) } } < / label >
feat: add TOTP 2FA, API key switcher, login progress bar, and alpha hardening plan
- TOTP 2FA: full setup/confirm/disable/login flow with Argon2id + ChaCha20-Poly1305
encrypted secret storage, QR code generation, and bcrypt-hashed backup codes
- API key switcher: OAuth vs personal API key toggle in AIUI chat settings with
status indicator, key validation, and help text
- Login progress bar: server startup detection with health check polling, form
disabled until server is ready
- AI quarantine docs: comprehensive HTML page documenting all 6 security layers
- Settings: AI Data Access permission toggles with per-category control
- Alpha hardening plan: 28-task overnight automation plan across 7 phases
(onboarding, login, app install, AIUI, UI polish, security, ISO build)
- Backlog: node discovery spatial map feature for alpha demo
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 12:23:57 +00:00
< input
v - model = "totpDisableCode"
type = "text"
inputmode = "numeric"
pattern = "[0-9]{6}"
maxlength = "6"
required
autocomplete = "one-time-code"
class = "w-full px-3 py-3 rounded-lg bg-white/10 text-white text-center text-2xl tracking-[0.5em] border border-white/20 focus:border-orange-500 focus:ring-1 focus:ring-orange-500 font-mono"
2026-03-11 13:45:59 +00:00
: placeholder = "t('login.totpPlaceholder')"
feat: add TOTP 2FA, API key switcher, login progress bar, and alpha hardening plan
- TOTP 2FA: full setup/confirm/disable/login flow with Argon2id + ChaCha20-Poly1305
encrypted secret storage, QR code generation, and bcrypt-hashed backup codes
- API key switcher: OAuth vs personal API key toggle in AIUI chat settings with
status indicator, key validation, and help text
- Login progress bar: server startup detection with health check polling, form
disabled until server is ready
- AI quarantine docs: comprehensive HTML page documenting all 6 security layers
- Settings: AI Data Access permission toggles with per-category control
- Alpha hardening plan: 28-task overnight automation plan across 7 phases
(onboarding, login, app install, AIUI, UI polish, security, ISO build)
- Backlog: node discovery spatial map feature for alpha demo
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 12:23:57 +00:00
/ >
< / div >
< p v-if = "totpDisableError" class="text-sm text-red-400" > {{ totpDisableError }} < / p >
< div class = "flex gap-3" >
< button
type = "submit"
: disabled = "totpDisableLoading"
class = "flex-1 px-4 py-2 rounded-lg bg-red-500 text-white font-medium hover:bg-red-600 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
>
2026-03-11 13:45:59 +00:00
{ { totpDisableLoading ? t ( 'common.disabling' ) : t ( 'settings.disable2fa' ) } }
feat: add TOTP 2FA, API key switcher, login progress bar, and alpha hardening plan
- TOTP 2FA: full setup/confirm/disable/login flow with Argon2id + ChaCha20-Poly1305
encrypted secret storage, QR code generation, and bcrypt-hashed backup codes
- API key switcher: OAuth vs personal API key toggle in AIUI chat settings with
status indicator, key validation, and help text
- Login progress bar: server startup detection with health check polling, form
disabled until server is ready
- AI quarantine docs: comprehensive HTML page documenting all 6 security layers
- Settings: AI Data Access permission toggles with per-category control
- Alpha hardening plan: 28-task overnight automation plan across 7 phases
(onboarding, login, app install, AIUI, UI polish, security, ISO build)
- Backlog: node discovery spatial map feature for alpha demo
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 12:23:57 +00:00
< / button >
2026-03-11 13:45:59 +00:00
< button type = "button" @click ="closeTotpDisable" class = "px-4 py-2 rounded-lg bg-white/10 text-white font-medium hover:bg-white/20 transition-colors" > { { t ( 'common.cancel' ) } } < / button >
feat: add TOTP 2FA, API key switcher, login progress bar, and alpha hardening plan
- TOTP 2FA: full setup/confirm/disable/login flow with Argon2id + ChaCha20-Poly1305
encrypted secret storage, QR code generation, and bcrypt-hashed backup codes
- API key switcher: OAuth vs personal API key toggle in AIUI chat settings with
status indicator, key validation, and help text
- Login progress bar: server startup detection with health check polling, form
disabled until server is ready
- AI quarantine docs: comprehensive HTML page documenting all 6 security layers
- Settings: AI Data Access permission toggles with per-category control
- Alpha hardening plan: 28-task overnight automation plan across 7 phases
(onboarding, login, app install, AIUI, UI polish, security, ISO build)
- Backlog: node discovery spatial map feature for alpha demo
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 12:23:57 +00:00
< / div >
< / form >
< / div >
< / div >
< / Teleport >
2026-01-24 22:59:20 +00:00
<!-- Logout Button -- >
< button
@ click = "handleLogout"
class = "w-full path-action-button path-action-button--continue flex items-center justify-center gap-2"
>
< svg class = "w-5 h-5" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" >
< path stroke -linecap = " round " stroke -linejoin = " round " stroke -width = " 2 " d = "M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1" / >
< / svg >
2026-03-11 13:45:59 +00:00
< span > { { t ( 'settings.logout' ) } } < / span >
2026-01-24 22:59:20 +00:00
< / button >
< / div >
2026-03-04 07:09:31 +00:00
<!-- Interface Mode Section -- >
2026-03-10 23:58:33 +00:00
< div class = "glass-card px-6 py-6 mb-6" >
2026-03-11 13:45:59 +00:00
< h2 class = "text-xl font-semibold text-white/96 mb-2" > { { t ( 'settings.interfaceMode' ) } } < / h2 >
< p class = "text-sm text-white/60 mb-6" > { { t ( 'settings.interfaceModeDesc' ) } } < / p >
2026-03-04 07:09:31 +00:00
< div class = "grid grid-cols-1 md:grid-cols-3 gap-4" >
< button
v - for = "m in interfaceModes"
: key = "m.id"
@ click = "uiMode.setMode(m.id)"
class = "path-option-card text-left p-5"
: class = "{ 'path-option-card--selected': uiMode.mode === m.id }"
>
< div class = "flex items-center gap-3 mb-3" >
< svg class = "w-6 h-6 text-white/70" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" >
< path
v - for = "(path, index) in m.iconPaths"
: key = "index"
stroke - linecap = "round"
stroke - linejoin = "round"
stroke - width = "2"
: d = "path"
/ >
< / svg >
< h3 class = "text-lg font-semibold text-white/96" > { { m . label } } < / h3 >
< / div >
< p class = "text-sm text-white/60 leading-relaxed" > { { m . description } } < / p >
< / button >
< / div >
< / div >
feat: cloud native file browser, settings Claude auth, deploy hardening
- Add native Cloud file browser with FileBrowser API integration
- Add cloud store, filebrowser-client, useAudioPlayer, useFileType composables
- Add Cloud components: FileGrid, FileCard, FileCardGrid, CloudToolbar
- Add Claude authentication section to Settings with OAuth status check
- Harden deploy script to preserve /aiui/ and claude-login.html
- Add nginx proxies for btcpay, homeassistant, filebrowser (HTTPS block)
- Add app configs for filebrowser, searxng, penpot in package.rs
- Update goal progress tracking with app aliases
- Improve mobile back button composable with ResizeObserver
- Update various views with cloud integration and UI refinements
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 23:05:01 +00:00
<!-- Claude Authentication Section -- >
2026-03-10 23:58:33 +00:00
< div class = "glass-card px-6 py-6 mb-6" >
2026-03-11 13:45:59 +00:00
< h2 class = "text-xl font-semibold text-white/96 mb-2" > { { t ( 'settings.claudeAuth' ) } } < / h2 >
< p class = "text-sm text-white/60 mb-6" > { { t ( 'settings.claudeAuthDesc' ) } } < / p >
feat: cloud native file browser, settings Claude auth, deploy hardening
- Add native Cloud file browser with FileBrowser API integration
- Add cloud store, filebrowser-client, useAudioPlayer, useFileType composables
- Add Cloud components: FileGrid, FileCard, FileCardGrid, CloudToolbar
- Add Claude authentication section to Settings with OAuth status check
- Harden deploy script to preserve /aiui/ and claude-login.html
- Add nginx proxies for btcpay, homeassistant, filebrowser (HTTPS block)
- Add app configs for filebrowser, searxng, penpot in package.rs
- Update goal progress tracking with app aliases
- Improve mobile back button composable with ResizeObserver
- Update various views with cloud integration and UI refinements
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 23:05:01 +00:00
<!-- Status -- >
< div class = "bg-black/20 rounded-xl px-5 py-4 border border-white/10 mb-4" >
< div class = "flex items-center gap-3 mb-2" >
< svg class = "w-5 h-5 shrink-0" : class = "claudeConnected ? 'text-green-400' : 'text-white/40'" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" >
< path v-if = "claudeConnected" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" / >
< path v -else stroke -linecap = " round " stroke -linejoin = " round " stroke -width = " 2 " d = "M18.364 5.636a9 9 0 11-12.728 0M12 9v4m0 4h.01" / >
< / svg >
2026-03-11 13:45:59 +00:00
< p class = "text-xs font-semibold text-white/60 uppercase tracking-wide" > { { t ( 'settings.connectionStatus' ) } } < / p >
feat: cloud native file browser, settings Claude auth, deploy hardening
- Add native Cloud file browser with FileBrowser API integration
- Add cloud store, filebrowser-client, useAudioPlayer, useFileType composables
- Add Cloud components: FileGrid, FileCard, FileCardGrid, CloudToolbar
- Add Claude authentication section to Settings with OAuth status check
- Harden deploy script to preserve /aiui/ and claude-login.html
- Add nginx proxies for btcpay, homeassistant, filebrowser (HTTPS block)
- Add app configs for filebrowser, searxng, penpot in package.rs
- Update goal progress tracking with app aliases
- Improve mobile back button composable with ResizeObserver
- Update various views with cloud integration and UI refinements
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 23:05:01 +00:00
< / div >
< p class = "text-base font-medium" : class = "claudeConnected ? 'text-green-400' : 'text-white/50'" >
2026-03-11 13:45:59 +00:00
{ { claudeConnected ? t ( 'common.connected' ) : t ( 'settings.notConnected' ) } }
feat: cloud native file browser, settings Claude auth, deploy hardening
- Add native Cloud file browser with FileBrowser API integration
- Add cloud store, filebrowser-client, useAudioPlayer, useFileType composables
- Add Cloud components: FileGrid, FileCard, FileCardGrid, CloudToolbar
- Add Claude authentication section to Settings with OAuth status check
- Harden deploy script to preserve /aiui/ and claude-login.html
- Add nginx proxies for btcpay, homeassistant, filebrowser (HTTPS block)
- Add app configs for filebrowser, searxng, penpot in package.rs
- Update goal progress tracking with app aliases
- Improve mobile back button composable with ResizeObserver
- Update various views with cloud integration and UI refinements
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 23:05:01 +00:00
< / p >
< / div >
< button
@ click = "showClaudeLoginModal = true"
class = "w-full flex items-center justify-center gap-2 px-4 py-3 rounded-lg border transition-colors"
: class = " claudeConnected
? 'border-white/20 text-white/70 hover:bg-white/5'
: 'border-orange-500/50 text-orange-400 font-medium hover:bg-orange-500/10' "
>
< svg class = "w-5 h-5" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" >
< path stroke -linecap = " round " stroke -linejoin = " round " stroke -width = " 2 " d = "M11 16l-4-4m0 0l4-4m-4 4h14m-5 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h7a3 3 0 013 3v1" / >
< / svg >
2026-03-11 13:45:59 +00:00
< span > { { claudeConnected ? t ( 'settings.reAuthenticate' ) : t ( 'settings.loginWithClaude' ) } } < / span >
feat: cloud native file browser, settings Claude auth, deploy hardening
- Add native Cloud file browser with FileBrowser API integration
- Add cloud store, filebrowser-client, useAudioPlayer, useFileType composables
- Add Cloud components: FileGrid, FileCard, FileCardGrid, CloudToolbar
- Add Claude authentication section to Settings with OAuth status check
- Harden deploy script to preserve /aiui/ and claude-login.html
- Add nginx proxies for btcpay, homeassistant, filebrowser (HTTPS block)
- Add app configs for filebrowser, searxng, penpot in package.rs
- Update goal progress tracking with app aliases
- Improve mobile back button composable with ResizeObserver
- Update various views with cloud integration and UI refinements
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 23:05:01 +00:00
< / button >
< / div >
<!-- Claude Login Modal ( iframe ) -- >
< Teleport to = "body" >
< div
v - if = "showClaudeLoginModal"
class = "fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/60 backdrop-blur-sm"
@ click . self = "showClaudeLoginModal = false"
>
< div class = "glass-card p-0 max-w-lg w-full overflow-hidden" style = "height: 480px" >
< div class = "flex items-center justify-between px-4 py-3 border-b border-white/10" >
2026-03-11 13:45:59 +00:00
< h3 class = "text-sm font-semibold text-white/80" > { { t ( 'settings.claudeAuth' ) } } < / h3 >
feat: cloud native file browser, settings Claude auth, deploy hardening
- Add native Cloud file browser with FileBrowser API integration
- Add cloud store, filebrowser-client, useAudioPlayer, useFileType composables
- Add Cloud components: FileGrid, FileCard, FileCardGrid, CloudToolbar
- Add Claude authentication section to Settings with OAuth status check
- Harden deploy script to preserve /aiui/ and claude-login.html
- Add nginx proxies for btcpay, homeassistant, filebrowser (HTTPS block)
- Add app configs for filebrowser, searxng, penpot in package.rs
- Update goal progress tracking with app aliases
- Improve mobile back button composable with ResizeObserver
- Update various views with cloud integration and UI refinements
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 23:05:01 +00:00
< button @ click = "showClaudeLoginModal = false" class = "text-white/50 hover:text-white/80 transition-colors" >
< svg class = "w-5 h-5" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" >
< path stroke -linecap = " round " stroke -linejoin = " round " stroke -width = " 2 " d = "M6 18L18 6M6 6l12 12" / >
< / svg >
< / button >
< / div >
< iframe
src = "/claude-login"
class = "w-full border-0"
style = "height: calc(100% - 49px)"
@ load = "onClaudeIframeLoad"
/ >
< / div >
< / div >
< / Teleport >
feat: AIUI chat mode integration with iframe, context broker, overnight loop
- Chat mode: AIUI loads in sandboxed iframe at /dashboard/chat with transparent bg
- Mode switcher: Easy + Pro tabs only, Chat is a launcher button
- Keyboard shortcuts: Cmd+1 (Easy), Cmd+2 (Pro), Cmd+3 (Chat), Cmd+M (cycle)
- Directional transitions: chat slides from/to left, dashboard from/to right
- Context broker: postMessage protocol for quarantined AIUI communication
- AI permissions store: user-controlled toggles for data access categories
- Settings UI: AI Data Access section with per-category toggles
- AIUI container manifest and nginx proxy config for /aiui/
- Deploy script builds AIUI with /aiui/ base path
- Overnight loop infrastructure (loop.sh, prepare.sh, plan.md, prompt.md)
- Security hooks for autonomous overnight runs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 12:06:20 +00:00
<!-- AI Data Access Section -- >
2026-03-10 23:58:33 +00:00
< div class = "glass-card px-6 py-6 mb-6" >
2026-03-06 01:11:06 +00:00
< div class = "mb-2" >
2026-03-11 13:45:59 +00:00
< h2 class = "text-xl font-semibold text-white/96" > { { t ( 'settings.aiDataAccess' ) } } < / h2 >
feat: AIUI chat mode integration with iframe, context broker, overnight loop
- Chat mode: AIUI loads in sandboxed iframe at /dashboard/chat with transparent bg
- Mode switcher: Easy + Pro tabs only, Chat is a launcher button
- Keyboard shortcuts: Cmd+1 (Easy), Cmd+2 (Pro), Cmd+3 (Chat), Cmd+M (cycle)
- Directional transitions: chat slides from/to left, dashboard from/to right
- Context broker: postMessage protocol for quarantined AIUI communication
- AI permissions store: user-controlled toggles for data access categories
- Settings UI: AI Data Access section with per-category toggles
- AIUI container manifest and nginx proxy config for /aiui/
- Deploy script builds AIUI with /aiui/ base path
- Overnight loop infrastructure (loop.sh, prepare.sh, plan.md, prompt.md)
- Security hooks for autonomous overnight runs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 12:06:20 +00:00
< / div >
2026-03-11 13:45:59 +00:00
< p class = "text-sm text-white/60 mb-6" > { { t ( 'settings.aiDataAccessDesc' ) } } < / p >
feat: AIUI chat mode integration with iframe, context broker, overnight loop
- Chat mode: AIUI loads in sandboxed iframe at /dashboard/chat with transparent bg
- Mode switcher: Easy + Pro tabs only, Chat is a launcher button
- Keyboard shortcuts: Cmd+1 (Easy), Cmd+2 (Pro), Cmd+3 (Chat), Cmd+M (cycle)
- Directional transitions: chat slides from/to left, dashboard from/to right
- Context broker: postMessage protocol for quarantined AIUI communication
- AI permissions store: user-controlled toggles for data access categories
- Settings UI: AI Data Access section with per-category toggles
- AIUI container manifest and nginx proxy config for /aiui/
- Deploy script builds AIUI with /aiui/ base path
- Overnight loop infrastructure (loop.sh, prepare.sh, plan.md, prompt.md)
- Security hooks for autonomous overnight runs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 12:06:20 +00:00
2026-03-06 01:11:06 +00:00
<!-- Enable All toggle -- >
< button
@ click = "aiPermissions.allEnabled ? aiPermissions.disableAll() : aiPermissions.enableAll()"
class = "w-full flex items-center gap-4 p-4 rounded-xl border transition-all text-left mb-6"
: class = " aiPermissions . allEnabled
? 'bg-white/10 border-orange-500/40'
: 'bg-black/20 border-white/10 hover:border-white/20' "
>
< svg class = "w-5 h-5 shrink-0" : class = "aiPermissions.allEnabled ? 'text-orange-400' : 'text-white/40'" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" >
< path stroke -linecap = " round " stroke -linejoin = " round " stroke -width = " 2 " d = "M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" / >
< / svg >
< div class = "flex-1 min-w-0" >
2026-03-11 13:45:59 +00:00
< p class = "text-sm font-medium" : class = "aiPermissions.allEnabled ? 'text-white/95' : 'text-white/70'" > { { t ( 'common.enableAll' ) } } < / p >
< p class = "text-xs text-white/50 mt-0.5" > { { t ( 'settings.enableAllDesc' ) } } < / p >
2026-03-06 01:11:06 +00:00
< / div >
< div
class = "w-10 h-6 rounded-full shrink-0 transition-colors relative"
: class = "aiPermissions.allEnabled ? 'bg-orange-500' : 'bg-white/15'"
>
< div
class = "absolute top-1 w-4 h-4 rounded-full bg-white shadow transition-transform"
: class = "aiPermissions.allEnabled ? 'translate-x-5' : 'translate-x-1'"
/ >
< / div >
< / button >
feat: complete AIUI integration — all 31 overnight tasks
- Protocol: 10 context categories (apps, system, network, bitcoin, media, files, notes, search, ai-local, wallet)
- ContextBroker: real data wiring for all categories with sanitization
- Permissions: user toggles for all categories in Settings
- Nginx: Claude API, OpenRouter, SearXNG proxy pass-through
- Actions: launch-app, search-web, install-app handlers
- Chat.vue: loading state + connection indicator
- Integration test page: test-aiui.html
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 14:34:02 +00:00
< div class = "space-y-5" >
< div v-for = "group in aiCategoryGroups" :key="group.label" >
< p class = "text-xs font-medium text-white/40 uppercase tracking-wider mb-2 px-1" > { { group . label } } < / p >
< div class = "space-y-2" >
< button
v - for = "cat in group.items"
: key = "cat.id"
@ click = "aiPermissions.toggle(cat.id)"
class = "w-full flex items-center gap-4 p-4 rounded-xl border transition-all text-left"
: class = " aiPermissions . isEnabled ( cat . id )
? 'bg-white/10 border-orange-500/40'
: 'bg-black/20 border-white/10 hover:border-white/20' "
>
< svg class = "w-5 h-5 shrink-0" : class = "aiPermissions.isEnabled(cat.id) ? 'text-orange-400' : 'text-white/40'" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" >
< path stroke -linecap = " round " stroke -linejoin = " round " stroke -width = " 2 " :d = "cat.icon" / >
< / svg >
< div class = "flex-1 min-w-0" >
< p class = "text-sm font-medium" : class = "aiPermissions.isEnabled(cat.id) ? 'text-white/95' : 'text-white/70'" > { { cat . label } } < / p >
< p class = "text-xs text-white/50 mt-0.5" > { { cat . description } } < / p >
< / div >
< div
class = "w-10 h-6 rounded-full shrink-0 transition-colors relative"
: class = "aiPermissions.isEnabled(cat.id) ? 'bg-orange-500' : 'bg-white/15'"
>
< div
class = "absolute top-1 w-4 h-4 rounded-full bg-white shadow transition-transform"
: class = "aiPermissions.isEnabled(cat.id) ? 'translate-x-5' : 'translate-x-1'"
/ >
< / div >
< / button >
feat: AIUI chat mode integration with iframe, context broker, overnight loop
- Chat mode: AIUI loads in sandboxed iframe at /dashboard/chat with transparent bg
- Mode switcher: Easy + Pro tabs only, Chat is a launcher button
- Keyboard shortcuts: Cmd+1 (Easy), Cmd+2 (Pro), Cmd+3 (Chat), Cmd+M (cycle)
- Directional transitions: chat slides from/to left, dashboard from/to right
- Context broker: postMessage protocol for quarantined AIUI communication
- AI permissions store: user-controlled toggles for data access categories
- Settings UI: AI Data Access section with per-category toggles
- AIUI container manifest and nginx proxy config for /aiui/
- Deploy script builds AIUI with /aiui/ base path
- Overnight loop infrastructure (loop.sh, prepare.sh, plan.md, prompt.md)
- Security hooks for autonomous overnight runs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 12:06:20 +00:00
< / div >
feat: complete AIUI integration — all 31 overnight tasks
- Protocol: 10 context categories (apps, system, network, bitcoin, media, files, notes, search, ai-local, wallet)
- ContextBroker: real data wiring for all categories with sanitization
- Permissions: user toggles for all categories in Settings
- Nginx: Claude API, OpenRouter, SearXNG proxy pass-through
- Actions: launch-app, search-web, install-app handlers
- Chat.vue: loading state + connection indicator
- Integration test page: test-aiui.html
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 14:34:02 +00:00
< / div >
2026-01-24 22:59:20 +00:00
< / div >
< / div >
2026-03-09 07:43:12 +00:00
2026-03-11 12:46:02 +00:00
<!-- System Updates Section -- >
< div class = "glass-card px-6 py-6 mb-6" >
< div class = "flex items-center justify-between" >
< div >
2026-03-11 13:45:59 +00:00
< h2 class = "text-xl font-semibold text-white/96" > { { t ( 'settings.systemUpdates' ) } } < / h2 >
< p class = "text-sm text-white/60 mt-1" > { { t ( 'settings.systemUpdatesDesc' ) } } < / p >
2026-03-11 12:46:02 +00:00
< / div >
< RouterLink to = "/dashboard/settings/update" class = "glass-button px-4 py-2 rounded-lg text-sm flex items-center gap-2" >
< svg class = "w-4 h-4" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" >
< path stroke -linecap = " round " stroke -linejoin = " round " stroke -width = " 2 " d = "M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" / >
< / svg >
2026-03-11 13:45:59 +00:00
{ { t ( 'common.manageUpdates' ) } }
2026-03-11 12:46:02 +00:00
< / RouterLink >
< / div >
< / div >
2026-03-13 00:13:38 +00:00
<!-- Tor Services Section -- >
< div class = "glass-card px-6 py-6 mb-6" >
< div class = "flex items-center justify-between mb-4" >
< div >
< h2 class = "text-xl font-semibold text-white/96" > Tor Services < / h2 >
< p class = "text-sm text-white/60 mt-1" > Manage hidden service addresses for your node and apps < / p >
< / div >
< button @click ="loadTorServices" class = "glass-button px-4 py-2 rounded-lg text-sm flex items-center gap-2" >
< svg class = "w-4 h-4" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" >
< path stroke -linecap = " round " stroke -linejoin = " round " stroke -width = " 2 " d = "M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" / >
< / svg >
Refresh
< / button >
< / div >
< div v-if = "torLoading" class="text-sm text-white/40 py-4 text-center" > Loading Tor services... < / div >
< div v -else -if = " torServices.length = = = 0 " class = "text-sm text-white/40 py-4 text-center" > No Tor services configured < / div >
< div v -else class = "space-y-2" >
< div v-for = "svc in torServices" :key="svc.name" class="bg-black/20 rounded-xl border border-white/10 p-3 flex items-center justify-between gap-3" >
< div class = "flex-1 min-w-0" >
< p class = "text-white text-sm font-medium" > { { svc . name } } < / p >
< p v-if = "svc.onion_address" class="text-amber-300/80 text-xs font-mono truncate" > {{ svc.onion_address }} < / p >
< p v -else class = "text-white/30 text-xs" > No . onion address < / p >
< / div >
< div class = "flex items-center gap-2 shrink-0" >
< button
v - if = "svc.name === 'archipelago'"
@ click = "rotateNodeAddress"
: disabled = "torRotating"
class = "glass-button px-3 py-1.5 rounded-lg text-xs"
>
{ { torRotating ? 'Rotating...' : 'Rotate' } }
< / button >
< label class = "tor-toggle-label" >
< input
type = "checkbox"
: checked = "svc.enabled"
@ change = "toggleTorApp(svc.name, !svc.enabled)"
class = "tor-toggle-input"
/ >
< span class = "tor-toggle-slider" > < / span >
< / label >
< / div >
< / div >
< / div >
< / div >
2026-03-11 12:55:13 +00:00
<!-- Webhook Notifications Section -- >
< div class = "glass-card px-6 py-6 mb-6" >
< div class = "flex items-center justify-between mb-4" >
< div >
2026-03-11 13:45:59 +00:00
< h2 class = "text-xl font-semibold text-white/96" > { { t ( 'settings.webhookNotifications' ) } } < / h2 >
< p class = "text-sm text-white/60 mt-1" > { { t ( 'settings.webhookNotificationsDesc' ) } } < / p >
2026-03-11 12:55:13 +00:00
< / div >
< div class = "flex items-center gap-3" >
< button
@ click = "toggleWebhookEnabled"
2026-03-11 13:04:31 +00:00
role = "switch"
: aria - checked = "webhookConfig.enabled"
2026-03-11 13:45:59 +00:00
: aria - label = "webhookConfig.enabled ? t('settings.disableWebhooks') : t('settings.enableWebhooks')"
2026-03-11 12:55:13 +00:00
class = "w-10 h-6 rounded-full shrink-0 transition-colors relative"
: class = "webhookConfig.enabled ? 'bg-orange-500' : 'bg-white/15'"
2026-03-11 13:45:59 +00:00
: title = "webhookConfig.enabled ? t('settings.disableWebhooks') : t('settings.enableWebhooks')"
2026-03-11 12:55:13 +00:00
>
< div
class = "absolute top-1 w-4 h-4 rounded-full bg-white shadow transition-transform"
: class = "webhookConfig.enabled ? 'translate-x-5' : 'translate-x-1'"
/ >
< / button >
< / div >
< / div >
< div class = "space-y-4" >
<!-- Webhook URL -- >
< div >
2026-03-11 13:45:59 +00:00
< label class = "text-xs text-white/50 block mb-1" > { { t ( 'settings.webhookUrlLabel' ) } } < / label >
2026-03-11 12:55:13 +00:00
< input
v - model = "webhookConfig.url"
type = "url"
2026-03-11 13:45:59 +00:00
: placeholder = "t('settings.webhookUrlPlaceholder')"
2026-03-11 12:55:13 +00:00
class = "w-full bg-black/30 border border-white/10 rounded-lg px-3 py-2 text-sm text-white placeholder-white/30 focus:outline-none focus:border-orange-500/50"
/ >
< / div >
<!-- Secret ( optional ) -- >
< div >
2026-03-11 13:45:59 +00:00
< label class = "text-xs text-white/50 block mb-1" > { { t ( 'settings.webhookSecretLabel' ) } } < / label >
2026-03-11 12:55:13 +00:00
< input
v - model = "webhookConfig.secret"
type = "password"
2026-03-11 13:45:59 +00:00
: placeholder = "t('settings.webhookSecretPlaceholderFull')"
2026-03-11 12:55:13 +00:00
class = "w-full bg-black/30 border border-white/10 rounded-lg px-3 py-2 text-sm text-white placeholder-white/30 focus:outline-none focus:border-orange-500/50"
/ >
< / div >
<!-- Event Types -- >
< div >
2026-03-11 13:45:59 +00:00
< label class = "text-xs text-white/50 block mb-2" > { { t ( 'settings.eventsToNotify' ) } } < / label >
2026-03-11 12:55:13 +00:00
< div class = "grid grid-cols-1 sm:grid-cols-2 gap-2" >
< button
v - for = "evt in webhookEventTypes"
: key = "evt.id"
@ click = "toggleWebhookEvent(evt.id)"
2026-03-11 13:04:31 +00:00
role = "checkbox"
: aria - checked = "webhookConfig.events.includes(evt.id)"
: aria - label = "evt.label"
2026-03-11 12:55:13 +00:00
class = "flex items-center gap-3 p-3 rounded-lg border transition-colors text-left"
: class = " webhookConfig . events . includes ( evt . id )
? 'bg-orange-500/10 border-orange-500/30'
: 'bg-white/5 border-white/10 hover:border-white/20' "
>
< div
class = "w-5 h-5 rounded border-2 flex items-center justify-center shrink-0 transition-colors"
: class = " webhookConfig . events . includes ( evt . id )
? 'border-orange-500 bg-orange-500'
: 'border-white/30' "
>
< svg v-if = "webhookConfig.events.includes(evt.id)" class="w-3 h-3 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24" >
< path stroke -linecap = " round " stroke -linejoin = " round " stroke -width = " 3 " d = "M5 13l4 4L19 7" / >
< / svg >
< / div >
< div class = "min-w-0" >
< p class = "text-sm text-white/90 font-medium" > { { evt . label } } < / p >
< p class = "text-xs text-white/50" > { { evt . description } } < / p >
< / div >
< / button >
< / div >
< / div >
<!-- Actions -- >
< div class = "flex flex-col sm:flex-row gap-2 pt-2" >
< button
@ click = "saveWebhookConfig"
: disabled = "savingWebhook"
class = "glass-button px-4 py-2 rounded-lg text-sm flex items-center justify-center gap-2 bg-orange-500/20 border-orange-500/30 disabled:opacity-50"
>
2026-03-11 13:45:59 +00:00
{ { savingWebhook ? t ( 'settings.savingWebhook' ) : t ( 'common.saveConfiguration' ) } }
2026-03-11 12:55:13 +00:00
< / button >
< button
@ click = "testWebhook"
: disabled = "testingWebhook || !webhookConfig.url"
class = "glass-button px-4 py-2 rounded-lg text-sm flex items-center justify-center gap-2 disabled:opacity-50"
>
2026-03-11 13:45:59 +00:00
{ { testingWebhook ? t ( 'common.sending' ) : t ( 'common.sendTest' ) } }
2026-03-11 12:55:13 +00:00
< / button >
< / div >
< / div >
<!-- Webhook status message -- >
2026-03-11 13:04:31 +00:00
< div v-if = "webhookStatusMsg" role="status" aria-live="polite" class="mt-3 text-xs px-3 py-2 rounded-lg" :class="webhookStatusType === 'error' ? 'bg-red-500/15 text-red-300' : 'bg-green-500/15 text-green-300'" >
2026-03-11 12:55:13 +00:00
{ { webhookStatusMsg } }
< / div >
< / div >
2026-03-11 12:46:02 +00:00
<!-- Backup & Restore Section -- >
< div class = "glass-card px-6 py-6 mb-6" >
< div class = "flex items-center justify-between mb-4" >
< div >
2026-03-11 13:45:59 +00:00
< h2 class = "text-xl font-semibold text-white/96" > { { t ( 'settings.backup' ) } } < / h2 >
< p class = "text-sm text-white/60 mt-1" > { { t ( 'settings.backupRestoreDesc' ) } } < / p >
2026-03-11 12:46:02 +00:00
< / div >
< button @ click = "showCreateBackupModal = true" class = "glass-button px-4 py-2 rounded-lg text-sm flex items-center gap-2" >
< svg class = "w-4 h-4" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" >
< path stroke -linecap = " round " stroke -linejoin = " round " stroke -width = " 2 " d = "M12 4v16m8-8H4" / >
< / svg >
2026-03-11 13:45:59 +00:00
{ { t ( 'settings.createBackup' ) } }
2026-03-11 12:46:02 +00:00
< / button >
< / div >
<!-- Backup List -- >
2026-03-11 13:45:59 +00:00
< div v-if = "loadingBackups" class="text-sm text-white/40 py-4 text-center" > {{ t ( ' settings.loadingBackups ' ) }} < / div >
< div v -else -if = " backupList.length = = = 0 " class = "text-sm text-white/40 py-4 text-center" > { { t ( 'settings.noBackups' ) } } < / div >
2026-03-11 12:46:02 +00:00
< div v -else class = "space-y-2" >
< div v-for = "b in backupList" :key="b.id" class="flex flex-col sm:flex-row sm:items-center sm:justify-between p-3 bg-white/5 rounded-lg gap-2" >
< div class = "min-w-0" >
2026-03-11 13:45:59 +00:00
< div class = "text-sm text-white font-medium" > { { b . description || t ( 'settings.systemBackup' ) } } < / div >
2026-03-11 12:46:02 +00:00
< div class = "text-xs text-white/50" > { { new Date ( b . created _at ) . toLocaleString ( ) } } & middot ; { { formatBackupSize ( b . size _bytes ) } } < / div >
< / div >
< div class = "flex items-center gap-2 shrink-0 flex-wrap" >
2026-03-11 13:45:59 +00:00
< button @click ="verifyBackup(b.id)" : disabled = "verifyingBackupId === b.id" class = "glass-button glass-button-sm px-3 py-1.5 rounded text-xs disabled:opacity-50" :title = "t('common.verify')" >
{ { verifyingBackupId === b . id ? '...' : t ( 'common.verify' ) } }
2026-03-11 12:46:02 +00:00
< / button >
2026-03-11 13:45:59 +00:00
< button @click ="backupToUsb(b.id)" : disabled = "usbCopyingId === b.id" class = "glass-button glass-button-sm px-3 py-1.5 rounded text-xs text-blue-400 disabled:opacity-50" :title = "t('settings.copyToUsb')" >
2026-03-11 12:46:02 +00:00
{ { usbCopyingId === b . id ? '...' : 'USB' } }
< / button >
2026-03-11 13:45:59 +00:00
< button @click ="confirmRestoreBackup(b.id)" class = "glass-button glass-button-sm px-3 py-1.5 rounded text-xs text-orange-400" :title = "t('common.restore')" >
{ { t ( 'common.restore' ) } }
2026-03-11 12:46:02 +00:00
< / button >
2026-03-11 13:45:59 +00:00
< button @click ="deleteBackup(b.id)" : disabled = "deletingBackupId === b.id" :aria-label = "t('settings.deleteBackup')" class = "glass-button glass-button-sm px-3 py-1.5 rounded text-xs text-red-400 disabled:opacity-50" :title = "t('common.delete')" >
2026-03-11 12:46:02 +00:00
& times ;
< / button >
< / div >
< / div >
< / div >
<!-- Backup status message -- >
2026-03-11 13:04:31 +00:00
< div v-if = "backupStatusMsg" role="status" aria-live="polite" class="mt-3 text-xs px-3 py-2 rounded-lg" :class="backupStatusType === 'error' ? 'bg-red-500/15 text-red-300' : 'bg-green-500/15 text-green-300'" >
2026-03-11 12:46:02 +00:00
{ { backupStatusMsg } }
< / div >
< / div >
<!-- Create Backup Modal -- >
< Teleport to = "body" >
< div v-if = "showCreateBackupModal" class="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/60 backdrop-blur-sm" @click.self="showCreateBackupModal = false" >
2026-03-11 13:04:31 +00:00
< div class = "glass-card p-6 w-full max-w-md" role = "dialog" aria -modal = " true " aria -labelledby = " create -backup -title " >
2026-03-11 13:45:59 +00:00
< h3 id = "create-backup-title" class = "text-lg font-semibold text-white mb-4" > { { t ( 'settings.createEncryptedBackup' ) } } < / h3 >
2026-03-11 12:46:02 +00:00
< div class = "space-y-3" >
< div >
2026-03-11 13:45:59 +00:00
< label class = "text-xs text-white/50 block mb-1" > { { t ( 'settings.encryptionPassphrase' ) } } < / label >
< input v-model = "backupPassphrase" type="password" :placeholder="t('settings.enterPassphrase')" class="w-full bg-black/30 border border-white/10 rounded-lg px-3 py-2 text-sm text-white placeholder-white/30 focus:outline-none focus:border-blue-500/50" / >
2026-03-11 12:46:02 +00:00
< / div >
< div >
2026-03-11 13:45:59 +00:00
< label class = "text-xs text-white/50 block mb-1" > { { t ( 'settings.descriptionOptional' ) } } < / label >
< input v-model = "backupDescription" type="text" :placeholder="t('settings.descriptionPlaceholder')" class="w-full bg-black/30 border border-white/10 rounded-lg px-3 py-2 text-sm text-white placeholder-white/30 focus:outline-none focus:border-blue-500/50" / >
2026-03-11 12:46:02 +00:00
< / div >
< / div >
< div class = "flex gap-3 mt-5" >
2026-03-11 13:45:59 +00:00
< button @ click = "showCreateBackupModal = false" class = "glass-button px-4 py-2 rounded-lg text-sm flex-1" > { { t ( 'common.cancel' ) } } < / button >
2026-03-11 12:46:02 +00:00
< button @click ="createBackup" : disabled = "creatingBackup || !backupPassphrase" class = "glass-button px-4 py-2 rounded-lg text-sm flex-1 bg-orange-500/20 border-orange-500/30 disabled:opacity-50" >
2026-03-11 13:45:59 +00:00
{ { creatingBackup ? t ( 'settings.creatingBackup' ) : t ( 'settings.createBackup' ) } }
2026-03-11 12:46:02 +00:00
< / button >
< / div >
< / div >
< / div >
< / Teleport >
<!-- Restore Backup Modal -- >
< Teleport to = "body" >
< div v-if = "showRestoreModal" class="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/60 backdrop-blur-sm" @click.self="showRestoreModal = false" >
2026-03-11 13:04:31 +00:00
< div class = "glass-card p-6 w-full max-w-md" role = "dialog" aria -modal = " true " aria -labelledby = " restore -backup -title " >
2026-03-11 13:45:59 +00:00
< h3 id = "restore-backup-title" class = "text-lg font-semibold text-white mb-2" > { { t ( 'settings.restoreBackupTitle' ) } } < / h3 >
< p class = "text-sm text-red-400/80 mb-4" > { { t ( 'settings.restoreWarning' ) } } < / p >
2026-03-11 12:46:02 +00:00
< div >
2026-03-11 13:45:59 +00:00
< label class = "text-xs text-white/50 block mb-1" > { { t ( 'settings.encryptionPassphrase' ) } } < / label >
< input v-model = "restorePassphrase" type="password" :placeholder="t('settings.enterBackupPassphrase')" class="w-full bg-black/30 border border-white/10 rounded-lg px-3 py-2 text-sm text-white placeholder-white/30 focus:outline-none focus:border-blue-500/50" / >
2026-03-11 12:46:02 +00:00
< / div >
< div class = "flex gap-3 mt-5" >
2026-03-11 13:45:59 +00:00
< button @ click = "showRestoreModal = false" class = "glass-button px-4 py-2 rounded-lg text-sm flex-1" > { { t ( 'common.cancel' ) } } < / button >
2026-03-11 12:46:02 +00:00
< button @click ="restoreBackup" : disabled = "restoringBackup || !restorePassphrase" class = "glass-button px-4 py-2 rounded-lg text-sm flex-1 bg-red-500/20 border-red-500/30 disabled:opacity-50" >
2026-03-11 13:45:59 +00:00
{ { restoringBackup ? t ( 'common.restoring' ) : t ( 'common.restore' ) } }
2026-03-11 12:46:02 +00:00
< / button >
< / div >
< / div >
< / div >
< / Teleport >
2026-03-11 00:01:15 +00:00
<!-- Network Diagnostics Link -- >
2026-03-10 23:58:33 +00:00
< div class = "glass-card px-6 py-6 mb-6" >
2026-03-11 00:01:15 +00:00
< div class = "flex items-center justify-between" >
< div >
2026-03-11 13:45:59 +00:00
< h2 class = "text-xl font-semibold text-white/96" > { { t ( 'common.network' ) } } < / h2 >
< p class = "text-sm text-white/60 mt-1" > { { t ( 'settings.networkDesc' ) } } < / p >
2026-03-09 07:43:12 +00:00
< / div >
2026-03-11 00:01:15 +00:00
< button @click ="router.push('/dashboard/server')" class = "glass-button px-4 py-2 rounded-lg text-sm flex items-center gap-2" >
< svg class = "w-4 h-4" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" >
< path stroke -linecap = " round " stroke -linejoin = " round " stroke -width = " 2 " d = "M13 7l5 5m0 0l-5 5m5-5H6" / >
< / svg >
2026-03-11 13:45:59 +00:00
{ { t ( 'common.networkDiagnostics' ) } }
2026-03-09 07:43:12 +00:00
< / button >
< / div >
< / div >
2026-01-24 22:59:20 +00:00
< / div >
< / template >
< script setup lang = "ts" >
2026-02-17 15:03:34 +00:00
import { computed , ref , onMounted } from 'vue'
2026-01-24 22:59:20 +00:00
import { useRouter } from 'vue-router'
2026-03-11 13:45:59 +00:00
import { useI18n } from 'vue-i18n'
2026-01-24 22:59:20 +00:00
import { useAppStore } from '../stores/app'
2026-03-04 07:09:31 +00:00
import { useUIModeStore } from '@/stores/uiMode'
feat: AIUI chat mode integration with iframe, context broker, overnight loop
- Chat mode: AIUI loads in sandboxed iframe at /dashboard/chat with transparent bg
- Mode switcher: Easy + Pro tabs only, Chat is a launcher button
- Keyboard shortcuts: Cmd+1 (Easy), Cmd+2 (Pro), Cmd+3 (Chat), Cmd+M (cycle)
- Directional transitions: chat slides from/to left, dashboard from/to right
- Context broker: postMessage protocol for quarantined AIUI communication
- AI permissions store: user-controlled toggles for data access categories
- Settings UI: AI Data Access section with per-category toggles
- AIUI container manifest and nginx proxy config for /aiui/
- Deploy script builds AIUI with /aiui/ base path
- Overnight loop infrastructure (loop.sh, prepare.sh, plan.md, prompt.md)
- Security hooks for autonomous overnight runs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 12:06:20 +00:00
import { useAIPermissionsStore , AI _PERMISSION _CATEGORIES } from '@/stores/aiPermissions'
2026-02-17 15:03:34 +00:00
import ControllerIndicator from '@/components/ControllerIndicator.vue'
import { rpcClient } from '@/api/rpc-client'
2026-02-17 22:10:38 +00:00
import { useModalKeyboard } from '@/composables/useModalKeyboard'
2026-03-04 07:09:31 +00:00
import type { UIMode } from '@/types/api'
2026-01-24 22:59:20 +00:00
const router = useRouter ( )
2026-03-11 13:45:59 +00:00
const { t } = useI18n ( )
2026-01-24 22:59:20 +00:00
const store = useAppStore ( )
2026-03-04 07:09:31 +00:00
const uiMode = useUIModeStore ( )
feat: AIUI chat mode integration with iframe, context broker, overnight loop
- Chat mode: AIUI loads in sandboxed iframe at /dashboard/chat with transparent bg
- Mode switcher: Easy + Pro tabs only, Chat is a launcher button
- Keyboard shortcuts: Cmd+1 (Easy), Cmd+2 (Pro), Cmd+3 (Chat), Cmd+M (cycle)
- Directional transitions: chat slides from/to left, dashboard from/to right
- Context broker: postMessage protocol for quarantined AIUI communication
- AI permissions store: user-controlled toggles for data access categories
- Settings UI: AI Data Access section with per-category toggles
- AIUI container manifest and nginx proxy config for /aiui/
- Deploy script builds AIUI with /aiui/ base path
- Overnight loop infrastructure (loop.sh, prepare.sh, plan.md, prompt.md)
- Security hooks for autonomous overnight runs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 12:06:20 +00:00
const aiPermissions = useAIPermissionsStore ( )
feat: complete AIUI integration — all 31 overnight tasks
- Protocol: 10 context categories (apps, system, network, bitcoin, media, files, notes, search, ai-local, wallet)
- ContextBroker: real data wiring for all categories with sanitization
- Permissions: user toggles for all categories in Settings
- Nginx: Claude API, OpenRouter, SearXNG proxy pass-through
- Actions: launch-app, search-web, install-app handlers
- Chat.vue: loading state + connection indicator
- Integration test page: test-aiui.html
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 14:34:02 +00:00
const aiCategoryGroups = computed ( ( ) => {
const groups : { label : string ; items : typeof AI _PERMISSION _CATEGORIES } [ ] = [ ]
for ( const cat of AI _PERMISSION _CATEGORIES ) {
const existing = groups . find ( g => g . label === cat . group )
if ( existing ) {
existing . items . push ( cat )
} else {
groups . push ( { label : cat . group , items : [ cat ] } )
}
}
return groups
} )
2026-03-04 07:09:31 +00:00
2026-03-11 13:45:59 +00:00
const interfaceModes = computed < { id : UIMode ; label : string ; description : string ; iconPaths : string [ ] } [ ] > ( ( ) => [
2026-03-04 07:09:31 +00:00
{
id : 'easy' ,
2026-03-11 13:45:59 +00:00
label : t ( 'settings.modeEasy' ) ,
description : t ( 'settings.modeEasyDesc' ) ,
2026-03-04 07:09:31 +00:00
iconPaths : [ 'M14.828 14.828a4 4 0 01-5.656 0M9 10h.01M15 10h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z' ] ,
} ,
{
id : 'gamer' ,
2026-03-11 13:45:59 +00:00
label : t ( 'settings.modePro' ) ,
description : t ( 'settings.modeProDesc' ) ,
2026-03-04 07:09:31 +00:00
iconPaths : [ 'M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z' , 'M15 12a3 3 0 11-6 0 3 3 0 016 0z' ] ,
} ,
{
id : 'chat' ,
2026-03-11 13:45:59 +00:00
label : t ( 'settings.modeChat' ) ,
description : t ( 'settings.modeChatDesc' ) ,
2026-03-04 07:09:31 +00:00
iconPaths : [ 'M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z' ] ,
} ,
2026-03-11 13:45:59 +00:00
] )
2026-01-24 22:59:20 +00:00
const serverName = computed ( ( ) => store . serverName )
const version = computed ( ( ) => store . serverInfo ? . version || '0.0.0' )
2026-02-17 15:03:34 +00:00
const serverTorAddressFromStore = computed ( ( ) => store . serverInfo ? . [ 'tor-address' ] || null )
const torAddressFromRpc = ref < string | null > ( null )
const serverTorAddress = computed ( ( ) => serverTorAddressFromStore . value || torAddressFromRpc . value )
const userDid = computed ( ( ) => {
try {
return localStorage . getItem ( 'neode_did' ) || null
} catch {
return null
}
} )
feat: cloud native file browser, settings Claude auth, deploy hardening
- Add native Cloud file browser with FileBrowser API integration
- Add cloud store, filebrowser-client, useAudioPlayer, useFileType composables
- Add Cloud components: FileGrid, FileCard, FileCardGrid, CloudToolbar
- Add Claude authentication section to Settings with OAuth status check
- Harden deploy script to preserve /aiui/ and claude-login.html
- Add nginx proxies for btcpay, homeassistant, filebrowser (HTTPS block)
- Add app configs for filebrowser, searxng, penpot in package.rs
- Update goal progress tracking with app aliases
- Improve mobile back button composable with ResizeObserver
- Update various views with cloud integration and UI refinements
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 23:05:01 +00:00
const claudeConnected = ref ( false )
const showClaudeLoginModal = ref ( false )
function checkClaudeStatus ( ) {
fetch ( '/aiui/api/claude/v1/messages' , { method : 'POST' , headers : { 'Content-Type' : 'application/json' } , body : JSON . stringify ( { model : 'haiku' , messages : [ { role : 'user' , content : 'ping' } ] } ) } )
. then ( r => {
if ( ! r . ok ) { claudeConnected . value = false ; return }
const reader = r . body ? . getReader ( )
if ( ! reader ) return
const decoder = new TextDecoder ( )
let text = ''
function read ( ) : Promise < void > {
return reader ! . read ( ) . then ( ( { done , value } ) => {
if ( done ) {
claudeConnected . value = ! text . includes ( 'Not logged in' ) && ! text . includes ( 'error' )
return
}
text += decoder . decode ( value , { stream : true } )
return read ( )
} )
}
read ( )
} )
. catch ( ( ) => { claudeConnected . value = false } )
}
function onClaudeIframeLoad ( ) {
// Listen for success message from login iframe
window . addEventListener ( 'message' , handleClaudeLoginMessage )
}
function handleClaudeLoginMessage ( e : MessageEvent ) {
if ( e . data ? . type === 'claude-auth-success' ) {
claudeConnected . value = true
showClaudeLoginModal . value = false
window . removeEventListener ( 'message' , handleClaudeLoginMessage )
}
}
feat: add TOTP 2FA, API key switcher, login progress bar, and alpha hardening plan
- TOTP 2FA: full setup/confirm/disable/login flow with Argon2id + ChaCha20-Poly1305
encrypted secret storage, QR code generation, and bcrypt-hashed backup codes
- API key switcher: OAuth vs personal API key toggle in AIUI chat settings with
status indicator, key validation, and help text
- Login progress bar: server startup detection with health check polling, form
disabled until server is ready
- AI quarantine docs: comprehensive HTML page documenting all 6 security layers
- Settings: AI Data Access permission toggles with per-category control
- Alpha hardening plan: 28-task overnight automation plan across 7 phases
(onboarding, login, app install, AIUI, UI polish, security, ISO build)
- Backlog: node discovery spatial map feature for alpha demo
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 12:23:57 +00:00
// --- 2FA State ---
const totpEnabled = ref ( false )
const showTotpSetupModal = ref ( false )
const showTotpDisableModal = ref ( false )
const totpSetupStep = ref ( 1 )
const totpSetupPassword = ref ( '' )
const totpSetupCode = ref ( '' )
const totpSetupError = ref ( '' )
const totpSetupLoading = ref ( false )
const totpQrSvg = ref ( '' )
const totpSecretBase32 = ref ( '' )
const totpPendingToken = ref ( '' )
const totpBackupCodes = ref < string [ ] > ( [ ] )
const backupCodesCopied = ref ( false )
const totpDisablePassword = ref ( '' )
const totpDisableCode = ref ( '' )
const totpDisableError = ref ( '' )
const totpDisableLoading = ref ( false )
async function loadTotpStatus ( ) {
try {
const res = await rpcClient . totpStatus ( )
totpEnabled . value = res . enabled
2026-03-11 00:58:55 +00:00
} catch ( e ) {
if ( import . meta . env . DEV ) console . warn ( 'TOTP status may not be available' , e )
feat: add TOTP 2FA, API key switcher, login progress bar, and alpha hardening plan
- TOTP 2FA: full setup/confirm/disable/login flow with Argon2id + ChaCha20-Poly1305
encrypted secret storage, QR code generation, and bcrypt-hashed backup codes
- API key switcher: OAuth vs personal API key toggle in AIUI chat settings with
status indicator, key validation, and help text
- Login progress bar: server startup detection with health check polling, form
disabled until server is ready
- AI quarantine docs: comprehensive HTML page documenting all 6 security layers
- Settings: AI Data Access permission toggles with per-category control
- Alpha hardening plan: 28-task overnight automation plan across 7 phases
(onboarding, login, app install, AIUI, UI polish, security, ISO build)
- Backlog: node discovery spatial map feature for alpha demo
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 12:23:57 +00:00
}
}
async function beginTotpSetup ( ) {
totpSetupError . value = ''
totpSetupLoading . value = true
try {
const res = await rpcClient . totpSetupBegin ( totpSetupPassword . value )
totpQrSvg . value = res . qr _svg
totpSecretBase32 . value = res . secret _base32
totpPendingToken . value = res . pending _token
totpSetupStep . value = 2
} catch ( e ) {
2026-03-11 13:45:59 +00:00
totpSetupError . value = e instanceof Error ? e . message : t ( 'settings.setupFailed' )
feat: add TOTP 2FA, API key switcher, login progress bar, and alpha hardening plan
- TOTP 2FA: full setup/confirm/disable/login flow with Argon2id + ChaCha20-Poly1305
encrypted secret storage, QR code generation, and bcrypt-hashed backup codes
- API key switcher: OAuth vs personal API key toggle in AIUI chat settings with
status indicator, key validation, and help text
- Login progress bar: server startup detection with health check polling, form
disabled until server is ready
- AI quarantine docs: comprehensive HTML page documenting all 6 security layers
- Settings: AI Data Access permission toggles with per-category control
- Alpha hardening plan: 28-task overnight automation plan across 7 phases
(onboarding, login, app install, AIUI, UI polish, security, ISO build)
- Backlog: node discovery spatial map feature for alpha demo
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 12:23:57 +00:00
} finally {
totpSetupLoading . value = false
}
}
async function confirmTotpSetup ( ) {
totpSetupError . value = ''
totpSetupLoading . value = true
try {
const res = await rpcClient . totpSetupConfirm ( {
code : totpSetupCode . value ,
password : totpSetupPassword . value ,
pendingToken : totpPendingToken . value ,
} )
totpBackupCodes . value = res . backup _codes
totpEnabled . value = true
totpSetupStep . value = 3
} catch ( e ) {
2026-03-11 13:45:59 +00:00
totpSetupError . value = e instanceof Error ? e . message : t ( 'settings.verificationFailed' )
feat: add TOTP 2FA, API key switcher, login progress bar, and alpha hardening plan
- TOTP 2FA: full setup/confirm/disable/login flow with Argon2id + ChaCha20-Poly1305
encrypted secret storage, QR code generation, and bcrypt-hashed backup codes
- API key switcher: OAuth vs personal API key toggle in AIUI chat settings with
status indicator, key validation, and help text
- Login progress bar: server startup detection with health check polling, form
disabled until server is ready
- AI quarantine docs: comprehensive HTML page documenting all 6 security layers
- Settings: AI Data Access permission toggles with per-category control
- Alpha hardening plan: 28-task overnight automation plan across 7 phases
(onboarding, login, app install, AIUI, UI polish, security, ISO build)
- Backlog: node discovery spatial map feature for alpha demo
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 12:23:57 +00:00
} finally {
totpSetupLoading . value = false
}
}
function closeTotpSetup ( ) {
showTotpSetupModal . value = false
totpSetupStep . value = 1
totpSetupPassword . value = ''
totpSetupCode . value = ''
totpSetupError . value = ''
totpQrSvg . value = ''
totpSecretBase32 . value = ''
totpPendingToken . value = ''
totpBackupCodes . value = [ ]
backupCodesCopied . value = false
}
async function disableTotp ( ) {
totpDisableError . value = ''
totpDisableLoading . value = true
try {
await rpcClient . totpDisable ( totpDisablePassword . value , totpDisableCode . value )
totpEnabled . value = false
closeTotpDisable ( )
} catch ( e ) {
2026-03-11 13:45:59 +00:00
totpDisableError . value = e instanceof Error ? e . message : t ( 'settings.disableFailed' )
feat: add TOTP 2FA, API key switcher, login progress bar, and alpha hardening plan
- TOTP 2FA: full setup/confirm/disable/login flow with Argon2id + ChaCha20-Poly1305
encrypted secret storage, QR code generation, and bcrypt-hashed backup codes
- API key switcher: OAuth vs personal API key toggle in AIUI chat settings with
status indicator, key validation, and help text
- Login progress bar: server startup detection with health check polling, form
disabled until server is ready
- AI quarantine docs: comprehensive HTML page documenting all 6 security layers
- Settings: AI Data Access permission toggles with per-category control
- Alpha hardening plan: 28-task overnight automation plan across 7 phases
(onboarding, login, app install, AIUI, UI polish, security, ISO build)
- Backlog: node discovery spatial map feature for alpha demo
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 12:23:57 +00:00
} finally {
totpDisableLoading . value = false
}
}
function closeTotpDisable ( ) {
showTotpDisableModal . value = false
totpDisablePassword . value = ''
totpDisableCode . value = ''
totpDisableError . value = ''
}
async function copyBackupCodes ( ) {
const text = totpBackupCodes . value . join ( '\n' )
try {
await navigator . clipboard . writeText ( text )
} catch {
const ta = document . createElement ( 'textarea' )
ta . value = text
ta . style . position = 'fixed'
ta . style . opacity = '0'
document . body . appendChild ( ta )
ta . select ( )
document . execCommand ( 'copy' )
document . body . removeChild ( ta )
}
backupCodesCopied . value = true
setTimeout ( ( ) => { backupCodesCopied . value = false } , 2000 )
}
2026-02-17 15:03:34 +00:00
const copiedOnion = ref ( false )
2026-03-11 12:46:02 +00:00
const copiedDid = ref ( false )
2026-02-17 15:03:34 +00:00
const showChangePasswordModal = ref ( false )
2026-02-17 22:10:38 +00:00
const changePasswordModalRef = ref < HTMLElement | null > ( null )
const changePasswordRestoreFocusRef = ref < HTMLElement | null > ( null )
useModalKeyboard ( changePasswordModalRef , showChangePasswordModal , closeChangePasswordModal , { restoreFocusRef : changePasswordRestoreFocusRef } )
2026-02-17 15:03:34 +00:00
const changingPassword = ref ( false )
const changePasswordError = ref ( '' )
const changePasswordSuccess = ref ( '' )
const changePasswordForm = ref ( {
currentPassword : '' ,
newPassword : '' ,
confirmPassword : '' ,
alsoChangeSsh : true ,
} )
function validatePasswordStrength ( pw : string ) : string | null {
2026-03-11 13:45:59 +00:00
if ( pw . length < 12 ) return t ( 'settings.passwordMinLength' )
if ( ! /[A-Z]/ . test ( pw ) ) return t ( 'settings.passwordNeedUppercase' )
if ( ! /[a-z]/ . test ( pw ) ) return t ( 'settings.passwordNeedLowercase' )
if ( ! /\d/ . test ( pw ) ) return t ( 'settings.passwordNeedDigit' )
if ( ! /[^A-Za-z0-9]/ . test ( pw ) ) return t ( 'settings.passwordNeedSpecial' )
2026-02-17 15:03:34 +00:00
return null
}
async function handleChangePassword ( ) {
changePasswordError . value = ''
changePasswordSuccess . value = ''
const { currentPassword , newPassword , confirmPassword , alsoChangeSsh } = changePasswordForm . value
if ( ! currentPassword || ! newPassword || ! confirmPassword ) {
2026-03-11 13:45:59 +00:00
changePasswordError . value = t ( 'settings.passwordAllFieldsRequired' )
2026-02-17 15:03:34 +00:00
return
}
if ( newPassword !== confirmPassword ) {
2026-03-11 13:45:59 +00:00
changePasswordError . value = t ( 'settings.passwordMismatch' )
2026-02-17 15:03:34 +00:00
return
}
const strengthError = validatePasswordStrength ( newPassword )
if ( strengthError ) {
changePasswordError . value = strengthError
return
}
changingPassword . value = true
try {
await rpcClient . changePassword ( {
currentPassword ,
newPassword ,
alsoChangeSsh ,
} )
2026-03-11 13:45:59 +00:00
changePasswordSuccess . value = t ( 'settings.passwordUpdatedSuccess' )
2026-02-17 15:03:34 +00:00
changePasswordForm . value = { currentPassword : '' , newPassword : '' , confirmPassword : '' , alsoChangeSsh : true }
setTimeout ( ( ) => {
closeChangePasswordModal ( )
} , 2000 )
} catch ( e ) {
2026-03-11 13:45:59 +00:00
changePasswordError . value = e instanceof Error ? e . message : t ( 'settings.passwordChangeFailed' )
2026-02-17 15:03:34 +00:00
} finally {
changingPassword . value = false
}
}
2026-03-01 18:07:35 +00:00
let copiedTimer : ReturnType < typeof setTimeout > | null = null
2026-02-17 15:03:34 +00:00
async function copyOnionAddress ( ) {
const addr = serverTorAddress . value
if ( ! addr ) return
try {
await navigator . clipboard . writeText ( addr )
} catch {
const ta = document . createElement ( 'textarea' )
ta . value = addr
2026-03-01 18:07:35 +00:00
ta . style . position = 'fixed'
ta . style . opacity = '0'
2026-02-17 15:03:34 +00:00
document . body . appendChild ( ta )
ta . select ( )
document . execCommand ( 'copy' )
document . body . removeChild ( ta )
}
2026-03-01 18:07:35 +00:00
copiedOnion . value = true
if ( copiedTimer ) clearTimeout ( copiedTimer )
copiedTimer = setTimeout ( ( ) => { copiedOnion . value = false } , 2000 )
2026-02-17 15:03:34 +00:00
}
2026-03-11 12:46:02 +00:00
async function copyDid ( ) {
if ( ! userDid . value ) return
try {
await navigator . clipboard . writeText ( userDid . value )
} catch {
const ta = document . createElement ( 'textarea' )
ta . value = userDid . value
ta . style . position = 'fixed'
ta . style . opacity = '0'
document . body . appendChild ( ta )
ta . select ( )
document . execCommand ( 'copy' )
document . body . removeChild ( ta )
}
copiedDid . value = true
setTimeout ( ( ) => { copiedDid . value = false } , 2000 )
}
2026-02-17 15:03:34 +00:00
function closeChangePasswordModal ( ) {
2026-02-17 22:10:38 +00:00
changePasswordRestoreFocusRef . value ? . focus ? . ( )
2026-02-17 15:03:34 +00:00
showChangePasswordModal . value = false
changePasswordError . value = ''
changePasswordSuccess . value = ''
changePasswordForm . value = { currentPassword : '' , newPassword : '' , confirmPassword : '' , alsoChangeSsh : true }
}
onMounted ( async ( ) => {
feat: cloud native file browser, settings Claude auth, deploy hardening
- Add native Cloud file browser with FileBrowser API integration
- Add cloud store, filebrowser-client, useAudioPlayer, useFileType composables
- Add Cloud components: FileGrid, FileCard, FileCardGrid, CloudToolbar
- Add Claude authentication section to Settings with OAuth status check
- Harden deploy script to preserve /aiui/ and claude-login.html
- Add nginx proxies for btcpay, homeassistant, filebrowser (HTTPS block)
- Add app configs for filebrowser, searxng, penpot in package.rs
- Update goal progress tracking with app aliases
- Improve mobile back button composable with ResizeObserver
- Update various views with cloud integration and UI refinements
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 23:05:01 +00:00
checkClaudeStatus ( )
feat: add TOTP 2FA, API key switcher, login progress bar, and alpha hardening plan
- TOTP 2FA: full setup/confirm/disable/login flow with Argon2id + ChaCha20-Poly1305
encrypted secret storage, QR code generation, and bcrypt-hashed backup codes
- API key switcher: OAuth vs personal API key toggle in AIUI chat settings with
status indicator, key validation, and help text
- Login progress bar: server startup detection with health check polling, form
disabled until server is ready
- AI quarantine docs: comprehensive HTML page documenting all 6 security layers
- Settings: AI Data Access permission toggles with per-category control
- Alpha hardening plan: 28-task overnight automation plan across 7 phases
(onboarding, login, app install, AIUI, UI polish, security, ISO build)
- Backlog: node discovery spatial map feature for alpha demo
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 12:23:57 +00:00
loadTotpStatus ( )
2026-03-11 12:46:02 +00:00
loadBackups ( )
2026-03-13 00:13:38 +00:00
loadTorServices ( )
2026-03-11 12:55:13 +00:00
loadWebhookConfig ( )
2026-02-17 15:03:34 +00:00
if ( ! serverTorAddressFromStore . value ) {
try {
const res = await rpcClient . getTorAddress ( )
torAddressFromRpc . value = res . tor _address ? ? null
2026-03-11 00:58:55 +00:00
} catch ( e ) {
if ( import . meta . env . DEV ) console . warn ( 'Tor address may not be available yet' , e )
2026-02-17 15:03:34 +00:00
}
}
} )
2026-01-24 22:59:20 +00:00
async function handleLogout ( ) {
2026-03-11 00:58:55 +00:00
try { await store . logout ( ) } catch ( e ) { if ( import . meta . env . DEV ) console . warn ( 'Logout failed, proceeding anyway' , e ) }
2026-03-01 18:07:35 +00:00
router . push ( '/login' ) . catch ( ( ) => { window . location . href = '/login' } )
2026-01-24 22:59:20 +00:00
}
2026-03-11 12:46:02 +00:00
// Backup & Restore
interface BackupEntry {
id : string
created _at : string
size _bytes : number
encrypted : boolean
description : string | null
}
const backupList = ref < BackupEntry [ ] > ( [ ] )
const loadingBackups = ref ( false )
const showCreateBackupModal = ref ( false )
const backupPassphrase = ref ( '' )
const backupDescription = ref ( '' )
const creatingBackup = ref ( false )
const showRestoreModal = ref ( false )
const restoreBackupId = ref ( '' )
const restorePassphrase = ref ( '' )
const restoringBackup = ref ( false )
const verifyingBackupId = ref < string | null > ( null )
const deletingBackupId = ref < string | null > ( null )
2026-03-13 00:13:38 +00:00
// Tor services state
interface TorServiceInfo {
name : string
local _port : number
onion _address : string | null
enabled : boolean
}
const torServices = ref < TorServiceInfo [ ] > ( [ ] )
const torLoading = ref ( false )
const torRotating = ref ( false )
2026-03-11 12:46:02 +00:00
const backupStatusMsg = ref ( '' )
const backupStatusType = ref < 'success' | 'error' > ( 'success' )
function formatBackupSize ( bytes : number ) : string {
if ( bytes < 1024 ) return ` ${ bytes } B `
if ( bytes < 1024 * 1024 ) return ` ${ ( bytes / 1024 ) . toFixed ( 1 ) } KB `
if ( bytes < 1024 * 1024 * 1024 ) return ` ${ ( bytes / ( 1024 * 1024 ) ) . toFixed ( 1 ) } MB `
return ` ${ ( bytes / ( 1024 * 1024 * 1024 ) ) . toFixed ( 2 ) } GB `
}
function showBackupStatus ( msg : string , type : 'success' | 'error' ) {
backupStatusMsg . value = msg
backupStatusType . value = type
setTimeout ( ( ) => { backupStatusMsg . value = '' } , 5000 )
}
2026-03-13 00:13:38 +00:00
async function loadTorServices ( ) {
torLoading . value = true
try {
const res = await rpcClient . torListServices ( )
torServices . value = res . services || [ ]
} catch {
torServices . value = [ ]
} finally {
torLoading . value = false
}
}
async function toggleTorApp ( appId : string , enabled : boolean ) {
try {
const res = await rpcClient . torToggleApp ( appId , enabled )
if ( res . changed ) {
await loadTorServices ( )
}
} catch ( e ) {
if ( import . meta . env . DEV ) console . warn ( 'Failed to toggle Tor app:' , e )
}
}
async function rotateNodeAddress ( ) {
if ( torRotating . value ) return
if ( ! confirm ( 'This will generate a new .onion address. The old address will work for 24 hours during transition. Federated peers will be notified automatically.' ) ) return
torRotating . value = true
try {
await rpcClient . torRotateService ( 'archipelago' )
await loadTorServices ( )
} catch ( e ) {
if ( import . meta . env . DEV ) console . warn ( 'Failed to rotate Tor address:' , e )
} finally {
torRotating . value = false
}
}
2026-03-11 12:46:02 +00:00
async function loadBackups ( ) {
loadingBackups . value = true
try {
const res = await rpcClient . call < { backups : BackupEntry [ ] } > ( { method : 'backup.list' } )
backupList . value = res . backups || [ ]
} catch {
backupList . value = [ ]
} finally {
loadingBackups . value = false
}
}
async function createBackup ( ) {
if ( creatingBackup . value || ! backupPassphrase . value ) return
creatingBackup . value = true
try {
await rpcClient . call ( { method : 'backup.create' , params : { passphrase : backupPassphrase . value , description : backupDescription . value || undefined } } )
showCreateBackupModal . value = false
backupPassphrase . value = ''
backupDescription . value = ''
2026-03-11 13:45:59 +00:00
showBackupStatus ( t ( 'settings.backupCreatedSuccess' ) , 'success' )
2026-03-11 12:46:02 +00:00
await loadBackups ( )
} catch {
2026-03-11 13:45:59 +00:00
showBackupStatus ( t ( 'settings.backupCreateFailed' ) , 'error' )
2026-03-11 12:46:02 +00:00
} finally {
creatingBackup . value = false
}
}
async function verifyBackup ( id : string ) {
2026-03-11 13:45:59 +00:00
const passphrase = prompt ( t ( 'settings.verifyPassphrasePrompt' ) )
2026-03-11 12:46:02 +00:00
if ( ! passphrase ) return
verifyingBackupId . value = id
try {
const res = await rpcClient . call < { valid : boolean ; error : string | null } > ( { method : 'backup.verify' , params : { id , passphrase } } )
if ( res . valid ) {
2026-03-11 13:45:59 +00:00
showBackupStatus ( t ( 'settings.backupVerifiedOk' ) , 'success' )
2026-03-11 12:46:02 +00:00
} else {
2026-03-11 13:45:59 +00:00
showBackupStatus ( t ( 'settings.backupVerifyFailed' , { error : res . error || 'Unknown error' } ) , 'error' )
2026-03-11 12:46:02 +00:00
}
} catch {
2026-03-11 13:45:59 +00:00
showBackupStatus ( t ( 'settings.backupVerifyRequestFailed' ) , 'error' )
2026-03-11 12:46:02 +00:00
} finally {
verifyingBackupId . value = null
}
}
function confirmRestoreBackup ( id : string ) {
restoreBackupId . value = id
restorePassphrase . value = ''
showRestoreModal . value = true
}
async function restoreBackup ( ) {
if ( restoringBackup . value || ! restorePassphrase . value ) return
restoringBackup . value = true
try {
await rpcClient . call ( { method : 'backup.restore' , params : { id : restoreBackupId . value , passphrase : restorePassphrase . value } } )
showRestoreModal . value = false
2026-03-11 13:45:59 +00:00
showBackupStatus ( t ( 'settings.backupRestored' ) , 'success' )
2026-03-11 12:46:02 +00:00
} catch {
2026-03-11 13:45:59 +00:00
showBackupStatus ( t ( 'settings.backupRestoreFailed' ) , 'error' )
2026-03-11 12:46:02 +00:00
} finally {
restoringBackup . value = false
}
}
async function deleteBackup ( id : string ) {
2026-03-11 13:45:59 +00:00
if ( ! confirm ( t ( 'settings.deleteBackupConfirm' ) ) ) return
2026-03-11 12:46:02 +00:00
deletingBackupId . value = id
try {
await rpcClient . call ( { method : 'backup.delete' , params : { id } } )
2026-03-11 13:45:59 +00:00
showBackupStatus ( t ( 'settings.backupDeleted' ) , 'success' )
2026-03-11 12:46:02 +00:00
await loadBackups ( )
} catch {
2026-03-11 13:45:59 +00:00
showBackupStatus ( t ( 'settings.backupDeleteFailed' ) , 'error' )
2026-03-11 12:46:02 +00:00
} finally {
deletingBackupId . value = null
}
}
2026-03-11 12:55:13 +00:00
// Webhook Notifications
interface WebhookConfigData {
enabled : boolean
url : string
secret : string
events : string [ ]
}
const webhookConfig = ref < WebhookConfigData > ( {
enabled : false ,
url : '' ,
secret : '' ,
events : [ ] ,
} )
const savingWebhook = ref ( false )
const testingWebhook = ref ( false )
const webhookStatusMsg = ref ( '' )
const webhookStatusType = ref < 'success' | 'error' > ( 'success' )
2026-03-11 13:45:59 +00:00
const webhookEventTypes = computed ( ( ) => [
{ id : 'container_crash' , label : t ( 'settings.containerCrash' ) , description : t ( 'settings.containerCrashDesc' ) } ,
{ id : 'update_available' , label : t ( 'settings.updateAvailableEvent' ) , description : t ( 'settings.updateAvailableDesc' ) } ,
{ id : 'disk_warning' , label : t ( 'settings.diskSpaceWarning' ) , description : t ( 'settings.diskWarningDesc' ) } ,
{ id : 'backup_complete' , label : t ( 'settings.backupComplete' ) , description : t ( 'settings.backupCompleteDesc' ) } ,
] )
2026-03-11 12:55:13 +00:00
function showWebhookStatus ( msg : string , type : 'success' | 'error' ) {
webhookStatusMsg . value = msg
webhookStatusType . value = type
setTimeout ( ( ) => { webhookStatusMsg . value = '' } , 5000 )
}
function toggleWebhookEvent ( id : string ) {
const idx = webhookConfig . value . events . indexOf ( id )
if ( idx >= 0 ) {
webhookConfig . value . events . splice ( idx , 1 )
} else {
webhookConfig . value . events . push ( id )
}
}
function toggleWebhookEnabled ( ) {
webhookConfig . value . enabled = ! webhookConfig . value . enabled
}
async function loadWebhookConfig ( ) {
try {
const res = await rpcClient . call < { enabled : boolean ; url : string ; events : string [ ] ; has _secret : boolean } > ( { method : 'webhook.get-config' } )
webhookConfig . value . enabled = res . enabled
webhookConfig . value . url = res . url
webhookConfig . value . events = res . events || [ ]
// Don't overwrite secret — server doesn't return it
} catch {
// Webhook system may not be available
}
}
async function saveWebhookConfig ( ) {
savingWebhook . value = true
try {
await rpcClient . call ( {
method : 'webhook.configure' ,
params : {
enabled : webhookConfig . value . enabled ,
url : webhookConfig . value . url ,
secret : webhookConfig . value . secret || null ,
events : webhookConfig . value . events ,
} ,
} )
2026-03-11 13:45:59 +00:00
showWebhookStatus ( t ( 'settings.webhookSaved' ) , 'success' )
2026-03-11 12:55:13 +00:00
} catch {
2026-03-11 13:45:59 +00:00
showWebhookStatus ( t ( 'settings.webhookSaveFailed' ) , 'error' )
2026-03-11 12:55:13 +00:00
} finally {
savingWebhook . value = false
}
}
async function testWebhook ( ) {
testingWebhook . value = true
try {
const res = await rpcClient . call < { sent : boolean ; url : string } > ( { method : 'webhook.test' } )
if ( res . sent ) {
2026-03-11 13:45:59 +00:00
showWebhookStatus ( t ( 'settings.webhookTestSent' ) , 'success' )
2026-03-11 12:55:13 +00:00
} else {
2026-03-11 13:45:59 +00:00
showWebhookStatus ( t ( 'settings.webhookTestFailed' ) , 'error' )
2026-03-11 12:55:13 +00:00
}
} catch {
2026-03-11 13:45:59 +00:00
showWebhookStatus ( t ( 'settings.webhookSendFailed' ) , 'error' )
2026-03-11 12:55:13 +00:00
} finally {
testingWebhook . value = false
}
}
2026-03-11 12:46:02 +00:00
// USB Drive Backup
interface UsbDriveInfo {
device : string
mount _point : string | null
label : string | null
size _bytes : number
removable : boolean
}
const usbCopyingId = ref < string | null > ( null )
async function backupToUsb ( backupId : string ) {
usbCopyingId . value = backupId
try {
const drivesRes = await rpcClient . call < { drives : UsbDriveInfo [ ] } > ( { method : 'backup.list-drives' } )
const drives = drivesRes . drives || [ ]
const mounted = drives . filter ( d => d . mount _point )
const target = mounted [ 0 ]
if ( ! target ? . mount _point ) {
2026-03-11 13:45:59 +00:00
showBackupStatus ( t ( 'settings.noUsbDrives' ) , 'error' )
2026-03-11 12:46:02 +00:00
return
}
const label = target . label || target . device
if ( ! confirm ( ` Copy backup to USB drive " ${ label } " at ${ target . mount _point } ? ` ) ) return
await rpcClient . call ( { method : 'backup.to-usb' , params : { id : backupId , mount _point : target . mount _point } } )
2026-03-11 13:45:59 +00:00
showBackupStatus ( t ( 'settings.backupCopiedToUsb' , { path : target . mount _point } ) , 'success' )
2026-03-11 12:46:02 +00:00
} catch {
2026-03-11 13:45:59 +00:00
showBackupStatus ( t ( 'settings.backupUsbFailed' ) , 'error' )
2026-03-11 12:46:02 +00:00
} finally {
usbCopyingId . value = null
}
}
2026-01-24 22:59:20 +00:00
< / script >