Files
web-app-launcher/PLAN_PROMPT.md
T

470 lines
19 KiB
Markdown

# Web App Launcher — Implementation Plan Prompt
## Project Overview
Build a **self-hosted web application launcher / dashboard** for a TrueNAS server environment. The app serves as a centralized portal to organize, discover, and navigate to self-hosted services (Plex, Nextcloud, Gitea, Home Assistant, etc.) via customizable boards with live health indicators.
**Repository:** `https://git.dolgolyov-family.by/alexei.dolgolyov/web-app-launcher`
**Git user:** `alexei.dolgolyov` / `dolgolyov.alexei@gmail.com` (Gitea instance)
---
## 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
- **Prisma ORM** — type-safe database access, migrations, seeding
- **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
- **Custom upload** — allow users to upload SVG/PNG icons as a fallback
- **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)
---
## Functional Requirements
### 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**
- Permission model per entity (board, section, app):
- `view` — can see the entity
- `edit` — can modify the entity
- `admin` — full control (delete, manage access)
- 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`
- Logout invalidates refresh token
### 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
- **description** (optional) — short text shown on hover/expand
- **category/tags** (optional) — for filtering/search
- **healthcheck** configuration:
- `enabled` (default: true)
- `interval` (default: 60s, min: 10s, max: 3600s)
- `method` — HTTP HEAD/GET to the URL (or custom endpoint)
- `expectedStatus` — default 200, configurable (e.g., 401 is "alive" for auth-protected services)
- `timeout` — max wait before marking as down (default: 5s)
- **Status**: `online | offline | degraded | unknown` — derived from healthcheck results
- Backend runs healthchecks on a per-app schedule; results are cached and pushed to frontend via polling (SvelteKit invalidation) or SSE
### 3. Boards
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
- `layout` — grid-based responsive 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)
- Board management (CRUD, access control)
- System settings:
- Auth mode selection
- Registration toggle
- OAuth provider configuration (client ID, secret, discovery URL)
- Default theme / primary color
- Healthcheck global defaults
---
## 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
- **Dark / Light / System theme** — with smooth CSS transition
- **Smooth animations everywhere** (using Svelte built-in transitions):
- Page transitions (`in:fade`, `out:fly`)
- Section expand/collapse (`transition:slide`)
- Card hover effects (subtle scale + shadow lift via CSS + Svelte `spring`)
- Status indicator pulse (CSS `@keyframes`)
- Skeleton loading states
- **Responsive** — works on desktop, tablet, and mobile
- **No emojis as icons** — use SVG icon libraries exclusively
### File Structure (Modularity)
SvelteKit route-based structure with one component per file:
```
src/
routes/
+layout.svelte # Root layout (sidebar, header, ambient bg)
+layout.server.ts # Root auth check, load user session
+page.svelte # Dashboard / default board redirect
login/
+page.svelte # Login page
+page.server.ts # Login form action
register/
+page.svelte # Registration page (if enabled)
+page.server.ts # Register form action
auth/
oauth/
authorize/+server.ts # Redirect to Authentik
callback/+server.ts # Handle OAuth callback
refresh/+server.ts # Token refresh
boards/
+page.svelte # Board list
+page.server.ts # Load boards (filtered by permissions)
[boardId]/
+page.svelte # Board view
+page.server.ts # Load board with sections/widgets
edit/
+page.svelte # Board editor
+page.server.ts # Board update actions
apps/
+page.svelte # App registry list
+page.server.ts # Load apps
[appId]/
+server.ts # App CRUD API
status/+server.ts # Healthcheck status API
admin/
+layout.svelte # Admin layout (admin-only guard)
+layout.server.ts # Admin auth check
users/
+page.svelte # User management
+page.server.ts # User CRUD actions
groups/
+page.svelte # Group management
+page.server.ts # Group CRUD actions
settings/
+page.svelte # System settings
+page.server.ts # Settings update actions
api/
apps/+server.ts # App CRUD REST endpoints
apps/[id]/+server.ts # Single app operations
apps/[id]/status/+server.ts # Healthcheck status
boards/+server.ts # Board CRUD
boards/[id]/+server.ts # Single board operations
boards/[id]/sections/+server.ts # Section CRUD
boards/[id]/sections/[sid]/+server.ts # Single section
boards/[id]/sections/[sid]/widgets/+server.ts # Widget CRUD
users/+server.ts # User management (admin)
groups/+server.ts # Group management (admin)
admin/settings/+server.ts # System settings (admin)
search/+server.ts # Global search
health/+server.ts # App healthcheck endpoint
lib/
components/
ui/ # shadcn-svelte primitives (Button, Dialog, etc.)
layout/
Sidebar.svelte
Header.svelte
MainLayout.svelte
ThemeToggle.svelte
board/
Board.svelte
BoardHeader.svelte
BoardCard.svelte
section/
Section.svelte
SectionHeader.svelte
SectionCollapsible.svelte
widget/
AppWidget.svelte
AppWidgetStatus.svelte
WidgetContainer.svelte
WidgetGrid.svelte
app/
AppForm.svelte
AppIconPicker.svelte
AppHealthBadge.svelte
AppCard.svelte
auth/
LoginForm.svelte
OAuthButton.svelte
RegisterForm.svelte
admin/
UserTable.svelte
GroupTable.svelte
SettingsForm.svelte
PermissionEditor.svelte
search/
SearchDialog.svelte
SearchResult.svelte
SearchTrigger.svelte
background/
AmbientBackground.svelte
MeshGradient.svelte
ParticleField.svelte
AuroraEffect.svelte
server/
services/
authService.ts
appService.ts
boardService.ts
healthcheckService.ts
userService.ts
groupService.ts
permissionService.ts
middleware/
authenticate.ts
authorize.ts
guestAccess.ts
jobs/
healthcheckScheduler.ts
utils/
jwt.ts
password.ts
iconResolver.ts
stores/
theme.svelte.ts # Svelte 5 rune-based store
ui.svelte.ts
search.svelte.ts
utils/
constants.ts
validators.ts
helpers.ts
types/
auth.ts
app.ts
board.ts
widget.ts
user.ts
group.ts
permission.ts
hooks.server.ts # Auth middleware, session injection
hooks.client.ts # Client-side error handling
app.css # Tailwind base + theme variables
app.d.ts # SvelteKit type augmentation (locals, session)
prisma/
schema.prisma
migrations/
seed.ts
static/
uploads/ # User-uploaded icons
```
### 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)
- `App` — id, name, url, icon, iconType, description, category, healthcheckEnabled, healthcheckInterval, healthcheckMethod, healthcheckExpectedStatus, healthcheckTimeout, createdById
- `AppStatus` — id, appId, status, responseTime, checkedAt (latest N records for history)
- `Board` — id, name, icon, description, isDefault, isGuestAccessible, backgroundConfig (JSON), createdById
- `Section` — id, boardId, title, icon, order, isExpandedByDefault
- `Widget` — id, sectionId, type, order, config (JSON — for AppWidget: `{ appId }`)
- `Permission` — id, entityType (board/app), entityId, targetType (user/group), targetId, level (view/edit/admin)
- `SystemSettings` — singleton row: authMode, registrationEnabled, oauthConfig (encrypted JSON), defaultTheme, defaultPrimaryColor, healthcheckDefaults (JSON)
### API Design
RESTful JSON API via SvelteKit `+server.ts` routes with consistent envelope:
```json
{
"success": true,
"data": { ... },
"error": null,
"meta": { "total": 100, "page": 1, "limit": 20 }
}
```
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
- `POST /api/auth/register` — self-registration (if enabled)
- `POST /api/auth/refresh` — token refresh
- `GET/POST/PATCH/DELETE /api/apps` — app CRUD
- `GET /api/apps/:id/status` — latest healthcheck status
- `GET/POST/PATCH/DELETE /api/boards` — board CRUD (filtered by user permissions)
- `GET/POST/PATCH/DELETE /api/boards/:id/sections` — section CRUD
- `GET/POST/PATCH/DELETE /api/boards/:id/sections/:sid/widgets` — widget CRUD
- `GET/POST/PATCH/DELETE /api/users` — user management (admin)
- `GET/POST/PATCH/DELETE /api/groups` — group management (admin)
- `GET/PATCH /api/admin/settings` — system settings (admin)
- `GET /api/search?q=...` — global search
- `GET /api/health` — app health endpoint for Docker healthcheck
### Docker Deployment
```dockerfile
# Multi-stage: build SvelteKit with Node adapter → slim runtime
FROM node:22-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npx prisma generate
RUN npm run build
FROM node:22-alpine AS runtime
WORKDIR /app
COPY --from=builder /app/build ./build
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./
COPY --from=builder /app/prisma ./prisma
EXPOSE 3000
HEALTHCHECK CMD wget -q --spider http://localhost:3000/api/health || exit 1
ENTRYPOINT ["sh", "-c", "npx prisma migrate deploy && node build"]
```
```yaml
# docker-compose.yml
services:
web-app-launcher:
build: .
ports:
- "3000:3000"
volumes:
- ./data:/app/data # SQLite DB
- ./uploads:/app/uploads # Custom icons
environment:
- DATABASE_URL=file:/app/data/launcher.db
- JWT_SECRET=changeme
- OAUTH_CLIENT_ID=
- OAUTH_CLIENT_SECRET=
- OAUTH_DISCOVERY_URL=
- ORIGIN=http://localhost:3000
restart: unless-stopped
```
### 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
---
## 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
4. **Ping history sparkline** — tiny inline SVG chart showing uptime over last 24h per app
5. **Import/Export** — JSON export of entire config for backup/migration
6. **PWA support** — installable on mobile home screen with offline shell (SvelteKit service worker support)
7. **Multi-tab sync** — broadcast theme/board changes across tabs via BroadcastChannel API
8. **Quick-add bookmarklet** — browser bookmarklet that adds current page as an app via the API
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
- Admin panel (user/app/board management)
- Dark theme + ambient background
- Docker deployment
- Basic Gitea CI
### Phase 2
- OAuth/Authentik integration
- Multi-board support with per-board access control
- Custom groups + granular permissions
- Drag-and-drop reordering
- Global search (Cmd+K)
- Additional widget types
### Phase 3
- Auto-discovery (Docker/Traefik)
- Import/Export
- PWA
- Ping history sparklines
- User theme overrides
- Quick-add bookmarklet
- Multi-tab sync
---
## Constraints & Preferences
- **Immutable data patterns** — never mutate objects in place; return new copies
- **Small files** — one component/service per file, 200-400 lines typical, 800 max
- **Comprehensive error handling** — every API call, every user action
- **Input validation** — Zod schemas shared between client (Superforms) and server
- **No hardcoded secrets** — all sensitive config via environment variables
- **Conventional commits** — `feat:`, `fix:`, `refactor:`, etc.
- **80%+ test coverage target** — unit tests with Vitest, integration tests for API routes, E2E with Playwright
- **Progressive enhancement** — SvelteKit form actions work without JavaScript where possible