+
+ {{ printf "%+.2f" $change }}%
+
+
diff --git a/config/finance/widgets/youtube.yml b/config/finance/widgets/youtube.yml
new file mode 100644
index 0000000..f9afb48
--- /dev/null
+++ b/config/finance/widgets/youtube.yml
@@ -0,0 +1,94 @@
+- type: group
+ widgets:
+ # Podcasts & Commentary
+ - type: videos
+ style: grid-cards
+ collapse-after-rows: 2
+ title: Pop-Culture
+ channels:
+ - UCzQUP1qoWDoEbmsQxvdjxgQ # PowerfulJRE
+ - UCJIfeSCssxSC_Dhc5s7woww # Lex Clips
+ - UC5PstSsGrRwj2o6asQpC4Rg # Flagrant 2
+ - UCKrpnfpTwncQ050VFXcVkuQ # Network State Podcast
+ - UC7oPkqeHTwuOZ5CZ-R9f-6w # Triggernometry
+ - UCIHdDJ0tjn_3j-FS7s_X1kQ # Valuetainment
+
+ # Programming & Tech
+ - type: videos
+ style: grid-cards
+ collapse-after-rows: 2
+ title: Programming
+ channels:
+ - UCUyeluBRhGPCW4rPe_UvBZQ # ThePrimeTime
+ - UCjSEJkpGbcZhvo0lr-44X_w # TechHut
+ - UCXuqSBlHAE6Xw-yeJA0Tunw # Linus Tech Tips
+ - UCs6KfncB4OV6Vug4o_bzijg # Techlore
+ - UCTwxaBjziKfy6y_uWu30orAby # BugBytes
+ - UCYeiozh-4QwuC1sjgCmB92w # DevOps Toolbox
+ - UCsBjURrPoezykLs9EqgamOA # Angular Firebase
+ - UC-mTIBh__DzAqW495JHXy5A # The Coding Gopher
+ - UC2WHjPDvbE6O328n17ZGcfg # ForrestKnight
+ - UCsUalyRg43M8D60mtHe6YcAD # Honeypot
+ - UCgfe2ooZD3VJPB6aJAnuQng # cloud
+ - UCdjsUXJ3QawK4O5L1kqqsew # IPFS
+ - UC3Wn3dABlgESm8Bzn8Vamgg # Sideprojects
+ - UCtMVHI3AJD4Qk4hcbZnI9ZQ # SomeOrdinaryGamers
+ - UCNJ1Ymd5yFuUPtn21xtRbbw # AI Explained
+ - UCutCcajxhR33k9UR-DdLsAQ # Systems Innovation
+
+ # Startups & Business
+ - type: videos
+ style: grid-cards
+ collapse-after-rows: 2
+ title: Startups
+ channels:
+ - UCcefcZRL2oaA_uBNeo5UOWg # Y Combinator
+ - UCllx7jOjBL794FK67jtb5Og # Dalton + Michael
+ - UCxIJaCMEptJjxmmQgGFsnCg # How to Start a Startup
+ - UCQrZfl1FeCN9c71yjOzWVRw # Startup Archive
+ - UCtieZ1nS_tpjGEama6Kjxcg # Steve Jobs Archive
+ - UCm_WkN0wg49-eG4SymAQ_4g # Harvard Innovation Labs
+ - UCSGc7c1yKxo-Uhz5_oivrfg # The Startup Club by Slidebean
+ - UCGq-a57w-aPwyi3pW7XLiHw # The Diary Of A CEO
+
+ # AI & Futurism
+ - type: videos
+ style: grid-cards
+ collapse-after-rows: 2
+ title: Futurism
+ channels:
+ - UCawZsQWqfGSbCI5yjkdVkTA # Matthew Berman
+ - UCZFipeZtQM5CKUjx6grh54g # Isaac Arthur
+ - UCR9sFzaG9Ia_kXJhfxtFMB # melodysheep
+ - UCSeuZvDGosq1zC6NqgqGo-Q # merlin
+ # Finance
+ - type: videos
+ style: grid-cards
+ collapse-after-rows: 2
+ title: Finance
+ channels:
+ - UCAeAB8ABXGoGMbXuYPmiu2A # The Swedish Investor
+ - UCvSXMi2LebwJEM1s4bz5IBA # New Money
+ - UCV6KDgJskWaEckne5aPA0aQ # Graham Stephan
+ - UCAzhpt9DmG6PnHXjmJTvRGQ # Federal Reserve
+
+
+ # Science, History & Education
+ - type: videos
+ style: grid-cards
+ collapse-after-rows: 2
+ title: Education
+ channels:
+ - UCHnyfMqiRRG1u-2MsSQLbXA # Veritasium
+ - UCsXVk37bltHxD1rDPwtNM8QL # Kurzgesagt – In a Nutshell
+ - UCxqAWLTk1CmBvZFPzeZMd9A # Domain of Science
+ - UCR2uRTQ53V_egXKFflMMaaw # Artem Kirsanov
+ - UCeUJFQ0D9qs6aVNyUt9fkeQ # The Armchair Historian
+ - UC5Dw9TFdbPJoTDMSiJdIQT # Whatifalthist
+ - UC0NCSdGglnmdWg-qHALhu1w # History102
+ - UCpCSAcbqs-sjEVfk_hMfY9w # MajorPrep
+ - UCmdugCMpC7BawYGwkRs5drQ # PhiloNautica
+ - UC4gHARRmw40kcLZgINlTK_g # Religion Camp
+ - UCPPrhcXtGR2aMoxrPJRKPLg # Para Bellum
+ - UCQUnqaW8Q33siyLYhisN7HQ # Antidote
+ - UCXzw-OdotBUcNA9yhuYQBw # Awesome
diff --git a/config/glance.yml b/config/glance.yml
new file mode 100644
index 0000000..7940191
--- /dev/null
+++ b/config/glance.yml
@@ -0,0 +1,42 @@
+server:
+ assets-path: /app/assets
+
+theme:
+ background-color: 240 27 11
+ contrast-multiplier: 1.5
+ primary-color: 321 100 71
+ positive-color: 165 78 51
+ negative-color: 360 100 71
+
+pages:
+ - name: Today
+ width: wide
+ head-widgets:
+ - $include: ./today/header.yml
+ columns:
+ - $include: ./today/left.yml
+ - $include: ./today/main.yml
+ - $include: ./today/right.yml
+
+ - name: Finance
+ head-widgets:
+ - $include: ./finance/header.yml
+ columns:
+ - $include: ./finance/left.yml
+ - $include: ./finance/main.yml
+ - $include: ./finance/right.yml
+
+ - name: Developer
+ columns:
+ - $include: ./developer/left.yml
+ - $include: ./developer/main.yml
+ - $include: ./developer/right.yml
+
+ - name: Movies
+ columns:
+ - $include: ./media/movies.yml
+
+ - name: TV
+ columns:
+ - $include: ./media/tv.yml
+
diff --git a/config/media/movies.yml b/config/media/movies.yml
new file mode 100644
index 0000000..0e0fde4
--- /dev/null
+++ b/config/media/movies.yml
@@ -0,0 +1,494 @@
+- size: full
+ widgets:
+ - type: custom-api
+ title: Latest Movies
+ frameless: true
+ cache: 5m
+ options:
+ base-url: http://umbrel.tail51ffca.ts.net:8096
+ api-key: df9e869fec0b460bb6577302f5efd049
+ user-name: "prad"
+ library-name: "Movies"
+ mode: "latest"
+ item-count: "10"
+ small-column: false
+ show-thumbnail: true
+ thumbnail-aspect-ratio: "default"
+ template: |
+ {{/* Required config options */}}
+ {{ $baseURL := .Options.StringOr "base-url" "" }}
+ {{ $apiKey := .Options.StringOr "api-key" "" }}
+ {{ $userName := .Options.StringOr "user-name" "" }}
+
+ {{/* Required config options for "latest" mode */}}
+ {{ $libraryName := .Options.StringOr "library-name" "" }}
+
+ {{/* Optional config options */}}
+ {{ $mode := .Options.StringOr "mode" "latest" }}
+ {{ $itemCount := .Options.StringOr "item-count" "10" }}
+ {{ $mediaTypes := .Options.StringOr "media-types" "Movie,Episode,MusicAlbum" }}
+ {{ $thumbAspectRatio := .Options.StringOr "thumbnail-aspect-ratio" "" }}
+ {{ $isSmallColumn:= .Options.BoolOr "small-column" false }}
+ {{ $showThumbnail := .Options.BoolOr "show-thumbnail" false }}
+ {{ $showProgressBar := .Options.BoolOr "progress-bar" true }}
+
+ {{/* Error message template */}}
+ {{ define "errorMsg" }}
+
+
{{ . }}
+ {{ end }}
+
+ {{/* Check required fields */}}
+ {{ if or (eq $baseURL "") (eq $apiKey "") (eq $userName "") (eq $mode "") (and (eq $mode "latest") (eq $libraryName "")) }}
+ {{ template "errorMsg" "Some required options are not set." }}
+ {{ else }}
+
+ {{/* Fetch user ID */}}
+ {{ $userID := "" }}
+ {{ $usersCall := newRequest (print $baseURL "/Users")
+ | withParameter "api_key" $apiKey
+ | withHeader "Accept" "application/json"
+ | getResponse }}
+
+ {{ range $i, $user := $usersCall.JSON.Array "" }}
+ {{ if eq ($user.String "Name") $userName }}
+ {{ $userID = $user.String "Id" }}
+ {{ break }}
+ {{ end }}
+ {{ end }}
+ {{ if eq $userID "" }}
+ {{ template "errorMsg" (printf "User '%s' not found." $userName) }}
+ {{ else }}
+
+ {{ $items := "" }}
+
+ {{ if eq $mode "latest" }}
+
+ {{/* Fetch library ID */}}
+ {{ $libraryID := "" }}
+ {{ $userViewsCall := newRequest (print $baseURL "/UserViews")
+ | withParameter "api_key" $apiKey
+ | withParameter "userId" $userID
+ | withHeader "Accept" "application/json"
+ | getResponse }}
+
+ {{ range $i, $item := $userViewsCall.JSON.Array "Items" }}
+ {{ if eq ($item.String "Name") $libraryName }}
+ {{ $libraryID = $item.String "Id" }}
+ {{ break }}
+ {{ end }}
+ {{ end }}
+
+ {{ if eq $libraryID "" }}
+ {{ template "errorMsg" (printf "Library '%s' not found." $libraryName) }}
+ {{ else }}
+ {{/* Fetch latest items */}}
+ {{ $latestCall := newRequest (print $baseURL "/Users/" $userID "/Items/Latest")
+ | withParameter "api_key" $apiKey
+ | withParameter "Limit" $itemCount
+ | withParameter "ParentId" $libraryID
+ | withParameter "IncludeItemTypes" $mediaTypes
+ | withParameter "GroupItems" "true"
+ | withHeader "Accept" "application/json"
+ | getResponse }}
+ {{ $items = $latestCall.JSON.Array "" }}
+ {{ end }}
+
+ {{ else if eq $mode "nextup" }}
+
+ {{/* Fetch next up items */}}
+ {{ $nextUpCall := newRequest (print $baseURL "/Shows/NextUp")
+ | withParameter "api_key" $apiKey
+ | withParameter "UserId" $userID
+ | withParameter "Limit" $itemCount
+ | withParameter "EnableResumable" "true"
+ | withHeader "Accept" "application/json"
+ | getResponse }}
+ {{ $items = $nextUpCall.JSON.Array "Items" }}
+
+ {{ else }}
+ {{ template "errorMsg" "Unknown mode, expected 'latest' or 'nextup'" }}
+ {{ end }}
+
+ {{ if eq (len $items) 0 }}
+
No items found, start streaming something!
+ {{ else }}
+
+ {{/* Display the item carousel */}}
+
+
+ {{ range $n, $item := $items }}
+ {{/* Common item variables */}}
+ {{ $mediaType := $item.String "Type" }}
+ {{ $title := $item.String "Name" }}
+ {{ $itemID := $item.String "Id" }}
+
+ {{/* Media type specific variables */}}
+ {{ $seriesTitle := "" }}
+ {{ $artist := "" }}
+ {{ $seriesID := "" }}
+ {{ $season := "" }}
+ {{ $episode := "" }}
+ {{ $playPercentage := "" }}
+ {{ $unwatchedEpisodeCount := "" }}
+
+ {{ if eq $mediaType "Movie" }}
+ {{ else if eq $mediaType "Series" }}
+ {{ $unwatchedEpisodeCount = $item.Int "UserData.UnplayedItemCount" }}
+ {{ else if eq $mediaType "Episode" }}
+ {{ $unwatchedEpisodeCount = 1 }}
+ {{ $seriesTitle = $item.String "SeriesName" }}
+ {{ $seriesID = $item.String "SeriesId" }}
+ {{ $season = $item.Int "ParentIndexNumber" }}
+ {{ $episode = $item.Int "IndexNumber" }}
+
+ {{ if $item.Exists "UserData.PlayedPercentage" }}
+ {{ $playPercentage = $item.String "UserData.PlayedPercentage" }}
+ {{ end }}
+
+ {{/* For latest always refer to the series not individual episodes */}}
+ {{ if eq $mode "latest" }}
+ {{ $itemID = $seriesID }}
+ {{ $title = $seriesTitle }}
+ {{ end }}
+ {{ else if eq $mediaType "MusicAlbum" }}
+ {{ $artist = $item.String "AlbumArtist" }}
+ {{ end }}
+
+ {{ $linkURL := print $baseURL "/web/#/details?id=" $itemID }}
+ {{ $thumbURL := "" }}
+ {{ if not (eq $playPercentage "") }}
+ {{/* $thumbURL = concat $baseURL "/Items/" $itemID "/Images/Primary?api_key=" $apiKey "&percentPlayed=" $playPercentage */}}
+ {{ $thumbURL = concat $baseURL "/Items/" $itemID "/Images/Primary?api_key=" $apiKey }}
+ {{ else }}
+ {{ $thumbURL = concat $baseURL "/Items/" $itemID "/Images/Primary?api_key=" $apiKey }}
+ {{ end }}
+
+
+ {{ if $showThumbnail }}
+
+

