diff --git a/public/locales/en/common.json b/public/locales/en/common.json index 52db2cb40..f144182f6 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -447,5 +447,12 @@ "photos": "Photos", "videos": "Videos", "storage": "Storage" + }, + "uptimekuma": { + "up": "Sites Up", + "down": "Sites Down", + "uptime": "Uptime", + "incident": "Incident", + "m": "m" } -} +} \ No newline at end of file diff --git a/src/widgets/components.js b/src/widgets/components.js index 43a46fa90..505807c48 100644 --- a/src/widgets/components.js +++ b/src/widgets/components.js @@ -63,6 +63,7 @@ const components = { watchtower: dynamic(() => import("./watchtower/component")), xteve: dynamic(() => import("./xteve/component")), immich: dynamic(() => import("./immich/component")), + uptimekuma: dynamic(() => import("./uptimekuma/component")), }; export default components; diff --git a/src/widgets/uptimekuma/component.jsx b/src/widgets/uptimekuma/component.jsx new file mode 100644 index 000000000..d71f9a63a --- /dev/null +++ b/src/widgets/uptimekuma/component.jsx @@ -0,0 +1,55 @@ +import { useTranslation } from "next-i18next"; + +import Container from "components/services/widget/container"; +import useWidgetAPI from "utils/proxy/use-widget-api"; +import Block from "components/services/widget/block"; + +export default function Component({ service }) { + const { t } = useTranslation(); + + const { widget } = service; + + const { data: statusData, error: statusError } = useWidgetAPI(widget, "status_page"); + const { data: heartbeatData, error: heartbeatError } = useWidgetAPI(widget, "heartbeat"); + + if (statusError || heartbeatError) { + return ; + } + + if (!statusData || !heartbeatData) { + return ( + + + + + + + ); + } + + let sitesUp = 0; + let sitesDown = 0; + Object.values(heartbeatData.heartbeatList).forEach((siteList) => { + const lastHeartbeat = siteList[siteList.length - 1]; + if (lastHeartbeat?.status === 1) { + sitesUp += 1; + } else { + sitesDown += 1; + } + }); + + // Adapted from https://github.com/bastienwirtz/homer/blob/b7cd8f9482e6836a96b354b11595b03b9c3d67cd/src/components/services/UptimeKuma.vue#L105 + const uptimeList = Object.values(heartbeatData.uptimeList); + const percent = uptimeList.reduce((a, b) => a + b, 0) / uptimeList.length || 0; + const uptime = (percent * 100).toFixed(1); + const incidentTime = statusData.incident ? (Math.abs(new Date(statusData.incident?.createdDate) - new Date()) / 1000) / (60 * 60) : null; + + return ( + + + + + {incidentTime && } + + ); +} diff --git a/src/widgets/uptimekuma/widget.js b/src/widgets/uptimekuma/widget.js new file mode 100644 index 000000000..928534b3a --- /dev/null +++ b/src/widgets/uptimekuma/widget.js @@ -0,0 +1,18 @@ +// import credentialedProxyHandler from "utils/proxy/handlers/credentialed"; +import genericProxyHandler from "utils/proxy/handlers/generic"; + +const widget = { + api: "{url}/api/{endpoint}/{slug}", + proxyHandler: genericProxyHandler, + + mappings: { + status_page: { + endpoint: "status-page", + }, + heartbeat: { + endpoint: "status-page/heartbeat", + }, + } +}; + +export default widget; diff --git a/src/widgets/widgets.js b/src/widgets/widgets.js index 133903fbe..7da77a0a6 100644 --- a/src/widgets/widgets.js +++ b/src/widgets/widgets.js @@ -57,6 +57,7 @@ import unifi from "./unifi/widget"; import watchtower from "./watchtower/widget"; import xteve from "./xteve/widget"; import immich from "./immich/widget"; +import uptimekuma from "./uptimekuma/widget"; const widgets = { adguard, @@ -121,6 +122,7 @@ const widgets = { watchtower, xteve, immich, + uptimekuma, }; export default widgets;