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 }}
+
+
+ {{ range $t := $downloadingTorrents }}
+ {{ $state := $t.String "state" }}
+ {{ $icon := "?" }}
+ {{ if ge ($t.Int "completed") ($t.Int "size") }}{{ $icon = "OK" }}
+ {{ else if eq $state "downloading" "forcedDL" }}{{ $icon = "v" }}
+ {{ else if eq $state "pausedDL" "stoppedDL" "pausedUP" "stalledDL" "stalledUP" "queuedDL" "queuedUP" }}{{ $icon = "||" }}
+ {{ else if eq $state "error" "missingFiles" }}{{ $icon = "!" }}
+ {{ else if eq $state "checkingDL" "checkingUP" "allocating" }}{{ $icon = "..." }}
+ {{ else if eq $state "checkingResumeData" }}{{ $icon = "R" }}
+ {{ end }}
+
+
+ {{ $icon }}
+
+
{{ $t.String "name" }}
+
+
+
+ {{ $dlSpeed := $t.Float "dlspeed" }}
+
+ {{ if eq $mode "upload" }}
+ {{ if lt $dlSpeed 1000.0 }}--{{ else }}{{ printf "%.1f MB/s" (div $dlSpeed 1000000.0) }}{{ end }}
+ {{ else }}
+ {{ if lt $dlSpeed 1024.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 }}
+
+ {{ $eta := $t.Int "eta" }}
+
+ {{ if eq $eta 8640000 }}inf
+ {{ else if gt $eta 3600 }}{{ printf "%dh %dm" (div $eta 3600) (mod (div $eta 60) 60) }}
+ {{ else if gt $eta 0 }}{{ printf "%dm" (div $eta 60) }}
+ {{ else }}--{{ end }}
+
+
+
+ {{ end }}
+
+
+ {{ end }}
+
+ {{ if eq $mode "upload" }}
+ {{ $seedingTorrents := $seeding.JSON.Array "" }}
+ {{ if gt (len $seedingTorrents) 0 }}
+
+
+ {{ range $t := $seedingTorrents }}
+ {{ $state := $t.String "state" }}
+ {{ $icon := "^" }}
+ {{ if eq $state "pausedUP" "stoppedUP" "stalledUP" "queuedUP" }}{{ $icon = "||" }}
+ {{ else if eq $state "error" "missingFiles" }}{{ $icon = "!" }}
+ {{ else if eq $state "checkingUP" }}{{ $icon = "..." }}
+ {{ else if eq $state "checkingResumeData" }}{{ $icon = "R" }}
+ {{ end }}
+
+
+ {{ $icon }}
+
+
{{ $t.String "name" }}
+
+ Ratio: {{ printf "%.2f" ($t.Float "ratio") }} |
+ Size: {{ printf "%.1f GB" (div ($t.Float "size") 1073741824.0) }}
+
+
+
+ {{ $ulSpeed := $t.Float "upspeed" }}
+
+ {{ if lt $ulSpeed 1000.0 }}--{{ else }}{{ printf "%.1f MB/s" (div $ulSpeed 1000000.0) }}{{ end }}
+
+
Upload
+
+
+ {{ end }}
+
+
+ {{ 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 "") }}
+
+ 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 }}
+
+
+
+ {{ range $indexData.JSON.Array "" }}
+
+ {{ .String "name" }}
+ {{ .String "privacy" }}
+ {{ if .Bool "enable" }}
+
+ {{ else }}
+
+ {{ end }}
+
+ {{ end }}
+
+ {{ 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