diff --git a/assets/user.css b/assets/user.css new file mode 100644 --- /dev/null +++ b/assets/user.css @@ -0,0 +1,85 @@ +.monitor-site { + --access-icon: url("/assets/icons/x.svg"); + --access-color: var(--color-negative); + --access-tooltip: "Admin-only page."; +} + +.monitor-site:has( + a:is( + [href^="https://jellyfin."], + [href^="https://seerr."], + [href^="https://anonymous-overflow."], + [href^="https://breezewiki."], + [href^="https://vaultwarden."]:not([href$="/admin"]), + [href^="https://gotify."], + [href^="https://authentik."], + [href^="https://speedtest."] + ) +) { + --access-icon: url("/assets/icons/check.svg"); + --access-color: var(--color-positive); + --access-tooltip: "User access allowed."; +} + +.monitor-site:has(a:is([href^="https://atuin."], [href^="https://firefox-sync."])) { + --access-icon: url("/assets/icons/warning.svg"); + --access-color: hsl(34, 95%, 55%); + --access-tooltip: "Ask for account creation."; +} + +.monitor-site .monitor-site-status-icon { + display: inline-flex; + align-items: center; + gap: 0.8rem; + width: auto; + position: relative; + pointer-events: none; +} + +.monitor-site .monitor-site-status-icon::before { + content: ""; + width: 2rem; + height: 2rem; + flex: 0 0 2rem; + background: var(--access-color); + opacity: 0.6; + pointer-events: auto; + mask: var(--access-icon) center / contain no-repeat; + -webkit-mask: var(--access-icon) center / contain no-repeat; +} + +.monitor-site .monitor-site-status-icon svg { + width: 2rem; + height: 2rem; + flex: 0 0 2rem; + pointer-events: none; +} + +.monitor-site .monitor-site-status-icon::after { + content: var(--access-tooltip); + position: absolute; + left: 1rem; + bottom: calc(100% + 0.8rem); + z-index: 20; + width: max-content; + max-width: 22rem; + padding: 0.6rem 0.8rem; + border: 1px solid var(--color-popover-border); + border-radius: var(--border-radius); + background: var(--color-popover-background); + color: var(--color-text-highlight); + font-size: 1.2rem; + line-height: 1.35; + box-shadow: 0 0.8rem 2rem hsl(0 0% 0% / 0.25); + opacity: 0; + pointer-events: none; + transform: translate(-50%, 0.3rem); + transition: + opacity 0.15s, + transform 0.15s; +} + +.monitor-site .monitor-site-status-icon:hover::after { + opacity: 1; + transform: translate(-50%, 0); +} diff --git a/config/glance.yml b/config/glance.yml new file mode 100644 --- /dev/null +++ b/config/glance.yml @@ -0,0 +1,25 @@ +server: + base-url: https://${GROUND_DOMAIN} + assets-path: /app/assets + proxied: true + +theme: + custom-css-file: /assets/user.css + background-color: 0 0 16 + primary-color: 43 59 81 + positive-color: 61 66 44 + negative-color: 6 96 59 + presets: + gruvbox-light: + light: true + background-color: 48 87 88 + primary-color: 20 5 22 + positive-color: 57 79 26 + negative-color: 358 100 31 + +pages: + - $include: home.yml + - $include: statistics.yml + +branding: + hide-footer: true diff --git a/config/home.yml b/config/home.yml new file mode 100644 --- /dev/null +++ b/config/home.yml @@ -0,0 +1,183 @@ +- name: Home + columns: + - size: small + widgets: + - type: monitor + cache: 8m + title: Media frontends + sites: + - title: jellyfin + url: https://jellyfin.${GROUND_DOMAIN} + icon: /assets/icons/jellyfin.svg + - title: Seerr + url: https://seerr.${GROUND_DOMAIN} + icon: /assets/icons/seerr.svg + + - type: monitor + cache: 8m + title: Privacy frontends + sites: + - title: Anonymous overflow + url: https://anonymous-overflow.${GROUND_DOMAIN} + icon: /assets/icons/anonymousoverflow.svg + basic-auth: + username: glance + password: ${AUTHENTIK_PASSWORD} + - title: Breezewiki + url: https://breezewiki.${GROUND_DOMAIN} + icon: /assets/icons/fandom.svg + basic-auth: + username: glance + password: ${AUTHENTIK_PASSWORD} + + - size: small + widgets: + - type: monitor + cache: 8m + title: Sync services + sites: + - title: Atuin + url: https://atuin.${GROUND_DOMAIN} + icon: /assets/icons/atuin.svg + - title: Firefox sync + url: https://firefox-sync.${GROUND_DOMAIN} + icon: /assets/icons/firefox.svg + + - type: monitor + cache: 8m + title: Other + sites: + - title: Vaultwarden + url: https://vaultwarden.${GROUND_DOMAIN} + icon: /assets/icons/vaultwarden.svg + - title: Authentik + url: https://authentik.${GROUND_DOMAIN} + icon: /assets/icons/authentik.svg + - title: Speedtest + url: https://speedtest.${GROUND_DOMAIN} + icon: /assets/icons/speedtest.svg + + - type: monitor + cache: 8m + title: Notifications + sites: + - title: Gotify + url: https://gotify.${GROUND_DOMAIN} + icon: /assets/icons/gotify.svg + - title: Apprise + url: https://apprise.${GROUND_DOMAIN} + icon: /assets/icons/apprise.svg + + - size: full + widgets: + - type: monitor + cache: 8m + title: Media backend + sites: + - title: Prowlarr + url: https://prowlarr.${GROUND_DOMAIN} + icon: /assets/icons/prowlarr.svg + basic-auth: + username: glance + password: ${AUTHENTIK_PASSWORD} + - title: Radarr + url: https://radarr.${GROUND_DOMAIN} + icon: /assets/icons/radarr.svg + basic-auth: + username: glance + password: ${AUTHENTIK_PASSWORD} + - title: Radarr anime + url: https://radarr-an.${GROUND_DOMAIN} + icon: /assets/icons/radarr.svg + basic-auth: + username: glance + password: ${AUTHENTIK_PASSWORD} + - title: Bazarr + url: https://bazarr.${GROUND_DOMAIN} + icon: /assets/icons/bazarr.svg + basic-auth: + username: glance + password: ${AUTHENTIK_PASSWORD} + - title: Sonarr + url: https://sonarr.${GROUND_DOMAIN} + icon: /assets/icons/sonarr.svg + basic-auth: + username: glance + password: ${AUTHENTIK_PASSWORD} + - title: Sonarr anime + url: https://sonarr-an.${GROUND_DOMAIN} + icon: /assets/icons/sonarr.svg + basic-auth: + username: glance + password: ${AUTHENTIK_PASSWORD} + - title: Tracearr + url: https://tracearr.${GROUND_DOMAIN} + icon: /assets/icons/tracearr.svg + - title: Fileflows + url: https://fileflows.${GROUND_DOMAIN} + icon: /assets/icons/fileflows.svg + basic-auth: + username: glance + password: ${AUTHENTIK_PASSWORD} + - title: Profilarr + url: https://profilarr.${GROUND_DOMAIN} + icon: /assets/icons/profilarr.svg + - title: Qui + url: https://qui.${GROUND_DOMAIN} + icon: /assets/icons/qui.svg + - title: Qui sync + url: https://qui-sync.${GROUND_DOMAIN} + icon: /assets/icons/qui.svg + basic-auth: + username: glance + password: ${AUTHENTIK_PASSWORD} + - title: Qbittorrent + url: https://qbittorrent.${GROUND_DOMAIN} + icon: /assets/icons/qbittorrent.svg + basic-auth: + username: glance + password: ${AUTHENTIK_PASSWORD} + - title: Sabnzbd + url: https://sabnzbd.${GROUND_DOMAIN} + icon: /assets/icons/sabnzbd.svg + basic-auth: + username: glance + password: ${AUTHENTIK_PASSWORD} + + - type: docker-containers + title: Media backend services + running-only: false + hide-by-default: true + containers: + unpackerr: + format-container-names: true + description: Automated archive extractions. + icon: /assets/icons/unpackerr.png + hide: false + tracearr-db: + name: Timescaledb for Tracearr + description: Time-series database, used by Tracearr. + icon: /assets/icons/timescaledb.svg + hide: false + tracearr-redis: + name: Redis for Tracearr + description: In-memory data store for caching and queues, used by Tracearr. + icon: /assets/icons/redis.svg + hide: false + + - type: monitor + cache: 8m + title: Administration + sites: + - title: Dockhand + url: https://dockhand.${GROUND_DOMAIN} + icon: /assets/icons/dockhand.png + - title: Cloudbeaver + url: https://cloudbeaver.${GROUND_DOMAIN} + icon: /assets/icons/cloudbeaver.svg + - title: Traefik + url: https://traefik.${GROUND_DOMAIN} + icon: /assets/icons/traefik.svg + - title: Vaultwarden admin + url: https://vaultwarden.${GROUND_DOMAIN}/admin + icon: /assets/icons/vaultwarden.svg diff --git a/config/statistics.yml b/config/statistics.yml new file mode 100644 --- /dev/null +++ b/config/statistics.yml @@ -0,0 +1,408 @@ +- name: Statistics + columns: + - size: small + widgets: + - type: custom-api + title: Jellyfin Stats + base-url: https://jellyfin.${GROUND_DOMAIN} + options: + url: https://jellyfin.${GROUND_DOMAIN} + key: ${JELLYFIN_API_KEY} + template: | + {{ $url := .Options.StringOr "url" "" }} + {{ $key := .Options.StringOr "key" "" }} + + {{- if or (eq $url "") (eq $key "") -}} +

