diff --git a/public/locales/en/common.json b/public/locales/en/common.json
index 7081ca7a..b804df36 100755
--- a/public/locales/en/common.json
+++ b/public/locales/en/common.json
@@ -864,5 +864,10 @@
"notifications": "Notifications",
"issues": "Issues",
"pulls": "Pull Requests"
+ },
+ "blueiris": {
+ "serverName": "Server Name",
+ "numberOfActiveCams": "Active Cameras",
+ "numberOfAlerts": "Total Alerts"
}
}
\ No newline at end of file
diff --git a/src/widgets/blueiris/component.jsx b/src/widgets/blueiris/component.jsx
new file mode 100644
index 00000000..ee61fc75
--- /dev/null
+++ b/src/widgets/blueiris/component.jsx
@@ -0,0 +1,37 @@
+import { useTranslation } from "next-i18next";
+
+import Block from "components/services/widget/block";
+import Container from "components/services/widget/container";
+import useWidgetAPI from "utils/proxy/use-widget-api";
+
+export default function Component({ service }) {
+ const { t } = useTranslation();
+
+ const { widget } = service;
+
+ const { data: blueirisData, error: blueirisAPIError } = useWidgetAPI(widget, "unified", {
+ refreshInterval: 5000,
+ });
+
+ if (blueirisAPIError) {
+ return ;
+ }
+
+ if (!blueirisData) {
+ return (
+
+
+
+
+
+ );
+ }
+
+ return (
+
+
+
+
+
+ );
+}
diff --git a/src/widgets/blueiris/proxy.js b/src/widgets/blueiris/proxy.js
new file mode 100644
index 00000000..109c82bc
--- /dev/null
+++ b/src/widgets/blueiris/proxy.js
@@ -0,0 +1,89 @@
+/* eslint-disable no-underscore-dangle */
+import { httpProxy } from "utils/proxy/http";
+import getServiceWidget from "utils/config/service-helpers";
+import createLogger from "utils/logger";
+
+const saltedMd5 = require('salted-md5');
+
+const proxyName = "blueirisProxyHandler";
+
+const logger = createLogger(proxyName);
+let globalUserData = null;
+
+const executeCMD = async (widgetUrl, body) => {
+ const url = `${widgetUrl}/json`
+ const params = {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body
+ };
+ const [status, , data] = await httpProxy(url, params);
+ if (status !== 200) {
+ logger.debug(`HTTP ${status} performing Request`, data);
+ throw new Error(`Failed fetching`);
+ }
+ let jsonData
+ try {
+ jsonData = JSON.parse(data.toString());
+ } catch (e) {
+ logger.debug(`Failed parsing response:`, data);
+ throw new Error(`Failed parsing response`);
+ }
+ return jsonData;
+}
+
+const getWidget = async (req) => {
+ const { group, service } = req.query;
+ if (!group || !service) {
+ logger.debug("Invalid or missing service '%s' or group '%s'", service, group);
+ return null;
+ }
+ const widget = await getServiceWidget(group, service);
+ if (!widget) {
+ logger.debug("Invalid or missing widget for service '%s' in group '%s'", service, group);
+ return null;
+ }
+
+ return widget;
+}
+
+const userLogin = async (widget) => {
+ let res = await executeCMD(widget.url, "{\"cmd\":\"login\"}");
+ const response = await saltedMd5(`${widget.user}:${res.session}:${widget.password}`, '', true);
+ res = await executeCMD(widget.url, `{"cmd":"login", "session":"${res.session}", "response":"${response}"}`);
+ return {"session": res.session, "serverName":res.data["system name"], "url": widget.url};
+}
+
+const activeCamCount = async (widget, session) => {
+ const res = await executeCMD(widget.url, `{"cmd":"camlist", "session":"${session}"}`);
+ const cameraList = res.data.filter(item => !item.optionDisplay.includes('+') && item.isEnabled);
+ return cameraList;
+};
+
+const numberOfAlerts = async (widget, session) => {
+ const res = await activeCamCount(widget, session);
+ const promises = res.map(item => executeCMD(widget.url, `{"cmd":"alertlist", "camera":"${item.optionDisplay}", "session":"${session}"}`));
+ const results = await Promise.all(promises);
+ const alerts = results.reduce((acc, r) => acc + r.data.length, 0);
+ return alerts;
+};
+
+
+export default async function blueirisProxyHandler(req, res) {
+ const widget = await getWidget(req);
+
+ if (!globalUserData) {
+ globalUserData = await userLogin(widget);
+ }
+ const activeCams = await activeCamCount(widget, globalUserData.session);
+ const alerts = await numberOfAlerts(widget, globalUserData.session);
+ const data = {
+ "serverName":globalUserData.serverName,
+ "numberOfActiveCams":activeCams.length,
+ "totalNumberOfAlerts":alerts,
+ };
+ return res.status(200).send(data);
+
+}
diff --git a/src/widgets/blueiris/widget.js b/src/widgets/blueiris/widget.js
new file mode 100644
index 00000000..a539bab6
--- /dev/null
+++ b/src/widgets/blueiris/widget.js
@@ -0,0 +1,14 @@
+import blueirisProxyHandler from "./proxy";
+
+const widget = {
+ api: "{url}/json",
+ proxyHandler: blueirisProxyHandler,
+
+ mappings: {
+ unified: {
+ endpoint: "/",
+ },
+ },
+};
+
+export default widget;
diff --git a/src/widgets/components.js b/src/widgets/components.js
index 12b5e0e6..f589fb98 100644
--- a/src/widgets/components.js
+++ b/src/widgets/components.js
@@ -8,6 +8,7 @@ const components = {
autobrr: dynamic(() => import("./autobrr/component")),
azuredevops: dynamic(() => import("./azuredevops/component")),
bazarr: dynamic(() => import("./bazarr/component")),
+ blueiris: dynamic(() => import("./blueiris/component")),
caddy: dynamic(() => import("./caddy/component")),
calendar: dynamic(() => import("./calendar/component")),
calibreweb: dynamic(() => import("./calibreweb/component")),
diff --git a/src/widgets/widgets.js b/src/widgets/widgets.js
index c8924322..285a0a49 100644
--- a/src/widgets/widgets.js
+++ b/src/widgets/widgets.js
@@ -111,6 +111,7 @@ import xteve from "./xteve/widget";
import jdrssdownloader from "./jdrssdownloader/widget";
import urbackup from "./urbackup/widget";
import wled from "./wled/widget";
+import blueiris from "./blueiris/widget";
const widgets = {
adguard,
@@ -120,6 +121,7 @@ const widgets = {
autobrr,
azuredevops,
bazarr,
+ blueiris,
caddy,
calibreweb,
changedetectionio,