mirror of
https://github.com/gethomepage/homepage.git
synced 2025-12-24 05:48:08 +08:00
Feature: Pangolin service widget (#6065)
Some checks failed
Crowdin Action / Crowdin Sync (push) Has been cancelled
Docker CI / Linting Checks (push) Has been cancelled
Docker CI / Docker Build & Push (push) Has been cancelled
Repository Maintenance / Stale (push) Has been cancelled
Repository Maintenance / Lock Old Threads (push) Has been cancelled
Repository Maintenance / Close Answered Discussions (push) Has been cancelled
Repository Maintenance / Close Outdated Discussions (push) Has been cancelled
Repository Maintenance / Close Unsupported Feature Requests (push) Has been cancelled
Some checks failed
Crowdin Action / Crowdin Sync (push) Has been cancelled
Docker CI / Linting Checks (push) Has been cancelled
Docker CI / Docker Build & Push (push) Has been cancelled
Repository Maintenance / Stale (push) Has been cancelled
Repository Maintenance / Lock Old Threads (push) Has been cancelled
Repository Maintenance / Close Answered Discussions (push) Has been cancelled
Repository Maintenance / Close Outdated Discussions (push) Has been cancelled
Repository Maintenance / Close Unsupported Feature Requests (push) Has been cancelled
Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
This commit is contained in:
29
docs/widgets/services/pangolin.md
Normal file
29
docs/widgets/services/pangolin.md
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
---
|
||||||
|
title: Pangolin
|
||||||
|
description: Pangolin Widget Configuration
|
||||||
|
---
|
||||||
|
|
||||||
|
Learn more about [Pangolin](https://github.com/fosrl/pangolin).
|
||||||
|
|
||||||
|
This widget shows sites (online/total), resources (healthy/total), targets (healthy/total), and traffic statistics for a Pangolin organization. A resource is considered healthy if at least one of its targets is healthy, or if it has no targets.
|
||||||
|
|
||||||
|
Allowed fields: `["sites", "resources", "targets", "traffic", "in", "out"]` (maximum of 4).
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
widget:
|
||||||
|
type: pangolin
|
||||||
|
url: https://api.pangolin.net
|
||||||
|
key: your-api-key
|
||||||
|
org: your-org-id
|
||||||
|
```
|
||||||
|
|
||||||
|
Find your organization ID in the URL when logged in (e.g., `https://app.pangolin.net/{org-id}/...`).
|
||||||
|
|
||||||
|
## API Key Setup
|
||||||
|
|
||||||
|
Create an API key with the following permissions:
|
||||||
|
|
||||||
|
- **List Sites**
|
||||||
|
- **List Resources**
|
||||||
|
|
||||||
|
**Self-Hosted:** Enable the [Integration API](https://docs.pangolin.net/self-host/advanced/integration-api) in your Pangolin configuration before creating the key.
|
||||||
@@ -122,6 +122,7 @@ nav:
|
|||||||
- widgets/services/opnsense.md
|
- widgets/services/opnsense.md
|
||||||
- widgets/services/openwrt.md
|
- widgets/services/openwrt.md
|
||||||
- widgets/services/overseerr.md
|
- widgets/services/overseerr.md
|
||||||
|
- widgets/services/pangolin.md
|
||||||
- widgets/services/paperlessngx.md
|
- widgets/services/paperlessngx.md
|
||||||
- widgets/services/peanut.md
|
- widgets/services/peanut.md
|
||||||
- widgets/services/pfsense.md
|
- widgets/services/pfsense.md
|
||||||
|
|||||||
@@ -599,6 +599,15 @@
|
|||||||
"inbox": "Inbox",
|
"inbox": "Inbox",
|
||||||
"total": "Total"
|
"total": "Total"
|
||||||
},
|
},
|
||||||
|
"pangolin": {
|
||||||
|
"orgs": "Orgs",
|
||||||
|
"sites": "Sites",
|
||||||
|
"resources": "Resources",
|
||||||
|
"targets": "Targets",
|
||||||
|
"traffic": "Traffic",
|
||||||
|
"in": "In",
|
||||||
|
"out": "Out"
|
||||||
|
},
|
||||||
"peanut": {
|
"peanut": {
|
||||||
"battery_charge": "Battery Charge",
|
"battery_charge": "Battery Charge",
|
||||||
"ups_load": "UPS Load",
|
"ups_load": "UPS Load",
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ export default async function credentialedProxyHandler(req, res, map) {
|
|||||||
"linkwarden",
|
"linkwarden",
|
||||||
"mealie",
|
"mealie",
|
||||||
"netalertx",
|
"netalertx",
|
||||||
|
"pangolin",
|
||||||
"tailscale",
|
"tailscale",
|
||||||
"tandoor",
|
"tandoor",
|
||||||
"pterodactyl",
|
"pterodactyl",
|
||||||
|
|||||||
@@ -97,6 +97,7 @@ const components = {
|
|||||||
openmediavault: dynamic(() => import("./openmediavault/component")),
|
openmediavault: dynamic(() => import("./openmediavault/component")),
|
||||||
openwrt: dynamic(() => import("./openwrt/component")),
|
openwrt: dynamic(() => import("./openwrt/component")),
|
||||||
paperlessngx: dynamic(() => import("./paperlessngx/component")),
|
paperlessngx: dynamic(() => import("./paperlessngx/component")),
|
||||||
|
pangolin: dynamic(() => import("./pangolin/component")),
|
||||||
pfsense: dynamic(() => import("./pfsense/component")),
|
pfsense: dynamic(() => import("./pfsense/component")),
|
||||||
photoprism: dynamic(() => import("./photoprism/component")),
|
photoprism: dynamic(() => import("./photoprism/component")),
|
||||||
proxmoxbackupserver: dynamic(() => import("./proxmoxbackupserver/component")),
|
proxmoxbackupserver: dynamic(() => import("./proxmoxbackupserver/component")),
|
||||||
|
|||||||
70
src/widgets/pangolin/component.jsx
Normal file
70
src/widgets/pangolin/component.jsx
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
import Block from "components/services/widget/block";
|
||||||
|
import Container from "components/services/widget/container";
|
||||||
|
import { useTranslation } from "next-i18next";
|
||||||
|
|
||||||
|
import useWidgetAPI from "utils/proxy/use-widget-api";
|
||||||
|
|
||||||
|
const MAX_ALLOWED_FIELDS = 4;
|
||||||
|
|
||||||
|
export default function Component({ service }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { widget } = service;
|
||||||
|
|
||||||
|
if (!widget.fields) {
|
||||||
|
widget.fields = ["sites", "resources", "targets", "traffic"];
|
||||||
|
} else if (widget.fields?.length > MAX_ALLOWED_FIELDS) {
|
||||||
|
widget.fields = widget.fields.slice(0, MAX_ALLOWED_FIELDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data: sitesData, error: sitesError } = useWidgetAPI(widget, "sites");
|
||||||
|
const { data: resourcesData, error: resourcesError } = useWidgetAPI(widget, "resources");
|
||||||
|
|
||||||
|
if (sitesError || resourcesError) {
|
||||||
|
return <Container service={service} error={sitesError || resourcesError} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sitesData || !resourcesData) {
|
||||||
|
return (
|
||||||
|
<Container service={service}>
|
||||||
|
<Block label="pangolin.sites" />
|
||||||
|
<Block label="pangolin.resources" />
|
||||||
|
<Block label="pangolin.targets" />
|
||||||
|
<Block label="pangolin.traffic" />
|
||||||
|
<Block label="pangolin.in" />
|
||||||
|
<Block label="pangolin.out" />
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const sites = sitesData.data?.sites || [];
|
||||||
|
const resources = resourcesData.data?.resources || [];
|
||||||
|
|
||||||
|
const sitesTotal = sites.length;
|
||||||
|
const sitesOnline = sites.filter((s) => s.online).length;
|
||||||
|
|
||||||
|
const resourcesTotal = resources.length;
|
||||||
|
const resourcesHealthy = resources.filter(
|
||||||
|
(r) => r.targets?.some((t) => t.healthStatus !== "unhealthy") || !r.targets?.length,
|
||||||
|
).length;
|
||||||
|
|
||||||
|
const targetsTotal = resources.reduce((sum, r) => sum + (r.targets?.length || 0), 0);
|
||||||
|
const targetsHealthy = resources.reduce(
|
||||||
|
(sum, r) => sum + (r.targets?.filter((t) => t.healthStatus !== "unhealthy").length || 0),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
|
||||||
|
const trafficIn = sites.reduce((sum, s) => sum + (s.megabytesIn || 0), 0) * 1_000_000;
|
||||||
|
const trafficOut = sites.reduce((sum, s) => sum + (s.megabytesOut || 0), 0) * 1_000_000;
|
||||||
|
const trafficTotal = trafficIn + trafficOut;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container service={service}>
|
||||||
|
<Block label="pangolin.sites" value={`${sitesOnline} / ${sitesTotal}`} />
|
||||||
|
<Block label="pangolin.resources" value={`${resourcesHealthy} / ${resourcesTotal}`} />
|
||||||
|
<Block label="pangolin.targets" value={`${targetsHealthy} / ${targetsTotal}`} />
|
||||||
|
<Block label="pangolin.traffic" value={t("common.bytes", { value: trafficTotal })} />
|
||||||
|
<Block label="pangolin.in" value={t("common.bytes", { value: trafficIn })} />
|
||||||
|
<Block label="pangolin.out" value={t("common.bytes", { value: trafficOut })} />
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
17
src/widgets/pangolin/widget.js
Normal file
17
src/widgets/pangolin/widget.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import credentialedProxyHandler from "utils/proxy/handlers/credentialed";
|
||||||
|
|
||||||
|
const widget = {
|
||||||
|
api: "{url}/v1/{endpoint}",
|
||||||
|
proxyHandler: credentialedProxyHandler,
|
||||||
|
|
||||||
|
mappings: {
|
||||||
|
sites: {
|
||||||
|
endpoint: "org/{org}/sites",
|
||||||
|
},
|
||||||
|
resources: {
|
||||||
|
endpoint: "org/{org}/resources",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default widget;
|
||||||
@@ -87,6 +87,7 @@ import openmediavault from "./openmediavault/widget";
|
|||||||
import openwrt from "./openwrt/widget";
|
import openwrt from "./openwrt/widget";
|
||||||
import opnsense from "./opnsense/widget";
|
import opnsense from "./opnsense/widget";
|
||||||
import overseerr from "./overseerr/widget";
|
import overseerr from "./overseerr/widget";
|
||||||
|
import pangolin from "./pangolin/widget";
|
||||||
import paperlessngx from "./paperlessngx/widget";
|
import paperlessngx from "./paperlessngx/widget";
|
||||||
import peanut from "./peanut/widget";
|
import peanut from "./peanut/widget";
|
||||||
import pfsense from "./pfsense/widget";
|
import pfsense from "./pfsense/widget";
|
||||||
@@ -237,6 +238,7 @@ const widgets = {
|
|||||||
openmediavault,
|
openmediavault,
|
||||||
openwrt,
|
openwrt,
|
||||||
paperlessngx,
|
paperlessngx,
|
||||||
|
pangolin,
|
||||||
peanut,
|
peanut,
|
||||||
pfsense,
|
pfsense,
|
||||||
photoprism,
|
photoprism,
|
||||||
|
|||||||
Reference in New Issue
Block a user