2026-01-24 22:59:20 +00:00
|
|
|
<template>
|
2026-03-14 17:12:41 +00:00
|
|
|
<div class="pb-6">
|
|
|
|
|
<!-- Controller indicator - Mobile only (desktop shows in sidebar) -->
|
|
|
|
|
<div class="md:hidden mb-4">
|
|
|
|
|
<ControllerIndicator />
|
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">
|
2026-03-14 04:14:04 +00:00
|
|
|
<!-- Server Name Card (editable) -->
|
2026-01-24 22:59:20 +00:00
|
|
|
<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>
|
2026-03-14 04:14:04 +00:00
|
|
|
<div v-if="editingServerName" class="flex items-center gap-2">
|
|
|
|
|
<input
|
|
|
|
|
ref="serverNameInput"
|
|
|
|
|
v-model="serverNameDraft"
|
|
|
|
|
type="text"
|
|
|
|
|
maxlength="64"
|
|
|
|
|
class="flex-1 px-3 py-1.5 bg-white/10 border border-white/20 rounded-lg text-white text-lg font-semibold focus:outline-none focus:border-white/40 transition-colors"
|
|
|
|
|
@keydown.enter="saveServerName"
|
|
|
|
|
@keydown.escape="editingServerName = false"
|
|
|
|
|
/>
|
|
|
|
|
<button
|
|
|
|
|
class="px-3 py-1.5 bg-white/10 border border-white/20 rounded-lg text-white/70 hover:text-white hover:bg-white/15 transition-colors text-sm"
|
|
|
|
|
@click="saveServerName"
|
|
|
|
|
>Save</button>
|
|
|
|
|
<button
|
|
|
|
|
class="px-3 py-1.5 text-white/50 hover:text-white/70 transition-colors text-sm"
|
|
|
|
|
@click="editingServerName = false"
|
|
|
|
|
>Cancel</button>
|
|
|
|
|
</div>
|
|
|
|
|
<div v-else class="flex items-center gap-2 group cursor-pointer" @click="startEditServerName">
|
|
|
|
|
<p class="text-lg font-semibold text-white/95">{{ serverName }}</p>
|
|
|
|
|
<svg class="w-4 h-4 text-white/30 group-hover:text-white/60 transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z" />
|
|
|
|
|
</svg>
|
|
|
|
|
</div>
|
2026-01-24 22:59:20 +00:00
|
|
|
</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>
|
2026-03-18 11:15:32 +00:00
|
|
|
<div class="flex items-center justify-between">
|
|
|
|
|
<p class="text-lg font-semibold text-white/95">{{ version }}</p>
|
|
|
|
|
<button
|
|
|
|
|
@click="showReleaseNotes = true"
|
|
|
|
|
class="glass-button px-3 py-1.5 text-xs"
|
|
|
|
|
>What's New</button>
|
|
|
|
|
</div>
|
2026-01-24 22:59:20 +00:00
|
|
|
</div>
|
|
|
|
|
|
2026-03-18 11:15:32 +00:00
|
|
|
<!-- Release Notes Modal -->
|
|
|
|
|
<Teleport to="body">
|
|
|
|
|
<Transition name="modal">
|
|
|
|
|
<div v-if="showReleaseNotes" class="fixed inset-0 z-[3000] flex items-center justify-center p-4" @click="showReleaseNotes = false">
|
|
|
|
|
<div class="absolute inset-0 bg-black/60 backdrop-blur-sm"></div>
|
|
|
|
|
<div @click.stop class="glass-card p-6 max-w-lg w-full relative z-10 max-h-[80vh] overflow-y-auto">
|
|
|
|
|
<div class="flex items-start justify-between gap-4 mb-5">
|
|
|
|
|
<h3 class="text-xl font-semibold text-white">What's New in {{ version }}</h3>
|
|
|
|
|
<button @click="showReleaseNotes = false" class="p-2 rounded-lg hover:bg-white/10 text-white/70 hover:text-white transition-colors" aria-label="Close">
|
|
|
|
|
<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>
|
|
|
|
|
<div class="space-y-4 text-sm text-white/80">
|
|
|
|
|
<div>
|
|
|
|
|
<h4 class="text-white font-medium mb-1">Mesh Radio Connection</h4>
|
|
|
|
|
<p>Your LoRa mesh radio now connects reliably every time. If the USB port changes, the system automatically finds the device. A new "Connect" button lets you pick which radio to use.</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<h4 class="text-white font-medium mb-1">Rock-Solid Containers</h4>
|
|
|
|
|
<p>Fixed crash loops that were causing apps to restart thousands of times. All containers now start cleanly and stay stable across reboots.</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<h4 class="text-white font-medium mb-1">Smarter App Store</h4>
|
|
|
|
|
<p>Apps that are still starting up now show their progress instead of appearing as available to install again. No more accidentally re-installing running apps.</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<h4 class="text-white font-medium mb-1">Security Hardening</h4>
|
|
|
|
|
<p>Backend services now run with strict sandboxing: read-only filesystem, restricted system calls, and least-privilege access. Your node is locked down by default.</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<h4 class="text-white font-medium mb-1">Tor by Default</h4>
|
|
|
|
|
<p>Bitcoin and Lightning now route through Tor automatically for maximum privacy. Your node's network traffic is protected out of the box.</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<h4 class="text-white font-medium mb-1">Off-Grid Bitcoin</h4>
|
|
|
|
|
<p>Receive Bitcoin block headers over mesh radio. A dead man's switch can broadcast your location to trusted contacts if you go silent. GPS sharing is opt-in only.</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<button @click="showReleaseNotes = false" class="glass-button w-full mt-6 py-2 text-sm">Close</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</Transition>
|
|
|
|
|
</Teleport>
|
|
|
|
|
|
2026-01-24 22:59:20 +00:00
|
|
|
<!-- 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' : ''">
|
2026-03-14 17:12:41 +00:00
|
|
|
<div class="flex items-center gap-3 mb-2">
|
|
|
|
|
<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>
|
|
|
|
|
<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>
|
2026-03-14 17:12:41 +00:00
|
|
|
<p class="text-sm font-mono text-amber-400/90 break-all mb-1" :title="serverTorAddress">{{ serverTorAddress }}</p>
|
|
|
|
|
<p class="text-xs text-white/50 mb-3">{{ t('settings.onionHelper') }}</p>
|
|
|
|
|
<button
|
|
|
|
|
@click="copyOnionAddress"
|
|
|
|
|
class="w-full min-h-[44px] rounded-lg glass-button text-sm font-medium text-white/90 hover:text-white transition-colors flex items-center justify-center gap-2"
|
|
|
|
|
>
|
|
|
|
|
<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>
|
|
|
|
|
<span v-if="!copiedOnion">{{ t('common.copy') }}</span>
|
|
|
|
|
<span v-else class="text-green-400">{{ t('common.copied') }}</span>
|
|
|
|
|
</button>
|
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"
|
2026-03-14 17:12:41 +00:00
|
|
|
class="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/10 backdrop-blur-md"
|
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"
|
2026-03-14 17:12:41 +00:00
|
|
|
class="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/10 backdrop-blur-md"
|
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
|
|
|
@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>
|
2026-03-18 00:55:00 +00:00
|
|
|
<div class="flex justify-center mb-4 bg-white rounded-xl p-4 mx-auto w-fit" v-html="sanitizedQrSvg" />
|
|
|
|
|
<div v-if="totpSecretBase32" class="bg-black/30 rounded-lg px-3 py-2 mb-4">
|
|
|
|
|
<p class="text-xs text-white/50 mb-1">Manual entry key (keep secret!):</p>
|
|
|
|
|
<div v-if="showTotpSecret" class="flex items-center gap-2">
|
|
|
|
|
<p class="text-sm font-mono text-orange-400 break-all">{{ totpSecretBase32 }}</p>
|
|
|
|
|
<button type="button" class="glass-button text-xs px-2 py-1" @click="showTotpSecret = false">Hide</button>
|
|
|
|
|
</div>
|
|
|
|
|
<button v-else type="button" class="glass-button text-xs px-3 py-1" @click="showTotpSecret = true">
|
|
|
|
|
Show manual entry key
|
|
|
|
|
</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 @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"
|
2026-03-14 17:12:41 +00:00
|
|
|
class="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/10 backdrop-blur-md"
|
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
|
|
|
@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>
|
|
|
|
|
|
2026-03-14 05:41:33 +00:00
|
|
|
<!-- Language Section -->
|
|
|
|
|
<div class="glass-card px-6 py-6 mb-6">
|
|
|
|
|
<h2 class="text-xl font-semibold text-white/96 mb-2">Language</h2>
|
|
|
|
|
<p class="text-sm text-white/60 mb-4">Choose your preferred language</p>
|
|
|
|
|
<div class="flex gap-3 flex-wrap">
|
|
|
|
|
<button
|
|
|
|
|
v-for="loc in supportedLocales"
|
|
|
|
|
:key="loc.code"
|
|
|
|
|
@click="changeLocale(loc.code)"
|
|
|
|
|
class="glass-button px-4 py-2 rounded-lg text-sm font-medium transition-all"
|
|
|
|
|
:class="currentLocale === loc.code ? 'ring-2 ring-orange-400/60 bg-white/10' : ''"
|
|
|
|
|
>
|
|
|
|
|
<span class="mr-2">{{ loc.flag }}</span>{{ loc.name }}
|
|
|
|
|
</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"
|
2026-03-14 17:12:41 +00:00
|
|
|
class="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/10 backdrop-blur-md"
|
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
|
|
|
@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
|
|
|
|
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">
|
2026-03-14 17:12:41 +00:00
|
|
|
<div class="mb-4">
|
|
|
|
|
<h2 class="text-xl font-semibold text-white/96 mb-1">{{ t('settings.backup') }}</h2>
|
|
|
|
|
<p class="text-sm text-white/60 mb-3">{{ t('settings.backupRestoreDesc') }}</p>
|
|
|
|
|
<button @click="showCreateBackupModal = true" class="w-full min-h-[44px] glass-button rounded-lg text-sm font-medium flex items-center justify-center gap-2">
|
2026-03-11 12:46:02 +00:00
|
|
|
<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() }} · {{ 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
|
|
|
×
|
|
|
|
|
</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">
|
2026-03-14 17:12:41 +00:00
|
|
|
<div v-if="showCreateBackupModal" class="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/10 backdrop-blur-md" @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">
|
2026-03-14 17:12:41 +00:00
|
|
|
<div v-if="showRestoreModal" class="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/10 backdrop-blur-md" @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-03-15 05:18:12 +00:00
|
|
|
|
|
|
|
|
<!-- Factory Reset Section -->
|
|
|
|
|
<div class="path-option-card px-6 py-6 mt-6 border-red-500/30">
|
|
|
|
|
<h2 class="text-xl font-semibold text-red-400/90 mb-3">Factory Reset</h2>
|
|
|
|
|
<p class="text-sm text-white/60 mb-4">
|
|
|
|
|
Wipe all user data, identities, and credentials. Container images are preserved. The node will restart and show the onboarding screen.
|
|
|
|
|
</p>
|
|
|
|
|
<button
|
|
|
|
|
class="glass-button text-red-400 border-red-500/30 hover:border-red-500/50"
|
|
|
|
|
@click="showFactoryResetConfirm = true"
|
|
|
|
|
>
|
|
|
|
|
Factory Reset
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Factory Reset Confirmation Modal -->
|
|
|
|
|
<Teleport to="body">
|
|
|
|
|
<div v-if="showFactoryResetConfirm" class="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm">
|
|
|
|
|
<div class="glass-card px-8 py-8 max-w-md mx-4">
|
|
|
|
|
<h3 class="text-lg font-semibold text-white/90 mb-3">Are you sure?</h3>
|
|
|
|
|
<p class="text-sm text-white/60 mb-6">
|
|
|
|
|
This will delete all identities, credentials, and settings. This cannot be undone.
|
|
|
|
|
</p>
|
|
|
|
|
<div class="flex gap-3 justify-end">
|
|
|
|
|
<button class="glass-button" @click="showFactoryResetConfirm = false">Cancel</button>
|
|
|
|
|
<button
|
|
|
|
|
class="glass-button text-red-400 border-red-500/30"
|
|
|
|
|
:disabled="factoryResetLoading"
|
|
|
|
|
@click="performFactoryReset"
|
|
|
|
|
>
|
|
|
|
|
{{ factoryResetLoading ? 'Resetting...' : 'Yes, Reset' }}
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</Teleport>
|
2026-01-24 22:59:20 +00:00
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
2026-03-14 04:14:04 +00:00
|
|
|
import { computed, ref, onMounted, nextTick } from 'vue'
|
2026-03-18 00:55:00 +00:00
|
|
|
import DOMPurify from 'dompurify'
|
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-03-14 05:41:33 +00:00
|
|
|
import { SUPPORTED_LOCALES, setLocale, type SupportedLocale } from '@/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-14 05:41:33 +00:00
|
|
|
const { t, locale } = useI18n()
|
2026-01-24 22:59:20 +00:00
|
|
|
const store = useAppStore()
|
2026-03-15 05:18:12 +00:00
|
|
|
|
|
|
|
|
// Factory Reset
|
|
|
|
|
const showFactoryResetConfirm = ref(false)
|
|
|
|
|
const factoryResetLoading = ref(false)
|
|
|
|
|
async function performFactoryReset() {
|
|
|
|
|
factoryResetLoading.value = true
|
|
|
|
|
try {
|
|
|
|
|
await rpcClient.call({ method: 'system.factory-reset', params: { confirm: true } })
|
|
|
|
|
localStorage.clear()
|
|
|
|
|
showFactoryResetConfirm.value = false
|
|
|
|
|
router.push('/onboarding/intro')
|
|
|
|
|
} catch (err) {
|
|
|
|
|
// Service likely restarted — redirect anyway
|
|
|
|
|
localStorage.clear()
|
|
|
|
|
showFactoryResetConfirm.value = false
|
|
|
|
|
router.push('/onboarding/intro')
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-03-14 05:41:33 +00:00
|
|
|
const supportedLocales = SUPPORTED_LOCALES
|
|
|
|
|
const currentLocale = computed(() => locale.value)
|
|
|
|
|
async function changeLocale(code: string) {
|
|
|
|
|
await setLocale(code as SupportedLocale)
|
|
|
|
|
}
|
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)
|
2026-03-14 04:14:04 +00:00
|
|
|
const editingServerName = ref(false)
|
|
|
|
|
const serverNameDraft = ref('')
|
|
|
|
|
const serverNameInput = ref<HTMLInputElement | null>(null)
|
|
|
|
|
|
|
|
|
|
function startEditServerName() {
|
|
|
|
|
serverNameDraft.value = serverName.value
|
|
|
|
|
editingServerName.value = true
|
|
|
|
|
nextTick(() => serverNameInput.value?.select())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function saveServerName() {
|
|
|
|
|
const name = serverNameDraft.value.trim()
|
|
|
|
|
if (!name || name === serverName.value) {
|
|
|
|
|
editingServerName.value = false
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
await rpcClient.call({ method: 'server.set-name', params: { name } })
|
|
|
|
|
} catch (e) {
|
feat: rootless podman, session hardening, boot stability, sidebar fix
Rootless podman migration (TASK-11):
- Remove sudo from all podman calls in PodmanClient + 8 backend files
- Remove sudo from all podman/docker calls in deploy script
- Restore full systemd security hardening: NoNewPrivileges,
RestrictAddressFamilies, MemoryDenyWriteExecute, RestrictRealtime,
RestrictNamespaces, RestrictSUIDSGID, SystemCallFilter, ProtectSystem=strict
- Enable loginctl linger for rootless container persistence
- Remove Ollama from auto-deploy (marketplace-only)
Session & auth hardening:
- Increase MAX_CONCURRENT_SESSIONS 20→50 (prevents eviction storms)
- Debounced 401 redirect in rpc-client.ts (prevents redirect storms)
Boot stability:
- optimize-debian.sh: adds chrony, swap, removes policy-rc.d
- deploy script: pre-restart chrony + swap setup
- ISO build: chrony package, swap file creation
- BootScreen: no longer clears localStorage (prevents splash replay)
- RootRedirect: sole owner of localStorage clearing on server ready
UI fixes:
- Sidebar opacity default changed from 0→visible (fixes missing sidebar
after page-persistence login without entrance animation)
- Console.log/error wrapped in import.meta.env.DEV guards
- Remove unused route import from RootRedirect
Beta tracking:
- CLAUDE.md: beta freeze protocol added
- MASTER_PLAN.md: TASK-11, TASK-17, phase structure
- BETA-PROGRESS.md: initial tracking doc
- Tagged v1.2.0-alpha.1 as pre-rootless baseline
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 13:53:27 +00:00
|
|
|
if (import.meta.env.DEV) console.error('Failed to rename server:', e)
|
2026-03-14 04:14:04 +00:00
|
|
|
}
|
|
|
|
|
editingServerName.value = false
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-24 22:59:20 +00:00
|
|
|
const version = computed(() => store.serverInfo?.version || '0.0.0')
|
2026-03-18 11:15:32 +00:00
|
|
|
const showReleaseNotes = ref(false)
|
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('')
|
2026-03-18 00:55:00 +00:00
|
|
|
const sanitizedQrSvg = computed(() => DOMPurify.sanitize(totpQrSvg.value, { USE_PROFILES: { svg: true } }))
|
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
|
|
|
const totpSecretBase32 = ref('')
|
2026-03-18 00:55:00 +00:00
|
|
|
const showTotpSecret = ref(false)
|
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
|
|
|
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-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
|
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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>
|