Files
xingrin/frontend/hooks/use-subdomains.ts
2025-12-12 18:04:57 +08:00

297 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client"
import { useMutation, useQuery, useQueryClient, keepPreviousData } from "@tanstack/react-query"
import { toast } from "sonner"
import { SubdomainService } from "@/services/subdomain.service"
import { OrganizationService } from "@/services/organization.service"
import type { Subdomain, GetSubdomainsResponse, GetAllSubdomainsParams } from "@/types/subdomain.types"
import type { PaginationParams } from "@/types/common.types"
// Query Keys
export const subdomainKeys = {
all: ['subdomains'] as const,
lists: () => [...subdomainKeys.all, 'list'] as const,
list: (params: PaginationParams & { organizationId?: string }) =>
[...subdomainKeys.lists(), params] as const,
details: () => [...subdomainKeys.all, 'detail'] as const,
detail: (id: number) => [...subdomainKeys.details(), id] as const,
}
// 获取单个子域名详情
export function useSubdomain(id: number) {
return useQuery({
queryKey: subdomainKeys.detail(id),
queryFn: () => SubdomainService.getSubdomainById(id),
enabled: !!id,
})
}
// 获取组织的子域名列表
export function useOrganizationSubdomains(
organizationId: number,
params?: { page?: number; pageSize?: number },
options?: { enabled?: boolean }
) {
return useQuery({
queryKey: ['organizations', 'detail', organizationId, 'subdomains', {
page: params?.page,
pageSize: params?.pageSize,
}],
queryFn: () => SubdomainService.getSubdomainsByOrgId(organizationId, {
page: params?.page || 1,
pageSize: params?.pageSize || 10,
}),
enabled: options?.enabled !== undefined ? options.enabled : true,
select: (response) => ({
domains: response.domains || [],
pagination: {
total: response.total || 0,
page: response.page || 1,
pageSize: response.pageSize || 10,
totalPages: response.totalPages || 0,
}
}),
})
}
// 创建子域名(绑定到资产)
export function useCreateSubdomain() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: (data: { domains: Array<{ name: string }>; assetId: number }) =>
SubdomainService.createSubdomains(data),
onMutate: async () => {
toast.loading('正在创建子域名...', { id: 'create-subdomain' })
},
onSuccess: (response) => {
toast.dismiss('create-subdomain')
const { createdCount, existedCount, skippedCount = 0 } = response
if (skippedCount > 0 && existedCount > 0) {
toast.warning(`成功创建 ${createdCount} 个子域名(${existedCount} 个已存在,${skippedCount} 个已跳过)`)
} else if (skippedCount > 0) {
toast.warning(`成功创建 ${createdCount} 个子域名(${skippedCount} 个已跳过)`)
} else if (existedCount > 0) {
toast.warning(`成功创建 ${createdCount} 个子域名(${existedCount} 个已存在)`)
} else {
toast.success(`成功创建 ${createdCount} 个子域名`)
}
queryClient.invalidateQueries({ queryKey: ['subdomains'] })
queryClient.invalidateQueries({ queryKey: ['assets'] })
},
onError: (error: any) => {
toast.dismiss('create-subdomain')
console.error('创建子域名失败:', error)
console.error('后端响应:', error?.response?.data || error)
toast.error('创建子域名失败,请查看控制台日志')
},
})
}
// 从组织中移除子域名
export function useDeleteSubdomainFromOrganization() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: (data: { organizationId: number; targetId: number }) =>
OrganizationService.unlinkTargetsFromOrganization({
organizationId: data.organizationId,
targetIds: [data.targetId],
}),
onMutate: ({ organizationId, targetId }) => {
toast.loading('正在移除子域名...', { id: `delete-${organizationId}-${targetId}` })
},
onSuccess: (_response, { organizationId }) => {
toast.dismiss(`delete-${organizationId}`)
toast.success('子域名已成功移除')
queryClient.invalidateQueries({ queryKey: ['subdomains'] })
queryClient.invalidateQueries({ queryKey: ['organizations'] })
},
onError: (error: any, { organizationId, targetId }) => {
toast.dismiss(`delete-${organizationId}-${targetId}`)
console.error('移除子域名失败:', error)
console.error('后端响应:', error?.response?.data || error)
toast.error('移除子域名失败,请查看控制台日志')
},
})
}
// 批量从组织中移除子域名
export function useBatchDeleteSubdomainsFromOrganization() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: (data: { organizationId: number; domainIds: number[] }) =>
SubdomainService.batchDeleteSubdomainsFromOrganization(data),
onMutate: ({ organizationId }) => {
toast.loading('正在批量移除子域名...', { id: `batch-delete-${organizationId}` })
},
onSuccess: (response, { organizationId }) => {
toast.dismiss(`batch-delete-${organizationId}`)
const successCount = response.successCount || 0
const failedCount = response.failedCount || 0
if (failedCount > 0) {
toast.warning(`批量移除完成(成功:${successCount},失败:${failedCount}`)
} else {
toast.success(`成功移除 ${successCount} 个子域名`)
}
queryClient.invalidateQueries({ queryKey: ['subdomains'] })
queryClient.invalidateQueries({ queryKey: ['organizations'] })
},
onError: (error: any, { organizationId }) => {
toast.dismiss(`batch-delete-${organizationId}`)
console.error('批量移除子域名失败:', error)
console.error('后端响应:', error?.response?.data || error)
toast.error('批量移除失败,请查看控制台日志')
},
})
}
// 删除单个子域名(使用单独的 DELETE API
export function useDeleteSubdomain() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: (id: number) => SubdomainService.deleteSubdomain(id),
onMutate: (id) => {
toast.loading('正在删除子域名...', { id: `delete-subdomain-${id}` })
},
onSuccess: (response, id) => {
toast.dismiss(`delete-subdomain-${id}`)
// 显示删除信息(单个删除 API 返回两阶段信息)
const { subdomainName, detail } = response
toast.success(`子域名 "${subdomainName}" 已成功删除`, {
description: `${detail.phase1}${detail.phase2}`,
duration: 4000
})
queryClient.invalidateQueries({ queryKey: ['subdomains'] })
queryClient.invalidateQueries({ queryKey: ['targets'] })
queryClient.invalidateQueries({ queryKey: ['scans'] })
queryClient.invalidateQueries({ queryKey: ['organizations'] })
},
onError: (error: any, id) => {
toast.dismiss(`delete-subdomain-${id}`)
console.error('删除子域名失败:', error)
console.error('后端响应:', error?.response?.data || error)
toast.error('删除子域名失败,请查看控制台日志')
},
})
}
// 批量删除子域名(使用统一的批量删除接口)
export function useBatchDeleteSubdomains() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: (ids: number[]) => SubdomainService.batchDeleteSubdomains(ids),
onMutate: () => {
toast.loading('正在批量删除子域名...', { id: 'batch-delete-subdomains' })
},
onSuccess: (response) => {
toast.dismiss('batch-delete-subdomains')
// 显示级联删除信息
const cascadeInfo = Object.entries(response.cascadeDeleted || {})
.filter(([key, count]) => key !== 'asset.Subdomain' && count > 0)
.map(([key, count]) => {
const modelName = key.split('.')[1]
return `${modelName}: ${count}`
})
.join(', ')
if (cascadeInfo) {
toast.success(`成功删除 ${response.deletedCount} 个子域名(级联删除: ${cascadeInfo}`)
} else {
toast.success(`成功删除 ${response.deletedCount} 个子域名`)
}
queryClient.invalidateQueries({ queryKey: ['subdomains'] })
queryClient.invalidateQueries({ queryKey: ['targets'] })
queryClient.invalidateQueries({ queryKey: ['scans'] })
queryClient.invalidateQueries({ queryKey: ['organizations'] })
},
onError: (error: any) => {
toast.dismiss('batch-delete-subdomains')
console.error('批量删除子域名失败:', error)
console.error('后端响应:', error?.response?.data || error)
toast.error('批量删除失败,请查看控制台日志')
},
})
}
// 更新子域名
export function useUpdateSubdomain() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: ({ id, data }: { id: number; data: { name?: string; description?: string } }) =>
SubdomainService.updateSubdomain({ id, ...data }),
onMutate: ({ id }) => {
toast.loading('正在更新子域名...', { id: `update-subdomain-${id}` })
},
onSuccess: (_response, { id }) => {
toast.dismiss(`update-subdomain-${id}`)
toast.success('更新成功')
queryClient.invalidateQueries({ queryKey: ['subdomains'] })
queryClient.invalidateQueries({ queryKey: ['organizations'] })
},
onError: (error: any, { id }) => {
toast.dismiss(`update-subdomain-${id}`)
console.error('更新子域名失败:', error)
console.error('后端响应:', error?.response?.data || error)
toast.error('更新子域名失败,请查看控制台日志')
},
})
}
// 获取所有子域名列表
export function useAllSubdomains(
params: GetAllSubdomainsParams = {},
options?: { enabled?: boolean }
) {
return useQuery({
queryKey: ['subdomains', 'all', { page: params.page, pageSize: params.pageSize }],
queryFn: () => SubdomainService.getAllSubdomains(params),
select: (response) => ({
domains: response.domains || [],
pagination: {
total: response.total || 0,
page: response.page || 1,
pageSize: response.pageSize || 10,
totalPages: response.totalPages || 0,
}
}),
enabled: options?.enabled !== undefined ? options.enabled : true,
})
}
// 获取目标的子域名列表
export function useTargetSubdomains(
targetId: number,
params?: { page?: number; pageSize?: number; search?: string },
options?: { enabled?: boolean }
) {
return useQuery({
queryKey: ['targets', targetId, 'subdomains', { page: params?.page, pageSize: params?.pageSize, search: params?.search }],
queryFn: () => SubdomainService.getSubdomainsByTargetId(targetId, params),
enabled: options?.enabled !== undefined ? options.enabled : !!targetId,
placeholderData: keepPreviousData,
})
}
// 获取扫描的子域名列表
export function useScanSubdomains(
scanId: number,
params?: { page?: number; pageSize?: number; search?: string },
options?: { enabled?: boolean }
) {
return useQuery({
queryKey: ['scans', scanId, 'subdomains', { page: params?.page, pageSize: params?.pageSize, search: params?.search }],
queryFn: () => SubdomainService.getSubdomainsByScanId(scanId, params),
enabled: options?.enabled !== undefined ? options.enabled : !!scanId,
placeholderData: keepPreviousData,
})
}