diff --git a/.dockerignore b/.dockerignore
index 8708dce..1f9515c 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -5,6 +5,7 @@
# Only add necessary files to the Docker build context (Dockerfiles are always included implicitly)
!/build/
!/internal/
+!/pkg/
!/go.mod
!/go.sum
!main.go
diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml
new file mode 100644
index 0000000..bdd4fe6
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.yml
@@ -0,0 +1,37 @@
+name: Bug report
+description: Let us know if something isn't working as expected
+labels: ["bug report"]
+body:
+ - type: markdown
+ attributes:
+ value: |
+ > [!NOTE]
+ >
+ > Do not prefix your title with "[BUG]", "[Bug report]", etc., a label will be added automatically.
+
+ If you're unsure whether you're experiencing a bug or not, consider using the [Discussions](https://github.com/glanceapp/glance/discussions) or [Discord](https://discord.com/invite/7KQ7Xa9kJd) to ask for help.
+
+ Please include only the information you think is relevant to the bug:
+
+ * How did you install Glance? (Docker container, manual binary install, etc)
+ * Which version of Glance are you using?
+ * Include the relevant parts of your `glance.yml` if applicable (widget, data source, properties used, etc)
+ * Include any relevant logs or screenshots if applicable
+ * Is the issue specific to a certain browser or OS?
+ * Steps to reliably reproduce the issue
+ * Are you hosting Glance on a VPS?
+ * Anything else you think might be relevant
+
+ **No need to copy the above list into your description, it's just a guide to help you provide the most useful information.**
+
+ - type: textarea
+ id: description
+ validations:
+ required: true
+ attributes:
+ label: Description
+
+ - type: markdown
+ attributes:
+ value: |
+ Thank you for taking the time to submit a bug report.
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 0000000..e8c34af
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,8 @@
+blank_issues_enabled: false
+contact_links:
+ - name: Discussions
+ url: https://github.com/glanceapp/glance/discussions
+ about: For help, feedback, guides, resources and more
+ - name: Discord
+ url: https://discord.com/invite/7KQ7Xa9kJd
+ about: Much like the discussions but more chatty
diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml
new file mode 100644
index 0000000..d8f5343
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.yml
@@ -0,0 +1,33 @@
+name: Feature request
+description: Share your ideas for new features or improvements
+labels: ["feature request"]
+body:
+ - type: markdown
+ attributes:
+ value: |
+ > [!NOTE]
+ >
+ > Do not prefix your title with "[REQUEST]", "[Feature request]", etc., a label will be added automatically.
+
+ Please provide a detailed description of what the feature would do and what it would look like:
+
+ * What problem would this feature solve?
+ * Are there any potential downsides to this feature?
+ * If applicable, what would the configuration for this feature look like?
+ * Are there any existing examples of this feature in other software?
+ * If applicable, include any external documentation required to implement this feature
+ * Anything else you think might be relevant
+
+ **No need to copy the above list into your description, it's just a guide to help you provide the most useful information.**
+
+ - type: textarea
+ id: description
+ validations:
+ required: true
+ attributes:
+ label: Description
+
+ - type: markdown
+ attributes:
+ value: |
+ Thank you for taking the time to submit your idea.
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 22b3d05..690586f 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -1,7 +1 @@
-
+
diff --git a/.gitignore b/.gitignore
index f7e0f6c..2cd84fc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
/assets
/build
/playground
-glance*.yml
+/.idea
+/glance*.yml
diff --git a/Dockerfile b/Dockerfile
index e4019ba..4d8cd87 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,13 +1,16 @@
-FROM golang:1.22.5-alpine3.20 AS builder
+FROM golang:1.23.6-alpine3.21 AS builder
WORKDIR /app
COPY . /app
RUN CGO_ENABLED=0 go build .
-FROM alpine:3.20
+FROM alpine:3.21
WORKDIR /app
COPY --from=builder /app/glance .
+HEALTHCHECK --timeout=10s --start-period=60s --interval=60s \
+ CMD wget --spider -q http://localhost:8080/api/healthz
+
EXPOSE 8080/tcp
-ENTRYPOINT ["/app/glance"]
+ENTRYPOINT ["/app/glance", "--config", "/app/config/glance.yml"]
diff --git a/Dockerfile.goreleaser b/Dockerfile.goreleaser
index dec9ac4..bbfa8ad 100644
--- a/Dockerfile.goreleaser
+++ b/Dockerfile.goreleaser
@@ -1,8 +1,10 @@
-FROM alpine:3.20
+FROM alpine:3.21
WORKDIR /app
COPY glance .
-EXPOSE 8080/tcp
+HEALTHCHECK --timeout=10s --start-period=60s --interval=60s \
+ CMD wget --spider -q http://localhost:8080/api/healthz
-ENTRYPOINT ["/app/glance"]
+EXPOSE 8080/tcp
+ENTRYPOINT ["/app/glance", "--config", "/app/config/glance.yml"]
diff --git a/README.md b/README.md
index 0e8cfb4..cf64d2f 100644
--- a/README.md
+++ b/README.md
@@ -2,110 +2,404 @@
-
+
-### Features
-#### Various widgets
+## Features
+### Various widgets
* RSS feeds
* Subreddit posts
-* Weather
-* Bookmarks
-* Hacker News
-* Lobsters
-* Latest YouTube videos from specific channels
-* Clock
-* Calendar
-* Stocks
-* iframe
-* Twitch channels & top games
-* GitHub releases
-* Repository overview
-* Site monitor
-* Search box
+* Hacker News posts
+* Weather forecasts
+* YouTube channel uploads
+* Twitch channels
+* Market prices
+* Docker containers status
+* Server stats
+* Custom widgets
+* [and many more...](docs/configuration.md)
-#### Themeable
-
+### Fast and lightweight
+* Low memory usage
+* Few dependencies
+* Minimal vanilla JS
+* Single <20mb binary available for multiple OSs & architectures and just as small Docker container
+* Uncached pages usually load within ~1s (depending on internet speed and number of widgets)
-#### Optimized for mobile devices
-
+### Tons of customizability
+* Different layouts
+* As many pages/tabs as you need
+* Numerous configuration options for each widget
+* Multiple styles for some widgets
+* Custom CSS
-#### Fast and lightweight
-* Minimal JS, no bloated frameworks
-* Very few dependencies
-* Single, easily distributed <15mb binary and just as small docker container
-* All requests are parallelized, uncached pages usually load within ~1s (depending on internet speed and number of widgets)
+### Optimized for mobile devices
+Because you'll want to take it with you on the go.
-### Configuration
-Checkout the [configuration docs](docs/configuration.md) to learn more. A [preconfigured page](docs/configuration.md#preconfigured-page) is also available to get you started quickly.
+
-### Installation
-> [!CAUTION]
->
-> The project is under active development, expect things to break every once in a while.
+### Themeable
+Easily create your own theme by tweaking a few numbers or choose from one of the [already available themes](docs/themes.md).
-#### Manual
-Checkout the [releases page](https://github.com/glanceapp/glance/releases) for available binaries. You can place the binary inside `/opt/glance/` and have it start with your server via a [systemd service](https://linuxhandbook.com/create-systemd-services/). To specify a different path for the config file use the `--config` option:
+
+
+
+
+## Configuration
+Configuration is done through YAML files, to learn more about how the layout works, how to add more pages and how to configure widgets, visit the [configuration documentation](docs/configuration.md).
+
+
+Preview example configuration file
+
+
+```yaml
+pages:
+ - name: Home
+ columns:
+ - size: small
+ widgets:
+ - type: calendar
+ first-day-of-week: monday
+
+ - type: rss
+ limit: 10
+ collapse-after: 3
+ cache: 12h
+ feeds:
+ - url: https://selfh.st/rss/
+ title: selfh.st
+ limit: 4
+ - url: https://ciechanow.ski/atom.xml
+ - url: https://www.joshwcomeau.com/rss.xml
+ title: Josh Comeau
+ - url: https://samwho.dev/rss.xml
+ - url: https://ishadeed.com/feed.xml
+ title: Ahmad Shadeed
+
+ - type: twitch-channels
+ channels:
+ - theprimeagen
+ - j_blow
+ - piratesoftware
+ - cohhcarnage
+ - christitustech
+ - EJ_SA
+
+ - size: full
+ widgets:
+ - type: group
+ widgets:
+ - type: hacker-news
+ - type: lobsters
+
+ - type: videos
+ channels:
+ - UCXuqSBlHAE6Xw-yeJA0Tunw # Linus Tech Tips
+ - UCR-DXc1voovS8nhAvccRZhg # Jeff Geerling
+ - UCsBjURrPoezykLs9EqgamOA # Fireship
+ - UCBJycsmduvYEL83R_U4JriQ # Marques Brownlee
+ - UCHnyfMqiRRG1u-2MsSQLbXA # Veritasium
+
+ - type: group
+ widgets:
+ - type: reddit
+ subreddit: technology
+ show-thumbnails: true
+ - type: reddit
+ subreddit: selfhosted
+ show-thumbnails: true
+
+ - size: small
+ widgets:
+ - type: weather
+ location: London, United Kingdom
+ units: metric
+ hour-format: 12h
+
+ - type: markets
+ markets:
+ - symbol: SPY
+ name: S&P 500
+ - symbol: BTC-USD
+ name: Bitcoin
+ - symbol: NVDA
+ name: NVIDIA
+ - symbol: AAPL
+ name: Apple
+ - symbol: MSFT
+ name: Microsoft
+
+ - type: releases
+ cache: 1d
+ repositories:
+ - glanceapp/glance
+ - go-gitea/gitea
+ - immich-app/immich
+ - syncthing/syncthing
+```
+
+
+
+
+## Installation
+
+Choose one of the following methods:
+
+
+Docker compose using provided directory structure (recommended)
+
+
+Create a new directory called `glance` as well as the template files within it by running:
+
+```bash
+mkdir glance && cd glance && curl -sL https://github.com/glanceapp/docker-compose-template/archive/refs/heads/main.tar.gz | tar -xzf - --strip-components 2
+```
+
+*[click here to view the files that will be created](https://github.com/glanceapp/docker-compose-template/tree/main/root)*
+
+Then, edit the following files as desired:
+* `docker-compose.yml` to configure the port, volumes and other containery things
+* `config/home.yml` to configure the widgets or layout of the home page
+* `config/glance.yml` if you want to change the theme or add more pages
+
+
+Other files you may want to edit
+
+* `.env` to configure environment variables that will be available inside configuration files
+* `assets/user.css` to add custom CSS
+
+
+When ready, run:
+
+```bash
+docker compose up -d
+```
+
+If you encounter any issues, you can check the logs by running:
+
+```bash
+docker compose logs
+```
+
+
+
+
+
+Docker compose manual
+
+
+Create a `docker-compose.yml` file with the following contents:
+
+```yaml
+services:
+ glance:
+ container_name: glance
+ image: glanceapp/glance
+ volumes:
+ - ./config:/app/config
+ ports:
+ - 8080:8080
+```
+
+Then, create a new directory called `config` and download the example starting [`glance.yml`](https://github.com/glanceapp/glance/blob/main/docs/glance.yml) file into it by running:
+
+```bash
+mkdir config && wget -O config/glance.yml https://raw.githubusercontent.com/glanceapp/glance/refs/heads/main/docs/glance.yml
+```
+
+Feel free to edit the `glance.yml` file to your liking, and when ready run:
+
+```bash
+docker compose up -d
+```
+
+If you encounter any issues, you can check the logs by running:
+
+```bash
+docker logs glance
+```
+
+
+
+
+
+Manual binary installation
+
+
+Precompiled binaries are available for Linux, Windows and macOS (x86, x86_64, ARM and ARM64 architectures).
+
+### Linux
+
+Visit the [latest release page](https://github.com/glanceapp/glance/releases/latest) for available binaries. You can place the binary in `/opt/glance/` and have it start with your server via a [systemd service](https://linuxhandbook.com/create-systemd-services/). By default, when running the binary, it will look for a `glance.yml` file in the directory it's placed in. To specify a different path for the config file, use the `--config` option:
```bash
/opt/glance/glance --config /etc/glance.yml
```
-#### Docker
-> [!IMPORTANT]
->
-> Make sure you have a valid `glance.yml` file in the same directory before running the container.
+To grab a starting template for the config file, run:
```bash
-docker run -d -p 8080:8080 \
- -v ./glance.yml:/app/glance.yml \
- -v /etc/timezone:/etc/timezone:ro \
- -v /etc/localtime:/etc/localtime:ro \
- glanceapp/glance
+wget https://raw.githubusercontent.com/glanceapp/glance/refs/heads/main/docs/glance.yml
```
-Or if you prefer docker compose:
+### Windows
-```yaml
-services:
- glance:
- image: glanceapp/glance
- volumes:
- - ./glance.yml:/app/glance.yml
- - /etc/timezone:/etc/timezone:ro
- - /etc/localtime:/etc/localtime:ro
- ports:
- - 8080:8080
- restart: unless-stopped
-```
+Download and extract the executable from the [latest release](https://github.com/glanceapp/glance/releases/latest) (most likely the file called `glance-windows-amd64.zip` if you're on a 64-bit system) and place it in a folder of your choice. Then, create a new text file called `glance.yml` in the same folder and paste the content from [here](https://raw.githubusercontent.com/glanceapp/glance/refs/heads/main/docs/glance.yml) in it. You should then be able to run the executable and access the dashboard by visiting `http://localhost:8080` in your browser.
-### Building from source
-Requirements: [Go](https://go.dev/dl/) >= v1.22
-To build:
+
+
+
+
+Other
+
+
+Glance can also be installed through the following 3rd party channels:
+* [Proxmox VE Helper Script](https://community-scripts.github.io/ProxmoxVE/scripts?id=glance)
+* [NixOS package](https://search.nixos.org/packages?channel=unstable&show=glance)
+* [Coolify.io](https://coolify.io/docs/services/glance/)
+
+
+
+
+
+
+## Building from source
+
+Choose one of the following methods:
+
+
+Build binary with Go
+
+
+Requirements: [Go](https://go.dev/dl/) >= v1.23
+
+To build the project for your current OS and architecture, run:
```bash
go build -o build/glance .
```
-To run:
+To build for a specific OS and architecture, run:
+
+```bash
+GOOS=linux GOARCH=amd64 go build -o build/glance .
+```
+
+[*click here for a full list of GOOS and GOARCH combinations*](https://go.dev/doc/install/source#:~:text=$GOOS%20and%20$GOARCH)
+
+Alternatively, if you just want to run the app without creating a binary, like when you're testing out changes, you can run:
```bash
go run .
```
+
+
-### Building Docker image
+
+Build project and Docker image with Docker
+
-Build the image:
+Requirements: [Docker](https://docs.docker.com/engine/install/)
-**Make sure to replace "owner" with your name or organization.**
+To build the project and image using just Docker, run:
+
+*(replace `owner` with your name or organization)*
```bash
docker build -t owner/glance:latest .
```
-Push the image to your registry:
+If you wish to push the image to a registry (by default Docker Hub), run:
```bash
docker push owner/glance:latest
```
+
+
+
+
+
+
+## FAQ
+
+Does the information on the page update automatically?
+No, a page refresh is required to update the information. Some things do dynamically update where it makes sense, like the clock widget and the relative time showing how long ago something happened.
+
+
+
+How frequently do widgets update?
+No requests are made periodically in the background, information is only fetched upon loading the page and then cached. The default cache lifetime is different for each widget and can be configured.
+
+
+
+Can I create my own widgets?
+
+Yes, there are multiple ways to create custom widgets:
+* `iframe` widget - allows you to embed things from other websites
+* `html` widget - allows you to insert your own static HTML
+* `extension` widget - fetch HTML from a URL
+* `custom-api` widget - fetch JSON from a URL and render it using custom HTML
+
+
+
+Can I change the title of a widget?
+
+Yes, the title of all widgets can be changed by specifying the `title` property in the widget's configuration:
+
+```yaml
+- type: rss
+ title: My custom title
+
+- type: markets
+ title: My custom title
+
+- type: videos
+ title: My custom title
+
+# and so on for all widgets...
+```
+
+
+
+
+## Feature requests
+
+New feature suggestions are always welcome and will be considered, though please keep in mind that some of them may be out of scope for what the project is trying to achieve (or is reasonably capable of). If you have an idea for a new feature and would like to share it, you can do so [here](https://github.com/glanceapp/glance/issues/new?template=feature_request.yml).
+
+Feature requests are tagged with one of the following:
+
+* [Roadmap](https://github.com/glanceapp/glance/labels/roadmap) - will be implemented in a future release
+* [Backlog](https://github.com/glanceapp/glance/labels/backlog) - may be implemented in the future but needs further feedback or interest from the community
+* [Icebox](https://github.com/glanceapp/glance/labels/icebox) - no plans to implement as it doesn't currently align with the project's goals or capabilities, may be revised at a later date
+
+
+
+## Contributing guidelines
+
+* Before working on a new feature it's preferable to submit a feature request first and state that you'd like to implement it yourself
+* Please don't submit PRs for feature requests that are either in the roadmap[1], backlog[2] or icebox[3]
+* Use `dev` for the base branch if you're adding new features or fixing bugs, otherwise use `main`
+* Avoid introducing new dependencies
+* Avoid making backwards-incompatible configuration changes
+* Avoid introducing new colors or hard-coding colors, use the standard `primary`, `positive` and `negative`
+* For icons, try to use [heroicons](https://heroicons.com/) where applicable
+* Provide a screenshot of the changes if UI related where possible
+* No `package.json`
+
+
+[1] [2] [3]
+
+[1] The feature likely already has work put into it that may conflict with your implementation
+
+[2] The demand, implementation or functionality for this feature is not yet clear
+
+[3] No plans to add this feature for the time being
+
+
+
+
+
+## Thank you
+
+To all the people who were generous enough to [sponsor](https://github.com/sponsors/glanceapp) the project and to everyone who has contributed in any way, be it PRs, submitting issues, helping others in the discussions or Discord server, creating guides and tools or just mentioning Glance on social media. Your support is greatly appreciated and helps keep the project going.
diff --git a/docs/configuration.md b/docs/configuration.md
index fd31f39..0cee1ec 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -1,8 +1,12 @@
-# Configuration
+# Configuring Glance
-- [Intro](#intro)
- [Preconfigured page](#preconfigured-page)
+- [The config file](#the-config-file)
+ - [Auto reload](#auto-reload)
+ - [Environment variables](#environment-variables)
+ - [Including other config files](#including-other-config-files)
- [Server](#server)
+- [Document](#document)
- [Branding](#branding)
- [Theme](#theme)
- [Themes](#themes)
@@ -15,14 +19,19 @@
- [Reddit](#reddit)
- [Search](#search-widget)
- [Group](#group)
+ - [Split Column](#split-column)
+ - [Custom API](#custom-api)
- [Extension](#extension)
- [Weather](#weather)
- [Monitor](#monitor)
- [Releases](#releases)
+ - [Docker Containers](#docker-containers)
- [DNS Stats](#dns-stats)
+ - [Server Stats](#server-stats)
- [Repository](#repository)
- [Bookmarks](#bookmarks)
- [Calendar](#calendar)
+ - [Calendar (legacy)](#calendar-legacy)
- [ChangeDetection.io](#changedetectionio)
- [Clock](#clock)
- [Markets](#markets)
@@ -31,85 +40,114 @@
- [iframe](#iframe)
- [HTML](#html)
-## Intro
-Configuration is done via a single YAML file and a server restart is required in order for any changes to take effect. Trying to start the server with an invalid config file will result in an error.
## Preconfigured page
-If you don't want to spend time reading through all the available configuration options and just want something to get you going quickly you can use the following `glance.yml` and make changes as you see fit:
+If you don't want to spend time reading through all the available configuration options and just want something to get you going quickly you can use [this `glance.yml` file](glance.yml) and make changes to it as you see fit. It will give you a page that looks like the following:
+
+
+
+Configure the widgets, add more of them, add extra pages, etc. Make it your own!
+
+## The config file
+
+### Auto reload
+Automatic config reload is supported, meaning that you can make changes to the config file and have them take effect on save without having to restart the container/service. Making changes to environment variables does not trigger a reload and requires manual restart. Deleting a config file will stop that file from being watched, even if it is recreated.
+
+> [!NOTE]
+>
+> If you attempt to start Glance with an invalid config it will exit with an error outright. If you successfully started Glance with a valid config and then made changes to it which result in an error, you'll see that error in the console and Glance will continue to run with the old configuration. You can then continue to make changes and when there are no errors the new configuration will be loaded.
+
+> [!CAUTION]
+>
+> Reloading the configuration file clears your cached data, meaning that you have to request the data anew each time you do this. This can lead to rate limiting for some APIs if you do it too frequently. Having a cache that persists between reloads will be added in the future.
+
+### Environment variables
+Inserting environment variables is supported anywhere in the config. This is done via the `${ENV_VAR}` syntax. Attempting to use an environment variable that doesn't exist will result in an error and Glance will either not start or load your new config on save. Example:
+
+```yaml
+server:
+ host: ${HOST}
+ port: ${PORT}
+```
+
+Can also be in the middle of a string:
+
+```yaml
+- type: rss
+ title: ${RSS_TITLE}
+ feeds:
+ - url: http://domain.com/rss/${RSS_CATEGORY}.xml
+```
+
+Works with any type of value, not just strings:
+
+```yaml
+- type: rss
+ limit: ${RSS_LIMIT}
+```
+
+If you need to use the syntax `${NAME}` in your config without it being interpreted as an environment variable, you can escape it by prefixing with a backslash `\`:
+
+```yaml
+something: \${NOT_AN_ENV_VAR}
+```
+
+### Including other config files
+Including config files from within your main config file is supported. This is done via the `!include` directive along with a relative or absolute path to the file you want to include. If the path is relative, it will be relative to the main config file. Additionally, environment variables can be used within included files, and changes to the included files will trigger an automatic reload. Example:
+
+```yaml
+pages:
+ !include home.yml
+ !include videos.yml
+ !include homelab.yml
+```
+
+The file you are including should not have any additional indentation, its values should be at the top level and the appropriate amount of indentation will be added automatically depending on where the file is included. Example:
+
+`glance.yml`
```yaml
pages:
- name: Home
columns:
- - size: small
- widgets:
- - type: calendar
-
- - type: rss
- limit: 10
- collapse-after: 3
- cache: 3h
- feeds:
- - url: https://ciechanow.ski/atom.xml
- - url: https://www.joshwcomeau.com/rss.xml
- title: Josh Comeau
- - url: https://samwho.dev/rss.xml
- - url: https://awesomekling.github.io/feed.xml
- - url: https://ishadeed.com/feed.xml
- title: Ahmad Shadeed
-
- - type: twitch-channels
- channels:
- - theprimeagen
- - cohhcarnage
- - christitustech
- - blurbs
- - asmongold
- - jembawls
-
- size: full
widgets:
- - type: hacker-news
-
- - type: videos
- channels:
- - UCR-DXc1voovS8nhAvccRZhg # Jeff Geerling
- - UCv6J_jJa8GJqFwQNgNrMuww # ServeTheHome
- - UCOk-gHyjcWZNj3Br4oxwh0A # Techno Tim
-
- - type: reddit
- subreddit: selfhosted
-
- - size: small
+ !include rss.yml
+ - name: News
+ columns:
+ - size: full
widgets:
- - type: weather
- location: London, United Kingdom
-
- - type: markets
- markets:
- - symbol: SPY
- name: S&P 500
- - symbol: BTC-USD
- name: Bitcoin
- - symbol: NVDA
- name: NVIDIA
- - symbol: AAPL
- name: Apple
- - symbol: MSFT
- name: Microsoft
- - symbol: GOOGL
- name: Google
- - symbol: AMD
- name: AMD
- - symbol: RDDT
- name: Reddit
+ - type: group
+ widgets:
+ !include rss.yml
+ - type: reddit
+ subreddit: news
```
-This will give you a page that looks like the following:
+`rss.yml`
-
+```yaml
+- type: rss
+ title: News
+ feeds:
+ - url: ${RSS_URL}
+```
-Configure the widgets, add more of them, add extra pages, etc. Make it your own!
+The `!include` directive can be used anywhere in the config file, not just in the `pages` property, however it must be on its own line and have the appropriate indentation.
+
+If you encounter YAML parsing errors when using the `!include` directive, the reported line numbers will likely be incorrect. This is because the inclusion of files is done before the YAML is parsed, as YAML itself does not support file inclusion. To help with debugging in cases like this, you can use the `config:print` command and pipe it into `less -N` to see the full config file with includes resolved and line numbers added:
+
+```sh
+glance --config /path/to/glance.yml config:print | less -N
+```
+
+This is a bit more convoluted when running Glance inside a Docker container:
+
+```sh
+docker run --rm -v ./glance.yml:/app/config/glance.yml glanceapp/glance config:print | less -N
+```
+
+This assumes that the config you want to print is in your current working directory and is named `glance.yml`.
## Server
Server configuration is done through a top level `server` property. Example:
@@ -179,6 +217,15 @@ To be able to point to an asset from your assets path, use the `/assets/` path l
icon: /assets/gitea-icon.png
```
+## Document
+If you want to insert custom HTML into the `` of the document for all pages, you can do so by using the `document` property. Example:
+
+```yaml
+document:
+ head: |
+
+```
+
## Branding
You can adjust the various parts of the branding through a top level `branding` property. Example:
@@ -314,6 +361,7 @@ pages:
| width | string | no | |
| center-vertically | boolean | no | false |
| hide-desktop-navigation | boolean | no | false |
+| expand-mobile-page-navigation | boolean | no | false |
| show-mobile-header | boolean | no | false |
| columns | array | yes | |
@@ -340,6 +388,9 @@ When set to `true`, vertically centers the content on the page. Has no effect if
#### `hide-desktop-navigation`
Whether to show the navigation links at the top of the page on desktop.
+#### `expand-mobile-page-navigation`
+Whether the mobile page navigation should be expanded by default.
+
#### `show-mobile-header`
Whether to show a header displaying the name of the page on mobile. The header purposefully has a lot of vertical whitespace in order to push the content down and make it easier to reach on tall devices.
@@ -480,9 +531,22 @@ Example:
| thumbnail-height | float | no | 10 |
| card-height | float | no | 27 |
| limit | integer | no | 25 |
+| preserve-order | bool | no | false |
| single-line-titles | boolean | no | false |
| collapse-after | integer | no | 5 |
+##### `limit`
+The maximum number of articles to show.
+
+##### `collapse-after`
+How many articles are visible before the "SHOW MORE" button appears. Set to `-1` to never collapse.
+
+##### `preserve-order`
+When set to `true`, the order of the articles will be preserved as they are in the feeds. Useful if a feed uses its own sorting order which denotes the importance of the articles. If you use this property while having a lot of feeds, it's recommended to set a `limit` to each individual feed since if the first defined feed has 15 articles, the articles from the second feed will start after the 15th article in the list.
+
+##### `single-line-titles`
+When set to `true`, truncates the title of each post if it exceeds one line. Only applies when the style is set to `vertical-list`.
+
##### `style`
Used to change the appearance of the widget. Possible values are:
@@ -525,19 +589,26 @@ An array of RSS/atom feeds. The title can optionally be changed.
| title | string | no | the title provided by the feed | |
| hide-categories | boolean | no | false | Only applicable for `detailed-list` style |
| hide-description | boolean | no | false | Only applicable for `detailed-list` style |
+| limit | integer | no | | |
| item-link-prefix | string | no | | |
+| headers | key (string) & value (string) | no | | |
+
+###### `limit`
+The maximum number of articles to show from that specific feed. Useful if you have a feed which posts a lot of articles frequently and you want to prevent it from excessively pushing down articles from other feeds.
###### `item-link-prefix`
If an RSS feed isn't returning item links with a base domain and Glance has failed to automatically detect the correct domain you can manually add a prefix to each link with this property.
-##### `limit`
-The maximum number of articles to show.
+###### `headers`
+Optionally specify the headers that will be sent with the request. Example:
-##### `single-line-titles`
-When set to `true`, truncates the title of each post if it exceeds one line. Only applies when the style is set to `vertical-list`.
-
-##### `collapse-after`
-How many articles are visible before the "SHOW MORE" button appears. Set to `-1` to never collapse.
+```yaml
+- type: rss
+ feeds:
+ - url: https://domain.com/rss
+ headers:
+ User-Agent: Custom User Agent
+```
### Videos
Display a list of the latest videos from specific YouTube channels.
@@ -559,14 +630,18 @@ Preview:
| Name | Type | Required | Default |
| ---- | ---- | -------- | ------- |
| channels | array | yes | |
+| playlists | array | no | |
| limit | integer | no | 25 |
| style | string | no | horizontal-cards |
+| collapse-after | integer | no | 7 |
| collapse-after-rows | integer | no | 4 |
| include-shorts | boolean | no | false |
| video-url-template | string | no | https://www.youtube.com/watch?v={VIDEO-ID} |
##### `channels`
-A list of channel IDs. One way of getting the ID of a channel is going to the channel's page and clicking on its description:
+A list of channels IDs.
+
+One way of getting the ID of a channel is going to the channel's page and clicking on its description:

@@ -574,14 +649,32 @@ Then scroll down and click on "Share channel", then "Copy channel ID":

+##### `playlists`
+
+A list of playlist IDs:
+
+```yaml
+- type: videos
+ playlists:
+ - PL8mG-RkN2uTyZZ00ObwZxxoG_nJbs3qec
+ - PL8mG-RkN2uTxTK4m_Vl2dYR9yE41kRdBg
+```
+
##### `limit`
The maximum number of videos to show.
+##### `collapse-after`
+Specify the number of videos to show when using the `vertical-list` style before the "SHOW MORE" button appears.
+
##### `collapse-after-rows`
Specify the number of rows to show when using the `grid-cards` style before the "SHOW MORE" button appears.
##### `style`
-Used to change the appearance of the widget. Possible values are `horizontal-cards` and `grid-cards`.
+Used to change the appearance of the widget. Possible values are `horizontal-cards`, `vertical-list` and `grid-cards`.
+
+Preview of `vertical-list`:
+
+
Preview of `grid-cards`:
@@ -716,6 +809,7 @@ Example:
| collapse-after | integer | no | 5 |
| comments-url-template | string | no | https://www.reddit.com/{POST-PATH} |
| request-url-template | string | no | |
+| proxy | string or multiple parameters | no | |
| sort-by | string | no | hot |
| top-period | string | no | day |
| search | string | no | |
@@ -777,7 +871,7 @@ r/selfhosted/comments/bsp01i/welcome_to_rselfhosted_please_read_this_first/
`{SUBREDDIT}` - the subreddit name
##### `request-url-template`
-A custom request url that will be used to fetch the data instead. This is useful when you're hosting Glance on a VPS and Reddit is blocking the requests, and you want to route it through an HTTP proxy.
+A custom request URL that will be used to fetch the data. This is useful when you're hosting Glance on a VPS where Reddit is blocking the requests and you want to route them through a proxy that accepts the URL as either a part of the path or a query parameter.
Placeholders:
@@ -788,6 +882,29 @@ https://proxy/{REQUEST-URL}
https://your.proxy/?url={REQUEST-URL}
```
+##### `proxy`
+A custom HTTP/HTTPS proxy URL that will be used to fetch the data. This is useful when you're hosting Glance on a VPS where Reddit is blocking the requests and you want to bypass the restriction by routing the requests through a proxy. Example:
+
+```yaml
+proxy: http://user:pass@proxy.com:8080
+proxy: https://user:pass@proxy.com:443
+```
+
+Alternatively, you can specify the proxy URL as well as additional options by using multiple parameters:
+
+```yaml
+proxy:
+ url: http://proxy.com:8080
+ allow-insecure: true
+ timeout: 10s
+```
+
+###### `allow-insecure`
+When set to `true`, allows the use of insecure connections such as when the proxy has a self-signed certificate.
+
+###### `timeout`
+The maximum time to wait for a response from the proxy. The value is a string and must be a number followed by one of s, m, h, d. Example: `10s` for 10 seconds, `1m` for 1 minute, etc
+
##### `sort-by`
Can be used to specify the order in which the posts should get returned. Possible values are `hot`, `new`, `top` and `rising`.
@@ -829,6 +946,7 @@ Preview:
| Enter | Perform search in the same tab | Search input is focused and not empty |
| Ctrl + Enter | Perform search in a new tab | Search input is focused and not empty |
| Escape | Leave focus | Search input is focused |
+| Up | Insert the last search query since the page was opened into the input field | Search input is focused |
> [!TIP]
>
@@ -840,6 +958,7 @@ Preview:
| search-engine | string | no | duckduckgo |
| new-tab | boolean | no | false |
| autofocus | boolean | no | false |
+| placeholder | string | no | Type here to search… |
| bangs | array | no | |
##### `search-engine`
@@ -856,6 +975,9 @@ When set to `true`, swaps the shortcuts for showing results in the same or new t
##### `autofocus`
When set to `true`, automatically focuses the search input on page load.
+##### `placeholder`
+When set, modifies the text displayed in the input field before typing.
+
##### `bangs`
What now? [Bangs](https://duckduckgo.com/bangs). They're shortcuts that allow you to use the same search box for many different sites. Assuming you have it configured, if for example you start your search input with `!yt` you'd be able to perform a search on YouTube:
@@ -891,7 +1013,7 @@ url: https://www.amazon.com/s?k={QUERY}
```
### Group
-Group multiple widgets into one using tabs. Widgets are defined using a `widgets` property exactly as you would on a page column. The only limitation is that you cannot place a group widget within a group widget.
+Group multiple widgets into one using tabs. Widgets are defined using a `widgets` property exactly as you would on a page column. The only limitation is that you cannot place a group widget or a split column widget within a group widget.
Example:
@@ -934,6 +1056,263 @@ Example:
<<: *shared-properties
```
+### Split Column
+Splits a full sized column in half, allowing you to place widgets side by side horizontally. This is converted to a single column on mobile devices or if not enough width is available. Widgets are defined using a `widgets` property exactly as you would on a page column.
+
+Two widgets side by side in a `full` column:
+
+
+
+
+View glance.yml
+
+
+```yaml
+# ...
+- size: full
+ widgets:
+ - type: split-column
+ widgets:
+ - type: hacker-news
+ collapse-after: 3
+ - type: lobsters
+ collapse-after: 3
+
+ - type: videos
+# ...
+```
+
+
+
+You can also achieve a number of different full page layouts using just this widget, such as:
+
+3 column layout where all columns have equal width:
+
+
+
+
+View glance.yml
+
+
+```yaml
+pages:
+ - name: Home
+ columns:
+ - size: full
+ widgets:
+ - type: split-column
+ max-columns: 3
+ widgets:
+ - type: reddit
+ subreddit: selfhosted
+ collapse-after: 15
+ - type: reddit
+ subreddit: homelab
+ collapse-after: 15
+ - type: reddit
+ subreddit: sysadmin
+ collapse-after: 15
+```
+
+
+
+4 column layout where all columns have equal width (and the page is set to `width: wide`):
+
+
+
+
+View glance.yml
+
+
+```yaml
+pages:
+ - name: Home
+ width: wide
+ columns:
+ - size: full
+ widgets:
+ - type: split-column
+ max-columns: 4
+ widgets:
+ - type: reddit
+ subreddit: selfhosted
+ collapse-after: 15
+ - type: reddit
+ subreddit: homelab
+ collapse-after: 15
+ - type: reddit
+ subreddit: linux
+ collapse-after: 15
+ - type: reddit
+ subreddit: sysadmin
+ collapse-after: 15
+```
+
+
+
+Masonry layout with up to 5 columns where all columns have equal width (and the page is set to `width: wide`):
+
+
+
+
+View glance.yml
+
+
+```yaml
+define:
+ - &subreddit-settings
+ type: reddit
+ collapse-after: 5
+
+pages:
+ - name: Home
+ width: wide
+ columns:
+ - size: full
+ widgets:
+ - type: split-column
+ max-columns: 5
+ widgets:
+ - subreddit: selfhosted
+ <<: *subreddit-settings
+ - subreddit: homelab
+ <<: *subreddit-settings
+ - subreddit: linux
+ <<: *subreddit-settings
+ - subreddit: sysadmin
+ <<: *subreddit-settings
+ - subreddit: DevOps
+ <<: *subreddit-settings
+ - subreddit: Networking
+ <<: *subreddit-settings
+ - subreddit: DataHoarding
+ <<: *subreddit-settings
+ - subreddit: OpenSource
+ <<: *subreddit-settings
+ - subreddit: Privacy
+ <<: *subreddit-settings
+ - subreddit: FreeSoftware
+ <<: *subreddit-settings
+```
+
+
+
+Just like the `group` widget, you can insert any widget type, you can even insert a `group` widget inside of a `split-column` widget, but you can't insert a `split-column` widget inside of a `group` widget.
+
+
+### Custom API
+
+Display data from a JSON API using a custom template.
+
+> [!NOTE]
+>
+> The configuration of this widget requires some basic knowledge of programming, HTML, CSS, the Go template language and Glance-specific concepts.
+
+Examples:
+
+
+
+
+View glance.yml
+
+
+```yaml
+- type: custom-api
+ title: Random Fact
+ cache: 6h
+ url: https://uselessfacts.jsph.pl/api/v2/facts/random
+ template: |
+
+```
+
+
+#### Properties
+| Name | Type | Required | Default |
+| ---- | ---- | -------- | ------- |
+| url | string | yes | |
+| headers | key (string) & value (string) | no | |
+| frameless | boolean | no | false |
+| template | string | yes | |
+
+##### `url`
+The URL to fetch the data from. It must be accessible from the server that Glance is running on.
+
+##### `headers`
+Optionally specify the headers that will be sent with the request. Example:
+
+```yaml
+headers:
+ x-api-key: your-api-key
+ Accept: application/json
+```
+
+##### `frameless`
+When set to `true`, removes the border and padding around the widget.
+
+##### `template`
+The template that will be used to display the data. It relies on Go's `html/template` package so it's recommended to go through [its documentation](https://pkg.go.dev/text/template) to understand how to do basic things such as conditionals, loops, etc. In addition, it also uses [tidwall's gjson](https://pkg.go.dev/github.com/tidwall/gjson) package to parse the JSON data so it's worth going through its documentation if you want to use more advanced JSON selectors. You can view additional examples with explanations and function definitions [here](custom-api.md).
+
### Extension
Display a widget provided by an external source (3rd party). If you want to learn more about developing extensions, checkout the [extensions documentation](extensions.md) (WIP).
@@ -949,12 +1328,16 @@ Display a widget provided by an external source (3rd party). If you want to lear
| Name | Type | Required | Default |
| ---- | ---- | -------- | ------- |
| url | string | yes | |
+| fallback-content-type | string | no | |
| allow-potentially-dangerous-html | boolean | no | false |
| parameters | key & value | no | |
##### `url`
The URL of the extension. **Note that the query gets stripped from this URL and the one defined by `parameters` gets used instead.**
+##### `fallback-content-type`
+Optionally specify the fallback content type of the extension if the URL does not return a valid `Widget-Content-Type` header. Currently the only supported value for this property is `html`.
+
##### `allow-potentially-dangerous-html`
Whether to allow the extension to display HTML.
@@ -1066,11 +1449,19 @@ You can hover over the "ERROR" text to view more information.
| Name | Type | Required | Default |
| ---- | ---- | -------- | ------- |
| sites | array | yes | |
+| style | string | no | |
| show-failing-only | boolean | no | false |
##### `show-failing-only`
Shows only a list of failing sites when set to `true`.
+##### `style`
+Used to change the appearance of the widget. Possible values are `compact`.
+
+Preview of `compact`:
+
+
+
##### `sites`
Properties for each site:
@@ -1080,9 +1471,11 @@ Properties for each site:
| title | string | yes | |
| url | string | yes | |
| check-url | string | no | |
+| error-url | string | no | |
| icon | string | no | |
| allow-insecure | boolean | no | false |
| same-tab | boolean | no | false |
+| alt-status-codes | array | no | |
`title`
@@ -1096,9 +1489,13 @@ The public facing URL of a monitored service, the user will be redirected here.
The URL which will be requested and its response will determine the status of the site. If not specified, the `url` property is used.
+`error-url`
+
+If the monitored service returns an error, the user will be redirected here. If not specified, the `url` property is used.
+
`icon`
-Optional URL to an image which will be used as the icon for the site. Can be an external URL or internal via [server configured assets](#assets-path). You can also directly use [Simple Icons](https://simpleicons.org/) via a `si:` prefix:
+Optional URL to an image which will be used as the icon for the site. Can be an external URL or internal via [server configured assets](#assets-path). You can also directly use [Simple Icons](https://simpleicons.org/) via a `si:` prefix or [Dashboard Icons](https://github.com/walkxcode/dashboard-icons) via a `di:` prefix:
```yaml
icon: si:jellyfin
@@ -1108,7 +1505,7 @@ icon: si:adguard
> [!WARNING]
>
-> Simple Icons are loaded externally and are hosted on `cdnjs.cloudflare.com`, if you do not wish to depend on a 3rd party you are free to download the icons individually and host them locally.
+> Simple Icons are loaded externally and are hosted on `cdn.jsdelivr.net`, if you do not wish to depend on a 3rd party you are free to download the icons individually and host them locally.
`allow-insecure`
@@ -1118,6 +1515,15 @@ Whether to ignore invalid/self-signed certificates.
Whether to open the link in the same or a new tab.
+`alt-status-codes`
+
+Status codes other than 200 that you want to return "OK".
+
+```yaml
+alt-status-codes:
+ - 403
+```
+
### Releases
Display a list of latest releases for specific repositories on Github, GitLab, Codeberg or Docker Hub.
@@ -1160,7 +1566,7 @@ repositories:
- codeberg:redict/redict
```
-Official images on Docker Hub can be specified by ommiting the owner:
+Official images on Docker Hub can be specified by omitting the owner:
```yaml
repositories:
@@ -1169,7 +1575,7 @@ repositories:
- dockerhub:alpine
```
-You can also specify specific tags for Docker Hub images:
+You can also specify exact tags for Docker Hub images:
```yaml
repositories:
@@ -1177,6 +1583,17 @@ repositories:
- dockerhub:nginx:stable-alpine
```
+To include prereleases you can specify the repository as an object and use the `include-prereleases` property:
+
+**Note: This feature is currently only available for GitHub repositories.**
+
+```yaml
+repositories:
+ - gitlab:inkscape/inkscape
+ - repository: glanceapp/glance
+ include-prereleases: true
+ - codeberg:redict/redict
+```
##### `show-source-icon`
Shows an icon of the source (GitHub/GitLab/Codeberg/Docker Hub) next to the repository name when set to `true`.
@@ -1213,6 +1630,112 @@ The maximum number of releases to show.
#### `collapse-after`
How many releases are visible before the "SHOW MORE" button appears. Set to `-1` to never collapse.
+### Docker Containers
+
+Display the status of your Docker containers along with an icon and an optional short description.
+
+
+
+```yaml
+- type: docker-containers
+ hide-by-default: false
+```
+
+> [!NOTE]
+>
+> The widget requires access to `docker.sock`. If you're running Glance inside a container, this can be done by mounting the socket as a volume:
+>
+> ```yaml
+> services:
+> glance:
+> image: glanceapp/glance
+> volumes:
+> - /var/run/docker.sock:/var/run/docker.sock
+> ```
+
+Configuration of the containers is done via labels applied to each container:
+
+```yaml
+ jellyfin:
+ image: jellyfin/jellyfin:latest
+ labels:
+ glance.name: Jellyfin
+ glance.icon: si:jellyfin
+ glance.url: https://jellyfin.domain.com
+ glance.description: Movies & shows
+```
+
+For services with multiple containers you can specify a `glance.id` on the "main" container and `glance.parent` on each "child" container:
+
+
+View docker-compose.yml
+
+
+```yaml
+servies:
+ immich-server:
+ image: ghcr.io/immich-app/immich-server
+ labels:
+ glance.name: Immich
+ glance.icon: si:immich
+ glance.url: https://immich.domain.com
+ glance.description: Image & video management
+ glance.id: immich
+
+ redis:
+ image: docker.io/redis:6.2-alpine
+ labels:
+ glance.parent: immich
+ glance.name: Redis
+
+ database:
+ image: docker.io/tensorchord/pgvecto-rs:pg14-v0.2.0
+ labels:
+ glance.parent: immich
+ glance.name: DB
+
+ proxy:
+ image: nginx:stable
+ labels:
+ glance.parent: immich
+ glance.name: Proxy
+```
+
+
+
+This will place all child containers under the `Immich` container when hovering over its icon:
+
+
+
+If any of the child containers are down, their status will propagate up to the parent container:
+
+
+
+#### Properties
+
+| Name | Type | Required | Default |
+| ---- | ---- | -------- | ------- |
+| hide-by-default | boolean | no | false |
+| sock-path | string | no | /var/run/docker.sock |
+
+##### `hide-by-default`
+Whether to hide the containers by default. If set to `true` you'll have to manually add a `glance.hide: false` label to each container you want to display. By default all containers will be shown and if you want to hide a specific container you can add a `glance.hide: true` label.
+
+##### `sock-path`
+The path to the Docker socket.
+
+#### Labels
+| Name | Description |
+| ---- | ----------- |
+| glance.name | The name displayed in the UI. If not specified, the name of the container will be used. |
+| glance.icon | The icon displayed in the UI. Can be an external URL or an icon prefixed with si:, sh: or di: like with the bookmarks and monitor widgets |
+| glance.url | The URL that the user will be redirected to when clicking on the container. |
+| glance.same-tab | Whether to open the link in the same or a new tab. Default is `false`. |
+| glance.description | A short description displayed in the UI. Default is empty. |
+| glance.hide | Whether to hide the container. If set to `true` the container will not be displayed. Defaults to `false`. |
+| glance.id | The custom ID of the container. Used to group containers under a single parent. |
+| glance.parent | The ID of the parent container. Used to group containers under a single parent. |
+
### DNS Stats
Display statistics from a self-hosted ad-blocking DNS resolver such as AdGuard Home or Pi-hole.
@@ -1239,15 +1762,21 @@ Preview:
| Name | Type | Required | Default |
| ---- | ---- | -------- | ------- |
| service | string | no | pihole |
+| allow-insecure | bool | no | false |
| url | string | yes | |
| username | string | when service is `adguard` | |
| password | string | when service is `adguard` | |
| token | string | when service is `pihole` | |
+| hide-graph | bool | no | false |
+| hide-top-domains | bool | no | false |
| hour-format | string | no | 12h |
##### `service`
Either `adguard` or `pihole`.
+##### `allow-insecure`
+Whether to allow invalid/self-signed certificates when making the request to the service.
+
##### `url`
The base URL of the service. Can be specified from an environment variable using the syntax `${VARIABLE_NAME}`.
@@ -1260,9 +1789,115 @@ Only required when using AdGuard Home. The password used to log into the admin d
##### `token`
Only required when using Pi-hole. The API token which can be found in `Settings -> API -> Show API token`. Can be specified from an environment variable using the syntax `${VARIABLE_NAME}`.
+##### `hide-graph`
+Whether to hide the graph showing the number of queries over time.
+
+##### `hide-top-domains`
+Whether to hide the list of top blocked domains.
+
##### `hour-format`
Whether to display the relative time in the graph in `12h` or `24h` format.
+### Server Stats
+Display statistics such as CPU usage, memory usage and disk usage of the server Glance is running on or other servers.
+
+Example:
+
+```yaml
+- type: server-stats
+ servers:
+ - type: local
+ name: Services
+```
+
+Preview:
+
+
+
+> [!NOTE]
+>
+> This widget is currently under development, some features might not function as expected or may change.
+
+To display data from a remote server you need to have the Glance Agent running on that server. You can download the agent from [here](https://github.com/glanceapp/agent), though keep in mind that it is still in development and may not work as expected. Support for other providers such as Glances will be added in the future.
+
+In the event that the CPU temperature goes over 80°C, a flame icon will appear next to the CPU. The progress indicators will also turn red (or the equivalent of your negative color) to hopefully grab your attention if anything is unusually high:
+
+
+
+#### Properties
+| Name | Type | Required | Default |
+| ---- | ---- | -------- | ------- |
+| servers | array | no | |
+
+##### `servers`
+If not provided it will display the statistics of the server Glance is running on.
+
+##### Properties for both `local` and `remote` servers
+| Name | Type | Required | Default |
+| ---- | ---- | -------- | ------- |
+| type | string | yes | |
+| name | string | no | |
+| hide-swap | boolean | no | false |
+
+###### `type`
+Whether to display statistics for the local server or a remote server. Possible values are `local` and `remote`.
+
+###### `name`
+The name of the server which will be displayed on the widget. If not provided it will default to the server's hostname.
+
+###### `hide-swap`
+Whether to hide the swap usage.
+
+##### Properties for the `local` server
+| Name | Type | Required | Default |
+| ---- | ---- | -------- | ------- |
+| cpu-temp-sensor | string | no | |
+| mountpoints | map\[string\]object | no | |
+
+###### `cpu-temp-sensor`
+The name of the sensor to use for the CPU temperature. When not provided the widget will attempt to find the correct one, if it fails to do so the temperature will not be displayed. To view the available sensors you can use `sensors` command.
+
+###### `mountpoints`
+A map of mountpoints to display disk usage for. The key is the path to the mountpoint and the value is an object with optional properties. Example:
+
+```yaml
+mountpoints:
+ "/":
+ name: Root
+ "/mnt/data":
+ name: Data
+ "/boot/efi":
+ hide: true
+```
+
+##### Properties for each `mountpoint`
+| Name | Type | Required | Default |
+| ---- | ---- | -------- | ------- |
+| name | string | no | |
+| hide | boolean | no | false |
+
+###### `name`
+The name of the mountpoint which will be displayed on the widget. If not provided it will default to the mountpoint's path.
+
+###### `hide`
+Whether to hide this mountpoint from the widget.
+
+##### Properties for `remote` servers
+| Name | Type | Required | Default |
+| ---- | ---- | -------- | ------- |
+| url | string | yes | |
+| token | string | no | |
+| timeout | string | no | 3s |
+
+###### `url`
+The URL and port of the server to fetch the statistics from.
+
+###### `token`
+The authentication token to use when fetching the statistics.
+
+###### `timeout`
+The maximum time to wait for a response from the server. The value is a string and must be a number followed by one of s, m, h, d. Example: `10s` for 10 seconds, `1m` for 1 minute, etc
+
### Repository
Display general information about a repository as well as a list of the latest open pull requests and issues.
@@ -1364,6 +1999,13 @@ An array of groups which can optionally have a title and a custom color.
| title | string | no | |
| color | HSL | no | the primary color of the theme |
| links | array | yes | |
+| same-tab | boolean | no | false |
+| hide-arrow | boolean | no | false |
+| target | string | no | |
+
+> [!TIP]
+>
+> You can set `same-tab`, `hide-arrow` and `target` either on the group which will apply them to all links in that group, or on each individual link which will override the value set on the group.
###### Properties for each link
| Name | Type | Required | Default |
@@ -1373,10 +2015,11 @@ An array of groups which can optionally have a title and a custom color.
| icon | string | no | |
| same-tab | boolean | no | false |
| hide-arrow | boolean | no | false |
+| target | string | no | |
`icon`
-URL pointing to an image. You can also directly use [Simple Icons](https://simpleicons.org/) via a `si:` prefix:
+URL pointing to an image. You can also directly use [Simple Icons](https://simpleicons.org/) via a `si:` prefix or [Dashboard Icons](https://github.com/walkxcode/dashboard-icons) via a `di:` prefix:
```yaml
icon: si:gmail
@@ -1386,7 +2029,7 @@ icon: si:reddit
> [!WARNING]
>
-> Simple Icons are loaded externally and are hosted on `cdnjs.cloudflare.com`, if you do not wish to depend on a 3rd party you are free to download the icons individually and host them locally.
+> Simple Icons are loaded externally and are hosted on `cdn.jsdelivr.net`, if you do not wish to depend on a 3rd party you are free to download the icons individually and host them locally.
`same-tab`
@@ -1396,6 +2039,10 @@ Whether to open the link in the same tab or a new one.
Whether to hide the colored arrow on each link.
+`target`
+
+Set a custom value for the link's `target` attribute. Possible values are `_blank`, `_self`, `_parent` and `_top`, you can read more about what they do [here](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#target). This property has precedence over `same-tab`.
+
### ChangeDetection.io
Display a list watches from changedetection.io.
@@ -1495,15 +2142,52 @@ Example:
```yaml
- type: calendar
+ first-day-of-week: monday
```
Preview:

+#### Properties
+
+| Name | Type | Required | Default |
+| ---- | ---- | -------- | ------- |
+| first-day-of-week | string | no | monday |
+
+##### `first-day-of-week`
+The day of the week that the calendar starts on. All week days are available as possible values.
+
+### Calendar (legacy)
+Display a calendar.
+
+Example:
+
+```yaml
+- type: calendar-legacy
+ start-sunday: false
+```
+
+Preview:
+
+
+
> [!NOTE]
>
-> There is currently no customizability available for the calendar. Extra features will be added in the future.
+> This widget is deprecated and may be removed in a future version.
+
+#### Properties
+
+| Name | Type | Required | Default |
+| ---- | ---- | -------- | ------- |
+| start-sunday | boolean | no | false |
+
+##### `start-sunday`
+Whether calendar weeks start on Sunday or Monday.
+
+> [!NOTE]
+>
+> There is currently little customizability available for the calendar. Extra features will be added in the future.
### Markets
Display a list of markets, their current value, change for the day and a small 21d chart. Data is taken from Yahoo Finance.
@@ -1535,14 +2219,30 @@ Preview:
| ---- | ---- | -------- |
| markets | array | yes |
| sort-by | string | no |
+| chart-link-template | string | no |
+| symbol-link-template | string | no |
##### `markets`
An array of markets for which to display information about.
##### `sort-by`
-By default the markets are displayed in the order they were defined. You can customize their ordering by setting the `sort-by` property to `absolute-change` for descending order based on the stock's absolute price change.
+By default the markets are displayed in the order they were defined. You can customize their ordering by setting the `sort-by` property to `change` for descending order based on the stock's percentage change (e.g. 1% would be sorted higher than -1%) or `absolute-change` for descending order based on the stock's absolute price change (e.g. -1% would be sorted higher than +0.5%).
-###### Properties for each stock
+##### `chart-link-template`
+A template for the link to go to when clicking on the chart that will be applied to all markets. The value `{SYMBOL}` will be replaced with the symbol of the market. You can override this on a per-market basis by specifying a `chart-link` property. Example:
+
+```yaml
+chart-link-template: https://www.tradingview.com/chart/?symbol={SYMBOL}
+```
+
+##### `symbol-link-template`
+A template for the link to go to when clicking on the symbol that will be applied to all markets. The value `{SYMBOL}` will be replaced with the symbol of the market. You can override this on a per-market basis by specifying a `symbol-link` property. Example:
+
+```yaml
+symbol-link-template: https://www.google.com/search?tbm=nws&q={SYMBOL}
+```
+
+###### Properties for each market
| Name | Type | Required |
| ---- | ---- | -------- |
| symbol | string | yes |
@@ -1559,9 +2259,11 @@ The symbol, as seen in Yahoo Finance.
The name that will be displayed under the symbol.
`symbol-link`
+
The link to go to when clicking on the symbol.
`chart-link`
+
The link to go to when clicking on the chart.
### Twitch Channels
diff --git a/docs/custom-api.md b/docs/custom-api.md
new file mode 100644
index 0000000..91c501b
--- /dev/null
+++ b/docs/custom-api.md
@@ -0,0 +1,296 @@
+[Jump to function definitions](#functions)
+
+## Examples
+
+The best way to get an idea of how the templates work would be with a bunch examples. Here are the most common use cases:
+
+JSON response:
+
+```json
+{
+ "title": "My Title",
+ "content": "My Content",
+}
+```
+
+To access the two fields in the JSON response, you would use the following:
+
+```html
+
{{ .JSON.String "title" }}
+
{{ .JSON.String "content" }}
+```
+
+Output:
+
+```html
+
My Title
+
My Content
+```
+
+
+
+JSON response:
+
+```json
+{
+ "author": "John Doe",
+ "posts": [
+ {
+ "title": "My Title",
+ "content": "My Content"
+ },
+ {
+ "title": "My Title 2",
+ "content": "My Content 2"
+ }
+ ]
+}
+```
+
+To loop through the array of posts, you would use the following:
+
+```html
+{{ range .JSON.Array "posts" }}
+
{{ .String "title" }}
+
{{ .String "content" }}
+{{ end }}
+```
+
+Output:
+
+```html
+
My Title
+
My Content
+
My Title 2
+
My Content 2
+```
+
+Notice the missing `.JSON` when accessing the title and content, this is because the range function sets the context to the current array element.
+
+If you want to access the top-level context within the range, you can use the following:
+
+```html
+{{ range .JSON.Array "posts" }}
+
{{ .String "title" }}
+
{{ .String "content" }}
+
{{ $.JSON.String "author" }}
+{{ end }}
+```
+
+Output:
+
+```html
+
My Title
+
My Content
+
John Doe
+
My Title 2
+
My Content 2
+
John Doe
+```
+
+
+
+JSON response:
+
+```json
+[
+ "Apple",
+ "Banana",
+ "Cherry",
+ "Watermelon"
+]
+```
+
+Somewhat awkwardly, when the current context is a basic type that isn't an object, the way you specify its type is to use an empty string as the key. So, to loop through the array of strings, you would use the following:
+
+```html
+{{ range .JSON.Array "" }}
+
{{ .String "" }}
+{{ end }}
+```
+
+Output:
+
+```html
+
Apple
+
Banana
+
Cherry
+
Watermelon
+```
+
+To access an item at a specific index, you could use the following:
+
+```html
+
{{ .JSON.String "0" }}
+```
+
+Output:
+
+```html
+
Apple
+```
+
+
+
+JSON response:
+
+```json
+{
+ "user": {
+ "address": {
+ "city": "New York",
+ "state": "NY"
+ }
+ }
+}
+```
+
+To easily access deeply nested objects, you can use the following dot notation:
+
+```html
+
{{ .JSON.String "user.address.city" }}
+
{{ .JSON.String "user.address.state" }}
+```
+
+Output:
+
+```html
+
New York
+
NY
+```
+
+Using indexes anywhere in the path is also supported:
+
+```json
+{
+ "users": [
+ {
+ "name": "John Doe"
+ },
+ {
+ "name": "Jane Doe"
+ }
+ ]
+}
+```
+
+```html
+
{{ .JSON.String "users.0.name" }}
+
{{ .JSON.String "users.1.name" }}
+```
+
+Output:
+
+```html
+
John Doe
+
Jane Doe
+```
+
+
+
+JSON response:
+
+```json
+{
+ "user": {
+ "name": "John Doe",
+ "age": 30
+ }
+}
+```
+
+To check if a field exists, you can use the following:
+
+```html
+{{ if .JSON.Exists "user.age" }}
+
{{ .JSON.Int "user.age" }}
+{{ else }}
+
Age not provided
+{{ end }}
+```
+
+Output:
+
+```html
+
30
+```
+
+
+
+JSON response:
+
+```json
+{
+ "price": 100,
+ "discount": 10
+}
+```
+
+Calculations can be performed, however all numbers must be converted to floats first if they are not already:
+
+```html
+
+```
+
+Other operations include `add`, `mul`, and `div`.
+
+
+
+In some instances, you may want to know the status code of the response. This can be done using the following:
+
+```html
+{{ if eq .Response.StatusCode 200 }}
+
Success!
+{{ else }}
+
Failed to fetch data
+{{ end }}
+```
+
+You can also access the response headers:
+
+```html
+
{{ .Response.Header.Get "Content-Type" }}
+```
+
+## Functions
+
+The following functions are available on the `JSON` object:
+
+- `String(key string) string`: Returns the value of the key as a string.
+- `Int(key string) int`: Returns the value of the key as an integer.
+- `Float(key string) float`: Returns the value of the key as a float.
+- `Bool(key string) bool`: Returns the value of the key as a boolean.
+- `Array(key string) []JSON`: Returns the value of the key as an array of `JSON` objects.
+- `Exists(key string) bool`: Returns true if the key exists in the JSON object.
+
+The following helper functions provided by Glance are available:
+
+- `toFloat(i int) float`: Converts an integer to a float.
+- `toInt(f float) int`: Converts a float to an integer.
+- `add(a, b float) float`: Adds two numbers.
+- `sub(a, b float) float`: Subtracts two numbers.
+- `mul(a, b float) float`: Multiplies two numbers.
+- `div(a, b float) float`: Divides two numbers.
+- `formatApproxNumber(n int) string`: Formats a number to be more human-readable, e.g. 1000 -> 1k.
+- `formatNumber(n float|int) string`: Formats a number with commas, e.g. 1000 -> 1,000.
+
+The following helper functions provided by Go's `text/template` are available:
+
+- `eq(a, b any) bool`: Compares two values for equality.
+- `ne(a, b any) bool`: Compares two values for inequality.
+- `lt(a, b any) bool`: Compares two values for less than.
+- `lte(a, b any) bool`: Compares two values for less than or equal to.
+- `gt(a, b any) bool`: Compares two values for greater than.
+- `gte(a, b any) bool`: Compares two values for greater than or equal to.
+- `and(a, b bool) bool`: Returns true if both values are true.
+- `or(a, b bool) bool`: Returns true if either value is true.
+- `not(a bool) bool`: Returns the opposite of the value.
+- `index(a any, b int) any`: Returns the value at the specified index of an array.
+- `len(a any) int`: Returns the length of an array.
+- `printf(format string, a ...any) string`: Returns a formatted string.
diff --git a/docs/extensions.md b/docs/extensions.md
index 06db1ae..b1fa4fa 100644
--- a/docs/extensions.md
+++ b/docs/extensions.md
@@ -29,6 +29,9 @@ Used to specify the title of the widget. If not provided, the widget's title wil
### `Widget-Content-Type`
Used to specify the content type that will be returned by the extension. If not provided, the content will be shown as plain text.
+### `Widget-Content-Frameless`
+When set to `true`, the widget's content will be displayed without the default background or "frame".
+
## Content Types
> [!NOTE]
diff --git a/docs/glance.yml b/docs/glance.yml
new file mode 100644
index 0000000..92b4de1
--- /dev/null
+++ b/docs/glance.yml
@@ -0,0 +1,108 @@
+pages:
+ - name: Home
+ # Optionally, if you only have a single page you can hide the desktop navigation for a cleaner look
+ # hide-desktop-navigation: true
+ columns:
+ - size: small
+ widgets:
+ - type: calendar
+ first-day-of-week: monday
+
+ - type: rss
+ limit: 10
+ collapse-after: 3
+ cache: 12h
+ feeds:
+ - url: https://selfh.st/rss/
+ title: selfh.st
+ limit: 4
+ - url: https://ciechanow.ski/atom.xml
+ - url: https://www.joshwcomeau.com/rss.xml
+ title: Josh Comeau
+ - url: https://samwho.dev/rss.xml
+ - url: https://ishadeed.com/feed.xml
+ title: Ahmad Shadeed
+
+ - type: twitch-channels
+ channels:
+ - theprimeagen
+ - j_blow
+ - piratesoftware
+ - cohhcarnage
+ - christitustech
+ - EJ_SA
+
+ - size: full
+ widgets:
+ - type: group
+ widgets:
+ - type: hacker-news
+ - type: lobsters
+
+ - type: videos
+ channels:
+ - UCXuqSBlHAE6Xw-yeJA0Tunw # Linus Tech Tips
+ - UCR-DXc1voovS8nhAvccRZhg # Jeff Geerling
+ - UCsBjURrPoezykLs9EqgamOA # Fireship
+ - UCBJycsmduvYEL83R_U4JriQ # Marques Brownlee
+ - UCHnyfMqiRRG1u-2MsSQLbXA # Veritasium
+
+ - type: group
+ widgets:
+ - type: reddit
+ subreddit: technology
+ show-thumbnails: true
+ - type: reddit
+ subreddit: selfhosted
+ show-thumbnails: true
+
+ - size: small
+ widgets:
+ - type: weather
+ location: London, United Kingdom
+ units: metric # alternatively "imperial"
+ hour-format: 12h # alternatively "24h"
+ # Optionally hide the location from being displayed in the widget
+ # hide-location: true
+
+ - type: markets
+ # The link to go to when clicking on the symbol in the UI,
+ # {SYMBOL} will be substituded with the symbol for each market
+ symbol-link-template: https://www.tradingview.com/symbols/{SYMBOL}/news
+ markets:
+ - symbol: SPY
+ name: S&P 500
+ - symbol: BTC-USD
+ name: Bitcoin
+ - symbol: NVDA
+ name: NVIDIA
+ - symbol: AAPL
+ name: Apple
+ - symbol: MSFT
+ name: Microsoft
+
+ - type: releases
+ cache: 1d
+ # Without authentication the Github API allows for up to 60 requests per hour. You can create a
+ # read-only token from your Github account settings and use it here to increase the limit.
+ # token: ...
+ repositories:
+ - glanceapp/glance
+ - go-gitea/gitea
+ - immich-app/immich
+ - syncthing/syncthing
+
+ # Add more pages here:
+ # - name: Your page name
+ # columns:
+ # - size: small
+ # widgets:
+ # # Add widgets here
+
+ # - size: full
+ # widgets:
+ # # Add widgets here
+
+ # - size: small
+ # widgets:
+ # # Add widgets here
diff --git a/docs/images/calendar-legacy-widget-preview.png b/docs/images/calendar-legacy-widget-preview.png
new file mode 100644
index 0000000..5a161bf
Binary files /dev/null and b/docs/images/calendar-legacy-widget-preview.png differ
diff --git a/docs/images/calendar-widget-preview.png b/docs/images/calendar-widget-preview.png
index 5a161bf..4922a9b 100644
Binary files a/docs/images/calendar-widget-preview.png and b/docs/images/calendar-widget-preview.png differ
diff --git a/docs/images/custom-api-preview-1.png b/docs/images/custom-api-preview-1.png
new file mode 100644
index 0000000..4cf4c30
Binary files /dev/null and b/docs/images/custom-api-preview-1.png differ
diff --git a/docs/images/custom-api-preview-2.png b/docs/images/custom-api-preview-2.png
new file mode 100644
index 0000000..481ef85
Binary files /dev/null and b/docs/images/custom-api-preview-2.png differ
diff --git a/docs/images/custom-api-preview-3.png b/docs/images/custom-api-preview-3.png
new file mode 100644
index 0000000..15d8cb2
Binary files /dev/null and b/docs/images/custom-api-preview-3.png differ
diff --git a/docs/images/docker-container-parent.png b/docs/images/docker-container-parent.png
new file mode 100644
index 0000000..479b3e8
Binary files /dev/null and b/docs/images/docker-container-parent.png differ
diff --git a/docs/images/docker-container-parent2.png b/docs/images/docker-container-parent2.png
new file mode 100644
index 0000000..4562239
Binary files /dev/null and b/docs/images/docker-container-parent2.png differ
diff --git a/docs/images/docker-containers-preview.png b/docs/images/docker-containers-preview.png
new file mode 100644
index 0000000..ba14fce
Binary files /dev/null and b/docs/images/docker-containers-preview.png differ
diff --git a/docs/images/docker-widget-preview.png b/docs/images/docker-widget-preview.png
new file mode 100644
index 0000000..5b644d4
Binary files /dev/null and b/docs/images/docker-widget-preview.png differ
diff --git a/docs/images/monitor-widget-compact-preview.png b/docs/images/monitor-widget-compact-preview.png
new file mode 100644
index 0000000..3e81fce
Binary files /dev/null and b/docs/images/monitor-widget-compact-preview.png differ
diff --git a/docs/images/preconfigured-page-preview.png b/docs/images/preconfigured-page-preview.png
index 0a57c14..e10084c 100644
Binary files a/docs/images/preconfigured-page-preview.png and b/docs/images/preconfigured-page-preview.png differ
diff --git a/docs/images/reddit-field-search.png b/docs/images/reddit-field-search.png
index 97ba04a..a84ee33 100644
Binary files a/docs/images/reddit-field-search.png and b/docs/images/reddit-field-search.png differ
diff --git a/docs/images/server-stats-flame-icon.png b/docs/images/server-stats-flame-icon.png
new file mode 100644
index 0000000..28cf0b8
Binary files /dev/null and b/docs/images/server-stats-flame-icon.png differ
diff --git a/docs/images/server-stats-preview.gif b/docs/images/server-stats-preview.gif
new file mode 100644
index 0000000..829679b
Binary files /dev/null and b/docs/images/server-stats-preview.gif differ
diff --git a/docs/images/split-column-widget-3-columns.png b/docs/images/split-column-widget-3-columns.png
new file mode 100644
index 0000000..88192c3
Binary files /dev/null and b/docs/images/split-column-widget-3-columns.png differ
diff --git a/docs/images/split-column-widget-4-columns.png b/docs/images/split-column-widget-4-columns.png
new file mode 100644
index 0000000..450f53e
Binary files /dev/null and b/docs/images/split-column-widget-4-columns.png differ
diff --git a/docs/images/split-column-widget-masonry.png b/docs/images/split-column-widget-masonry.png
new file mode 100644
index 0000000..145b2d6
Binary files /dev/null and b/docs/images/split-column-widget-masonry.png differ
diff --git a/docs/images/split-column-widget-preview.png b/docs/images/split-column-widget-preview.png
new file mode 100644
index 0000000..1ee336d
Binary files /dev/null and b/docs/images/split-column-widget-preview.png differ
diff --git a/docs/images/videos-widget-vertical-list-preview.png b/docs/images/videos-widget-vertical-list-preview.png
new file mode 100644
index 0000000..e33ce86
Binary files /dev/null and b/docs/images/videos-widget-vertical-list-preview.png differ
diff --git a/docs/v0.7.0-upgrade.md b/docs/v0.7.0-upgrade.md
new file mode 100644
index 0000000..93fa6b8
--- /dev/null
+++ b/docs/v0.7.0-upgrade.md
@@ -0,0 +1,57 @@
+## Upgrading to v0.7.0 from previous versions
+
+In essence, the `glance.yml` file has been moved from the root of the project to a `config/` directory and you now need to mount that directory to `/app/config` in the container.
+
+### Before
+
+Versions before v0.7.0 used a `docker-compose.yml` that looked like the following:
+
+```yaml
+services:
+ glance:
+ image: glanceapp/glance
+ volumes:
+ - ./glance.yml:/app/glance.yml
+ ports:
+ - 8080:8080
+```
+
+And expected you to have the following directory structure:
+
+```plaintext
+glance/
+ docker-compose.yml
+ glance.yml
+```
+
+### After
+
+With the release of v0.7.0, the recommended `docker-compose.yml` looks like the following:
+
+```yaml
+services:
+ glance:
+ container_name: glance
+ image: glanceapp/glance
+ volumes:
+ - ./config:/app/config
+ ports:
+ - 8080:8080
+```
+
+And expects you to have the following directory structure:
+
+```plaintext
+glance/
+ docker-compose.yml
+ config/
+ glance.yml
+```
+
+## Why this change was necessary
+
+1. Mounting a file rather than a directory is not common practice and leads to some issues, such as creating a directory if the file is not present, which has tripped up multiple people and caused unnecessary confusion
+2. v0.7.0 added automatic reloads when the configuration file changes, which based on testing didn't work when mounting a single file
+3. v0.7.0 added the ability to include config files, so you'd have to make this change anyways if you wanted to take advantage of that feature
+
+Taking all of these into account, it felt like the right time to implement the change.
diff --git a/go.mod b/go.mod
index 7034fe5..0ded337 100644
--- a/go.mod
+++ b/go.mod
@@ -1,19 +1,32 @@
module github.com/glanceapp/glance
-go 1.22.5
+go 1.23.6
require (
+ github.com/fsnotify/fsnotify v1.8.0
github.com/mmcdole/gofeed v1.3.0
- golang.org/x/text v0.21.0
+ github.com/shirou/gopsutil/v4 v4.25.1
+ github.com/tidwall/gjson v1.18.0
+ golang.org/x/text v0.22.0
gopkg.in/yaml.v3 v3.0.1
)
require (
- github.com/PuerkitoBio/goquery v1.9.2 // indirect
- github.com/andybalholm/cascadia v1.3.2 // indirect
+ github.com/PuerkitoBio/goquery v1.10.1 // indirect
+ github.com/andybalholm/cascadia v1.3.3 // indirect
+ github.com/ebitengine/purego v0.8.2 // indirect
+ github.com/go-ole/go-ole v1.3.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
+ github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 // indirect
github.com/mmcdole/goxpp v1.1.1 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
- golang.org/x/net v0.33.0 // indirect
+ github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
+ github.com/tidwall/match v1.1.1 // indirect
+ github.com/tidwall/pretty v1.2.1 // indirect
+ github.com/tklauser/go-sysconf v0.3.14 // indirect
+ github.com/tklauser/numcpus v0.9.0 // indirect
+ github.com/yusufpapurcu/wmi v1.2.4 // indirect
+ golang.org/x/net v0.34.0 // indirect
+ golang.org/x/sys v0.30.0 // indirect
)
diff --git a/go.sum b/go.sum
index 1f81f1c..97af31d 100644
--- a/go.sum
+++ b/go.sum
@@ -1,13 +1,24 @@
-github.com/PuerkitoBio/goquery v1.9.2 h1:4/wZksC3KgkQw7SQgkKotmKljk0M6V8TUvA8Wb4yPeE=
-github.com/PuerkitoBio/goquery v1.9.2/go.mod h1:GHPCaP0ODyyxqcNoFGYlAprUFH81NuRPd0GX3Zu2Mvk=
-github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss=
-github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU=
+github.com/PuerkitoBio/goquery v1.10.1 h1:Y8JGYUkXWTGRB6Ars3+j3kN0xg1YqqlwvdTV8WTFQcU=
+github.com/PuerkitoBio/goquery v1.10.1/go.mod h1:IYiHrOMps66ag56LEH7QYDDupKXyo5A8qrjIx3ZtujY=
+github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM=
+github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/ebitengine/purego v0.8.2 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z1I=
+github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
+github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
+github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
+github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
+github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
+github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
+github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
+github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
+github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 h1:7UMa6KCCMjZEMDtTVdcGu0B1GmmC7QJKiCCjyTAWQy0=
+github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k=
github.com/mmcdole/gofeed v1.3.0 h1:5yn+HeqlcvjMeAI4gu6T+crm7d0anY85+M+v6fIFNG4=
github.com/mmcdole/gofeed v1.3.0/go.mod h1:9TGv2LcJhdXePDzxiuMnukhV2/zb6VtnZt1mS+SjkLE=
github.com/mmcdole/goxpp v1.1.1 h1:RGIX+D6iQRIunGHrKqnA2+700XMCnNv0bAOOv5MUhx8=
@@ -19,47 +30,99 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
+github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
+github.com/shirou/gopsutil/v4 v4.25.1 h1:QSWkTc+fu9LTAWfkZwZ6j8MSUk4A2LV7rbH0ZqmLjXs=
+github.com/shirou/gopsutil/v4 v4.25.1/go.mod h1:RoUCUpndaJFtT+2zsZzzmhvbfGoDCJ7nFXKJf8GqJbI=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
-github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
-github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
+github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
+github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
+github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
+github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
+github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
+github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
+github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
+github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU=
+github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY=
+github.com/tklauser/numcpus v0.9.0 h1:lmyCHtANi8aRUgkckBgoDk1nHCux3n2cgkJLXdQGPDo=
+github.com/tklauser/numcpus v0.9.0/go.mod h1:SN6Nq1O3VychhC1npsWostA+oW+VOQTxZrS604NSRyI=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
+github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
+github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
+golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
+golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
+golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
+golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
-golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
-golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
+golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
+golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
+golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
+golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
+golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
+golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
+golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
+golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
-golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
+golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
+golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
+golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
+golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
+golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
-golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
+golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
+golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
+golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
+golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
+golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
+golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
diff --git a/internal/assets/files.go b/internal/assets/files.go
deleted file mode 100644
index 2c7c09e..0000000
--- a/internal/assets/files.go
+++ /dev/null
@@ -1,56 +0,0 @@
-package assets
-
-import (
- "crypto/md5"
- "embed"
- "encoding/hex"
- "io"
- "io/fs"
- "log/slog"
- "strconv"
- "time"
-)
-
-//go:embed static
-var _publicFS embed.FS
-
-//go:embed templates
-var _templateFS embed.FS
-
-var PublicFS, _ = fs.Sub(_publicFS, "static")
-var TemplateFS, _ = fs.Sub(_templateFS, "templates")
-
-func getFSHash(files fs.FS) string {
- hash := md5.New()
-
- err := fs.WalkDir(files, ".", func(path string, d fs.DirEntry, err error) error {
- if err != nil {
- return err
- }
-
- if d.IsDir() {
- return nil
- }
-
- file, err := files.Open(path)
-
- if err != nil {
- return err
- }
-
- if _, err := io.Copy(hash, file); err != nil {
- return err
- }
-
- return nil
- })
-
- if err == nil {
- return hex.EncodeToString(hash.Sum(nil))[:10]
- }
-
- slog.Warn("Could not compute assets cache", "err", err)
- return strconv.FormatInt(time.Now().Unix(), 10)
-}
-
-var PublicFSHash = getFSHash(PublicFS)
diff --git a/internal/assets/templates.go b/internal/assets/templates.go
deleted file mode 100644
index 85abb69..0000000
--- a/internal/assets/templates.go
+++ /dev/null
@@ -1,109 +0,0 @@
-package assets
-
-import (
- "fmt"
- "html/template"
- "math"
- "strconv"
- "time"
-
- "golang.org/x/text/language"
- "golang.org/x/text/message"
-)
-
-var (
- PageTemplate = compileTemplate("page.html", "document.html", "page-style-overrides.gotmpl")
- PageContentTemplate = compileTemplate("content.html")
- CalendarTemplate = compileTemplate("calendar.html", "widget-base.html")
- ClockTemplate = compileTemplate("clock.html", "widget-base.html")
- BookmarksTemplate = compileTemplate("bookmarks.html", "widget-base.html")
- IFrameTemplate = compileTemplate("iframe.html", "widget-base.html")
- WeatherTemplate = compileTemplate("weather.html", "widget-base.html")
- ForumPostsTemplate = compileTemplate("forum-posts.html", "widget-base.html")
- RedditCardsHorizontalTemplate = compileTemplate("reddit-horizontal-cards.html", "widget-base.html")
- RedditCardsVerticalTemplate = compileTemplate("reddit-vertical-cards.html", "widget-base.html")
- ReleasesTemplate = compileTemplate("releases.html", "widget-base.html")
- ChangeDetectionTemplate = compileTemplate("change-detection.html", "widget-base.html")
- VideosTemplate = compileTemplate("videos.html", "widget-base.html", "video-card-contents.html")
- VideosGridTemplate = compileTemplate("videos-grid.html", "widget-base.html", "video-card-contents.html")
- MarketsTemplate = compileTemplate("markets.html", "widget-base.html")
- RSSListTemplate = compileTemplate("rss-list.html", "widget-base.html")
- RSSDetailedListTemplate = compileTemplate("rss-detailed-list.html", "widget-base.html")
- RSSHorizontalCardsTemplate = compileTemplate("rss-horizontal-cards.html", "widget-base.html")
- RSSHorizontalCards2Template = compileTemplate("rss-horizontal-cards-2.html", "widget-base.html")
- MonitorTemplate = compileTemplate("monitor.html", "widget-base.html")
- TwitchGamesListTemplate = compileTemplate("twitch-games-list.html", "widget-base.html")
- TwitchChannelsTemplate = compileTemplate("twitch-channels.html", "widget-base.html")
- RepositoryTemplate = compileTemplate("repository.html", "widget-base.html")
- SearchTemplate = compileTemplate("search.html", "widget-base.html")
- ExtensionTemplate = compileTemplate("extension.html", "widget-base.html")
- GroupTemplate = compileTemplate("group.html", "widget-base.html")
- DNSStatsTemplate = compileTemplate("dns-stats.html", "widget-base.html")
-)
-
-var globalTemplateFunctions = template.FuncMap{
- "relativeTime": relativeTimeSince,
- "formatViewerCount": formatViewerCount,
- "formatNumber": intl.Sprint,
- "absInt": func(i int) int {
- return int(math.Abs(float64(i)))
- },
- "formatPrice": func(price float64) string {
- return intl.Sprintf("%.2f", price)
- },
- "dynamicRelativeTimeAttrs": func(t time.Time) template.HTMLAttr {
- return template.HTMLAttr(fmt.Sprintf(`data-dynamic-relative-time="%d"`, t.Unix()))
- },
-}
-
-func compileTemplate(primary string, dependencies ...string) *template.Template {
- t, err := template.New(primary).
- Funcs(globalTemplateFunctions).
- ParseFS(TemplateFS, append([]string{primary}, dependencies...)...)
-
- if err != nil {
- panic(err)
- }
-
- return t
-}
-
-var intl = message.NewPrinter(language.English)
-
-func formatViewerCount(count int) string {
- if count < 1_000 {
- return strconv.Itoa(count)
- }
-
- if count < 10_000 {
- return fmt.Sprintf("%.1fk", float64(count)/1_000)
- }
-
- if count < 1_000_000 {
- return fmt.Sprintf("%dk", count/1_000)
- }
-
- return fmt.Sprintf("%.1fm", float64(count)/1_000_000)
-}
-
-func relativeTimeSince(t time.Time) string {
- delta := time.Since(t)
-
- if delta < time.Minute {
- return "1m"
- }
- if delta < time.Hour {
- return fmt.Sprintf("%dm", delta/time.Minute)
- }
- if delta < 24*time.Hour {
- return fmt.Sprintf("%dh", delta/time.Hour)
- }
- if delta < 30*24*time.Hour {
- return fmt.Sprintf("%dd", delta/(24*time.Hour))
- }
- if delta < 12*30*24*time.Hour {
- return fmt.Sprintf("%dmo", delta/(30*24*time.Hour))
- }
-
- return fmt.Sprintf("%dy", delta/(365*24*time.Hour))
-}
diff --git a/internal/assets/templates/extension.html b/internal/assets/templates/extension.html
deleted file mode 100644
index e5794c8..0000000
--- a/internal/assets/templates/extension.html
+++ /dev/null
@@ -1,5 +0,0 @@
-{{ template "widget-base.html" . }}
-
-{{ define "widget-content" }}
-{{ .Extension.Content }}
-{{ end }}
diff --git a/internal/assets/templates/forum-posts.html b/internal/assets/templates/forum-posts.html
deleted file mode 100644
index 8a71d22..0000000
--- a/internal/assets/templates/forum-posts.html
+++ /dev/null
@@ -1,49 +0,0 @@
-{{ template "widget-base.html" . }}
-
-{{ define "widget-content" }}
-
- {{ range .Posts }}
-
-
- {{ if $.ShowThumbnails }}
- {{ if .IsCrosspost }}
-
- {{ else if ne .ThumbnailUrl "" }}
-
- {{ else if .HasTargetUrl }}
-
- {{ else }}
-
- {{ end }}
- {{ end }}
-
+{{ end }}
+{{ end }}
diff --git a/internal/assets/templates/monitor.html b/internal/glance/templates/monitor.html
similarity index 62%
rename from internal/assets/templates/monitor.html
rename to internal/glance/templates/monitor.html
index b19f0e2..7e95b99 100644
--- a/internal/assets/templates/monitor.html
+++ b/internal/glance/templates/monitor.html
@@ -21,11 +21,11 @@
{{ end }}
{{ define "site" }}
-{{ if .IconUrl }}
-
+{{ if .Icon.URL }}
+
{{ end }}
+{{ end }}
+
+{{ end }}
diff --git a/internal/assets/templates/rss-detailed-list.html b/internal/glance/templates/rss-detailed-list.html
similarity index 100%
rename from internal/assets/templates/rss-detailed-list.html
rename to internal/glance/templates/rss-detailed-list.html
diff --git a/internal/assets/templates/rss-horizontal-cards-2.html b/internal/glance/templates/rss-horizontal-cards-2.html
similarity index 91%
rename from internal/assets/templates/rss-horizontal-cards-2.html
rename to internal/glance/templates/rss-horizontal-cards-2.html
index 0404fce..496e56a 100644
--- a/internal/assets/templates/rss-horizontal-cards-2.html
+++ b/internal/glance/templates/rss-horizontal-cards-2.html
@@ -16,7 +16,7 @@
{{ end }}
+ The default location of glance.yml in the Docker image has
+ changed since v0.7.0, please see the migration guide
+ for instructions or visit the release notes
+ to find out more about why this change was necessary. Sorry for the inconvenience.
+
+
+
Migration should take around 5 minutes.
+
+
+
+
+
diff --git a/internal/assets/templates/video-card-contents.html b/internal/glance/templates/video-card-contents.html
similarity index 78%
rename from internal/assets/templates/video-card-contents.html
rename to internal/glance/templates/video-card-contents.html
index 375fd08..c6340c5 100644
--- a/internal/assets/templates/video-card-contents.html
+++ b/internal/glance/templates/video-card-contents.html
@@ -1,7 +1,7 @@
{{ define "video-card-contents" }}