configurable default homepage
This commit is contained in:
10 files changed
+440
-8
No files matched your search
+15
-2
@@ -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();
|
||||
|
||||
@@ -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() });
|
||||
};
|
||||
@@ -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}
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in new issue
Block a user