Add dashboard
parent
ae458c8046
commit
4613025579
@ -0,0 +1,14 @@
|
|||||||
|
import { HA_TOKEN, HA_URL } from "./config.js";
|
||||||
|
|
||||||
|
export type HA_RESPONSE = { state: string; attributes: Record<string, unknown> };
|
||||||
|
|
||||||
|
export async function get_state(entity: string): Promise<HA_RESPONSE> {
|
||||||
|
const response = (await (
|
||||||
|
await fetch(`${HA_URL}/api/states/${entity}`, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${HA_TOKEN}`,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
).json()) as HA_RESPONSE;
|
||||||
|
return response;
|
||||||
|
}
|
@ -0,0 +1,87 @@
|
|||||||
|
import { TempstreamJSX, Templatable } from "tempstream";
|
||||||
|
import { BaseContext } from "koa";
|
||||||
|
import { StatefulPage } from "@sealcode/sealgen";
|
||||||
|
import html from "../html.js";
|
||||||
|
import { get_state, HA_RESPONSE } from "../ha_api.js";
|
||||||
|
import { sleep } from "../util.js";
|
||||||
|
|
||||||
|
export const actionName = "Dashboard";
|
||||||
|
|
||||||
|
const actions = {
|
||||||
|
add: (state: State, inputs: Record<string, string>) => {
|
||||||
|
console.log({ inputs });
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
elements: [...state.elements, inputs.element_to_add || "new element"],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
remove: (state: State, _: unknown, index_to_remove: number) => {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
elements: state.elements.filter((_, index) => index != index_to_remove),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
type State = {
|
||||||
|
elements: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
async function with_unit(s: HA_RESPONSE | Promise<HA_RESPONSE>) {
|
||||||
|
s = await s;
|
||||||
|
return `${s.state}${s.attributes.unit_of_measurement as string}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new (class DashboardPage extends StatefulPage<State, typeof actions> {
|
||||||
|
actions = actions;
|
||||||
|
|
||||||
|
getInitialState() {
|
||||||
|
return { elements: ["one", "two", "three"] };
|
||||||
|
}
|
||||||
|
|
||||||
|
wrapInLayout(ctx: BaseContext, content: Templatable): Templatable {
|
||||||
|
return html(ctx, "Dashboard", content, { navbar: () => "" });
|
||||||
|
}
|
||||||
|
|
||||||
|
render(ctx: BaseContext, state: State, inputs: Record<string, string>) {
|
||||||
|
const date = new Date();
|
||||||
|
const stuff = [
|
||||||
|
{ label: "Temperatura na balkonie", entity: "input_number.balkon_average" },
|
||||||
|
{ label: "Temperatura w salonie", entity: "sensor.ewelink_th01_temperature" },
|
||||||
|
{ label: "Wilgotność w salonie", entity: "sensor.ewelink_th01_humidity" },
|
||||||
|
{ label: "Temperatura na strychu", entity: "sensor.strych_temperature_3" },
|
||||||
|
{
|
||||||
|
label: "Docelowa temp. na strychu",
|
||||||
|
entity: "input_number.strych_target_temperature",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style="font-size: 2rem">
|
||||||
|
<div style="font-size: 10rem; text-align: center;">
|
||||||
|
{date.getHours()}:{date.getMinutes().toString().padStart(2, "0")}
|
||||||
|
</div>
|
||||||
|
<table style="margin: 0 auto">
|
||||||
|
<tbody>
|
||||||
|
{stuff.map(({ label, entity }) => (
|
||||||
|
<tr>
|
||||||
|
<td style="text-align: right">{label}:</td>
|
||||||
|
<td>
|
||||||
|
<strong>{with_unit(get_state(entity))}</strong>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
{
|
||||||
|
/* HTML */ `<script>
|
||||||
|
setInterval(function () {
|
||||||
|
document.location = document.location;
|
||||||
|
}, 15 * 1000);
|
||||||
|
</script>`
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})();
|
@ -0,0 +1,48 @@
|
|||||||
|
import { withProdApp } from "../test_utils/with-prod-app.js";
|
||||||
|
import { VERY_LONG_TEST_TIMEOUT, webhintURL } from "../test_utils/webhint.js";
|
||||||
|
import { DashboardURL } from "./urls.js";
|
||||||
|
import { getBrowser } from "../test_utils/browser-creator.js";
|
||||||
|
import { Browser, BrowserContext, Page } from "@playwright/test";
|
||||||
|
|
||||||
|
describe("Dashboard webhint", () => {
|
||||||
|
it(
|
||||||
|
"doesn't crash",
|
||||||
|
async function () {
|
||||||
|
return withProdApp(async ({ base_url, rest_api }) => {
|
||||||
|
await rest_api.get(DashboardURL);
|
||||||
|
await webhintURL(base_url + DashboardURL);
|
||||||
|
// alternatively you can use webhintHTML for faster but less precise scans
|
||||||
|
// or for scanning responses of requests that use some form of authorization:
|
||||||
|
// const response = await rest_api.get(DashboardURL);
|
||||||
|
// await webhintHTML(response);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
VERY_LONG_TEST_TIMEOUT
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Dashboard", () => {
|
||||||
|
let page: Page;
|
||||||
|
let browser: Browser;
|
||||||
|
let context: BrowserContext;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
browser = await getBrowser();
|
||||||
|
context = await browser.newContext();
|
||||||
|
page = await context.newPage();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await context.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
it(
|
||||||
|
"works as expected",
|
||||||
|
async function () {
|
||||||
|
return withProdApp(async ({ base_url }) => {
|
||||||
|
await page.goto(base_url + DashboardURL);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
VERY_LONG_TEST_TIMEOUT
|
||||||
|
);
|
||||||
|
});
|
Loading…
Reference in New Issue