fix(frontend): 设置页恢复 Telegram Chat ID 自动检测

Vue SPA 迁移遗漏 getUpdates 检测按钮;对接 GET /settings/telegram/chats,并修正 cron 脚本 bash 调用。
This commit is contained in:
Nexus Agent
2026-06-07 19:31:13 +08:00
parent e3fe161ae5
commit a5d5518054
6 changed files with 225 additions and 6 deletions
+60
View File
@@ -276,3 +276,63 @@
{"ts":"2026-06-07T18:05:19+08:00","date":"2026-06-07","gate":"shell_eol","result":"PASS","detail":"all tracked sh LF"}
{"ts":"2026-06-07T18:05:19+08:00","date":"2026-06-07","gate":"text_eol","result":"PASS","detail":"git index LF"}
{"ts":"2026-06-07T18:05:19+08:00","date":"2026-06-07","gate":"summary","result":"PASS","detail":"7/7"}
{"ts":"2026-06-07T19:29:17+08:00","date":"2026-06-07","gate":"changelog","result":"PASS","detail":"2026-06-07-settings-telegram-detect-chat-id.md 29lines"}
{"ts":"2026-06-07T19:29:17+08:00","date":"2026-06-07","gate":"audit","result":"PASS","detail":"2026-06-07-code-review-followups.md"}
{"ts":"2026-06-07T19:29:17+08:00","date":"2026-06-07","gate":"test","result":"PASS","detail":"all passed"}
{"ts":"2026-06-07T19:29:17+08:00","date":"2026-06-07","gate":"lint","result":"PASS","detail":"0 violations"}
{"ts":"2026-06-07T19:29:17+08:00","date":"2026-06-07","gate":"import","result":"PASS","detail":"ok"}
{"ts":"2026-06-07T19:29:17+08:00","date":"2026-06-07","gate":"security","result":"PASS","detail":"0 HIGH"}
{"ts":"2026-06-07T19:29:17+08:00","date":"2026-06-07","gate":"review","result":"BLOCK","detail":"audit missing changed files"}
{"ts":"2026-06-07T19:29:17+08:00","date":"2026-06-07","gate":"shell_eol","result":"PASS","detail":"all tracked sh LF"}
{"ts":"2026-06-07T19:29:17+08:00","date":"2026-06-07","gate":"text_eol","result":"PASS","detail":"git index LF"}
{"ts":"2026-06-07T19:29:17+08:00","date":"2026-06-07","gate":"summary","result":"BLOCK","detail":"6/7"}
{"ts":"2026-06-07T19:29:31+08:00","date":"2026-06-07","gate":"changelog","result":"PASS","detail":"2026-06-07-settings-telegram-detect-chat-id.md 29lines"}
{"ts":"2026-06-07T19:29:31+08:00","date":"2026-06-07","gate":"audit","result":"BLOCK","detail":"missing sections"}
{"ts":"2026-06-07T19:29:31+08:00","date":"2026-06-07","gate":"test","result":"BLOCK","detail":"tests failed"}
{"ts":"2026-06-07T19:29:31+08:00","date":"2026-06-07","gate":"lint","result":"PASS","detail":"0 violations"}
{"ts":"2026-06-07T19:29:31+08:00","date":"2026-06-07","gate":"import","result":"PASS","detail":"ok"}
{"ts":"2026-06-07T19:29:31+08:00","date":"2026-06-07","gate":"security","result":"PASS","detail":"0 HIGH"}
{"ts":"2026-06-07T19:29:31+08:00","date":"2026-06-07","gate":"review","result":"BLOCK","detail":"audit missing changed files"}
{"ts":"2026-06-07T19:29:31+08:00","date":"2026-06-07","gate":"shell_eol","result":"PASS","detail":"all tracked sh LF"}
{"ts":"2026-06-07T19:29:31+08:00","date":"2026-06-07","gate":"text_eol","result":"PASS","detail":"git index LF"}
{"ts":"2026-06-07T19:29:31+08:00","date":"2026-06-07","gate":"summary","result":"BLOCK","detail":"4/7"}
{"ts":"2026-06-07T19:29:59+08:00","date":"2026-06-07","gate":"changelog","result":"PASS","detail":"2026-06-07-settings-telegram-detect-chat-id.md 29lines"}
{"ts":"2026-06-07T19:29:59+08:00","date":"2026-06-07","gate":"audit","result":"BLOCK","detail":"missing sections"}
{"ts":"2026-06-07T19:29:59+08:00","date":"2026-06-07","gate":"test","result":"PASS","detail":"all passed"}
{"ts":"2026-06-07T19:29:59+08:00","date":"2026-06-07","gate":"lint","result":"PASS","detail":"0 violations"}
{"ts":"2026-06-07T19:29:59+08:00","date":"2026-06-07","gate":"import","result":"PASS","detail":"ok"}
{"ts":"2026-06-07T19:29:59+08:00","date":"2026-06-07","gate":"security","result":"PASS","detail":"0 HIGH"}
{"ts":"2026-06-07T19:29:59+08:00","date":"2026-06-07","gate":"review","result":"BLOCK","detail":"audit missing changed files"}
{"ts":"2026-06-07T19:29:59+08:00","date":"2026-06-07","gate":"shell_eol","result":"PASS","detail":"all tracked sh LF"}
{"ts":"2026-06-07T19:29:59+08:00","date":"2026-06-07","gate":"text_eol","result":"PASS","detail":"git index LF"}
{"ts":"2026-06-07T19:29:59+08:00","date":"2026-06-07","gate":"summary","result":"BLOCK","detail":"5/7"}
{"ts":"2026-06-07T19:30:41+08:00","date":"2026-06-07","gate":"changelog","result":"PASS","detail":"2026-06-07-settings-telegram-detect-chat-id.md 29lines"}
{"ts":"2026-06-07T19:30:41+08:00","date":"2026-06-07","gate":"audit","result":"BLOCK","detail":"missing sections"}
{"ts":"2026-06-07T19:30:41+08:00","date":"2026-06-07","gate":"test","result":"PASS","detail":"all passed"}
{"ts":"2026-06-07T19:30:41+08:00","date":"2026-06-07","gate":"lint","result":"PASS","detail":"0 violations"}
{"ts":"2026-06-07T19:30:41+08:00","date":"2026-06-07","gate":"import","result":"PASS","detail":"ok"}
{"ts":"2026-06-07T19:30:41+08:00","date":"2026-06-07","gate":"security","result":"PASS","detail":"0 HIGH"}
{"ts":"2026-06-07T19:30:41+08:00","date":"2026-06-07","gate":"review","result":"PASS","detail":"audit covers changes"}
{"ts":"2026-06-07T19:30:41+08:00","date":"2026-06-07","gate":"shell_eol","result":"PASS","detail":"all tracked sh LF"}
{"ts":"2026-06-07T19:30:41+08:00","date":"2026-06-07","gate":"text_eol","result":"PASS","detail":"git index LF"}
{"ts":"2026-06-07T19:30:41+08:00","date":"2026-06-07","gate":"summary","result":"BLOCK","detail":"6/7"}
{"ts":"2026-06-07T19:30:49+08:00","date":"2026-06-07","gate":"changelog","result":"PASS","detail":"2026-06-07-settings-telegram-detect-chat-id.md 29lines"}
{"ts":"2026-06-07T19:30:49+08:00","date":"2026-06-07","gate":"audit","result":"BLOCK","detail":"missing sections"}
{"ts":"2026-06-07T19:30:49+08:00","date":"2026-06-07","gate":"test","result":"PASS","detail":"all passed"}
{"ts":"2026-06-07T19:30:49+08:00","date":"2026-06-07","gate":"lint","result":"PASS","detail":"0 violations"}
{"ts":"2026-06-07T19:30:49+08:00","date":"2026-06-07","gate":"import","result":"PASS","detail":"ok"}
{"ts":"2026-06-07T19:30:49+08:00","date":"2026-06-07","gate":"security","result":"PASS","detail":"0 HIGH"}
{"ts":"2026-06-07T19:30:49+08:00","date":"2026-06-07","gate":"review","result":"PASS","detail":"audit covers changes"}
{"ts":"2026-06-07T19:30:49+08:00","date":"2026-06-07","gate":"shell_eol","result":"PASS","detail":"all tracked sh LF"}
{"ts":"2026-06-07T19:30:49+08:00","date":"2026-06-07","gate":"text_eol","result":"PASS","detail":"git index LF"}
{"ts":"2026-06-07T19:30:49+08:00","date":"2026-06-07","gate":"summary","result":"BLOCK","detail":"6/7"}
{"ts":"2026-06-07T19:31:03+08:00","date":"2026-06-07","gate":"changelog","result":"PASS","detail":"2026-06-07-settings-telegram-detect-chat-id.md 29lines"}
{"ts":"2026-06-07T19:31:03+08:00","date":"2026-06-07","gate":"audit","result":"PASS","detail":"2026-06-07-settings-telegram-detect-chat-id.md"}
{"ts":"2026-06-07T19:31:03+08:00","date":"2026-06-07","gate":"test","result":"PASS","detail":"all passed"}
{"ts":"2026-06-07T19:31:03+08:00","date":"2026-06-07","gate":"lint","result":"PASS","detail":"0 violations"}
{"ts":"2026-06-07T19:31:03+08:00","date":"2026-06-07","gate":"import","result":"PASS","detail":"ok"}
{"ts":"2026-06-07T19:31:03+08:00","date":"2026-06-07","gate":"security","result":"PASS","detail":"0 HIGH"}
{"ts":"2026-06-07T19:31:03+08:00","date":"2026-06-07","gate":"review","result":"PASS","detail":"audit covers changes"}
{"ts":"2026-06-07T19:31:03+08:00","date":"2026-06-07","gate":"shell_eol","result":"PASS","detail":"all tracked sh LF"}
{"ts":"2026-06-07T19:31:03+08:00","date":"2026-06-07","gate":"text_eol","result":"PASS","detail":"git index LF"}
{"ts":"2026-06-07T19:31:03+08:00","date":"2026-06-07","gate":"summary","result":"PASS","detail":"7/7"}
+2 -2
View File
@@ -64,8 +64,8 @@ done
touch "$HEALTH_LOG" "$BACKUP_LOG" 2>/dev/null || true
chmod 644 "$HEALTH_LOG" "$BACKUP_LOG" 2>/dev/null || true
HEALTH_LINE="* * * * * NEXUS_DEPLOY_DIR=${NEXUS_ROOT} ${DEPLOY_DIR}/health_monitor.sh >> ${HEALTH_LOG} 2>&1 # ${HEALTH_MARKER}"
BACKUP_LINE="0 3 * * * NEXUS_ROOT=${NEXUS_ROOT} ${DEPLOY_DIR}/db_backup.sh >> ${BACKUP_LOG} 2>&1 # ${BACKUP_MARKER}"
HEALTH_LINE="* * * * * NEXUS_DEPLOY_DIR=${NEXUS_ROOT} bash ${DEPLOY_DIR}/health_monitor.sh >> ${HEALTH_LOG} 2>&1 # ${HEALTH_MARKER}"
BACKUP_LINE="0 3 * * * NEXUS_ROOT=${NEXUS_ROOT} bash ${DEPLOY_DIR}/db_backup.sh >> ${BACKUP_LOG} 2>&1 # ${BACKUP_MARKER}"
existing="$(crontab -l 2>/dev/null || true)"
filtered="$(printf '%s\n' "$existing" | grep -v "$HEALTH_MARKER" | grep -v "$BACKUP_MARKER" | grep -v 'deploy/health_monitor.sh' | grep -v 'deploy/db_backup.sh' || true)"
@@ -0,0 +1,43 @@
# 审计 — 设置页 Telegram Chat ID 检测恢复
## 范围
| 文件 | 变更 |
|------|------|
| `frontend/src/pages/SettingsPage.vue` | 恢复「检测 Chat ID」按钮与选择对话框 |
| `frontend/src/types/api.ts` | `TelegramChatsResponse` 类型 |
| `deploy/install_ops_cron.sh` | crontab 改用 `bash` 调用(避免 +x 丢失) |
| `deploy/deploy-production.sh` | `PORT` 缺省 8600(同日前序 commit |
| `deploy/gate_log.jsonl` | 门控记录(自动生成) |
## 结论
- 后端 `GET /settings/telegram/chats` 未删,仅 Vue 设置页遗漏 UI
- 无 API 契约变更;检测前若 Token 草稿存在则先 PUT 保存
## Step 3 规则扫描
| H | 规则 | 结论 |
|---|------|------|
| H1 | Chat ID 检测 API 仍存在 | PASS — `GET /settings/telegram/chats` 未改 |
| H2 | Vue 设置页功能对等 | FIXED — 恢复检测按钮与对话框 |
| H3 | Token 未保存时检测 | FIXED — 检测前自动 PUT token 草稿 |
## Closure
| H | 判定 | 依据 |
|---|------|------|
| H1 | PASS | `server/api/settings.py` `telegram_get_chats` |
| H2 | FIXED | `SettingsPage.vue` `detectTelegramChats` |
| H3 | FIXED | 检测前 `http.put telegram_bot_token` |
## DoD
- [x] 前端 vite build 通过
- [x] changelog / audit 已写
- [x] 无 API 破坏性变更
## 验证
- `cd frontend && npx vite build` 通过
- 设置页:Bot 收消息后检测 → 列表 → 选中写入 Chat ID
@@ -0,0 +1,29 @@
# Changelog — 设置页恢复 Telegram Chat ID 自动检测
**日期**2026-06-07
## 摘要
Vue SPA 设置页补回「检测 Chat ID」按钮,对接已有 `GET /api/settings/telegram/chats`getUpdates)。
## 动机
迁移至 `frontend/src/pages/SettingsPage.vue` 时仅保留 Token / Chat ID 手工输入与测试发送,遗漏旧版 settings 的 getUpdates 检测能力。
## 涉及文件
- `frontend/src/pages/SettingsPage.vue`
- `frontend/src/types/api.ts`
## 行为
- 点击「检测 Chat ID」:若 Token 草稿未保存则先写入 DB,再拉取最近对话列表供选择
- 选中后自动写入并保存 `telegram_chat_id`
## 迁移 / 重启
需重新构建前端并部署(Docker 镜像内 vite build)。
## 验证
设置页 → Telegram → 向 Bot 发消息后点「检测 Chat ID」→ 弹出对话列表 → 选择后 Chat ID 填入并保存。
+78 -4
View File
@@ -68,7 +68,13 @@
/>
<v-chip v-if="telegramTokenSet && !telegramTokenDraft" size="small" color="success" variant="tonal" class="mb-3">Token 已配置</v-chip>
<v-text-field v-model="settings.telegram_chat_id" label="Chat ID" variant="outlined" density="compact" class="mb-3" />
<v-btn size="small" variant="tonal" prepend-icon="mdi-message-text" @click="testTelegram" :loading="testingTg">发送测试消息</v-btn>
<p class="text-caption text-medium-emphasis mb-3">
Telegram Bot 发送任意消息后可点击下方按钮自动检测 Chat ID
</p>
<div class="d-flex flex-wrap ga-2">
<v-btn size="small" variant="tonal" prepend-icon="mdi-radar" @click="detectTelegramChats" :loading="detectingTgChats">检测 Chat ID</v-btn>
<v-btn size="small" variant="tonal" prepend-icon="mdi-message-text" @click="testTelegram" :loading="testingTg">发送测试消息</v-btn>
</div>
</v-card-text>
</v-card>
</v-col>
@@ -188,6 +194,31 @@
</v-card>
</v-dialog>
<!-- Telegram Chat ID picker -->
<v-dialog v-model="tgChatDialog" max-width="480">
<v-card border>
<v-card-title>选择 Chat ID</v-card-title>
<v-card-text>
<div v-if="!tgChats.length" class="text-body-2 text-medium-emphasis">
未检测到对话请先在 Telegram Bot 发送一条消息然后重新检测
</div>
<v-list v-else density="compact" lines="two">
<v-list-item
v-for="chat in tgChats"
:key="chat.id"
:title="chat.title || chat.username || `Chat ${chat.id}`"
:subtitle="`${chat.type}${chat.username ? ' · @' + chat.username : ''} · ID ${chat.id}`"
@click="applyTelegramChat(chat)"
/>
</v-list>
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn variant="text" @click="tgChatDialog = false">关闭</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<!-- TOTP Disable Dialog -->
<v-dialog v-model="showDisableTotp" max-width="400">
<v-card border>
@@ -213,7 +244,7 @@ import { useAuthStore } from '@/stores/auth'
import { useSnackbar } from '@/composables/useSnackbar'
import { required, numberRange, isNumber, matchOther } from '@/utils/validation'
import TerminalQuickCommandsSettings from '@/components/TerminalQuickCommandsSettings.vue'
import type { SettingsResponse, AllowlistResponse, SettingItem } from '@/types/api'
import type { SettingsResponse, AllowlistResponse, SettingItem, TelegramChatItem, TelegramChatsResponse } from '@/types/api'
const snackbar = useSnackbar()
@@ -231,6 +262,9 @@ const telegramTokenDraft = ref('')
const telegramTokenSet = ref(false)
const saving = ref(false)
const testingTg = ref(false)
const detectingTgChats = ref(false)
const tgChatDialog = ref(false)
const tgChats = ref<TelegramChatItem[]>([])
const showToken = ref(false)
// ── Password ──
@@ -332,8 +366,48 @@ async function testTelegram() {
try {
await http.post('/settings/telegram/test')
snackbar('测试消息已发送')
} catch (e: any) { snackbar(e.message || '发送失败', 'error') }
finally { testingTg.value = false }
} catch (e: unknown) {
snackbar(e instanceof Error ? e.message : '发送失败', 'error')
} finally { testingTg.value = false }
}
async function detectTelegramChats() {
detectingTgChats.value = true
try {
const tokenUpdate = telegramTokenDraft.value.trim()
if (tokenUpdate) {
await http.put('/settings/telegram_bot_token', { value: tokenUpdate })
telegramTokenSet.value = true
telegramTokenDraft.value = ''
showToken.value = false
} else if (!telegramTokenSet.value) {
snackbar('请先填写 Bot Token', 'error')
return
}
const res = await http.get<TelegramChatsResponse>('/settings/telegram/chats')
tgChats.value = res.chats || []
if (!tgChats.value.length) {
snackbar('未检测到对话,请先向 Bot 发送一条消息后再试', 'warning')
return
}
tgChatDialog.value = true
} catch (e: unknown) {
snackbar(e instanceof Error ? e.message : '检测失败', 'error')
} finally {
detectingTgChats.value = false
}
}
async function applyTelegramChat(chat: TelegramChatItem) {
const chatId = String(chat.id)
settings.value.telegram_chat_id = chatId
tgChatDialog.value = false
try {
await http.put('/settings/telegram_chat_id', { value: chatId })
snackbar(`已选择 Chat ID: ${chatId}`)
} catch (e: unknown) {
snackbar(e instanceof Error ? e.message : '保存 Chat ID 失败', 'error')
}
}
async function changePassword() {
+13
View File
@@ -22,6 +22,19 @@ export interface SettingItem {
updated_at?: string
}
/** Telegram getUpdates chat detection */
export interface TelegramChatItem {
id: number
type: string
title: string
username: string
}
export interface TelegramChatsResponse {
chats: TelegramChatItem[]
count: number
}
/** IP Allowlist response */
export interface AllowlistResponse {
enabled: boolean