diff --git a/Dockerfile b/Dockerfile index 1903bc5..45afa13 100644 --- a/Dockerfile +++ b/Dockerfile @@ -60,6 +60,6 @@ EXPOSE 8420 USER appuser HEALTHCHECK --interval=30s --timeout=5s --retries=3 --start-period=10s \ - CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8420/api/health')" + CMD python -c "import os, urllib.request; urllib.request.urlopen(f'http://localhost:{os.environ.get(\"NOTIFY_BRIDGE_PORT\", 8420)}/api/health')" CMD ["notify-bridge"] diff --git a/README.md b/README.md index 574a80d..f8b5aca 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,49 @@ packages/ frontend/ — SvelteKit dashboard (Svelte 5, Tailwind CSS v4) ``` -## Quick Start +## Quick Docker Deploy + +```bash +docker run -d \ + --name notify-bridge \ + --restart unless-stopped \ + -p 8420:8420 \ + -v notify-bridge-data:/data \ + -e NOTIFY_BRIDGE_SECRET_KEY=$(openssl rand -hex 32) \ + git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge:latest +``` + +Then open `http://localhost:8420` in your browser. + +### Environment Variables + +| Variable | Required | Default | Description | +| -------- | -------- | ------- | ----------- | +| `NOTIFY_BRIDGE_SECRET_KEY` | Yes | — | Secret key for JWT tokens (min 32 chars) | +| `NOTIFY_BRIDGE_PORT` | No | `8420` | Server listen port | +| `NOTIFY_BRIDGE_CORS_ALLOWED_ORIGINS` | No | `*` | Comma-separated allowed CORS origins | +| `NOTIFY_BRIDGE_DEBUG` | No | `false` | Enable debug logging | + +### Docker Compose + +```yaml +services: + notify-bridge: + image: git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge:latest + container_name: notify-bridge + restart: unless-stopped + ports: + - "8420:8420" + volumes: + - notify-bridge-data:/data + environment: + - NOTIFY_BRIDGE_SECRET_KEY=your-secret-key-min-32-characters + +volumes: + notify-bridge-data: +``` + +## Quick Start (Development) ```bash # Backend diff --git a/frontend/src/app.css b/frontend/src/app.css index 925eb5a..af791dc 100644 --- a/frontend/src/app.css +++ b/frontend/src/app.css @@ -12,7 +12,7 @@ --color-background: #f8f9fb; --color-foreground: #1a1a2e; --color-muted: #eef0f4; - --color-muted-foreground: #6b7280; + --color-muted-foreground: #525866; --color-border: #e2e4ea; --color-primary: #0d9488; --color-primary-foreground: #ffffff; @@ -34,6 +34,15 @@ --font-sans: 'DM Sans', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; --font-mono: 'JetBrains Mono', ui-monospace, 'Cascadia Code', 'Consolas', monospace; --radius: 0.625rem; + /* Layered z-index scale — refer to these instead of ad-hoc numbers. + Ordered: base < sticky < dropdown < overlay < modal < tooltip < toast */ + --z-base: 1; + --z-sticky: 10; + --z-dropdown: 30; + --z-overlay: 40; + --z-modal: 50; + --z-tooltip: 60; + --z-toast: 70; } /* Dark theme overrides */ diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index 14a4267..f0f0cba 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -4,6 +4,13 @@ const API_BASE = '/api'; +/** Normalize a caught error to a user-safe message. */ +export function errMsg(err: unknown, fallback = 'Unexpected error'): string { + if (err instanceof Error && err.message) return err.message; + if (typeof err === 'string' && err) return err; + return fallback; +} + function getToken(): string | null { if (typeof window === 'undefined') return null; return localStorage.getItem('access_token'); diff --git a/frontend/src/lib/components/Hint.svelte b/frontend/src/lib/components/Hint.svelte index 1e27881..372fe09 100644 --- a/frontend/src/lib/components/Hint.svelte +++ b/frontend/src/lib/components/Hint.svelte @@ -21,20 +21,22 @@ {#if visible} -