1c0a7cb850
Phase 4 — New Widget Types: - Clock/Weather, System Stats, RSS/Feed, Calendar, Markdown, Metric/Counter, Link Group, Camera/Stream widgets - Backend services with caching for each data source - Full creation form with dynamic config fields per type Phase 5 — Visual & Styling Enhancements: - Glassmorphism card style (solid/glass/outline) - Board-level themes with per-board hue/saturation - Animated SVG status rings replacing static dots - Card size options (compact/medium/large) - Custom CSS injection (admin + per-board, sanitized) - Wallpaper backgrounds with blur/overlay/parallax Phase 6 — Functional Features: - Favorites bar with drag-and-drop reordering - Recent apps tracking with privacy toggle - Uptime dashboard page (/status, guest-accessible) - Notifications system (Discord/Slack/Telegram/HTTP webhooks) - App tags with filtering in board view - Multi-URL app cards with expandable sub-links - Personal API tokens with scoped permissions - Audit log with retention and admin viewer Phase 7 — Quality of Life: - Onboarding wizard (5-step first-launch setup) - App URL health preview with favicon/title detection - Board templates (4 built-in + custom import/export) - Keyboard shortcut overlay (j/k nav, 1-9 boards, ? help) 212 files changed, 15641 insertions, 980 deletions. Build, lint, type check, and 222 tests all pass.
153 lines
7.9 KiB
Markdown
153 lines
7.9 KiB
Markdown
# Phase 2: New Widget Services & APIs
|
|
|
|
**Status:** ✅ Complete
|
|
**Parent plan:** [PLAN.md](./PLAN.md)
|
|
**Domain:** backend
|
|
|
|
## Objective
|
|
|
|
Implement backend services and API routes for all 8 new widget types. Each widget type that fetches external data needs a dedicated service for data fetching, caching, and error handling.
|
|
|
|
## Tasks
|
|
|
|
### 2.1 Clock/Weather service
|
|
|
|
- [x] Create `src/lib/server/services/weatherService.ts`
|
|
- Fetch weather from OpenMeteo API (no API key required): `https://api.open-meteo.com/v1/forecast`
|
|
- Accept latitude/longitude, return current temp, condition, icon
|
|
- Cache responses for 30 minutes (in-memory Map with TTL)
|
|
- Graceful fallback when API is unreachable
|
|
- [x] Create `src/routes/api/widgets/weather/+server.ts` — GET endpoint with lat/lng query params
|
|
|
|
### 2.2 System Stats service
|
|
|
|
- [x] Create `src/lib/server/services/systemStatsService.ts`
|
|
- Adapter pattern: `TrueNasAdapter`, `GlancesAdapter`, `CustomAdapter`
|
|
- Each adapter fetches metrics (CPU, RAM, disk) from its source URL
|
|
- Return normalized `{ metric: string, value: number, unit: string }[]`
|
|
- Configurable refresh interval, in-memory cache per source URL
|
|
- [x] Create `src/routes/api/widgets/system-stats/+server.ts` — GET with sourceUrl, sourceType params
|
|
|
|
### 2.3 RSS/Feed service
|
|
|
|
- [x] Create `src/lib/server/services/rssFeedService.ts`
|
|
- Fetch and parse RSS/Atom feeds (use built-in XML parsing or lightweight lib)
|
|
- Return `{ title, link, pubDate, summary }[]` limited to maxItems
|
|
- Cache feeds for 15 minutes per URL
|
|
- Handle malformed feeds gracefully
|
|
- [x] Create `src/routes/api/widgets/rss/+server.ts` — GET with feedUrl, maxItems params
|
|
|
|
### 2.4 Calendar service
|
|
|
|
- [x] Create `src/lib/server/services/calendarService.ts`
|
|
- Fetch and parse iCal (.ics) files from URLs
|
|
- Extract events within daysAhead range
|
|
- Return `{ summary, start, end, location?, calendarLabel, calendarColor }[]`
|
|
- Cache per URL for 30 minutes
|
|
- Handle multiple calendar URLs, merge and sort by start time
|
|
- [x] Create `src/routes/api/widgets/calendar/+server.ts` — POST with icalUrls[], daysAhead
|
|
|
|
### 2.5 Metric/Counter service
|
|
|
|
- [x] Create `src/lib/server/services/metricService.ts`
|
|
- `fetchHttpMetric(url, jsonPath)` — fetch JSON endpoint, extract value via JSONPath
|
|
- `fetchPrometheusMetric(url, query)` — query Prometheus API, extract instant value
|
|
- `getStaticMetric(value)` — passthrough for static values
|
|
- Store previous value for trend calculation (up/down/flat)
|
|
- Cache per source for configurable interval
|
|
- [x] Create `src/routes/api/widgets/metric/+server.ts` — GET with source type params
|
|
|
|
### 2.6 Camera/Stream proxy
|
|
|
|
- [x] Create `src/lib/server/services/cameraService.ts`
|
|
- `fetchSnapshot(url)` — proxy HTTP request to camera URL, return image buffer
|
|
- Validate URL is http/https only
|
|
- Timeout after 10s
|
|
- Rate limit: max 1 request per 5s per URL
|
|
- [x] Create `src/routes/api/widgets/camera/+server.ts` — GET with streamUrl param, returns proxied image
|
|
|
|
### 2.7 Widget data aggregation endpoint
|
|
|
|
- [x] Create `src/routes/api/widgets/data/+server.ts` — generic endpoint that routes to the correct service based on widget type and config
|
|
- POST with `{ widgetType, config }` body
|
|
- Routes to weatherService, systemStatsService, etc. based on type
|
|
- Returns unified response format
|
|
|
|
### 2.8 Update existing widget service
|
|
|
|
- [x] Update `src/lib/server/services/boardService.ts` to handle new widget types in create/update operations
|
|
- [x] Ensure new widget type configs are validated with the correct Zod schema on create/update
|
|
|
|
## Files to Modify/Create
|
|
|
|
- `src/lib/server/services/weatherService.ts` — new
|
|
- `src/lib/server/services/systemStatsService.ts` — new
|
|
- `src/lib/server/services/rssFeedService.ts` — new
|
|
- `src/lib/server/services/calendarService.ts` — new
|
|
- `src/lib/server/services/metricService.ts` — new
|
|
- `src/lib/server/services/cameraService.ts` — new
|
|
- `src/routes/api/widgets/weather/+server.ts` — new
|
|
- `src/routes/api/widgets/system-stats/+server.ts` — new
|
|
- `src/routes/api/widgets/rss/+server.ts` — new
|
|
- `src/routes/api/widgets/calendar/+server.ts` — new
|
|
- `src/routes/api/widgets/metric/+server.ts` — new
|
|
- `src/routes/api/widgets/camera/+server.ts` — new
|
|
- `src/routes/api/widgets/data/+server.ts` — new
|
|
- `src/lib/server/services/boardService.ts` — modify
|
|
|
|
## Acceptance Criteria
|
|
|
|
- Each service handles errors gracefully (network failures, malformed responses, timeouts)
|
|
- In-memory caching prevents excessive external API calls
|
|
- All API routes use consistent envelope response format
|
|
- All user inputs validated with Zod schemas from Phase 1
|
|
- Camera proxy validates URLs and prevents SSRF (allowlist http/https, no private IPs)
|
|
- Services are stateless (cache is ephemeral, no DB state needed for widget data)
|
|
|
|
## Notes
|
|
|
|
- OpenMeteo API is free, no key needed: `https://api.open-meteo.com/v1/forecast?latitude=X&longitude=Y¤t_weather=true`
|
|
- For RSS parsing, consider using a lightweight approach (DOMParser or regex) to avoid adding a heavy dependency. If needed, `fast-xml-parser` is a good lightweight option.
|
|
- iCal parsing: use `node-ical` or hand-parse VEVENT blocks
|
|
- JSONPath extraction: use simple dot-notation traversal rather than a full JSONPath library
|
|
- SSRF protection for camera proxy: reject private IP ranges (10.x, 172.16-31.x, 192.168.x, 127.x, ::1)
|
|
|
|
## Review Checklist
|
|
|
|
- [x] All tasks completed
|
|
- [x] Code follows project conventions
|
|
- [ ] No unintended side effects
|
|
- [ ] Build passes (Big Bang: code quality check only)
|
|
- [ ] Tests pass (Big Bang: skipped for intermediate phase)
|
|
|
|
## Handoff to Next Phase
|
|
|
|
### What was done
|
|
|
|
- Created 6 new backend services: weatherService, systemStatsService, rssFeedService, calendarService, metricService, cameraService
|
|
- Created 7 new API routes under `/api/widgets/`: weather, system-stats, rss, calendar, metric, camera, data (aggregation)
|
|
- Updated boardService to validate widget configs against Zod schemas on create/update
|
|
- Updated boardService to pass through new theme/visual fields (themeHue, themeSaturation, backgroundType, cardSize, wallpaperUrl, wallpaperBlur, wallpaperOverlay, customCss) and section cardSize
|
|
- All services use in-memory caching with TTL (Map + expiry timestamps)
|
|
- Camera proxy includes SSRF protection (blocks private IPs, localhost, link-local) and rate limiting (1 req/5s per URL)
|
|
- RSS service uses lightweight regex-based XML parsing (no external dependency)
|
|
- Calendar service uses hand-parsed VEVENT blocks from iCal text (no external dependency)
|
|
- Metric service supports dot-notation JSONPath extraction (no external dependency)
|
|
|
|
### What the next phase needs to know
|
|
|
|
- All widget data endpoints follow the pattern: `/api/widgets/{type}` with GET (or POST for calendar)
|
|
- The aggregation endpoint `/api/widgets/data` accepts POST with `{ widgetType, config }` and routes to the correct service
|
|
- Camera endpoint returns raw image binary (not JSON envelope) for direct `<img>` src usage
|
|
- Markdown and LinkGroup widget types return no-op from the aggregation endpoint (they are client-side only)
|
|
- Clock widget without weather enabled also returns no-op (time is client-side)
|
|
- All services export a `clearCache()` function for testing/manual refresh
|
|
- The `validateStreamUrl()` function on cameraService is exported for reuse (used by aggregation endpoint)
|
|
|
|
### Potential concerns
|
|
|
|
- RSS/Atom XML parsing uses regex, which handles common feeds but may fail on exotic feed formats. If issues arise, consider adding `fast-xml-parser` as a dependency.
|
|
- iCal parsing handles standard VEVENT blocks but does not support RRULE (recurring events). A future enhancement could add recurrence expansion.
|
|
- SSRF protection checks IP format only at the URL level — DNS rebinding attacks could bypass hostname checks. For production hardening, consider resolving DNS before connecting.
|
|
- System stats adapters assume specific API shapes for Glances and Prometheus. Custom adapter is a generic JSON fallback.
|