1985 lines
111 KiB
Markdown
1985 lines
111 KiB
Markdown
# 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 示例
|
||
|
||
```typescript
|
||
// 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 }),
|
||
}));
|
||
```
|
||
|
||
```typescript
|
||
// 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 示例
|
||
|
||
```typescript
|
||
// 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;
|
||
```
|
||
|
||
```typescript
|
||
// 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 配置
|
||
|
||
```typescript
|
||
// 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 审计中间件(配置变更自动追踪)
|
||
|
||
```typescript
|
||
// 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)
|
||
|
||
```typescript
|
||
// 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
|
||
|
||
```typescript
|
||
// 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 → 微服务
|
||
```
|
||
|
||
```typescript
|
||
// 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 数据请求层
|
||
|
||
```typescript
|
||
// 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;
|
||
```
|
||
|
||
```typescript
|
||
// 使用示例 — 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 实时通信
|
||
|
||
```typescript
|
||
// 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 的命令面板,提供全局快速操作:
|
||
|
||
```typescript
|
||
// 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. 键盘快捷键
|
||
|
||
```typescript
|
||
// 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 配置(根布局)
|
||
|
||
```typescript
|
||
// 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>
|
||
);
|
||
}
|
||
```
|
||
|
||
```typescript
|
||
// 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. 侧边栏导航结构
|
||
|
||
```typescript
|
||
// 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 组件规范
|
||
|
||
```typescript
|
||
// 标准组件模板
|
||
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
|
||
# .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 参考
|
||
|
||
```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"
|
||
}
|
||
}
|
||
```
|