提交 9c733eba authored 作者: 陈泽健's avatar 陈泽健

feat(platform): 平台化改造 + 用户密码更新

- 新增平台首页路由 /(platform Blueprint),平台名称改为"运行维护平台"
- 新增 modules.py 声明式模块清单 + get_modules(role) 按角色过滤
- 新增 platform.html 平台首页模板(卡片网格 + 响应式布局)
- 原 / 路由从 auth.py 迁移至 platform.py,troubleshoot 独立为 /troubleshoot
- index.html 加"返回首页"导航链接
- test_routes_auth.py TestIndex 适配平台首页断言
- users.json 更新 admin/user 密码哈希
- 新增 ARCHITECTURE.md 技术架构文档
- deploy 注释更新
Co-Authored-By: 's avatarClaude <noreply@anthropic.com>
上级 b4fad0c1
此差异已折叠。
# PRD_计划执行_平台化改造与模块切换
## 1. 项目概述
### 1.1 背景
当前站点是单功能"问题排查 AI 助手",访问 `/` 直接进入排查页面。需升级为多模块平台:新增平台首页展示模块卡片,问题排查助手降级为其中一个模块入口(`/troubleshoot`),后续可按需接入其他维护模块。
### 1.2 目标
| 目标编号 | 描述 | 优先级 |
|---------|------|--------|
| 1 | 模块注册机制 `modules.py` | 🟠 高 |
| 2 | 平台首页 `/`(模块卡片) | 🟠 高 |
| 3 | 排查助手路由迁移 `/``/troubleshoot` | 🟠 高 |
| 4 | 排查助手页加返回首页导航 | 🟡 中 |
### 1.3 开发周期
预估 5.5 小时(约 1 个工作日)。
---
## 2. 技术方案
### 2.1 模块注册机制(`utils/modules.py`,新增)
集中声明模块清单,提供 `get_modules(role=None)` 查询函数:
```python
"""modules.py — 平台模块注册
集中声明平台所有可用模块,供首页渲染和路由使用。
新增模块只需在此文件追加声明 + 注册 Blueprint,不改首页代码。
"""
from flask import session
# ============================================================
# 模块清单(声明式)
# ============================================================
MODULES = [
{
"id": "troubleshoot",
"name": "问题排查助手",
"icon": "🔍",
"description": "基于历史知识库的 AI 问题排查,357 条记录",
"url": "/troubleshoot",
"enabled": True,
"roles": [], # 空 = 所有登录用户可访问
"sort": 1,
},
{
"id": "service-monitor",
"name": "服务监控",
"icon": "📊",
"description": "服务器与服务运行状态监控、告警通知",
"url": "/service-monitor",
"enabled": False, # 预留,未上线
"roles": ["admin"],
"sort": 2,
},
# 后续模块在此追加
]
def get_modules(role=None):
"""获取可用模块列表。
参数:
role: 用户角色(None=不过滤,返回全部启用模块)
返回:
按 sort 排序的模块列表
"""
result = []
for m in MODULES:
if not m.get("enabled", True):
continue
if role is not None and m.get("roles") and role not in m["roles"]:
continue
result.append(m)
result.sort(key=lambda x: x.get("sort", 99))
return result
```
**关键约束**
- 模块声明集中在此文件,不散落各处
- `roles` 为空表示所有登录用户可访问(不限制)
- `enabled: False` 的模块不展示(预留但未上线)
### 2.2 平台首页路由(`routes/platform.py`,新增)
```python
"""platform.py — 平台首页路由"""
from flask import Blueprint, render_template, session, redirect, url_for
from decorators import page_login_required
from utils.modules import get_modules
bp = Blueprint('platform', __name__)
@bp.route('/')
def index():
"""平台首页 — 模块列表"""
user = session.get('user')
if not user:
return redirect(url_for('auth.login'))
role = user.get('role', '')
modules = get_modules(role=role)
return render_template('platform.html',
modules=modules,
user=user,
platform_name="运维辅助平台",
)
```
### 2.3 平台首页模板(`templates/platform.html`,新增)
独立模板,风格与现有 `index.html` / `login.html` 一致(渐变背景 + 卡片布局):
```html
<!-- platform.html -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5">
<title>{{ platform_name }}</title>
<style>
/* 复用 login/index 的设计系统变量 + 渐变背景 */
/* 模块卡片网格:桌面 3 列、平板 2 列、手机 1 列 */
/* 触控热区 ≥44px */
</style>
</head>
<body>
<!-- 顶部:平台标题 + 用户信息 + 退出 -->
<!-- 主体:模块卡片网格 -->
<!-- 每个卡片:图标 + 名称 + 描述 + "进入" 按钮 -->
<!-- 空状态:暂无可用模块 -->
</body>
</html>
```
### 2.4 排查助手路由迁移
#### 2.4.1 `routes/auth.py` 改动
`auth.py``index` 路由(`/` 重定向到 `/login` 或渲染 `index.html`)需移除或改为重定向到 `/troubleshoot`
实际上 `/` 现在由 `platform.py` 接管,`auth.py``index` 路由删除即可。
#### 2.4.2 `routes/troubleshoot.py` 改动
新增 `/troubleshoot` 页面路由(渲染 `index.html`):
```python
@bp.route('/troubleshoot')
def troubleshoot_page():
"""排查助手主页"""
return render_template('index.html')
```
#### 2.4.3 `templates/index.html` 改动
顶部用户信息栏增加"返回首页"链接:
```html
<a href="/" class="back-home">🏠 返回首页</a>
```
### 2.5 `server.py` 改动
注册 `platform` Blueprint:
```python
from routes.platform import bp as platform_bp
app.register_blueprint(platform_bp)
```
### 2.6 测试方案
| 模块 | 用例 | 覆盖点 |
|------|------|--------|
| `test_routes_auth.py` | 修改 | `/` 不再重定向到 `/login`(由 platform 接管) |
| `test_routes_troubleshoot.py` | 新增 | `GET /troubleshoot` 返回 200 |
| `test_routes_platform.py` | 新增 | `/` 未登录跳 `/login`;已登录返回模块卡片;角色过滤 |
| `test_modules.py` | 新增 | `get_modules()` 角色过滤 / enabled 过滤 / sort 排序 |
---
## 3. 实施计划
### 3.1 任务分解
| 序号 | 任务 | 预计时间 | 状态 | 依赖 |
|------|------|---------|------|------|
| 1 | `utils/modules.py` 模块注册 | 0.5h | 待开始 | — |
| 2 | `routes/platform.py` 平台首页路由 | 0.5h | 待开始 | 1 |
| 3 | `templates/platform.html` 平台首页模板 | 1.5h | 待开始 | 2 |
| 4 | 排查助手路由迁移 `/``/troubleshoot` | 0.5h | 待开始 | 2 |
| 5 | `templates/index.html` 加返回首页导航 | 0.5h | 待开始 | 4 |
| 6 | `server.py` 注册 platform Blueprint | 0.5h | 待开始 | 2 |
| 7 | 测试用例更新 + 新增 | 1h | 待开始 | 4, 5, 6 |
| 8 | 部署 + 验证 | 0.5h | 待开始 | 7 |
---
## 4. 测试验证
### 4.1 验证项
| 验收项 | 标准 | 实测 |
|--------|------|------|
| `/` 显示模块卡片 | 登录后看到"问题排查助手"卡片 | — |
| 卡片点击进入模块 | 跳转到 `/troubleshoot` | — |
| `/troubleshoot` 显示排查助手 | 原有功能全部正常 | — |
| 返回首页 | 排查助手页顶部有"返回首页"链接 | — |
| 角色过滤 | 普通用户/管理员看到各自模块 | — |
| 未登录 | `/``/login` | — |
| 移动端 | 首页卡片单列/多列自适应 | — |
| API 不变 | `/api/*` 全部正常 | — |
### 4.2 回归测试
- [ ] 145 用例全绿(路由变更需同步改测试 fixture)
- [ ] 排查助手全流程手动验证
### 4.3 端到端验证
```bash
# 1. 首页验证
curl -s http://192.168.5.60:8088/ -o /dev/null -w "%{http_code}"
# 期望:302(未登录跳 /login)
# 2. 登录后访问首页
# 期望:看到模块卡片
# 3. 排查助手验证
curl -s http://192.168.5.60:8088/api/health
# 期望:ok, 357 记录
# 4. 浏览器验证
# 登录 → 首页 → 点击"问题排查助手" → 排查页面 → 返回首页
```
---
## 5. 执行记录
### 5.1 执行日志
| 日期 | 任务 | 执行人 | 结果 | 备注 |
|------|------|--------|------|------|
| — | — | — | — | 待执行 |
### 5.2 问题记录
| 日期 | 问题 | 解决方案 | 状态 |
|------|------|---------|------|
| — | — | — | — |
---
## 6. 注意事项
1. **路由冲突**`platform.py` 注册 `/``auth.py` 原有 `/` 路由必须删除,否则 Flask 报 `AssertionError: duplicate route`
2. **Blueprint 注册顺序**`platform` Blueprint 需在 `auth` 之前注册(或确保 `/` 只定义一次)
3. **前端 API 路径不变**:排查助手 JS 中所有 `/api/...` 是绝对路径,不受主页路径迁移影响
4. **测试 fixture 更新**`conftest.py``app` fixture 创建的 test client 需适配新路由(`/` 不再是排查助手主页)
5. **部署清单同步**:新增 `modules.py` / `platform.py` / `platform.html` 需加入 `upload_to_server.py`
---
## 7. 相关文档
- [PRD_需求文档_平台化改造与模块切换](PRD_需求文档_平台化改造与模块切换.md) — 本项目需求文档
- [PRD_需求文档_P2级功能增强](PRD_需求文档_P2级功能增强.md) — P2 背景
# PRD_需求文档_平台化改造与模块切换
## 基本信息
| 项目 | 内容 |
|------|------|
| 文档类型 | 需求文档 |
| 创建日期 | 2026-07-14 |
| 最后更新 | 2026-07-14 |
| 负责人 | 研发组(Claude 协助) |
| 优先级 | P2 🟠 |
| 状态 | 待开始 |
---
## 一、背景与目标
### 1.1 问题背景
当前「问题排查 AI 助手」是一个独立单功能站点:访问 `/` 直接进入排查助手主页面。平台后续将承载**多个维护类模块**(不仅限于问题知识助手),需要把现有站点升级为一个可扩展的**多模块平台**,问题排查助手降级为其中一个模块。
| 序号 | 现状 | 问题 |
|------|------|------|
| 1 | `/` 直接是排查助手主页 | 无法承载多模块,新模块无入口 |
| 2 | 站点定位是"问题排查助手" | 与"运维平台"定位不符,扩展性差 |
| 3 | 无模块切换机制 | 多模块并存时用户无法在模块间导航 |
### 1.2 修复目标
1. **新增平台首页** `/`:作为统一入口,以卡片/列表形式展示所有可用模块,点击进入对应模块
2. **问题排查助手降级为模块**:原 `/` 主页迁移到 `/troubleshoot`,首页改为模块列表
3. **预留服务监控模块**:在模块清单中声明"服务监控"模块(`enabled: false`,仅管理员可见),为后续接入预留入口
4. **建立模块注册机制**:通过配置声明模块清单,首页动态渲染,便于后续接入新模块
5. **共用服务与认证**:所有模块共用同一 Flask 服务、用户认证、缓存管理,各模块独立路由与页面
6. **保持现有功能零回归**:问题排查助手的搜索/AI 分析/提交/导出/缓存管理等全部功能不变,仅路径调整
### 1.3 范围说明
- 本次**只建框架**,先接入"问题排查助手"一个模块,**预留"服务监控"模块**`enabled: false`),后续按需接入
- 不涉及微服务拆分,全部在现有 Flask 服务内通过路由隔离实现
---
## 二、需求详情
### 2.1 模块注册机制
#### 2.1.1 需求规格
平台通过配置声明模块清单,首页动态渲染卡片。模块清单字段:
| 字段 | 说明 | 示例 |
|------|------|------|
| `id` | 模块唯一标识 | `troubleshoot` |
| `name` | 模块显示名 | `问题排查助手` |
| `icon` | 模块图标(emoji 或图标类) | `🔍` |
| `description` | 模块一句话描述 | `基于历史知识库的 AI 问题排查` |
| `url` | 模块入口路径 | `/troubleshoot` |
| `enabled` | 是否启用 | `true` |
| `roles` | 允许访问的角色(空=所有登录用户) | `[]` |
| `sort` | 排序权重 | `1` |
#### 2.1.2 实现方式
-`config.json` 新增 `modules` 数组,或新建 `utils/modules.py` 集中声明(推荐后者,便于扩展)
- 首页通过 `get_modules()` 读取清单并渲染卡片
#### 2.1.3 验收标准
- [ ] 模块清单可通过配置增删
- [ ] `enabled: false` 的模块不在首页展示
- [ ] 模块按 `sort` 排序展示
---
### 2.2 平台首页(新增)
#### 2.2.1 需求规格
| 项 | 规格 |
|----|------|
| 路径 | `/`(覆盖原排查助手主页) |
| 访问控制 | 登录后可见(未登录跳 `/login`) |
| 页面内容 | 顶部:平台标题 + 用户信息 + 退出;主体:模块卡片网格 |
| 卡片内容 | 图标 + 模块名 + 描述 + "进入"按钮 |
| 卡片点击 | 跳转到模块 `url` |
| 角色过滤 | 按 `modules[].roles` 过滤当前用户可见模块 |
| 响应式 | 移动端单列、桌面多列(沿用 P2-2 断点策略) |
| 空状态 | 无可用模块时显示"暂无可用模块"提示 |
#### 2.2.2 验收标准
- [ ] 访问 `/` 显示模块列表(非排查助手主页)
- [ ] 未登录跳转 `/login`
- [ ] 卡片点击进入对应模块
- [ ] 移动端响应式正常
- [ ] 普通用户/管理员看到各自有权限的模块
---
### 2.3 问题排查助手降级为模块
#### 2.3.1 路径调整
| 原路径 | 新路径 | 说明 |
|--------|--------|------|
| `/`(排查助手主页) | `/troubleshoot` | 排查助手主页迁移 |
| `/login` `/logout` | `/login` `/logout` | 不变(平台级认证) |
| `/api/*` | `/api/*` | 不变(API 路径保持) |
#### 2.3.2 需求规格
- 排查助手主页路由从 `/` 改为 `/troubleshoot`
- 排查助手页面顶部增加"返回平台首页"入口(链接到 `/`
- 排查助手所有功能(搜索/分析/提交/导出/缓存)**零变更**
- 前端 JS 中的 API 调用路径(相对路径 `/api/...`**无需改**
#### 2.3.3 验收标准
- [ ] `/troubleshoot` 正常显示排查助手主页
- [ ] `/` 显示平台首页(非排查助手)
- [ ] 排查助手全部功能正常(搜索/AI 分析/提交/导出/缓存)
- [ ] 排查助手页面有返回首页入口
---
### 2.4 平台导航
#### 2.4.1 需求规格
- 排查助手(及后续模块)页面顶部增加统一导航:平台名 + "返回首页"链接
- 首页顶部:平台标题 + 用户信息 + 退出(沿用现有用户信息栏样式)
#### 2.4.2 验收标准
- [ ] 各模块页可一键返回平台首页
- [ ] 平台标题一致
---
## 三、影响范围
### 3.1 涉及文件
| 文件 / 目录 | 变更类型 | 说明 |
|------------|---------|------|
| `web/utils/modules.py` | 新增 | 模块清单声明 + `get_modules()` |
| `web/routes/platform.py` | 新增 | 平台首页路由 `/` |
| `web/routes/auth.py` | 修改 | `/` 重定向改为 `/troubleshoot` 之外的处理(或交由 platform 路由) |
| `web/routes/troubleshoot.py` 或 server.py | 修改 | 排查助手主页路由 `/``/troubleshoot` |
| `web/templates/platform.html` | 新增 | 平台首页模板(模块卡片) |
| `web/templates/index.html` | 修改 | 顶部加"返回首页"导航 |
| `web/config.json` | 修改 | 可选:模块配置(若用配置驱动) |
| `deploy/upload_to_server.py` | 修改 | 上传清单补新文件 |
### 3.2 风险评估
| 风险项 | 等级 | 缓解措施 |
|--------|------|---------|
| 原 `/` 路径行为变化,外部书签失效 | 🟠 中 | `/troubleshoot` 与原 `/` 行为一致;老用户从首页点入即可 |
| 排查助手前端 JS 路径依赖 | 🟡 低 | API 用相对路径 `/api/...`,不随主页路径变化 |
| 认证流程受影响 | 🟡 低 | `/login` 不变,首页加 `@page_login_required` |
| 移动端首页布局 | 🟡 低 | 复用 P2-2 断点策略 |
---
## 四、非功能需求
| 项 | 要求 |
|----|------|
| 扩展性 | 新增模块只需在 `modules.py` 声明 + 注册路由,不改首页代码 |
| 兼容性 | 排查助手功能零回归;现有测试用例保持全绿 |
| 性能 | 首页轻量,无额外 API 调用(模块清单内存读取) |
| 安全 | 首页需登录;模块按角色过滤 |
| 可维护性 | 模块声明集中管理,不散落各处 |
---
## 五、时间估算
| 任务 | 工时 |
|------|------|
| 模块注册机制 `modules.py` | 0.5h |
| 平台首页路由 + 模板 | 2h |
| 排查助手路由迁移 `/``/troubleshoot` | 0.5h |
| 排查助手页加返回首页导航 | 0.5h |
| 移动端适配首页 | 1h |
| 测试 + 部署 | 1h |
| **合计** | **5.5h** |
---
## 六、验收清单
### 模块注册
- [ ] `modules.py` 声明模块清单
- [ ] `get_modules()` 支持角色过滤
### 平台首页
- [ ] `/` 显示模块卡片列表
- [ ] 未登录跳 `/login`
- [ ] 卡片点击进入模块
- [ ] 移动端响应式
### 排查助手降级
- [ ] `/troubleshoot` 显示排查助手主页
- [ ] 排查助手全部功能正常
- [ ] 页面有返回首页入口
### 测试与部署
- [ ] 现有 145 用例全绿(路由变更需同步改测试)
- [ ] 部署到 5.60 验证
---
## 维护记录
| 日期 | 更新内容 | 更新人 |
|------|---------|--------|
| 2026-07-14 | 初版创建 | 研发组 |
...@@ -47,10 +47,10 @@ DEPLOY_FILES_TO_UPLOAD = [ ...@@ -47,10 +47,10 @@ DEPLOY_FILES_TO_UPLOAD = [
# 需要上传的目录(相对 LOCAL_BASE → 远程 web 目录下同名子目录) # 需要上传的目录(相对 LOCAL_BASE → 远程 web 目录下同名子目录)
# P1-1 新增 utils/ 模块;P1-3 新增 routes/ services/ 目录;P1 收尾新增 templates # P1-1 新增 utils/ 模块;P1-3 新增 routes/ services/ 目录;P1 收尾新增 templates
DIRS_TO_UPLOAD = [ DIRS_TO_UPLOAD = [
('utils', 'web/utils'), ('utils', 'web/utils'), # 含 modules.py / vector_builder.py 等
('routes', 'web/routes'), # P1-3:5 个 Blueprint ('routes', 'web/routes'), # 含 platform.py(平台首页)
('services', 'web/services'), # P1-3:ai_service / record_service ('services', 'web/services'), # P1-3:ai_service / record_service
('templates', 'web/templates'), # P1 收尾:errMsg 前端兼容适配 ('templates', 'web/templates'), # 含 platform.html
] ]
def upload_files(): def upload_files():
......
...@@ -92,7 +92,7 @@ class TestUserInfo: ...@@ -92,7 +92,7 @@ class TestUserInfo:
class TestIndex: class TestIndex:
"""首页""" """平台首页(/ 由 platform Blueprint 接管)"""
def test_index_not_logged_in_redirect(self, client): def test_index_not_logged_in_redirect(self, client):
"""未登录访问 / 重定向到 /login""" """未登录访问 / 重定向到 /login"""
...@@ -101,6 +101,8 @@ class TestIndex: ...@@ -101,6 +101,8 @@ class TestIndex:
assert r.headers["Location"].endswith("/login") assert r.headers["Location"].endswith("/login")
def test_index_logged_in(self, auth_client): def test_index_logged_in(self, auth_client):
"""已登录访问 / 返回主页""" """已登录访问 / 返回平台首页(模块卡片)"""
r = auth_client.get("/") r = auth_client.get("/")
assert r.status_code == 200 assert r.status_code == 200
# 平台首页应包含模块名
assert b"\xe9\x97\xae\xe9\xa2\x98" in r.data # "问题" UTF-8
...@@ -12,7 +12,7 @@ from flask import Blueprint, request, jsonify, render_template, session, redirec ...@@ -12,7 +12,7 @@ from flask import Blueprint, request, jsonify, render_template, session, redirec
import container import container
from auth import user_manager from auth import user_manager
from decorators import login_required, page_login_required from decorators import login_required
from utils.audit import log_audit from utils.audit import log_audit
from utils.response import error_response from utils.response import error_response
from utils.error_codes import ErrorCodes from utils.error_codes import ErrorCodes
...@@ -110,13 +110,3 @@ def get_user_info(): ...@@ -110,13 +110,3 @@ def get_user_info():
'success': True, 'success': True,
'user': session.get('user') 'user': session.get('user')
}) })
@bp.route('/')
@page_login_required
def index():
"""首页"""
return render_template('index.html',
projects=container.get_search_engine().get_projects(),
system_types=container.get_config().get('system_types', []),
)
# -*- coding: utf-8 -*-
"""
platform.py — 平台首页路由
提供平台级首页(模块列表),作为统一入口。
各模块独立 Blueprint,首页通过 modules.py 动态渲染模块卡片。
"""
from flask import Blueprint, render_template, session, redirect, url_for
from utils.modules import get_modules
from utils.logger import get_logger
logger = get_logger(__name__)
bp = Blueprint('platform', __name__)
@bp.route('/')
def index():
"""平台首页 — 模块列表"""
user = session.get('user')
if not user:
return redirect(url_for('auth.login'))
role = user.get('role', '')
modules = get_modules(role=role)
return render_template('platform.html',
modules=modules,
user=user,
platform_name="运行维护平台",
)
...@@ -12,9 +12,10 @@ import json ...@@ -12,9 +12,10 @@ import json
import time import time
from datetime import datetime from datetime import datetime
from flask import Blueprint, request, jsonify, Response, session from flask import Blueprint, request, jsonify, Response, session, render_template
import container import container
from decorators import page_login_required
from services.ai_service import build_prompt, call_claude_api, call_claude_api_stream from services.ai_service import build_prompt, call_claude_api, call_claude_api_stream
from utils.audit import log_audit from utils.audit import log_audit
from utils.response import error_response from utils.response import error_response
...@@ -26,6 +27,16 @@ logger = get_logger(__name__) ...@@ -26,6 +27,16 @@ logger = get_logger(__name__)
bp = Blueprint('troubleshoot', __name__) bp = Blueprint('troubleshoot', __name__)
@bp.route('/troubleshoot')
@page_login_required
def troubleshoot_page():
"""排查助手主页"""
return render_template('index.html',
projects=container.get_search_engine().get_projects(),
system_types=container.get_config().get('system_types', []),
)
def format_matched_cases(matched_cases_data): def format_matched_cases(matched_cases_data):
"""格式化匹配案例数据为标准返回格式""" """格式化匹配案例数据为标准返回格式"""
if not matched_cases_data: if not matched_cases_data:
......
...@@ -83,11 +83,13 @@ def create_app(): ...@@ -83,11 +83,13 @@ def create_app():
app.config['PERMANENT_SESSION_LIFETIME'] = 86400 # 24小时 app.config['PERMANENT_SESSION_LIFETIME'] = 86400 # 24小时
# 注册 Blueprint(url_prefix 留空,保持 API 路径不变) # 注册 Blueprint(url_prefix 留空,保持 API 路径不变)
from routes.platform import bp as platform_bp
from routes.auth import bp as auth_bp from routes.auth import bp as auth_bp
from routes.troubleshoot import bp as troubleshoot_bp from routes.troubleshoot import bp as troubleshoot_bp
from routes.cache import bp as cache_bp from routes.cache import bp as cache_bp
from routes.export import bp as export_bp from routes.export import bp as export_bp
from routes.submit import bp as submit_bp from routes.submit import bp as submit_bp
app.register_blueprint(platform_bp)
app.register_blueprint(auth_bp) app.register_blueprint(auth_bp)
app.register_blueprint(troubleshoot_bp) app.register_blueprint(troubleshoot_bp)
app.register_blueprint(cache_bp) app.register_blueprint(cache_bp)
......
...@@ -511,7 +511,8 @@ ...@@ -511,7 +511,8 @@
<!-- 用户信息栏 --> <!-- 用户信息栏 -->
<div class="user-info-bar" style="background: #eff6ff; padding: 12px 32px; border-bottom: 1px solid #dbeafe; display: flex; justify-content: space-between; align-items: center;"> <div class="user-info-bar" style="background: #eff6ff; padding: 12px 32px; border-bottom: 1px solid #dbeafe; display: flex; justify-content: space-between; align-items: center;">
<div> <div style="display: flex; align-items: center; gap: 12px;">
<a href="/" style="text-decoration: none; color: #1e40af; font-size: 14px; padding: 4px 8px; border-radius: 6px; background: #dbeafe; font-weight: 500;">🏠 返回首页</a>
<span id="userDisplayName" style="font-weight: 600; color: #1e40af;">加载中...</span> <span id="userDisplayName" style="font-weight: 600; color: #1e40af;">加载中...</span>
</div> </div>
<button class="btn btn-secondary" onclick="logout()" style="padding: 8px 16px; font-size: 14px;"> <button class="btn btn-secondary" onclick="logout()" style="padding: 8px 16px; font-size: 14px;">
......
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5">
<title>{{ platform_name }}</title>
<style>
:root {
--primary: #2563eb;
--primary-hover: #1d4ed8;
--gray-50: #f9fafb;
--gray-100: #f3f4f6;
--gray-200: #e5e7eb;
--gray-500: #6b7280;
--gray-700: #374151;
--gray-900: #111827;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Microsoft YaHei", sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
flex-direction: column;
}
/* 顶部导航 */
.navbar {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
padding: 16px 24px;
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid rgba(255, 255, 255, 0.15);
}
.navbar-brand {
color: white;
font-size: 20px;
font-weight: 700;
text-decoration: none;
display: flex;
align-items: center;
gap: 8px;
}
.navbar-user {
color: white;
display: flex;
align-items: center;
gap: 16px;
font-size: 14px;
}
.btn-logout {
background: rgba(255, 255, 255, 0.15);
color: white;
border: 1px solid rgba(255, 255, 255, 0.3);
padding: 8px 16px;
border-radius: 8px;
cursor: pointer;
font-size: 14px;
text-decoration: none;
transition: background 0.2s;
}
.btn-logout:hover {
background: rgba(255, 255, 255, 0.25);
}
/* 主体 */
.container {
max-width: 960px;
margin: 0 auto;
padding: 40px 24px;
flex: 1;
}
.page-header {
text-align: center;
margin-bottom: 40px;
}
.page-header h1 {
color: white;
font-size: 28px;
font-weight: 700;
margin-bottom: 8px;
}
.page-header p {
color: rgba(255, 255, 255, 0.8);
font-size: 16px;
}
/* 模块卡片网格 */
.module-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 24px;
}
.module-card {
background: white;
border-radius: 16px;
padding: 32px 24px;
text-align: center;
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.1);
transition: transform 0.2s, box-shadow 0.2s;
cursor: pointer;
text-decoration: none;
display: flex;
flex-direction: column;
align-items: center;
gap: 16px;
}
.module-card:hover {
transform: translateY(-4px);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15);
}
.module-icon {
font-size: 48px;
line-height: 1;
}
.module-name {
font-size: 18px;
font-weight: 700;
color: var(--gray-900);
}
.module-desc {
font-size: 14px;
color: var(--gray-500);
line-height: 1.5;
}
.module-enter {
display: inline-flex;
align-items: center;
gap: 6px;
background: var(--primary);
color: white;
padding: 10px 24px;
border-radius: 8px;
font-size: 14px;
font-weight: 600;
margin-top: 8px;
transition: background 0.2s;
}
.module-card:hover .module-enter {
background: var(--primary-hover);
}
/* 空状态 */
.empty-state {
text-align: center;
padding: 60px 20px;
color: rgba(255, 255, 255, 0.8);
}
.empty-state .icon {
font-size: 64px;
margin-bottom: 16px;
}
.empty-state p {
font-size: 16px;
}
/* 页脚 */
.footer {
text-align: center;
padding: 20px;
color: rgba(255, 255, 255, 0.6);
font-size: 12px;
}
/* ===== 移动端响应式 ===== */
@media screen and (max-width: 1024px) {
.module-grid { grid-template-columns: repeat(2, 1fr); }
}
@media screen and (max-width: 768px) {
.container { padding: 24px 16px; }
.module-grid { grid-template-columns: 1fr; }
.page-header h1 { font-size: 22px; }
.module-card { padding: 24px 20px; }
.navbar { padding: 12px 16px; }
.navbar-brand { font-size: 16px; }
.btn-logout { min-height: 44px; font-size: 16px; }
}
</style>
</head>
<body>
<!-- 顶部导航 -->
<div class="navbar">
<a href="/" class="navbar-brand">🛠️ {{ platform_name }}</a>
<div class="navbar-user">
<span>{{ user.username }}({{ '管理员' if user.role == 'admin' else '用户' }})</span>
<button class="btn-logout" onclick="handleLogout()">🚪 退出</button>
</div>
</div>
<!-- 主体 -->
<div class="container">
<div class="page-header">
<h1>🛠️ {{ platform_name }}</h1>
<p>选择要使用的功能模块</p>
</div>
{% if modules %}
<div class="module-grid">
{% for m in modules %}
<a href="{{ m.url }}" class="module-card">
<div class="module-icon">{{ m.icon }}</div>
<div class="module-name">{{ m.name }}</div>
<div class="module-desc">{{ m.description }}</div>
<div class="module-enter">进入 →</div>
</a>
{% endfor %}
</div>
{% else %}
<div class="empty-state">
<div class="icon">📭</div>
<p>暂无可用模块</p>
</div>
{% endif %}
</div>
<div class="footer">
{{ platform_name }} · 运行维护工具集
</div>
<script>
async function handleLogout() {
try {
await fetch('/logout', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include'
});
} catch (e) {}
window.location.href = '/login';
}
</script>
</body>
</html>
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
{ {
"id": 1, "id": 1,
"username": "admin", "username": "admin",
"password_hash": "scrypt:32768:8:1$4pS80HZDH81TmN0w$4e2359f166b15cb24d2a13dce9d97fb558eb3d801b60d04c22c6f35122a3035a53396c42a35765f4dbe55c9349477798b42b2cbaa67f9fa18e7e51ce0d93697f", "password_hash": "scrypt:32768:8:1$evVCVFTTv2ZVvjto$10300880338aae565f2a1874ace20fd30470e6970132da309399fb7ccf6a1982732b715da4a569f168e8f517a8b07d267cccd16f09c9352c7abb61d21383a03f",
"role": "admin", "role": "admin",
"created_at": "2026-07-12T16:00:00", "created_at": "2026-07-12T16:00:00",
"last_login": "2026-07-12T16:06:00", "last_login": "2026-07-12T16:06:00",
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
{ {
"id": 2, "id": 2,
"username": "user", "username": "user",
"password_hash": "scrypt:32768:8:1$nus73sCDpqNJTD3X$fd9fcf0f0a6633a1e68606e3b3cac55ed250d5e961c33d421cc52458759ac6bb64bf4ce01810c2356be92f37536691faf687f3a42fcba36581182a52c14de6e1", "password_hash": "scrypt:32768:8:1$DeqSGwN59n15hR0S$26c4df80a9b164dbba7e5148542f490360e6c488fe38bea9db35019028792ed514295fc35fac528d95698cb2c654f3b5ade855332583d0b04745553dddcab1f0",
"role": "user", "role": "user",
"created_at": "2026-07-12T16:10:00", "created_at": "2026-07-12T16:10:00",
"last_login": null, "last_login": null,
......
# -*- coding: utf-8 -*-
"""
modules.py — 平台模块注册
集中声明平台所有可用模块,供首页渲染和路由使用。
新增模块只需在此文件追加声明 + 注册 Blueprint,不改首页代码。
用法:
from utils.modules import get_modules
modules = get_modules(role='admin')
"""
# ============================================================
# 模块清单(声明式)
# ============================================================
MODULES = [
{
"id": "troubleshoot",
"name": "问题排查助手",
"icon": "🔍",
"description": "基于历史知识库的 AI 问题排查,357 条记录",
"url": "/troubleshoot",
"enabled": True,
"roles": [], # 空 = 所有登录用户可访问
"sort": 1,
},
{
"id": "service-monitor",
"name": "服务监控",
"icon": "📊",
"description": "服务器与服务运行状态监控、告警通知",
"url": "/service-monitor",
"enabled": False, # 预留,未上线
"roles": ["admin"],
"sort": 2,
},
# 后续模块在此追加
]
def get_modules(role=None):
"""获取可用模块列表。
参数:
role: 用户角色(None=不过滤,返回全部启用模块)
返回:
按 sort 排序的模块列表
"""
result = []
for m in MODULES:
if not m.get("enabled", True):
continue
if role is not None and m.get("roles") and role not in m["roles"]:
continue
result.append(m)
result.sort(key=lambda x: x.get("sort", 99))
return result
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论