diff --git a/package.json b/package.json
index 8c774188..14fedf03 100644
--- a/package.json
+++ b/package.json
@@ -34,6 +34,7 @@
"swr": "^1.3.0",
"systeminformation": "^5.17.12",
"tough-cookie": "^4.1.2",
+ "urbackup-server-api": "^0.8.9",
"winston": "^3.8.2",
"xml-js": "^1.6.11"
},
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 78cff342..a1231d8c 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -73,6 +73,9 @@ dependencies:
tough-cookie:
specifier: ^4.1.2
version: 4.1.2
+ urbackup-server-api:
+ specifier: ^0.8.9
+ version: 0.8.9
winston:
specifier: ^3.8.2
version: 3.8.2
@@ -659,6 +662,12 @@ packages:
resolution: {integrity: sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==}
dev: true
+ /async-mutex@0.3.2:
+ resolution: {integrity: sha512-HuTK7E7MT7jZEh1P9GtRW9+aTWiDWWi9InbZ5hjxrnRa39KS4BW04+xLBhYNS2aXhHUIKZSw3gj4Pn1pj+qGAA==}
+ dependencies:
+ tslib: 2.5.0
+ dev: false
+
/async@3.2.4:
resolution: {integrity: sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==}
dev: false
@@ -2691,6 +2700,18 @@ packages:
- babel-plugin-macros
dev: false
+ /node-fetch@2.6.12:
+ resolution: {integrity: sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==}
+ engines: {node: 4.x || >=6.0.0}
+ peerDependencies:
+ encoding: ^0.1.0
+ peerDependenciesMeta:
+ encoding:
+ optional: true
+ dependencies:
+ whatwg-url: 5.0.0
+ dev: false
+
/node-int64@0.4.0:
resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==}
dev: false
@@ -3719,6 +3740,10 @@ packages:
url-parse: 1.5.10
dev: false
+ /tr46@0.0.3:
+ resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
+ dev: false
+
/triple-beam@1.3.0:
resolution: {integrity: sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==}
dev: false
@@ -3822,6 +3847,15 @@ packages:
picocolors: 1.0.0
dev: true
+ /urbackup-server-api@0.8.9:
+ resolution: {integrity: sha512-Igu6A0xSZeMsiN6PWT7zG4aD+iJR5fXT/j5+xwAvnD/vCNfvVrettIsXv6MftxOajvTmtlgaYu8KDoH1EJQ6DQ==}
+ dependencies:
+ async-mutex: 0.3.2
+ node-fetch: 2.6.12
+ transitivePeerDependencies:
+ - encoding
+ dev: false
+
/uri-js@4.4.1:
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
dependencies:
@@ -3867,6 +3901,17 @@ packages:
engines: {node: '>=0.10.0'}
dev: false
+ /webidl-conversions@3.0.1:
+ resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
+ dev: false
+
+ /whatwg-url@5.0.0:
+ resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
+ dependencies:
+ tr46: 0.0.3
+ webidl-conversions: 3.0.1
+ dev: false
+
/which-boxed-primitive@1.0.2:
resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==}
dependencies:
diff --git a/public/locales/en/common.json b/public/locales/en/common.json
index 66aa19ab..46d8e991 100644
--- a/public/locales/en/common.json
+++ b/public/locales/en/common.json
@@ -686,5 +686,11 @@
"maxPlayers": "Max players",
"bots": "Bots",
"ping": "Ping"
+ },
+ "urbackup": {
+ "ok" : "Ok",
+ "errored": "Errors",
+ "noRecent": "Out of Date",
+ "totalUsed": "Used Storage"
}
}
diff --git a/src/widgets/components.js b/src/widgets/components.js
index 0c372ce9..ddaca4cf 100644
--- a/src/widgets/components.js
+++ b/src/widgets/components.js
@@ -90,6 +90,7 @@ const components = {
unifi: dynamic(() => import("./unifi/component")),
unmanic: dynamic(() => import("./unmanic/component")),
uptimekuma: dynamic(() => import("./uptimekuma/component")),
+ urbackup: dynamic(() => import("./urbackup/component")),
watchtower: dynamic(() => import("./watchtower/component")),
whatsupdocker: dynamic(() => import("./whatsupdocker/component")),
xteve: dynamic(() => import("./xteve/component")),
diff --git a/src/widgets/urbackup/component.jsx b/src/widgets/urbackup/component.jsx
new file mode 100644
index 00000000..9b03b595
--- /dev/null
+++ b/src/widgets/urbackup/component.jsx
@@ -0,0 +1,93 @@
+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";
+
+const Status = Object.freeze({
+ ok: Symbol("Ok"),
+ errored: Symbol("Errored"),
+ noRecent: Symbol("No Recent Backups")
+});
+
+function hasRecentBackups(client, maxDays){
+ const days = maxDays || 3;
+ const diffTime = days*24*60*60 // 7 days
+ const recentFile = (client.lastbackup > (Date.now() / 1000 - diffTime));
+ const recentImage = ((client.lastbackup_image > (Date.now() / 1000 - diffTime)||client.image_not_supported));
+ return (recentFile && recentImage);
+}
+
+function determineStatuses(urbackupData) {
+ let ok = 0;
+ let errored = 0;
+ let noRecent = 0;
+ let status;
+ urbackupData.clientStatuses.forEach((client) => {
+ status = Status.noRecent;
+ if (hasRecentBackups(client, urbackupData.maxDays)) {
+ status = (client.file_ok && (client.image_ok || client.image_not_supported)) ? Status.ok : Status.errored;
+ }
+ switch (status) {
+ case Status.ok:
+ ok += 1;
+ break;
+ case Status.errored:
+ errored += 1;
+ break;
+ case Status.noRecent:
+ noRecent += 1;
+ break;
+ default:
+ break;
+ }
+ });
+
+ let totalUsage = false;
+
+ // calculate total disk space if provided
+ if (urbackupData.diskUsage) {
+ totalUsage = 0.0;
+ urbackupData.diskUsage.forEach((client) => {
+ totalUsage += client.used;
+ });
+ }
+
+ return { ok, errored, noRecent, totalUsage };
+}
+
+export default function Component({ service }) {
+ const { t } = useTranslation();
+
+ const { widget } = service;
+
+ const showDiskUsage = widget.fields?.includes('totalUsed')
+
+ const { data: urbackupData, error: urbackupError } = useWidgetAPI(widget, "status");
+
+ if (urbackupError) {
+ return ;
+ }
+
+ if (!urbackupData) {
+ return (
+
+
+
+
+ {showDiskUsage && }
+
+ );
+ }
+
+ const statusData = determineStatuses(urbackupData, widget);
+
+ return (
+
+
+
+
+ {showDiskUsage && }
+
+ );
+}
diff --git a/src/widgets/urbackup/proxy.js b/src/widgets/urbackup/proxy.js
new file mode 100644
index 00000000..3075afd0
--- /dev/null
+++ b/src/widgets/urbackup/proxy.js
@@ -0,0 +1,33 @@
+import {UrbackupServer} from "urbackup-server-api";
+
+import getServiceWidget from "utils/config/service-helpers";
+
+export default async function urbackupProxyHandler(req, res) {
+ const {group, service} = req.query;
+ const serviceWidget = await getServiceWidget(group, service);
+
+ const server = new UrbackupServer({
+ url: serviceWidget.url,
+ username: serviceWidget.username,
+ password: serviceWidget.password
+ });
+
+await (async () => {
+ try {
+ const allClients = await server.getStatus({includeRemoved: false});
+ let diskUsage = false
+ if (serviceWidget.fields?.includes("totalUsed")) {
+ diskUsage = await server.getUsage();
+ }
+ res.status(200).send({
+ clientStatuses: allClients,
+ diskUsage,
+ maxDays: serviceWidget.maxDays
+ });
+ } catch (error) {
+ res.status(500).json({ error: "Something Broke" })
+ }
+ })();
+
+
+}
diff --git a/src/widgets/urbackup/widget.js b/src/widgets/urbackup/widget.js
new file mode 100644
index 00000000..5eac66d0
--- /dev/null
+++ b/src/widgets/urbackup/widget.js
@@ -0,0 +1,7 @@
+import urbackupProxyHandler from "./proxy";
+
+const widget = {
+ proxyHandler: urbackupProxyHandler,
+};
+
+export default widget;
diff --git a/src/widgets/widgets.js b/src/widgets/widgets.js
index 14dc2cd2..46143447 100644
--- a/src/widgets/widgets.js
+++ b/src/widgets/widgets.js
@@ -87,6 +87,7 @@ import uptimekuma from "./uptimekuma/widget";
import watchtower from "./watchtower/widget";
import whatsupdocker from "./whatsupdocker/widget";
import xteve from "./xteve/widget";
+import urbackup from "./urbackup/widget";
const widgets = {
adguard,
@@ -177,6 +178,7 @@ const widgets = {
unifi_console: unifi,
unmanic,
uptimekuma,
+ urbackup,
watchtower,
whatsupdocker,
xteve,