Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9a36187333 | ||
|
|
b0c4d70628 | ||
|
|
d5fa6424c1 | ||
|
|
07fe5b3cb1 | ||
|
|
672547cd07 | ||
|
|
28167403a4 | ||
|
|
eaf08d42dd | ||
|
|
bae95a5e07 |
@@ -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.
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -1050,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 {
|
||||||
|
|||||||
+28
-7
@@ -1,6 +1,7 @@
|
|||||||
package feed
|
package feed
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -9,13 +10,33 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
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) {
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
Reference in New Issue
Block a user