feat:Update pnpm-lock.yaml to link shared package; modify API routes to utilize new schemas for domain and service management; enhance DNS record handling with CNAME support; refactor service and subdomain routes for improved functionality; implement confirm dialog for domain deletion in the frontend; clean up unused components and improve UI consistency.
Build, Test, and Push CFDM Docker Image / test (push) Failing after 51s
Build, Test, and Push CFDM Docker Image / build-and-push (push) Has been skipped
Build, Test, and Push CFDM Docker Image / update-wiki (push) Has been skipped
Build, Test, and Push CFDM Docker Image / create-release (push) Has been skipped
Build, Test, and Push CFDM Docker Image / test (push) Failing after 51s
Build, Test, and Push CFDM Docker Image / build-and-push (push) Has been skipped
Build, Test, and Push CFDM Docker Image / update-wiki (push) Has been skipped
Build, Test, and Push CFDM Docker Image / create-release (push) Has been skipped
This commit is contained in:
1 parent
4a70cf5854
commit
6d1d8ca4f3
99 files changed
+6692
-1483
No files matched your search
@@ -9,3 +9,13 @@ export const CERT_WARNING = "warning";
|
||||
export const CERT_EXPIRED = "expired";
|
||||
export const CERT_ERROR = "error";
|
||||
export const CERT_UNKNOWN = "unknown";
|
||||
|
||||
export const CERT_MONITOR_AUTO = "auto";
|
||||
export const CERT_MONITOR_REQUIRED = "required";
|
||||
export const CERT_MONITOR_SKIPPED = "skipped";
|
||||
|
||||
export const CERT_MONITORING_VALUES = [
|
||||
CERT_MONITOR_AUTO,
|
||||
CERT_MONITOR_REQUIRED,
|
||||
CERT_MONITOR_SKIPPED,
|
||||
] as const;
|
||||
@@ -11,6 +11,7 @@ export type {
|
||||
LoginResponse,
|
||||
JwtClaims,
|
||||
SyncJob,
|
||||
ServiceBinding,
|
||||
ServiceBindingView,
|
||||
ServiceDomainBindingView,
|
||||
Subdomain,
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { z } from 'zod'
|
||||
|
||||
export const certMonitoringSchema = z.enum(['auto', 'required', 'skipped'])
|
||||
|
||||
export type CertMonitoring = z.infer<typeof certMonitoringSchema>
|
||||
|
||||
export const groupSchema = z.object({
|
||||
id: z.number(),
|
||||
name: z.string(),
|
||||
@@ -50,8 +54,10 @@ export const serviceDomainBindingSchema = z
|
||||
zone_name: z.string(),
|
||||
hostname: z.string(),
|
||||
fqdn: z.string(),
|
||||
record_type: z.enum(['A', 'CNAME']).default('A'),
|
||||
target_ips: z.array(z.string()).optional(),
|
||||
target_ip: z.string().nullable().optional(),
|
||||
target_cname: z.string().nullable().optional(),
|
||||
sync_status: z.string().nullable(),
|
||||
})
|
||||
.transform((binding) => ({
|
||||
@@ -62,6 +68,10 @@ export const serviceDomainBindingSchema = z
|
||||
: binding.target_ip
|
||||
? [binding.target_ip]
|
||||
: [],
|
||||
target_cname: binding.target_cname?.trim() || null,
|
||||
record_type: binding.target_cname?.trim()
|
||||
? 'CNAME'
|
||||
: binding.record_type ?? 'A',
|
||||
}))
|
||||
|
||||
export const serviceViewSchema = serviceSchema.extend({
|
||||
@@ -86,6 +96,7 @@ export const domainSchema = z.object({
|
||||
zone_name: z.string(),
|
||||
cf_zone_id: z.string(),
|
||||
status: z.string(),
|
||||
cert_monitoring: certMonitoringSchema.default('auto'),
|
||||
last_synced_at: z.string().nullable(),
|
||||
created_at: z.string(),
|
||||
updated_at: z.string(),
|
||||
@@ -96,22 +107,33 @@ export const domainListItemSchema = domainSchema.extend({
|
||||
service_count: z.number(),
|
||||
})
|
||||
|
||||
export const serviceBindingSchema = z.object({
|
||||
id: z.number(),
|
||||
domain_id: z.number(),
|
||||
service_id: z.number(),
|
||||
hostname: z.string(),
|
||||
dns_record_id: z.number().nullable(),
|
||||
zone_name: z.string(),
|
||||
group_id: z.number().nullable(),
|
||||
group_name: z.string().nullable(),
|
||||
service_name: z.string(),
|
||||
service_slug: z.string(),
|
||||
target_ip: z.string().nullable(),
|
||||
sync_status: z.string().nullable(),
|
||||
created_at: z.string(),
|
||||
updated_at: z.string(),
|
||||
})
|
||||
export const serviceBindingSchema = z
|
||||
.object({
|
||||
id: z.number(),
|
||||
domain_id: z.number(),
|
||||
service_id: z.number(),
|
||||
hostname: z.string(),
|
||||
dns_record_id: z.number().nullable(),
|
||||
zone_name: z.string(),
|
||||
group_id: z.number().nullable(),
|
||||
group_name: z.string().nullable(),
|
||||
service_name: z.string(),
|
||||
service_slug: z.string(),
|
||||
target_ip: z.string().nullable(),
|
||||
target_ips: z.array(z.string()).optional(),
|
||||
sync_status: z.string().nullable(),
|
||||
created_at: z.string(),
|
||||
updated_at: z.string(),
|
||||
})
|
||||
.transform((binding) => ({
|
||||
...binding,
|
||||
target_ips:
|
||||
binding.target_ips && binding.target_ips.length > 0
|
||||
? binding.target_ips
|
||||
: binding.target_ip
|
||||
? [binding.target_ip]
|
||||
: [],
|
||||
}))
|
||||
|
||||
export const dnsRecordSchema = z.object({
|
||||
id: z.number(),
|
||||
@@ -153,7 +175,6 @@ export type ServiceGroupView = z.infer<typeof serviceGroupViewSchema>
|
||||
export type ServiceGroupsResponse = z.infer<typeof serviceGroupsResponseSchema>
|
||||
export type Domain = z.infer<typeof domainSchema>
|
||||
export type DomainListItem = z.infer<typeof domainListItemSchema>
|
||||
export type ServiceBinding = z.infer<typeof serviceBindingSchema>
|
||||
export type DnsRecord = z.infer<typeof dnsRecordSchema>
|
||||
export type Certificate = z.infer<typeof certificateSchema>
|
||||
|
||||
@@ -169,10 +190,30 @@ const ipv4Schema = z
|
||||
'Некорректный IPv4',
|
||||
)
|
||||
|
||||
const serviceDomainInputSchema = z.object({
|
||||
fqdn: z.string().min(1, 'Укажите FQDN'),
|
||||
target_ips: z.array(ipv4Schema).min(1, 'Выберите хотя бы один IP'),
|
||||
})
|
||||
const serviceDomainInputSchema = z
|
||||
.object({
|
||||
fqdn: z.string().min(1, 'Укажите FQDN'),
|
||||
target_ips: z.array(ipv4Schema).optional(),
|
||||
target_cname: z.string().min(1, 'Укажите CNAME-цель').optional(),
|
||||
})
|
||||
.superRefine((data, ctx) => {
|
||||
const hasIps = (data.target_ips?.length ?? 0) > 0
|
||||
const hasCname = Boolean(data.target_cname?.trim())
|
||||
if (!hasIps && !hasCname) {
|
||||
ctx.addIssue({
|
||||
code: 'custom',
|
||||
message: 'Укажите IP или CNAME-цель',
|
||||
path: ['target_ips'],
|
||||
})
|
||||
}
|
||||
if (hasIps && hasCname) {
|
||||
ctx.addIssue({
|
||||
code: 'custom',
|
||||
message: 'Укажите либо IP, либо CNAME-цель',
|
||||
path: ['target_cname'],
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
export const createServiceSchema = z.object({
|
||||
name: z.string().min(1, 'Укажите название'),
|
||||
@@ -220,12 +261,34 @@ export const subdomainSchema = z.object({
|
||||
domain_id: z.number(),
|
||||
name: z.string(),
|
||||
fqdn: z.string(),
|
||||
enabled: z.boolean(),
|
||||
cert_monitoring: certMonitoringSchema.default('auto'),
|
||||
created_at: z.string(),
|
||||
updated_at: z.string(),
|
||||
})
|
||||
|
||||
export type SubdomainRecord = z.infer<typeof subdomainSchema>
|
||||
|
||||
export const createSubdomainSchema = z.object({
|
||||
name: z.string().min(1, 'Укажите имя поддомена'),
|
||||
})
|
||||
|
||||
export const updateSubdomainSchema = z.object({
|
||||
name: z.string().min(1, 'Укажите имя поддомена').optional(),
|
||||
enabled: z.boolean().optional(),
|
||||
cert_monitoring: certMonitoringSchema.optional(),
|
||||
})
|
||||
|
||||
export const updateDomainSchema = z.object({
|
||||
group_id: z.number().nullable().optional(),
|
||||
status: z.string().optional(),
|
||||
cert_monitoring: certMonitoringSchema.optional(),
|
||||
})
|
||||
|
||||
export type CreateSubdomainInput = z.infer<typeof createSubdomainSchema>
|
||||
export type UpdateSubdomainInput = z.infer<typeof updateSubdomainSchema>
|
||||
export type UpdateDomainInput = z.infer<typeof updateDomainSchema>
|
||||
|
||||
export type CreateGroupInput = z.infer<typeof createGroupSchema>
|
||||
export type CreateServiceInput = z.infer<typeof createServiceSchema>
|
||||
export type CreateServiceWithConfigInput = z.infer<typeof createServiceWithConfigSchema>
|
||||
@@ -253,8 +316,14 @@ export const toggleEnabledSchema = z.object({
|
||||
enabled: z.boolean(),
|
||||
})
|
||||
|
||||
export const reorderServicesSchema = z.object({
|
||||
group_id: z.union([z.number(), z.null()]).optional().default(null),
|
||||
service_ids: z.array(z.number().int().positive()).min(1),
|
||||
})
|
||||
|
||||
export type CreateServiceGroupInput = z.infer<typeof createServiceGroupSchema>
|
||||
export type ToggleEnabledInput = z.infer<typeof toggleEnabledSchema>
|
||||
export type ReorderServicesInput = z.infer<typeof reorderServicesSchema>
|
||||
export type CreateServiceBindingInput = z.infer<typeof createServiceBindingSchema>
|
||||
export type CreateDomainInput = z.infer<typeof createDomainSchema>
|
||||
export type LoginInput = z.infer<typeof loginSchema>
|
||||
|
||||
@@ -27,3 +27,26 @@ export function dnsNameToSubdomainLabel(
|
||||
export function subdomainLabelToFqdn(label: string, zoneName: string): string {
|
||||
return label === "@" ? zoneName : `${label}.${zoneName}`;
|
||||
}
|
||||
|
||||
/** Canonical DNS record name as returned by Cloudflare for a zone. */
|
||||
export function normalizeDnsRecordName(
|
||||
recordName: string,
|
||||
zoneName: string,
|
||||
): string {
|
||||
const label = dnsNameToSubdomainLabel(recordName, zoneName);
|
||||
if (label == null) {
|
||||
return recordName.trim().replace(/\.+$/, "");
|
||||
}
|
||||
return subdomainLabelToFqdn(label, zoneName);
|
||||
}
|
||||
|
||||
export function dnsRecordNamesMatch(
|
||||
left: string,
|
||||
right: string,
|
||||
zoneName: string,
|
||||
): boolean {
|
||||
return (
|
||||
normalizeDnsRecordName(left, zoneName).toLowerCase() ===
|
||||
normalizeDnsRecordName(right, zoneName).toLowerCase()
|
||||
);
|
||||
}
|
||||
@@ -33,6 +33,7 @@ export interface Service {
|
||||
service_group_id: number | null;
|
||||
subdomain: string;
|
||||
enabled: boolean;
|
||||
sort_order: number;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
@@ -43,6 +44,7 @@ export interface Domain {
|
||||
zone_name: string;
|
||||
cf_zone_id: string;
|
||||
status: string;
|
||||
cert_monitoring: string;
|
||||
last_synced_at: string | null;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
@@ -53,6 +55,8 @@ export interface Subdomain {
|
||||
domain_id: number;
|
||||
name: string;
|
||||
fqdn: string;
|
||||
enabled: boolean;
|
||||
cert_monitoring: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
@@ -92,6 +96,7 @@ export interface ServiceBinding {
|
||||
domain_id: number;
|
||||
service_id: number;
|
||||
hostname: string;
|
||||
cname_target: string | null;
|
||||
dns_record_id: number | null;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
@@ -109,6 +114,7 @@ export interface ServiceBindingView {
|
||||
service_name: string;
|
||||
service_slug: string;
|
||||
target_ip: string | null;
|
||||
target_ips: string[];
|
||||
sync_status: string | null;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
@@ -120,7 +126,9 @@ export interface ServiceDomainBindingView {
|
||||
zone_name: string;
|
||||
hostname: string;
|
||||
fqdn: string;
|
||||
record_type: "A" | "CNAME";
|
||||
target_ips: string[];
|
||||
target_cname: string | null;
|
||||
sync_status: string | null;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
CERT_OK,
|
||||
CERT_WARNING,
|
||||
} from "./constants.js";
|
||||
import type { Service, ServiceGroup } from "./types.js";
|
||||
|
||||
const NAME_RE =
|
||||
/^(@|\*|[a-zA-Z0-9_]([a-zA-Z0-9_-]*[a-zA-Z0-9_])?(\.[a-zA-Z0-9_]([a-zA-Z0-9_-]*[a-zA-Z0-9_])?)*)$/;
|
||||
@@ -70,6 +71,15 @@ export function certStatusFromExpiry(daysLeft: number): string {
|
||||
return CERT_OK;
|
||||
}
|
||||
|
||||
export function shouldMonitorService(
|
||||
service: Pick<Service, "enabled" | "service_group_id">,
|
||||
group?: Pick<ServiceGroup, "enabled"> | null,
|
||||
): boolean {
|
||||
if (!service.enabled) return false;
|
||||
if (!service.service_group_id) return true;
|
||||
return group?.enabled ?? false;
|
||||
}
|
||||
|
||||
export function isValidIpv4(ip: string): boolean {
|
||||
const parts = ip.split(".");
|
||||
if (parts.length !== 4) return false;
|
||||
|
||||
Reference in new issue
Block a user