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)));