Skip to content

Commit

Permalink
Merge pull request #11 from mibe-iot/feature/settings
Browse files Browse the repository at this point in the history
Feature/settings
  • Loading branch information
Sintexer authored May 21, 2022
2 parents c8348db + 9db5544 commit 18e7bfe
Show file tree
Hide file tree
Showing 49 changed files with 1,088 additions and 185 deletions.
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

0 comments on commit 18e7bfe

Please sign in to comment.