Merge branch 'dev' into feature/gitlab-widget
# Conflicts: # public/locales/en/common.json
This commit is contained in:
commit
6dddb1c031
35
.github/workflows/docker-publish.yml
vendored
35
.github/workflows/docker-publish.yml
vendored
@ -26,8 +26,6 @@ on:
|
||||
merge_group:
|
||||
|
||||
env:
|
||||
# Use docker.io for Docker Hub if empty
|
||||
REGISTRY: ghcr.io
|
||||
# github.repository as <account>/<repo>
|
||||
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
|
||||
|
||||
33
docs/widgets/services/argocd.md
Normal file
33
docs/widgets/services/argocd.md
Normal file
@ -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)
|
||||
|
||||
15
docs/widgets/services/spoolman.md
Normal file
15
docs/widgets/services/spoolman.md
Normal file
@ -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
|
||||
```
|
||||
@ -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
|
||||
@ -138,6 +139,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
|
||||
|
||||
@ -989,6 +989,19 @@
|
||||
"disk": "Disk",
|
||||
"network": "NET"
|
||||
},
|
||||
"argocd": {
|
||||
"apps": "Apps",
|
||||
"synced": "Synced",
|
||||
"outOfSync": "Out Of Sync",
|
||||
"healthy": "Healthy",
|
||||
"degraded": "Degraded",
|
||||
"progressing": "Progressing",
|
||||
"missing": "Missing",
|
||||
"suspended": "Suspended"
|
||||
},
|
||||
"spoolman": {
|
||||
"loading": "Loading"
|
||||
},
|
||||
"gitlab": {
|
||||
"events": "Events",
|
||||
"issues": "Issues",
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -36,6 +36,7 @@ export default async function credentialedProxyHandler(req, res, map) {
|
||||
headers["X-gotify-Key"] = `${widget.key}`;
|
||||
} else if (
|
||||
[
|
||||
"argocd",
|
||||
"authentik",
|
||||
"cloudflared",
|
||||
"ghostfolio",
|
||||
|
||||
52
src/widgets/argocd/component.jsx
Normal file
52
src/widgets/argocd/component.jsx
Normal file
@ -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
src/widgets/argocd/widget.js
Normal file
14
src/widgets/argocd/widget.js
Normal file
@ -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;
|
||||
@ -46,11 +46,8 @@ export default function Component({ service }) {
|
||||
<Block label="audiobookshelf.books" value={t("common.number", { value: totalBooks })} />
|
||||
<Block
|
||||
label="audiobookshelf.booksDuration"
|
||||
value={t("common.number", {
|
||||
value: totalBooksDuration / 60,
|
||||
maximumFractionDigits: 0,
|
||||
style: "unit",
|
||||
unit: "minute",
|
||||
value={t("common.duration", {
|
||||
value: totalBooksDuration,
|
||||
})}
|
||||
/>
|
||||
</Container>
|
||||
|
||||
@ -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")),
|
||||
@ -111,6 +112,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")),
|
||||
|
||||
@ -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;
|
||||
|
||||
63
src/widgets/spoolman/component.jsx
Normal file
63
src/widgets/spoolman/component.jsx
Normal file
@ -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 <Container service={service} error={spoolError} />;
|
||||
}
|
||||
|
||||
if (!spoolData) {
|
||||
const nBlocksGuess = widget.spoolIds?.length ?? 4;
|
||||
return (
|
||||
<Container service={service}>
|
||||
{[...Array(nBlocksGuess)].map((_, i) => (
|
||||
// eslint-disable-next-line react/no-array-index-key
|
||||
<Block key={i} label="spoolman.loading" />
|
||||
))}
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
if (spoolData.error || spoolData.message) {
|
||||
return <Container service={service} error={spoolData?.error ?? spoolData} />;
|
||||
}
|
||||
|
||||
if (spoolData.length === 0) {
|
||||
return (
|
||||
<Container service={service}>
|
||||
<Block label="spoolman.noSpools" />
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
if (widget.spoolIds?.length) {
|
||||
spoolData = spoolData.filter((spool) => widget.spoolIds.includes(spool.id));
|
||||
}
|
||||
|
||||
if (spoolData.length > 4) {
|
||||
spoolData = spoolData.slice(0, 4);
|
||||
}
|
||||
|
||||
return (
|
||||
<Container service={service}>
|
||||
{spoolData.map((spool) => (
|
||||
<Block
|
||||
key={spool.id}
|
||||
label={spool.filament.name}
|
||||
value={t("common.percent", {
|
||||
value: (spool.remaining_weight / spool.initial_weight) * 100,
|
||||
})}
|
||||
/>
|
||||
))}
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
14
src/widgets/spoolman/widget.js
Normal file
14
src/widgets/spoolman/widget.js
Normal file
@ -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;
|
||||
@ -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";
|
||||
@ -102,6 +103,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";
|
||||
@ -131,6 +133,7 @@ import zabbix from "./zabbix/widget";
|
||||
|
||||
const widgets = {
|
||||
adguard,
|
||||
argocd,
|
||||
atsumeru,
|
||||
audiobookshelf,
|
||||
authentik,
|
||||
@ -237,6 +240,7 @@ const widgets = {
|
||||
scrutiny,
|
||||
sonarr,
|
||||
speedtest,
|
||||
spoolman,
|
||||
stash,
|
||||
stocks,
|
||||
strelaysrv,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user