Files
EvoBGP/apps/web/src/queries/modules.ts
T
DenozordecandCursor e190785d4f
quality / commitlint (push) Skipped
quality / changes (push) Successful in 6s
quality / go (push) Skipped
quality / bird2 (push) Skipped
quality / docker-check (push) Skipped
quality / openapi (push) Successful in 22s
quality / web (push) Successful in 58s
CD / quality (push) Successful in 1m30s
CD / publish (push) Successful in 3m1s
feat(web): add module create form on /modules/new
Форма POST /v1/modules вместо заглушки: тип, название, расписание и DoH для DOMAINS. После 201 переход на карточку; кнопка «Создать» только при bgp:modules:write.

Co-authored-by: Cursor <[email protected]>
2026-08-18 20:17:51 +07:00

92 lines
3.3 KiB
TypeScript

import { queryOptions, useMutation, useQueryClient } from '@tanstack/react-query'
import { toast } from 'sonner'
import { apiJSON, apiMutate } from '@/lib/api-client'
import { overviewKeys } from '@/queries/overview'
import type {
ModuleCreate,
ModulePatch,
ModuleRow,
ModulesResponse,
Page,
} from '@/types/api'
export const modulesKeys = {
all: ['modules'] as const,
list: () => [...modulesKeys.all, 'list'] as const,
detail: (id: string) => [...modulesKeys.all, 'detail', id] as const,
domainEntries: (id: string) => [...modulesKeys.all, 'domain-entries', id] as const,
asEntries: (id: string) => [...modulesKeys.all, 'as-entries', id] as const,
cdnSources: (id: string) => [...modulesKeys.all, 'cdn-sources', id] as const,
ipRangeEntries: (id: string) => [...modulesKeys.all, 'ip-range-entries', id] as const,
}
function invalidateModules(qc: ReturnType<typeof useQueryClient>, id?: string) {
void qc.invalidateQueries({ queryKey: modulesKeys.all })
void qc.invalidateQueries({ queryKey: overviewKeys.modules() })
if (id) {
void qc.invalidateQueries({ queryKey: modulesKeys.detail(id) })
}
}
export function modulesListQueryOptions() {
return queryOptions<ModulesResponse>({
queryKey: modulesKeys.list(),
queryFn: () => apiJSON<ModulesResponse>('/v1/modules?limit=200'),
})
}
export function moduleDetailQueryOptions(id: string) {
return queryOptions<ModuleRow>({
queryKey: modulesKeys.detail(id),
queryFn: () => apiJSON<ModuleRow>(`/v1/modules/${id}`),
})
}
export type ModuleEntriesPage = Page<Record<string, unknown>>
export function moduleEntriesQueryOptions(id: string, type: ModuleRow['type']) {
const pathByType: Record<ModuleRow['type'], string> = {
DOMAINS: `/v1/modules/${id}/domain-entries?limit=500`,
AS_PREFIXES: `/v1/modules/${id}/as-entries?limit=500`,
CDN_CIDRS: `/v1/modules/${id}/cdn-sources?limit=500`,
IP_RANGES: `/v1/modules/${id}/ip-range-entries?limit=500`,
}
const path = pathByType[type]
const keyByType: Record<ModuleRow['type'], readonly string[]> = {
DOMAINS: modulesKeys.domainEntries(id),
AS_PREFIXES: modulesKeys.asEntries(id),
CDN_CIDRS: modulesKeys.cdnSources(id),
IP_RANGES: modulesKeys.ipRangeEntries(id),
}
return queryOptions<ModuleEntriesPage>({
queryKey: keyByType[type],
queryFn: () => apiJSON<ModuleEntriesPage>(path),
})
}
export function useCreateModuleMutation() {
const qc = useQueryClient()
return useMutation({
mutationFn: (body: ModuleCreate) => apiMutate<ModuleRow>('/v1/modules', 'POST', body),
onSuccess: (data) => {
toast.success('Модуль создан')
invalidateModules(qc, data.id)
},
onError: (e) => toast.error(e instanceof Error ? e.message : 'Не удалось создать модуль'),
})
}
export function useUpdateModuleMutation() {
const qc = useQueryClient()
return useMutation({
mutationFn: ({ id, body }: { id: string; body: ModulePatch }) =>
apiMutate<ModuleRow>(`/v1/modules/${id}`, 'PATCH', body, { idempotent: false }),
onSuccess: (_data, vars) => {
toast.success('Модуль обновлён')
invalidateModules(qc, vars.id)
},
onError: (e) => toast.error(e instanceof Error ? e.message : 'Не удалось обновить модуль'),
})
}