feat: Updated edit monitor page
Chart Release / chart (push) Successful in 14s
Server Deploy / deploy (push) Successful in 43s

This commit is contained in:
2026-08-24 09:25:43 +00:00
parent aa1c8e4aa1
commit 83cdf92575
+171 -167
View File
@@ -138,193 +138,197 @@ export function MonitorForm({
const downAfter = formatDuration(new Date(Date.now() - intervalSec * Math.max(retries, 1) * 1000).toISOString());
return (
<form onSubmit={handleSubmit} className="flex max-w-3xl flex-col gap-5">
<Section title="Check" hint={typeCopy[type].target}>
<Field label="Name" help="Shown in the fleet list and in every alert this check sends.">
<input className={inputClass} value={name} onChange={(e) => setName(e.target.value)} placeholder="Billing API" required />
</Field>
<form onSubmit={handleSubmit} className="flex flex-col gap-5">
<div className="grid grid-cols-1 items-start gap-5 lg:grid-cols-[minmax(0,1fr)_minmax(320px,400px)]">
<Section title="Check" hint={typeCopy[type].target}>
<Field label="Name" help="Shown in the fleet list and in every alert this check sends.">
<input className={inputClass} value={name} onChange={(e) => setName(e.target.value)} placeholder="Billing API" required />
</Field>
<div>
<span className="mb-1.5 block text-sm font-medium text-text-secondary">Kind</span>
<div className="grid grid-cols-2 gap-2 sm:grid-cols-4">
{(Object.keys(typeCopy) as MonitorType[]).map((t) => (
<button
key={t}
type="button"
onClick={() => setType(t)}
aria-pressed={type === t}
className={`rounded-lg border px-3 py-2.5 text-left transition-colors ${
type === t
? "border-accent bg-accent/10"
: "border-border bg-surface-2 hover:border-accent/40"
}`}
>
<span
className={`block font-mono text-[11px] uppercase tracking-[0.1em] ${type === t ? "text-accent" : "text-text-secondary"}`}
<div>
<span className="mb-1.5 block text-sm font-medium text-text-secondary">Kind</span>
<div className="grid grid-cols-2 gap-2 sm:grid-cols-4">
{(Object.keys(typeCopy) as MonitorType[]).map((t) => (
<button
key={t}
type="button"
onClick={() => setType(t)}
aria-pressed={type === t}
className={`rounded-lg border px-3 py-2.5 text-left transition-colors ${
type === t
? "border-accent bg-accent/10"
: "border-border bg-surface-2 hover:border-accent/40"
}`}
>
{typeCopy[t].title}
</span>
<span className="mt-1 block text-[11px] leading-snug text-text-tertiary">{typeCopy[t].blurb}</span>
</button>
))}
<span
className={`block font-mono text-[11px] uppercase tracking-[0.1em] ${type === t ? "text-accent" : "text-text-secondary"}`}
>
{typeCopy[t].title}
</span>
<span className="mt-1 block text-[11px] leading-snug text-text-tertiary">{typeCopy[t].blurb}</span>
</button>
))}
</div>
</div>
</div>
{type === "http" && (
<>
<Field label="URL">
{type === "http" && (
<>
<Field label="URL">
<input
className={`${inputClass} font-mono`}
value={url}
onChange={(e) => setUrl(e.target.value)}
placeholder="https://example.com/healthz"
required
/>
</Field>
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
<Field label="Method">
<select className={inputClass} value={method} onChange={(e) => setMethod(e.target.value)}>
<option>GET</option>
<option>HEAD</option>
</select>
</Field>
<Field label="Expected status">
<input
type="number"
className={`${inputClass} font-mono tabular-nums`}
value={expectedStatus}
onChange={(e) => setExpectedStatus(Number(e.target.value))}
/>
</Field>
</div>
<Field label="Body must contain" help="Optional. The check fails if the response body is missing this text.">
<input className={inputClass} value={keyword} onChange={(e) => setKeyword(e.target.value)} placeholder="ok" />
</Field>
<Check
checked={insecure}
onChange={setInsecure}
title="Accept any certificate"
detail="Use for self-signed or expired certificates. The check stops reporting TLS problems."
/>
</>
)}
{(type === "tcp" || type === "tls" || type === "icmp") && (
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
<Field label="Host">
<input
className={`${inputClass} font-mono`}
value={host}
onChange={(e) => setHost(e.target.value)}
placeholder="example.com"
required
/>
</Field>
{type !== "icmp" && (
<Field label="Port">
<input
type="number"
className={`${inputClass} font-mono tabular-nums`}
value={port}
onChange={(e) => setPort(Number(e.target.value))}
/>
</Field>
)}
</div>
)}
{type === "tls" && (
<Field label="Warn this many days before expiry">
<input
className={`${inputClass} font-mono`}
value={url}
onChange={(e) => setUrl(e.target.value)}
placeholder="https://example.com/healthz"
required
type="number"
className={`${inputClass} font-mono tabular-nums`}
value={tlsWarnDays}
onChange={(e) => setTlsWarnDays(Number(e.target.value))}
min={1}
/>
</Field>
)}
</Section>
<div className="flex flex-col gap-5">
<Section title="Schedule">
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
<Field label="Method">
<select className={inputClass} value={method} onChange={(e) => setMethod(e.target.value)}>
<option>GET</option>
<option>HEAD</option>
</select>
</Field>
<Field label="Expected status">
<Field label="Run every" help="Seconds between checks. Minimum 10.">
<input
type="number"
className={`${inputClass} font-mono tabular-nums`}
value={expectedStatus}
onChange={(e) => setExpectedStatus(Number(e.target.value))}
value={intervalSec}
onChange={(e) => setIntervalSec(Number(e.target.value))}
min={10}
/>
</Field>
<Field label="Fails after" help="Consecutive failures before an incident opens.">
<input
type="number"
className={`${inputClass} font-mono tabular-nums`}
value={retries}
onChange={(e) => setRetries(Number(e.target.value))}
min={1}
/>
</Field>
</div>
<Field label="Body must contain" help="Optional. The check fails if the response body is missing this text.">
<input className={inputClass} value={keyword} onChange={(e) => setKeyword(e.target.value)} placeholder="ok" />
</Field>
<Check
checked={insecure}
onChange={setInsecure}
title="Accept any certificate"
detail="Use for self-signed or expired certificates. The check stops reporting TLS problems."
/>
</>
)}
{(type === "tcp" || type === "tls" || type === "icmp") && (
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
<Field label="Host">
<input
className={`${inputClass} font-mono`}
value={host}
onChange={(e) => setHost(e.target.value)}
placeholder="example.com"
required
/>
<Field
label="Runs from"
help="Pick an agent for anything only reachable from inside that network. Everything else runs centrally."
>
<select className={inputClass} value={runner} onChange={(e) => setRunner(e.target.value)}>
<option value="server">Control plane</option>
{servers?.map((s) => (
<option key={s.server_id} value={s.server_id}>
Agent · {s.hostname}
</option>
))}
</select>
</Field>
{type !== "icmp" && (
<Field label="Port">
<input
type="number"
className={`${inputClass} font-mono tabular-nums`}
value={port}
onChange={(e) => setPort(Number(e.target.value))}
/>
</Field>
<p className="rounded-lg bg-well px-4 py-3 font-mono text-[11.5px] leading-relaxed text-text-secondary">
Checked every {intervalSec} s from {runnerName}. Reported down after {retries}{" "}
{retries === 1 ? "failure" : "consecutive failures"} roughly {downAfter}.
</p>
</Section>
<Section title="Alerts" hint={channelIds.length > 0 ? `${channelIds.length} selected` : undefined}>
{!channels || channels.length === 0 ? (
<p className="text-sm text-text-secondary">
No channels exist yet, so nobody will be told when this check fails.{" "}
<Link href="/settings/notifications" className="text-accent hover:underline">
Add a channel
</Link>
.
</p>
) : (
<div className="flex flex-col gap-2">
{channels.map((ch) => (
<Check
key={ch.channel_id}
checked={channelIds.includes(ch.channel_id)}
onChange={(v) =>
setChannelIds((prev) => (v ? [...prev, ch.channel_id] : prev.filter((id) => id !== ch.channel_id)))
}
title={ch.name}
detail={
<span className="font-mono uppercase tracking-[0.1em]">
{ch.type}
{!ch.enabled && " · disabled, sends nothing"}
</span>
}
/>
))}
</div>
)}
</div>
)}
{type === "tls" && (
<Field label="Warn this many days before expiry">
<input
type="number"
className={`${inputClass} font-mono tabular-nums`}
value={tlsWarnDays}
onChange={(e) => setTlsWarnDays(Number(e.target.value))}
min={1}
<Check
checked={enabled}
onChange={setEnabled}
title="Start checking straight away"
detail="Turn this off to save the monitor without running it. You can resume it any time."
/>
</Field>
)}
</Section>
<Section title="Schedule">
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
<Field label="Run every" help="Seconds between checks. Minimum 10.">
<input
type="number"
className={`${inputClass} font-mono tabular-nums`}
value={intervalSec}
onChange={(e) => setIntervalSec(Number(e.target.value))}
min={10}
/>
</Field>
<Field label="Fails after" help="Consecutive failures before an incident opens.">
<input
type="number"
className={`${inputClass} font-mono tabular-nums`}
value={retries}
onChange={(e) => setRetries(Number(e.target.value))}
min={1}
/>
</Field>
</Section>
</div>
<Field
label="Runs from"
help="Pick an agent for anything only reachable from inside that network. Everything else runs centrally."
>
<select className={inputClass} value={runner} onChange={(e) => setRunner(e.target.value)}>
<option value="server">Control plane</option>
{servers?.map((s) => (
<option key={s.server_id} value={s.server_id}>
Agent · {s.hostname}
</option>
))}
</select>
</Field>
<p className="rounded-lg bg-well px-4 py-3 font-mono text-[11.5px] leading-relaxed text-text-secondary">
Checked every {intervalSec} s from {runnerName}. Reported down after {retries}{" "}
{retries === 1 ? "failure" : "consecutive failures"} roughly {downAfter}.
</p>
</Section>
<Section title="Alerts" hint={channelIds.length > 0 ? `${channelIds.length} selected` : undefined}>
{!channels || channels.length === 0 ? (
<p className="text-sm text-text-secondary">
No channels exist yet, so nobody will be told when this check fails.{" "}
<Link href="/settings/notifications" className="text-accent hover:underline">
Add a channel
</Link>
.
</p>
) : (
<div className="flex flex-col gap-2">
{channels.map((ch) => (
<Check
key={ch.channel_id}
checked={channelIds.includes(ch.channel_id)}
onChange={(v) =>
setChannelIds((prev) => (v ? [...prev, ch.channel_id] : prev.filter((id) => id !== ch.channel_id)))
}
title={ch.name}
detail={
<span className="font-mono uppercase tracking-[0.1em]">
{ch.type}
{!ch.enabled && " · disabled, sends nothing"}
</span>
}
/>
))}
</div>
)}
<Check
checked={enabled}
onChange={setEnabled}
title="Start checking straight away"
detail="Turn this off to save the monitor without running it. You can resume it any time."
/>
</Section>
</div>
{error && (
<p className="rounded-lg border border-danger/30 bg-danger/10 px-4 py-3 text-sm text-danger">{error.message}</p>