From deda42af47a969a8f0c23d24bb0e9db715a13539 Mon Sep 17 00:00:00 2001 From: Felix Cornelius Date: Sun, 17 Nov 2024 21:40:50 +0100 Subject: [PATCH] Add ArgoCD widget + documentation --- docs/widgets/services/argocd.md | 33 ++++++++++++++++++ docs/widgets/services/index.md | 1 + mkdocs.yml | 1 + public/locales/en/common.json | 10 ++++++ src/utils/proxy/handlers/credentialed.js | 1 + src/widgets/argocd/component.jsx | 44 ++++++++++++++++++++++++ src/widgets/argocd/widget.js | 14 ++++++++ src/widgets/components.js | 1 + src/widgets/widgets.js | 2 ++ 9 files changed, 107 insertions(+) create mode 100644 docs/widgets/services/argocd.md create mode 100644 src/widgets/argocd/component.jsx create mode 100644 src/widgets/argocd/widget.js diff --git a/docs/widgets/services/argocd.md b/docs/widgets/services/argocd.md new file mode 100644 index 000000000..1587bd9ac --- /dev/null +++ b/docs/widgets/services/argocd.md @@ -0,0 +1,33 @@ +--- +title: ArgoCD +description: ArgoCD Widget Configuration +--- + +Learn more about [ArgoCD](https://argo-cd.readthedocs.io/en/stable/). + +Allowed fields: `["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. diff --git a/docs/widgets/services/index.md b/docs/widgets/services/index.md index 8ea2e9331..f52a0641e 100644 --- a/docs/widgets/services/index.md +++ b/docs/widgets/services/index.md @@ -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](atsumeru.md) - [Atsumeru](atsumeru.md) - [Audiobookshelf](audiobookshelf.md) - [Authentik](authentik.md) diff --git a/mkdocs.yml b/mkdocs.yml index 42abce301..1e9d59cc8 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -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 diff --git a/public/locales/en/common.json b/public/locales/en/common.json index 81576984e..e38b5dd0c 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -988,5 +988,15 @@ "memory": "MEM", "disk": "Disk", "network": "NET" + }, + "argocd": { + "apps": "Apps", + "synced": "Synced", + "outOfSync": "OutOfSync", + "healthy": "Healthy", + "degraded": "Degraded", + "progressing": "Progressing", + "missing": "Missing", + "suspended": "Suspended" } } diff --git a/src/utils/proxy/handlers/credentialed.js b/src/utils/proxy/handlers/credentialed.js index eb2aab69a..8d4340c2a 100644 --- a/src/utils/proxy/handlers/credentialed.js +++ b/src/utils/proxy/handlers/credentialed.js @@ -36,6 +36,7 @@ export default async function credentialedProxyHandler(req, res, map) { headers["X-gotify-Key"] = `${widget.key}`; } else if ( [ + "argocd", "authentik", "cloudflared", "ghostfolio", diff --git a/src/widgets/argocd/component.jsx b/src/widgets/argocd/component.jsx new file mode 100644 index 000000000..aac29cf06 --- /dev/null +++ b/src/widgets/argocd/component.jsx @@ -0,0 +1,44 @@ +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; + + const { data: appsData, error: appsError } = useWidgetAPI(widget, "applications"); + + const appCounts = ["apps", "synced", "outOfSync", "healthy", "progressing", "degraded", "suspended", "missing"].map( + (status) => { + if (status === "apps") { + return { status, count: appsData?.items?.length }; + } + const apiStatus = status.charAt(0).toUpperCase() + status.slice(1); + const count = appsData?.items?.filter( + (item) => item.status?.sync?.status === apiStatus || item.status?.health?.status === apiStatus, + ).length; + return { status, count }; + }, + ); + + if (appsError) { + return ; + } + + if (!appsData) { + return ( + + {appCounts.map((a) => ( + + ))} + + ); + } + + return ( + + {appCounts.map((a) => ( + + ))} + + ); +} diff --git a/src/widgets/argocd/widget.js b/src/widgets/argocd/widget.js new file mode 100644 index 000000000..5030adaa1 --- /dev/null +++ b/src/widgets/argocd/widget.js @@ -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; diff --git a/src/widgets/components.js b/src/widgets/components.js index 3cba84d2d..aa476c464 100644 --- a/src/widgets/components.js +++ b/src/widgets/components.js @@ -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")), diff --git a/src/widgets/widgets.js b/src/widgets/widgets.js index 791103789..0cad5346d 100644 --- a/src/widgets/widgets.js +++ b/src/widgets/widgets.js @@ -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,