diff --git a/atsf_server/web/features/config-versions/components/config-versions-page.tsx b/atsf_server/web/features/config-versions/components/config-versions-page.tsx
index cfc7b3e3..2fef4983 100644
--- a/atsf_server/web/features/config-versions/components/config-versions-page.tsx
+++ b/atsf_server/web/features/config-versions/components/config-versions-page.tsx
@@ -1,7 +1,7 @@
'use client';
-import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
-import { useState } from 'react';
+import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
+import { useState } from 'react';
import { EmptyState } from '@/components/feedback/empty-state';
import { ErrorState } from '@/components/feedback/error-state';
@@ -28,7 +28,7 @@ import {
PrimaryButton,
SecondaryButton,
} from '@/features/shared/components/resource-primitives';
-import { formatDateTime } from '@/lib/utils/date';
+import { formatDateTime } from '@/lib/utils/date';
const versionsQueryKey = ['config-versions'];
@@ -249,7 +249,7 @@ export function ConfigVersionsPage() {
},
});
- const handleOpenPublishPreview = async () => {
+ const handleOpenPublishPreview = async () => {
setFeedback(null);
setIsPreviewLoading(true);
@@ -280,20 +280,20 @@ export function ConfigVersionsPage() {
return (
-
- {isPreviewLoading ? '加载预览中...' : '预览并发布'}
-
- }
- />
+
+ {isPreviewLoading ? '加载预览中...' : '预览并发布'}
+
+ }
+ />
{feedback ? : null}
-
- {publishPreview ? (
+
+ {publishPreview ? (
publishMutation.mutate()}
onCancel={() => setPublishPreview(null)}
/>
- ) : (
-
-
-
1. 使用“预览并发布”查看新增、删除和修改的域名清单。
-
2. 确认渲染出的 Nginx 配置与支持文件内容符合预期。
-
3. 若需要回滚,可在下方历史版本列表中重新激活旧版本。
-
-
- )}
+ ) : null}
-
+
{versionsQuery.isLoading ? (
) : versionsQuery.isError ? (
diff --git a/atsf_server/web/features/dashboard/components/dashboard-overview.tsx b/atsf_server/web/features/dashboard/components/dashboard-overview.tsx
index a6980c8a..40aa1ddf 100644
--- a/atsf_server/web/features/dashboard/components/dashboard-overview.tsx
+++ b/atsf_server/web/features/dashboard/components/dashboard-overview.tsx
@@ -219,36 +219,6 @@ export function DashboardOverview() {
return (
-
}
- >
-
-
-
信息架构
-
首页负责总览
-
- 模块页保留列表、表单和具体操作,摘要统一回收到仪表盘。
-
-
-
-
导航效率
-
顶栏与侧栏已精简
-
- 顶栏只保留版本、主题切换和用户入口,侧栏宽度与按钮高度同步压缩。
-
-
-
-
响应式
-
小屏可正常展开侧栏
-
- 小于 1000px 时切换为抽屉侧栏,不再出现按钮可点但导航不可见的问题。
-
-
-
-
-
{dashboardCards.map((card) => (
diff --git a/atsf_server/web/features/managed-domains/components/managed-domains-page.tsx b/atsf_server/web/features/managed-domains/components/managed-domains-page.tsx
index 57e006e8..f66ebebe 100644
--- a/atsf_server/web/features/managed-domains/components/managed-domains-page.tsx
+++ b/atsf_server/web/features/managed-domains/components/managed-domains-page.tsx
@@ -228,7 +228,6 @@ export function ManagedDomainsPage() {
(null);
- const [editingNodeId, setEditingNodeId] = useState(null);
- const [isEditorOpen, setIsEditorOpen] = useState(false);
- const [selectedNode, setSelectedNode] = useState(null);
- const [serverUrl, setServerUrl] = useState('');
- const [, setRefreshTick] = useState(0);
+export function NodesPage() {
+ const queryClient = useQueryClient();
+ const [feedback, setFeedback] = useState(null);
+ const [editingNodeId, setEditingNodeId] = useState(null);
+ const [isEditorOpen, setIsEditorOpen] = useState(false);
+ const [selectedNode, setSelectedNode] = useState(null);
+ const [serverUrl, setServerUrl] = useState('');
+ const [, setRefreshTick] = useState(0);
const form = useForm({
resolver: zodResolver(nodeSchema),
@@ -209,15 +209,15 @@ export function NodesPage() {
return editingNodeId ? updateNode(editingNodeId, payload) : createNode(payload);
},
onSuccess: async (node) => {
- setFeedback({
- tone: 'success',
- message: editingNodeId ? '节点已更新。' : '节点已创建。',
- });
- setEditingNodeId(null);
- setIsEditorOpen(false);
- setSelectedNode(node);
- form.reset(defaultValues);
- await Promise.all([
+ setFeedback({
+ tone: 'success',
+ message: editingNodeId ? '节点已更新。' : '节点已创建。',
+ });
+ setEditingNodeId(null);
+ setIsEditorOpen(false);
+ setSelectedNode(node);
+ form.reset(defaultValues);
+ await Promise.all([
queryClient.invalidateQueries({ queryKey: nodesQueryKey }),
queryClient.invalidateQueries({ queryKey: bootstrapQueryKey }),
]);
@@ -276,30 +276,30 @@ export function NodesPage() {
return nodes.find((item) => item.id === selectedNode.id) ?? selectedNode;
}, [nodes, selectedNode]);
- const handleReset = () => {
- setFeedback(null);
- setEditingNodeId(null);
- setIsEditorOpen(false);
- form.reset(defaultValues);
- };
-
- const handleCreate = () => {
- setFeedback(null);
- setEditingNodeId(null);
- form.reset(defaultValues);
- setIsEditorOpen(true);
- };
-
- const handleEdit = (node: NodeItem) => {
- setFeedback(null);
- setEditingNodeId(node.id);
- setSelectedNode(node);
- form.reset({
- name: node.name,
- auto_update_enabled: node.auto_update_enabled,
- });
- setIsEditorOpen(true);
- };
+ const handleReset = () => {
+ setFeedback(null);
+ setEditingNodeId(null);
+ setIsEditorOpen(false);
+ form.reset(defaultValues);
+ };
+
+ const handleCreate = () => {
+ setFeedback(null);
+ setEditingNodeId(null);
+ form.reset(defaultValues);
+ setIsEditorOpen(true);
+ };
+
+ const handleEdit = (node: NodeItem) => {
+ setFeedback(null);
+ setEditingNodeId(node.id);
+ setSelectedNode(node);
+ form.reset({
+ name: node.name,
+ auto_update_enabled: node.auto_update_enabled,
+ });
+ setIsEditorOpen(true);
+ };
const handleDelete = (node: NodeItem) => {
if (
@@ -345,76 +345,30 @@ export function NodesPage() {
? buildNodeInstallCommand(normalizedServerUrl, selectedNodeFromList.agent_token)
: '';
- return (
- <>
-
-
-
- 新增节点
-
-
- 应用记录
-
- >
- }
- />
-
- {feedback ? : null}
-
-
-
-
- {rotateMutation.isPending ? '生成中...' : '重新生成 Token'}
-
- {discoveryCommand ? (
- void handleCopy(discoveryCommand, 'Discovery 部署命令已复制。')}
- >
- 复制命令
-
- ) : null}
-
- }
- >
- {bootstrapQuery.isLoading ? (
-
- ) : bootstrapQuery.isError ? (
-
- ) : (
-
-
- setServerUrl(event.target.value)} />
-
+ return (
+ <>
+
+
+
+ 新增节点
+
+
+ 应用记录
+
+ >
+ }
+ />
-
-
-
Discovery Token
-
- {bootstrapToken || '暂无'}
-
-
-
- 创建节点后也可以使用节点专属 Token 安装,这种方式适合预分配节点名与更新策略。
-
-
-
- {discoveryCommand ? {discoveryCommand} : null}
-
- )}
-
+ {feedback ?
: null}
+
-
void queryClient.invalidateQueries({ queryKey: nodesQueryKey })}
- >
- 刷新列表
-
- }
- >
+ void queryClient.invalidateQueries({ queryKey: nodesQueryKey })}
+ >
+ 刷新列表
+
+ }
+ >
{nodesQuery.isLoading ? (
) : nodesQuery.isError ? (
@@ -594,41 +548,41 @@ export function NodesPage() {
)}
-
-
-
- 取消
-
-
- {saveMutation.isPending ? '保存中...' : editingNodeId ? '保存修改' : '新增节点'}
-
-
- }
- >
-
-
- >
- );
-}
+
+
+
+ 取消
+
+
+ {saveMutation.isPending ? '保存中...' : editingNodeId ? '保存修改' : '新增节点'}
+
+
+ }
+ >
+
+
+ >
+ );
+}
diff --git a/atsf_server/web/features/proxy-routes/components/proxy-routes-page.tsx b/atsf_server/web/features/proxy-routes/components/proxy-routes-page.tsx
index e7fdd936..b2fc08e6 100644
--- a/atsf_server/web/features/proxy-routes/components/proxy-routes-page.tsx
+++ b/atsf_server/web/features/proxy-routes/components/proxy-routes-page.tsx
@@ -7,13 +7,13 @@ import { useFieldArray, useForm, useWatch } from 'react-hook-form';
import { z } from 'zod';
import { EmptyState } from '@/components/feedback/empty-state';
-import { ErrorState } from '@/components/feedback/error-state';
-import { InlineMessage } from '@/components/feedback/inline-message';
-import { LoadingState } from '@/components/feedback/loading-state';
-import { PageHeader } from '@/components/layout/page-header';
-import { AppModal } from '@/components/ui/app-modal';
-import { AppCard } from '@/components/ui/app-card';
-import { StatusBadge } from '@/components/ui/status-badge';
+import { ErrorState } from '@/components/feedback/error-state';
+import { InlineMessage } from '@/components/feedback/inline-message';
+import { LoadingState } from '@/components/feedback/loading-state';
+import { PageHeader } from '@/components/layout/page-header';
+import { AppModal } from '@/components/ui/app-modal';
+import { AppCard } from '@/components/ui/app-card';
+import { StatusBadge } from '@/components/ui/status-badge';
import { publishConfigVersion } from '@/features/config-versions/api/config-versions';
import {
createProxyRoute,
@@ -212,13 +212,13 @@ function getMatchMessage(
return '未找到匹配证书,可继续手动选择。';
}
-export function ProxyRoutesPage() {
- const queryClient = useQueryClient();
- const [feedback, setFeedback] = useState(null);
- const [editingRouteId, setEditingRouteId] = useState(null);
- const [isEditorOpen, setIsEditorOpen] = useState(false);
- const [matchResult, setMatchResult] = useState(null);
- const [isMatching, setIsMatching] = useState(false);
+export function ProxyRoutesPage() {
+ const queryClient = useQueryClient();
+ const [feedback, setFeedback] = useState(null);
+ const [editingRouteId, setEditingRouteId] = useState(null);
+ const [isEditorOpen, setIsEditorOpen] = useState(false);
+ const [matchResult, setMatchResult] = useState(null);
+ const [isMatching, setIsMatching] = useState(false);
const form = useForm({
resolver: zodResolver(proxyRouteSchema),
@@ -253,13 +253,13 @@ export function ProxyRoutesPage() {
? updateProxyRoute(editingRouteId, payload)
: createProxyRoute(payload);
},
- onSuccess: async () => {
- setFeedback({ tone: 'success', message: editingRouteId ? '规则已更新。' : '规则已创建。' });
- setEditingRouteId(null);
- setIsEditorOpen(false);
- setMatchResult(null);
- form.reset(defaultValues);
- await queryClient.invalidateQueries({ queryKey: routesQueryKey });
+ onSuccess: async () => {
+ setFeedback({ tone: 'success', message: editingRouteId ? '规则已更新。' : '规则已创建。' });
+ setEditingRouteId(null);
+ setIsEditorOpen(false);
+ setMatchResult(null);
+ form.reset(defaultValues);
+ await queryClient.invalidateQueries({ queryKey: routesQueryKey });
},
onError: (error) => {
setFeedback({ tone: 'danger', message: getErrorMessage(error) });
@@ -339,34 +339,34 @@ export function ProxyRoutesPage() {
[certificates],
);
- const handleReset = () => {
- setFeedback(null);
- setEditingRouteId(null);
- setIsEditorOpen(false);
- setMatchResult(null);
- form.reset(defaultValues);
- };
-
- const handleCreate = () => {
- setFeedback(null);
- setEditingRouteId(null);
- setMatchResult(null);
- form.reset(defaultValues);
- setIsEditorOpen(true);
- };
+ const handleReset = () => {
+ setFeedback(null);
+ setEditingRouteId(null);
+ setIsEditorOpen(false);
+ setMatchResult(null);
+ form.reset(defaultValues);
+ };
+
+ const handleCreate = () => {
+ setFeedback(null);
+ setEditingRouteId(null);
+ setMatchResult(null);
+ form.reset(defaultValues);
+ setIsEditorOpen(true);
+ };
const handleSubmit = form.handleSubmit((values) => {
setFeedback(null);
saveMutation.mutate(values);
});
- const handleEdit = (route: ProxyRouteItem) => {
- setFeedback(null);
- setEditingRouteId(route.id);
- setMatchResult(null);
- form.reset(toFormValues(route));
- setIsEditorOpen(true);
- };
+ const handleEdit = (route: ProxyRouteItem) => {
+ setFeedback(null);
+ setEditingRouteId(route.id);
+ setMatchResult(null);
+ form.reset(toFormValues(route));
+ setIsEditorOpen(true);
+ };
const handleDelete = (route: ProxyRouteItem) => {
if (!window.confirm(`确认删除规则 ${route.domain} 吗?`)) {
@@ -388,27 +388,27 @@ export function ProxyRoutesPage() {
const routes = routesQuery.data || [];
- return (
- <>
-
-
- publishMutation.mutate()} disabled={publishMutation.isPending}>
- {publishMutation.isPending ? '发布中...' : '发布当前规则'}
-
-
- 新增规则
-
- >
- }
- />
-
- {feedback ? : null}
-
-
+ return (
+ <>
+
+
+ publishMutation.mutate()} disabled={publishMutation.isPending}>
+ {publishMutation.isPending ? '发布中...' : '发布当前规则'}
+
+
+ 新增规则
+
+ >
+ }
+ />
+
+ {feedback ? : null}
+
+
{routesQuery.isLoading ? (
) : routesQuery.isError ? (
@@ -494,184 +494,184 @@ export function ProxyRoutesPage() {
)}
-
-
-
- 取消
-
-
- {saveMutation.isPending ? '保存中...' : editingRouteId ? '保存修改' : '新增规则'}
-
-
- }
- >
-
-
- >
- );
-}
+
+
+
+ 取消
+
+
+ {saveMutation.isPending ? '保存中...' : editingRouteId ? '保存修改' : '新增规则'}
+
+
+ }
+ >
+
+
+ >
+ );
+}
diff --git a/atsf_server/web/features/tls-certificates/components/tls-certificates-page.tsx b/atsf_server/web/features/tls-certificates/components/tls-certificates-page.tsx
index c59fa4e5..b1747bcc 100644
--- a/atsf_server/web/features/tls-certificates/components/tls-certificates-page.tsx
+++ b/atsf_server/web/features/tls-certificates/components/tls-certificates-page.tsx
@@ -221,7 +221,6 @@ export function TlsCertificatesPage() {
(null);
- const [isEditorOpen, setIsEditorOpen] = useState(false);
- const [feedback, setFeedback] = useState(null);
+export function UsersPage() {
+ const queryClient = useQueryClient();
+ const { user: currentUser } = useAuth();
+ const [page, setPage] = useState(0);
+ const [searchInput, setSearchInput] = useState('');
+ const [searchKeyword, setSearchKeyword] = useState('');
+ const [editingUserId, setEditingUserId] = useState(null);
+ const [isEditorOpen, setIsEditorOpen] = useState(false);
+ const [feedback, setFeedback] = useState(null);
const form = useForm({
resolver: zodResolver(userSchema),
@@ -129,15 +129,15 @@ export function UsersPage() {
enabled: isAdmin && editingUserId !== null,
});
- useEffect(() => {
- if (editingUserQuery.data) {
- form.reset({
- username: editingUserQuery.data.username,
- display_name: editingUserQuery.data.display_name || '',
- password: '',
- });
- }
- }, [editingUserQuery.data, form]);
+ useEffect(() => {
+ if (editingUserQuery.data) {
+ form.reset({
+ username: editingUserQuery.data.username,
+ display_name: editingUserQuery.data.display_name || '',
+ password: '',
+ });
+ }
+ }, [editingUserQuery.data, form]);
const saveMutation = useMutation({
mutationFn: async (values: UserFormValues) => {
@@ -155,13 +155,13 @@ export function UsersPage() {
await createUser(payload);
return '用户已创建。';
},
- onSuccess: async (message) => {
- setFeedback({ tone: 'success', message });
- setEditingUserId(null);
- setIsEditorOpen(false);
- form.reset(defaultValues);
- await Promise.all([
- queryClient.invalidateQueries({ queryKey: usersListQueryKey }),
+ onSuccess: async (message) => {
+ setFeedback({ tone: 'success', message });
+ setEditingUserId(null);
+ setIsEditorOpen(false);
+ form.reset(defaultValues);
+ await Promise.all([
+ queryClient.invalidateQueries({ queryKey: usersListQueryKey }),
queryClient.invalidateQueries({ queryKey: ['users', 'detail'] }),
]);
},
@@ -198,7 +198,7 @@ export function UsersPage() {
const activeQuery = searchKeyword.length > 0 ? searchQuery : usersQuery;
const users = useMemo(() => activeQuery.data ?? [], [activeQuery.data]);
- const handleSearchSubmit = () => {
+ const handleSearchSubmit = () => {
setFeedback(null);
setPage(0);
setSearchKeyword(searchInput.trim());
@@ -211,25 +211,25 @@ export function UsersPage() {
setFeedback(null);
};
- const handleResetForm = () => {
- setEditingUserId(null);
- setIsEditorOpen(false);
- setFeedback(null);
- form.reset(defaultValues);
- };
-
- const handleCreate = () => {
- setEditingUserId(null);
- setFeedback(null);
- form.reset(defaultValues);
- setIsEditorOpen(true);
- };
-
- const handleEdit = (targetUser: UserItem) => {
- setFeedback(null);
- setEditingUserId(targetUser.id);
- setIsEditorOpen(true);
- };
+ const handleResetForm = () => {
+ setEditingUserId(null);
+ setIsEditorOpen(false);
+ setFeedback(null);
+ form.reset(defaultValues);
+ };
+
+ const handleCreate = () => {
+ setEditingUserId(null);
+ setFeedback(null);
+ form.reset(defaultValues);
+ setIsEditorOpen(true);
+ };
+
+ const handleEdit = (targetUser: UserItem) => {
+ setFeedback(null);
+ setEditingUserId(targetUser.id);
+ setIsEditorOpen(true);
+ };
const handleManage = (targetUser: UserItem, action: ManageUserAction) => {
const actionText = {
@@ -258,10 +258,10 @@ export function UsersPage() {
});
if (!isAdmin) {
- return (
-
-
+
-
-
- 新建用户
-
- }
- />
-
- {feedback ? : null}
-
-
+
+
+ 新建用户
+
+ }
+ />
+
+ {feedback ? : null}
+
+
-
-
-
- 取消
-
-
- {saveMutation.isPending ? '保存中...' : editingUserId ? '保存修改' : '创建用户'}
-
-
- }
- >
- {editingUserId && editingUserQuery.isLoading ? (
-
- ) : editingUserId && editingUserQuery.isError ? (
-
- ) : (
-
- )}
-
- >
- );
-}
+
+
+
+ 取消
+
+
+ {saveMutation.isPending ? '保存中...' : editingUserId ? '保存修改' : '创建用户'}
+
+
+ }
+ >
+ {editingUserId && editingUserQuery.isLoading ? (
+
+ ) : editingUserId && editingUserQuery.isError ? (
+
+ ) : (
+
+ )}
+
+ >
+ );
+}
diff --git a/atsf_server/web/lib/constants/navigation.ts b/atsf_server/web/lib/constants/navigation.ts
index afa8fa95..4f99d6fc 100644
--- a/atsf_server/web/lib/constants/navigation.ts
+++ b/atsf_server/web/lib/constants/navigation.ts
@@ -13,7 +13,7 @@ export const dashboardNavigation: NavigationItem[] = [
},
{
href: '/proxy-route',
- label: '反向代理',
+ label: '规则',
icon: 'proxy',
},
{