Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/settings #11

Merged
merged 14 commits into from
May 21, 2022
2 changes: 2 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules
.vscode
5 changes: 0 additions & 5 deletions .idea/.gitignore

This file was deleted.

6 changes: 0 additions & 6 deletions .idea/inspectionProfiles/Project_Default.xml

This file was deleted.

8 changes: 0 additions & 8 deletions .idea/modules.xml

This file was deleted.

12 changes: 0 additions & 12 deletions .idea/thinker-react.iml

This file was deleted.

6 changes: 0 additions & 6 deletions .idea/vcs.xml

This file was deleted.

18 changes: 18 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
FROM node:16 as build-stage

WORKDIR /app

COPY package*.json /app/

RUN npm ci --only=production

COPY ./ /app/

RUN npm run build


FROM nginx:1.21

COPY --from=build-stage /app/build/ /usr/share/nginx/html

COPY --from=build-stage /nginx.conf /etc/nginx/conf.d/default.conf
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,5 @@
"last 1 firefox version",
"last 1 safari version"
]
},
"proxy": "http://thinker.local:8080/"
}
}
6 changes: 6 additions & 0 deletions src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import { Main } from "./pages/Main";
import Home from "./pages/Home";
import { ConnectPage } from "./pages/ConnectPage";
import { SettingsPage } from "pages/settings/SettingsPage";
import { HooksPage } from "pages/hooks/HooksPage";
import { AddDeviceTriggersPage } from "pages/AddDeviceTriggersPage";

