# 审计 — 文件管理服务器搜索 + 监测槽点击修复

**日期**：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（仅前端）。
