diff --git a/assets/dev.png b/assets/dev.png
new file mode 100644
index 0000000..952cbba
Binary files /dev/null and b/assets/dev.png differ
diff --git a/assets/user.css b/assets/user.css
new file mode 100644
index 0000000..4d0608e
--- /dev/null
+++ b/assets/user.css
@@ -0,0 +1,13 @@
+@import url('https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,300..800;1,300..800&display=swap');
+
+body, html {
+ font-family: 'Open Sans', sans-serif !important; /* Use !important cautiously if needed to override defaults */
+background: #3A5F5F;
+background: radial-gradient(circle, rgba(58, 95, 95, 1) 0%, rgba(33, 33, 33, 1) 100%);
+ color: #e7ebeb;
+}
+
+/* You might need more specific selectors depending on what you want to change */
+.widget-content {
+ font-family: 'Open Sans', sans-serif !important;
+}
\ No newline at end of file
diff --git a/config/backup.yml b/config/backup.yml
new file mode 100644
index 0000000..e7b5de8
--- /dev/null
+++ b/config/backup.yml
@@ -0,0 +1,1562 @@
+- name: Home
+ columns:
+ - size: small
+ widgets:
+ - type: calendar
+ first-day-of-week: sunday
+ - type: clock # Added Clock widget
+ hour-format: 12h # Assuming 12h based on weather widget preference
+ timezones:
+ - timezone: America/Los_Angeles # San Francisco uses this timezone
+ label: San Francisco
+ - timezone: America/New_York
+ label: New York
+ - timezone: Europe/London
+ label: London
+ - timezone: Europe/Amsterdam
+ label: Amsterdam
+ - timezone: Asia/Kolkata # New Delhi uses this timezone
+ label: New Delhi
+ - timezone: Asia/Tokyo
+ label: Tokyo
+ - type: custom-api
+ title: Astronomy Picture of the Day
+ cache: 1d
+ url: https://api.nasa.gov/planetary/apod?api_key=${NASA_API_KEY}
+ headers:
+ Accept: application/json
+ template: |
+ {{- if eq (.JSON.String "media_type") "image" -}}
+
+ {{- else -}}
+
+ No image available today.
+
+ {{- end }}
+
+ - size: full
+ widgets:
+ - type: search
+ search-engine: google
+ bangs: # Added bangs section
+ - title: YouTube
+ shortcut: "!yt"
+ url: https://www.youtube.com/results?search_query={QUERY}
+ - title: Wikipedia
+ shortcut: "!w"
+ url: https://en.wikipedia.org/wiki/Special:Search?search={QUERY}
+ - title: Amazon
+ shortcut: "!a"
+ url: https://www.amazon.com/s?k={QUERY}
+ - title: Reddit
+ shortcut: "!r"
+ url: https://www.reddit.com/search/?q={QUERY}
+ - title: GitHub
+ shortcut: "!gh"
+ url: https://github.com/search?q={QUERY}
+ - type: bookmarks # Rewritten Quick Links widget
+ title: Quick Links
+ groups:
+ - links:
+ - title: Google
+ url: https://www.google.com/
+ icon: si:google
+ - title: GMail
+ url: https://mail.google.com/
+ icon: si:gmail
+ - title: GDrive
+ url: https://drive.google.com/
+ icon: si:googledrive
+ - links:
+ - title: GCal
+ url: https://calendar.google.com/
+ icon: si:googlecalendar
+ - title: Youtube
+ url: https://www.youtube.com/
+ icon: si:youtube
+ - title: Amazon
+ url: https://www.amazon.com/
+ icon: si:amazon
+ - links:
+ - title: Github
+ url: https://github.com/
+ icon: si:github
+ - title: LinkedIn
+ url: https://www.linkedin.com/
+ icon: si:linkedin
+ - title: Plex
+ url: https://app.plex.tv/
+ icon: si:plex
+ - links:
+ - title: MCSManager
+ url: http://panel.sirblob.co/
+ icon: mdi:server
+ - title: Gitea
+ url: https://git.sirblob.co/
+ icon: si:gitea
+ - title: Coder
+ url: https://mcbugj8flucdg.pit-1.try.coder.app
+ icon: mdi:code-braces
+ - links:
+ - title: qBittorrent
+ url: https://torr.sirblob.co/
+ icon: si:qbittorrent
+ - title: Excalidraw
+ url: https://draw.sirblob.co/
+ icon: mdi:draw
+ - title: Immich
+ url: https://immich.sirblob.co/
+ icon: si:immich
+ - links:
+ - title: Canvas
+ url: https://canvas.gmu.edu/
+ icon: si:instructure # Instructure is the company behind Canvas
+ - title: Mason360
+ url: https://mason360.gmu.edu/
+ icon: mdi:school # Using a generic school icon
+ - title: Nginx Proxy Manager # Added NPM
+ url: https://npm.sirblob.co/
+ icon: si:nginxproxymanager
+
+ - type: monitor
+ cache: 1m
+ title: My Services
+ sites:
+ - title: Plex
+ url: https://plex1.sirblob.co/web/index.html#!/
+ icon: si:plex
+ - title: Immich
+ url: https://immich.sirblob.co
+ icon: si:immich
+ - title: MCSManager
+ url: http://panel.sirblob.co/
+ icon: mdi:server
+ - title: Coder
+ url: https://mcbugj8flucdg.pit-1.try.coder.app
+ icon: mdi:code-braces
+ - title: Excalidraw
+ url: https://draw.sirblob.co/
+ icon: mdi:draw
+ - title: qBittorrent
+ url: https://torr.sirblob.co/
+ icon: si:qbittorrent
+ - title: Gitea
+ url: https://git.sirblob.co/
+ icon: si:gitea
+ - title: Nginx Proxy Manager # Added NPM
+ url: https://npm.sirblob.co/
+ icon: si:nginxproxymanager
+
+ - type: group
+ widgets:
+ - type: custom-api
+ title: mason.sirblob.co
+ cache: 30s
+ url: https://api.mcstatus.io/v2/status/java/mason.sirblob.co
+ template: &minecraft-widget-template |
+
+
+ {{ if .JSON.Bool "online" }}
+

