Update pnpm-lock.yaml to include new dependencies for @dnd-kit packages; enhance frontend documentation with MCP patterns and shadcn guidelines; refactor route handling for groups and services; implement new DataTableCard and KanbanBoard components for better domain and service management; add service binding functionality and improve DNS record management with new schemas and queries.
Build, Test, and Push CFDM Docker Image / test (push) Failing after 16h45m4s
Build, Test, and Push CFDM Docker Image / create-release (push) Has been cancelled
Build, Test, and Push CFDM Docker Image / build-and-push (push) Has been cancelled
Build, Test, and Push CFDM Docker Image / update-wiki (push) Has been cancelled

This commit is contained in:
Denozordec
2026-06-15 16:41:10 +07:00
parent b1467575c5
commit 5f7b3ac388
50 changed files with 3364 additions and 367 deletions
+46
View File
@@ -0,0 +1,46 @@
import type { DnsRecord } from '@/lib/schemas'
export interface DnsRecordGroup {
key: string
recordType: string
name: string
records: DnsRecord[]
isMultiValue: boolean
}
const MULTI_VALUE_TYPES = new Set(['A', 'AAAA'])
function groupKey(record: DnsRecord): string {
return `${record.record_type}:${record.name}`
}
export function groupDnsRecords(records: DnsRecord[]): DnsRecordGroup[] {
const map = new Map<string, DnsRecord[]>()
for (const record of records) {
const key = groupKey(record)
const list = map.get(key) ?? []
list.push(record)
map.set(key, list)
}
return [...map.entries()].map(([key, groupRecords]) => {
const [recordType, ...nameParts] = key.split(':')
const name = nameParts.join(':')
const isMultiValue =
MULTI_VALUE_TYPES.has(recordType) && groupRecords.length > 1
return {
key,
recordType,
name,
records: groupRecords,
isMultiValue,
}
})
}
export function allSameValue<T>(values: T[]): boolean {
if (values.length <= 1) return true
return values.every((v) => v === values[0])
}
+32
View File
@@ -0,0 +1,32 @@
import type { ServiceBinding } from '@/lib/schemas'
export function getDomainIps(bindings: ServiceBinding[], domainId: number): string[] {
const ips = bindings
.filter((b) => b.domain_id === domainId && b.target_ip)
.map((b) => b.target_ip as string)
return [...new Set(ips)]
}
export function groupBindingsByHostname(
bindings: ServiceBinding[],
): Map<string, ServiceBinding[]> {
const map = new Map<string, ServiceBinding[]>()
for (const binding of bindings) {
const list = map.get(binding.hostname) ?? []
list.push(binding)
map.set(binding.hostname, list)
}
return map
}
export function buildIpsByDomainId(bindings: ServiceBinding[]): Map<number, string[]> {
const map = new Map<number, string[]>()
for (const binding of bindings) {
if (!binding.target_ip) continue
const existing = map.get(binding.domain_id) ?? []
if (!existing.includes(binding.target_ip)) {
map.set(binding.domain_id, [...existing, binding.target_ip])
}
}
return map
}
+42
View File
@@ -8,6 +8,10 @@ export const groupSchema = z.object({
updated_at: z.string(),
})
export const groupWithStatsSchema = groupSchema.extend({
domain_count: z.number(),
})
export const serviceSchema = z.object({
id: z.number(),
name: z.string(),
@@ -27,6 +31,28 @@ export const domainSchema = z.object({
updated_at: z.string(),
})
export const domainListItemSchema = domainSchema.extend({
group_name: z.string().nullable(),
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 dnsRecordSchema = z.object({
id: z.number(),
domain_id: z.number(),
@@ -58,8 +84,11 @@ export const certificateSchema = z.object({
})
export type Group = z.infer<typeof groupSchema>
export type GroupWithStats = z.infer<typeof groupWithStatsSchema>
export type Service = z.infer<typeof serviceSchema>
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>
@@ -73,6 +102,18 @@ export const createServiceSchema = z.object({
slug: z.string().min(1, 'Укажите slug'),
})
export const createServiceBindingSchema = z.object({
domain_id: z.string().min(1, 'Выберите домен'),
service_id: z.string().min(1, 'Выберите сервис'),
hostname: z.string().optional(),
target_ip: z.string().optional(),
})
export const updateDomainGroupSchema = z.object({
group_id: z.number().nullable(),
status: z.string().optional(),
})
export const createDomainSchema = z.object({
zone_name: z.string().min(1, 'Укажите имя зоны'),
group_id: z.string(),
@@ -93,6 +134,7 @@ export const createDnsRecordSchema = z.object({
export type CreateGroupInput = z.infer<typeof createGroupSchema>
export type CreateServiceInput = z.infer<typeof createServiceSchema>
export type CreateServiceBindingInput = z.infer<typeof createServiceBindingSchema>
export type CreateDomainInput = z.infer<typeof createDomainSchema>
export type LoginInput = z.infer<typeof loginSchema>
export type CreateDnsRecordInput = z.infer<typeof createDnsRecordSchema>