Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
634a9dc362 | ||
|
|
1bf6cfa0d0 | ||
|
|
3da6de9311 | ||
|
|
7a8bacade9 | ||
|
|
5d84c7bf6c |
@@ -18,6 +18,7 @@ import {
|
||||
SYNC_PENDING_PUSH,
|
||||
SYNC_SYNCED,
|
||||
dnsRecordNamesMatch,
|
||||
isIpLiteral,
|
||||
normalizeDnsRecordName,
|
||||
} from "@cfdm/shared";
|
||||
import type { CloudflareClient } from "../lib/cf-client.js";
|
||||
@@ -344,6 +345,64 @@ async function buildView(db: Db, serviceId: number): Promise<ServiceView> {
|
||||
};
|
||||
}
|
||||
|
||||
const HEALTH_RANK: Record<string, number> = {
|
||||
down: 3,
|
||||
degraded: 2,
|
||||
unknown: 1,
|
||||
up: 0,
|
||||
};
|
||||
|
||||
function cnameLookupKeys(value: string, zoneName?: string | null): string[] {
|
||||
const trimmed = value.trim();
|
||||
if (!trimmed) return [];
|
||||
const noDot = trimmed.replace(/\.+$/, "");
|
||||
const lower = noDot.toLowerCase();
|
||||
const keys = new Set([trimmed, noDot, lower]);
|
||||
if (zoneName && !lower.includes(".")) {
|
||||
keys.add(`${lower}.${zoneName.trim().toLowerCase().replace(/\.+$/, "")}`);
|
||||
}
|
||||
return [...keys];
|
||||
}
|
||||
|
||||
type ServiceHealthRow = {
|
||||
ip: string;
|
||||
status: IpHealthState;
|
||||
latency_ms: number | null;
|
||||
last_checked_at: string | null;
|
||||
last_error: string | null;
|
||||
provider: ServiceView["ip_health"][number]["provider"];
|
||||
colo: string | null;
|
||||
};
|
||||
|
||||
/** Health rows keyed by CNAME hostname (legacy probes) applied to service IPs. */
|
||||
function fallbackCnameHealth(
|
||||
rows: ServiceHealthRow[],
|
||||
view: ServiceView,
|
||||
): ServiceHealthRow | undefined {
|
||||
const cnameKeys = new Set<string>();
|
||||
for (const domain of view.domains ?? []) {
|
||||
const cname = domain.target_cname?.trim();
|
||||
if (!cname) continue;
|
||||
for (const key of cnameLookupKeys(cname, domain.zone_name)) {
|
||||
cnameKeys.add(key);
|
||||
}
|
||||
}
|
||||
const hostnameRows = rows.filter((row) => !isIpLiteral(row.ip));
|
||||
if (hostnameRows.length === 0) return undefined;
|
||||
const matched =
|
||||
cnameKeys.size === 0
|
||||
? hostnameRows
|
||||
: hostnameRows.filter((row) =>
|
||||
cnameLookupKeys(row.ip).some((key) => cnameKeys.has(key)),
|
||||
);
|
||||
const candidates = matched.length > 0 ? matched : hostnameRows;
|
||||
return candidates.reduce((worst, row) =>
|
||||
(HEALTH_RANK[row.status] ?? 0) > (HEALTH_RANK[worst.status] ?? 0)
|
||||
? row
|
||||
: worst,
|
||||
);
|
||||
}
|
||||
|
||||
function attachServiceHealth(
|
||||
db: Db,
|
||||
views: ServiceView[],
|
||||
@@ -353,11 +412,16 @@ function attachServiceHealth(
|
||||
const ipHealthByService = repos.listIpHealthByServiceIds(db, ids);
|
||||
return views.map((view) => {
|
||||
const health = healthByService.get(view.id);
|
||||
const byIp = new Map(
|
||||
(ipHealthByService.get(view.id) ?? []).map((row) => [row.ip, row]),
|
||||
const rows = ipHealthByService.get(view.id) ?? [];
|
||||
const byIp = new Map(rows.map((row) => [row.ip, row]));
|
||||
const cnameFallback = fallbackCnameHealth(rows, view);
|
||||
const aRecordIps = new Set(
|
||||
(view.domains ?? []).flatMap((domain) =>
|
||||
domain.target_cname?.trim() ? [] : (domain.target_ips ?? []),
|
||||
),
|
||||
);
|
||||
const ip_health = (view.ips ?? []).map((ip) => {
|
||||
const row = byIp.get(ip);
|
||||
const row = byIp.get(ip) ?? (aRecordIps.has(ip) ? undefined : cnameFallback);
|
||||
return {
|
||||
ip,
|
||||
status: row?.status ?? ("unknown" as const),
|
||||
|
||||
@@ -235,4 +235,95 @@ describe("health-check state derivation via runAllChecks", () => {
|
||||
expect(targets).toHaveLength(1);
|
||||
expect(targets[0]?.verify_tls).toBe(true);
|
||||
});
|
||||
|
||||
it("unwraps CNAME target to origin A record IPs", async () => {
|
||||
const { createMemoryDb, repos, runMigrations } = await import("@cfdm/db");
|
||||
const { db, sqlite } = createMemoryDb();
|
||||
runMigrations(sqlite);
|
||||
|
||||
const domain = repos.createDomain(db, null, "rkns.top", "zone-id");
|
||||
repos.insertDnsRecord(
|
||||
db,
|
||||
domain.id,
|
||||
"A",
|
||||
"ihome",
|
||||
"2.59.161.102",
|
||||
1,
|
||||
false,
|
||||
null,
|
||||
"synced",
|
||||
"cf",
|
||||
null,
|
||||
);
|
||||
const service = repos.createService(db, "RW Sub", "rw-sub");
|
||||
const binding = repos.insertBinding(db, domain.id, service.id, "s", null);
|
||||
repos.setBindingCnameTarget(db, binding.id, "ihome.rkns.top");
|
||||
repos.updateBindingLbConfig(db, binding.id, {
|
||||
health_check_enabled: true,
|
||||
health_check_type: "tcp",
|
||||
health_check_port: 443,
|
||||
});
|
||||
|
||||
const targets = repos.listHealthCheckTargets(db);
|
||||
expect(targets).toHaveLength(1);
|
||||
expect(targets[0]?.ip).toBe("2.59.161.102");
|
||||
expect(targets[0]?.hostname).toBe("s.rkns.top");
|
||||
});
|
||||
|
||||
it("unwraps CNAME target to service IP pool when origin DNS is empty", async () => {
|
||||
const { createMemoryDb, repos, runMigrations } = await import("@cfdm/db");
|
||||
const { db, sqlite } = createMemoryDb();
|
||||
runMigrations(sqlite);
|
||||
|
||||
const domain = repos.createDomain(db, null, "rkns.top", "zone-id");
|
||||
const service = repos.createService(db, "RW Sub", "rw-sub");
|
||||
repos.replaceServiceIps(db, service.id, ["2.59.161.102"]);
|
||||
const binding = repos.insertBinding(db, domain.id, service.id, "s", null);
|
||||
repos.setBindingCnameTarget(db, binding.id, "ihome.rkns.top");
|
||||
repos.updateBindingLbConfig(db, binding.id, {
|
||||
health_check_enabled: true,
|
||||
health_check_type: "tcp",
|
||||
health_check_port: 443,
|
||||
});
|
||||
|
||||
const targets = repos.listHealthCheckTargets(db);
|
||||
expect(targets).toHaveLength(1);
|
||||
expect(targets[0]?.ip).toBe("2.59.161.102");
|
||||
expect(targets[0]?.hostname).toBe("s.rkns.top");
|
||||
});
|
||||
});
|
||||
|
||||
describe("CNAME health mapped onto service IPs", () => {
|
||||
it("getView copies CNAME-keyed health onto the service IP row", async () => {
|
||||
const { createMemoryDb, repos, runMigrations } = await import("@cfdm/db");
|
||||
const { getView } = await import("../src/services/service-config-service.js");
|
||||
const { db, sqlite } = createMemoryDb();
|
||||
runMigrations(sqlite);
|
||||
|
||||
const domain = repos.createDomain(db, null, "rkns.top", "zone-id");
|
||||
const service = repos.createService(db, "RW Sub", "rw-sub");
|
||||
repos.replaceServiceIps(db, service.id, ["2.59.161.102"]);
|
||||
const binding = repos.insertBinding(db, domain.id, service.id, "s", null);
|
||||
repos.setBindingCnameTarget(db, binding.id, "ihome.rkns.top");
|
||||
repos.upsertIpHealthStatus(
|
||||
db,
|
||||
"binding",
|
||||
binding.id,
|
||||
"ihome.rkns.top",
|
||||
"up",
|
||||
12,
|
||||
0,
|
||||
null,
|
||||
);
|
||||
|
||||
const view = await getView(db, service.id);
|
||||
expect(view.health_status).toBe("up");
|
||||
expect(view.ip_health).toEqual([
|
||||
expect.objectContaining({
|
||||
ip: "2.59.161.102",
|
||||
status: "up",
|
||||
latency_ms: 12,
|
||||
}),
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { serviceGroupsResponseSchema } from "@cfdm/shared";
|
||||
import { serviceGroupsResponseSchema, updateServiceConfigSchema } from "@cfdm/shared";
|
||||
import { repos } from "@cfdm/db";
|
||||
import type { CloudflareClient } from "../src/lib/cf-client.js";
|
||||
import { buildApp } from "../src/app.js";
|
||||
@@ -51,6 +51,26 @@ async function authHeaders(app: Awaited<ReturnType<typeof buildApp>>) {
|
||||
}
|
||||
|
||||
describe("create service then list groups", () => {
|
||||
it("accepts sqlite-shaped health fields on service config PATCH", () => {
|
||||
const parsed = updateServiceConfigSchema.parse({
|
||||
domains: [
|
||||
{
|
||||
fqdn: "gw.example.com",
|
||||
target_ips: ["1.2.3.4"],
|
||||
health_check_enabled: 1,
|
||||
health_check_verify_tls: 0,
|
||||
health_check_providers: '["local","cloudflare"]',
|
||||
health_check_aggregate: "majority",
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(parsed.domains?.[0]?.health_check_enabled).toBe(true);
|
||||
expect(parsed.domains?.[0]?.health_check_verify_tls).toBe(false);
|
||||
expect(parsed.domains?.[0]?.health_check_providers).toEqual([
|
||||
"local",
|
||||
"cloudflare",
|
||||
]);
|
||||
});
|
||||
it("create + updateConfig then listGroupViews parses with shared Zod schema", async () => {
|
||||
const app = await buildApp({
|
||||
config: { ...loadConfig(), staticDir: null },
|
||||
@@ -133,6 +153,69 @@ describe("create service then list groups", () => {
|
||||
await app.close();
|
||||
});
|
||||
|
||||
it("PATCH /services/:id persists health providers and aggregate", async () => {
|
||||
const app = await buildApp({
|
||||
config: { ...loadConfig(), staticDir: null },
|
||||
memory: true,
|
||||
});
|
||||
const headers = await authHeaders(app);
|
||||
const cf = mockCf();
|
||||
|
||||
repos.createDomain(app.db, null, "example.com", "zone-1");
|
||||
const createRes = await app.inject({
|
||||
method: "POST",
|
||||
url: "/api/v1/services",
|
||||
headers,
|
||||
payload: { name: "GW", slug: "gw" },
|
||||
});
|
||||
expect(createRes.statusCode).toBe(200);
|
||||
const created = createRes.json() as { id: number };
|
||||
|
||||
await updateConfig(app.db, cf, created.id, {
|
||||
ips: ["1.2.3.4"],
|
||||
domains: [
|
||||
{
|
||||
fqdn: "gw.example.com",
|
||||
target_ips: ["1.2.3.4"],
|
||||
health_check_enabled: true,
|
||||
health_check_type: "tcp",
|
||||
health_check_interval_sec: 30,
|
||||
health_check_timeout_ms: 3000,
|
||||
health_check_providers: ["local", "cloudflare"],
|
||||
health_check_aggregate: "majority",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const stored = repos.listBindingsByService(app.db, created.id)[0]!;
|
||||
expect(stored.health_check_enabled).toBe(true);
|
||||
expect(Array.isArray(stored.health_check_providers)).toBe(true);
|
||||
expect(stored.health_check_providers).toEqual(["local", "cloudflare"]);
|
||||
expect(stored.health_check_aggregate).toBe("majority");
|
||||
|
||||
const getRes = await app.inject({
|
||||
method: "GET",
|
||||
url: `/api/v1/services/${created.id}`,
|
||||
headers,
|
||||
});
|
||||
expect(getRes.statusCode).toBe(200);
|
||||
const view = getRes.json() as {
|
||||
domains: Array<{
|
||||
health_check_enabled: boolean;
|
||||
health_check_providers: string[];
|
||||
health_check_aggregate: string;
|
||||
}>;
|
||||
};
|
||||
expect(view.domains[0]?.health_check_enabled).toBe(true);
|
||||
expect(view.domains[0]?.health_check_providers).toEqual([
|
||||
"local",
|
||||
"cloudflare",
|
||||
]);
|
||||
expect(view.domains[0]?.health_check_aggregate).toBe("majority");
|
||||
|
||||
await app.close();
|
||||
});
|
||||
|
||||
it("POST /services returns resolved ServiceView with numeric id", async () => {
|
||||
const app = await buildApp({
|
||||
config: { ...loadConfig(), staticDir: null },
|
||||
|
||||
@@ -28,7 +28,7 @@ import {
|
||||
type HealthAggregate,
|
||||
type HealthProvider,
|
||||
} from '@/components/reui-kit/health-source-tiles'
|
||||
import { uniqueHealthProviders } from '@cfdm/shared'
|
||||
import { parseHealthProviders } from '@cfdm/shared'
|
||||
|
||||
export type LbMode = 'round_robin' | 'failover' | 'weighted'
|
||||
export type HealthCheckType = 'tcp' | 'http'
|
||||
@@ -116,10 +116,10 @@ export function HealthCheckConfigFields({
|
||||
onChange({ ...value, ...next })
|
||||
}
|
||||
|
||||
const providers =
|
||||
value.providers?.length > 0
|
||||
? uniqueHealthProviders(value.providers)
|
||||
: uniqueHealthProviders([value.provider ?? 'local'])
|
||||
const providers = parseHealthProviders(
|
||||
value.providers,
|
||||
value.provider ?? 'local',
|
||||
)
|
||||
const aggregate = value.aggregate ?? 'majority'
|
||||
const isHttp = value.type === 'http'
|
||||
const rowClass = 'gap-3 px-0 py-3'
|
||||
|
||||
@@ -14,7 +14,11 @@ import {
|
||||
} from '@cfdm/ui/components/item'
|
||||
import { HealthCheckBadge } from '@/components/health-check-badge'
|
||||
import { cn } from '@cfdm/ui/lib/utils'
|
||||
import type { HealthCheckAggregate, HealthCheckProvider } from '@cfdm/shared'
|
||||
import {
|
||||
ToggleGroup,
|
||||
ToggleGroupItem,
|
||||
} from '@cfdm/ui/components/toggle-group'
|
||||
import { parseHealthProviders, type HealthCheckAggregate, type HealthCheckProvider } from '@cfdm/shared'
|
||||
import type { HealthLogStatus } from '@/lib/health-log'
|
||||
|
||||
export type HealthProvider = HealthCheckProvider
|
||||
@@ -189,7 +193,7 @@ export function HealthSourceTiles({
|
||||
value: HealthProvider[]
|
||||
onChange: (next: HealthProvider[]) => void
|
||||
}) {
|
||||
const selected = value.length > 0 ? value : (['local'] as HealthProvider[])
|
||||
const selected = parseHealthProviders(value)
|
||||
|
||||
function toggle(id: HealthProvider) {
|
||||
if (selected.includes(id)) {
|
||||
@@ -261,6 +265,81 @@ export function HealthAggregateTiles({
|
||||
)
|
||||
}
|
||||
|
||||
function toggleProviders(
|
||||
active: HealthProvider[],
|
||||
id: HealthProvider,
|
||||
): HealthProvider[] {
|
||||
if (active.includes(id)) {
|
||||
if (active.length === 1) return active
|
||||
return active.filter((item) => item !== id)
|
||||
}
|
||||
return [...active, id]
|
||||
}
|
||||
|
||||
/**
|
||||
* Компактный мультивыбор типа пробы (toolbar в stacked Frame).
|
||||
* Preview: https://reui.io/preview/base/list-9 · https://reui.io/preview/base/chart-17
|
||||
* Docs: https://reui.io/docs/components/base/icon-tile · https://reui.io/docs/components/base/badge
|
||||
*/
|
||||
export function HealthSourceFilterBar({
|
||||
enabled,
|
||||
selected,
|
||||
statuses,
|
||||
onChange,
|
||||
}: {
|
||||
enabled: HealthProvider[]
|
||||
selected: HealthProvider[]
|
||||
statuses: Partial<Record<HealthProvider, HealthLogStatus>>
|
||||
onChange: (next: HealthProvider[]) => void
|
||||
}) {
|
||||
const visible = HEALTH_PROVIDER_ITEMS.filter((item) => enabled.includes(item.id))
|
||||
if (visible.length === 0) return null
|
||||
|
||||
const active = selected.length > 0 ? selected : enabled
|
||||
|
||||
return (
|
||||
<ToggleGroup
|
||||
multiple
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="w-full min-w-0"
|
||||
value={active}
|
||||
aria-label="Тип пробы"
|
||||
onValueChange={(next) => {
|
||||
const values = next.filter((value): value is HealthProvider =>
|
||||
visible.some((item) => item.id === value),
|
||||
)
|
||||
if (values.length === 0) return
|
||||
onChange(values)
|
||||
}}
|
||||
>
|
||||
{visible.map((item) => (
|
||||
<ToggleGroupItem
|
||||
key={item.id}
|
||||
value={item.id}
|
||||
aria-label={item.title}
|
||||
className="min-w-0 flex-1 gap-1.5"
|
||||
>
|
||||
<IconTile
|
||||
variant="elevated"
|
||||
size="xs"
|
||||
className={item.iconClassName}
|
||||
aria-hidden="true"
|
||||
>
|
||||
{item.icon}
|
||||
</IconTile>
|
||||
<span className="truncate">{item.title}</span>
|
||||
<HealthCheckBadge
|
||||
status={statuses[item.id] ?? 'unknown'}
|
||||
provider={item.id}
|
||||
size="xs"
|
||||
/>
|
||||
</ToggleGroupItem>
|
||||
))}
|
||||
</ToggleGroup>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Read-only status tiles for enabled probe sources; click filters the monitor.
|
||||
* Preview: https://reui.io/preview/base/list-9 · https://reui.io/preview/base/stats-12
|
||||
@@ -282,15 +361,6 @@ export function HealthProviderStatusTiles({
|
||||
|
||||
const active = selected.length > 0 ? selected : enabled
|
||||
|
||||
function toggle(id: HealthProvider) {
|
||||
if (active.includes(id)) {
|
||||
if (active.length === 1) return
|
||||
onChange(active.filter((item) => item !== id))
|
||||
return
|
||||
}
|
||||
onChange([...active, id])
|
||||
}
|
||||
|
||||
return (
|
||||
<ChoiceFrame>
|
||||
{visible.map((item) => (
|
||||
@@ -309,7 +379,7 @@ export function HealthProviderStatusTiles({
|
||||
size="xs"
|
||||
/>
|
||||
}
|
||||
onActivate={() => toggle(item.id)}
|
||||
onActivate={() => onChange(toggleProviders(active, item.id))}
|
||||
/>
|
||||
))}
|
||||
</ChoiceFrame>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export { UptimeChart, type UptimeProbe, type UptimePeriodKey } from './uptime-chart'
|
||||
export { UptimeChart, type UptimeProbe, type UptimePeriodKey, probeUptimePercent, lastProbeLatency } from './uptime-chart'
|
||||
export { ServiceHealthMonitor } from './service-health-monitor'
|
||||
export { applyFiltersToData, getActiveFilters, renderSingleSelectedLabel } from './filter-utils'
|
||||
export { CertStatusChart, GroupDomainsChart } from './dashboard-analytics'
|
||||
@@ -24,6 +24,7 @@ export {
|
||||
HealthSourceTiles,
|
||||
HealthAggregateTiles,
|
||||
HealthProviderStatusTiles,
|
||||
HealthSourceFilterBar,
|
||||
type HealthProvider,
|
||||
type HealthAggregate,
|
||||
} from './health-source-tiles'
|
||||
|
||||
@@ -9,40 +9,78 @@ import {
|
||||
FrameTitle,
|
||||
} from '@/components/reui/frame'
|
||||
import { UptimeChart, UPTIME_PERIODS, type UptimePeriodKey } from '@/components/reui-kit/uptime-chart'
|
||||
import { HealthSourceFilterBar } from '@/components/reui-kit/health-source-tiles'
|
||||
import {
|
||||
collapseStatusChanges,
|
||||
filterByPeriod,
|
||||
filterByProviders,
|
||||
type HealthLogProbe,
|
||||
type HealthLogStatus,
|
||||
} from '@/lib/health-log'
|
||||
import type { HealthCheckProvider } from '@cfdm/shared'
|
||||
|
||||
/**
|
||||
* Combined uptime chart + status-change timeline (solution-analytics-8 DNA).
|
||||
* Preview: https://reui.io/preview/base/solution-analytics-8 · https://reui.io/preview/base/chart-17
|
||||
* Docs: https://reui.io/docs/components/base/frame · https://reui.io/docs/components/base/timeline
|
||||
* Единый блок мониторинга: компактный мультивыбор типа пробы (list-9 / ToggleGroup)
|
||||
* + график (chart-17) + таймлайн смен статуса.
|
||||
*
|
||||
* Preview: https://reui.io/preview/base/list-9
|
||||
* Preview: https://reui.io/preview/base/chart-17
|
||||
* Preview: https://reui.io/preview/base/solution-ai-ops-1
|
||||
* Docs: https://reui.io/docs/components/base/frame
|
||||
* Docs: https://reui.io/docs/components/base/icon-tile
|
||||
* Docs: https://reui.io/docs/components/base/timeline
|
||||
* Docs: https://reui.io/docs/components/base/badge
|
||||
*/
|
||||
export function ServiceHealthMonitor({
|
||||
items,
|
||||
selectedProviders,
|
||||
enabledProviders,
|
||||
statuses,
|
||||
isLoading = false,
|
||||
}: {
|
||||
items: HealthLogProbe[]
|
||||
selectedProviders: readonly HealthCheckProvider[]
|
||||
enabledProviders: readonly HealthCheckProvider[]
|
||||
statuses: Partial<Record<HealthCheckProvider, HealthLogStatus>>
|
||||
isLoading?: boolean
|
||||
}) {
|
||||
const [period, setPeriod] = useState<UptimePeriodKey>('5D')
|
||||
const [selected, setSelected] = useState<HealthCheckProvider[] | null>(null)
|
||||
const days = UPTIME_PERIODS.find((entry) => entry.key === period)?.days ?? 5
|
||||
|
||||
const enabled = useMemo(
|
||||
() => [...enabledProviders],
|
||||
[enabledProviders],
|
||||
)
|
||||
|
||||
const activeProviders = useMemo((): HealthCheckProvider[] => {
|
||||
const picked = (selected ?? enabled).filter((provider) =>
|
||||
enabled.includes(provider),
|
||||
)
|
||||
return picked.length > 0 ? picked : enabled
|
||||
}, [enabled, selected])
|
||||
|
||||
const periodItems = useMemo(
|
||||
() => filterByPeriod(items, days),
|
||||
[items, days],
|
||||
)
|
||||
|
||||
const filtered = useMemo(
|
||||
() => filterByProviders(filterByPeriod(items, days), selectedProviders),
|
||||
[items, days, selectedProviders],
|
||||
() => filterByProviders(periodItems, activeProviders),
|
||||
[periodItems, activeProviders],
|
||||
)
|
||||
|
||||
const changes = useMemo(() => collapseStatusChanges(filtered), [filtered])
|
||||
|
||||
return (
|
||||
<Frame stacked spacing="sm" className="min-w-0 w-full">
|
||||
<FramePanel>
|
||||
<HealthSourceFilterBar
|
||||
enabled={enabled}
|
||||
selected={activeProviders}
|
||||
statuses={statuses}
|
||||
onChange={setSelected}
|
||||
/>
|
||||
</FramePanel>
|
||||
|
||||
<UptimeChart
|
||||
items={filtered}
|
||||
isLoading={isLoading}
|
||||
@@ -50,12 +88,15 @@ export function ServiceHealthMonitor({
|
||||
onPeriodChange={setPeriod}
|
||||
skipPeriodFilter
|
||||
embedded
|
||||
hideHeader
|
||||
/>
|
||||
|
||||
<FramePanel className="flex flex-col gap-3">
|
||||
<FrameHeader className="px-0 py-0">
|
||||
<FrameTitle>Смены статуса</FrameTitle>
|
||||
<FrameDescription>
|
||||
Только переходы up / degraded / down · Cloudflare = Worker, не Health Checks API
|
||||
Только переходы up / degraded / down · Cloudflare = Worker, не Health
|
||||
Checks API
|
||||
</FrameDescription>
|
||||
</FrameHeader>
|
||||
<HealthTimeline
|
||||
|
||||
@@ -112,6 +112,23 @@ function UptimeDelta({ delta }: { delta: number }) {
|
||||
)
|
||||
}
|
||||
|
||||
export function probeUptimePercent(items: UptimeProbe[]): number | null {
|
||||
return uptimePercent(toSeries(items))
|
||||
}
|
||||
|
||||
export function lastProbeLatency(items: UptimeProbe[]): number | null {
|
||||
if (items.length === 0) return null
|
||||
const latest = [...items].sort(
|
||||
(a, b) => probeTime(b.checked_at) - probeTime(a.checked_at),
|
||||
)[0]
|
||||
return latest?.latency_ms ?? null
|
||||
}
|
||||
|
||||
function formatUptime(value: number | null): string {
|
||||
if (value == null) return '—'
|
||||
return `${value.toFixed(value >= 99.95 ? 2 : 1)}%`
|
||||
}
|
||||
|
||||
interface UptimeChartProps {
|
||||
items: UptimeProbe[]
|
||||
isLoading?: boolean
|
||||
@@ -119,6 +136,8 @@ interface UptimeChartProps {
|
||||
onPeriodChange?: (period: UptimePeriodKey) => void
|
||||
skipPeriodFilter?: boolean
|
||||
embedded?: boolean
|
||||
/** dashboard-4: chrome живёт в родительском FrameHeader (переключатель серий). */
|
||||
hideHeader?: boolean
|
||||
}
|
||||
|
||||
export function UptimeChart({
|
||||
@@ -128,6 +147,7 @@ export function UptimeChart({
|
||||
onPeriodChange,
|
||||
skipPeriodFilter = false,
|
||||
embedded = false,
|
||||
hideHeader = false,
|
||||
}: UptimeChartProps) {
|
||||
const gradientId = useId().replace(/:/g, '')
|
||||
const [internalPeriod, setInternalPeriod] = useState<UptimePeriodKey>('5D')
|
||||
@@ -155,43 +175,45 @@ export function UptimeChart({
|
||||
|
||||
const panel = (
|
||||
<FramePanel className="flex flex-col gap-6">
|
||||
<div className="border-border flex items-center justify-between gap-2 border-b border-dashed pb-4">
|
||||
<div className="flex items-center gap-2.5">
|
||||
<IconTile
|
||||
variant="elevated"
|
||||
className={`size-10.5 ${tileClass}`}
|
||||
aria-hidden="true"
|
||||
>
|
||||
<ActivityIcon />
|
||||
</IconTile>
|
||||
<div className="flex flex-col justify-center">
|
||||
<h3 className="text-base font-semibold">Uptime</h3>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
Пробы health-check за период
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<TooltipProvider delay={150}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
render={
|
||||
<Button
|
||||
aria-label="О графике uptime"
|
||||
className="text-muted-foreground/70 -mr-1"
|
||||
size="icon-sm"
|
||||
type="button"
|
||||
variant="ghost"
|
||||
/>
|
||||
}
|
||||
{hideHeader ? null : (
|
||||
<div className="border-border flex items-center justify-between gap-2 border-b border-dashed pb-4">
|
||||
<div className="flex items-center gap-2.5">
|
||||
<IconTile
|
||||
variant="elevated"
|
||||
className={`size-10.5 ${tileClass}`}
|
||||
aria-hidden="true"
|
||||
>
|
||||
<InfoIcon data-icon="inline-start" aria-hidden="true" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" sideOffset={8}>
|
||||
<p>Доля успешных проб и задержка (мс) по журналу health-log.</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
<ActivityIcon />
|
||||
</IconTile>
|
||||
<div className="flex flex-col justify-center">
|
||||
<h3 className="text-base font-semibold">Uptime</h3>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
Пробы health-check за период
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<TooltipProvider delay={150}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
render={
|
||||
<Button
|
||||
aria-label="О графике uptime"
|
||||
className="text-muted-foreground/70 -mr-1"
|
||||
size="icon-sm"
|
||||
type="button"
|
||||
variant="ghost"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<InfoIcon data-icon="inline-start" aria-hidden="true" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" sideOffset={8}>
|
||||
<p>Доля успешных проб и задержка (мс) по журналу health-log.</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isLoading ? (
|
||||
<div className="bg-muted h-40 w-full animate-pulse rounded-xl" />
|
||||
@@ -207,7 +229,7 @@ export function UptimeChart({
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="text-foreground text-3xl font-semibold tabular-nums">
|
||||
{uptime == null ? '—' : `${uptime.toFixed(uptime >= 99.95 ? 2 : 1)}%`}
|
||||
{formatUptime(uptime)}
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-sm">
|
||||
{delta == null ? (
|
||||
|
||||
@@ -18,6 +18,7 @@ import type {
|
||||
ServiceView,
|
||||
UpdateServiceConfigInput,
|
||||
} from '@/lib/schemas'
|
||||
import { parseHealthProviders } from '@cfdm/shared'
|
||||
import { bindingToFqdn, parseFqdn } from '@/lib/parse-fqdn'
|
||||
import { Badge } from '@/components/reui/badge'
|
||||
import { toast } from 'sonner'
|
||||
@@ -108,19 +109,19 @@ function toBindingDrafts(service: ServiceView): ServiceBindingDraft[] {
|
||||
target_cname: binding.target_cname ?? '',
|
||||
lb_mode: binding.lb_mode,
|
||||
health: {
|
||||
enabled: binding.health_check_enabled,
|
||||
enabled: Boolean(binding.health_check_enabled),
|
||||
type: binding.health_check_type === 'http' ? 'http' : 'tcp',
|
||||
port: binding.health_check_port,
|
||||
path: binding.health_check_path,
|
||||
expected_status: binding.health_check_expected_status,
|
||||
interval_sec: binding.health_check_interval_sec,
|
||||
timeout_ms: binding.health_check_timeout_ms,
|
||||
verify_tls: binding.health_check_verify_tls ?? false,
|
||||
verify_tls: Boolean(binding.health_check_verify_tls),
|
||||
provider: binding.health_check_provider ?? 'local',
|
||||
providers:
|
||||
binding.health_check_providers?.length > 0
|
||||
? binding.health_check_providers
|
||||
: [binding.health_check_provider ?? 'local'],
|
||||
providers: parseHealthProviders(
|
||||
binding.health_check_providers,
|
||||
binding.health_check_provider ?? 'local',
|
||||
),
|
||||
aggregate: binding.health_check_aggregate ?? 'majority',
|
||||
},
|
||||
target_ip_weights: binding.target_ip_weights ?? {},
|
||||
@@ -232,6 +233,8 @@ export function ServiceEditSheet({
|
||||
[groups],
|
||||
)
|
||||
|
||||
// Reset only when the sheet opens or the service id changes.
|
||||
// Health polling replaces `service` by identity and would wipe unsaved settings.
|
||||
useEffect(() => {
|
||||
if (!open) return
|
||||
if (mode === 'edit' && service) {
|
||||
@@ -260,7 +263,7 @@ export function ServiceEditSheet({
|
||||
setLbWeight(1)
|
||||
setLbPriority(1)
|
||||
}
|
||||
}, [open, mode, service, defaultGroupId])
|
||||
}, [open, mode, service?.id, defaultGroupId])
|
||||
|
||||
const zoneHints = useMemo(
|
||||
() => knownDomains.map((domain) => domain.zone_name),
|
||||
|
||||
@@ -291,14 +291,14 @@ const lbModeSchema = z.enum(['round_robin', 'failover', 'weighted'])
|
||||
const healthCheckTypeSchema = z.enum(['tcp', 'http', 'ping', 'dns'])
|
||||
|
||||
const healthCheckConfigFields = {
|
||||
health_check_enabled: z.boolean().optional(),
|
||||
health_check_enabled: z.coerce.boolean().optional(),
|
||||
health_check_type: healthCheckTypeSchema.optional(),
|
||||
health_check_port: z.number().int().min(1).max(65535).nullable().optional(),
|
||||
health_check_path: z.string().nullable().optional(),
|
||||
health_check_expected_status: z.number().int().min(100).max(599).nullable().optional(),
|
||||
health_check_interval_sec: z.number().int().min(5).max(3600).optional(),
|
||||
health_check_timeout_ms: z.number().int().min(100).max(30000).optional(),
|
||||
health_check_verify_tls: z.boolean().optional(),
|
||||
health_check_verify_tls: z.coerce.boolean().optional(),
|
||||
health_check_provider: z.enum(['local', 'cloudflare', 'globalping']).optional(),
|
||||
health_check_providers: z.array(z.enum(['local', 'cloudflare', 'globalping'])).min(1).optional(),
|
||||
health_check_aggregate: z.enum(['any', 'all', 'majority']).optional(),
|
||||
|
||||
@@ -28,7 +28,6 @@ import {
|
||||
} from '@/components/services/service-detail-grid'
|
||||
import { LbModeTile } from '@/components/services/service-unit-card'
|
||||
import {
|
||||
HealthProviderStatusTiles,
|
||||
KpiStatGrid,
|
||||
ServiceHealthMonitor,
|
||||
} from '@/components/reui-kit'
|
||||
@@ -45,7 +44,6 @@ import {
|
||||
providerHealthStatuses,
|
||||
} from '@/lib/health-log'
|
||||
import type { ServiceView, UpdateServiceConfigInput } from '@/lib/schemas'
|
||||
import type { HealthCheckProvider } from '@cfdm/shared'
|
||||
import {
|
||||
createServiceNode,
|
||||
deleteServiceNode,
|
||||
@@ -112,9 +110,6 @@ function ServiceDetailPage() {
|
||||
|
||||
const [editOpen, setEditOpen] = useState(false)
|
||||
const [saving, setSaving] = useState(false)
|
||||
const [selectedProviders, setSelectedProviders] = useState<
|
||||
HealthCheckProvider[] | null
|
||||
>(null)
|
||||
const [togglingIp, setTogglingIp] = useState<string | null>(null)
|
||||
const [changeIp, setChangeIp] = useState<{
|
||||
bindingId: number
|
||||
@@ -234,11 +229,6 @@ function ServiceDetailPage() {
|
||||
() => enabledHealthProviders(service?.domains ?? []),
|
||||
[service],
|
||||
)
|
||||
const activeProviders =
|
||||
selectedProviders?.filter((provider) => enabledProviders.includes(provider)) ??
|
||||
enabledProviders
|
||||
const effectiveProviders =
|
||||
activeProviders.length > 0 ? activeProviders : enabledProviders
|
||||
const providerStatuses = useMemo(
|
||||
() => providerHealthStatuses(logItems, enabledProviders),
|
||||
[logItems, enabledProviders],
|
||||
@@ -333,20 +323,14 @@ function ServiceDetailPage() {
|
||||
]}
|
||||
/>
|
||||
|
||||
<HealthProviderStatusTiles
|
||||
enabled={enabledProviders}
|
||||
selected={effectiveProviders}
|
||||
statuses={providerStatuses}
|
||||
onChange={setSelectedProviders}
|
||||
/>
|
||||
|
||||
<section
|
||||
aria-label="Мониторинг"
|
||||
className="grid min-w-0 items-start gap-2 @3xl:grid-cols-2"
|
||||
>
|
||||
<ServiceHealthMonitor
|
||||
items={logItems}
|
||||
selectedProviders={effectiveProviders}
|
||||
enabledProviders={enabledProviders}
|
||||
statuses={providerStatuses}
|
||||
isLoading={logQuery.isLoading}
|
||||
/>
|
||||
<Frame dense spacing="sm" className="min-w-0 w-full">
|
||||
|
||||
Vendored
+74
-1
@@ -1613,6 +1613,25 @@ declare const serviceBindings: drizzle_orm_sqlite_core.SQLiteTableWithColumns<{
|
||||
}, {}, {
|
||||
length: number | undefined;
|
||||
}>;
|
||||
cert_monitoring: drizzle_orm_sqlite_core.SQLiteColumn<{
|
||||
name: "cert_monitoring";
|
||||
tableName: "service_bindings";
|
||||
dataType: "string";
|
||||
columnType: "SQLiteText";
|
||||
data: string;
|
||||
driverParam: string;
|
||||
notNull: true;
|
||||
hasDefault: true;
|
||||
isPrimaryKey: false;
|
||||
isAutoincrement: false;
|
||||
hasRuntimeDefault: false;
|
||||
enumValues: [string, ...string[]];
|
||||
baseColumn: never;
|
||||
identity: undefined;
|
||||
generated: undefined;
|
||||
}, {}, {
|
||||
length: number | undefined;
|
||||
}>;
|
||||
routing_strategy: drizzle_orm_sqlite_core.SQLiteColumn<{
|
||||
name: "routing_strategy";
|
||||
tableName: "service_bindings";
|
||||
@@ -2682,6 +2701,23 @@ declare const certificates: drizzle_orm_sqlite_core.SQLiteTableWithColumns<{
|
||||
identity: undefined;
|
||||
generated: undefined;
|
||||
}, {}, {}>;
|
||||
service_id: drizzle_orm_sqlite_core.SQLiteColumn<{
|
||||
name: "service_id";
|
||||
tableName: "certificates";
|
||||
dataType: "number";
|
||||
columnType: "SQLiteInteger";
|
||||
data: number;
|
||||
driverParam: number;
|
||||
notNull: false;
|
||||
hasDefault: false;
|
||||
isPrimaryKey: false;
|
||||
isAutoincrement: false;
|
||||
hasRuntimeDefault: false;
|
||||
enumValues: undefined;
|
||||
baseColumn: never;
|
||||
identity: undefined;
|
||||
generated: undefined;
|
||||
}, {}, {}>;
|
||||
hostname: drizzle_orm_sqlite_core.SQLiteColumn<{
|
||||
name: "hostname";
|
||||
tableName: "certificates";
|
||||
@@ -6296,6 +6332,25 @@ declare const schema: {
|
||||
}, {}, {
|
||||
length: number | undefined;
|
||||
}>;
|
||||
cert_monitoring: drizzle_orm_sqlite_core.SQLiteColumn<{
|
||||
name: "cert_monitoring";
|
||||
tableName: "service_bindings";
|
||||
dataType: "string";
|
||||
columnType: "SQLiteText";
|
||||
data: string;
|
||||
driverParam: string;
|
||||
notNull: true;
|
||||
hasDefault: true;
|
||||
isPrimaryKey: false;
|
||||
isAutoincrement: false;
|
||||
hasRuntimeDefault: false;
|
||||
enumValues: [string, ...string[]];
|
||||
baseColumn: never;
|
||||
identity: undefined;
|
||||
generated: undefined;
|
||||
}, {}, {
|
||||
length: number | undefined;
|
||||
}>;
|
||||
routing_strategy: drizzle_orm_sqlite_core.SQLiteColumn<{
|
||||
name: "routing_strategy";
|
||||
tableName: "service_bindings";
|
||||
@@ -7365,6 +7420,23 @@ declare const schema: {
|
||||
identity: undefined;
|
||||
generated: undefined;
|
||||
}, {}, {}>;
|
||||
service_id: drizzle_orm_sqlite_core.SQLiteColumn<{
|
||||
name: "service_id";
|
||||
tableName: "certificates";
|
||||
dataType: "number";
|
||||
columnType: "SQLiteInteger";
|
||||
data: number;
|
||||
driverParam: number;
|
||||
notNull: false;
|
||||
hasDefault: false;
|
||||
isPrimaryKey: false;
|
||||
isAutoincrement: false;
|
||||
hasRuntimeDefault: false;
|
||||
enumValues: undefined;
|
||||
baseColumn: never;
|
||||
identity: undefined;
|
||||
generated: undefined;
|
||||
}, {}, {}>;
|
||||
hostname: drizzle_orm_sqlite_core.SQLiteColumn<{
|
||||
name: "hostname";
|
||||
tableName: "certificates";
|
||||
@@ -9677,6 +9749,7 @@ interface BindingLbPatch {
|
||||
health_check_provider?: HealthCheckProvider;
|
||||
health_check_providers?: HealthCheckProvider[];
|
||||
health_check_aggregate?: HealthCheckAggregate;
|
||||
cert_monitoring?: string;
|
||||
}
|
||||
declare function updateBindingLbConfig(db: Db, bindingId: number, patch: BindingLbPatch): void;
|
||||
declare function setBindingCnameTarget(db: Db, bindingId: number, target: string | null): void;
|
||||
@@ -9702,7 +9775,7 @@ declare function deleteBindingsExcept(db: Db, serviceId: number, keepIds: number
|
||||
declare function deleteBinding(db: Db, id: number): void;
|
||||
declare function listCertificates(db: Db, status?: string): Certificate[];
|
||||
declare function getCertificate(db: Db, id: number): Certificate;
|
||||
declare function upsertCertificateCheck(db: Db, domainId: number, subdomainId: number | null, hostname: string, expiresAt: string | null, status: string, lastError: string | null): Certificate;
|
||||
declare function upsertCertificateCheck(db: Db, domainId: number, subdomainId: number | null, hostname: string, expiresAt: string | null, status: string, lastError: string | null, serviceId?: number | null): Certificate;
|
||||
declare function countCertificatesByStatus(db: Db): Array<[string, number]>;
|
||||
declare function deleteCertificatesNotIn(db: Db, hostnames: string[]): number;
|
||||
declare function createSyncJob(db: Db, id: string, domainId: number | null): void;
|
||||
|
||||
Vendored
+111
-20
@@ -123,6 +123,7 @@ var serviceBindings = sqliteTable(
|
||||
health_check_provider: text("health_check_provider").notNull().default("local"),
|
||||
health_check_providers: text("health_check_providers").notNull().default('["local"]'),
|
||||
health_check_aggregate: text("health_check_aggregate").notNull().default("majority"),
|
||||
cert_monitoring: text("cert_monitoring").notNull().default("auto"),
|
||||
routing_strategy: text("routing_strategy").notNull().default("round_robin"),
|
||||
operation_version: integer("operation_version").notNull().default(0),
|
||||
created_at: text("created_at").notNull().default(sql`datetime('now')`),
|
||||
@@ -228,6 +229,9 @@ var certificates = sqliteTable("certificates", {
|
||||
subdomain_id: integer("subdomain_id").references(() => subdomains.id, {
|
||||
onDelete: "set null"
|
||||
}),
|
||||
service_id: integer("service_id").references(() => services.id, {
|
||||
onDelete: "set null"
|
||||
}),
|
||||
hostname: text("hostname").notNull().unique(),
|
||||
expires_at: text("expires_at"),
|
||||
last_checked_at: text("last_checked_at"),
|
||||
@@ -1221,15 +1225,16 @@ function mapServiceBinding(row) {
|
||||
cname_target: row.cname_target,
|
||||
dns_record_id: row.dns_record_id,
|
||||
lb_mode: row.lb_mode,
|
||||
health_check_enabled: row.health_check_enabled,
|
||||
health_check_enabled: Boolean(row.health_check_enabled),
|
||||
health_check_type: row.health_check_type,
|
||||
health_check_port: row.health_check_port,
|
||||
health_check_path: row.health_check_path,
|
||||
health_check_expected_status: row.health_check_expected_status,
|
||||
health_check_interval_sec: row.health_check_interval_sec,
|
||||
health_check_timeout_ms: row.health_check_timeout_ms,
|
||||
health_check_verify_tls: row.health_check_verify_tls,
|
||||
health_check_verify_tls: Boolean(row.health_check_verify_tls),
|
||||
...mapHealthFields(row),
|
||||
cert_monitoring: row.cert_monitoring ?? "auto",
|
||||
routing_strategy: row.routing_strategy,
|
||||
operation_version: row.operation_version,
|
||||
created_at: row.created_at,
|
||||
@@ -1625,6 +1630,8 @@ function updateBindingLbConfig(db, bindingId, patch) {
|
||||
update.health_check_timeout_ms = patch.health_check_timeout_ms;
|
||||
if (patch.health_check_verify_tls !== void 0)
|
||||
update.health_check_verify_tls = patch.health_check_verify_tls;
|
||||
if (patch.cert_monitoring !== void 0)
|
||||
update.cert_monitoring = patch.cert_monitoring;
|
||||
Object.assign(update, healthProviderColumns(patch));
|
||||
db.update(serviceBindings).set(update).where(eq3(serviceBindings.id, bindingId)).run();
|
||||
}
|
||||
@@ -1685,7 +1692,7 @@ var SERVICE_BINDING_SELECT_COLUMNS = `sb.id, sb.domain_id, sb.service_id, sb.hos
|
||||
sb.lb_mode, sb.health_check_enabled, sb.health_check_type, sb.health_check_port,
|
||||
sb.health_check_path, sb.health_check_expected_status, sb.health_check_interval_sec,
|
||||
sb.health_check_timeout_ms, sb.health_check_verify_tls, sb.health_check_provider,
|
||||
sb.health_check_providers, sb.health_check_aggregate, sb.cname_target,
|
||||
sb.health_check_providers, sb.health_check_aggregate, sb.cert_monitoring, sb.cname_target,
|
||||
d.zone_name, d.group_id, g.name AS group_name,
|
||||
s.name AS service_name, s.slug AS service_slug,
|
||||
dr.content AS target_ip, dr.sync_status,
|
||||
@@ -1733,6 +1740,7 @@ function enrichServiceBindingView(db, row) {
|
||||
return {
|
||||
...row,
|
||||
cname_target: row.cname_target ?? null,
|
||||
cert_monitoring: row.cert_monitoring ?? "auto",
|
||||
...mapHealthFields(row),
|
||||
target_ips,
|
||||
target_ip: target_ips[0] ?? null,
|
||||
@@ -1765,11 +1773,15 @@ function listBindingsByDomain(db, domainId) {
|
||||
`).map((row) => enrichServiceBindingView(db, row));
|
||||
}
|
||||
function listBindingsByService(db, serviceId) {
|
||||
return db.all(sql2`
|
||||
const rows = db.all(sql2`
|
||||
SELECT sb.*, d.zone_name FROM service_bindings sb
|
||||
JOIN domains d ON d.id = sb.domain_id
|
||||
WHERE sb.service_id = ${serviceId}
|
||||
`);
|
||||
return rows.map((row) => ({
|
||||
...mapServiceBinding(row),
|
||||
zone_name: row.zone_name
|
||||
}));
|
||||
}
|
||||
function getBinding(db, id) {
|
||||
const row = db.select().from(serviceBindings).where(eq3(serviceBindings.id, id)).get();
|
||||
@@ -1839,23 +1851,57 @@ function deleteBinding(db, id) {
|
||||
const result = db.delete(serviceBindings).where(eq3(serviceBindings.id, id)).run();
|
||||
if (result.changes === 0) throw new NotFoundError(`service binding ${id}`);
|
||||
}
|
||||
var CERTIFICATE_SELECT = `c.id, c.domain_id, c.subdomain_id, c.service_id, c.hostname,
|
||||
c.expires_at, c.last_checked_at, c.last_error, c.status, c.created_at, c.updated_at,
|
||||
s.name AS service_name`;
|
||||
function mapCertificate(row) {
|
||||
return {
|
||||
id: row.id,
|
||||
domain_id: row.domain_id,
|
||||
subdomain_id: row.subdomain_id,
|
||||
service_id: row.service_id ?? null,
|
||||
service_name: row.service_name ?? null,
|
||||
hostname: row.hostname,
|
||||
expires_at: row.expires_at,
|
||||
last_checked_at: row.last_checked_at,
|
||||
last_error: row.last_error,
|
||||
status: row.status,
|
||||
created_at: row.created_at,
|
||||
updated_at: row.updated_at
|
||||
};
|
||||
}
|
||||
function listCertificates(db, status) {
|
||||
if (status) {
|
||||
return db.select().from(certificates).where(eq3(certificates.status, status)).orderBy(asc(certificates.expires_at)).all();
|
||||
}
|
||||
return db.select().from(certificates).orderBy(asc(certificates.expires_at)).all();
|
||||
const rows = status ? db.all(sql2`
|
||||
SELECT ${sql2.raw(CERTIFICATE_SELECT)}
|
||||
FROM certificates c
|
||||
LEFT JOIN services s ON s.id = c.service_id
|
||||
WHERE c.status = ${status}
|
||||
ORDER BY c.expires_at ASC
|
||||
`) : db.all(sql2`
|
||||
SELECT ${sql2.raw(CERTIFICATE_SELECT)}
|
||||
FROM certificates c
|
||||
LEFT JOIN services s ON s.id = c.service_id
|
||||
ORDER BY c.expires_at ASC
|
||||
`);
|
||||
return rows.map(mapCertificate);
|
||||
}
|
||||
function getCertificate(db, id) {
|
||||
const row = db.select().from(certificates).where(eq3(certificates.id, id)).get();
|
||||
const row = db.all(sql2`
|
||||
SELECT ${sql2.raw(CERTIFICATE_SELECT)}
|
||||
FROM certificates c
|
||||
LEFT JOIN services s ON s.id = c.service_id
|
||||
WHERE c.id = ${id}
|
||||
`)[0];
|
||||
if (!row) throw new NotFoundError(`certificate ${id}`);
|
||||
return row;
|
||||
return mapCertificate(row);
|
||||
}
|
||||
function upsertCertificateCheck(db, domainId, subdomainId, hostname, expiresAt, status, lastError) {
|
||||
function upsertCertificateCheck(db, domainId, subdomainId, hostname, expiresAt, status, lastError, serviceId) {
|
||||
const existing = db.select().from(certificates).where(eq3(certificates.hostname, hostname)).get();
|
||||
if (existing) {
|
||||
db.update(certificates).set({
|
||||
domain_id: domainId,
|
||||
subdomain_id: subdomainId,
|
||||
service_id: serviceId === void 0 ? existing.service_id : serviceId,
|
||||
expires_at: expiresAt,
|
||||
last_checked_at: sql2`datetime('now')`,
|
||||
last_error: lastError,
|
||||
@@ -1867,6 +1913,7 @@ function upsertCertificateCheck(db, domainId, subdomainId, hostname, expiresAt,
|
||||
const id = db.insert(certificates).values({
|
||||
domain_id: domainId,
|
||||
subdomain_id: subdomainId,
|
||||
service_id: serviceId ?? null,
|
||||
hostname,
|
||||
expires_at: expiresAt,
|
||||
last_checked_at: sql2`datetime('now')`,
|
||||
@@ -2170,6 +2217,40 @@ function pruneStaleIpHealthStatus(db, activeTargets) {
|
||||
}
|
||||
return deleted;
|
||||
}
|
||||
function normalizeCnameHost(target, zoneName) {
|
||||
const trimmed = target.trim().toLowerCase().replace(/\.+$/, "");
|
||||
if (!trimmed) return "";
|
||||
if (trimmed.includes(".")) return trimmed;
|
||||
const zone = zoneName.trim().toLowerCase().replace(/\.+$/, "");
|
||||
return zone ? `${trimmed}.${zone}` : trimmed;
|
||||
}
|
||||
function resolveCnameProbeIps(db, cnameTarget, zoneName, serviceId) {
|
||||
const fqdn = normalizeCnameHost(cnameTarget, zoneName);
|
||||
if (fqdn) {
|
||||
const fromDns = listOriginIpsForFqdn(db, fqdn).filter(isIpLiteral);
|
||||
if (fromDns.length > 0) return [...new Set(fromDns)];
|
||||
}
|
||||
if (serviceId > 0) {
|
||||
return [...new Set(listServiceIps(db, serviceId).filter(isIpLiteral))];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
function expandCnameHealthTargets(db, rows) {
|
||||
const expanded = [];
|
||||
for (const row of rows) {
|
||||
const ips = resolveCnameProbeIps(
|
||||
db,
|
||||
row.ip,
|
||||
row.zone_name ?? "",
|
||||
row.service_id ?? 0
|
||||
);
|
||||
const resolved = ips.length > 0 ? ips : [row.ip];
|
||||
for (const ip of resolved) {
|
||||
expanded.push({ ...row, ip });
|
||||
}
|
||||
}
|
||||
return expanded;
|
||||
}
|
||||
function listHealthCheckTargets(db) {
|
||||
const fqdnExpr = sql2`CASE WHEN sb.hostname = '@' OR sb.hostname IS NULL THEN d.zone_name ELSE sb.hostname || '.' || d.zone_name END`;
|
||||
const bindingTargets = db.all(sql2`
|
||||
@@ -2188,7 +2269,7 @@ function listHealthCheckTargets(db) {
|
||||
JOIN service_bindings sb ON sb.id = sbi.binding_id
|
||||
JOIN domains d ON d.id = sb.domain_id
|
||||
WHERE sb.health_check_enabled = 1
|
||||
`);
|
||||
`).filter((t) => isIpLiteral(t.ip));
|
||||
const groupTargets = db.all(sql2`
|
||||
SELECT DISTINCT 'group' AS scope, sg.id AS ref_id, sbi.ip,
|
||||
sg.domain AS hostname,
|
||||
@@ -2211,7 +2292,7 @@ function listHealthCheckTargets(db) {
|
||||
AND s.enabled = 1
|
||||
AND sg.enabled = 1
|
||||
AND (sb.cname_target IS NULL OR sb.cname_target = '')
|
||||
`);
|
||||
`).filter((t) => isIpLiteral(t.ip));
|
||||
const groupInheritedBindingTargets = db.all(sql2`
|
||||
SELECT 'binding' AS scope, sb.id AS ref_id, sbi.ip,
|
||||
${fqdnExpr} AS hostname,
|
||||
@@ -2234,8 +2315,10 @@ function listHealthCheckTargets(db) {
|
||||
AND s.enabled = 1
|
||||
AND sg.enabled = 1
|
||||
AND sb.health_check_enabled = 0
|
||||
`);
|
||||
const cnameBindingTargets = db.all(sql2`
|
||||
`).filter((t) => isIpLiteral(t.ip));
|
||||
const cnameBindingTargets = expandCnameHealthTargets(
|
||||
db,
|
||||
db.all(sql2`
|
||||
SELECT 'binding' AS scope, sb.id AS ref_id, sb.cname_target AS ip,
|
||||
${fqdnExpr} AS hostname,
|
||||
sb.health_check_type AS type,
|
||||
@@ -2246,7 +2329,9 @@ function listHealthCheckTargets(db) {
|
||||
sb.health_check_verify_tls AS verify_tls,
|
||||
sb.health_check_providers AS providers_json,
|
||||
COALESCE(sb.health_check_aggregate, 'majority') AS aggregate,
|
||||
COALESCE(sb.health_check_provider, 'local') AS provider
|
||||
COALESCE(sb.health_check_provider, 'local') AS provider,
|
||||
d.zone_name AS zone_name,
|
||||
sb.service_id AS service_id
|
||||
FROM service_bindings sb
|
||||
JOIN domains d ON d.id = sb.domain_id
|
||||
JOIN services s ON s.id = sb.service_id
|
||||
@@ -2254,8 +2339,11 @@ function listHealthCheckTargets(db) {
|
||||
AND sb.cname_target IS NOT NULL
|
||||
AND sb.cname_target <> ''
|
||||
AND s.enabled = 1
|
||||
`);
|
||||
const groupInheritedCnameBindingTargets = db.all(sql2`
|
||||
`)
|
||||
);
|
||||
const groupInheritedCnameBindingTargets = expandCnameHealthTargets(
|
||||
db,
|
||||
db.all(sql2`
|
||||
SELECT 'binding' AS scope, sb.id AS ref_id, sb.cname_target AS ip,
|
||||
${fqdnExpr} AS hostname,
|
||||
sg.health_check_type AS type,
|
||||
@@ -2266,7 +2354,9 @@ function listHealthCheckTargets(db) {
|
||||
sg.health_check_verify_tls AS verify_tls,
|
||||
sg.health_check_providers AS providers_json,
|
||||
COALESCE(sg.health_check_aggregate, 'majority') AS aggregate,
|
||||
COALESCE(sg.health_check_provider, 'local') AS provider
|
||||
COALESCE(sg.health_check_provider, 'local') AS provider,
|
||||
d.zone_name AS zone_name,
|
||||
sb.service_id AS service_id
|
||||
FROM service_bindings sb
|
||||
JOIN domains d ON d.id = sb.domain_id
|
||||
JOIN services s ON s.id = sb.service_id
|
||||
@@ -2278,7 +2368,8 @@ function listHealthCheckTargets(db) {
|
||||
AND sb.health_check_enabled = 0
|
||||
AND sb.cname_target IS NOT NULL
|
||||
AND sb.cname_target <> ''
|
||||
`);
|
||||
`)
|
||||
);
|
||||
return [
|
||||
...bindingTargets,
|
||||
...groupTargets,
|
||||
|
||||
+94
-16
@@ -842,14 +842,14 @@ function mapServiceBinding(
|
||||
cname_target: row.cname_target,
|
||||
dns_record_id: row.dns_record_id,
|
||||
lb_mode: row.lb_mode as LbMode,
|
||||
health_check_enabled: row.health_check_enabled,
|
||||
health_check_enabled: Boolean(row.health_check_enabled),
|
||||
health_check_type: row.health_check_type as HealthCheckType,
|
||||
health_check_port: row.health_check_port,
|
||||
health_check_path: row.health_check_path,
|
||||
health_check_expected_status: row.health_check_expected_status,
|
||||
health_check_interval_sec: row.health_check_interval_sec,
|
||||
health_check_timeout_ms: row.health_check_timeout_ms,
|
||||
health_check_verify_tls: row.health_check_verify_tls,
|
||||
health_check_verify_tls: Boolean(row.health_check_verify_tls),
|
||||
...mapHealthFields(row),
|
||||
cert_monitoring: row.cert_monitoring ?? "auto",
|
||||
routing_strategy: row.routing_strategy as LbMode,
|
||||
@@ -1777,11 +1777,15 @@ export function listBindingsByDomain(db: Db, domainId: number): ServiceBindingVi
|
||||
}
|
||||
|
||||
export function listBindingsByService(db: Db, serviceId: number): Array<ServiceBinding & { zone_name: string }> {
|
||||
return db.all(sql`
|
||||
const rows = db.all<typeof serviceBindings.$inferSelect & { zone_name: string }>(sql`
|
||||
SELECT sb.*, d.zone_name FROM service_bindings sb
|
||||
JOIN domains d ON d.id = sb.domain_id
|
||||
WHERE sb.service_id = ${serviceId}
|
||||
`);
|
||||
return rows.map((row) => ({
|
||||
...mapServiceBinding(row),
|
||||
zone_name: row.zone_name,
|
||||
}));
|
||||
}
|
||||
|
||||
export function getBinding(db: Db, id: number): ServiceBinding {
|
||||
@@ -2489,13 +2493,73 @@ export function pruneStaleIpHealthStatus(
|
||||
|
||||
// --- Health Check Targets ---
|
||||
|
||||
function normalizeCnameHost(target: string, zoneName: string): string {
|
||||
const trimmed = target.trim().toLowerCase().replace(/\.+$/, "");
|
||||
if (!trimmed) return "";
|
||||
if (trimmed.includes(".")) return trimmed;
|
||||
const zone = zoneName.trim().toLowerCase().replace(/\.+$/, "");
|
||||
return zone ? `${trimmed}.${zone}` : trimmed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unwrap a CNAME health target to origin IPs:
|
||||
* 1) A/AAAA from local dns_records (follows CNAME chain)
|
||||
* 2) this service's IP pool
|
||||
*
|
||||
* Hostname is kept as fallback so probes still run when nothing resolves.
|
||||
*/
|
||||
function resolveCnameProbeIps(
|
||||
db: Db,
|
||||
cnameTarget: string,
|
||||
zoneName: string,
|
||||
serviceId: number,
|
||||
): string[] {
|
||||
const fqdn = normalizeCnameHost(cnameTarget, zoneName);
|
||||
if (fqdn) {
|
||||
const fromDns = listOriginIpsForFqdn(db, fqdn).filter(isIpLiteral);
|
||||
if (fromDns.length > 0) return [...new Set(fromDns)];
|
||||
}
|
||||
if (serviceId > 0) {
|
||||
return [...new Set(listServiceIps(db, serviceId).filter(isIpLiteral))];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
type RawHealthTarget = HealthCheckTarget & {
|
||||
providers_json?: string | null;
|
||||
aggregate?: string | null;
|
||||
zone_name?: string | null;
|
||||
service_id?: number | null;
|
||||
};
|
||||
|
||||
function expandCnameHealthTargets(
|
||||
db: Db,
|
||||
rows: RawHealthTarget[],
|
||||
): RawHealthTarget[] {
|
||||
const expanded: RawHealthTarget[] = [];
|
||||
for (const row of rows) {
|
||||
const ips = resolveCnameProbeIps(
|
||||
db,
|
||||
row.ip,
|
||||
row.zone_name ?? "",
|
||||
row.service_id ?? 0,
|
||||
);
|
||||
const resolved = ips.length > 0 ? ips : [row.ip];
|
||||
for (const ip of resolved) {
|
||||
expanded.push({ ...row, ip });
|
||||
}
|
||||
}
|
||||
return expanded;
|
||||
}
|
||||
|
||||
export function listHealthCheckTargets(db: Db): HealthCheckTarget[] {
|
||||
// FQDN for a binding: "@" => zone_name, else "<hostname>.<zone_name>".
|
||||
// Used as SNI / Host header for HTTP(S) probes — the raw `sb.hostname` is just the record name
|
||||
// (e.g. "de" or "@"), which would break TLS SNI (ssl alert 112 "unrecognized name").
|
||||
const fqdnExpr = sql`CASE WHEN sb.hostname = '@' OR sb.hostname IS NULL THEN d.zone_name ELSE sb.hostname || '.' || d.zone_name END`;
|
||||
|
||||
const bindingTargets = db.all<HealthCheckTarget>(sql`
|
||||
const bindingTargets = db
|
||||
.all<RawHealthTarget>(sql`
|
||||
SELECT 'binding' AS scope, sb.id AS ref_id, sbi.ip,
|
||||
${fqdnExpr} AS hostname,
|
||||
sb.health_check_type AS type,
|
||||
@@ -2511,12 +2575,14 @@ export function listHealthCheckTargets(db: Db): HealthCheckTarget[] {
|
||||
JOIN service_bindings sb ON sb.id = sbi.binding_id
|
||||
JOIN domains d ON d.id = sb.domain_id
|
||||
WHERE sb.health_check_enabled = 1
|
||||
`);
|
||||
`)
|
||||
.filter((t) => isIpLiteral(t.ip));
|
||||
|
||||
// Group FQDN probes the same A-record IPs that DNS publishes (binding IPs of
|
||||
// enabled services) — NOT the full service_ips pool. Pool-only dead IPs must
|
||||
// not mark the group Down while the published domain stays healthy.
|
||||
const groupTargets = db.all<HealthCheckTarget>(sql`
|
||||
const groupTargets = db
|
||||
.all<RawHealthTarget>(sql`
|
||||
SELECT DISTINCT 'group' AS scope, sg.id AS ref_id, sbi.ip,
|
||||
sg.domain AS hostname,
|
||||
sg.health_check_type AS type,
|
||||
@@ -2538,13 +2604,14 @@ export function listHealthCheckTargets(db: Db): HealthCheckTarget[] {
|
||||
AND s.enabled = 1
|
||||
AND sg.enabled = 1
|
||||
AND (sb.cname_target IS NULL OR sb.cname_target = '')
|
||||
`);
|
||||
`)
|
||||
.filter((t) => isIpLiteral(t.ip));
|
||||
|
||||
// IPs of A-bindings of enabled services in a group whose group has health-check enabled.
|
||||
// These inherit the group's health-check config (scope='binding', ref_id=binding_id),
|
||||
// so per-domain badges reflect group rules. Skipped for bindings that already have
|
||||
// their own health_check_enabled=1 (covered by bindingTargets above).
|
||||
const groupInheritedBindingTargets = db.all<HealthCheckTarget>(sql`
|
||||
const groupInheritedBindingTargets = db.all<RawHealthTarget>(sql`
|
||||
SELECT 'binding' AS scope, sb.id AS ref_id, sbi.ip,
|
||||
${fqdnExpr} AS hostname,
|
||||
sg.health_check_type AS type,
|
||||
@@ -2566,10 +2633,13 @@ export function listHealthCheckTargets(db: Db): HealthCheckTarget[] {
|
||||
AND s.enabled = 1
|
||||
AND sg.enabled = 1
|
||||
AND sb.health_check_enabled = 0
|
||||
`);
|
||||
`)
|
||||
.filter((t) => isIpLiteral(t.ip));
|
||||
|
||||
// CNAME-bindings with their own health_check_enabled: probe the CNAME target host.
|
||||
const cnameBindingTargets = db.all<HealthCheckTarget>(sql`
|
||||
// CNAME-bindings: unwrap to origin/service IPs so ip_health keys match the IP table.
|
||||
const cnameBindingTargets = expandCnameHealthTargets(
|
||||
db,
|
||||
db.all<RawHealthTarget>(sql`
|
||||
SELECT 'binding' AS scope, sb.id AS ref_id, sb.cname_target AS ip,
|
||||
${fqdnExpr} AS hostname,
|
||||
sb.health_check_type AS type,
|
||||
@@ -2580,7 +2650,9 @@ export function listHealthCheckTargets(db: Db): HealthCheckTarget[] {
|
||||
sb.health_check_verify_tls AS verify_tls,
|
||||
sb.health_check_providers AS providers_json,
|
||||
COALESCE(sb.health_check_aggregate, 'majority') AS aggregate,
|
||||
COALESCE(sb.health_check_provider, 'local') AS provider
|
||||
COALESCE(sb.health_check_provider, 'local') AS provider,
|
||||
d.zone_name AS zone_name,
|
||||
sb.service_id AS service_id
|
||||
FROM service_bindings sb
|
||||
JOIN domains d ON d.id = sb.domain_id
|
||||
JOIN services s ON s.id = sb.service_id
|
||||
@@ -2588,11 +2660,14 @@ export function listHealthCheckTargets(db: Db): HealthCheckTarget[] {
|
||||
AND sb.cname_target IS NOT NULL
|
||||
AND sb.cname_target <> ''
|
||||
AND s.enabled = 1
|
||||
`);
|
||||
`),
|
||||
);
|
||||
|
||||
// CNAME-bindings of services in a group with group health-check enabled
|
||||
// (inherit group config). Only for bindings without their own health_check_enabled.
|
||||
const groupInheritedCnameBindingTargets = db.all<HealthCheckTarget>(sql`
|
||||
const groupInheritedCnameBindingTargets = expandCnameHealthTargets(
|
||||
db,
|
||||
db.all<RawHealthTarget>(sql`
|
||||
SELECT 'binding' AS scope, sb.id AS ref_id, sb.cname_target AS ip,
|
||||
${fqdnExpr} AS hostname,
|
||||
sg.health_check_type AS type,
|
||||
@@ -2603,7 +2678,9 @@ export function listHealthCheckTargets(db: Db): HealthCheckTarget[] {
|
||||
sg.health_check_verify_tls AS verify_tls,
|
||||
sg.health_check_providers AS providers_json,
|
||||
COALESCE(sg.health_check_aggregate, 'majority') AS aggregate,
|
||||
COALESCE(sg.health_check_provider, 'local') AS provider
|
||||
COALESCE(sg.health_check_provider, 'local') AS provider,
|
||||
d.zone_name AS zone_name,
|
||||
sb.service_id AS service_id
|
||||
FROM service_bindings sb
|
||||
JOIN domains d ON d.id = sb.domain_id
|
||||
JOIN services s ON s.id = sb.service_id
|
||||
@@ -2615,7 +2692,8 @@ export function listHealthCheckTargets(db: Db): HealthCheckTarget[] {
|
||||
AND sb.health_check_enabled = 0
|
||||
AND sb.cname_target IS NOT NULL
|
||||
AND sb.cname_target <> ''
|
||||
`);
|
||||
`),
|
||||
);
|
||||
|
||||
return [
|
||||
...bindingTargets,
|
||||
|
||||
Vendored
+103
-39
@@ -96,6 +96,7 @@ interface ServiceBinding {
|
||||
health_check_provider: HealthCheckProvider;
|
||||
health_check_providers: HealthCheckProvider[];
|
||||
health_check_aggregate: HealthCheckAggregate;
|
||||
cert_monitoring: string;
|
||||
routing_strategy: LbMode;
|
||||
operation_version: number;
|
||||
created_at: string;
|
||||
@@ -129,6 +130,7 @@ interface ServiceBindingView {
|
||||
health_check_provider: HealthCheckProvider;
|
||||
health_check_providers: HealthCheckProvider[];
|
||||
health_check_aggregate: HealthCheckAggregate;
|
||||
cert_monitoring: string;
|
||||
sync_status: string | null;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
@@ -156,6 +158,7 @@ interface ServiceDomainBindingView {
|
||||
health_check_provider: HealthCheckProvider;
|
||||
health_check_providers: HealthCheckProvider[];
|
||||
health_check_aggregate: HealthCheckAggregate;
|
||||
cert_monitoring: string;
|
||||
sync_status: string | null;
|
||||
}
|
||||
interface ServiceView$1 {
|
||||
@@ -424,11 +427,11 @@ declare const healthCheckAggregateSchema: z.ZodEnum<{
|
||||
all: "all";
|
||||
majority: "majority";
|
||||
}>;
|
||||
declare const healthCheckProvidersSchema: z.ZodPipe<z.ZodArray<z.ZodEnum<{
|
||||
declare const healthCheckProvidersSchema: z.ZodPreprocess<z.ZodPipe<z.ZodArray<z.ZodEnum<{
|
||||
local: "local";
|
||||
cloudflare: "cloudflare";
|
||||
globalping: "globalping";
|
||||
}>>, z.ZodTransform<("local" | "cloudflare" | "globalping")[] | readonly ["local"], ("local" | "cloudflare" | "globalping")[]>>;
|
||||
}>>, z.ZodTransform<("local" | "cloudflare" | "globalping")[] | readonly ["local"], ("local" | "cloudflare" | "globalping")[]>>>;
|
||||
declare const healthCheckScopeSchema: z.ZodEnum<{
|
||||
binding: "binding";
|
||||
group: "group";
|
||||
@@ -560,11 +563,11 @@ declare const serviceGroupSchema: z.ZodObject<{
|
||||
cloudflare: "cloudflare";
|
||||
globalping: "globalping";
|
||||
}>>;
|
||||
health_check_providers: z.ZodCatch<z.ZodPipe<z.ZodArray<z.ZodEnum<{
|
||||
health_check_providers: z.ZodCatch<z.ZodPreprocess<z.ZodPipe<z.ZodArray<z.ZodEnum<{
|
||||
local: "local";
|
||||
cloudflare: "cloudflare";
|
||||
globalping: "globalping";
|
||||
}>>, z.ZodTransform<("local" | "cloudflare" | "globalping")[] | readonly ["local"], ("local" | "cloudflare" | "globalping")[]>>>;
|
||||
}>>, z.ZodTransform<("local" | "cloudflare" | "globalping")[] | readonly ["local"], ("local" | "cloudflare" | "globalping")[]>>>>;
|
||||
health_check_aggregate: z.ZodCatch<z.ZodEnum<{
|
||||
any: "any";
|
||||
all: "all";
|
||||
@@ -624,16 +627,21 @@ declare const serviceDomainBindingSchema: z.ZodPipe<z.ZodObject<{
|
||||
cloudflare: "cloudflare";
|
||||
globalping: "globalping";
|
||||
}>>;
|
||||
health_check_providers: z.ZodCatch<z.ZodPipe<z.ZodArray<z.ZodEnum<{
|
||||
health_check_providers: z.ZodCatch<z.ZodPreprocess<z.ZodPipe<z.ZodArray<z.ZodEnum<{
|
||||
local: "local";
|
||||
cloudflare: "cloudflare";
|
||||
globalping: "globalping";
|
||||
}>>, z.ZodTransform<("local" | "cloudflare" | "globalping")[] | readonly ["local"], ("local" | "cloudflare" | "globalping")[]>>>;
|
||||
}>>, z.ZodTransform<("local" | "cloudflare" | "globalping")[] | readonly ["local"], ("local" | "cloudflare" | "globalping")[]>>>>;
|
||||
health_check_aggregate: z.ZodCatch<z.ZodEnum<{
|
||||
any: "any";
|
||||
all: "all";
|
||||
majority: "majority";
|
||||
}>>;
|
||||
cert_monitoring: z.ZodDefault<z.ZodEnum<{
|
||||
auto: "auto";
|
||||
required: "required";
|
||||
skipped: "skipped";
|
||||
}>>;
|
||||
sync_status: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
||||
}, z.core.$strip>, z.ZodTransform<{
|
||||
target_ips: string[];
|
||||
@@ -658,6 +666,7 @@ declare const serviceDomainBindingSchema: z.ZodPipe<z.ZodObject<{
|
||||
health_check_provider: "local" | "cloudflare" | "globalping";
|
||||
health_check_providers: ("local" | "cloudflare" | "globalping")[] | readonly ["local"];
|
||||
health_check_aggregate: "any" | "all" | "majority";
|
||||
cert_monitoring: "auto" | "required" | "skipped";
|
||||
sync_status: string | null;
|
||||
target_ip?: string | null | undefined;
|
||||
}, {
|
||||
@@ -679,6 +688,7 @@ declare const serviceDomainBindingSchema: z.ZodPipe<z.ZodObject<{
|
||||
health_check_provider: "local" | "cloudflare" | "globalping";
|
||||
health_check_providers: ("local" | "cloudflare" | "globalping")[] | readonly ["local"];
|
||||
health_check_aggregate: "any" | "all" | "majority";
|
||||
cert_monitoring: "auto" | "required" | "skipped";
|
||||
sync_status: string | null;
|
||||
target_ips?: string[] | undefined;
|
||||
target_ip?: string | null | undefined;
|
||||
@@ -737,16 +747,21 @@ declare const serviceViewSchema: z.ZodObject<{
|
||||
cloudflare: "cloudflare";
|
||||
globalping: "globalping";
|
||||
}>>;
|
||||
health_check_providers: z.ZodCatch<z.ZodPipe<z.ZodArray<z.ZodEnum<{
|
||||
health_check_providers: z.ZodCatch<z.ZodPreprocess<z.ZodPipe<z.ZodArray<z.ZodEnum<{
|
||||
local: "local";
|
||||
cloudflare: "cloudflare";
|
||||
globalping: "globalping";
|
||||
}>>, z.ZodTransform<("local" | "cloudflare" | "globalping")[] | readonly ["local"], ("local" | "cloudflare" | "globalping")[]>>>;
|
||||
}>>, z.ZodTransform<("local" | "cloudflare" | "globalping")[] | readonly ["local"], ("local" | "cloudflare" | "globalping")[]>>>>;
|
||||
health_check_aggregate: z.ZodCatch<z.ZodEnum<{
|
||||
any: "any";
|
||||
all: "all";
|
||||
majority: "majority";
|
||||
}>>;
|
||||
cert_monitoring: z.ZodDefault<z.ZodEnum<{
|
||||
auto: "auto";
|
||||
required: "required";
|
||||
skipped: "skipped";
|
||||
}>>;
|
||||
sync_status: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
||||
}, z.core.$strip>, z.ZodTransform<{
|
||||
target_ips: string[];
|
||||
@@ -771,6 +786,7 @@ declare const serviceViewSchema: z.ZodObject<{
|
||||
health_check_provider: "local" | "cloudflare" | "globalping";
|
||||
health_check_providers: ("local" | "cloudflare" | "globalping")[] | readonly ["local"];
|
||||
health_check_aggregate: "any" | "all" | "majority";
|
||||
cert_monitoring: "auto" | "required" | "skipped";
|
||||
sync_status: string | null;
|
||||
target_ip?: string | null | undefined;
|
||||
}, {
|
||||
@@ -792,6 +808,7 @@ declare const serviceViewSchema: z.ZodObject<{
|
||||
health_check_provider: "local" | "cloudflare" | "globalping";
|
||||
health_check_providers: ("local" | "cloudflare" | "globalping")[] | readonly ["local"];
|
||||
health_check_aggregate: "any" | "all" | "majority";
|
||||
cert_monitoring: "auto" | "required" | "skipped";
|
||||
sync_status: string | null;
|
||||
target_ips?: string[] | undefined;
|
||||
target_ip?: string | null | undefined;
|
||||
@@ -869,11 +886,11 @@ declare const serviceGroupViewSchema: z.ZodObject<{
|
||||
cloudflare: "cloudflare";
|
||||
globalping: "globalping";
|
||||
}>>;
|
||||
health_check_providers: z.ZodCatch<z.ZodPipe<z.ZodArray<z.ZodEnum<{
|
||||
health_check_providers: z.ZodCatch<z.ZodPreprocess<z.ZodPipe<z.ZodArray<z.ZodEnum<{
|
||||
local: "local";
|
||||
cloudflare: "cloudflare";
|
||||
globalping: "globalping";
|
||||
}>>, z.ZodTransform<("local" | "cloudflare" | "globalping")[] | readonly ["local"], ("local" | "cloudflare" | "globalping")[]>>>;
|
||||
}>>, z.ZodTransform<("local" | "cloudflare" | "globalping")[] | readonly ["local"], ("local" | "cloudflare" | "globalping")[]>>>>;
|
||||
health_check_aggregate: z.ZodCatch<z.ZodEnum<{
|
||||
any: "any";
|
||||
all: "all";
|
||||
@@ -932,16 +949,21 @@ declare const serviceGroupViewSchema: z.ZodObject<{
|
||||
cloudflare: "cloudflare";
|
||||
globalping: "globalping";
|
||||
}>>;
|
||||
health_check_providers: z.ZodCatch<z.ZodPipe<z.ZodArray<z.ZodEnum<{
|
||||
health_check_providers: z.ZodCatch<z.ZodPreprocess<z.ZodPipe<z.ZodArray<z.ZodEnum<{
|
||||
local: "local";
|
||||
cloudflare: "cloudflare";
|
||||
globalping: "globalping";
|
||||
}>>, z.ZodTransform<("local" | "cloudflare" | "globalping")[] | readonly ["local"], ("local" | "cloudflare" | "globalping")[]>>>;
|
||||
}>>, z.ZodTransform<("local" | "cloudflare" | "globalping")[] | readonly ["local"], ("local" | "cloudflare" | "globalping")[]>>>>;
|
||||
health_check_aggregate: z.ZodCatch<z.ZodEnum<{
|
||||
any: "any";
|
||||
all: "all";
|
||||
majority: "majority";
|
||||
}>>;
|
||||
cert_monitoring: z.ZodDefault<z.ZodEnum<{
|
||||
auto: "auto";
|
||||
required: "required";
|
||||
skipped: "skipped";
|
||||
}>>;
|
||||
sync_status: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
||||
}, z.core.$strip>, z.ZodTransform<{
|
||||
target_ips: string[];
|
||||
@@ -966,6 +988,7 @@ declare const serviceGroupViewSchema: z.ZodObject<{
|
||||
health_check_provider: "local" | "cloudflare" | "globalping";
|
||||
health_check_providers: ("local" | "cloudflare" | "globalping")[] | readonly ["local"];
|
||||
health_check_aggregate: "any" | "all" | "majority";
|
||||
cert_monitoring: "auto" | "required" | "skipped";
|
||||
sync_status: string | null;
|
||||
target_ip?: string | null | undefined;
|
||||
}, {
|
||||
@@ -987,6 +1010,7 @@ declare const serviceGroupViewSchema: z.ZodObject<{
|
||||
health_check_provider: "local" | "cloudflare" | "globalping";
|
||||
health_check_providers: ("local" | "cloudflare" | "globalping")[] | readonly ["local"];
|
||||
health_check_aggregate: "any" | "all" | "majority";
|
||||
cert_monitoring: "auto" | "required" | "skipped";
|
||||
sync_status: string | null;
|
||||
target_ips?: string[] | undefined;
|
||||
target_ip?: string | null | undefined;
|
||||
@@ -1073,11 +1097,11 @@ declare const serviceGroupsResponseSchema: z.ZodObject<{
|
||||
cloudflare: "cloudflare";
|
||||
globalping: "globalping";
|
||||
}>>;
|
||||
health_check_providers: z.ZodCatch<z.ZodPipe<z.ZodArray<z.ZodEnum<{
|
||||
health_check_providers: z.ZodCatch<z.ZodPreprocess<z.ZodPipe<z.ZodArray<z.ZodEnum<{
|
||||
local: "local";
|
||||
cloudflare: "cloudflare";
|
||||
globalping: "globalping";
|
||||
}>>, z.ZodTransform<("local" | "cloudflare" | "globalping")[] | readonly ["local"], ("local" | "cloudflare" | "globalping")[]>>>;
|
||||
}>>, z.ZodTransform<("local" | "cloudflare" | "globalping")[] | readonly ["local"], ("local" | "cloudflare" | "globalping")[]>>>>;
|
||||
health_check_aggregate: z.ZodCatch<z.ZodEnum<{
|
||||
any: "any";
|
||||
all: "all";
|
||||
@@ -1136,16 +1160,21 @@ declare const serviceGroupsResponseSchema: z.ZodObject<{
|
||||
cloudflare: "cloudflare";
|
||||
globalping: "globalping";
|
||||
}>>;
|
||||
health_check_providers: z.ZodCatch<z.ZodPipe<z.ZodArray<z.ZodEnum<{
|
||||
health_check_providers: z.ZodCatch<z.ZodPreprocess<z.ZodPipe<z.ZodArray<z.ZodEnum<{
|
||||
local: "local";
|
||||
cloudflare: "cloudflare";
|
||||
globalping: "globalping";
|
||||
}>>, z.ZodTransform<("local" | "cloudflare" | "globalping")[] | readonly ["local"], ("local" | "cloudflare" | "globalping")[]>>>;
|
||||
}>>, z.ZodTransform<("local" | "cloudflare" | "globalping")[] | readonly ["local"], ("local" | "cloudflare" | "globalping")[]>>>>;
|
||||
health_check_aggregate: z.ZodCatch<z.ZodEnum<{
|
||||
any: "any";
|
||||
all: "all";
|
||||
majority: "majority";
|
||||
}>>;
|
||||
cert_monitoring: z.ZodDefault<z.ZodEnum<{
|
||||
auto: "auto";
|
||||
required: "required";
|
||||
skipped: "skipped";
|
||||
}>>;
|
||||
sync_status: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
||||
}, z.core.$strip>, z.ZodTransform<{
|
||||
target_ips: string[];
|
||||
@@ -1170,6 +1199,7 @@ declare const serviceGroupsResponseSchema: z.ZodObject<{
|
||||
health_check_provider: "local" | "cloudflare" | "globalping";
|
||||
health_check_providers: ("local" | "cloudflare" | "globalping")[] | readonly ["local"];
|
||||
health_check_aggregate: "any" | "all" | "majority";
|
||||
cert_monitoring: "auto" | "required" | "skipped";
|
||||
sync_status: string | null;
|
||||
target_ip?: string | null | undefined;
|
||||
}, {
|
||||
@@ -1191,6 +1221,7 @@ declare const serviceGroupsResponseSchema: z.ZodObject<{
|
||||
health_check_provider: "local" | "cloudflare" | "globalping";
|
||||
health_check_providers: ("local" | "cloudflare" | "globalping")[] | readonly ["local"];
|
||||
health_check_aggregate: "any" | "all" | "majority";
|
||||
cert_monitoring: "auto" | "required" | "skipped";
|
||||
sync_status: string | null;
|
||||
target_ips?: string[] | undefined;
|
||||
target_ip?: string | null | undefined;
|
||||
@@ -1291,16 +1322,21 @@ declare const serviceGroupsResponseSchema: z.ZodObject<{
|
||||
cloudflare: "cloudflare";
|
||||
globalping: "globalping";
|
||||
}>>;
|
||||
health_check_providers: z.ZodCatch<z.ZodPipe<z.ZodArray<z.ZodEnum<{
|
||||
health_check_providers: z.ZodCatch<z.ZodPreprocess<z.ZodPipe<z.ZodArray<z.ZodEnum<{
|
||||
local: "local";
|
||||
cloudflare: "cloudflare";
|
||||
globalping: "globalping";
|
||||
}>>, z.ZodTransform<("local" | "cloudflare" | "globalping")[] | readonly ["local"], ("local" | "cloudflare" | "globalping")[]>>>;
|
||||
}>>, z.ZodTransform<("local" | "cloudflare" | "globalping")[] | readonly ["local"], ("local" | "cloudflare" | "globalping")[]>>>>;
|
||||
health_check_aggregate: z.ZodCatch<z.ZodEnum<{
|
||||
any: "any";
|
||||
all: "all";
|
||||
majority: "majority";
|
||||
}>>;
|
||||
cert_monitoring: z.ZodDefault<z.ZodEnum<{
|
||||
auto: "auto";
|
||||
required: "required";
|
||||
skipped: "skipped";
|
||||
}>>;
|
||||
sync_status: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
||||
}, z.core.$strip>, z.ZodTransform<{
|
||||
target_ips: string[];
|
||||
@@ -1325,6 +1361,7 @@ declare const serviceGroupsResponseSchema: z.ZodObject<{
|
||||
health_check_provider: "local" | "cloudflare" | "globalping";
|
||||
health_check_providers: ("local" | "cloudflare" | "globalping")[] | readonly ["local"];
|
||||
health_check_aggregate: "any" | "all" | "majority";
|
||||
cert_monitoring: "auto" | "required" | "skipped";
|
||||
sync_status: string | null;
|
||||
target_ip?: string | null | undefined;
|
||||
}, {
|
||||
@@ -1346,6 +1383,7 @@ declare const serviceGroupsResponseSchema: z.ZodObject<{
|
||||
health_check_provider: "local" | "cloudflare" | "globalping";
|
||||
health_check_providers: ("local" | "cloudflare" | "globalping")[] | readonly ["local"];
|
||||
health_check_aggregate: "any" | "all" | "majority";
|
||||
cert_monitoring: "auto" | "required" | "skipped";
|
||||
sync_status: string | null;
|
||||
target_ips?: string[] | undefined;
|
||||
target_ip?: string | null | undefined;
|
||||
@@ -1471,6 +1509,11 @@ declare const serviceBindingSchema: z.ZodPipe<z.ZodObject<{
|
||||
health_check_interval_sec: z.ZodDefault<z.ZodNumber>;
|
||||
health_check_timeout_ms: z.ZodDefault<z.ZodNumber>;
|
||||
health_check_verify_tls: z.ZodDefault<z.ZodCoercedBoolean<unknown>>;
|
||||
cert_monitoring: z.ZodDefault<z.ZodEnum<{
|
||||
auto: "auto";
|
||||
required: "required";
|
||||
skipped: "skipped";
|
||||
}>>;
|
||||
sync_status: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
||||
created_at: z.ZodString;
|
||||
updated_at: z.ZodString;
|
||||
@@ -1498,6 +1541,7 @@ declare const serviceBindingSchema: z.ZodPipe<z.ZodObject<{
|
||||
health_check_interval_sec: number;
|
||||
health_check_timeout_ms: number;
|
||||
health_check_verify_tls: boolean;
|
||||
cert_monitoring: "auto" | "required" | "skipped";
|
||||
sync_status: string | null;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
@@ -1522,6 +1566,7 @@ declare const serviceBindingSchema: z.ZodPipe<z.ZodObject<{
|
||||
health_check_interval_sec: number;
|
||||
health_check_timeout_ms: number;
|
||||
health_check_verify_tls: boolean;
|
||||
cert_monitoring: "auto" | "required" | "skipped";
|
||||
sync_status: string | null;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
@@ -1549,6 +1594,8 @@ declare const certificateSchema: z.ZodObject<{
|
||||
id: z.ZodNumber;
|
||||
domain_id: z.ZodNumber;
|
||||
subdomain_id: z.ZodNullable<z.ZodNumber>;
|
||||
service_id: z.ZodDefault<z.ZodOptional<z.ZodNullable<z.ZodNumber>>>;
|
||||
service_name: z.ZodDefault<z.ZodOptional<z.ZodNullable<z.ZodString>>>;
|
||||
hostname: z.ZodString;
|
||||
expires_at: z.ZodNullable<z.ZodString>;
|
||||
last_checked_at: z.ZodNullable<z.ZodString>;
|
||||
@@ -1557,6 +1604,22 @@ declare const certificateSchema: z.ZodObject<{
|
||||
created_at: z.ZodString;
|
||||
updated_at: z.ZodString;
|
||||
}, z.core.$strip>;
|
||||
declare const serviceCertificateRowSchema: z.ZodObject<{
|
||||
binding_id: z.ZodNumber;
|
||||
domain_id: z.ZodNumber;
|
||||
service_id: z.ZodNumber;
|
||||
hostname: z.ZodString;
|
||||
cert_monitoring: z.ZodEnum<{
|
||||
auto: "auto";
|
||||
required: "required";
|
||||
skipped: "skipped";
|
||||
}>;
|
||||
id: z.ZodNullable<z.ZodNumber>;
|
||||
status: z.ZodString;
|
||||
expires_at: z.ZodNullable<z.ZodString>;
|
||||
last_checked_at: z.ZodNullable<z.ZodString>;
|
||||
last_error: z.ZodNullable<z.ZodString>;
|
||||
}, z.core.$strip>;
|
||||
type Group = z.infer<typeof groupSchema>;
|
||||
type GroupWithStats = z.infer<typeof groupWithStatsSchema>;
|
||||
type Service = z.infer<typeof serviceSchema>;
|
||||
@@ -1569,12 +1632,13 @@ type Domain = z.infer<typeof domainSchema>;
|
||||
type DomainListItem = z.infer<typeof domainListItemSchema>;
|
||||
type DnsRecord = z.infer<typeof dnsRecordSchema>;
|
||||
type Certificate = z.infer<typeof certificateSchema>;
|
||||
type ServiceCertificateRow = z.infer<typeof serviceCertificateRowSchema>;
|
||||
declare const createGroupSchema: z.ZodObject<{
|
||||
name: z.ZodString;
|
||||
slug: z.ZodString;
|
||||
}, z.core.$strip>;
|
||||
declare const healthCheckConfigSchema: z.ZodObject<{
|
||||
health_check_enabled: z.ZodOptional<z.ZodBoolean>;
|
||||
health_check_enabled: z.ZodOptional<z.ZodCoercedBoolean<unknown>>;
|
||||
health_check_type: z.ZodOptional<z.ZodEnum<{
|
||||
tcp: "tcp";
|
||||
http: "http";
|
||||
@@ -1586,17 +1650,17 @@ declare const healthCheckConfigSchema: z.ZodObject<{
|
||||
health_check_expected_status: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
||||
health_check_interval_sec: z.ZodOptional<z.ZodNumber>;
|
||||
health_check_timeout_ms: z.ZodOptional<z.ZodNumber>;
|
||||
health_check_verify_tls: z.ZodOptional<z.ZodBoolean>;
|
||||
health_check_verify_tls: z.ZodOptional<z.ZodCoercedBoolean<unknown>>;
|
||||
health_check_provider: z.ZodOptional<z.ZodEnum<{
|
||||
local: "local";
|
||||
cloudflare: "cloudflare";
|
||||
globalping: "globalping";
|
||||
}>>;
|
||||
health_check_providers: z.ZodOptional<z.ZodPipe<z.ZodArray<z.ZodEnum<{
|
||||
health_check_providers: z.ZodOptional<z.ZodPreprocess<z.ZodPipe<z.ZodArray<z.ZodEnum<{
|
||||
local: "local";
|
||||
cloudflare: "cloudflare";
|
||||
globalping: "globalping";
|
||||
}>>, z.ZodTransform<("local" | "cloudflare" | "globalping")[] | readonly ["local"], ("local" | "cloudflare" | "globalping")[]>>>;
|
||||
}>>, z.ZodTransform<("local" | "cloudflare" | "globalping")[] | readonly ["local"], ("local" | "cloudflare" | "globalping")[]>>>>;
|
||||
health_check_aggregate: z.ZodOptional<z.ZodEnum<{
|
||||
any: "any";
|
||||
all: "all";
|
||||
@@ -1616,7 +1680,7 @@ declare const createServiceWithConfigSchema: z.ZodObject<{
|
||||
lb_weight: z.ZodOptional<z.ZodNumber>;
|
||||
lb_priority: z.ZodOptional<z.ZodNumber>;
|
||||
domains: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
||||
health_check_enabled: z.ZodOptional<z.ZodBoolean>;
|
||||
health_check_enabled: z.ZodOptional<z.ZodCoercedBoolean<unknown>>;
|
||||
health_check_type: z.ZodOptional<z.ZodEnum<{
|
||||
tcp: "tcp";
|
||||
http: "http";
|
||||
@@ -1628,17 +1692,17 @@ declare const createServiceWithConfigSchema: z.ZodObject<{
|
||||
health_check_expected_status: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
||||
health_check_interval_sec: z.ZodOptional<z.ZodNumber>;
|
||||
health_check_timeout_ms: z.ZodOptional<z.ZodNumber>;
|
||||
health_check_verify_tls: z.ZodOptional<z.ZodBoolean>;
|
||||
health_check_verify_tls: z.ZodOptional<z.ZodCoercedBoolean<unknown>>;
|
||||
health_check_provider: z.ZodOptional<z.ZodEnum<{
|
||||
local: "local";
|
||||
cloudflare: "cloudflare";
|
||||
globalping: "globalping";
|
||||
}>>;
|
||||
health_check_providers: z.ZodOptional<z.ZodPipe<z.ZodArray<z.ZodEnum<{
|
||||
health_check_providers: z.ZodOptional<z.ZodPreprocess<z.ZodPipe<z.ZodArray<z.ZodEnum<{
|
||||
local: "local";
|
||||
cloudflare: "cloudflare";
|
||||
globalping: "globalping";
|
||||
}>>, z.ZodTransform<("local" | "cloudflare" | "globalping")[] | readonly ["local"], ("local" | "cloudflare" | "globalping")[]>>>;
|
||||
}>>, z.ZodTransform<("local" | "cloudflare" | "globalping")[] | readonly ["local"], ("local" | "cloudflare" | "globalping")[]>>>>;
|
||||
health_check_aggregate: z.ZodOptional<z.ZodEnum<{
|
||||
any: "any";
|
||||
all: "all";
|
||||
@@ -1819,7 +1883,7 @@ declare const updateServiceConfigSchema: z.ZodObject<{
|
||||
lb_weight: z.ZodOptional<z.ZodNumber>;
|
||||
lb_priority: z.ZodOptional<z.ZodNumber>;
|
||||
domains: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
||||
health_check_enabled: z.ZodOptional<z.ZodBoolean>;
|
||||
health_check_enabled: z.ZodOptional<z.ZodCoercedBoolean<unknown>>;
|
||||
health_check_type: z.ZodOptional<z.ZodEnum<{
|
||||
tcp: "tcp";
|
||||
http: "http";
|
||||
@@ -1831,17 +1895,17 @@ declare const updateServiceConfigSchema: z.ZodObject<{
|
||||
health_check_expected_status: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
||||
health_check_interval_sec: z.ZodOptional<z.ZodNumber>;
|
||||
health_check_timeout_ms: z.ZodOptional<z.ZodNumber>;
|
||||
health_check_verify_tls: z.ZodOptional<z.ZodBoolean>;
|
||||
health_check_verify_tls: z.ZodOptional<z.ZodCoercedBoolean<unknown>>;
|
||||
health_check_provider: z.ZodOptional<z.ZodEnum<{
|
||||
local: "local";
|
||||
cloudflare: "cloudflare";
|
||||
globalping: "globalping";
|
||||
}>>;
|
||||
health_check_providers: z.ZodOptional<z.ZodPipe<z.ZodArray<z.ZodEnum<{
|
||||
health_check_providers: z.ZodOptional<z.ZodPreprocess<z.ZodPipe<z.ZodArray<z.ZodEnum<{
|
||||
local: "local";
|
||||
cloudflare: "cloudflare";
|
||||
globalping: "globalping";
|
||||
}>>, z.ZodTransform<("local" | "cloudflare" | "globalping")[] | readonly ["local"], ("local" | "cloudflare" | "globalping")[]>>>;
|
||||
}>>, z.ZodTransform<("local" | "cloudflare" | "globalping")[] | readonly ["local"], ("local" | "cloudflare" | "globalping")[]>>>>;
|
||||
health_check_aggregate: z.ZodOptional<z.ZodEnum<{
|
||||
any: "any";
|
||||
all: "all";
|
||||
@@ -1861,7 +1925,7 @@ declare const updateServiceConfigSchema: z.ZodObject<{
|
||||
}, z.core.$strip>;
|
||||
type UpdateServiceConfigInput = z.infer<typeof updateServiceConfigSchema>;
|
||||
declare const createServiceGroupSchema: z.ZodObject<{
|
||||
health_check_enabled: z.ZodOptional<z.ZodBoolean>;
|
||||
health_check_enabled: z.ZodOptional<z.ZodCoercedBoolean<unknown>>;
|
||||
health_check_type: z.ZodOptional<z.ZodEnum<{
|
||||
tcp: "tcp";
|
||||
http: "http";
|
||||
@@ -1873,17 +1937,17 @@ declare const createServiceGroupSchema: z.ZodObject<{
|
||||
health_check_expected_status: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
||||
health_check_interval_sec: z.ZodOptional<z.ZodNumber>;
|
||||
health_check_timeout_ms: z.ZodOptional<z.ZodNumber>;
|
||||
health_check_verify_tls: z.ZodOptional<z.ZodBoolean>;
|
||||
health_check_verify_tls: z.ZodOptional<z.ZodCoercedBoolean<unknown>>;
|
||||
health_check_provider: z.ZodOptional<z.ZodEnum<{
|
||||
local: "local";
|
||||
cloudflare: "cloudflare";
|
||||
globalping: "globalping";
|
||||
}>>;
|
||||
health_check_providers: z.ZodOptional<z.ZodPipe<z.ZodArray<z.ZodEnum<{
|
||||
health_check_providers: z.ZodOptional<z.ZodPreprocess<z.ZodPipe<z.ZodArray<z.ZodEnum<{
|
||||
local: "local";
|
||||
cloudflare: "cloudflare";
|
||||
globalping: "globalping";
|
||||
}>>, z.ZodTransform<("local" | "cloudflare" | "globalping")[] | readonly ["local"], ("local" | "cloudflare" | "globalping")[]>>>;
|
||||
}>>, z.ZodTransform<("local" | "cloudflare" | "globalping")[] | readonly ["local"], ("local" | "cloudflare" | "globalping")[]>>>>;
|
||||
health_check_aggregate: z.ZodOptional<z.ZodEnum<{
|
||||
any: "any";
|
||||
all: "all";
|
||||
@@ -1906,7 +1970,7 @@ declare const createServiceGroupSchema: z.ZodObject<{
|
||||
}>>;
|
||||
}, z.core.$strip>;
|
||||
declare const updateServiceGroupSchema: z.ZodObject<{
|
||||
health_check_enabled: z.ZodOptional<z.ZodBoolean>;
|
||||
health_check_enabled: z.ZodOptional<z.ZodCoercedBoolean<unknown>>;
|
||||
health_check_type: z.ZodOptional<z.ZodEnum<{
|
||||
tcp: "tcp";
|
||||
http: "http";
|
||||
@@ -1918,17 +1982,17 @@ declare const updateServiceGroupSchema: z.ZodObject<{
|
||||
health_check_expected_status: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
||||
health_check_interval_sec: z.ZodOptional<z.ZodNumber>;
|
||||
health_check_timeout_ms: z.ZodOptional<z.ZodNumber>;
|
||||
health_check_verify_tls: z.ZodOptional<z.ZodBoolean>;
|
||||
health_check_verify_tls: z.ZodOptional<z.ZodCoercedBoolean<unknown>>;
|
||||
health_check_provider: z.ZodOptional<z.ZodEnum<{
|
||||
local: "local";
|
||||
cloudflare: "cloudflare";
|
||||
globalping: "globalping";
|
||||
}>>;
|
||||
health_check_providers: z.ZodOptional<z.ZodPipe<z.ZodArray<z.ZodEnum<{
|
||||
health_check_providers: z.ZodOptional<z.ZodPreprocess<z.ZodPipe<z.ZodArray<z.ZodEnum<{
|
||||
local: "local";
|
||||
cloudflare: "cloudflare";
|
||||
globalping: "globalping";
|
||||
}>>, z.ZodTransform<("local" | "cloudflare" | "globalping")[] | readonly ["local"], ("local" | "cloudflare" | "globalping")[]>>>;
|
||||
}>>, z.ZodTransform<("local" | "cloudflare" | "globalping")[] | readonly ["local"], ("local" | "cloudflare" | "globalping")[]>>>>;
|
||||
health_check_aggregate: z.ZodOptional<z.ZodEnum<{
|
||||
any: "any";
|
||||
all: "all";
|
||||
@@ -2348,4 +2412,4 @@ declare const ingestAuditEventSchema: z.ZodObject<{
|
||||
}, z.core.$strip>;
|
||||
type IngestAuditEvent = z.infer<typeof ingestAuditEventSchema>;
|
||||
|
||||
export { AUDIT_SEVERITIES, AUDIT_SOURCE_APPS, AUDIT_TARGET_TYPES, type AppSettingsPatch, type AppSwitcherConfig, type AppSwitcherEntry, type AuditListQuery, type AuditLogEntry, type AuditSeverity, type AuditSourceApp, type AuditTargetType, type BulkUpdateDomainsInput, CERT_ERROR, CERT_EXPIRED, CERT_MONITORING_VALUES, CERT_MONITOR_AUTO, CERT_MONITOR_REQUIRED, CERT_MONITOR_SKIPPED, CERT_OK, CERT_UNKNOWN, CERT_WARNING, type CertMonitoring, type Certificate, type CfDnsRecord, type CfHealthCheck, type CfZone, type CfdmBindingSyncItem, type ChangeDomainInput, type ChangeIpInput, type CreateDnsRecordInput, type CreateDnsRecordPayload, type CreateDomainInput, type CreateDomainMonitorInput, type CreateGroupInput, type CreateOriginHealthCheckInput, type CreateServiceBindingInput, type CreateServiceGroupInput, type CreateServiceInput, type CreateServiceNodeInput, type CreateServiceWithConfigInput, type CreateSubdomainInput, type DnsRecord, type Domain, type DomainEnvironment, type DomainListItem, type DomainMonitor, type DomainMonitorResult, type DomainMonitorType, type Group, type GroupWithStats, HEALTH_CHECK_AGGREGATES, HEALTH_CHECK_PROVIDERS, HEALTH_KV_CURSOR_KEY, HEALTH_KV_RESULTS_KEY, HEALTH_KV_TARGETS_KEY, HEALTH_PROBE_BATCH, HEALTH_PROBE_CONCURRENCY, HEALTH_PROBE_KV_TITLE, HEALTH_PROBE_SCRIPT_NAME, HEALTH_STATUS_PROVIDERS, type HealthCheckAggregate, type HealthCheckConfig, type HealthCheckProvider, type HealthCheckScope, type HealthCheckTarget, type HealthCheckType, type HealthProbeLog, type HealthProbeResultItem, type HealthProbeResultsDoc, type HealthProbeTargetItem, type HealthProbeTargetsDoc, type HealthStatusProvider, type HealthStatusQuery, type HealthWorkerStatus, type IngestAuditEvent, type IpHealthState, type IpHealthStatus, type JwtClaims, type LbMode, type LoginInput, type LoginRequest, type LoginResponse, type NodeHealthState, type NotificationLog, type OriginHealthCheck, type OriginHealthCheckRecord, type ParsedFqdn, type PatchDnsRecordPayload, type ReorderServicesInput, SYNC_CONFLICT, SYNC_ERROR, SYNC_PENDING_DELETE, SYNC_PENDING_PUSH, SYNC_SYNCED, type Service, type ServiceBinding, type ServiceBindingView, type ServiceDomainBinding, type ServiceDomainBindingView, type ServiceGroup, type ServiceGroupView, type ServiceGroupsResponse, type ServiceIpHealth, type ServiceNode, type ServiceNodeRecord, type ServiceOverview, type ServiceView, type Subdomain, type SubdomainRecord, type SyncJob, type ToggleEnabledInput, type ToggleServiceIpInput, type UpdateDomainInput, type UpdateServiceConfigInput, type UpdateServiceGroupInput, type UpdateServiceNodeInput, type UpdateSubdomainInput, ValidationError, type VpsTrackerEvent, aggregateHealthOk, appSettingsPatchSchema, appSwitcherConfigSchema, appSwitcherEntrySchema, appSwitcherIconSchema, auditListQuerySchema, auditLogEntrySchema, auditSeveritySchema, auditSourceAppSchema, auditTargetTypeSchema, bindingToFqdn, bulkUpdateDomainsSchema, certMonitoringSchema, certStatusFromExpiry, certificateSchema, cfdmBindingSyncItemSchema, cfdmSyncBindingsBodySchema, changeDomainSchema, changeIpSchema, clampGlobalpingLimit, createDnsRecordSchema, createDomainMonitorSchema, createDomainSchema, createGroupSchema, createOriginHealthCheckSchema, createServiceBindingSchema, createServiceGroupSchema, createServiceNodeSchema, createServiceSchema, createServiceWithConfigSchema, createSubdomainSchema, derivePrimaryProvider, dnsNameToSubdomainLabel, dnsRecordNamesMatch, dnsRecordSchema, domainEnvironmentSchema, domainListItemSchema, domainMonitorResultSchema, domainMonitorSchema, domainMonitorTypeSchema, domainSchema, fqdnToDisplay, groupSchema, groupWithStatsSchema, healthCheckAggregateSchema, healthCheckConfigSchema, healthCheckProviderSchema, healthCheckProvidersSchema, healthCheckScopeSchema, healthCheckTypeSchema, healthProbeLogSchema, healthStatusProviderSchema, healthStatusQuerySchema, ingestAuditEventSchema, ipHealthStateSchema, ipHealthStatusSchema, isIpLiteral, isValidIpv4, lbModeSchema, loginSchema, nodeHealthStateSchema, normalizeDnsRecordName, normalizeProbeProvider, normalizeStatusProvider, notificationLogSchema, originHealthCheckSchema, parseFqdn, parseGlobalpingLocations, parseHealthAggregate, parseHealthProviders, reorderServicesSchema, serializeHealthProviders, serviceBindingSchema, serviceDomainBindingSchema, serviceGroupSchema, serviceGroupTypeSchema, serviceGroupViewSchema, serviceGroupsResponseSchema, serviceIpHealthSchema, serviceNodeSchema, serviceSchema, serviceViewSchema, shouldMonitorService, subdomainLabelToFqdn, subdomainSchema, targetHasProvider, targetProviders, toggleEnabledSchema, toggleServiceIpSchema, uniqueHealthProviders, updateDomainGroupSchema, updateDomainSchema, updateServiceConfigSchema, updateServiceGroupSchema, updateServiceNodeSchema, updateSubdomainSchema, validateDnsRecord, vpsTrackerEventSchema };
|
||||
export { AUDIT_SEVERITIES, AUDIT_SOURCE_APPS, AUDIT_TARGET_TYPES, type AppSettingsPatch, type AppSwitcherConfig, type AppSwitcherEntry, type AuditListQuery, type AuditLogEntry, type AuditSeverity, type AuditSourceApp, type AuditTargetType, type BulkUpdateDomainsInput, CERT_ERROR, CERT_EXPIRED, CERT_MONITORING_VALUES, CERT_MONITOR_AUTO, CERT_MONITOR_REQUIRED, CERT_MONITOR_SKIPPED, CERT_OK, CERT_UNKNOWN, CERT_WARNING, type CertMonitoring, type Certificate, type CfDnsRecord, type CfHealthCheck, type CfZone, type CfdmBindingSyncItem, type ChangeDomainInput, type ChangeIpInput, type CreateDnsRecordInput, type CreateDnsRecordPayload, type CreateDomainInput, type CreateDomainMonitorInput, type CreateGroupInput, type CreateOriginHealthCheckInput, type CreateServiceBindingInput, type CreateServiceGroupInput, type CreateServiceInput, type CreateServiceNodeInput, type CreateServiceWithConfigInput, type CreateSubdomainInput, type DnsRecord, type Domain, type DomainEnvironment, type DomainListItem, type DomainMonitor, type DomainMonitorResult, type DomainMonitorType, type Group, type GroupWithStats, HEALTH_CHECK_AGGREGATES, HEALTH_CHECK_PROVIDERS, HEALTH_KV_CURSOR_KEY, HEALTH_KV_RESULTS_KEY, HEALTH_KV_TARGETS_KEY, HEALTH_PROBE_BATCH, HEALTH_PROBE_CONCURRENCY, HEALTH_PROBE_KV_TITLE, HEALTH_PROBE_SCRIPT_NAME, HEALTH_STATUS_PROVIDERS, type HealthCheckAggregate, type HealthCheckConfig, type HealthCheckProvider, type HealthCheckScope, type HealthCheckTarget, type HealthCheckType, type HealthProbeLog, type HealthProbeResultItem, type HealthProbeResultsDoc, type HealthProbeTargetItem, type HealthProbeTargetsDoc, type HealthStatusProvider, type HealthStatusQuery, type HealthWorkerStatus, type IngestAuditEvent, type IpHealthState, type IpHealthStatus, type JwtClaims, type LbMode, type LoginInput, type LoginRequest, type LoginResponse, type NodeHealthState, type NotificationLog, type OriginHealthCheck, type OriginHealthCheckRecord, type ParsedFqdn, type PatchDnsRecordPayload, type ReorderServicesInput, SYNC_CONFLICT, SYNC_ERROR, SYNC_PENDING_DELETE, SYNC_PENDING_PUSH, SYNC_SYNCED, type Service, type ServiceBinding, type ServiceBindingView, type ServiceCertificateRow, type ServiceDomainBinding, type ServiceDomainBindingView, type ServiceGroup, type ServiceGroupView, type ServiceGroupsResponse, type ServiceIpHealth, type ServiceNode, type ServiceNodeRecord, type ServiceOverview, type ServiceView, type Subdomain, type SubdomainRecord, type SyncJob, type ToggleEnabledInput, type ToggleServiceIpInput, type UpdateDomainInput, type UpdateServiceConfigInput, type UpdateServiceGroupInput, type UpdateServiceNodeInput, type UpdateSubdomainInput, ValidationError, type VpsTrackerEvent, aggregateHealthOk, appSettingsPatchSchema, appSwitcherConfigSchema, appSwitcherEntrySchema, appSwitcherIconSchema, auditListQuerySchema, auditLogEntrySchema, auditSeveritySchema, auditSourceAppSchema, auditTargetTypeSchema, bindingToFqdn, bulkUpdateDomainsSchema, certMonitoringSchema, certStatusFromExpiry, certificateSchema, cfdmBindingSyncItemSchema, cfdmSyncBindingsBodySchema, changeDomainSchema, changeIpSchema, clampGlobalpingLimit, createDnsRecordSchema, createDomainMonitorSchema, createDomainSchema, createGroupSchema, createOriginHealthCheckSchema, createServiceBindingSchema, createServiceGroupSchema, createServiceNodeSchema, createServiceSchema, createServiceWithConfigSchema, createSubdomainSchema, derivePrimaryProvider, dnsNameToSubdomainLabel, dnsRecordNamesMatch, dnsRecordSchema, domainEnvironmentSchema, domainListItemSchema, domainMonitorResultSchema, domainMonitorSchema, domainMonitorTypeSchema, domainSchema, fqdnToDisplay, groupSchema, groupWithStatsSchema, healthCheckAggregateSchema, healthCheckConfigSchema, healthCheckProviderSchema, healthCheckProvidersSchema, healthCheckScopeSchema, healthCheckTypeSchema, healthProbeLogSchema, healthStatusProviderSchema, healthStatusQuerySchema, ingestAuditEventSchema, ipHealthStateSchema, ipHealthStatusSchema, isIpLiteral, isValidIpv4, lbModeSchema, loginSchema, nodeHealthStateSchema, normalizeDnsRecordName, normalizeProbeProvider, normalizeStatusProvider, notificationLogSchema, originHealthCheckSchema, parseFqdn, parseGlobalpingLocations, parseHealthAggregate, parseHealthProviders, reorderServicesSchema, serializeHealthProviders, serviceBindingSchema, serviceCertificateRowSchema, serviceDomainBindingSchema, serviceGroupSchema, serviceGroupTypeSchema, serviceGroupViewSchema, serviceGroupsResponseSchema, serviceIpHealthSchema, serviceNodeSchema, serviceSchema, serviceViewSchema, shouldMonitorService, subdomainLabelToFqdn, subdomainSchema, targetHasProvider, targetProviders, toggleEnabledSchema, toggleServiceIpSchema, uniqueHealthProviders, updateDomainGroupSchema, updateDomainSchema, updateServiceConfigSchema, updateServiceGroupSchema, updateServiceNodeSchema, updateSubdomainSchema, validateDnsRecord, vpsTrackerEventSchema };
|
||||
|
||||
Vendored
+29
-6
@@ -276,10 +276,16 @@ var nodeHealthStateSchema = z.enum([
|
||||
var healthCheckProviderSchema = z.enum(HEALTH_CHECK_PROVIDERS);
|
||||
var healthStatusProviderSchema = z.enum(HEALTH_STATUS_PROVIDERS);
|
||||
var healthCheckAggregateSchema = z.enum(HEALTH_CHECK_AGGREGATES);
|
||||
var healthCheckProvidersSchema = z.array(healthCheckProviderSchema).min(1).transform((arr) => {
|
||||
const unique = uniqueHealthProviders(arr);
|
||||
return unique.length > 0 ? unique : ["local"];
|
||||
});
|
||||
var healthCheckProvidersSchema = z.preprocess(
|
||||
(value) => {
|
||||
if (value === void 0) return void 0;
|
||||
return Array.isArray(value) ? value : parseHealthProviders(value);
|
||||
},
|
||||
z.array(healthCheckProviderSchema).min(1).transform((arr) => {
|
||||
const unique = uniqueHealthProviders(arr);
|
||||
return unique.length > 0 ? unique : ["local"];
|
||||
})
|
||||
);
|
||||
var healthCheckScopeSchema = z.enum(["binding", "group"]);
|
||||
var ipHealthStatusSchema = z.object({
|
||||
scope: healthCheckScopeSchema,
|
||||
@@ -392,6 +398,7 @@ var serviceDomainBindingSchema = z.object({
|
||||
health_check_provider: healthCheckProviderSchema.catch("local"),
|
||||
health_check_providers: healthCheckProvidersSchema.catch(["local"]),
|
||||
health_check_aggregate: healthCheckAggregateSchema.catch("majority"),
|
||||
cert_monitoring: certMonitoringSchema.default("auto"),
|
||||
sync_status: z.string().nullable().default(null)
|
||||
}).transform((binding) => ({
|
||||
...binding,
|
||||
@@ -465,6 +472,7 @@ var serviceBindingSchema = z.object({
|
||||
health_check_interval_sec: z.number().default(30),
|
||||
health_check_timeout_ms: z.number().default(3e3),
|
||||
health_check_verify_tls: z.coerce.boolean().default(false),
|
||||
cert_monitoring: certMonitoringSchema.default("auto"),
|
||||
sync_status: z.string().nullable().default(null),
|
||||
created_at: z.string(),
|
||||
updated_at: z.string()
|
||||
@@ -494,6 +502,8 @@ var certificateSchema = z.object({
|
||||
id: z.number(),
|
||||
domain_id: z.number(),
|
||||
subdomain_id: z.number().nullable(),
|
||||
service_id: z.number().nullable().optional().default(null),
|
||||
service_name: z.string().nullable().optional().default(null),
|
||||
hostname: z.string(),
|
||||
expires_at: z.string().nullable(),
|
||||
last_checked_at: z.string().nullable(),
|
||||
@@ -502,6 +512,18 @@ var certificateSchema = z.object({
|
||||
created_at: z.string(),
|
||||
updated_at: z.string()
|
||||
});
|
||||
var serviceCertificateRowSchema = z.object({
|
||||
binding_id: z.number(),
|
||||
domain_id: z.number(),
|
||||
service_id: z.number(),
|
||||
hostname: z.string(),
|
||||
cert_monitoring: certMonitoringSchema,
|
||||
id: z.number().nullable(),
|
||||
status: z.string(),
|
||||
expires_at: z.string().nullable(),
|
||||
last_checked_at: z.string().nullable(),
|
||||
last_error: z.string().nullable()
|
||||
});
|
||||
var createGroupSchema = z.object({
|
||||
name: z.string().min(1, "\u0423\u043A\u0430\u0436\u0438\u0442\u0435 \u043D\u0430\u0437\u0432\u0430\u043D\u0438\u0435"),
|
||||
slug: z.string().min(1, "\u0423\u043A\u0430\u0436\u0438\u0442\u0435 slug")
|
||||
@@ -512,14 +534,14 @@ var ipv4Schema = z.string().regex(
|
||||
);
|
||||
var nodeAddressSchema = z.string().min(1, "\u0423\u043A\u0430\u0436\u0438\u0442\u0435 IP \u0438\u043B\u0438 hostname").max(255);
|
||||
var healthCheckConfigFields = {
|
||||
health_check_enabled: z.boolean().optional(),
|
||||
health_check_enabled: z.coerce.boolean().optional(),
|
||||
health_check_type: healthCheckTypeSchema.optional(),
|
||||
health_check_port: z.number().int().min(1).max(65535).nullable().optional(),
|
||||
health_check_path: z.string().nullable().optional(),
|
||||
health_check_expected_status: z.number().int().min(100).max(599).nullable().optional(),
|
||||
health_check_interval_sec: z.number().int().min(5).max(3600).optional(),
|
||||
health_check_timeout_ms: z.number().int().min(100).max(3e4).optional(),
|
||||
health_check_verify_tls: z.boolean().optional(),
|
||||
health_check_verify_tls: z.coerce.boolean().optional(),
|
||||
health_check_provider: healthCheckProviderSchema.optional(),
|
||||
health_check_providers: healthCheckProvidersSchema.optional(),
|
||||
health_check_aggregate: healthCheckAggregateSchema.optional()
|
||||
@@ -1029,6 +1051,7 @@ export {
|
||||
reorderServicesSchema,
|
||||
serializeHealthProviders,
|
||||
serviceBindingSchema,
|
||||
serviceCertificateRowSchema,
|
||||
serviceDomainBindingSchema,
|
||||
serviceGroupSchema,
|
||||
serviceGroupTypeSchema,
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
HEALTH_CHECK_AGGREGATES,
|
||||
HEALTH_CHECK_PROVIDERS,
|
||||
HEALTH_STATUS_PROVIDERS,
|
||||
parseHealthProviders,
|
||||
uniqueHealthProviders,
|
||||
} from './health-providers.js'
|
||||
|
||||
@@ -41,13 +42,16 @@ export const healthStatusProviderSchema = z.enum(HEALTH_STATUS_PROVIDERS)
|
||||
|
||||
export const healthCheckAggregateSchema = z.enum(HEALTH_CHECK_AGGREGATES)
|
||||
|
||||
export const healthCheckProvidersSchema = z
|
||||
.array(healthCheckProviderSchema)
|
||||
.min(1)
|
||||
.transform((arr) => {
|
||||
export const healthCheckProvidersSchema = z.preprocess(
|
||||
(value) => {
|
||||
if (value === undefined) return undefined
|
||||
return Array.isArray(value) ? value : parseHealthProviders(value)
|
||||
},
|
||||
z.array(healthCheckProviderSchema).min(1).transform((arr) => {
|
||||
const unique = uniqueHealthProviders(arr)
|
||||
return unique.length > 0 ? unique : (['local'] as const)
|
||||
})
|
||||
}),
|
||||
)
|
||||
|
||||
export const healthCheckScopeSchema = z.enum(['binding', 'group'])
|
||||
export type HealthCheckScope = z.infer<typeof healthCheckScopeSchema>
|
||||
@@ -361,14 +365,14 @@ const nodeAddressSchema = z
|
||||
.max(255)
|
||||
|
||||
const healthCheckConfigFields = {
|
||||
health_check_enabled: z.boolean().optional(),
|
||||
health_check_enabled: z.coerce.boolean().optional(),
|
||||
health_check_type: healthCheckTypeSchema.optional(),
|
||||
health_check_port: z.number().int().min(1).max(65535).nullable().optional(),
|
||||
health_check_path: z.string().nullable().optional(),
|
||||
health_check_expected_status: z.number().int().min(100).max(599).nullable().optional(),
|
||||
health_check_interval_sec: z.number().int().min(5).max(3600).optional(),
|
||||
health_check_timeout_ms: z.number().int().min(100).max(30000).optional(),
|
||||
health_check_verify_tls: z.boolean().optional(),
|
||||
health_check_verify_tls: z.coerce.boolean().optional(),
|
||||
health_check_provider: healthCheckProviderSchema.optional(),
|
||||
health_check_providers: healthCheckProvidersSchema.optional(),
|
||||
health_check_aggregate: healthCheckAggregateSchema.optional(),
|
||||
|
||||
Reference in New Issue
Block a user