import { Alert, Button, Checkbox, Col, Descriptions, Form, Input, InputNumber, Modal, Row, Select, Space, Switch, Table, Tag, Tooltip, Typography, message, } from 'antd'; import { Bot, CirclePlus, KeyRound, Pencil, PlugZap, RefreshCw, Save, ShieldCheck, Trash2, } from 'lucide-react'; import { useCallback, useEffect, useState } from 'react'; import { PageHeader, Surface } from '@/components/Page'; import { apiRequest, jsonOptions, setCsrfToken, setSelectedBotId } from '@/services/api'; import type { AdminUser, BotPermissionDefinition, BotProfile, BotRole, TelegramSettings, } from '@/types'; interface SystemSettings { web_address: string; bind_host: string; upload_limit_mb: number; supervisor_mode: boolean; bot: { connected: boolean; id: string; username: string; name: string }; } interface BotFormValues { label: string; bot_token?: string; enabled: boolean; sudo_users_id?: string; log_group_id?: string; gban_log_group_id?: string; message_dump_chat?: string; role_ids: string[]; } interface RoleFormValues { name: string; description?: string; permissions: string[]; } const TELEGRAM_APP_URL = 'https://my.telegram.org/apps'; const API_HASH_PATTERN = /^[A-Fa-f0-9]{32}$/; function runtimeTag(profile: BotProfile) { if (!profile.enabled) return 已停用; if (!profile.ready_to_connect) return 配置未完成; const state = profile.runtime?.state || 'stopped'; const values: Record = { running: { color: 'success', text: '运行中' }, starting: { color: 'processing', text: '启动中' }, stopping: { color: 'processing', text: '停止中' }, failed: { color: 'error', text: '启动失败' }, stopped: { color: 'default', text: '已停止' }, }; const current = values[state] || values.stopped; return ( {current.text} ); } export default function SettingsPage() { const [settings, setSettings] = useState(null); const [telegram, setTelegram] = useState(null); const [editingBot, setEditingBot] = useState(null); const [botModalOpen, setBotModalOpen] = useState(false); const [editingRole, setEditingRole] = useState(null); const [roleModalOpen, setRoleModalOpen] = useState(false); const [globalForm] = Form.useForm(); const [botForm] = Form.useForm(); const [roleForm] = Form.useForm(); const [passwordForm] = Form.useForm(); const reloadTelegram = useCallback(async () => { const current = await apiRequest('/settings/telegram'); setTelegram(current); globalForm.setFieldsValue({ api_id: current.api_id || undefined, api_hash: '' }); window.dispatchEvent(new Event('bot-profiles-changed')); }, [globalForm]); useEffect(() => { apiRequest('/settings').then(setSettings).catch((error) => message.error(error.message)); reloadTelegram().catch((error) => message.error(error.message)); }, [reloadTelegram]); const openBotModal = (profile?: BotProfile) => { const target = profile || null; setEditingBot(target); botForm.setFieldsValue( target ? { label: target.label, bot_token: '', enabled: target.enabled, sudo_users_id: target.sudo_users_id.join(' '), log_group_id: target.log_group_id, gban_log_group_id: target.gban_log_group_id, message_dump_chat: target.message_dump_chat, role_ids: target.role_ids || ['full_access'], } : { label: '', bot_token: '', enabled: true, sudo_users_id: '', log_group_id: '0', gban_log_group_id: '0', message_dump_chat: '0', role_ids: ['full_access'], }, ); setBotModalOpen(true); }; const saveBot = (values: BotFormValues) => { Modal.confirm({ title: editingBot ? '保存并重启该机器人?' : '创建并启动该机器人?', content: '启用且配置完整时,系统会立即连接 Telegram。', okText: '确认保存', cancelText: '取消', onOk: async () => { const path = editingBot ? `/bots/${editingBot.bot_id}` : '/bots'; const method = editingBot ? 'PUT' : 'POST'; const saved = await apiRequest( path, jsonOptions(method, { ...values, confirm: true }), ); setBotModalOpen(false); message.success(editingBot ? '机器人配置已更新' : '机器人已创建'); await reloadTelegram(); if (!editingBot) setSelectedBotId(saved.bot_id); }, }); }; const openRoleModal = (role?: BotRole) => { const target = role || null; setEditingRole(target); roleForm.setFieldsValue( target ? { name: target.name, description: target.description, permissions: target.permissions, } : { name: '', description: '', permissions: [], }, ); setRoleModalOpen(true); }; const saveRole = (values: RoleFormValues) => { Modal.confirm({ title: editingRole ? '保存角色权限?' : '创建职责角色?', content: editingRole ? '使用该角色的机器人会自动重启并应用新权限。' : '创建后可将角色分配给一个或多个机器人。', okText: '确认保存', cancelText: '取消', onOk: async () => { const path = editingRole ? `/roles/${editingRole.role_id}` : '/roles'; const method = editingRole ? 'PUT' : 'POST'; await apiRequest( path, jsonOptions(method, { ...values, confirm: true }), ); setRoleModalOpen(false); message.success(editingRole ? '角色权限已更新' : '职责角色已创建'); await reloadTelegram(); }, }); }; const roleNames = new Map( (telegram?.roles || []).map((role) => [role.role_id, role.name]), ); const permissionNames = new Map( (telegram?.permission_catalog || []).map((permission) => [ permission.key, permission.name, ]), ); const permissionGroups = (telegram?.permission_catalog || []).reduce< Record >((groups, permission) => { (groups[permission.group] ||= []).push(permission); return groups; }, {}); const botColumns = [ { title: '机器人', key: 'bot', render: (_: unknown, profile: BotProfile) => ( {profile.label} {profile.identity?.username ? `@${profile.identity.username}` : '尚未验证身份'} ), }, { title: '令牌', dataIndex: 'bot_token_configured', width: 96, render: (configured: boolean) => ( {configured ? '已配置' : '未配置'} ), }, { title: '连接状态', key: 'runtime', width: 120, render: (_: unknown, profile: BotProfile) => runtimeTag(profile), }, { title: '职责角色', dataIndex: 'role_ids', minWidth: 180, render: (roleIds: string[]) => ( {(roleIds || []).map((roleId) => ( {roleNames.get(roleId) || roleId} ))} {!roleIds?.length ? 未分配职责 : null} ), }, { title: '媒体中转群', dataIndex: 'message_dump_chat', width: 150, render: (value: string) => (value && value !== '0' ? value : '未配置'), }, { title: '操作', key: 'actions', width: 190, fixed: 'right' as const, render: (_: unknown, profile: BotProfile) => ( } disabled={!profile.bot_token_configured} onClick={() => Modal.confirm({ title: `验证 ${profile.label} 的机器人令牌?`, okText: '开始验证', cancelText: '取消', onOk: async () => { const identity = await apiRequest<{ username: string }>( `/bots/${profile.bot_id}/test`, jsonOptions('POST', { confirm: true }), ); message.success(`机器人令牌有效:@${identity.username}`); await reloadTelegram(); }, }) } /> } disabled={!profile.enabled || !profile.ready_to_connect} onClick={() => Modal.confirm({ title: `重启 ${profile.label}?`, okText: '确认重启', cancelText: '取消', onOk: async () => { await apiRequest( `/bots/${profile.bot_id}/restart`, jsonOptions('POST', { confirm: true }), ); message.success('机器人正在重启'); await reloadTelegram(); }, }) } /> } onClick={() => openBotModal(profile)} /> } onClick={() => Modal.confirm({ title: `删除 ${profile.label}?`, content: '工作进程会停止,群数据和操作日志不会被删除。', okText: '确认删除', okButtonProps: { danger: true }, cancelText: '取消', onOk: async () => { await apiRequest( `/bots/${profile.bot_id}`, jsonOptions('DELETE', { confirm: true }), ); message.success('机器人配置已删除'); await reloadTelegram(); }, }) } /> ), }, ]; const roleColumns = [ { title: '角色', key: 'role', minWidth: 180, render: (_: unknown, role: BotRole) => ( {role.name} {role.builtin ? 内置 : 自定义} {role.description || '无说明'} ), }, { title: '权限', dataIndex: 'permissions', minWidth: 360, render: (permissions: string[]) => ( {permissions.map((permission) => ( {permissionNames.get(permission) || permission} ))} {!permissions.length ? 无权限 : null} ), }, { title: '操作', key: 'actions', width: 110, fixed: 'right' as const, render: (_: unknown, role: BotRole) => role.builtin ? null : ( } onClick={() => openRoleModal(role)} /> } onClick={() => Modal.confirm({ title: `删除角色 ${role.name}?`, content: '已分配给机器人的角色不能删除。', okText: '确认删除', okButtonProps: { danger: true }, cancelText: '取消', onOk: async () => { await apiRequest( `/roles/${role.role_id}`, jsonOptions('DELETE', { confirm: true }), ); message.success('职责角色已删除'); await reloadTelegram(); }, }) } /> ), }, ]; return ( <> } onClick={() => openBotModal()}> 添加机器人 } > Modal.confirm({ title: '更新 Telegram 应用接口凭据?', content: '所有启用的机器人会依次重启并应用新配置。', okText: '确认更新', cancelText: '取消', onOk: async () => { await apiRequest( '/settings/telegram', jsonOptions('PUT', { ...values, confirm: true }), ); message.success('Telegram 应用接口配置已更新'); await reloadTelegram(); }, }) } > 来自{' '} my.telegram.org/apps ,不是机器人编号。 } rules={[{ required: true, message: '请输入 Telegram 应用编号' }]} > } block> 保存 API 配置 {!telegram?.api_ready ? ( ) : null} rowKey="bot_id" columns={botColumns} dataSource={telegram?.bots || []} pagination={false} scroll={{ x: 980 }} locale={{ emptyText: '尚未添加机器人' }} /> } onClick={() => openRoleModal()}> 新建角色 } > rowKey="role_id" columns={roleColumns} dataSource={telegram?.roles || []} pagination={false} scroll={{ x: 760 }} /> {settings ? ( {settings.web_address} {settings.bind_host} {settings.upload_limit_mb} MB {settings.supervisor_mode ? '多机器人监管' : '机器人工作进程'} ) : null} { const user = await apiRequest( '/auth/password', jsonOptions('PUT', values), ); setCsrfToken(user.csrf_token); passwordForm.resetFields(); message.success('管理员密码已更新,其他会话已失效'); }} > }> 修改密码 本地 0600 文件 scrypt + 独立盐 跨站请求防护校验 setBotModalOpen(false)} onOk={() => botForm.submit()} okText="继续" cancelText="取消" destroyOnHidden > ({ value: role.role_id, label: role.name, }))} /> setRoleModalOpen(false)} onOk={() => roleForm.submit()} okText="继续" cancelText="取消" width={720} destroyOnHidden > {Object.entries(permissionGroups).map(([group, permissions]) => ( {group} {permissions.map((permission) => ( {permission.name} {permission.description} ))} ))} > ); }