diff --git a/public/locales/en/common.json b/public/locales/en/common.json index cd3e40c4e..ba02a70f8 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -429,5 +429,12 @@ "temp_tool": "Tool temp", "temp_bed": "Bed temp", "job_completion": "Completion" + }, + "diskstation": { + "uptime": "Uptime", + "volumeUsage": "Volume Usage", + "volumeTotal": "Total size", + "cpuLoad": "CPU Load", + "memoryUsage": "Memory Usage" } -} \ No newline at end of file +} diff --git a/src/widgets/components.js b/src/widgets/components.js index 601e3f24b..fd3d39752 100644 --- a/src/widgets/components.js +++ b/src/widgets/components.js @@ -8,6 +8,7 @@ const components = { changedetectionio: dynamic(() => import("./changedetectionio/component")), coinmarketcap: dynamic(() => import("./coinmarketcap/component")), deluge: dynamic(() => import("./deluge/component")), + diskstation: dynamic(() => import("./diskstation/component")), downloadstation: dynamic(() => import("./downloadstation/component")), docker: dynamic(() => import("./docker/component")), kubernetes: dynamic(() => import("./kubernetes/component")), diff --git a/src/widgets/diskstation/component.jsx b/src/widgets/diskstation/component.jsx new file mode 100644 index 000000000..aaf69d317 --- /dev/null +++ b/src/widgets/diskstation/component.jsx @@ -0,0 +1,40 @@ +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; + + const { data: dsData, error: dsError } = useWidgetAPI(widget); + + if (dsError) { + return ; + } + + if (!dsData) { + return ( + + + + + + + + ); + } + + + return ( + + + + + + + + ); +} diff --git a/src/widgets/diskstation/proxy.js b/src/widgets/diskstation/proxy.js new file mode 100644 index 000000000..ba9f287bd --- /dev/null +++ b/src/widgets/diskstation/proxy.js @@ -0,0 +1,119 @@ + +import { formatApiCall } from "utils/proxy/api-helpers"; +import { httpProxy } from "utils/proxy/http"; +import createLogger from "utils/logger"; +import getServiceWidget from "utils/config/service-helpers"; + +const proxyName = "synologyProxyHandler"; + +const logger = createLogger(proxyName); + + +function formatUptime(uptime) { + const [hour, minutes, seconds] = uptime.split(":"); + const days = Math.floor(hour/24); + const hours = hour % 24; + + return `${days} d ${hours}h${minutes}m${seconds}s` +} + +async function getApiInfo(api, widget) { + const infoAPI = "{url}/webapi/query.cgi?api=SYNO.API.Info&version=1&method=query" + + const infoUrl = formatApiCall(infoAPI, widget); + // eslint-disable-next-line no-unused-vars + const [status, contentType, data] = await httpProxy(infoUrl); + + if (status === 200) { + const json = JSON.parse(data.toString()); + if (json.data[api]) { + const { path, minVersion, maxVersion } = json.data[api]; + return [ path, minVersion, maxVersion ]; + } + } + return [null, null, null]; +} + +async function login(widget) { + // eslint-disable-next-line no-unused-vars + const [path, minVersion, maxVersion] = await getApiInfo("SYNO.API.Auth", widget); + const authApi = `{url}/webapi/${path}?api=SYNO.API.Auth&version=${maxVersion}&method=login&account={username}&passwd={password}&format=cookie` + const loginUrl = formatApiCall(authApi, widget); + const [status, contentType, data] = await httpProxy(loginUrl); + if (status !== 200) { + return [status, contentType, data]; + } + + const json = JSON.parse(data.toString()); + + if (json?.success !== true) { + let message = "Authentication failed."; + if (json?.error?.code >= 403) message += " 2FA enabled."; + logger.warn("Unable to login. Code: %d", json?.error?.code); + return [401, "application/json", JSON.stringify({ code: json?.error?.code, message })]; + } + + return [status, contentType, data]; +} + +export default async function synologyProxyHandler(req, res) { + const { group, service } = req.query; + + if (!group || !service) { + return res.status(400).json({ error: "Invalid proxy service type" }); + } + + const widget = await getServiceWidget(group, service); + // eslint-disable-next-line no-unused-vars + let [status, contentType, data] = await login(widget); + if (status !== 200) { + return res.status(status).end(data) + } + const { sid } = JSON.parse(data.toString()).data; + let api = "SYNO.Core.System"; + // eslint-disable-next-line no-unused-vars + let [ path, minVersion, maxVersion] = await getApiInfo(api, widget); + + const storageUrl = `${widget.url}/webapi/${path}?api=${api}&version=${maxVersion}&method=info&type="storage"&_sid=${sid}`; + + [status, contentType, data] = await httpProxy(storageUrl ); + + if (status !== 200) { + return res.status(status).set("Content-Type", contentType).send(data); + } + let json=JSON.parse(data.toString()); + if (json?.success !== true) { + return res.status(401).json({ error: "Error getting volume stats" }); + } + const totalSize = parseFloat(json.data.vol_info[0].total_size); + const usedVolume = 100 * parseFloat(json.data.vol_info[0].used_size) / parseFloat(json.data.vol_info[0].total_size); + + const healthUrl = `${widget.url}/webapi/${path}?api=${api}&version=${maxVersion}&method=info&_sid=${sid}`; + [status, contentType, data] = await httpProxy(healthUrl); + + if (status !== 200) { + return res.status(status).set("Content-Type", contentType).send(data); + } + json=JSON.parse(data.toString()); + if (json?.success !== true) { + return res.status(401).json({ error: "Error getting uptime" }); + } + const uptime = formatUptime(json.data.up_time); + api = "SYNO.Core.System.Utilization"; + // eslint-disable-next-line no-unused-vars + [ path, minVersion, maxVersion] = await getApiInfo(api, widget); + const sysUrl = `${widget.url}/webapi/${path}?api=${api}&version=${maxVersion}&method=get&_sid=${sid}`; + [status, contentType, data] = await httpProxy(sysUrl ); + + const memoryUsage = 100 - (100 * (parseFloat(JSON.parse(data.toString()).data.memory.avail_real) + parseFloat(JSON.parse(data.toString()).data.memory.cached)) / parseFloat(JSON.parse(data.toString()).data.memory.total_real)); + const cpuLoad = parseFloat(JSON.parse(data.toString()).data.cpu.user_load) + parseFloat(JSON.parse(data.toString()).data.cpu.system_load); + + if (contentType) res.setHeader("Content-Type", contentType); + return res.status(status).send(JSON.stringify({ + uptime, + usedVolume, + totalSize, + memoryUsage, + cpuLoad, + })); +} diff --git a/src/widgets/diskstation/widget.js b/src/widgets/diskstation/widget.js new file mode 100644 index 000000000..65a585867 --- /dev/null +++ b/src/widgets/diskstation/widget.js @@ -0,0 +1,7 @@ +import synologyProxyHandler from "./proxy"; + +const widget = { + proxyHandler: synologyProxyHandler, +}; + +export default widget; diff --git a/src/widgets/widgets.js b/src/widgets/widgets.js index 55436cec0..0b3376143 100644 --- a/src/widgets/widgets.js +++ b/src/widgets/widgets.js @@ -5,6 +5,7 @@ import bazarr from "./bazarr/widget"; import changedetectionio from "./changedetectionio/widget"; import coinmarketcap from "./coinmarketcap/widget"; import deluge from "./deluge/widget"; +import diskstation from "./diskstation/widget"; import downloadstation from "./downloadstation/widget"; import emby from "./emby/widget"; import flood from "./flood/widget"; @@ -63,7 +64,7 @@ const widgets = { changedetectionio, coinmarketcap, deluge, - diskstation: downloadstation, + diskstation, downloadstation, emby, flood,