+
+ {{ if and ($showProgressBar) (not (eq $playPercentage "")) }}
+
+ {{ end }}
+
+ {{ end }}
+
+
+
+ {{ end }}
+
+
+ {{ end }}
+
+ {{ end }}
+
+ {{ end }}
+ - type: custom-api
+ title: Up Next Movies
+ frameless: true
+ cache: 5m
+ options:
+ base-url: http://umbrel.tail51ffca.ts.net:8096
+ api-key: df9e869fec0b460bb6577302f5efd049
+ user-name: "prad"
+ library-name: "Movies"
+ mode: "nextup"
+ item-count: "10"
+ small-column: false
+ show-thumbnail: true
+ thumbnail-aspect-ratio: "default"
+ template: |
+ {{/* Required config options */}}
+ {{ $baseURL := .Options.StringOr "base-url" "" }}
+ {{ $apiKey := .Options.StringOr "api-key" "" }}
+ {{ $userName := .Options.StringOr "user-name" "" }}
+
+ {{/* Required config options for "latest" mode */}}
+ {{ $libraryName := .Options.StringOr "library-name" "" }}
+
+ {{/* Optional config options */}}
+ {{ $mode := .Options.StringOr "mode" "latest" }}
+ {{ $itemCount := .Options.StringOr "item-count" "10" }}
+ {{ $mediaTypes := .Options.StringOr "media-types" "Movie,Episode,MusicAlbum" }}
+ {{ $thumbAspectRatio := .Options.StringOr "thumbnail-aspect-ratio" "" }}
+ {{ $isSmallColumn:= .Options.BoolOr "small-column" false }}
+ {{ $showThumbnail := .Options.BoolOr "show-thumbnail" false }}
+ {{ $showProgressBar := .Options.BoolOr "progress-bar" true }}
+
+ {{/* Error message template */}}
+ {{ define "errorMsg" }}
+
+
{{ . }}
+ {{ end }}
+
+ {{/* Check required fields */}}
+ {{ if or (eq $baseURL "") (eq $apiKey "") (eq $userName "") (eq $mode "") (and (eq $mode "latest") (eq $libraryName "")) }}
+ {{ template "errorMsg" "Some required options are not set." }}
+ {{ else }}
+
+ {{/* Fetch user ID */}}
+ {{ $userID := "" }}
+ {{ $usersCall := newRequest (print $baseURL "/Users")
+ | withParameter "api_key" $apiKey
+ | withHeader "Accept" "application/json"
+ | getResponse }}
+
+ {{ range $i, $user := $usersCall.JSON.Array "" }}
+ {{ if eq ($user.String "Name") $userName }}
+ {{ $userID = $user.String "Id" }}
+ {{ break }}
+ {{ end }}
+ {{ end }}
+ {{ if eq $userID "" }}
+ {{ template "errorMsg" (printf "User '%s' not found." $userName) }}
+ {{ else }}
+
+ {{ $items := "" }}
+
+ {{ if eq $mode "latest" }}
+
+ {{/* Fetch library ID */}}
+ {{ $libraryID := "" }}
+ {{ $userViewsCall := newRequest (print $baseURL "/UserViews")
+ | withParameter "api_key" $apiKey
+ | withParameter "userId" $userID
+ | withHeader "Accept" "application/json"
+ | getResponse }}
+
+ {{ range $i, $item := $userViewsCall.JSON.Array "Items" }}
+ {{ if eq ($item.String "Name") $libraryName }}
+ {{ $libraryID = $item.String "Id" }}
+ {{ break }}
+ {{ end }}
+ {{ end }}
+
+ {{ if eq $libraryID "" }}
+ {{ template "errorMsg" (printf "Library '%s' not found." $libraryName) }}
+ {{ else }}
+ {{/* Fetch latest items */}}
+ {{ $latestCall := newRequest (print $baseURL "/Users/" $userID "/Items/Latest")
+ | withParameter "api_key" $apiKey
+ | withParameter "Limit" $itemCount
+ | withParameter "ParentId" $libraryID
+ | withParameter "IncludeItemTypes" $mediaTypes
+ | withParameter "GroupItems" "true"
+ | withHeader "Accept" "application/json"
+ | getResponse }}
+ {{ $items = $latestCall.JSON.Array "" }}
+ {{ end }}
+
+ {{ else if eq $mode "nextup" }}
+
+ {{/* Fetch next up items */}}
+ {{ $nextUpCall := newRequest (print $baseURL "/Shows/NextUp")
+ | withParameter "api_key" $apiKey
+ | withParameter "UserId" $userID
+ | withParameter "Limit" $itemCount
+ | withParameter "EnableResumable" "true"
+ | withHeader "Accept" "application/json"
+ | getResponse }}
+ {{ $items = $nextUpCall.JSON.Array "Items" }}
+
+ {{ else }}
+ {{ template "errorMsg" "Unknown mode, expected 'latest' or 'nextup'" }}
+ {{ end }}
+
+ {{ if eq (len $items) 0 }}
+
No items found, start streaming something!
+ {{ else }}
+
+ {{/* Display the item carousel */}}
+
+
+ {{ range $n, $item := $items }}
+ {{/* Common item variables */}}
+ {{ $mediaType := $item.String "Type" }}
+ {{ $title := $item.String "Name" }}
+ {{ $itemID := $item.String "Id" }}
+
+ {{/* Media type specific variables */}}
+ {{ $seriesTitle := "" }}
+ {{ $artist := "" }}
+ {{ $seriesID := "" }}
+ {{ $season := "" }}
+ {{ $episode := "" }}
+ {{ $playPercentage := "" }}
+ {{ $unwatchedEpisodeCount := "" }}
+
+ {{ if eq $mediaType "Movie" }}
+ {{ else if eq $mediaType "Series" }}
+ {{ $unwatchedEpisodeCount = $item.Int "UserData.UnplayedItemCount" }}
+ {{ else if eq $mediaType "Episode" }}
+ {{ $unwatchedEpisodeCount = 1 }}
+ {{ $seriesTitle = $item.String "SeriesName" }}
+ {{ $seriesID = $item.String "SeriesId" }}
+ {{ $season = $item.Int "ParentIndexNumber" }}
+ {{ $episode = $item.Int "IndexNumber" }}
+
+ {{ if $item.Exists "UserData.PlayedPercentage" }}
+ {{ $playPercentage = $item.String "UserData.PlayedPercentage" }}
+ {{ end }}
+
+ {{/* For latest always refer to the series not individual episodes */}}
+ {{ if eq $mode "latest" }}
+ {{ $itemID = $seriesID }}
+ {{ $title = $seriesTitle }}
+ {{ end }}
+ {{ else if eq $mediaType "MusicAlbum" }}
+ {{ $artist = $item.String "AlbumArtist" }}
+ {{ end }}
+
+ {{ $linkURL := print $baseURL "/web/#/details?id=" $itemID }}
+ {{ $thumbURL := "" }}
+ {{ if not (eq $playPercentage "") }}
+ {{/* $thumbURL = concat $baseURL "/Items/" $itemID "/Images/Primary?api_key=" $apiKey "&percentPlayed=" $playPercentage */}}
+ {{ $thumbURL = concat $baseURL "/Items/" $itemID "/Images/Primary?api_key=" $apiKey }}
+ {{ else }}
+ {{ $thumbURL = concat $baseURL "/Items/" $itemID "/Images/Primary?api_key=" $apiKey }}
+ {{ end }}
+
+
+ {{ if $showThumbnail }}
+
+

