Compare commits

..
8 Commits
Author SHA1 Message Date
Svilen Markov db1ed9e257 Fix & refactor adguard stats
Create release / release (push) Has been cancelled
2024-09-10 05:38:14 +01:00
Svilen Markov adef35049f Add warning when pihole doesn't return expected data points 2024-09-10 05:38:14 +01:00
Svilen Markov bd9abf2e52 Fix mobile nav links wrapping 2024-09-10 05:38:14 +01:00
Svilen Markov aa74c00fd4 Merge pull request #210 from micash545/main
releases: Add support for Codeberg
2024-09-08 19:36:20 +01:00
Svilen Markov a68a907fa7 Update SVG 2024-09-08 19:35:01 +01:00
Svilen Markov 1a05d6d03a Don't export function 2024-09-08 19:34:43 +01:00
micash 60f4183057 add codeberg releases 2024-09-08 16:59:24 +02:00
Svilen Markov 04c3afc850 Document center-vertically property 2024-09-08 06:25:06 +01:00
8 changed files with 102 additions and 22 deletions
+8 -3
View File
@@ -307,6 +307,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 +329,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 +1114,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 +1125,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 +1146,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 +1174,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
View File
@@ -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 {
+39 -18
View File
@@ -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)
} }
+39
View File
@@ -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
}
+6
View File
@@ -2,6 +2,7 @@ package feed
import ( import (
"errors" "errors"
"log/slog"
"net/http" "net/http"
"sort" "sort"
"strings" "strings"
@@ -63,6 +64,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
} }
+3
View File
@@ -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:
+5 -1
View File
@@ -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])
} }