mirror of
https://github.com/karl0ss/homepage.git
synced 2025-04-29 12:03:41 +01:00
Feature: UrBackup Widget (#1735)
* Add initial UrBackup widget with counts of ok, errored, and out-of date clients * Add configurable number of days since last backup before a client is considered out-of-date * Don't count a lack of recent (or error free) image backup if image backup isn't supported. * Add support for reporting total disk usage * add support for "fields" from services.yaml * fix field filtering, syntax * Consolidate urbackup code, syntax changes * Revert pnpm changes * re-add urbackup-server-api --------- Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
This commit is contained in:
parent
2f4d4e52be
commit
992516cebd
@ -34,6 +34,7 @@
|
|||||||
"swr": "^1.3.0",
|
"swr": "^1.3.0",
|
||||||
"systeminformation": "^5.17.12",
|
"systeminformation": "^5.17.12",
|
||||||
"tough-cookie": "^4.1.2",
|
"tough-cookie": "^4.1.2",
|
||||||
|
"urbackup-server-api": "^0.8.9",
|
||||||
"winston": "^3.8.2",
|
"winston": "^3.8.2",
|
||||||
"xml-js": "^1.6.11"
|
"xml-js": "^1.6.11"
|
||||||
},
|
},
|
||||||
|
45
pnpm-lock.yaml
generated
45
pnpm-lock.yaml
generated
@ -73,6 +73,9 @@ dependencies:
|
|||||||
tough-cookie:
|
tough-cookie:
|
||||||
specifier: ^4.1.2
|
specifier: ^4.1.2
|
||||||
version: 4.1.2
|
version: 4.1.2
|
||||||
|
urbackup-server-api:
|
||||||
|
specifier: ^0.8.9
|
||||||
|
version: 0.8.9
|
||||||
winston:
|
winston:
|
||||||
specifier: ^3.8.2
|
specifier: ^3.8.2
|
||||||
version: 3.8.2
|
version: 3.8.2
|
||||||
@ -659,6 +662,12 @@ packages:
|
|||||||
resolution: {integrity: sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==}
|
resolution: {integrity: sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==}
|
||||||
dev: true
|
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:
|
/async@3.2.4:
|
||||||
resolution: {integrity: sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==}
|
resolution: {integrity: sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==}
|
||||||
dev: false
|
dev: false
|
||||||
@ -2691,6 +2700,18 @@ packages:
|
|||||||
- babel-plugin-macros
|
- babel-plugin-macros
|
||||||
dev: false
|
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:
|
/node-int64@0.4.0:
|
||||||
resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==}
|
resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==}
|
||||||
dev: false
|
dev: false
|
||||||
@ -3719,6 +3740,10 @@ packages:
|
|||||||
url-parse: 1.5.10
|
url-parse: 1.5.10
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/tr46@0.0.3:
|
||||||
|
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/triple-beam@1.3.0:
|
/triple-beam@1.3.0:
|
||||||
resolution: {integrity: sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==}
|
resolution: {integrity: sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==}
|
||||||
dev: false
|
dev: false
|
||||||
@ -3822,6 +3847,15 @@ packages:
|
|||||||
picocolors: 1.0.0
|
picocolors: 1.0.0
|
||||||
dev: true
|
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:
|
/uri-js@4.4.1:
|
||||||
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
|
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -3867,6 +3901,17 @@ packages:
|
|||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
dev: false
|
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:
|
/which-boxed-primitive@1.0.2:
|
||||||
resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==}
|
resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -686,5 +686,11 @@
|
|||||||
"maxPlayers": "Max players",
|
"maxPlayers": "Max players",
|
||||||
"bots": "Bots",
|
"bots": "Bots",
|
||||||
"ping": "Ping"
|
"ping": "Ping"
|
||||||
|
},
|
||||||
|
"urbackup": {
|
||||||
|
"ok" : "Ok",
|
||||||
|
"errored": "Errors",
|
||||||
|
"noRecent": "Out of Date",
|
||||||
|
"totalUsed": "Used Storage"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -90,6 +90,7 @@ const components = {
|
|||||||
unifi: dynamic(() => import("./unifi/component")),
|
unifi: dynamic(() => import("./unifi/component")),
|
||||||
unmanic: dynamic(() => import("./unmanic/component")),
|
unmanic: dynamic(() => import("./unmanic/component")),
|
||||||
uptimekuma: dynamic(() => import("./uptimekuma/component")),
|
uptimekuma: dynamic(() => import("./uptimekuma/component")),
|
||||||
|
urbackup: dynamic(() => import("./urbackup/component")),
|
||||||
watchtower: dynamic(() => import("./watchtower/component")),
|
watchtower: dynamic(() => import("./watchtower/component")),
|
||||||
whatsupdocker: dynamic(() => import("./whatsupdocker/component")),
|
whatsupdocker: dynamic(() => import("./whatsupdocker/component")),
|
||||||
xteve: dynamic(() => import("./xteve/component")),
|
xteve: dynamic(() => import("./xteve/component")),
|
||||||
|
93
src/widgets/urbackup/component.jsx
Normal file
93
src/widgets/urbackup/component.jsx
Normal file
@ -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 <Container service={service} error={urbackupError} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!urbackupData) {
|
||||||
|
return (
|
||||||
|
<Container service={service}>
|
||||||
|
<Block label="urbackup.ok" />
|
||||||
|
<Block label="urbackup.errored" />
|
||||||
|
<Block label="urbackup.noRecent" />
|
||||||
|
{showDiskUsage && <Block label="urbackup.totalUsed" />}
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const statusData = determineStatuses(urbackupData, widget);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container service={service}>
|
||||||
|
<Block label="urbackup.ok" value={t("common.number", { value: parseInt(statusData.ok, 10) })} />
|
||||||
|
<Block label="urbackup.errored" value={t("common.number", { value: parseInt(statusData.errored, 10) })} />
|
||||||
|
<Block label="urbackup.noRecent" value={t("common.number", { value: parseInt(statusData.noRecent, 10) })} />
|
||||||
|
{showDiskUsage && <Block label="urbackup.totalUsed" value={t("common.bbytes", {value: parseFloat(statusData.totalUsage, 10)})} />}
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
33
src/widgets/urbackup/proxy.js
Normal file
33
src/widgets/urbackup/proxy.js
Normal file
@ -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" })
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
|
||||||
|
}
|
7
src/widgets/urbackup/widget.js
Normal file
7
src/widgets/urbackup/widget.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import urbackupProxyHandler from "./proxy";
|
||||||
|
|
||||||
|
const widget = {
|
||||||
|
proxyHandler: urbackupProxyHandler,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default widget;
|
@ -87,6 +87,7 @@ import uptimekuma from "./uptimekuma/widget";
|
|||||||
import watchtower from "./watchtower/widget";
|
import watchtower from "./watchtower/widget";
|
||||||
import whatsupdocker from "./whatsupdocker/widget";
|
import whatsupdocker from "./whatsupdocker/widget";
|
||||||
import xteve from "./xteve/widget";
|
import xteve from "./xteve/widget";
|
||||||
|
import urbackup from "./urbackup/widget";
|
||||||
|
|
||||||
const widgets = {
|
const widgets = {
|
||||||
adguard,
|
adguard,
|
||||||
@ -177,6 +178,7 @@ const widgets = {
|
|||||||
unifi_console: unifi,
|
unifi_console: unifi,
|
||||||
unmanic,
|
unmanic,
|
||||||
uptimekuma,
|
uptimekuma,
|
||||||
|
urbackup,
|
||||||
watchtower,
|
watchtower,
|
||||||
whatsupdocker,
|
whatsupdocker,
|
||||||
xteve,
|
xteve,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user