mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-07 16:29:51 +03:00
Merge branch 'dev' into dev
This commit is contained in:
@@ -28,7 +28,7 @@ const App: FC = () => {
|
||||
<AppRouting />
|
||||
<ToastContainer
|
||||
position="bottom-left"
|
||||
autoClose={2000}
|
||||
autoClose={3000}
|
||||
hideProgressBar={false}
|
||||
newestOnTop={false}
|
||||
closeOnClick={true}
|
||||
|
||||
@@ -30,6 +30,8 @@ import { ReactComponent as TRflag } from 'i18n/TR.svg';
|
||||
const SignIn: FC = () => {
|
||||
const authenticationContext = useContext(AuthenticationContext);
|
||||
|
||||
const { LL, setLocale, locale } = useContext(I18nContext);
|
||||
|
||||
const [signInRequest, setSignInRequest] = useState<SignInRequest>({
|
||||
username: '',
|
||||
password: ''
|
||||
@@ -39,20 +41,6 @@ const SignIn: FC = () => {
|
||||
|
||||
const updateLoginRequestValue = updateValue(setSignInRequest);
|
||||
|
||||
const validateAndSignIn = async () => {
|
||||
setProcessing(true);
|
||||
SIGN_IN_REQUEST_VALIDATOR.messages({
|
||||
required: LL.IS_REQUIRED('%s')
|
||||
});
|
||||
try {
|
||||
await validate(SIGN_IN_REQUEST_VALIDATOR, signInRequest);
|
||||
signIn();
|
||||
} catch (errors: any) {
|
||||
setFieldErrors(errors);
|
||||
setProcessing(false);
|
||||
}
|
||||
};
|
||||
|
||||
const signIn = async () => {
|
||||
try {
|
||||
const { data: loginResponse } = await AuthenticationApi.signIn(signInRequest);
|
||||
@@ -69,9 +57,21 @@ const SignIn: FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const submitOnEnter = onEnterCallback(signIn);
|
||||
const validateAndSignIn = async () => {
|
||||
setProcessing(true);
|
||||
SIGN_IN_REQUEST_VALIDATOR.messages({
|
||||
required: LL.IS_REQUIRED('%s')
|
||||
});
|
||||
try {
|
||||
await validate(SIGN_IN_REQUEST_VALIDATOR, signInRequest);
|
||||
signIn();
|
||||
} catch (errors: any) {
|
||||
setFieldErrors(errors);
|
||||
setProcessing(false);
|
||||
}
|
||||
};
|
||||
|
||||
const { LL, setLocale, locale } = useContext(I18nContext);
|
||||
const submitOnEnter = onEnterCallback(signIn);
|
||||
|
||||
const selectLocale = async (loc: Locales) => {
|
||||
localStorage.setItem('lang', loc);
|
||||
|
||||
@@ -7,8 +7,11 @@ export const API_BASE_URL = '/rest/';
|
||||
export const ES_BASE_URL = '/es/';
|
||||
export const EMSESP_API_BASE_URL = '/api/';
|
||||
export const ACCESS_TOKEN = 'access_token';
|
||||
export const WEB_SOCKET_ROOT = calculateWebSocketRoot(WS_BASE_URL);
|
||||
export const EVENT_SOURCE_ROOT = calculateEventSourceRoot(ES_BASE_URL);
|
||||
|
||||
const location = window.location;
|
||||
const webProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
export const WEB_SOCKET_ROOT = webProtocol + '//' + location.host + WS_BASE_URL;
|
||||
export const EVENT_SOURCE_ROOT = location.protocol + '//' + location.host + ES_BASE_URL;
|
||||
|
||||
export const AXIOS = axios.create({
|
||||
baseURL: API_BASE_URL,
|
||||
@@ -76,17 +79,6 @@ export const AXIOS_BIN = axios.create({
|
||||
]
|
||||
});
|
||||
|
||||
function calculateWebSocketRoot(webSocketPath: string) {
|
||||
const location = window.location;
|
||||
const webProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
return webProtocol + '//' + location.host + webSocketPath;
|
||||
}
|
||||
|
||||
function calculateEventSourceRoot(endpointPath: string) {
|
||||
const location = window.location;
|
||||
return location.protocol + '//' + location.host + endpointPath;
|
||||
}
|
||||
|
||||
export interface FileUploadConfig {
|
||||
cancelToken?: CancelToken;
|
||||
onUploadProgress?: (progressEvent: AxiosProgressEvent) => void;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { FC } from 'react';
|
||||
import { FC } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { Tabs, useMediaQuery, useTheme } from '@mui/material';
|
||||
@@ -15,7 +15,7 @@ const RouterTabs: FC<RouterTabsProps> = ({ value, children }) => {
|
||||
const theme = useTheme();
|
||||
const smallDown = useMediaQuery(theme.breakpoints.down('sm'));
|
||||
|
||||
const handleTabChange = (event: React.ChangeEvent<{}>, path: string) => {
|
||||
const handleTabChange = (event: React.ChangeEvent<HTMLInputElement>, path: string) => {
|
||||
navigate(path);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { FC, useContext } from 'react';
|
||||
import { FC, useContext } from 'react';
|
||||
import { Navigate, Route, Routes } from 'react-router-dom';
|
||||
|
||||
import { Tab } from '@mui/material';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { FC, useState, useEffect } from 'react';
|
||||
import { FC, useState, useEffect } from 'react';
|
||||
import Schema, { ValidateFieldsError } from 'async-validator';
|
||||
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import { StrictMode } from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import { createBrowserRouter, createRoutesFromElements, Route, RouterProvider } from 'react-router-dom';
|
||||
|
||||
import App from 'App';
|
||||
|
||||
const router = createBrowserRouter(createRoutesFromElements(<Route path="/*" element={<App />} />));
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
|
||||
<React.StrictMode>
|
||||
createRoot(document.getElementById('root') as HTMLElement).render(
|
||||
<StrictMode>
|
||||
<RouterProvider router={router} />
|
||||
</React.StrictMode>
|
||||
</StrictMode>
|
||||
);
|
||||
|
||||
@@ -102,7 +102,6 @@ const DashboardData: FC = () => {
|
||||
text-transform: uppercase;
|
||||
background-color: black;
|
||||
color: #90CAF9;
|
||||
|
||||
.th {
|
||||
border-bottom: 1px solid #565656;
|
||||
}
|
||||
@@ -111,18 +110,15 @@ const DashboardData: FC = () => {
|
||||
background-color: #1e1e1e;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
|
||||
.td {
|
||||
padding: 8px;
|
||||
border-top: 1px solid #565656;
|
||||
border-bottom: 1px solid #565656;
|
||||
}
|
||||
|
||||
&.tr.tr-body.row-select.row-select-single-selected {
|
||||
background-color: #3d4752;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
&:hover .td {
|
||||
border-top: 1px solid #177ac9;
|
||||
border-bottom: 1px solid #177ac9;
|
||||
@@ -279,6 +275,53 @@ const DashboardData: FC = () => {
|
||||
}
|
||||
);
|
||||
|
||||
const fetchSensorData = async () => {
|
||||
try {
|
||||
setSensorData((await EMSESP.readSensorData()).data);
|
||||
} catch (error) {
|
||||
toast.error(extractErrorMessage(error, LL.PROBLEM_LOADING()));
|
||||
}
|
||||
};
|
||||
|
||||
const fetchDeviceData = async (id: string) => {
|
||||
const unique_id = parseInt(id);
|
||||
try {
|
||||
setDeviceData((await EMSESP.readDeviceData({ id: unique_id })).data);
|
||||
} catch (error) {
|
||||
toast.error(extractErrorMessage(error, LL.PROBLEM_LOADING()));
|
||||
}
|
||||
};
|
||||
|
||||
const fetchCoreData = useCallback(async () => {
|
||||
try {
|
||||
setCoreData((await EMSESP.readCoreData()).data);
|
||||
} catch (error) {
|
||||
toast.error(extractErrorMessage(error, LL.PROBLEM_LOADING()));
|
||||
}
|
||||
}, [LL]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchCoreData();
|
||||
}, [fetchCoreData]);
|
||||
|
||||
const refreshDataIndex = (selectedDevice: string) => {
|
||||
if (selectedDevice === 'sensor') {
|
||||
fetchSensorData();
|
||||
return;
|
||||
}
|
||||
|
||||
setSensorData({ sensors: [], analogs: [] });
|
||||
if (selectedDevice) {
|
||||
fetchDeviceData(selectedDevice);
|
||||
} else {
|
||||
fetchCoreData();
|
||||
}
|
||||
};
|
||||
|
||||
const refreshData = () => {
|
||||
refreshDataIndex(device_select.state.id);
|
||||
};
|
||||
|
||||
function onSelectChange(action: any, state: any) {
|
||||
if (action.type === 'ADD_BY_ID_EXCLUSIVELY') {
|
||||
refreshData();
|
||||
@@ -337,36 +380,6 @@ const DashboardData: FC = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const refreshDataIndex = (selectedDevice: string) => {
|
||||
if (selectedDevice === 'sensor') {
|
||||
fetchSensorData();
|
||||
return;
|
||||
}
|
||||
|
||||
setSensorData({ sensors: [], analogs: [] });
|
||||
if (selectedDevice) {
|
||||
fetchDeviceData(selectedDevice);
|
||||
} else {
|
||||
fetchCoreData();
|
||||
}
|
||||
};
|
||||
|
||||
const refreshData = () => {
|
||||
refreshDataIndex(device_select.state.id);
|
||||
};
|
||||
|
||||
const fetchCoreData = useCallback(async () => {
|
||||
try {
|
||||
setCoreData((await EMSESP.readCoreData()).data);
|
||||
} catch (error) {
|
||||
toast.error(extractErrorMessage(error, LL.PROBLEM_LOADING()));
|
||||
}
|
||||
}, [LL]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchCoreData();
|
||||
}, [fetchCoreData]);
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setInterval(() => refreshData(), 60000);
|
||||
return () => {
|
||||
@@ -375,23 +388,6 @@ const DashboardData: FC = () => {
|
||||
// eslint-disable-next-line
|
||||
}, [analog, sensor, deviceValue, sensorData]);
|
||||
|
||||
const fetchDeviceData = async (id: string) => {
|
||||
const unique_id = parseInt(id);
|
||||
try {
|
||||
setDeviceData((await EMSESP.readDeviceData({ id: unique_id })).data);
|
||||
} catch (error) {
|
||||
toast.error(extractErrorMessage(error, LL.PROBLEM_LOADING()));
|
||||
}
|
||||
};
|
||||
|
||||
const fetchSensorData = async () => {
|
||||
try {
|
||||
setSensorData((await EMSESP.readSensorData()).data);
|
||||
} catch (error) {
|
||||
toast.error(extractErrorMessage(error, LL.PROBLEM_LOADING()));
|
||||
}
|
||||
};
|
||||
|
||||
const isCmdOnly = (dv: DeviceValue) => dv.v === '' && dv.c;
|
||||
|
||||
const formatDurationMin = (duration_min: number) => {
|
||||
|
||||
@@ -63,14 +63,14 @@ const SettingsApplication: FC = () => {
|
||||
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
|
||||
const [processingBoard, setProcessingBoard] = useState<boolean>(false);
|
||||
|
||||
const updateBoardProfile = async (board_profile: string) => {
|
||||
const updateBoardProfile = async (boardProfile: string) => {
|
||||
setProcessingBoard(true);
|
||||
try {
|
||||
const response = await EMSESP.getBoardProfile({ board_profile: board_profile });
|
||||
const response = await EMSESP.getBoardProfile({ board_profile: boardProfile });
|
||||
if (data) {
|
||||
setData({
|
||||
...data,
|
||||
board_profile: board_profile,
|
||||
board_profile: boardProfile,
|
||||
led_gpio: response.data.led_gpio,
|
||||
dallas_gpio: response.data.dallas_gpio,
|
||||
rx_gpio: response.data.rx_gpio,
|
||||
@@ -105,15 +105,15 @@ const SettingsApplication: FC = () => {
|
||||
};
|
||||
|
||||
const changeBoardProfile = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const board_profile = event.target.value;
|
||||
const boardProfile = event.target.value;
|
||||
updateFormValue(event);
|
||||
if (board_profile === 'CUSTOM') {
|
||||
if (boardProfile === 'CUSTOM') {
|
||||
setData({
|
||||
...data,
|
||||
board_profile: board_profile
|
||||
board_profile: boardProfile
|
||||
});
|
||||
} else {
|
||||
updateBoardProfile(board_profile);
|
||||
updateBoardProfile(boardProfile);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -70,6 +70,32 @@ const SettingsCustomization: FC = () => {
|
||||
// eslint-disable-next-line
|
||||
const [masks, setMasks] = useState(() => ['']);
|
||||
|
||||
function hasEntityChanged(de: DeviceEntity) {
|
||||
return (de?.cn || '') !== (de?.o_cn || '') || de.m !== de.o_m || de.ma !== de.o_ma || de.mi !== de.o_mi;
|
||||
}
|
||||
|
||||
const getChanges = () => {
|
||||
if (!deviceEntities || selectedDevice === -1) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return deviceEntities
|
||||
.filter((de) => hasEntityChanged(de))
|
||||
.map(
|
||||
(new_de) =>
|
||||
new_de.m.toString(16).padStart(2, '0') +
|
||||
new_de.id +
|
||||
(new_de.cn || new_de.mi || new_de.ma ? '|' : '') +
|
||||
(new_de.cn ? new_de.cn : '') +
|
||||
(new_de.mi ? '>' + new_de.mi : '') +
|
||||
(new_de.ma ? '<' + new_de.ma : '')
|
||||
);
|
||||
};
|
||||
|
||||
const countChanges = () => {
|
||||
setNumChanges(getChanges().length);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
countChanges();
|
||||
});
|
||||
@@ -262,32 +288,6 @@ const SettingsCustomization: FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
function hasEntityChanged(de: DeviceEntity) {
|
||||
return (de?.cn || '') !== (de?.o_cn || '') || de.m !== de.o_m || de.ma !== de.o_ma || de.mi !== de.o_mi;
|
||||
}
|
||||
|
||||
const getChanges = () => {
|
||||
if (!deviceEntities || selectedDevice === -1) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return deviceEntities
|
||||
.filter((de) => hasEntityChanged(de))
|
||||
.map(
|
||||
(new_de) =>
|
||||
new_de.m.toString(16).padStart(2, '0') +
|
||||
new_de.id +
|
||||
(new_de.cn || new_de.mi || new_de.ma ? '|' : '') +
|
||||
(new_de.cn ? new_de.cn : '') +
|
||||
(new_de.mi ? '>' + new_de.mi : '') +
|
||||
(new_de.ma ? '<' + new_de.ma : '')
|
||||
);
|
||||
};
|
||||
|
||||
const countChanges = () => {
|
||||
setNumChanges(getChanges().length);
|
||||
};
|
||||
|
||||
const restart = async () => {
|
||||
try {
|
||||
await EMSESP.restart();
|
||||
|
||||
@@ -92,6 +92,26 @@ const SettingsScheduler: FC = () => {
|
||||
return days.map((date) => formatter.format(date));
|
||||
}
|
||||
|
||||
function hasScheduleChanged(si: ScheduleItem) {
|
||||
return (
|
||||
si.id !== si.o_id ||
|
||||
(si?.name || '') !== (si?.o_name || '') ||
|
||||
si.active !== si.o_active ||
|
||||
si.deleted !== si.o_deleted ||
|
||||
si.flags !== si.o_flags ||
|
||||
si.time !== si.o_time ||
|
||||
si.cmd !== si.o_cmd ||
|
||||
si.value !== si.o_value
|
||||
);
|
||||
}
|
||||
|
||||
const getNumChanges = () => {
|
||||
if (!schedule) {
|
||||
return 0;
|
||||
}
|
||||
return schedule.filter((si) => hasScheduleChanged(si)).length;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setNumChanges(getNumChanges());
|
||||
});
|
||||
@@ -141,20 +161,6 @@ const SettingsScheduler: FC = () => {
|
||||
`
|
||||
});
|
||||
|
||||
const fetchSchedule = useCallback(async () => {
|
||||
try {
|
||||
const response = await EMSESP.readSchedule();
|
||||
setOriginalSchedule(response.data.schedule);
|
||||
} catch (error) {
|
||||
setErrorMessage(extractErrorMessage(error, LL.PROBLEM_LOADING()));
|
||||
}
|
||||
setDow(getDayNames());
|
||||
}, [LL]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchSchedule();
|
||||
}, [fetchSchedule]);
|
||||
|
||||
const setOriginalSchedule = (data: ScheduleItem[]) => {
|
||||
setSchedule(
|
||||
data.map((si) => ({
|
||||
@@ -171,6 +177,20 @@ const SettingsScheduler: FC = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const fetchSchedule = useCallback(async () => {
|
||||
try {
|
||||
const response = await EMSESP.readSchedule();
|
||||
setOriginalSchedule(response.data.schedule);
|
||||
} catch (error) {
|
||||
setErrorMessage(extractErrorMessage(error, LL.PROBLEM_LOADING()));
|
||||
}
|
||||
setDow(getDayNames());
|
||||
}, [LL]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchSchedule();
|
||||
}, [fetchSchedule]);
|
||||
|
||||
const getFlagNumber = (newFlag: string[]) => {
|
||||
let new_flag = 0;
|
||||
for (const entry of newFlag) {
|
||||
@@ -208,26 +228,6 @@ const SettingsScheduler: FC = () => {
|
||||
return new_flags;
|
||||
};
|
||||
|
||||
function hasScheduleChanged(si: ScheduleItem) {
|
||||
return (
|
||||
si.id !== si.o_id ||
|
||||
(si?.name || '') !== (si?.o_name || '') ||
|
||||
si.active !== si.o_active ||
|
||||
si.deleted !== si.o_deleted ||
|
||||
si.flags !== si.o_flags ||
|
||||
si.time !== si.o_time ||
|
||||
si.cmd !== si.o_cmd ||
|
||||
si.value !== si.o_value
|
||||
);
|
||||
}
|
||||
|
||||
const getNumChanges = () => {
|
||||
if (!schedule) {
|
||||
return 0;
|
||||
}
|
||||
return schedule.filter((si) => hasScheduleChanged(si)).length;
|
||||
};
|
||||
|
||||
const saveSchedule = async () => {
|
||||
if (schedule) {
|
||||
try {
|
||||
|
||||
@@ -8,6 +8,16 @@ export const SECURITY_SETTINGS_VALIDATOR = new Schema({
|
||||
]
|
||||
});
|
||||
|
||||
export const createUniqueUsernameValidator = (users: User[]) => ({
|
||||
validator(rule: InternalRuleItem, username: string, callback: (error?: string) => void) {
|
||||
if (username && users.find((u) => u.username === username)) {
|
||||
callback('Username already in use');
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export const createUserValidator = (users: User[], creating: boolean) =>
|
||||
new Schema({
|
||||
username: [
|
||||
@@ -24,13 +34,3 @@ export const createUserValidator = (users: User[], creating: boolean) =>
|
||||
{ type: 'string', min: 1, max: 64, message: 'Password must be 1-64 characters' }
|
||||
]
|
||||
});
|
||||
|
||||
export const createUniqueUsernameValidator = (users: User[]) => ({
|
||||
validator(rule: InternalRuleItem, username: string, callback: (error?: string) => void) {
|
||||
if (username && users.find((u) => u.username === username)) {
|
||||
callback('Username already in use');
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user