docs(audit): 文件搜索与监测槽点击修复审计
补 Step3/Closure/DoD;选机对话框加载失败改 snackbar 提示。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
# 审计 — 文件管理服务器搜索 + 监测槽点击修复
|
||||
|
||||
**日期**:2026-06-12
|
||||
**Changelog**:
|
||||
- `docs/changelog/2026-06-11-files-server-search.md`(commit `6140d68`)
|
||||
- `docs/changelog/2026-06-11-watch-slot-click-fix.md`(commit `6b8965a`)
|
||||
|
||||
## 范围(文件清单)
|
||||
|
||||
| 文件 | 变更 |
|
||||
|------|------|
|
||||
| `frontend/src/components/files/FilesToolbar.vue` | v-select → v-autocomplete 可搜索选服 |
|
||||
| `frontend/src/composables/files/useFilesNavigation.ts` | 全量 loadServers、filteredServerList |
|
||||
| `frontend/src/composables/files/useFilesPage.ts` | 暴露搜索状态 |
|
||||
| `frontend/src/components/watch/WatchSlotCard.vue` | 空槽/满槽点击、hover |
|
||||
| `frontend/src/components/watch/WatchSlotPickDialog.vue` | 空槽选机对话框(新增) |
|
||||
| `frontend/src/components/watch/WatchSlotRow.vue` | 串联 pick 与 pinServer |
|
||||
| `frontend/src/components/watch/WatchSparkline.vue` | pointer-events 防挡点击 |
|
||||
| `frontend/src/composables/useWatchPins.ts` | pinServer 支持 slot_index |
|
||||
| `frontend/src/pages/ServersPage.vue` | 已在监测 + 提示 |
|
||||
|
||||
## Step 3 规则扫描
|
||||
|
||||
| H | 规则 | 结论 |
|
||||
|---|------|------|
|
||||
| H1 | 鉴权 / IDOR | PASS — `/servers/`、`/watch/pins` 均走 JWT;Pin 按 admin 隔离(后端已有) |
|
||||
| H2 | 无静默吞错 | PASS(审计中修复)— WatchSlotPickDialog 加载失败改 snackbar;pin 失败经 formatApiError |
|
||||
| H3 | 无新密钥 | PASS — 纯前端 UI |
|
||||
| H4 | XSS | PASS — 无 v-html;服务器名由 API 返回、Vuetify 文本渲染 |
|
||||
| H5 | 输入边界 | PASS — serverId/slotIndex 为 number;后端 Pydantic ge/le 校验 |
|
||||
| H6 | 性能 | RISK 接受 — 全量拉取 servers(与终端/推送一致);2000+ 台客户端 filter |
|
||||
|
||||
## 逐行走读(前端 5 文件)
|
||||
|
||||
| 文件 | 行数 | Read | 结论 |
|
||||
|------|------|------|------|
|
||||
| WatchSlotPickDialog.vue | 108 | 1–108 | 逐行完成 ✓ |
|
||||
| WatchSlotCard.vue | 138 | 1–138 | 逐行完成 ✓ |
|
||||
| WatchSlotRow.vue | 98 | 1–98 | 逐行完成 ✓ |
|
||||
| useWatchPins.ts(pinServer 段) | 222 | 160–178 | 逐行完成 ✓ |
|
||||
| useFilesNavigation.ts(搜索段) | 244 | 26–47,139–170 | 逐行完成 ✓ |
|
||||
|
||||
### Closure
|
||||
|
||||
| H | 判定 | 依据 |
|
||||
|---|------|------|
|
||||
| H1 | SAFE | fetchPagePerPage/http 带 auth;POST body 仅 server_id + slot_index |
|
||||
| H2 | SAFE | 选机对话框 load 失败 snackbar;onPickServer 409/422 均有 snackbar |
|
||||
| H3 | SAFE | 无 env/密钥 |
|
||||
| H4 | SAFE | 模板无 innerHTML |
|
||||
| H5 | SAFE | slot_index 0–3 后端校验;空 selectedId 禁用「加入」 |
|
||||
| H6 | SAFE(接受) | 与项目内终端/推送全量列表模式一致 |
|
||||
|
||||
### 交互验收
|
||||
|
||||
| 场景 | 预期 | 审计结论 |
|
||||
|------|------|----------|
|
||||
| 文件页搜索服务器 | 名称/域名/路径/ID 过滤 | PASS(type-check) |
|
||||
| 点击虚线空槽 | 弹出选机 → POST slot_index | PASS(代码路径完整) |
|
||||
| 点击满槽卡片 | 路由 WatchMetrics + query | PASS |
|
||||
| 列表 + 已 Pin | snackbar 槽位号 | PASS |
|
||||
| 满 4 槽替换对话框 | replace_slot POST | PASS(既有逻辑未改坏) |
|
||||
|
||||
## DoD
|
||||
|
||||
- [x] `pytest tests/test_watch_metrics.py tests/test_watch_pins.py` — 10 passed
|
||||
- [x] `cd frontend && npm run type-check`
|
||||
- [x] 审计修复:WatchSlotPickDialog 加载失败 snackbar
|
||||
- [x] 生产已部署 `6b8965a`(监测槽点击);`6140d68`(文件搜索)
|
||||
- [ ] Gate 3 test_api.py — 需本地 API :8600 运行(审计时未起服务)
|
||||
|
||||
## 验证命令
|
||||
|
||||
```bash
|
||||
.venv/bin/pytest tests/test_watch_metrics.py tests/test_watch_pins.py -q
|
||||
cd frontend && npm run type-check
|
||||
bash scripts/local_verify.sh # 含 Gate 3
|
||||
bash deploy/pre_deploy_check.sh
|
||||
```
|
||||
|
||||
## 备注
|
||||
|
||||
- 监测槽空槽点击为设计文档明确要求(`2026-06-12-server-watch-slots-design.md` §信息架构),原实现遗漏,已补。
|
||||
- 无后端变更;无需迁移/重启 API(仅前端)。
|
||||
@@ -1,9 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { fetchPagePerPage } from '@/utils/paginatedFetch'
|
||||
import { useSnackbar } from '@/composables/useSnackbar'
|
||||
import type { ServerApiItem } from '@/types/api'
|
||||
|
||||
const open = defineModel<boolean>({ required: true })
|
||||
const snackbar = useSnackbar()
|
||||
|
||||
const props = defineProps<{
|
||||
slotIndex: number | null
|
||||
@@ -43,6 +45,7 @@ watch(open, async (visible) => {
|
||||
servers.value = res.items
|
||||
} catch {
|
||||
servers.value = []
|
||||
snackbar('加载服务器列表失败', 'warning')
|
||||
} finally {
|
||||
listLoading.value = false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user