Files
Checkmate/client/src/Pages/CreateMonitor/index.tsx
T

1037 lines
29 KiB
TypeScript

import { useMemo, useState } from "react";
import { useEffect } from "react";
import { logger } from "@/Utils/logger";
import { ALL_HTTP_STATUS_CODES } from "@/Utils/statusCode";
import { useParams, useLocation, useNavigate } from "react-router";
import { useForm, FormProvider } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { useTheme } from "@mui/material";
import Stack from "@mui/material/Stack";
import { Trans, useTranslation } from "react-i18next";
import Typography from "@mui/material/Typography";
import Link from "@mui/material/Link";
import { HeaderDeleteControls } from "@/Components/monitors";
import { GeoContinents } from "@/Types/GeoCheck";
import Box from "@mui/material/Box";
import {
BasePage,
ColoredLabel,
ConfigBox,
StepProgress,
} from "@/Components/design-elements";
import { Button, Dialog } from "@/Components/inputs";
import { LAYOUT } from "@/Utils/Theme/constants";
import { useGet, usePost, usePatch, useDelete } from "@/Hooks/UseApi";
import { useMonitorForm, getMonitorDefaults } from "@/Hooks/useMonitorForm";
import {
type Monitor,
type MonitorType,
type GamesMap,
type HttpMethod,
supportsGeoCheck,
DefaultPageSpeedStrategy,
DefaultHttpMethod,
HttpMethods,
SelectableMonitorTypes,
monitorTypeLabelKey,
PageSpeedStrategies,
DnsRecordTypes,
MonitorIntervalOptions,
DefaultMonitorMatchMethod,
MonitorMatchMethods,
GeoCheckIntervalOptions,
ProxyModes,
type ProxyMode,
} from "@/Types/Monitor";
import type { Notification } from "@/Types/Notification";
import type { Tag } from "@/Types/Tag";
import {
stepFieldsFor,
monitorStepCount,
type MonitorFormData,
} from "@/Validation/monitor";
import { FormNumberField } from "@/Components/inputs/forms/FormNumberField";
import { FormTextField } from "@/Components/inputs/forms/FormTextField";
import { FormRadioGroup } from "@/Components/inputs/forms/FormRadioGroupField";
import { FormMultiSelectField } from "@/Components/inputs/forms/FormMultiSelectField";
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,
label: method,
}));
interface GeneralSettingsConfig {
urlLabel: string;
urlPlaceholder: string;
namePlaceholder: string;
showUrl: boolean;
showProxy: boolean;
showPort: boolean;
showGameSelect: boolean;
showSecret: boolean;
showGrpcServiceName: boolean;
showIgnoreTls: boolean;
showStrategy: boolean;
showDnsServer: boolean;
showDnsRecordType: boolean;
}
const getGeneralSettingsConfig = (
type: MonitorType,
t: (key: string) => string
): GeneralSettingsConfig => {
const configs: Record<string, GeneralSettingsConfig> = {
http: {
urlLabel: t("pages.createMonitor.form.general.option.url.label"),
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,
showGrpcServiceName: false,
showIgnoreTls: false,
showStrategy: false,
showDnsServer: false,
showDnsRecordType: false,
},
ping: {
urlLabel: t("pages.createMonitor.form.general.option.host.label"),
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,
showGrpcServiceName: false,
showIgnoreTls: false,
showStrategy: false,
showDnsServer: false,
showDnsRecordType: false,
},
docker: {
urlLabel: t("pages.createMonitor.form.general.option.container.label"),
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,
showGrpcServiceName: false,
showIgnoreTls: false,
showStrategy: false,
showDnsServer: false,
showDnsRecordType: false,
},
port: {
urlLabel: t("pages.createMonitor.form.general.option.url.label"),
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,
showGrpcServiceName: false,
showIgnoreTls: false,
showStrategy: false,
showDnsServer: false,
showDnsRecordType: false,
},
game: {
urlLabel: t("pages.createMonitor.form.general.option.url.label"),
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,
showGrpcServiceName: false,
showIgnoreTls: false,
showStrategy: false,
showDnsServer: false,
showDnsRecordType: false,
},
grpc: {
urlLabel: t("pages.createMonitor.form.general.option.host.label"),
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,
showGrpcServiceName: true,
showIgnoreTls: true,
showStrategy: false,
showDnsServer: false,
showDnsRecordType: false,
},
pagespeed: {
urlLabel: t("pages.createMonitor.form.general.option.url.label"),
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,
showGrpcServiceName: false,
showIgnoreTls: false,
showStrategy: true,
showDnsServer: false,
showDnsRecordType: false,
},
hardware: {
urlLabel: t("pages.createMonitor.form.general.option.captureUrl.label"),
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,
showGrpcServiceName: false,
showIgnoreTls: false,
showStrategy: false,
showDnsServer: false,
showDnsRecordType: false,
},
websocket: {
urlLabel: t("pages.createMonitor.form.general.option.wsUrl.label"),
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,
showGrpcServiceName: false,
showIgnoreTls: true,
showStrategy: false,
showDnsServer: false,
showDnsRecordType: false,
},
dns: {
urlLabel: t("pages.createMonitor.form.general.option.url.label"),
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,
showGrpcServiceName: false,
showIgnoreTls: false,
showStrategy: false,
showDnsServer: true,
showDnsRecordType: true,
},
};
return configs[type] || configs.http;
};
const CreateMonitorPage = () => {
const theme = useTheme();
const { t } = useTranslation();
const { monitorId } = useParams();
const location = useLocation();
const navigate = useNavigate();
const isEditMode = Boolean(monitorId);
// Extract page type from URL path (e.g., /pagespeed/create -> pagespeed)
const pageType = useMemo(() => {
const pathSegments = location.pathname.split("/").filter(Boolean);
const firstSegment = pathSegments[0];
if (firstSegment === "pagespeed") return "pagespeed";
if (firstSegment === "infrastructure") return "hardware";
return "uptime";
}, [location.pathname]);
const showTypeSelector = pageType === "uptime" && !isEditMode;
const defaultType: MonitorType =
pageType === "pagespeed"
? "pagespeed"
: pageType === "hardware"
? "hardware"
: "http";
const { data: existingMonitor, refetch: refetchMonitor } = useGet<Monitor>(
isEditMode ? `/monitors/${monitorId}` : null
);
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,
defaultType,
});
const form = useForm<MonitorFormData>({
resolver: zodResolver(schema),
defaultValues: defaults,
});
const { watch, handleSubmit, clearErrors, trigger, reset, setValue } = form;
useEffect(() => {
reset(defaults);
}, [defaults, reset]);
// Multi-step wizard on every create flow; edit mode keeps the flat form
// where showStep() is always true.
const [currentStep, setCurrentStep] = useState(0);
const showStep = (step: number) => isEditMode || currentStep === step;
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;
// Steps without an advanced section drop it, so the last step is the form's.
const totalSteps = monitorStepCount(watchedType);
const handleNext = async () => {
const isValid = await trigger(stepFieldsFor(watchedType, currentStep));
if (isValid) {
setCurrentStep((step) => step + 1);
}
};
const isWizardStep = !isEditMode && currentStep < totalSteps - 1;
const handleBack = () => setCurrentStep((step) => step - 1);
// Reset the form when the monitor type changes so stale type-specific fields
// can't produce an invalid monitor. Type can only be changed on step 0.
useEffect(() => {
if (showTypeSelector) {
reset(getMonitorDefaults(watchedType));
} else {
clearErrors();
}
}, [watchedType, showTypeSelector, reset, clearErrors]);
const generalSettingsConfig = useMemo(
() => getGeneralSettingsConfig(watchedType, t),
[watchedType, t]
);
const { post, loading: isCreating } = usePost<MonitorFormData, Monitor>();
const { patch, loading: isUpdating } = usePatch<MonitorFormData, Monitor>();
const isSubmitting = isCreating || isUpdating;
// Delete functionality
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
const { deleteFn, loading: isDeleting } = useDelete();
const handleDeleteClick = () => {
setIsDeleteDialogOpen(true);
};
const handleDeleteConfirm = async () => {
if (!monitorId) return;
await deleteFn(`/monitors/${monitorId}`);
setIsDeleteDialogOpen(false);
// Navigate based on page type
if (pageType === "pagespeed") {
navigate("/pagespeed");
} else if (pageType === "hardware") {
navigate("/infrastructure");
} else {
navigate("/uptime");
}
};
const handleDeleteCancel = () => {
setIsDeleteDialogOpen(false);
};
const onSubmit = async (data: MonitorFormData) => {
let result;
if (isEditMode && monitorId) {
result = await patch(`/monitors/${monitorId}`, data);
} else {
result = await post("/monitors", data);
}
if (result?.success) {
if (pageType === "pagespeed") {
navigate("/pagespeed");
} else if (pageType === "hardware") {
navigate("/infrastructure");
} else {
navigate("/uptime");
}
}
};
const onError = (errors: unknown) => {
logger.debug("Monitor creation validation errors", errors);
};
const handleFormSubmit = isWizardStep
? (event: React.FormEvent) => {
event.preventDefault();
void handleNext();
}
: handleSubmit(onSubmit, onError);
const notificationOptions = useMemo(() => {
return (notifications ?? []).map((n) => ({
...n,
name: n.notificationName,
}));
}, [notifications]);
const locationOptions = useMemo(
() =>
GeoContinents.map((continent) => ({
id: continent,
name: t(
`pages.createMonitor.form.geoChecks.option.locations.options.${continent}`
),
})),
[t]
);
const typeOptions = useMemo(
() =>
SelectableMonitorTypes.map((type) => ({
value: type,
label: t(`pages.common.monitors.monitorTypes.${monitorTypeLabelKey[type]}`),
description: t(
`pages.createMonitor.form.type.${monitorTypeLabelKey[type]}Description`
),
})),
[t]
);
const strategyOptions = useMemo(
() =>
PageSpeedStrategies.map((strategy) => ({
value: strategy,
label: t(`pages.createMonitor.form.general.option.strategy.${strategy}`),
})),
[t]
);
const gameOptions = useMemo(
() => [
{
value: "",
label: t("pages.createMonitor.form.general.option.game.placeholder"),
},
...Object.entries(games ?? {}).map(([key, game]) => ({
value: key,
label: game.name,
})),
],
[games, t]
);
const dnsRecordTypeOptions = useMemo(
() =>
DnsRecordTypes.map((recordType) => ({
value: recordType,
label: t(
`pages.createMonitor.form.general.option.dnsRecordType.value.${recordType}`
),
})),
[t]
);
const intervalOptions = useMemo(
() =>
MonitorIntervalOptions.map((option) => ({
value: option.value,
label: t(
`pages.createMonitor.form.frequency.option.frequency.value.${option.labelKey}`
),
})),
[t]
);
const matchMethodOptions = useMemo(
() =>
MonitorMatchMethods.map((method) => ({
value: method,
label: t(`pages.createMonitor.form.advanced.option.matchMethod.${method}`),
})),
[t]
);
const geoCheckIntervalOptions = useMemo(
() =>
GeoCheckIntervalOptions.map((option) => ({
value: option.value,
label: t(
`pages.createMonitor.form.geoChecks.option.interval.value.${option.labelKey}`
),
})),
[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}>
<BasePage
component="form"
onSubmit={handleFormSubmit}
>
<HeaderDeleteControls
monitor={existingMonitor}
isAdmin={true}
refetch={refetchMonitor}
onDelete={handleDeleteClick}
/>
{!isEditMode && (
<StepProgress
steps={totalSteps}
current={currentStep}
/>
)}
{/* Monitor Type Selection - only shown for uptime monitors */}
{showTypeSelector && showStep(0) && (
<ConfigBox
title={t("pages.createMonitor.form.type.title")}
subtitle={t("pages.createMonitor.form.type.description")}
rightContent={
<FormRadioGroup
name={"type"}
options={typeOptions}
/>
}
/>
)}
{showStep(0) && (
<ConfigBox
title={t("pages.createMonitor.form.general.title")}
subtitle={
<Trans
i18nKey={`pages.createMonitor.form.general.description.${watchedType}`}
components={{
gamedigLink: (
<Link
href="https://github.com/gamedig/node-gamedig/blob/master/GAMES_LIST.md"
target="_blank"
rel="noopener noreferrer"
/>
),
}}
/>
}
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
name="url"
fieldLabel={generalSettingsConfig.urlLabel}
placeholder={generalSettingsConfig.urlPlaceholder}
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 && (
<FormSelectField
name="strategy"
fieldLabel={t(
"pages.createMonitor.form.general.option.strategy.label"
)}
options={strategyOptions}
fallbackValue={DefaultPageSpeedStrategy}
/>
)}
{/* Port field - only for port and game types */}
{generalSettingsConfig.showPort && (
<FormNumberField
name={"port"}
fieldLabel={t("pages.createMonitor.form.general.option.port.label")}
placeholder={t(
"pages.createMonitor.form.general.option.port.placeholder"
)}
/>
)}
{/* Game select - only for game type */}
{generalSettingsConfig.showGameSelect && (
<FormSelectField
name="gameId"
fieldLabel={t("pages.createMonitor.form.general.option.game.label")}
options={gameOptions}
/>
)}
{/* gRPC Service Name field - only for grpc type */}
{generalSettingsConfig.showGrpcServiceName && (
<FormTextField
name="grpcServiceName"
fieldLabel={t(
"pages.createMonitor.form.general.option.grpcServiceName.label"
)}
placeholder={t(
"pages.createMonitor.form.general.option.grpcServiceName.placeholder"
)}
/>
)}
{/* Secret field - only for hardware type */}
{generalSettingsConfig.showSecret && (
<FormTextField
name="secret"
fieldLabel={t("pages.createMonitor.form.general.option.secret.label")}
placeholder={t(
"pages.createMonitor.form.general.option.secret.placeholder"
)}
/>
)}
{generalSettingsConfig.showDnsServer && (
<FormTextField
name="dnsServer"
fieldLabel={t(
"pages.createMonitor.form.general.option.dnsServer.label"
)}
placeholder={t(
"pages.createMonitor.form.general.option.dnsServer.placeholder"
)}
/>
)}
{generalSettingsConfig.showDnsRecordType && (
<FormSelectField
name="dnsRecordType"
fieldLabel={t(
"pages.createMonitor.form.general.option.dnsRecordType.label"
)}
options={dnsRecordTypeOptions}
/>
)}
</Stack>
}
/>
)}
{showStep(1) && (
<ConfigBox
title={t("pages.createMonitor.form.frequency.title")}
subtitle={t("pages.createMonitor.form.frequency.description")}
rightContent={
<FormSelectField
name="interval"
fieldLabel={t(
"pages.createMonitor.form.frequency.option.frequency.label"
)}
options={intervalOptions}
/>
}
/>
)}
{/* Alert Thresholds - only for hardware type */}
{watchedType === "hardware" && showStep(1) && (
<ConfigBox
title={t("pages.createMonitor.form.thresholds.title")}
subtitle={t("pages.createMonitor.form.thresholds.description")}
rightContent={
<Stack spacing={theme.spacing(LAYOUT.MD)}>
<FormSliderField
name="cpuAlertThreshold"
fieldLabel={t(
"pages.createMonitor.form.thresholds.option.cpuThreshold.label"
)}
min={0}
max={100}
step={1}
valueLabelDisplay="auto"
valueLabelFormat={(value) => `${value}%`}
/>
<FormSliderField
name="memoryAlertThreshold"
fieldLabel={t(
"pages.createMonitor.form.thresholds.option.memoryThreshold.label"
)}
min={0}
max={100}
step={1}
valueLabelDisplay="auto"
valueLabelFormat={(value) => `${value}%`}
/>
<FormSliderField
name="diskAlertThreshold"
fieldLabel={t(
"pages.createMonitor.form.thresholds.option.diskThreshold.label"
)}
min={0}
max={100}
step={1}
valueLabelDisplay="auto"
valueLabelFormat={(value) => `${value}%`}
/>
<FormSliderField
name="tempAlertThreshold"
fieldLabel={t(
"pages.createMonitor.form.thresholds.option.tempThreshold.label"
)}
min={0}
max={100}
step={1}
valueLabelDisplay="auto"
valueLabelFormat={(value) => `${value}°C`}
/>
</Stack>
}
/>
)}
{showStep(1) && (
<ConfigBox
title={t("pages.createMonitor.form.incidents.title")}
subtitle={t("pages.createMonitor.form.incidents.description")}
rightContent={
<Stack spacing={theme.spacing(LAYOUT.MD)}>
<FormSliderField
name="statusWindowSize"
fieldLabel={t("pages.createMonitor.form.incidents.option.checks.label")}
min={1}
max={25}
valueLabelDisplay="auto"
/>
<FormSliderField
name="statusWindowThreshold"
fieldLabel={t(
"pages.createMonitor.form.incidents.option.percentage.label"
)}
min={1}
max={100}
valueLabelDisplay="auto"
/>
</Stack>
}
/>
)}
{showStep(1) && (
<ConfigBox
title={t("pages.createMonitor.form.notifications.title")}
subtitle={t("pages.createMonitor.form.notifications.description")}
rightContent={
<FormMultiSelectField
name="notifications"
options={notificationOptions}
/>
}
/>
)}
{showStep(1) && (
<ConfigBox
title={t("pages.createMonitor.form.tags.title")}
subtitle={t("pages.createMonitor.form.tags.description")}
rightContent={
<FormMultiSelectField
name="tags"
options={tags ?? []}
renderRow={(tag) => (
<Box flexGrow={1}>
<ColoredLabel
text={tag.name}
color={tag.color}
/>
</Box>
)}
renderOptionContent={(option) => (
<ColoredLabel
text={option.name}
color={option.color}
/>
)}
/>
}
/>
)}
{showStep(2) &&
(watchedType === "http" ||
watchedType === "grpc" ||
watchedType === "websocket") && (
<ConfigBox
title={t("pages.createMonitor.form.ignoreTls.title")}
subtitle={t("pages.createMonitor.form.ignoreTls.description")}
rightContent={
<FormSwitchField
name="ignoreTlsErrors"
label={t("pages.createMonitor.form.ignoreTls.option.tls.label")}
/>
}
/>
)}
{showStep(2) && watchedType === "http" && (
<ConfigBox
title={t("pages.createMonitor.form.advanced.title")}
subtitle={t("pages.createMonitor.form.advanced.description")}
rightContent={
<Stack spacing={theme.spacing(LAYOUT.MD)}>
<FormSelectField
name="method"
fieldLabel={t("pages.createMonitor.form.advanced.option.method.label")}
fallbackValue={DefaultHttpMethod}
onValueChange={(value) => {
// HEAD has no response body, reset advanced matching fields if selected
if (value === "HEAD") {
setValue("useAdvancedMatching", false);
setValue("matchMethod", DefaultMonitorMatchMethod);
setValue("expectedValue", "");
setValue("jsonPath", "");
}
}}
options={httpMethodOptions}
/>
<Typography
component="span"
color={theme.palette.text.secondary}
sx={{ opacity: 0.8 }}
>
{t(
`pages.createMonitor.form.advanced.option.method.description.${watchedMethod}`
)}
</Typography>
<FormMultiSelectField
name="customUpCodes"
options={ALL_HTTP_STATUS_CODES}
fieldLabel={t(
"pages.createMonitor.form.advanced.option.customUpCodes.label"
)}
description={t(
"pages.createMonitor.form.advanced.option.customUpCodes.description"
)}
/>
{watchedMethod !== "HEAD" && (
<FormSwitchField
name="useAdvancedMatching"
label={t(
"pages.createMonitor.form.advanced.option.advancedMatching.label"
)}
/>
)}
{watchedUseAdvancedMatching && watchedMethod !== "HEAD" && (
<Stack spacing={theme.spacing(LAYOUT.MD)}>
<FormSelectField
name="matchMethod"
fieldLabel={t(
"pages.createMonitor.form.advanced.option.matchMethod.label"
)}
fallbackValue={DefaultMonitorMatchMethod}
options={matchMethodOptions}
/>
<FormTextField
name="expectedValue"
fieldLabel={t(
"pages.createMonitor.form.advanced.option.expectedValue.label"
)}
/>
<FormTextField
name="jsonPath"
fieldLabel={t(
"pages.createMonitor.form.advanced.option.jsonPath.label"
)}
/>
<Typography
component="span"
color={theme.palette.text.secondary}
sx={{ opacity: 0.8 }}
>
<Trans
i18nKey="pages.createMonitor.form.advanced.option.jsonPath.description"
components={{
jmesLink: (
<Link
href="https://jmespath.org/"
target="_blank"
rel="noopener noreferrer"
/>
),
}}
/>
</Typography>
</Stack>
)}
</Stack>
}
/>
)}
{showStep(2) && supportsGeoCheck(watchedType) && (
<ConfigBox
title={t("pages.createMonitor.form.geoChecks.title")}
subtitle={t("pages.createMonitor.form.geoChecks.description")}
rightContent={
<Stack spacing={theme.spacing(LAYOUT.MD)}>
<FormSwitchField
name="geoCheckEnabled"
label={t("pages.createMonitor.form.geoChecks.option.enabled.label")}
/>
{watchGeoCheckEnabled && (
<Stack spacing={theme.spacing(LAYOUT.MD)}>
<FormMultiSelectField
name="geoCheckLocations"
options={locationOptions}
fieldLabel={t(
"pages.createMonitor.form.geoChecks.option.locations.label"
)}
/>
<FormSelectField
name="geoCheckInterval"
fieldLabel={t(
"pages.createMonitor.form.geoChecks.option.interval.label"
)}
options={geoCheckIntervalOptions}
/>
</Stack>
)}
</Stack>
}
/>
)}
<Stack
direction="row"
justifyContent={!isEditMode ? "space-between" : "flex-end"}
>
{!isEditMode && (
<Button
type="button"
variant="outlined"
color="secondary"
disabled={currentStep === 0}
onClick={handleBack}
>
{t("common.buttons.back")}
</Button>
)}
{!isEditMode && currentStep < totalSteps - 1 ? (
<Button
key="wizard-next"
type="submit"
variant="contained"
color="primary"
>
{t("common.buttons.next")}
</Button>
) : (
<Button
key="wizard-save"
loading={isSubmitting}
type="submit"
variant="contained"
color="primary"
>
{t("common.buttons.save")}
</Button>
)}
</Stack>
<Dialog
open={isDeleteDialogOpen}
title={t("common.dialogs.delete.title")}
content={t("common.dialogs.delete.description")}
onConfirm={handleDeleteConfirm}
onCancel={handleDeleteCancel}
loading={isDeleting}
/>
</BasePage>
</FormProvider>
);
};
export default CreateMonitorPage;