+
+ {{ if and ($showProgressBar) (not (eq $playPercentage "")) }}
+
+ {{ end }}
+
+ {{ end }}
+
+
+
+ {{ end }}
+
+
+ {{ end }}
+
+ {{ end }}
+
+ {{ end }}
+
+
diff --git a/config/media/tv.yml b/config/media/tv.yml
new file mode 100644
index 0000000..c29ef1a
--- /dev/null
+++ b/config/media/tv.yml
@@ -0,0 +1,494 @@
+- size: full
+ widgets:
+ - type: custom-api
+ title: Latest TV
+ frameless: true
+ cache: 5m
+ options:
+ base-url: http://umbrel.tail51ffca.ts.net:8096
+ api-key: df9e869fec0b460bb6577302f5efd049
+ user-name: "prad"
+ library-name: "TV"
+ mode: "latest"
+ item-count: "10"
+ small-column: false
+ show-thumbnail: true
+ thumbnail-aspect-ratio: "default"
+ template: |
+ {{/* Required config options */}}
+ {{ $baseURL := .Options.StringOr "base-url" "" }}
+ {{ $apiKey := .Options.StringOr "api-key" "" }}
+ {{ $userName := .Options.StringOr "user-name" "" }}
+
+ {{/* Required config options for "latest" mode */}}
+ {{ $libraryName := .Options.StringOr "library-name" "" }}
+
+ {{/* Optional config options */}}
+ {{ $mode := .Options.StringOr "mode" "latest" }}
+ {{ $itemCount := .Options.StringOr "item-count" "10" }}
+ {{ $mediaTypes := .Options.StringOr "media-types" "Movie,Episode,MusicAlbum" }}
+ {{ $thumbAspectRatio := .Options.StringOr "thumbnail-aspect-ratio" "" }}
+ {{ $isSmallColumn:= .Options.BoolOr "small-column" false }}
+ {{ $showThumbnail := .Options.BoolOr "show-thumbnail" false }}
+ {{ $showProgressBar := .Options.BoolOr "progress-bar" true }}
+
+ {{/* Error message template */}}
+ {{ define "errorMsg" }}
+
+
{{ . }}
+ {{ end }}
+
+ {{/* Check required fields */}}
+ {{ if or (eq $baseURL "") (eq $apiKey "") (eq $userName "") (eq $mode "") (and (eq $mode "latest") (eq $libraryName "")) }}
+ {{ template "errorMsg" "Some required options are not set." }}
+ {{ else }}
+
+ {{/* Fetch user ID */}}
+ {{ $userID := "" }}
+ {{ $usersCall := newRequest (print $baseURL "/Users")
+ | withParameter "api_key" $apiKey
+ | withHeader "Accept" "application/json"
+ | getResponse }}
+
+ {{ range $i, $user := $usersCall.JSON.Array "" }}
+ {{ if eq ($user.String "Name") $userName }}
+ {{ $userID = $user.String "Id" }}
+ {{ break }}
+ {{ end }}
+ {{ end }}
+ {{ if eq $userID "" }}
+ {{ template "errorMsg" (printf "User '%s' not found." $userName) }}
+ {{ else }}
+
+ {{ $items := "" }}
+
+ {{ if eq $mode "latest" }}
+
+ {{/* Fetch library ID */}}
+ {{ $libraryID := "" }}
+ {{ $userViewsCall := newRequest (print $baseURL "/UserViews")
+ | withParameter "api_key" $apiKey
+ | withParameter "userId" $userID
+ | withHeader "Accept" "application/json"
+ | getResponse }}
+
+ {{ range $i, $item := $userViewsCall.JSON.Array "Items" }}
+ {{ if eq ($item.String "Name") $libraryName }}
+ {{ $libraryID = $item.String "Id" }}
+ {{ break }}
+ {{ end }}
+ {{ end }}
+
+ {{ if eq $libraryID "" }}
+ {{ template "errorMsg" (printf "Library '%s' not found." $libraryName) }}
+ {{ else }}
+ {{/* Fetch latest items */}}
+ {{ $latestCall := newRequest (print $baseURL "/Users/" $userID "/Items/Latest")
+ | withParameter "api_key" $apiKey
+ | withParameter "Limit" $itemCount
+ | withParameter "ParentId" $libraryID
+ | withParameter "IncludeItemTypes" $mediaTypes
+ | withParameter "GroupItems" "true"
+ | withHeader "Accept" "application/json"
+ | getResponse }}
+ {{ $items = $latestCall.JSON.Array "" }}
+ {{ end }}
+
+ {{ else if eq $mode "nextup" }}
+
+ {{/* Fetch next up items */}}
+ {{ $nextUpCall := newRequest (print $baseURL "/Shows/NextUp")
+ | withParameter "api_key" $apiKey
+ | withParameter "UserId" $userID
+ | withParameter "Limit" $itemCount
+ | withParameter "EnableResumable" "true"
+ | withHeader "Accept" "application/json"
+ | getResponse }}
+ {{ $items = $nextUpCall.JSON.Array "Items" }}
+
+ {{ else }}
+ {{ template "errorMsg" "Unknown mode, expected 'latest' or 'nextup'" }}
+ {{ end }}
+
+ {{ if eq (len $items) 0 }}
+
No items found, start streaming something!
+ {{ else }}
+
+ {{/* Display the item carousel */}}
+
+
+ {{ range $n, $item := $items }}
+ {{/* Common item variables */}}
+ {{ $mediaType := $item.String "Type" }}
+ {{ $title := $item.String "Name" }}
+ {{ $itemID := $item.String "Id" }}
+
+ {{/* Media type specific variables */}}
+ {{ $seriesTitle := "" }}
+ {{ $artist := "" }}
+ {{ $seriesID := "" }}
+ {{ $season := "" }}
+ {{ $episode := "" }}
+ {{ $playPercentage := "" }}
+ {{ $unwatchedEpisodeCount := "" }}
+
+ {{ if eq $mediaType "Movie" }}
+ {{ else if eq $mediaType "Series" }}
+ {{ $unwatchedEpisodeCount = $item.Int "UserData.UnplayedItemCount" }}
+ {{ else if eq $mediaType "Episode" }}
+ {{ $unwatchedEpisodeCount = 1 }}
+ {{ $seriesTitle = $item.String "SeriesName" }}
+ {{ $seriesID = $item.String "SeriesId" }}
+ {{ $season = $item.Int "ParentIndexNumber" }}
+ {{ $episode = $item.Int "IndexNumber" }}
+
+ {{ if $item.Exists "UserData.PlayedPercentage" }}
+ {{ $playPercentage = $item.String "UserData.PlayedPercentage" }}
+ {{ end }}
+
+ {{/* For latest always refer to the series not individual episodes */}}
+ {{ if eq $mode "latest" }}
+ {{ $itemID = $seriesID }}
+ {{ $title = $seriesTitle }}
+ {{ end }}
+ {{ else if eq $mediaType "MusicAlbum" }}
+ {{ $artist = $item.String "AlbumArtist" }}
+ {{ end }}
+
+ {{ $linkURL := print $baseURL "/web/#/details?id=" $itemID }}
+ {{ $thumbURL := "" }}
+ {{ if not (eq $playPercentage "") }}
+ {{/* $thumbURL = concat $baseURL "/Items/" $itemID "/Images/Primary?api_key=" $apiKey "&percentPlayed=" $playPercentage */}}
+ {{ $thumbURL = concat $baseURL "/Items/" $itemID "/Images/Primary?api_key=" $apiKey }}
+ {{ else }}
+ {{ $thumbURL = concat $baseURL "/Items/" $itemID "/Images/Primary?api_key=" $apiKey }}
+ {{ end }}
+
+
+ {{ if $showThumbnail }}
+
+

