From 250351f735d33cfdbffce9e80be4f8de4a93aac7 Mon Sep 17 00:00:00 2001
From: shamoon <4887959+shamoon@users.noreply.github.com>
Date: Sun, 17 Nov 2024 16:16:18 -0800
Subject: [PATCH 1/4] Fix: use duration for audiobookshelf books too
See #4228
---
src/widgets/audiobookshelf/component.jsx | 7 ++-----
1 file changed, 2 insertions(+), 5 deletions(-)
diff --git a/src/widgets/audiobookshelf/component.jsx b/src/widgets/audiobookshelf/component.jsx
index 6eb78638..06439e8f 100755
--- a/src/widgets/audiobookshelf/component.jsx
+++ b/src/widgets/audiobookshelf/component.jsx
@@ -46,11 +46,8 @@ export default function Component({ service }) {
From adde687331294185a652202e99ef8fa2e5351e80 Mon Sep 17 00:00:00 2001
From: shamoon <4887959+shamoon@users.noreply.github.com>
Date: Mon, 18 Nov 2024 07:31:05 -0800
Subject: [PATCH 2/4] Try publish to docker hub
---
.github/workflows/docker-publish.yml | 35 ++++++++--------------------
1 file changed, 10 insertions(+), 25 deletions(-)
diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml
index 368040cc..2ac8b3e8 100644
--- a/.github/workflows/docker-publish.yml
+++ b/.github/workflows/docker-publish.yml
@@ -26,8 +26,6 @@ on:
merge_group:
env:
- # Use docker.io for Docker Hub if empty
- REGISTRY: ghcr.io
# github.repository as /
IMAGE_NAME: ${{ github.repository }}
@@ -66,14 +64,6 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v4
- # Install the cosign tool except on PR
- # https://github.com/sigstore/cosign-installer
- - name: Install cosign
- if: github.event_name != 'pull_request'
- uses: sigstore/cosign-installer@main
- with:
- cosign-release: 'v1.13.1' # optional
-
# Setup QEMU
# https://github.com/marketplace/actions/docker-setup-buildx#with-qemu
- name: Setup QEMU
@@ -99,9 +89,15 @@ jobs:
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
- registry: ${{ env.REGISTRY }}
+ registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
+ - name: Login to Docker Hub
+ if: github.event_name != 'pull_request'
+ uses: docker/login-action@v3
+ with:
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
# Extract metadata (tags, labels) for Docker
# https://github.com/docker/metadata-action
@@ -109,7 +105,9 @@ jobs:
id: meta
uses: docker/metadata-action@v5
with:
- images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
+ images: |
+ ${{ env.IMAGE_NAME }}
+ ghcr.io/${{ env.IMAGE_NAME }}
flavor: |
latest=auto
@@ -133,19 +131,6 @@ jobs:
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max
- # Sign the resulting Docker image digest except on PRs.
- # This will only write to the public Rekor transparency log when the Docker
- # repository is public to avoid leaking data. If you would like to publish
- # transparency data even for private images, pass --force to cosign below.
- # https://github.com/sigstore/cosign
-# - name: Sign the published Docker image
-# if: ${{ github.event_name != 'pull_request' }}
-# env:
-# COSIGN_EXPERIMENTAL: "true"
-# # This step uses the identity token to provision an ephemeral certificate
-# # against the sigstore community Fulcio instance.
-# run: echo "${{ steps.meta.outputs.tags }}" | xargs -I {} cosign sign {}@${{ steps.build-and-push.outputs.digest }}
-
# Temp fix
# https://github.com/docker/build-push-action/issues/252
# https://github.com/moby/buildkit/issues/1896
From 4a3a4c846e122707e6bb6eacd5805be33e5236bf Mon Sep 17 00:00:00 2001
From: Felix Cornelius <9767036+fcornelius@users.noreply.github.com>
Date: Tue, 19 Nov 2024 22:59:52 +0100
Subject: [PATCH 3/4] Feature: Add ArgoCD widget (#4305)
Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
---
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 | 52 ++++++++++++++++++++++
src/widgets/argocd/widget.js | 14 ++++++
src/widgets/components.js | 1 +
src/widgets/prometheusmetric/component.jsx | 1 +
src/widgets/widgets.js | 2 +
10 files changed, 116 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 00000000..6a81b8db
--- /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 (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.
diff --git a/docs/widgets/services/index.md b/docs/widgets/services/index.md
index 8ea2e933..ae506f08 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](argocd.md)
- [Atsumeru](atsumeru.md)
- [Audiobookshelf](audiobookshelf.md)
- [Authentik](authentik.md)
diff --git a/mkdocs.yml b/mkdocs.yml
index 42abce30..1e9d59cc 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 81576984..ab7dcfc9 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": "Out Of Sync",
+ "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 eb2aab69..8d4340c2 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 00000000..d3b51936
--- /dev/null
+++ b/src/widgets/argocd/component.jsx
@@ -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 ;
+ }
+
+ 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 00000000..5030adaa
--- /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 3cba84d2..aa476c46 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/prometheusmetric/component.jsx b/src/widgets/prometheusmetric/component.jsx
index 347aaa0c..350a6b7d 100644
--- a/src/widgets/prometheusmetric/component.jsx
+++ b/src/widgets/prometheusmetric/component.jsx
@@ -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;
diff --git a/src/widgets/widgets.js b/src/widgets/widgets.js
index 79110378..0cad5346 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,
From 94bbcbe1fb868f8b60cefe5331d581760fecde32 Mon Sep 17 00:00:00 2001
From: Florian Geckeler <43751896+fgeck@users.noreply.github.com>
Date: Fri, 22 Nov 2024 00:32:04 +0100
Subject: [PATCH 4/4] Feature: Spoolman Widget (#3959)
Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
---
docs/widgets/services/spoolman.md | 15 +++++++
mkdocs.yml | 1 +
public/locales/en/common.json | 3 ++
src/utils/config/service-helpers.js | 6 +++
src/widgets/components.js | 1 +
src/widgets/spoolman/component.jsx | 63 +++++++++++++++++++++++++++++
src/widgets/spoolman/widget.js | 14 +++++++
src/widgets/widgets.js | 2 +
8 files changed, 105 insertions(+)
create mode 100644 docs/widgets/services/spoolman.md
create mode 100644 src/widgets/spoolman/component.jsx
create mode 100644 src/widgets/spoolman/widget.js
diff --git a/docs/widgets/services/spoolman.md b/docs/widgets/services/spoolman.md
new file mode 100644
index 00000000..5baa9268
--- /dev/null
+++ b/docs/widgets/services/spoolman.md
@@ -0,0 +1,15 @@
+---
+title: Spoolman
+description: Spoolman Widget Configuration
+---
+
+Learn more about [Spoolman](https://github.com/Donkie/Spoolman).
+
+4 spools are displayed by default. If more than 4 spools are configured in spoolman you can use the spoolIds configuration option to control which are displayed.
+
+```yaml
+widget:
+ type: spoolman
+ url: http://spoolman.host.or.ip
+ spoolIds: [1, 2, 3, 4] # optional
+```
diff --git a/mkdocs.yml b/mkdocs.yml
index 1e9d59cc..5b350d71 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -138,6 +138,7 @@ nav:
- widgets/services/scrutiny.md
- widgets/services/sonarr.md
- widgets/services/speedtest-tracker.md
+ - widgets/services/spoolman.md
- widgets/services/stash.md
- widgets/services/stocks.md
- widgets/services/swagdashboard.md
diff --git a/public/locales/en/common.json b/public/locales/en/common.json
index ab7dcfc9..5abb9a4b 100644
--- a/public/locales/en/common.json
+++ b/public/locales/en/common.json
@@ -998,5 +998,8 @@
"progressing": "Progressing",
"missing": "Missing",
"suspended": "Suspended"
+ },
+ "spoolman": {
+ "loading": "Loading"
}
}
diff --git a/src/utils/config/service-helpers.js b/src/utils/config/service-helpers.js
index 1566a135..ea82c735 100644
--- a/src/utils/config/service-helpers.js
+++ b/src/utils/config/service-helpers.js
@@ -492,6 +492,9 @@ export function cleanServiceGroups(groups) {
// technitium
range,
+
+ // spoolman
+ spoolIds,
} = cleanedService.widget;
let fieldsList = fields;
@@ -653,6 +656,9 @@ export function cleanServiceGroups(groups) {
if (metrics) cleanedService.widget.metrics = metrics;
if (refreshInterval) cleanedService.widget.refreshInterval = refreshInterval;
}
+ if (type === "spoolman") {
+ if (spoolIds !== undefined) cleanedService.widget.spoolIds = spoolIds;
+ }
}
return cleanedService;
diff --git a/src/widgets/components.js b/src/widgets/components.js
index aa476c46..bea37cf2 100644
--- a/src/widgets/components.js
+++ b/src/widgets/components.js
@@ -111,6 +111,7 @@ const components = {
scrutiny: dynamic(() => import("./scrutiny/component")),
sonarr: dynamic(() => import("./sonarr/component")),
speedtest: dynamic(() => import("./speedtest/component")),
+ spoolman: dynamic(() => import("./spoolman/component")),
stash: dynamic(() => import("./stash/component")),
stocks: dynamic(() => import("./stocks/component")),
strelaysrv: dynamic(() => import("./strelaysrv/component")),
diff --git a/src/widgets/spoolman/component.jsx b/src/widgets/spoolman/component.jsx
new file mode 100644
index 00000000..523ecea7
--- /dev/null
+++ b/src/widgets/spoolman/component.jsx
@@ -0,0 +1,63 @@
+import { useTranslation } from "next-i18next";
+
+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 { t } = useTranslation();
+ const { widget } = service;
+
+ // eslint-disable-next-line prefer-const
+ let { data: spoolData, error: spoolError } = useWidgetAPI(widget, "spools");
+
+ if (spoolError) {
+ return ;
+ }
+
+ if (!spoolData) {
+ const nBlocksGuess = widget.spoolIds?.length ?? 4;
+ return (
+
+ {[...Array(nBlocksGuess)].map((_, i) => (
+ // eslint-disable-next-line react/no-array-index-key
+
+ ))}
+
+ );
+ }
+
+ if (spoolData.error || spoolData.message) {
+ return ;
+ }
+
+ if (spoolData.length === 0) {
+ return (
+
+
+
+ );
+ }
+
+ if (widget.spoolIds?.length) {
+ spoolData = spoolData.filter((spool) => widget.spoolIds.includes(spool.id));
+ }
+
+ if (spoolData.length > 4) {
+ spoolData = spoolData.slice(0, 4);
+ }
+
+ return (
+
+ {spoolData.map((spool) => (
+
+ ))}
+
+ );
+}
diff --git a/src/widgets/spoolman/widget.js b/src/widgets/spoolman/widget.js
new file mode 100644
index 00000000..2c8a3475
--- /dev/null
+++ b/src/widgets/spoolman/widget.js
@@ -0,0 +1,14 @@
+import credentialedProxyHandler from "utils/proxy/handlers/credentialed";
+
+const widget = {
+ api: "{url}/api/v1/{endpoint}",
+ proxyHandler: credentialedProxyHandler,
+
+ mappings: {
+ spools: {
+ endpoint: "spool",
+ },
+ },
+};
+
+export default widget;
diff --git a/src/widgets/widgets.js b/src/widgets/widgets.js
index 0cad5346..8eb3f51f 100644
--- a/src/widgets/widgets.js
+++ b/src/widgets/widgets.js
@@ -102,6 +102,7 @@ import sabnzbd from "./sabnzbd/widget";
import scrutiny from "./scrutiny/widget";
import sonarr from "./sonarr/widget";
import speedtest from "./speedtest/widget";
+import spoolman from "./spoolman/widget";
import stash from "./stash/widget";
import stocks from "./stocks/widget";
import strelaysrv from "./strelaysrv/widget";
@@ -237,6 +238,7 @@ const widgets = {
scrutiny,
sonarr,
speedtest,
+ spoolman,
stash,
stocks,
strelaysrv,