mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-06 07:36:37 +08:00
63cd906cfc
- Merge all files inside openflare-server to the repository root directory. - Relocate agent, relay, and flared subprojects from internal/ to internal/apps/. - Combine docker-compose files and update build context paths to root. - Update GitHub workflows and Dockerfiles to refer to new directories and package names. - Rewrite Go package imports across all files. - Resolve database renew test race condition and clean up docs.
79 lines
2.3 KiB
TypeScript
79 lines
2.3 KiB
TypeScript
'use client';
|
|
|
|
import {X} from 'lucide-react';
|
|
|
|
import {Badge} from '@/components/ui/badge';
|
|
import {Button} from '@/components/ui/button';
|
|
|
|
interface RuleListSectionProps {
|
|
title: string;
|
|
description: string;
|
|
items: string[];
|
|
groupItems?: Array<{ id: number; name: string; enabled: boolean }>;
|
|
tone: 'whitelist' | 'blacklist';
|
|
emptyText: string;
|
|
onRemove: (item: string) => void;
|
|
onRemoveGroup?: (id: number) => void;
|
|
}
|
|
|
|
export function RuleListSection({
|
|
title,
|
|
description,
|
|
items,
|
|
groupItems = [],
|
|
tone,
|
|
emptyText,
|
|
onRemove,
|
|
onRemoveGroup,
|
|
}: RuleListSectionProps) {
|
|
const total = items.length + groupItems.length;
|
|
const badgeVariant = tone === 'whitelist' ? 'secondary' : 'destructive';
|
|
|
|
return (
|
|
<div className="rounded-lg border border-dashed p-4 space-y-3">
|
|
<div className="flex items-start justify-between gap-3">
|
|
<div>
|
|
<h4 className="text-sm font-semibold">{title}</h4>
|
|
<p className="text-xs text-muted-foreground mt-1">{description}</p>
|
|
</div>
|
|
<Badge variant="outline">{total}</Badge>
|
|
</div>
|
|
{total > 0 ? (
|
|
<div className="flex flex-wrap gap-2">
|
|
{groupItems.map((group) => (
|
|
<Badge key={`group-${group.id}`} variant={badgeVariant} className="gap-1 pr-1">
|
|
IP组: {group.name}
|
|
{!group.enabled ? ' (停用)' : ''}
|
|
<Button
|
|
type="button"
|
|
variant="ghost"
|
|
size="icon"
|
|
className="size-4 hover:bg-transparent"
|
|
onClick={() => onRemoveGroup?.(group.id)}
|
|
>
|
|
<X className="size-3" />
|
|
</Button>
|
|
</Badge>
|
|
))}
|
|
{items.map((item) => (
|
|
<Badge key={item} variant={badgeVariant} className="gap-1 pr-1 font-mono text-xs">
|
|
{item}
|
|
<Button
|
|
type="button"
|
|
variant="ghost"
|
|
size="icon"
|
|
className="size-4 hover:bg-transparent"
|
|
onClick={() => onRemove(item)}
|
|
>
|
|
<X className="size-3" />
|
|
</Button>
|
|
</Badge>
|
|
))}
|
|
</div>
|
|
) : (
|
|
<p className="text-sm text-muted-foreground">{emptyText}</p>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|