function App() {
return (
Expand All @@ -10,6 +13,9 @@ function App() {
<Route path="/" element={<Main />}>
<Route index element={<Home />} />
<Route path={"connect"} element={<ConnectPage />} />
<Route path={"settings"} element={<SettingsPage />} />
<Route path={"hooks"} element={<HooksPage />} />
<Route path={"devices/:deviceId/triggers"} element={<AddDeviceTriggersPage />} />
</Route>
</Routes>
</Router>
Expand Down
3 changes: 2 additions & 1 deletion src/api/ThinkerApi.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import canNdjsonStream from "can-ndjson-stream";
import { BACKEND_URL } from "./contants";

export const BASE_URL = "/api"
export const BASE_URL = `${BACKEND_URL}/api`

export const buildApiUrl = (url, options) => {
let apiUrl = BASE_URL + url;
Expand Down
16 changes: 15 additions & 1 deletion src/api/contants.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,21 @@
export const BACKEND_URL = "http://thinker.local:8080"

export const DEVICE_STATUS_WAITING_CONFIGURATION = "WAITING_CONFIGURATION";

export const DEVICE_NAME_MIN_LENGTH = 4;
export const DEVICE_NAME_LENGTH = 256;
export const DEVICE_DESCRIPTION_LENGTH = 2048;
export const DEVICE_CLASS_MAX_LENGTH = 256;
export const DEVICE_REPORT_TYPES_MIN_LENGTH = 1;
export const DEVICE_REPORT_TYPES_MIN_LENGTH = 1;

export const WIFI_PASSWORD_MIN_LENGTH = 8
export const WIFI_PASSWORD_MAX_LENGTH = 256
export const WIFI_SSID_MIN_LENGTH = 2
export const WIFI_SSID_MAX_LENGTH = 256

export const EMAIL_MIN_LENGTH = 6
export const EMAIL_MAX_LENGTH = 256
export const EMAIL_PASSWORD_MIN_LENGTH = 2
export const EMAIL_PASSWORD_MAX_LENGTH = 256

export const HOOK_TYPE_SEND_EMAIL = "send_email"
36 changes: 36 additions & 0 deletions src/api/services/appSettingsApi.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/dist/query/react";
import { BACKEND_URL } from "api/contants";

export const APP_SETTINGS_TYPE = "APPLICATION";
export const MAIL_SETTINGS_TYPE = "MAIL";

export const appSettingsApi = createApi({
reducerPath: "appSettingsApi",
baseQuery: fetchBaseQuery({ baseUrl: `${BACKEND_URL}/api/settings` }),
endpoints: builder => ({
getSettingsStatus: builder.query({
query: () => ({ method: "GET", url: "/status" })
}),
getSettingsByType: builder.query({
query: ({type}) => ({ method: "GET", url: `/${type}` })
}),
getAppSettings: builder.query({
query: () => ({ method: "GET", url: "" })
}),
updateMailSettings: builder.mutation({
query: ({...data }) => ({ method: "POST", url: "/mail", body: { ...data } })
}),
updateAppSettings: builder.mutation({
query: ({...data }) => ({ method: "POST", url: "", body: { ...data } })
})
})
});

export const {
useGetSettingsStatusQuery,
useGetAppSettingsQuery,
useGetSettingsByTypeQuery,
useUpdateMailSettingsMutation,
useUpdateAppSettingsMutation
} = appSettingsApi

5 changes: 3 additions & 2 deletions src/api/services/devicesApi.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/dist/query/react";
import { BACKEND_URL } from "api/contants";


export const devicesApi = createApi({
reducerPath: "devicesApi",
baseQuery: fetchBaseQuery({ baseUrl: "/api/devices/" }),
baseQuery: fetchBaseQuery({ baseUrl: `${BACKEND_URL}/api/devices` }),
endpoints: builder => ({
getReportsPage: builder.query({
query: ({ deviceId, page, pageSize }) => ({ url: `${deviceId}/reports?page=${page}&pageSize=${pageSize}` })
}),
executeAction: builder.mutation({
query: (deviceId, actionName) => ({ method: "POST", url: `${deviceId}/${actionName}` })
query: ({deviceId, actionName}) => ({ method: "POST", url: `${deviceId}/${actionName}` })
}),
patchDevice: builder.mutation({
query: ({ deviceId, ...data }) => ({ method: "PATCH", url: `${deviceId}`, body: { ...data } })
Expand Down
3 changes: 2 additions & 1 deletion src/api/services/discoveryApi.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/dist/query/react";
import { BACKEND_URL } from "api/contants";

export const discoveryApi = createApi({
reducerPath: "discoveryApi",
baseQuery: fetchBaseQuery({ baseUrl: "/api/discovery" }),
baseQuery: fetchBaseQuery({ baseUrl: `${BACKEND_URL}/api/discovery` }),
endpoints: builder => ({
getDiscoveryStatus: builder.query({
query: () => ({ url: "/status" }),
Expand Down
37 changes: 37 additions & 0 deletions src/api/services/hooksApi.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/dist/query/react";
import { BACKEND_URL } from "api/contants";

export const hooksApi = createApi({
reducerPath: "hooksApi",
baseQuery: fetchBaseQuery({ baseUrl: `${BACKEND_URL}/api` }),
endpoints: builder => ({
getAllHooks: builder.query({
query: () => ({ url: "/hooks" })
}),
createEmailHook: builder.mutation({
query: ({...hook}) => ({ method: "POST", url: "/hooks/sendEmail", body: {...hook}})
}),
deleteHook: builder.mutation({
query: (id) => ({ method: "DELETE", url: `/hooks/${id}`})
}),
getDeviceTriggers: builder.query({
query: (deviceId) => ({ url: `/triggers/${deviceId}`})
}),
createTriggers: builder.mutation({
query: ({deviceId, ...triggersAndHooks}) => ({ method: "POST", url: `/triggers/${deviceId}`, body: {...triggersAndHooks}})
}),
deleteTrigger: builder.mutation({
query: (id) => ({ method: "DELETE", url: `/triggers/${id}`})
}),
})
});


export const {
useGetAllHooksQuery,
useCreateEmailHookMutation,
useGetDeviceTriggersQuery,
useCreateTriggersMutation,
useDeleteHookMutation,
useDeleteTriggerMutation
} = hooksApi
2 changes: 1 addition & 1 deletion src/components/button/ActionButton.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { IconButton } from "@chakra-ui/react";

const ActionButton = props => {
return (
<IconButton variant="ghost" borderRadius="full" disabled={props.isLoading} {...props}>
<IconButton variant="ghost" borderRadius="full" disabled={props.isLoading || props.isDisabled} {...props}>
{props.children}
</IconButton>
);
Expand Down
7 changes: 7 additions & 0 deletions src/components/button/DeleteButton.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { CloseIcon } from "@chakra-ui/icons";
import { ActionButton } from "components/button/ActionButton";
import { ChakraIcon } from "components/icon/ChakraIcon";

export const DeleteButton = props => {
return <ActionButton variant="outline" mt={0} icon={<CloseIcon />} {...props} />;
};
8 changes: 8 additions & 0 deletions src/components/button/PlusButton.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { ActionButton } from "components/button/ActionButton";
import { ChakraIcon } from "components/icon/ChakraIcon";
import { IoAdd } from "react-icons/io5";

export const PlusButton = props => {
const icon = <ChakraIcon icon={IoAdd} fontSize="1.2rem" />;
return <ActionButton variant="outline" mt={0} icon={icon} {...props} />;
};
8 changes: 8 additions & 0 deletions src/components/button/SettingsButton.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { SettingsIcon } from "@chakra-ui/icons";
import { ActionButton } from "components/button/ActionButton";
import { Link } from "react-router-dom";

export const SettingsButton = () => {
const icon = <SettingsIcon />;
return <Link to="/settings"><ActionButton mt={0} icon={icon} /></Link>
};
30 changes: 23 additions & 7 deletions src/components/devices/discovery/DiscoveredDeviceCard.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,28 @@
import { Box, Button, Flex, Spacer, Text, VStack } from "@chakra-ui/react";
import { Box, Button, Center, Flex, Spacer, Text, VStack } from "@chakra-ui/react";
import { useConnectDeviceMutation } from "api/services/discoveryApi";
import { SpinnerContainer } from "components/spinner/SpinnerContainer";
import { useState } from "react";
import { useBackgroundColors, useBorderColors, useTextColors } from "styles/theme/foundations/colors";


export const DiscoveredDeviceCard = ({ id, name, address, discoveredAt, rssi, knownDevice }) => {
const [connectDevice, { isLoading }] = useConnectDeviceMutation(address);
export const DiscoveredDeviceCard = ({ id, name, address, discoveredAt, rssi, knownDevice, refresh }) => {
const [connectDevice] = useConnectDeviceMutation(address);
const widgetBorderColor = useBorderColors().widget;
const [isLoading, setLoading] = useState(false);

if (isLoading) {
return <Center height="100%" width="100%">
<SpinnerContainer isLoading={true}></SpinnerContainer>
</Center>
}

return (
<Flex
border={1}
p={0}
borderRadius="2xl"
borderStyle={"solid"}
borderColor={useBorderColors().widget}
borderColor={widgetBorderColor}
boxSizing="fitContent"
overflow="hidden"
flexDirection="column"
Expand All @@ -20,15 +31,20 @@ export const DiscoveredDeviceCard = ({ id, name, address, discoveredAt, rssi, kn
<Flex flex={1} w="100%" mb={3} alignItems="top">
<Text fontSize="2xl" align="start">{name ? name : "[unknown]"}</Text>
<Spacer />
<Button variant="outline" size="sm" px={5} borderRadius="full"
onClick={() => connectDevice(address)}>
<Button variant="outline" size="sm" px={5} borderRadius="full" isDisabled={isLoading}
onClick={() => {
connectDevice(address);
setLoading(true)
refresh()
}}>
Connect
</Button>
</Flex>

<VStack alignItems="start" spacing={0.5}>
<Text fontSize="sm">{address}</Text>
<Text fontSize="sm">{discoveredAt}</Text>
<Text fontSize="sm">{new Date(discoveredAt).toLocaleDateString("ru-RU",
{ year: "numeric", month: "numeric", day: "numeric", hour: "numeric", minute: "numeric" })}</Text>
<Text fontSize="sm">Rssi: {rssi}</Text>
</VStack>
</VStack>
Expand Down
28 changes: 21 additions & 7 deletions src/components/devices/discovery/DiscoveredDevices.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,40 @@
import { SimpleGrid } from "@chakra-ui/react";
import { useGetDiscoveredDevicesQuery } from "api/services/discoveryApi";
import { useEffect, useState } from "react";
import { useFetchDevicesQuery } from "store/slice/devicesSlice";
import { delay } from "utils/utils";
import { DiscoveredDeviceCard } from "./DiscoveredDeviceCard";

export const DiscoveredDevices = () => {
const { ids } = useFetchDevicesQuery();
const { sorted: discoveredDevices, isFetching, isError } = useGetDiscoveredDevicesQuery(undefined, {
const [ isRefetching, setIsRefetching ] = useState(false);
const { ids, refetch: refetchConnected } = useFetchDevicesQuery();
const { sorted: discoveredDevices, isFetching, isError, refetch, isSuccess } = useGetDiscoveredDevicesQuery(undefined, {

selectFromResult: result => ({
...result,
sorted: [...result.data]
.filter(device => !ids.includes(device.address))
.sort((a, b) => {
return b.name.localeCompare(a)
return b.knownDevice - a.knownDevice + b.name.localeCompare(a)
})
})
});
useEffect(() => {
if (discoveredDevices.length === 0 && isSuccess && !isRefetching) {
setIsRefetching(true)
delay(1, () => {
refetch();
setIsRefetching(false);
})

}
}, [discoveredDevices, isSuccess, isRefetching, refetch])
return (
!isFetching && !isError ?
<SimpleGrid w="100%" columns={{ base: 1, md: 2, lg: 3 }} spacing="1.5rem">
<SimpleGrid w="100%" columns={{ base: 1, md: 2, lg: 3 }} pb={6} spacing="1.5rem">
{discoveredDevices
// .filter(device => !connectedDevicesIds.includes(device.address))
.map((device, i) => <DiscoveredDeviceCard key={device.address} {...device} />)}
.map(device => <DiscoveredDeviceCard key={device.address} {...device} refresh={() => {delay(2, refetchConnected)} }/>)}
</SimpleGrid> : <></>
);
}
}

Loading