Skip to content

Releases: glanceapp/glance

v0.8.3

19 May 20:45
Compare
Choose a tag to compare

New

  • Added the ability to disable the theme picker via disable-picker: true under theme (thanks @NeoNyaa)
  • Added mod function (modulo) to the custom API widget (thanks @anant-j)
  • Added safeHTML function to the custom API widget which prevents HTML from being escaped (thanks @sudoexec)
  • Added .Options.JSON to the custom API widget which takes any nested option value and turns it into a JSON string (thanks @ralphocdol)
View example
- type: custom-api
  options: 
    request-body: 
      service: Session
      method: login
      params: 
        username: ${USERNAME}
        password: ${PASSWORD}

  # useful if you need to send JSON in a request body
  template: |
    ...
    | withStringBody (.Options.JSON "request-body")
    ...
  • Added the theme key to the document root so that you can apply styles based on the selected theme (thanks @EricKotato)
View example
:root[data-theme="default-dark"] {
    .color-primary-if-not-visited {
        color: #ff0000;
    }
}

:root[data-theme="default-light"] {
    .color-primary-if-not-visited {
        color: #00ff00;
    }
}

:root[data-theme="your-theme-name"] {
    .color-primary-if-not-visited { 
        color: #0000ff;
    }
}

Fixes

  • Fixed HTTP_PROXY and HTTPS_PROXY environment variables not getting used (thanks @mazzz1y)
  • Fixed truncated links' hover text having unnecessary whitespace (thanks @ralphocdol)

Visit the release notes for v0.8.0 to learn about all new features

v0.8.2

14 May 21:34
Compare
Choose a tag to compare
  • Fixed hide-deskop-navigation not working (thanks @LZStealth)
  • Fixed custom favicons not working in Chromium browsers (thanks @AWildLeon)
  • Fixed server crash when using head-widgets with the releases widget (thanks @aykhans)
  • Fixed URLs getting escaped when using video-url-template in the videos widget with the vertical-list style (thanks @Kamalaja)
  • Added startOfDay and endOfDay functions to the custom API widget (thanks @ralphocdol)

Visit the release notes for v0.8.0 to learn about all new features

v0.8.1

13 May 19:42
Compare
Choose a tag to compare

Fixed an error that prevented the page from loading when hide-desktop-navigation is set to true (thanks @Geffreyvanderbos and @Blooym)

Visit the release notes for v0.8.0 to learn about all new features

v0.8.0

13 May 18:35
9de6843
Compare
Choose a tag to compare

Another big update is here! This one includes authentication, a theme picker, new customization options, a to-do widget and lots of improvements to existing widgets. Before we get to details of all that...

Feedback wanted

Deciding what features to add to a project like this is no easy task. Most self-hosted services focus on doing one thing and adding features around that thing, but with a personal dashboard/feed aggregator you can practically display anything, so how do you decide what does and doesn't belong? So far I've been prioritizing feature requests with a high number of upvotes and those I believe fit with the vision of the project, although admittedly Glance has deviated quite a bit from its initial purpose.

As with almost all communities, a minority of people are very vocal about their wants and needs, while the majority are just riding along. Rather than guessing what the community as a whole would benefit from the most moving forward, I thought I could just ask through a short survey.

Link to the survey

It's entirely anonymous, should only take about 1 to 3 minutes to complete, and I'd be happy to share the results once there are enough responses. Thank you to those who take the time to fill it out.

Jump to section

Community widgets

If you weren't aware, there's now a repository for widgets made by the community with 40+ widgets available so far. Most are made using the custom-api widget, so adding them is as simple as copying them into your glance.yml. It's a great place to find new widgets, and if you have a widget that you think others would benefit from, please consider sharing it there. Thank you to everyone who has contributed!

New logo

Big thanks to @Tom607 for offering help with the logo and going over dozens of different iterations!

logo

I believe it does a great job at conveying the widgety layout of Glance, while also being simple and clean, as is the rest of the design language.

New features

Nested config includes and new include syntax

