configurable default homepage

This commit is contained in:
jarek committed 2026-08-09 10:31:27 +02:00
1 parent 1cec7d0814
commit 86ef3f743b
10 files changed
+440 -8

No files matched your search

+15 -2
View File
@@ -61,6 +61,9 @@
let locked = $state(false);
let viewMode = $state<'grid' | 'list'>('grid');
// Where a tile click navigates (nav preference; default containers). Loaded in onMount.
let envClickPage = $state('containers');
// List view filter state
let listSearchQuery = $state('');
let listConnectionFilter = $state<string[]>([]);
@@ -658,12 +661,12 @@
return tiles.find(t => t.id === id);
}
// Handle tile click - select environment and navigate to containers
// Handle tile click - select environment and navigate to the env-click target page
function handleTileClick(envId: number) {
const tile = getTileById(envId);
if (tile?.stats) {
currentEnvironment.set({ id: envId, name: tile.stats.name });
goto('/containers');
goto(`/${envClickPage}`);
}
}
@@ -939,6 +942,16 @@
// Chrome 77+ Page Lifecycle API - fires when frozen tab is resumed
document.addEventListener('resume', handleVisibilityChange);
// Env-click target for the tiles. The landing redirect itself lives in +page.ts (runs before
// this component mounts), so reaching here means we're staying on the dashboard.
try {
const res = await fetch('/api/settings/navigation');
if (res.ok) {
const nav = (await res.json())?.effective;
if (nav?.envClickPage) envClickPage = nav.envClickPage;
}
} catch { /* default env-click */ }
// Load label filter and custom label colors
loadLabelFilter();
labelColorOverrides.load();
+20
View File
@@ -0,0 +1,20 @@
import { redirect } from '@sveltejs/kit';
import { landingRedirectTarget } from '$lib/utils/landing';
// SSR is off, so this load runs on the client before the dashboard mounts. Opening `/` redirects
// to the configured landing page here - no dashboard flash, no placeholder. The sidebar's Dashboard
// link carries `?home` to suppress the redirect (see landingRedirectTarget).
export const load = async ({ url, fetch }) => {
if (url.searchParams.has('home')) return {};
try {
const res = await fetch('/api/settings/navigation');
if (!res.ok) return {};
const nav = (await res.json())?.effective;
const target = landingRedirectTarget(nav, false);
if (target) throw redirect(307, target);
} catch (e) {
// Re-throw the redirect; swallow only a failed nav fetch (stay on the dashboard).
if (e && typeof e === 'object' && 'status' in e && 'location' in e) throw e;
}
return {};
};
@@ -0,0 +1,49 @@
import { json } from '@sveltejs/kit';
import type { RequestHandler } from '@sveltejs/kit';
import {
getGlobalNavPreferences, setGlobalNavPreferences,
getUserNavPreferences, setUserNavPreferences,
getEffectiveNavPreferences,
parseNavPatch,
type NavPreferences
} from '$lib/server/nav-preferences';
import { validateSession, isAuthEnabled } from '$lib/server/auth';
import { authorize } from '$lib/server/authorize';
// GET /api/settings/navigation — returns effective prefs (for redirect/env-click), plus the raw
// global + per-user values so the UI can show what's inherited. Auth off => global only.
export const GET: RequestHandler = async ({ cookies }) => {
const authEnabled = await isAuthEnabled();
const user = authEnabled ? await validateSession(cookies) : null;
const global = await getGlobalNavPreferences();
const userPrefs = user ? await getUserNavPreferences(user.id) : null;
const effective = await getEffectiveNavPreferences(user?.id ?? null);
return json({ effective, global, user: userPrefs });
};
// PUT /api/settings/navigation?scope=global|user
export const PUT: RequestHandler = async ({ request, url, cookies }) => {
const scope = url.searchParams.get('scope') ?? 'global';
let patch: Partial<NavPreferences>;
try {
patch = parseNavPatch(await request.json());
} catch (e) {
return json({ error: e instanceof Error ? e.message : 'Invalid request' }, { status: 400 });
}
if (scope === 'user') {
if (!(await isAuthEnabled())) return json({ error: 'Authentication is not enabled' }, { status: 400 });
const user = await validateSession(cookies);
if (!user) return json({ error: 'Not authenticated' }, { status: 401 });
await setUserNavPreferences(user.id, patch);
return json({ success: true, user: await getUserNavPreferences(user.id) });
}
// global scope — needs settings-edit permission (mirrors /api/settings/general POST)
const auth = await authorize(cookies);
if (auth.authEnabled && !(await auth.can('settings', 'edit'))) {
return json({ error: 'Permission denied' }, { status: 403 });
}
await setGlobalNavPreferences(patch);
return json({ success: true, global: await getGlobalNavPreferences() });
};
+17 -1
View File
@@ -24,7 +24,8 @@
Trash2,
TriangleAlert,
Palette,
Plus
Plus,
Compass
} from 'lucide-svelte';
import { authStore } from '$lib/stores/auth';
import { formatDateTime } from '$lib/stores/settings';
@@ -38,6 +39,7 @@
import ConfirmPopover from '$lib/components/ConfirmPopover.svelte';
import * as Table from '$lib/components/ui/table';
import ThemeSelector from '$lib/components/ThemeSelector.svelte';
import NavigationSelector from '$lib/components/NavigationSelector.svelte';
import AnimateIconsToggle from '$lib/components/AnimateIconsToggle.svelte';
import ColoredActionsToggle from '$lib/components/ColoredActionsToggle.svelte';
import { themeStore } from '$lib/stores/theme';
@@ -732,6 +734,20 @@
</Card.Content>
</Card.Root>
<!-- Navigation card (per-user overrides) -->
<Card.Root>
<Card.Header>
<Card.Title class="flex items-center gap-2">
<Compass class="w-5 h-5" />
Navigation
</Card.Title>
<Card.Description>Your landing page and environment-click target. Leave on "Use global default" to follow the workspace setting.</Card.Description>
</Card.Header>
<Card.Content>
<NavigationSelector scope="user" />
</Card.Content>
</Card.Root>
</div>
</div>
{/if}
+14 -1
View File
@@ -8,12 +8,13 @@
import { TogglePill, ToggleSwitch } from '$lib/components/ui/toggle-pill';
import CronEditor from '$lib/components/cron-editor.svelte';
import TimezoneSelector from '$lib/components/TimezoneSelector.svelte';
import { Eye, Bell, Database, Calendar, ShieldCheck, FileText, AlertTriangle, HelpCircle, Globe, Activity, Clock, Info, Save, RotateCcw, LayoutDashboard, Tags, Archive, ChevronRight, ChevronDown } from 'lucide-svelte';
import { Eye, Bell, Database, Calendar, ShieldCheck, FileText, AlertTriangle, HelpCircle, Globe, Activity, Clock, Info, Save, RotateCcw, LayoutDashboard, Tags, Archive, ChevronRight, ChevronDown, Compass } from 'lucide-svelte';
import CodeEditor from '$lib/components/CodeEditor.svelte';
import { appSettings, type DateFormat, type DownloadFormat, type EventCollectionMode, type LabelFilterMode } from '$lib/stores/settings';
import { canAccess, authStore } from '$lib/stores/auth';
import { toast } from 'svelte-sonner';
import ThemeSelector from '$lib/components/ThemeSelector.svelte';
import NavigationSelector from '$lib/components/NavigationSelector.svelte';
import AnimateIconsToggle from '$lib/components/AnimateIconsToggle.svelte';
import ColoredActionsToggle from '$lib/components/ColoredActionsToggle.svelte';
import * as Tooltip from '$lib/components/ui/tooltip';
@@ -523,6 +524,18 @@ services:
</Card.Content>
</Card.Root>
<Card.Root>
<Card.Header>
<Card.Title class="text-sm font-medium flex items-center gap-2">
<Compass class="w-4 h-4" />
Navigation
</Card.Title>
</Card.Header>
<Card.Content>
<NavigationSelector />
</Card.Content>
</Card.Root>
<Card.Root>
<Card.Header>
<Card.Title class="text-sm font-medium flex items-center gap-2">