mirror of
https://github.com/gethomepage/homepage.git
synced 2025-12-24 22:16:39 +08:00
Compare commits
6 Commits
feature/se
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
be7a00d631 | ||
|
|
0d99a8766f | ||
|
|
e66b58dc53 | ||
|
|
1b32cbbbfd | ||
|
|
681a8a828b | ||
|
|
f8009a7067 |
2
.github/workflows/docker-publish.yml
vendored
2
.github/workflows/docker-publish.yml
vendored
@@ -84,7 +84,7 @@ jobs:
|
|||||||
latest=auto
|
latest=auto
|
||||||
|
|
||||||
- name: Next.js build cache
|
- name: Next.js build cache
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v5
|
||||||
with:
|
with:
|
||||||
path: .next/cache
|
path: .next/cache
|
||||||
key: nextjs-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}-${{ hashFiles('**/*.js', '**/*.jsx') }}
|
key: nextjs-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}-${{ hashFiles('**/*.js', '**/*.jsx') }}
|
||||||
|
|||||||
4
.github/workflows/docs-publish.yml
vendored
4
.github/workflows/docs-publish.yml
vendored
@@ -37,7 +37,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
python-version: 3.x
|
python-version: 3.x
|
||||||
- run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV
|
- run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV
|
||||||
- uses: actions/cache@v4
|
- uses: actions/cache@v5
|
||||||
with:
|
with:
|
||||||
key: mkdocs-material-${{ env.cache_id }}
|
key: mkdocs-material-${{ env.cache_id }}
|
||||||
path: .cache
|
path: .cache
|
||||||
@@ -63,7 +63,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
python-version: 3.x
|
python-version: 3.x
|
||||||
- run: echo "cache_id=${{github.sha}}" >> $GITHUB_ENV
|
- run: echo "cache_id=${{github.sha}}" >> $GITHUB_ENV
|
||||||
- uses: actions/cache@v4
|
- uses: actions/cache@v5
|
||||||
with:
|
with:
|
||||||
key: mkdocs-material-${{ env.cache_id }}
|
key: mkdocs-material-${{ env.cache_id }}
|
||||||
path: .cache
|
path: .cache
|
||||||
|
|||||||
2
.github/workflows/repo-maintenance.yml
vendored
2
.github/workflows/repo-maintenance.yml
vendored
@@ -32,7 +32,7 @@ jobs:
|
|||||||
name: 'Lock Old Threads'
|
name: 'Lock Old Threads'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: dessant/lock-threads@v5
|
- uses: dessant/lock-threads@v6
|
||||||
with:
|
with:
|
||||||
issue-inactive-days: '30'
|
issue-inactive-days: '30'
|
||||||
pr-inactive-days: '30'
|
pr-inactive-days: '30'
|
||||||
|
|||||||
@@ -189,6 +189,8 @@ labels: ...
|
|||||||
- homepage.widgets[1].slug=youreventslughere
|
- homepage.widgets[1].slug=youreventslughere
|
||||||
```
|
```
|
||||||
|
|
||||||
|
To pass custom HTTP headers with a widget request when using labels, use the same dot-notation: `homepage.widget.headers.X-Auth-Key=secret` (or `homepage.widgets[0].headers.X-Auth-Key=secret` when multiple widgets are present).
|
||||||
|
|
||||||
You can add specify fields for e.g. the [CustomAPI](../widgets/services/customapi.md) widget by using array-style dot notation:
|
You can add specify fields for e.g. the [CustomAPI](../widgets/services/customapi.md) widget by using array-style dot notation:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
|
|||||||
@@ -94,6 +94,7 @@ metadata:
|
|||||||
gethomepage.dev/name: Emby
|
gethomepage.dev/name: Emby
|
||||||
gethomepage.dev/widget.type: "emby"
|
gethomepage.dev/widget.type: "emby"
|
||||||
gethomepage.dev/widget.url: "https://emby.example.com"
|
gethomepage.dev/widget.url: "https://emby.example.com"
|
||||||
|
gethomepage.dev/widget.headers.X-Auth-Key: "your-secret-here"
|
||||||
gethomepage.dev/pod-selector: ""
|
gethomepage.dev/pod-selector: ""
|
||||||
gethomepage.dev/weight: 10 # optional
|
gethomepage.dev/weight: 10 # optional
|
||||||
gethomepage.dev/instance: "public" # optional
|
gethomepage.dev/instance: "public" # optional
|
||||||
|
|||||||
@@ -101,6 +101,25 @@ Each service can have multiple widgets attached to it, for example:
|
|||||||
|
|
||||||
Multiple widgets per service are not yet supported with Kubernetes ingress annotations.
|
Multiple widgets per service are not yet supported with Kubernetes ingress annotations.
|
||||||
|
|
||||||
|
#### Custom HTTP headers
|
||||||
|
|
||||||
|
Widgets that make HTTP calls support extra request headers via `headers`. This is useful when a reverse proxy expects a secret header.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- UptimeRobot:
|
||||||
|
icon: uptimekuma.png
|
||||||
|
href: https://uptimerobot.com/
|
||||||
|
widget:
|
||||||
|
type: uptimerobot
|
||||||
|
url: https://api.uptimerobot.com
|
||||||
|
key: ${UPTIMEROBOT_API_KEY}
|
||||||
|
headers:
|
||||||
|
User-Agent: homepage
|
||||||
|
X-Auth-Key: your-secret-here
|
||||||
|
```
|
||||||
|
|
||||||
|
If you define services via Docker labels or Kubernetes annotations, use the same key with dot-notation (for example `homepage.widget.headers.X-Auth-Key=secret` or `gethomepage.dev/widget.headers.X-Auth-Key: "secret"`).
|
||||||
|
|
||||||
#### Field Visibility
|
#### Field Visibility
|
||||||
|
|
||||||
Each widget can optionally provide a list of which fields should be visible via the `fields` widget property. If no fields are specified, then all fields will be displayed. The `fields` property must be a valid YAML array of strings. As an example, here is the entry for Sonarr showing only a couple of fields.
|
Each widget can optionally provide a list of which fields should be visible via the `fields` widget property. If no fields are specified, then all fields will be displayed. The `fields` property must be a valid YAML array of strings. As an example, here is the entry for Sonarr showing only a couple of fields.
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
"luxon": "^3.6.1",
|
"luxon": "^3.6.1",
|
||||||
"memory-cache": "^0.2.0",
|
"memory-cache": "^0.2.0",
|
||||||
"minecraftstatuspinger": "^1.2.2",
|
"minecraftstatuspinger": "^1.2.2",
|
||||||
"next": "^15.5.7",
|
"next": "^15.5.9",
|
||||||
"next-i18next": "^12.1.0",
|
"next-i18next": "^12.1.0",
|
||||||
"ping": "^0.4.4",
|
"ping": "^0.4.4",
|
||||||
"pretty-bytes": "^7.1.0",
|
"pretty-bytes": "^7.1.0",
|
||||||
|
|||||||
32
pnpm-lock.yaml
generated
32
pnpm-lock.yaml
generated
@@ -51,11 +51,11 @@ importers:
|
|||||||
specifier: ^1.2.2
|
specifier: ^1.2.2
|
||||||
version: 1.2.2
|
version: 1.2.2
|
||||||
next:
|
next:
|
||||||
specifier: ^15.5.7
|
specifier: ^15.5.9
|
||||||
version: 15.5.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
version: 15.5.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
next-i18next:
|
next-i18next:
|
||||||
specifier: ^12.1.0
|
specifier: ^12.1.0
|
||||||
version: 12.1.0(next@15.5.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
version: 12.1.0(next@15.5.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
ping:
|
ping:
|
||||||
specifier: ^0.4.4
|
specifier: ^0.4.4
|
||||||
version: 0.4.4
|
version: 0.4.4
|
||||||
@@ -466,8 +466,8 @@ packages:
|
|||||||
'@napi-rs/wasm-runtime@0.2.8':
|
'@napi-rs/wasm-runtime@0.2.8':
|
||||||
resolution: {integrity: sha512-OBlgKdX7gin7OIq4fadsjpg+cp2ZphvAIKucHsNfTdJiqdOmOEwQd/bHi0VwNrcw5xpBJyUw6cK/QilCqy1BSg==}
|
resolution: {integrity: sha512-OBlgKdX7gin7OIq4fadsjpg+cp2ZphvAIKucHsNfTdJiqdOmOEwQd/bHi0VwNrcw5xpBJyUw6cK/QilCqy1BSg==}
|
||||||
|
|
||||||
'@next/env@15.5.7':
|
'@next/env@15.5.9':
|
||||||
resolution: {integrity: sha512-4h6Y2NyEkIEN7Z8YxkA27pq6zTkS09bUSYC0xjd0NpwFxjnIKeZEeH591o5WECSmjpUhLn3H2QLJcDye3Uzcvg==}
|
resolution: {integrity: sha512-4GlTZ+EJM7WaW2HEZcyU317tIQDjkQIyENDLxYJfSWlfqguN+dHkZgyQTV/7ykvobU7yEH5gKvreNrH4B6QgIg==}
|
||||||
|
|
||||||
'@next/eslint-plugin-next@15.2.4':
|
'@next/eslint-plugin-next@15.2.4':
|
||||||
resolution: {integrity: sha512-O8ScvKtnxkp8kL9TpJTTKnMqlkZnS+QxwoQnJwPGBxjBbzd6OVVPEJ5/pMNrktSyXQD/chEfzfFzYLM6JANOOQ==}
|
resolution: {integrity: sha512-O8ScvKtnxkp8kL9TpJTTKnMqlkZnS+QxwoQnJwPGBxjBbzd6OVVPEJ5/pMNrktSyXQD/chEfzfFzYLM6JANOOQ==}
|
||||||
@@ -1120,8 +1120,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
|
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
caniuse-lite@1.0.30001759:
|
caniuse-lite@1.0.30001760:
|
||||||
resolution: {integrity: sha512-Pzfx9fOKoKvevQf8oCXoyNRQ5QyxJj+3O0Rqx2V5oxT61KGx8+n6hV/IUyJeifUci2clnmmKVpvtiqRzgiWjSw==}
|
resolution: {integrity: sha512-7AAMPcueWELt1p3mi13HR/LHH0TJLT11cnwDJEs3xA4+CK/PLKeO9Kl1oru24htkyUKtkGCvAx4ohB0Ttry8Dw==}
|
||||||
|
|
||||||
chalk@4.1.2:
|
chalk@4.1.2:
|
||||||
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
|
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
|
||||||
@@ -2212,8 +2212,8 @@ packages:
|
|||||||
next: '>= 10.0.0'
|
next: '>= 10.0.0'
|
||||||
react: '>= 16.8.0'
|
react: '>= 16.8.0'
|
||||||
|
|
||||||
next@15.5.7:
|
next@15.5.9:
|
||||||
resolution: {integrity: sha512-+t2/0jIJ48kUpGKkdlhgkv+zPTEOoXyr60qXe68eB/pl3CMJaLeIGjzp5D6Oqt25hCBiBTt8wEeeAzfJvUKnPQ==}
|
resolution: {integrity: sha512-agNLK89seZEtC5zUHwtut0+tNrc0Xw4FT/Dg+B/VLEo9pAcS9rtTKpek3V6kVcVwsB2YlqMaHdfZL4eLEVYuCg==}
|
||||||
engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0}
|
engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -3374,7 +3374,7 @@ snapshots:
|
|||||||
'@tybys/wasm-util': 0.9.0
|
'@tybys/wasm-util': 0.9.0
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@next/env@15.5.7': {}
|
'@next/env@15.5.9': {}
|
||||||
|
|
||||||
'@next/eslint-plugin-next@15.2.4':
|
'@next/eslint-plugin-next@15.2.4':
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -4024,7 +4024,7 @@ snapshots:
|
|||||||
|
|
||||||
callsites@3.1.0: {}
|
callsites@3.1.0: {}
|
||||||
|
|
||||||
caniuse-lite@1.0.30001759: {}
|
caniuse-lite@1.0.30001760: {}
|
||||||
|
|
||||||
chalk@4.1.2:
|
chalk@4.1.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -5259,7 +5259,7 @@ snapshots:
|
|||||||
|
|
||||||
net@1.0.2: {}
|
net@1.0.2: {}
|
||||||
|
|
||||||
next-i18next@12.1.0(next@15.5.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
next-i18next@12.1.0(next@15.5.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.26.9
|
'@babel/runtime': 7.26.9
|
||||||
'@types/hoist-non-react-statics': 3.3.6
|
'@types/hoist-non-react-statics': 3.3.6
|
||||||
@@ -5267,18 +5267,18 @@ snapshots:
|
|||||||
hoist-non-react-statics: 3.3.2
|
hoist-non-react-statics: 3.3.2
|
||||||
i18next: 21.10.0
|
i18next: 21.10.0
|
||||||
i18next-fs-backend: 1.2.0
|
i18next-fs-backend: 1.2.0
|
||||||
next: 15.5.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
next: 15.5.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
react-i18next: 11.18.6(i18next@21.10.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
react-i18next: 11.18.6(i18next@21.10.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- react-dom
|
- react-dom
|
||||||
- react-native
|
- react-native
|
||||||
|
|
||||||
next@15.5.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
next@15.5.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@next/env': 15.5.7
|
'@next/env': 15.5.9
|
||||||
'@swc/helpers': 0.5.15
|
'@swc/helpers': 0.5.15
|
||||||
caniuse-lite: 1.0.30001759
|
caniuse-lite: 1.0.30001760
|
||||||
postcss: 8.4.31
|
postcss: 8.4.31
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
react-dom: 18.3.1(react@18.3.1)
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ export default async function handler(req, res) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (type === "network") {
|
if (type === "network") {
|
||||||
let networkData = await si.networkStats();
|
let networkData = await si.networkStats("*");
|
||||||
let interfaceDefault;
|
let interfaceDefault;
|
||||||
logger.debug("networkData:", JSON.stringify(networkData));
|
logger.debug("networkData:", JSON.stringify(networkData));
|
||||||
if (interfaceName && interfaceName !== "default") {
|
if (interfaceName && interfaceName !== "default") {
|
||||||
|
|||||||
@@ -27,6 +27,9 @@ export default async function credentialedProxyHandler(req, res, map) {
|
|||||||
|
|
||||||
const headers = {
|
const headers = {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
|
...(widgets[widget.type].headers ?? {}),
|
||||||
|
...(widget.headers ?? {}),
|
||||||
|
...(req.extraHeaders ?? {}),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (widget.type === "stocks") {
|
if (widget.type === "stocks") {
|
||||||
|
|||||||
@@ -25,7 +25,11 @@ export default async function genericProxyHandler(req, res, map) {
|
|||||||
}
|
}
|
||||||
const url = new URL(urlString);
|
const url = new URL(urlString);
|
||||||
|
|
||||||
const headers = req.extraHeaders ?? widget.headers ?? widgets[widget.type].headers ?? {};
|
const headers = {
|
||||||
|
...(widgets[widget.type].headers ?? {}),
|
||||||
|
...(widget.headers ?? {}),
|
||||||
|
...(req.extraHeaders ?? {}),
|
||||||
|
};
|
||||||
|
|
||||||
if (widget.username && widget.password) {
|
if (widget.username && widget.password) {
|
||||||
headers.Authorization = `Basic ${Buffer.from(`${widget.username}:${widget.password}`).toString("base64")}`;
|
headers.Authorization = `Basic ${Buffer.from(`${widget.username}:${widget.password}`).toString("base64")}`;
|
||||||
|
|||||||
@@ -49,10 +49,12 @@ export default function Component({ service }) {
|
|||||||
|
|
||||||
// single monitor
|
// single monitor
|
||||||
const monitor = uptimerobotData.monitors[0];
|
const monitor = uptimerobotData.monitors[0];
|
||||||
|
const logs = Array.isArray(monitor.logs) ? monitor.logs : [];
|
||||||
|
const lastUpLog = logs.find((log) => log.type === 2);
|
||||||
|
const lastDownLog = logs.find((log) => log.type === 1);
|
||||||
|
|
||||||
let status;
|
let status;
|
||||||
let uptime = 0;
|
let uptime = 0;
|
||||||
let logIndex = 0;
|
|
||||||
const hasLogs = Array.isArray(monitor.logs) && monitor.logs.length > 0;
|
|
||||||
|
|
||||||
switch (monitor.status) {
|
switch (monitor.status) {
|
||||||
case 0:
|
case 0:
|
||||||
@@ -63,8 +65,7 @@ export default function Component({ service }) {
|
|||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
status = t("uptimerobot.up");
|
status = t("uptimerobot.up");
|
||||||
uptime = t("common.duration", { value: hasLogs ? monitor.logs[0].duration : 0 });
|
uptime = t("common.duration", { value: lastUpLog?.duration ?? 0 });
|
||||||
logIndex = 1;
|
|
||||||
break;
|
break;
|
||||||
case 8:
|
case 8:
|
||||||
status = t("uptimerobot.seemsdown");
|
status = t("uptimerobot.seemsdown");
|
||||||
@@ -77,14 +78,14 @@ export default function Component({ service }) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
const lastDown = hasLogs ? new Date(monitor.logs[logIndex].datetime * 1000).toLocaleString() : "";
|
const lastDown = lastDownLog ? new Date(lastDownLog.datetime * 1000).toLocaleString() : "";
|
||||||
const downDuration = t("common.duration", { value: hasLogs ? monitor.logs[logIndex].duration : 0 });
|
const downDuration = t("common.duration", { value: lastDownLog?.duration ?? 0 });
|
||||||
const hideDown = !hasLogs || (logIndex === 1 && monitor.logs[logIndex].type !== 1);
|
const hideDown = !lastDownLog;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container service={service}>
|
<Container service={service}>
|
||||||
<Block label="uptimerobot.status" value={status} />
|
<Block label="uptimerobot.status" value={status} />
|
||||||
{hasLogs && <Block label="uptimerobot.uptime" value={uptime} />}
|
<Block label="uptimerobot.uptime" value={uptime} />
|
||||||
{!hideDown && <Block label="uptimerobot.lastDown" value={lastDown} />}
|
{!hideDown && <Block label="uptimerobot.lastDown" value={lastDown} />}
|
||||||
{!hideDown && <Block label="uptimerobot.downDuration" value={downDuration} />}
|
{!hideDown && <Block label="uptimerobot.downDuration" value={downDuration} />}
|
||||||
</Container>
|
</Container>
|
||||||
|
|||||||
Reference in New Issue
Block a user