diff --git a/docs/widgets/services/fileflows.md b/docs/widgets/services/fileflows.md
index e05ca527a..a0d37b3ea 100644
--- a/docs/widgets/services/fileflows.md
+++ b/docs/widgets/services/fileflows.md
@@ -5,10 +5,19 @@ description: Fileflows Widget Configuration
Learn more about [FileFlows](https://github.com/revenz/FileFlows).
-Allowed fields: `["queue", "processing", "processed", "time"]`.
+Allowed fields: `["queue", "processing", "processed", "time"]` (version 1) or `["queue", "processing", "processed", "failed"]` (version 2).
+
+| FileFlows Version | Homepage Widget Version |
+| ----------------- | ----------------------- |
+| < 26.10 | 1 (default) |
+| >= 26.10 | 2 |
+
+Version 2 uses the ["Fenrus Dashboard Endpoint"](https://fileflows.com/docs/webconsole/config/settings/general#fenrus-dashboard-endpoint), which must be enabled. Use the token shown there as the `key`.
```yaml
widget:
type: fileflows
url: http://your.fileflows.host:port
+ version: 2 # optional, defaults to 1
+ key: yourfenrustoken # required for version 2
```
diff --git a/public/locales/en/common.json b/public/locales/en/common.json
index f818143d6..978685a35 100644
--- a/public/locales/en/common.json
+++ b/public/locales/en/common.json
@@ -790,7 +790,8 @@
"queue": "Queue",
"processing": "Processing",
"processed": "Processed",
- "time": "Time"
+ "time": "Time",
+ "failed": "Failed"
},
"firefly": {
"networth": "Net Worth",
diff --git a/src/utils/config/service-helpers.js b/src/utils/config/service-helpers.js
index 98375b765..b82e4696d 100644
--- a/src/utils/config/service-helpers.js
+++ b/src/utils/config/service-helpers.js
@@ -619,6 +619,7 @@ export function cleanServiceGroups(groups) {
[
"authentik",
"beszel",
+ "fileflows",
"glances",
"immich",
"jellyfin",
diff --git a/src/utils/proxy/handlers/credentialed.js b/src/utils/proxy/handlers/credentialed.js
index 37c80c363..756c4e57f 100644
--- a/src/utils/proxy/handlers/credentialed.js
+++ b/src/utils/proxy/handlers/credentialed.js
@@ -130,6 +130,10 @@ export default async function credentialedProxyHandler(req, res, map) {
headers.Authorization = widget.key;
} else if (widget.type === "gitlab") {
headers["PRIVATE-TOKEN"] = widget.key;
+ } else if (widget.type === "fileflows") {
+ if (widget.key) {
+ headers["X-Webhook-Token"] = `${widget.key}`;
+ }
} else if (widget.type === "speedtest") {
if (widget.key) {
// v1 does not require a key
diff --git a/src/utils/proxy/handlers/credentialed.test.js b/src/utils/proxy/handlers/credentialed.test.js
index 6f1eb91bb..206f83a44 100644
--- a/src/utils/proxy/handlers/credentialed.test.js
+++ b/src/utils/proxy/handlers/credentialed.test.js
@@ -40,6 +40,7 @@ vi.mock("widgets/widgets", () => ({
checkmk: { api: "{url}/{endpoint}" },
stocks: { api: "{url}/{endpoint}" },
speedtest: { api: "{url}/{endpoint}" },
+ fileflows: { api: "{url}/{endpoint}" },
tubearchivist: { api: "{url}/{endpoint}" },
autobrr: { api: "{url}/{endpoint}" },
jellystat: { api: "{url}/{endpoint}" },
@@ -328,6 +329,7 @@ describe("utils/proxy/handlers/credentialed", () => {
[{ type: "trilium", url: "http://x", key: "k" }, { Authorization: "k" }],
[{ type: "gitlab", url: "http://x", key: "k" }, { "PRIVATE-TOKEN": "k" }],
[{ type: "speedtest", url: "http://x", key: "k" }, { Authorization: "Bearer k" }],
+ [{ type: "fileflows", url: "http://x", key: "k" }, { "X-Webhook-Token": "k" }],
[
{ type: "azuredevops", url: "http://x", key: "k" },
{ Authorization: `Basic ${Buffer.from("$:k").toString("base64")}` },
diff --git a/src/widgets/fileflows/component.jsx b/src/widgets/fileflows/component.jsx
index 46f46c884..f33e05283 100755
--- a/src/widgets/fileflows/component.jsx
+++ b/src/widgets/fileflows/component.jsx
@@ -8,8 +8,9 @@ export default function Component({ service }) {
const { t } = useTranslation();
const { widget } = service;
+ const version = widget.version ?? 1;
- const { data: fileflowsData, error: fileflowsError } = useWidgetAPI(widget, "status");
+ const { data: fileflowsData, error: fileflowsError } = useWidgetAPI(widget, version === 2 ? "status_v2" : "status");
if (fileflowsError) {
return ;
@@ -21,7 +22,18 @@ export default function Component({ service }) {
-
+ {version === 2 ? : }
+
+ );
+ }
+
+ if (version === 2) {
+ return (
+
+
+
+
+
);
}
diff --git a/src/widgets/fileflows/component.test.jsx b/src/widgets/fileflows/component.test.jsx
index 5b0e42771..a9a45e76c 100644
--- a/src/widgets/fileflows/component.test.jsx
+++ b/src/widgets/fileflows/component.test.jsx
@@ -62,4 +62,23 @@ describe("widgets/fileflows/component", () => {
expectBlockValue(container, "fileflows.processed", 3);
expectBlockValue(container, "fileflows.time", "0:00");
});
+
+ it("uses the webhook endpoint and fields for version 2", () => {
+ useWidgetAPI.mockReturnValue({
+ data: { Unprocessed: 4, Processing: 1, Processed: 10, Failed: 2 },
+ error: undefined,
+ });
+
+ const widget = { type: "fileflows", url: "http://x", version: 2 };
+ const { container } = renderWithProviders(, {
+ settings: { hideErrors: false },
+ });
+
+ expect(useWidgetAPI).toHaveBeenCalledWith(widget, "status_v2");
+ expectBlockValue(container, "fileflows.queue", 4);
+ expectBlockValue(container, "fileflows.processing", 1);
+ expectBlockValue(container, "fileflows.processed", 10);
+ expectBlockValue(container, "fileflows.failed", 2);
+ expect(screen.queryByText("fileflows.time")).not.toBeInTheDocument();
+ });
});
diff --git a/src/widgets/fileflows/widget.js b/src/widgets/fileflows/widget.js
index ffa9c0d86..5a39ef8fa 100755
--- a/src/widgets/fileflows/widget.js
+++ b/src/widgets/fileflows/widget.js
@@ -1,12 +1,15 @@
-import genericProxyHandler from "utils/proxy/handlers/generic";
+import credentialedProxyHandler from "utils/proxy/handlers/credentialed";
const widget = {
- api: "{url}/api/{endpoint}",
- proxyHandler: genericProxyHandler,
+ api: "{url}/{endpoint}",
+ proxyHandler: credentialedProxyHandler,
mappings: {
status: {
- endpoint: "status",
+ endpoint: "api/status",
+ },
+ status_v2: {
+ endpoint: "webhook/fenrus",
},
},
};