Enhance CDN Manager: Add Cloudflare API token support, update documentation, and introduce new routes for managing nodes, aliases, and topology. Improve UI components and status badges for better user experience.
quality / commitlint (push) Skipped
quality / changes (push) Successful in 5s
quality / docker-check (push) Skipped
CD / update-wiki (push) Successful in 6s
quality / web (push) Successful in 53s
quality / api (push) Successful in 49s
CD / quality (push) Successful in 1m57s
CD / publish (push) Successful in 27s

This commit is contained in:
Denozordec
2026-09-04 12:49:51 +07:00
parent d480325357
commit 8700dc2957
47 changed files with 10550 additions and 69 deletions
+192
View File
@@ -0,0 +1,192 @@
import { queryOptions } from '@tanstack/react-query'
import type {
Alias,
AliasCreate,
AliasPatch,
AliasRetarget,
BindExport,
DashboardStats,
Location,
Node,
NodeCreate,
NodePatch,
Topology,
Zone,
ZoneCreate,
ZonePatch,
SyncJob,
CfZone,
} from '@cdnmanager/shared'
import { api } from '@/lib/api-client'
export const fleetKeys = {
all: ['fleet'] as const,
locations: () => [...fleetKeys.all, 'locations'] as const,
zones: () => [...fleetKeys.all, 'zones'] as const,
cfZones: () => [...fleetKeys.all, 'cf-zones'] as const,
nodes: (filters?: Record<string, string | undefined>) =>
[...fleetKeys.all, 'nodes', filters ?? {}] as const,
node: (id: string) => [...fleetKeys.all, 'node', id] as const,
aliases: (filters?: Record<string, string | undefined>) =>
[...fleetKeys.all, 'aliases', filters ?? {}] as const,
alias: (id: string) => [...fleetKeys.all, 'alias', id] as const,
dashboard: () => [...fleetKeys.all, 'dashboard'] as const,
topology: (zoneId?: string) =>
[...fleetKeys.all, 'topology', zoneId ?? 'all'] as const,
syncJobs: (zoneId: string) =>
[...fleetKeys.all, 'sync-jobs', zoneId] as const,
bindExport: (zoneId: string) =>
[...fleetKeys.all, 'bind', zoneId] as const,
namingPreview: (params: Record<string, string>) =>
[...fleetKeys.all, 'naming', params] as const,
}
function qs(filters?: Record<string, string | undefined>) {
if (!filters) return ''
const p = new URLSearchParams()
for (const [k, v] of Object.entries(filters)) {
if (v) p.set(k, v)
}
const s = p.toString()
return s ? `?${s}` : ''
}
export const locationsQueryOptions = () =>
queryOptions({
queryKey: fleetKeys.locations(),
queryFn: () => api.get<Location[]>('/api/v1/locations'),
})
export const zonesQueryOptions = () =>
queryOptions({
queryKey: fleetKeys.zones(),
queryFn: () => api.get<Zone[]>('/api/v1/zones'),
})
export const cfZonesQueryOptions = () =>
queryOptions({
queryKey: fleetKeys.cfZones(),
queryFn: () => api.get<CfZone[]>('/api/v1/cloudflare/zones'),
retry: false,
})
export const nodesQueryOptions = (filters?: Record<string, string | undefined>) =>
queryOptions({
queryKey: fleetKeys.nodes(filters),
queryFn: () => api.get<Node[]>(`/api/v1/nodes${qs(filters)}`),
})
export const aliasesQueryOptions = (
filters?: Record<string, string | undefined>,
) =>
queryOptions({
queryKey: fleetKeys.aliases(filters),
queryFn: () => api.get<Alias[]>(`/api/v1/aliases${qs(filters)}`),
})
export const dashboardStatsQueryOptions = () =>
queryOptions({
queryKey: fleetKeys.dashboard(),
queryFn: () => api.get<DashboardStats>('/api/v1/dashboard/stats'),
staleTime: 15_000,
})
export const topologyQueryOptions = (zoneId?: string) =>
queryOptions({
queryKey: fleetKeys.topology(zoneId),
queryFn: () =>
api.get<Topology>(
`/api/v1/topology${zoneId ? `?zoneId=${encodeURIComponent(zoneId)}` : ''}`,
),
})
export const syncJobsQueryOptions = (zoneId: string) =>
queryOptions({
queryKey: fleetKeys.syncJobs(zoneId),
queryFn: () => api.get<SyncJob[]>(`/api/v1/zones/${zoneId}/sync-jobs`),
enabled: Boolean(zoneId),
})
export const bindExportQueryOptions = (zoneId: string) =>
queryOptions({
queryKey: fleetKeys.bindExport(zoneId),
queryFn: () =>
api.get<BindExport>(`/api/v1/zones/${zoneId}/export/bind`),
enabled: Boolean(zoneId),
})
export async function createZone(body: ZoneCreate) {
return api.post<Zone>('/api/v1/zones', body)
}
export async function patchZone(id: string, body: ZonePatch) {
return api.patch<Zone>(`/api/v1/zones/${id}`, body)
}
export async function removeZone(id: string) {
return api.delete(`/api/v1/zones/${id}`)
}
export async function syncZone(id: string) {
return api.post<SyncJob>(`/api/v1/zones/${id}/sync`, {})
}
export async function applyZone(id: string, opIds?: string[]) {
return api.post<SyncJob>(`/api/v1/zones/${id}/apply`, { opIds })
}
export async function createNode(body: NodeCreate) {
return api.post<Node>('/api/v1/nodes', body)
}
export async function patchNode(id: string, body: NodePatch) {
return api.patch<Node>(`/api/v1/nodes/${id}`, body)
}
export async function removeNode(id: string) {
return api.delete(`/api/v1/nodes/${id}`)
}
export async function createAlias(body: AliasCreate) {
return api.post<Alias>('/api/v1/aliases', body)
}
export async function patchAlias(id: string, body: AliasPatch) {
return api.patch<Alias>(`/api/v1/aliases/${id}`, body)
}
export async function retargetAliasApi(id: string, body: AliasRetarget) {
return api.post<Alias>(`/api/v1/aliases/${id}/retarget`, body)
}
export async function removeAlias(id: string) {
return api.delete(`/api/v1/aliases/${id}`)
}
export async function ignoreOrphan(
zoneId: string,
recordName: string,
recordType: string,
) {
return api.post(`/api/v1/zones/${zoneId}/orphans/ignore`, {
recordName,
recordType,
})
}
export async function previewHostname(params: {
zoneId: string
locationId: string
role: string
indexNum?: number
providerTag?: string
}) {
const p = new URLSearchParams({
zoneId: params.zoneId,
locationId: params.locationId,
role: params.role,
indexNum: String(params.indexNum ?? 1),
})
if (params.providerTag) p.set('providerTag', params.providerTag)
return api.get<{ hostname: string }>(`/api/v1/naming/preview?${p}`)
}