diff --git a/WebApp/src/app.css b/WebApp/src/app.css
index 65201fc..60ddff0 100644
--- a/WebApp/src/app.css
+++ b/WebApp/src/app.css
@@ -38,7 +38,6 @@
--sidebar-border: oklch(0.922 0 0);
--sidebar-ring: oklch(0.708 0 0);
}
-
.dark {
--background: oklch(0.145 0 0);
--foreground: oklch(0.985 0 0);
@@ -109,6 +108,13 @@
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring);
+
+ /* Custom brand colors and glassmorphism tokens */
+ --color-premium: #2563eb;
+ --color-premium-foreground: #ffffff;
+ --color-glass-background: rgb(25 25 30 / 60%);
+ --color-glass-border: rgb(255 255 255 / 8%);
+ --color-glass-panel: rgb(15 15 20 / 70%);
}
@layer base {
@@ -123,7 +129,7 @@
}
}
-/* Preserve existing simulator utility styles alongside shadcn tokens. */
+/* Scrollbar customizations */
::-webkit-scrollbar {
width: 6px;
height: 6px;
@@ -142,29 +148,6 @@
background: rgb(255 255 255 / 20%);
}
-.glass-panel {
- background: rgb(15 15 20 / 70%);
- backdrop-filter: blur(16px);
- border: 1px solid rgb(255 255 255 / 5%);
-}
-
-.glass-card {
- background: rgb(25 25 30 / 60%);
- border: 1px solid rgb(255 255 255 / 8%);
-}
-
-.btn-premium {
- background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
- border: none;
- color: white;
- transition: all 0.2s ease;
-}
-
-.btn-premium:hover {
- transform: translateY(-1px);
- box-shadow: 0 4px 12px rgb(37 99 235 / 30%);
-}
-
.status-dot {
height: 8px;
width: 8px;
@@ -193,7 +176,3 @@
opacity: 1;
}
}
-
-.toast-enter {
- animation: slideIn 0.3s cubic-bezier(0.16, 1, 0.3, 1) forwards;
-}
diff --git a/WebApp/src/lib/app/controller.js b/WebApp/src/lib/app/controller.js
index d2c431d..44a0c2e 100644
--- a/WebApp/src/lib/app/controller.js
+++ b/WebApp/src/lib/app/controller.js
@@ -805,39 +805,6 @@ const stopPolling = () => {
}
};
-const pollClientData = async () => {
- const { device } = getAppState();
- if (!device || device.role !== 'client') return;
-
- const [recs, links, deviceList, notifications] = await Promise.all([
- api.ops.listRecordings().catch(() => ({ recordings: [] })),
- api.devices.listLinks().catch(() => ({ links: [] })),
- api.devices.list().catch(() => ({ devices: [] })),
- api.ops.listNotifications().catch(() => ({ notifications: [] }))
- ]);
-
- const cameraById = new Map(
- (deviceList.devices || [])
- .filter((entry) => entry.role === 'camera')
- .map((entry) => [entry.id, entry])
- );
-
- const linkedCameras = (links.links || []).map((link) => {
- const camera = cameraById.get(link.cameraDeviceId);
- return {
- ...link,
- cameraName: camera?.name ?? null,
- cameraStatus: camera?.status ?? 'offline'
- };
- });
-
- setAppState({
- recordings: recs.recordings || [],
- linkedCameras
- });
- syncMotionNotificationsFromDeliveries(notifications.notifications);
-};
-
const startPolling = () => {
stopPolling();
void pollClientData();
diff --git a/WebApp/src/lib/components/ui/alert/alert.svelte b/WebApp/src/lib/components/ui/alert/alert.svelte
index abf7487..d7bc03c 100644
--- a/WebApp/src/lib/components/ui/alert/alert.svelte
+++ b/WebApp/src/lib/components/ui/alert/alert.svelte
@@ -7,6 +7,7 @@
variant: {
default: "bg-card text-card-foreground",
destructive: "text-destructive bg-card *:data-[slot=alert-description]:text-destructive/90 *:[svg]:text-current",
+ glass: "bg-glass-background border-glass-border backdrop-blur-xl shadow-lg",
},
},
defaultVariants: {
diff --git a/WebApp/src/lib/components/ui/button/button.svelte b/WebApp/src/lib/components/ui/button/button.svelte
index 89cedcb..1ae7f3a 100644
--- a/WebApp/src/lib/components/ui/button/button.svelte
+++ b/WebApp/src/lib/components/ui/button/button.svelte
@@ -13,6 +13,7 @@
ghost: "hover:bg-muted hover:text-foreground dark:hover:bg-muted/50 aria-expanded:bg-muted aria-expanded:text-foreground",
destructive: "bg-destructive/10 hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/20 text-destructive focus-visible:border-destructive/40 dark:hover:bg-destructive/30",
link: "text-primary underline-offset-4 hover:underline",
+ premium: "bg-premium text-premium-foreground shadow-lg shadow-premium/20 transition-all hover:-translate-y-0.5 hover:shadow-xl hover:shadow-premium/30 active:translate-y-0",
},
size: {
default: "h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
diff --git a/WebApp/src/lib/components/ui/card/card.svelte b/WebApp/src/lib/components/ui/card/card.svelte
index 2b4f81a..de42e90 100644
--- a/WebApp/src/lib/components/ui/card/card.svelte
+++ b/WebApp/src/lib/components/ui/card/card.svelte
@@ -1,21 +1,51 @@
-
+
+
img:first-child]:pt-0 data-[size=sm]:gap-3 data-[size=sm]:py-3 data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl group/card flex flex-col", className)}
+ class={cn(cardVariants({ variant, size }), className)}
{...restProps}
>
{@render children?.()}
diff --git a/WebApp/src/lib/components/ui/dialog/dialog-content.svelte b/WebApp/src/lib/components/ui/dialog/dialog-content.svelte
index c0551ee..f035598 100644
--- a/WebApp/src/lib/components/ui/dialog/dialog-content.svelte
+++ b/WebApp/src/lib/components/ui/dialog/dialog-content.svelte
@@ -14,12 +14,19 @@
portalProps,
children,
showCloseButton = true,
+ variant = "default",
...restProps
}: WithoutChildrenOrChild
& {
portalProps?: WithoutChildrenOrChild>;
children: Snippet;
showCloseButton?: boolean;
+ variant?: "default" | "glass";
} = $props();
+
+ const variantClasses = {
+ default: "bg-popover text-popover-foreground ring-1 ring-foreground/10",
+ glass: "bg-glass-background border-glass-border border backdrop-blur-xl shadow-2xl text-foreground"
+ };
@@ -28,7 +35,8 @@
bind:ref
data-slot="dialog-content"
class={cn(
- "bg-popover text-popover-foreground data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 ring-foreground/10 grid max-w-[calc(100%-2rem)] gap-4 rounded-xl p-4 text-sm ring-1 duration-100 sm:max-w-sm fixed top-1/2 left-1/2 z-50 w-full -translate-x-1/2 -translate-y-1/2 outline-none",
+ "data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 grid max-w-[calc(100%-2rem)] gap-4 rounded-xl p-4 text-sm duration-100 sm:max-w-sm fixed top-1/2 left-1/2 z-50 w-full -translate-x-1/2 -translate-y-1/2 outline-none",
+ variantClasses[variant],
className
)}
{...restProps}
diff --git a/WebApp/src/lib/components/ui/skeleton/index.ts b/WebApp/src/lib/components/ui/skeleton/index.ts
new file mode 100644
index 0000000..186db21
--- /dev/null
+++ b/WebApp/src/lib/components/ui/skeleton/index.ts
@@ -0,0 +1,7 @@
+import Root from "./skeleton.svelte";
+
+export {
+ Root,
+ //
+ Root as Skeleton,
+};
diff --git a/WebApp/src/lib/components/ui/skeleton/skeleton.svelte b/WebApp/src/lib/components/ui/skeleton/skeleton.svelte
new file mode 100644
index 0000000..187e4ff
--- /dev/null
+++ b/WebApp/src/lib/components/ui/skeleton/skeleton.svelte
@@ -0,0 +1,16 @@
+
+
+
diff --git a/WebApp/src/lib/components/ui/switch/index.ts b/WebApp/src/lib/components/ui/switch/index.ts
new file mode 100644
index 0000000..f5533db
--- /dev/null
+++ b/WebApp/src/lib/components/ui/switch/index.ts
@@ -0,0 +1,7 @@
+import Root from "./switch.svelte";
+
+export {
+ Root,
+ //
+ Root as Switch,
+};
diff --git a/WebApp/src/lib/components/ui/switch/switch.svelte b/WebApp/src/lib/components/ui/switch/switch.svelte
new file mode 100644
index 0000000..874dbe8
--- /dev/null
+++ b/WebApp/src/lib/components/ui/switch/switch.svelte
@@ -0,0 +1,25 @@
+
+
+
+
+
diff --git a/WebApp/src/lib/sim/ui/AppChrome.svelte b/WebApp/src/lib/sim/ui/AppChrome.svelte
index c2589ad..20d0e86 100644
--- a/WebApp/src/lib/sim/ui/AppChrome.svelte
+++ b/WebApp/src/lib/sim/ui/AppChrome.svelte
@@ -9,6 +9,18 @@
import { Button } from '$lib/components/ui/button/index.js';
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '$lib/components/ui/dialog/index.js';
import { Separator } from '$lib/components/ui/separator/index.js';
+ import { Skeleton } from '$lib/components/ui/skeleton/index.js';
+ import {
+ LayoutDashboard,
+ Bell,
+ Settings,
+ X,
+ Download,
+ Info,
+ AlertTriangle,
+ RefreshCw,
+ CheckCircle2
+ } from '@lucide/svelte';
let { children, pageKey } = $props<{ children: () => unknown; pageKey: string }>();
let recordingDialogOpen = $state(false);
@@ -51,11 +63,22 @@
{#each $appState.toasts as toast (toast.id)}
- {toastTitle(toast.type)}
- {toast.message}
+
+ {#if toast.type === 'error'}
+
+ {:else if toast.type === 'success'}
+
+ {:else}
+
+ {/if}
+
+
{toastTitle(toast.type)}
+
{toast.message}
+
+
appController.removeToast(toast.id)}
>
-
-
-
+
Dismiss notification
@@ -73,7 +94,8 @@
{/each}
{#if $pwaState.installAvailable}
-
+
+
Install App
Add PhoneCam to your home screen for a more native dashboard experience.
@@ -93,7 +115,8 @@
{/if}
{#if $pwaState.showIosInstallHint}
-
+
+
Install on iPhone
In Safari, tap the Share button, then choose “Add to Home Screen” to install PhoneCam.
@@ -108,6 +131,7 @@
{#if $pwaState.updateAvailable}
+
Update Ready
A newer version of PhoneCam is ready. Reload to use the latest app shell.
@@ -121,7 +145,8 @@
{/if}
{#if $pwaState.offlineReady}
-
+
+
Offline Shell Ready
The app shell is now cached for quicker relaunches, but live camera features still need connectivity.
@@ -142,20 +167,20 @@
{#if showSidebarSkeleton()}
{#each Array(3) as _, index (index)}
-
+
{/each}
{:else}
@@ -187,21 +212,7 @@
class="nav-btn h-12 w-full justify-center gap-3 rounded-xl border border-transparent text-gray-400 hover:text-white lg:justify-start"
onclick={() => appController.navigate('home')}
>
-
-
-
+
Dashboard
@@ -210,21 +221,7 @@
class="nav-btn relative h-12 w-full justify-center gap-3 rounded-xl border border-transparent text-gray-400 hover:text-white lg:justify-start"
onclick={() => appController.navigate('activity')}
>
-
-
-
+
Activity Feed
{#if $unreadNotificationsCount > 0}
appController.navigate('settings')}
>
-
-
-
-
+
Settings
@@ -276,8 +253,11 @@
{#if $pwaState.isOffline}
-
- {offlineTitle()}
+
+
{offlineDescription()}
@@ -294,7 +274,8 @@
@@ -313,9 +294,7 @@
title="Close recording modal"
onclick={() => appController.closeRecordingModal()}
>
-
-
-
+
diff --git a/WebApp/src/routes/+page.svelte b/WebApp/src/routes/+page.svelte
index a475af1..eae7b4e 100644
--- a/WebApp/src/routes/+page.svelte
+++ b/WebApp/src/routes/+page.svelte
@@ -8,23 +8,17 @@
import { Input } from '$lib/components/ui/input/index.js';
import { Label } from '$lib/components/ui/label/index.js';
import { Separator } from '$lib/components/ui/separator/index.js';
+ import { ShieldCheck } from '@lucide/svelte';
-
+
PhoneCam Web
@@ -71,7 +65,8 @@
appController.submitAuth()}
>
{$appState.isRegistering ? 'Create Account' : 'Sign In'}
diff --git a/WebApp/src/routes/activity/+page.svelte b/WebApp/src/routes/activity/+page.svelte
index 359d6c3..66ebb6f 100644
--- a/WebApp/src/routes/activity/+page.svelte
+++ b/WebApp/src/routes/activity/+page.svelte
@@ -7,6 +7,7 @@
import { Button } from '$lib/components/ui/button/index.js';
import { Card, CardContent, CardHeader, CardTitle } from '$lib/components/ui/card/index.js';
import { ScrollArea } from '$lib/components/ui/scroll-area/index.js';
+ import { BellOff, Trash2, Calendar } from '@lucide/svelte';
@@ -16,14 +17,15 @@
appController.clearNotifications()}
>
+
Clear Read
-
+
Motion Alerts
@@ -31,17 +33,10 @@
{#if $appState.motionNotifications.length === 0}
-
-
-
-
- All quiet
-
+
+
+ All quiet
+
No notifications yet.
@@ -50,11 +45,14 @@
appController.openMotionNotificationTarget(notification.id, notification.cameraDeviceId)}
>
- {notification.message}
- {new Date(notification.createdAt).toLocaleString()}
+ {notification.message}
+
+
+
{new Date(notification.createdAt).toLocaleString()}
+
{/each}
{/if}
diff --git a/WebApp/src/routes/camera/+page.svelte b/WebApp/src/routes/camera/+page.svelte
index 245164a..0e97469 100644
--- a/WebApp/src/routes/camera/+page.svelte
+++ b/WebApp/src/routes/camera/+page.svelte
@@ -10,6 +10,17 @@
import { Label } from '$lib/components/ui/label/index.js';
import { ScrollArea } from '$lib/components/ui/scroll-area/index.js';
import { Select, SelectContent, SelectItem, SelectTrigger } from '$lib/components/ui/select/index.js';
+ import { Switch } from '$lib/components/ui/switch/index.js';
+ import {
+ VideoOff,
+ RefreshCw,
+ Zap,
+ Square,
+ Activity,
+ Settings2,
+ Terminal,
+ Camera
+ } from '@lucide/svelte';
let cameraVideoElement: HTMLVideoElement | null = null;
@@ -40,7 +51,7 @@
-
+
-
- REC
+
+ REC
@@ -63,27 +74,20 @@
id="cameraOfflineOverlay"
class="absolute inset-0 bg-[#0a0a0c]/80 backdrop-blur-sm z-10 flex flex-col items-center justify-center gap-4 {$appState.socketConnected ? 'hidden' : ''}"
>
-
-
-
-
- Camera Offline
-
+
+
+ Camera Offline
+
Reconnect this device to resume live monitoring.
-
+
appController.goOnline()}
- >
- Go Online
+ id="cameraGoOnlineBtn"
+ variant="outline"
+ class="rounded-xl border-emerald-500/50 text-emerald-400 hover:border-emerald-400 hover:bg-emerald-500/10"
+ onclick={() => appController.goOnline()}
+ >
+ Go Online
@@ -91,9 +95,10 @@
-
-
- Logs
+
+
+
+ Activity Logs
Awaiting connection...
{:else}
{#each $appState.activityLog as log (log.id)}
-
[{new Date(log.createdAt).toLocaleTimeString()}] {log.type}: {log.message}
+
+ [{new Date(log.createdAt).toLocaleTimeString()}]
+ {log.type}:
+ {log.message}
+
{/each}
{/if}
-
-
- Actions / Options
+
+
+
+ Control Plane
-
-
- Camera Input
+
+
+
+ Camera Input
appController.refreshCameraInputs()}
- >
- Refresh
+ id="refreshCameraInputsBtn"
+ variant="ghost"
+ size="xs"
+ class="h-6 rounded-lg text-gray-500 hover:text-white"
+ onclick={() => appController.refreshCameraInputs()}
+ >
+
+ Refresh
-
+
appController.selectCameraInput(value)}
- >
+ type="single"
+ value={$appState.selectedCameraInputId}
+ items={cameraInputItems()}
+ onValueChange={(value) => appController.selectCameraInput(value)}
+ >
+
-
Automatic Detection
-
- {$appState.motionDetection.enabled ? 'Armed for automatic motion events.' : 'Paused until manually armed.'}
+
Automatic Detection
+
+ {$appState.motionDetection.enabled ? 'Armed' : 'Disarmed'}
-
- Designed for a plugged-in browser kept in the foreground.
+
+ Foreground browser monitoring.
appController.setMotionDetectionEnabled(!$appState.motionDetection.enabled)}
>
{$appState.motionDetection.enabled ? 'Pause' : 'Arm'}
@@ -172,10 +185,10 @@
-
- Detection Profile
+
+ Profile
- {$appState.motionDetection.state}
+ {$appState.motionDetection.state}
-
+
-
Detector State
-
{$appState.motionDetection.state}
+
Detector
+
{$appState.motionDetection.state}
-
Motion Score
-
{$appState.motionDetection.score.toFixed(3)}
+
Score
+
{$appState.motionDetection.score.toFixed(3)}
-
-
Show detector debug info
-
Useful while tuning thresholds and sample profiles.
+
+
Debug Overlay
+
Sample profiles & metrics.
-
appController.setMotionDetectionDebug(!$appState.motionDetection.debug)}
- >
-
-
+
appController.setMotionDetectionDebug(v)}
+ />
-
+
+
{#if !$appState.isMotionActive}
appController.startMotion()}
- >
-
-
-
- Simulate Motion Event
+ id="startMotionBtn"
+ variant="outline"
+ class="h-12 w-full justify-start rounded-xl border-white/5 bg-white/5 font-bold text-sm hover:border-blue-500/30 hover:bg-blue-500/10 hover:text-blue-400 group"
+ onclick={() => appController.startMotion()}
+ >
+
+ Simulate Motion
{:else}
appController.endMotion()}
- >
-
-
-
-
- Stop Recording
+ id="endMotionBtn"
+ variant="outline"
+ class="h-12 w-full justify-start rounded-xl border-white/5 bg-white/5 font-bold text-sm hover:bg-white/10"
+ onclick={() => appController.endMotion()}
+ >
+
+ Stop Recording
{/if}
diff --git a/WebApp/src/routes/client/+page.svelte b/WebApp/src/routes/client/+page.svelte
index 9a7fbd3..8cf47da 100644
--- a/WebApp/src/routes/client/+page.svelte
+++ b/WebApp/src/routes/client/+page.svelte
@@ -9,6 +9,20 @@
import { Card, CardContent, CardHeader, CardTitle } from '$lib/components/ui/card/index.js';
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '$lib/components/ui/dropdown-menu/index.js';
import { ScrollArea } from '$lib/components/ui/scroll-area/index.js';
+ import {
+ Plus,
+ RefreshCw,
+ Monitor,
+ Eye,
+ Video,
+ VideoOff,
+ MoreVertical,
+ X,
+ History,
+ Activity,
+ ShieldCheck,
+ Clock
+ } from '@lucide/svelte';
let clientVideoElement: HTMLVideoElement | null = null;
@@ -56,62 +70,55 @@
-
-
+
+
Client Dashboard
-
+
appController.linkCamera()}
>
-
-
-
+
Link Camera
appController.refreshClientData()}
>
-
-
-
+
-
-
- Your Cameras
+
+
+
+ Your Cameras
{#if $appState.linkedCameras.length === 0}
-
- No cameras linked
-
+
+ No cameras linked
+
Link a camera to start viewing live feeds and recordings.
{:else}
-
+
{#each $appState.linkedCameras as link (link.id)}
{
if ((event.target as HTMLElement).closest('[data-menu-trigger]')) return;
appController.selectCamera(link.cameraDeviceId);
@@ -122,25 +129,20 @@
>
{#if $appState.activeCameraDeviceId === link.cameraDeviceId}
{:else}
-
-
-
-
-
{isCameraLive(link.cameraDeviceId) ? 'Live Stream Active' : 'Click to view'}
+
+
+
{isCameraLive(link.cameraDeviceId) ? 'LIVE' : 'CLICK TO VIEW'}
{/if}
@@ -152,9 +154,9 @@
-
+
{link.cameraStatus || 'offline'}
@@ -168,11 +170,7 @@
class="text-gray-400 hover:text-white"
{...props}
>
-
-
-
-
-
+
{/snippet}
@@ -207,31 +205,32 @@
-
+
-
+
-
-
- Live Feed: {activeCameraLabel()}
+ {#if isCameraLive($appState.activeCameraDeviceId)}
+
+ {/if}
+
+ {activeCameraLabel()}
appController.closeStreamViewer()}
>
-
-
-
+
@@ -246,44 +245,40 @@
bind:this={clientVideoElement}
>
-
-
-
-
- Stream unavailable
-
+
+
+
+ Stream unavailable
+
{$appState.clientPlaceholderText}
-
-
+
+
{#if activeStreamDiagnostics()}
-
-
Live Diagnostics
-
+
+
+
Diagnostics
+
{activeStreamSessionId()?.slice(0, 8)}
{#if isCameraLive($appState.activeCameraDeviceId)}
-
Peer connected
+
Peer connected
{/if}
-
- {#each activeStreamDiagnostics().entries.slice(0, 6) as entry (entry.id)}
-
+
+ {#each activeStreamDiagnostics().entries.slice(0, 4) as entry (entry.id)}
+
- {entry.stage}
- {new Date(entry.createdAt).toLocaleTimeString()}
+ {entry.stage}
+ {new Date(entry.createdAt).toLocaleTimeString()}
-
{entry.message}
+
{entry.message}
{/each}
@@ -292,34 +287,38 @@
-
-
- Recent Recordings
+
+
+
+ Recent Clips
-
+
{#if $appState.recordings.length === 0}
-
- No recordings found
-
- Captured clips will appear here when they are ready.
+
+ No recordings found
+
+ Captured clips will appear here.
{:else}
- {#each $appState.recordings.slice(0, 5) as recording (recording.id)}
-
+ {#each $appState.recordings.slice(0, 8) as recording (recording.id)}
+
-
-
{new Date(recording.createdAt).toLocaleString()}
-
- {recording.durationSeconds != null ? `${recording.durationSeconds}s duration` : 'Duration pending'} · {recording.status ?? 'unknown'}
+
+
+
+ {new Date(recording.createdAt).toLocaleTimeString()}
+
+
+ {recording.status ?? 'unknown'} · {recording.durationSeconds != null ? `${recording.durationSeconds}s` : '--'}
appController.openRecording(recording.id)}
>
diff --git a/WebApp/src/routes/onboarding/+page.svelte b/WebApp/src/routes/onboarding/+page.svelte
index 9c8a0a8..b5208e9 100644
--- a/WebApp/src/routes/onboarding/+page.svelte
+++ b/WebApp/src/routes/onboarding/+page.svelte
@@ -8,13 +8,14 @@
import { Input } from '$lib/components/ui/input/index.js';
import { Label } from '$lib/components/ui/label/index.js';
import { ToggleGroup, ToggleGroupItem } from '$lib/components/ui/toggle-group/index.js';
+ import { Settings } from '@lucide/svelte';
let showAdvancedSettings = false;
-
+
Configure Device
Set up this browser dashboard role
@@ -75,15 +76,7 @@
aria-expanded={showAdvancedSettings}
onclick={() => (showAdvancedSettings = !showAdvancedSettings)}
>
-
-
-
-
+
@@ -107,7 +100,8 @@
appController.registerDevice()}
>
Complete Setup
diff --git a/WebApp/src/routes/settings/+page.svelte b/WebApp/src/routes/settings/+page.svelte
index ceaa6d7..06bced2 100644
--- a/WebApp/src/routes/settings/+page.svelte
+++ b/WebApp/src/routes/settings/+page.svelte
@@ -8,6 +8,15 @@
import { Button } from '$lib/components/ui/button/index.js';
import { Card, CardContent, CardHeader, CardTitle } from '$lib/components/ui/card/index.js';
import { Separator } from '$lib/components/ui/separator/index.js';
+ import {
+ BarChart3,
+ ChevronRight,
+ Settings,
+ LogOut,
+ Database,
+ ShieldCheck,
+ Activity
+ } from '@lucide/svelte';
const profileName = () => $appState.session?.user?.name || 'User';
const profileEmail = () => $appState.session?.user?.email || 'user@example.com';
@@ -58,8 +67,8 @@
Settings
-
-
+
+
{profileInitial()}
@@ -70,9 +79,9 @@
-
+
- Tools
+ Tools & Diagnostics
-
Run Diagnostics
+
Run System Diagnostics
-
-
-
+
-
MinIO Storage Check
-
- Test whether the backend can still reach the object storage server.
+
+
+
MinIO Storage Check
+
+
+ Test backend connectivity to object storage.
- {isCheckingMinio ? 'Checking…' : 'Check MinIO Server'}
+ {isCheckingMinio ? 'Checking…' : 'Check Now'}
@@ -127,12 +131,19 @@
: 'border-red-500/30 bg-red-500/10 text-red-300'
}`}
>
-
- {minioStatus === 'ok' ? 'MinIO is up' : 'MinIO check failed'}
-
-
{minioStatusMessage}
+
+ {#if minioStatus === 'ok'}
+
+ {:else}
+
+ {/if}
+
+ {minioStatus === 'ok' ? 'MinIO is up' : 'MinIO check failed'}
+
+
+
{minioStatusMessage}
{#if minioCheckedAt}
-
Last checked: {formatCheckedAt(minioCheckedAt)}
+
Last checked: {formatCheckedAt(minioCheckedAt)}
{/if}
{/if}
@@ -142,21 +153,11 @@
-
-
-
+
@@ -164,17 +165,10 @@
appController.signOut()}
>
-
-
-
+
Sign Out