Feature: Add ArgoCD widget (#4305)

Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
dev
Felix Cornelius 2 days ago committed by GitHub
parent adde687331
commit 4a3a4c846e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,33 @@
---
title: ArgoCD
description: ArgoCD Widget Configuration
---
Learn more about [ArgoCD](https://argo-cd.readthedocs.io/en/stable/).
Allowed fields (limited to a max of 4): `["apps", "synced", "outOfSync", "healthy", "progressing", "degraded", "suspended", "missing"]`
```yaml
widget:
type: argocd
url: http://argocd.host.or.ip:port
key: argocdapikey
```
You can generate an API key either by creating a bearer token for an existing account, see [Authorization](https://argo-cd.readthedocs.io/en/latest/developer-guide/api-docs/#authorization) (not recommended) or create a new local user account with limited privileges and generate an authentication token for this account. To do this the steps are:
- [Create a new local user](https://argo-cd.readthedocs.io/en/stable/operator-manual/user-management/#create-new-user) and give it the `apiKey` capability
- Setup [RBAC configuration](https://argo-cd.readthedocs.io/en/stable/operator-manual/rbac/#rbac-configuration) for your the user and give it readonly access to your ArgoCD resources, e.g. by giving it the `role:readonly` role.
- In your ArgoCD project under _Settings / Accounts_ open the newly created account and in the _Tokens_ section click on _Generate New_ to generate an access token, optionally specifying an expiry date.
If you installed ArgoCD via the official Helm chart, the account creation and rbac config can be achived by overriding these helm values:
```yaml
configs:
cm:
accounts.readonly: apiKey
rbac:
policy.csv: "g, readonly, role:readonly"
```
This creates a new account called `readonly` and attaches the `role:readonly` role to it.

@ -8,6 +8,7 @@ search:
You can also find a list of all available service widgets in the sidebar navigation.
- [Adguard Home](adguard-home.md)
- [ArgoCD](argocd.md)
- [Atsumeru](atsumeru.md)
- [Audiobookshelf](audiobookshelf.md)
- [Authentik](authentik.md)

@ -31,6 +31,7 @@ nav:
- "Service Widgets":
- widgets/services/index.md
- widgets/services/adguard-home.md
- widgets/services/argocd.md
- widgets/services/atsumeru.md
- widgets/services/audiobookshelf.md
- widgets/services/authentik.md

@ -988,5 +988,15 @@
"memory": "MEM",
"disk": "Disk",
"network": "NET"
},
"argocd": {
"apps": "Apps",
"synced": "Synced",
"outOfSync": "Out Of Sync",
"healthy": "Healthy",
"degraded": "Degraded",
"progressing": "Progressing",
"missing": "Missing",
"suspended": "Suspended"
}
}

@ -36,6 +36,7 @@ export default async function credentialedProxyHandler(req, res, map) {
headers["X-gotify-Key"] = `${widget.key}`;
} else if (
[
"argocd",
"authentik",
"cloudflared",
"ghostfolio",

@ -0,0 +1,52 @@
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 { widget } = service;
if (!widget.fields) {
widget.fields = ["apps", "synced", "outOfSync", "healthy"];
}
const MAX_ALLOWED_FIELDS = 4;
if (widget.fields.length > MAX_ALLOWED_FIELDS) {
widget.fields = widget.fields.slice(0, MAX_ALLOWED_FIELDS);
}
const { data: appsData, error: appsError } = useWidgetAPI(widget, "applications");
const appCounts = widget.fields.map((status) => {
if (status === "apps") {
return { status, count: appsData?.items?.length };
}
const count = appsData?.items?.filter(
(item) =>
item.status?.sync?.status.toLowerCase() === status.toLowerCase() ||
item.status?.health?.status.toLowerCase() === status.toLowerCase(),
).length;
return { status, count };
});
if (appsError) {
return <Container service={service} error={appsError} />;
}
if (!appsData) {
return (
<Container service={service}>
{appCounts.map((a) => (
<Block label={`argocd.${a.status}`} key={a.status} />
))}
</Container>
);
}
return (
<Container service={service}>
{appCounts.map((a) => (
<Block label={`argocd.${a.status}`} key={a.status} value={a.count} />
))}
</Container>
);
}

@ -0,0 +1,14 @@
import credentialedProxyHandler from "utils/proxy/handlers/credentialed";
const widget = {
api: "{url}/api/v1/{endpoint}",
proxyHandler: credentialedProxyHandler,
mappings: {
applications: {
endpoint: "applications",
},
},
};
export default widget;

@ -2,6 +2,7 @@ import dynamic from "next/dynamic";
const components = {
adguard: dynamic(() => import("./adguard/component")),
argocd: dynamic(() => import("./argocd/component")),
atsumeru: dynamic(() => import("./atsumeru/component")),
audiobookshelf: dynamic(() => import("./audiobookshelf/component")),
authentik: dynamic(() => import("./authentik/component")),

@ -5,6 +5,7 @@ import Block from "components/services/widget/block";
import useWidgetAPI from "utils/proxy/use-widget-api";
function formatValue(t, metric, rawValue) {
if (!metric?.format) return rawValue;
if (!rawValue) return "-";
let value = rawValue;

@ -1,4 +1,5 @@
import adguard from "./adguard/widget";
import argocd from "./argocd/widget";
import atsumeru from "./atsumeru/widget";
import audiobookshelf from "./audiobookshelf/widget";
import authentik from "./authentik/widget";
@ -130,6 +131,7 @@ import zabbix from "./zabbix/widget";
const widgets = {
adguard,
argocd,
atsumeru,
audiobookshelf,
authentik,

Loading…
Cancel
Save