Up until now, you could only use the !include directive in your glance.yml file, but not within any of the included files. This has been changed so that you can includes files from within included files as many times as you need, meaning you can have a separate file for each page, a separate file for each column, and even a separate file for each widget if you wanted to:

glance.yml
├── home.yml
│   ├── column1.yml
│   │   ├── widget1.yml
│   │   └── widget2.yml
│   └── column2.yml
│       ├── widget3.yml
│       └── widget4.yml

In addition, the !include directive can now instead be written as $include. The reason for this change is that it turns out !include, with the way it was used in Glance, is not actually valid YAML (whoops), meaning it makes YAML validators and syntax highlighters angry. The new $include syntax is valid YAML and will be the preferred syntax moving forward. You can also add a - at the beginning of a line when including array items, since that's the expected syntax for YAML arrays.

Before:

theme:
  !include: theme.yml

pages:
  !include: home.yml
  !include: homelab.yml
  !include: startpage.yml

Now:

theme:
  $include: theme.yml

pages:
  - $include: home.yml
  - $include: homelab.yml
  - $include: startpage.yml

Using Docker secrets within the config

Along with environment variables, you can now use Docker secrets in your config file using the following syntax:

token: ${secret:github_token} # Reads the contents of /run/secrets/github_token
token: ${GITHUB_TOKEN} # Environment variable

(thanks @gomeology)


Theme picker

You can now define multiple theme presets and switch between them in the UI, no more config changes needed to change the theme!

theme-switcher-preview

View config
theme:
  presets:
    neon-focus:
      background-color: 255 14 6
      primary-color: 156 50 65
      negative-color: 342 65 65
      contrast-multiplier: 0.9
      text-saturation-multiplier: 0.8

    gruvbox-dark:
      background-color: 0 0 16
      primary-color: 43 59 81
      positive-color: 61 66 44
      negative-color: 6 96 59

    catppuccin-macchiato:
      background-color: 232 23 18
      contrast-multiplier: 1.2
      primary-color: 220 83 75
      positive-color: 105 48 72
      negative-color: 351 74 73

    peachy:
      light: true
      background-color: 28 40 77
      primary-color: 155 100 20
      negative-color: 0 100 60
      contrast-multiplier: 1.1
      text-saturation-multiplier: 0.5

Glance will include its own default dark and light themes in the list of presets, which you can override by using the default-dark and default-light keys.

(thanks @hecht-a)


Authentication

You can now add authentication via username and password:

auth-preview

auth:
  secret-key: # this must be set to a random value generated using the secret:make CLI command
  users:
    admin:
      password: ${ADMIN_PASSWORD}

While there is support for multiple users, you cannot customize the dashboard per user at this time, so all users will see the same dashboard.

To generate the secret key, use the secret:make CLI command:

docker run --rm glanceapp/glance secret:make

Or with the binary:

/opt/glance/glance secret:make

If you do not want to store plain passwords in environment variables or in files, you can hash your password and provide it via the password-hash property instead:

auth:
  secret-key: # this must be set to a random value generated using the secret:make CLI command
  users:
    admin:
      password-hash: $2y$10$...

To hash your password, use the password:hash <password> CLI command:

docker run --rm glanceapp/glance password:hash mypasswordishunter2

Or with the binary:

/opt/glance/glance password:hash mypasswordishunter2

To-do widget

A fairly simple to-do widget with the ability to add, edit, reorder and delete tasks:

todo-preview

- type: to-do

There are a number of keyboard shortcuts available that make it easier to use, which you can find over at the config docs.

The tasks are stored in the browser's local storage, so they won't be available on all devices. If you want to add multiple todo widgets, you must specify a unique id for each one:

- type: to-do
  title: Work tasks
  id: work

- type: to-do
  title: Personal tasks
  id: personal

(thanks @AMS003010)


Hiding widget headers

A fairly simple addition, but one that's been requested quite a few times - you can now hide widget headers by setting the hide-header property to true:

widgets:
  - type: calendar
    hide-header: true

If you do this for all widgets, you get something like this:

