fix: 修复proxy_service和account_service中不当的顶层import

- proxy_service.py: 将requests和WhitelistManager改为函数内延迟import,
  避免启动时加载不需要的依赖;移除未使用的get_exit_ip_via_proxy导入
- account_service.py: 移除未使用的func、joinedload、AuditLog、
  user_has_permission顶层导入
This commit is contained in:
yml2213
2026-06-23 08:35:29 +08:00
parent 2ab6724543
commit dd56de9bd4
40 changed files with 2006 additions and 936 deletions
+23 -83
View File
@@ -1,10 +1,12 @@
import { useEffect, useState, useRef, useMemo, useCallback } from 'react';
import { useEffect, useState, useMemo, useCallback } from 'react';
import {
Table, Button, Select, message, Tag, Space, Spin, InputNumber, Tooltip, Popconfirm, theme,
Table, Button, Select, message, Tag, Space, InputNumber, Tooltip, Popconfirm, theme,
} from 'antd';
import { PlayCircleOutlined, StopOutlined, FilterOutlined, ThunderboltOutlined, ReloadOutlined, DownOutlined, UpOutlined, DeleteOutlined, SwapOutlined } from '@ant-design/icons';
import { PlayCircleOutlined, StopOutlined, FilterOutlined, ThunderboltOutlined, ReloadOutlined, DeleteOutlined, SwapOutlined } from '@ant-design/icons';
import { accountApi, loginApi, type AccountItem, type LoginTaskItem } from '../api/modules';
import { getUser, hasPerm } from '../store/auth';
import RealtimeLogPanel from '../components/RealtimeLogPanel';
import { usePermissions } from '../hooks/usePermissions';
import { useWebSocketLogs } from '../hooks/useWebSocketLogs';
import { formatTime } from '../utils/time';
import { getErrorMessage } from '../utils/error';
@@ -35,20 +37,16 @@ export default function LoginTasksPage() {
const [tasks, setTasks] = useState<LoginTaskItem[]>([]);
const [loading, setLoading] = useState(false);
const [batchId, setBatchId] = useState<string | null>(null);
const [logs, setLogs] = useState<{ level: string; message: string }[]>([]);
const [wsConnected, setWsConnected] = useState(false);
const [selectedTags, setSelectedTags] = useState<string[]>([]);
const [concurrency, setConcurrency] = useState(3);
const [maxProxyRetries, setMaxProxyRetries] = useState(10);
const [logVisible, setLogVisible] = useState(true);
const [selectedRowKeys, setSelectedRowKeys] = useState<number[]>([]);
const wsRef = useRef<WebSocket | null>(null);
const logEndRef = useRef<HTMLDivElement | null>(null);
const user = getUser();
const { logs, connected: wsConnected, connect: connectLogs } = useWebSocketLogs();
const { can } = usePermissions();
const { token } = theme.useToken();
const canBatch = hasPerm(user, 'login:batch');
const canBatch = can('login:batch');
// 从账号中提取所有标签
const allTags = useMemo(() => {
@@ -113,20 +111,15 @@ export default function LoginTasksPage() {
try {
const data = await loginApi.listTasks(batchId || undefined);
setTasks(data);
} catch {}
} catch {
// 忽略轮询失败,下一次定时刷新会继续尝试。
}
};
useEffect(() => {
Promise.all([loadAccounts(), loadTasks()]);
}, []);
// 日志自动滚动到底部
useEffect(() => {
if (logVisible && logEndRef.current) {
logEndRef.current.scrollIntoView({ behavior: 'smooth' });
}
}, [logs, logVisible]);
useEffect(() => {
const timer = setInterval(loadTasks, 3000);
return () => clearInterval(timer);
@@ -139,31 +132,15 @@ export default function LoginTasksPage() {
return;
}
setLoading(true);
setLogs([]);
try {
const result = await loginApi.createBatch(accountIds, 5, concurrency, maxProxyRetries);
setBatchId(result.batch_id);
message.success(`已创建登录任务,共 ${result.count} 个账号`);
// 连接 WebSocket
const wsUrl = `${window.location.protocol === 'https:' ? 'wss' : 'ws'}://${window.location.host}/api/login/ws/login/${result.batch_id}`;
const ws = new WebSocket(wsUrl);
wsRef.current = ws;
setWsConnected(true);
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
if (msg.level === 'heartbeat') return;
if (msg.level === 'result') return;
setLogs((prev) => [...prev, msg]);
};
ws.onclose = () => {
wsRef.current = null;
setWsConnected(false);
setBatchId(null);
};
ws.onerror = () => {
setWsConnected(false);
};
connectLogs(`/api/login/ws/login/${result.batch_id}`, {
onClose: () => setBatchId(null),
onResult: () => setBatchId(null),
});
} catch (e: unknown) {
message.error(getErrorMessage(e));
} finally {
@@ -432,50 +409,13 @@ export default function LoginTasksPage() {
</div>
{/* 实时日志 - 底部可折叠 */}
<div style={{ flexShrink: 0, borderTop: `1px solid ${token.colorBorderSecondary}`, marginTop: 4 }}>
<div
style={{ display: 'flex', alignItems: 'center', cursor: 'pointer', padding: '4px 0', userSelect: 'none' }}
onClick={() => setLogVisible((v) => !v)}
>
<span style={{ fontWeight: 500, fontSize: 13 }}></span>
{logVisible ? <UpOutlined style={{ marginLeft: 6, fontSize: 10 }} /> : <DownOutlined style={{ marginLeft: 6, fontSize: 10 }} />}
{logs.length > 0 && <span style={{ marginLeft: 8, fontSize: 12, color: token.colorTextTertiary }}>{logs.length} </span>}
{wsConnected && <Tag color="processing" style={{ marginLeft: 8 }}></Tag>}
</div>
{logVisible && (
<div
style={{
height: '20vh',
overflow: 'auto',
fontFamily: 'monospace',
fontSize: 12,
padding: 4,
backgroundColor: token.colorBgLayout,
borderRadius: 4,
}}
>
{logs.length === 0 ? (
<Spin spinning={wsConnected} size="small" />
) : (
logs.map((log, i) => (
<div
key={i}
style={{
color:
log.level === 'error' ? token.colorError :
log.level === 'success' ? token.colorSuccess :
log.level === 'warning' ? token.colorWarning :
token.colorText,
}}
>
{log.message}
</div>
))
)}
<div ref={logEndRef} />
</div>
)}
</div>
<RealtimeLogPanel
logs={logs}
connected={wsConnected}
collapsible
spinWhenEmpty
style={{ marginTop: 4 }}
/>
</div>
);
}