mirror of
https://github.com/gethomepage/homepage.git
synced 2025-07-09 03:04:18 -04:00
Enhancement: Komodo widget (#5407)
Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
This commit is contained in:
parent
6e16adc460
commit
8ce9e57ed8
22
docs/widgets/services/komodo.md
Normal file
22
docs/widgets/services/komodo.md
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
---
|
||||||
|
title: Komodo
|
||||||
|
description: Komodo Widget Configuration
|
||||||
|
---
|
||||||
|
|
||||||
|
This widget shows either details about all containers or stacks (if `showStacks` is true) managed by [Komodo](https://komo.do/) or the number of running servers, containers and stacks when `showSummary` is enabled.
|
||||||
|
|
||||||
|
The api key and secret can be found in the Komodo settings.
|
||||||
|
|
||||||
|
Allowed fields (max 4): `["total", "running", "stopped", "unhealthy", "unknown"]`.
|
||||||
|
Allowed fields with `showStacks` (max 4): `["total", "running", "down", "unhealthy", "unknown"]`.
|
||||||
|
Allowed fields with `showSummary`: `["servers", "stacks", "containers"]`.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
widget:
|
||||||
|
type: komodo
|
||||||
|
url: http://komodo.hostname.or.ip:port
|
||||||
|
key: K-xxxxxx...
|
||||||
|
secret: S-xxxxxx...
|
||||||
|
showSummary: true # optional, default: false
|
||||||
|
showStacks: true # optional, default: false
|
||||||
|
```
|
@ -1060,5 +1060,16 @@
|
|||||||
"checkmk": {
|
"checkmk": {
|
||||||
"serviceErrors": "Service issues",
|
"serviceErrors": "Service issues",
|
||||||
"hostErrors": "Host issues"
|
"hostErrors": "Host issues"
|
||||||
|
},
|
||||||
|
"komodo": {
|
||||||
|
"total": "Total",
|
||||||
|
"running": "Running",
|
||||||
|
"stopped": "Stopped",
|
||||||
|
"down": "Down",
|
||||||
|
"unhealthy": "Unhealthy",
|
||||||
|
"unknown": "Unknown",
|
||||||
|
"servers": "Servers",
|
||||||
|
"stacks": "Stacks",
|
||||||
|
"containers": "Containers"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -338,6 +338,10 @@ export function cleanServiceGroups(groups) {
|
|||||||
// jellystat
|
// jellystat
|
||||||
days,
|
days,
|
||||||
|
|
||||||
|
// komodo
|
||||||
|
showSummary,
|
||||||
|
showStacks,
|
||||||
|
|
||||||
// kopia
|
// kopia
|
||||||
snapshotHost,
|
snapshotHost,
|
||||||
snapshotPath,
|
snapshotPath,
|
||||||
@ -450,6 +454,10 @@ export function cleanServiceGroups(groups) {
|
|||||||
if (type === "proxmoxbackupserver") {
|
if (type === "proxmoxbackupserver") {
|
||||||
if (datastore) widget.datastore = datastore;
|
if (datastore) widget.datastore = datastore;
|
||||||
}
|
}
|
||||||
|
if (type === "komodo") {
|
||||||
|
if (showSummary !== undefined) widget.showSummary = !!JSON.parse(showSummary);
|
||||||
|
if (showStacks !== undefined) widget.showStacks = !!JSON.parse(showStacks);
|
||||||
|
}
|
||||||
if (type === "kubernetes") {
|
if (type === "kubernetes") {
|
||||||
if (namespace) widget.namespace = namespace;
|
if (namespace) widget.namespace = namespace;
|
||||||
if (app) widget.app = app;
|
if (app) widget.app = app;
|
||||||
|
@ -63,6 +63,7 @@ const components = {
|
|||||||
jellystat: dynamic(() => import("./jellystat/component")),
|
jellystat: dynamic(() => import("./jellystat/component")),
|
||||||
kavita: dynamic(() => import("./kavita/component")),
|
kavita: dynamic(() => import("./kavita/component")),
|
||||||
komga: dynamic(() => import("./komga/component")),
|
komga: dynamic(() => import("./komga/component")),
|
||||||
|
komodo: dynamic(() => import("./komodo/component")),
|
||||||
kopia: dynamic(() => import("./kopia/component")),
|
kopia: dynamic(() => import("./kopia/component")),
|
||||||
lidarr: dynamic(() => import("./lidarr/component")),
|
lidarr: dynamic(() => import("./lidarr/component")),
|
||||||
linkwarden: dynamic(() => import("./linkwarden/component")),
|
linkwarden: dynamic(() => import("./linkwarden/component")),
|
||||||
|
84
src/widgets/komodo/component.jsx
Normal file
84
src/widgets/komodo/component.jsx
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
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;
|
||||||
|
const containersEndpoint = !(!widget.showSummary && widget.showStacks) ? "containers" : "";
|
||||||
|
const { data: containersData, error: containersError } = useWidgetAPI(widget, containersEndpoint);
|
||||||
|
const stacksEndpoint = widget.showSummary || widget.showStacks ? "stacks" : "";
|
||||||
|
const { data: stacksData, error: stacksError } = useWidgetAPI(widget, stacksEndpoint);
|
||||||
|
const serversEndpoint = widget.showSummary ? "servers" : "";
|
||||||
|
const { data: serversData, error: serversError } = useWidgetAPI(widget, serversEndpoint);
|
||||||
|
|
||||||
|
if (containersError || stacksError || serversError) {
|
||||||
|
return <Container service={service} error={containersError ?? stacksError ?? serversError} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!widget.fields || widget.fields.length === 0) {
|
||||||
|
widget.fields = widget.showSummary
|
||||||
|
? ["servers", "stacks", "containers"]
|
||||||
|
: widget.showStacks
|
||||||
|
? ["total", "running", "down", "unhealthy"]
|
||||||
|
: ["total", "running", "stopped", "unhealthy"];
|
||||||
|
} else if (widget.fields?.length > MAX_ALLOWED_FIELDS) {
|
||||||
|
widget.fields = widget.fields.slice(0, MAX_ALLOWED_FIELDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
(!widget.showStacks && !containersData) ||
|
||||||
|
(widget.showSummary && (!stacksData || !serversData)) ||
|
||||||
|
(widget.showStacks && !stacksData)
|
||||||
|
) {
|
||||||
|
return widget.showSummary ? (
|
||||||
|
<Container service={service}>
|
||||||
|
<Block label="komodo.servers" />
|
||||||
|
<Block label="komodo.stacks" />
|
||||||
|
<Block label="komodo.containers" />
|
||||||
|
</Container>
|
||||||
|
) : widget.showStacks ? (
|
||||||
|
<Container service={service}>
|
||||||
|
<Block label="komodo.total" />
|
||||||
|
<Block label="komodo.running" />
|
||||||
|
<Block label="komodo.down" />
|
||||||
|
<Block label="komodo.unhealthy" />
|
||||||
|
</Container>
|
||||||
|
) : (
|
||||||
|
<Container service={service}>
|
||||||
|
<Block label="komodo.total" />
|
||||||
|
<Block label="komodo.running" />
|
||||||
|
<Block label="komodo.stopped" />
|
||||||
|
<Block label="komodo.unhealthy" />
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return widget.showSummary ? (
|
||||||
|
<Container service={service}>
|
||||||
|
<Block label="komodo.servers" value={`${serversData.healthy} / ${serversData.total}`} />
|
||||||
|
<Block label="komodo.stacks" value={`${stacksData.running} / ${stacksData.total}`} />
|
||||||
|
<Block label="komodo.containers" value={`${containersData.running} / ${containersData.total}`} />
|
||||||
|
</Container>
|
||||||
|
) : widget.showStacks ? (
|
||||||
|
<Container service={service}>
|
||||||
|
<Block label="komodo.total" value={t("common.number", { value: stacksData.total })} />
|
||||||
|
<Block label="komodo.running" value={t("common.number", { value: stacksData.running })} />
|
||||||
|
<Block label="komodo.down" value={t("common.number", { value: stacksData.stopped + stacksData.down })} />
|
||||||
|
<Block label="komodo.unhealthy" value={t("common.number", { value: stacksData.unhealthy })} />
|
||||||
|
<Block label="komodo.unknown" value={t("common.number", { value: stacksData.unknown })} />
|
||||||
|
</Container>
|
||||||
|
) : (
|
||||||
|
<Container service={service}>
|
||||||
|
<Block label="komodo.total" value={t("common.number", { value: containersData.total })} />
|
||||||
|
<Block label="komodo.running" value={t("common.number", { value: containersData.running })} />
|
||||||
|
<Block label="komodo.stopped" value={t("common.number", { value: containersData.stopped })} />
|
||||||
|
<Block label="komodo.unhealthy" value={t("common.number", { value: containersData.unhealthy })} />
|
||||||
|
<Block label="komodo.unknown" value={t("common.number", { value: containersData.unknown })} />
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
55
src/widgets/komodo/proxy.js
Normal file
55
src/widgets/komodo/proxy.js
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import getServiceWidget from "utils/config/service-helpers";
|
||||||
|
import createLogger from "utils/logger";
|
||||||
|
import { formatApiCall, sanitizeErrorURL } from "utils/proxy/api-helpers";
|
||||||
|
import { httpProxy } from "utils/proxy/http";
|
||||||
|
import validateWidgetData from "utils/proxy/validate-widget-data";
|
||||||
|
import widgets from "widgets/widgets";
|
||||||
|
|
||||||
|
const logger = createLogger("komodoProxyHandler");
|
||||||
|
|
||||||
|
export default async function komodoProxyHandler(req, res) {
|
||||||
|
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) {
|
||||||
|
// api uses unified read endpoint
|
||||||
|
const url = new URL(formatApiCall(widgets[widget.type].api, { endpoint: "read", ...widget })).toString();
|
||||||
|
|
||||||
|
const headers = {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"X-API-Key": `${widget.key}`,
|
||||||
|
"X-API-Secret": `${widget.secret}`,
|
||||||
|
};
|
||||||
|
const [status, contentType, data] = await httpProxy(url, {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify(widgets[widget.type].mappings?.[endpoint]?.body || {}),
|
||||||
|
headers,
|
||||||
|
});
|
||||||
|
|
||||||
|
let resultData = data;
|
||||||
|
|
||||||
|
if (status >= 400) {
|
||||||
|
logger.error("HTTP Error %d calling %s", status, sanitizeErrorURL(url));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status === 200) {
|
||||||
|
if (!validateWidgetData(widget, endpoint, resultData)) {
|
||||||
|
return res
|
||||||
|
.status(500)
|
||||||
|
.json({ error: { message: "Invalid data", url: sanitizeErrorURL(url), data: resultData } });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (contentType) res.setHeader("Content-Type", contentType);
|
||||||
|
return res.status(status).send(resultData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug("Invalid or missing proxy service type '%s' in group '%s'", service, group);
|
||||||
|
return res.status(400).json({ error: "Invalid proxy service type" });
|
||||||
|
}
|
32
src/widgets/komodo/widget.js
Normal file
32
src/widgets/komodo/widget.js
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import komodoProxyHandler from "./proxy";
|
||||||
|
|
||||||
|
const widget = {
|
||||||
|
api: "{url}/{endpoint}",
|
||||||
|
proxyHandler: komodoProxyHandler,
|
||||||
|
|
||||||
|
mappings: {
|
||||||
|
containers: {
|
||||||
|
endpoint: "containers", // api actually uses unified read endpoint
|
||||||
|
body: {
|
||||||
|
type: "GetDockerContainersSummary",
|
||||||
|
params: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
stacks: {
|
||||||
|
endpoint: "stacks", // api actually uses unified read endpoint
|
||||||
|
body: {
|
||||||
|
type: "GetStacksSummary",
|
||||||
|
params: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
servers: {
|
||||||
|
endpoint: "servers", // api actually uses unified read endpoint
|
||||||
|
body: {
|
||||||
|
type: "GetServersSummary",
|
||||||
|
params: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default widget;
|
@ -54,6 +54,7 @@ import jellystat from "./jellystat/widget";
|
|||||||
import karakeep from "./karakeep/widget";
|
import karakeep from "./karakeep/widget";
|
||||||
import kavita from "./kavita/widget";
|
import kavita from "./kavita/widget";
|
||||||
import komga from "./komga/widget";
|
import komga from "./komga/widget";
|
||||||
|
import komodo from "./komodo/widget";
|
||||||
import kopia from "./kopia/widget";
|
import kopia from "./kopia/widget";
|
||||||
import lidarr from "./lidarr/widget";
|
import lidarr from "./lidarr/widget";
|
||||||
import linkwarden from "./linkwarden/widget";
|
import linkwarden from "./linkwarden/widget";
|
||||||
@ -197,6 +198,7 @@ const widgets = {
|
|||||||
jellystat,
|
jellystat,
|
||||||
kavita,
|
kavita,
|
||||||
komga,
|
komga,
|
||||||
|
komodo,
|
||||||
kopia,
|
kopia,
|
||||||
lidarr,
|
lidarr,
|
||||||
linkwarden,
|
linkwarden,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user