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,9 +1,9 @@
|
||||
"use client"
|
||||
'use client';
|
||||
|
||||
import {useEffect, useMemo, useState} from "react"
|
||||
import {useQuery} from "@tanstack/react-query"
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import {Button} from "@/components/ui/button"
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@@ -11,17 +11,17 @@ import {
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog"
|
||||
import {Input} from "@/components/ui/input"
|
||||
import {Label} from "@/components/ui/label"
|
||||
import {ProxyRouteService} from "@/lib/services/openflare"
|
||||
} from '@/components/ui/dialog';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { ProxyRouteService } from '@/lib/services/openflare';
|
||||
|
||||
type UptimeKumaSiteSelectModalProps = {
|
||||
open: boolean
|
||||
selectedSites: string[]
|
||||
onOpenChange: (open: boolean) => void
|
||||
onSave: (sites: string[]) => void
|
||||
}
|
||||
open: boolean;
|
||||
selectedSites: string[];
|
||||
onOpenChange: (open: boolean) => void;
|
||||
onSave: (sites: string[]) => void;
|
||||
};
|
||||
|
||||
export function UptimeKumaSiteSelectModal({
|
||||
open,
|
||||
@@ -29,62 +29,66 @@ export function UptimeKumaSiteSelectModal({
|
||||
onOpenChange,
|
||||
onSave,
|
||||
}: UptimeKumaSiteSelectModalProps) {
|
||||
const [searchTerm, setSearchTerm] = useState("")
|
||||
const [tempSelected, setTempSelected] = useState<Set<string>>(new Set())
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [tempSelected, setTempSelected] = useState<Set<string>>(new Set());
|
||||
|
||||
const routesQuery = useQuery({
|
||||
queryKey: ["openflare", "proxy-routes"],
|
||||
queryKey: ['openflare', 'proxy-routes'],
|
||||
queryFn: () => ProxyRouteService.list(),
|
||||
enabled: open,
|
||||
})
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return
|
||||
setTempSelected(new Set(selectedSites.map((site) => site.trim()).filter(Boolean)))
|
||||
setSearchTerm("")
|
||||
}, [open, selectedSites])
|
||||
if (!open) return;
|
||||
setTempSelected(
|
||||
new Set(selectedSites.map((site) => site.trim()).filter(Boolean)),
|
||||
);
|
||||
setSearchTerm('');
|
||||
}, [open, selectedSites]);
|
||||
|
||||
const filteredRoutes = useMemo(() => {
|
||||
const routes = routesQuery.data ?? []
|
||||
const keyword = searchTerm.trim().toLowerCase()
|
||||
if (!keyword) return routes
|
||||
const routes = routesQuery.data ?? [];
|
||||
const keyword = searchTerm.trim().toLowerCase();
|
||||
if (!keyword) return routes;
|
||||
return routes.filter((route) => {
|
||||
const domains = (route.zone_domains ?? []).map((item) => item.domain).join(' ')
|
||||
const domains = (route.zone_domains ?? [])
|
||||
.map((item) => item.domain)
|
||||
.join(' ');
|
||||
return (
|
||||
route.site_name.toLowerCase().includes(keyword) ||
|
||||
domains.toLowerCase().includes(keyword)
|
||||
)
|
||||
})
|
||||
}, [routesQuery.data, searchTerm])
|
||||
);
|
||||
});
|
||||
}, [routesQuery.data, searchTerm]);
|
||||
|
||||
const toggleSite = (siteName: string) => {
|
||||
setTempSelected((previous) => {
|
||||
const next = new Set(previous)
|
||||
if (next.has(siteName)) next.delete(siteName)
|
||||
else next.add(siteName)
|
||||
return next
|
||||
})
|
||||
}
|
||||
const next = new Set(previous);
|
||||
if (next.has(siteName)) next.delete(siteName);
|
||||
else next.add(siteName);
|
||||
return next;
|
||||
});
|
||||
};
|
||||
|
||||
const handleSelectAll = () => {
|
||||
setTempSelected((previous) => {
|
||||
const next = new Set(previous)
|
||||
filteredRoutes.forEach((route) => next.add(route.site_name))
|
||||
return next
|
||||
})
|
||||
}
|
||||
const next = new Set(previous);
|
||||
filteredRoutes.forEach((route) => next.add(route.site_name));
|
||||
return next;
|
||||
});
|
||||
};
|
||||
|
||||
const handleDeselectAll = () => {
|
||||
setTempSelected((previous) => {
|
||||
const next = new Set(previous)
|
||||
filteredRoutes.forEach((route) => next.delete(route.site_name))
|
||||
return next
|
||||
})
|
||||
}
|
||||
const next = new Set(previous);
|
||||
filteredRoutes.forEach((route) => next.delete(route.site_name));
|
||||
return next;
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="max-w-2xl">
|
||||
<DialogContent className='max-w-2xl'>
|
||||
<DialogHeader>
|
||||
<DialogTitle>选择监控站点</DialogTitle>
|
||||
<DialogDescription>
|
||||
@@ -92,88 +96,111 @@ export function UptimeKumaSiteSelectModal({
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-1.5">
|
||||
<div className='space-y-4'>
|
||||
<div className='space-y-1.5'>
|
||||
<Label>搜索站点</Label>
|
||||
<Input
|
||||
value={searchTerm}
|
||||
onChange={(event) => setSearchTerm(event.target.value)}
|
||||
placeholder="按名称或域名搜索..."
|
||||
placeholder='按名称或域名搜索...'
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Button type="button" variant="outline" size="sm" onClick={handleSelectAll}>
|
||||
<div className='flex flex-wrap gap-2'>
|
||||
<Button
|
||||
type='button'
|
||||
variant='outline'
|
||||
size='sm'
|
||||
onClick={handleSelectAll}
|
||||
>
|
||||
全选过滤项
|
||||
</Button>
|
||||
<Button type="button" variant="outline" size="sm" onClick={handleDeselectAll}>
|
||||
<Button
|
||||
type='button'
|
||||
variant='outline'
|
||||
size='sm'
|
||||
onClick={handleDeselectAll}
|
||||
>
|
||||
清空过滤项
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="max-h-72 overflow-y-auto rounded-lg border">
|
||||
<div className='max-h-72 overflow-y-auto rounded-lg border'>
|
||||
{routesQuery.isLoading ? (
|
||||
<p className="p-4 text-sm text-muted-foreground">加载站点列表...</p>
|
||||
<p className='p-4 text-sm text-muted-foreground'>
|
||||
加载站点列表...
|
||||
</p>
|
||||
) : routesQuery.isError ? (
|
||||
<p className="p-4 text-sm text-destructive">
|
||||
<p className='p-4 text-sm text-destructive'>
|
||||
{routesQuery.error instanceof Error
|
||||
? routesQuery.error.message
|
||||
: "加载站点失败"}
|
||||
: '加载站点失败'}
|
||||
</p>
|
||||
) : filteredRoutes.length === 0 ? (
|
||||
<p className="p-4 text-sm text-muted-foreground">无匹配的站点</p>
|
||||
<p className='p-4 text-sm text-muted-foreground'>无匹配的站点</p>
|
||||
) : (
|
||||
<table className="w-full text-left text-sm">
|
||||
<thead className="sticky top-0 bg-muted/60 text-xs uppercase text-muted-foreground">
|
||||
<table className='w-full text-left text-sm'>
|
||||
<thead className='sticky top-0 bg-muted/60 text-xs uppercase text-muted-foreground'>
|
||||
<tr>
|
||||
<th className="w-12 px-3 py-2">选择</th>
|
||||
<th className="px-3 py-2">站点名称</th>
|
||||
<th className="px-3 py-2">主域名</th>
|
||||
<th className='w-12 px-3 py-2'>选择</th>
|
||||
<th className='px-3 py-2'>站点名称</th>
|
||||
<th className='px-3 py-2'>主域名</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y">
|
||||
<tbody className='divide-y'>
|
||||
{filteredRoutes.map((route) => {
|
||||
const checked = tempSelected.has(route.site_name)
|
||||
const checked = tempSelected.has(route.site_name);
|
||||
return (
|
||||
<tr
|
||||
key={route.id}
|
||||
className="cursor-pointer hover:bg-muted/30"
|
||||
className='cursor-pointer hover:bg-muted/30'
|
||||
onClick={() => toggleSite(route.site_name)}
|
||||
>
|
||||
<td className="px-3 py-2" onClick={(event) => event.stopPropagation()}>
|
||||
<td
|
||||
className='px-3 py-2'
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
type='checkbox'
|
||||
checked={checked}
|
||||
onChange={() => toggleSite(route.site_name)}
|
||||
className="size-4 rounded border-input"
|
||||
className='size-4 rounded border-input'
|
||||
/>
|
||||
</td>
|
||||
<td className="px-3 py-2 font-medium">{route.site_name}</td>
|
||||
<td className="px-3 py-2 text-muted-foreground">
|
||||
{(route.zone_domains ?? []).map((item) => item.domain).join(', ') || '—'}
|
||||
<td className='px-3 py-2 font-medium'>
|
||||
{route.site_name}
|
||||
</td>
|
||||
<td className='px-3 py-2 text-muted-foreground'>
|
||||
{(route.zone_domains ?? [])
|
||||
.map((item) => item.domain)
|
||||
.join(', ') || '—'}
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<p className="text-right text-xs text-muted-foreground">
|
||||
<p className='text-right text-xs text-muted-foreground'>
|
||||
已选择 {tempSelected.size} 个监控站点
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<Button type="button" variant="outline" onClick={() => onOpenChange(false)}>
|
||||
<Button
|
||||
type='button'
|
||||
variant='outline'
|
||||
onClick={() => onOpenChange(false)}
|
||||
>
|
||||
取消
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
type='button'
|
||||
onClick={() => {
|
||||
onSave(Array.from(tempSelected))
|
||||
onOpenChange(false)
|
||||
onSave(Array.from(tempSelected));
|
||||
onOpenChange(false);
|
||||
}}
|
||||
>
|
||||
保存选择
|
||||
@@ -181,5 +208,5 @@ export function UptimeKumaSiteSelectModal({
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user