-
Zone 设置
-
- 根域和备注可在此 Zone 的编辑操作中维护。
-
-
+
+
+
基本信息
+
+ 维护 Zone 的可注册根域。根域变更后,请确认其下域名仍归属该根域。
+
+
+
+
当前根域
+
{overview.zone.domain}
+
+
+
域名数量
+
{overview.domains.length}
+
+
+
+
+
+
+
危险操作
+
+ 删除 Zone 前须清空其下全部域名;删除后不可恢复。
+
+
+
diff --git a/frontend/app/(main)/websites/page.tsx b/frontend/app/(main)/websites/page.tsx
index 8c1e0a01..edcbe2d9 100644
--- a/frontend/app/(main)/websites/page.tsx
+++ b/frontend/app/(main)/websites/page.tsx
@@ -3,20 +3,24 @@
import Link from 'next/link';
import {useMemo, useState} from 'react';
import {useQuery} from '@tanstack/react-query';
-import {Globe, Plus, Search} from 'lucide-react';
+import {useRouter} from 'next/navigation';
+import {Eye, Globe, Plus, Search} from 'lucide-react';
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 {Input} from '@/components/ui/input';
import {LoadingStateWithBorder} from '@/components/layout/loading';
+import {Table, TableBody, TableCell, TableHead, TableHeader, TableRow} from '@/components/ui/table';
+import {Tooltip, TooltipContent, TooltipProvider, TooltipTrigger} from '@/components/ui/tooltip';
import {ZoneService, zoneQueryKey} from '@/lib/services/openflare';
+import {formatDateTime} from '@/lib/utils';
import {ZoneEditorDialog} from './[zoneId]/components/zone-editor-dialog';
import {getErrorMessage} from './components/website-utils';
export default function WebsitesPage() {
+ const router = useRouter();
const [search, setSearch] = useState('');
const [editorOpen, setEditorOpen] = useState(false);
const zonesQuery = useQuery({
@@ -34,83 +38,133 @@ export default function WebsitesPage() {
}, [search, zonesQuery.data]);
return (
-
-
+
+
网站
-
-
-
-
- Zone 列表
-
- 以可注册根域组织网站,并在详情中维护明确 FQDN、证书与路由关联。
-
-
-
-
- setSearch(event.target.value)}
- className="h-8 pl-8 text-xs"
- />
-
-
-
- {zonesQuery.isLoading ? (
-
- ) : zonesQuery.isError ? (
-
- void zonesQuery.refetch()}
- />
-
- ) : zones.length === 0 ? (
-
- ) : (
-
- {zones.map((zone) => (
-
-
-
-
{zone.domain}
-
- {zone.domain_count ?? 0} 个域名
-
-
-
+
+ setSearch(event.target.value)}
+ className="h-8 pl-8 text-xs"
+ />
+
+
+ {zonesQuery.isLoading ? (
+
+ ) : zonesQuery.isError ? (
+
+ void zonesQuery.refetch()}
+ />
+
+ ) : zones.length === 0 ? (
+
+ ) : (
+
+
+
+
+
+
+ ID
+
+
+ 根域
+
+
+ 域名数
+
+
+ 创建时间
+
+
+ 更新时间
+
+
+ 操作
+
+
+
+
+ {zones.map((zone) => (
+ router.push(`/websites/${zone.id}`)}
+ >
+
+ {zone.id}
+
+
+ {zone.domain}
+
+
+ {zone.domain_count ?? 0}
+
+
+ {formatDateTime(zone.created_at)}
+
+
+ {formatDateTime(zone.updated_at)}
+
+ event.stopPropagation()}
>
- 管理
-
-
-
- ))}
-
- )}
-
-
+
+
+
+
+
+
+ 管理
+
+
+
+
+ 管理 Zone
+
+
+
+
+
+ ))}
+
+
+
+
+ )}
diff --git a/frontend/tests/zone/websites-page.test.tsx b/frontend/tests/zone/websites-page.test.tsx
index 7de45078..84820773 100644
--- a/frontend/tests/zone/websites-page.test.tsx
+++ b/frontend/tests/zone/websites-page.test.tsx
@@ -11,6 +11,10 @@ vi.mock('next/link', () => ({
),
}));
+vi.mock('next/navigation', () => ({
+ useRouter: () => ({push: vi.fn()}),
+}));
+
vi.mock('@/lib/services/openflare', async (importOriginal) => {
const actual = await importOriginal
();
return {...actual, ZoneService: {list: vi.fn()}};
@@ -46,9 +50,10 @@ describe('WebsitesPage', () => {
renderPage();
- expect(await screen.findByRole('heading', {name: 'arctel.de'})).toBeVisible();
- expect(screen.getByText(/3 个域名/)).toBeVisible();
- expect(screen.getByText(/0 个域名/)).toBeVisible();
+ expect(await screen.findByText('arctel.de')).toBeVisible();
+ expect(screen.getByText('3')).toBeVisible();
+ expect(screen.getByText('0')).toBeVisible();
+ expect(screen.getByRole('columnheader', {name: '根域'})).toBeVisible();
fireEvent.change(screen.getByPlaceholderText('搜索 Zone 根域'), {
target: {value: 'arctel'},
diff --git a/frontend/tests/zone/zone-page.test.tsx b/frontend/tests/zone/zone-page.test.tsx
index c193ae36..f86a90a1 100644
--- a/frontend/tests/zone/zone-page.test.tsx
+++ b/frontend/tests/zone/zone-page.test.tsx
@@ -14,7 +14,7 @@ vi.mock('next/link', () => ({
}));
vi.mock('next/navigation', () => ({
- useRouter: () => ({replace: replaceMock}),
+ useRouter: () => ({replace: replaceMock, back: vi.fn()}),
usePathname: () => '/websites/42',
useSearchParams: () => new URLSearchParams(),
}));