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.
7.9 KiB
7.9 KiB
Phase 2: New Widget Services & APIs
Status: ✅ Complete Parent plan: 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
- 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
- Fetch weather from OpenMeteo API (no API key required):
- Create
src/routes/api/widgets/weather/+server.ts— GET endpoint with lat/lng query params
2.2 System Stats service
- 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
- Adapter pattern:
- Create
src/routes/api/widgets/system-stats/+server.ts— GET with sourceUrl, sourceType params
2.3 RSS/Feed service
- 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
- Create
src/routes/api/widgets/rss/+server.ts— GET with feedUrl, maxItems params
2.4 Calendar service
- 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
- Create
src/routes/api/widgets/calendar/+server.ts— POST with icalUrls[], daysAhead
2.5 Metric/Counter service
- Create
src/lib/server/services/metricService.tsfetchHttpMetric(url, jsonPath)— fetch JSON endpoint, extract value via JSONPathfetchPrometheusMetric(url, query)— query Prometheus API, extract instant valuegetStaticMetric(value)— passthrough for static values- Store previous value for trend calculation (up/down/flat)
- Cache per source for configurable interval
- Create
src/routes/api/widgets/metric/+server.ts— GET with source type params
2.6 Camera/Stream proxy
- Create
src/lib/server/services/cameraService.tsfetchSnapshot(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
- Create
src/routes/api/widgets/camera/+server.ts— GET with streamUrl param, returns proxied image
2.7 Widget data aggregation endpoint
- 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
- POST with
2.8 Update existing widget service
- Update
src/lib/server/services/boardService.tsto handle new widget types in create/update operations - 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— newsrc/lib/server/services/systemStatsService.ts— newsrc/lib/server/services/rssFeedService.ts— newsrc/lib/server/services/calendarService.ts— newsrc/lib/server/services/metricService.ts— newsrc/lib/server/services/cameraService.ts— newsrc/routes/api/widgets/weather/+server.ts— newsrc/routes/api/widgets/system-stats/+server.ts— newsrc/routes/api/widgets/rss/+server.ts— newsrc/routes/api/widgets/calendar/+server.ts— newsrc/routes/api/widgets/metric/+server.ts— newsrc/routes/api/widgets/camera/+server.ts— newsrc/routes/api/widgets/data/+server.ts— newsrc/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-parseris a good lightweight option. - iCal parsing: use
node-icalor 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
- All tasks completed
- 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/dataaccepts 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-parseras 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.