From 179e95eefe4fc371de2463e2bd535c38cb2e3287 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 26 Sep 2024 16:18:25 +0800 Subject: [PATCH 1/8] add redux state for settings --- Client/src/Features/Settings/settingsSlice.js | 119 ++++++++++++++++++ Client/src/Pages/Settings/index.jsx | 36 +++++- Client/src/Utils/NetworkService.js | 21 ++++ 3 files changed, 175 insertions(+), 1 deletion(-) create mode 100644 Client/src/Features/Settings/settingsSlice.js diff --git a/Client/src/Features/Settings/settingsSlice.js b/Client/src/Features/Settings/settingsSlice.js new file mode 100644 index 000000000..0a7cb52c1 --- /dev/null +++ b/Client/src/Features/Settings/settingsSlice.js @@ -0,0 +1,119 @@ +import { networkService } from "../../main"; +import { createAsyncThunk, createSlice } from "@reduxjs/toolkit"; +import { jwtDecode } from "jwt-decode"; +import axios from "axios"; + +const initialState = { + isLoading: false, + apiBaseUrl: "http://localhost:5000/api/v1", + logLevel: "debug", + clientHost: "http://localhost:5173", + jwtSecret: "my_secret", + dbType: "MongoDB", + dbConnectionString: "mongodb://localhost:27017/uptime_db", + redisHost: "127.0.0.1", + redisPort: 6379, + jwtTTL: "99d", + pagespeedApiKey: "", + systemEmailHost: "smtp.gmail.com", + systemEmailPort: 465, + systemEmailAddress: "", + systemEmailPassword: "", +}; + +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 (form, thunkApi) => { + try { + const res = await networkService.loginUser(form); + 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; +}; +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; +}; +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, + reducers: { + clearAuthState: (state) => { + state.authToken = ""; + state.user = ""; + state.isLoading = false; + state.success = true; + state.msg = "Logged out successfully"; + }, + }, + extraReducers: (builder) => { + // Register thunk + builder + .addCase(getAppSettings.pending, (state) => { + state.isLoading = true; + }) + .addCase(getAppSettings.fulfilled, handleGetSettingsFulfilled) + .addCase(getAppSettings.rejected, handleGetSettingsRejected); + + // Login thunk + builder + .addCase(updateAppSettings.pending, (state) => { + state.isLoading = true; + }) + .addCase(updateAppSettings.fulfilled, handleUpdateSettingsFulfilled) + .addCase(updateAppSettings.rejected, handleUpdateSettingsRejected); + }, +}); + +export default settingsSlice.reducer; +export const { clearAuthState } = settingsSlice.actions; diff --git a/Client/src/Pages/Settings/index.jsx b/Client/src/Pages/Settings/index.jsx index 2e59c8187..4a7ed13a9 100644 --- a/Client/src/Pages/Settings/index.jsx +++ b/Client/src/Pages/Settings/index.jsx @@ -13,11 +13,12 @@ import { deleteAllMonitors, } from "../../Features/UptimeMonitors/uptimeMonitorsSlice"; import { update } from "../../Features/Auth/authSlice"; +import { getAppSettings } from "../../Features/Settings/settingsSlice"; import PropTypes from "prop-types"; import LoadingButton from "@mui/lab/LoadingButton"; import { setTimezone } from "../../Features/UI/uiSlice"; import timezones from "../../Utils/timezones.json"; -import { useState } from "react"; +import { useEffect, useState } from "react"; import { ConfigBox } from "./styled"; import { networkService } from "../../main"; import { settingsValidation } from "../../Validation/validation"; @@ -35,9 +36,25 @@ const Settings = ({ isAdmin }) => { const [form, setForm] = useState({ ttl: checkTTL ? (checkTTL / SECONDS_PER_DAY).toString() : 0, }); + const [settings, setSettings] = useState({ + apiBaseUrl: "", + }); const [errors, setErrors] = useState({}); const dispatch = useDispatch(); + useEffect(() => { + const fetchSettings = async () => { + const action = await dispatch(getAppSettings({ authToken })); + if (getAppSettings.fulfilled.match(action)) { + const settings = action.payload.data; + setSettings(settings); + } else if (getAppSettings.rejected.match(action)) { + throw new Error(action.error.message); + } + }; + fetchSettings(); + }, [dispatch, authToken]); + const handleChange = (event) => { const { value, id } = event.target; const { error } = settingsValidation.validate( @@ -249,6 +266,23 @@ const Settings = ({ isAdmin }) => { )} + + + Client Settings + + Here you can modify settings for the client. + + + + + + About diff --git a/Client/src/Utils/NetworkService.js b/Client/src/Utils/NetworkService.js index 905385e70..9545fb247 100644 --- a/Client/src/Utils/NetworkService.js +++ b/Client/src/Utils/NetworkService.js @@ -619,6 +619,27 @@ class NetworkService { } ); } + + /** + * ************************************ + * Get app settings + * ************************************ + * + * @async + * @param {Object} config - The configuration object. + * @param {string} config.authToken - The authorization token to be used in the request header. + * @returns {Promise} The response from the axios GET request. + * + */ + + async getAppSettings(config) { + return this.axiosInstance.get("/settings", { + headers: { + Authorization: `Bearer ${config.authToken}`, + "Content-Type": "application/json", + }, + }); + } } export default NetworkService; From 488ba717acb44809b7c902c819277a8a7843ad05 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 27 Sep 2024 10:55:37 +0800 Subject: [PATCH 2/8] Add advacned settings page --- Client/src/App.jsx | 19 ++- Client/src/Features/Settings/settingsSlice.js | 17 +- Client/src/Pages/AdvancedSettings/index.css | 0 Client/src/Pages/AdvancedSettings/index.jsx | 155 ++++++++++++++++++ Client/src/Pages/Settings/index.jsx | 44 ++--- Client/src/store.js | 4 +- 6 files changed, 194 insertions(+), 45 deletions(-) create mode 100644 Client/src/Pages/AdvancedSettings/index.css create mode 100644 Client/src/Pages/AdvancedSettings/index.jsx diff --git a/Client/src/App.jsx b/Client/src/App.jsx index ca3894c28..73015d3b0 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,9 @@ 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"; function App() { const AdminCheckedRegister = withAdminCheck(Register); const MonitorsWithAdminProp = withAdminProp(Monitors); @@ -42,8 +45,14 @@ 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(() => { + dispatch(getAppSettings(authToken)); + }, [dispatch, authToken]); return ( @@ -96,6 +105,12 @@ function App() { path="settings" element={} /> + + } + /> } diff --git a/Client/src/Features/Settings/settingsSlice.js b/Client/src/Features/Settings/settingsSlice.js index 0a7cb52c1..3778ca801 100644 --- a/Client/src/Features/Settings/settingsSlice.js +++ b/Client/src/Features/Settings/settingsSlice.js @@ -23,10 +23,10 @@ const initialState = { export const getAppSettings = createAsyncThunk( "settings/getSettings", - async (data, thunkApi) => { + async (authToken, thunkApi) => { try { const res = await networkService.getAppSettings({ - authToken: data.authToken, + authToken: authToken, }); return res.data; } catch (error) { @@ -65,6 +65,7 @@ const handleGetSettingsFulfilled = (state, action) => { state.isLoading = false; state.success = action.payload.success; state.msg = action.payload.msg; + Object.assign(state, action.payload.data); }; const handleGetSettingsRejected = (state, action) => { state.isLoading = false; @@ -87,17 +88,7 @@ const handleUpdateSettingsRejected = (state, action) => { const settingsSlice = createSlice({ name: "settings", initialState, - reducers: { - clearAuthState: (state) => { - state.authToken = ""; - state.user = ""; - state.isLoading = false; - state.success = true; - state.msg = "Logged out successfully"; - }, - }, extraReducers: (builder) => { - // Register thunk builder .addCase(getAppSettings.pending, (state) => { state.isLoading = true; @@ -105,7 +96,6 @@ const settingsSlice = createSlice({ .addCase(getAppSettings.fulfilled, handleGetSettingsFulfilled) .addCase(getAppSettings.rejected, handleGetSettingsRejected); - // Login thunk builder .addCase(updateAppSettings.pending, (state) => { state.isLoading = true; @@ -116,4 +106,3 @@ const settingsSlice = createSlice({ }); export default settingsSlice.reducer; -export const { clearAuthState } = settingsSlice.actions; 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..6d227ba5d --- /dev/null +++ b/Client/src/Pages/AdvancedSettings/index.jsx @@ -0,0 +1,155 @@ +import { useTheme } from "@emotion/react"; +import { Box, Stack, Typography, Button } from "@mui/material"; +import Field from "../../Components/Inputs/Field"; +import Link from "../../Components/Link"; +import { logger } from "../../Utils/Logger"; +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 } from "../../Features/Settings/settingsSlice"; +import { useEffect, useState } from "react"; +import Select from "../../Components/Inputs/Select"; +const AdvancedSettings = ({ isAdmin }) => { + const theme = useTheme(); + const { user, authToken } = useSelector((state) => state.auth); + const navigate = useNavigate(); + const settings = useSelector((state) => state.settings); + const [localSettings, setLocalSettings] = useState(settings); + + 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 }); + }; + + return ( + + + \{" "} + + + Client Settings + + Modify client settings here + + + + + { /> + + + Save + + ); diff --git a/Client/src/Utils/Logger.js b/Client/src/Utils/Logger.js index 02fffd6be..79b2ed4f9 100644 --- a/Client/src/Utils/Logger.js +++ b/Client/src/Utils/Logger.js @@ -1,6 +1,16 @@ -const LOG_LEVEL = import.meta.env.VITE_APP_LOG_LEVEL; +import store from "../store"; +const LOG_LEVEL = import.meta.env.VITE_APP_LOG_LEVEL || "debug"; class Logger { - constructor(logLevel) { + constructor() { + let logLevel = LOG_LEVEL; + this.unsubscribe = store.subscribe(() => { + const state = store.getState(); + logLevel = state.settings.logLevel || "debug"; + this.updateLogLevel(logLevel); + }); + } + + updateLogLevel(logLevel) { const NO_OP = () => {}; if (logLevel === "none") { @@ -25,6 +35,12 @@ class Logger { } this.log = console.log.bind(console); } + + cleanup() { + if (this.unsubscribe) { + this.unsubscribe(); + } + } } -export const logger = new Logger(LOG_LEVEL); +export const logger = new Logger(); diff --git a/Client/src/Utils/NetworkService.js b/Client/src/Utils/NetworkService.js index 9545fb247..00b52f7f7 100644 --- a/Client/src/Utils/NetworkService.js +++ b/Client/src/Utils/NetworkService.js @@ -1,10 +1,18 @@ import axios from "axios"; -const BASE_URL = import.meta.env.VITE_APP_API_BASE_URL; +const BASE_URL = + import.meta.env.VITE_APP_API_BASE_URL || "http://localhost:5000/api/v1"; import { logger } from "./Logger"; class NetworkService { constructor(store) { this.store = store; - this.axiosInstance = axios.create({ baseURL: BASE_URL }); + let baseURL = BASE_URL; + this.axiosInstance = axios.create(); + this.setBaseUrl(baseURL); + this.unsubscribe = store.subscribe(() => { + const state = store.getState(); + baseURL = state.settings.apiBaseUrl || BASE_URL; + this.setBaseUrl(baseURL); + }); this.axiosInstance.interceptors.response.use( (response) => response, (error) => { @@ -17,6 +25,16 @@ class NetworkService { ); } + setBaseUrl = (url) => { + this.axiosInstance.defaults.baseURL = url; + }; + + cleanup() { + if (this.unsubscribe) { + this.unsubscribe(); + } + } + /** * * ************************************ @@ -640,6 +658,27 @@ class NetworkService { }, }); } + + /** + * + * ************************************ + * Create a new monitor + * ************************************ + * + * @async + * @param {Object} config - The configuration object. + * @param {string} config.authToken - The authorization token to be used in the request header. + * @param {Object} config.settings - The monitor object to be sent in the request body. + * @returns {Promise} The response from the axios POST request. + */ + async updateAppSettings(config) { + return this.axiosInstance.put(`/settings`, config.settings, { + headers: { + Authorization: `Bearer ${config.authToken}`, + "Content-Type": "application/json", + }, + }); + } } export default NetworkService; diff --git a/Client/src/store.js b/Client/src/store.js index 10c296392..644da5e62 100644 --- a/Client/src/store.js +++ b/Client/src/store.js @@ -51,3 +51,4 @@ export const store = configureStore({ }); export const persistor = persistStore(store); +export default store; From d418e2f1f860b1a0428bc5b5e6188ba683595762 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 27 Sep 2024 12:22:50 +0800 Subject: [PATCH 4/8] Add remainder of settings --- Client/src/Pages/AdvancedSettings/index.jsx | 78 ++++++++++++++++++--- 1 file changed, 68 insertions(+), 10 deletions(-) diff --git a/Client/src/Pages/AdvancedSettings/index.jsx b/Client/src/Pages/AdvancedSettings/index.jsx index 61cdd86a9..59e677138 100644 --- a/Client/src/Pages/AdvancedSettings/index.jsx +++ b/Client/src/Pages/AdvancedSettings/index.jsx @@ -1,8 +1,7 @@ import { useTheme } from "@emotion/react"; -import { Box, Stack, Typography, Button } from "@mui/material"; +import { Box, Stack, Typography } from "@mui/material"; import Field from "../../Components/Inputs/Field"; import Link from "../../Components/Link"; -import { logger } from "../../Utils/Logger"; import "./index.css"; import { useDispatch, useSelector } from "react-redux"; import { createToast } from "../../Utils/toastUtils"; @@ -10,18 +9,19 @@ 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 { useEffect, useState } from "react"; +import { updateAppSettings } from "../../Features/Settings/settingsSlice"; +import { useState } from "react"; import Select from "../../Components/Inputs/Select"; const AdvancedSettings = ({ isAdmin }) => { - const theme = useTheme(); - const { user, authToken } = useSelector((state) => state.auth); - const dispatch = useDispatch(); const navigate = useNavigate(); + if (!isAdmin) { + navigate("/"); + } + + const theme = useTheme(); + const { authToken } = useSelector((state) => state.auth); + const dispatch = useDispatch(); const settings = useSelector((state) => state.settings); const [localSettings, setLocalSettings] = useState(settings); const logItems = [ @@ -143,6 +143,64 @@ const AdvancedSettings = ({ isAdmin }) => { /> + + + Server Settings + + Modify server settings here + + + + + + + + + + + About From 43a56e1de14e95e5e0a227d4a954b1636aea0a50 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 27 Sep 2024 13:17:28 +0800 Subject: [PATCH 5/8] Fix setting name --- Server/service/settingsService.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Server/service/settingsService.js b/Server/service/settingsService.js index b1027a17c..abec31146 100644 --- a/Server/service/settingsService.js +++ b/Server/service/settingsService.js @@ -8,7 +8,7 @@ const envConfig = { dbConnectionString: process.env.DB_CONNECTION_STRING, redisHost: process.env.REDIS_HOST, redisPort: process.env.REDIS_PORT, - tokenTTL: process.env.TOKEN_TTL, + jwtTTL: process.env.TOKEN_TTL, pagespeedApiKey: process.env.PAGESPEED_API_KEY, systemEmailHost: process.env.SYSTEM_EMAIL_HOST, systemEmailPort: process.env.SYSTEM_EMAIL_PORT, From b8984c01603aeae8171a2416cf2abd2bd87087ea Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 27 Sep 2024 13:38:57 +0800 Subject: [PATCH 6/8] Restrict access to advanced settings page --- Client/src/Pages/AdvancedSettings/index.jsx | 12 ++++--- Client/src/Pages/Settings/index.jsx | 38 +++++++++++---------- 2 files changed, 27 insertions(+), 23 deletions(-) diff --git a/Client/src/Pages/AdvancedSettings/index.jsx b/Client/src/Pages/AdvancedSettings/index.jsx index 59e677138..29a9fe53c 100644 --- a/Client/src/Pages/AdvancedSettings/index.jsx +++ b/Client/src/Pages/AdvancedSettings/index.jsx @@ -10,14 +10,17 @@ import LoadingButton from "@mui/lab/LoadingButton"; import { ConfigBox } from "../Settings/styled"; import { useNavigate } from "react-router"; import { updateAppSettings } from "../../Features/Settings/settingsSlice"; -import { useState } from "react"; +import { useState, useEffect } from "react"; import Select from "../../Components/Inputs/Select"; const AdvancedSettings = ({ isAdmin }) => { const navigate = useNavigate(); - if (!isAdmin) { - navigate("/"); - } + + useEffect(() => { + if (!isAdmin) { + navigate("/"); + } + }, [navigate, isAdmin]); const theme = useTheme(); const { authToken } = useSelector((state) => state.auth); @@ -75,7 +78,6 @@ const AdvancedSettings = ({ isAdmin }) => { noValidate spellCheck="false" > - \{" "} Client Settings diff --git a/Client/src/Pages/Settings/index.jsx b/Client/src/Pages/Settings/index.jsx index 63199f9f4..bf8cfcc79 100644 --- a/Client/src/Pages/Settings/index.jsx +++ b/Client/src/Pages/Settings/index.jsx @@ -251,26 +251,28 @@ const Settings = ({ isAdmin }) => { )} - - - Advanced Settings - - Click here to modify advanced settings - - - + {isAdmin && ( + - + Advanced Settings + + Click here to modify advanced settings + - - + + + + + + + )} About From 6268b2573c560062e8007a56a33514589edd47a9 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 30 Sep 2024 11:14:53 +0800 Subject: [PATCH 7/8] remove jwtSecret from settings --- Client/src/App.jsx | 2 +- Client/src/Features/Settings/settingsSlice.js | 22 +++------- Client/src/Pages/AdvancedSettings/index.jsx | 43 ++++++++++++++----- Server/controllers/settingsController.js | 6 ++- Server/middleware/verifyJWT.js | 1 - Server/service/settingsService.js | 6 +-- Server/validation/joi.js | 1 - 7 files changed, 47 insertions(+), 34 deletions(-) diff --git a/Client/src/App.jsx b/Client/src/App.jsx index 7bfc630fd..df3e79c80 100644 --- a/Client/src/App.jsx +++ b/Client/src/App.jsx @@ -53,7 +53,7 @@ function App() { const dispatch = useDispatch(); useEffect(() => { - dispatch(getAppSettings(authToken)); + dispatch(getAppSettings({ authToken })); }, [dispatch, authToken]); // Cleanup diff --git a/Client/src/Features/Settings/settingsSlice.js b/Client/src/Features/Settings/settingsSlice.js index 8bec2f87c..ab6a5c746 100644 --- a/Client/src/Features/Settings/settingsSlice.js +++ b/Client/src/Features/Settings/settingsSlice.js @@ -5,26 +5,14 @@ const initialState = { isLoading: false, apiBaseUrl: "http://localhost:5000/api/v1", logLevel: "debug", - clientHost: "http://localhost:5173", - jwtSecret: "my_secret", - dbType: "MongoDB", - dbConnectionString: "mongodb://localhost:27017/uptime_db", - redisHost: "127.0.0.1", - redisPort: 6379, - jwtTTL: "99d", - pagespeedApiKey: "", - systemEmailHost: "smtp.gmail.com", - systemEmailPort: 465, - systemEmailAddress: "", - systemEmailPassword: "", }; export const getAppSettings = createAsyncThunk( "settings/getSettings", - async (authToken, thunkApi) => { + async (data, thunkApi) => { try { const res = await networkService.getAppSettings({ - authToken: authToken, + authToken: data.authToken, }); return res.data; } catch (error) { @@ -83,7 +71,8 @@ const handleGetSettingsFulfilled = (state, action) => { state.isLoading = false; state.success = action.payload.success; state.msg = action.payload.msg; - Object.assign(state, action.payload.data); + state.apiBaseUrl = action.payload.data.apiBaseUrl; + state.logLevel = action.payload.data.logLevel; }; const handleGetSettingsRejected = (state, action) => { state.isLoading = false; @@ -94,7 +83,8 @@ const handleUpdateSettingsFulfilled = (state, action) => { state.isLoading = false; state.success = action.payload.success; state.msg = action.payload.msg; - Object.assign(state, action.payload.data); + state.apiBaseUrl = action.payload.data.apiBaseUrl; + state.logLevel = action.payload.data.logLevel; }; const handleUpdateSettingsRejected = (state, action) => { state.isLoading = false; diff --git a/Client/src/Pages/AdvancedSettings/index.jsx b/Client/src/Pages/AdvancedSettings/index.jsx index 29a9fe53c..38c0a3b12 100644 --- a/Client/src/Pages/AdvancedSettings/index.jsx +++ b/Client/src/Pages/AdvancedSettings/index.jsx @@ -9,7 +9,10 @@ import PropTypes from "prop-types"; import LoadingButton from "@mui/lab/LoadingButton"; import { ConfigBox } from "../Settings/styled"; import { useNavigate } from "react-router"; -import { updateAppSettings } from "../../Features/Settings/settingsSlice"; +import { + getAppSettings, + updateAppSettings, +} from "../../Features/Settings/settingsSlice"; import { useState, useEffect } from "react"; import Select from "../../Components/Inputs/Select"; @@ -26,7 +29,33 @@ const AdvancedSettings = ({ isAdmin }) => { const { authToken } = useSelector((state) => state.auth); const dispatch = useDispatch(); const settings = useSelector((state) => state.settings); - const [localSettings, setLocalSettings] = useState(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" }, @@ -58,6 +87,8 @@ const AdvancedSettings = ({ isAdmin }) => { ); 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"; @@ -153,14 +184,6 @@ const AdvancedSettings = ({ isAdmin }) => { - { try { - const settings = await req.settingsService.getSettings(); + const settings = { ...(await req.settingsService.getSettings()) }; + delete settings.jwtSecret; return res.status(200).json({ success: true, msg: successMessages.GET_APP_SETTINGS, @@ -31,7 +32,8 @@ const updateAppSettings = async (req, res, next) => { try { await req.db.updateAppSettings(req.body); - const updatedSettings = await req.settingsService.reloadSettings(); + const updatedSettings = { ...(await req.settingsService.reloadSettings()) }; + delete updatedSettings.jwtSecret; return res.status(200).json({ success: true, msg: successMessages.UPDATE_APP_SETTINGS, diff --git a/Server/middleware/verifyJWT.js b/Server/middleware/verifyJWT.js index ffbcd55a5..0007bad39 100644 --- a/Server/middleware/verifyJWT.js +++ b/Server/middleware/verifyJWT.js @@ -36,7 +36,6 @@ const verifyJWT = (req, res, next) => { const parsedToken = token.slice(TOKEN_PREFIX.length, token.length); // Verify the token's authenticity const { jwtSecret } = req.settingsService.getSettings(); - jwt.verify(parsedToken, jwtSecret, (err, decoded) => { if (err) { return res diff --git a/Server/service/settingsService.js b/Server/service/settingsService.js index 13b19eb6c..752f86607 100644 --- a/Server/service/settingsService.js +++ b/Server/service/settingsService.js @@ -2,6 +2,8 @@ const { env } = require("process"); const AppSettings = require("../models/AppSettings"); const SERVICE_NAME = "SettingsService"; const envConfig = { + logLevel: undefined, + apiBaseUrl: undefined, clientHost: process.env.CLIENT_HOST, jwtSecret: process.env.JWT_SECRET, dbType: process.env.DB_TYPE, @@ -18,7 +20,7 @@ const envConfig = { class SettingsService { constructor() { - this.settings = envConfig; + this.settings = { ...envConfig }; } async loadSettings() { @@ -27,7 +29,6 @@ class SettingsService { if (!this.settings) { throw new Error("Settings not found"); } - // Try to load settings from env first, if not found, load from db for (const key in envConfig) { if (envConfig[key] === undefined && dbSettings[key] !== undefined) { @@ -38,7 +39,6 @@ class SettingsService { if (!this.settings) { throw new Error("Settings not found"); } - return this.settings; } catch (error) { error.service === undefined ? (error.service = SERVICE_NAME) : null; diff --git a/Server/validation/joi.js b/Server/validation/joi.js index 7b26ea8e1..91ba53b94 100644 --- a/Server/validation/joi.js +++ b/Server/validation/joi.js @@ -389,7 +389,6 @@ const updateAppSettingsBodyValidation = joi.object({ apiBaseUrl: joi.string().allow(""), logLevel: joi.string().valid("debug", "none", "error", "warn").allow(""), clientHost: joi.string().allow(""), - jwtSecret: joi.string().allow(""), dbType: joi.string().allow(""), dbConnectionString: joi.string().allow(""), redisHost: joi.string().allow(""), From 9b3085c84d8971cdc59d973a9119e7016d8ed2a1 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 30 Sep 2024 11:30:52 +0800 Subject: [PATCH 8/8] Add default empty value for pagespeedApiKey in settings --- Client/src/App.jsx | 4 +++- Client/src/Pages/AdvancedSettings/index.jsx | 2 +- Server/models/AppSettings.js | 1 + 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Client/src/App.jsx b/Client/src/App.jsx index df3e79c80..56c279e78 100644 --- a/Client/src/App.jsx +++ b/Client/src/App.jsx @@ -53,7 +53,9 @@ function App() { const dispatch = useDispatch(); useEffect(() => { - dispatch(getAppSettings({ authToken })); + if (authToken) { + dispatch(getAppSettings({ authToken })); + } }, [dispatch, authToken]); // Cleanup diff --git a/Client/src/Pages/AdvancedSettings/index.jsx b/Client/src/Pages/AdvancedSettings/index.jsx index 38c0a3b12..7609b2578 100644 --- a/Client/src/Pages/AdvancedSettings/index.jsx +++ b/Client/src/Pages/AdvancedSettings/index.jsx @@ -119,7 +119,7 @@ const AdvancedSettings = ({ isAdmin }) => { diff --git a/Server/models/AppSettings.js b/Server/models/AppSettings.js index 79ede650a..f95d58f4b 100644 --- a/Server/models/AppSettings.js +++ b/Server/models/AppSettings.js @@ -48,6 +48,7 @@ const AppSettingsSchema = mongoose.Schema( }, pagespeedApiKey: { type: String, + default: "", }, systemEmailHost: { type: String,