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

feat(platform): 新增服务管理/服务监测占位模块 + 导航栏改造

- 新增服务管理模块占位(service-manage Blueprint + 静态页面)
- 新增服务监测模块占位(service-monitor Blueprint + 静态页面)
- modules.py 声明 service-manage(管理员)+ service-monitor(所有用户)
- index.html 返回首页改为顶部导航栏 + 🏠 返回首页按钮
- deploy 上传清单补 users.json
- 新增离线部署方案 PRD 文档
Co-Authored-By: 's avatarClaude <noreply@anthropic.com>
上级 9c733eba
# PRD 计划执行文档 — 离线部署方案
> 版本:1.0 | 日期:2026-07-14 | 分支:troubleshoot-ai-assistant
---
## 执行步骤总览
| 步骤 | 内容 | 优先级 |
|------|------|--------|
| 1 | config.json 新增 offline_mode 配置 | P0 |
| 2 | container.py 加载 offline_mode 默认值 | P0 |
| 3 | ai_service.py 新增离线模式分支 | P0 |
| 4 | routes/troubleshoot.py 适配离线返回格式 | P0 |
| 5 | index.html 前端适配离线模式展示 | P0 |
| 6 | /api/health 暴露 offline_mode | P1 |
| 7 | 单元测试补充离线模式用例 | P1 |
| 8 | 离线部署文档 | P2 |
---
### 步骤 1:config.json 新增配置
**文件**`skill/code/web/config.json`
```json
{
"offline_mode": false,
...
}
```
---
### 步骤 2:container.py 加载默认值
**文件**`skill/code/web/container.py`
`load_config()` 中补充 `offline_mode` 默认值 `False`,与 `embedding_enabled` 一致。
---
### 步骤 3:ai_service.py 新增离线模式分支
**文件**`skill/code/web/services/ai_service.py`
新增函数:
```python
def build_offline_analysis(matched_cases, query):
"""离线模式:从匹配案例提取排查建议(不调 AI)"""
# 1. 提取 Top N 案例的解决方案字段
# 2. 拼接成结构化排查建议
# 3. 返回格式与在线模式 analysis 结构兼容
return {
"summary": "基于知识库匹配的排查建议",
"possible_causes": [...], # 从匹配案例提取
"steps": [...], # 从匹配案例提取
"offline": True,
}
```
**注意**:不修改 `call_claude_api` / `call_claude_api_stream` 签名。
---
### 步骤 4:routes/troubleshoot.py 适配离线
**文件**`skill/code/web/routes/troubleshoot.py`
`/api/troubleshoot``/api/analyze_stream` 中加分支:
```python
offline = container.get_config().get('offline_mode', False)
if offline:
# 离线模式:跳过 AI,直接格式化搜索结果
analysis = build_offline_analysis(matched_cases, query)
return success_response(data={
'analysis': analysis,
'cases': matched_cases,
'offline': True,
})
else:
# 在线模式:原有逻辑
...
```
---
### 步骤 5:index.html 前端适配
**文件**`skill/code/web/templates/index.html`
适配点:
1. 响应中若 `offline: true`
- 标题显示"📋 相关案例参考(离线模式)"
- 不显示流式输出动画,直接渲染结果
- 顶部加横幅:"当前为离线模式,结果基于知识库匹配"
2. 结果区域渲染 `analysis``cases`(结构兼容)
---
### 步骤 6:/api/health 暴露 offline_mode
**文件**`skill/code/web/routes/troubleshoot.py``health_check`
```python
'offline_mode': container.get_config().get('offline_mode', False),
```
---
### 步骤 7:单元测试
**文件**`skill/code/tests/test_routes_troubleshoot.py`
补充用例:
- `test_troubleshoot_offline_mode` — offline_mode=True 时返回 offline 标记,不调 AI
- `test_troubleshoot_online_mode` — offline_mode=False 时调 AI(原有用例)
---
### 步骤 8:离线部署文档
**文件**`ARCHITECTURE.md` 补充"离线部署"章节
内容:
- 离线环境前置条件
- config.json 配置说明
- 向量文件离线生成方法(在线生成后拷贝)
- 服务管理/服务监测离线部署说明
---
## 变更文件清单
| 文件 | 操作 | 说明 |
|------|------|------|
| `skill/code/web/config.json` | 修改 | 新增 offline_mode |
| `skill/code/web/container.py` | 修改 | 加载 offline_mode 默认值 |
| `skill/code/web/services/ai_service.py` | 修改 | 新增 build_offline_analysis |
| `skill/code/web/routes/troubleshoot.py` | 修改 | 离线模式分支 + health |
| `skill/code/web/templates/index.html` | 修改 | 离线模式前端展示 |
| `skill/code/tests/test_routes_troubleshoot.py` | 修改 | 补充离线用例 |
| `ARCHITECTURE.md` | 修改 | 离线部署章节 |
---
## 验证
1. `cd skill/code && python -m pytest -v` — 全绿
2. 在线模式验证:`offline_mode: false`,排查流程调 AI 正常
3. 离线模式验证:`offline_mode: true`,排查返回搜索结果 + offline 标记,无 AI 调用
4. `/api/health` 返回 `offline_mode` 字段
5. 部署到 5.60,分别验证两种模式
---
## 风险与注意
- **向量搜索**:离线环境无法调 Embedding API 生成查询向量。若已预生成 `搜索向量.json`,可在离线环境使用(查询向量仍需实时生成,离线会降级 TF-IDF)。建议离线环境 `embedding_enabled: false`
- **缓存一致性**:离线模式结果也可缓存,cache_manager 逻辑不变。
- **降级链路**:在线模式下 API 调用失败时,可考虑自动降级到离线模式(本期不做,配置开关即可)。
# PRD 计划执行文档 — 返回首页导航栏改造 + 新增服务管理/服务监测模块
> 版本:1.0 | 日期:2026-07-14 | 分支:troubleshoot-ai-assistant
---
## 执行步骤
### 步骤 1:修改 modules.py — 新增模块声明
**文件**`skill/code/web/utils/modules.py`
**操作**:在 `MODULES` 列表中追加两个模块声明
```python
{
"id": "service-manage",
"name": "服务管理",
"icon": "🛠️",
"description": "新统一平台服务授权、服务升级、服务信息管理",
"url": "/service-manage",
"enabled": True,
"roles": ["admin"],
"sort": 2,
},
{
"id": "service-monitor",
"name": "服务监测",
"icon": "📊",
"description": "通过 SSH 连接监测服务器服务运行状态、输出报告",
"url": "/service-monitor",
"enabled": True,
"roles": [],
"sort": 3,
},
```
**注意**:原 `service-monitor`(enabled:False, roles:["admin"], sort:2)替换为新声明。
---
### 步骤 2:新建服务管理占位路由
**新建文件**`skill/code/web/routes/service_manage.py`
- Blueprint: `service-manage`
- 路由: `@bp.route('/service-manage')`
- 装饰器: `@page_login_required`
- 渲染: `service_manage.html`,传入 `user=session.get('user', {})`
---
### 步骤 3:新建服务管理占位模板
**新建文件**`skill/code/web/templates/service_manage.html`
- 顶部导航栏:`🛠️ 运行维护平台` + `/ 服务管理` + 用户名 + 退出
- 主体:`🚧 服务管理模块开发中` + 计划功能列表(服务授权/服务升级/服务信息)+ 返回首页按钮
- 移动端响应式
---
### 步骤 4:新建服务监测占位路由
**新建文件**`skill/code/web/routes/service_monitor.py`
- Blueprint: `service-monitor`
- 路由: `@bp.route('/service-monitor')`
- 装饰器: `@page_login_required`
- 渲染: `service_monitor.html`,传入 `user=session.get('user', {})`
---
### 步骤 5:新建服务监测占位模板
**新建文件**`skill/code/web/templates/service_monitor.html`
- 顶部导航栏:`🛠️ 运行维护平台` + `/ 服务监测` + 用户名 + 退出
- 主体:`🚧 服务监测模块开发中` + 计划功能列表(SSH连接监测/服务状态监测/报告输出)+ 返回首页按钮
- 移动端响应式
---
### 步骤 6:改造 index.html 顶部导航栏
**文件**`skill/code/web/templates/index.html`
**操作**
1.`<style>` 中添加 `.navbar` / `.navbar-left` / `.navbar-brand` / `.navbar-module` / `.navbar-user` / `.navbar-logout` 样式
2. 在移动端 `@media` 中添加导航栏适配(面包屑隐藏、触控热区 ≥44px)
3.`<body>` 后、`<div class="container">` 前插入导航栏 HTML
4. 删除 card 内部的 `user-info-bar` div(返回首页 + 用户名 + 退出已移到导航栏)
5. 删除移动端中已无用的 `.user-info` 样式
**导航栏结构**
```html
<div class="navbar">
<div class="navbar-left">
<a href="/" class="navbar-brand">🛠️ 运行维护平台</a>
<span class="navbar-module">/ 问题排查助手</span>
</div>
<div class="navbar-user">
<span id="userDisplayName">加载中...</span>
<button class="navbar-logout" onclick="logout()">🚪 退出</button>
</div>
</div>
```
---
### 步骤 7:server.py 注册新 Blueprint
**文件**`skill/code/web/server.py`
**操作**:在 `create_app()` 中注册两个新 Blueprint
```python
from routes.service_manage import bp as service_manage_bp
from routes.service_monitor import bp as service_monitor_bp
app.register_blueprint(service_manage_bp)
app.register_blueprint(service_monitor_bp)
```
---
### 步骤 8:部署清单确认
**文件**`deploy/upload_to_server.py`
**无需修改**`routes/``templates/` 目录已在 `DIRS_TO_UPLOAD` 中,新文件自动包含。
---
### 步骤 9:测试验证
1. `cd skill/code && python -m pytest -v` — 145 用例全绿
2. Chrome DevTools 验证:
- `/` 平台首页显示 3 个模块卡片(问题排查助手 + 服务管理 + 服务监测)
- 点击"服务管理"进入占位页,顶部导航栏可返回首页
- 点击"服务监测"进入占位页,顶部导航栏可返回首页
- 问题排查助手顶部导航栏可返回首页
3. 部署到 5.60 + `verify_deployment.py`
---
## 变更文件清单
| 文件 | 操作 | 说明 |
|------|------|------|
| `skill/code/web/utils/modules.py` | 修改 | 新增 service-manage + service-monitor 声明 |
| `skill/code/web/routes/service_manage.py` | **新建** | 服务管理占位路由 |
| `skill/code/web/routes/service_monitor.py` | **新建** | 服务监测占位路由 |
| `skill/code/web/templates/service_manage.html` | **新建** | 服务管理占位页面 |
| `skill/code/web/templates/service_monitor.html` | **新建** | 服务监测占位页面 |
| `skill/code/web/templates/index.html` | 修改 | 返回首页改为顶部导航栏 |
| `skill/code/web/server.py` | 修改 | 注册新 Blueprint |
# PRD 需求文档 — 离线部署方案
> 版本:1.0 | 日期:2026-07-14 | 分支:troubleshoot-ai-assistant
---
## 1. 需求背景
运行维护平台部署到客户现场服务器时,通常处于内网/离线环境,无法访问外部 AI API(Claude)。当前问题排查助手依赖 Claude API 进行 AI 分析,在离线环境下不可用。
需要为平台提供离线部署能力,使三个核心模块在内网环境下均可正常使用。
---
## 2. 现状分析
| 模块 | 外部依赖 | 离线可用性 |
|------|----------|-----------|
| 问题排查助手 | Claude API(AI 分析)+ TF-IDF 搜索(本地) | ❌ AI 不可用,搜索可用 |
| 服务管理 | 无(占位) | ✅ |
| 服务监测 | SSH 连接(局域网) | ✅ |
| 平台首页 / 认证 | 无(Flask 本地) | ✅ |
**核心卡点**:问题排查助手的 AI 分析功能依赖外网 API。
---
## 3. 离线方案设计
### 3.1 问题排查助手 — 离线 Q&A 模式
**原理**:跳过 Claude API 调用,直接将 TF-IDF 搜索匹配结果作为排查建议返回。
**在线 vs 离线流程对比**
```
【在线模式】
用户提问 → TF-IDF 搜索匹配 → 匹配结果 + 提问 → Claude API → AI 分析报告
【离线模式】
用户提问 → TF-IDF 搜索匹配 → 匹配结果格式化 → 直接返回排查建议
```
**离线模式返回内容**
| 区域 | 在线模式 | 离线模式 |
|------|---------|---------|
| 问题概述 | AI 生成 | 从匹配结果中提取摘要 |
| 可能原因 | AI 生成 | 列出匹配案例的常见原因 |
| 排查步骤 | AI 生成 | 列出匹配案例的历史解决步骤 |
| 相关案例 | 搜索结果 | 搜索结果(与在线一致) |
**前端展示差异**
- 离线模式下标题显示"📋 相关案例参考"而非"🤖 AI 分析结果"
- 离线模式下不显示流式输出动画,直接渲染搜索结果
- 离线模式下加提示横幅:"当前为离线模式,结果基于知识库匹配,无 AI 分析"
### 3.2 配置开关
**文件**`config.json`
新增配置项:
```json
{
"offline_mode": false
}
```
- `offline_mode: false`(默认)— 在线模式,调 Claude API
- `offline_mode: true` — 离线模式,跳过 API,直接返回搜索结果
**读取方式**:通过 `container.get_config()` 读取,与现有 `embedding_enabled` 等配置一致。
### 3.3 API 行为变更
**`POST /api/troubleshoot`**
| 场景 | 在线模式 | 离线模式 |
|------|---------|---------|
| 搜索 | TF-IDF / 向量搜索 | TF-IDF 搜索(向量在离线环境也无法生成,降级 TF-IDF) |
| AI 分析 | 调 Claude API | 跳过,直接格式化搜索结果 |
| 返回格式 | `{analysis, cases}` | `{analysis: null, cases, offline: true}` |
| 耗时 | 20-60 秒 | <1 秒 |
**`POST /api/analyze_stream`**
离线模式下不调用流式 API,直接返回完整 JSON 响应(非 SSE)。
### 3.4 /api/health 响应
新增 `offline_mode` 字段:
```json
{
"status": "ok",
"version": "1.4.0",
"offline_mode": true,
...
}
```
---
## 4. 非功能需求
| 项目 | 要求 |
|------|------|
| 部署无感切换 | 只需改 config.json 即可切换在线/离线模式,无需改代码 |
| 搜索质量 | 离线模式下搜索逻辑不变,TF-IDF 完全本地运行 |
| 响应速度 | 离线模式下排查响应 <1 秒(无 API 调用) |
| 向量搜索 | 离线环境下无法调 Embedding API 生成向量,自动降级 TF-IDF |
| 前端适配 | 离线模式结果展示需清晰标注"离线模式" |
---
## 5. 约束
- 离线模式下不提供 AI 总结和步骤建议,仅返回知识库匹配结果
- 离线模式不影响现有在线模式的行为(配置开关隔离)
- 不修改 `search_engine.py` 公开接口
- 向量预计算文件(`搜索向量.json`)可在在线环境预生成后拷贝到离线环境使用
---
## 6. 后续扩展(本期不做)
- **本地小模型推理**:部署 Ollama + 小参数模型,在离线环境提供轻量 AI 分析
- **预缓存 AI 回答**:对高频问题预生成 AI 回答,离线时查缓存
- **知识库离线导入**:支持 U 盘/离线文件导入新的 Q&A 记录
# PRD 需求文档 — 返回首页导航栏改造 + 新增服务管理/服务监测模块
> 版本:1.0 | 日期:2026-07-14 | 分支:troubleshoot-ai-assistant
---
## 1. 需求背景
### 1.1 返回首页问题
当前问题排查助手模块(`/troubleshoot`)的"返回首页"链接嵌在用户信息栏内,样式为蓝色小文字按钮,不够显眼,用户反馈"无法返回主页"。
**目标**:将返回首页改为顶部导航栏形式,与平台首页(`/`)风格统一,确保跨模块导航清晰可见。
### 1.2 新增模块
平台首页当前仅有"问题排查助手"一个模块卡片,需要新增两个模块占位:
- **服务管理**:用于新统一平台的服务授权、服务升级、服务信息管理(本期仅占位)
- **服务监测**:用于通过 SSH 连接监测服务器服务运行状态、输出报告(本期仅占位)
---
## 2. 功能需求
### 2.1 顶部导航栏改造
| 需求项 | 说明 |
|--------|------|
| 导航栏位置 | 页面顶部,card 外部上方 |
| 导航栏风格 | 与平台首页统一:渐变背景 + 毛玻璃效果 + 白色文字 |
| 左侧内容 | `🛠️ 运行维护平台` 品牌链接(href="/")+ `/ 模块名` 面包屑 |
| 右侧内容 | 用户名(角色)+ 退出按钮 |
| 移动端适配 | 面包屑隐藏,退出按钮 ≥44px 触控热区 |
| 影响范围 | `index.html`(问题排查助手页面) |
### 2.2 服务管理模块(占位)
| 需求项 | 说明 |
|--------|------|
| 模块 ID | `service-manage` |
| 模块名称 | 服务管理 |
| 图标 | 🛠️ |
| 描述 | 新统一平台服务授权、服务升级、服务信息管理 |
| URL | `/service-manage` |
| 访问权限 | 仅管理员(`roles: ["admin"]`) |
| 页面内容 | 占位页面:顶部导航栏 + "🚧 服务管理模块开发中" + 计划功能列表 + 返回首页按钮 |
| 本期实现 | 仅占位卡片 + 静态页面,不实现具体功能 |
### 2.3 服务监测模块(占位)
| 需求项 | 说明 |
|--------|------|
| 模块 ID | `service-monitor` |
| 模块名称 | 服务监测 |
| 图标 | 📊 |
| 描述 | 通过 SSH 连接监测服务器服务运行状态、输出报告 |
| URL | `/service-monitor` |
| 访问权限 | 所有登录用户(`roles: []`) |
| 页面内容 | 占位页面:顶部导航栏 + "🚧 服务监测模块开发中" + 计划功能列表 + 返回首页按钮 |
| 本期实现 | 仅占位卡片 + 静态页面,不实现具体功能 |
---
## 3. 非功能需求
| 项目 | 要求 |
|------|------|
| 移动端适配 | 所有新增页面需响应式适配(≤768px 手机、≤1024px 平板) |
| 导航一致性 | 所有模块页面顶部导航栏风格统一 |
| 单元测试 | 现有 145 用例不受影响,全绿 |
| 部署 | 更新到 192.168.5.60:8088 |
---
## 4. 约束
- 服务管理/服务监测本期**仅占位**,不实现具体功能
- 不修改 `search_engine.py` / `safety_filter.py` / `cache_manager.py` 公开接口
- 新增模块遵循现有 routes/services/utils 三层架构
......@@ -36,6 +36,7 @@ FILES_TO_UPLOAD = [
('decorators.py', 'web/decorators.py'), # P1-3:url_for('login')→url_for('auth.login')
('container.py', 'web/container.py'), # P1-3:依赖容器
('search_engine.py', 'web/search_engine.py'), # P2-1:向量搜索 + TF-IDF 降级
('users.json', 'web/users.json'), # 用户数据(含密码哈希)
]
# 需要上传的部署文件(相对仓库根目录,非 LOCAL_BASE)
......
# -*- coding: utf-8 -*-
"""
service_manage.py — 服务管理模块路由
占位模块,后续实现服务授权、服务升级、服务信息管理功能。
"""
from flask import Blueprint, render_template, session
from decorators import page_login_required
from utils.logger import get_logger
logger = get_logger(__name__)
bp = Blueprint('service-manage', __name__)
@bp.route('/service-manage')
@page_login_required
def service_manage_page():
"""服务管理主页(占位)"""
user = session.get('user', {})
return render_template('service_manage.html', user=user)
# -*- coding: utf-8 -*-
"""
service_monitor.py — 服务监测模块路由
占位模块,后续实现 SSH 连接监测服务器服务状态、输出报告功能。
"""
from flask import Blueprint, render_template, session
from decorators import page_login_required
from utils.logger import get_logger
logger = get_logger(__name__)
bp = Blueprint('service-monitor', __name__)
@bp.route('/service-monitor')
@page_login_required
def service_monitor_page():
"""服务监测主页(占位)"""
user = session.get('user', {})
return render_template('service_monitor.html', user=user)
......@@ -86,12 +86,16 @@ def create_app():
from routes.platform import bp as platform_bp
from routes.auth import bp as auth_bp
from routes.troubleshoot import bp as troubleshoot_bp
from routes.service_manage import bp as service_manage_bp
from routes.service_monitor import bp as service_monitor_bp
from routes.cache import bp as cache_bp
from routes.export import bp as export_bp
from routes.submit import bp as submit_bp
app.register_blueprint(platform_bp)
app.register_blueprint(auth_bp)
app.register_blueprint(troubleshoot_bp)
app.register_blueprint(service_manage_bp)
app.register_blueprint(service_monitor_bp)
app.register_blueprint(cache_bp)
app.register_blueprint(export_bp)
app.register_blueprint(submit_bp)
......
......@@ -28,6 +28,89 @@
padding: 40px 20px;
}
/* 顶部导航栏 — 与平台首页统一风格 */
.navbar {
max-width: 900px;
margin: 0 auto 20px;
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
padding: 14px 24px;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: space-between;
border: 1px solid rgba(255, 255, 255, 0.15);
}
.navbar-left {
display: flex;
align-items: center;
gap: 16px;
}
.navbar-brand {
color: white;
font-size: 18px;
font-weight: 700;
text-decoration: none;
display: flex;
align-items: center;
gap: 8px;
}
.navbar-brand:hover {
opacity: 0.9;
}
.navbar-module {
color: rgba(255, 255, 255, 0.7);
font-size: 14px;
}
.navbar-home {
background: rgba(255, 255, 255, 0.2);
color: white;
border: 1px solid rgba(255, 255, 255, 0.35);
padding: 6px 14px;
border-radius: 8px;
font-size: 13px;
font-weight: 600;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 4px;
transition: background 0.2s;
white-space: nowrap;
}
.navbar-home:hover {
background: rgba(255, 255, 255, 0.3);
}
.navbar-user {
color: white;
display: flex;
align-items: center;
gap: 16px;
font-size: 14px;
}
.navbar-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;
}
.navbar-logout:hover {
background: rgba(255, 255, 255, 0.25);
}
.container {
max-width: 900px;
margin: 0 auto;
......@@ -458,6 +541,13 @@
.container { padding: 12px; }
.form-grid { grid-template-columns: 1fr; }
/* 导航栏适配 */
.navbar { margin: 0 auto 12px; padding: 12px 16px; border-radius: 8px; }
.navbar-brand { font-size: 16px; }
.navbar-module { display: none; }
.navbar-home { min-height: 44px; font-size: 14px; padding: 8px 14px; }
.navbar-logout { min-height: 44px; font-size: 16px; }
/* 表单控件触控热区 */
input, select, textarea, .btn {
min-height: 44px;
......@@ -486,12 +576,6 @@
.result-card {
padding: 12px;
}
/* 用户信息栏 */
.user-info {
flex-direction: column;
gap: 8px;
}
}
/* 小屏手机(≤375px)*/
......@@ -502,6 +586,19 @@
</style>
</head>
<body>
<!-- 顶部导航栏 — 与平台首页统一风格 -->
<div class="navbar">
<div class="navbar-left">
<a href="/" class="navbar-brand">🛠️ 运行维护平台</a>
<span class="navbar-module">/ 问题排查助手</span>
<a href="/" class="navbar-home">🏠 返回首页</a>
</div>
<div class="navbar-user">
<span id="userDisplayName">加载中...</span>
<button class="navbar-logout" onclick="logout()">🚪 退出</button>
</div>
</div>
<div class="container">
<div class="card">
<div class="card-header">
......@@ -509,17 +606,6 @@
<p>基于 357 条历史问题记录 · AI 智能分析 · 安全过滤</p>
</div>
<!-- 用户信息栏 -->
<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 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>
</div>
<button class="btn btn-secondary" onclick="logout()" style="padding: 8px 16px; font-size: 14px;">
🚪 退出
</button>
</div>
<div class="card-body">
<div class="alert alert-warning">
⚠️ 排查步骤均为只读操作,不会修改系统。如需执行重启/删除等危险操作,请联系研发组。
......
<!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>服务管理 - 运行维护平台</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: #f3f4f6;
min-height: 100vh;
display: flex;
flex-direction: column;
}
/* 顶部导航栏 — 与平台首页统一风格 */
.navbar {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 16px 24px;
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid rgba(255, 255, 255, 0.15);
}
.navbar-left {
display: flex;
align-items: center;
gap: 16px;
}
.navbar-brand {
color: white;
font-size: 18px;
font-weight: 700;
text-decoration: none;
display: flex;
align-items: center;
gap: 8px;
}
.navbar-brand:hover {
opacity: 0.9;
}
.navbar-module {
color: rgba(255, 255, 255, 0.7);
font-size: 14px;
}
.navbar-home {
background: rgba(255, 255, 255, 0.2);
color: white;
border: 1px solid rgba(255, 255, 255, 0.35);
padding: 6px 14px;
border-radius: 8px;
font-size: 13px;
font-weight: 600;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 4px;
transition: background 0.2s;
white-space: nowrap;
}
.navbar-home:hover {
background: rgba(255, 255, 255, 0.3);
}
.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;
transition: background 0.2s;
}
.btn-logout:hover {
background: rgba(255, 255, 255, 0.25);
}
/* 主体 */
.container {
max-width: 960px;
margin: 0 auto;
padding: 60px 24px;
flex: 1;
display: flex;
align-items: center;
justify-content: center;
}
.placeholder-card {
background: white;
border-radius: 16px;
padding: 60px 40px;
text-align: center;
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.08);
max-width: 480px;
width: 100%;
}
.placeholder-icon {
font-size: 64px;
margin-bottom: 24px;
}
.placeholder-title {
font-size: 24px;
font-weight: 700;
color: var(--gray-900);
margin-bottom: 12px;
}
.placeholder-desc {
font-size: 16px;
color: var(--gray-500);
line-height: 1.6;
margin-bottom: 24px;
}
.placeholder-features {
text-align: left;
background: var(--gray-50);
border-radius: 12px;
padding: 20px 24px;
margin-bottom: 24px;
}
.placeholder-features h3 {
font-size: 14px;
color: var(--gray-700);
margin-bottom: 12px;
font-weight: 600;
}
.placeholder-features ul {
list-style: none;
padding: 0;
}
.placeholder-features li {
font-size: 14px;
color: var(--gray-500);
padding: 6px 0;
padding-left: 20px;
position: relative;
}
.placeholder-features li::before {
content: "📋";
position: absolute;
left: 0;
}
.btn-back {
display: inline-flex;
align-items: center;
gap: 8px;
background: var(--primary);
color: white;
padding: 12px 24px;
border-radius: 8px;
font-size: 14px;
font-weight: 600;
text-decoration: none;
transition: background 0.2s;
}
.btn-back:hover {
background: var(--primary-hover);
}
/* 页脚 */
.footer {
text-align: center;
padding: 20px;
color: var(--gray-500);
font-size: 12px;
}
/* ===== 移动端响应式 ===== */
@media screen and (max-width: 768px) {
.container { padding: 24px 16px; }
.placeholder-card { padding: 40px 24px; }
.placeholder-title { font-size: 20px; }
.navbar { padding: 12px 16px; }
.navbar-brand { font-size: 16px; }
.navbar-module { display: none; }
.navbar-home { min-height: 44px; font-size: 14px; padding: 8px 14px; }
.btn-logout { min-height: 44px; font-size: 16px; }
}
</style>
</head>
<body>
<!-- 顶部导航栏 -->
<div class="navbar">
<div class="navbar-left">
<a href="/" class="navbar-brand">🛠️ 运行维护平台</a>
<span class="navbar-module">/ 服务管理</span>
<a href="/" class="navbar-home">🏠 返回首页</a>
</div>
<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="placeholder-card">
<div class="placeholder-icon">🚧</div>
<div class="placeholder-title">服务管理模块开发中</div>
<div class="placeholder-desc">该模块正在规划开发,敬请期待</div>
<div class="placeholder-features">
<h3>计划功能</h3>
<ul>
<li>服务授权管理</li>
<li>服务升级管理</li>
<li>服务信息管理</li>
</ul>
</div>
<a href="/" class="btn-back">🏠 返回首页</a>
</div>
</div>
<div class="footer">
运行维护平台 · 运行维护工具集
</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>
<!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>服务监测 - 运行维护平台</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: #f3f4f6;
min-height: 100vh;
display: flex;
flex-direction: column;
}
/* 顶部导航栏 — 与平台首页统一风格 */
.navbar {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 16px 24px;
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid rgba(255, 255, 255, 0.15);
}
.navbar-left {
display: flex;
align-items: center;
gap: 16px;
}
.navbar-brand {
color: white;
font-size: 18px;
font-weight: 700;
text-decoration: none;
display: flex;
align-items: center;
gap: 8px;
}
.navbar-brand:hover {
opacity: 0.9;
}
.navbar-module {
color: rgba(255, 255, 255, 0.7);
font-size: 14px;
}
.navbar-home {
background: rgba(255, 255, 255, 0.2);
color: white;
border: 1px solid rgba(255, 255, 255, 0.35);
padding: 6px 14px;
border-radius: 8px;
font-size: 13px;
font-weight: 600;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 4px;
transition: background 0.2s;
white-space: nowrap;
}
.navbar-home:hover {
background: rgba(255, 255, 255, 0.3);
}
.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;
transition: background 0.2s;
}
.btn-logout:hover {
background: rgba(255, 255, 255, 0.25);
}
/* 主体 */
.container {
max-width: 960px;
margin: 0 auto;
padding: 60px 24px;
flex: 1;
display: flex;
align-items: center;
justify-content: center;
}
.placeholder-card {
background: white;
border-radius: 16px;
padding: 60px 40px;
text-align: center;
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.08);
max-width: 480px;
width: 100%;
}
.placeholder-icon {
font-size: 64px;
margin-bottom: 24px;
}
.placeholder-title {
font-size: 24px;
font-weight: 700;
color: var(--gray-900);
margin-bottom: 12px;
}
.placeholder-desc {
font-size: 16px;
color: var(--gray-500);
line-height: 1.6;
margin-bottom: 24px;
}
.placeholder-features {
text-align: left;
background: var(--gray-50);
border-radius: 12px;
padding: 20px 24px;
margin-bottom: 24px;
}
.placeholder-features h3 {
font-size: 14px;
color: var(--gray-700);
margin-bottom: 12px;
font-weight: 600;
}
.placeholder-features ul {
list-style: none;
padding: 0;
}
.placeholder-features li {
font-size: 14px;
color: var(--gray-500);
padding: 6px 0;
padding-left: 20px;
position: relative;
}
.placeholder-features li::before {
content: "📋";
position: absolute;
left: 0;
}
.btn-back {
display: inline-flex;
align-items: center;
gap: 8px;
background: var(--primary);
color: white;
padding: 12px 24px;
border-radius: 8px;
font-size: 14px;
font-weight: 600;
text-decoration: none;
transition: background 0.2s;
}
.btn-back:hover {
background: var(--primary-hover);
}
/* 页脚 */
.footer {
text-align: center;
padding: 20px;
color: var(--gray-500);
font-size: 12px;
}
/* ===== 移动端响应式 ===== */
@media screen and (max-width: 768px) {
.container { padding: 24px 16px; }
.placeholder-card { padding: 40px 24px; }
.placeholder-title { font-size: 20px; }
.navbar { padding: 12px 16px; }
.navbar-brand { font-size: 16px; }
.navbar-module { display: none; }
.navbar-home { min-height: 44px; font-size: 14px; padding: 8px 14px; }
.btn-logout { min-height: 44px; font-size: 16px; }
}
</style>
</head>
<body>
<!-- 顶部导航栏 -->
<div class="navbar">
<div class="navbar-left">
<a href="/" class="navbar-brand">🛠️ 运行维护平台</a>
<span class="navbar-module">/ 服务监测</span>
<a href="/" class="navbar-home">🏠 返回首页</a>
</div>
<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="placeholder-card">
<div class="placeholder-icon">🚧</div>
<div class="placeholder-title">服务监测模块开发中</div>
<div class="placeholder-desc">该模块正在规划开发,敬请期待</div>
<div class="placeholder-features">
<h3>计划功能</h3>
<ul>
<li>SSH 连接监测服务器服务状态</li>
<li>服务运行状态实时监测</li>
<li>服务状态报告输出</li>
</ul>
</div>
<a href="/" class="btn-back">🏠 返回首页</a>
</div>
</div>
<div class="footer">
运行维护平台 · 运行维护工具集
</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>
......@@ -25,15 +25,25 @@ MODULES = [
"roles": [], # 空 = 所有登录用户可访问
"sort": 1,
},
{
"id": "service-manage",
"name": "服务管理",
"icon": "🛠️",
"description": "新统一平台服务授权、服务升级、服务信息管理",
"url": "/service-manage",
"enabled": True,
"roles": ["admin"],
"sort": 2,
},
{
"id": "service-monitor",
"name": "服务监",
"name": "服务监",
"icon": "📊",
"description": "服务器与服务运行状态监控、告警通知",
"description": "通过 SSH 连接监测服务器服务运行状态、输出报告",
"url": "/service-monitor",
"enabled": False, # 预留,未上线
"roles": ["admin"],
"sort": 2,
"enabled": True,
"roles": [], # 所有登录用户可访问
"sort": 3,
},
# 后续模块在此追加
]
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论