refactor(services): update service creation logic and enhance service group schema
Build and Push CFDM Docker Image / build-and-push (push) Successful in 2m2s
Build and Push CFDM Docker Image / create-release (push) Skipped
Build and Push CFDM Docker Image / update-wiki (push) Successful in 6s

- Changed service creation to default `enabled` to true in the database schema.
- Updated service group schema to set default values for `icon`, `domain`, and health check parameters.
- Refactored service routes to use `await` for fetching service views, ensuring proper asynchronous handling.
- Improved error handling in service groups query to provide clearer feedback on response validation.

This commit enhances the overall service management experience by ensuring services are enabled by default and improving the robustness of the service group schema.
This commit is contained in:
Denozordec
2026-08-07 13:18:47 +07:00
parent 3fb8480f8a
commit ce8393e0c3
12 changed files with 346 additions and 186 deletions
+3 -2
View File
@@ -40,7 +40,7 @@ export async function serviceRoutes(app: FastifyInstance) {
body.service_group_id, body.service_group_id,
); );
} }
const view = serviceConfig.getView(request.server.db, service.id); const view = await serviceConfig.getView(request.server.db, service.id);
recordAudit(request.server, request, { recordAudit(request.server, request, {
action: "service.create", action: "service.create",
targetType: "app_resource", targetType: "app_resource",
@@ -56,6 +56,7 @@ export async function serviceRoutes(app: FastifyInstance) {
return serviceConfig.getView(request.server.db, Number(id)); return serviceConfig.getView(request.server.db, Number(id));
}); });
app.patch("/services/:id", async (request) => { app.patch("/services/:id", async (request) => {
const { id } = request.params as { id: string }; const { id } = request.params as { id: string };
const body = updateServiceConfigSchema.parse(request.body); const body = updateServiceConfigSchema.parse(request.body);
@@ -77,7 +78,7 @@ export async function serviceRoutes(app: FastifyInstance) {
app.delete("/services/:id", async (request) => { app.delete("/services/:id", async (request) => {
const { id } = request.params as { id: string }; const { id } = request.params as { id: string };
const view = serviceConfig.getView(request.server.db, Number(id)); const view = await serviceConfig.getView(request.server.db, Number(id));
repos.deleteService(request.server.db, Number(id)); repos.deleteService(request.server.db, Number(id));
recordAudit(request.server, request, { recordAudit(request.server, request, {
action: "service.delete", action: "service.delete",
+13 -52
View File
@@ -823,49 +823,6 @@ async function findOrImportDnsARecord(
); );
} }
async function serviceBindingsExistInDns(
db: Db,
cf: CloudflareClient,
serviceId: number,
): Promise<boolean> {
const bindings = repos.listBindingsByService(db, serviceId);
if (bindings.length === 0) return false;
for (const binding of bindings) {
const cnameTarget = binding.cname_target?.trim() || null;
if (cnameTarget) {
const record = await findOrImportDnsRecord(
db,
cf,
binding.domain_id,
binding.zone_name,
binding.hostname,
"CNAME",
cnameTarget,
);
if (!record) return false;
continue;
}
const targetIps = repos.listBindingIps(db, binding.id);
if (targetIps.length === 0) return false;
for (const ip of targetIps) {
const record = await findOrImportDnsARecord(
db,
cf,
binding.domain_id,
binding.zone_name,
binding.hostname,
ip,
);
if (!record) return false;
}
}
return true;
}
async function syncServiceBindingsToDns( async function syncServiceBindingsToDns(
db: Db, db: Db,
cf: CloudflareClient, cf: CloudflareClient,
@@ -1281,20 +1238,24 @@ export async function updateConfig(
service = repos.getService(db, id); service = repos.getService(db, id);
const remainingBindings = repos.listBindingsByService(db, id); const remainingBindings = repos.listBindingsByService(db, id);
// Creating/editing with FQDN bindings should publish DNS. Previously a
// disabled service stayed off unless records already existed in Cloudflare
// (chicken-and-egg for brand-new services).
if (
req.domains &&
req.domains.length > 0 &&
!service.enabled
) {
repos.setServiceEnabled(db, id, true);
service = repos.getService(db, id);
}
if (shouldPushDns(db, service)) { if (shouldPushDns(db, service)) {
if (remainingBindings.length > 0) { if (remainingBindings.length > 0) {
await syncServiceBindingsToDns(db, cf, id); await syncServiceBindingsToDns(db, cf, id);
} }
await syncGroupDomainForService(db, cf, id); await syncGroupDomainForService(db, cf, id);
} else if (
req.domains &&
req.domains.length > 0 &&
!service.enabled &&
(await serviceBindingsExistInDns(db, cf, id))
) {
repos.setServiceEnabled(db, id, true);
await syncServiceBindingsToDns(db, cf, id);
await syncGroupDomainForService(db, cf, id);
} }
void syncServiceToVpsTracker(db, id, removedBindingIds); void syncServiceToVpsTracker(db, id, removedBindingIds);
+168
View File
@@ -0,0 +1,168 @@
import { describe, expect, it } from "vitest";
import { serviceGroupsResponseSchema } from "@cfdm/shared";
import { repos } from "@cfdm/db";
import type { CloudflareClient } from "../src/lib/cf-client.js";
import { buildApp } from "../src/app.js";
import { loadConfig } from "../src/config.js";
import {
listGroupViews,
updateConfig,
} from "../src/services/service-config-service.js";
function mockCf(): CloudflareClient {
return {
listDnsRecords: async () => [],
createDnsRecord: async (_zoneId: string, payload: { type: string; name: string; content: string }) => ({
id: `cf-${payload.name}`,
type: payload.type,
name: payload.name,
content: payload.content,
ttl: 1,
proxied: false,
}),
updateDnsRecord: async (
_zoneId: string,
id: string,
payload: { type: string; name: string; content: string },
) => ({
id,
type: payload.type,
name: payload.name,
content: payload.content,
ttl: 1,
proxied: false,
}),
deleteDnsRecord: async () => undefined,
verifyToken: async () => true,
listZones: async () => [{ id: "zone-1", name: "example.com", status: "active" }],
} as unknown as CloudflareClient;
}
async function authHeaders(app: Awaited<ReturnType<typeof buildApp>>) {
const config = loadConfig();
const res = await app.inject({
method: "POST",
url: "/api/v1/auth/login",
payload: { username: config.adminUsername, password: "admin" },
});
expect(res.statusCode).toBe(200);
const { token } = res.json() as { token: string };
return { authorization: `Bearer ${token}` };
}
describe("create service then list groups", () => {
it("create + updateConfig then listGroupViews parses with shared Zod schema", 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 group = repos.createServiceGroup(
app.db,
"VPN",
"vpn",
null,
"vpn.example.com",
);
const createRes = await app.inject({
method: "POST",
url: "/api/v1/services",
headers,
payload: {
name: "Panel",
slug: "panel",
service_group_id: group.id,
},
});
expect(createRes.statusCode).toBe(200);
const created = createRes.json() as {
id: number;
name: string;
enabled: boolean;
};
expect(created.id).toBeTypeOf("number");
expect(created.enabled).toBe(true);
await updateConfig(app.db, cf, created.id, {
ips: ["1.2.3.4"],
service_group_id: group.id,
domains: [
{
fqdn: "panel.example.com",
target_ips: ["1.2.3.4"],
target_ip_weights: { "1.2.3.4": 1 },
target_ip_priorities: { "1.2.3.4": 1 },
lb_mode: "round_robin",
health_check_enabled: true,
health_check_type: "tcp",
health_check_port: 443,
health_check_path: null,
health_check_expected_status: null,
health_check_interval_sec: 30,
health_check_timeout_ms: 3000,
health_check_verify_tls: false,
},
],
});
const body = await listGroupViews(app.db);
const parsed = serviceGroupsResponseSchema.safeParse(body);
expect(parsed.success, JSON.stringify(parsed.error?.issues)).toBe(true);
const listRes = await app.inject({
method: "GET",
url: "/api/v1/service-groups",
headers,
});
expect(listRes.statusCode).toBe(200);
const httpParsed = serviceGroupsResponseSchema.safeParse(listRes.json());
expect(httpParsed.success, JSON.stringify(httpParsed.error?.issues)).toBe(
true,
);
expect(
httpParsed.data!.groups
.find((g) => g.id === group.id)
?.services.some((s) => s.id === created.id),
).toBe(true);
await app.close();
});
it("POST /services returns resolved ServiceView with numeric id", async () => {
const app = await buildApp({
config: { ...loadConfig(), staticDir: null },
memory: true,
});
const headers = await authHeaders(app);
const createRes = await app.inject({
method: "POST",
url: "/api/v1/services",
headers,
payload: { name: "Bare", slug: "bare" },
});
expect(createRes.statusCode).toBe(200);
const created = createRes.json() as Record<string, unknown>;
expect(typeof created.id).toBe("number");
expect(created.name).toBe("Bare");
expect(created.enabled).toBe(true);
expect(Array.isArray(created.ips)).toBe(true);
expect(Array.isArray(created.domains)).toBe(true);
const listRes = await app.inject({
method: "GET",
url: "/api/v1/service-groups",
headers,
});
expect(listRes.statusCode).toBe(200);
const parsed = serviceGroupsResponseSchema.safeParse(listRes.json());
expect(parsed.success, JSON.stringify(parsed.error?.issues)).toBe(true);
expect(parsed.data!.ungrouped.some((s) => s.slug === "bare")).toBe(true);
await app.close();
});
});
+21 -21
View File
@@ -24,15 +24,15 @@ export const serviceGroupSchema = z.object({
id: z.number(), id: z.number(),
name: z.string(), name: z.string(),
type: serviceGroupTypeSchema.catch('custom'), type: serviceGroupTypeSchema.catch('custom'),
icon: z.string().nullable(), icon: z.string().nullable().default(null),
domain: z.string().nullable(), domain: z.string().nullable().default(null),
enabled: z.coerce.boolean(), enabled: z.coerce.boolean(),
lb_mode: z.enum(['round_robin', 'failover', 'weighted']).catch('round_robin'), lb_mode: z.enum(['round_robin', 'failover', 'weighted']).catch('round_robin'),
health_check_enabled: z.coerce.boolean().default(false), health_check_enabled: z.coerce.boolean().default(false),
health_check_type: z.enum(['tcp', 'http']).catch('tcp'), health_check_type: z.enum(['tcp', 'http', 'ping', 'dns']).catch('tcp'),
health_check_port: z.number().nullable(), health_check_port: z.number().nullable().default(null),
health_check_path: z.string().nullable(), health_check_path: z.string().nullable().default(null),
health_check_expected_status: z.number().nullable(), health_check_expected_status: z.number().nullable().default(null),
health_check_interval_sec: z.number().default(30), health_check_interval_sec: z.number().default(30),
health_check_timeout_ms: z.number().default(3000), health_check_timeout_ms: z.number().default(3000),
health_check_verify_tls: z.coerce.boolean().default(false), health_check_verify_tls: z.coerce.boolean().default(false),
@@ -64,19 +64,19 @@ export const serviceDomainBindingSchema = z
record_type: z.enum(['A', 'CNAME']).default('A'), record_type: z.enum(['A', 'CNAME']).default('A'),
target_ips: z.array(z.string()).optional(), target_ips: z.array(z.string()).optional(),
target_ip: z.string().nullable().optional(), target_ip: z.string().nullable().optional(),
target_ip_weights: z.record(z.string(), z.number()).optional(), target_ip_weights: z.record(z.string(), z.coerce.number()).optional(),
target_ip_priorities: z.record(z.string(), z.number()).optional(), target_ip_priorities: z.record(z.string(), z.coerce.number()).optional(),
target_cname: z.string().nullable().optional(), target_cname: z.string().nullable().optional(),
lb_mode: z.enum(['round_robin', 'failover', 'weighted']).catch('round_robin'), lb_mode: z.enum(['round_robin', 'failover', 'weighted']).catch('round_robin'),
health_check_enabled: z.coerce.boolean().default(false), health_check_enabled: z.coerce.boolean().default(false),
health_check_type: z.enum(['tcp', 'http']).catch('tcp'), health_check_type: z.enum(['tcp', 'http', 'ping', 'dns']).catch('tcp'),
health_check_port: z.number().nullable(), health_check_port: z.number().nullable().default(null),
health_check_path: z.string().nullable(), health_check_path: z.string().nullable().default(null),
health_check_expected_status: z.number().nullable(), health_check_expected_status: z.number().nullable().default(null),
health_check_interval_sec: z.number().default(30), health_check_interval_sec: z.number().default(30),
health_check_timeout_ms: z.number().default(3000), health_check_timeout_ms: z.number().default(3000),
health_check_verify_tls: z.coerce.boolean().default(false), health_check_verify_tls: z.coerce.boolean().default(false),
sync_status: z.string().nullable(), sync_status: z.string().nullable().default(null),
}) })
.transform((binding) => ({ .transform((binding) => ({
...binding, ...binding,
@@ -149,18 +149,18 @@ export const serviceBindingSchema = z
service_slug: z.string(), service_slug: z.string(),
target_ip: z.string().nullable(), target_ip: z.string().nullable(),
target_ips: z.array(z.string()).optional(), target_ips: z.array(z.string()).optional(),
target_ip_weights: z.record(z.string(), z.number()).optional(), target_ip_weights: z.record(z.string(), z.coerce.number()).optional(),
target_ip_priorities: z.record(z.string(), z.number()).optional(), target_ip_priorities: z.record(z.string(), z.coerce.number()).optional(),
lb_mode: z.enum(['round_robin', 'failover', 'weighted']).catch('round_robin'), lb_mode: z.enum(['round_robin', 'failover', 'weighted']).catch('round_robin'),
health_check_enabled: z.coerce.boolean().default(false), health_check_enabled: z.coerce.boolean().default(false),
health_check_type: z.enum(['tcp', 'http']).catch('tcp'), health_check_type: z.enum(['tcp', 'http', 'ping', 'dns']).catch('tcp'),
health_check_port: z.number().nullable(), health_check_port: z.number().nullable().default(null),
health_check_path: z.string().nullable(), health_check_path: z.string().nullable().default(null),
health_check_expected_status: z.number().nullable(), health_check_expected_status: z.number().nullable().default(null),
health_check_interval_sec: z.number().default(30), health_check_interval_sec: z.number().default(30),
health_check_timeout_ms: z.number().default(3000), health_check_timeout_ms: z.number().default(3000),
health_check_verify_tls: z.coerce.boolean().default(false), health_check_verify_tls: z.coerce.boolean().default(false),
sync_status: z.string().nullable(), sync_status: z.string().nullable().default(null),
created_at: z.string(), created_at: z.string(),
updated_at: z.string(), updated_at: z.string(),
}) })
@@ -233,7 +233,7 @@ const ipv4Schema = z
) )
const lbModeSchema = z.enum(['round_robin', 'failover', 'weighted']) const lbModeSchema = z.enum(['round_robin', 'failover', 'weighted'])
const healthCheckTypeSchema = z.enum(['tcp', 'http']) const healthCheckTypeSchema = z.enum(['tcp', 'http', 'ping', 'dns'])
const healthCheckConfigFields = { const healthCheckConfigFields = {
health_check_enabled: z.boolean().optional(), health_check_enabled: z.boolean().optional(),
+13 -1
View File
@@ -20,7 +20,19 @@ export const serviceGroupsQueryOptions = () =>
queryKey: serviceGroupKeys.all, queryKey: serviceGroupKeys.all,
queryFn: async () => { queryFn: async () => {
const data = await api.get<unknown>('/api/v1/service-groups') const data = await api.get<unknown>('/api/v1/service-groups')
return serviceGroupsResponseSchema.parse(data) const parsed = serviceGroupsResponseSchema.safeParse(data)
if (!parsed.success) {
const detail = parsed.error.issues
.slice(0, 3)
.map((issue) => `${issue.path.join('.') || '(root)'}: ${issue.message}`)
.join('; ')
throw new Error(
detail
? `Некорректный ответ /service-groups: ${detail}`
: 'Некорректный ответ /service-groups',
)
}
return parsed.data
}, },
}) })
+39 -21
View File
@@ -159,11 +159,22 @@ function ServicesPage() {
data, data,
isLoading, isLoading,
isError, isError,
isRefetchError,
error, error,
refetch, refetch,
} = useQuery(serviceGroupsQueryOptions()) } = useQuery(serviceGroupsQueryOptions())
const { data: domains } = useQuery(domainsListQueryOptions()) const { data: domains } = useQuery(domainsListQueryOptions())
// Refetch after create/update must not wipe the list when cached data remains.
useEffect(() => {
if (!isRefetchError || !data) return
toast.error(
error instanceof Error && error.message
? error.message
: 'Не удалось обновить список сервисов',
)
}, [isRefetchError, data, error])
const dragDisabled = domainId != null const dragDisabled = domainId != null
const { const {
@@ -219,18 +230,20 @@ function ServicesPage() {
}) })
} }
function invalidateAll() { async function invalidateAll() {
queryClient.invalidateQueries({ queryKey: serviceGroupKeys.all }) await Promise.all([
queryClient.invalidateQueries({ queryKey: serviceKeys.all }) queryClient.invalidateQueries({ queryKey: serviceGroupKeys.all }),
queryClient.invalidateQueries({ queryKey: serviceBindingKeys.all }) queryClient.invalidateQueries({ queryKey: serviceKeys.all }),
queryClient.invalidateQueries({ queryKey: domainKeys.all }) queryClient.invalidateQueries({ queryKey: serviceBindingKeys.all }),
queryClient.invalidateQueries({ queryKey: domainKeys.all }),
])
} }
const createGroupMutation = useMutation({ const createGroupMutation = useMutation({
mutationFn: (body: CreateServiceGroupInput) => mutationFn: (body: CreateServiceGroupInput) =>
api.post('/api/v1/service-groups', body), api.post('/api/v1/service-groups', body),
onSuccess: () => { onSuccess: async () => {
invalidateAll() await invalidateAll()
setCreateGroupSheetOpen(false) setCreateGroupSheetOpen(false)
toast.success('Группа создана') toast.success('Группа создана')
}, },
@@ -242,8 +255,8 @@ function ServicesPage() {
const updateGroupMutation = useMutation({ const updateGroupMutation = useMutation({
mutationFn: ({ id, body }: { id: number; body: CreateServiceGroupInput }) => mutationFn: ({ id, body }: { id: number; body: CreateServiceGroupInput }) =>
api.patch(`/api/v1/service-groups/${id}`, body), api.patch(`/api/v1/service-groups/${id}`, body),
onSuccess: () => { onSuccess: async () => {
invalidateAll() await invalidateAll()
setEditingGroup(null) setEditingGroup(null)
toast.success('Группа сохранена, DNS синхронизируется') toast.success('Группа сохранена, DNS синхронизируется')
}, },
@@ -259,16 +272,21 @@ function ServicesPage() {
slug: body.slug, slug: body.slug,
service_group_id: body.service_group_id ?? null, service_group_id: body.service_group_id ?? null,
}) })
if (created?.id == null) {
throw new Error('Сервер не вернул id созданного сервиса')
}
const hasConfig = body.ips.length > 0 || body.domains.length > 0 const hasConfig = body.ips.length > 0 || body.domains.length > 0
if (!hasConfig) return created if (!hasConfig) return created
return api.patch<ServiceView>(`/api/v1/services/${created.id}`, { return api.patch<ServiceView>(`/api/v1/services/${created.id}`, {
ips: body.ips, ips: body.ips,
domains: body.domains, domains: body.domains,
service_group_id: body.service_group_id ?? null, service_group_id: body.service_group_id ?? null,
...(body.lb_weight != null ? { lb_weight: body.lb_weight } : {}),
...(body.lb_priority != null ? { lb_priority: body.lb_priority } : {}),
}) })
}, },
onSuccess: () => { onSuccess: async () => {
invalidateAll() await invalidateAll()
setCreateSheetOpen(false) setCreateSheetOpen(false)
toast.success('Сервис создан') toast.success('Сервис создан')
}, },
@@ -280,8 +298,8 @@ function ServicesPage() {
const updateServiceMutation = useMutation({ const updateServiceMutation = useMutation({
mutationFn: ({ id, body }: { id: number; body: UpdateServiceConfigInput }) => mutationFn: ({ id, body }: { id: number; body: UpdateServiceConfigInput }) =>
api.patch<ServiceView>(`/api/v1/services/${id}`, body), api.patch<ServiceView>(`/api/v1/services/${id}`, body),
onSuccess: () => { onSuccess: async () => {
invalidateAll() await invalidateAll()
setEditingService(null) setEditingService(null)
clearServiceSearch() clearServiceSearch()
toast.success('Сервис сохранён') toast.success('Сервис сохранён')
@@ -296,8 +314,8 @@ function ServicesPage() {
const deleteServiceMutation = useMutation({ const deleteServiceMutation = useMutation({
mutationFn: (id: number) => api.delete(`/api/v1/services/${id}`), mutationFn: (id: number) => api.delete(`/api/v1/services/${id}`),
onSuccess: () => { onSuccess: async () => {
invalidateAll() await invalidateAll()
setEditingService(null) setEditingService(null)
setDeletingService(null) setDeletingService(null)
clearServiceSearch() clearServiceSearch()
@@ -340,16 +358,16 @@ function ServicesPage() {
: 'Сервис выключен, DNS-записи удалены из Cloudflare', : 'Сервис выключен, DNS-записи удалены из Cloudflare',
) )
}, },
onSettled: () => { onSettled: async () => {
setTogglingServiceId(null) setTogglingServiceId(null)
invalidateAll() await invalidateAll()
}, },
}) })
const deleteGroupMutation = useMutation({ const deleteGroupMutation = useMutation({
mutationFn: (id: number) => api.delete(`/api/v1/service-groups/${id}`), mutationFn: (id: number) => api.delete(`/api/v1/service-groups/${id}`),
onSuccess: () => { onSuccess: async () => {
invalidateAll() await invalidateAll()
setDeletingGroup(null) setDeletingGroup(null)
toast.success('Группа удалена') toast.success('Группа удалена')
}, },
@@ -430,7 +448,7 @@ function ServicesPage() {
} }
setBulkToggling(false) setBulkToggling(false)
invalidateAll() await invalidateAll()
} }
const columnConfigs = useMemo( const columnConfigs = useMemo(
@@ -598,7 +616,7 @@ function ServicesPage() {
) )
} }
if (isError) { if (isError && !data) {
return ( return (
<PageShell> <PageShell>
<PageHeader title="Сервисы" description={pageDescription} /> <PageHeader title="Сервисы" description={pageDescription} />
+2 -2
View File
@@ -29,7 +29,7 @@ var services = sqliteTable("services", {
{ onDelete: "set null" } { onDelete: "set null" }
), ),
subdomain: text("subdomain"), subdomain: text("subdomain"),
enabled: integer("enabled", { mode: "boolean" }).notNull().default(false), enabled: integer("enabled", { mode: "boolean" }).notNull().default(true),
sort_order: integer("sort_order").notNull().default(0), sort_order: integer("sort_order").notNull().default(0),
lb_weight: integer("lb_weight").notNull().default(1), lb_weight: integer("lb_weight").notNull().default(1),
lb_priority: integer("lb_priority").notNull().default(1), lb_priority: integer("lb_priority").notNull().default(1),
@@ -932,7 +932,7 @@ function getService(db, id) {
} }
function createService(db, name, slug) { function createService(db, name, slug) {
const sortOrder = maxSortOrderInGroup(db, null) + 1; const sortOrder = maxSortOrderInGroup(db, null) + 1;
const id = db.insert(services).values({ name, slug, subdomain: slug, sort_order: sortOrder }).returning({ id: services.id }).get().id; const id = db.insert(services).values({ name, slug, subdomain: slug, sort_order: sortOrder, enabled: true }).returning({ id: services.id }).get().id;
return getService(db, id); return getService(db, id);
} }
function updateService(db, id, name, slug) { function updateService(db, id, name, slug) {
+1 -1
View File
@@ -649,7 +649,7 @@ export function createService(db: Db, name: string, slug: string): Service {
const sortOrder = maxSortOrderInGroup(db, null) + 1; const sortOrder = maxSortOrderInGroup(db, null) + 1;
const id = db const id = db
.insert(services) .insert(services)
.values({ name, slug, subdomain: slug, sort_order: sortOrder }) .values({ name, slug, subdomain: slug, sort_order: sortOrder, enabled: true })
.returning({ id: services.id }) .returning({ id: services.id })
.get()!.id; .get()!.id;
return getService(db, id); return getService(db, id);
+1 -1
View File
@@ -28,7 +28,7 @@ export const services = sqliteTable("services", {
{ onDelete: "set null" }, { onDelete: "set null" },
), ),
subdomain: text("subdomain"), subdomain: text("subdomain"),
enabled: integer("enabled", { mode: "boolean" }).notNull().default(false), enabled: integer("enabled", { mode: "boolean" }).notNull().default(true),
sort_order: integer("sort_order").notNull().default(0), sort_order: integer("sort_order").notNull().default(0),
lb_weight: integer("lb_weight").notNull().default(1), lb_weight: integer("lb_weight").notNull().default(1),
lb_priority: integer("lb_priority").notNull().default(1), lb_priority: integer("lb_priority").notNull().default(1),
+51 -51
View File
@@ -304,8 +304,8 @@ declare const serviceGroupSchema: z.ZodObject<{
bgp: "bgp"; bgp: "bgp";
custom: "custom"; custom: "custom";
}>>; }>>;
icon: z.ZodNullable<z.ZodString>; icon: z.ZodDefault<z.ZodNullable<z.ZodString>>;
domain: z.ZodNullable<z.ZodString>; domain: z.ZodDefault<z.ZodNullable<z.ZodString>>;
enabled: z.ZodCoercedBoolean<unknown>; enabled: z.ZodCoercedBoolean<unknown>;
lb_mode: z.ZodCatch<z.ZodEnum<{ lb_mode: z.ZodCatch<z.ZodEnum<{
round_robin: "round_robin"; round_robin: "round_robin";
@@ -319,9 +319,9 @@ declare const serviceGroupSchema: z.ZodObject<{
ping: "ping"; ping: "ping";
dns: "dns"; dns: "dns";
}>>; }>>;
health_check_port: z.ZodNullable<z.ZodNumber>; health_check_port: z.ZodDefault<z.ZodNullable<z.ZodNumber>>;
health_check_path: z.ZodNullable<z.ZodString>; health_check_path: z.ZodDefault<z.ZodNullable<z.ZodString>>;
health_check_expected_status: z.ZodNullable<z.ZodNumber>; health_check_expected_status: z.ZodDefault<z.ZodNullable<z.ZodNumber>>;
health_check_interval_sec: z.ZodDefault<z.ZodNumber>; health_check_interval_sec: z.ZodDefault<z.ZodNumber>;
health_check_timeout_ms: z.ZodDefault<z.ZodNumber>; health_check_timeout_ms: z.ZodDefault<z.ZodNumber>;
health_check_verify_tls: z.ZodDefault<z.ZodCoercedBoolean<unknown>>; health_check_verify_tls: z.ZodDefault<z.ZodCoercedBoolean<unknown>>;
@@ -353,8 +353,8 @@ declare const serviceDomainBindingSchema: z.ZodPipe<z.ZodObject<{
}>>; }>>;
target_ips: z.ZodOptional<z.ZodArray<z.ZodString>>; target_ips: z.ZodOptional<z.ZodArray<z.ZodString>>;
target_ip: z.ZodOptional<z.ZodNullable<z.ZodString>>; target_ip: z.ZodOptional<z.ZodNullable<z.ZodString>>;
target_ip_weights: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodNumber>>; target_ip_weights: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodCoercedNumber<unknown>>>;
target_ip_priorities: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodNumber>>; target_ip_priorities: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodCoercedNumber<unknown>>>;
target_cname: z.ZodOptional<z.ZodNullable<z.ZodString>>; target_cname: z.ZodOptional<z.ZodNullable<z.ZodString>>;
lb_mode: z.ZodCatch<z.ZodEnum<{ lb_mode: z.ZodCatch<z.ZodEnum<{
round_robin: "round_robin"; round_robin: "round_robin";
@@ -368,13 +368,13 @@ declare const serviceDomainBindingSchema: z.ZodPipe<z.ZodObject<{
ping: "ping"; ping: "ping";
dns: "dns"; dns: "dns";
}>>; }>>;
health_check_port: z.ZodNullable<z.ZodNumber>; health_check_port: z.ZodDefault<z.ZodNullable<z.ZodNumber>>;
health_check_path: z.ZodNullable<z.ZodString>; health_check_path: z.ZodDefault<z.ZodNullable<z.ZodString>>;
health_check_expected_status: z.ZodNullable<z.ZodNumber>; health_check_expected_status: z.ZodDefault<z.ZodNullable<z.ZodNumber>>;
health_check_interval_sec: z.ZodDefault<z.ZodNumber>; health_check_interval_sec: z.ZodDefault<z.ZodNumber>;
health_check_timeout_ms: z.ZodDefault<z.ZodNumber>; health_check_timeout_ms: z.ZodDefault<z.ZodNumber>;
health_check_verify_tls: z.ZodDefault<z.ZodCoercedBoolean<unknown>>; health_check_verify_tls: z.ZodDefault<z.ZodCoercedBoolean<unknown>>;
sync_status: z.ZodNullable<z.ZodString>; sync_status: z.ZodDefault<z.ZodNullable<z.ZodString>>;
}, z.core.$strip>, z.ZodTransform<{ }, z.core.$strip>, z.ZodTransform<{
target_ips: string[]; target_ips: string[];
target_ip_weights: Record<string, number>; target_ip_weights: Record<string, number>;
@@ -445,8 +445,8 @@ declare const serviceViewSchema: z.ZodObject<{
}>>; }>>;
target_ips: z.ZodOptional<z.ZodArray<z.ZodString>>; target_ips: z.ZodOptional<z.ZodArray<z.ZodString>>;
target_ip: z.ZodOptional<z.ZodNullable<z.ZodString>>; target_ip: z.ZodOptional<z.ZodNullable<z.ZodString>>;
target_ip_weights: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodNumber>>; target_ip_weights: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodCoercedNumber<unknown>>>;
target_ip_priorities: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodNumber>>; target_ip_priorities: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodCoercedNumber<unknown>>>;
target_cname: z.ZodOptional<z.ZodNullable<z.ZodString>>; target_cname: z.ZodOptional<z.ZodNullable<z.ZodString>>;
lb_mode: z.ZodCatch<z.ZodEnum<{ lb_mode: z.ZodCatch<z.ZodEnum<{
round_robin: "round_robin"; round_robin: "round_robin";
@@ -460,13 +460,13 @@ declare const serviceViewSchema: z.ZodObject<{
ping: "ping"; ping: "ping";
dns: "dns"; dns: "dns";
}>>; }>>;
health_check_port: z.ZodNullable<z.ZodNumber>; health_check_port: z.ZodDefault<z.ZodNullable<z.ZodNumber>>;
health_check_path: z.ZodNullable<z.ZodString>; health_check_path: z.ZodDefault<z.ZodNullable<z.ZodString>>;
health_check_expected_status: z.ZodNullable<z.ZodNumber>; health_check_expected_status: z.ZodDefault<z.ZodNullable<z.ZodNumber>>;
health_check_interval_sec: z.ZodDefault<z.ZodNumber>; health_check_interval_sec: z.ZodDefault<z.ZodNumber>;
health_check_timeout_ms: z.ZodDefault<z.ZodNumber>; health_check_timeout_ms: z.ZodDefault<z.ZodNumber>;
health_check_verify_tls: z.ZodDefault<z.ZodCoercedBoolean<unknown>>; health_check_verify_tls: z.ZodDefault<z.ZodCoercedBoolean<unknown>>;
sync_status: z.ZodNullable<z.ZodString>; sync_status: z.ZodDefault<z.ZodNullable<z.ZodString>>;
}, z.core.$strip>, z.ZodTransform<{ }, z.core.$strip>, z.ZodTransform<{
target_ips: string[]; target_ips: string[];
target_ip_weights: Record<string, number>; target_ip_weights: Record<string, number>;
@@ -530,8 +530,8 @@ declare const serviceGroupViewSchema: z.ZodObject<{
bgp: "bgp"; bgp: "bgp";
custom: "custom"; custom: "custom";
}>>; }>>;
icon: z.ZodNullable<z.ZodString>; icon: z.ZodDefault<z.ZodNullable<z.ZodString>>;
domain: z.ZodNullable<z.ZodString>; domain: z.ZodDefault<z.ZodNullable<z.ZodString>>;
enabled: z.ZodCoercedBoolean<unknown>; enabled: z.ZodCoercedBoolean<unknown>;
lb_mode: z.ZodCatch<z.ZodEnum<{ lb_mode: z.ZodCatch<z.ZodEnum<{
round_robin: "round_robin"; round_robin: "round_robin";
@@ -545,9 +545,9 @@ declare const serviceGroupViewSchema: z.ZodObject<{
ping: "ping"; ping: "ping";
dns: "dns"; dns: "dns";
}>>; }>>;
health_check_port: z.ZodNullable<z.ZodNumber>; health_check_port: z.ZodDefault<z.ZodNullable<z.ZodNumber>>;
health_check_path: z.ZodNullable<z.ZodString>; health_check_path: z.ZodDefault<z.ZodNullable<z.ZodString>>;
health_check_expected_status: z.ZodNullable<z.ZodNumber>; health_check_expected_status: z.ZodDefault<z.ZodNullable<z.ZodNumber>>;
health_check_interval_sec: z.ZodDefault<z.ZodNumber>; health_check_interval_sec: z.ZodDefault<z.ZodNumber>;
health_check_timeout_ms: z.ZodDefault<z.ZodNumber>; health_check_timeout_ms: z.ZodDefault<z.ZodNumber>;
health_check_verify_tls: z.ZodDefault<z.ZodCoercedBoolean<unknown>>; health_check_verify_tls: z.ZodDefault<z.ZodCoercedBoolean<unknown>>;
@@ -578,8 +578,8 @@ declare const serviceGroupViewSchema: z.ZodObject<{
}>>; }>>;
target_ips: z.ZodOptional<z.ZodArray<z.ZodString>>; target_ips: z.ZodOptional<z.ZodArray<z.ZodString>>;
target_ip: z.ZodOptional<z.ZodNullable<z.ZodString>>; target_ip: z.ZodOptional<z.ZodNullable<z.ZodString>>;
target_ip_weights: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodNumber>>; target_ip_weights: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodCoercedNumber<unknown>>>;
target_ip_priorities: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodNumber>>; target_ip_priorities: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodCoercedNumber<unknown>>>;
target_cname: z.ZodOptional<z.ZodNullable<z.ZodString>>; target_cname: z.ZodOptional<z.ZodNullable<z.ZodString>>;
lb_mode: z.ZodCatch<z.ZodEnum<{ lb_mode: z.ZodCatch<z.ZodEnum<{
round_robin: "round_robin"; round_robin: "round_robin";
@@ -593,13 +593,13 @@ declare const serviceGroupViewSchema: z.ZodObject<{
ping: "ping"; ping: "ping";
dns: "dns"; dns: "dns";
}>>; }>>;
health_check_port: z.ZodNullable<z.ZodNumber>; health_check_port: z.ZodDefault<z.ZodNullable<z.ZodNumber>>;
health_check_path: z.ZodNullable<z.ZodString>; health_check_path: z.ZodDefault<z.ZodNullable<z.ZodString>>;
health_check_expected_status: z.ZodNullable<z.ZodNumber>; health_check_expected_status: z.ZodDefault<z.ZodNullable<z.ZodNumber>>;
health_check_interval_sec: z.ZodDefault<z.ZodNumber>; health_check_interval_sec: z.ZodDefault<z.ZodNumber>;
health_check_timeout_ms: z.ZodDefault<z.ZodNumber>; health_check_timeout_ms: z.ZodDefault<z.ZodNumber>;
health_check_verify_tls: z.ZodDefault<z.ZodCoercedBoolean<unknown>>; health_check_verify_tls: z.ZodDefault<z.ZodCoercedBoolean<unknown>>;
sync_status: z.ZodNullable<z.ZodString>; sync_status: z.ZodDefault<z.ZodNullable<z.ZodString>>;
}, z.core.$strip>, z.ZodTransform<{ }, z.core.$strip>, z.ZodTransform<{
target_ips: string[]; target_ips: string[];
target_ip_weights: Record<string, number>; target_ip_weights: Record<string, number>;
@@ -672,8 +672,8 @@ declare const serviceGroupsResponseSchema: z.ZodObject<{
bgp: "bgp"; bgp: "bgp";
custom: "custom"; custom: "custom";
}>>; }>>;
icon: z.ZodNullable<z.ZodString>; icon: z.ZodDefault<z.ZodNullable<z.ZodString>>;
domain: z.ZodNullable<z.ZodString>; domain: z.ZodDefault<z.ZodNullable<z.ZodString>>;
enabled: z.ZodCoercedBoolean<unknown>; enabled: z.ZodCoercedBoolean<unknown>;
lb_mode: z.ZodCatch<z.ZodEnum<{ lb_mode: z.ZodCatch<z.ZodEnum<{
round_robin: "round_robin"; round_robin: "round_robin";
@@ -687,9 +687,9 @@ declare const serviceGroupsResponseSchema: z.ZodObject<{
ping: "ping"; ping: "ping";
dns: "dns"; dns: "dns";
}>>; }>>;
health_check_port: z.ZodNullable<z.ZodNumber>; health_check_port: z.ZodDefault<z.ZodNullable<z.ZodNumber>>;
health_check_path: z.ZodNullable<z.ZodString>; health_check_path: z.ZodDefault<z.ZodNullable<z.ZodString>>;
health_check_expected_status: z.ZodNullable<z.ZodNumber>; health_check_expected_status: z.ZodDefault<z.ZodNullable<z.ZodNumber>>;
health_check_interval_sec: z.ZodDefault<z.ZodNumber>; health_check_interval_sec: z.ZodDefault<z.ZodNumber>;
health_check_timeout_ms: z.ZodDefault<z.ZodNumber>; health_check_timeout_ms: z.ZodDefault<z.ZodNumber>;
health_check_verify_tls: z.ZodDefault<z.ZodCoercedBoolean<unknown>>; health_check_verify_tls: z.ZodDefault<z.ZodCoercedBoolean<unknown>>;
@@ -720,8 +720,8 @@ declare const serviceGroupsResponseSchema: z.ZodObject<{
}>>; }>>;
target_ips: z.ZodOptional<z.ZodArray<z.ZodString>>; target_ips: z.ZodOptional<z.ZodArray<z.ZodString>>;
target_ip: z.ZodOptional<z.ZodNullable<z.ZodString>>; target_ip: z.ZodOptional<z.ZodNullable<z.ZodString>>;
target_ip_weights: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodNumber>>; target_ip_weights: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodCoercedNumber<unknown>>>;
target_ip_priorities: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodNumber>>; target_ip_priorities: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodCoercedNumber<unknown>>>;
target_cname: z.ZodOptional<z.ZodNullable<z.ZodString>>; target_cname: z.ZodOptional<z.ZodNullable<z.ZodString>>;
lb_mode: z.ZodCatch<z.ZodEnum<{ lb_mode: z.ZodCatch<z.ZodEnum<{
round_robin: "round_robin"; round_robin: "round_robin";
@@ -735,13 +735,13 @@ declare const serviceGroupsResponseSchema: z.ZodObject<{
ping: "ping"; ping: "ping";
dns: "dns"; dns: "dns";
}>>; }>>;
health_check_port: z.ZodNullable<z.ZodNumber>; health_check_port: z.ZodDefault<z.ZodNullable<z.ZodNumber>>;
health_check_path: z.ZodNullable<z.ZodString>; health_check_path: z.ZodDefault<z.ZodNullable<z.ZodString>>;
health_check_expected_status: z.ZodNullable<z.ZodNumber>; health_check_expected_status: z.ZodDefault<z.ZodNullable<z.ZodNumber>>;
health_check_interval_sec: z.ZodDefault<z.ZodNumber>; health_check_interval_sec: z.ZodDefault<z.ZodNumber>;
health_check_timeout_ms: z.ZodDefault<z.ZodNumber>; health_check_timeout_ms: z.ZodDefault<z.ZodNumber>;
health_check_verify_tls: z.ZodDefault<z.ZodCoercedBoolean<unknown>>; health_check_verify_tls: z.ZodDefault<z.ZodCoercedBoolean<unknown>>;
sync_status: z.ZodNullable<z.ZodString>; sync_status: z.ZodDefault<z.ZodNullable<z.ZodString>>;
}, z.core.$strip>, z.ZodTransform<{ }, z.core.$strip>, z.ZodTransform<{
target_ips: string[]; target_ips: string[];
target_ip_weights: Record<string, number>; target_ip_weights: Record<string, number>;
@@ -828,8 +828,8 @@ declare const serviceGroupsResponseSchema: z.ZodObject<{
}>>; }>>;
target_ips: z.ZodOptional<z.ZodArray<z.ZodString>>; target_ips: z.ZodOptional<z.ZodArray<z.ZodString>>;
target_ip: z.ZodOptional<z.ZodNullable<z.ZodString>>; target_ip: z.ZodOptional<z.ZodNullable<z.ZodString>>;
target_ip_weights: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodNumber>>; target_ip_weights: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodCoercedNumber<unknown>>>;
target_ip_priorities: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodNumber>>; target_ip_priorities: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodCoercedNumber<unknown>>>;
target_cname: z.ZodOptional<z.ZodNullable<z.ZodString>>; target_cname: z.ZodOptional<z.ZodNullable<z.ZodString>>;
lb_mode: z.ZodCatch<z.ZodEnum<{ lb_mode: z.ZodCatch<z.ZodEnum<{
round_robin: "round_robin"; round_robin: "round_robin";
@@ -843,13 +843,13 @@ declare const serviceGroupsResponseSchema: z.ZodObject<{
ping: "ping"; ping: "ping";
dns: "dns"; dns: "dns";
}>>; }>>;
health_check_port: z.ZodNullable<z.ZodNumber>; health_check_port: z.ZodDefault<z.ZodNullable<z.ZodNumber>>;
health_check_path: z.ZodNullable<z.ZodString>; health_check_path: z.ZodDefault<z.ZodNullable<z.ZodString>>;
health_check_expected_status: z.ZodNullable<z.ZodNumber>; health_check_expected_status: z.ZodDefault<z.ZodNullable<z.ZodNumber>>;
health_check_interval_sec: z.ZodDefault<z.ZodNumber>; health_check_interval_sec: z.ZodDefault<z.ZodNumber>;
health_check_timeout_ms: z.ZodDefault<z.ZodNumber>; health_check_timeout_ms: z.ZodDefault<z.ZodNumber>;
health_check_verify_tls: z.ZodDefault<z.ZodCoercedBoolean<unknown>>; health_check_verify_tls: z.ZodDefault<z.ZodCoercedBoolean<unknown>>;
sync_status: z.ZodNullable<z.ZodString>; sync_status: z.ZodDefault<z.ZodNullable<z.ZodString>>;
}, z.core.$strip>, z.ZodTransform<{ }, z.core.$strip>, z.ZodTransform<{
target_ips: string[]; target_ips: string[];
target_ip_weights: Record<string, number>; target_ip_weights: Record<string, number>;
@@ -967,8 +967,8 @@ declare const serviceBindingSchema: z.ZodPipe<z.ZodObject<{
service_slug: z.ZodString; service_slug: z.ZodString;
target_ip: z.ZodNullable<z.ZodString>; target_ip: z.ZodNullable<z.ZodString>;
target_ips: z.ZodOptional<z.ZodArray<z.ZodString>>; target_ips: z.ZodOptional<z.ZodArray<z.ZodString>>;
target_ip_weights: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodNumber>>; target_ip_weights: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodCoercedNumber<unknown>>>;
target_ip_priorities: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodNumber>>; target_ip_priorities: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodCoercedNumber<unknown>>>;
lb_mode: z.ZodCatch<z.ZodEnum<{ lb_mode: z.ZodCatch<z.ZodEnum<{
round_robin: "round_robin"; round_robin: "round_robin";
failover: "failover"; failover: "failover";
@@ -981,13 +981,13 @@ declare const serviceBindingSchema: z.ZodPipe<z.ZodObject<{
ping: "ping"; ping: "ping";
dns: "dns"; dns: "dns";
}>>; }>>;
health_check_port: z.ZodNullable<z.ZodNumber>; health_check_port: z.ZodDefault<z.ZodNullable<z.ZodNumber>>;
health_check_path: z.ZodNullable<z.ZodString>; health_check_path: z.ZodDefault<z.ZodNullable<z.ZodString>>;
health_check_expected_status: z.ZodNullable<z.ZodNumber>; health_check_expected_status: z.ZodDefault<z.ZodNullable<z.ZodNumber>>;
health_check_interval_sec: z.ZodDefault<z.ZodNumber>; health_check_interval_sec: z.ZodDefault<z.ZodNumber>;
health_check_timeout_ms: z.ZodDefault<z.ZodNumber>; health_check_timeout_ms: z.ZodDefault<z.ZodNumber>;
health_check_verify_tls: z.ZodDefault<z.ZodCoercedBoolean<unknown>>; health_check_verify_tls: z.ZodDefault<z.ZodCoercedBoolean<unknown>>;
sync_status: z.ZodNullable<z.ZodString>; sync_status: z.ZodDefault<z.ZodNullable<z.ZodString>>;
created_at: z.ZodString; created_at: z.ZodString;
updated_at: z.ZodString; updated_at: z.ZodString;
}, z.core.$strip>, z.ZodTransform<{ }, z.core.$strip>, z.ZodTransform<{
+17 -17
View File
@@ -203,15 +203,15 @@ var serviceGroupSchema = z.object({
id: z.number(), id: z.number(),
name: z.string(), name: z.string(),
type: serviceGroupTypeSchema.catch("custom"), type: serviceGroupTypeSchema.catch("custom"),
icon: z.string().nullable(), icon: z.string().nullable().default(null),
domain: z.string().nullable(), domain: z.string().nullable().default(null),
enabled: z.coerce.boolean(), enabled: z.coerce.boolean(),
lb_mode: lbModeSchema.catch("round_robin"), lb_mode: lbModeSchema.catch("round_robin"),
health_check_enabled: z.coerce.boolean().default(false), health_check_enabled: z.coerce.boolean().default(false),
health_check_type: healthCheckTypeSchema.catch("tcp"), health_check_type: healthCheckTypeSchema.catch("tcp"),
health_check_port: z.number().nullable(), health_check_port: z.number().nullable().default(null),
health_check_path: z.string().nullable(), health_check_path: z.string().nullable().default(null),
health_check_expected_status: z.number().nullable(), health_check_expected_status: z.number().nullable().default(null),
health_check_interval_sec: z.number().default(30), health_check_interval_sec: z.number().default(30),
health_check_timeout_ms: z.number().default(3e3), health_check_timeout_ms: z.number().default(3e3),
health_check_verify_tls: z.coerce.boolean().default(false), health_check_verify_tls: z.coerce.boolean().default(false),
@@ -240,19 +240,19 @@ var serviceDomainBindingSchema = z.object({
record_type: z.enum(["A", "CNAME"]).default("A"), record_type: z.enum(["A", "CNAME"]).default("A"),
target_ips: z.array(z.string()).optional(), target_ips: z.array(z.string()).optional(),
target_ip: z.string().nullable().optional(), target_ip: z.string().nullable().optional(),
target_ip_weights: z.record(z.string(), z.number()).optional(), target_ip_weights: z.record(z.string(), z.coerce.number()).optional(),
target_ip_priorities: z.record(z.string(), z.number()).optional(), target_ip_priorities: z.record(z.string(), z.coerce.number()).optional(),
target_cname: z.string().nullable().optional(), target_cname: z.string().nullable().optional(),
lb_mode: lbModeSchema.catch("round_robin"), lb_mode: lbModeSchema.catch("round_robin"),
health_check_enabled: z.coerce.boolean().default(false), health_check_enabled: z.coerce.boolean().default(false),
health_check_type: healthCheckTypeSchema.catch("tcp"), health_check_type: healthCheckTypeSchema.catch("tcp"),
health_check_port: z.number().nullable(), health_check_port: z.number().nullable().default(null),
health_check_path: z.string().nullable(), health_check_path: z.string().nullable().default(null),
health_check_expected_status: z.number().nullable(), health_check_expected_status: z.number().nullable().default(null),
health_check_interval_sec: z.number().default(30), health_check_interval_sec: z.number().default(30),
health_check_timeout_ms: z.number().default(3e3), health_check_timeout_ms: z.number().default(3e3),
health_check_verify_tls: z.coerce.boolean().default(false), health_check_verify_tls: z.coerce.boolean().default(false),
sync_status: z.string().nullable() sync_status: z.string().nullable().default(null)
}).transform((binding) => ({ }).transform((binding) => ({
...binding, ...binding,
target_ips: binding.target_ips && binding.target_ips.length > 0 ? binding.target_ips : binding.target_ip ? [binding.target_ip] : [], target_ips: binding.target_ips && binding.target_ips.length > 0 ? binding.target_ips : binding.target_ip ? [binding.target_ip] : [],
@@ -310,18 +310,18 @@ var serviceBindingSchema = z.object({
service_slug: z.string(), service_slug: z.string(),
target_ip: z.string().nullable(), target_ip: z.string().nullable(),
target_ips: z.array(z.string()).optional(), target_ips: z.array(z.string()).optional(),
target_ip_weights: z.record(z.string(), z.number()).optional(), target_ip_weights: z.record(z.string(), z.coerce.number()).optional(),
target_ip_priorities: z.record(z.string(), z.number()).optional(), target_ip_priorities: z.record(z.string(), z.coerce.number()).optional(),
lb_mode: lbModeSchema.catch("round_robin"), lb_mode: lbModeSchema.catch("round_robin"),
health_check_enabled: z.coerce.boolean().default(false), health_check_enabled: z.coerce.boolean().default(false),
health_check_type: healthCheckTypeSchema.catch("tcp"), health_check_type: healthCheckTypeSchema.catch("tcp"),
health_check_port: z.number().nullable(), health_check_port: z.number().nullable().default(null),
health_check_path: z.string().nullable(), health_check_path: z.string().nullable().default(null),
health_check_expected_status: z.number().nullable(), health_check_expected_status: z.number().nullable().default(null),
health_check_interval_sec: z.number().default(30), health_check_interval_sec: z.number().default(30),
health_check_timeout_ms: z.number().default(3e3), health_check_timeout_ms: z.number().default(3e3),
health_check_verify_tls: z.coerce.boolean().default(false), health_check_verify_tls: z.coerce.boolean().default(false),
sync_status: z.string().nullable(), sync_status: z.string().nullable().default(null),
created_at: z.string(), created_at: z.string(),
updated_at: z.string() updated_at: z.string()
}).transform((binding) => ({ }).transform((binding) => ({
+17 -17
View File
@@ -59,15 +59,15 @@ export const serviceGroupSchema = z.object({
id: z.number(), id: z.number(),
name: z.string(), name: z.string(),
type: serviceGroupTypeSchema.catch('custom'), type: serviceGroupTypeSchema.catch('custom'),
icon: z.string().nullable(), icon: z.string().nullable().default(null),
domain: z.string().nullable(), domain: z.string().nullable().default(null),
enabled: z.coerce.boolean(), enabled: z.coerce.boolean(),
lb_mode: lbModeSchema.catch('round_robin'), lb_mode: lbModeSchema.catch('round_robin'),
health_check_enabled: z.coerce.boolean().default(false), health_check_enabled: z.coerce.boolean().default(false),
health_check_type: healthCheckTypeSchema.catch('tcp'), health_check_type: healthCheckTypeSchema.catch('tcp'),
health_check_port: z.number().nullable(), health_check_port: z.number().nullable().default(null),
health_check_path: z.string().nullable(), health_check_path: z.string().nullable().default(null),
health_check_expected_status: z.number().nullable(), health_check_expected_status: z.number().nullable().default(null),
health_check_interval_sec: z.number().default(30), health_check_interval_sec: z.number().default(30),
health_check_timeout_ms: z.number().default(3000), health_check_timeout_ms: z.number().default(3000),
health_check_verify_tls: z.coerce.boolean().default(false), health_check_verify_tls: z.coerce.boolean().default(false),
@@ -99,19 +99,19 @@ export const serviceDomainBindingSchema = z
record_type: z.enum(['A', 'CNAME']).default('A'), record_type: z.enum(['A', 'CNAME']).default('A'),
target_ips: z.array(z.string()).optional(), target_ips: z.array(z.string()).optional(),
target_ip: z.string().nullable().optional(), target_ip: z.string().nullable().optional(),
target_ip_weights: z.record(z.string(), z.number()).optional(), target_ip_weights: z.record(z.string(), z.coerce.number()).optional(),
target_ip_priorities: z.record(z.string(), z.number()).optional(), target_ip_priorities: z.record(z.string(), z.coerce.number()).optional(),
target_cname: z.string().nullable().optional(), target_cname: z.string().nullable().optional(),
lb_mode: lbModeSchema.catch('round_robin'), lb_mode: lbModeSchema.catch('round_robin'),
health_check_enabled: z.coerce.boolean().default(false), health_check_enabled: z.coerce.boolean().default(false),
health_check_type: healthCheckTypeSchema.catch('tcp'), health_check_type: healthCheckTypeSchema.catch('tcp'),
health_check_port: z.number().nullable(), health_check_port: z.number().nullable().default(null),
health_check_path: z.string().nullable(), health_check_path: z.string().nullable().default(null),
health_check_expected_status: z.number().nullable(), health_check_expected_status: z.number().nullable().default(null),
health_check_interval_sec: z.number().default(30), health_check_interval_sec: z.number().default(30),
health_check_timeout_ms: z.number().default(3000), health_check_timeout_ms: z.number().default(3000),
health_check_verify_tls: z.coerce.boolean().default(false), health_check_verify_tls: z.coerce.boolean().default(false),
sync_status: z.string().nullable(), sync_status: z.string().nullable().default(null),
}) })
.transform((binding) => ({ .transform((binding) => ({
...binding, ...binding,
@@ -184,18 +184,18 @@ export const serviceBindingSchema = z
service_slug: z.string(), service_slug: z.string(),
target_ip: z.string().nullable(), target_ip: z.string().nullable(),
target_ips: z.array(z.string()).optional(), target_ips: z.array(z.string()).optional(),
target_ip_weights: z.record(z.string(), z.number()).optional(), target_ip_weights: z.record(z.string(), z.coerce.number()).optional(),
target_ip_priorities: z.record(z.string(), z.number()).optional(), target_ip_priorities: z.record(z.string(), z.coerce.number()).optional(),
lb_mode: lbModeSchema.catch('round_robin'), lb_mode: lbModeSchema.catch('round_robin'),
health_check_enabled: z.coerce.boolean().default(false), health_check_enabled: z.coerce.boolean().default(false),
health_check_type: healthCheckTypeSchema.catch('tcp'), health_check_type: healthCheckTypeSchema.catch('tcp'),
health_check_port: z.number().nullable(), health_check_port: z.number().nullable().default(null),
health_check_path: z.string().nullable(), health_check_path: z.string().nullable().default(null),
health_check_expected_status: z.number().nullable(), health_check_expected_status: z.number().nullable().default(null),
health_check_interval_sec: z.number().default(30), health_check_interval_sec: z.number().default(30),
health_check_timeout_ms: z.number().default(3000), health_check_timeout_ms: z.number().default(3000),
health_check_verify_tls: z.coerce.boolean().default(false), health_check_verify_tls: z.coerce.boolean().default(false),
sync_status: z.string().nullable(), sync_status: z.string().nullable().default(null),
created_at: z.string(), created_at: z.string(),
updated_at: z.string(), updated_at: z.string(),
}) })