+
+ {{ if and ($showProgressBar) (not (eq $playPercentage "")) }}
+
+ {{ end }}
+
+ {{ end }}
+
+
+
+ {{ end }}
+
+
+ {{ end }}
+
+ {{ end }}
+
+ {{ end }}
+ - type: custom-api
+ title: Up Next TV
+ frameless: true
+ cache: 5m
+ options:
+ base-url: http://umbrel.tail51ffca.ts.net:8096
+ api-key: df9e869fec0b460bb6577302f5efd049
+ user-name: "prad"
+ library-name: "TV"
+ mode: "nextup"
+ item-count: "10"
+ small-column: false
+ show-thumbnail: true
+ thumbnail-aspect-ratio: "default"
+ template: |
+ {{/* Required config options */}}
+ {{ $baseURL := .Options.StringOr "base-url" "" }}
+ {{ $apiKey := .Options.StringOr "api-key" "" }}
+ {{ $userName := .Options.StringOr "user-name" "" }}
+
+ {{/* Required config options for "latest" mode */}}
+ {{ $libraryName := .Options.StringOr "library-name" "" }}
+
+ {{/* Optional config options */}}
+ {{ $mode := .Options.StringOr "mode" "latest" }}
+ {{ $itemCount := .Options.StringOr "item-count" "10" }}
+ {{ $mediaTypes := .Options.StringOr "media-types" "Movie,Episode,MusicAlbum" }}
+ {{ $thumbAspectRatio := .Options.StringOr "thumbnail-aspect-ratio" "" }}
+ {{ $isSmallColumn:= .Options.BoolOr "small-column" false }}
+ {{ $showThumbnail := .Options.BoolOr "show-thumbnail" false }}
+ {{ $showProgressBar := .Options.BoolOr "progress-bar" true }}
+
+ {{/* Error message template */}}
+ {{ define "errorMsg" }}
+
+
{{ . }}
+ {{ end }}
+
+ {{/* Check required fields */}}
+ {{ if or (eq $baseURL "") (eq $apiKey "") (eq $userName "") (eq $mode "") (and (eq $mode "latest") (eq $libraryName "")) }}
+ {{ template "errorMsg" "Some required options are not set." }}
+ {{ else }}
+
+ {{/* Fetch user ID */}}
+ {{ $userID := "" }}
+ {{ $usersCall := newRequest (print $baseURL "/Users")
+ | withParameter "api_key" $apiKey
+ | withHeader "Accept" "application/json"
+ | getResponse }}
+
+ {{ range $i, $user := $usersCall.JSON.Array "" }}
+ {{ if eq ($user.String "Name") $userName }}
+ {{ $userID = $user.String "Id" }}
+ {{ break }}
+ {{ end }}
+ {{ end }}
+ {{ if eq $userID "" }}
+ {{ template "errorMsg" (printf "User '%s' not found." $userName) }}
+ {{ else }}
+
+ {{ $items := "" }}
+
+ {{ if eq $mode "latest" }}
+
+ {{/* Fetch library ID */}}
+ {{ $libraryID := "" }}
+ {{ $userViewsCall := newRequest (print $baseURL "/UserViews")
+ | withParameter "api_key" $apiKey
+ | withParameter "userId" $userID
+ | withHeader "Accept" "application/json"
+ | getResponse }}
+
+ {{ range $i, $item := $userViewsCall.JSON.Array "Items" }}
+ {{ if eq ($item.String "Name") $libraryName }}
+ {{ $libraryID = $item.String "Id" }}
+ {{ break }}
+ {{ end }}
+ {{ end }}
+
+ {{ if eq $libraryID "" }}
+ {{ template "errorMsg" (printf "Library '%s' not found." $libraryName) }}
+ {{ else }}
+ {{/* Fetch latest items */}}
+ {{ $latestCall := newRequest (print $baseURL "/Users/" $userID "/Items/Latest")
+ | withParameter "api_key" $apiKey
+ | withParameter "Limit" $itemCount
+ | withParameter "ParentId" $libraryID
+ | withParameter "IncludeItemTypes" $mediaTypes
+ | withParameter "GroupItems" "true"
+ | withHeader "Accept" "application/json"
+ | getResponse }}
+ {{ $items = $latestCall.JSON.Array "" }}
+ {{ end }}
+
+ {{ else if eq $mode "nextup" }}
+
+ {{/* Fetch next up items */}}
+ {{ $nextUpCall := newRequest (print $baseURL "/Shows/NextUp")
+ | withParameter "api_key" $apiKey
+ | withParameter "UserId" $userID
+ | withParameter "Limit" $itemCount
+ | withParameter "EnableResumable" "true"
+ | withHeader "Accept" "application/json"
+ | getResponse }}
+ {{ $items = $nextUpCall.JSON.Array "Items" }}
+
+ {{ else }}
+ {{ template "errorMsg" "Unknown mode, expected 'latest' or 'nextup'" }}
+ {{ end }}
+
+ {{ if eq (len $items) 0 }}
+
No items found, start streaming something!
+ {{ else }}
+
+ {{/* Display the item carousel */}}
+
+
+ {{ range $n, $item := $items }}
+ {{/* Common item variables */}}
+ {{ $mediaType := $item.String "Type" }}
+ {{ $title := $item.String "Name" }}
+ {{ $itemID := $item.String "Id" }}
+
+ {{/* Media type specific variables */}}
+ {{ $seriesTitle := "" }}
+ {{ $artist := "" }}
+ {{ $seriesID := "" }}
+ {{ $season := "" }}
+ {{ $episode := "" }}
+ {{ $playPercentage := "" }}
+ {{ $unwatchedEpisodeCount := "" }}
+
+ {{ if eq $mediaType "Movie" }}
+ {{ else if eq $mediaType "Series" }}
+ {{ $unwatchedEpisodeCount = $item.Int "UserData.UnplayedItemCount" }}
+ {{ else if eq $mediaType "Episode" }}
+ {{ $unwatchedEpisodeCount = 1 }}
+ {{ $seriesTitle = $item.String "SeriesName" }}
+ {{ $seriesID = $item.String "SeriesId" }}
+ {{ $season = $item.Int "ParentIndexNumber" }}
+ {{ $episode = $item.Int "IndexNumber" }}
+
+ {{ if $item.Exists "UserData.PlayedPercentage" }}
+ {{ $playPercentage = $item.String "UserData.PlayedPercentage" }}
+ {{ end }}
+
+ {{/* For latest always refer to the series not individual episodes */}}
+ {{ if eq $mode "latest" }}
+ {{ $itemID = $seriesID }}
+ {{ $title = $seriesTitle }}
+ {{ end }}
+ {{ else if eq $mediaType "MusicAlbum" }}
+ {{ $artist = $item.String "AlbumArtist" }}
+ {{ end }}
+
+ {{ $linkURL := print $baseURL "/web/#/details?id=" $itemID }}
+ {{ $thumbURL := "" }}
+ {{ if not (eq $playPercentage "") }}
+ {{/* $thumbURL = concat $baseURL "/Items/" $itemID "/Images/Primary?api_key=" $apiKey "&percentPlayed=" $playPercentage */}}
+ {{ $thumbURL = concat $baseURL "/Items/" $itemID "/Images/Primary?api_key=" $apiKey }}
+ {{ else }}
+ {{ $thumbURL = concat $baseURL "/Items/" $itemID "/Images/Primary?api_key=" $apiKey }}
+ {{ end }}
+
+
+ {{ if $showThumbnail }}
+
+