+ {{ else }}
+
+ {{ end }}
+
+
+
+ {{ .JSON.String "host" }}
+ {{ if .JSON.Bool "online" }}
+
+ {{ else }}
+
+ {{ end }}
+
+
+ -
+ {{ if .JSON.Bool "online" }}
+ {{ .JSON.String "version.name_clean" }}
+ {{ else }}
+ Offline
+ {{ end }}
+
+ {{ if .JSON.Bool "online" }}
+ -
+
+ {{ range .JSON.Array "players.list" }}{{ .String "name_clean" }}
{{ end }}
+
+
+
+ {{ .JSON.Int "players.online" | formatNumber }}/{{ .JSON.Int "players.max" | formatNumber }} players
+
+
+ {{ else }}
+ -
+
+
+ 0 players
+
+
+ {{ end }}
+
+
+
+ - type: custom-api
+ title: cobble.sirblob.co
+ cache: 30s
+ url: https://api.mcstatus.io/v2/status/java/cobble.sirblob.co
+ template: *minecraft-widget-template
+ - type: custom-api
+ title: cgc.sirblob.co
+ cache: 30s
+ url: https://api.mcstatus.io/v2/status/java/cgc.sirblob.co
+ template: *minecraft-widget-template
+ - type: custom-api
+ title: presto.sirblob.co
+ cache: 30s
+ url: https://api.mcstatus.io/v2/status/java/presto.sirblob.co
+ template: *minecraft-widget-template
+ - type: custom-api
+ title: event.sirblob.co
+ cache: 30s
+ url: https://api.mcstatus.io/v2/status/java/event.sirblob.co
+ template: *minecraft-widget-template
+
+ - type: custom-api
+ title: LeetCode Daily Question
+ cache: 6h
+ url: https://leetcode.com/graphql
+ method: POST
+ headers:
+ Accept: application/json
+ body-type: json
+ body:
+ query: |
+ query questionOfToday {
+ activeDailyCodingChallengeQuestion {
+ link
+ question {
+ questionId
+ title
+ difficulty
+ topicTags {
+ name
+ slug
+ }
+ }
+ }
+ }
+ operationName: questionOfToday
+ template: |
+
+
+
+
Difficulty: {{ .JSON.String "data.activeDailyCodingChallengeQuestion.question.difficulty" }}
+
Topics:
+
+ {{ if .JSON.Exists "data.activeDailyCodingChallengeQuestion.question.topicTags" }}
+ {{ range .JSON.Array "data.activeDailyCodingChallengeQuestion.question.topicTags" }}
+ {{ .String "name" }}
+ {{ end }}
+ {{ else }}
+ None
+ {{ end }}
+
+ {{ if .JSON.Bool "data.activeDailyCodingChallengeQuestion.question.paidOnly" }}
+
This is a Premium question
+ {{ end }}
+
+
+ - size: small
+ widgets:
+ - type: weather
+ location: Great Falls, Virginia, United States
+ units: imperial
+ hour-format: 12h
+
+ - type: custom-api
+ title: Air Quality
+ cache: 10m
+ url: https://api.waqi.info/feed/geo:38.9979;-77.2894/?token=${AIR_TOKEN}
+ template: |
+ {{ $aqi := printf "%03s" (.JSON.String "data.aqi") }}
+ {{ $aqiraw := .JSON.String "data.aqi" }}
+ {{ $updated := .JSON.String "data.time.iso" }}
+ {{ $humidity := .JSON.String "data.iaqi.h.v" }}
+ {{ $ozone := .JSON.String "data.iaqi.o3.v" }}
+ {{ $pm25 := .JSON.String "data.iaqi.pm25.v" }}
+ {{ $pressure := .JSON.String "data.iaqi.p.v" }}
+
+
+
+ {{ if le $aqi "050" }}
+
Good air quality
+ {{ else if le $aqi "100" }}
+
Moderate air quality
+ {{ else }}
+
Bad air quality
+ {{ end }}
+
+
+
+ AQI: {{ $aqiraw }}
+
+
+
+
+
+
+
{{ $humidity }}%
+
HUMIDITY
+
+
+
+
{{ $ozone }} μg/m³
+
OZONE
+
+
+
+
{{ $pm25 }} μg/m³
+
PM2.5
+
+
+
+
{{ $pressure }} hPa
+
PRESSURE
+
+
+
+
+
Last Updated at {{ slice $updated 11 16 }}
+
+
+ - type: custom-api
+ title: GitHub Repositories
+ url: https://api.github.com/users/SirBlobby/repos
+ cache: 30m
+ parameters:
+ affiliation: owner
+ sort: updated
+ visibility: all
+ headers:
+ Accept: application/vnd.github.v3+json
+ User-Agent: Glance-Dashboard
+ template: |
+
+
+- name: Media
+ columns:
+ - size: small # Added new small column on the left
+ widgets:
+ - type: hacker-news # Added Hacker News widget
+ limit: 15
+ collapse-after: 5
+ - size: full # Existing full column is now second
+ widgets:
+ - type: custom-api
+ title: Epic Games
+ cache: 1h
+ url: https://store-site-backend-static.ak.epicgames.com/freeGamesPromotions?locale=en&country=US&allowCountries=US
+ template: |
+
+ {{ if eq .Response.StatusCode 200 }}
+
+ {{ else }}
+
Error fetching Epic Games data.
+ {{ end }}
+
+
+ - type: videos
+ title: For You
+ style: grid-cards
+ collapse-after-rows: 2
+ channels:
+ - UCwWhs_6x42TyRM4Wstoq8HA
+ - UCLuYADJ6hESLHX87JnsGbjA
+ - UC-gW4TeZAlKm7UATp24JsWQ
+ - UCETqJYEne9Tks-eZYIrFZvg
+ - UCOT2iLov0V7Re7ku_3UBtcQ
+ - UCFe2Kq8Hg15UomoVYdmRg_Q
+
+ - type: videos
+ title: Engineering
+ style: grid-cards
+ collapse-after-rows: 2
+ channels:
+ - UCUyeluBRhGPCW4rPe_UvBZQ
+ - UCFhXFikryT4aFcLkLw2LBLA
+ - UCsBjURrPoezykLs9EqgamOA
+ - UCHnyfMqiRRG1u-2MsSQLbXA
+ - UC6biysICWOJ-C3P4Tyeggzg
+
+- name: Services
+ columns:
+ - size: full
+ widgets:
+ - type: monitor
+ cache: 1m
+ title: My Services
+ sites:
+ - title: Plex
+ url: https://plex1.sirblob.co/web/index.html#!/
+ icon: si:plex
+ - title: Immich
+ url: https://immich.sirblob.co
+ icon: si:immich
+ - title: MCSManager
+ url: http://panel.sirblob.co/
+ icon: mdi:server
+ - title: Coder
+ url: https://mcbugj8flucdg.pit-1.try.coder.app
+ icon: mdi:code-braces
+ - title: Excalidraw
+ url: https://draw.sirblob.co/
+ icon: mdi:draw
+ - title: qBittorrent
+ url: https://torr.sirblob.co/
+ icon: si:qbittorrent
+ - title: Gitea
+ url: https://git.sirblob.co/
+ icon: si:gitea
+ - title: Nginx Proxy Manager # Added NPM
+ url: https://npm.sirblob.co/
+ icon: si:nginxproxymanager
+
+ - type: custom-api
+ title: Immich stats
+ cache: 10m
+ url: https://immich.sirblob.co/api/server/statistics
+ headers:
+ x-api-key: ${IMMICH_API_KEY}
+ Accept: application/json
+ template: |
+
+
+
{{ .JSON.Int "photos" | formatNumber }}
+
PHOTOS
+
+
+
{{ .JSON.Int "videos" | formatNumber }}
+
VIDEOS
+
+
+
{{ div (.JSON.Int "usage" | toFloat) 1073741824 | toInt | formatNumber }}GB
+
USAGE
+
+
+
+ - type: group
+ widgets:
+ - type: custom-api
+ title: mason.sirblob.co
+ cache: 30s
+ url: https://api.mcstatus.io/v2/status/java/mason.sirblob.co
+ template: *minecraft-widget-template
+ - type: custom-api
+ title: cobble.sirblob.co
+ cache: 30s
+ url: https://api.mcstatus.io/v2/status/java/cobble.sirblob.co
+ template: *minecraft-widget-template
+ - type: custom-api
+ title: cgc.sirblob.co
+ cache: 30s
+ url: https://api.mcstatus.io/v2/status/java/cgc.sirblob.co
+ template: *minecraft-widget-template
+ - type: custom-api
+ title: presto.sirblob.co
+ cache: 30s
+ url: https://api.mcstatus.io/v2/status/java/presto.sirblob.co
+ template: *minecraft-widget-template
+ - type: custom-api
+ title: create.sirblob.co
+ cache: 30s
+ url: https://api.mcstatus.io/v2/status/java/create.sirblob.co
+ template: *minecraft-widget-template
+
+ - type: custom-api
+ title: qBittorrent
+ cache: 10s
+ options:
+ view: "detailed" # "basic" or "detailed"
+ mode: "default" # "default" or "upload"
+ subrequests:
+ transfer:
+ url: http://${QBITTORRENT_IP}/api/v2/transfer/info
+ seeding:
+ url: http://${QBITTORRENT_IP}/api/v2/torrents/info
+ parameters:
+ filter: seeding
+ leeching:
+ url: http://${QBITTORRENT_IP}/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 = "✔" }}
+ {{ else if eq $state "downloading" "forcedDL" }}{{ $icon = "↓" }}
+ {{ 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 = "⟳" }}
+ {{ 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 }}∞
+ {{ 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 = "⟳" }}
+ {{ 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 URL and authentication bypass settings.
+
+ {{ end }}
+
+ - type: custom-api
+ title: Plex Now Playing
+ cache: 1m
+ options:
+ media-server: "plex"
+ base-url: https://plex1.sirblob.co
+ api-key: ${PLEX_TOKEN}
+ small-column: false
+ compact: false
+ play-state: "indicator"
+ show-thumbnail: true
+ full-thumbnail: false
+ show-paused: false
+ show-progress-bar: false
+ show-progress-info: true
+ time-format: "15:04"
+ template: |
+ {{ $mediaServer := .Options.StringOr "media-server" "" }}
+ {{ $baseURL := .Options.StringOr "base-url" "" }}
+ {{ $apiKey := .Options.StringOr "api-key" "" }}
+
+ {{ define "errorMsg" }}
+
+ {{ . }}
+ {{ end }}
+
+ {{ if or
+ (eq $mediaServer "")
+ (eq $baseURL "")
+ (eq $apiKey "")
+ }}
+ {{ template "errorMsg" "Some required options are not set" }}
+ {{ else }}
+
+ {{ $isSmallColumn := .Options.BoolOr "small-column" false }}
+ {{ $isCompact := .Options.BoolOr "compact" true }}
+ {{ $playState := .Options.StringOr "play-state" "indicator" }}
+ {{ $showThumbnail := .Options.BoolOr "show-thumbnail" false }}
+ {{ $fullThumbnail := .Options.BoolOr "full-thumbnail" false }}
+ {{ $showPaused := .Options.BoolOr "show-paused" false }}
+ {{ $showProgressBar := .Options.BoolOr "show-progress-bar" false }}
+ {{ $showProgressInfo := .Options.BoolOr "show-progress-info" true }}
+ {{ $timeFormat := .Options.StringOr "time-format" "15:04" }}
+
+ {{ $userID := "" }}
+ {{ $sessionsRequestURL := "" }}
+ {{ $sessionsCall := "" }}
+ {{ $sessions := "" }}
+ {{ $activeSessions := 0 }}
+
+ {{ if eq $mediaServer "plex" }}
+ {{ $sessionsRequestURL = concat $baseURL "/status/sessions" }}
+ {{ $sessionsCall = newRequest $sessionsRequestURL
+ | withHeader "Accept" "application/json"
+ | withHeader "X-Plex-Token" $apiKey
+ | getResponse }}
+
+ {{ if $sessionsCall.JSON.Exists "MediaContainer" }}
+ {{ $sessions = $sessionsCall.JSON.Array "MediaContainer.Metadata" }}
+ {{ $activeSessions = len $sessions }}
+ {{ else }}
+ {{ template "errorMsg" (concat "Could not fetch " $mediaServer " API.") }}
+ {{ end }}
+
+ {{ else if eq $mediaServer "tautulli" }}
+ {{ $sessionsRequestURL = concat $baseURL "/api/v2" }}
+ {{ $sessionsCall = newRequest $sessionsRequestURL
+ | withParameter "apikey" $apiKey
+ | withParameter "cmd" "get_activity"
+ | withHeader "Accept" "application/json"
+ | getResponse }}
+
+ {{ if eq $sessionsCall.Response.StatusCode 200 }}
+ {{ $sessions = $sessionsCall.JSON.Array "response.data.sessions" }}
+ {{ $activeSessions = len $sessions }}
+ {{ else }}
+ {{ template "errorMsg" (concat "Could not fetch " $mediaServer " API.") }}
+ {{ end }}
+
+ {{ else if or (eq $mediaServer "jellyfin") (eq $mediaServer "emby") }}
+ {{ $sessionsRequestURL = concat $baseURL "/Sessions" }}
+ {{ $sessionsCall = newRequest $sessionsRequestURL
+ | withParameter "api_key" $apiKey
+ | withParameter "activeWithinSeconds" "30"
+ | withHeader "Accept" "application/json"
+ | getResponse }}
+
+ {{ if eq $sessionsCall.Response.StatusCode 200 }}
+ {{ $sessions = $sessionsCall.JSON.Array "" }}
+ {{ if eq $mediaServer "emby" }}
+ {{ range $session := $sessions }}
+ {{ if $session.Bool "PlayState.CanSeek" }}
+ {{ $activeSessions = 1 }}
+ {{ break }}
+ {{ end }}
+ {{ end }}
+ {{ else }}
+ {{ $activeSessions = len $sessions }}
+ {{ end }}
+ {{ else }}
+ {{ template "errorMsg" (concat "Could not fetch " $mediaServer " API.") }}
+ {{ end }}
+ {{ end }}
+
+ {{ if and (eq $sessionsCall.Response.StatusCode 200) (eq $activeSessions 0) }}
+ Nothing is playing right now.
+ {{ else if $sessionsCall.JSON.Exists "MediaContainer" }}
+
+
+
+
+ {{ end }}
+ {{ end }}
+
+ - type: custom-api
+ title: Media Server History
+ frameless: true
+ cache: 5m
+ options:
+ media-server: "plex"
+ base-url: https://plex1.sirblob.co
+ api-key: ${PLEX_TOKEN}
+ history-length: "10"
+ small-column: false
+ compact: true
+ show-thumbnail: true
+ thumbnail-aspect-ratio: "default"
+ show-user: true
+ time-absolute: false
+ time-format: "Jan 02 15:04"
+ template: |
+ {{ $mediaServer := .Options.StringOr "media-server" "" }}
+ {{ $baseURL := .Options.StringOr "base-url" "" }}
+ {{ $apiKey := .Options.StringOr "api-key" "" }}
+ {{ $userName := .Options.StringOr "user-name" "" }}
+
+ {{ define "errorMsg" }}
+
+ {{ . }}
+ {{ end }}
+
+ {{ if or
+ (eq $mediaServer "")
+ (eq $baseURL "")
+ (eq $apiKey "")
+ (and (eq $mediaServer "jellyfin") (eq $userName ""))
+ }}
+ {{ template "errorMsg" "Some required options are not set" }}
+ {{ else }}
+
+ {{ $historyLength := .Options.StringOr "history-length" "10" }}
+ {{ $mediaTypes := .Options.StringOr "media-types" "" }}
+ {{ if eq $mediaServer "tautulli" }}
+ {{ $mediaTypes = .Options.StringOr "media-types" "movie,episode,track" }}
+ {{ else if or (eq $mediaServer "jellyfin") (eq $mediaServer "emby") }}
+ {{ $mediaTypes = .Options.StringOr "media-types" "Movie,Episode,Audio" }}
+ {{ end }}
+ {{ $isSmallColumn := .Options.BoolOr "small-column" false }}
+ {{ $isCompact := .Options.BoolOr "compact" true }}
+ {{ $showThumbnail := .Options.BoolOr "show-thumbnail" false }}
+ {{ $thumbAspectRatio := .Options.StringOr "thumbnail-aspect-ratio" "" }}
+ {{ $showUser := .Options.BoolOr "show-user" true }}
+ {{ $timeAbsolute := .Options.BoolOr "time-absolute" false }}
+ {{ $timeFormat := .Options.StringOr "time-format" "Jan 02 15:04" }}
+
+ {{ $userID := "" }}
+ {{ $historyRequestURL := "" }}
+ {{ $usersRequestURL := "" }}
+ {{ $historyCall := "" }}
+ {{ $usersCall := "" }}
+ {{ $history := "" }}
+ {{ $users := "" }}
+
+ {{ if eq $mediaServer "plex" }}
+ {{ $historyRequestURL = concat $baseURL "/status/sessions/history/all" }}
+ {{ $historyCall = newRequest $historyRequestURL
+ | withParameter "limit" $historyLength
+ | withParameter "sort" "viewedAt:desc"
+ | withHeader "Accept" "application/json"
+ | withHeader "X-Plex-Token" $apiKey
+ | getResponse }}
+
+ {{ if $historyCall.JSON.Exists "MediaContainer" }}
+ {{ $history = $historyCall.JSON.Array "MediaContainer.Metadata" }}
+ {{ else }}
+ {{ template "errorMsg" (concat "Could not fetch " $mediaServer " API.") }}
+ {{ end }}
+
+ {{ $usersRequestURL = concat $baseURL "/accounts" }}
+ {{ $usersCall = newRequest $usersRequestURL
+ | withHeader "Accept" "application/json"
+ | withHeader "X-Plex-Token" $apiKey
+ | getResponse }}
+ {{ $users = $usersCall.JSON.Array "MediaContainer.Account" }}
+
+ {{ else if eq $mediaServer "tautulli" }}
+ {{ $historyRequestURL = concat $baseURL "/api/v2" }}
+ {{ $historyCall = newRequest $historyRequestURL
+ | withParameter "apikey" $apiKey
+ | withParameter "cmd" "get_history"
+ | withParameter "length" $historyLength
+ | withParameter "media_type" $mediaTypes
+ | withHeader "Accept" "application/json"
+ | getResponse }}
+
+ {{ if eq $historyCall.Response.StatusCode 200 }}
+ {{ $history = $historyCall.JSON.Array "response.data.data" }}
+ {{ else }}
+ {{ template "errorMsg" (concat "Could not fetch " $mediaServer " API.") }}
+ {{ end }}
+
+ {{ else if or (eq $mediaServer "jellyfin") (eq $mediaServer "emby") }}
+ {{ $usersRequestURL = concat $baseURL "/Users" }}
+ {{ $usersCall = newRequest $usersRequestURL
+ | withParameter "api_key" $apiKey
+ | withHeader "Accept" "application/json"
+ | getResponse }}
+
+ {{ $usersList := $usersCall.JSON.Array "" }}
+ {{ range $i, $user := $usersList }}
+ {{ if eq ($user.String "Name") $userName }}
+ {{ $userID = $user.String "Id" }}
+ {{ break }}
+ {{ end }}
+ {{ end }}
+ {{ if eq $userID "" }}
+ {{ template "errorMsg" (concat "User '" $userName "' not found.") }}
+ {{ end }}
+
+ {{ $historyRequestURL = concat $baseURL "/Users/" $userID "/Items" }}
+ {{ $historyCall = newRequest $historyRequestURL
+ | withParameter "api_key" $apiKey
+ | withParameter "Limit" $historyLength
+ | withParameter "IncludeItemTypes" $mediaTypes
+ | withParameter "Recursive" "true"
+ | withParameter "isPlayed" "true"
+ | withParameter "sortBy" "DatePlayed"
+ | withParameter "sortOrder" "Descending"
+ | withParameter "Fields" "UserDataLastPlayedDate"
+ | withHeader "Accept" "application/json"
+ | getResponse }}
+
+ {{ $history = $historyCall.JSON.Array "Items" }}
+ {{ end }}
+
+ {{ if and (eq $historyCall.Response.StatusCode 200) (eq (len $history) 0) }}
+ Nothing has been played. Start streaming something!
+ {{ else if $historyCall.JSON.Exists "MediaContainer" }}
+
+
+ {{ range $n, $item := $history }}
+ {{ $mediaType := "" }}
+ {{ $isMovie := false }}
+ {{ $isShows := false }}
+ {{ $isMusic := false }}
+ {{ $title := "" }}
+ {{ $showTitle := "" }}
+ {{ $showSeason := "" }}
+ {{ $showEpisode := "" }}
+ {{ $artist := "" }}
+ {{ $albumTitle := "" }}
+ {{ $thumbURL := "" }}
+ {{ $playedAt := "" }}
+
+ {{ if eq $mediaServer "plex" }}
+ {{ $userID = $item.Int "accountID" }}
+ {{ range $n, $u := $users }}
+ {{ if eq $userID ($u.Int "id") }}
+ {{ $userName = $u.String "name" }}
+ {{ break }}
+ {{ end }}
+ {{ end }}
+
+ {{ $mediaType = $item.String "type" }}
+ {{ $isMovie = eq $mediaType "movie" }}
+ {{ $isShows = eq $mediaType "episode" }}
+ {{ $isMusic = eq $mediaType "track" }}
+
+ {{ $title = $item.String "title" }}
+ {{ if $isShows }}
+ {{ $showTitle = $item.String "grandparentTitle" }}
+ {{ $showSeason = $item.String "parentIndex" }}
+ {{ $showEpisode = $item.String "index" }}
+ {{ else if $isMusic }}
+ {{ $artist = $item.String "grandparentTitle" }}
+ {{ $albumTitle = $item.String "parentTitle" }}
+ {{ end }}
+
+ {{ $thumbID := $item.String "thumb" }}
+ {{ if or $isShows $isMusic}}
+ {{ $thumbID = $item.String "parentThumb" }}
+ {{ end }}
+ {{ $thumbURL = concat "https://plex1.sirblob.co" $thumbID "?X-Plex-Token=" $apiKey }}
+
+ {{ $time := $item.String "viewedAt" }}
+ {{ if $timeAbsolute }}
+ {{ $playedAt = $time | parseLocalTime "unix" | formatTime $timeFormat }}
+ {{ else }}
+ {{ $playedAt = $time | parseRelativeTime "unix" }}
+ {{ end }}
+
+ {{ else if eq $mediaServer "tautulli" }}
+ {{ $userName = $item.String "user" }}
+ {{ $mediaType = $item.String "media_type" }}
+ {{ $isMovie = eq $mediaType "movie" }}
+ {{ $isShows = eq $mediaType "episode" }}
+ {{ $isMusic = eq $mediaType "track" }}
+
+ {{ $title = $item.String "title" }}
+ {{ if $isShows }}
+ {{ $showTitle = $item.String "grandparent_title" }}
+ {{ $showSeason = $item.String "parent_media_index" }}
+ {{ $showEpisode = $item.String "media_index" }}
+ {{ else if $isMusic }}
+ {{ $artist = $item.String "grandparent_title" }}
+ {{ $albumTitle = $item.String "parent_title" }}
+ {{ end }}
+
+ {{ $thumbID := $item.String "thumb" }}
+ {{ $thumbURL = concat $baseURL "/api/v2?apikey=" $apiKey "&cmd=pms_image_proxy&img=" $thumbID }}
+
+ {{ $time := $item.String "date" }}
+ {{ if $timeAbsolute }}
+ {{ $playedAt = $time | parseLocalTime "unix" | formatTime $timeFormat }}
+ {{ else }}
+ {{ $playedAt = $time | parseRelativeTime "unix" }}
+ {{ end }}
+
+ {{ else if or (eq $mediaServer "jellyfin") (eq $mediaServer "emby") }}
+ {{ $mediaType = $item.String "Type" }}
+ {{ $isMovie = eq $mediaType "Movie" }}
+ {{ $isShows = eq $mediaType "Episode" }}
+ {{ $isMusic = eq $mediaType "Audio" }}
+
+ {{ $title = $item.String "Name" }}
+ {{ if $isShows }}
+ {{ $showTitle = $item.String "SeriesName" }}
+ {{ $showSeason = $item.String "ParentIndexNumber" }}
+ {{ $showEpisode = $item.String "IndexNumber" }}
+ {{ else if $isMusic }}
+ {{ $artist = $item.String "AlbumArtist" }}
+ {{ $albumTitle = $item.String "Album" }}
+ {{ end }}
+
+ {{ $thumbID := $item.String "Id" }}
+ {{ if $isShows }}
+ {{ $thumbID = $item.String "SeasonId" }}
+ {{ end }}
+ {{ $thumbURL = concat $baseURL "/Items/" $thumbID "/Images/Primary?api_key=" $apiKey }}
+
+ {{ $time := $item.String "UserData.LastPlayedDate" }}
+ {{ if $timeAbsolute }}
+ {{ $playedAt = $time | parseLocalTime "rfc3339" | formatTime $timeFormat }}
+ {{ else }}
+ {{ $playedAt = $time | parseRelativeTime "rfc3339" }}
+ {{ end }}
+ {{ end }}
+
+ {{ $showInfoFormat := concat "Season " $showSeason " Episode " $showEpisode}}
+ {{ if $isCompact }}
+ {{ $showInfoFormat = concat "S" $showSeason "E" $showEpisode}}
+ {{ end }}
+
+
+ {{ if $showThumbnail }}
+ {{ if $thumbURL }}
+

+ {{ else }}
+
+ {{ end }}
+ {{ end }}
+
+
+
+ {{ end }}
+
+
+ {{ end }}
+ {{ end }}
+
diff --git a/config/glance.yml b/config/glance.yml
new file mode 100644
index 0000000..31b80e8
--- /dev/null
+++ b/config/glance.yml
@@ -0,0 +1,23 @@
+server:
+ assets-path: /app/assets
+
+branding:
+ app-name: "Sir Blob's Dash" # PWA/Tab Name
+ app-background-color: "#22232e" # PWA Background
+
+theme:
+ contrast-multiplier: 1.5
+ background-color: 0 0 13
+ primary-color: 319 23 50 # Original lightness 81
+ negative-color: 78 13 70 # Original lightness 59
+
+ disable-picker: true
+
+ # Note: assets are cached by the browser, changes to the CSS file
+ # will not be reflected until the browser cache is cleared (Ctrl+F5)
+ custom-css-file: /assets/user.css
+
+pages:
+ # It's not necessary to create a new file for each page and include it, you can simply
+ # put its contents here, though multiple pages are easier to manage when separated
+ - $include: home.yml
diff --git a/config/home.yml b/config/home.yml
new file mode 100644
index 0000000..e7b5de8
--- /dev/null
+++ b/config/home.yml
@@ -0,0 +1,1562 @@
+- name: Home
+ columns:
+ - size: small
+ widgets:
+ - type: calendar
+ first-day-of-week: sunday
+ - type: clock # Added Clock widget
+ hour-format: 12h # Assuming 12h based on weather widget preference
+ timezones:
+ - timezone: America/Los_Angeles # San Francisco uses this timezone
+ label: San Francisco
+ - timezone: America/New_York
+ label: New York
+ - timezone: Europe/London
+ label: London
+ - timezone: Europe/Amsterdam
+ label: Amsterdam
+ - timezone: Asia/Kolkata # New Delhi uses this timezone
+ label: New Delhi
+ - timezone: Asia/Tokyo
+ label: Tokyo
+ - type: custom-api
+ title: Astronomy Picture of the Day
+ cache: 1d
+ url: https://api.nasa.gov/planetary/apod?api_key=${NASA_API_KEY}
+ headers:
+ Accept: application/json
+ template: |
+ {{- if eq (.JSON.String "media_type") "image" -}}
+
+ {{- else -}}
+
+ No image available today.
+
+ {{- end }}
+
+ - size: full
+ widgets:
+ - type: search
+ search-engine: google
+ bangs: # Added bangs section
+ - title: YouTube
+ shortcut: "!yt"
+ url: https://www.youtube.com/results?search_query={QUERY}
+ - title: Wikipedia
+ shortcut: "!w"
+ url: https://en.wikipedia.org/wiki/Special:Search?search={QUERY}
+ - title: Amazon
+ shortcut: "!a"
+ url: https://www.amazon.com/s?k={QUERY}
+ - title: Reddit
+ shortcut: "!r"
+ url: https://www.reddit.com/search/?q={QUERY}
+ - title: GitHub
+ shortcut: "!gh"
+ url: https://github.com/search?q={QUERY}
+ - type: bookmarks # Rewritten Quick Links widget
+ title: Quick Links
+ groups:
+ - links:
+ - title: Google
+ url: https://www.google.com/
+ icon: si:google
+ - title: GMail
+ url: https://mail.google.com/
+ icon: si:gmail
+ - title: GDrive
+ url: https://drive.google.com/
+ icon: si:googledrive
+ - links:
+ - title: GCal
+ url: https://calendar.google.com/
+ icon: si:googlecalendar
+ - title: Youtube
+ url: https://www.youtube.com/
+ icon: si:youtube
+ - title: Amazon
+ url: https://www.amazon.com/
+ icon: si:amazon
+ - links:
+ - title: Github
+ url: https://github.com/
+ icon: si:github
+ - title: LinkedIn
+ url: https://www.linkedin.com/
+ icon: si:linkedin
+ - title: Plex
+ url: https://app.plex.tv/
+ icon: si:plex
+ - links:
+ - title: MCSManager
+ url: http://panel.sirblob.co/
+ icon: mdi:server
+ - title: Gitea
+ url: https://git.sirblob.co/
+ icon: si:gitea
+ - title: Coder
+ url: https://mcbugj8flucdg.pit-1.try.coder.app
+ icon: mdi:code-braces
+ - links:
+ - title: qBittorrent
+ url: https://torr.sirblob.co/
+ icon: si:qbittorrent
+ - title: Excalidraw
+ url: https://draw.sirblob.co/
+ icon: mdi:draw
+ - title: Immich
+ url: https://immich.sirblob.co/
+ icon: si:immich
+ - links:
+ - title: Canvas
+ url: https://canvas.gmu.edu/
+ icon: si:instructure # Instructure is the company behind Canvas
+ - title: Mason360
+ url: https://mason360.gmu.edu/
+ icon: mdi:school # Using a generic school icon
+ - title: Nginx Proxy Manager # Added NPM
+ url: https://npm.sirblob.co/
+ icon: si:nginxproxymanager
+
+ - type: monitor
+ cache: 1m
+ title: My Services
+ sites:
+ - title: Plex
+ url: https://plex1.sirblob.co/web/index.html#!/
+ icon: si:plex
+ - title: Immich
+ url: https://immich.sirblob.co
+ icon: si:immich
+ - title: MCSManager
+ url: http://panel.sirblob.co/
+ icon: mdi:server
+ - title: Coder
+ url: https://mcbugj8flucdg.pit-1.try.coder.app
+ icon: mdi:code-braces
+ - title: Excalidraw
+ url: https://draw.sirblob.co/
+ icon: mdi:draw
+ - title: qBittorrent
+ url: https://torr.sirblob.co/
+ icon: si:qbittorrent
+ - title: Gitea
+ url: https://git.sirblob.co/
+ icon: si:gitea
+ - title: Nginx Proxy Manager # Added NPM
+ url: https://npm.sirblob.co/
+ icon: si:nginxproxymanager
+
+ - type: group
+ widgets:
+ - type: custom-api
+ title: mason.sirblob.co
+ cache: 30s
+ url: https://api.mcstatus.io/v2/status/java/mason.sirblob.co
+ template: &minecraft-widget-template |
+
+
+ {{ if .JSON.Bool "online" }}
+

+ {{ else }}
+
+ {{ end }}
+
+
+
+ {{ .JSON.String "host" }}
+ {{ if .JSON.Bool "online" }}
+
+ {{ else }}
+
+ {{ end }}
+
+
+ -
+ {{ if .JSON.Bool "online" }}
+ {{ .JSON.String "version.name_clean" }}
+ {{ else }}
+ Offline
+ {{ end }}
+
+ {{ if .JSON.Bool "online" }}
+ -
+
+ {{ range .JSON.Array "players.list" }}{{ .String "name_clean" }}
{{ end }}
+
+
+
+ {{ .JSON.Int "players.online" | formatNumber }}/{{ .JSON.Int "players.max" | formatNumber }} players
+
+
+ {{ else }}
+ -
+
+
+ 0 players
+
+
+ {{ end }}
+
+
+
+ - type: custom-api
+ title: cobble.sirblob.co
+ cache: 30s
+ url: https://api.mcstatus.io/v2/status/java/cobble.sirblob.co
+ template: *minecraft-widget-template
+ - type: custom-api
+ title: cgc.sirblob.co
+ cache: 30s
+ url: https://api.mcstatus.io/v2/status/java/cgc.sirblob.co
+ template: *minecraft-widget-template
+ - type: custom-api
+ title: presto.sirblob.co
+ cache: 30s
+ url: https://api.mcstatus.io/v2/status/java/presto.sirblob.co
+ template: *minecraft-widget-template
+ - type: custom-api
+ title: event.sirblob.co
+ cache: 30s
+ url: https://api.mcstatus.io/v2/status/java/event.sirblob.co
+ template: *minecraft-widget-template
+
+ - type: custom-api
+ title: LeetCode Daily Question
+ cache: 6h
+ url: https://leetcode.com/graphql
+ method: POST
+ headers:
+ Accept: application/json
+ body-type: json
+ body:
+ query: |
+ query questionOfToday {
+ activeDailyCodingChallengeQuestion {
+ link
+ question {
+ questionId
+ title
+ difficulty
+ topicTags {
+ name
+ slug
+ }
+ }
+ }
+ }
+ operationName: questionOfToday
+ template: |
+
+
+
+
Difficulty: {{ .JSON.String "data.activeDailyCodingChallengeQuestion.question.difficulty" }}
+
Topics:
+
+ {{ if .JSON.Exists "data.activeDailyCodingChallengeQuestion.question.topicTags" }}
+ {{ range .JSON.Array "data.activeDailyCodingChallengeQuestion.question.topicTags" }}
+ {{ .String "name" }}
+ {{ end }}
+ {{ else }}
+ None
+ {{ end }}
+
+ {{ if .JSON.Bool "data.activeDailyCodingChallengeQuestion.question.paidOnly" }}
+
This is a Premium question
+ {{ end }}
+
+
+ - size: small
+ widgets:
+ - type: weather
+ location: Great Falls, Virginia, United States
+ units: imperial
+ hour-format: 12h
+
+ - type: custom-api
+ title: Air Quality
+ cache: 10m
+ url: https://api.waqi.info/feed/geo:38.9979;-77.2894/?token=${AIR_TOKEN}
+ template: |
+ {{ $aqi := printf "%03s" (.JSON.String "data.aqi") }}
+ {{ $aqiraw := .JSON.String "data.aqi" }}
+ {{ $updated := .JSON.String "data.time.iso" }}
+ {{ $humidity := .JSON.String "data.iaqi.h.v" }}
+ {{ $ozone := .JSON.String "data.iaqi.o3.v" }}
+ {{ $pm25 := .JSON.String "data.iaqi.pm25.v" }}
+ {{ $pressure := .JSON.String "data.iaqi.p.v" }}
+
+
+
+ {{ if le $aqi "050" }}
+
Good air quality
+ {{ else if le $aqi "100" }}
+
Moderate air quality
+ {{ else }}
+
Bad air quality
+ {{ end }}
+
+
+
+ AQI: {{ $aqiraw }}
+
+
+
+
+
+
+
{{ $humidity }}%
+
HUMIDITY
+
+
+
+
{{ $ozone }} μg/m³
+
OZONE
+
+
+
+
{{ $pm25 }} μg/m³
+
PM2.5
+
+
+
+
{{ $pressure }} hPa
+
PRESSURE
+
+
+
+
+
Last Updated at {{ slice $updated 11 16 }}
+
+
+ - type: custom-api
+ title: GitHub Repositories
+ url: https://api.github.com/users/SirBlobby/repos
+ cache: 30m
+ parameters:
+ affiliation: owner
+ sort: updated
+ visibility: all
+ headers:
+ Accept: application/vnd.github.v3+json
+ User-Agent: Glance-Dashboard
+ template: |
+
+
+- name: Media
+ columns:
+ - size: small # Added new small column on the left
+ widgets:
+ - type: hacker-news # Added Hacker News widget
+ limit: 15
+ collapse-after: 5
+ - size: full # Existing full column is now second
+ widgets:
+ - type: custom-api
+ title: Epic Games
+ cache: 1h
+ url: https://store-site-backend-static.ak.epicgames.com/freeGamesPromotions?locale=en&country=US&allowCountries=US
+ template: |
+
+ {{ if eq .Response.StatusCode 200 }}
+
+ {{ else }}
+
Error fetching Epic Games data.
+ {{ end }}
+
+
+ - type: videos
+ title: For You
+ style: grid-cards
+ collapse-after-rows: 2
+ channels:
+ - UCwWhs_6x42TyRM4Wstoq8HA
+ - UCLuYADJ6hESLHX87JnsGbjA
+ - UC-gW4TeZAlKm7UATp24JsWQ
+ - UCETqJYEne9Tks-eZYIrFZvg
+ - UCOT2iLov0V7Re7ku_3UBtcQ
+ - UCFe2Kq8Hg15UomoVYdmRg_Q
+
+ - type: videos
+ title: Engineering
+ style: grid-cards
+ collapse-after-rows: 2
+ channels:
+ - UCUyeluBRhGPCW4rPe_UvBZQ
+ - UCFhXFikryT4aFcLkLw2LBLA
+ - UCsBjURrPoezykLs9EqgamOA
+ - UCHnyfMqiRRG1u-2MsSQLbXA
+ - UC6biysICWOJ-C3P4Tyeggzg
+
+- name: Services
+ columns:
+ - size: full
+ widgets:
+ - type: monitor
+ cache: 1m
+ title: My Services
+ sites:
+ - title: Plex
+ url: https://plex1.sirblob.co/web/index.html#!/
+ icon: si:plex
+ - title: Immich
+ url: https://immich.sirblob.co
+ icon: si:immich
+ - title: MCSManager
+ url: http://panel.sirblob.co/
+ icon: mdi:server
+ - title: Coder
+ url: https://mcbugj8flucdg.pit-1.try.coder.app
+ icon: mdi:code-braces
+ - title: Excalidraw
+ url: https://draw.sirblob.co/
+ icon: mdi:draw
+ - title: qBittorrent
+ url: https://torr.sirblob.co/
+ icon: si:qbittorrent
+ - title: Gitea
+ url: https://git.sirblob.co/
+ icon: si:gitea
+ - title: Nginx Proxy Manager # Added NPM
+ url: https://npm.sirblob.co/
+ icon: si:nginxproxymanager
+
+ - type: custom-api
+ title: Immich stats
+ cache: 10m
+ url: https://immich.sirblob.co/api/server/statistics
+ headers:
+ x-api-key: ${IMMICH_API_KEY}
+ Accept: application/json
+ template: |
+
+
+
{{ .JSON.Int "photos" | formatNumber }}
+
PHOTOS
+
+
+
{{ .JSON.Int "videos" | formatNumber }}
+
VIDEOS
+
+
+
{{ div (.JSON.Int "usage" | toFloat) 1073741824 | toInt | formatNumber }}GB
+
USAGE
+
+
+
+ - type: group
+ widgets:
+ - type: custom-api
+ title: mason.sirblob.co
+ cache: 30s
+ url: https://api.mcstatus.io/v2/status/java/mason.sirblob.co
+ template: *minecraft-widget-template
+ - type: custom-api
+ title: cobble.sirblob.co
+ cache: 30s
+ url: https://api.mcstatus.io/v2/status/java/cobble.sirblob.co
+ template: *minecraft-widget-template
+ - type: custom-api
+ title: cgc.sirblob.co
+ cache: 30s
+ url: https://api.mcstatus.io/v2/status/java/cgc.sirblob.co
+ template: *minecraft-widget-template
+ - type: custom-api
+ title: presto.sirblob.co
+ cache: 30s
+ url: https://api.mcstatus.io/v2/status/java/presto.sirblob.co
+ template: *minecraft-widget-template
+ - type: custom-api
+ title: create.sirblob.co
+ cache: 30s
+ url: https://api.mcstatus.io/v2/status/java/create.sirblob.co
+ template: *minecraft-widget-template
+
+ - type: custom-api
+ title: qBittorrent
+ cache: 10s
+ options:
+ view: "detailed" # "basic" or "detailed"
+ mode: "default" # "default" or "upload"
+ subrequests:
+ transfer:
+ url: http://${QBITTORRENT_IP}/api/v2/transfer/info
+ seeding:
+ url: http://${QBITTORRENT_IP}/api/v2/torrents/info
+ parameters:
+ filter: seeding
+ leeching:
+ url: http://${QBITTORRENT_IP}/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 = "✔" }}
+ {{ else if eq $state "downloading" "forcedDL" }}{{ $icon = "↓" }}
+ {{ 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 = "⟳" }}
+ {{ 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 }}∞
+ {{ 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 = "⟳" }}
+ {{ 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 URL and authentication bypass settings.
+
+ {{ end }}
+
+ - type: custom-api
+ title: Plex Now Playing
+ cache: 1m
+ options:
+ media-server: "plex"
+ base-url: https://plex1.sirblob.co
+ api-key: ${PLEX_TOKEN}
+ small-column: false
+ compact: false
+ play-state: "indicator"
+ show-thumbnail: true
+ full-thumbnail: false
+ show-paused: false
+ show-progress-bar: false
+ show-progress-info: true
+ time-format: "15:04"
+ template: |
+ {{ $mediaServer := .Options.StringOr "media-server" "" }}
+ {{ $baseURL := .Options.StringOr "base-url" "" }}
+ {{ $apiKey := .Options.StringOr "api-key" "" }}
+
+ {{ define "errorMsg" }}
+
+ {{ . }}
+ {{ end }}
+
+ {{ if or
+ (eq $mediaServer "")
+ (eq $baseURL "")
+ (eq $apiKey "")
+ }}
+ {{ template "errorMsg" "Some required options are not set" }}
+ {{ else }}
+
+ {{ $isSmallColumn := .Options.BoolOr "small-column" false }}
+ {{ $isCompact := .Options.BoolOr "compact" true }}
+ {{ $playState := .Options.StringOr "play-state" "indicator" }}
+ {{ $showThumbnail := .Options.BoolOr "show-thumbnail" false }}
+ {{ $fullThumbnail := .Options.BoolOr "full-thumbnail" false }}
+ {{ $showPaused := .Options.BoolOr "show-paused" false }}
+ {{ $showProgressBar := .Options.BoolOr "show-progress-bar" false }}
+ {{ $showProgressInfo := .Options.BoolOr "show-progress-info" true }}
+ {{ $timeFormat := .Options.StringOr "time-format" "15:04" }}
+
+ {{ $userID := "" }}
+ {{ $sessionsRequestURL := "" }}
+ {{ $sessionsCall := "" }}
+ {{ $sessions := "" }}
+ {{ $activeSessions := 0 }}
+
+ {{ if eq $mediaServer "plex" }}
+ {{ $sessionsRequestURL = concat $baseURL "/status/sessions" }}
+ {{ $sessionsCall = newRequest $sessionsRequestURL
+ | withHeader "Accept" "application/json"
+ | withHeader "X-Plex-Token" $apiKey
+ | getResponse }}
+
+ {{ if $sessionsCall.JSON.Exists "MediaContainer" }}
+ {{ $sessions = $sessionsCall.JSON.Array "MediaContainer.Metadata" }}
+ {{ $activeSessions = len $sessions }}
+ {{ else }}
+ {{ template "errorMsg" (concat "Could not fetch " $mediaServer " API.") }}
+ {{ end }}
+
+ {{ else if eq $mediaServer "tautulli" }}
+ {{ $sessionsRequestURL = concat $baseURL "/api/v2" }}
+ {{ $sessionsCall = newRequest $sessionsRequestURL
+ | withParameter "apikey" $apiKey
+ | withParameter "cmd" "get_activity"
+ | withHeader "Accept" "application/json"
+ | getResponse }}
+
+ {{ if eq $sessionsCall.Response.StatusCode 200 }}
+ {{ $sessions = $sessionsCall.JSON.Array "response.data.sessions" }}
+ {{ $activeSessions = len $sessions }}
+ {{ else }}
+ {{ template "errorMsg" (concat "Could not fetch " $mediaServer " API.") }}
+ {{ end }}
+
+ {{ else if or (eq $mediaServer "jellyfin") (eq $mediaServer "emby") }}
+ {{ $sessionsRequestURL = concat $baseURL "/Sessions" }}
+ {{ $sessionsCall = newRequest $sessionsRequestURL
+ | withParameter "api_key" $apiKey
+ | withParameter "activeWithinSeconds" "30"
+ | withHeader "Accept" "application/json"
+ | getResponse }}
+
+ {{ if eq $sessionsCall.Response.StatusCode 200 }}
+ {{ $sessions = $sessionsCall.JSON.Array "" }}
+ {{ if eq $mediaServer "emby" }}
+ {{ range $session := $sessions }}
+ {{ if $session.Bool "PlayState.CanSeek" }}
+ {{ $activeSessions = 1 }}
+ {{ break }}
+ {{ end }}
+ {{ end }}
+ {{ else }}
+ {{ $activeSessions = len $sessions }}
+ {{ end }}
+ {{ else }}
+ {{ template "errorMsg" (concat "Could not fetch " $mediaServer " API.") }}
+ {{ end }}
+ {{ end }}
+
+ {{ if and (eq $sessionsCall.Response.StatusCode 200) (eq $activeSessions 0) }}
+ Nothing is playing right now.
+ {{ else if $sessionsCall.JSON.Exists "MediaContainer" }}
+
+
+
+
+ {{ end }}
+ {{ end }}
+
+ - type: custom-api
+ title: Media Server History
+ frameless: true
+ cache: 5m
+ options:
+ media-server: "plex"
+ base-url: https://plex1.sirblob.co
+ api-key: ${PLEX_TOKEN}
+ history-length: "10"
+ small-column: false
+ compact: true
+ show-thumbnail: true
+ thumbnail-aspect-ratio: "default"
+ show-user: true
+ time-absolute: false
+ time-format: "Jan 02 15:04"
+ template: |
+ {{ $mediaServer := .Options.StringOr "media-server" "" }}
+ {{ $baseURL := .Options.StringOr "base-url" "" }}
+ {{ $apiKey := .Options.StringOr "api-key" "" }}
+ {{ $userName := .Options.StringOr "user-name" "" }}
+
+ {{ define "errorMsg" }}
+
+ {{ . }}
+ {{ end }}
+
+ {{ if or
+ (eq $mediaServer "")
+ (eq $baseURL "")
+ (eq $apiKey "")
+ (and (eq $mediaServer "jellyfin") (eq $userName ""))
+ }}
+ {{ template "errorMsg" "Some required options are not set" }}
+ {{ else }}
+
+ {{ $historyLength := .Options.StringOr "history-length" "10" }}
+ {{ $mediaTypes := .Options.StringOr "media-types" "" }}
+ {{ if eq $mediaServer "tautulli" }}
+ {{ $mediaTypes = .Options.StringOr "media-types" "movie,episode,track" }}
+ {{ else if or (eq $mediaServer "jellyfin") (eq $mediaServer "emby") }}
+ {{ $mediaTypes = .Options.StringOr "media-types" "Movie,Episode,Audio" }}
+ {{ end }}
+ {{ $isSmallColumn := .Options.BoolOr "small-column" false }}
+ {{ $isCompact := .Options.BoolOr "compact" true }}
+ {{ $showThumbnail := .Options.BoolOr "show-thumbnail" false }}
+ {{ $thumbAspectRatio := .Options.StringOr "thumbnail-aspect-ratio" "" }}
+ {{ $showUser := .Options.BoolOr "show-user" true }}
+ {{ $timeAbsolute := .Options.BoolOr "time-absolute" false }}
+ {{ $timeFormat := .Options.StringOr "time-format" "Jan 02 15:04" }}
+
+ {{ $userID := "" }}
+ {{ $historyRequestURL := "" }}
+ {{ $usersRequestURL := "" }}
+ {{ $historyCall := "" }}
+ {{ $usersCall := "" }}
+ {{ $history := "" }}
+ {{ $users := "" }}
+
+ {{ if eq $mediaServer "plex" }}
+ {{ $historyRequestURL = concat $baseURL "/status/sessions/history/all" }}
+ {{ $historyCall = newRequest $historyRequestURL
+ | withParameter "limit" $historyLength
+ | withParameter "sort" "viewedAt:desc"
+ | withHeader "Accept" "application/json"
+ | withHeader "X-Plex-Token" $apiKey
+ | getResponse }}
+
+ {{ if $historyCall.JSON.Exists "MediaContainer" }}
+ {{ $history = $historyCall.JSON.Array "MediaContainer.Metadata" }}
+ {{ else }}
+ {{ template "errorMsg" (concat "Could not fetch " $mediaServer " API.") }}
+ {{ end }}
+
+ {{ $usersRequestURL = concat $baseURL "/accounts" }}
+ {{ $usersCall = newRequest $usersRequestURL
+ | withHeader "Accept" "application/json"
+ | withHeader "X-Plex-Token" $apiKey
+ | getResponse }}
+ {{ $users = $usersCall.JSON.Array "MediaContainer.Account" }}
+
+ {{ else if eq $mediaServer "tautulli" }}
+ {{ $historyRequestURL = concat $baseURL "/api/v2" }}
+ {{ $historyCall = newRequest $historyRequestURL
+ | withParameter "apikey" $apiKey
+ | withParameter "cmd" "get_history"
+ | withParameter "length" $historyLength
+ | withParameter "media_type" $mediaTypes
+ | withHeader "Accept" "application/json"
+ | getResponse }}
+
+ {{ if eq $historyCall.Response.StatusCode 200 }}
+ {{ $history = $historyCall.JSON.Array "response.data.data" }}
+ {{ else }}
+ {{ template "errorMsg" (concat "Could not fetch " $mediaServer " API.") }}
+ {{ end }}
+
+ {{ else if or (eq $mediaServer "jellyfin") (eq $mediaServer "emby") }}
+ {{ $usersRequestURL = concat $baseURL "/Users" }}
+ {{ $usersCall = newRequest $usersRequestURL
+ | withParameter "api_key" $apiKey
+ | withHeader "Accept" "application/json"
+ | getResponse }}
+
+ {{ $usersList := $usersCall.JSON.Array "" }}
+ {{ range $i, $user := $usersList }}
+ {{ if eq ($user.String "Name") $userName }}
+ {{ $userID = $user.String "Id" }}
+ {{ break }}
+ {{ end }}
+ {{ end }}
+ {{ if eq $userID "" }}
+ {{ template "errorMsg" (concat "User '" $userName "' not found.") }}
+ {{ end }}
+
+ {{ $historyRequestURL = concat $baseURL "/Users/" $userID "/Items" }}
+ {{ $historyCall = newRequest $historyRequestURL
+ | withParameter "api_key" $apiKey
+ | withParameter "Limit" $historyLength
+ | withParameter "IncludeItemTypes" $mediaTypes
+ | withParameter "Recursive" "true"
+ | withParameter "isPlayed" "true"
+ | withParameter "sortBy" "DatePlayed"
+ | withParameter "sortOrder" "Descending"
+ | withParameter "Fields" "UserDataLastPlayedDate"
+ | withHeader "Accept" "application/json"
+ | getResponse }}
+
+ {{ $history = $historyCall.JSON.Array "Items" }}
+ {{ end }}
+
+ {{ if and (eq $historyCall.Response.StatusCode 200) (eq (len $history) 0) }}
+ Nothing has been played. Start streaming something!
+ {{ else if $historyCall.JSON.Exists "MediaContainer" }}
+
+
+ {{ range $n, $item := $history }}
+ {{ $mediaType := "" }}
+ {{ $isMovie := false }}
+ {{ $isShows := false }}
+ {{ $isMusic := false }}
+ {{ $title := "" }}
+ {{ $showTitle := "" }}
+ {{ $showSeason := "" }}
+ {{ $showEpisode := "" }}
+ {{ $artist := "" }}
+ {{ $albumTitle := "" }}
+ {{ $thumbURL := "" }}
+ {{ $playedAt := "" }}
+
+ {{ if eq $mediaServer "plex" }}
+ {{ $userID = $item.Int "accountID" }}
+ {{ range $n, $u := $users }}
+ {{ if eq $userID ($u.Int "id") }}
+ {{ $userName = $u.String "name" }}
+ {{ break }}
+ {{ end }}
+ {{ end }}
+
+ {{ $mediaType = $item.String "type" }}
+ {{ $isMovie = eq $mediaType "movie" }}
+ {{ $isShows = eq $mediaType "episode" }}
+ {{ $isMusic = eq $mediaType "track" }}
+
+ {{ $title = $item.String "title" }}
+ {{ if $isShows }}
+ {{ $showTitle = $item.String "grandparentTitle" }}
+ {{ $showSeason = $item.String "parentIndex" }}
+ {{ $showEpisode = $item.String "index" }}
+ {{ else if $isMusic }}
+ {{ $artist = $item.String "grandparentTitle" }}
+ {{ $albumTitle = $item.String "parentTitle" }}
+ {{ end }}
+
+ {{ $thumbID := $item.String "thumb" }}
+ {{ if or $isShows $isMusic}}
+ {{ $thumbID = $item.String "parentThumb" }}
+ {{ end }}
+ {{ $thumbURL = concat "https://plex1.sirblob.co" $thumbID "?X-Plex-Token=" $apiKey }}
+
+ {{ $time := $item.String "viewedAt" }}
+ {{ if $timeAbsolute }}
+ {{ $playedAt = $time | parseLocalTime "unix" | formatTime $timeFormat }}
+ {{ else }}
+ {{ $playedAt = $time | parseRelativeTime "unix" }}
+ {{ end }}
+
+ {{ else if eq $mediaServer "tautulli" }}
+ {{ $userName = $item.String "user" }}
+ {{ $mediaType = $item.String "media_type" }}
+ {{ $isMovie = eq $mediaType "movie" }}
+ {{ $isShows = eq $mediaType "episode" }}
+ {{ $isMusic = eq $mediaType "track" }}
+
+ {{ $title = $item.String "title" }}
+ {{ if $isShows }}
+ {{ $showTitle = $item.String "grandparent_title" }}
+ {{ $showSeason = $item.String "parent_media_index" }}
+ {{ $showEpisode = $item.String "media_index" }}
+ {{ else if $isMusic }}
+ {{ $artist = $item.String "grandparent_title" }}
+ {{ $albumTitle = $item.String "parent_title" }}
+ {{ end }}
+
+ {{ $thumbID := $item.String "thumb" }}
+ {{ $thumbURL = concat $baseURL "/api/v2?apikey=" $apiKey "&cmd=pms_image_proxy&img=" $thumbID }}
+
+ {{ $time := $item.String "date" }}
+ {{ if $timeAbsolute }}
+ {{ $playedAt = $time | parseLocalTime "unix" | formatTime $timeFormat }}
+ {{ else }}
+ {{ $playedAt = $time | parseRelativeTime "unix" }}
+ {{ end }}
+
+ {{ else if or (eq $mediaServer "jellyfin") (eq $mediaServer "emby") }}
+ {{ $mediaType = $item.String "Type" }}
+ {{ $isMovie = eq $mediaType "Movie" }}
+ {{ $isShows = eq $mediaType "Episode" }}
+ {{ $isMusic = eq $mediaType "Audio" }}
+
+ {{ $title = $item.String "Name" }}
+ {{ if $isShows }}
+ {{ $showTitle = $item.String "SeriesName" }}
+ {{ $showSeason = $item.String "ParentIndexNumber" }}
+ {{ $showEpisode = $item.String "IndexNumber" }}
+ {{ else if $isMusic }}
+ {{ $artist = $item.String "AlbumArtist" }}
+ {{ $albumTitle = $item.String "Album" }}
+ {{ end }}
+
+ {{ $thumbID := $item.String "Id" }}
+ {{ if $isShows }}
+ {{ $thumbID = $item.String "SeasonId" }}
+ {{ end }}
+ {{ $thumbURL = concat $baseURL "/Items/" $thumbID "/Images/Primary?api_key=" $apiKey }}
+
+ {{ $time := $item.String "UserData.LastPlayedDate" }}
+ {{ if $timeAbsolute }}
+ {{ $playedAt = $time | parseLocalTime "rfc3339" | formatTime $timeFormat }}
+ {{ else }}
+ {{ $playedAt = $time | parseRelativeTime "rfc3339" }}
+ {{ end }}
+ {{ end }}
+
+ {{ $showInfoFormat := concat "Season " $showSeason " Episode " $showEpisode}}
+ {{ if $isCompact }}
+ {{ $showInfoFormat = concat "S" $showSeason "E" $showEpisode}}
+ {{ end }}
+
+
+ {{ if $showThumbnail }}
+ {{ if $thumbURL }}
+

+ {{ else }}
+
+ {{ end }}
+ {{ end }}
+
+
+
+ {{ end }}
+
+
+ {{ end }}
+ {{ end }}
+