Error: The URL or API key was not configured in the widget options.

+ {{- else -}} + {{- $requestUrl := printf "%s/emby/Items/Counts?api_key=%s" $url $key -}} + {{- $jellyfinData := newRequest $requestUrl | getResponse -}} + + {{- if eq $jellyfinData.Response.StatusCode 200 -}} +
+
+
+
{{ $jellyfinData.JSON.Int "MovieCount" | formatNumber }}
+
Movies
+
+
+
{{ $jellyfinData.JSON.Int "SeriesCount" | formatNumber }}
+
TV Shows
+
+
+
{{ $jellyfinData.JSON.Int "EpisodeCount" | formatNumber }}
+
Episodes
+
+
+
{{ $jellyfinData.JSON.Int "SongCount" | formatNumber }}
+
Songs
+
+
+
+ {{- else -}} +

Failed: {{ $jellyfinData.Response.Status }}

+ {{- end -}} + {{- end -}} + + - type: custom-api + title: SABnzbd Status + cache: 30s + url: https://sabnzbd.${GROUND_DOMAIN}/api?output=json&apikey=${SABNZBD_API_KEY}&mode=queue + headers: + Authorization: Basic ${AUTHENTIK_BASIC_AUTH} + Accept: application/json + template: | +
+
+
+
SPEED
+
{{ if eq (.JSON.String "queue.status") "Downloading" }}{{ .JSON.String "queue.speed" }}B/s{{ else }}Paused{{ end }}
+
+
+
TIME LEFT
+
{{ if eq (.JSON.String "queue.status") "Downloading" }}{{ .JSON.String "queue.timeleft" }}{{ else }}--:--:--{{ end }}
+
+
+
+
+
QUEUE
+
{{ .JSON.Int "queue.noofslots" }} items
+
+
+
SIZE
+
{{ .JSON.Float "queue.mb" | printf "%.1f" }}MB
+
+
+
+ + - type: custom-api + title: qBittorrent + cache: 10s + options: + view: "basic" + mode: "upload" + subrequests: + transfer: + url: https://qui.${GROUND_DOMAIN}/proxy/${QUI_API_TOKEN}/api/v2/transfer/info + seeding: + url: https://qui.${GROUND_DOMAIN}/proxy/${QUI_API_TOKEN}/api/v2/torrents/info + parameters: + filter: seeding + leeching: + url: https://qui.${GROUND_DOMAIN}/proxy/${QUI_API_TOKEN}/api/v2/torrents/info + parameters: + filter: downloading + template: | + {{ $transfer := .Subrequest "transfer" }} + {{ $seeding := .Subrequest "seeding" }} + {{ $leeching := .Subrequest "leeching" }} + + {{ if and (eq $transfer.Response.StatusCode 200) (eq $seeding.Response.StatusCode 200) (eq $leeching.Response.StatusCode 200) }} + {{ $isDetailed := eq (.Options.StringOr "view" "detailed") "detailed" }} + {{ $mode := .Options.StringOr "mode" "default" }} + + {{ if $isDetailed }} +
+
+
+ {{ $dlSpeed := $transfer.JSON.Float "dl_info_speed" }} + {{ if eq $mode "upload" }} +
{{ printf "%.1f MB/s" (div $dlSpeed 1000000.0) }}
+ {{ else }} + {{ if lt $dlSpeed 1048576.0 }} +
{{ printf "%.0f KiB/s" (div $dlSpeed 1024.0) }}
+ {{ else }} +
{{ printf "%.1f MiB/s" (div $dlSpeed 1048576.0) }}
+ {{ end }} + {{ end }} +
DOWNLOADING
+
+ + {{ if eq $mode "upload" }} +
+ {{ $ulSpeed := $transfer.JSON.Float "up_info_speed" }} +
{{ printf "%.1f MB/s" (div $ulSpeed 1000000.0) }}
+
UPLOADING
+
+ {{ end }} + +
+
{{ len ($seeding.JSON.Array "") }}
+
SEEDING
+
+ + {{ if eq $mode "default" }} +
+
{{ len ($leeching.JSON.Array "") }}
+
LEECHING
+
+ {{ end }} +
+ + {{ $downloadingTorrents := $leeching.JSON.Array "" }} + {{ if gt (len $downloadingTorrents) 0 }} +
+ +
+ {{ end }} + + {{ if eq $mode "upload" }} + {{ $seedingTorrents := $seeding.JSON.Array "" }} + {{ if gt (len $seedingTorrents) 0 }} +
+ +
+ {{ end }} + {{ end }} +
+ + {{ else }} +
+
+ {{ $dlSpeed := $transfer.JSON.Float "dl_info_speed" }} +
{{ printf "%.1f MB/s" (div $dlSpeed 1000000.0) }}
+
DOWNLOADING
+
+ {{ if eq $mode "upload" }} +
+ {{ $ulSpeed := $transfer.JSON.Float "up_info_speed" }} +
{{ printf "%.1f MB/s" (div $ulSpeed 1000000.0) }}
+
UPLOADING
+
+ {{ end }} +
+
{{ len ($seeding.JSON.Array "") }}
+
SEEDING
+
+ {{ if eq $mode "default" }} +
+
{{ len ($leeching.JSON.Array "") }}
+
LEECHING
+
+ {{ end }} +
+ {{ end }} + {{ else }} +
+