+
+ {{ if and ($showProgressBar) (not (eq $playPercentage "")) }}
+
+ {{ end }}
+
+ {{ end }}
+
+
+
+ {{ end }}
+
+
+ {{ end }}
+
+ {{ end }}
+
+ {{ end }}
+
+
diff --git a/config/today/header.yml b/config/today/header.yml
new file mode 100644
index 0000000..dff5119
--- /dev/null
+++ b/config/today/header.yml
@@ -0,0 +1,11 @@
+- type: markets
+ hide-header: true
+ markets:
+ - symbol: BTC-USD
+ name: Bitcoin
+ - symbol: ETH-USD
+ name: Ethereum
+ - symbol: MAGS
+ name: Mag7
+ - symbol: QQQ
+ name: Nasdaq
diff --git a/config/today/left.yml b/config/today/left.yml
new file mode 100644
index 0000000..546919d
--- /dev/null
+++ b/config/today/left.yml
@@ -0,0 +1,25 @@
+- size: small
+ widgets:
+ - type: weather
+ location: Ashburn, Virginia, United States
+ units: imperial # alternatively "metric"
+ hour-format: 12h # alternatively "24h"
+ - $include: ./widgets/ical-work.yml
+ - type: clock
+ hour-format: 12h
+ timezones:
+ - timezone: America/Los_Angeles
+ label: Los Angeles
+ - timezone: Asia/Singapore
+ label: Singapore
+ - timezone: Europe/London
+ label: London
+ - type: rss
+ title: Deals
+ limit: 10
+ collapse-after: 2
+ feeds:
+ - url: https://rss.app/feeds/rb0smDdeyhIkusel.xml
+ title: Crypto Fundraising
+
+
diff --git a/config/today/main.yml b/config/today/main.yml
new file mode 100644
index 0000000..0aa9123
--- /dev/null
+++ b/config/today/main.yml
@@ -0,0 +1,10 @@
+- size: full
+ widgets:
+ # Top Headlines
+ - $include: ./widgets/headlines.yml
+ # Community Discussions
+ - $include: ./widgets/reddit.yml
+ # Videos
+ - $include: ./widgets/youtube.yml
+
+
diff --git a/config/today/right.yml b/config/today/right.yml
new file mode 100644
index 0000000..0dbc535
--- /dev/null
+++ b/config/today/right.yml
@@ -0,0 +1,13 @@
+- size: small
+ widgets:
+ - type: to-do
+ - type: calendar
+ first-day-of-week: sunday
+ - $include: ./widgets/ical-personal.yml
+ - type: rss
+ title: Crypto
+ limit: 20
+ collapse-after: 5
+ feeds:
+ - url: https://rss.app/feeds/wiGtpLZuOjLgKlC4.xml
+ title: WatcherGuru
diff --git a/config/today/widgets/alerts.yml b/config/today/widgets/alerts.yml
new file mode 100644
index 0000000..ec15bfe
--- /dev/null
+++ b/config/today/widgets/alerts.yml
@@ -0,0 +1,28 @@
+- type: group
+ widgets:
+ - type: rss
+ title: Crypto
+ limit: 30
+ collapse-after: 13
+ feeds:
+ - url: https://rss.app/feeds/wiGtpLZuOjLgKlC4.xml
+ title: WatcherGuru
+
+ - type: rss
+ title: Deals
+ limit: 30
+ collapse-after: 13
+ feeds:
+ - url: https://rss.app/feeds/rb0smDdeyhIkusel.xml
+ title: Crypto Fundraising
+
+
+ - type: rss
+ title: Finance
+ limit: 30
+ collapse-after: 13
+ feeds:
+ - url: https://www.ft.com/technology?format=rss
+ title: Financial Times
+ - url: https://feeds.a.dj.com/rss/RSSMarketsMain.xml
+ title: Wall Street Journal
diff --git a/config/today/widgets/headlines.yml b/config/today/widgets/headlines.yml
new file mode 100644
index 0000000..2c34665
--- /dev/null
+++ b/config/today/widgets/headlines.yml
@@ -0,0 +1,24 @@
+- type: group
+ widgets:
+ - type: rss
+ title: Techmeme
+ style: horizontal-cards
+ feeds:
+ - url: https://www.techmeme.com/feed.xml
+ title: TechMeme
+
+ - type: rss
+ title: ProductHunt
+ style: horizontal-cards
+ feeds:
+ - url: https://rss.app/feeds/_tBKt0Q2vzII4baWl.xml
+ title: ProductHunt
+
+ - type: rss
+ title: TerminalTrove
+ style: horizontal-cards
+ feeds:
+ - url: https://terminaltrove.com/new.xml
+ title: TerminalTrove
+
+
diff --git a/config/today/widgets/ical-personal.yml b/config/today/widgets/ical-personal.yml
new file mode 100644
index 0000000..a0572aa
--- /dev/null
+++ b/config/today/widgets/ical-personal.yml
@@ -0,0 +1,76 @@
+- type: custom-api
+ title: Personal Events
+ cache: 15m
+ url: http://glances-ical-api:8076/events
+ parameters:
+ url: https://calendar.google.com/calendar/ical/prnk28%40gmail.com/public/basic.ics
+ limit: 5
+ template: |
+ {{ $events := .JSON.Array "events" }}
+ {{ $count := len $events }}
+ {{ $limit := 3 }}
+
+ {{ if eq $count 0 }}
+
+ No entries found.
+
+ {{ end }}
+
+
+
+ {{ range $i, $e := $events }}
+ {{ $ongoing := $e.Bool "ongoing" }}
+ {{ if $ongoing }}
+ {{ $start := $e.String "start" | parseTime "rfc3339" }}
+ {{ $end := $e.String "end" | parseTime "rfc3339" }}
+ {{ $name := $e.String "name" }}
+ {{ $url := $e.String "url" }}
+ -
+
+
+
+ {{ if $url }}
+
{{ $name }}
+ {{ else }}
+
{{ $name }}
+ {{ end }}
+
{{ $start | formatTime "Mon, 02 Jan 2006" }}
+
+
+
+ ends
+
+
+
+ {{ end }}
+ {{ end }}
+
+
+
+ {{ $shown := 0 }}
+
+ {{ range $i, $e := $events }}
+ {{ $ongoing := $e.Bool "ongoing" }}
+ {{ if not $ongoing }}
+ {{ $start := $e.String "start" | parseTime "rfc3339" }}
+ {{ $name := $e.String "name" }}
+ {{ $url := $e.String "url" }}
+ -
+
+
+
+ {{ if $url }}
+
{{ $name }}
+ {{ else }}
+
{{ $name }}
+ {{ end }}
+
{{ $start | formatTime "Mon, 02 Jan 2006" }}
+
+
+
+
+
+ {{ $shown = add $shown 1 }}
+ {{ end }}
+ {{ end }}
+
diff --git a/config/today/widgets/ical-work.yml b/config/today/widgets/ical-work.yml
new file mode 100644
index 0000000..1e4eb7c
--- /dev/null
+++ b/config/today/widgets/ical-work.yml
@@ -0,0 +1,76 @@
+- type: custom-api
+ title: Work Schedule
+ cache: 15m
+ url: http://glances-ical-api:8076/events
+ parameters:
+ url: https://calendar.google.com/calendar/ical/prad%40sonr.io/public/basic.ics
+ limit: 5
+ template: |
+ {{ $events := .JSON.Array "events" }}
+ {{ $count := len $events }}
+ {{ $limit := 3 }}
+
+ {{ if eq $count 0 }}
+
+ No entries found.
+
+ {{ end }}
+
+
+
+ {{ range $i, $e := $events }}
+ {{ $ongoing := $e.Bool "ongoing" }}
+ {{ if $ongoing }}
+ {{ $start := $e.String "start" | parseTime "rfc3339" }}
+ {{ $end := $e.String "end" | parseTime "rfc3339" }}
+ {{ $name := $e.String "name" }}
+ {{ $url := $e.String "url" }}
+ -
+
+
+
+ {{ if $url }}
+
{{ $name }}
+ {{ else }}
+
{{ $name }}
+ {{ end }}
+
{{ $start | formatTime "Mon, 02 Jan 2006" }}
+
+
+
+ ends
+
+
+
+ {{ end }}
+ {{ end }}
+
+
+
+ {{ $shown := 0 }}
+
+ {{ range $i, $e := $events }}
+ {{ $ongoing := $e.Bool "ongoing" }}
+ {{ if not $ongoing }}
+ {{ $start := $e.String "start" | parseTime "rfc3339" }}
+ {{ $name := $e.String "name" }}
+ {{ $url := $e.String "url" }}
+ -
+
+
+
+ {{ if $url }}
+
{{ $name }}
+ {{ else }}
+
{{ $name }}
+ {{ end }}
+
{{ $start | formatTime "Mon, 02 Jan 2006" }}
+
+
+
+
+
+ {{ $shown = add $shown 1 }}
+ {{ end }}
+ {{ end }}
+
diff --git a/config/today/widgets/reddit.yml b/config/today/widgets/reddit.yml
new file mode 100644
index 0000000..a606e5d
--- /dev/null
+++ b/config/today/widgets/reddit.yml
@@ -0,0 +1,41 @@
+# Community Discussions
+- type: split-column
+ max-columns: 3
+ widgets:
+ - type: reddit
+ show-thumbnails: true
+ subreddit: commandline
+ - type: reddit
+ show-thumbnails: true
+ subreddit: ethereum
+ - type: reddit
+ show-thumbnails: true
+ subreddit: golang
+ - type: reddit
+ show-thumbnails: true
+ subreddit: hyprland
+ - type: reddit
+ show-thumbnails: true
+ subreddit: ipfs
+ - type: reddit
+ show-thumbnails: true
+ subreddit: libp2p
+ - type: reddit
+ show-thumbnails: true
+ subreddit: LinuxCirclejerk
+ - type: reddit
+ show-thumbnails: true
+ subreddit: niri
+ - type: reddit
+ show-thumbnails: true
+ subreddit: omarchy
+ - type: reddit
+ show-thumbnails: true
+ subreddit: selfhosted
+ - type: reddit
+ show-thumbnails: true
+ subreddit: termux
+ - type: reddit
+ show-thumbnails: true
+ subreddit: unixporn
+
diff --git a/config/today/widgets/youtube.yml b/config/today/widgets/youtube.yml
new file mode 100644
index 0000000..f9afb48
--- /dev/null
+++ b/config/today/widgets/youtube.yml
@@ -0,0 +1,94 @@
+- type: group
+ widgets:
+ # Podcasts & Commentary
+ - type: videos
+ style: grid-cards
+ collapse-after-rows: 2
+ title: Pop-Culture
+ channels:
+ - UCzQUP1qoWDoEbmsQxvdjxgQ # PowerfulJRE
+ - UCJIfeSCssxSC_Dhc5s7woww # Lex Clips
+ - UC5PstSsGrRwj2o6asQpC4Rg # Flagrant 2
+ - UCKrpnfpTwncQ050VFXcVkuQ # Network State Podcast
+ - UC7oPkqeHTwuOZ5CZ-R9f-6w # Triggernometry
+ - UCIHdDJ0tjn_3j-FS7s_X1kQ # Valuetainment
+
+ # Programming & Tech
+ - type: videos
+ style: grid-cards
+ collapse-after-rows: 2
+ title: Programming
+ channels:
+ - UCUyeluBRhGPCW4rPe_UvBZQ # ThePrimeTime
+ - UCjSEJkpGbcZhvo0lr-44X_w # TechHut
+ - UCXuqSBlHAE6Xw-yeJA0Tunw # Linus Tech Tips
+ - UCs6KfncB4OV6Vug4o_bzijg # Techlore
+ - UCTwxaBjziKfy6y_uWu30orAby # BugBytes
+ - UCYeiozh-4QwuC1sjgCmB92w # DevOps Toolbox
+ - UCsBjURrPoezykLs9EqgamOA # Angular Firebase
+ - UC-mTIBh__DzAqW495JHXy5A # The Coding Gopher
+ - UC2WHjPDvbE6O328n17ZGcfg # ForrestKnight
+ - UCsUalyRg43M8D60mtHe6YcAD # Honeypot
+ - UCgfe2ooZD3VJPB6aJAnuQng # cloud
+ - UCdjsUXJ3QawK4O5L1kqqsew # IPFS
+ - UC3Wn3dABlgESm8Bzn8Vamgg # Sideprojects
+ - UCtMVHI3AJD4Qk4hcbZnI9ZQ # SomeOrdinaryGamers
+ - UCNJ1Ymd5yFuUPtn21xtRbbw # AI Explained
+ - UCutCcajxhR33k9UR-DdLsAQ # Systems Innovation
+
+ # Startups & Business
+ - type: videos
+ style: grid-cards
+ collapse-after-rows: 2
+ title: Startups
+ channels:
+ - UCcefcZRL2oaA_uBNeo5UOWg # Y Combinator
+ - UCllx7jOjBL794FK67jtb5Og # Dalton + Michael
+ - UCxIJaCMEptJjxmmQgGFsnCg # How to Start a Startup
+ - UCQrZfl1FeCN9c71yjOzWVRw # Startup Archive
+ - UCtieZ1nS_tpjGEama6Kjxcg # Steve Jobs Archive
+ - UCm_WkN0wg49-eG4SymAQ_4g # Harvard Innovation Labs
+ - UCSGc7c1yKxo-Uhz5_oivrfg # The Startup Club by Slidebean
+ - UCGq-a57w-aPwyi3pW7XLiHw # The Diary Of A CEO
+
+ # AI & Futurism
+ - type: videos
+ style: grid-cards
+ collapse-after-rows: 2
+ title: Futurism
+ channels:
+ - UCawZsQWqfGSbCI5yjkdVkTA # Matthew Berman
+ - UCZFipeZtQM5CKUjx6grh54g # Isaac Arthur
+ - UCR9sFzaG9Ia_kXJhfxtFMB # melodysheep
+ - UCSeuZvDGosq1zC6NqgqGo-Q # merlin
+ # Finance
+ - type: videos
+ style: grid-cards
+ collapse-after-rows: 2
+ title: Finance
+ channels:
+ - UCAeAB8ABXGoGMbXuYPmiu2A # The Swedish Investor
+ - UCvSXMi2LebwJEM1s4bz5IBA # New Money
+ - UCV6KDgJskWaEckne5aPA0aQ # Graham Stephan
+ - UCAzhpt9DmG6PnHXjmJTvRGQ # Federal Reserve
+
+
+ # Science, History & Education
+ - type: videos
+ style: grid-cards
+ collapse-after-rows: 2
+ title: Education
+ channels:
+ - UCHnyfMqiRRG1u-2MsSQLbXA # Veritasium
+ - UCsXVk37bltHxD1rDPwtNM8QL # Kurzgesagt – In a Nutshell
+ - UCxqAWLTk1CmBvZFPzeZMd9A # Domain of Science
+ - UCR2uRTQ53V_egXKFflMMaaw # Artem Kirsanov
+ - UCeUJFQ0D9qs6aVNyUt9fkeQ # The Armchair Historian
+ - UC5Dw9TFdbPJoTDMSiJdIQT # Whatifalthist
+ - UC0NCSdGglnmdWg-qHALhu1w # History102
+ - UCpCSAcbqs-sjEVfk_hMfY9w # MajorPrep
+ - UCmdugCMpC7BawYGwkRs5drQ # PhiloNautica
+ - UC4gHARRmw40kcLZgINlTK_g # Religion Camp
+ - UCPPrhcXtGR2aMoxrPJRKPLg # Para Bellum
+ - UCQUnqaW8Q33siyLYhisN7HQ # Antidote
+ - UCXzw-OdotBUcNA9yhuYQBw # Awesome
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..2e03f52
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,29 @@
+services:
+ glance:
+ env_file:
+ - .env
+ container_name: glance
+ image: glanceapp/glance
+ restart: unless-stopped
+ volumes:
+ - ./config:/app/config
+ - ./assets:/app/assets
+ - /etc/localtime:/etc/localtime:ro
+ - /var/run/docker.sock:/var/run/docker.sock:ro
+ ports:
+ - 6969:8080
+
+ glance-github-graph:
+ image: haumea/glance-github-graph:latest
+ ports:
+ - 8181:8080
+ environment:
+ - CACHE_ENABLED=true
+ - CACHE_TYPE=memory
+ - CACHE_DURATION_SECS=3600
+
+ glances-ical-api:
+ image: ghcr.io/awildleon/glance-ical-events:latest
+ restart: always
+ ports:
+ - "8076:8072"