Glance
A self-hosted dashboard that puts all your feeds in one place
Alternative to: netvibes, igoogle, feedly
v0.7.8
2025-03-26The 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-apiwidget - Fixed always overriding query parameters in the
extensionwidget, they will now only be overridden if theparametersproperty is set