2026-01-24 22:59:20 +00:00
|
|
|
<template>
|
2026-02-17 19:19:54 +00:00
|
|
|
<div class="min-h-screen flex relative dashboard-view" :class="{ 'glass-throw-active': showZoomIn }">
|
2026-03-11 13:11:45 +00:00
|
|
|
<!-- Skip to main content link for keyboard users -->
|
feat: gamepad navigation rewrite, focus styling, container grid system
- Rewrite useControllerNav.ts with clean console-style navigation:
Sidebar (up/down wrap, right→containers, left→nothing),
Container tile grid (spatial nav, no wrap at edges),
Nav bar support (up from containers, down to grid),
Inner controls (enter drills in, escape exits, trapped arrows)
- Add data-controller-container to Mesh, Fleet, Settings pages
- Fix Home.vue fragment (modals outside root div) causing Vue warnings
- Remove skip-to-content link (handled by controller nav)
- Orange ambient glow focus styling matching glass aesthetic
- Disable PWA service worker in dev mode (fixes HMR caching)
- Add gamepad-nav skill and GAMEPAD-NAV-MAP.md spec document
- 39 tests covering all navigation patterns
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 17:01:17 +00:00
|
|
|
<!-- Skip-to-content handled by controller nav sidebar→main transition -->
|
2026-02-17 19:19:54 +00:00
|
|
|
<!-- Background container with 3D perspective - full width to avoid letterboxing -->
|
2026-01-24 22:59:20 +00:00
|
|
|
<div class="bg-perspective-container">
|
2026-03-01 18:07:35 +00:00
|
|
|
<!-- Background - primary layer (visible for all routes, transitions out only for detail pages) -->
|
2026-02-17 19:19:54 +00:00
|
|
|
<div
|
2026-01-24 22:59:20 +00:00
|
|
|
ref="bgDefault"
|
2026-02-17 19:19:54 +00:00
|
|
|
class="bg-layer bg-fullwidth"
|
|
|
|
|
:class="[
|
2026-03-01 18:07:35 +00:00
|
|
|
{ 'bg-transitioning-out': showAltBackground },
|
2026-02-17 19:19:54 +00:00
|
|
|
{ 'zoom-reveal-bg': showZoomIn }
|
|
|
|
|
]"
|
2026-03-01 18:07:35 +00:00
|
|
|
:style="{ backgroundImage: `url(/assets/img/${backgroundImage})` }"
|
2026-02-17 19:19:54 +00:00
|
|
|
/>
|
2026-03-01 18:07:35 +00:00
|
|
|
<!-- Background - detail layer (only visible during app/marketplace detail 3D transition) -->
|
2026-01-24 22:59:20 +00:00
|
|
|
<div
|
|
|
|
|
ref="bgAlt"
|
2026-02-17 19:19:54 +00:00
|
|
|
class="bg-layer bg-fullwidth"
|
2026-03-01 18:07:35 +00:00
|
|
|
:class="{ 'bg-transitioning-in': showAltBackground }"
|
|
|
|
|
style="background-image: url(/assets/img/bg-intro-3.jpg)"
|
2026-01-24 22:59:20 +00:00
|
|
|
/>
|
|
|
|
|
<!-- Glitch overlays - trigger on background change -->
|
2026-03-22 03:30:21 +00:00
|
|
|
<div
|
2026-01-24 22:59:20 +00:00
|
|
|
class="bg-glitch-layer-1"
|
|
|
|
|
:class="{ 'glitch-active': isGlitching }"
|
2026-03-01 18:07:35 +00:00
|
|
|
:style="{ backgroundImage: `url(/assets/img/${backgroundImage})` }"
|
2026-01-24 22:59:20 +00:00
|
|
|
/>
|
2026-03-22 03:30:21 +00:00
|
|
|
<div
|
2026-01-24 22:59:20 +00:00
|
|
|
class="bg-glitch-layer-2"
|
|
|
|
|
:class="{ 'glitch-active': isGlitching }"
|
2026-03-01 18:07:35 +00:00
|
|
|
:style="{ backgroundImage: `url(/assets/img/${backgroundImage})` }"
|
2026-01-24 22:59:20 +00:00
|
|
|
/>
|
2026-03-22 03:30:21 +00:00
|
|
|
<div
|
2026-01-24 22:59:20 +00:00
|
|
|
class="bg-glitch-scan"
|
|
|
|
|
:class="{ 'glitch-active': isGlitching }"
|
|
|
|
|
/>
|
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
|
|
|
<!-- Glitch overlays removed — only intro glitch plays (via isGlitching) -->
|
2026-01-24 22:59:20 +00:00
|
|
|
</div>
|
|
|
|
|
|
2026-02-17 19:19:54 +00:00
|
|
|
<!-- Oomph accent - brief impact flash when dashboard loads -->
|
|
|
|
|
<div
|
|
|
|
|
v-if="showZoomIn"
|
|
|
|
|
class="fixed inset-0 pointer-events-none z-[100] oomph-flash"
|
|
|
|
|
aria-hidden="true"
|
|
|
|
|
/>
|
|
|
|
|
<!-- Reveal flashes and glitch overlay - enthralling entrance -->
|
|
|
|
|
<div
|
|
|
|
|
v-if="showZoomIn"
|
|
|
|
|
class="fixed inset-0 pointer-events-none z-[99] reveal-flash-glitch"
|
|
|
|
|
aria-hidden="true"
|
|
|
|
|
/>
|
|
|
|
|
|
2026-03-14 17:12:41 +00:00
|
|
|
<!-- Background overlay - uniform 0.2 opacity -->
|
|
|
|
|
<div
|
|
|
|
|
class="fixed inset-0 pointer-events-none bg-black/20"
|
2026-01-24 22:59:20 +00:00
|
|
|
style="z-index: -5;"
|
|
|
|
|
/>
|
|
|
|
|
|
2026-03-22 03:30:21 +00:00
|
|
|
<!-- Sidebar - Desktop Only -->
|
|
|
|
|
<DashboardSidebar :show-zoom-in="showZoomIn" @logout="handleLogout" />
|
2026-01-24 22:59:20 +00:00
|
|
|
|
2026-02-17 19:19:54 +00:00
|
|
|
<!-- Main Content (Xbox: Right goes here from sidebar) -->
|
|
|
|
|
<main
|
2026-03-11 13:11:45 +00:00
|
|
|
id="main-content"
|
2026-02-17 19:19:54 +00:00
|
|
|
data-controller-zone="main"
|
2026-03-14 17:12:41 +00:00
|
|
|
class="flex-1 overflow-hidden relative pb-0 glass-piece z-10"
|
2026-02-17 19:19:54 +00:00
|
|
|
:class="{ 'glass-throw-main': showZoomIn }"
|
|
|
|
|
>
|
2026-02-18 11:29:05 +00:00
|
|
|
<div data-controller-main-entry class="absolute top-4 right-4 md:top-6 md:right-8 z-20">
|
2026-03-04 05:23:42 +00:00
|
|
|
<!-- Controller zone entry point - no switcher -->
|
2026-02-18 10:26:33 +00:00
|
|
|
</div>
|
|
|
|
|
|
2026-03-22 03:30:21 +00:00
|
|
|
<!-- Connection Status Banners -->
|
|
|
|
|
<ConnectionBanner />
|
2026-01-24 22:59:20 +00:00
|
|
|
|
2026-03-05 10:14:10 +00:00
|
|
|
<div class="perspective-container-wrapper glass-piece" :class="{ 'glass-throw-content': showZoomIn && !isHomeRoute }">
|
2026-01-24 22:59:20 +00:00
|
|
|
<div class="perspective-container">
|
|
|
|
|
<RouterView v-slot="{ Component, route }">
|
|
|
|
|
<Transition :name="getTransitionName(route)">
|
|
|
|
|
<div :key="route.path" class="view-wrapper">
|
|
|
|
|
<div
|
feat: v1.2.0-alpha — E2E encrypted mesh relay, steganography, relay status polling
Phase 5 mesh networking:
- E2E encrypted TX relay (X25519 + ChaCha20-Poly1305) — non-Archy nodes
relay encrypted blobs transparently via Meshcore native routing
- Steganographic encoding modes (WeatherStation, SensorNetwork) — traffic
looks like sensor data on the wire, 0xAA marker, configurable per-node
- Pre-flight Bitcoin Core health check on relay node — specific error codes
(bitcoin_unreachable, bitcoin_syncing, tx_rejected) instead of generic fails
- mesh.relay-status RPC endpoint — frontend polls for relay result every 3s
- On-Chain / Lightning tabs in Off-Grid Bitcoin panel
- Archy Peers vs Mesh Broadcast relay mode selector
- Mesh view fills viewport (no page scroll), internal panel scrolling
- Version bump to 1.2.0-alpha
Also includes: deploy hardening, container fixes, IndeedHub updates,
boot screen, dashboard improvements, MASTER_PLAN task tracking
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 23:56:37 +00:00
|
|
|
v-if="route.path === '/dashboard/chat' || route.path === '/dashboard/mesh'"
|
security+feat: v1.3.0 — pentest remediation, container reliability, UI overhaul
Security (33 pentest findings addressed):
- CRITICAL: backend binds 127.0.0.1, path traversal in tor.rs/dwn fixed
- HIGH: federation requires signatures, XSS login redirect, RBAC viewer restricted
- HIGH: tar slip prevention, S3 SSRF validation, backup ID validation
- MEDIUM: remember-me random secret, TOTP session rotation, password re-auth
- LOW: CSP unsafe-inline removed, CORS dev-only, onion/webhook validation
Container reliability:
- Memory limits on all 37 containers (OOM prevention)
- Exited vs stopped state distinction with health-aware status badges
- Crash recovery coordination (no more restart cascade)
- User-stopped tracking survives reboots
- Tiered boot recovery (databases → core → services → apps)
UI:
- Wallet TransactionsModal, health-aware app status badges
- Restart button on containers, exited/crashed red state
- Mesh view overhaul, glass button updates, BaseModal/ToggleSwitch
- Apps sticky header removed, dev faucet, mutable mock wallet
Infrastructure:
- LND REST port 8080 exposed over Tor (LND Connect fix)
- Nginx cookie_session fix, deploy script Tor config updated
- Dev environment: podman auto-start, boot mode simulation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 12:44:31 +00:00
|
|
|
:class="['h-full', mobileTabPaddingTop ? 'overflow-y-auto' : '']"
|
fix: overhaul container lifecycle — recovery, health, uninstall, UI state
Container recovery:
- Health monitor: MAX_RESTART_ATTEMPTS 3→10, interval 60s→120s
- Dependency-aware restarts: won't restart services before their deps
- Reset dependent counters when a dependency recovers
- Handle "created" state containers (were invisible to health monitor)
- Added IndeedHub, mempool-api, mysql to tier system
- Crash recovery: podman start timeout 30s→120s with retry
- Podman client: socket timeout 5s→30s, added restart policy
UI state representation:
- Exit code 0 shows "stopped" (gray), not "crashed" (red)
- Exit code 137 shows "killed (OOM)"
- Non-zero exit shows "crashed" (red)
- Added exit_code field to PackageDataEntry
Install/uninstall fixes:
- Install returns error when container doesn't start (was silent success)
- Post-install hooks awaited instead of fire-and-forget tokio::spawn
- Uninstall: graceful rm before force, volume prune, network cleanup
- Uninstall returns error on partial failure (was 200 OK)
Config consistency:
- DB passwords read from /var/lib/archipelago/secrets/ (was hardcoded)
- Bitcoin: added ZMQ ports 28332/28333 for LND block notifications
- IndeedHub port 7777→8190 (was conflicting with strfry)
- Marketplace versions: LND 0.17.4→0.18.4, Mempool 2.5.0→3.0.0
Performance:
- Metrics collector interval 60s→300s (was duplicating health monitor)
- Podman client: proper error propagation instead of unwrap_or_default
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 07:03:57 +01:00
|
|
|
:style="{ paddingTop: mobileTabPaddingTop ? (mobileTabPaddingTop + 16) + 'px' : undefined }"
|
|
|
|
|
class="mobile-safe-top"
|
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
|
|
|
>
|
|
|
|
|
<component :is="Component" />
|
|
|
|
|
</div>
|
|
|
|
|
<div
|
|
|
|
|
v-else
|
2026-01-24 22:59:20 +00:00
|
|
|
:class="[
|
fix: overhaul container lifecycle — recovery, health, uninstall, UI state
Container recovery:
- Health monitor: MAX_RESTART_ATTEMPTS 3→10, interval 60s→120s
- Dependency-aware restarts: won't restart services before their deps
- Reset dependent counters when a dependency recovers
- Handle "created" state containers (were invisible to health monitor)
- Added IndeedHub, mempool-api, mysql to tier system
- Crash recovery: podman start timeout 30s→120s with retry
- Podman client: socket timeout 5s→30s, added restart policy
UI state representation:
- Exit code 0 shows "stopped" (gray), not "crashed" (red)
- Exit code 137 shows "killed (OOM)"
- Non-zero exit shows "crashed" (red)
- Added exit_code field to PackageDataEntry
Install/uninstall fixes:
- Install returns error when container doesn't start (was silent success)
- Post-install hooks awaited instead of fire-and-forget tokio::spawn
- Uninstall: graceful rm before force, volume prune, network cleanup
- Uninstall returns error on partial failure (was 200 OK)
Config consistency:
- DB passwords read from /var/lib/archipelago/secrets/ (was hardcoded)
- Bitcoin: added ZMQ ports 28332/28333 for LND block notifications
- IndeedHub port 7777→8190 (was conflicting with strfry)
- Marketplace versions: LND 0.17.4→0.18.4, Mempool 2.5.0→3.0.0
Performance:
- Metrics collector interval 60s→300s (was duplicating health monitor)
- Podman client: proper error propagation instead of unwrap_or_default
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 07:03:57 +01:00
|
|
|
'absolute inset-0 px-4 pt-4 md:pt-8 md:px-8 overflow-y-auto mobile-safe-top',
|
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
|
|
|
needsMobileBackButtonSpace
|
2026-03-14 17:12:41 +00:00
|
|
|
? 'mobile-scroll-pad-back'
|
|
|
|
|
: 'mobile-scroll-pad'
|
2026-01-24 22:59:20 +00:00
|
|
|
]"
|
2026-03-05 10:14:10 +00:00
|
|
|
:style="mobileTabPaddingTop ? { paddingTop: (mobileTabPaddingTop + 16) + 'px' } : undefined"
|
2026-01-24 22:59:20 +00:00
|
|
|
>
|
2026-03-14 17:12:41 +00:00
|
|
|
<component :is="Component" class="view-container flex-none" />
|
|
|
|
|
<!-- Bottom spacer — scroll clearance on all pages -->
|
|
|
|
|
<div class="shrink-0 h-6 md:h-12" aria-hidden="true"></div>
|
2026-01-24 22:59:20 +00:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</Transition>
|
|
|
|
|
</RouterView>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2026-03-16 12:58:35 +00:00
|
|
|
|
|
|
|
|
<!-- Panel mode app session — renders alongside current page content -->
|
|
|
|
|
<Transition name="panel-slide">
|
|
|
|
|
<div v-if="appLauncher.panelAppId" class="app-panel-container">
|
|
|
|
|
<AppSession :app-id-prop="appLauncher.panelAppId" @close="appLauncher.closePanel()" />
|
|
|
|
|
</div>
|
|
|
|
|
</Transition>
|
2026-01-24 22:59:20 +00:00
|
|
|
</main>
|
|
|
|
|
|
2026-03-25 18:13:22 +00:00
|
|
|
<!-- Persistent Mobile Tabs + Bottom Tab Bar — outside <main> so position:fixed isn't broken by will-change:transform -->
|
|
|
|
|
<DashboardMobileNav ref="mobileNavRef" :show-zoom-in="showZoomIn" />
|
|
|
|
|
|
2026-03-11 13:04:31 +00:00
|
|
|
<!-- Health Notifications Toast -->
|
2026-03-22 03:30:21 +00:00
|
|
|
<HealthNotifications />
|
2026-04-11 20:00:05 +01:00
|
|
|
|
|
|
|
|
<!-- First-use companion intro overlay -->
|
|
|
|
|
<CompanionIntroOverlay />
|
2026-01-24 22:59:20 +00:00
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
2026-04-11 13:35:52 +01:00
|
|
|
import { computed, ref, watch, nextTick, onMounted, onBeforeUnmount } from 'vue'
|
2026-03-22 03:30:21 +00:00
|
|
|
import { RouterView, useRouter, useRoute } from 'vue-router'
|
2026-01-24 22:59:20 +00:00
|
|
|
import { useAppStore } from '../stores/app'
|
2026-03-16 12:58:35 +00:00
|
|
|
import { useAppLauncherStore } from '../stores/appLauncher'
|
|
|
|
|
import AppSession from '@/views/AppSession.vue'
|
2026-02-17 19:19:54 +00:00
|
|
|
import { useLoginTransitionStore } from '../stores/loginTransition'
|
|
|
|
|
import { playDashboardLoadOomph } from '@/composables/useLoginSounds'
|
2026-03-04 07:09:31 +00:00
|
|
|
|
2026-03-22 03:30:21 +00:00
|
|
|
import DashboardSidebar from '@/views/dashboard/DashboardSidebar.vue'
|
|
|
|
|
import DashboardMobileNav from '@/views/dashboard/DashboardMobileNav.vue'
|
|
|
|
|
import ConnectionBanner from '@/views/dashboard/ConnectionBanner.vue'
|
|
|
|
|
import HealthNotifications from '@/views/dashboard/HealthNotifications.vue'
|
2026-04-11 20:00:05 +01:00
|
|
|
import CompanionIntroOverlay from '@/components/CompanionIntroOverlay.vue'
|
2026-03-22 03:30:21 +00:00
|
|
|
import { useRouteTransitions, isDetailRoute, ROUTE_BACKGROUNDS } from '@/views/dashboard/useRouteTransitions'
|
|
|
|
|
import '@/views/dashboard/dashboard-styles.css'
|
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-01-24 22:59:20 +00:00
|
|
|
const router = useRouter()
|
|
|
|
|
const route = useRoute()
|
|
|
|
|
const store = useAppStore()
|
2026-03-16 12:58:35 +00:00
|
|
|
const appLauncher = useAppLauncherStore()
|
2026-02-17 19:19:54 +00:00
|
|
|
const loginTransition = useLoginTransitionStore()
|
2026-03-22 03:30:21 +00:00
|
|
|
const { getTransitionName } = useRouteTransitions()
|
2026-02-17 19:19:54 +00:00
|
|
|
|
|
|
|
|
const showZoomIn = ref(false)
|
2026-03-01 18:07:35 +00:00
|
|
|
const pendingTimers: ReturnType<typeof setTimeout>[] = []
|
|
|
|
|
|
|
|
|
|
function scheduledTimeout(fn: () => void, ms: number) {
|
|
|
|
|
const id = setTimeout(fn, ms)
|
|
|
|
|
pendingTimers.push(id)
|
|
|
|
|
return id
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-24 22:59:20 +00:00
|
|
|
const showAltBackground = ref(false)
|
2026-02-17 19:19:54 +00:00
|
|
|
const isHomeRoute = computed(() => route.path === '/dashboard' || route.path === '/dashboard/')
|
2026-01-24 22:59:20 +00:00
|
|
|
const isGlitching = ref(false)
|
|
|
|
|
|
2026-03-01 18:07:35 +00:00
|
|
|
const backgroundImage = computed(() => {
|
2026-04-11 13:35:52 +01:00
|
|
|
const mapped = ROUTE_BACKGROUNDS[route.path]
|
|
|
|
|
if (mapped) return mapped
|
2026-04-12 00:27:02 -04:00
|
|
|
// Cloud subpages (folders) use the same background as Cloud
|
|
|
|
|
if (route.path.startsWith('/dashboard/cloud/')) return 'bg-cloud.jpg'
|
2026-03-01 18:07:35 +00:00
|
|
|
if (isDetailRoute(route.path)) return 'bg-intro.jpg'
|
2026-04-11 13:35:52 +01:00
|
|
|
return 'bg-home.jpg'
|
2026-01-24 22:59:20 +00:00
|
|
|
})
|
|
|
|
|
|
2026-03-01 18:07:35 +00:00
|
|
|
const isDarkRoute = computed(() => {
|
|
|
|
|
const p = route.path
|
|
|
|
|
return p.includes('/dashboard/web5') ||
|
|
|
|
|
p.includes('/dashboard/server') ||
|
|
|
|
|
p.includes('/dashboard/settings') ||
|
|
|
|
|
(p.includes('/dashboard/apps') && !isDetailRoute(p)) ||
|
|
|
|
|
p.includes('/dashboard/marketplace') ||
|
2026-03-19 16:12:01 +00:00
|
|
|
p.includes('/dashboard/discover') ||
|
2026-03-01 18:07:35 +00:00
|
|
|
p.includes('/dashboard/cloud')
|
2026-01-24 22:59:20 +00:00
|
|
|
})
|
|
|
|
|
|
2026-03-01 18:07:35 +00:00
|
|
|
const showDarkOverlay = ref(isDarkRoute.value)
|
|
|
|
|
let overlayTimer: ReturnType<typeof setTimeout> | null = null
|
|
|
|
|
|
|
|
|
|
watch(isDarkRoute, (dark) => {
|
|
|
|
|
if (overlayTimer) { clearTimeout(overlayTimer); overlayTimer = null }
|
|
|
|
|
if (dark) {
|
|
|
|
|
showDarkOverlay.value = true
|
|
|
|
|
} else {
|
|
|
|
|
overlayTimer = scheduledTimeout(() => { showDarkOverlay.value = false }, 450)
|
|
|
|
|
}
|
2026-01-24 22:59:20 +00:00
|
|
|
})
|
2026-03-01 18:07:35 +00:00
|
|
|
|
2026-04-11 13:35:52 +01:00
|
|
|
const WEB5_DETAIL_ROUTES = ['/dashboard/server/federation', '/dashboard/monitoring', '/dashboard/fleet']
|
|
|
|
|
const needsMobileBackButtonSpace = computed(() =>
|
|
|
|
|
isDetailRoute(route.path) || WEB5_DETAIL_ROUTES.includes(route.path)
|
|
|
|
|
)
|
2026-03-22 03:30:21 +00:00
|
|
|
|
|
|
|
|
const mobileNavRef = ref<InstanceType<typeof DashboardMobileNav> | null>(null)
|
2026-01-24 22:59:20 +00:00
|
|
|
|
2026-03-22 03:30:21 +00:00
|
|
|
const mobileTabPaddingTop = computed(() => {
|
|
|
|
|
return mobileNavRef.value?.mobileTabPaddingTop ?? 0
|
|
|
|
|
})
|
2026-01-24 22:59:20 +00:00
|
|
|
|
2026-04-11 13:35:52 +01:00
|
|
|
// Scroll position save/restore — only restore when coming BACK from a detail page
|
|
|
|
|
const savedScrollPositions = new Map<string, number>()
|
|
|
|
|
let previousRoutePath = ''
|
|
|
|
|
|
|
|
|
|
function isAnyDetailRoute(path: string): boolean {
|
|
|
|
|
return isDetailRoute(path) || WEB5_DETAIL_ROUTES.includes(path)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function saveCurrentScroll() {
|
|
|
|
|
const el = document.querySelector<HTMLElement>('.perspective-container .view-wrapper > div[class*="overflow-y-auto"]')
|
|
|
|
|
if (el && previousRoutePath) {
|
|
|
|
|
savedScrollPositions.set(previousRoutePath, el.scrollTop)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function restoreScroll(path: string) {
|
|
|
|
|
const saved = savedScrollPositions.get(path)
|
|
|
|
|
if (saved == null) return
|
|
|
|
|
nextTick(() => {
|
|
|
|
|
// Wait for transition to settle
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
const el = document.querySelector<HTMLElement>('.perspective-container .view-wrapper > div[class*="overflow-y-auto"]')
|
|
|
|
|
if (el) el.scrollTop = saved
|
|
|
|
|
}, 50)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-24 22:59:20 +00:00
|
|
|
watch(() => route.path, (newPath) => {
|
|
|
|
|
const isAppDetails = isDetailRoute(newPath)
|
|
|
|
|
const wasAppDetails = showAltBackground.value
|
2026-04-11 13:35:52 +01:00
|
|
|
const oldPath = previousRoutePath
|
|
|
|
|
const wasDetail = isAnyDetailRoute(oldPath)
|
|
|
|
|
const isDetail = isAnyDetailRoute(newPath)
|
|
|
|
|
|
|
|
|
|
// Save scroll position of the page we're leaving
|
|
|
|
|
saveCurrentScroll()
|
|
|
|
|
|
|
|
|
|
// Restore scroll only when returning from a detail page to the parent list
|
|
|
|
|
if (wasDetail && !isDetail) {
|
|
|
|
|
restoreScroll(newPath)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
previousRoutePath = newPath
|
2026-03-01 18:07:35 +00:00
|
|
|
|
2026-01-24 22:59:20 +00:00
|
|
|
showAltBackground.value = isAppDetails
|
2026-03-01 18:07:35 +00:00
|
|
|
|
2026-01-24 22:59:20 +00:00
|
|
|
if (isAppDetails && !wasAppDetails) {
|
2026-03-01 18:07:35 +00:00
|
|
|
scheduledTimeout(() => {
|
2026-01-24 22:59:20 +00:00
|
|
|
isGlitching.value = true
|
2026-03-01 18:07:35 +00:00
|
|
|
scheduledTimeout(() => { isGlitching.value = false }, 375)
|
|
|
|
|
}, 500)
|
2026-01-24 22:59:20 +00:00
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
onMounted(() => {
|
2026-04-11 13:35:52 +01:00
|
|
|
previousRoutePath = route.path
|
2026-02-17 19:19:54 +00:00
|
|
|
document.body.classList.add('dashboard-active')
|
2026-04-22 04:45:33 -04:00
|
|
|
if (loginTransition.justCompletedOnboarding) {
|
|
|
|
|
// Full glitchy reveal — only on the very first dashboard entry
|
|
|
|
|
// right after onboarding (one-time event, persists in feel).
|
2026-02-17 19:19:54 +00:00
|
|
|
playDashboardLoadOomph()
|
|
|
|
|
showZoomIn.value = true
|
|
|
|
|
loginTransition.setPendingWelcomeTyping(true)
|
2026-04-22 04:45:33 -04:00
|
|
|
loginTransition.setJustCompletedOnboarding(false)
|
2026-02-17 19:19:54 +00:00
|
|
|
loginTransition.setJustLoggedIn(false)
|
|
|
|
|
const triggerRevealGlitch = () => {
|
|
|
|
|
isGlitching.value = true
|
2026-03-01 18:07:35 +00:00
|
|
|
scheduledTimeout(() => { isGlitching.value = false }, 380)
|
2026-02-17 19:19:54 +00:00
|
|
|
}
|
2026-03-01 18:07:35 +00:00
|
|
|
scheduledTimeout(triggerRevealGlitch, 500)
|
|
|
|
|
scheduledTimeout(triggerRevealGlitch, 1200)
|
|
|
|
|
scheduledTimeout(triggerRevealGlitch, 2000)
|
|
|
|
|
scheduledTimeout(() => { showZoomIn.value = false }, 8000)
|
|
|
|
|
scheduledTimeout(() => {
|
2026-02-17 19:19:54 +00:00
|
|
|
loginTransition.setStartWelcomeTyping(true)
|
|
|
|
|
loginTransition.setPendingWelcomeTyping(false)
|
|
|
|
|
}, 4000)
|
2026-04-22 04:45:33 -04:00
|
|
|
} else if (loginTransition.justLoggedIn) {
|
2026-04-22 05:42:52 -04:00
|
|
|
// Regular re-login — no zoom, no glitch. Just land on the
|
|
|
|
|
// dashboard and kick off the welcome typing quickly.
|
2026-04-22 04:45:33 -04:00
|
|
|
playDashboardLoadOomph()
|
|
|
|
|
loginTransition.setPendingWelcomeTyping(true)
|
|
|
|
|
loginTransition.setJustLoggedIn(false)
|
|
|
|
|
scheduledTimeout(() => {
|
|
|
|
|
loginTransition.setStartWelcomeTyping(true)
|
|
|
|
|
loginTransition.setPendingWelcomeTyping(false)
|
2026-04-22 05:42:52 -04:00
|
|
|
}, 300)
|
2026-02-17 19:19:54 +00:00
|
|
|
}
|
|
|
|
|
|
2026-03-11 13:04:31 +00:00
|
|
|
window.addEventListener('keydown', handleKioskShortcuts)
|
2026-01-24 22:59:20 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
onBeforeUnmount(() => {
|
2026-02-17 19:19:54 +00:00
|
|
|
document.body.classList.remove('dashboard-active')
|
2026-03-11 13:04:31 +00:00
|
|
|
window.removeEventListener('keydown', handleKioskShortcuts)
|
2026-03-01 18:07:35 +00:00
|
|
|
for (const id of pendingTimers) clearTimeout(id)
|
|
|
|
|
pendingTimers.length = 0
|
|
|
|
|
if (overlayTimer) { clearTimeout(overlayTimer); overlayTimer = null }
|
2026-01-24 22:59:20 +00:00
|
|
|
})
|
|
|
|
|
|
2026-03-11 13:04:31 +00:00
|
|
|
function isKioskMode(): boolean {
|
|
|
|
|
try {
|
|
|
|
|
return localStorage.getItem('kiosk') === 'true' || new URLSearchParams(window.location.search).has('kiosk')
|
|
|
|
|
} catch { return false }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function handleKioskShortcuts(e: KeyboardEvent) {
|
|
|
|
|
if (!isKioskMode()) return
|
|
|
|
|
if (e.ctrlKey && e.shiftKey) {
|
|
|
|
|
if (e.key === 'R' || e.key === 'r') {
|
|
|
|
|
e.preventDefault()
|
|
|
|
|
router.push('/recovery')
|
|
|
|
|
} else if (e.key === 'H' || e.key === 'h') {
|
|
|
|
|
e.preventDefault()
|
|
|
|
|
router.push('/dashboard')
|
|
|
|
|
} else if (e.key === 'Q' || e.key === 'q') {
|
|
|
|
|
e.preventDefault()
|
|
|
|
|
if (confirm('Reboot the server?')) {
|
|
|
|
|
fetch('/rpc/', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ method: 'system.reboot' }) }).catch(() => {})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-24 22:59:20 +00:00
|
|
|
async function handleLogout() {
|
2026-03-01 17:53:18 +00:00
|
|
|
try {
|
|
|
|
|
await store.logout()
|
|
|
|
|
} catch {
|
|
|
|
|
/* proceed to login regardless */
|
|
|
|
|
}
|
|
|
|
|
router.push('/login').catch(() => {
|
|
|
|
|
window.location.href = '/login'
|
|
|
|
|
})
|
2026-01-24 22:59:20 +00:00
|
|
|
}
|
|
|
|
|
</script>
|
|
|
|
|
|
2026-03-22 03:30:21 +00:00
|
|
|
<!-- Styles extracted to dashboard/dashboard-styles.css -->
|