mirror of
https://github.com/gethomepage/homepage.git
synced 2025-12-05 21:47:48 +01:00
Compare commits
3 Commits
cf990063b9
...
feature/60
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a3c7823f28 | ||
|
|
5b50e8ff81 | ||
|
|
c36c6a9012 |
@@ -14,4 +14,6 @@ widget:
|
|||||||
type: frigate
|
type: frigate
|
||||||
url: http://frigate.host.or.ip:port
|
url: http://frigate.host.or.ip:port
|
||||||
enableRecentEvents: true # Optional, defaults to false
|
enableRecentEvents: true # Optional, defaults to false
|
||||||
|
username: username # optional
|
||||||
|
password: password # optional
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -12,11 +12,17 @@ Learn more about [Gluetun](https://github.com/qdm12/gluetun).
|
|||||||
Allowed fields: `["public_ip", "region", "country", "port_forwarded"]`.
|
Allowed fields: `["public_ip", "region", "country", "port_forwarded"]`.
|
||||||
Default fields: `["public_ip", "region", "country"]`.
|
Default fields: `["public_ip", "region", "country"]`.
|
||||||
|
|
||||||
To setup authentication, follow [the official Gluetun documentation](https://github.com/qdm12/gluetun-wiki/blob/main/setup/advanced/control-server.md#authentication). Note that to use the api key method, you must add the route `GET /v1/publicip/ip` to the `routes` array in your Gluetun config.toml. Similarly, if you want to include the `port_forwarded` field, you must add the route `GET /v1/openvpn/portforwarded` to your Gluetun config.toml.
|
To setup authentication, follow [the official Gluetun documentation](https://github.com/qdm12/gluetun-wiki/blob/main/setup/advanced/control-server.md#authentication). Note that to use the api key method, you must add the route `GET /v1/publicip/ip` to the `routes` array in your Gluetun config.toml. Similarly, if you want to include the `port_forwarded` field, you must add the route `GET /v1/openvpn/portforwarded` (or `/v1/portforward`) to your Gluetun config.toml.
|
||||||
|
|
||||||
|
| Gluetun Version | Homepage Widget Version |
|
||||||
|
| --------------- | ----------------------- |
|
||||||
|
| < 3.40.1 | 1 (default) |
|
||||||
|
| >= 3.40.1 | 2 |
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
widget:
|
widget:
|
||||||
type: gluetun
|
type: gluetun
|
||||||
url: http://gluetun.host.or.ip:port
|
url: http://gluetun.host.or.ip:port
|
||||||
key: gluetunkey # Not required if /v1/publicip/ip endpoint is configured with `auth = none`
|
key: gluetunkey # Not required if /v1/publicip/ip endpoint is configured with `auth = none`
|
||||||
|
version: 2 # optional, default is 1
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -563,6 +563,7 @@ export function cleanServiceGroups(groups) {
|
|||||||
"speedtest",
|
"speedtest",
|
||||||
"wgeasy",
|
"wgeasy",
|
||||||
"grafana",
|
"grafana",
|
||||||
|
"gluetun",
|
||||||
].includes(type)
|
].includes(type)
|
||||||
) {
|
) {
|
||||||
if (version) widget.version = parseInt(version, 10);
|
if (version) widget.version = parseInt(version, 10);
|
||||||
|
|||||||
95
src/widgets/frigate/proxy.js
Normal file
95
src/widgets/frigate/proxy.js
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
import getServiceWidget from "utils/config/service-helpers";
|
||||||
|
import createLogger from "utils/logger";
|
||||||
|
import { asJson, formatApiCall, sanitizeErrorURL } from "utils/proxy/api-helpers";
|
||||||
|
import { addCookieToJar } from "utils/proxy/cookie-jar";
|
||||||
|
import { httpProxy } from "utils/proxy/http";
|
||||||
|
import widgets from "widgets/widgets";
|
||||||
|
|
||||||
|
const proxyName = "frigateProxyHandler";
|
||||||
|
const logger = createLogger(proxyName);
|
||||||
|
|
||||||
|
export default async function frigateProxyHandler(req, res, map) {
|
||||||
|
const { group, service, endpoint, index } = req.query;
|
||||||
|
|
||||||
|
if (group && service) {
|
||||||
|
const widget = await getServiceWidget(group, service, index);
|
||||||
|
|
||||||
|
if (!widgets?.[widget.type]?.api) {
|
||||||
|
return res.status(403).json({ error: "Service does not support API calls" });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (widget) {
|
||||||
|
const url = formatApiCall(widgets[widget.type].api, { endpoint, ...widget });
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let [status, , data] = await httpProxy(url, params);
|
||||||
|
|
||||||
|
if (status === 401 && widget.username && widget.password) {
|
||||||
|
const loginUrl = `${widget.url}/api/login`;
|
||||||
|
logger.debug("Attempting login to Frigate at %s", loginUrl);
|
||||||
|
const [loginStatus, , , loginResponseHeaders] = await httpProxy(loginUrl, {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify({ user: widget.username, password: widget.password }),
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (loginStatus !== 200) {
|
||||||
|
logger.error("HTTP Error %d calling %s", loginStatus, sanitizeErrorURL(loginUrl));
|
||||||
|
return res.status(status).json({
|
||||||
|
error: {
|
||||||
|
message: `HTTP Error ${status} while trying to login to Frigate`,
|
||||||
|
url: sanitizeErrorURL(url),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
addCookieToJar(url, loginResponseHeaders);
|
||||||
|
// Retry original request with cookie set
|
||||||
|
[status, , data] = await httpProxy(url, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status >= 400) {
|
||||||
|
logger.error("HTTP Error %d calling %s", status, sanitizeErrorURL(url));
|
||||||
|
return res.status(status).json({
|
||||||
|
error: {
|
||||||
|
message: `HTTP Error ${status} from Frigate`,
|
||||||
|
url: sanitizeErrorURL(url),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
data = asJson(data);
|
||||||
|
|
||||||
|
if (endpoint == "stats") {
|
||||||
|
res.status(status).send({
|
||||||
|
num_cameras: data?.cameras !== undefined ? Object.keys(data?.cameras).length : 0,
|
||||||
|
uptime: data?.service?.uptime,
|
||||||
|
version: data?.service.version,
|
||||||
|
});
|
||||||
|
} else if (endpoint == "events") {
|
||||||
|
return res.status(status).send(
|
||||||
|
data.slice(0, 5).map((event) => ({
|
||||||
|
id: event.id,
|
||||||
|
camera: event.camera,
|
||||||
|
label: event.label,
|
||||||
|
start_time: new Date(event.start_time * 1000),
|
||||||
|
thumbnail: event.thumbnail,
|
||||||
|
score: event.data.score,
|
||||||
|
type: event.data.type,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug("Invalid or missing proxy service type '%s' in group '%s'", service, group);
|
||||||
|
return res.status(400).json({ error: "Invalid proxy service type" });
|
||||||
|
}
|
||||||
@@ -1,37 +1,12 @@
|
|||||||
import { asJson } from "utils/proxy/api-helpers";
|
import frigateProxyHandler from "./proxy";
|
||||||
import genericProxyHandler from "utils/proxy/handlers/generic";
|
|
||||||
|
|
||||||
const widget = {
|
const widget = {
|
||||||
api: "{url}/api/{endpoint}",
|
api: "{url}/api/{endpoint}",
|
||||||
proxyHandler: genericProxyHandler,
|
proxyHandler: frigateProxyHandler,
|
||||||
|
|
||||||
mappings: {
|
mappings: {
|
||||||
stats: {
|
stats: { endpoint: "stats" },
|
||||||
endpoint: "stats",
|
events: { endpoint: "events" },
|
||||||
map: (data) => {
|
|
||||||
const jsonData = asJson(data);
|
|
||||||
return {
|
|
||||||
num_cameras: jsonData?.cameras !== undefined ? Object.keys(jsonData?.cameras).length : 0,
|
|
||||||
uptime: jsonData?.service?.uptime,
|
|
||||||
version: jsonData?.service.version,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
},
|
|
||||||
events: {
|
|
||||||
endpoint: "events",
|
|
||||||
map: (data) =>
|
|
||||||
asJson(data)
|
|
||||||
.slice(0, 5)
|
|
||||||
.map((event) => ({
|
|
||||||
id: event.id,
|
|
||||||
camera: event.camera,
|
|
||||||
label: event.label,
|
|
||||||
start_time: new Date(event.start_time * 1000),
|
|
||||||
thumbnail: event.thumbnail,
|
|
||||||
score: event.data.score,
|
|
||||||
type: event.data.type,
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -12,10 +12,8 @@ export default function Component({ service }) {
|
|||||||
|
|
||||||
const { data: gluetunData, error: gluetunError } = useWidgetAPI(widget, "ip");
|
const { data: gluetunData, error: gluetunError } = useWidgetAPI(widget, "ip");
|
||||||
const includePF = widget.fields.includes("port_forwarded");
|
const includePF = widget.fields.includes("port_forwarded");
|
||||||
const { data: portForwardedData, error: portForwardedError } = useWidgetAPI(
|
const pfEndpoint = widget.version > 1 ? "port_forwarded_v2" : "port_forwarded";
|
||||||
widget,
|
const { data: portForwardedData, error: portForwardedError } = useWidgetAPI(widget, includePF ? pfEndpoint : "");
|
||||||
includePF ? "port_forwarded" : "",
|
|
||||||
);
|
|
||||||
|
|
||||||
if (gluetunError || (includePF && portForwardedError)) {
|
if (gluetunError || (includePF && portForwardedError)) {
|
||||||
return <Container service={service} error={gluetunError || portForwardedError} />;
|
return <Container service={service} error={gluetunError || portForwardedError} />;
|
||||||
|
|||||||
@@ -13,6 +13,10 @@ const widget = {
|
|||||||
endpoint: "openvpn/portforwarded",
|
endpoint: "openvpn/portforwarded",
|
||||||
validate: ["port"],
|
validate: ["port"],
|
||||||
},
|
},
|
||||||
|
port_forwarded_v2: {
|
||||||
|
endpoint: "portforward",
|
||||||
|
validate: ["port"],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -38,12 +38,13 @@ export default function Component({ service }) {
|
|||||||
|
|
||||||
if (Array.isArray(statusData.volume.volumeUseList.volumeUse)) {
|
if (Array.isArray(statusData.volume.volumeUseList.volumeUse)) {
|
||||||
if (widget.volume) {
|
if (widget.volume) {
|
||||||
const volumeSelected = statusData.volume.volumeList.volume.findIndex(
|
const volumeSelected = statusData.volume.volumeList.volume.find((vl) => vl.volumeLabel._cdata === widget.volume);
|
||||||
(vl) => vl.volumeLabel._cdata === widget.volume,
|
if (volumeSelected) {
|
||||||
);
|
const volumeUsed = statusData.volume.volumeUseList.volumeUse.find(
|
||||||
if (volumeSelected !== -1) {
|
(vu) => vu.volumeValue._cdata === volumeSelected.volumeValue._cdata,
|
||||||
volumeTotalSize = statusData.volume.volumeUseList.volumeUse[volumeSelected].total_size._cdata;
|
);
|
||||||
volumeFreeSize = statusData.volume.volumeUseList.volumeUse[volumeSelected].free_size._cdata;
|
volumeTotalSize = volumeUsed.total_size._cdata;
|
||||||
|
volumeFreeSize = volumeUsed.free_size._cdata;
|
||||||
} else {
|
} else {
|
||||||
validVolume = false;
|
validVolume = false;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user