Add proxies to create/edit monitors page
This commit is contained in:
1 parent
cc137e1d06
commit
ccfda938d5
9 files changed
+213
-16
No files matched your search
@@ -11,10 +11,11 @@ interface SelectInputProps<T> extends Omit<SelectProps<T>, "label"> {
|
||||
fieldLabel?: string;
|
||||
required?: boolean;
|
||||
placeholder?: string;
|
||||
helperText?: string;
|
||||
}
|
||||
|
||||
const SelectInputInner = <T,>(
|
||||
{ fieldLabel, required, placeholder, ...props }: SelectInputProps<T>,
|
||||
{ fieldLabel, required, placeholder, helperText, ...props }: SelectInputProps<T>,
|
||||
ref: React.ForwardedRef<HTMLDivElement>
|
||||
) => {
|
||||
const theme = useTheme();
|
||||
@@ -27,7 +28,7 @@ const SelectInputInner = <T,>(
|
||||
: selected === undefined || selected === null || selected === "";
|
||||
|
||||
if (isEmpty && placeholder) {
|
||||
return <Typography sx={{ color: emptyPlaceholderColor }}>{placeholder}</Typography>;
|
||||
return <Typography color={emptyPlaceholderColor}>{placeholder}</Typography>;
|
||||
}
|
||||
|
||||
if (isMultiple) {
|
||||
@@ -92,11 +93,21 @@ const SelectInputInner = <T,>(
|
||||
/>
|
||||
);
|
||||
|
||||
if (fieldLabel) {
|
||||
const helper = helperText ? (
|
||||
<Typography
|
||||
variant="caption"
|
||||
color={props.error ? theme.palette.error.main : theme.palette.text.secondary}
|
||||
>
|
||||
{helperText}
|
||||
</Typography>
|
||||
) : null;
|
||||
|
||||
if (fieldLabel || helper) {
|
||||
return (
|
||||
<Stack spacing={theme.spacing(2)}>
|
||||
<FieldLabel required={required}>{fieldLabel}</FieldLabel>
|
||||
{fieldLabel && <FieldLabel required={required}>{fieldLabel}</FieldLabel>}
|
||||
{select}
|
||||
{helper}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ export const FormSelectField = <T extends FieldValues>({
|
||||
}}
|
||||
error={!!fieldState.error}
|
||||
{...rest}
|
||||
helperText={fieldState.error?.message ?? rest.helperText}
|
||||
>
|
||||
{options.map((option) => (
|
||||
<MenuItem
|
||||
|
||||
@@ -18,8 +18,8 @@ import {
|
||||
Lock,
|
||||
Users,
|
||||
Tag,
|
||||
Waypoints,
|
||||
} from "lucide-react";
|
||||
|
||||
export const getMenu = (t: Function) => {
|
||||
return [
|
||||
{
|
||||
@@ -47,6 +47,11 @@ export const getMenu = (t: Function) => {
|
||||
path: "tags",
|
||||
icon: <Icon icon={Tag} />,
|
||||
},
|
||||
{
|
||||
name: t("components.sidebar.menu.proxies"),
|
||||
path: "proxies",
|
||||
icon: <Icon icon={Waypoints} />,
|
||||
},
|
||||
{
|
||||
name: t("components.sidebar.menu.checks"),
|
||||
path: "checks",
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
DefaultHttpMethod,
|
||||
DefaultMonitorMatchMethod,
|
||||
DefaultPageSpeedStrategy,
|
||||
DefaultProxyMode,
|
||||
} from "@/Types/Monitor";
|
||||
import type { Monitor, MonitorType } from "@/Types/Monitor";
|
||||
|
||||
@@ -46,6 +47,8 @@ export const getMonitorDefaults = (
|
||||
url: data?.url || "",
|
||||
method: data?.method ?? DefaultHttpMethod,
|
||||
ignoreTlsErrors: data?.ignoreTlsErrors || false,
|
||||
proxyMode: data?.proxyMode ?? DefaultProxyMode,
|
||||
proxyId: data?.proxyId || "",
|
||||
useAdvancedMatching: data?.useAdvancedMatching || false,
|
||||
matchMethod: data?.matchMethod || DefaultMonitorMatchMethod,
|
||||
expectedValue: data?.expectedValue || "",
|
||||
@@ -141,6 +144,8 @@ export const getMonitorDefaults = (
|
||||
url: "",
|
||||
method: DefaultHttpMethod,
|
||||
ignoreTlsErrors: false,
|
||||
proxyMode: DefaultProxyMode,
|
||||
proxyId: "",
|
||||
useAdvancedMatching: false,
|
||||
matchMethod: DefaultMonitorMatchMethod,
|
||||
expectedValue: "",
|
||||
|
||||
@@ -41,6 +41,8 @@ import {
|
||||
DefaultMonitorMatchMethod,
|
||||
MonitorMatchMethods,
|
||||
GeoCheckIntervalOptions,
|
||||
ProxyModes,
|
||||
type ProxyMode,
|
||||
} from "@/Types/Monitor";
|
||||
import type { Notification } from "@/Types/Notification";
|
||||
import type { Tag } from "@/Types/Tag";
|
||||
@@ -56,6 +58,7 @@ import { FormMultiSelectField } from "@/Components/inputs/forms/FormMultiSelectF
|
||||
import { FormSelectField } from "@/Components/inputs/forms/FormSelectField";
|
||||
import { FormSliderField } from "@/Components/inputs/forms/FormSliderField";
|
||||
import { FormSwitchField } from "@/Components/inputs/forms/FormSwitchField";
|
||||
import type { ProxyResponse } from "@/Types/Proxy";
|
||||
|
||||
const httpMethodOptions = HttpMethods.map((method) => ({
|
||||
value: method,
|
||||
@@ -67,6 +70,7 @@ interface GeneralSettingsConfig {
|
||||
urlPlaceholder: string;
|
||||
namePlaceholder: string;
|
||||
showUrl: boolean;
|
||||
showProxy: boolean;
|
||||
showPort: boolean;
|
||||
showGameSelect: boolean;
|
||||
showSecret: boolean;
|
||||
@@ -87,6 +91,7 @@ const getGeneralSettingsConfig = (
|
||||
urlPlaceholder: t("pages.createMonitor.form.general.option.url.placeholder"),
|
||||
namePlaceholder: t("pages.createMonitor.form.general.option.name.placeholder"),
|
||||
showUrl: true,
|
||||
showProxy: true,
|
||||
showPort: false,
|
||||
showGameSelect: false,
|
||||
showSecret: false,
|
||||
@@ -101,6 +106,7 @@ const getGeneralSettingsConfig = (
|
||||
urlPlaceholder: t("pages.createMonitor.form.general.option.host.placeholder"),
|
||||
namePlaceholder: t("pages.createMonitor.form.general.option.name.placeholder"),
|
||||
showUrl: true,
|
||||
showProxy: false,
|
||||
showPort: false,
|
||||
showGameSelect: false,
|
||||
showSecret: false,
|
||||
@@ -115,6 +121,7 @@ const getGeneralSettingsConfig = (
|
||||
urlPlaceholder: t("pages.createMonitor.form.general.option.container.placeholder"),
|
||||
namePlaceholder: t("pages.createMonitor.form.general.option.name.placeholder"),
|
||||
showUrl: true,
|
||||
showProxy: false,
|
||||
showPort: false,
|
||||
showGameSelect: false,
|
||||
showSecret: false,
|
||||
@@ -129,6 +136,7 @@ const getGeneralSettingsConfig = (
|
||||
urlPlaceholder: t("pages.createMonitor.form.general.option.url.placeholder"),
|
||||
namePlaceholder: t("pages.createMonitor.form.general.option.name.placeholder"),
|
||||
showUrl: true,
|
||||
showProxy: false,
|
||||
showPort: true,
|
||||
showGameSelect: false,
|
||||
showSecret: false,
|
||||
@@ -143,6 +151,7 @@ const getGeneralSettingsConfig = (
|
||||
urlPlaceholder: t("pages.createMonitor.form.general.option.url.placeholder"),
|
||||
namePlaceholder: t("pages.createMonitor.form.general.option.name.placeholder"),
|
||||
showUrl: true,
|
||||
showProxy: false,
|
||||
showPort: true,
|
||||
showGameSelect: true,
|
||||
showSecret: false,
|
||||
@@ -157,6 +166,7 @@ const getGeneralSettingsConfig = (
|
||||
urlPlaceholder: t("pages.createMonitor.form.general.option.host.placeholder"),
|
||||
namePlaceholder: t("pages.createMonitor.form.general.option.name.placeholder"),
|
||||
showUrl: true,
|
||||
showProxy: false,
|
||||
showPort: true,
|
||||
showGameSelect: false,
|
||||
showSecret: false,
|
||||
@@ -171,6 +181,7 @@ const getGeneralSettingsConfig = (
|
||||
urlPlaceholder: t("pages.createMonitor.form.general.option.url.placeholder"),
|
||||
namePlaceholder: t("pages.createMonitor.form.general.option.name.placeholder"),
|
||||
showUrl: true,
|
||||
showProxy: false,
|
||||
showPort: false,
|
||||
showGameSelect: false,
|
||||
showSecret: false,
|
||||
@@ -185,6 +196,7 @@ const getGeneralSettingsConfig = (
|
||||
urlPlaceholder: t("pages.createMonitor.form.general.option.captureUrl.placeholder"),
|
||||
namePlaceholder: t("pages.createMonitor.form.general.option.name.placeholder"),
|
||||
showUrl: true,
|
||||
showProxy: false,
|
||||
showPort: false,
|
||||
showGameSelect: false,
|
||||
showSecret: true,
|
||||
@@ -199,6 +211,7 @@ const getGeneralSettingsConfig = (
|
||||
urlPlaceholder: t("pages.createMonitor.form.general.option.wsUrl.placeholder"),
|
||||
namePlaceholder: t("pages.createMonitor.form.general.option.name.placeholder"),
|
||||
showUrl: true,
|
||||
showProxy: false,
|
||||
showPort: false,
|
||||
showGameSelect: false,
|
||||
showSecret: false,
|
||||
@@ -213,6 +226,7 @@ const getGeneralSettingsConfig = (
|
||||
urlPlaceholder: t("pages.createMonitor.form.general.option.url.placeholder"),
|
||||
namePlaceholder: t("pages.createMonitor.form.general.option.name.placeholder"),
|
||||
showUrl: true,
|
||||
showProxy: false,
|
||||
showPort: false,
|
||||
showGameSelect: false,
|
||||
showSecret: false,
|
||||
@@ -258,6 +272,11 @@ const CreateMonitorPage = () => {
|
||||
const { data: notifications } = useGet<Notification[]>("/notifications/team");
|
||||
const { data: games } = useGet<GamesMap>("/monitors/games");
|
||||
const { data: tags } = useGet<Tag[]>("/tags/team");
|
||||
const { data: proxies } = useGet<ProxyResponse[]>("/proxies/team");
|
||||
const { data: appSettings } = useGet<{
|
||||
globalProxyEnabled?: boolean;
|
||||
globalProxyId?: string | null;
|
||||
}>("/settings");
|
||||
|
||||
const { schema, defaults } = useMonitorForm({
|
||||
data: existingMonitor ?? null,
|
||||
@@ -281,6 +300,7 @@ const CreateMonitorPage = () => {
|
||||
|
||||
const watchedType = watch("type") as MonitorType;
|
||||
const watchedMethod = watch("method") as HttpMethod | undefined;
|
||||
const watchedProxyMode = watch("proxyMode") as ProxyMode | undefined;
|
||||
const watchedUseAdvancedMatching = watch("useAdvancedMatching") as boolean;
|
||||
const watchGeoCheckEnabled = watch("geoCheckEnabled") as boolean;
|
||||
|
||||
@@ -466,6 +486,28 @@ const CreateMonitorPage = () => {
|
||||
})),
|
||||
[t]
|
||||
);
|
||||
const proxyModeOptions = useMemo(
|
||||
() =>
|
||||
ProxyModes.map((mode) => ({
|
||||
value: mode,
|
||||
label: t(`pages.createMonitor.form.general.option.proxyMode.value.${mode}`),
|
||||
})),
|
||||
[t]
|
||||
);
|
||||
|
||||
const proxyOptions = useMemo(
|
||||
() =>
|
||||
(proxies ?? []).map((proxy) => ({
|
||||
value: proxy.id,
|
||||
label: `${proxy.name} (${proxy.host}:${proxy.port})`,
|
||||
})),
|
||||
[proxies]
|
||||
);
|
||||
|
||||
const globalProxyName =
|
||||
appSettings?.globalProxyEnabled === true
|
||||
? proxies?.find((proxy) => proxy.id === appSettings?.globalProxyId)?.name
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
<FormProvider {...form}>
|
||||
@@ -518,6 +560,11 @@ const CreateMonitorPage = () => {
|
||||
}
|
||||
rightContent={
|
||||
<Stack spacing={theme.spacing(LAYOUT.MD)}>
|
||||
<FormTextField
|
||||
name="name"
|
||||
fieldLabel={t("pages.createMonitor.form.general.option.name.label")}
|
||||
placeholder={generalSettingsConfig.namePlaceholder}
|
||||
/>
|
||||
{/* URL/Host/Container field - not shown for hardware */}
|
||||
{generalSettingsConfig.showUrl && (
|
||||
<FormTextField
|
||||
@@ -527,6 +574,47 @@ const CreateMonitorPage = () => {
|
||||
disabled={isEditMode}
|
||||
/>
|
||||
)}
|
||||
{/* Proxy fields - only shown for HTTP */}
|
||||
{generalSettingsConfig.showProxy && (
|
||||
<>
|
||||
<FormSelectField
|
||||
name="proxyMode"
|
||||
fieldLabel={t(
|
||||
"pages.createMonitor.form.general.option.proxyMode.label"
|
||||
)}
|
||||
options={proxyModeOptions}
|
||||
/>
|
||||
{watchedProxyMode === "inherit" && (
|
||||
<Typography
|
||||
component="span"
|
||||
color={
|
||||
globalProxyName
|
||||
? theme.palette.text.secondary
|
||||
: theme.palette.warning.main
|
||||
}
|
||||
sx={{ opacity: 0.8 }}
|
||||
>
|
||||
{globalProxyName
|
||||
? t(
|
||||
"pages.createMonitor.form.general.option.proxyMode.effect.global",
|
||||
{ name: globalProxyName }
|
||||
)
|
||||
: t(
|
||||
"pages.createMonitor.form.general.option.proxyMode.effect.direct"
|
||||
)}
|
||||
</Typography>
|
||||
)}
|
||||
{watchedProxyMode === "custom" && (
|
||||
<FormSelectField
|
||||
name="proxyId"
|
||||
fieldLabel={t(
|
||||
"pages.createMonitor.form.general.option.proxy.label"
|
||||
)}
|
||||
options={proxyOptions}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Strategy field - only for pagespeed type */}
|
||||
{generalSettingsConfig.showStrategy && (
|
||||
@@ -603,11 +691,6 @@ const CreateMonitorPage = () => {
|
||||
options={dnsRecordTypeOptions}
|
||||
/>
|
||||
)}
|
||||
<FormTextField
|
||||
name="name"
|
||||
fieldLabel={t("pages.createMonitor.form.general.option.name.label")}
|
||||
placeholder={generalSettingsConfig.namePlaceholder}
|
||||
/>
|
||||
</Stack>
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -32,7 +32,7 @@ const ProxiesPage = () => {
|
||||
setSelectedProxy(null);
|
||||
refetch();
|
||||
};
|
||||
``;
|
||||
|
||||
const handleCancel = () => {
|
||||
setSelectedProxy(null);
|
||||
};
|
||||
|
||||
@@ -39,11 +39,14 @@ import CreateNotifications from "@/Pages/Notifications/create";
|
||||
import Tags from "@/Pages/Tags";
|
||||
import CreateTags from "@/Pages/Tags/create";
|
||||
|
||||
// Proxies
|
||||
import Proxies from "@/Pages/Proxies";
|
||||
import CreateProxies from "@/Pages/Proxies/create";
|
||||
|
||||
// Settings
|
||||
import Account from "@/Pages/Account";
|
||||
import EditUser from "@/Pages/Account/EditUser";
|
||||
import Settings from "@/Pages/Settings";
|
||||
|
||||
import Maintenance from "@/Pages/Maintenance";
|
||||
import CreateNewMaintenanceWindow from "@/Pages/Maintenance/create";
|
||||
|
||||
@@ -52,10 +55,8 @@ import Logs from "@/Pages/Logs";
|
||||
|
||||
// Routing
|
||||
import { ProtectedRoute, RoleProtectedRoute } from "@/Components/routing/RouteProtected";
|
||||
|
||||
import CreateMonitor from "@/Pages/CreateMonitor";
|
||||
import { isCustomDomainHost } from "@/Utils/statusPageUrl";
|
||||
|
||||
const Routes = () => {
|
||||
if (isCustomDomainHost()) {
|
||||
return (
|
||||
@@ -188,12 +189,24 @@ const Routes = () => {
|
||||
path="tags/create"
|
||||
element={<CreateTags />}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path="tags/configure/:tagId"
|
||||
element={<CreateTags />}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path="proxies"
|
||||
element={<Proxies />}
|
||||
/>
|
||||
<Route
|
||||
path="proxies/create"
|
||||
element={<CreateProxies />}
|
||||
/>
|
||||
<Route
|
||||
path="proxies/configure/:proxyId"
|
||||
element={<CreateProxies />}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path="maintenance"
|
||||
element={<Maintenance />}
|
||||
|
||||
@@ -109,6 +109,10 @@ export const HttpMethods = ["GET", "HEAD"] as const;
|
||||
export type HttpMethod = (typeof HttpMethods)[number];
|
||||
export const DefaultHttpMethod: HttpMethod = "GET";
|
||||
|
||||
export const ProxyModes = ["inherit", "none", "custom"] as const;
|
||||
export type ProxyMode = (typeof ProxyModes)[number];
|
||||
export const DefaultProxyMode: ProxyMode = "inherit";
|
||||
|
||||
export interface Monitor {
|
||||
id: string;
|
||||
userId: string;
|
||||
@@ -121,6 +125,8 @@ export interface Monitor {
|
||||
statusWindowThreshold: number;
|
||||
type: MonitorType;
|
||||
ignoreTlsErrors: boolean;
|
||||
proxyMode: ProxyMode;
|
||||
proxyId?: string;
|
||||
useAdvancedMatching: boolean;
|
||||
jsonPath?: string;
|
||||
expectedValue?: string;
|
||||
|
||||
@@ -205,6 +205,7 @@
|
||||
"infrastructure": "Infrastructure",
|
||||
"notifications": "Notifications",
|
||||
"tags": "Tags",
|
||||
"proxies": "Proxies",
|
||||
"checks": "Checks",
|
||||
"incidents": "Incidents",
|
||||
"statusPages": "Status pages",
|
||||
@@ -244,6 +245,63 @@
|
||||
}
|
||||
},
|
||||
"pages": {
|
||||
"proxies": {
|
||||
"header": {
|
||||
"title": "Proxies",
|
||||
"description": "Manage your proxy servers for routing HTTP monitoring traffic."
|
||||
},
|
||||
"fallback": {
|
||||
"title": "No proxies yet",
|
||||
"description": "Route HTTP monitor checks through a proxy server. Once you create a proxy, you can select it on individual monitors or set it as the global default in Settings.",
|
||||
"actionButton": "Create a proxy"
|
||||
},
|
||||
"form": {
|
||||
"name": {
|
||||
"title": "Proxy name",
|
||||
"description": "A friendly name to identify this proxy",
|
||||
"optionName": "Proxy name",
|
||||
"placeholder": "My proxy"
|
||||
},
|
||||
"server": {
|
||||
"title": "Proxy server",
|
||||
"description": "Where the proxy server is reachable. Set the protocol Checkmate uses to connect to it, its hostname or IP address, and the port it listens on.",
|
||||
"option": {
|
||||
"protocol": {
|
||||
"label": "Protocol"
|
||||
},
|
||||
"host": {
|
||||
"label": "Hostname / IP Address",
|
||||
"placeholder": "proxy.example.com"
|
||||
},
|
||||
"port": {
|
||||
"label": "Port"
|
||||
}
|
||||
}
|
||||
},
|
||||
"auth": {
|
||||
"title": "Authentication",
|
||||
"description": "Optional. Only needed if the proxy server requires basic authentication.",
|
||||
"option": {
|
||||
"username": {
|
||||
"label": "Username",
|
||||
"placeholder": "Proxy username",
|
||||
"labelSet": "Username is set. Click Reset to change it."
|
||||
},
|
||||
"password": {
|
||||
"label": "Password",
|
||||
"labelSet": "Password is set. Click Reset to change it."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
"headers": {
|
||||
"protocol": "Protocol",
|
||||
"host": "Host",
|
||||
"port": "Port"
|
||||
}
|
||||
}
|
||||
},
|
||||
"notFound": {
|
||||
"title": "Oh no! You dropped your sushi!",
|
||||
"subtitle": "Either the URL doesn’t exist, or you don’t have access to it."
|
||||
@@ -581,6 +639,21 @@
|
||||
"label": "URL",
|
||||
"placeholder": "https://www.google.com"
|
||||
},
|
||||
"proxyMode": {
|
||||
"label": "Proxy mode",
|
||||
"value": {
|
||||
"inherit": "Use global proxy setting",
|
||||
"none": "No proxy (direct connection)",
|
||||
"custom": "Custom"
|
||||
},
|
||||
"effect": {
|
||||
"global": "Checks currently go through the global proxy: {{name}}",
|
||||
"direct": "Global proxy not enabled, will use direct connection"
|
||||
}
|
||||
},
|
||||
"proxy": {
|
||||
"label": "Proxy server"
|
||||
},
|
||||
"game": {
|
||||
"label": "Choose game",
|
||||
"placeholder": "Select a game"
|
||||
@@ -624,7 +697,7 @@
|
||||
},
|
||||
"title": "General settings",
|
||||
"description": {
|
||||
"http": "Enter the URL or IP to monitor (e.g., https://example.com/ or 192.168.1.100) and add a clear display name that appears on the dashboard.",
|
||||
"http": "Enter the URL or IP to monitor (e.g., https://example.com/ or 192.168.1.100) and add a clear display name that appears on the dashboard. Set an optional proxy server to connect through.",
|
||||
"ping": "Enter the IP address or hostname to ping (e.g., 192.168.1.100 or example.com) and add a clear display name that appears on the dashboard.",
|
||||
"port": "Enter the URL or IP of the server, the port number and a clear display name that appears on the dashboard.",
|
||||
"docker": "Enter the Docker container name or ID. You can use either the container name (e.g., my-app) or the container ID (full 64-char ID or short ID).",
|
||||
|
||||
Reference in new issue
Block a user