Error fetching qBittorrent data.

+

Check QUI proxy URL and token.

+
+ {{ end }} + + - size: full + widgets: + - type: split-column + widgets: + - type: custom-api + title: Prowlarr Indexers + cache: 1m + options: + url: https://prowlarr.${GROUND_DOMAIN} + base-url: https://prowlarr.${GROUND_DOMAIN} + api-key: ${PROWLARR_API_KEY} + collapse-after: 999 + template: | + {{ $apiBaseUrl := .Options.StringOr "base-url" "" }} + {{ $key := .Options.StringOr "api-key" "" }} + {{ $url := .Options.StringOr "url" "" }} + {{ $collapseAfter := .Options.IntOr "collapse-after" 5 }} + + {{ if or (eq $apiBaseUrl "") (eq $key "") (eq $url "") }} +
+
ERROR
+ + + +
+

Some options are not set or malformed.

+ {{ else }} + {{ $indexUrl := printf "%s/api/v1/indexer" $apiBaseUrl }} + {{ $indexData := newRequest $indexUrl + | withHeader "Accept" "application/json" + | withHeader "Authorization" "Basic ${AUTHENTIK_BASIC_AUTH}" + | withHeader "X-Api-Key" $key + | getResponse }} + + {{ if eq $indexData.Response.StatusCode 200 }} + + + + {{ else }} +

