fix(frontend): 设置页恢复 Telegram Chat ID 自动检测
Vue SPA 迁移遗漏 getUpdates 检测按钮;对接 GET /settings/telegram/chats,并修正 cron 脚本 bash 调用。
This commit is contained in:
@@ -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"}
|
||||
|
||||
@@ -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 填入并保存。
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user