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,