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/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/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..48fa61ffe --- /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, null, { refreshInterval: 60000 }); + if (error) { + return ; + } + + return + {data?.map(d => )} + ; +} diff --git a/src/widgets/homeassistant/proxy.js b/src/widgets/homeassistant/proxy.js new file mode 100644 index 000000000..648525ef5 --- /dev/null +++ b/src/widgets/homeassistant/proxy.js @@ -0,0 +1,89 @@ +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|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, null, { 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" }); + } + + 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))); + + 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,