Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9a36187333 | ||
|
|
b0c4d70628 | ||
|
|
d5fa6424c1 | ||
|
|
07fe5b3cb1 | ||
|
|
672547cd07 | ||
|
|
28167403a4 | ||
|
|
eaf08d42dd | ||
|
|
bae95a5e07 | ||
|
|
db1ed9e257 | ||
|
|
adef35049f | ||
|
|
bd9abf2e52 | ||
|
|
aa74c00fd4 | ||
|
|
a68a907fa7 | ||
|
|
1a05d6d03a | ||
|
|
60f4183057 | ||
|
|
04c3afc850 |
+12
-3
@@ -138,6 +138,10 @@ A number between 1 and 65,535, so long as that port isn't already used by anythi
|
|||||||
#### `base-url`
|
#### `base-url`
|
||||||
The base URL that Glance is hosted under. No need to specify this unless you're using a reverse proxy and are hosting Glance under a directory. If that's the case then you can set this value to `/glance` or whatever the directory is called. Note that the forward slash (`/`) in the beginning is required unless you specify the full domain and path.
|
The base URL that Glance is hosted under. No need to specify this unless you're using a reverse proxy and are hosting Glance under a directory. If that's the case then you can set this value to `/glance` or whatever the directory is called. Note that the forward slash (`/`) in the beginning is required unless you specify the full domain and path.
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> You need to strip the `base-url` prefix before forwarding the request to the Glance server.
|
||||||
|
> In Caddy you can do this using [`handle_path`](https://caddyserver.com/docs/caddyfile/directives/handle_path) or [`uri strip_prefix`](https://caddyserver.com/docs/caddyfile/directives/uri).
|
||||||
|
|
||||||
#### `assets-path`
|
#### `assets-path`
|
||||||
The path to a directory that will be served by the server under the `/assets/` path. This is handy for widgets like the Monitor where you have to specify an icon URL and you want to self host all the icons rather than pointing to an external source.
|
The path to a directory that will be served by the server under the `/assets/` path. This is handy for widgets like the Monitor where you have to specify an icon URL and you want to self host all the icons rather than pointing to an external source.
|
||||||
|
|
||||||
@@ -307,6 +311,7 @@ pages:
|
|||||||
| title | string | yes | |
|
| title | string | yes | |
|
||||||
| slug | string | no | |
|
| slug | string | no | |
|
||||||
| width | string | no | |
|
| width | string | no | |
|
||||||
|
| center-vertically | boolean | no | false |
|
||||||
| hide-desktop-navigation | boolean | no | false |
|
| hide-desktop-navigation | boolean | no | false |
|
||||||
| show-mobile-header | boolean | no | false |
|
| show-mobile-header | boolean | no | false |
|
||||||
| columns | array | yes | |
|
| columns | array | yes | |
|
||||||
@@ -328,6 +333,8 @@ The maximum width of the page on desktop. Possible values are `slim` and `wide`.
|
|||||||
>
|
>
|
||||||
> When using `slim`, the maximum number of columns allowed for that page is `2`.
|
> When using `slim`, the maximum number of columns allowed for that page is `2`.
|
||||||
|
|
||||||
|
#### `center-vertically`
|
||||||
|
When set to `true`, vertically centers the content on the page. Has no effect if the content is taller than the height of the viewport.
|
||||||
|
|
||||||
#### `hide-desktop-navigation`
|
#### `hide-desktop-navigation`
|
||||||
Whether to show the navigation links at the top of the page on desktop.
|
Whether to show the navigation links at the top of the page on desktop.
|
||||||
@@ -1111,7 +1118,7 @@ Whether to ignore invalid/self-signed certificates.
|
|||||||
Whether to open the link in the same or a new tab.
|
Whether to open the link in the same or a new tab.
|
||||||
|
|
||||||
### Releases
|
### Releases
|
||||||
Display a list of latest releases for specific repositories on Github, GitLab or Docker Hub.
|
Display a list of latest releases for specific repositories on Github, GitLab, Codeberg or Docker Hub.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
@@ -1122,6 +1129,7 @@ Example:
|
|||||||
- go-gitea/gitea
|
- go-gitea/gitea
|
||||||
- jellyfin/jellyfin
|
- jellyfin/jellyfin
|
||||||
- glanceapp/glance
|
- glanceapp/glance
|
||||||
|
- codeberg:redict/redict
|
||||||
- gitlab:fdroid/fdroidclient
|
- gitlab:fdroid/fdroidclient
|
||||||
- dockerhub:gotify/server
|
- dockerhub:gotify/server
|
||||||
```
|
```
|
||||||
@@ -1142,12 +1150,13 @@ Preview:
|
|||||||
| collapse-after | integer | no | 5 |
|
| collapse-after | integer | no | 5 |
|
||||||
|
|
||||||
##### `repositories`
|
##### `repositories`
|
||||||
A list of repositores to fetch the latest release for. Only the name/repo is required, not the full URL. A prefix can be specified for repositories hosted elsewhere such as GitLab and Docker Hub. Example:
|
A list of repositores to fetch the latest release for. Only the name/repo is required, not the full URL. A prefix can be specified for repositories hosted elsewhere such as GitLab, Codeberg and Docker Hub. Example:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
repositories:
|
repositories:
|
||||||
- gitlab:inkscape/inkscape
|
- gitlab:inkscape/inkscape
|
||||||
- dockerhub:glanceapp/glance
|
- dockerhub:glanceapp/glance
|
||||||
|
- codeberg:redict/redict
|
||||||
```
|
```
|
||||||
|
|
||||||
Official images on Docker Hub can be specified by ommiting the owner:
|
Official images on Docker Hub can be specified by ommiting the owner:
|
||||||
@@ -1169,7 +1178,7 @@ repositories:
|
|||||||
|
|
||||||
|
|
||||||
##### `show-source-icon`
|
##### `show-source-icon`
|
||||||
Shows an icon of the source (GitHub/GitLab/Docker Hub) next to the repository name when set to `true`.
|
Shows an icon of the source (GitHub/GitLab/Codeberg/Docker Hub) next to the repository name when set to `true`.
|
||||||
|
|
||||||
##### `token`
|
##### `token`
|
||||||
Without authentication Github allows for up to 60 requests per hour. You can easily exceed this limit and start seeing errors if you're tracking lots of repositories or your cache time is low. To circumvent this you can [create a read only token from your Github account](https://github.com/settings/personal-access-tokens/new) and provide it here.
|
Without authentication Github allows for up to 60 requests per hour. You can easily exceed this limit and start seeing errors if you're tracking lots of repositories or your cache time is low. To circumvent this you can [create a read only token from your Github account](https://github.com/settings/personal-access-tokens/new) and provide it here.
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M11.955.49A12 12 0 0 0 0 12.49a12 12 0 0 0 1.832 6.373L11.838 5.928a.187.14 0 0 1 .324 0l10.006 12.935A12 12 0 0 0 24 12.49a12 12 0 0 0-12-12 12 12 0 0 0-.045 0zm.375 6.467l4.416 16.553a12 12 0 0 0 5.137-4.213z"/></svg>
|
||||||
|
After Width: | Height: | Size: 300 B |
@@ -1,27 +1,5 @@
|
|||||||
import { setupPopovers } from './popover.js';
|
import { setupPopovers } from './popover.js';
|
||||||
|
import { throttledDebounce, isElementVisible } from './utils.js';
|
||||||
function throttledDebounce(callback, maxDebounceTimes, debounceDelay) {
|
|
||||||
let debounceTimeout;
|
|
||||||
let timesDebounced = 0;
|
|
||||||
|
|
||||||
return function () {
|
|
||||||
if (timesDebounced == maxDebounceTimes) {
|
|
||||||
clearTimeout(debounceTimeout);
|
|
||||||
timesDebounced = 0;
|
|
||||||
callback();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
clearTimeout(debounceTimeout);
|
|
||||||
timesDebounced++;
|
|
||||||
|
|
||||||
debounceTimeout = setTimeout(() => {
|
|
||||||
timesDebounced = 0;
|
|
||||||
callback();
|
|
||||||
}, debounceDelay);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
async function fetchPageContent(pageData) {
|
async function fetchPageContent(pageData) {
|
||||||
// TODO: handle non 200 status codes/time outs
|
// TODO: handle non 200 status codes/time outs
|
||||||
@@ -427,7 +405,7 @@ function setupCollapsibleGrids() {
|
|||||||
|
|
||||||
const button = attachExpandToggleButton(gridElement);
|
const button = attachExpandToggleButton(gridElement);
|
||||||
|
|
||||||
let cardsPerRow = 2;
|
let cardsPerRow;
|
||||||
|
|
||||||
const resolveCollapsibleItems = () => {
|
const resolveCollapsibleItems = () => {
|
||||||
const hideItemsAfterIndex = cardsPerRow * collapseAfterRows;
|
const hideItemsAfterIndex = cardsPerRow * collapseAfterRows;
|
||||||
@@ -457,12 +435,11 @@ function setupCollapsibleGrids() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
afterContentReady(() => {
|
const observer = new ResizeObserver(() => {
|
||||||
cardsPerRow = getCardsPerRow();
|
if (!isElementVisible(gridElement)) {
|
||||||
resolveCollapsibleItems();
|
return;
|
||||||
});
|
}
|
||||||
|
|
||||||
window.addEventListener("resize", () => {
|
|
||||||
const newCardsPerRow = getCardsPerRow();
|
const newCardsPerRow = getCardsPerRow();
|
||||||
|
|
||||||
if (cardsPerRow == newCardsPerRow) {
|
if (cardsPerRow == newCardsPerRow) {
|
||||||
@@ -472,6 +449,8 @@ function setupCollapsibleGrids() {
|
|||||||
cardsPerRow = newCardsPerRow;
|
cardsPerRow = newCardsPerRow;
|
||||||
resolveCollapsibleItems();
|
resolveCollapsibleItems();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
afterContentReady(() => observer.observe(gridElement));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
export function throttledDebounce(callback, maxDebounceTimes, debounceDelay) {
|
||||||
|
let debounceTimeout;
|
||||||
|
let timesDebounced = 0;
|
||||||
|
|
||||||
|
return function () {
|
||||||
|
if (timesDebounced == maxDebounceTimes) {
|
||||||
|
clearTimeout(debounceTimeout);
|
||||||
|
timesDebounced = 0;
|
||||||
|
callback();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearTimeout(debounceTimeout);
|
||||||
|
timesDebounced++;
|
||||||
|
|
||||||
|
debounceTimeout = setTimeout(() => {
|
||||||
|
timesDebounced = 0;
|
||||||
|
callback();
|
||||||
|
}, debounceDelay);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export function isElementVisible(element) {
|
||||||
|
return !!(element.offsetWidth || element.offsetHeight || element.getClientRects().length);
|
||||||
|
}
|
||||||
@@ -851,6 +851,7 @@ details[open] .summary::after {
|
|||||||
border-bottom: 2px solid transparent;
|
border-bottom: 2px solid transparent;
|
||||||
transition: color .3s, border-color .3s;
|
transition: color .3s, border-color .3s;
|
||||||
font-size: var(--font-size-h3);
|
font-size: var(--font-size-h3);
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-item:not(.nav-item-current):hover {
|
.nav-item:not(.nav-item-current):hover {
|
||||||
@@ -1049,6 +1050,7 @@ details[open] .summary::after {
|
|||||||
border-radius: var(--border-radius);
|
border-radius: var(--border-radius);
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bookmarks-icon {
|
.bookmarks-icon {
|
||||||
|
|||||||
+39
-18
@@ -31,10 +31,13 @@ func FetchAdguardStats(instanceURL, username, password string) (*DNSStats, error
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var topBlockedDomainsCount = min(len(responseJson.TopBlockedDomains), 5)
|
||||||
|
|
||||||
stats := &DNSStats{
|
stats := &DNSStats{
|
||||||
TotalQueries: responseJson.TotalQueries,
|
TotalQueries: responseJson.TotalQueries,
|
||||||
BlockedQueries: responseJson.BlockedQueries,
|
BlockedQueries: responseJson.BlockedQueries,
|
||||||
ResponseTime: int(responseJson.ResponseTime * 1000),
|
ResponseTime: int(responseJson.ResponseTime * 1000),
|
||||||
|
TopBlockedDomains: make([]DNSStatsBlockedDomain, 0, topBlockedDomainsCount),
|
||||||
}
|
}
|
||||||
|
|
||||||
if stats.TotalQueries <= 0 {
|
if stats.TotalQueries <= 0 {
|
||||||
@@ -43,8 +46,6 @@ func FetchAdguardStats(instanceURL, username, password string) (*DNSStats, error
|
|||||||
|
|
||||||
stats.BlockedPercent = int(float64(responseJson.BlockedQueries) / float64(responseJson.TotalQueries) * 100)
|
stats.BlockedPercent = int(float64(responseJson.BlockedQueries) / float64(responseJson.TotalQueries) * 100)
|
||||||
|
|
||||||
var topBlockedDomainsCount = min(len(responseJson.TopBlockedDomains), 5)
|
|
||||||
|
|
||||||
for i := 0; i < topBlockedDomainsCount; i++ {
|
for i := 0; i < topBlockedDomainsCount; i++ {
|
||||||
domain := responseJson.TopBlockedDomains[i]
|
domain := responseJson.TopBlockedDomains[i]
|
||||||
var firstDomain string
|
var firstDomain string
|
||||||
@@ -59,31 +60,51 @@ func FetchAdguardStats(instanceURL, username, password string) (*DNSStats, error
|
|||||||
}
|
}
|
||||||
|
|
||||||
stats.TopBlockedDomains = append(stats.TopBlockedDomains, DNSStatsBlockedDomain{
|
stats.TopBlockedDomains = append(stats.TopBlockedDomains, DNSStatsBlockedDomain{
|
||||||
Domain: firstDomain,
|
Domain: firstDomain,
|
||||||
PercentBlocked: int(float64(domain[firstDomain]) / float64(responseJson.BlockedQueries) * 100),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if stats.BlockedQueries > 0 {
|
||||||
|
stats.TopBlockedDomains[i].PercentBlocked = int(float64(domain[firstDomain]) / float64(responseJson.BlockedQueries) * 100)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adguard _should_ return data for the last 24 hours in a 1 hour interval
|
queriesSeries := responseJson.QueriesSeries
|
||||||
if len(responseJson.QueriesSeries) != 24 || len(responseJson.BlockedSeries) != 24 {
|
blockedSeries := responseJson.BlockedSeries
|
||||||
return stats, nil
|
|
||||||
|
const bars = 8
|
||||||
|
const hoursSpan = 24
|
||||||
|
const hoursPerBar int = hoursSpan / bars
|
||||||
|
|
||||||
|
if len(queriesSeries) > hoursSpan {
|
||||||
|
queriesSeries = queriesSeries[len(queriesSeries)-hoursSpan:]
|
||||||
|
} else if len(queriesSeries) < hoursSpan {
|
||||||
|
queriesSeries = append(make([]int, hoursSpan-len(queriesSeries)), queriesSeries...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(blockedSeries) > hoursSpan {
|
||||||
|
blockedSeries = blockedSeries[len(blockedSeries)-hoursSpan:]
|
||||||
|
} else if len(blockedSeries) < hoursSpan {
|
||||||
|
blockedSeries = append(make([]int, hoursSpan-len(blockedSeries)), blockedSeries...)
|
||||||
}
|
}
|
||||||
|
|
||||||
maxQueriesInSeries := 0
|
maxQueriesInSeries := 0
|
||||||
|
|
||||||
for i := 0; i < 8; i++ {
|
for i := 0; i < bars; i++ {
|
||||||
queries := 0
|
queries := 0
|
||||||
blocked := 0
|
blocked := 0
|
||||||
|
|
||||||
for j := 0; j < 3; j++ {
|
for j := 0; j < hoursPerBar; j++ {
|
||||||
queries += responseJson.QueriesSeries[i*3+j]
|
queries += queriesSeries[i*hoursPerBar+j]
|
||||||
blocked += responseJson.BlockedSeries[i*3+j]
|
blocked += blockedSeries[i*hoursPerBar+j]
|
||||||
}
|
}
|
||||||
|
|
||||||
stats.Series[i] = DNSStatsSeries{
|
stats.Series[i] = DNSStatsSeries{
|
||||||
Queries: queries,
|
Queries: queries,
|
||||||
Blocked: blocked,
|
Blocked: blocked,
|
||||||
PercentBlocked: int(float64(blocked) / float64(queries) * 100),
|
}
|
||||||
|
|
||||||
|
if queries > 0 {
|
||||||
|
stats.Series[i].PercentBlocked = int(float64(blocked) / float64(queries) * 100)
|
||||||
}
|
}
|
||||||
|
|
||||||
if queries > maxQueriesInSeries {
|
if queries > maxQueriesInSeries {
|
||||||
@@ -91,7 +112,7 @@ func FetchAdguardStats(instanceURL, username, password string) (*DNSStats, error
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := 0; i < 8; i++ {
|
for i := 0; i < bars; i++ {
|
||||||
stats.Series[i].PercentTotal = int(float64(stats.Series[i].Queries) / float64(maxQueriesInSeries) * 100)
|
stats.Series[i].PercentTotal = int(float64(stats.Series[i].Queries) / float64(maxQueriesInSeries) * 100)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package feed
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type codebergReleaseResponseJson struct {
|
||||||
|
TagName string `json:"tag_name"`
|
||||||
|
PublishedAt string `json:"published_at"`
|
||||||
|
HtmlUrl string `json:"html_url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchLatestCodebergRelease(request *ReleaseRequest) (*AppRelease, error) {
|
||||||
|
httpRequest, err := http.NewRequest(
|
||||||
|
"GET",
|
||||||
|
fmt.Sprintf(
|
||||||
|
"https://codeberg.org/api/v1/repos/%s/releases/latest",
|
||||||
|
request.Repository,
|
||||||
|
),
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := decodeJsonFromRequest[codebergReleaseResponseJson](defaultClient, httpRequest)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &AppRelease{
|
||||||
|
Source: ReleaseSourceCodeberg,
|
||||||
|
Name: request.Repository,
|
||||||
|
Version: normalizeVersionFormat(response.TagName),
|
||||||
|
NotesUrl: response.HtmlUrl,
|
||||||
|
TimeReleased: parseRFC3339Time(response.PublishedAt),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
+34
-7
@@ -1,20 +1,42 @@
|
|||||||
package feed
|
package feed
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type piholeStatsResponse struct {
|
type piholeStatsResponse struct {
|
||||||
TotalQueries int `json:"dns_queries_today"`
|
TotalQueries int `json:"dns_queries_today"`
|
||||||
QueriesSeries map[int64]int `json:"domains_over_time"`
|
QueriesSeries map[int64]int `json:"domains_over_time"`
|
||||||
BlockedQueries int `json:"ads_blocked_today"`
|
BlockedQueries int `json:"ads_blocked_today"`
|
||||||
BlockedSeries map[int64]int `json:"ads_over_time"`
|
BlockedSeries map[int64]int `json:"ads_over_time"`
|
||||||
BlockedPercentage float64 `json:"ads_percentage_today"`
|
BlockedPercentage float64 `json:"ads_percentage_today"`
|
||||||
TopBlockedDomains map[string]int `json:"top_ads"`
|
TopBlockedDomains piholeTopBlockedDomains `json:"top_ads"`
|
||||||
DomainsBlocked int `json:"domains_being_blocked"`
|
DomainsBlocked int `json:"domains_being_blocked"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// If user has some level of privacy enabled on Pihole, `json:"top_ads"` is an empty array
|
||||||
|
// Use custom unmarshal behavior to avoid not getting the rest of the valid data when unmarshalling
|
||||||
|
type piholeTopBlockedDomains map[string]int
|
||||||
|
|
||||||
|
func (p *piholeTopBlockedDomains) UnmarshalJSON(data []byte) error {
|
||||||
|
// NOTE: do not change to piholeTopBlockedDomains type here or it will cause a stack overflow
|
||||||
|
// because of the UnmarshalJSON method getting called recursively
|
||||||
|
temp := make(map[string]int)
|
||||||
|
|
||||||
|
err := json.Unmarshal(data, &temp)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
*p = make(piholeTopBlockedDomains)
|
||||||
|
} else {
|
||||||
|
*p = temp
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func FetchPiholeStats(instanceURL, token string) (*DNSStats, error) {
|
func FetchPiholeStats(instanceURL, token string) (*DNSStats, error) {
|
||||||
@@ -63,6 +85,11 @@ func FetchPiholeStats(instanceURL, token string) (*DNSStats, error) {
|
|||||||
|
|
||||||
// Pihole _should_ return data for the last 24 hours in a 10 minute interval, 6*24 = 144
|
// Pihole _should_ return data for the last 24 hours in a 10 minute interval, 6*24 = 144
|
||||||
if len(responseJson.QueriesSeries) != 144 || len(responseJson.BlockedSeries) != 144 {
|
if len(responseJson.QueriesSeries) != 144 || len(responseJson.BlockedSeries) != 144 {
|
||||||
|
slog.Warn(
|
||||||
|
"DNS stats for pihole: did not get expected 144 data points",
|
||||||
|
"len(queries)", len(responseJson.QueriesSeries),
|
||||||
|
"len(blocked)", len(responseJson.BlockedSeries),
|
||||||
|
)
|
||||||
return stats, nil
|
return stats, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
type ReleaseSource string
|
type ReleaseSource string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
ReleaseSourceCodeberg ReleaseSource = "codeberg"
|
||||||
ReleaseSourceGithub ReleaseSource = "github"
|
ReleaseSourceGithub ReleaseSource = "github"
|
||||||
ReleaseSourceGitlab ReleaseSource = "gitlab"
|
ReleaseSourceGitlab ReleaseSource = "gitlab"
|
||||||
ReleaseSourceDockerHub ReleaseSource = "dockerhub"
|
ReleaseSourceDockerHub ReleaseSource = "dockerhub"
|
||||||
@@ -57,6 +58,8 @@ func FetchLatestReleases(requests []*ReleaseRequest) (AppReleases, error) {
|
|||||||
|
|
||||||
func fetchLatestReleaseTask(request *ReleaseRequest) (*AppRelease, error) {
|
func fetchLatestReleaseTask(request *ReleaseRequest) (*AppRelease, error) {
|
||||||
switch request.Source {
|
switch request.Source {
|
||||||
|
case ReleaseSourceCodeberg:
|
||||||
|
return fetchLatestCodebergRelease(request)
|
||||||
case ReleaseSourceGithub:
|
case ReleaseSourceGithub:
|
||||||
return fetchLatestGithubRelease(request)
|
return fetchLatestGithubRelease(request)
|
||||||
case ReleaseSourceGitlab:
|
case ReleaseSourceGitlab:
|
||||||
|
|||||||
@@ -275,6 +275,9 @@ func (a *Application) Serve() error {
|
|||||||
|
|
||||||
mux.HandleFunc("GET /api/pages/{page}/content/{$}", a.HandlePageContentRequest)
|
mux.HandleFunc("GET /api/pages/{page}/content/{$}", a.HandlePageContentRequest)
|
||||||
mux.HandleFunc("/api/widgets/{widget}/{path...}", a.HandleWidgetRequest)
|
mux.HandleFunc("/api/widgets/{widget}/{path...}", a.HandleWidgetRequest)
|
||||||
|
mux.HandleFunc("GET /api/healthz", func(w http.ResponseWriter, _ *http.Request) {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
})
|
||||||
|
|
||||||
mux.Handle(
|
mux.Handle(
|
||||||
fmt.Sprintf("GET /static/%s/{path...}", a.Config.Server.AssetsHash),
|
fmt.Sprintf("GET /static/%s/{path...}", a.Config.Server.AssetsHash),
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ func (widget *Releases) Initialize() error {
|
|||||||
for _, repository := range widget.Repositories {
|
for _, repository := range widget.Repositories {
|
||||||
parts := strings.SplitN(repository, ":", 2)
|
parts := strings.SplitN(repository, ":", 2)
|
||||||
var request *feed.ReleaseRequest
|
var request *feed.ReleaseRequest
|
||||||
|
|
||||||
if len(parts) == 1 {
|
if len(parts) == 1 {
|
||||||
request = &feed.ReleaseRequest{
|
request = &feed.ReleaseRequest{
|
||||||
Source: feed.ReleaseSourceGithub,
|
Source: feed.ReleaseSourceGithub,
|
||||||
@@ -65,6 +64,11 @@ func (widget *Releases) Initialize() error {
|
|||||||
Source: feed.ReleaseSourceDockerHub,
|
Source: feed.ReleaseSourceDockerHub,
|
||||||
Repository: parts[1],
|
Repository: parts[1],
|
||||||
}
|
}
|
||||||
|
} else if parts[0] == string(feed.ReleaseSourceCodeberg) {
|
||||||
|
request = &feed.ReleaseRequest{
|
||||||
|
Source: feed.ReleaseSourceCodeberg,
|
||||||
|
Repository: parts[1],
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return errors.New("invalid repository source " + parts[0])
|
return errors.New("invalid repository source " + parts[0])
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user