diff --git a/PLAN_PROMPT.md b/PLAN_PROMPT.md index b9ef1ef..9476a3f 100644 --- a/PLAN_PROMPT.md +++ b/PLAN_PROMPT.md @@ -12,17 +12,20 @@ Build a **self-hosted web application launcher / dashboard** for a TrueNAS serve ## Tech Stack ### Framework (Full-Stack) + - **SvelteKit** — all-in-one framework: SSR, routing, API routes (`+server.ts`), form actions — single process, no separate backend needed - **Svelte 5** (runes mode) — `$state`, `$derived`, `$effect` for reactive state; compiler-based, no virtual DOM, minimal runtime - **TypeScript** — strict mode throughout ### UI & Styling + - **Tailwind CSS v4** — utility-first styling with smooth animation support - **shadcn-svelte** (Bits UI primitives) — accessible, unstyled component library; each component is a separate file - **Svelte built-in transitions** — `transition:`, `animate:`, `in:/out:` directives for page transitions, expand/collapse, hover effects - **svelte/motion** — `tweened` and `spring` stores for ambient background animations ### Data & State + - **SvelteKit load functions** — server-side data loading with automatic invalidation - **Svelte runes** (`$state`, `$derived`) — client-side reactive state (theme, sidebar, UI) - **Superforms + Zod** — type-safe form handling with progressive enhancement and server-side validation @@ -30,11 +33,13 @@ Build a **self-hosted web application launcher / dashboard** for a TrueNAS serve - **SQLite** — zero-config, file-based, perfect for single-server deployment (easy Docker volume mount, simple backups). Migrate to PostgreSQL later if needed. ### Auth + - **openid-client** — Authentik OIDC/OAuth2 integration - **bcrypt + JWT** — local auth with refresh token rotation via HTTP-only cookies - **SvelteKit hooks** (`handle`) — auth middleware, session management ### Icons + - **Lucide Svelte** — 1500+ clean SVG icons for UI chrome - **Simple Icons** (via `simple-icons` npm package) — 3000+ brand/service icons (Plex, Nextcloud, Docker, Grafana, etc.) — perfect for self-hosted app logos - **Dashboard Icons** (CDN fallback) — community-maintained self-hosted app icon set @@ -42,9 +47,11 @@ Build a **self-hosted web application launcher / dashboard** for a TrueNAS serve - **No emojis** — strictly SVG/image icons only ### Background Jobs + - **node-cron** — scheduled healthcheck pings (runs in SvelteKit server process) ### DevOps + - **Docker** — multi-stage build (SvelteKit build → Node adapter → lightweight runtime) - **docker-compose.yml** — single-command deployment with volume mounts for SQLite + uploads - **Gitea Actions** — CI/CD workflows (lint, type-check, test, Docker image push to Gitea Container Registry) @@ -56,19 +63,23 @@ Build a **self-hosted web application launcher / dashboard** for a TrueNAS serve ### 1. Authentication & Authorization #### Auth Modes (Admin-Configurable) + The system supports three auth modes, selectable by admin in settings: + - **OAuth only** — all users authenticate via Authentik (OIDC/OAuth2) - **Local only** — email/password login with optional registration - **Both** — user chooses OAuth or local login on the login page - **Guest mode** — unauthenticated users see boards marked as `guest-accessible` #### User Management + - Admin can create/edit/delete users manually - Self-registration is **disabled by default**; admin toggles it on/off - OAuth auto-provisions users on first login (maps Authentik groups to local groups) - Users have: `id`, `email`, `displayName`, `avatarUrl`, `authProvider`, `role`, `groupIds[]` #### Groups & Access Control + - **Default groups:** `admin`, `user` - Admin can create custom groups - Permissions are hierarchical: **User-level overrides > Group-level > Default** @@ -79,6 +90,7 @@ The system supports three auth modes, selectable by admin in settings: - Guest access is a separate boolean flag per board #### Session Management + - JWT access tokens stored in HTTP-only cookies (managed by SvelteKit hooks) - Refresh token rotation (7-day expiry) - Server-side session validation in `hooks.server.ts` @@ -87,6 +99,7 @@ The system supports three auth modes, selectable by admin in settings: ### 2. Apps (Service Registry) Each app represents a self-hosted service: + - **url** (required) — base URL of the service - **name** (required) — display name - **icon** — one of: Simple Icons slug, Lucide icon name, Dashboard Icons ID, or uploaded image path @@ -106,6 +119,7 @@ Each app represents a self-hosted service: Boards are the primary organizational unit — each board is a full-page layout of sections and widgets. #### Board Properties + - `name`, `icon`, `description` - `accessLevel` — per-user, per-group, guest-accessible (boolean) - `isDefault` — one board can be marked as the landing page @@ -113,33 +127,39 @@ Boards are the primary organizational unit — each board is a full-page layout - `backgroundConfig` — ambient background settings (see Appearance) #### Sections (Groups) + - Collapsible/expandable containers within a board - Properties: `title`, `icon`, `isExpanded` (default state), `order` - Contain an ordered list of widgets - Smooth expand/collapse animation (Svelte `slide` transition) #### Widgets + Widgets are the atomic content units inside sections. **App Widget (MVP):** + - Displays app icon, name, status indicator (colored dot/ring), optional description - Click opens the app URL in a new tab - Hover shows description tooltip + last healthcheck time - Visual states: online (green pulse), offline (red), degraded (yellow), unknown (gray) **Future widget types (post-MVP, design the schema to support them):** + - **Bookmark widget** — simple URL + label (no healthcheck) - **Note widget** — rich text or markdown note - **Embed widget** — iframe embed of a service - **Status widget** — aggregated status of multiple apps ### 4. Search & Navigation + - Global search bar (Cmd/Ctrl+K) — searches across all accessible apps and boards - Keyboard navigation support - Sidebar with board list (collapsible) - Breadcrumb navigation within nested views ### 5. Admin Panel + - User management (CRUD, group assignment) - Group management (CRUD, permission templates) - App management (CRUD, healthcheck config, bulk import/export) @@ -156,6 +176,7 @@ Widgets are the atomic content units inside sections. ## Non-Functional Requirements ### Appearance & UX + - **Modern, clean design** — inspired by Homarr / Heimdall / Organizr but with smoother polish - **Ambient animated backgrounds** — subtle mesh gradient, particle field, or aurora effect (configurable, can be disabled); implemented with Svelte `tweened`/`spring` + CSS/Canvas - **Customizable primary color** — HSL-based theme system; admin sets default, users can override @@ -172,6 +193,7 @@ Widgets are the atomic content units inside sections. ### File Structure (Modularity) SvelteKit route-based structure with one component per file: + ``` src/ routes/ @@ -324,6 +346,7 @@ static/ ### Database Schema (Prisma) Key models: + - `User` — id, email, password (nullable for OAuth), displayName, avatarUrl, authProvider, role - `Group` — id, name, description, isDefault - `UserGroup` — userId, groupId (many-to-many) @@ -338,6 +361,7 @@ Key models: ### API Design RESTful JSON API via SvelteKit `+server.ts` routes with consistent envelope: + ```json { "success": true, @@ -348,6 +372,7 @@ RESTful JSON API via SvelteKit `+server.ts` routes with consistent envelope: ``` Key endpoints (all under `src/routes/api/`): + - `POST /api/auth/login` — local login - `GET /api/auth/oauth/authorize` — redirect to Authentik - `GET /api/auth/oauth/callback` — handle OAuth callback @@ -393,10 +418,10 @@ services: web-app-launcher: build: . ports: - - "3000:3000" + - '3000:3000' volumes: - - ./data:/app/data # SQLite DB - - ./uploads:/app/uploads # Custom icons + - ./data:/app/data # SQLite DB + - ./uploads:/app/uploads # Custom icons environment: - DATABASE_URL=file:/app/data/launcher.db - JWT_SECRET=changeme @@ -410,6 +435,7 @@ services: ### CI/CD (Gitea Actions) `.gitea/workflows/ci.yml`: + - **On push to any branch:** lint (`eslint`), type-check (`svelte-check`), unit tests (`vitest`) - **On push to main:** build Docker image, push to Gitea Container Registry (`git.dolgolyov-family.by/alexei.dolgolyov/web-app-launcher`) - **On tag (vX.Y.Z):** build + push tagged image, create Gitea release @@ -419,6 +445,7 @@ services: ## Additional Features ### Cool Ideas (Included in Phases) + 1. **Drag-and-drop board editor** — reorder sections and widgets with `svelte-dnd-action` (Svelte-native, accessible, performant) 2. **Auto-discovery** — scan a Docker socket or Traefik API to auto-register running containers as apps 3. **Favicon auto-fetch** — if no icon is selected, attempt to fetch the favicon from the app's URL @@ -430,7 +457,9 @@ services: 9. **Keyboard-first navigation** — Vim-style `j/k` to move between apps, `Enter` to open ### MVP Scope (Phase 1) + To avoid scope creep, the MVP should include: + - Local auth + guest mode (OAuth in Phase 2) - App CRUD + healthcheck with status display - Single default board with sections and app widgets @@ -440,6 +469,7 @@ To avoid scope creep, the MVP should include: - Basic Gitea CI ### Phase 2 + - OAuth/Authentik integration - Multi-board support with per-board access control - Custom groups + granular permissions @@ -447,18 +477,214 @@ To avoid scope creep, the MVP should include: - Global search (Cmd+K) - Additional widget types -### Phase 3 -- ~~Auto-discovery (Docker/Traefik)~~ **DONE** — Phase 5 implementation: discoveryService.ts, /api/admin/discover endpoints, DiscoveryPanel.svelte, SettingsForm discovery config, i18n EN/RU -- Import/Export -- PWA -- Ping history sparklines -- User theme overrides -- Quick-add bookmarklet -- Multi-tab sync +### Phase 3 (DONE) + +- ~~Auto-discovery (Docker/Traefik)~~ **DONE** +- ~~Import/Export~~ **DONE** +- ~~PWA~~ **DONE** +- ~~Ping history sparklines~~ **DONE** +- ~~User theme overrides~~ **DONE** +- ~~Quick-add bookmarklet~~ **DONE** +- ~~Multi-tab sync~~ **DONE** + +### Phase 4 — New Widget Types + +New widget types to expand dashboard capabilities beyond app launching: + +1. **Clock / Weather Widget** + - Local time display with configurable timezone + - Optional weather via free OpenMeteo API (no API key required) + - Analog or digital clock face, minimal design + - Config: `{ timezone: string, showWeather: boolean, latitude?: number, longitude?: number, clockStyle: 'analog' | 'digital' }` + +2. **System Stats Widget** + - CPU, RAM, disk usage pulled from TrueNAS API or Glances API + - Tiny gauge/donut charts with auto-refresh + - Threshold coloring: green (< 60%) → yellow (60-85%) → red (> 85%) + - Config: `{ sourceUrl: string, sourceType: 'truenas' | 'glances' | 'custom', metrics: string[], refreshInterval: number }` + +3. **RSS/Feed Widget** + - Subscribe to any RSS/Atom feed (release notes, changelogs, security advisories) + - Shows latest N items with title + date, expandable to show summary + - Config: `{ feedUrl: string, maxItems: number, showSummary: boolean }` + +4. **Calendar Widget** + - iCal URL subscription (Nextcloud, Google Calendar, etc.) + - Compact list of today's + upcoming events + - Color-coded by calendar source + - Config: `{ icalUrls: Array<{ url: string, color: string, label: string }>, daysAhead: number }` + +5. **Markdown Widget** (upgrade from existing Note widget) + - Full markdown rendering with syntax highlighting (via `shiki` or `highlight.js`) + - Live preview split-pane edit mode + - Useful for runbooks, quick-reference docs, IP tables, cheat sheets + - Config: `{ content: string, syntaxTheme: string }` + +6. **Metric/Counter Widget** + - Single big number with label (e.g., "12 containers running", "99.7% uptime") + - Data source: static value, HTTP JSON endpoint + JSONPath, or Prometheus PromQL query + - Trend arrow (up/down/flat vs last poll) + - Config: `{ label: string, source: 'static' | 'http' | 'prometheus', value?: string, url?: string, jsonPath?: string, query?: string, unit?: string, refreshInterval: number }` + +7. **Link Group Widget** + - Compact list of related URLs (lighter than full app cards) + - Example: "Documentation" group with links to wikis, API docs, Swagger pages + - Collapsible, optional numbering and icons per link + - Config: `{ links: Array<{ label: string, url: string, icon?: string }>, collapsible: boolean }` + +8. **Camera/Stream Widget** + - MJPEG or HLS stream thumbnail from security cameras or media servers + - Click to open fullscreen stream in modal or new tab + - Auto-refresh snapshot at configurable interval + - Config: `{ streamUrl: string, type: 'mjpeg' | 'hls' | 'snapshot', refreshInterval: number, aspectRatio: string }` + +### Phase 5 — Visual & Styling Enhancements + +Polish the visual experience with advanced theming and card styles: + +1. **Glassmorphism Card Style** + - Frosted glass effect on cards (`backdrop-filter: blur(12px)` + semi-transparent bg) + - Ambient background effect bleeds through cards + - Toggle between `solid` / `glass` / `outline` card styles in theme settings + - Applies globally or per-board + +2. **Board-Level Themes** + - Each board gets its own color accent (hue/saturation) + background effect + - Example: "Work" = blue + mesh gradient, "Media" = purple + aurora, "Infra" = green + particles + - Board theme overrides global theme when viewing that board + - Smooth transition when switching boards + - Schema: add `themeHue`, `themeSaturation`, `backgroundType` to Board model + +3. **Animated Status Ring** + - Replace the static status dot with an SVG ring around the app icon + - Online = animated green fill sweep, Offline = pulsing red ring, Degraded = partial yellow arc, Unknown = gray dashed + - More visually striking, scales well with different card sizes + +4. **Card Size Options** + - Three sizes: `compact` (icon + name), `medium` (current), `large` (icon + name + description + sparkline + tags) + - Configurable per-section or per-board + - Responsive: auto-downsizes on mobile + - Schema: add `cardSize` to Section and Board models + +5. **Custom CSS Injection** + - Admin-level custom CSS textarea in system settings + - Per-board CSS overrides field + - Sanitized (strip ` + +
No audit log entries found
+| Timestamp | +User | +Action | +Entity | +Details | +
|---|---|---|---|---|
| + {new Date(log.createdAt).toLocaleString()} + | ++ {log.user?.displayName ?? log.userId ?? 'System'} + | ++ + {actionLabel(log.action)} + + | ++ {log.entityType} + {log.entityId.substring(0, 8)}... + | ++ {#if log.details && log.details !== '{}'} + + {:else} + — + {/if} + | +
+ {formatDetails(log.details)}
+ |
+ ||||
{$t('admin.custom_css_description') ?? 'System-wide custom CSS applied to all pages. Scoped to .custom-css-scope to prevent breaking core UI.'}
+ +{$errors._errors}
{/if} diff --git a/src/lib/components/admin/TagManager.svelte b/src/lib/components/admin/TagManager.svelte new file mode 100644 index 0000000..ed2b1d2 --- /dev/null +++ b/src/lib/components/admin/TagManager.svelte @@ -0,0 +1,248 @@ + + +No tags created yet
++ {recent.app.name} +
++ {formatTimeAgo(recent.clickedAt)} +
+Choose a template (optional)
+ + {#if loading} +