111 KiB
111 KiB
IT0 PC Web 管理前端开发指导文档
IT Operations Intelligent Agent — PC Web 管理控制台(React + Next.js + Zustand/Redux Toolkit)
1. 项目概述
1.1 定位
IT0 Web Admin 是管理员级别的 PC 端控制台,与 Flutter App(移动端运维驾驶舱)互补:
| 维度 | Flutter App(移动端) | Web Admin(PC 端) |
|---|---|---|
| 角色 | 一线运维操作 | 管理配置 + 深度分析 |
| 场景 | 随时随地审批、巡检、对话 | 坐在电脑前做系统配置 |
| 核心功能 | AI 对话、快捷任务、审批、告警 | Agent 配置、Runbook 管理、权限控制、审计回溯 |
| 数据展示 | 简洁仪表盘 | 复杂表格、多维图表、终端全屏 |
1.2 技术栈
| 层面 | 技术选型 |
|---|---|
| 框架 | Next.js 14+ (App Router) |
| 语言 | TypeScript (strict mode) |
| 状态管理 | Zustand(轻量级 UI 状态) + Redux Toolkit(复杂业务状态) |
| 样式 | Tailwind CSS + shadcn/ui 组件库 |
| 数据请求 | TanStack Query (React Query) v5 |
| 表单 | React Hook Form + Zod |
| 表格 | TanStack Table v8 |
| 图表 | Recharts / Tremor |
| 终端 | xterm.js |
| 代码编辑器 | Monaco Editor (Runbook/Prompt 编辑) |
| WebSocket | 原生 WebSocket + 自动重连 |
| 测试 | Vitest + React Testing Library |
| Lint | ESLint + Prettier |
1.3 设计原则
- Clean Architecture:严格分层(domain → application → infrastructure → presentation)
- Zustand + Redux Toolkit 混合:Zustand 管理 UI/交互状态(侧边栏、模态框、主题),Redux Toolkit 管理复杂业务状态(Agent 配置、Runbook 编辑、权限矩阵)
- Server Components 优先:Next.js App Router 的 RSC 用于静态/数据密集型页面,Client Components 用于交互
- 管理员体验:大屏优化、键盘快捷键、批量操作、深度搜索
- 多租户感知:顶栏租户选择器 + 所有 API 请求自动携带
X-Tenant-Id,超级管理员可切换租户视角
2. 项目结构
2.1 整体目录
it0-web-admin/
├── src/
│ ├── app/ # Next.js App Router(路由层)
│ │ ├── layout.tsx # 根布局(Providers、主题)
│ │ ├── page.tsx # 首页(重定向到 dashboard)
│ │ ├── (auth)/
│ │ │ ├── login/page.tsx
│ │ │ └── layout.tsx
│ │ ├── (admin)/ # 管理后台布局组
│ │ │ ├── layout.tsx # 侧边栏 + 顶栏布局
│ │ │ ├── dashboard/page.tsx
│ │ │ ├── agent-config/ # Agent 配置
│ │ │ │ ├── page.tsx # 引擎管理
│ │ │ │ ├── prompts/page.tsx # System Prompt 管理
│ │ │ │ ├── hooks/page.tsx # Hook 脚本管理
│ │ │ │ └── skills/ # ★ Claude Skills 管理
│ │ │ │ ├── page.tsx # Skill 列表
│ │ │ │ ├── [name]/page.tsx # Skill 详情/编辑
│ │ │ │ └── new/page.tsx # 新建 Skill
│ │ │ ├── runbooks/ # Runbook 管理
│ │ │ │ ├── page.tsx # 列表
│ │ │ │ ├── [id]/page.tsx # 详情/编辑
│ │ │ │ └── new/page.tsx # 新建
│ │ │ ├── standing-orders/ # ★ 驻留指令管理
│ │ │ │ ├── page.tsx # 指令列表 + 执行概况
│ │ │ │ ├── [id]/page.tsx # 指令详情/编辑/执行历史
│ │ │ │ └── [id]/executions/page.tsx # 执行记录详情
│ │ │ ├── servers/ # 服务器管理
│ │ │ │ ├── page.tsx
│ │ │ │ ├── [id]/page.tsx
│ │ │ │ └── clusters/page.tsx
│ │ │ ├── security/ # 安全配置
│ │ │ │ ├── risk-rules/page.tsx # 风险规则管理
│ │ │ │ ├── credentials/page.tsx # 凭证管理
│ │ │ │ └── permissions/page.tsx # RBAC 权限管理
│ │ │ ├── monitoring/ # 监控配置
│ │ │ │ ├── alert-rules/page.tsx # 告警规则
│ │ │ │ ├── health-checks/page.tsx # 健康检查配置
│ │ │ │ └── metrics/page.tsx # 指标面板
│ │ │ ├── communication/ # 通信配置
│ │ │ │ ├── channels/page.tsx # 渠道管理
│ │ │ │ ├── contacts/page.tsx # 联系人管理
│ │ │ │ └── escalation/page.tsx # 升级策略
│ │ │ ├── audit/ # 审计日志
│ │ │ │ └── page.tsx
│ │ │ ├── sessions/ # 会话历史
│ │ │ │ ├── page.tsx
│ │ │ │ └── [id]/page.tsx # 会话回放
│ │ │ ├── tenants/ # ★ 多租户管理(超级管理员)
│ │ │ │ ├── page.tsx # 租户列表
│ │ │ │ ├── [id]/page.tsx # 租户详情/编辑
│ │ │ │ └── new/page.tsx # 新建租户
│ │ │ └── terminal/ # Web 终端
│ │ │ └── page.tsx
│ │ └── api/ # Next.js API Routes(BFF 层)
│ │ ├── auth/[...nextauth]/route.ts
│ │ └── proxy/[...path]/route.ts # 反向代理到后端微服务
│ │
│ ├── core/ # 核心基础设施
│ │ ├── config/
│ │ │ └── app-config.ts # 环境配置
│ │ ├── api/
│ │ │ ├── api-client.ts # Fetch 封装(token 注入)
│ │ │ ├── websocket-client.ts # WebSocket 管理
│ │ │ └── query-keys.ts # TanStack Query key 管理
│ │ ├── auth/
│ │ │ ├── auth-provider.tsx # 认证上下文
│ │ │ └── use-auth.ts # 认证 hook
│ │ ├── theme/
│ │ │ └── theme-config.ts
│ │ └── utils/
│ │ ├── date.ts
│ │ ├── format.ts
│ │ └── cn.ts # Tailwind className 合并
│ │
│ ├── domain/ # 领域层(纯 TypeScript,零框架依赖)
│ │ ├── entities/
│ │ │ ├── agent-engine.ts
│ │ │ ├── skill.ts # ★ Claude Skill 实体
│ │ │ ├── server.ts
│ │ │ ├── runbook.ts
│ │ │ ├── alert-rule.ts
│ │ │ ├── risk-rule.ts
│ │ │ ├── credential.ts
│ │ │ ├── user.ts
│ │ │ ├── role.ts
│ │ │ ├── audit-log.ts
│ │ │ ├── session.ts
│ │ │ ├── contact.ts
│ │ │ ├── escalation-policy.ts
│ │ │ ├── tenant.ts # ★ 租户实体
│ │ │ └── standing-order.ts # ★ 驻留指令实体
│ │ ├── value-objects/
│ │ │ ├── engine-type.ts
│ │ │ ├── risk-level.ts
│ │ │ ├── server-environment.ts
│ │ │ ├── alert-severity.ts
│ │ │ └── channel-type.ts
│ │ └── repositories/ # 仓储接口(抽象)
│ │ ├── agent-config.repository.ts
│ │ ├── server.repository.ts
│ │ ├── runbook.repository.ts
│ │ ├── alert-rule.repository.ts
│ │ ├── security.repository.ts
│ │ ├── audit.repository.ts
│ │ └── communication.repository.ts
│ │
│ ├── application/ # 应用层(用例)
│ │ ├── use-cases/
│ │ │ ├── agent-config/
│ │ │ │ ├── switch-engine.ts
│ │ │ │ ├── update-system-prompt.ts
│ │ │ │ └── manage-hooks.ts
│ │ │ ├── runbooks/
│ │ │ │ ├── create-runbook.ts
│ │ │ │ ├── update-runbook.ts
│ │ │ │ ├── delete-runbook.ts
│ │ │ │ └── test-runbook.ts
│ │ │ ├── servers/
│ │ │ │ ├── add-server.ts
│ │ │ │ ├── update-server.ts
│ │ │ │ └── remove-server.ts
│ │ │ ├── security/
│ │ │ │ ├── manage-risk-rules.ts
│ │ │ │ ├── manage-credentials.ts
│ │ │ │ └── manage-permissions.ts
│ │ │ └── monitoring/
│ │ │ ├── create-alert-rule.ts
│ │ │ └── configure-health-check.ts
│ │ └── dto/
│ │ ├── agent-config.dto.ts
│ │ ├── runbook.dto.ts
│ │ └── server.dto.ts
│ │
│ ├── infrastructure/ # 基础设施层(适配器实现)
│ │ └── repositories/
│ │ ├── api-agent-config.repository.ts
│ │ ├── api-server.repository.ts
│ │ ├── api-runbook.repository.ts
│ │ ├── api-alert-rule.repository.ts
│ │ ├── api-security.repository.ts
│ │ ├── api-audit.repository.ts
│ │ ├── api-communication.repository.ts
│ │ └── api-tenant.repository.ts # ★ 租户管理 API
│ │
│ ├── stores/ # 状态管理
│ │ ├── zustand/ # Zustand — UI/交互状态
│ │ │ ├── ui-store.ts # 侧边栏、模态框、面包屑
│ │ │ ├── theme-store.ts # 主题切换
│ │ │ ├── terminal-store.ts # 终端会话状态
│ │ │ ├── notification-store.ts # Toast 通知队列
│ │ │ └── tenant-store.ts # ★ 当前租户上下文(选中的租户ID)
│ │ └── redux/ # Redux Toolkit — 复杂业务状态
│ │ ├── store.ts # Redux store 配置
│ │ ├── slices/
│ │ │ ├── agent-config.slice.ts # Agent 引擎配置状态
│ │ │ ├── runbook-editor.slice.ts # Runbook 编辑器状态(复杂表单+版本控制)
│ │ │ ├── permission-matrix.slice.ts # RBAC 权限矩阵(多维度交叉编辑)
│ │ │ ├── risk-rules.slice.ts # 风险规则集管理
│ │ │ └── session-replay.slice.ts # 会话回放状态(时间线+步骤)
│ │ └── middleware/
│ │ └── audit-middleware.ts # 配置变更自动记录审计
│ │
│ ├── components/ # 共享 UI 组件
│ │ ├── layout/
│ │ │ ├── sidebar.tsx # 侧边导航
│ │ │ ├── header.tsx # 顶栏(搜索、通知、用户菜单、★ 租户选择器)
│ │ │ ├── breadcrumb.tsx
│ │ │ └── main-layout.tsx
│ │ ├── ui/ # shadcn/ui 基础组件
│ │ │ ├── button.tsx
│ │ │ ├── input.tsx
│ │ │ ├── dialog.tsx
│ │ │ ├── data-table.tsx # TanStack Table 封装
│ │ │ ├── command.tsx # 命令面板 (Cmd+K)
│ │ │ ├── toast.tsx
│ │ │ └── ...
│ │ └── domain/ # 业务组件
│ │ ├── server-status-badge.tsx
│ │ ├── risk-level-tag.tsx
│ │ ├── engine-type-badge.tsx
│ │ ├── alert-severity-icon.tsx
│ │ ├── command-syntax-highlight.tsx # Bash 命令高亮
│ │ ├── json-viewer.tsx # JSON 树形查看器
│ │ └── metrics-chart.tsx # 指标图表组件
│ │
│ └── hooks/ # 自定义 Hooks
│ ├── use-websocket.ts # WebSocket 连接管理
│ ├── use-debounce.ts
│ ├── use-keyboard-shortcut.ts # 键盘快捷键
│ └── use-confirmation.ts # 危险操作确认弹窗
│
├── public/
├── next.config.ts
├── tailwind.config.ts
├── tsconfig.json
├── package.json
└── vitest.config.ts
3. Clean Architecture 分层规则
┌─────────────────────────────────────────┐
│ Presentation Layer │
│ (app/ pages, components/, hooks/) │
│ - Next.js 页面 & React 组件 │
│ - 只能调用 application 层的 use-cases │
│ - 通过 Zustand/Redux 管理 UI 状态 │
│ - 通过 TanStack Query 管理服务端状态 │
└────────────────┬────────────────────────┘
│ 调用
┌────────────────▼────────────────────────┐
│ Application Layer │
│ (application/use-cases/) │
│ - 用例编排(业务流程) │
│ - 调用 domain 层 repository 接口 │
│ - DTO 转换 │
└────────────────┬────────────────────────┘
│ 依赖倒置
┌────────────────▼────────────────────────┐
│ Domain Layer │
│ (domain/entities, value-objects, │
│ repositories 接口) │
│ - 纯 TypeScript,零框架依赖 │
│ - Entity 定义、业务规则 │
│ - Repository 接口(抽象类/interface) │
└────────────────△────────────────────────┘
│ 实现
┌────────────────┴────────────────────────┐
│ Infrastructure Layer │
│ (infrastructure/repositories/) │
│ - Repository 接口的具体实现 │
│ - 调用后端 API (fetch/axios) │
│ - 数据模型 ↔ 领域实体转换 │
└─────────────────────────────────────────┘
依赖方向:presentation → application → domain ← infrastructure
4. 状态管理策略:Zustand + Redux Toolkit 混合
4.1 分工原则
┌────────────────────────────────────────────────────────┐
│ Zustand │
│ 管理「轻量级 UI 状态」— 直接、简单、无样板代码 │
│ │
│ • 侧边栏展开/折叠 │
│ • 模态框开关 │
│ • 当前主题(暗色/亮色) │
│ • Toast 通知队列 │
│ • 终端会话(当前标签页、连接状态) │
│ • 命令面板(Cmd+K)开关 │
│ • 面包屑路径 │
│ │
│ 特点:一个 store = 一个 create(),直接 get/set │
└────────────────────────────────────────────────────────┘
┌────────────────────────────────────────────────────────┐
│ Redux Toolkit │
│ 管理「复杂业务状态」— 可预测、可追溯、中间件生态 │
│ │
│ • Agent 配置管理 │
│ (引擎选择、参数配置、提示词版本控制) │
│ • Runbook 编辑器 │
│ (多步骤表单、版本对比、草稿保存、撤销/重做) │
│ • RBAC 权限矩阵 │
│ (角色×权限×资源 三维编辑、批量操作、变更对比) │
│ • 风险规则集管理 │
│ (规则排序、优先级、条件组合、测试模拟) │
│ • 会话回放 │
│ (时间线导航、步骤切换、工具执行展开/折叠) │
│ │
│ 特点:slice + createAsyncThunk + middleware │
│ 优势:DevTools 调试、时间旅行、审计中间件 │
└────────────────────────────────────────────────────────┘
┌────────────────────────────────────────────────────────┐
│ TanStack Query │
│ 管理「服务端状态」— 缓存、失效、重新获取 │
│ │
│ • 服务器列表、详情 │
│ • 告警规则列表 │
│ • 审计日志查询 │
│ • 联系人列表 │
│ • 所有 GET 请求的缓存管理 │
│ │
│ 特点:声明式数据获取,自动缓存失效 │
└────────────────────────────────────────────────────────┘
4.2 Zustand Store 示例
// stores/zustand/ui-store.ts
interface UiState {
sidebarCollapsed: boolean;
commandPaletteOpen: boolean;
activeModal: string | null;
breadcrumbs: BreadcrumbItem[];
}
interface UiActions {
toggleSidebar: () => void;
openCommandPalette: () => void;
closeCommandPalette: () => void;
openModal: (id: string) => void;
closeModal: () => void;
setBreadcrumbs: (items: BreadcrumbItem[]) => void;
}
export const useUiStore = create<UiState & UiActions>((set) => ({
// State
sidebarCollapsed: false,
commandPaletteOpen: false,
activeModal: null,
breadcrumbs: [],
// Actions
toggleSidebar: () => set((s) => ({ sidebarCollapsed: !s.sidebarCollapsed })),
openCommandPalette: () => set({ commandPaletteOpen: true }),
closeCommandPalette: () => set({ commandPaletteOpen: false }),
openModal: (id) => set({ activeModal: id }),
closeModal: () => set({ activeModal: null }),
setBreadcrumbs: (items) => set({ breadcrumbs: items }),
}));
// stores/zustand/theme-store.ts
type Theme = 'light' | 'dark' | 'system';
interface ThemeState {
theme: Theme;
setTheme: (theme: Theme) => void;
}
export const useThemeStore = create<ThemeState>()(
persist(
(set) => ({
theme: 'dark',
setTheme: (theme) => set({ theme }),
}),
{ name: 'it0-theme' },
),
);
4.3 Redux Toolkit Slice 示例
// stores/redux/slices/agent-config.slice.ts
interface AgentConfigState {
activeEngine: EngineType;
engines: Record<EngineType, EngineConfig>;
systemPromptVersions: SystemPromptVersion[];
currentPromptDraft: string | null;
hookScripts: HookScript[];
isLoading: boolean;
error: string | null;
// 变更追踪
unsavedChanges: boolean;
lastSavedAt: string | null;
}
const initialState: AgentConfigState = {
activeEngine: 'claude_code_cli',
engines: {
claude_code_cli: { path: '/usr/local/bin/claude', maxTurns: 30, maxBudgetUsd: 5 },
claude_api: { model: 'claude-sonnet-4-5-20250929', apiKey: '', maxTokens: 8192 },
custom: { endpoint: '', authToken: '' },
},
systemPromptVersions: [],
currentPromptDraft: null,
hookScripts: [],
isLoading: false,
error: null,
unsavedChanges: false,
lastSavedAt: null,
};
export const agentConfigSlice = createSlice({
name: 'agentConfig',
initialState,
reducers: {
setActiveEngine: (state, action: PayloadAction<EngineType>) => {
state.activeEngine = action.payload;
state.unsavedChanges = true;
},
updateEngineConfig: (state, action: PayloadAction<{ type: EngineType; config: Partial<EngineConfig> }>) => {
state.engines[action.payload.type] = {
...state.engines[action.payload.type],
...action.payload.config,
};
state.unsavedChanges = true;
},
setPromptDraft: (state, action: PayloadAction<string>) => {
state.currentPromptDraft = action.payload;
state.unsavedChanges = true;
},
addHookScript: (state, action: PayloadAction<HookScript>) => {
state.hookScripts.push(action.payload);
state.unsavedChanges = true;
},
removeHookScript: (state, action: PayloadAction<string>) => {
state.hookScripts = state.hookScripts.filter(h => h.id !== action.payload);
state.unsavedChanges = true;
},
markSaved: (state) => {
state.unsavedChanges = false;
state.lastSavedAt = new Date().toISOString();
},
},
extraReducers: (builder) => {
builder
.addCase(fetchAgentConfig.pending, (state) => {
state.isLoading = true;
state.error = null;
})
.addCase(fetchAgentConfig.fulfilled, (state, action) => {
state.isLoading = false;
Object.assign(state, action.payload);
state.unsavedChanges = false;
})
.addCase(fetchAgentConfig.rejected, (state, action) => {
state.isLoading = false;
state.error = action.error.message ?? 'Failed to load config';
})
.addCase(saveAgentConfig.fulfilled, (state) => {
state.unsavedChanges = false;
state.lastSavedAt = new Date().toISOString();
});
},
});
// Async Thunks
export const fetchAgentConfig = createAsyncThunk(
'agentConfig/fetch',
async (_, { extra }) => {
const repo = extra as { agentConfigRepo: AgentConfigRepository };
return repo.agentConfigRepo.getConfig();
},
);
export const saveAgentConfig = createAsyncThunk(
'agentConfig/save',
async (_, { getState, extra }) => {
const state = (getState() as RootState).agentConfig;
const repo = extra as { agentConfigRepo: AgentConfigRepository };
return repo.agentConfigRepo.saveConfig({
activeEngine: state.activeEngine,
engines: state.engines,
hookScripts: state.hookScripts,
});
},
);
export const { setActiveEngine, updateEngineConfig, setPromptDraft, addHookScript, removeHookScript, markSaved } =
agentConfigSlice.actions;
// stores/redux/slices/runbook-editor.slice.ts
interface RunbookEditorState {
currentRunbook: Runbook | null;
draft: Partial<Runbook>;
originalSnapshot: string | null; // JSON snapshot 用于变更对比
validationErrors: Record<string, string>;
testResult: RunbookTestResult | null;
isTesting: boolean;
history: RunbookHistoryEntry[]; // 撤销/重做栈
historyIndex: number;
}
const runbookEditorSlice = createSlice({
name: 'runbookEditor',
initialState: { /* ... */ } as RunbookEditorState,
reducers: {
loadRunbook: (state, action: PayloadAction<Runbook>) => {
state.currentRunbook = action.payload;
state.draft = { ...action.payload };
state.originalSnapshot = JSON.stringify(action.payload);
state.validationErrors = {};
state.history = [];
state.historyIndex = -1;
},
updateDraftField: (state, action: PayloadAction<{ field: string; value: unknown }>) => {
const { field, value } = action.payload;
// 保存历史(撤销支持)
state.history = state.history.slice(0, state.historyIndex + 1);
state.history.push({ field, oldValue: (state.draft as any)[field], newValue: value });
state.historyIndex = state.history.length - 1;
// 更新值
(state.draft as any)[field] = value;
},
undo: (state) => {
if (state.historyIndex < 0) return;
const entry = state.history[state.historyIndex];
(state.draft as any)[entry.field] = entry.oldValue;
state.historyIndex--;
},
redo: (state) => {
if (state.historyIndex >= state.history.length - 1) return;
state.historyIndex++;
const entry = state.history[state.historyIndex];
(state.draft as any)[entry.field] = entry.newValue;
},
setValidationErrors: (state, action: PayloadAction<Record<string, string>>) => {
state.validationErrors = action.payload;
},
clearEditor: (state) => {
state.currentRunbook = null;
state.draft = {};
state.originalSnapshot = null;
state.history = [];
state.historyIndex = -1;
},
},
extraReducers: (builder) => {
builder
.addCase(testRunbook.pending, (state) => { state.isTesting = true; })
.addCase(testRunbook.fulfilled, (state, action) => {
state.isTesting = false;
state.testResult = action.payload;
})
.addCase(testRunbook.rejected, (state) => { state.isTesting = false; });
},
});
4.4 Redux Store 配置
// stores/redux/store.ts
import { configureStore } from '@reduxjs/toolkit';
import { agentConfigSlice } from './slices/agent-config.slice';
import { runbookEditorSlice } from './slices/runbook-editor.slice';
import { permissionMatrixSlice } from './slices/permission-matrix.slice';
import { riskRulesSlice } from './slices/risk-rules.slice';
import { sessionReplaySlice } from './slices/session-replay.slice';
import { auditMiddleware } from './middleware/audit-middleware';
// 依赖注入:通过 extra 参数注入 repository 实现
export const createStore = (repositories: Repositories) =>
configureStore({
reducer: {
agentConfig: agentConfigSlice.reducer,
runbookEditor: runbookEditorSlice.reducer,
permissionMatrix: permissionMatrixSlice.reducer,
riskRules: riskRulesSlice.reducer,
sessionReplay: sessionReplaySlice.reducer,
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
thunk: { extraArgument: repositories },
serializableCheck: {
ignoredActions: ['sessionReplay/loadSession'],
},
}).concat(auditMiddleware),
});
export type RootState = ReturnType<ReturnType<typeof createStore>['getState']>;
export type AppDispatch = ReturnType<typeof createStore>['dispatch'];
4.5 审计中间件(配置变更自动追踪)
// stores/redux/middleware/audit-middleware.ts
import { Middleware } from '@reduxjs/toolkit';
/**
* 审计中间件:
* 拦截所有「配置变更」类 action,自动向 audit-service 发送记录
* 这是使用 Redux 的核心价值之一 — 所有状态变更经过统一管道
*/
export const auditMiddleware: Middleware = (store) => (next) => (action) => {
const result = next(action);
// 只追踪配置变更类 action
const auditableActions = [
'agentConfig/setActiveEngine',
'agentConfig/updateEngineConfig',
'agentConfig/saveAgentConfig/fulfilled',
'riskRules/addRule',
'riskRules/removeRule',
'riskRules/updateRule',
'permissionMatrix/updatePermission',
];
if (auditableActions.includes(action.type)) {
// 异步发送审计记录(不阻塞 UI)
fetch('/api/proxy/audit-service/logs', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
actionType: 'config_change',
actorType: 'user',
detail: {
action: action.type,
payload: action.payload,
timestamp: new Date().toISOString(),
},
}),
}).catch(console.error);
}
return result;
};
5. 核心页面详细设计
5.1 Agent 配置页(核心)
这是 Web Admin 最重要的页面 — 配置 AI Agent 的行为。
┌──────────────────────────────────────────────────────────────────┐
│ IT0 Admin > Agent Configuration │
├──────────────────────────────────────────────────────────────────┤
│ │
│ ── 引擎选择 ──────────────────────────────────────────── │
│ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ ● Claude Code │ │ ○ Claude API │ │ ○ Custom Agent │ │
│ │ CLI │ │ │ │ (Coming Soon) │ │
│ │ Active ✓ │ │ │ │ │ │
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
│ │
│ ── Claude Code CLI 配置 ──────────────────────────────── │
│ │
│ CLI 路径: [/usr/local/bin/claude ] │
│ 最大轮次: [30 ] │
│ 预算上限: [$5.00 ] / 每次任务 │
│ 允许工具: [✓ Bash] [✓ Read] [✓ Grep] [✓ Glob] [□ Write] │
│ │
│ ── System Prompt 管理 ────────────────────────────────── │
│ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ [Monaco Editor] │ │
│ │ │ │
│ │ 你是 IT0 运维智能体,负责管理以下服务器集群... │ │
│ │ │ │
│ │ ## 操作规范 │ │
│ │ ### 权限分级 │ │
│ │ - Level 0(自动执行):... │ │
│ │ │ │
│ └────────────────────────────────────────────────────────┘ │
│ 版本: v3 (2026-02-08) [变更历史] [还原上一版] [保存新版本] │
│ │
│ ── Hook 脚本管理 ─────────────────────────────────────── │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ PreToolUse │ Bash │ validate.py [编辑][删除] │ │
│ │ PostToolUse │ * │ audit-logger.py [编辑][删除] │ │
│ └──────────────────────────────────────────────────┘ │
│ [+ 添加 Hook 脚本] │
│ │
│ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │
│ [有未保存的更改] [取消] [保存配置] │
└──────────────────────────────────────────────────────────────────┘
5.2 Runbook 管理页
┌──────────────────────────────────────────────────────────────────┐
│ IT0 Admin > Runbooks [+ 新建 Runbook] │
├──────────────────────────────────────────────────────────────────┤
│ │
│ 搜索: [________________] 筛选: [全部类型 ▼] [全部状态 ▼] │
│ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ 名称 │ 触发方式 │ 最高风险│ 自动审批│ 状态 │ │
│ ├────────────────────┼──────────┼─────────┼────────┼───────┤ │
│ │ 全面健康检查 │ 手动/定时 │ L0 │ ✓ │ 启用 │ │
│ │ 磁盘空间清理 │ 告警触发 │ L1 │ ✓ │ 启用 │ │
│ │ 应用滚动部署 │ 手动 │ L2 │ ✗ │ 启用 │ │
│ │ 数据库主从切换 │ 手动 │ L2 │ ✗ │ 禁用 │ │
│ │ 紧急回滚 │ 手动 │ L2 │ ✗ │ 启用 │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
│ 显示 1-5 共 5 条 │
└──────────────────────────────────────────────────────────────────┘
Runbook 编辑页
┌──────────────────────────────────────────────────────────────────┐
│ IT0 Admin > Runbooks > 磁盘空间清理 │
├──────────────────────────────────────────────────────────────────┤
│ │
│ 名称: [磁盘空间清理 ] │
│ 描述: [当磁盘使用率超过阈值时自动清理旧日志文件 ] │
│ │
│ 触发方式: ○ 手动 ● 告警触发 ○ 定时 │
│ 关联告警: [disk_usage_high ▼] │
│ │
│ 最高风险等级: [L1 ▼] │
│ 自动审批: [✓] (仅 Level 0-1 的命令自动通过) │
│ 允许工具: [✓ Bash] [✓ Read] [✓ Grep] [□ Write] │
│ │
│ ── Prompt 模板 ──────────────────────────────────────── │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ [Monaco Editor] │ │
│ │ │ │
│ │ 请检查以下服务器的磁盘使用情况: │ │
│ │ {{target_servers}} │ │
│ │ │ │
│ │ 执行步骤: │ │
│ │ 1. 检查 df -h 输出,找到使用率 > 80% 的分区 │ │
│ │ 2. 查看 /var/log 下的大文件 │ │
│ │ 3. 清理 30 天前的日志文件 │ │
│ │ 4. 验证清理后的磁盘使用率 │ │
│ │ 5. 汇报清理结果和释放的空间 │ │
│ │ │ │
│ └──────────────────────────────────────────────────────┘ │
│ 模板变量: {{target_servers}} {{alert_message}} {{threshold}} │
│ │
│ ── 测试 ─────────────────────────────────────────────── │
│ 目标服务器: [prod-1, prod-2 ▼] │
│ [模拟测试(Dry Run)] [实际测试] │
│ │
│ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │
│ [Ctrl+Z 撤销] [Ctrl+Y 重做] [取消] [保存草稿] [发布] │
└──────────────────────────────────────────────────────────────────┘
5.3 ★ 驻留指令管理(Standing Orders)
驻留指令列表页
┌──────────────────────────────────────────────────────────────────┐
│ IT0 Admin > Standing Orders │
├──────────────────────────────────────────────────────────────────┤
│ │
│ 驻留指令 — Agent 自治执行的任务定义 │
│ (通过 Flutter App 对话创建,此处管理和监控) │
│ │
│ 状态: [全部 ▾] 触发: [全部 ▾] [搜索...] │
│ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ 指令ID │ 名称 │ 触发 │ 上次执行 │ 下次 │ 状态 │
│ ├─────────────────┼────────────────┼──────────┼───────────┼──────┼────────┤
│ │ SO-20260208-001 │ 每日生产巡检 │ ⏰ 08:00 │ 今天 08:00│ 明天 │ ✓ 活跃 │
│ │ │ │ (每日) │ ✓ 正常 │08:00 │ │
│ │ SO-20260208-002 │ 磁盘空间监控 │ ⚡ 事件 │ 1h 前 │ — │ ✓ 活跃 │
│ │ │ │ (告警触发)│ ⚠ 已升级 │ │ │
│ │ SO-20260210-001 │ 周末日志归档 │ ⏰ 02:00 │ 上周六 │ 本周 │ ✓ 活跃 │
│ │ │ │ (每周六) │ ✓ 正常 │ 六 │ │
│ │ SO-20260205-001 │ 部署后烟雾测试 │ ⚡ 事件 │ 2天前 │ — │ ⏸ 暂停 │
│ │ │ │ (部署后) │ ✓ 正常 │ │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
│ 共 4 条指令 · 活跃 3 · 暂停 1 │
│ 今日已执行 5 次 · 成功 4 · 升级 1 │
│ │
└──────────────────────────────────────────────────────────────────┘
驻留指令详情页
┌──────────────────────────────────────────────────────────────────┐
│ IT0 Admin > Standing Orders > 每日生产巡检 │
├──────────────────────────────────────────────────────────────────┤
│ │
│ ── 基本信息 ────────────────────────────────────────────────── │
│ ID: SO-20260208-001 │
│ 名称: 每日生产巡检 │
│ 状态: ● 活跃 [⏸ 暂停] [🗑 归档] │
│ 创建方式: 对话定义(会话 sess-abc-123) [查看原始对话] │
│ 最后修改: 对话修改(会话 sess-def-456) [查看修改对话] │
│ │
│ ── 触发配置 ────────────────────────────────────────────────── │
│ 触发方式: [⏰ 定时 (Cron) ▾] │
│ Cron 表达式: [0 8 * * * ] → 每天 08:00 │
│ 生效时间: 2026-02-09 起 │
│ 过期时间: [无 (永久) ▾] │
│ │
│ ── 目标范围 ────────────────────────────────────────────────── │
│ ☑ 按环境筛选: [prod ▾] │
│ ☐ 指定服务器 ☐ 指定集群 ☐ 所有服务器 │
│ 匹配服务器: prod-1, prod-2, prod-3 (共 3 台) │
│ │
│ ── Agent 执行指令 ──────────────────────────────────────────── │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ (Monaco Editor — 可编辑 Agent Prompt) │ │
│ │ │ │
│ │ 检查所有目标服务器的 CPU、内存、磁盘使用率。 │ │
│ │ 如果磁盘使用率超过 80%,清理 7 天前的日志文件。 │ │
│ │ 汇总所有指标,生成巡检报告。 │ │
│ └──────────────────────────────────────────────────────────┘ │
│ 关联 Skills: [cleanup_logs] [inspect] │
│ 关联 Runbook: (无) │
│ 最高风险等级: [L1 — 低风险写入 ▾] │
│ 最大轮次: [20] 预算上限: [$0.50] │
│ │
│ ── 决策边界 ────────────────────────────────────────────────── │
│ ✅ 允许的操作: │
│ ┌────────────────────────────────────────────────────┐ │
│ │ cleanup_logs (清理日志) [✕ 移除]│ │
│ │ report_metrics (汇报指标) [✕ 移除]│ │
│ │ [+ 添加操作] │ │
│ └────────────────────────────────────────────────────┘ │
│ │
│ 🚨 升级规则: │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ 条件 │ 渠道 │ 优先级 │ 操作 │ │
│ ├─────────────────────────┼─────────────┼───────────┼────────┤ │
│ │ disk_usage > 95% │ 🔔 推送+电话│ 🔴 紧急 │ [编辑] │ │
│ │ cpu > 90% for 10min │ 🔔 推送+电话│ 🔴 紧急 │ [编辑] │ │
│ │ other_anomaly │ 📱 推送+短信│ 🟡 普通 │ [编辑] │ │
│ │ [+ 添加规则] │ │ │ │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
│ ── 执行历史 ────────────────────────────────────────────────── │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ 时间 │ 状态 │ 耗时 │ 操作 │ 详情 │ │
│ ├───────────────────┼──────────┼───────┼──────────────┼──────┤ │
│ │ 2026-02-08 08:00 │ ✓ 完成 │ 45s │ 清理 3.2GB │ [▶] │ │
│ │ 2026-02-07 08:00 │ ✓ 完成 │ 38s │ 全部正常 │ [▶] │ │
│ │ 2026-02-06 08:00 │ ⚠ 已升级 │ 12min │ 磁盘 97% → │ [▶] │ │
│ │ │ │ │ 电话通知 │ │ │
│ └────────────────────────────────────────────────────────────┘ │
│ [查看全部执行记录] │
│ │
│ ─────────────────────────────────────────────────────────── │
│ [取消] [保存修改] │
│ │
└──────────────────────────────────────────────────────────────────┘
注意:驻留指令主要通过 Flutter App 对话 创建(更自然的交互方式)。 Web Admin 侧重于管理、监控和微调已有的指令,提供更精确的配置编辑能力。
5.4 服务器管理 — 添加/编辑/凭证/跳板机
服务器列表页
┌──────────────────────────────────────────────────────────────────┐
│ IT0 Admin > Servers [+ 添加服务器] │
├──────────────────────────────────────────────────────────────────┤
│ │
│ 搜索: [________________] 环境: [全部 ▼] 角色: [全部 ▼] │
│ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ 名称 │ IP/主机 │环境 │角色 │网络 │跳板 │状态 │ │
│ ├──────────┼──────────────┼─────┼──────┼──────┼─────┼──────┤ │
│ │ jump-01 │ 203.0.113.10 │prod │gateway│公网 │ — │● 在线│ │
│ │ prod-1 │ 10.0.1.11 │prod │web │内网 │jump-01│● 在线│ │
│ │ prod-2 │ 10.0.1.12 │prod │web │内网 │jump-01│● 在线│ │
│ │ prod-db │ 10.0.1.20 │prod │db │内网 │jump-01│● 在线│ │
│ │ staging-1│ 10.0.2.11 │stg │web │内网 │jump-01│● 在线│ │
│ │ dev-1 │ 192.168.1.100│dev │web │公网 │ — │● 在线│ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
│ [批量测试连接] [导出 SSH Config] │
└──────────────────────────────────────────────────────────────────┘
添加/编辑服务器表单
┌──────────────────────────────────────────────────────────────────┐
│ IT0 Admin > Servers > 添加服务器 │
├──────────────────────────────────────────────────────────────────┤
│ │
│ ── 基本信息 ──────────────────────────────────────────── │
│ │
│ 服务器名称: [prod-web-03 ] │
│ 描述: [生产环境 Web 服务器 #3 ] │
│ 环境: ○ dev ○ staging ● production │
│ 角色: [Web Server ▼] │
│ 所属集群: [main-cluster ▼] (可选) │
│ 标签: [region:us-east] [tier:1] [+ 添加] │
│ │
│ ── SSH 连接配置 ──────────────────────────────────────── │
│ │
│ 主机地址: [10.0.1.13 ] │
│ SSH 端口: [22 ] │
│ SSH 用户名: [ops-agent ] │
│ │
│ ── 认证方式 ──────────────────────────────────────────── │
│ │
│ ● SSH 密钥 ○ 密码 │
│ │
│ ┌─ SSH 密钥认证 ──────────────────────────────────────────┐ │
│ │ │ │
│ │ 选择已有凭证: [prod-ssh-key-ed25519 ▼] │ │
│ │ 指纹: SHA256:abc123... 类型: ed25519 │ │
│ │ │ │
│ │ 或上传新密钥: │ │
│ │ ┌─────────────────────────────────────────────────┐ │ │
│ │ │ 将私钥文件拖拽到此处,或 [点击上传] │ │ │
│ │ │ 支持: id_rsa, id_ed25519, id_ecdsa │ │ │
│ │ └─────────────────────────────────────────────────┘ │ │
│ │ 密钥名称: [prod-key-03 ] │ │
│ │ 密钥密码: [●●●●●●●● ] (如果密钥有 passphrase) │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ┌─ 密码认证(不推荐,仅用于遗留系统)─────────────────────┐ │
│ │ │ │
│ │ 选择已有凭证: [选择... ▼] │ │
│ │ 或输入新密码: [●●●●●●●● ] │ │
│ │ 凭证名称: [ ] │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ── 网络拓扑 ──────────────────────────────────────────── │
│ │
│ 网络类型: ○ 公网(可直接连接) │
│ ● 内网(需要跳板机) │
│ │
│ ┌─ 跳板机配置(SSH ProxyJump)────────────────────────────┐ │
│ │ │ │
│ │ 跳板机: [jump-01 (203.0.113.10) ▼] │ │
│ │ 已配置的跳板机/网关服务器列表 │ │
│ │ │ │
│ │ 连接链路预览: │ │
│ │ ┌─────────────────────────────────────────────────┐ │ │
│ │ │ 你的管理服务器 │ │ │
│ │ │ ↓ SSH (203.0.113.10:22, user: jump-user) │ │ │
│ │ │ jump-01 (跳板机) │ │ │
│ │ │ ↓ SSH (10.0.1.13:22, user: ops-agent) │ │ │
│ │ │ prod-web-03 (目标服务器) ✓ │ │ │
│ │ └─────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ ⚠ 多级跳板: 如果跳板机本身也在内网,将自动递归构建 │ │
│ │ ProxyJump 链 (如 jump-01 → jump-02 → target) │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ── 高级 SSH 选项(可选)──────────────────────────────── │
│ │
│ [✓] StrictHostKeyChecking=no (首次连接不验证指纹) │
│ [ ] ServerAliveInterval=60 (保持连接) │
│ [ ] ServerAliveCountMax=3 │
│ 自定义选项: [ ] │
│ │
│ ── 连接测试 ──────────────────────────────────────────── │
│ │
│ [测试 SSH 连接] │
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ ✓ 跳板机 jump-01 连接成功 (延迟 23ms) │ │
│ │ ✓ 目标 prod-web-03 连接成功 (延迟 45ms) │ │
│ │ ✓ 用户 ops-agent 权限验证通过 │ │
│ │ ✓ 基本命令执行正常 (whoami, uname -a) │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │
│ [取消] [保存服务器] │
└──────────────────────────────────────────────────────────────────┘
凭证管理页(Security > Credentials 的补充说明)
┌──────────────────────────────────────────────────────────────────┐
│ IT0 Admin > Security > Credentials [+ 添加凭证] │
├──────────────────────────────────────────────────────────────────┤
│ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ 名称 │ 类型 │ 指纹/信息 │ 引用 │ 操作 │ │
│ ├─────────────────┼──────────┼────────────────┼──────┼──────┤ │
│ │ prod-ssh-key │ ed25519 │ SHA256:abc1... │ 4台 │[编辑]│ │
│ │ staging-ssh-key │ rsa-4096 │ SHA256:def4... │ 2台 │[编辑]│ │
│ │ dev-password │ 密码 │ ●●●●●●●● │ 1台 │[编辑]│ │
│ │ jump-key │ ed25519 │ SHA256:ghi7... │ 1台 │[编辑]│ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
│ ⚠ 凭证内容加密存储(AES-256-GCM),仅在 Agent 执行 SSH 时 │
│ 临时解密到内存,绝不写入日志或磁盘。 │
│ ⚠ 「引用」列显示使用该凭证的服务器数量,删除前需先解除关联。 │
│ │
└──────────────────────────────────────────────────────────────────┘
SSH 连接拓扑可视化
┌──────────────────────────────────────────────────────────────────┐
│ IT0 Admin > Servers > Network Topology │
├──────────────────────────────────────────────────────────────────┤
│ │
│ ┌───────────┐ │
│ │ IT0 管理机 │ │
│ └─────┬─────┘ │
│ │ │
│ ┌─────▼─────┐ │
│ │ jump-01 │ 203.0.113.10 (公网) │
│ │ (跳板机) │ │
│ └──┬──┬──┬──┘ │
│ ┌─────┘ │ └─────┐ │
│ ┌─────▼───┐┌───▼────┐┌──▼──────┐ │
│ │ prod-1 ││ prod-2 ││ prod-db │ 10.0.1.x (内网) │
│ │ 10.0.1.11│ 10.0.1.12│ 10.0.1.20│ │
│ └─────────┘└────────┘└─────────┘ │
│ │
│ ┌─────────┐ │
│ │ dev-1 │ 192.168.1.100 (公网直连) │
│ └─────────┘ │
│ │
│ 图例: ● 在线 ○ 离线 ⚠ 告警 │
└──────────────────────────────────────────────────────────────────┘
5.5 安全配置 — 风险规则管理
┌──────────────────────────────────────────────────────────────────┐
│ IT0 Admin > Security > Risk Rules [+ 添加规则] │
├──────────────────────────────────────────────────────────────────┤
│ │
│ ── 命令风险分级规则(按优先级排序,可拖拽排序)────────── │
│ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ ⠿ │ Level 3 🟣│ rm\s+-rf\s+/ │ 禁止 │ [编辑] │ │
│ │ ⠿ │ Level 3 🟣│ DROP\s+(DATABASE|TABLE) │ 禁止 │ [编辑] │ │
│ │ ⠿ │ Level 3 🟣│ shutdown|reboot │ 禁止 │ [编辑] │ │
│ │ ⠿ │ Level 3 🟣│ mkfs|dd\s+if= │ 禁止 │ [编辑] │ │
│ │ ⠿ │ Level 2 🔴│ systemctl\s+(stop|restart) │ 审批│ [编辑] │ │
│ │ ⠿ │ Level 2 🔴│ kubectl\s+delete │ 审批 │ [编辑] │ │
│ │ ⠿ │ Level 2 🔴│ docker\s+(rm|stop) │ 审批 │ [编辑] │ │
│ │ ⠿ │ Level 1 🟠│ systemctl\s+restart │ 确认 │ [编辑] │ │
│ │ ⠿ │ Level 1 🟠│ find.*-delete │ 确认 │ [编辑] │ │
│ │ ⠿ │ Level 0 🟢│ *(默认) │ 自动 │ — │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
│ ── 规则测试 ──────────────────────────────────────────── │
│ 输入命令: [ssh prod-1 'systemctl restart nginx' ] │
│ 结果: Level 2 🔴 — 匹配规则 "systemctl\s+(stop|restart)" │
│ 动作: 需要审批 │
│ │
└──────────────────────────────────────────────────────────────────┘
5.6 审计日志页
┌──────────────────────────────────────────────────────────────────┐
│ IT0 Admin > Audit Logs │
├──────────────────────────────────────────────────────────────────┤
│ │
│ 时间范围: [2026-02-01] ~ [2026-02-08] │
│ 操作类型: [全部 ▼] 执行者: [全部 ▼] 搜索: [___________] │
│ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ 时间 │ 操作类型 │ 执行者 │ 详情 │ │
│ ├────────────────┼───────────────┼──────────┼──────────────┤ │
│ │ 02-08 15:23:01 │ command_exec │ agent │ df -h (prod-1)│ │
│ │ 02-08 15:22:45 │ task_created │ admin │ 全面巡检 │ │
│ │ 02-08 14:15:32 │ approval_ok │ admin │ restart nginx │ │
│ │ 02-08 14:15:10 │ approval_req │ agent │ restart nginx │ │
│ │ 02-08 13:00:00 │ config_change │ admin │ 引擎切换→CLI │ │
│ │ 02-08 12:01:15 │ alert_fired │ system │ disk > 90% │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
│ [← 上一页] 第 1 页 / 共 12 页 [下一页 →] │
│ │
│ [导出 CSV] │
└──────────────────────────────────────────────────────────────────┘
5.7 会话回放页
┌──────────────────────────────────────────────────────────────────┐
│ IT0 Admin > Sessions > Session #a3f8c2 │
├──────────────────────────────────────────────────────────────────┤
│ │
│ 任务: 全面巡检 引擎: Claude Code CLI 时间: 2026-02-08 15:22│
│ 状态: ✓ 完成 耗时: 3m 42s 命令数: 12 │
│ │
│ ── 执行时间线 ───────────────────────────────────────── │
│ │
│ ●──○──○──○──○──○──○──○──○──○──○──● │
│ ↑ ↑ │
│ 开始 完成 │
│ │
│ ── 步骤详情 ─────────────────────────────────────────── │
│ │
│ [Step 1] 🧠 思考 │
│ "我需要检查所有服务器的 CPU、内存、磁盘状态..." │
│ │
│ [Step 2] 🔧 Bash: ssh prod-1 'top -bn1 | head -5' L0 🟢 │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ top - 15:22:35 up 45 days, 12:34, 2 users │ │
│ │ Tasks: 234 total, 1 running, 233 sleeping │ │
│ │ %Cpu(s): 45.2 us, 3.1 sy, 0.0 ni, 51.7 id │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ [Step 3] 🔧 Bash: ssh prod-1 'df -h' L0 🟢 │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Filesystem Size Used Avail Use% Mounted on │ │
│ │ /dev/sda1 100G 71G 29G 71% / │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ [Step 4] 🧠 思考 │
│ "prod-1 状态正常,继续检查 prod-2..." │
│ │
│ ... (可展开/折叠每一步) │
│ │
│ [Step 12] 📝 总结 │
│ "巡检完成。prod-2 磁盘使用率 92%,建议清理旧日志..." │
│ │
└──────────────────────────────────────────────────────────────────┘
5.8 通信配置页
┌──────────────────────────────────────────────────────────────────┐
│ IT0 Admin > Communication > Channels │
├──────────────────────────────────────────────────────────────────┤
│ │
│ ── 通信渠道配置 ──────────────────────────────────────── │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ 渠道 │ 状态 │ 配置 │ 操作 │ │
│ ├─────────────┼───────┼─────────────────────────┼─────────┤ │
│ │ App 推送 │ ✓ 启用│ FCM + WebSocket 自动启用 │ — │ │
│ │ 语音对话 │ ✓ 启用│ Pipecat (voice-service) │ [状态] │ │
│ │ 短信 (SMS) │ ✓ 启用│ Twilio +1-234-567-8900 │ [配置] │ │
│ │ 电话语音 │ ✓ 启用│ Pipecat 拨号 (via Twilio) │ [配置] │ │
│ │ 邮件 │ ✓ 启用│ smtp.gmail.com:587 │ [配置] │ │
│ │ Telegram │ ✓ 启用│ @IT0OpsBot │ [配置] │ │
│ │ 企业微信 │ ○ 禁用│ 未配置 │ [配置] │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
│ ── 升级策略 ──────────────────────────────────────────── │
│ │
│ [默认策略 — 三层递进] │
│ Layer 1: App 推送 (FCM) → 立即 │
│ └─ 用户打开 App → 语音对话 (Pipecat voice-service) │
│ Layer 2: Pipecat 电话 (via Twilio) → 2 分钟无响应后 │
│ └─ Agent 接通即说话,直接语音对话(无 IVR 菜单) │
│ Layer 3: IM + 短信 → 5 分钟无响应后 │
│ Layer 4: 通知备用联系人 → 10 分钟无响应后 │
│ Layer 5: 邮件通知管理组 → 20 分钟无响应后 │
│ │
│ [编辑策略] [+ 新建策略] │
│ │
│ ── 联系人 ────────────────────────────────────────────── │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ 姓名 │ 手机 │ 邮箱 │ Telegram │ 角色 │ │
│ ├───────┼──────────────┼───────────────┼──────────┼──────┤ │
│ │ Admin │ 138-xxxx-xxxx│ admin@xx.com │ @admin │ 主要 │ │
│ │ Ops-1 │ 139-xxxx-xxxx│ ops1@xx.com │ — │ 备用 │ │
│ └────────────────────────────────────────────────────────┘ │
│ [+ 添加联系人] │
│ │
└──────────────────────────────────────────────────────────────────┘
5.9 多租户管理(超级管理员)
仅
super_admin角色可见此模块。普通租户管理员只能在自己的租户范围内操作。
5.8.1 顶栏租户选择器
┌──────────────────────────────────────────────────────────────────┐
│ IT0 Admin [🔍 搜索...] [🔔 3] [租户: Acme Corp ▾] [👤 Admin] │
│ ┌─────────────────────────┐ │
│ │ ★ Acme Corp (当前) │ │
│ │ Globex Inc │ │
│ │ Initech Ltd │ │
│ │ ────────────────────── │ │
│ │ [管理所有租户] │ │
│ └─────────────────────────┘ │
└──────────────────────────────────────────────────────────────────┘
行为:
- 切换租户后,所有页面数据刷新为目标租户的上下文
- API 请求自动注入
X-Tenant-Id请求头 - Zustand
tenant-store持久化当前选中的租户 ID(localStorage) - 页面 URL 不包含 tenantId(通过 header 传递,避免 URL 泄露)
5.8.2 租户列表页
┌──────────────────────────────────────────────────────────────────┐
│ IT0 Admin > Tenants │
├──────────────────────────────────────────────────────────────────┤
│ │
│ [+ 新建租户] [搜索租户...] 状态: [全部 ▾] 计划: [全部 ▾] │
│ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ 租户ID │ 名称 │ 计划 │ 服务器 │ 用户 │ 状态 │ │
│ ├────────┼─────────────┼─────────────┼────────┼──────┼────────┤ │
│ │ t001 │ Acme Corp │ Enterprise │ 15/50 │ 8/20 │ ✓ 活跃 │ │
│ │ t002 │ Globex Inc │ Pro │ 5/20 │ 3/10 │ ✓ 活跃 │ │
│ │ t003 │ Initech Ltd │ Free │ 2/5 │ 1/3 │ ✓ 活跃 │ │
│ │ t004 │ Umbrella │ Pro │ 0/20 │ 1/10 │ ⏸ 暂停 │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
│ 共 4 个租户 · 活跃 3 · 暂停 1 │
│ │
└──────────────────────────────────────────────────────────────────┘
5.8.3 租户详情/编辑页
┌──────────────────────────────────────────────────────────────────┐
│ IT0 Admin > Tenants > Acme Corp │
├──────────────────────────────────────────────────────────────────┤
│ │
│ ── 基本信息 ────────────────────────────────────────────────── │
│ │
│ 租户 ID: t001 (不可修改) │
│ 名称: [Acme Corp ] │
│ URL Slug: [acme-corp ] │
│ 状态: [● 活跃 ▾] (活跃 / 暂停 / 已删除) │
│ 所有者: admin@acme.com (user_id: xxx) │
│ │
│ ── 订阅计划与配额 ──────────────────────────────────────────── │
│ │
│ 当前计划: [Enterprise ▾] │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ 资源 │ 配额 │ 已用 │ 使用率 │ 操作 │ │
│ ├───────────────┼────────┼────────┼─────────────────┼───────┤ │
│ │ 服务器数量 │ 50 │ 15 │ ████░░░░ 30% │ [调整]│ │
│ │ Skills 数量 │ 100 │ 23 │ ██░░░░░░ 23% │ [调整]│ │
│ │ 用户数量 │ 20 │ 8 │ ████░░░░ 40% │ [调整]│ │
│ │ 日会话上限 │ 500 │ 127 │ ██░░░░░░ 25% │ [调整]│ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
│ ── 允许的引擎 ────────────────────────────────────────────── │
│ ☑ Claude Code CLI ☑ Claude API ☐ Custom Agent │
│ │
│ ── 危险操作区 ────────────────────────────────────────────── │
│ [⏸ 暂停租户] [🗑 删除租户] (需二次确认 + 输入租户名称) │
│ │
│ ─────────────────────────────────────────────────────────── │
│ [取消] [保存修改] │
│ │
└──────────────────────────────────────────────────────────────────┘
5.8.4 新建租户向导
┌──────────────────────────────────────────────────────────────────┐
│ IT0 Admin > Tenants > 新建租户 │
├──────────────────────────────────────────────────────────────────┤
│ │
│ Step 1 of 3: 基本信息 │
│ ───────────────────────────────────────── │
│ 租户名称: [ ] │
│ URL Slug: [ ] (自动生成) │
│ 管理员邮箱: [ ] │
│ 管理员密码: [ ] │
│ │
│ Step 2 of 3: 选择计划 │
│ ───────────────────────────────────────── │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ Free │ │ Pro │ │ Enterprise │ │
│ │ 5 服务器 │ │ 20 服务器 │ │ 50 服务器 │ │
│ │ 3 用户 │ │ 10 用户 │ │ 20 用户 │ │
│ │ 10 Skills │ │ 50 Skills │ │ 100 Skills │ │
│ │ [选择] │ │ [★ 选择] │ │ [选择] │ │
│ └────────────┘ └────────────┘ └────────────┘ │
│ │
│ Step 3 of 3: 确认 │
│ ───────────────────────────────────────── │
│ 系统将自动: │
│ • 创建租户 Schema (it0_t_xxx) │
│ • 运行数据库迁移 │
│ • 创建管理员账户 │
│ • 初始化默认 System Prompt 和 Skills │
│ │
│ [上一步] [创建租户] │
│ │
└──────────────────────────────────────────────────────────────────┘
5.8.5 Tenant Store(Zustand)
// stores/zustand/tenant-store.ts
interface TenantState {
currentTenantId: string | null;
tenants: TenantSummary[]; // 当前用户可访问的租户列表
isLoading: boolean;
// Actions
switchTenant: (tenantId: string) => void;
fetchTenants: () => Promise<void>;
}
export const useTenantStore = create<TenantState>()(
persist(
(set, get) => ({
currentTenantId: null,
tenants: [],
isLoading: false,
switchTenant: (tenantId) => {
set({ currentTenantId: tenantId });
// 触发全局数据刷新 — invalidate 所有 TanStack Query
queryClient.invalidateQueries();
},
fetchTenants: async () => {
set({ isLoading: true });
const tenants = await apiClient.get<TenantSummary[]>('/tenants');
set({ tenants, isLoading: false });
// 如果尚未选择租户,自动选中第一个
if (!get().currentTenantId && tenants.length > 0) {
set({ currentTenantId: tenants[0].id });
}
},
}),
{ name: 'it0-tenant' } // localStorage key
)
);
5.8.6 API Client 自动注入租户 Header
// core/api/api-client.ts — 拦截器追加
const apiClient = {
async fetch(url: string, options: RequestInit = {}) {
const tenantId = useTenantStore.getState().currentTenantId;
const headers = new Headers(options.headers);
// ★ 自动注入租户标识
if (tenantId) {
headers.set('X-Tenant-Id', tenantId);
}
// JWT token 注入(已有逻辑)
const token = getAuthToken();
if (token) {
headers.set('Authorization', `Bearer ${token}`);
}
return fetch(`${API_BASE_URL}${url}`, { ...options, headers });
},
};
6. BFF 层(Next.js API Routes)
Web Admin 不直接调用后端微服务,而是通过 Next.js API Routes 做 BFF 代理:
Flutter App → API Gateway → 微服务
Web Admin → Next.js BFF → API Gateway → 微服务
// app/api/proxy/[...path]/route.ts
import { NextRequest, NextResponse } from 'next/server';
/**
* 通用代理路由:
* /api/proxy/agent-service/config → http://api-gateway:8000/agent-service/config
* /api/proxy/ops-service/tasks → http://api-gateway:8000/ops-service/tasks
*
* 好处:
* 1. 前端不需要知道后端微服务的地址
* 2. 可以在 BFF 层做额外的数据聚合
* 3. SSR 页面可以在服务端直接调用
* 4. 统一处理认证 token 注入
*/
export async function GET(request: NextRequest, { params }: { params: { path: string[] } }) {
const backendUrl = `${process.env.API_GATEWAY_URL}/${params.path.join('/')}`;
const token = request.cookies.get('access_token')?.value;
const response = await fetch(backendUrl, {
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
});
const data = await response.json();
return NextResponse.json(data, { status: response.status });
}
export async function POST(request: NextRequest, { params }: { params: { path: string[] } }) {
const backendUrl = `${process.env.API_GATEWAY_URL}/${params.path.join('/')}`;
const token = request.cookies.get('access_token')?.value;
const body = await request.json();
const response = await fetch(backendUrl, {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
});
const data = await response.json();
return NextResponse.json(data, { status: response.status });
}
// PUT, DELETE 同理...
7. TanStack Query 数据请求层
// core/api/query-keys.ts
export const queryKeys = {
servers: {
all: ['servers'] as const,
list: (filters?: ServerFilters) => [...queryKeys.servers.all, 'list', filters] as const,
detail: (id: string) => [...queryKeys.servers.all, 'detail', id] as const,
metrics: (id: string) => [...queryKeys.servers.all, 'metrics', id] as const,
},
runbooks: {
all: ['runbooks'] as const,
list: (filters?: RunbookFilters) => [...queryKeys.runbooks.all, 'list', filters] as const,
detail: (id: string) => [...queryKeys.runbooks.all, 'detail', id] as const,
},
alertRules: {
all: ['alertRules'] as const,
list: () => [...queryKeys.alertRules.all, 'list'] as const,
},
audit: {
all: ['audit'] as const,
list: (filters?: AuditFilters) => [...queryKeys.audit.all, 'list', filters] as const,
},
sessions: {
all: ['sessions'] as const,
list: (filters?: SessionFilters) => [...queryKeys.sessions.all, 'list', filters] as const,
detail: (id: string) => [...queryKeys.sessions.all, 'detail', id] as const,
},
} as const;
// 使用示例 — Server 列表页
// hooks/use-servers.ts
export function useServers(filters?: ServerFilters) {
const apiClient = useApiClient();
return useQuery({
queryKey: queryKeys.servers.list(filters),
queryFn: () => apiClient.get<PaginatedResponse<Server>>('/api/proxy/inventory-service/servers', { params: filters }),
staleTime: 30 * 1000, // 30 秒内不重新请求
refetchInterval: 60 * 1000, // 每 60 秒自动刷新
});
}
export function useAddServer() {
const queryClient = useQueryClient();
const apiClient = useApiClient();
return useMutation({
mutationFn: (data: CreateServerDto) =>
apiClient.post('/api/proxy/inventory-service/servers', data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: queryKeys.servers.all });
},
});
}
8. WebSocket 实时通信
// core/api/websocket-client.ts
class WebSocketClient {
private ws: WebSocket | null = null;
private listeners = new Map<string, Set<(data: any) => void>>();
private reconnectAttempts = 0;
private maxReconnectAttempts = 10;
constructor(private baseUrl: string) {}
connect(token: string) {
this.ws = new WebSocket(`${this.baseUrl}/ws?token=${token}`);
this.ws.onmessage = (event) => {
const data = JSON.parse(event.data);
const type = data.type as string;
this.listeners.get(type)?.forEach((cb) => cb(data));
this.listeners.get('*')?.forEach((cb) => cb(data)); // 通配符
};
this.ws.onclose = () => this.handleReconnect(token);
this.ws.onerror = () => this.handleReconnect(token);
}
on(eventType: string, callback: (data: any) => void) {
if (!this.listeners.has(eventType)) {
this.listeners.set(eventType, new Set());
}
this.listeners.get(eventType)!.add(callback);
return () => this.listeners.get(eventType)?.delete(callback);
}
private handleReconnect(token: string) {
if (this.reconnectAttempts < this.maxReconnectAttempts) {
const delay = Math.pow(2, this.reconnectAttempts) * 1000;
setTimeout(() => {
this.reconnectAttempts++;
this.connect(token);
}, delay);
}
}
disconnect() {
this.ws?.close();
this.listeners.clear();
}
}
// React Hook 封装
export function useWebSocketEvent<T>(eventType: string, callback: (data: T) => void) {
const wsClient = useWebSocketClient(); // 从 Context 获取
useEffect(() => {
const unsubscribe = wsClient.on(eventType, callback);
return unsubscribe;
}, [wsClient, eventType, callback]);
}
9. 命令面板(Cmd+K)
类似 VS Code 的命令面板,提供全局快速操作:
// components/ui/command-palette.tsx
const commands: Command[] = [
// 导航
{ id: 'nav-dashboard', label: '转到仪表盘', action: () => router.push('/dashboard'), category: '导航' },
{ id: 'nav-servers', label: '转到服务器管理', action: () => router.push('/servers'), category: '导航' },
{ id: 'nav-runbooks', label: '转到 Runbook', action: () => router.push('/runbooks'), category: '导航' },
{ id: 'nav-audit', label: '转到审计日志', action: () => router.push('/audit'), category: '导航' },
// 快捷操作
{ id: 'action-inspect', label: '发起全面巡检', action: () => createQuickTask('inspection'), category: '操作' },
{ id: 'action-terminal', label: '打开终端', action: () => router.push('/terminal'), category: '操作' },
{ id: 'action-switch-engine', label: '切换 Agent 引擎', action: () => openEngineSelector(), category: '配置' },
// 搜索
{ id: 'search-server', label: '搜索服务器...', action: () => openServerSearch(), category: '搜索' },
{ id: 'search-audit', label: '搜索审计日志...', action: () => openAuditSearch(), category: '搜索' },
];
// 使用 shadcn/ui 的 Command 组件
// Cmd+K (Mac) / Ctrl+K (Windows) 触发
10. 键盘快捷键
// hooks/use-keyboard-shortcut.ts
export const globalShortcuts: KeyboardShortcut[] = [
{ key: 'k', meta: true, action: 'openCommandPalette', description: '命令面板' },
{ key: 'b', meta: true, action: 'toggleSidebar', description: '切换侧边栏' },
{ key: '1', meta: true, action: 'navDashboard', description: '仪表盘' },
{ key: '2', meta: true, action: 'navServers', description: '服务器' },
{ key: '3', meta: true, action: 'navRunbooks', description: 'Runbook' },
{ key: '4', meta: true, action: 'navAudit', description: '审计日志' },
{ key: '`', meta: true, action: 'openTerminal', description: '终端' },
{ key: 's', meta: true, action: 'save', description: '保存当前编辑' },
{ key: 'z', meta: true, action: 'undo', description: '撤销' },
{ key: 'z', meta: true, shift: true, action: 'redo', description: '重做' },
];
11. Provider 配置(根布局)
// app/layout.tsx
import { ReduxProvider } from '@/stores/redux/provider';
import { QueryProvider } from '@/core/api/query-provider';
import { AuthProvider } from '@/core/auth/auth-provider';
import { WebSocketProvider } from '@/core/api/websocket-provider';
import { ThemeProvider } from '@/core/theme/theme-provider';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="zh-CN" suppressHydrationWarning>
<body>
<ThemeProvider>
<AuthProvider>
<ReduxProvider>
<QueryProvider>
<WebSocketProvider>
{children}
</WebSocketProvider>
</QueryProvider>
</ReduxProvider>
</AuthProvider>
</ThemeProvider>
</body>
</html>
);
}
// stores/redux/provider.tsx
'use client';
import { Provider } from 'react-redux';
import { createStore } from './store';
import { useRepositories } from '@/infrastructure/di';
export function ReduxProvider({ children }: { children: React.ReactNode }) {
const repositories = useRepositories();
const store = useMemo(() => createStore(repositories), [repositories]);
return <Provider store={store}>{children}</Provider>;
}
12. 侧边栏导航结构
// components/layout/sidebar.tsx
const navigationItems: NavItem[] = [
{
label: '仪表盘',
icon: LayoutDashboard,
href: '/dashboard',
},
{
label: 'Agent 配置',
icon: Bot,
children: [
{ label: '引擎管理', href: '/agent-config', icon: Cpu },
{ label: 'Skills 管理', href: '/agent-config/skills', icon: Zap },
{ label: 'System Prompt', href: '/agent-config/prompts', icon: FileText },
{ label: 'Hook 脚本', href: '/agent-config/hooks', icon: Webhook },
],
},
{
label: 'Runbook',
icon: BookOpen,
href: '/runbooks',
},
{
label: '驻留指令',
icon: CalendarClock, // lucide-react
href: '/standing-orders',
},
{
label: '服务器',
icon: Server,
children: [
{ label: '服务器列表', href: '/servers', icon: List },
{ label: '集群管理', href: '/servers/clusters', icon: Network },
],
},
{
label: '安全',
icon: Shield,
children: [
{ label: '风险规则', href: '/security/risk-rules', icon: AlertTriangle },
{ label: '凭证管理', href: '/security/credentials', icon: Key },
{ label: '权限管理', href: '/security/permissions', icon: Users },
],
},
{
label: '监控',
icon: Activity,
children: [
{ label: '告警规则', href: '/monitoring/alert-rules', icon: Bell },
{ label: '健康检查', href: '/monitoring/health-checks', icon: HeartPulse },
{ label: '指标面板', href: '/monitoring/metrics', icon: BarChart },
],
},
{
label: '通信',
icon: MessageSquare,
children: [
{ label: '渠道配置', href: '/communication/channels', icon: Radio },
{ label: '联系人', href: '/communication/contacts', icon: UserCircle },
{ label: '升级策略', href: '/communication/escalation', icon: ArrowUpCircle },
],
},
{
label: '会话历史',
icon: History,
href: '/sessions',
},
{
label: '审计日志',
icon: ScrollText,
href: '/audit',
},
{
label: '终端',
icon: Terminal,
href: '/terminal',
},
// ★ 仅超级管理员可见
{
label: '租户管理',
icon: Building2,
href: '/tenants',
requiredRole: 'super_admin', // 路由守卫检查
},
];
13. 开发规范
13.1 文件命名
| 类型 | 规范 | 示例 |
|---|---|---|
| 组件文件 | kebab-case | server-status-badge.tsx |
| 页面文件 | page.tsx (Next.js 约定) |
app/(admin)/servers/page.tsx |
| Store 文件 | kebab-case + 后缀 | agent-config.slice.ts, ui-store.ts |
| Hook 文件 | use- 前缀 |
use-servers.ts |
| 实体 | kebab-case | agent-engine.ts |
| 工具函数 | kebab-case | date.ts, format.ts |
13.2 组件规范
// 标准组件模板
interface Props {
// 明确 props 类型
}
export function ComponentName({ prop1, prop2 }: Props) {
// Hooks 在顶部
// 事件处理函数
// 渲染
return (/* JSX */);
}
- Server Components 优先:数据密集型列表页、详情页用 RSC
- Client Components:带交互的表单、实时更新、状态管理相关
- 文件顶部
'use client'声明客户端组件
13.3 Zustand vs Redux 判断清单
| 问题 | 是 → Redux Toolkit | 否 → Zustand |
|---|---|---|
| 状态变更需要审计追踪? | ✓ | |
| 需要撤销/重做? | ✓ | |
| 多个组件需要协调复杂的状态转换? | ✓ | |
| 状态涉及异步工作流(多步骤)? | ✓ | |
| 需要 Redux DevTools 调试? | ✓ | |
| 只是简单的 UI 开关/计数器? | ✓ | |
| 状态只在单个组件树内使用? | ✓ |
14. 环境变量
# .env.local
# API
NEXT_PUBLIC_APP_URL=http://localhost:3000
API_GATEWAY_URL=http://localhost:8000
# Auth
NEXTAUTH_SECRET=your-secret
NEXTAUTH_URL=http://localhost:3000
# WebSocket
NEXT_PUBLIC_WS_URL=ws://localhost:8000
15. 开发路线图
Phase 1: 基础骨架(Week 1-2)
- Next.js 项目初始化(App Router + Tailwind + shadcn/ui)
- Clean Architecture 目录结构搭建
- Zustand + Redux Toolkit 配置
- TanStack Query 配置
- 认证流程(登录页 + JWT)
- 布局框架(侧边栏 + 顶栏 + 面包屑)
- BFF 代理路由
Phase 2: Agent 配置(Week 3-4)
- Agent 引擎管理页
- System Prompt 编辑器(Monaco Editor)
- Hook 脚本管理
- 配置保存 + 审计中间件
Phase 3: 运维管理(Week 5-6)
- 服务器列表 + 详情 + 指标图表
- Runbook 列表 + 编辑器(含模板变量、测试)
- 仪表盘(摘要卡片 + 快捷操作)
Phase 4: 安全与监控(Week 7-8)
- 风险规则管理(拖拽排序 + 规则测试)
- 凭证管理(加密展示 + 过期提醒)
- RBAC 权限矩阵
- 告警规则配置
- 健康检查配置
Phase 5: 通信与审计(Week 9-10)
- 通信渠道配置
- 联系人管理
- 升级策略编辑器
- 审计日志(搜索 + 过滤 + 导出)
- 会话回放页面
Phase 6: 高级功能(Week 11-12)
- 命令面板(Cmd+K)
- Web 终端(xterm.js)
- 全局键盘快捷键
- 暗色/亮色主题切换
- 响应式布局优化
16. package.json 参考
{
"name": "it0-web-admin",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"test": "vitest",
"test:ui": "vitest --ui"
},
"dependencies": {
"next": "^14.2.0",
"react": "^18.3.0",
"react-dom": "^18.3.0",
"@reduxjs/toolkit": "^2.2.0",
"react-redux": "^9.1.0",
"zustand": "^4.5.0",
"@tanstack/react-query": "^5.50.0",
"@tanstack/react-table": "^8.19.0",
"react-hook-form": "^7.52.0",
"zod": "^3.23.0",
"@hookform/resolvers": "^3.9.0",
"tailwindcss": "^3.4.0",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.0",
"tailwind-merge": "^2.4.0",
"lucide-react": "^0.400.0",
"@monaco-editor/react": "^4.6.0",
"recharts": "^2.12.0",
"@xterm/xterm": "^5.5.0",
"@xterm/addon-fit": "^0.10.0",
"@xterm/addon-web-links": "^0.11.0",
"next-auth": "^5.0.0",
"cmdk": "^1.0.0",
"sonner": "^1.5.0",
"@dnd-kit/core": "^6.1.0",
"@dnd-kit/sortable": "^8.0.0",
"date-fns": "^3.6.0"
},
"devDependencies": {
"typescript": "^5.5.0",
"@types/react": "^18.3.0",
"@types/react-dom": "^18.3.0",
"vitest": "^2.0.0",
"@testing-library/react": "^16.0.0",
"@testing-library/jest-dom": "^6.4.0",
"eslint": "^9.0.0",
"eslint-config-next": "^14.2.0",
"prettier": "^3.3.0",
"prettier-plugin-tailwindcss": "^0.6.0"
}
}