no-headers-preview

Note that you cannot hide the header of the group widget since it's used to switch between each widget.


Head widgets

You can now define "head widgets" on each page, which are basically widgets that sit above all columns and take up the full width. Combined with the hide-header property, you get something like this:

head-widgets-preview

View config
pages:
  - name: Home
    head-widgets:
      - type: markets
        hide-header: true
        markets:
          - symbol: SPY
            name: S&P 500
          - symbol: BTC-USD
            name: Bitcoin
          - symbol: NVDA
            name: NVIDIA
          - symbol: AAPL
            name: Apple
          - symbol: MSFT
            name: Microsoft

    columns:
      - size: small
        widgets:
          - type: calendar
      - size: full
        widgets:
    ...
Read more

v0.7.13

22 Apr 22:19
Compare
Choose a tag to compare

New

  • [Custom API] Added parseLocalTime function, which works like parseTime, however in the absence of a timezone it uses the local timezone instead of UTC.
  • [Search widget] Added target property which is used when opening the search results in a new tab (thanks @Jacksaur)

Fixes

  • [Server stats widget] Fixed being unable to retrieve temperature sensors in some instances (thanks @anxdpanic)
  • [Reddit widget] Fixed the URL not being correctly escaped when using request-url-template (thanks @yurigiu)

v0.7.12

14 Apr 00:16
Compare
Choose a tag to compare

Fixed server crash in some configurations when a widget is provided with a single resource to fetch instead of multiple

v0.7.11

13 Apr 16:22
Compare
Choose a tag to compare

What's changed

  • [RSS widget] Duplicate post links will now be filtered out (thanks @HtFilia)
  • [Custom API widget] Added replaceMatches and unique functions (thanks @ralphocdol)
  • Added mountpoint:info CLI command
  • Added more details to the output of sensors:print CLI command

Fixes

  • [Twitch channels widget] Fixed live channels with 0 viewers getting incorrectly sorted lower than offline channels
  • [Server stats widget] Potential fix for missing mountpoint stats
  • Fixed the Yahoo finance test request in the diagnose CLI command

v0.7.10

09 Apr 23:51
Compare
Choose a tag to compare

What's Changed

  • Slightly increased the scale of everything on mobile to make things easier to tap on
  • Added sensors:print CLI command that lists all temperature sensors that Glance has access to
  • Bumped versions of dependencies

New Contributors

Full Changelog: v0.7.9...v0.7.10

v0.7.9

29 Mar 18:08
Compare
Choose a tag to compare

Another small release that's primarily focused around custom-api widget improvements.

Changed

You no longer need to convert numbers to float before doing math operations with them. When adding two integers together, the result will be an integer. Adding two floats together or an integer and a float will result in a float.

Before:

{{ (add (.JSON.Int "foo" | toFloat) (.JSON.Int "bar" | toFloat)) | toInt }}

Now:

{{ add (.JSON.Int "foo") (.JSON.Int "bar") }}

(this is consistent for all math operations, not just addition)

New

skip-json-validation and .JSONLines

- type: custom-api
  url: https://api.example.com
  skip-json-validation: true

Some API's return newline separated JSON objects instead of a single JSON array. This is not valid JSON, so the custom-api widget will fail to parse it. You can now set skip-json-validation to true to skip the validation step and parse the response as a list of JSON objects within your template.

{"name": "Steve", "age": 30}
{"name": "Alex", "age": 25}
{"name": "John", "age": 35}

You can then access the JSON objects using .JSONLines:

{{ range .JSONLines }}
  {{ .String "name" }}
{{ end }}

New functions

concat

{{ concat "foo" "bar" "baz" }}

will return foobarbaz.

(thanks @ralphocdol)

now

Returns the current time as a time.Time object.

{{ now }}
{{ now.Hour }}:{{ now.Minute }}:{{ now.Second }}
{{ now.Unix }}
2025-03-29 17:20:18.4905224 +0000 GMT m=+376.954385401
17:20:18
1743268818

This can also be used to convert time to your current timezone:

