diff --git a/Containerfile b/Containerfile index f9244c3..b9cf14a 100644 --- a/Containerfile +++ b/Containerfile @@ -4,6 +4,6 @@ COPY package.json bun.lock ./ RUN bun install --frozen-lockfile COPY . . ENV NODE_ENV=production -ENV WATSUP_CONFIG_PATH=/app/config/default.json +ENV WATSUP_CONFIG_PATH=/app/config/default.toml EXPOSE 3000 CMD ["bun", "run", "src/index.ts"] diff --git a/README.md b/README.md index 76ce661..acb97e0 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,8 @@ In addition to some pleasantries (top 5 stories on Hacker News, weather, search) ## configuration -The dashboard is configured at runtime from JSON. The bundled -[`config/default.json`](config/default.json) reproduces the default dashboard, +The dashboard is configured at runtime from TOML. The bundled +[`config/default.toml`](config/default.toml) reproduces the default dashboard, but the container can use any mounted file by setting `WATSUP_CONFIG_PATH`: ```yaml @@ -19,59 +19,54 @@ services: ports: - "3000:3000" environment: - WATSUP_CONFIG_PATH: /run/configs/watsup.json + WATSUP_CONFIG_PATH: /run/configs/watsup.toml configs: - source: watsup-dashboard - target: /run/configs/watsup.json + target: /run/configs/watsup.toml configs: watsup-dashboard: - file: ./watsup.json + file: ./watsup.toml ``` The config contains an ordered list of sections. Add as many `services` and `widgets` sections as needed: -```json -{ - "title": "Home", - "sections": [ - { - "type": "widgets", - "columns": 4, - "widgets": [ - { "type": "weather" }, - { "type": "search", "span": 3 } - ] - }, - { - "type": "services", - "title": "Public services", - "columns": 3, - "refreshInterval": 20000, - "services": [ - { - "name": "Example", - "url": "https://example.com", - "healthUrl": "https://example.com/health", - "goodStatuses": [200, 204] - } - ] - }, - { - "type": "services", - "title": "Internal services", - "columns": 4, - "services": [ - { - "name": "Grafana", - "url": "https://grafana.example.com", - "icon": "https://example.com/grafana.png" - } - ] - } - ] -} +```toml +title = "Home" + +[[sections]] +type = "widgets" +columns = 4 + +[[sections.widgets]] +type = "weather" + +[[sections.widgets]] +type = "search" +span = 3 + +[[sections]] +type = "services" +title = "Public services" +columns = 3 +refreshInterval = 20000 + +[[sections.services]] +name = "Example" +url = "https://example.com" +healthUrl = "https://example.com/health" +goodStatuses = [200, 204] + +[[sections]] +type = "services" +title = "Internal services" +columns = 4 + +[[sections.services]] +name = "Grafana" +url = "https://grafana.example.com" +icon = "https://example.com/grafana.png" ``` `columns` and widget `span` accept values from 1 through 4. Available widget diff --git a/compose.example.yaml b/compose.example.yaml index 5e22f24..dde2932 100644 --- a/compose.example.yaml +++ b/compose.example.yaml @@ -4,11 +4,11 @@ services: ports: - "3000:3000" environment: - WATSUP_CONFIG_PATH: /run/configs/watsup.json + WATSUP_CONFIG_PATH: /run/configs/watsup.toml configs: - source: watsup-dashboard - target: /run/configs/watsup.json + target: /run/configs/watsup.toml configs: watsup-dashboard: - file: ./config/default.json + file: ./config/default.toml diff --git a/config/default.json b/config/default.json deleted file mode 100644 index 9fb9672..0000000 --- a/config/default.json +++ /dev/null @@ -1,159 +0,0 @@ -{ - "title": "watsup", - "sections": [ - { - "type": "widgets", - "columns": 4, - "widgets": [ - { "type": "weather" }, - { "type": "search", "span": 3 } - ] - }, - { - "type": "widgets", - "columns": 2, - "widgets": [ - { "type": "lobsters", "title": "Lobsters" }, - { "type": "hacker-news", "title": "Hacker News" } - ] - }, - { - "type": "services", - "title": "Community Apps+Infra", - "columns": 4, - "services": [ - { "name": "atbbs", "url": "https://atbbs.xyz/" }, - { "name": "morsels.blue", "url": "https://morsels.blue/" }, - { - "name": "aly.social", - "url": "https://pds.cute.haus", - "icon": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/bluesky.png" - }, - { - "name": "slingshot", - "url": "https://slingshot.cute.haus", - "icon": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/bluesky.png" - } - ] - }, - { - "type": "services", - "title": "Webpages", - "columns": 3, - "services": [ - { "name": "Aly Raffauf", "url": "https://aly.codes/" }, - { "name": "Switchyard", "url": "https://switchyard.aly.codes/" }, - { "name": "Morgan Tamayo", "url": "https://morgantamayo.com/" } - ] - }, - { - "type": "services", - "title": "Apps", - "columns": 4, - "services": [ - { - "name": "Pocket ID", - "url": "https://id.cute.haus/", - "icon": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/pocket-id.png" - }, - { - "name": "Jellyfin", - "url": "https://jellyfin.cute.haus", - "icon": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/jellyfin.png" - }, - { - "name": "Plex", - "url": "https://plex.cute.haus/", - "icon": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/plex.png" - }, - { - "name": "Ombi", - "url": "https://ombi.cute.haus/", - "icon": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/ombi.png" - }, - { - "name": "Audiobookshelf", - "url": "https://audiobookshelf.cute.haus/", - "icon": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/audiobookshelf.png" - }, - { - "name": "Immich", - "url": "https://immich.cute.haus/", - "icon": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/immich.png" - }, - { - "name": "Navidrome", - "url": "https://navidrome.cute.haus", - "icon": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/navidrome.png" - }, - { - "name": "Paperless", - "url": "https://paperless.cute.haus", - "icon": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/paperless-ngx.png" - }, - { - "name": "Forĝejo", - "url": "https://git.aly.codes/", - "icon": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/forgejo.png" - }, - { - "name": "Vaultwarden", - "url": "https://vault.cute.haus/", - "icon": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/vaultwarden.png" - }, - { - "name": "Uptime Kuma", - "url": "https://kuma.cute.haus/", - "icon": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/uptime-kuma.png" - } - ] - }, - { - "type": "services", - "title": "Homelab", - "columns": 4, - "services": [ - { - "name": "Sonarr", - "url": "https://sonarr.narwhal-snapper.ts.net/", - "icon": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/sonarr.png" - }, - { - "name": "Radarr", - "url": "https://radarr.narwhal-snapper.ts.net/", - "icon": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/radarr.png" - }, - { - "name": "Lidarr", - "url": "https://lidarr.narwhal-snapper.ts.net/", - "icon": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/lidarr.png" - }, - { - "name": "Prowlarr", - "url": "https://prowlarr.narwhal-snapper.ts.net/", - "icon": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/prowlarr.png" - }, - { - "name": "Bazarr", - "url": "https://bazarr.narwhal-snapper.ts.net/", - "icon": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/bazarr.png" - }, - { - "name": "Tautulli", - "url": "https://tautulli.narwhal-snapper.ts.net/", - "icon": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/tautulli.png" - }, - { - "name": "qBittorrent", - "url": "https://qbittorrent.narwhal-snapper.ts.net/", - "icon": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/qbittorrent.png" - }, - { - "name": "Grafana", - "url": "https://grafana.narwhal-snapper.ts.net/", - "icon": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/grafana.png" - } - ] - } - ] -} diff --git a/config/default.toml b/config/default.toml new file mode 100644 index 0000000..a42304f --- /dev/null +++ b/config/default.toml @@ -0,0 +1,169 @@ +title = "watsup" + +[[sections]] +type = "widgets" +columns = 4 + +[[sections.widgets]] +type = "weather" + +[[sections.widgets]] +type = "search" +span = 3 + +[[sections]] +type = "widgets" +columns = 2 + +[[sections.widgets]] +type = "lobsters" +title = "Lobsters" + +[[sections.widgets]] +type = "hacker-news" +title = "Hacker News" + +[[sections]] +type = "services" +title = "Community Apps+Infra" +columns = 4 + +[[sections.services]] +name = "atbbs" +url = "https://atbbs.xyz/" + +[[sections.services]] +name = "morsels.blue" +url = "https://morsels.blue/" + +[[sections.services]] +name = "aly.social" +url = "https://pds.cute.haus" +icon = "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/bluesky.png" + +[[sections.services]] +name = "slingshot" +url = "https://slingshot.cute.haus" +icon = "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/bluesky.png" + +[[sections]] +type = "services" +title = "Webpages" +columns = 3 + +[[sections.services]] +name = "Aly Raffauf" +url = "https://aly.codes/" + +[[sections.services]] +name = "Switchyard" +url = "https://switchyard.aly.codes/" + +[[sections.services]] +name = "Morgan Tamayo" +url = "https://morgantamayo.com/" + +[[sections]] +type = "services" +title = "Apps" +columns = 4 + +[[sections.services]] +name = "Pocket ID" +url = "https://id.cute.haus/" +icon = "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/pocket-id.png" + +[[sections.services]] +name = "Jellyfin" +url = "https://jellyfin.cute.haus" +icon = "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/jellyfin.png" + +[[sections.services]] +name = "Plex" +url = "https://plex.cute.haus/" +icon = "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/plex.png" + +[[sections.services]] +name = "Ombi" +url = "https://ombi.cute.haus/" +icon = "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/ombi.png" + +[[sections.services]] +name = "Audiobookshelf" +url = "https://audiobookshelf.cute.haus/" +icon = "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/audiobookshelf.png" + +[[sections.services]] +name = "Immich" +url = "https://immich.cute.haus/" +icon = "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/immich.png" + +[[sections.services]] +name = "Navidrome" +url = "https://navidrome.cute.haus" +icon = "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/navidrome.png" + +[[sections.services]] +name = "Paperless" +url = "https://paperless.cute.haus" +icon = "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/paperless-ngx.png" + +[[sections.services]] +name = "Forĝejo" +url = "https://git.aly.codes/" +icon = "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/forgejo.png" + +[[sections.services]] +name = "Vaultwarden" +url = "https://vault.cute.haus/" +icon = "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/vaultwarden.png" + +[[sections.services]] +name = "Uptime Kuma" +url = "https://kuma.cute.haus/" +icon = "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/uptime-kuma.png" + +[[sections]] +type = "services" +title = "Homelab" +columns = 4 + +[[sections.services]] +name = "Sonarr" +url = "https://sonarr.narwhal-snapper.ts.net/" +icon = "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/sonarr.png" + +[[sections.services]] +name = "Radarr" +url = "https://radarr.narwhal-snapper.ts.net/" +icon = "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/radarr.png" + +[[sections.services]] +name = "Lidarr" +url = "https://lidarr.narwhal-snapper.ts.net/" +icon = "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/lidarr.png" + +[[sections.services]] +name = "Prowlarr" +url = "https://prowlarr.narwhal-snapper.ts.net/" +icon = "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/prowlarr.png" + +[[sections.services]] +name = "Bazarr" +url = "https://bazarr.narwhal-snapper.ts.net/" +icon = "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/bazarr.png" + +[[sections.services]] +name = "Tautulli" +url = "https://tautulli.narwhal-snapper.ts.net/" +icon = "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/tautulli.png" + +[[sections.services]] +name = "qBittorrent" +url = "https://qbittorrent.narwhal-snapper.ts.net/" +icon = "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/qbittorrent.png" + +[[sections.services]] +name = "Grafana" +url = "https://grafana.narwhal-snapper.ts.net/" +icon = "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/grafana.png" diff --git a/src/config.ts b/src/config.ts index a42595a..b227d5c 100644 --- a/src/config.ts +++ b/src/config.ts @@ -7,7 +7,7 @@ import type { WidgetType, } from "./types"; -const DEFAULT_CONFIG_PATH = new URL("../config/default.json", import.meta.url); +const DEFAULT_CONFIG_PATH = new URL("../config/default.toml", import.meta.url); const VALID_COLUMNS = new Set([1, 2, 3, 4]); const VALID_WIDGET_TYPES = new Set([ "weather", @@ -26,10 +26,10 @@ export async function loadDashboardConfig(): Promise { let value: unknown; try { - value = await configFile.json(); + value = Bun.TOML.parse(await configFile.text()); } catch (error) { throw new Error( - `Dashboard config is not valid JSON: ${getErrorMessage(error)}`, + `Dashboard config is not valid TOML: ${getErrorMessage(error)}`, ); }