mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-13 18:36:37 +08:00
prettier
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import {useEffect, useMemo, useState} from 'react';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import {
|
||||
closestCenter,
|
||||
DndContext,
|
||||
@@ -17,14 +17,14 @@ import {
|
||||
useSortable,
|
||||
verticalListSortingStrategy,
|
||||
} from '@dnd-kit/sortable';
|
||||
import {CSS} from '@dnd-kit/utilities';
|
||||
import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query';
|
||||
import {ArrowDown, ArrowUp, GripVertical} from 'lucide-react';
|
||||
import {toast} from 'sonner';
|
||||
import { CSS } from '@dnd-kit/utilities';
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { ArrowDown, ArrowUp, GripVertical } from 'lucide-react';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
import {Badge} from '@/components/ui/badge';
|
||||
import {Button} from '@/components/ui/button';
|
||||
import {Checkbox} from '@/components/ui/checkbox';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import {
|
||||
Field,
|
||||
FieldDescription,
|
||||
@@ -33,15 +33,15 @@ import {
|
||||
FieldLegend,
|
||||
FieldSet,
|
||||
} from '@/components/ui/field';
|
||||
import {EmptyStateWithBorder} from '@/components/layout/empty';
|
||||
import {ErrorInline} from '@/components/layout/error';
|
||||
import {LoadingStateWithBorder} from '@/components/layout/loading';
|
||||
import type {ProxyRouteItem} from '@/lib/services/openflare';
|
||||
import {WafService} from '@/lib/services/openflare';
|
||||
import { EmptyStateWithBorder } from '@/components/layout/empty';
|
||||
import { ErrorInline } from '@/components/layout/error';
|
||||
import { LoadingStateWithBorder } from '@/components/layout/loading';
|
||||
import type { ProxyRouteItem } from '@/lib/services/openflare';
|
||||
import { WafService } from '@/lib/services/openflare';
|
||||
|
||||
import {getErrorMessage} from '../../components/helpers';
|
||||
import {proxyRouteFormIds} from '../helpers';
|
||||
import {SectionShell} from './section-shell';
|
||||
import { getErrorMessage } from '../../components/helpers';
|
||||
import { proxyRouteFormIds } from '../helpers';
|
||||
import { SectionShell } from './section-shell';
|
||||
|
||||
interface WafSectionProps {
|
||||
route: ProxyRouteItem;
|
||||
@@ -56,50 +56,57 @@ interface SortableRuleProps {
|
||||
onMove: (from: number, to: number) => void;
|
||||
}
|
||||
|
||||
export function reorderRuleIDs(ids: number[], activeID: number, overID: number) {
|
||||
export function reorderRuleIDs(
|
||||
ids: number[],
|
||||
activeID: number,
|
||||
overID: number,
|
||||
) {
|
||||
const from = ids.indexOf(activeID);
|
||||
const to = ids.indexOf(overID);
|
||||
return from < 0 || to < 0 || from === to ? ids : arrayMove(ids, from, to);
|
||||
}
|
||||
|
||||
function SortableRule({id, name, index, total, onMove}: SortableRuleProps) {
|
||||
const {attributes, listeners, setNodeRef, transform, transition} = useSortable({id});
|
||||
function SortableRule({ id, name, index, total, onMove }: SortableRuleProps) {
|
||||
const { attributes, listeners, setNodeRef, transform, transition } =
|
||||
useSortable({ id });
|
||||
return (
|
||||
<div
|
||||
ref={setNodeRef}
|
||||
style={{transform: CSS.Transform.toString(transform), transition}}
|
||||
className="flex items-center gap-2 rounded-lg border p-3"
|
||||
style={{ transform: CSS.Transform.toString(transform), transition }}
|
||||
className='flex items-center gap-2 rounded-lg border p-3'
|
||||
>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
type='button'
|
||||
variant='ghost'
|
||||
size='icon-sm'
|
||||
aria-label={`拖动${name}`}
|
||||
{...attributes}
|
||||
{...listeners}
|
||||
>
|
||||
<GripVertical data-icon="inline-start" />
|
||||
<GripVertical data-icon='inline-start' />
|
||||
</Button>
|
||||
<span className="min-w-0 flex-1 truncate text-sm font-medium">{name}</span>
|
||||
<span className='min-w-0 flex-1 truncate text-sm font-medium'>
|
||||
{name}
|
||||
</span>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
type='button'
|
||||
variant='ghost'
|
||||
size='icon-sm'
|
||||
aria-label={`上移${name}`}
|
||||
disabled={index === 0}
|
||||
onClick={() => onMove(index, index - 1)}
|
||||
>
|
||||
<ArrowUp data-icon="inline-start" />
|
||||
<ArrowUp data-icon='inline-start' />
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
type='button'
|
||||
variant='ghost'
|
||||
size='icon-sm'
|
||||
aria-label={`下移${name}`}
|
||||
disabled={index === total - 1}
|
||||
onClick={() => onMove(index, index + 1)}
|
||||
>
|
||||
<ArrowDown data-icon="inline-start" />
|
||||
<ArrowDown data-icon='inline-start' />
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
@@ -110,7 +117,9 @@ export function WafSection({ route, onSavingChange }: WafSectionProps) {
|
||||
const [selectedIDs, setSelectedIDs] = useState<number[]>([]);
|
||||
const sensors = useSensors(
|
||||
useSensor(PointerSensor),
|
||||
useSensor(KeyboardSensor, {coordinateGetter: sortableKeyboardCoordinates}),
|
||||
useSensor(KeyboardSensor, {
|
||||
coordinateGetter: sortableKeyboardCoordinates,
|
||||
}),
|
||||
);
|
||||
|
||||
const wafQuery = useQuery({
|
||||
@@ -119,7 +128,8 @@ export function WafSection({ route, onSavingChange }: WafSectionProps) {
|
||||
});
|
||||
|
||||
const wafMutation = useMutation({
|
||||
mutationFn: (ids: number[]) => WafService.updateSiteRuleGroups(route.id, ids),
|
||||
mutationFn: (ids: number[]) =>
|
||||
WafService.updateSiteRuleGroups(route.id, ids),
|
||||
onMutate: () => {
|
||||
onSavingChange?.(true);
|
||||
},
|
||||
@@ -133,8 +143,12 @@ export function WafSection({ route, onSavingChange }: WafSectionProps) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ['openflare', 'waf', 'site-rule-groups', route.id],
|
||||
}),
|
||||
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'],
|
||||
}),
|
||||
]);
|
||||
},
|
||||
onError: (error) => {
|
||||
@@ -150,7 +164,10 @@ export function WafSection({ route, onSavingChange }: WafSectionProps) {
|
||||
|
||||
const selectedSet = useMemo(() => new Set(selectedIDs), [selectedIDs]);
|
||||
const ruleMap = useMemo(
|
||||
() => new Map((wafQuery.data?.rule_groups ?? []).map((rule) => [rule.id, rule])),
|
||||
() =>
|
||||
new Map(
|
||||
(wafQuery.data?.rule_groups ?? []).map((rule) => [rule.id, rule]),
|
||||
),
|
||||
[wafQuery.data?.rule_groups],
|
||||
);
|
||||
|
||||
@@ -159,7 +176,7 @@ export function WafSection({ route, onSavingChange }: WafSectionProps) {
|
||||
setSelectedIDs((current) => arrayMove(current, from, to));
|
||||
};
|
||||
|
||||
const handleDragEnd = ({active, over}: DragEndEvent) => {
|
||||
const handleDragEnd = ({ active, over }: DragEndEvent) => {
|
||||
if (!over || active.id === over.id) return;
|
||||
setSelectedIDs((current) =>
|
||||
reorderRuleIDs(current, Number(active.id), Number(over.id)),
|
||||
@@ -168,13 +185,13 @@ export function WafSection({ route, onSavingChange }: WafSectionProps) {
|
||||
|
||||
return (
|
||||
<SectionShell
|
||||
title="WAF"
|
||||
description="全局规则组始终生效;这里可以为当前网站叠加自定义规则组。"
|
||||
title='WAF'
|
||||
description='全局规则组始终生效;这里可以为当前网站叠加自定义规则组。'
|
||||
formId={proxyRouteFormIds.waf}
|
||||
saving={wafMutation.isPending}
|
||||
>
|
||||
{wafQuery.isLoading ? (
|
||||
<LoadingStateWithBorder description="加载 WAF 规则组..." />
|
||||
<LoadingStateWithBorder description='加载 WAF 规则组...' />
|
||||
) : wafQuery.isError ? (
|
||||
<ErrorInline
|
||||
message={getErrorMessage(wafQuery.error)}
|
||||
@@ -183,39 +200,47 @@ export function WafSection({ route, onSavingChange }: WafSectionProps) {
|
||||
) : (
|
||||
<form
|
||||
id={proxyRouteFormIds.waf}
|
||||
className="flex flex-col gap-5"
|
||||
className='flex flex-col gap-5'
|
||||
onSubmit={(event) => {
|
||||
event.preventDefault();
|
||||
wafMutation.mutate(selectedIDs);
|
||||
}}
|
||||
>
|
||||
{wafQuery.data?.global_rule_group ? (
|
||||
<div className="rounded-lg border bg-muted/30 p-4" data-testid="global-waf-rule">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div
|
||||
className='rounded-lg border bg-muted/30 p-4'
|
||||
data-testid='global-waf-rule'
|
||||
>
|
||||
<div className='flex items-center justify-between gap-3'>
|
||||
<div>
|
||||
<p className="text-[10px] font-semibold uppercase tracking-wider text-muted-foreground">
|
||||
<p className='text-[10px] font-semibold uppercase tracking-wider text-muted-foreground'>
|
||||
Global Rule Group
|
||||
</p>
|
||||
<p className="mt-1 text-sm font-semibold">
|
||||
<p className='mt-1 text-sm font-semibold'>
|
||||
{wafQuery.data.global_rule_group.name}
|
||||
</p>
|
||||
</div>
|
||||
<Badge variant="outline">始终生效</Badge>
|
||||
<Badge variant='outline'>始终生效</Badge>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<FieldSet>
|
||||
<FieldLegend variant="label">选择自定义规则</FieldLegend>
|
||||
<FieldDescription>选中的规则会按下方执行顺序依次运行。</FieldDescription>
|
||||
<FieldGroup data-slot="checkbox-group" className="grid gap-3 md:grid-cols-2">
|
||||
<FieldLegend variant='label'>选择自定义规则</FieldLegend>
|
||||
<FieldDescription>
|
||||
选中的规则会按下方执行顺序依次运行。
|
||||
</FieldDescription>
|
||||
<FieldGroup
|
||||
data-slot='checkbox-group'
|
||||
className='grid gap-3 md:grid-cols-2'
|
||||
>
|
||||
{(wafQuery.data?.rule_groups ?? []).map((group) => {
|
||||
const checkboxID = `waf-rule-${group.id}`;
|
||||
return (
|
||||
<Field
|
||||
key={group.id}
|
||||
orientation="horizontal"
|
||||
className="rounded-lg border p-4"
|
||||
orientation='horizontal'
|
||||
className='rounded-lg border p-4'
|
||||
>
|
||||
<Checkbox
|
||||
id={checkboxID}
|
||||
@@ -231,10 +256,12 @@ export function WafSection({ route, onSavingChange }: WafSectionProps) {
|
||||
/>
|
||||
<FieldLabel
|
||||
htmlFor={checkboxID}
|
||||
className="min-w-0 cursor-pointer flex-col items-start gap-1"
|
||||
className='min-w-0 cursor-pointer flex-col items-start gap-1'
|
||||
>
|
||||
<span className="truncate text-sm font-semibold">{group.name}</span>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
<span className='truncate text-sm font-semibold'>
|
||||
{group.name}
|
||||
</span>
|
||||
<span className='text-xs text-muted-foreground'>
|
||||
{group.enabled ? '启用中' : '已停用'} ·{' '}
|
||||
{group.graph.nodes.length} 个节点
|
||||
</span>
|
||||
@@ -246,9 +273,9 @@ export function WafSection({ route, onSavingChange }: WafSectionProps) {
|
||||
</FieldSet>
|
||||
|
||||
{selectedIDs.length > 0 ? (
|
||||
<div className="flex flex-col gap-2">
|
||||
<p className="text-sm font-medium">执行顺序</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
<div className='flex flex-col gap-2'>
|
||||
<p className='text-sm font-medium'>执行顺序</p>
|
||||
<p className='text-xs text-muted-foreground'>
|
||||
自定义规则按此顺序执行,可拖动或使用上下移动按钮调整。
|
||||
</p>
|
||||
<DndContext
|
||||
@@ -256,8 +283,11 @@ export function WafSection({ route, onSavingChange }: WafSectionProps) {
|
||||
collisionDetection={closestCenter}
|
||||
onDragEnd={handleDragEnd}
|
||||
>
|
||||
<SortableContext items={selectedIDs} strategy={verticalListSortingStrategy}>
|
||||
<div className="flex flex-col gap-2">
|
||||
<SortableContext
|
||||
items={selectedIDs}
|
||||
strategy={verticalListSortingStrategy}
|
||||
>
|
||||
<div className='flex flex-col gap-2'>
|
||||
{selectedIDs.map((id, index) => {
|
||||
const rule = ruleMap.get(id);
|
||||
return rule ? (
|
||||
@@ -278,7 +308,7 @@ export function WafSection({ route, onSavingChange }: WafSectionProps) {
|
||||
) : null}
|
||||
|
||||
{(wafQuery.data?.rule_groups ?? []).length === 0 ? (
|
||||
<EmptyStateWithBorder description="暂无自定义 WAF 规则组" />
|
||||
<EmptyStateWithBorder description='暂无自定义 WAF 规则组' />
|
||||
) : null}
|
||||
</form>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user