{{ $parsedTime := .JSON.String "date_created" | parseTime "rfc3339" }}
{{ $created := $parsedTime.In now.Location }}
{{ $created.Hour }}:{{ $created.Minute }}:{{ $created.Second }}

(thanks @not-first & @ralphocdol)

offsetNow

Returns the current time offset by a given amount:

{{ offsetNow "1h" }}
{{ now }}
{{ offsetNow "-1h" }}
2025-03-29 18:44:04.2719241 +0000 GMT m=+4178.324981601
2025-03-29 17:44:04.2719241 +0000 GMT m=+578.324981601
2025-03-29 16:44:04.2719241 +0000 GMT m=-3021.675018399

This can be used to check if something happened within a certain time frame:

{{ $created := .JSON.String "date_created" | parseTime "rfc3339" }}
{{ if ($created.After (offsetNow "-1h")) }}
  Created less than 1 hour ago
{{ end }}

Widget-Title-URL header for extension widget

The extension widget can now return a Widget-Title-URL header to set the link of the widget's title if it has not already been set by the user.

(thanks @not-first)

Fixes

  • The markets widget will now properly display prices of markets where the price is less than 0.01 (thanks @nsdont)

v0.7.8

26 Mar 19:59
Compare
Choose a tag to compare

The custom-api widget has received a lot of positive feedback since its introduction in v0.7.0. Many people have made and shared their own widgets over at the new community-widgets repository as well as the Discord server. This release includes many improvements based on feedback from the community that will make it even more capable and easier to use.

New

Insecure requests

You can now allow insecure requests (those to APIs behind a self-signed certificate) via a allow-insecure property:

- type: custom-api
  url: https://api.example.com
  allow-insecure: true

(thanks @ralphocdol)

Parameters

You can now specify query parameters via a parameters property:

- type: custom-api
  url: https://api.example.com
  parameters:
    foo: bar
    baz: qux

Note

Using the parameters property will override any query parameters specified in the URL.

(thanks @ralphocdol)

Request method and body

You can now specify the request method and body via the method, body-type and body properties:

- type: custom-api
  url: https://api.example.com
  method: POST
  body-type: json
  body:
    foo: bar
    baz: qux

If you set a body, the method will automatically be set to POST and the body-type will be set to json, so you don't have to specify them explicitly.

- type: custom-api
  url: https://api.example.com
  body-type: string
  body: |
    foo=bar&baz=qux

(thanks @not-first)

Subrequests

You can now make multiple requests in a single custom-api widget via a subrequests property:

- type: custom-api
  url: https://api.example.com
  subrequests:
    another-one:
      url: https://api.example.com/another-one
    and-another-one:
      url: https://api.example.com/and-another-one

Subrequests can take all of the same properties as the main request, such as: parameters, headers, method, body-type, body and allow-insecure.

To access the JSON of a subrequest, you can use .Subrequest "key":

<p>{{ (.Subrequest "another-one").JSON.String "foo" }}</p>

This can get cumbersome to write if you need to reference the subrequest in multiple places, so you can instead assign it to a variable:

{{ $anotherOne := .Subrequest "another-one" }}
<p>{{ $anotherOne.JSON.String "foo" }}</p>
<p>{{ $anotherOne.JSON.Int "bar" }}</p>

You can also access the response as you would on the main request:

{{ $andAnotherOne := .Subrequest "and-another-one" }}
<p>{{ $andAnotherOne.Response.StatusCode }}</p>

(thanks @ralphocdol)

New functions

trimPrefix

{"foo": "bazbar"}
<p>{{ .JSON.String "foo" | trimPrefix "baz" }}</p>
<p>bar</p>

trimSuffix

{"foo": "barbaz"}
<p>{{ .JSON.String "foo" | trimSuffix "baz" }}</p>
<p>bar</p>

trimSpace

{"foo": "  bar  "}
<p>{{ .JSON.String "foo" | trimSpace }}</p>
<p>bar</p>

replaceAll

