mirror of
https://github.com/DI0IK/homepage-plus.git
synced 2025-07-10 07:18:47 +00:00
Merge branch 'main' of github.com:chazzbg/homepage into truenas-widget
This commit is contained in:
commit
30c35f99d2
44 changed files with 784 additions and 25 deletions
|
@ -27,10 +27,12 @@ export default function DateTime({ options }) {
|
|||
const dateFormat = new Intl.DateTimeFormat(i18n.language, { ...format });
|
||||
|
||||
return (
|
||||
<div className="flex flex-row items-center grow justify-end">
|
||||
<span className={`text-theme-800 dark:text-theme-200 ${textSizes[textSize || "lg"]}`}>
|
||||
{dateFormat.format(date)}
|
||||
</span>
|
||||
<div className="flex flex-col justify-center first:ml-0 ml-4">
|
||||
<div className="flex flex-row items-center grow justify-end">
|
||||
<span className={`text-theme-800 dark:text-theme-200 ${textSizes[textSize || "lg"]}`}>
|
||||
{dateFormat.format(date)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ const Version = dynamic(() => import("components/version"), {
|
|||
ssr: false,
|
||||
});
|
||||
|
||||
const rightAlignedWidgets = ["weatherapi", "openweathermap", "weather", "search", "datetime"];
|
||||
const rightAlignedWidgets = ["weatherapi", "openweathermap", "weather", "openmeteo", "search", "datetime"];
|
||||
|
||||
export async function getStaticProps() {
|
||||
let logger;
|
||||
|
@ -272,7 +272,7 @@ function Home({ initialSettings }) {
|
|||
)}
|
||||
|
||||
{bookmarks && (
|
||||
<div className="grow flex flex-wrap pt-0 p-4 sm:p-8">
|
||||
<div className="grow flex flex-wrap pt-0 p-4 sm:p-8 gap-x-2">
|
||||
{bookmarks.map((group) => (
|
||||
<BookmarksGroup key={group.name} group={group} />
|
||||
))}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
# For configuration options and examples, please see:
|
||||
# https://github.com/benphelps/homepage/wiki/Bookmarks
|
||||
# https://gethomepage.dev/en/configs/bookmarks
|
||||
|
||||
- Developer:
|
||||
- Github:
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
# For configuration options and examples, please see:
|
||||
# https://github.com/benphelps/homepage/wiki/Docker-Integration
|
||||
# https://gethomepage.dev/en/configs/docker/
|
||||
|
||||
# my-docker:
|
||||
# host: 127.0.0.1
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
# For configuration options and examples, please see:
|
||||
# https://github.com/benphelps/homepage/wiki/Services
|
||||
# https://gethomepage.dev/en/configs/services
|
||||
|
||||
- My First Group:
|
||||
- My First Service:
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
# For configuration options and examples, please see:
|
||||
# https://github.com/benphelps/homepage/wiki/Settings
|
||||
# https://gethomepage.dev/en/configs/settings
|
||||
|
||||
providers:
|
||||
openweathermap: openweathermapapikey
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
# For configuration options and examples, please see:
|
||||
# https://github.com/benphelps/homepage/wiki/Information-Widgets
|
||||
# https://gethomepage.dev/en/configs/widgets
|
||||
|
||||
- resources:
|
||||
cpu: true
|
||||
|
|
|
@ -33,6 +33,8 @@ export default async function credentialedProxyHandler(req, res) {
|
|||
headers.Authorization = `PVEAPIToken=${widget.username}=${widget.password}`;
|
||||
} else if (widget.type === "autobrr") {
|
||||
headers["X-API-Token"] = `${widget.key}`;
|
||||
} else if (widget.type === "tubearchivist") {
|
||||
headers.Authorization = `Token ${widget.key}`;
|
||||
} else {
|
||||
headers["X-API-Key"] = `${widget.key}`;
|
||||
}
|
||||
|
|
|
@ -36,8 +36,10 @@ const components = {
|
|||
tautulli: dynamic(() => import("./tautulli/component")),
|
||||
traefik: dynamic(() => import("./traefik/component")),
|
||||
transmission: dynamic(() => import("./transmission/component")),
|
||||
tubearchivist: dynamic(() => import("./tubearchivist/component")),
|
||||
truenas: dynamic(() => import("./truenas/component")),
|
||||
unifi: dynamic(() => import("./unifi/component")),
|
||||
watchtower: dynamic(() => import("./watchtower/component")),
|
||||
};
|
||||
|
||||
export default components;
|
||||
|
|
|
@ -10,7 +10,7 @@ const widget = {
|
|||
},
|
||||
PlayControl: {
|
||||
method: "POST",
|
||||
enpoint: "Sessions/{sessionId}/Playing/{command}",
|
||||
endpoint: "Sessions/{sessionId}/Playing/{command}",
|
||||
segments: ["sessionId", "command"],
|
||||
},
|
||||
},
|
||||
|
|
40
src/widgets/tubearchivist/component.jsx
Normal file
40
src/widgets/tubearchivist/component.jsx
Normal file
|
@ -0,0 +1,40 @@
|
|||
import { useTranslation } from "next-i18next";
|
||||
|
||||
import Container from "components/services/widget/container";
|
||||
import Block from "components/services/widget/block";
|
||||
import useWidgetAPI from "utils/proxy/use-widget-api";
|
||||
|
||||
export default function Component({ service }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { widget } = service;
|
||||
|
||||
const { data: downloadsData, error: downloadsError } = useWidgetAPI(widget, "downloads");
|
||||
const { data: videosData, error: videosError } = useWidgetAPI(widget, "videos");
|
||||
const { data: channelsData, error: channelsError } = useWidgetAPI(widget, "channels");
|
||||
const { data: playlistsData, error: playlistsError } = useWidgetAPI(widget, "playlists");
|
||||
|
||||
if (downloadsError || videosError || channelsError || playlistsError) {
|
||||
return <Container error={t("widget.api_error")} />;
|
||||
}
|
||||
|
||||
if (!downloadsData || !videosData || !channelsData || !playlistsData) {
|
||||
return (
|
||||
<Container service={service}>
|
||||
<Block label="tubearchivist.downloads" />
|
||||
<Block label="tubearchivist.videos" />
|
||||
<Block label="tubearchivist.channels" />
|
||||
<Block label="tubearchivist.playlists" />
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Container service={service}>
|
||||
<Block label="tubearchivist.downloads" value={t("common.number", { value: downloadsData?.paginate?.total_hits })} />
|
||||
<Block label="tubearchivist.videos" value={t("common.number", { value: videosData?.paginate?.total_hits })} />
|
||||
<Block label="tubearchivist.channels" value={t("common.number", { value: channelsData?.paginate?.total_hits })} />
|
||||
<Block label="tubearchivist.playlists" value={t("common.number", { value: playlistsData?.paginate?.total_hits })} />
|
||||
</Container>
|
||||
);
|
||||
}
|
23
src/widgets/tubearchivist/widget.js
Normal file
23
src/widgets/tubearchivist/widget.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
import credentialedProxyHandler from "utils/proxy/handlers/credentialed";
|
||||
|
||||
const widget = {
|
||||
api: "{url}/api/{endpoint}",
|
||||
proxyHandler: credentialedProxyHandler,
|
||||
|
||||
mappings: {
|
||||
downloads: {
|
||||
endpoint: "download",
|
||||
},
|
||||
videos: {
|
||||
endpoint: "video",
|
||||
},
|
||||
channels: {
|
||||
endpoint: "channel",
|
||||
},
|
||||
playlists: {
|
||||
endpoint: "playlist",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default widget;
|
36
src/widgets/watchtower/component.jsx
Normal file
36
src/widgets/watchtower/component.jsx
Normal file
|
@ -0,0 +1,36 @@
|
|||
import { useTranslation } from "next-i18next";
|
||||
|
||||
import Container from "components/services/widget/container";
|
||||
import Block from "components/services/widget/block";
|
||||
import useWidgetAPI from "utils/proxy/use-widget-api";
|
||||
|
||||
|
||||
export default function Component({ service }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { widget } = service;
|
||||
|
||||
const { data: watchData, error: watchError } = useWidgetAPI(widget, "watchtower");
|
||||
|
||||
if (watchError || !watchData) {
|
||||
return <Container error={t("widget.api_error")} />;
|
||||
}
|
||||
|
||||
if (!watchData) {
|
||||
return (
|
||||
<Container service={service}>
|
||||
<Block label="watchtower.containers_scanned " />
|
||||
<Block label="watchtower.containers_updated" />
|
||||
<Block label="watchtower.containers_failed" />
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Container service={service}>
|
||||
<Block label="watchtower.containers_scanned" value={t("common.number", { value: watchData.watchtower_containers_scanned })} />
|
||||
<Block label="watchtower.containers_updated" value={t("common.number", { value: watchData.watchtower_containers_updated })} />
|
||||
<Block label="watchtower.containers_failed" value={t("common.number", { value: watchData.watchtower_containers_failed })} />
|
||||
</Container>
|
||||
);
|
||||
}
|
48
src/widgets/watchtower/proxy.js
Normal file
48
src/widgets/watchtower/proxy.js
Normal file
|
@ -0,0 +1,48 @@
|
|||
import { httpProxy } from "utils/proxy/http";
|
||||
import { formatApiCall } from "utils/proxy/api-helpers";
|
||||
import getServiceWidget from "utils/config/service-helpers";
|
||||
import createLogger from "utils/logger";
|
||||
import widgets from "widgets/widgets";
|
||||
|
||||
const proxyName = "watchtowerProxyHandler";
|
||||
const logger = createLogger(proxyName);
|
||||
|
||||
export default async function watchtowerProxyHandler(req, res) {
|
||||
const { group, service, endpoint } = req.query;
|
||||
|
||||
if (!group || !service) {
|
||||
logger.debug("Invalid or missing service '%s' or group '%s'", service, group);
|
||||
return res.status(400).json({ error: "Invalid proxy service type" });
|
||||
}
|
||||
|
||||
const widget = await getServiceWidget(group, service);
|
||||
|
||||
if (!widget) {
|
||||
logger.debug("Invalid or missing widget for service '%s' in group '%s'", service, group);
|
||||
return res.status(400).json({ error: "Invalid proxy service type" });
|
||||
}
|
||||
|
||||
const url = new URL(formatApiCall(widgets[widget.type].api, { endpoint, ...widget }));
|
||||
|
||||
const [status, contentType, data] = await httpProxy(url, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Authorization": `Bearer ${widget.key}`,
|
||||
}
|
||||
});
|
||||
|
||||
if (status !== 200 || !data) {
|
||||
logger.error("Error getting data from WatchTower: %d. Data: %s", status, data);
|
||||
}
|
||||
|
||||
const cleanData = data.toString().split("\n").filter(s => s.startsWith("watchtower"))
|
||||
const jsonRes = {}
|
||||
|
||||
cleanData.map(e => e.split(" ")).forEach(strArray => {
|
||||
const [key, value] = strArray
|
||||
jsonRes[key] = value
|
||||
})
|
||||
|
||||
if (contentType) res.setHeader("Content-Type", contentType);
|
||||
return res.status(status).send(jsonRes);
|
||||
}
|
14
src/widgets/watchtower/widget.js
Normal file
14
src/widgets/watchtower/widget.js
Normal file
|
@ -0,0 +1,14 @@
|
|||
import watchtowerProxyHandler from "./proxy";
|
||||
|
||||
const widget = {
|
||||
api: "{url}/{endpoint}",
|
||||
proxyHandler: watchtowerProxyHandler,
|
||||
|
||||
mappings: {
|
||||
"watchtower": {
|
||||
endpoint: "v1/metrics",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default widget;
|
|
@ -31,8 +31,10 @@ import strelaysrv from "./strelaysrv/widget";
|
|||
import tautulli from "./tautulli/widget";
|
||||
import traefik from "./traefik/widget";
|
||||
import transmission from "./transmission/widget";
|
||||
import tubearchivist from "./tubearchivist/widget";
|
||||
import truenas from "./truenas/widget";
|
||||
import unifi from "./unifi/widget";
|
||||
import watchtower from './watchtower/widget'
|
||||
|
||||
const widgets = {
|
||||
adguard,
|
||||
|
@ -69,9 +71,11 @@ const widgets = {
|
|||
tautulli,
|
||||
traefik,
|
||||
transmission,
|
||||
tubearchivist,
|
||||
truenas,
|
||||
unifi,
|
||||
unifi_console: unifi,
|
||||
watchtower,
|
||||
};
|
||||
|
||||
export default widgets;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue