Merge pull request #2478 from bluewave-labs/feat/handle-api-and-password-input

feat: handle api and password input, resolves #2385
This commit is contained in:
Alexander Holliday authored and GitHub committed 2025-06-18 09:43:59 +08:00
commit 68f1895cda
7 files changed
+95 -49

No files matched your search

+20 -4
View File
@@ -3,7 +3,7 @@ import { networkService } from "../main";
import { createToast } from "../Utils/toastUtils";
import { useTranslation } from "react-i18next";
const useFetchSettings = ({ setSettingsData }) => {
const useFetchSettings = ({ setSettingsData, setIsApiKeySet, setIsEmailPasswordSet }) => {
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(undefined);
useEffect(() => {
@@ -12,6 +12,8 @@ const useFetchSettings = ({ setSettingsData }) => {
try {
const response = await networkService.getAppSettings();
setSettingsData(response?.data?.data);
setIsApiKeySet(response?.data?.data?.pagespeedKeySet);
setIsEmailPasswordSet(response?.data?.data?.emailPasswordSet);
} catch (error) {
createToast({ body: "Failed to fetch settings" });
setError(error);
@@ -20,12 +22,18 @@ const useFetchSettings = ({ setSettingsData }) => {
}
};
fetchSettings();
}, []);
}, [setSettingsData]);
return [isLoading, error];
};
const useSaveSettings = () => {
const useSaveSettings = ({
setSettingsData,
setIsApiKeySet,
setApiKeyHasBeenReset,
setIsEmailPasswordSet,
setEmailPasswordHasBeenReset,
}) => {
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(undefined);
const { t } = useTranslation();
@@ -39,7 +47,15 @@ const useSaveSettings = () => {
ttl: settings.checkTTL,
});
}
console.log({ settingsResponse });
setIsApiKeySet(settingsResponse.data.data.pagespeedKeySet);
setIsEmailPasswordSet(settingsResponse.data.data.emailPasswordSet);
if (settingsResponse.data.data.pagespeedKeySet === true) {
setApiKeyHasBeenReset(false);
}
if (settingsResponse.data.data.emailPasswordSet === true) {
setEmailPasswordHasBeenReset(false);
}
setSettingsData(settingsResponse.data.data);
createToast({ body: t("settingsSuccessSaved") });
} catch (error) {
createToast({ body: t("settingsFailedToSave") });
+8 -6
View File
@@ -7,7 +7,7 @@ import Stack from "@mui/material/Stack";
// Utils
import { useTheme } from "@emotion/react";
import { PropTypes } from "prop-types";
import { useState } from "react";
import { useState, useEffect } from "react";
import { useTranslation } from "react-i18next";
import { PasswordEndAdornment } from "../../Components/Inputs/TextInput/Adornments";
import { useSendTestEmail } from "../../Hooks/useSendTestEmail";
@@ -20,7 +20,9 @@ const SettingsEmail = ({
handleChange,
settingsData,
setSettingsData,
isPasswordSet,
isEmailPasswordSet,
emailPasswordHasBeenReset,
setEmailPasswordHasBeenReset,
}) => {
// Setup
const { t } = useTranslation();
@@ -43,7 +45,6 @@ const SettingsEmail = ({
} = settingsData?.settings || {};
// Local state
const [password, setPassword] = useState("");
const [hasBeenReset, setHasBeenReset] = useState(false);
// Network
const [isSending, , sendTestEmail] = useSendTestEmail(); // Using empty placeholder for unused error variable
@@ -152,7 +153,7 @@ const SettingsEmail = ({
onChange={handleChange}
/>
</Box>
{(isPasswordSet === false || hasBeenReset === true) && (
{(isEmailPasswordSet === false || emailPasswordHasBeenReset === true) && (
<Box>
<Typography>{t("settingsEmailPassword")}</Typography>
<TextInput
@@ -165,7 +166,8 @@ const SettingsEmail = ({
/>
</Box>
)}
{isPasswordSet === true && hasBeenReset === false && (
{isEmailPasswordSet === true && emailPasswordHasBeenReset === false && (
<Box>
<Typography>{t("settingsEmailFieldResetLabel")}</Typography>
<Button
@@ -175,7 +177,7 @@ const SettingsEmail = ({
...settingsData,
settings: { ...settingsData.settings, systemEmailPassword: "" },
});
setHasBeenReset(true);
setEmailPasswordHasBeenReset(true);
}}
variant="contained"
color="error"
@@ -17,13 +17,14 @@ const SettingsPagespeed = ({
settingsData,
setSettingsData,
isApiKeySet,
apiKeyHasBeenReset,
setApiKeyHasBeenReset,
}) => {
const { t } = useTranslation();
const theme = useTheme();
// Local state
const [apiKey, setApiKey] = useState("");
const [hasBeenReset, setHasBeenReset] = useState(false);
// Handler
const handleChange = (e) => {
@@ -50,7 +51,7 @@ const SettingsPagespeed = ({
<Typography sx={HEADING_SX}>{t("pageSpeedApiKeyFieldDescription")}</Typography>
</Box>
<Stack gap={theme.spacing(20)}>
{(isApiKeySet === false || hasBeenReset === true) && (
{(isApiKeySet === false || apiKeyHasBeenReset === true) && (
<TextInput
name="pagespeedApiKey"
label={t("pageSpeedApiKeyFieldLabel")}
@@ -62,7 +63,7 @@ const SettingsPagespeed = ({
/>
)}
{isApiKeySet === true && hasBeenReset === false && (
{isApiKeySet === true && apiKeyHasBeenReset === false && (
<Box>
<Typography>{t("pageSpeedApiKeyFieldResetLabel")}</Typography>
<Button
@@ -72,7 +73,7 @@ const SettingsPagespeed = ({
...settingsData,
settings: { ...settingsData.settings, pagespeedApiKey: "" },
});
setHasBeenReset(true);
setApiKeyHasBeenReset(true);
}}
variant="contained"
color="error"
@@ -93,6 +94,9 @@ SettingsPagespeed.propTypes = {
settingsData: PropTypes.object,
setSettingsData: PropTypes.func,
isApiKeySet: PropTypes.bool,
setIsApiKeySet: PropTypes.func,
apiKeyHasBeenReset: PropTypes.bool,
setApiKeyHasBeenReset: PropTypes.func,
};
export default SettingsPagespeed;
+38 -19
View File
@@ -31,19 +31,32 @@ const BREADCRUMBS = [{ name: `Settings`, path: "/settings" }];
const Settings = () => {
// Redux state
const { mode, language, timezone, showURL } = useSelector((state) => state.ui);
const { user } = useSelector((state) => state.auth);
// Local state
const [settingsData, setSettingsData] = useState({});
const [errors, setErrors] = useState({});
const [isApiKeySet, setIsApiKeySet] = useState(settingsData?.pagespeedKeySet ?? false);
const [apiKeyHasBeenReset, setApiKeyHasBeenReset] = useState(false);
const [isEmailPasswordSet, setIsEmailPasswordSet] = useState(
settingsData?.emailPasswordSet ?? false
);
const [emailPasswordHasBeenReset, setEmailPasswordHasBeenReset] = useState(false);
// Network
const [isSettingsLoading, settingsError] = useFetchSettings({
setSettingsData,
setIsApiKeySet,
setIsEmailPasswordSet,
});
const [addDemoMonitors, isAddingDemoMonitors] = useAddDemoMonitors();
const [isSaving, saveError, saveSettings] = useSaveSettings();
const [isSaving, saveError, saveSettings] = useSaveSettings({
setSettingsData,
setIsApiKeySet,
setApiKeyHasBeenReset,
setIsEmailPasswordSet,
setEmailPasswordHasBeenReset,
});
const [deleteAllMonitors, isDeletingMonitors] = useDeleteAllMonitors();
const [deleteMonitorStats, isDeletingMonitorStats] = useDeleteMonitorStats();
@@ -53,7 +66,6 @@ const Settings = () => {
const HEADING_SX = { mt: theme.spacing(2), mb: theme.spacing(2) };
const { t } = useTranslation();
const dispatch = useDispatch();
// Handlers
const handleChange = async (e) => {
const { name, value, checked } = e.target;
@@ -79,30 +91,19 @@ const Settings = () => {
settings: { ...settingsData.settings, [name]: newValue ?? value },
};
// Validate
const { error } = settingsValidation.validate(newSettingsData.settings, {
abortEarly: false,
});
if (!error || error.details.length === 0) {
setErrors({});
} else {
const newErrors = {};
error.details.forEach((err) => {
newErrors[err.path[0]] = err.message;
});
setErrors(newErrors);
}
if (name === "timezone") {
dispatch(setTimezone({ timezone: value }));
return;
}
if (name === "mode") {
dispatch(setMode(value));
return;
}
if (name === "language") {
dispatch(setLanguage(value));
return;
}
if (name === "deleteStats") {
@@ -120,6 +121,20 @@ const Settings = () => {
return;
}
// Validate
const { error } = settingsValidation.validate(newSettingsData.settings, {
abortEarly: false,
});
if (!error || error.details.length === 0) {
setErrors({});
} else {
const newErrors = {};
error.details.forEach((err) => {
newErrors[err.path[0]] = err.message;
});
setErrors(newErrors);
}
setSettingsData(newSettingsData);
};
@@ -159,7 +174,9 @@ const Settings = () => {
HEADING_SX={HEADING_SX}
settingsData={settingsData}
setSettingsData={setSettingsData}
isApiKeySet={settingsData?.pagespeedKeySet ?? false}
isApiKeySet={isApiKeySet}
apiKeyHasBeenReset={apiKeyHasBeenReset}
setApiKeyHasBeenReset={setApiKeyHasBeenReset}
/>
<SettingsURL
HEADING_SX={HEADING_SX}
@@ -187,7 +204,9 @@ const Settings = () => {
handleChange={handleChange}
settingsData={settingsData}
setSettingsData={setSettingsData}
isPasswordSet={settingsData?.emailPasswordSet ?? false}
isEmailPasswordSet={isEmailPasswordSet}
emailPasswordHasBeenReset={emailPasswordHasBeenReset}
setEmailPasswordHasBeenReset={setEmailPasswordHasBeenReset}
/>
<SettingsAbout />
<Stack
+11 -6
View File
@@ -11,8 +11,7 @@ class SettingsController {
this.emailService = emailService;
}
getAppSettings = async (req, res, next) => {
const dbSettings = await this.settingsService.getDBSettings();
buildAppSettings = (dbSettings) => {
const sanitizedSettings = { ...dbSettings };
delete sanitizedSettings.version;
@@ -30,6 +29,13 @@ class SettingsController {
delete sanitizedSettings.systemEmailPassword;
}
returnSettings.settings = sanitizedSettings;
return returnSettings;
};
getAppSettings = async (req, res, next) => {
const dbSettings = await this.settingsService.getDBSettings();
const returnSettings = this.buildAppSettings(dbSettings);
return res.success({
msg: this.stringService.getAppSettings,
data: returnSettings,
@@ -45,12 +51,11 @@ class SettingsController {
}
try {
await this.db.updateAppSettings(req.body);
const updatedSettings = { ...(await this.settingsService.reloadSettings()) };
delete updatedSettings.jwtSecret;
const updatedSettings = await this.db.updateAppSettings(req.body);
const returnSettings = this.buildAppSettings(updatedSettings);
return res.success({
msg: this.stringService.updateAppSettings,
data: updatedSettings,
data: returnSettings,
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "updateAppSettings"));
+4 -2
View File
@@ -26,10 +26,12 @@ const updateAppSettings = async (newSettings) => {
delete update.$set.systemEmailPassword;
}
const settings = await AppSettings.findOneAndUpdate({}, update, {
new: true,
await AppSettings.findOneAndUpdate({}, update, {
upsert: true,
});
const settings = await AppSettings.findOne()
.select("-__v -_id -createdAt -updatedAt -singleton")
.lean();
return settings;
} catch (error) {
error.service = SERVICE_NAME;
+6 -8
View File
@@ -28,8 +28,8 @@ class SettingsService {
* Constructs a new SettingsService
* @constructor
* @throws {Error}
*/ constructor(appSettings) {
this.appSettings = appSettings;
*/ constructor(AppSettings) {
this.AppSettings = AppSettings;
this.settings = { ...envConfig };
}
/**
@@ -60,16 +60,14 @@ class SettingsService {
async getDBSettings() {
// Remove any old settings
await this.appSettings.deleteMany({ version: { $exists: false } });
await this.AppSettings.deleteMany({ version: { $exists: false } });
let settings = await this.appSettings
.findOne({ singleton: true })
let settings = await this.AppSettings.findOne({ singleton: true })
.select("-__v -_id -createdAt -updatedAt -singleton")
.lean();
if (settings === null) {
await this.appSettings.create({});
settings = await this.appSettings
.findOne({ singleton: true })
await this.AppSettings.create({});
settings = await this.AppSettings.findOne({ singleton: true })
.select("-__v -_id -createdAt -updatedAt -singleton")
.lean();
}