Failed to fetch data (Status: {{ $indexData.Response.StatusCode }})

+ {{ end }} + {{ end }} + + - type: server-stats + title: Server Statistics + servers: + - type: local + name: Euclid + hide-mountpoints-by-default: true + mountpoints: + "/mnt/root": + name: Root + hide: false + "/mnt/media": + name: Media + hide: false + "/mnt/immich": + name: Immich + hide: false + + - size: small + widgets: + - type: custom-api + cache: 1h + title: Internet Speed + title-url: https://speedtest.${GROUND_DOMAIN} + url: https://speedtest.${GROUND_DOMAIN}/api/v1/results/latest + headers: + Authorization: Bearer ${SPEEDTEST_API_TOKEN} + Accept: application/json + subrequests: + stats: + url: https://speedtest.${GROUND_DOMAIN}/api/v1/stats + headers: + Authorization: Bearer ${SPEEDTEST_API_TOKEN} + Accept: application/json + options: + showPercentageDiff: true + template: | + {{ $showPercentage := .Options.BoolOr "showPercentageDiff" true }} + {{ $stats := .Subrequest "stats" }} + +
+
+ {{ $downloadChange := percentChange (.JSON.Float "data.download_bits") ($stats.JSON.Float "data.download.avg_bits") }} + {{ if $showPercentage }} +
+ {{ $downloadChange | printf "%+.1f%%" }} +
+ {{ end }} +
{{ .JSON.Float "data.download_bits" | mul 0.000001 | printf "%.1f" }}
+
DOWNLOAD
+
+
+ {{ $uploadChange := percentChange (.JSON.Float "data.upload_bits") ($stats.JSON.Float "data.upload.avg_bits") }} + {{ if $showPercentage }} +
+ {{ $uploadChange | printf "%+.1f%%" }} +
+ {{ end }} +
{{ .JSON.Float "data.upload_bits" | mul 0.000001 | printf "%.1f" }}
+
UPLOAD
+
+
+ {{ $pingChange := percentChange (.JSON.Float "data.ping") ($stats.JSON.Float "data.ping.avg") }} + {{ if $showPercentage }} +
+ {{ $pingChange | printf "%+.1f%%" }} +
+ {{ end }} +
{{ .JSON.Float "data.ping" | printf "%.0f ms" }}
+
PING
+
+
diff --git a/assets/icons/anonymousoverflow.svg b/assets/icons/anonymousoverflow.svg new file mode 100644 --- /dev/null +++ b/assets/icons/anonymousoverflow.svg @@ -0,0 +1,1 @@ + \ No newline at end of file diff --git a/assets/icons/apprise.svg b/assets/icons/apprise.svg new file mode 100644 --- /dev/null +++ b/assets/icons/apprise.svg @@ -0,0 +1,1 @@ + \ No newline at end of file diff --git a/assets/icons/atuin.svg b/assets/icons/atuin.svg new file mode 100644 --- /dev/null +++ b/assets/icons/atuin.svg @@ -0,0 +1,1 @@ + \ No newline at end of file diff --git a/assets/icons/authentik.svg b/assets/icons/authentik.svg new file mode 100644 --- /dev/null +++ b/assets/icons/authentik.svg @@ -0,0 +1,1 @@ + \ No newline at end of file diff --git a/assets/icons/bazarr.svg b/assets/icons/bazarr.svg new file mode 100644 --- /dev/null +++ b/assets/icons/bazarr.svg @@ -0,0 +1,1 @@ + \ No newline at end of file diff --git a/assets/icons/check.svg b/assets/icons/check.svg new file mode 100644 --- /dev/null +++ b/assets/icons/check.svg @@ -0,0 +1,1 @@ + \ No newline at end of file diff --git a/assets/icons/cloudbeaver.svg b/assets/icons/cloudbeaver.svg new file mode 100644 --- /dev/null +++ b/assets/icons/cloudbeaver.svg @@ -0,0 +1,1 @@ + \ No newline at end of file diff --git a/assets/icons/dockhand.png b/assets/icons/dockhand.png new file mode 100644 --- /dev/null +++ b/assets/icons/dockhand.png diff --git a/assets/icons/fandom.svg b/assets/icons/fandom.svg new file mode 100644 --- /dev/null +++ b/assets/icons/fandom.svg @@ -0,0 +1,1 @@ + \ No newline at end of file diff --git a/assets/icons/fileflows.svg b/assets/icons/fileflows.svg new file mode 100644 --- /dev/null +++ b/assets/icons/fileflows.svg @@ -0,0 +1,1 @@ + \ No newline at end of file diff --git a/assets/icons/firefox.svg b/assets/icons/firefox.svg new file mode 100644 --- /dev/null +++ b/assets/icons/firefox.svg @@ -0,0 +1,1 @@ + \ No newline at end of file diff --git a/assets/icons/gotify.svg b/assets/icons/gotify.svg new file mode 100644 --- /dev/null +++ b/assets/icons/gotify.svg @@ -0,0 +1,1 @@ + \ No newline at end of file diff --git a/assets/icons/jellyfin.svg b/assets/icons/jellyfin.svg new file mode 100644 --- /dev/null +++ b/assets/icons/jellyfin.svg @@ -0,0 +1,1 @@ + \ No newline at end of file diff --git a/assets/icons/profilarr.svg b/assets/icons/profilarr.svg new file mode 100644 --- /dev/null +++ b/assets/icons/profilarr.svg @@ -0,0 +1,1 @@ + \ No newline at end of file diff --git a/assets/icons/prowlarr.svg b/assets/icons/prowlarr.svg new file mode 100644 --- /dev/null +++ b/assets/icons/prowlarr.svg @@ -0,0 +1,1 @@ + \ No newline at end of file diff --git a/assets/icons/qbittorrent.svg b/assets/icons/qbittorrent.svg new file mode 100644 --- /dev/null +++ b/assets/icons/qbittorrent.svg @@ -0,0 +1,1 @@ + \ No newline at end of file diff --git a/assets/icons/qui.svg b/assets/icons/qui.svg new file mode 100644 --- /dev/null +++ b/assets/icons/qui.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/assets/icons/radarr.svg b/assets/icons/radarr.svg new file mode 100644 --- /dev/null +++ b/assets/icons/radarr.svg @@ -0,0 +1,1 @@ + \ No newline at end of file diff --git a/assets/icons/redis.svg b/assets/icons/redis.svg new file mode 100644 --- /dev/null +++ b/assets/icons/redis.svg @@ -0,0 +1,1 @@ + \ No newline at end of file diff --git a/assets/icons/sabnzbd.svg b/assets/icons/sabnzbd.svg new file mode 100644 --- /dev/null +++ b/assets/icons/sabnzbd.svg @@ -0,0 +1,1 @@ + \ No newline at end of file diff --git a/assets/icons/seerr.svg b/assets/icons/seerr.svg new file mode 100644 --- /dev/null +++ b/assets/icons/seerr.svg @@ -0,0 +1,1 @@ + \ No newline at end of file diff --git a/assets/icons/sonarr.svg b/assets/icons/sonarr.svg new file mode 100644 --- /dev/null +++ b/assets/icons/sonarr.svg @@ -0,0 +1,1 @@ + \ No newline at end of file diff --git a/assets/icons/speedtest.svg b/assets/icons/speedtest.svg new file mode 100644 --- /dev/null +++ b/assets/icons/speedtest.svg @@ -0,0 +1,1 @@ + \ No newline at end of file diff --git a/assets/icons/timescaledb.svg b/assets/icons/timescaledb.svg new file mode 100644 --- /dev/null +++ b/assets/icons/timescaledb.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/assets/icons/tracearr.svg b/assets/icons/tracearr.svg new file mode 100644 --- /dev/null +++ b/assets/icons/tracearr.svg @@ -0,0 +1,1 @@ + \ No newline at end of file diff --git a/assets/icons/traefik.svg b/assets/icons/traefik.svg new file mode 100644 --- /dev/null +++ b/assets/icons/traefik.svg @@ -0,0 +1,1 @@ + \ No newline at end of file diff --git a/assets/icons/unpackerr.png b/assets/icons/unpackerr.png new file mode 100644 --- /dev/null +++ b/assets/icons/unpackerr.png diff --git a/assets/icons/vaultwarden.svg b/assets/icons/vaultwarden.svg new file mode 100644 --- /dev/null +++ b/assets/icons/vaultwarden.svg @@ -0,0 +1,1 @@ + \ No newline at end of file diff --git a/assets/icons/warning.svg b/assets/icons/warning.svg new file mode 100644 --- /dev/null +++ b/assets/icons/warning.svg @@ -0,0 +1,1 @@ + \ No newline at end of file diff --git a/assets/icons/x.svg b/assets/icons/x.svg new file mode 100644 --- /dev/null +++ b/assets/icons/x.svg @@ -0,0 +1,1 @@ + \ No newline at end of file