This commit is contained in:
ryan
2026-07-13 15:08:40 +08:00
parent 08e8eea932
commit 1eff7878a1
441 changed files with 26491 additions and 18405 deletions
+84 -44
View File
@@ -1,9 +1,9 @@
'use client';
import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query';
import {Loader2, Network, Plus, RefreshCw} from 'lucide-react';
import {useState} from 'react';
import {toast} from 'sonner';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { Loader2, Network, Plus, RefreshCw } from 'lucide-react';
import { useState } from 'react';
import { toast } from 'sonner';
import {
AlertDialog,
@@ -15,19 +15,33 @@ import {
AlertDialogHeader,
AlertDialogTitle,
} from '@/components/ui/alert-dialog';
import {Button} from '@/components/ui/button';
import {Card, CardContent, CardDescription, CardHeader, CardTitle,} from '@/components/ui/card';
import {EmptyStateWithBorder} from '@/components/layout/empty';
import {ErrorInline} from '@/components/layout/error';
import {LoadingStateWithBorder} from '@/components/layout/loading';
import type {WAFIPGroup, WAFIPGroupAutoTestResult, WAFIPGroupPayload,} from '@/lib/services/openflare';
import {WafService} from '@/lib/services/openflare';
import { Button } from '@/components/ui/button';
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from '@/components/ui/card';
import { EmptyStateWithBorder } from '@/components/layout/empty';
import { ErrorInline } from '@/components/layout/error';
import { LoadingStateWithBorder } from '@/components/layout/loading';
import type {
WAFIPGroup,
WAFIPGroupAutoTestResult,
WAFIPGroupPayload,
} from '@/lib/services/openflare';
import { WafService } from '@/lib/services/openflare';
import {buildIPGroupPayloadFromGroup, getErrorMessage, parseAutomaticConfig} from '../waf/components/helpers';
import {IPGroupDialog} from '../waf/components/ip-group-dialog';
import {IPGroupTestDialog} from '../waf/components/ip-group-test-dialog';
import {IPGroupViewDialog} from '../waf/components/ip-group-view-dialog';
import {IPGroupsTable} from '../waf/components/ip-groups-table';
import {
buildIPGroupPayloadFromGroup,
getErrorMessage,
parseAutomaticConfig,
} from '../waf/components/helpers';
import { IPGroupDialog } from '../waf/components/ip-group-dialog';
import { IPGroupTestDialog } from '../waf/components/ip-group-test-dialog';
import { IPGroupViewDialog } from '../waf/components/ip-group-view-dialog';
import { IPGroupsTable } from '../waf/components/ip-groups-table';
const ipGroupsQueryKey = ['openflare', 'waf', 'ip-groups'];
@@ -37,7 +51,9 @@ export default function WafIPGroupsPage() {
const [editingGroup, setEditingGroup] = useState<WAFIPGroup | null>(null);
const [deleteTarget, setDeleteTarget] = useState<WAFIPGroup | null>(null);
const [testOpen, setTestOpen] = useState(false);
const [testResult, setTestResult] = useState<WAFIPGroupAutoTestResult | null>(null);
const [testResult, setTestResult] = useState<WAFIPGroupAutoTestResult | null>(
null,
);
const [syncingId, setSyncingId] = useState<number | null>(null);
const [viewOpen, setViewOpen] = useState(false);
const [viewingGroup, setViewingGroup] = useState<WAFIPGroup | null>(null);
@@ -51,8 +67,12 @@ export default function WafIPGroupsPage() {
const invalidate = async () => {
await Promise.all([
queryClient.invalidateQueries({ queryKey: ipGroupsQueryKey }),
queryClient.invalidateQueries({ queryKey: ['openflare', 'waf', 'rule-groups'] }),
queryClient.invalidateQueries({ queryKey: ['openflare', 'config-versions', 'diff'] }),
queryClient.invalidateQueries({
queryKey: ['openflare', 'waf', 'rule-groups'],
}),
queryClient.invalidateQueries({
queryKey: ['openflare', 'config-versions', 'diff'],
}),
]);
};
@@ -118,7 +138,10 @@ export default function WafIPGroupsPage() {
const removeIpMutation = useMutation({
mutationFn: async ({ group, ip }: { group: WAFIPGroup; ip: string }) => {
const nextIpList = group.ip_list.filter((item) => item !== ip);
return WafService.updateIPGroup(group.id, buildIPGroupPayloadFromGroup(group, nextIpList));
return WafService.updateIPGroup(
group.id,
buildIPGroupPayloadFromGroup(group, nextIpList),
);
},
onMutate: ({ ip }) => {
setRemovingIp(ip);
@@ -192,40 +215,47 @@ export default function WafIPGroupsPage() {
const viewGroup = viewGroupQuery.data ?? viewingGroup;
return (
<div className="py-6 px-1 space-y-6">
<div className="flex items-center justify-between gap-3">
<div className="flex items-center gap-2">
<Network className="size-5 text-primary" />
<h1 className="text-2xl font-semibold tracking-tight">IP 组</h1>
<div className='py-6 px-1 space-y-6'>
<div className='flex items-center justify-between gap-3'>
<div className='flex items-center gap-2'>
<Network className='size-5 text-primary' />
<h1 className='text-2xl font-semibold tracking-tight'>IP 组</h1>
</div>
<div className="flex items-center gap-2">
<Button variant="secondary" size="sm" className="h-7 text-xs" onClick={handleCreate}>
<Plus className="size-3.5 mr-1" />
<div className='flex items-center gap-2'>
<Button
variant='secondary'
size='sm'
className='h-7 text-xs'
onClick={handleCreate}
>
<Plus className='size-3.5 mr-1' />
新建 IP 组
</Button>
</div>
</div>
<Card className="border-dashed shadow-none">
<CardHeader className="pb-3">
<div className="flex items-center justify-between gap-3">
<Card className='border-dashed shadow-none'>
<CardHeader className='pb-3'>
<div className='flex items-center justify-between gap-3'>
<div>
<CardTitle className="text-base font-semibold">IP 组列表</CardTitle>
<CardTitle className='text-base font-semibold'>
IP 组列表
</CardTitle>
<CardDescription>
维护可被 WAF IP 黑白名单引用的手动、自动与订阅 IP 集合。
</CardDescription>
</div>
<Button
variant="outline"
size="sm"
className="h-7 text-xs"
variant='outline'
size='sm'
className='h-7 text-xs'
onClick={handleRefresh}
disabled={groupsQuery.isFetching}
>
{groupsQuery.isFetching ? (
<Loader2 className="size-3.5 mr-1 animate-spin" />
<Loader2 className='size-3.5 mr-1 animate-spin' />
) : (
<RefreshCw className="size-3.5 mr-1" />
<RefreshCw className='size-3.5 mr-1' />
)}
刷新
</Button>
@@ -233,17 +263,23 @@ export default function WafIPGroupsPage() {
</CardHeader>
<CardContent>
{groupsQuery.isLoading ? (
<LoadingStateWithBorder icon={Network} description="加载 IP 组中..." />
<LoadingStateWithBorder
icon={Network}
description='加载 IP 组中...'
/>
) : groupsQuery.isError ? (
<div className="p-8 border border-dashed rounded-lg">
<div className='p-8 border border-dashed rounded-lg'>
<ErrorInline
message={getErrorMessage(groupsQuery.error)}
onRetry={handleRefresh}
className="justify-center"
className='justify-center'
/>
</div>
) : groups.length === 0 ? (
<EmptyStateWithBorder icon={Network} description="暂无 IP 组,请先创建一个。" />
<EmptyStateWithBorder
icon={Network}
description='暂无 IP 组,请先创建一个。'
/>
) : (
<IPGroupsTable
groups={groups}
@@ -304,11 +340,15 @@ export default function WafIPGroupsPage() {
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel disabled={deleteMutation.isPending}>取消</AlertDialogCancel>
<AlertDialogCancel disabled={deleteMutation.isPending}>
取消
</AlertDialogCancel>
<AlertDialogAction
className="bg-destructive text-white hover:bg-destructive/90"
className='bg-destructive text-white hover:bg-destructive/90'
disabled={deleteMutation.isPending}
onClick={() => deleteTarget && deleteMutation.mutate(deleteTarget.id)}
onClick={() =>
deleteTarget && deleteMutation.mutate(deleteTarget.id)
}
>
{deleteMutation.isPending ? '删除中...' : '确认删除'}
</AlertDialogAction>