mirror of
https://github.com/bluewave-labs/Checkmate.git
synced 2026-01-04 16:50:22 -06:00
Merge pull request #890 from bluewave-labs/feat/fe/in-app-settings
Feat/fe/in app settings
This commit is contained in:
@@ -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 (
|
||||
<ThemeProvider theme={mode === "light" ? lightTheme : darkTheme}>
|
||||
@@ -96,6 +117,12 @@ function App() {
|
||||
path="settings"
|
||||
element={<ProtectedRoute Component={SettingsWithAdminProp} />}
|
||||
/>
|
||||
<Route
|
||||
path="advanced-settings"
|
||||
element={
|
||||
<ProtectedRoute Component={AdvancedSettingsWithAdminProp} />
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="account/profile"
|
||||
element={<ProtectedRoute Component={Account} open="profile" />}
|
||||
|
||||
117
Client/src/Features/Settings/settingsSlice.js
Normal file
117
Client/src/Features/Settings/settingsSlice.js
Normal file
@@ -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;
|
||||
0
Client/src/Pages/AdvancedSettings/index.css
Normal file
0
Client/src/Pages/AdvancedSettings/index.css
Normal file
266
Client/src/Pages/AdvancedSettings/index.jsx
Normal file
266
Client/src/Pages/AdvancedSettings/index.jsx
Normal file
@@ -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 (
|
||||
<Box
|
||||
className="settings"
|
||||
style={{
|
||||
paddingBottom: 0,
|
||||
}}
|
||||
>
|
||||
<Stack
|
||||
component="form"
|
||||
gap={theme.spacing(12)}
|
||||
noValidate
|
||||
spellCheck="false"
|
||||
>
|
||||
<ConfigBox>
|
||||
<Box>
|
||||
<Typography component="h1">Client Settings</Typography>
|
||||
<Typography sx={{ mt: theme.spacing(2) }}>
|
||||
Modify client settings here
|
||||
</Typography>
|
||||
</Box>
|
||||
<Stack gap={theme.spacing(20)}>
|
||||
<Field
|
||||
id="apiBaseUrl"
|
||||
label="API URL Host"
|
||||
value={localSettings.apiBaseUrl}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<Select
|
||||
id="logLevel"
|
||||
label="logLevel"
|
||||
name="logLevel"
|
||||
items={logItems}
|
||||
value={logItemLookup[localSettings.logLevel]}
|
||||
onChange={handleLogLevel}
|
||||
/>
|
||||
</Stack>
|
||||
</ConfigBox>
|
||||
<ConfigBox>
|
||||
<Box>
|
||||
<Typography component="h1">Email Settings</Typography>
|
||||
<Typography sx={{ mt: theme.spacing(2) }}>
|
||||
Set your host email settings here. These settings are used for
|
||||
sending system emails
|
||||
</Typography>
|
||||
</Box>
|
||||
<Stack gap={theme.spacing(20)}>
|
||||
<Field
|
||||
type="text"
|
||||
id="systemEmailHost"
|
||||
label="Email Host"
|
||||
name="systemEmailHost"
|
||||
value={localSettings.systemEmailHost}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<Field
|
||||
type="number"
|
||||
id="systemEmailPort"
|
||||
label="System Email Address"
|
||||
name="systemEmailPort"
|
||||
value={localSettings.systemEmailPort.toString()}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<Field
|
||||
type="email"
|
||||
id="systemEmailAddress"
|
||||
label="System Email Address"
|
||||
name="systemEmailAddress"
|
||||
value={localSettings.systemEmailAddress}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<Field
|
||||
type="text"
|
||||
id="systemEmailPassword"
|
||||
label="System Email Password"
|
||||
name="systemEmailPassword"
|
||||
value={localSettings.systemEmailPassword}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</Stack>
|
||||
</ConfigBox>
|
||||
<ConfigBox>
|
||||
<Box>
|
||||
<Typography component="h1">Server Settings</Typography>
|
||||
<Typography sx={{ mt: theme.spacing(2) }}>
|
||||
Modify server settings here
|
||||
</Typography>
|
||||
</Box>
|
||||
<Stack gap={theme.spacing(20)}>
|
||||
<Field
|
||||
type="text"
|
||||
id="jwtTTL"
|
||||
label="JWT Time To Live"
|
||||
name="jwtTTL"
|
||||
value={localSettings.jwtTTL}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<Field
|
||||
type="text"
|
||||
id="dbType"
|
||||
label="Database Type"
|
||||
name="dbType"
|
||||
value={localSettings.dbType}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<Field
|
||||
type="text"
|
||||
id="redisHost"
|
||||
label="Redis Host"
|
||||
name="redisHost"
|
||||
value={localSettings.redisHost}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<Field
|
||||
type="number"
|
||||
id="redisPort"
|
||||
label="Redis Port"
|
||||
name="redisPort"
|
||||
value={localSettings.redisPort.toString()}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<Field
|
||||
type="text"
|
||||
id="pagespeedApiKey"
|
||||
label="PageSpeed API Key"
|
||||
name="pagespeedApiKey"
|
||||
value={localSettings.pagespeedApiKey}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</Stack>
|
||||
</ConfigBox>
|
||||
<ConfigBox>
|
||||
<Box>
|
||||
<Typography component="h1">About</Typography>
|
||||
</Box>
|
||||
<Box>
|
||||
<Typography component="h2">BlueWave Uptime v1.0.0</Typography>
|
||||
<Typography
|
||||
sx={{ mt: theme.spacing(2), mb: theme.spacing(6), opacity: 0.6 }}
|
||||
>
|
||||
Developed by Bluewave Labs.
|
||||
</Typography>
|
||||
<Link
|
||||
level="secondary"
|
||||
url="https://github.com/bluewave-labs"
|
||||
label="https://github.com/bluewave-labs"
|
||||
/>
|
||||
</Box>
|
||||
</ConfigBox>
|
||||
<Stack direction="row" justifyContent="flex-end">
|
||||
<LoadingButton
|
||||
loading={settings.isLoading || settings.authIsLoading}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
sx={{ px: theme.spacing(12), mt: theme.spacing(20) }}
|
||||
onClick={handleSave}
|
||||
>
|
||||
Save
|
||||
</LoadingButton>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
AdvancedSettings.propTypes = {
|
||||
isAdmin: PropTypes.bool,
|
||||
};
|
||||
export default AdvancedSettings;
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { Box, Stack, Typography } from "@mui/material";
|
||||
import { Box, Stack, Typography, Button } from "@mui/material";
|
||||
import Field from "../../Components/Inputs/Field";
|
||||
import Link from "../../Components/Link";
|
||||
import Select from "../../Components/Inputs/Select";
|
||||
@@ -21,6 +21,7 @@ import { useState } from "react";
|
||||
import { ConfigBox } from "./styled";
|
||||
import { networkService } from "../../main";
|
||||
import { settingsValidation } from "../../Validation/validation";
|
||||
import { useNavigate } from "react-router";
|
||||
|
||||
const SECONDS_PER_DAY = 86400;
|
||||
|
||||
@@ -37,6 +38,7 @@ const Settings = ({ isAdmin }) => {
|
||||
});
|
||||
const [errors, setErrors] = useState({});
|
||||
const dispatch = useDispatch();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleChange = (event) => {
|
||||
const { value, id } = event.target;
|
||||
@@ -249,6 +251,28 @@ const Settings = ({ isAdmin }) => {
|
||||
</Stack>
|
||||
</ConfigBox>
|
||||
)}
|
||||
{isAdmin && (
|
||||
<ConfigBox>
|
||||
<Box>
|
||||
<Typography component="h1">Advanced Settings</Typography>
|
||||
<Typography sx={{ mt: theme.spacing(2) }}>
|
||||
Click here to modify advanced settings
|
||||
</Typography>
|
||||
</Box>
|
||||
<Stack gap={theme.spacing(20)}>
|
||||
<Box>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={() => {
|
||||
navigate("/advanced-settings");
|
||||
}}
|
||||
>
|
||||
Advanced Settings
|
||||
</Button>
|
||||
</Box>
|
||||
</Stack>
|
||||
</ConfigBox>
|
||||
)}
|
||||
<ConfigBox>
|
||||
<Box>
|
||||
<Typography component="h1">About</Typography>
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* ************************************
|
||||
@@ -619,6 +637,48 @@ 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<AxiosResponse>} The response from the axios GET request.
|
||||
*
|
||||
*/
|
||||
|
||||
async getAppSettings(config) {
|
||||
return this.axiosInstance.get("/settings", {
|
||||
headers: {
|
||||
Authorization: `Bearer ${config.authToken}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* ************************************
|
||||
* 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<AxiosResponse>} 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;
|
||||
|
||||
@@ -4,6 +4,7 @@ import uptimeMonitorsReducer from "./Features/UptimeMonitors/uptimeMonitorsSlice
|
||||
import pageSpeedMonitorReducer from "./Features/PageSpeedMonitor/pageSpeedMonitorSlice";
|
||||
import authReducer from "./Features/Auth/authSlice";
|
||||
import uiReducer from "./Features/UI/uiSlice";
|
||||
import settingsReducer from "./Features/Settings/settingsSlice";
|
||||
import storage from "redux-persist/lib/storage";
|
||||
import { persistReducer, persistStore, createTransform } from "redux-persist";
|
||||
|
||||
@@ -21,7 +22,7 @@ const authTransform = createTransform(
|
||||
const persistConfig = {
|
||||
key: "root",
|
||||
storage,
|
||||
whitielist: ["auth", "monitors", "pageSpeed", "ui"],
|
||||
whitelist: ["auth", "monitors", "pageSpeed", "ui", "settings"],
|
||||
transforms: [authTransform],
|
||||
};
|
||||
|
||||
@@ -30,6 +31,7 @@ const rootReducer = combineReducers({
|
||||
auth: authReducer,
|
||||
pageSpeedMonitors: pageSpeedMonitorReducer,
|
||||
ui: uiReducer,
|
||||
settings: settingsReducer,
|
||||
});
|
||||
|
||||
const persistedReducer = persistReducer(persistConfig, rootReducer);
|
||||
@@ -49,3 +51,4 @@ export const store = configureStore({
|
||||
});
|
||||
|
||||
export const persistor = persistStore(store);
|
||||
export default store;
|
||||
|
||||
@@ -4,7 +4,8 @@ const { updateAppSettingsBodyValidation } = require("../validation/joi");
|
||||
|
||||
const getAppSettings = async (req, res, next) => {
|
||||
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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -48,6 +48,7 @@ const AppSettingsSchema = mongoose.Schema(
|
||||
},
|
||||
pagespeedApiKey: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
systemEmailHost: {
|
||||
type: String,
|
||||
|
||||
@@ -2,13 +2,15 @@ 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,
|
||||
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,
|
||||
@@ -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;
|
||||
|
||||
@@ -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(""),
|
||||
|
||||
Reference in New Issue
Block a user