diff --git a/web/src/api/client.ts b/web/src/api/client.ts index 0523f44..3db4d67 100644 --- a/web/src/api/client.ts +++ b/web/src/api/client.ts @@ -14,11 +14,12 @@ api.interceptors.request.use((config) => { return config; }); -// Handle 401 responses +// Handle 401 responses (skip login endpoint — 401 there means bad credentials, not expired session) api.interceptors.response.use( (response) => response, (error) => { - if (error.response?.status === 401) { + const url = error.config?.url ?? ""; + if (error.response?.status === 401 && !url.includes("/auth/login")) { localStorage.removeItem("cb_admin_token"); window.location.href = "/login"; } diff --git a/web/src/stores/settings.ts b/web/src/stores/settings.ts index 8f0a73d..10ae34a 100644 --- a/web/src/stores/settings.ts +++ b/web/src/stores/settings.ts @@ -7,6 +7,8 @@ export interface AppSettings { maxDevices: number; autoSync: boolean; theme: "light" | "dark" | "system"; + sessionTimeoutMinutes: number; + language: string; } const DEFAULT_SETTINGS: AppSettings = { @@ -14,6 +16,8 @@ const DEFAULT_SETTINGS: AppSettings = { maxDevices: 10, autoSync: true, theme: "system", + sessionTimeoutMinutes: 60, + language: "en", }; export const useSettingsStore = defineStore("settings", () => { @@ -25,8 +29,6 @@ export const useSettingsStore = defineStore("settings", () => { try { const { data } = await api.get("/settings"); settings.value = { ...DEFAULT_SETTINGS, ...data }; - } catch { - // Use defaults if settings endpoint doesn't exist yet } finally { loading.value = false; } diff --git a/web/src/views/CookiesView.vue b/web/src/views/CookiesView.vue index 9e06dcf..1f51492 100644 --- a/web/src/views/CookiesView.vue +++ b/web/src/views/CookiesView.vue @@ -1,6 +1,5 @@ diff --git a/web/src/views/DashboardView.vue b/web/src/views/DashboardView.vue index 247a283..0b15494 100644 --- a/web/src/views/DashboardView.vue +++ b/web/src/views/DashboardView.vue @@ -8,6 +8,8 @@ interface DashboardData { onlineDevices: number; totalCookies: number; uniqueDomains: number; + syncCount: number; + uptimeSeconds: number; } interface DeviceSummary { @@ -21,6 +23,7 @@ interface DeviceSummary { const dashboard = ref(null); const devices = ref([]); const loading = ref(true); +const error = ref(null); const offlineDevices = computed( () => (dashboard.value?.totalDevices ?? 0) - (dashboard.value?.onlineDevices ?? 0), @@ -35,45 +38,60 @@ function platformIcon(platform: string): string { return "device"; } -onMounted(async () => { +function formatUptime(seconds: number): string { + const d = Math.floor(seconds / 86400); + const h = Math.floor((seconds % 86400) / 3600); + if (d > 0) return `${d}d ${h}h`; + const m = Math.floor((seconds % 3600) / 60); + if (h > 0) return `${h}h ${m}m`; + return `${m}m`; +} + +async function fetchData() { + loading.value = true; + error.value = null; try { - const [dashRes, devRes] = await Promise.all([ - api.get("/dashboard"), - api.get("/devices"), - ]); + const dashRes = await api.get("/dashboard"); dashboard.value = dashRes.data; + } catch { + error.value = "Failed to load dashboard data"; + } + try { + const devRes = await api.get("/devices"); devices.value = devRes.data.devices ?? []; } catch { - // Server might be down - } finally { - loading.value = false; + // Devices list is optional — dashboard still shows stats } -}); + loading.value = false; +} + +onMounted(fetchData);