Skip to content

Commit

Permalink
Feature: Add ArgoCD widget (#4305)
Browse files Browse the repository at this point in the history
Co-authored-by: shamoon <[email protected]>
  • Loading branch information
fcornelius and shamoon authored Nov 19, 2024
1 parent adde687 commit 4a3a4c8
Show file tree
Hide file tree
Showing 10 changed files with 116 additions and 0 deletions.
33 changes: 33 additions & 0 deletions docs/widgets/services/argocd.md
Original file line number Diff line number Diff line change
@@ -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.
1 change: 1 addition & 0 deletions docs/widgets/services/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 10 additions & 0 deletions public/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
1 change: 1 addition & 0 deletions src/utils/proxy/handlers/credentialed.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export default async function credentialedProxyHandler(req, res, map) {
headers["X-gotify-Key"] = `${widget.key}`;
} else if (
[
"argocd",
"authentik",
"cloudflared",
"ghostfolio",
Expand Down
52 changes: 52 additions & 0 deletions src/widgets/argocd/component.jsx
Original file line number Diff line number Diff line change
@@ -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>
);
}
14 changes: 14 additions & 0 deletions src/widgets/argocd/widget.js
Original file line number Diff line number Diff line change
@@ -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;
1 change: 1 addition & 0 deletions src/widgets/components.js
Original file line number Diff line number Diff line change
Expand Up @@ -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")),
Expand Down
1 change: 1 addition & 0 deletions src/widgets/prometheusmetric/component.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions src/widgets/widgets.js
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -130,6 +131,7 @@ import zabbix from "./zabbix/widget";

const widgets = {
adguard,
argocd,
atsumeru,
audiobookshelf,
authentik,
Expand Down

0 comments on commit 4a3a4c8

Please sign in to comment.