{"foo": "barbazbar"}
<p>{{ .JSON.String "foo" | replaceAll "baz" "bar" }}</p>
<p>barbarbar</p>

findMatch

{"foo": "bar-123456-baz"}
<p>{{ .JSON.String "foo" | findMatch "\\d+" }}</p>

The pattern is a regular expression, although note that backslashes need to be escaped, so \d in a normal regular expression would be \\d here.

<p>123456</p>

findSubmatch

{"foo": "bar-unknown-value"}
<p>{{ .JSON.String "foo" | findSubmatch "bar-(.*)" }}</p>

The pattern is a regular expression, and only the first submatch is returned.

<p>unknown-value</p>

parseTime

{"foo": "2021-01-02T15:04:05Z"}
{{ $parsedTime := .JSON.String "foo" | parseTime "rfc3339" }}
<p>Year: {{ $parsedTime.Year }}</p>
<p>Month: {{ $parsedTime.Month }}</p>
<p>Day: {{ $parsedTime.Day }}</p>
<p>Hour: {{ $parsedTime.Hour }}</p>
<p>Minute: {{ $parsedTime.Minute }}</p>
<p>Second: {{ $parsedTime.Second }}</p>

Other accepted time formats are unix, rfc3339nano, datetime, dateonly or a custom format using Go's date formatting. The returned object is Go's time.Time.

<p>Year: 2021</p>
<p>Month: January</p>
<p>Day: 2</p>
<p>Hour: 15</p>
<p>Minute: 4</p>
<p>Second: 5</p>

toRelativeTime

{"foo": "2021-01-02T15:04:05Z"}
<p {{ .JSON.String "foo" | parseTime "rfc3339" | toRelativeTime }}></p>

Note

The return value of this function must be placed within a tag's attributes, not within its content.

<p data-dynamic-relative-time="1609602245"></p>

This will automatically be converted to 1d, 2h, etc. on the client side.

parseRelativeTime

This is just a shorthand for parsing time and converting it to relative time. Instead of:

<p {{ .JSON.String "foo" | parseTime "rfc3339" | toRelativeTime }}></p>

You can simply do:

<p {{ .JSON.String "foo" | parseRelativeTime "rfc3339" }}></p>

sortByString, sortByInt, sortByFloat, sortByTime

{"students": [
  {"name": "Bob", "age": 20},
  {"name": "Alice", "age": 30},
  {"name": "Charlie", "age": 10}
]}
{{ range .JSON.Array "students" | sortByString "name" "asc" }}
  <p>{{ .String "name" }}</p>
{{ end }}
<p>Alice</p>
<p>Bob</p>
<p>Charlie</p>

{{ range .JSON.Array "students" | sortByInt "age" "desc" }}
  <p>{{ .Int "age" }}</p>
{{ end }}
<p>30</p>
<p>20</p>
<p>10</p>

{"students": [
  {"name": "Bob", "gpa": 3.5},
  {"name": "Alice", "gpa": 4.0},
  {"name": "Charlie", "gpa": 2.0}
]}
{{ range .JSON.Array "students" | sortByFloat "gpa" "asc" }}
  <p>{{ .Float "gpa" }}</p>
{{ end }}
<p>2</p>
<p>3.5</p>
<p>4</p>

{"students": [
  {"name": "Bob", "dob": "2000-01-01"},
  {"name": "Alice", "dob": "1990-01-01"},
  {"name": "Charlie", "dob": "2010-01-01"}
]}
{{ range .JSON.Array "students" | sortByTime "dob" "dateonly" "asc"  }}
  <p>{{ .String "name" }}</p>
{{ end }}

Here, dateonly is the same format that you would use with parseTime.

<p>Alice</p>
<p>Bob</p>
<p>Charlie</p>

Other additions

Extension widget headers property

You can now specify headers in the extension widget:

- type: extension
  headers:
    Authorization: Bearer token

Fixes

  • Fixed being unable to parse an empty response body in the custom-api widget
  • Fixed always overriding query parameters in the extension widget, they will now only be overridden if the parameters property is set