From 4bdaea61caad19a6798a5c2e4f4ac867c22c7711 Mon Sep 17 00:00:00 2001
From: mdvanes <4253562+mdvanes@users.noreply.github.com>
Date: Mon, 15 Apr 2024 12:59:47 +0200
Subject: [PATCH 01/18] fix: temperature chart range
---
.../src/energyusage/energyusage.controller.ts | 51 ++++++++++---------
1 file changed, 27 insertions(+), 24 deletions(-)
diff --git a/apps/server/src/energyusage/energyusage.controller.ts b/apps/server/src/energyusage/energyusage.controller.ts
index aa8474e5..79ea709c 100644
--- a/apps/server/src/energyusage/energyusage.controller.ts
+++ b/apps/server/src/energyusage/energyusage.controller.ts
@@ -40,47 +40,37 @@ const strToConfigs = (sensorConfig: string): SensorConfig[] => {
};
const temperatureResponseToEntry =
- (temperatureSensors: SensorConfig[], index: number) =>
+ (temperatureSensors: SensorConfig[], date: string) =>
(response: GotTempResponse, temperatureIndex: number) => {
const sensor = temperatureSensors[temperatureIndex];
- const temperatureEntry = response.result[index];
- if (!temperatureEntry) {
- return [
- sensor.name,
- {
- avg: undefined,
- high: undefined,
- low: undefined,
- },
- ];
- }
- if (temperatureEntry.d !== temperatureEntry.d) {
- throw new Error("days do not match");
- }
+ const temperatureEntry = response.result.find((r) => r.d === date);
return [
sensor.name,
{
- avg: temperatureEntry.ta,
- high: temperatureEntry.te,
- low: temperatureEntry.tm,
+ avg: temperatureEntry?.ta ?? "",
+ high: temperatureEntry?.te ?? "",
+ low: temperatureEntry?.tm ?? "",
},
];
};
const sensorResultsToAggregated =
(
+ gasEntries: GasUsageItem[],
temperatureSensors: SensorConfig[],
temperatureResponses: GotTempResponse[]
) =>
- (gasEntry: GasUsageItem, index: number) => {
+ (date: string) => {
const temperatureEntries = temperatureResponses.map(
- temperatureResponseToEntry(temperatureSensors, index)
+ temperatureResponseToEntry(temperatureSensors, date)
);
+ const gasEntry = gasEntries.find((r) => r.d === date);
const result: EnergyUsageGasItem = {
- counter: gasEntry.c,
- used: gasEntry.v,
- day: gasEntry.d,
+ day: date,
+ counter: gasEntry?.c ?? "",
+ used: gasEntry?.v ?? "",
+
temp: Object.fromEntries(temperatureEntries),
};
return result;
@@ -133,10 +123,23 @@ export class EnergyUsageController {
temperaturePromises
);
+ const now = Date.now();
+ const NR_OF_DAYS = 31;
+ const lastMonth = new Date(now - 1000 * 60 * 60 * 24 * NR_OF_DAYS);
+ const dateRange = new Array(NR_OF_DAYS)
+ .fill(0)
+ .map((n, index) =>
+ new Date(
+ lastMonth.getTime() + 1000 * 60 * 60 * 24 * (index + 1)
+ )
+ .toISOString()
+ .slice(0, 10)
+ );
const aggregated: EnergyUsageGetGasUsageResponse = {
...gasCounterResponse,
- result: gasCounterResponse.result.map(
+ result: dateRange.map(
sensorResultsToAggregated(
+ gasCounterResponse.result,
temperatureSensors,
temperatureResponses
)
From 3c41170b157b63ce9afc36ca0467951916d51a7d Mon Sep 17 00:00:00 2001
From: mdvanes <4253562+mdvanes@users.noreply.github.com>
Date: Mon, 15 Apr 2024 13:08:19 +0200
Subject: [PATCH 02/18] chore: show homesec items
---
.../Components/Molecules/HomeSec/HomeSec.tsx | 21 ++++++
.../Components/Pages/Dashboard/Dashboard.tsx | 2 +
apps/client/src/Reducers/index.ts | 2 +
apps/client/src/Services/homesecApi.ts | 18 +++++
apps/client/src/store.ts | 2 +
apps/server/.env.example | 5 +-
apps/server/src/app/app.module.ts | 2 +
apps/server/src/homesec/homesec.controller.ts | 68 +++++++++++++++++++
libs/types/src/index.ts | 1 +
libs/types/src/lib/homesec.types.ts | 57 ++++++++++++++++
10 files changed, 177 insertions(+), 1 deletion(-)
create mode 100644 apps/client/src/Components/Molecules/HomeSec/HomeSec.tsx
create mode 100644 apps/client/src/Services/homesecApi.ts
create mode 100644 apps/server/src/homesec/homesec.controller.ts
create mode 100644 libs/types/src/lib/homesec.types.ts
diff --git a/apps/client/src/Components/Molecules/HomeSec/HomeSec.tsx b/apps/client/src/Components/Molecules/HomeSec/HomeSec.tsx
new file mode 100644
index 00000000..8904bbad
--- /dev/null
+++ b/apps/client/src/Components/Molecules/HomeSec/HomeSec.tsx
@@ -0,0 +1,21 @@
+import { FC } from "react";
+import { useGetHomesecDeviceListQuery } from "../../../Services/homesecApi";
+
+export const HomeSec: FC = () => {
+ // TODO handle error/loading
+ const { data: devices } = useGetHomesecDeviceListQuery(undefined);
+
+ console.log(devices);
+
+ return (
+
+ {devices?.senrows.map((sensor) => (
+
+ {sensor.name}: {sensor.status}
+
+ ))}
+
+ );
+};
+
+export default HomeSec;
diff --git a/apps/client/src/Components/Pages/Dashboard/Dashboard.tsx b/apps/client/src/Components/Pages/Dashboard/Dashboard.tsx
index 1f0e89ce..341f7628 100644
--- a/apps/client/src/Components/Pages/Dashboard/Dashboard.tsx
+++ b/apps/client/src/Components/Pages/Dashboard/Dashboard.tsx
@@ -15,6 +15,7 @@ import StreamContainer from "../../Molecules/StreamContainer/StreamContainer";
import SwitchBarList from "../../Molecules/SwitchBarList/SwitchBarList";
import UrlToMusic from "../../Molecules/UrlToMusic/UrlToMusic";
import VideoStream from "../../Molecules/VideoStream/VideoStream";
+import HomeSec from "../../Molecules/HomeSec/HomeSec";
import Docker from "../Docker/Docker";
const useStyles = makeStyles()((theme) => ({
@@ -43,6 +44,7 @@ const Dashboard: FC = () => {
+
diff --git a/apps/client/src/Reducers/index.ts b/apps/client/src/Reducers/index.ts
index e51f174d..7d8c8fea 100644
--- a/apps/client/src/Reducers/index.ts
+++ b/apps/client/src/Reducers/index.ts
@@ -10,6 +10,7 @@ import { dataloraApi } from "../Services/dataloraApi";
import { dockerListApi } from "../Services/dockerListApi";
import { downloadListApi } from "../Services/downloadListApi";
import { energyUsageApi } from "../Services/energyUsageApi";
+import { homesecApi } from "../Services/homesecApi";
import { jukeboxApi } from "../Services/jukeboxApi";
import { monitApi } from "../Services/monitApi";
import { nextupApi } from "../Services/nextupApi";
@@ -30,6 +31,7 @@ const rootReducer = combineReducers({
[dockerListApi.reducerPath]: dockerListApi.reducer,
[downloadListApi.reducerPath]: downloadListApi.reducer,
[energyUsageApi.reducerPath]: energyUsageApi.reducer,
+ [homesecApi.reducerPath]: homesecApi.reducer,
[jukeboxApi.reducerPath]: jukeboxApi.reducer,
[monitApi.reducerPath]: monitApi.reducer,
[nextupApi.reducerPath]: nextupApi.reducer,
diff --git a/apps/client/src/Services/homesecApi.ts b/apps/client/src/Services/homesecApi.ts
new file mode 100644
index 00000000..e32a1d0e
--- /dev/null
+++ b/apps/client/src/Services/homesecApi.ts
@@ -0,0 +1,18 @@
+import { HomesecDevicesResponse } from "@homeremote/types";
+import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/dist/query/react";
+import { willAddCredentials } from "../devUtils";
+
+export const homesecApi = createApi({
+ reducerPath: "homesecApi",
+ baseQuery: fetchBaseQuery({
+ baseUrl: `${process.env.NX_BASE_URL}/api/homesec`,
+ credentials: willAddCredentials(),
+ }),
+ endpoints: (builder) => ({
+ getHomesecDeviceList: builder.query({
+ query: () => "/devices",
+ }),
+ }),
+});
+
+export const { useGetHomesecDeviceListQuery } = homesecApi;
diff --git a/apps/client/src/store.ts b/apps/client/src/store.ts
index b5fb8252..bfcbb4ed 100644
--- a/apps/client/src/store.ts
+++ b/apps/client/src/store.ts
@@ -8,6 +8,7 @@ import { dataloraApi } from "./Services/dataloraApi";
import { dockerListApi } from "./Services/dockerListApi";
import { downloadListApi } from "./Services/downloadListApi";
import { energyUsageApi } from "./Services/energyUsageApi";
+import { homesecApi } from "./Services/homesecApi";
import { jukeboxApi } from "./Services/jukeboxApi";
import { monitApi } from "./Services/monitApi";
import { nextupApi } from "./Services/nextupApi";
@@ -26,6 +27,7 @@ export const store = configureStore({
dockerListApi.middleware,
downloadListApi.middleware,
energyUsageApi.middleware,
+ homesecApi.middleware,
jukeboxApi.middleware,
monitApi.middleware,
nextupApi.middleware,
diff --git a/apps/server/.env.example b/apps/server/.env.example
index 98411fae..b5488853 100644
--- a/apps/server/.env.example
+++ b/apps/server/.env.example
@@ -27,4 +27,7 @@ NEXTUP_USER_ID=123
CARTWIN_VIN=123
CARTWIN_VCC_API_KEY=123
VIDEO_STREAM_HASH=123
-VIDEO_STREAM_URL=http://localhost:52998/
\ No newline at end of file
+VIDEO_STREAM_URL=http://localhost:52998/
+HOMESEC_BASE_URL=http://192.168.0.123/
+HOMESEC_USERNAME=user
+HOMESEC_PASSWORD=pass
\ No newline at end of file
diff --git a/apps/server/src/app/app.module.ts b/apps/server/src/app/app.module.ts
index 69f5be24..1dec97d2 100644
--- a/apps/server/src/app/app.module.ts
+++ b/apps/server/src/app/app.module.ts
@@ -8,6 +8,7 @@ import { DataloraController } from "../datalora/datalora.controller";
import { DockerlistController } from "../dockerlist/dockerlist.controller";
import { DownloadlistController } from "../downloadlist/downloadlist.controller";
import { EnergyUsageController } from "../energyusage/energyusage.controller";
+import { HomesecController } from "../homesec/homesec.controller";
import { JukeboxController } from "../jukebox/jukebox.controller";
import { LoginController } from "../login/login.controller";
import { LogoutController } from "../logout/logout.controller";
@@ -52,6 +53,7 @@ import { AppService } from "./app.service";
DockerlistController,
DownloadlistController,
EnergyUsageController,
+ HomesecController,
JukeboxController,
LoginController,
LogoutController,
diff --git a/apps/server/src/homesec/homesec.controller.ts b/apps/server/src/homesec/homesec.controller.ts
new file mode 100644
index 00000000..f6afb0ff
--- /dev/null
+++ b/apps/server/src/homesec/homesec.controller.ts
@@ -0,0 +1,68 @@
+import {
+ Controller,
+ Get,
+ HttpException,
+ HttpStatus,
+ Logger,
+ Request,
+ UseGuards,
+} from "@nestjs/common";
+import { ConfigService } from "@nestjs/config";
+import { JwtAuthGuard } from "../auth/jwt-auth.guard";
+import { AuthenticatedRequest } from "../login/LoginRequest.types";
+import got from "got";
+import {
+ HomesecDevicesResponse,
+ HomesecPanelResponse,
+} from "@homeremote/types";
+
+@Controller("api/homesec")
+export class HomesecController {
+ private readonly logger: Logger;
+ private readonly baseUrl: string;
+ private readonly username: string;
+ private readonly password: string;
+
+ constructor(private configService: ConfigService) {
+ this.logger = new Logger(HomesecController.name);
+ this.baseUrl = this.configService.get("HOMESEC_BASE_URL") || "";
+ this.username =
+ this.configService.get("HOMESEC_USERNAME") || "";
+ this.password =
+ this.configService.get("HOMESEC_PASSWORD") || "";
+ }
+
+ @UseGuards(JwtAuthGuard)
+ @Get("devices")
+ async getDevices(
+ @Request() req: AuthenticatedRequest
+ ): Promise {
+ this.logger.verbose(`[${req.user.name}] GET to /api/homesec/devices`);
+
+ try {
+ const url = `${this.baseUrl}/action/deviceListGet`;
+
+ const response1: HomesecPanelResponse = await got(
+ `${this.baseUrl}/action/panelCondGet`,
+ {
+ username: this.username,
+ password: this.password,
+ }
+ ).json();
+ this.logger.log(response1);
+
+ const response: HomesecDevicesResponse = await got(url, {
+ username: this.username,
+ password: this.password,
+ }).json();
+ // TODO this.logger.log(response);
+ return response;
+ } catch (err) {
+ this.logger.error(err);
+ throw new HttpException(
+ "failed to receive downstream data",
+ HttpStatus.INTERNAL_SERVER_ERROR
+ );
+ }
+ }
+}
diff --git a/libs/types/src/index.ts b/libs/types/src/index.ts
index 33c9a510..85ea9af7 100644
--- a/libs/types/src/index.ts
+++ b/libs/types/src/index.ts
@@ -3,6 +3,7 @@ export * from "./lib/datalora.types";
export * from "./lib/dockerlist.types";
export * from "./lib/downloadlist.types";
export * from "./lib/energyusage.types";
+export * from "./lib/homesec.types";
export * from "./lib/jukebox.types";
export * from "./lib/monit.types";
export * from "./lib/nextup.types";
diff --git a/libs/types/src/lib/homesec.types.ts b/libs/types/src/lib/homesec.types.ts
new file mode 100644
index 00000000..c41608eb
--- /dev/null
+++ b/libs/types/src/lib/homesec.types.ts
@@ -0,0 +1,57 @@
+interface SensorRow {
+ area: number;
+ zone: number;
+ type: number;
+ type_f:
+ | "Door Contact"
+ | "Smoke Detector"
+ | "Keypad"
+ | "IR"
+ | "Remote Controller";
+ name: string;
+ cond: "";
+ cond_ok: "0" | "1";
+ battery: "";
+ battery_ok: "0" | "1";
+ tamper: "";
+ tamper_ok: "0" | "1";
+ bypass: "No" | "Yes";
+ temp_bypass: "0" | "1";
+ rssi: string; // "Strong, 9";
+ status: "" | "Door Close" | "Door Open";
+ id: string;
+ su: number;
+}
+
+export interface HomesecDevicesResponse {
+ senrows: SensorRow[];
+}
+
+export interface HomesecPanelResponse {
+ updates: {
+ mode_a1: "Disarm";
+ mode_a2: "Disarm";
+ battery_ok: "1";
+ battery: "Normal";
+ tamper_ok: "1";
+ tamper: "N/A";
+ interference_ok: "1";
+ interference: "Normal";
+ ac_activation_ok: "1";
+ ac_activation: "Normal";
+ sys_in_inst: "System in maintenance";
+ rssi: "1";
+ sig_gsm_ok: "1";
+ sig_gsm: "N/A";
+ };
+ forms: {
+ pcondform1: {
+ mode: "0";
+ f_arm: "0";
+ };
+ pcondform2: {
+ mode: "0";
+ f_arm: "0";
+ };
+ };
+}
From 2df4cb7019cc66a44cd762b58c77ae82bb15174e Mon Sep 17 00:00:00 2001
From: mdvanes <4253562+mdvanes@users.noreply.github.com>
Date: Mon, 15 Apr 2024 13:34:03 +0200
Subject: [PATCH 03/18] chore: show homesec status and devices
---
.../Components/Molecules/HomeSec/HomeSec.tsx | 11 ++---
apps/client/src/Services/homesecApi.ts | 8 ++--
apps/server/src/homesec/homesec.controller.ts | 45 ++++++++++++++-----
libs/types/src/lib/homesec.types.ts | 20 +++++++--
4 files changed, 59 insertions(+), 25 deletions(-)
diff --git a/apps/client/src/Components/Molecules/HomeSec/HomeSec.tsx b/apps/client/src/Components/Molecules/HomeSec/HomeSec.tsx
index 8904bbad..cc99860f 100644
--- a/apps/client/src/Components/Molecules/HomeSec/HomeSec.tsx
+++ b/apps/client/src/Components/Molecules/HomeSec/HomeSec.tsx
@@ -1,17 +1,18 @@
import { FC } from "react";
-import { useGetHomesecDeviceListQuery } from "../../../Services/homesecApi";
+import { useGetHomesecStatusQuery } from "../../../Services/homesecApi";
export const HomeSec: FC = () => {
// TODO handle error/loading
- const { data: devices } = useGetHomesecDeviceListQuery(undefined);
+ const { data } = useGetHomesecStatusQuery(undefined);
- console.log(devices);
+ console.log(data?.status);
return (
- {devices?.senrows.map((sensor) => (
+ {data?.status}
+ {data?.devices?.map((sensor) => (
- {sensor.name}: {sensor.status}
+ {sensor.name}: {sensor.status} {sensor.type_f} {sensor.rssi}
))}
diff --git a/apps/client/src/Services/homesecApi.ts b/apps/client/src/Services/homesecApi.ts
index e32a1d0e..1007f279 100644
--- a/apps/client/src/Services/homesecApi.ts
+++ b/apps/client/src/Services/homesecApi.ts
@@ -1,4 +1,4 @@
-import { HomesecDevicesResponse } from "@homeremote/types";
+import { HomesecStatusResponse } from "@homeremote/types";
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/dist/query/react";
import { willAddCredentials } from "../devUtils";
@@ -9,10 +9,10 @@ export const homesecApi = createApi({
credentials: willAddCredentials(),
}),
endpoints: (builder) => ({
- getHomesecDeviceList: builder.query({
- query: () => "/devices",
+ getHomesecStatus: builder.query({
+ query: () => "/status",
}),
}),
});
-export const { useGetHomesecDeviceListQuery } = homesecApi;
+export const { useGetHomesecStatusQuery } = homesecApi;
diff --git a/apps/server/src/homesec/homesec.controller.ts b/apps/server/src/homesec/homesec.controller.ts
index f6afb0ff..88a7600f 100644
--- a/apps/server/src/homesec/homesec.controller.ts
+++ b/apps/server/src/homesec/homesec.controller.ts
@@ -14,6 +14,7 @@ import got from "got";
import {
HomesecDevicesResponse,
HomesecPanelResponse,
+ HomesecStatusResponse,
} from "@homeremote/types";
@Controller("api/homesec")
@@ -33,30 +34,50 @@ export class HomesecController {
}
@UseGuards(JwtAuthGuard)
- @Get("devices")
+ @Get("status")
async getDevices(
@Request() req: AuthenticatedRequest
- ): Promise {
+ ): Promise {
this.logger.verbose(`[${req.user.name}] GET to /api/homesec/devices`);
try {
- const url = `${this.baseUrl}/action/deviceListGet`;
-
- const response1: HomesecPanelResponse = await got(
+ const panelResponse: HomesecPanelResponse = await got(
`${this.baseUrl}/action/panelCondGet`,
{
username: this.username,
password: this.password,
}
).json();
- this.logger.log(response1);
+ this.logger.log(panelResponse);
- const response: HomesecDevicesResponse = await got(url, {
- username: this.username,
- password: this.password,
- }).json();
- // TODO this.logger.log(response);
- return response;
+ try {
+ const devicesResponse: HomesecDevicesResponse = await got(
+ `${this.baseUrl}/action/deviceListGet`,
+ {
+ username: this.username,
+ password: this.password,
+ }
+ ).json();
+ this.logger.log(devicesResponse);
+ return {
+ status: panelResponse.updates.mode_a1,
+ devices: devicesResponse.senrows.map(
+ ({ id, name, type_f, status, rssi }) => ({
+ id,
+ name,
+ type_f,
+ status,
+ rssi,
+ })
+ ),
+ };
+ } catch (err) {
+ this.logger.error("deviceListGet:", err);
+ return {
+ status: panelResponse.updates.mode_a1,
+ devices: [],
+ };
+ }
} catch (err) {
this.logger.error(err);
throw new HttpException(
diff --git a/libs/types/src/lib/homesec.types.ts b/libs/types/src/lib/homesec.types.ts
index c41608eb..ae8f7617 100644
--- a/libs/types/src/lib/homesec.types.ts
+++ b/libs/types/src/lib/homesec.types.ts
@@ -27,10 +27,17 @@ export interface HomesecDevicesResponse {
senrows: SensorRow[];
}
+type Modes = "Disarm" | "Home Arm 1";
+
+export enum PcondformModes {
+ Disarm = "0",
+ HomeArm = "2",
+}
+
export interface HomesecPanelResponse {
updates: {
- mode_a1: "Disarm";
- mode_a2: "Disarm";
+ mode_a1: Modes;
+ mode_a2: Modes;
battery_ok: "1";
battery: "Normal";
tamper_ok: "1";
@@ -46,12 +53,17 @@ export interface HomesecPanelResponse {
};
forms: {
pcondform1: {
- mode: "0";
+ mode: PcondformModes;
f_arm: "0";
};
pcondform2: {
- mode: "0";
+ mode: PcondformModes;
f_arm: "0";
};
};
}
+
+export interface HomesecStatusResponse {
+ status: Modes;
+ devices: Pick[];
+}
From 8c0ee981c49d3ddbbf5f43b2f523de1fd265f5a6 Mon Sep 17 00:00:00 2001
From: mdvanes <4253562+mdvanes@users.noreply.github.com>
Date: Mon, 15 Apr 2024 14:18:19 +0200
Subject: [PATCH 04/18] chore: restyle homesec
---
.../Components/Molecules/HomeSec/HomeSec.tsx | 92 +++++++++++++++++--
.../HomeSec/SimpleHomeSecListItem.tsx | 34 +++++++
apps/server/src/homesec/homesec.controller.ts | 5 +-
libs/types/src/lib/homesec.types.ts | 14 +--
4 files changed, 127 insertions(+), 18 deletions(-)
create mode 100644 apps/client/src/Components/Molecules/HomeSec/SimpleHomeSecListItem.tsx
diff --git a/apps/client/src/Components/Molecules/HomeSec/HomeSec.tsx b/apps/client/src/Components/Molecules/HomeSec/HomeSec.tsx
index cc99860f..2302c237 100644
--- a/apps/client/src/Components/Molecules/HomeSec/HomeSec.tsx
+++ b/apps/client/src/Components/Molecules/HomeSec/HomeSec.tsx
@@ -1,21 +1,93 @@
import { FC } from "react";
import { useGetHomesecStatusQuery } from "../../../Services/homesecApi";
+import {
+ List,
+ ListItem,
+ ListItemAvatar,
+ ListItemButton,
+ ListItemText,
+ Paper,
+ Icon,
+} from "@mui/material";
+import { HomesecStatusResponse, TypeF } from "@homeremote/types";
+import LoadingDot from "../LoadingDot/LoadingDot";
+import SimpleHomeSecListItem from "./SimpleHomeSecListItem";
-export const HomeSec: FC = () => {
- // TODO handle error/loading
- const { data } = useGetHomesecStatusQuery(undefined);
+const statusClass: Record = {
+ Error: "black",
+ Disarm: "green",
+ "Home Arm 1": "yellow",
+};
- console.log(data?.status);
+const typeIcon: Record = {
+ "Door Contact": "sensor_door",
+ "Smoke Detector": "smoking_rooms",
+ Keypad: "keyboard",
+ IR: "animation",
+ "Remote Controller": "settings_remote",
+};
+
+export const HomeSec: FC = () => {
+ const { data, isLoading, isFetching, isError, refetch } =
+ useGetHomesecStatusQuery(undefined);
return (
-
- {data?.status}
+
+
+ {isError ? (
+ refetch()}
+ />
+ ) : (
+ ""
+ )}
+ {!data?.devices || data?.devices.length === 0 ? (
+ refetch()}
+ />
+ ) : (
+ ""
+ )}
{data?.devices?.map((sensor) => (
-
- {sensor.name}: {sensor.status} {sensor.type_f} {sensor.rssi}
-
+
+
+
+ {typeIcon[sensor.type_f]}
+
+
+ {sensor.name}
+
+
{sensor.status}
+
{sensor.rssi}
+
+
+ }
+ />
+
+
))}
-
+
);
};
diff --git a/apps/client/src/Components/Molecules/HomeSec/SimpleHomeSecListItem.tsx b/apps/client/src/Components/Molecules/HomeSec/SimpleHomeSecListItem.tsx
new file mode 100644
index 00000000..c56e7346
--- /dev/null
+++ b/apps/client/src/Components/Molecules/HomeSec/SimpleHomeSecListItem.tsx
@@ -0,0 +1,34 @@
+import RestartAltIcon from "@mui/icons-material/RestartAlt";
+import {
+ IconButton,
+ ListItem,
+ ListItemAvatar,
+ ListItemButton,
+ ListItemText,
+} from "@mui/material";
+import { FC } from "react";
+
+const SimpleHomeSecListItem: FC<{ title: string; onClick: () => void }> = ({
+ title,
+ onClick,
+}) => {
+ return (
+
+
+
+
+ {title}
+
+
+
+ >
+ }
+ />
+
+
+ );
+};
+
+export default SimpleHomeSecListItem;
diff --git a/apps/server/src/homesec/homesec.controller.ts b/apps/server/src/homesec/homesec.controller.ts
index 88a7600f..6ed6b825 100644
--- a/apps/server/src/homesec/homesec.controller.ts
+++ b/apps/server/src/homesec/homesec.controller.ts
@@ -16,6 +16,7 @@ import {
HomesecPanelResponse,
HomesecStatusResponse,
} from "@homeremote/types";
+import { wait } from "../util/wait";
@Controller("api/homesec")
export class HomesecController {
@@ -48,9 +49,9 @@ export class HomesecController {
password: this.password,
}
).json();
- this.logger.log(panelResponse);
try {
+ await wait(5000);
const devicesResponse: HomesecDevicesResponse = await got(
`${this.baseUrl}/action/deviceListGet`,
{
@@ -58,7 +59,7 @@ export class HomesecController {
password: this.password,
}
).json();
- this.logger.log(devicesResponse);
+
return {
status: panelResponse.updates.mode_a1,
devices: devicesResponse.senrows.map(
diff --git a/libs/types/src/lib/homesec.types.ts b/libs/types/src/lib/homesec.types.ts
index ae8f7617..635bd1f3 100644
--- a/libs/types/src/lib/homesec.types.ts
+++ b/libs/types/src/lib/homesec.types.ts
@@ -1,13 +1,15 @@
+export type TypeF =
+ | "Door Contact"
+ | "Smoke Detector"
+ | "Keypad"
+ | "IR"
+ | "Remote Controller";
+
interface SensorRow {
area: number;
zone: number;
type: number;
- type_f:
- | "Door Contact"
- | "Smoke Detector"
- | "Keypad"
- | "IR"
- | "Remote Controller";
+ type_f: TypeF;
name: string;
cond: "";
cond_ok: "0" | "1";
From a4df566a9e3989378d1507fdd9a80379c64971b9 Mon Sep 17 00:00:00 2001
From: mdvanes <4253562+mdvanes@users.noreply.github.com>
Date: Mon, 15 Apr 2024 16:36:01 +0200
Subject: [PATCH 05/18] chore: add tests for homesec
---
.../Molecules/HomeSec/HomeSec.test.tsx | 38 ++++++
.../Components/Molecules/HomeSec/HomeSec.tsx | 2 +-
.../src/homesec/homesec.controller.spec.ts | 117 ++++++++++++++++++
apps/server/src/homesec/homesec.controller.ts | 7 +-
4 files changed, 160 insertions(+), 4 deletions(-)
create mode 100644 apps/client/src/Components/Molecules/HomeSec/HomeSec.test.tsx
create mode 100644 apps/server/src/homesec/homesec.controller.spec.ts
diff --git a/apps/client/src/Components/Molecules/HomeSec/HomeSec.test.tsx b/apps/client/src/Components/Molecules/HomeSec/HomeSec.test.tsx
new file mode 100644
index 00000000..809027ca
--- /dev/null
+++ b/apps/client/src/Components/Molecules/HomeSec/HomeSec.test.tsx
@@ -0,0 +1,38 @@
+import { render, screen } from "@testing-library/react";
+import { jukeboxApi } from "../../../Services/jukeboxApi";
+import { MockStoreProvider } from "../../../testHelpers";
+import { FC, ReactNode } from "react";
+import HomeSec from "./HomeSec";
+import fetchMock, { enableFetchMocks } from "jest-fetch-mock";
+import { HomesecStatusResponse, PlaylistsResponse } from "@homeremote/types";
+import { homesecApi } from "../../../Services/homesecApi";
+
+enableFetchMocks();
+
+const Wrapper: FC<{ children: ReactNode }> = ({ children }) => {
+ return (
+ {children}
+ );
+};
+
+describe("HomeSec", () => {
+ it("shows status and list of devices", async () => {
+ const mockStatusResponse: HomesecStatusResponse = {
+ status: "Disarm",
+ devices: [
+ {
+ id: "1",
+ name: "Front door",
+ status: "Door Close",
+ rssi: "Strong, 9",
+ type_f: "Door Contact",
+ },
+ ],
+ };
+ fetchMock.mockResponse(JSON.stringify(mockStatusResponse));
+ render(, { wrapper: Wrapper });
+
+ await screen.findByText("sensor_door");
+ expect(screen.getByText("Front door")).toBeVisible();
+ });
+});
diff --git a/apps/client/src/Components/Molecules/HomeSec/HomeSec.tsx b/apps/client/src/Components/Molecules/HomeSec/HomeSec.tsx
index 2302c237..491af252 100644
--- a/apps/client/src/Components/Molecules/HomeSec/HomeSec.tsx
+++ b/apps/client/src/Components/Molecules/HomeSec/HomeSec.tsx
@@ -58,7 +58,7 @@ export const HomeSec: FC = () => {
""
)}
{data?.devices?.map((sensor) => (
-
+
{typeIcon[sensor.type_f]}
diff --git a/apps/server/src/homesec/homesec.controller.spec.ts b/apps/server/src/homesec/homesec.controller.spec.ts
new file mode 100644
index 00000000..bd1e5f73
--- /dev/null
+++ b/apps/server/src/homesec/homesec.controller.spec.ts
@@ -0,0 +1,117 @@
+import { ConfigService } from "@nestjs/config";
+import { Test, TestingModule } from "@nestjs/testing";
+import got, { CancelableRequest } from "got";
+import { mocked } from "jest-mock";
+import { AuthenticatedRequest } from "../login/LoginRequest.types";
+import { HomesecController } from "./homesec.controller";
+
+jest.mock("got");
+const mockGot = mocked(got);
+
+const mockAuthenticatedRequest = {
+ user: { name: "someuser", id: 1 },
+} as AuthenticatedRequest;
+
+describe("HomeSec Controller", () => {
+ let controller: HomesecController;
+ let configService: ConfigService;
+
+ beforeEach(async () => {
+ const module: TestingModule = await Test.createTestingModule({
+ controllers: [HomesecController],
+ providers: [
+ { provide: ConfigService, useValue: { get: jest.fn() } },
+ ],
+ }).compile();
+
+ configService = module.get(ConfigService);
+ controller = module.get(HomesecController);
+
+ jest.spyOn(configService, "get").mockImplementation((envName) => {
+ if (envName === "HOMESEC_BASE_URL") {
+ return "url";
+ }
+ if (envName === "HOMESEC_USERNAME") {
+ return "user";
+ }
+ if (envName === "HOMESEC_PASSWORD") {
+ return "pass";
+ }
+ });
+ });
+
+ afterAll(() => {
+ mockGot.mockRestore();
+ });
+
+ it("returns devices and panel status on /GET", async () => {
+ mockGot
+ .mockReturnValueOnce({
+ json: () =>
+ Promise.resolve({
+ updates: {
+ mode_a1: "Disarm",
+ },
+ }),
+ } as CancelableRequest)
+ .mockReturnValue({
+ json: () =>
+ Promise.resolve({
+ senrows: [{ id: "123" }],
+ }),
+ } as CancelableRequest);
+
+ const response = await controller.getStatus(mockAuthenticatedRequest);
+ expect(response).toEqual({
+ status: "Disarm",
+ devices: [
+ {
+ id: "123",
+ name: undefined,
+ rssi: undefined,
+ status: undefined,
+ type_f: undefined,
+ },
+ ],
+ });
+ expect(mockGot).toBeCalledTimes(2);
+ expect(mockGot).toBeCalledWith("/action/panelCondGet", {
+ password: "",
+ username: "",
+ });
+ expect(mockGot).toBeCalledWith("/action/deviceListGet", {
+ password: "",
+ username: "",
+ });
+ }, 10000);
+
+ it("throws error on /GET panel failure", async () => {
+ mockGot.mockReturnValue({
+ json: () => Promise.reject("some error"),
+ } as CancelableRequest);
+ await expect(
+ controller.getStatus(mockAuthenticatedRequest)
+ ).rejects.toThrow("failed to receive downstream data");
+ });
+
+ // TODO this should work, but throws out of the inner catch. This does not happen runtime.
+ it.skip("returns [] on /GET devices failure", async () => {
+ mockGot
+ .mockReturnValueOnce({
+ json: () =>
+ Promise.resolve({
+ updates: {
+ mode_a1: "Disarm",
+ },
+ }),
+ } as CancelableRequest)
+ .mockReturnValue({
+ json: () => Promise.resolve({}),
+ } as CancelableRequest);
+ controller.getStatus(mockAuthenticatedRequest);
+
+ const response = await controller.getStatus(mockAuthenticatedRequest);
+ expect(response).toEqual({ status: "Disarm", devices: [] });
+ expect(mockGot).toBeCalledTimes(2);
+ }, 10000);
+});
diff --git a/apps/server/src/homesec/homesec.controller.ts b/apps/server/src/homesec/homesec.controller.ts
index 6ed6b825..948b9330 100644
--- a/apps/server/src/homesec/homesec.controller.ts
+++ b/apps/server/src/homesec/homesec.controller.ts
@@ -36,7 +36,7 @@ export class HomesecController {
@UseGuards(JwtAuthGuard)
@Get("status")
- async getDevices(
+ async getStatus(
@Request() req: AuthenticatedRequest
): Promise {
this.logger.verbose(`[${req.user.name}] GET to /api/homesec/devices`);
@@ -49,6 +49,7 @@ export class HomesecController {
password: this.password,
}
).json();
+ const status = panelResponse.updates.mode_a1;
try {
await wait(5000);
@@ -61,7 +62,7 @@ export class HomesecController {
).json();
return {
- status: panelResponse.updates.mode_a1,
+ status,
devices: devicesResponse.senrows.map(
({ id, name, type_f, status, rssi }) => ({
id,
@@ -75,7 +76,7 @@ export class HomesecController {
} catch (err) {
this.logger.error("deviceListGet:", err);
return {
- status: panelResponse.updates.mode_a1,
+ status,
devices: [],
};
}
From c01ce111d7ce73b2b78a9214dac1e2c6a171435a Mon Sep 17 00:00:00 2001
From: mdvanes <4253562+mdvanes@users.noreply.github.com>
Date: Mon, 15 Apr 2024 16:37:50 +0200
Subject: [PATCH 06/18] clean up
---
.../src/Components/Molecules/HomeSec/HomeSec.test.tsx | 9 ++++-----
1 file changed, 4 insertions(+), 5 deletions(-)
diff --git a/apps/client/src/Components/Molecules/HomeSec/HomeSec.test.tsx b/apps/client/src/Components/Molecules/HomeSec/HomeSec.test.tsx
index 809027ca..83e18ea3 100644
--- a/apps/client/src/Components/Molecules/HomeSec/HomeSec.test.tsx
+++ b/apps/client/src/Components/Molecules/HomeSec/HomeSec.test.tsx
@@ -1,11 +1,10 @@
+import { HomesecStatusResponse } from "@homeremote/types";
import { render, screen } from "@testing-library/react";
-import { jukeboxApi } from "../../../Services/jukeboxApi";
-import { MockStoreProvider } from "../../../testHelpers";
-import { FC, ReactNode } from "react";
-import HomeSec from "./HomeSec";
import fetchMock, { enableFetchMocks } from "jest-fetch-mock";
-import { HomesecStatusResponse, PlaylistsResponse } from "@homeremote/types";
+import { FC, ReactNode } from "react";
import { homesecApi } from "../../../Services/homesecApi";
+import { MockStoreProvider } from "../../../testHelpers";
+import HomeSec from "./HomeSec";
enableFetchMocks();
From ded8cb0054fcfda58e79495d1d45e05323457083 Mon Sep 17 00:00:00 2001
From: mdvanes <4253562+mdvanes@users.noreply.github.com>
Date: Mon, 15 Apr 2024 19:42:18 +0200
Subject: [PATCH 07/18] chore: normalize error retry banners
---
.../Molecules/DockerList/DockerList.tsx | 17 ++-
.../Molecules/DownloadList/DownloadList.tsx | 28 +++--
.../Molecules/ErrorRetry/ErrorRetry.tsx | 52 ++++++++
.../Components/Molecules/HomeSec/HomeSec.tsx | 118 ++++++++++--------
4 files changed, 139 insertions(+), 76 deletions(-)
create mode 100644 apps/client/src/Components/Molecules/ErrorRetry/ErrorRetry.tsx
diff --git a/apps/client/src/Components/Molecules/DockerList/DockerList.tsx b/apps/client/src/Components/Molecules/DockerList/DockerList.tsx
index b4871091..666bda17 100644
--- a/apps/client/src/Components/Molecules/DockerList/DockerList.tsx
+++ b/apps/client/src/Components/Molecules/DockerList/DockerList.tsx
@@ -1,4 +1,4 @@
-import { Alert, Box, Grid } from "@mui/material";
+import { Box, Grid } from "@mui/material";
import { Stack } from "@mui/system";
import { FC, useEffect, useState } from "react";
import {
@@ -8,6 +8,7 @@ import {
import { getErrorMessage } from "../../../Utils/getErrorMessage";
import CardExpandBar from "../CardExpandBar/CardExpandBar";
import DockerInfo from "../DockerInfo/DockerInfo";
+import ErrorRetry from "../ErrorRetry/ErrorRetry";
import LoadingDot from "../LoadingDot/LoadingDot";
import ContainerDot from "./ContainerDot";
@@ -22,14 +23,12 @@ interface DockerListProps {
const DockerList: FC = ({ onError }) => {
const [isOpen, setIsOpen] = useState(false);
const [isSkippingBecauseError, setIsSkippingBecauseError] = useState(false);
- const { data, isLoading, isFetching, error } = useGetDockerListQuery(
- undefined,
- {
+ const { data, isLoading, isFetching, error, refetch } =
+ useGetDockerListQuery(undefined, {
pollingInterval: isSkippingBecauseError
? undefined
: UPDATE_INTERVAL_MS,
- }
- );
+ });
useEffect(() => {
if (error) {
@@ -40,9 +39,9 @@ const DockerList: FC = ({ onError }) => {
if (error) {
return (
-
- {getErrorMessage(error)}
-
+ refetch()}>
+ DockerList could not load
+
);
}
diff --git a/apps/client/src/Components/Molecules/DownloadList/DownloadList.tsx b/apps/client/src/Components/Molecules/DownloadList/DownloadList.tsx
index 82bfea2b..f85fc7bf 100644
--- a/apps/client/src/Components/Molecules/DownloadList/DownloadList.tsx
+++ b/apps/client/src/Components/Molecules/DownloadList/DownloadList.tsx
@@ -1,11 +1,13 @@
+import { List, Paper } from "@mui/material";
import { FC, useEffect, useState } from "react";
-import { Alert, List, Paper } from "@mui/material";
-import { useAppDispatch } from "../../../store";
-import DownloadListItem from "./DownloadListItem";
-import { logError } from "../LogCard/logSlice";
import { useGetDownloadListQuery } from "../../../Services/downloadListApi";
+import { getErrorMessage } from "../../../Utils/getErrorMessage";
+import { useAppDispatch } from "../../../store";
import CardExpandBar from "../CardExpandBar/CardExpandBar";
+import ErrorRetry from "../ErrorRetry/ErrorRetry";
import LoadingDot from "../LoadingDot/LoadingDot";
+import { logError } from "../LogCard/logSlice";
+import DownloadListItem from "./DownloadListItem";
const UPDATE_INTERVAL_MS = 30000;
@@ -14,20 +16,20 @@ const DownloadList: FC = () => {
const [isSkippingBecauseError, setIsSkippingBecauseError] = useState(false);
const dispatch = useAppDispatch();
- const { data, error, isLoading, isFetching } = useGetDownloadListQuery(
- undefined,
- {
+ const { data, error, isLoading, isFetching, refetch } =
+ useGetDownloadListQuery(undefined, {
pollingInterval: isSkippingBecauseError
? undefined
: UPDATE_INTERVAL_MS,
- }
- );
+ });
const [listItems, setListItems] = useState([]);
useEffect(() => {
if (error) {
setIsSkippingBecauseError(true);
- dispatch(logError("GetDownloadList failed"));
+ dispatch(
+ logError(`GetDownloadList failed: ${getErrorMessage(error)}`)
+ );
}
}, [dispatch, error]);
@@ -50,9 +52,9 @@ const DownloadList: FC = () => {
{error && (
-
- There is an error, data may be stale
-
+ refetch()}>
+ DL could not load
+
)}
{listItems}
void;
+}
+
+export const ErrorRetry: FC = ({
+ marginate = false,
+ children,
+ retry,
+}) => {
+ return (
+
+
+ {children}
+
+
+
+
+
+
+
+ );
+};
+
+export default ErrorRetry;
diff --git a/apps/client/src/Components/Molecules/HomeSec/HomeSec.tsx b/apps/client/src/Components/Molecules/HomeSec/HomeSec.tsx
index 491af252..273a2241 100644
--- a/apps/client/src/Components/Molecules/HomeSec/HomeSec.tsx
+++ b/apps/client/src/Components/Molecules/HomeSec/HomeSec.tsx
@@ -1,15 +1,17 @@
-import { FC } from "react";
-import { useGetHomesecStatusQuery } from "../../../Services/homesecApi";
+import { HomesecStatusResponse, TypeF } from "@homeremote/types";
import {
+ Icon,
List,
ListItem,
ListItemAvatar,
ListItemButton,
ListItemText,
Paper,
- Icon,
+ Tooltip,
} from "@mui/material";
-import { HomesecStatusResponse, TypeF } from "@homeremote/types";
+import { FC } from "react";
+import { useGetHomesecStatusQuery } from "../../../Services/homesecApi";
+import ErrorRetry from "../ErrorRetry/ErrorRetry";
import LoadingDot from "../LoadingDot/LoadingDot";
import SimpleHomeSecListItem from "./SimpleHomeSecListItem";
@@ -31,63 +33,71 @@ export const HomeSec: FC = () => {
const { data, isLoading, isFetching, isError, refetch } =
useGetHomesecStatusQuery(undefined);
+ const hasNoDevices =
+ !isError &&
+ !isLoading &&
+ !isFetching &&
+ (!data?.devices || data?.devices.length === 0);
+
return (
-
-
- {isError ? (
- refetch()}
- />
- ) : (
- ""
- )}
- {!data?.devices || data?.devices.length === 0 ? (
- refetch()}
- />
- ) : (
- ""
- )}
- {data?.devices?.map((sensor) => (
-
-
-
- {typeIcon[sensor.type_f]}
-
-
- {sensor.name}
+
+
+
+ {isError && (
+ refetch()}>
+ HomeSec could not load
+
+ )}
+ {hasNoDevices && (
+ refetch()}
+ />
+ )}
+ {data?.devices?.map((sensor) => (
+
+
+
+ {typeIcon[sensor.type_f]}
+
+
- {sensor.status}
- {sensor.rssi}
+ {sensor.name}
+
+
{sensor.status}
+
{sensor.rssi}
+
-
- }
- />
-
-
- ))}
-
+ }
+ />
+
+
+ ))}
+
+
);
};
From 8a2698563e457f4a9e15671ff36b5132ea1ba0bb Mon Sep 17 00:00:00 2001
From: mdvanes <4253562+mdvanes@users.noreply.github.com>
Date: Mon, 15 Apr 2024 20:08:35 +0200
Subject: [PATCH 08/18] chore: refactor loading dot
---
.../Components/Molecules/HomeSec/HomeSec.tsx | 29 ++++++++++++++++---
.../Molecules/LoadingDot/LoadingDot.tsx | 13 +++++----
2 files changed, 32 insertions(+), 10 deletions(-)
diff --git a/apps/client/src/Components/Molecules/HomeSec/HomeSec.tsx b/apps/client/src/Components/Molecules/HomeSec/HomeSec.tsx
index 273a2241..c95920e7 100644
--- a/apps/client/src/Components/Molecules/HomeSec/HomeSec.tsx
+++ b/apps/client/src/Components/Molecules/HomeSec/HomeSec.tsx
@@ -9,10 +9,13 @@ import {
Paper,
Tooltip,
} from "@mui/material";
-import { FC } from "react";
+import { FC, useEffect, useState } from "react";
import { useGetHomesecStatusQuery } from "../../../Services/homesecApi";
+import { getErrorMessage } from "../../../Utils/getErrorMessage";
+import { useAppDispatch } from "../../../store";
import ErrorRetry from "../ErrorRetry/ErrorRetry";
import LoadingDot from "../LoadingDot/LoadingDot";
+import { logError } from "../LogCard/logSlice";
import SimpleHomeSecListItem from "./SimpleHomeSecListItem";
const statusClass: Record = {
@@ -29,9 +32,24 @@ const typeIcon: Record = {
"Remote Controller": "settings_remote",
};
+const UPDATE_INTERVAL_MS = 120000;
+
export const HomeSec: FC = () => {
- const { data, isLoading, isFetching, isError, refetch } =
- useGetHomesecStatusQuery(undefined);
+ const [isSkippingBecauseError, setIsSkippingBecauseError] = useState(false);
+ const dispatch = useAppDispatch();
+ const { data, isLoading, isFetching, isError, error, refetch } =
+ useGetHomesecStatusQuery(undefined, {
+ pollingInterval: isSkippingBecauseError
+ ? undefined
+ : UPDATE_INTERVAL_MS,
+ });
+
+ useEffect(() => {
+ if (error) {
+ setIsSkippingBecauseError(true);
+ dispatch(logError(`HomeSec failed: ${getErrorMessage(error)}`));
+ }
+ }, [dispatch, error]);
const hasNoDevices =
!isError &&
@@ -49,7 +67,10 @@ export const HomeSec: FC = () => {
borderColor: statusClass[data?.status ?? "Error"],
}}
>
-
+
{isError && (
refetch()}>
HomeSec could not load
diff --git a/apps/client/src/Components/Molecules/LoadingDot/LoadingDot.tsx b/apps/client/src/Components/Molecules/LoadingDot/LoadingDot.tsx
index fecca696..80597a88 100644
--- a/apps/client/src/Components/Molecules/LoadingDot/LoadingDot.tsx
+++ b/apps/client/src/Components/Molecules/LoadingDot/LoadingDot.tsx
@@ -3,10 +3,11 @@ import { FC, useEffect, useState } from "react";
const SLOW_UPDATE_MS = 1000; // if the response takes longer than 1000ms, it is considered slow and the full progress bar is shown
-const LoadingDot: FC<{ isLoading: boolean; noMargin?: boolean }> = ({
- isLoading,
- noMargin = false,
-}) => {
+const LoadingDot: FC<{
+ isLoading: boolean;
+ noMargin?: boolean;
+ slowUpdateMs?: number;
+}> = ({ isLoading, noMargin = false, slowUpdateMs = SLOW_UPDATE_MS }) => {
const [isSlow, setIsSlow] = useState(false);
useEffect(() => {
@@ -16,14 +17,14 @@ const LoadingDot: FC<{ isLoading: boolean; noMargin?: boolean }> = ({
setIsSlow(false);
timer = setTimeout(() => {
setIsSlow(true);
- }, SLOW_UPDATE_MS);
+ }, slowUpdateMs);
}
return () => {
if (timer) {
clearTimeout(timer);
}
};
- }, [isLoading]);
+ }, [isLoading, slowUpdateMs]);
return (
Date: Mon, 15 Apr 2024 20:28:30 +0200
Subject: [PATCH 09/18] chore: fix validate
---
.github/workflows/validateAndBuild.yml | 6 +++---
.../src/Components/Pages/Dashboard/Dashboard.test.tsx | 1 +
.../Pages/Dashboard/__snapshots__/Dashboard.test.tsx.snap | 1 +
3 files changed, 5 insertions(+), 3 deletions(-)
diff --git a/.github/workflows/validateAndBuild.yml b/.github/workflows/validateAndBuild.yml
index 9d731786..135acd5e 100644
--- a/.github/workflows/validateAndBuild.yml
+++ b/.github/workflows/validateAndBuild.yml
@@ -9,13 +9,13 @@ jobs:
strategy:
matrix:
- node-version: [16.x]
+ node-version: [20.x]
steps:
- name: Checkout ๐๏ธ
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
- uses: actions/setup-node@v3
+ uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- name: Install and Build components ๐ง
diff --git a/apps/client/src/Components/Pages/Dashboard/Dashboard.test.tsx b/apps/client/src/Components/Pages/Dashboard/Dashboard.test.tsx
index 8dc7a66d..d58010d6 100644
--- a/apps/client/src/Components/Pages/Dashboard/Dashboard.test.tsx
+++ b/apps/client/src/Components/Pages/Dashboard/Dashboard.test.tsx
@@ -30,6 +30,7 @@ jest.mock(
() => "mock-previously-played-card"
);
jest.mock("../../Molecules/CarTwin/CarTwinCard", () => "mock-cartwin-card");
+jest.mock("../../Molecules/HomeSec/HomeSec", () => "mock-homesec");
describe("Dashboard page", () => {
it("contains all the control components", () => {
diff --git a/apps/client/src/Components/Pages/Dashboard/__snapshots__/Dashboard.test.tsx.snap b/apps/client/src/Components/Pages/Dashboard/__snapshots__/Dashboard.test.tsx.snap
index 165fd2d9..5c0a5293 100644
--- a/apps/client/src/Components/Pages/Dashboard/__snapshots__/Dashboard.test.tsx.snap
+++ b/apps/client/src/Components/Pages/Dashboard/__snapshots__/Dashboard.test.tsx.snap
@@ -15,6 +15,7 @@ exports[`Dashboard page contains all the control components 1`] = `
+
From 5932ec53f4d94d797ca0ada906db5b13c972b959 Mon Sep 17 00:00:00 2001
From: mdvanes <4253562+mdvanes@users.noreply.github.com>
Date: Tue, 16 Apr 2024 10:36:20 +0200
Subject: [PATCH 10/18] refactor: Monit error logging
---
.../src/Components/Molecules/Monit/Monit.tsx | 32 ++++++++++++++-----
1 file changed, 24 insertions(+), 8 deletions(-)
diff --git a/apps/client/src/Components/Molecules/Monit/Monit.tsx b/apps/client/src/Components/Molecules/Monit/Monit.tsx
index e3bb88b9..fe35c3ce 100644
--- a/apps/client/src/Components/Molecules/Monit/Monit.tsx
+++ b/apps/client/src/Components/Molecules/Monit/Monit.tsx
@@ -1,23 +1,39 @@
-import { Alert, Card, CardContent } from "@mui/material";
-import { FC } from "react";
+import { Card, CardContent } from "@mui/material";
+import { FC, useEffect, useState } from "react";
import { useGetMonitStatusQuery } from "../../../Services/monitApi";
import { getErrorMessage } from "../../../Utils/getErrorMessage";
+import { useAppDispatch } from "../../../store";
+import ErrorRetry from "../ErrorRetry/ErrorRetry";
import LoadingDot from "../LoadingDot/LoadingDot";
+import { logError } from "../LogCard/logSlice";
import MonitInstance from "./MonitInstance";
// Monit only updates once per minute on the backend
const UPDATE_INTERVAL_MS = 60 * 1000;
const Monit: FC = () => {
- const { data, isLoading, isFetching, error } = useGetMonitStatusQuery(
- undefined,
- {
- pollingInterval: UPDATE_INTERVAL_MS,
+ const [isSkippingBecauseError, setIsSkippingBecauseError] = useState(false);
+ const dispatch = useAppDispatch();
+ const { data, isLoading, isFetching, error, refetch } =
+ useGetMonitStatusQuery(undefined, {
+ pollingInterval: isSkippingBecauseError
+ ? undefined
+ : UPDATE_INTERVAL_MS,
+ });
+
+ useEffect(() => {
+ if (error) {
+ setIsSkippingBecauseError(true);
+ dispatch(logError(`Monit failed: ${getErrorMessage(error)}`));
}
- );
+ }, [dispatch, error]);
if (error) {
- return {getErrorMessage(error)};
+ return (
+ refetch()}>
+ Monit could not load
+
+ );
}
return (
From 0f44b64c861027614a9b29c82927bf651c4329bc Mon Sep 17 00:00:00 2001
From: mdvanes <4253562+mdvanes@users.noreply.github.com>
Date: Tue, 16 Apr 2024 10:59:08 +0200
Subject: [PATCH 11/18] chore: collapse non doors in homesec
---
.../Components/Molecules/HomeSec/HomeSec.tsx | 38 +++++++++++++++++--
.../HomeSec/SimpleHomeSecListItem.tsx | 9 ++++-
libs/types/src/lib/homesec.types.ts | 5 ++-
3 files changed, 47 insertions(+), 5 deletions(-)
diff --git a/apps/client/src/Components/Molecules/HomeSec/HomeSec.tsx b/apps/client/src/Components/Molecules/HomeSec/HomeSec.tsx
index c95920e7..86696a1a 100644
--- a/apps/client/src/Components/Molecules/HomeSec/HomeSec.tsx
+++ b/apps/client/src/Components/Molecules/HomeSec/HomeSec.tsx
@@ -13,6 +13,7 @@ import { FC, useEffect, useState } from "react";
import { useGetHomesecStatusQuery } from "../../../Services/homesecApi";
import { getErrorMessage } from "../../../Utils/getErrorMessage";
import { useAppDispatch } from "../../../store";
+import CardExpandBar from "../CardExpandBar/CardExpandBar";
import ErrorRetry from "../ErrorRetry/ErrorRetry";
import LoadingDot from "../LoadingDot/LoadingDot";
import { logError } from "../LogCard/logSlice";
@@ -35,6 +36,7 @@ const typeIcon: Record = {
const UPDATE_INTERVAL_MS = 120000;
export const HomeSec: FC = () => {
+ const [isOpen, setIsOpen] = useState(false);
const [isSkippingBecauseError, setIsSkippingBecauseError] = useState(false);
const dispatch = useAppDispatch();
const { data, isLoading, isFetching, isError, error, refetch } =
@@ -57,6 +59,13 @@ export const HomeSec: FC = () => {
!isFetching &&
(!data?.devices || data?.devices.length === 0);
+ const devices = data?.devices ?? [];
+
+ // By default, only show doors
+ const shownDevices = isOpen
+ ? devices
+ : devices.filter(({ type_f }) => type_f === "Door Contact");
+
return (
{
onClick={() => refetch()}
/>
)}
- {data?.devices?.map((sensor) => (
+ {shownDevices.map((sensor) => (
{
>
- {typeIcon[sensor.type_f]}
+
+ {typeIcon[sensor.type_f]}
+
{
gap: "16px",
}}
>
- {sensor.status}
+
+ {sensor.status}
+
+ {sensor.cond_ok === "1" ? (
+
+ check_circle_outline
+
+ ) : (
+
+ error_outline
+
+ )}
+
{sensor.rssi}
@@ -117,6 +140,15 @@ export const HomeSec: FC = () => {
))}
+ {devices.length > 0 && (
+
+ )}
);
diff --git a/apps/client/src/Components/Molecules/HomeSec/SimpleHomeSecListItem.tsx b/apps/client/src/Components/Molecules/HomeSec/SimpleHomeSecListItem.tsx
index c56e7346..f04c0900 100644
--- a/apps/client/src/Components/Molecules/HomeSec/SimpleHomeSecListItem.tsx
+++ b/apps/client/src/Components/Molecules/HomeSec/SimpleHomeSecListItem.tsx
@@ -20,7 +20,14 @@ const SimpleHomeSecListItem: FC<{ title: string; onClick: () => void }> = ({
primary={
<>
{title}
-
+
>
diff --git a/libs/types/src/lib/homesec.types.ts b/libs/types/src/lib/homesec.types.ts
index 635bd1f3..9e8408fd 100644
--- a/libs/types/src/lib/homesec.types.ts
+++ b/libs/types/src/lib/homesec.types.ts
@@ -67,5 +67,8 @@ export interface HomesecPanelResponse {
export interface HomesecStatusResponse {
status: Modes;
- devices: Pick[];
+ devices: Pick<
+ SensorRow,
+ "id" | "name" | "type_f" | "status" | "rssi" | "cond_ok"
+ >[];
}
From aff8f6ecac1e506d0e89530b6fb42928d4360a5b Mon Sep 17 00:00:00 2001
From: mdvanes <4253562+mdvanes@users.noreply.github.com>
Date: Tue, 16 Apr 2024 11:16:48 +0200
Subject: [PATCH 12/18] chore: try splitting build
---
.github/workflows/validateAndBuild.yml | 4 +++-
.../Components/Molecules/HomeSec/HomeSec.tsx | 21 ++++++++++++++-----
apps/server/src/homesec/homesec.controller.ts | 3 ++-
3 files changed, 21 insertions(+), 7 deletions(-)
diff --git a/.github/workflows/validateAndBuild.yml b/.github/workflows/validateAndBuild.yml
index 135acd5e..195bb037 100644
--- a/.github/workflows/validateAndBuild.yml
+++ b/.github/workflows/validateAndBuild.yml
@@ -18,11 +18,13 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- - name: Install and Build components ๐ง
+ - name: Install dependencies ๐ง
run: |
npm i --legacy-peer-deps
# Workaround for missing binary
npm i @swc/core-linux-x64-gnu --legacy-peer-deps
+ - name: Build components ๐ง
+ run: |
npm run build --if-present
env:
CI: false # true -> fails on warning
diff --git a/apps/client/src/Components/Molecules/HomeSec/HomeSec.tsx b/apps/client/src/Components/Molecules/HomeSec/HomeSec.tsx
index 86696a1a..a252c1a9 100644
--- a/apps/client/src/Components/Molecules/HomeSec/HomeSec.tsx
+++ b/apps/client/src/Components/Molecules/HomeSec/HomeSec.tsx
@@ -116,18 +116,29 @@ export const HomeSec: FC = () => {
+
{sensor.status}
- {sensor.status}
-
{sensor.cond_ok === "1" ? (
-
+
check_circle_outline
) : (
-
+
error_outline
)}
diff --git a/apps/server/src/homesec/homesec.controller.ts b/apps/server/src/homesec/homesec.controller.ts
index 948b9330..b327e42e 100644
--- a/apps/server/src/homesec/homesec.controller.ts
+++ b/apps/server/src/homesec/homesec.controller.ts
@@ -64,12 +64,13 @@ export class HomesecController {
return {
status,
devices: devicesResponse.senrows.map(
- ({ id, name, type_f, status, rssi }) => ({
+ ({ id, name, type_f, status, rssi, cond_ok }) => ({
id,
name,
type_f,
status,
rssi,
+ cond_ok,
})
),
};
From 363d5141d4d1f7410a20833fd490caa1066458b2 Mon Sep 17 00:00:00 2001
From: mdvanes <4253562+mdvanes@users.noreply.github.com>
Date: Tue, 16 Apr 2024 11:26:29 +0200
Subject: [PATCH 13/18] chore: split build into steps
---
.github/workflows/validateAndBuild.yml | 17 +++++++++++++----
1 file changed, 13 insertions(+), 4 deletions(-)
diff --git a/.github/workflows/validateAndBuild.yml b/.github/workflows/validateAndBuild.yml
index 195bb037..f18164cf 100644
--- a/.github/workflows/validateAndBuild.yml
+++ b/.github/workflows/validateAndBuild.yml
@@ -18,17 +18,26 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- - name: Install dependencies ๐ง
+ - name: Install dependencies โ๏ธ
run: |
npm i --legacy-peer-deps
# Workaround for missing binary
npm i @swc/core-linux-x64-gnu --legacy-peer-deps
- - name: Build components ๐ง
+ - name: Typecheck ๐ค
+ run: |
+ npm run typecheck --if-present
+ - name: Lint ๐
+ run: |
+ npm run lint --if-present
+ - name: Test ๐งช
run: |
- npm run build --if-present
+ npm run test:ci --if-present
env:
CI: false # true -> fails on warning
- - name: Build docker image
+ - name: Build components ๐ง
+ run: |
+ npm run build --ignore-scripts --if-present
+ - name: Build docker image ๐ฟ
if: ${{ github.ref == 'main' }}
run: |
docker build . -t mdworld/homeremote:latest
\ No newline at end of file
From 1efe1f019dd163080d1715bcdc78af360596ac26 Mon Sep 17 00:00:00 2001
From: mdvanes <4253562+mdvanes@users.noreply.github.com>
Date: Tue, 16 Apr 2024 11:30:38 +0200
Subject: [PATCH 14/18] chore: update publish script
---
.github/workflows/publish.yml | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 0c8f1451..2f911a62 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -10,20 +10,20 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
- node-version: [16.x]
+ node-version: [20.x]
steps:
- name: Check out the repo ๐๏ธ
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: Log in to Docker Hub
- uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9
+ uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Extract metadata (tags, labels) for Docker
id: meta
- uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38
+ uses: docker/metadata-action@v5
with:
images: mdworld/homeremote
@@ -40,7 +40,7 @@ jobs:
- name: Build and push Docker image
# if: ${{ github.ref == 'main' }}
- uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc
+ uses: docker/build-push-action@v5
with:
context: .
push: true
From dd5924e09bfc244bdc7a4a1bddb0228f5b3bb362 Mon Sep 17 00:00:00 2001
From: mdvanes <4253562+mdvanes@users.noreply.github.com>
Date: Tue, 16 Apr 2024 11:33:50 +0200
Subject: [PATCH 15/18] chore: add writeGitInfo to build
---
.github/workflows/validateAndBuild.yml | 14 ++++++--------
1 file changed, 6 insertions(+), 8 deletions(-)
diff --git a/.github/workflows/validateAndBuild.yml b/.github/workflows/validateAndBuild.yml
index f18164cf..c98ab7de 100644
--- a/.github/workflows/validateAndBuild.yml
+++ b/.github/workflows/validateAndBuild.yml
@@ -23,20 +23,18 @@ jobs:
npm i --legacy-peer-deps
# Workaround for missing binary
npm i @swc/core-linux-x64-gnu --legacy-peer-deps
+ - name: writeGitInfo โ๏ธ
+ run: npm run writeGitInfo --if-present
- name: Typecheck ๐ค
- run: |
- npm run typecheck --if-present
+ run: npm run typecheck --if-present
- name: Lint ๐
- run: |
- npm run lint --if-present
+ run: npm run lint --if-present
- name: Test ๐งช
- run: |
- npm run test:ci --if-present
+ run: npm run test:ci --if-present
env:
CI: false # true -> fails on warning
- name: Build components ๐ง
- run: |
- npm run build --ignore-scripts --if-present
+ run: npm run build --ignore-scripts --if-present
- name: Build docker image ๐ฟ
if: ${{ github.ref == 'main' }}
run: |
From 8f1db075579596178637ecaae789253772a905d6 Mon Sep 17 00:00:00 2001
From: mdvanes <4253562+mdvanes@users.noreply.github.com>
Date: Tue, 16 Apr 2024 11:52:10 +0200
Subject: [PATCH 16/18] fix test
---
apps/client/src/Components/Molecules/HomeSec/HomeSec.test.tsx | 1 +
1 file changed, 1 insertion(+)
diff --git a/apps/client/src/Components/Molecules/HomeSec/HomeSec.test.tsx b/apps/client/src/Components/Molecules/HomeSec/HomeSec.test.tsx
index 83e18ea3..eb03ad13 100644
--- a/apps/client/src/Components/Molecules/HomeSec/HomeSec.test.tsx
+++ b/apps/client/src/Components/Molecules/HomeSec/HomeSec.test.tsx
@@ -25,6 +25,7 @@ describe("HomeSec", () => {
status: "Door Close",
rssi: "Strong, 9",
type_f: "Door Contact",
+ cond_ok: "1",
},
],
};
From e4b1372aebdd69e3655de51bff75b3869e9e4c73 Mon Sep 17 00:00:00 2001
From: mdvanes <4253562+mdvanes@users.noreply.github.com>
Date: Tue, 16 Apr 2024 12:19:43 +0200
Subject: [PATCH 17/18] chore: show docker compose project
---
.../Molecules/DockerInfo/DockerInfo.tsx | 10 ++++--
.../Molecules/DockerList/ContainerDot.tsx | 2 +-
.../Molecules/DockerList/DockerList.tsx | 6 ++--
apps/client/src/Services/dockerListApi.ts | 13 +-------
.../src/dockerlist/dockerlist.controller.ts | 32 +++++++++++++------
libs/types/src/lib/dockerlist.types.ts | 3 ++
6 files changed, 36 insertions(+), 30 deletions(-)
diff --git a/apps/client/src/Components/Molecules/DockerInfo/DockerInfo.tsx b/apps/client/src/Components/Molecules/DockerInfo/DockerInfo.tsx
index db8fa3c6..bc3af90f 100644
--- a/apps/client/src/Components/Molecules/DockerInfo/DockerInfo.tsx
+++ b/apps/client/src/Components/Molecules/DockerInfo/DockerInfo.tsx
@@ -1,3 +1,4 @@
+import { DockerContainerInfo } from "@homeremote/types";
import {
Alert,
Button,
@@ -10,7 +11,6 @@ import {
} from "@mui/material";
import { FC, useState } from "react";
import {
- DockerContainerInfo,
useStartDockerMutation,
useStopDockerMutation,
} from "../../../Services/dockerListApi";
@@ -18,7 +18,7 @@ import {
const DockerInfo: FC<{ info: DockerContainerInfo }> = ({ info }) => {
const [startDocker] = useStartDockerMutation();
const [stopDocker] = useStopDockerMutation();
- const { Names, Status, Id, State } = info;
+ const { Names, Status, Id, State, Labels } = info;
const isUp = Status.indexOf("Up") === 0;
const toggleContainer = () => {
@@ -50,7 +50,11 @@ const DockerInfo: FC<{ info: DockerContainerInfo }> = ({ info }) => {
cursor: "pointer",
}}
>
- {name} | {Status}
+ {name}{" "}
+ {Labels["com.docker.compose.project"]
+ ? `(${Labels["com.docker.compose.project"]})`
+ : ""}{" "}
+ | {Status}