add global proxy setting config and translation keys
This commit is contained in:
1 parent
6e271f834c
commit
ea8ad88ae9
4 files changed
+150
-58
No files matched your search
@@ -43,6 +43,8 @@ export const useSettingsForm = ({ data = null }: UseSettingsFormOptions = {}) =>
|
||||
checkTTL: data?.checkTTL ?? 30,
|
||||
pagespeedApiKey: "",
|
||||
systemEmailPassword: "",
|
||||
globalProxyEnabled: data?.globalProxyEnabled ?? false,
|
||||
globalProxyId: data?.globalProxyId ?? null,
|
||||
};
|
||||
|
||||
return { schema: settingsSchema, defaults };
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
Alert,
|
||||
type SelectChangeEvent,
|
||||
} from "@mui/material";
|
||||
import { useEffect } from "react";
|
||||
import { useEffect, useMemo } from "react";
|
||||
import { FormProvider, useForm, type FieldPath } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -43,6 +43,8 @@ import { FormTextField } from "@/Components/inputs/forms/FormTextField";
|
||||
import { FormSliderField } from "@/Components/inputs/forms/FormSliderField";
|
||||
import { FormSwitchField } from "@/Components/inputs/forms/FormSwitchField";
|
||||
import { FormNumberField } from "@/Components/inputs/forms/FormNumberField";
|
||||
import { FormSelectField } from "@/Components/inputs/forms/FormSelectField";
|
||||
import type { ProxyResponse } from "@/Types/Proxy";
|
||||
|
||||
interface SettingsResponse {
|
||||
settings: any;
|
||||
@@ -65,6 +67,17 @@ export const SettingsPage = () => {
|
||||
|
||||
// Fetch settings data from API
|
||||
const { data: fetchedSettings } = useGet<SettingsResponse>("/settings");
|
||||
// Get proxies
|
||||
const { data: proxies } = useGet<ProxyResponse[]>("/proxies/team");
|
||||
const proxyOptions = useMemo(
|
||||
() =>
|
||||
(proxies ?? []).map((proxy) => ({
|
||||
value: proxy.id,
|
||||
label: `${proxy.name} (${proxy.host}:${proxy.port})`,
|
||||
})),
|
||||
[proxies]
|
||||
);
|
||||
|
||||
// Form submission
|
||||
const { patch, loading: isSaving } = usePatch<SettingsFormData, SettingsResponse>();
|
||||
|
||||
@@ -115,6 +128,8 @@ export const SettingsPage = () => {
|
||||
mode: "onChange",
|
||||
});
|
||||
|
||||
const { watch } = form;
|
||||
|
||||
// Reset form when defaults change
|
||||
useEffect(() => {
|
||||
form.reset(defaults);
|
||||
@@ -292,6 +307,21 @@ export const SettingsPage = () => {
|
||||
|
||||
const languages = Object.keys(i18n.options.resources || {});
|
||||
|
||||
const globalProxyEnabled = watch("globalProxyEnabled");
|
||||
|
||||
// Enabling the proxy with nothing selected preselects the first proxy
|
||||
useEffect(() => {
|
||||
if (
|
||||
globalProxyEnabled &&
|
||||
!form.getValues("globalProxyId") &&
|
||||
proxyOptions.length > 0
|
||||
) {
|
||||
form.setValue("globalProxyId", proxyOptions[0].value, {
|
||||
shouldValidate: true,
|
||||
});
|
||||
}
|
||||
}, [globalProxyEnabled, proxyOptions, form]);
|
||||
|
||||
return (
|
||||
<FormProvider {...form}>
|
||||
<BasePage
|
||||
@@ -365,6 +395,38 @@ export const SettingsPage = () => {
|
||||
</Stack>
|
||||
}
|
||||
/>
|
||||
{isAdmin && (
|
||||
<ConfigBox
|
||||
title={t("pages.settings.form.globalProxy.title")}
|
||||
subtitle={t("pages.settings.form.globalProxy.description")}
|
||||
rightContent={
|
||||
proxyOptions.length > 0 ? (
|
||||
<Stack>
|
||||
<FormSwitchField
|
||||
name="globalProxyEnabled"
|
||||
labelPlacement="start"
|
||||
label={t("pages.settings.form.globalProxy.option.enabled.label")}
|
||||
/>
|
||||
{globalProxyEnabled && (
|
||||
<FormSelectField
|
||||
fieldLabel={t(
|
||||
"pages.settings.form.globalProxy.option.proxy.label"
|
||||
)}
|
||||
name="globalProxyId"
|
||||
options={proxyOptions}
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
) : (
|
||||
<TextLink
|
||||
text={t("pages.settings.form.globalProxy.option.empty.text")}
|
||||
linkText={t("pages.settings.form.globalProxy.option.empty.link")}
|
||||
href="/proxies"
|
||||
/>
|
||||
)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{isAdmin && (
|
||||
<ConfigBox
|
||||
title={t("pages.settings.form.pagespeed.title")}
|
||||
|
||||
@@ -1,61 +1,73 @@
|
||||
import { CHECK_TTL_SENTINEL } from "@/Types/Check";
|
||||
import { z } from "zod";
|
||||
|
||||
export const settingsSchema = z.object({
|
||||
systemEmailIgnoreTLS: z.boolean(),
|
||||
systemEmailRequireTLS: z.boolean(),
|
||||
systemEmailRejectUnauthorized: z.boolean(),
|
||||
systemEmailConnectionHost: z
|
||||
.string()
|
||||
.transform((val) => (val.trim() === "" ? null : val.trim()))
|
||||
.optional(),
|
||||
systemEmailSecure: z.boolean().optional(),
|
||||
systemEmailPool: z.boolean().optional(),
|
||||
showURL: z.boolean().optional(),
|
||||
checkTTL: z
|
||||
.number()
|
||||
.int()
|
||||
.min(1, "Please enter a value")
|
||||
.max(CHECK_TTL_SENTINEL, `Maximum ${CHECK_TTL_SENTINEL}`),
|
||||
pagespeedApiKey: z
|
||||
.string()
|
||||
.transform((val) => (val.trim() === "" ? null : val.trim()))
|
||||
.optional(),
|
||||
systemEmailHost: z
|
||||
.string()
|
||||
.regex(/^[a-zA-Z0-9.-]*$/, "Invalid hostname or IP address")
|
||||
.transform((val) => (val.trim() === "" ? null : val.trim()))
|
||||
.optional(),
|
||||
systemEmailPort: z.number().int().min(1).max(65535).optional(),
|
||||
systemEmailAddress: z
|
||||
.email("Please enter a valid email address")
|
||||
.or(z.literal(""))
|
||||
.transform((val) => (val === "" ? null : val.toLowerCase().trim()))
|
||||
.optional(),
|
||||
systemEmailDisplayName: z
|
||||
.string()
|
||||
.max(100, "Display name must be 100 characters or fewer")
|
||||
.transform((val) => (val.trim() === "" ? null : val.trim()))
|
||||
.optional(),
|
||||
systemEmailUser: z
|
||||
.string()
|
||||
.transform((val) => (val.trim() === "" ? null : val.trim()))
|
||||
.optional(),
|
||||
systemEmailPassword: z
|
||||
.string()
|
||||
.transform((val) => (val.trim() === "" ? null : val.trim()))
|
||||
.optional(),
|
||||
systemEmailTLSServername: z
|
||||
.string()
|
||||
.transform((val) => (val.trim() === "" ? null : val.trim()))
|
||||
.optional(),
|
||||
globalThresholds: z.object({
|
||||
cpu: z.number().int().min(1).max(100),
|
||||
memory: z.number().int().min(1).max(100),
|
||||
disk: z.number().int().min(1).max(100),
|
||||
temperature: z.number().int().min(1).max(150),
|
||||
}),
|
||||
});
|
||||
export const settingsSchema = z
|
||||
.object({
|
||||
systemEmailIgnoreTLS: z.boolean(),
|
||||
systemEmailRequireTLS: z.boolean(),
|
||||
systemEmailRejectUnauthorized: z.boolean(),
|
||||
systemEmailConnectionHost: z
|
||||
.string()
|
||||
.transform((val) => (val.trim() === "" ? null : val.trim()))
|
||||
.optional(),
|
||||
systemEmailSecure: z.boolean().optional(),
|
||||
systemEmailPool: z.boolean().optional(),
|
||||
showURL: z.boolean().optional(),
|
||||
checkTTL: z
|
||||
.number()
|
||||
.int()
|
||||
.min(1, "Please enter a value")
|
||||
.max(CHECK_TTL_SENTINEL, `Maximum ${CHECK_TTL_SENTINEL}`),
|
||||
pagespeedApiKey: z
|
||||
.string()
|
||||
.transform((val) => (val.trim() === "" ? null : val.trim()))
|
||||
.optional(),
|
||||
systemEmailHost: z
|
||||
.string()
|
||||
.regex(/^[a-zA-Z0-9.-]*$/, "Invalid hostname or IP address")
|
||||
.transform((val) => (val.trim() === "" ? null : val.trim()))
|
||||
.optional(),
|
||||
systemEmailPort: z.number().int().min(1).max(65535).optional(),
|
||||
systemEmailAddress: z
|
||||
.email("Please enter a valid email address")
|
||||
.or(z.literal(""))
|
||||
.transform((val) => (val === "" ? null : val.toLowerCase().trim()))
|
||||
.optional(),
|
||||
systemEmailDisplayName: z
|
||||
.string()
|
||||
.max(100, "Display name must be 100 characters or fewer")
|
||||
.transform((val) => (val.trim() === "" ? null : val.trim()))
|
||||
.optional(),
|
||||
systemEmailUser: z
|
||||
.string()
|
||||
.transform((val) => (val.trim() === "" ? null : val.trim()))
|
||||
.optional(),
|
||||
systemEmailPassword: z
|
||||
.string()
|
||||
.transform((val) => (val.trim() === "" ? null : val.trim()))
|
||||
.optional(),
|
||||
systemEmailTLSServername: z
|
||||
.string()
|
||||
.transform((val) => (val.trim() === "" ? null : val.trim()))
|
||||
.optional(),
|
||||
globalThresholds: z.object({
|
||||
cpu: z.number().int().min(1).max(100),
|
||||
memory: z.number().int().min(1).max(100),
|
||||
disk: z.number().int().min(1).max(100),
|
||||
temperature: z.number().int().min(1).max(150),
|
||||
}),
|
||||
globalProxyEnabled: z.boolean(),
|
||||
globalProxyId: z.string().nullable().optional(),
|
||||
})
|
||||
.superRefine((body, ctx) => {
|
||||
if (body.globalProxyEnabled === true && !body.globalProxyId) {
|
||||
ctx.addIssue({
|
||||
code: "custom",
|
||||
path: ["globalProxyId"],
|
||||
message: "A proxy must be selected to enable the global proxy",
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export type SettingsFormInput = z.input<typeof settingsSchema>;
|
||||
export type SettingsFormData = z.infer<typeof settingsSchema>;
|
||||
@@ -643,13 +643,13 @@
|
||||
"label": "Proxy",
|
||||
"value": {
|
||||
"inheritDirect": "Default (direct connection)",
|
||||
"inheritGlobal": "Default (global proxy: {{name}})",
|
||||
"inheritGlobal": "Default ({{name}})",
|
||||
"none": "Never use a proxy",
|
||||
"custom": "Custom proxy"
|
||||
},
|
||||
"description": {
|
||||
"inheritDirect": "No global proxy is configured, connect directly. If a global proxy is later configured, this monitor will use it.",
|
||||
"inheritGlobal": "Follows the global proxy setting — changing it in Settings also changes this monitor's connection.",
|
||||
"inheritGlobal": "Use the global proxy setting",
|
||||
"none": "Always connects directly even if a global proxy is enabled later.",
|
||||
"custom": "Route this monitor's checks through a specific proxy. Overrides global proxy"
|
||||
}
|
||||
@@ -1362,6 +1362,22 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"globalProxy": {
|
||||
"title": "Global proxy",
|
||||
"description": "Route monitoring checks through a proxy by default. Applies to all HTTP monitors whose proxy setting is \"Default\"; monitors set to \"Never use a proxy\" or a custom proxy are unaffected.",
|
||||
"option": {
|
||||
"enabled": {
|
||||
"label": "Use global proxy"
|
||||
},
|
||||
"proxy": {
|
||||
"label": "Choose a global proxy"
|
||||
},
|
||||
"empty": {
|
||||
"text": "No proxies configured yet.",
|
||||
"link": "Create one first"
|
||||
}
|
||||
}
|
||||
},
|
||||
"pagespeed": {
|
||||
"title": "Google PageSpeed API key",
|
||||
"description": "Enter your Google PageSpeed API key to enable Google PageSpeed monitoring. Click Reset to update the key.",
|
||||
|
||||
Reference in new issue
Block a user