mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-08 00:26:37 +08:00
优化仪表板页面,移除冗余组件,简化代码结构
This commit is contained in:
@@ -1,13 +1,8 @@
|
|||||||
import { DashboardOverview } from '@/features/dashboard/components/dashboard-overview';
|
import { DashboardOverview } from '@/features/dashboard/components/dashboard-overview';
|
||||||
import { PageHeader } from '@/components/layout/page-header';
|
|
||||||
|
|
||||||
export default function DashboardPage() {
|
export default function DashboardPage() {
|
||||||
return (
|
return (
|
||||||
<div className='space-y-6'>
|
<div className='space-y-6'>
|
||||||
<PageHeader
|
|
||||||
title='ATSFlare 管理端'
|
|
||||||
description='集中展示系统核心数据概览,快速导航至各功能模块进行管理和配置。'
|
|
||||||
/>
|
|
||||||
<DashboardOverview />
|
<DashboardOverview />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -20,8 +20,8 @@ interface RootLayoutProps {
|
|||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function RootLayout({ children }: RootLayoutProps) {
|
export default function RootLayout({ children }: RootLayoutProps) {
|
||||||
return (
|
return (
|
||||||
<html lang='zh-CN' suppressHydrationWarning>
|
<html lang='zh-CN' suppressHydrationWarning>
|
||||||
<body>
|
<body>
|
||||||
<Script id='theme-init' strategy='beforeInteractive'>
|
<Script id='theme-init' strategy='beforeInteractive'>
|
||||||
@@ -30,5 +30,5 @@ export default function RootLayout({ children }: RootLayoutProps) {
|
|||||||
<AppProviders>{children}</AppProviders>
|
<AppProviders>{children}</AppProviders>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -235,7 +235,6 @@ export function DashboardOverview() {
|
|||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<WorldStage
|
<WorldStage
|
||||||
generatedAt={overview.generated_at}
|
|
||||||
summary={overview.summary}
|
summary={overview.summary}
|
||||||
traffic={overview.traffic}
|
traffic={overview.traffic}
|
||||||
capacity={overview.capacity}
|
capacity={overview.capacity}
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import type {
|
|||||||
DistributionItem,
|
DistributionItem,
|
||||||
} from '@/features/dashboard/types';
|
} from '@/features/dashboard/types';
|
||||||
import { cn } from '@/lib/utils/cn';
|
import { cn } from '@/lib/utils/cn';
|
||||||
import { formatDateTime } from '@/lib/utils/date';
|
|
||||||
|
|
||||||
type Tone = 'healthy' | 'warning' | 'danger';
|
type Tone = 'healthy' | 'warning' | 'danger';
|
||||||
|
|
||||||
@@ -157,14 +156,12 @@ function CountrySignal({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function WorldStage({
|
export function WorldStage({
|
||||||
generatedAt,
|
|
||||||
summary,
|
summary,
|
||||||
traffic,
|
traffic,
|
||||||
capacity,
|
capacity,
|
||||||
nodes,
|
nodes,
|
||||||
sourceCountries,
|
sourceCountries,
|
||||||
}: {
|
}: {
|
||||||
generatedAt: string;
|
|
||||||
summary: DashboardSummary;
|
summary: DashboardSummary;
|
||||||
traffic: DashboardTraffic;
|
traffic: DashboardTraffic;
|
||||||
capacity: DashboardCapacity;
|
capacity: DashboardCapacity;
|
||||||
@@ -236,43 +233,14 @@ export function WorldStage({
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="flex flex-col gap-4 xl:flex-row xl:items-end xl:justify-between">
|
<div className="flex flex-col gap-4 xl:flex-row xl:items-end xl:justify-between">
|
||||||
<div className="space-y-2">
|
<p
|
||||||
<p
|
|
||||||
className={cn(
|
className={cn(
|
||||||
'text-[11px] tracking-[0.34em] uppercase',
|
'text-[11px] tracking-[0.34em] uppercase',
|
||||||
isDark ? 'text-sky-200/80' : 'text-sky-700/80',
|
isDark ? 'text-sky-200/80' : 'text-sky-700/80',
|
||||||
)}
|
)}
|
||||||
>
|
|
||||||
Global Stage
|
|
||||||
</p>
|
|
||||||
<h2
|
|
||||||
className={cn(
|
|
||||||
'text-2xl font-semibold',
|
|
||||||
isDark ? 'text-white' : 'text-slate-950',
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
全球态势板
|
|
||||||
</h2>
|
|
||||||
<p
|
|
||||||
className={cn(
|
|
||||||
'max-w-3xl text-sm leading-6',
|
|
||||||
isDark ? 'text-slate-300' : 'text-slate-600',
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
在同一视图汇总节点在线率、运行状态、资源负载与全球流量来源,
|
|
||||||
让首屏可以直接承担值守与研判入口。
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
'rounded-full border px-4 py-2 text-sm backdrop-blur',
|
|
||||||
isDark
|
|
||||||
? 'border-white/10 bg-white/6 text-slate-200'
|
|
||||||
: 'border-slate-200/80 bg-white/80 text-slate-700',
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
数据生成于 {formatDateTime(generatedAt)}
|
Global Stage
|
||||||
</div>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -320,7 +320,7 @@ export function NodeDetailPage({ nodeId }: { nodeId: string }) {
|
|||||||
const saveMutation = useMutation({
|
const saveMutation = useMutation({
|
||||||
mutationFn: async (payload: Parameters<typeof updateNode>[1]) =>
|
mutationFn: async (payload: Parameters<typeof updateNode>[1]) =>
|
||||||
updateNode(Number(nodeId), payload),
|
updateNode(Number(nodeId), payload),
|
||||||
onSuccess: async (updatedNode) => {
|
onSuccess: async () => {
|
||||||
setFeedback({ tone: 'success', message: '节点已更新。' });
|
setFeedback({ tone: 'success', message: '节点已更新。' });
|
||||||
setIsEditorOpen(false);
|
setIsEditorOpen(false);
|
||||||
await queryClient.invalidateQueries({ queryKey: nodesQueryKey });
|
await queryClient.invalidateQueries({ queryKey: nodesQueryKey });
|
||||||
|
|||||||
@@ -161,13 +161,6 @@ function isCacheLevelsValue(value: string) {
|
|||||||
return /^\d{1,2}(?::\d{1,2}){0,2}$/.test(value.trim());
|
return /^\d{1,2}(?::\d{1,2}){0,2}$/.test(value.trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
function extractMainConfigLines(content: string, matcher: RegExp) {
|
|
||||||
return content
|
|
||||||
.split('\n')
|
|
||||||
.map((line) => line.trim())
|
|
||||||
.filter((line) => line && matcher.test(line));
|
|
||||||
}
|
|
||||||
|
|
||||||
export function PerformancePage() {
|
export function PerformancePage() {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const {user} = useAuth();
|
const {user} = useAuth();
|
||||||
@@ -595,15 +588,6 @@ export function PerformancePage() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const cachePreviewLines = extractMainConfigLines(
|
|
||||||
preview.main_config,
|
|
||||||
/proxy_cache_path|proxy_cache_key|proxy_cache_lock(?:_timeout)?|proxy_cache_use_stale/,
|
|
||||||
);
|
|
||||||
const gzipPreviewLines = extractMainConfigLines(
|
|
||||||
preview.main_config,
|
|
||||||
/gzip(?:_| )/,
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<PageHeader
|
<PageHeader
|
||||||
|
|||||||
@@ -196,48 +196,54 @@ export function TlsCertificatesPage() {
|
|||||||
</AppCard>
|
</AppCard>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<CertificateImportModal
|
{isImportOpen ? (
|
||||||
isOpen={isImportOpen}
|
<CertificateImportModal
|
||||||
onClose={() => setIsImportOpen(false)}
|
isOpen={isImportOpen}
|
||||||
onImported={(certificate) => {
|
onClose={() => setIsImportOpen(false)}
|
||||||
setFeedback({
|
onImported={(certificate) => {
|
||||||
tone: 'success',
|
setFeedback({
|
||||||
message: `证书 ${certificate.name} 已导入。`,
|
tone: 'success',
|
||||||
});
|
message: `证书 ${certificate.name} 已导入。`,
|
||||||
}}
|
});
|
||||||
/>
|
}}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
|
||||||
<CertificateDetailModal
|
{isDetailOpen ? (
|
||||||
certificateId={selectedCertificateId}
|
<CertificateDetailModal
|
||||||
isOpen={isDetailOpen}
|
certificateId={selectedCertificateId}
|
||||||
onClose={() => setIsDetailOpen(false)}
|
isOpen={isDetailOpen}
|
||||||
onEdit={() => {
|
onClose={() => setIsDetailOpen(false)}
|
||||||
setIsDetailOpen(false);
|
onEdit={() => {
|
||||||
setIsEditorOpen(true);
|
|
||||||
}}
|
|
||||||
onDelete={() => {
|
|
||||||
const certificate = certificates.find(
|
|
||||||
(item) => item.id === selectedCertificateId,
|
|
||||||
);
|
|
||||||
if (certificate) {
|
|
||||||
setIsDetailOpen(false);
|
setIsDetailOpen(false);
|
||||||
handleDeleteCertificate(certificate);
|
setIsEditorOpen(true);
|
||||||
}
|
}}
|
||||||
}}
|
onDelete={() => {
|
||||||
deleting={deleteCertificateMutation.isPending}
|
const certificate = certificates.find(
|
||||||
/>
|
(item) => item.id === selectedCertificateId,
|
||||||
|
);
|
||||||
|
if (certificate) {
|
||||||
|
setIsDetailOpen(false);
|
||||||
|
handleDeleteCertificate(certificate);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
deleting={deleteCertificateMutation.isPending}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
|
||||||
<CertificateEditorModal
|
{isEditorOpen ? (
|
||||||
certificateId={selectedCertificateId}
|
<CertificateEditorModal
|
||||||
isOpen={isEditorOpen}
|
certificateId={selectedCertificateId}
|
||||||
onClose={() => setIsEditorOpen(false)}
|
isOpen={isEditorOpen}
|
||||||
onSaved={(certificate) => {
|
onClose={() => setIsEditorOpen(false)}
|
||||||
setFeedback({
|
onSaved={(certificate) => {
|
||||||
tone: 'success',
|
setFeedback({
|
||||||
message: `证书 ${certificate.name} 已更新。`,
|
tone: 'success',
|
||||||
});
|
message: `证书 ${certificate.name} 已更新。`,
|
||||||
}}
|
});
|
||||||
/>
|
}}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -434,62 +434,70 @@ export function WebsiteDetailPage({websiteId}: { websiteId: string }) {
|
|||||||
</AppCard>
|
</AppCard>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<WebsiteEditorModal
|
{isEditorOpen ? (
|
||||||
isOpen={isEditorOpen}
|
<WebsiteEditorModal
|
||||||
onClose={() => {
|
isOpen={isEditorOpen}
|
||||||
setIsEditorOpen(false);
|
onClose={() => {
|
||||||
setPreferredCertificateId(null);
|
setIsEditorOpen(false);
|
||||||
}}
|
setPreferredCertificateId(null);
|
||||||
initialDomain={website}
|
}}
|
||||||
certificates={certificates}
|
initialDomain={website}
|
||||||
certificatesLoading={certificatesQuery.isLoading}
|
certificates={certificates}
|
||||||
preferredCertificateId={preferredCertificateId}
|
certificatesLoading={certificatesQuery.isLoading}
|
||||||
onRequestImportCertificate={() => setIsCertificateImportOpen(true)}
|
preferredCertificateId={preferredCertificateId}
|
||||||
onSaved={() => {
|
onRequestImportCertificate={() => setIsCertificateImportOpen(true)}
|
||||||
setPreferredCertificateId(null);
|
onSaved={() => {
|
||||||
setFeedback({tone: 'success', message: '网站已更新。'});
|
setPreferredCertificateId(null);
|
||||||
}}
|
setFeedback({tone: 'success', message: '网站已更新。'});
|
||||||
/>
|
}}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
|
||||||
<CertificateImportModal
|
{isCertificateImportOpen ? (
|
||||||
isOpen={isCertificateImportOpen}
|
<CertificateImportModal
|
||||||
onClose={() => setIsCertificateImportOpen(false)}
|
isOpen={isCertificateImportOpen}
|
||||||
onImported={(importedCertificate) => {
|
onClose={() => setIsCertificateImportOpen(false)}
|
||||||
setPreferredCertificateId(importedCertificate.id);
|
onImported={(importedCertificate) => {
|
||||||
setFeedback({
|
setPreferredCertificateId(importedCertificate.id);
|
||||||
tone: 'success',
|
setFeedback({
|
||||||
message: `证书 ${importedCertificate.name} 已导入,可在编辑网站时直接应用。`,
|
tone: 'success',
|
||||||
});
|
message: `证书 ${importedCertificate.name} 已导入,可在编辑网站时直接应用。`,
|
||||||
setIsEditorOpen(true);
|
});
|
||||||
}}
|
setIsEditorOpen(true);
|
||||||
/>
|
}}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
|
||||||
<CertificateDetailModal
|
{isCertificateDetailOpen ? (
|
||||||
certificateId={certificate?.id ?? null}
|
<CertificateDetailModal
|
||||||
isOpen={isCertificateDetailOpen}
|
certificateId={certificate?.id ?? null}
|
||||||
onClose={() => setIsCertificateDetailOpen(false)}
|
isOpen={isCertificateDetailOpen}
|
||||||
onEdit={() => {
|
onClose={() => setIsCertificateDetailOpen(false)}
|
||||||
setIsCertificateDetailOpen(false);
|
onEdit={() => {
|
||||||
setIsCertificateEditorOpen(true);
|
setIsCertificateDetailOpen(false);
|
||||||
}}
|
setIsCertificateEditorOpen(true);
|
||||||
onDelete={() => {
|
}}
|
||||||
setIsCertificateDetailOpen(false);
|
onDelete={() => {
|
||||||
handleDeleteCertificate();
|
setIsCertificateDetailOpen(false);
|
||||||
}}
|
handleDeleteCertificate();
|
||||||
deleting={deleteCertificateMutation.isPending}
|
}}
|
||||||
/>
|
deleting={deleteCertificateMutation.isPending}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
|
||||||
<CertificateEditorModal
|
{isCertificateEditorOpen ? (
|
||||||
certificateId={certificate?.id ?? null}
|
<CertificateEditorModal
|
||||||
isOpen={isCertificateEditorOpen}
|
certificateId={certificate?.id ?? null}
|
||||||
onClose={() => setIsCertificateEditorOpen(false)}
|
isOpen={isCertificateEditorOpen}
|
||||||
onSaved={(updatedCertificate) => {
|
onClose={() => setIsCertificateEditorOpen(false)}
|
||||||
setFeedback({
|
onSaved={(updatedCertificate) => {
|
||||||
tone: 'success',
|
setFeedback({
|
||||||
message: `证书 ${updatedCertificate.name} 已更新。`,
|
tone: 'success',
|
||||||
});
|
message: `证书 ${updatedCertificate.name} 已更新。`,
|
||||||
}}
|
});
|
||||||
/>
|
}}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ export function WebsiteEditorModal({
|
|||||||
? toManagedDomainFormValues(initialDomain)
|
? toManagedDomainFormValues(initialDomain)
|
||||||
: defaultManagedDomainValues,
|
: defaultManagedDomainValues,
|
||||||
);
|
);
|
||||||
}, [form, initialDomain, isOpen]);
|
}, [form, initialDomain, isOpen, saveMutation]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isOpen || !preferredCertificateId) {
|
if (!isOpen || !preferredCertificateId) {
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ import {
|
|||||||
DangerButton,
|
DangerButton,
|
||||||
PrimaryButton,
|
PrimaryButton,
|
||||||
} from '@/features/shared/components/resource-primitives';
|
} from '@/features/shared/components/resource-primitives';
|
||||||
import { formatDateTime } from '@/lib/utils/date';
|
|
||||||
|
|
||||||
type FeedbackState = {
|
type FeedbackState = {
|
||||||
tone: 'info' | 'success' | 'danger';
|
tone: 'info' | 'success' | 'danger';
|
||||||
@@ -205,36 +204,40 @@ export function WebsitesPage() {
|
|||||||
</AppCard>
|
</AppCard>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<WebsiteEditorModal
|
{isWebsiteModalOpen ? (
|
||||||
isOpen={isWebsiteModalOpen}
|
<WebsiteEditorModal
|
||||||
onClose={() => {
|
isOpen={isWebsiteModalOpen}
|
||||||
setIsWebsiteModalOpen(false);
|
onClose={() => {
|
||||||
setPreferredCertificateId(null);
|
setIsWebsiteModalOpen(false);
|
||||||
}}
|
setPreferredCertificateId(null);
|
||||||
certificates={certificates}
|
}}
|
||||||
certificatesLoading={certificatesQuery.isLoading}
|
certificates={certificates}
|
||||||
preferredCertificateId={preferredCertificateId}
|
certificatesLoading={certificatesQuery.isLoading}
|
||||||
onRequestImportCertificate={() => setIsCertificateImportOpen(true)}
|
preferredCertificateId={preferredCertificateId}
|
||||||
onSaved={(_, mode) => {
|
onRequestImportCertificate={() => setIsCertificateImportOpen(true)}
|
||||||
setPreferredCertificateId(null);
|
onSaved={(_, mode) => {
|
||||||
setFeedback({
|
setPreferredCertificateId(null);
|
||||||
tone: 'success',
|
setFeedback({
|
||||||
message: mode === 'create' ? '网站已创建。' : '网站已更新。',
|
tone: 'success',
|
||||||
});
|
message: mode === 'create' ? '网站已创建。' : '网站已更新。',
|
||||||
}}
|
});
|
||||||
/>
|
}}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
|
||||||
<CertificateImportModal
|
{isCertificateImportOpen ? (
|
||||||
isOpen={isCertificateImportOpen}
|
<CertificateImportModal
|
||||||
onClose={() => setIsCertificateImportOpen(false)}
|
isOpen={isCertificateImportOpen}
|
||||||
onImported={(certificate) => {
|
onClose={() => setIsCertificateImportOpen(false)}
|
||||||
setPreferredCertificateId(certificate.id);
|
onImported={(certificate) => {
|
||||||
setFeedback({
|
setPreferredCertificateId(certificate.id);
|
||||||
tone: 'success',
|
setFeedback({
|
||||||
message: `证书 ${certificate.name} 已导入,可直接用于当前网站。`,
|
tone: 'success',
|
||||||
});
|
message: `证书 ${certificate.name} 已导入,可直接用于当前网站。`,
|
||||||
}}
|
});
|
||||||
/>
|
}}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import type {
|
|||||||
ManagedDomainItem,
|
ManagedDomainItem,
|
||||||
ManagedDomainMutationPayload,
|
ManagedDomainMutationPayload,
|
||||||
} from '@/features/managed-domains/types';
|
} from '@/features/managed-domains/types';
|
||||||
import type { ProxyRouteItem } from '@/features/proxy-routes/types';
|
|
||||||
import type {
|
import type {
|
||||||
TlsCertificateFileImportPayload,
|
TlsCertificateFileImportPayload,
|
||||||
TlsCertificateItem,
|
TlsCertificateItem,
|
||||||
|
|||||||
Reference in New Issue
Block a user