diff --git a/Client/src/App.jsx b/Client/src/App.jsx index ca3894c28..56c279e78 100644 --- a/Client/src/App.jsx +++ b/Client/src/App.jsx @@ -19,6 +19,7 @@ import SetNewPassword from "./Pages/Auth/SetNewPassword"; import NewPasswordConfirmed from "./Pages/Auth/NewPasswordConfirmed"; import ProtectedRoute from "./Components/ProtectedRoute"; import Details from "./Pages/Monitors/Details"; +import AdvancedSettings from "./Pages/AdvancedSettings"; // import Maintenance from "./Pages/Maintenance"; import withAdminCheck from "./HOC/withAdminCheck"; import withAdminProp from "./HOC/withAdminProp"; @@ -33,7 +34,11 @@ import lightTheme from "./Utils/Theme/lightTheme"; import darkTheme from "./Utils/Theme/darkTheme"; import { useSelector } from "react-redux"; import { CssBaseline } from "@mui/material"; - +import { useEffect } from "react"; +import { useDispatch } from "react-redux"; +import { getAppSettings } from "./Features/Settings/settingsSlice"; +import { logger } from "./Utils/Logger"; // Import the logger +import { networkService } from "./main"; function App() { const AdminCheckedRegister = withAdminCheck(Register); const MonitorsWithAdminProp = withAdminProp(Monitors); @@ -42,8 +47,24 @@ function App() { const PageSpeedDetailsWithAdminProp = withAdminProp(PageSpeedDetails); // const MaintenanceWithAdminProp = withAdminProp(Maintenance); const SettingsWithAdminProp = withAdminProp(Settings); - + const AdvancedSettingsWithAdminProp = withAdminProp(AdvancedSettings); const mode = useSelector((state) => state.ui.mode); + const { authToken } = useSelector((state) => state.auth); + const dispatch = useDispatch(); + + useEffect(() => { + if (authToken) { + dispatch(getAppSettings({ authToken })); + } + }, [dispatch, authToken]); + + // Cleanup + useEffect(() => { + return () => { + logger.cleanup(); + networkService.cleanup(); + }; + }, []); return ( @@ -96,6 +117,12 @@ function App() { path="settings" element={} /> + + } + /> } diff --git a/Client/src/Features/Settings/settingsSlice.js b/Client/src/Features/Settings/settingsSlice.js new file mode 100644 index 000000000..ab6a5c746 --- /dev/null +++ b/Client/src/Features/Settings/settingsSlice.js @@ -0,0 +1,117 @@ +import { networkService } from "../../main"; +import { createAsyncThunk, createSlice } from "@reduxjs/toolkit"; + +const initialState = { + isLoading: false, + apiBaseUrl: "http://localhost:5000/api/v1", + logLevel: "debug", +}; + +export const getAppSettings = createAsyncThunk( + "settings/getSettings", + async (data, thunkApi) => { + try { + const res = await networkService.getAppSettings({ + authToken: data.authToken, + }); + return res.data; + } catch (error) { + if (error.response.data) { + return thunkApi.rejectWithValue(error.response.data); + } + const payload = { + status: false, + msg: error.message ? error.message : "Unknown error", + }; + return thunkApi.rejectWithValue(payload); + } + } +); + +export const updateAppSettings = createAsyncThunk( + "settings/updateSettings", + async ({ settings, authToken }, thunkApi) => { + networkService.setBaseUrl(settings.apiBaseUrl); + try { + const parsedSettings = { + apiBaseUrl: settings.apiBaseUrl, + logLevel: settings.logLevel, + clientHost: settings.clientHost, + jwtSecret: settings.jwtSecret, + dbType: settings.dbType, + dbConnectionString: settings.dbConnectionString, + redisHost: settings.redisHost, + redisPort: settings.redisPort, + jwtTTL: settings.jwtTTL, + pagespeedApiKey: settings.pagespeedApiKey, + systemEmailHost: settings.systemEmailHost, + systemEmailPort: settings.systemEmailPort, + systemEmailAddress: settings.systemEmailAddress, + systemEmailPassword: settings.systemEmailPassword, + }; + const res = await networkService.updateAppSettings({ + settings: parsedSettings, + authToken, + }); + return res.data; + } catch (error) { + if (error.response && error.response.data) { + return thunkApi.rejectWithValue(error.response.data); + } + const payload = { + status: false, + msg: error.message ? error.message : "Unknown error", + }; + return thunkApi.rejectWithValue(payload); + } + } +); + +const handleGetSettingsFulfilled = (state, action) => { + state.isLoading = false; + state.success = action.payload.success; + state.msg = action.payload.msg; + state.apiBaseUrl = action.payload.data.apiBaseUrl; + state.logLevel = action.payload.data.logLevel; +}; +const handleGetSettingsRejected = (state, action) => { + state.isLoading = false; + state.success = false; + state.msg = action.payload ? action.payload.msg : "Failed to get settings."; +}; +const handleUpdateSettingsFulfilled = (state, action) => { + state.isLoading = false; + state.success = action.payload.success; + state.msg = action.payload.msg; + state.apiBaseUrl = action.payload.data.apiBaseUrl; + state.logLevel = action.payload.data.logLevel; +}; +const handleUpdateSettingsRejected = (state, action) => { + state.isLoading = false; + state.success = false; + state.msg = action.payload + ? action.payload.msg + : "Failed to update settings."; +}; + +const settingsSlice = createSlice({ + name: "settings", + initialState, + extraReducers: (builder) => { + builder + .addCase(getAppSettings.pending, (state) => { + state.isLoading = true; + }) + .addCase(getAppSettings.fulfilled, handleGetSettingsFulfilled) + .addCase(getAppSettings.rejected, handleGetSettingsRejected); + + builder + .addCase(updateAppSettings.pending, (state) => { + state.isLoading = true; + }) + .addCase(updateAppSettings.fulfilled, handleUpdateSettingsFulfilled) + .addCase(updateAppSettings.rejected, handleUpdateSettingsRejected); + }, +}); + +export default settingsSlice.reducer; diff --git a/Client/src/Pages/AdvancedSettings/index.css b/Client/src/Pages/AdvancedSettings/index.css new file mode 100644 index 000000000..e69de29bb diff --git a/Client/src/Pages/AdvancedSettings/index.jsx b/Client/src/Pages/AdvancedSettings/index.jsx new file mode 100644 index 000000000..7609b2578 --- /dev/null +++ b/Client/src/Pages/AdvancedSettings/index.jsx @@ -0,0 +1,266 @@ +import { useTheme } from "@emotion/react"; +import { Box, Stack, Typography } from "@mui/material"; +import Field from "../../Components/Inputs/Field"; +import Link from "../../Components/Link"; +import "./index.css"; +import { useDispatch, useSelector } from "react-redux"; +import { createToast } from "../../Utils/toastUtils"; +import PropTypes from "prop-types"; +import LoadingButton from "@mui/lab/LoadingButton"; +import { ConfigBox } from "../Settings/styled"; +import { useNavigate } from "react-router"; +import { + getAppSettings, + updateAppSettings, +} from "../../Features/Settings/settingsSlice"; +import { useState, useEffect } from "react"; +import Select from "../../Components/Inputs/Select"; + +const AdvancedSettings = ({ isAdmin }) => { + const navigate = useNavigate(); + + useEffect(() => { + if (!isAdmin) { + navigate("/"); + } + }, [navigate, isAdmin]); + + const theme = useTheme(); + const { authToken } = useSelector((state) => state.auth); + const dispatch = useDispatch(); + const settings = useSelector((state) => state.settings); + const [localSettings, setLocalSettings] = useState({ + apiBaseUrl: "", + logLevel: "debug", + systemEmailHost: "", + systemEmailPort: "", + systemEmailAddress: "", + systemEmailPassword: "", + jwtTTL: "", + dbType: "", + redisHost: "", + redisPort: "", + pagespeedApiKey: "", + }); + + useEffect(() => { + const getSettings = async () => { + const action = await dispatch(getAppSettings({ authToken })); + if (action.payload.success) { + console.log(action.payload.data); + setLocalSettings(action.payload.data); + } else { + createToast({ body: "Failed to get settings" }); + } + }; + getSettings(); + }, [authToken, dispatch]); + + const logItems = [ + { _id: 1, name: "none" }, + { _id: 2, name: "debug" }, + { _id: 3, name: "error" }, + { _id: 4, name: "warn" }, + ]; + + const logItemLookup = { + none: 1, + debug: 2, + error: 3, + warn: 4, + }; + + const handleLogLevel = (e) => { + const id = e.target.value; + const newLogLevel = logItems.find((item) => item._id === id).name; + setLocalSettings({ ...localSettings, logLevel: newLogLevel }); + }; + + const handleChange = (event) => { + const { value, id } = event.target; + setLocalSettings({ ...localSettings, [id]: value }); + }; + + const handleSave = async () => { + const action = await dispatch( + updateAppSettings({ settings: localSettings, authToken }) + ); + let body = ""; + if (action.payload.success) { + console.log(action.payload.data); + setLocalSettings(action.payload.data); + body = "Settings saved successfully"; + } else { + body = "Failed to save settings"; + } + createToast({ body }); + }; + + return ( + + + + + Client Settings + + Modify client settings here + + + + +