From 59448b72770f19987f0f4bccf9ce5e6d0b613747 Mon Sep 17 00:00:00 2001
From: henter <43324482+hen-ter@users.noreply.github.com>
Date: Tue, 11 Apr 2023 20:54:13 +0200
Subject: [PATCH 1/2] Added homeassistant widget
---
public/locales/en/common.json | 5 ++
src/widgets/components.js | 1 +
src/widgets/homeassistant/component.jsx | 16 +++++
src/widgets/homeassistant/proxy.js | 87 +++++++++++++++++++++++++
src/widgets/homeassistant/widget.js | 7 ++
src/widgets/widgets.js | 2 +
6 files changed, 118 insertions(+)
create mode 100644 src/widgets/homeassistant/component.jsx
create mode 100644 src/widgets/homeassistant/proxy.js
create mode 100644 src/widgets/homeassistant/widget.js
diff --git a/public/locales/en/common.json b/public/locales/en/common.json
index 3eb3c4786..c46e6c38b 100755
--- a/public/locales/en/common.json
+++ b/public/locales/en/common.json
@@ -571,5 +571,10 @@
"books": "Books",
"podcastsDuration": "Duration",
"booksDuration": "Duration"
+ },
+ "homeassistant": {
+ "people_home": "People Home",
+ "lights_on": "Lights On",
+ "switches_on": "Switches On"
}
}
diff --git a/src/widgets/components.js b/src/widgets/components.js
index aa54e2462..ec7be376e 100644
--- a/src/widgets/components.js
+++ b/src/widgets/components.js
@@ -22,6 +22,7 @@ const components = {
gotify: dynamic(() => import("./gotify/component")),
grafana: dynamic(() => import("./grafana/component")),
hdhomerun: dynamic(() => import("./hdhomerun/component")),
+ homeassistant: dynamic(() => import("./homeassistant/component")),
homebridge: dynamic(() => import("./homebridge/component")),
healthchecks: dynamic(() => import("./healthchecks/component")),
immich: dynamic(() => import("./immich/component")),
diff --git a/src/widgets/homeassistant/component.jsx b/src/widgets/homeassistant/component.jsx
new file mode 100644
index 000000000..2ba809285
--- /dev/null
+++ b/src/widgets/homeassistant/component.jsx
@@ -0,0 +1,16 @@
+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 { widget } = service;
+
+ const { data, error } = useWidgetAPI(widget, "", { refreshInterval: 60000 });
+ if (error) {
+ return ;
+ }
+ const panels = [];
+ data?.forEach(d => panels.push());
+
+ return {panels};
+}
diff --git a/src/widgets/homeassistant/proxy.js b/src/widgets/homeassistant/proxy.js
new file mode 100644
index 000000000..2c6a517be
--- /dev/null
+++ b/src/widgets/homeassistant/proxy.js
@@ -0,0 +1,87 @@
+import { httpProxy } from "utils/proxy/http";
+import getServiceWidget from "utils/config/service-helpers";
+import createLogger from "utils/logger";
+
+const logger = createLogger("homeassistantProxyHandler");
+
+const defaultQueries = [
+ {
+ template: "{{ states.person|selectattr('state','equalto','home')|list|length }} / {{ states.person|selectattr('state','search','(^home$|^not home$)')|list|length }}",
+ label: "homeassistant.people_home"
+ },
+ {
+ template: "{{ states.light|selectattr('state','equalto','on')|list|length }} / {{ states.light|list|length }}",
+ label: "homeassistant.lights_on"
+ },
+ {
+ template: "{{ states.switch|selectattr('state','equalto','on')|list|length }} / {{ states.switch|list|length }}",
+ label: "homeassistant.switches_on"
+ }
+];
+
+function formatOutput(output, data) {
+ return output.replace(/\{.*?\}/g,
+ (match) => match.replace(/\{|\}/g, "").split(".").reduce((o, p) => o ? o[p] : "", data) ?? "");
+}
+
+async function getQuery(query, { url, key }) {
+ const headers = { Authorization: `Bearer ${key}` };
+ const { state, template, label, value } = query;
+ if (state) {
+ return {
+ result: await httpProxy(new URL(`${url}/api/states/${state}`), {
+ headers,
+ method: "GET"
+ }),
+ output: (data) => {
+ const jsonData = JSON.parse(data);
+ return {
+ label: formatOutput(label ?? "{attributes.friendly_name}", jsonData),
+ value: formatOutput(value ?? "{state} {attributes.unit_of_measurement}", jsonData)
+ };
+ }
+ };
+ }
+ if (template) {
+ return {
+ result: await httpProxy(new URL(`${url}/api/template`), {
+ headers,
+ method: "POST",
+ body: JSON.stringify({ template })
+ }),
+ output: (data) => ({ label, value: data.toString() })
+ };
+ }
+ return { result: [500, "", { error: { message: `invalid query ${JSON.stringify(query)}` } }] };
+}
+
+export default async function homeassistantProxyHandler(req, res) {
+ const { group, service } = req.query;
+
+ if (!group || !service) {
+ logger.debug("Invalid or missing service '%s' or group '%s'", service, group);
+ return res.status(400).json({ error: "Invalid proxy service type" });
+ }
+
+ const widget = await getServiceWidget(group, service);
+ if (!widget) {
+ logger.debug("Invalid or missing widget for service '%s' in group '%s'", service, group);
+ return res.status(400).json({ error: "Invalid proxy service type" });
+ }
+
+ const queries = widget.custom !== undefined && widget.fields === undefined ?
+ widget.custom : defaultQueries.filter(q => widget.fields?.includes(q.label.split(".")[1]) ?? true);
+
+ const results = await Promise.all(queries.map(q => getQuery(q, widget)));
+
+ const err = results.find(r => r.result[2]?.error);
+ if (err) {
+ const [status, , data] = err.result;
+ return res.status(status).send(data);
+ }
+
+ return res.status(200).send(results.map(r => {
+ const [status, , data] = r.result;
+ return status === 200 ? r.output(data) : { label: status, value: data.toString() };
+ }));
+}
diff --git a/src/widgets/homeassistant/widget.js b/src/widgets/homeassistant/widget.js
new file mode 100644
index 000000000..71f319716
--- /dev/null
+++ b/src/widgets/homeassistant/widget.js
@@ -0,0 +1,7 @@
+import homeassistantProxyHandler from "./proxy";
+
+const widget = {
+ proxyHandler: homeassistantProxyHandler,
+};
+
+export default widget;
diff --git a/src/widgets/widgets.js b/src/widgets/widgets.js
index ab2d44b2f..a8ce5282f 100644
--- a/src/widgets/widgets.js
+++ b/src/widgets/widgets.js
@@ -17,6 +17,7 @@ import gluetun from "./gluetun/widget";
import gotify from "./gotify/widget";
import grafana from "./grafana/widget";
import hdhomerun from "./hdhomerun/widget";
+import homeassistant from "./homeassistant/widget";
import homebridge from "./homebridge/widget";
import healthchecks from "./healthchecks/widget";
import immich from "./immich/widget";
@@ -94,6 +95,7 @@ const widgets = {
gotify,
grafana,
hdhomerun,
+ homeassistant,
homebridge,
healthchecks,
immich,
From 352b4146f7984b2000dda18838665abd53a876e7 Mon Sep 17 00:00:00 2001
From: shamoon <4887959+shamoon@users.noreply.github.com>
Date: Wed, 12 Apr 2023 01:06:48 -0700
Subject: [PATCH 2/2] homeassistant widget minor code cleanup / changes
- limit to 4 blocks
- allow container to handle field filtering
---
src/components/services/widget/container.jsx | 2 +-
src/widgets/homeassistant/component.jsx | 10 +++++-----
src/widgets/homeassistant/proxy.js | 12 +++++++-----
3 files changed, 13 insertions(+), 11 deletions(-)
diff --git a/src/components/services/widget/container.jsx b/src/components/services/widget/container.jsx
index 11dd4f563..c2249f56f 100644
--- a/src/components/services/widget/container.jsx
+++ b/src/components/services/widget/container.jsx
@@ -14,7 +14,7 @@ export default function Container({ error = false, children, service }) {
// fields: [ "resources.cpu", "resources.mem", "field"]
// or even
// fields: [ "resources.cpu", "widget_type.field" ]
- visibleChildren = children.filter(child => fields.some(field => {
+ visibleChildren = children?.filter(child => fields.some(field => {
let fullField = field;
if (!field.includes(".")) {
fullField = `${type}.${field}`;
diff --git a/src/widgets/homeassistant/component.jsx b/src/widgets/homeassistant/component.jsx
index 2ba809285..48fa61ffe 100644
--- a/src/widgets/homeassistant/component.jsx
+++ b/src/widgets/homeassistant/component.jsx
@@ -5,12 +5,12 @@ import useWidgetAPI from "utils/proxy/use-widget-api";
export default function Component({ service }) {
const { widget } = service;
- const { data, error } = useWidgetAPI(widget, "", { refreshInterval: 60000 });
+ const { data, error } = useWidgetAPI(widget, null, { refreshInterval: 60000 });
if (error) {
return ;
}
- const panels = [];
- data?.forEach(d => panels.push());
-
- return {panels};
+
+ return
+ {data?.map(d => )}
+ ;
}
diff --git a/src/widgets/homeassistant/proxy.js b/src/widgets/homeassistant/proxy.js
index 2c6a517be..648525ef5 100644
--- a/src/widgets/homeassistant/proxy.js
+++ b/src/widgets/homeassistant/proxy.js
@@ -6,7 +6,7 @@ const logger = createLogger("homeassistantProxyHandler");
const defaultQueries = [
{
- template: "{{ states.person|selectattr('state','equalto','home')|list|length }} / {{ states.person|selectattr('state','search','(^home$|^not home$)')|list|length }}",
+ template: "{{ states.person|selectattr('state','equalto','home')|list|length }} / {{ states.person|list|length }}",
label: "homeassistant.people_home"
},
{
@@ -52,7 +52,7 @@ async function getQuery(query, { url, key }) {
output: (data) => ({ label, value: data.toString() })
};
}
- return { result: [500, "", { error: { message: `invalid query ${JSON.stringify(query)}` } }] };
+ return { result: [500, null, { error: { message: `invalid query ${JSON.stringify(query)}` } }] };
}
export default async function homeassistantProxyHandler(req, res) {
@@ -68,9 +68,11 @@ export default async function homeassistantProxyHandler(req, res) {
logger.debug("Invalid or missing widget for service '%s' in group '%s'", service, group);
return res.status(400).json({ error: "Invalid proxy service type" });
}
-
- const queries = widget.custom !== undefined && widget.fields === undefined ?
- widget.custom : defaultQueries.filter(q => widget.fields?.includes(q.label.split(".")[1]) ?? true);
+
+ let queries = defaultQueries;
+ if (!widget.fields && widget.custom) {
+ queries = widget.custom.slice(0, 4);
+ }
const results = await Promise.all(queries.map(q => getQuery(q, widget)));