refactor(services): update service creation logic and enhance service group schema
- 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:
@@ -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",
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
@@ -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(),
|
||||||
|
|||||||
@@ -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
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -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} />
|
||||||
|
|||||||
Vendored
+2
-2
@@ -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) {
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
Vendored
+51
-51
@@ -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<{
|
||||||
|
|||||||
Vendored
+17
-17
@@ -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) => ({
|
||||||
|
|||||||
@@ -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(),
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user