Enhancement: add kubernetes support to portainer widget (#5414)

Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
This commit is contained in:
Albin Médoc 2025-06-13 09:17:20 +02:00 committed by GitHub
parent 8ce9e57ed8
commit 73aa2018a8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 81 additions and 13 deletions

View File

@ -7,12 +7,16 @@ Learn more about [Portainer](https://github.com/portainer/portainer).
You'll need to make sure you have the correct environment set for the integration to work properly. From the Environments section inside of Portainer, click the one you'd like to connect to and observe the ID at the end of the URL (should be), something like `#!/endpoints/1`, here `1` is the value to set as the `env` value. In order to generate an API key, please follow the steps outlined here https://docs.portainer.io/api/access.
Allowed fields: `["running", "stopped", "total"]`.
Allowed fields:
- For Docker mode (default): `["running", "stopped", "total"]`
- For Kubernetes mode (`kubernetes: true`): `["applications", "services", "namespaces"]`
```yaml
widget:
type: portainer
url: https://portainer.host.or.ip:9443
env: 1
kubernetes: true # optional, defaults to false
key: ptr_accesskeyaccesskeyaccesskeyaccesskey
```

View File

@ -367,6 +367,9 @@ export function cleanServiceGroups(groups) {
// opnsense, pfsense
wan,
// portainer
kubernetes,
// prometheusmetric
metrics,
@ -448,6 +451,9 @@ export function cleanServiceGroups(groups) {
if (type === "unifi") {
if (site) widget.site = site;
}
if (type === "portainer") {
if (kubernetes) widget.kubernetes = !!JSON.parse(kubernetes);
}
if (type === "proxmox") {
if (node) widget.node = node;
}

View File

@ -6,15 +6,64 @@ import useWidgetAPI from "utils/proxy/use-widget-api";
export default function Component({ service }) {
const { widget } = service;
const { data: containersData, error: containersError } = useWidgetAPI(widget, "docker/containers/json", {
all: 1,
});
if (!widget.fields) {
widget.fields = widget.kubernetes ? ["applications", "services", "namespaces"] : ["running", "stopped", "total"];
}
const { data: containersCount, error: containersError } = useWidgetAPI(
widget,
widget.kubernetes ? "" : "docker/containers",
{
all: 1,
},
);
const { data: applicationsCount, error: applicationsError } = useWidgetAPI(
widget,
widget.kubernetes ? "kubernetes/applications" : "",
);
const { data: servicesCount, error: servicesError } = useWidgetAPI(
widget,
widget.kubernetes ? "kubernetes/services" : "",
);
const { data: namespacesCount, error: namespacesError } = useWidgetAPI(
widget,
widget.kubernetes ? "kubernetes/namespaces" : "",
);
if (widget.kubernetes) {
// count can be an error object
const error = applicationsError ?? servicesError ?? namespacesError ?? applicationsCount;
if (error) {
return <Container service={service} error={error} />;
}
if (applicationsCount == undefined || servicesCount == undefined || namespacesCount == undefined) {
return (
<Container service={service}>
<Block label="portainer.applications" />
<Block label="portainer.services" />
<Block label="portainer.namespaces" />
</Container>
);
}
return (
<Container service={service}>
<Block label="portainer.applications" value={applicationsCount ?? 0} />
<Block label="portainer.services" value={servicesCount ?? 0} />
<Block label="portainer.namespaces" value={namespacesCount ?? 0} />
</Container>
);
}
if (containersError) {
return <Container service={service} error={containersError} />;
}
if (!containersData) {
if (!containersCount) {
return (
<Container service={service}>
<Block label="portainer.running" />
@ -24,14 +73,14 @@ export default function Component({ service }) {
);
}
if (containersData.error || containersData.message) {
if (containersCount.error || containersCount.message) {
// containersData can be itself an error object e.g. if environment fails
return <Container service={service} error={containersData?.error ?? containersData} />;
return <Container service={service} error={containersCount?.error ?? containersCount} />;
}
const running = containersData.filter((c) => c.State === "running").length;
const stopped = containersData.filter((c) => c.State === "exited").length;
const total = containersData.length;
const running = containersCount.filter((c) => c.State === "running").length;
const stopped = containersCount.filter((c) => c.State === "exited").length;
const total = containersCount.length;
return (
<Container service={service}>

View File

@ -1,14 +1,23 @@
import credentialedProxyHandler from "utils/proxy/handlers/credentialed";
const widget = {
api: "{url}/api/endpoints/{env}/{endpoint}",
api: "{url}/api/{endpoint}",
proxyHandler: credentialedProxyHandler,
mappings: {
"docker/containers/json": {
endpoint: "docker/containers/json",
"docker/containers": {
endpoint: "endpoints/{env}/docker/containers/json",
params: ["all"],
},
"kubernetes/applications": {
endpoint: "kubernetes/{env}/applications/count",
},
"kubernetes/services": {
endpoint: "kubernetes/{env}/services/count",
},
"kubernetes/namespaces": {
endpoint: "kubernetes/{env}/namespaces/count",
},
},
};