refactor: 消除前端52处any类型+删除冗余requirements.txt+调试print改日志
- 删除 requirements.txt,pyproject.toml 为唯一依赖源(含 requests[socks] extra) - core/geetest 下 5 处 print() 替换为 loguru logger.debug() - api/modules.ts 新增 13 个响应接口,消除所有 api.xxx<any, any> - 新建 utils/error.ts 提供 getErrorMessage(e: unknown) 安全取消息 - 11 个页面组件 catch (e: any) 改为 catch (e: unknown) - useState<any[]>、columns: any[]、render 参数全部类型化 - TypeScript 类型检查零错误通过
This commit is contained in:
@@ -2,6 +2,7 @@ import requests
|
|||||||
import cv2
|
import cv2
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
REQUEST_TIMEOUT = (3.05, 12)
|
REQUEST_TIMEOUT = (3.05, 12)
|
||||||
|
|
||||||
@@ -60,7 +61,7 @@ def restore_geetest_image(input_path:str, output_path:str) -> None:
|
|||||||
new_img.paste(l, box=(_ % 26 * 10, 80 if _ > 25 else 0))
|
new_img.paste(l, box=(_ % 26 * 10, 80 if _ > 25 else 0))
|
||||||
|
|
||||||
new_img.save(output_path)
|
new_img.save(output_path)
|
||||||
print(f"图像已还原并保存到: {output_path}")
|
logger.debug("图像已还原并保存到: {}", output_path)
|
||||||
|
|
||||||
# 下载图片
|
# 下载图片
|
||||||
def download_picture(bg:str, fullbg:str, slice:str) -> int:
|
def download_picture(bg:str, fullbg:str, slice:str) -> int:
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import requests
|
|||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
from typing import Mapping, Optional, Tuple
|
from typing import Mapping, Optional, Tuple
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
REQUEST_TIMEOUT = (10, 30)
|
REQUEST_TIMEOUT = (10, 30)
|
||||||
PASSPORT_REFERER = "https://passport.douyu.com/"
|
PASSPORT_REFERER = "https://passport.douyu.com/"
|
||||||
@@ -263,13 +264,8 @@ def get_picture(gt:str, challenge:str) -> tuple[str, str, list[int], str, str, s
|
|||||||
timeout=REQUEST_TIMEOUT,
|
timeout=REQUEST_TIMEOUT,
|
||||||
)
|
)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
print(response.text)
|
logger.debug("极验 get.php 原始响应: {}", response.text[:500])
|
||||||
match = re.search(r'\((.*)\)$', response.text)
|
match = re.search(r'\((.*)\)$', response.text)
|
||||||
if match:
|
|
||||||
json_str = match.group(1)
|
|
||||||
data = json.loads(json_str)
|
|
||||||
|
|
||||||
# 检查验证码类型
|
|
||||||
if 'data' in data and isinstance(data['data'], dict):
|
if 'data' in data and isinstance(data['data'], dict):
|
||||||
# 新版极验格式
|
# 新版极验格式
|
||||||
inner_data = data['data']
|
inner_data = data['data']
|
||||||
@@ -317,12 +313,12 @@ def req_end(gt:str, challenge:str, w:str) -> dict:
|
|||||||
timeout=REQUEST_TIMEOUT,
|
timeout=REQUEST_TIMEOUT,
|
||||||
)
|
)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
print(response.text)
|
logger.debug("极验 ajax.php 原始响应: {}", response.text[:500])
|
||||||
match = re.search(r'\((.*)\)$', response.text)
|
match = re.search(r'\((.*)\)$', response.text)
|
||||||
if match:
|
if match:
|
||||||
json_str = match.group(1)
|
json_str = match.group(1)
|
||||||
data = json.loads(json_str)
|
data = json.loads(json_str)
|
||||||
print(f"极验验证响应: {data}")
|
logger.debug("极验验证响应: {}", data)
|
||||||
|
|
||||||
# 检查验证是否成功
|
# 检查验证是否成功
|
||||||
if data.get('success') == 1:
|
if data.get('success') == 1:
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import time
|
import time
|
||||||
import random
|
import random
|
||||||
import json
|
import json
|
||||||
|
from loguru import logger
|
||||||
from core.geetest.common.trajectory import generate_realistic_trajectory, process_mouse_trajectory, compress_trajectory, TrajectoryEncoder, \
|
from core.geetest.common.trajectory import generate_realistic_trajectory, process_mouse_trajectory, compress_trajectory, TrajectoryEncoder, \
|
||||||
H
|
H
|
||||||
from core.geetest.common.crypto import four_random_chart, RSA_jiami_r, AES_O, geetest_base64_encode, encrypt_string, simple_md5
|
from core.geetest.common.crypto import four_random_chart, RSA_jiami_r, AES_O, geetest_base64_encode, encrypt_string, simple_md5
|
||||||
@@ -106,7 +107,7 @@ def get_w3(str_16:str, challenge:str, hkjl:int, c:list[int], s:str, gt:str) -> s
|
|||||||
|
|
||||||
|
|
||||||
trajectory = get_slide_track(hkjl)[0]
|
trajectory = get_slide_track(hkjl)[0]
|
||||||
print(trajectory)
|
logger.debug("滑动轨迹: {}", trajectory)
|
||||||
|
|
||||||
# trajectory = generator.generate(target_x)
|
# trajectory = generator.generate(target_x)
|
||||||
userresponse = H(trajectory[-1][0], challenge)
|
userresponse = H(trajectory[-1][0], challenge)
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
requests>=2.31.0
|
|
||||||
pycryptodome>=3.19.0
|
|
||||||
numpy>=1.24.0
|
|
||||||
opencv-python-headless>=4.8.0
|
|
||||||
Pillow>=10.0.0
|
|
||||||
loguru>=0.7.0
|
|
||||||
fastapi>=0.110.0
|
|
||||||
uvicorn[standard]>=0.27.0
|
|
||||||
sqlalchemy>=2.0.0
|
|
||||||
python-jose[cryptography]>=3.3.0
|
|
||||||
bcrypt>=4.0.0
|
|
||||||
pydantic>=2.0.0
|
|
||||||
python-multipart>=0.0.9
|
|
||||||
+167
-26
@@ -1,5 +1,22 @@
|
|||||||
import api from './index';
|
import api from './index';
|
||||||
|
|
||||||
|
// ==================== 通用响应类型 ====================
|
||||||
|
|
||||||
|
interface MessageResponse {
|
||||||
|
message: string;
|
||||||
|
success: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MessageCountResponse extends MessageResponse {
|
||||||
|
count: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MessageDeletedResponse extends MessageResponse {
|
||||||
|
deleted: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== Auth ====================
|
||||||
|
|
||||||
export interface LoginResult {
|
export interface LoginResult {
|
||||||
access_token: string;
|
access_token: string;
|
||||||
token_type: string;
|
token_type: string;
|
||||||
@@ -8,6 +25,16 @@ export interface LoginResult {
|
|||||||
permissions: string[];
|
permissions: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CurrentUser {
|
||||||
|
id: number;
|
||||||
|
username: string;
|
||||||
|
role: string;
|
||||||
|
role_label: string;
|
||||||
|
is_active: boolean;
|
||||||
|
remark: string | null;
|
||||||
|
permissions: string[];
|
||||||
|
}
|
||||||
|
|
||||||
export interface UserInfo {
|
export interface UserInfo {
|
||||||
id: number;
|
id: number;
|
||||||
username: string;
|
username: string;
|
||||||
@@ -18,13 +45,127 @@ export interface UserInfo {
|
|||||||
custom_permissions?: string[] | null;
|
custom_permissions?: string[] | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==================== Account ====================
|
||||||
|
|
||||||
|
export interface AccountItem {
|
||||||
|
id: number;
|
||||||
|
username: string;
|
||||||
|
password?: string | null;
|
||||||
|
email?: string | null;
|
||||||
|
email_password?: string | null;
|
||||||
|
tag: string;
|
||||||
|
assigned_to: number | null;
|
||||||
|
assigned_username: string | null;
|
||||||
|
remark: string;
|
||||||
|
created_at: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AssignmentsSummary {
|
||||||
|
support_users: SupportUserItem[];
|
||||||
|
unassigned_count: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SupportUserItem {
|
||||||
|
id: number;
|
||||||
|
username: string;
|
||||||
|
assigned_count: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== Login Task ====================
|
||||||
|
|
||||||
|
export interface LoginTaskItem {
|
||||||
|
id: number;
|
||||||
|
batch_id: string;
|
||||||
|
account_id: number;
|
||||||
|
account_username: string;
|
||||||
|
status: string;
|
||||||
|
cookie: string;
|
||||||
|
message: string;
|
||||||
|
created_by: number;
|
||||||
|
created_at: string | null;
|
||||||
|
finished_at: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BatchLoginResult {
|
||||||
|
batch_id: string;
|
||||||
|
count: number;
|
||||||
|
success: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== Cookie ====================
|
||||||
|
|
||||||
|
export interface CookieItem {
|
||||||
|
id: number;
|
||||||
|
batch_id: string;
|
||||||
|
account_id: number;
|
||||||
|
account_username: string;
|
||||||
|
assigned_to: number | null;
|
||||||
|
assigned_username: string | null;
|
||||||
|
created_at: string | null;
|
||||||
|
cookie: string;
|
||||||
|
cookie_preview: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== Proxy ====================
|
||||||
|
|
||||||
|
export interface ProxyConfig {
|
||||||
|
enabled: boolean;
|
||||||
|
api_url: string;
|
||||||
|
http: string;
|
||||||
|
https: string;
|
||||||
|
whitelist_enabled: boolean;
|
||||||
|
whitelist_uid: string;
|
||||||
|
whitelist_ukey: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProxyTestResult {
|
||||||
|
test_id: string;
|
||||||
|
success: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== Log ====================
|
||||||
|
|
||||||
|
export interface HttpLogEntry {
|
||||||
|
timestamp: string;
|
||||||
|
ts: number;
|
||||||
|
category: string;
|
||||||
|
tag: string;
|
||||||
|
method: string;
|
||||||
|
url: string;
|
||||||
|
proxy: string | null;
|
||||||
|
request: { headers: Record<string, string>; body: string };
|
||||||
|
response: { status_code: number | null; headers: Record<string, string>; body: string };
|
||||||
|
duration_ms: number | null;
|
||||||
|
error: string | null;
|
||||||
|
level: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HttpLogListResult {
|
||||||
|
items: HttpLogEntry[];
|
||||||
|
total: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HttpLogClearResult {
|
||||||
|
success: boolean;
|
||||||
|
cleared: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== Permissions ====================
|
||||||
|
|
||||||
|
export interface PermissionsListResult {
|
||||||
|
permissions: Record<string, string>;
|
||||||
|
role_permissions: Record<string, string[]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== API 定义 ====================
|
||||||
|
|
||||||
export const authApi = {
|
export const authApi = {
|
||||||
login: (username: string, password: string) =>
|
login: (username: string, password: string) =>
|
||||||
api.post<any, LoginResult>('/auth/login', { username, password }),
|
api.post<any, LoginResult>('/auth/login', { username, password }),
|
||||||
|
|
||||||
me: () => api.get<any, any>('/auth/me'),
|
me: () => api.get<any, CurrentUser>('/auth/me'),
|
||||||
|
|
||||||
logout: () => api.post<any, any>('/auth/logout'),
|
logout: () => api.post<any, MessageResponse>('/auth/logout'),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const userApi = {
|
export const userApi = {
|
||||||
@@ -33,55 +174,55 @@ export const userApi = {
|
|||||||
api.post<any, UserInfo>('/users', data),
|
api.post<any, UserInfo>('/users', data),
|
||||||
update: (id: number, data: { password?: string; role?: string; is_active?: boolean; remark?: string; custom_permissions?: string[] | null }) =>
|
update: (id: number, data: { password?: string; role?: string; is_active?: boolean; remark?: string; custom_permissions?: string[] | null }) =>
|
||||||
api.put<any, UserInfo>(`/users/${id}`, data),
|
api.put<any, UserInfo>(`/users/${id}`, data),
|
||||||
delete: (id: number) => api.delete<any, any>(`/users/${id}`),
|
delete: (id: number) => api.delete<any, MessageResponse>(`/users/${id}`),
|
||||||
listPermissions: () => api.get<any, any>('/users/permissions/list'),
|
listPermissions: () => api.get<any, PermissionsListResult>('/users/permissions/list'),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const accountApi = {
|
export const accountApi = {
|
||||||
list: (params?: { assigned_only?: boolean; tag?: string; has_cookie?: boolean }) =>
|
list: (params?: { assigned_only?: boolean; tag?: string; has_cookie?: boolean }) =>
|
||||||
api.get<any, any[]>('/accounts', { params }),
|
api.get<any, AccountItem[]>('/accounts', { params }),
|
||||||
import: (text: string) => api.post<any, any>('/accounts/import', { text }),
|
import: (text: string) => api.post<any, MessageCountResponse>('/accounts/import', { text }),
|
||||||
assign: (id: number, assigned_to: number | null) =>
|
assign: (id: number, assigned_to: number | null) =>
|
||||||
api.put<any, any>(`/accounts/${id}/assign`, { assigned_to }),
|
api.put<any, MessageResponse>(`/accounts/${id}/assign`, { assigned_to }),
|
||||||
batchAssign: (account_ids: number[], assigned_to: number | null) =>
|
batchAssign: (account_ids: number[], assigned_to: number | null) =>
|
||||||
api.post<any, any>('/accounts/batch-assign', { account_ids, assigned_to }),
|
api.post<any, MessageCountResponse>('/accounts/batch-assign', { account_ids, assigned_to }),
|
||||||
assignmentsSummary: () =>
|
assignmentsSummary: () =>
|
||||||
api.get<any, any>('/accounts/assignments/summary'),
|
api.get<any, AssignmentsSummary>('/accounts/assignments/summary'),
|
||||||
setTag: (id: number, tag: string) =>
|
setTag: (id: number, tag: string) =>
|
||||||
api.put<any, any>(`/accounts/${id}/tag`, { tag }),
|
api.put<any, MessageResponse>(`/accounts/${id}/tag`, { tag }),
|
||||||
batchTag: (account_ids: number[], tag: string) =>
|
batchTag: (account_ids: number[], tag: string) =>
|
||||||
api.put<any, any>('/accounts/batch-tag', { account_ids, tag }),
|
api.put<any, MessageCountResponse>('/accounts/batch-tag', { account_ids, tag }),
|
||||||
listTags: () => api.get<any, string[]>('/accounts/tags/list'),
|
listTags: () => api.get<any, string[]>('/accounts/tags/list'),
|
||||||
delete: (id: number) => api.delete<any, any>(`/accounts/${id}`),
|
delete: (id: number) => api.delete<any, MessageResponse>(`/accounts/${id}`),
|
||||||
batchDelete: (account_ids: number[]) =>
|
batchDelete: (account_ids: number[]) =>
|
||||||
api.delete<any, any>('/accounts/batch/delete', { params: { account_ids: account_ids.join(',') } }),
|
api.delete<any, MessageDeletedResponse>('/accounts/batch/delete', { params: { account_ids: account_ids.join(',') } }),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const loginApi = {
|
export const loginApi = {
|
||||||
createBatch: (account_ids: number[], max_geetest_retries?: number, concurrency?: number, max_proxy_retries?: number) =>
|
createBatch: (account_ids: number[], max_geetest_retries?: number, concurrency?: number, max_proxy_retries?: number) =>
|
||||||
api.post<any, any>('/login/batch', { account_ids, max_geetest_retries, concurrency, max_proxy_retries }),
|
api.post<any, BatchLoginResult>('/login/batch', { account_ids, max_geetest_retries, concurrency, max_proxy_retries }),
|
||||||
listTasks: (batch_id?: string) =>
|
listTasks: (batch_id?: string) =>
|
||||||
api.get<any, any[]>('/login/tasks', { params: batch_id ? { batch_id } : {} }),
|
api.get<any, LoginTaskItem[]>('/login/tasks', { params: batch_id ? { batch_id } : {} }),
|
||||||
stop: (batch_id: string) => api.post<any, any>(`/login/stop/${batch_id}`),
|
stop: (batch_id: string) => api.post<any, MessageResponse>(`/login/stop/${batch_id}`),
|
||||||
deleteTask: (id: number) => api.delete<any, any>(`/login/tasks/${id}`),
|
deleteTask: (id: number) => api.delete<any, MessageResponse>(`/login/tasks/${id}`),
|
||||||
deleteTasks: (ids: number[]) => api.delete<any, any>(`/login/tasks`, { params: { task_ids: ids.join(',') } }),
|
deleteTasks: (ids: number[]) => api.delete<any, MessageDeletedResponse>(`/login/tasks`, { params: { task_ids: ids.join(',') } }),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const cookieApi = {
|
export const cookieApi = {
|
||||||
list: () => api.get<any, any[]>('/cookies'),
|
list: () => api.get<any, CookieItem[]>('/cookies'),
|
||||||
exportCsv: (format?: string) => api.get('/cookies/export', { responseType: 'blob', params: format ? { format } : {} }),
|
exportCsv: (format?: string) => api.get('/cookies/export', { responseType: 'blob', params: format ? { format } : {} }),
|
||||||
delete: (id: number) => api.delete<any, any>(`/cookies/${id}`),
|
delete: (id: number) => api.delete<any, MessageResponse>(`/cookies/${id}`),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const proxyApi = {
|
export const proxyApi = {
|
||||||
get: () => api.get<any, any>('/proxy'),
|
get: () => api.get<any, ProxyConfig>('/proxy'),
|
||||||
update: (data: any) => api.put<any, any>('/proxy', data),
|
update: (data: ProxyConfig) => api.put<any, ProxyConfig>('/proxy', data),
|
||||||
test: () => api.post<any, any>('/proxy/test'),
|
test: () => api.post<any, ProxyTestResult>('/proxy/test'),
|
||||||
testWhitelist: () => api.post<any, any>('/proxy/whitelist/test'),
|
testWhitelist: () => api.post<any, ProxyTestResult>('/proxy/whitelist/test'),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const logApi = {
|
export const logApi = {
|
||||||
listHttp: (params?: { limit?: number; offset?: number; category?: string; level?: string; keyword?: string }) =>
|
listHttp: (params?: { limit?: number; offset?: number; category?: string; level?: string; keyword?: string }) =>
|
||||||
api.get<any, { items: any[]; total: number }>('/logs/http', { params }),
|
api.get<any, HttpLogListResult>('/logs/http', { params }),
|
||||||
clearHttp: () => api.delete<any, { success: boolean; cleared: number }>('/logs/http'),
|
clearHttp: () => api.delete<any, HttpLogClearResult>('/logs/http'),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export default function MainLayout({ onLogout }: { onLogout?: () => void }) {
|
|||||||
|
|
||||||
if (!user) return null;
|
if (!user) return null;
|
||||||
|
|
||||||
const menuItems: any[] = [];
|
const menuItems: { key: string; label: string; icon: React.ReactNode }[] = [];
|
||||||
|
|
||||||
// Dashboard - 所有人可见
|
// Dashboard - 所有人可见
|
||||||
menuItems.push({ key: '/', label: '概览', icon: <DashboardOutlined /> });
|
menuItems.push({ key: '/', label: '概览', icon: <DashboardOutlined /> });
|
||||||
|
|||||||
@@ -4,8 +4,9 @@ import {
|
|||||||
Row, Col, Card, Statistic,
|
Row, Col, Card, Statistic,
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
import { ImportOutlined, DeleteOutlined, TagOutlined, FilterOutlined } from '@ant-design/icons';
|
import { ImportOutlined, DeleteOutlined, TagOutlined, FilterOutlined } from '@ant-design/icons';
|
||||||
import { accountApi, userApi } from '../api/modules';
|
import { accountApi, userApi, type AccountItem, type UserInfo } from '../api/modules';
|
||||||
import { getUser, hasPerm } from '../store/auth';
|
import { getUser, hasPerm } from '../store/auth';
|
||||||
|
import { getErrorMessage } from '../utils/error';
|
||||||
|
|
||||||
const { TextArea } = Input;
|
const { TextArea } = Input;
|
||||||
const { Text } = Typography;
|
const { Text } = Typography;
|
||||||
@@ -15,8 +16,8 @@ const TAG_COLORS = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export default function AccountsPage() {
|
export default function AccountsPage() {
|
||||||
const [accounts, setAccounts] = useState<any[]>([]);
|
const [accounts, setAccounts] = useState<AccountItem[]>([]);
|
||||||
const [users, setUsers] = useState<any[]>([]);
|
const [users, setUsers] = useState<UserInfo[]>([]);
|
||||||
const [tags, setTags] = useState<string[]>([]);
|
const [tags, setTags] = useState<string[]>([]);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [importOpen, setImportOpen] = useState(false);
|
const [importOpen, setImportOpen] = useState(false);
|
||||||
@@ -36,12 +37,12 @@ export default function AccountsPage() {
|
|||||||
const loadAccounts = async () => {
|
const loadAccounts = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const params: any = {};
|
const params: { tag?: string } = {};
|
||||||
if (tagFilter) params.tag = tagFilter;
|
if (tagFilter) params.tag = tagFilter;
|
||||||
const data = await accountApi.list(params);
|
const data = await accountApi.list(params);
|
||||||
setAccounts(data);
|
setAccounts(data);
|
||||||
} catch (e: any) {
|
} catch (e: unknown) {
|
||||||
message.error(e.message);
|
message.error(getErrorMessage(e));
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -50,7 +51,7 @@ export default function AccountsPage() {
|
|||||||
const loadUsers = async () => {
|
const loadUsers = async () => {
|
||||||
try {
|
try {
|
||||||
const data = await userApi.list();
|
const data = await userApi.list();
|
||||||
setUsers(data.filter((u: any) => u.role === 'support'));
|
setUsers(data.filter((u) => u.role === 'support'));
|
||||||
} catch {}
|
} catch {}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -92,8 +93,8 @@ export default function AccountsPage() {
|
|||||||
setImportText('');
|
setImportText('');
|
||||||
loadAccounts();
|
loadAccounts();
|
||||||
loadTags();
|
loadTags();
|
||||||
} catch (e: any) {
|
} catch (e: unknown) {
|
||||||
message.error(e.message);
|
message.error(getErrorMessage(e));
|
||||||
} finally {
|
} finally {
|
||||||
setImporting(false);
|
setImporting(false);
|
||||||
}
|
}
|
||||||
@@ -104,8 +105,8 @@ export default function AccountsPage() {
|
|||||||
await accountApi.assign(accountId, assignedTo);
|
await accountApi.assign(accountId, assignedTo);
|
||||||
message.success('已分配');
|
message.success('已分配');
|
||||||
loadAccounts();
|
loadAccounts();
|
||||||
} catch (e: any) {
|
} catch (e: unknown) {
|
||||||
message.error(e.message);
|
message.error(getErrorMessage(e));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -115,8 +116,8 @@ export default function AccountsPage() {
|
|||||||
message.success('标签已更新');
|
message.success('标签已更新');
|
||||||
loadAccounts();
|
loadAccounts();
|
||||||
loadTags();
|
loadTags();
|
||||||
} catch (e: any) {
|
} catch (e: unknown) {
|
||||||
message.error(e.message);
|
message.error(getErrorMessage(e));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -133,8 +134,8 @@ export default function AccountsPage() {
|
|||||||
setSelectedRowKeys([]);
|
setSelectedRowKeys([]);
|
||||||
loadAccounts();
|
loadAccounts();
|
||||||
loadTags();
|
loadTags();
|
||||||
} catch (e: any) {
|
} catch (e: unknown) {
|
||||||
message.error(e.message);
|
message.error(getErrorMessage(e));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -145,8 +146,8 @@ export default function AccountsPage() {
|
|||||||
setSelectedRowKeys((prev) => prev.filter((k) => k !== id));
|
setSelectedRowKeys((prev) => prev.filter((k) => k !== id));
|
||||||
loadAccounts();
|
loadAccounts();
|
||||||
loadTags();
|
loadTags();
|
||||||
} catch (e: any) {
|
} catch (e: unknown) {
|
||||||
message.error(e.message);
|
message.error(getErrorMessage(e));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -161,19 +162,19 @@ export default function AccountsPage() {
|
|||||||
setSelectedRowKeys([]);
|
setSelectedRowKeys([]);
|
||||||
loadAccounts();
|
loadAccounts();
|
||||||
loadTags();
|
loadTags();
|
||||||
} catch (e: any) {
|
} catch (e: unknown) {
|
||||||
message.error(e.message);
|
message.error(getErrorMessage(e));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const columns: any[] = [
|
const columns = [
|
||||||
{ title: 'ID', dataIndex: 'id', width: 60 },
|
{ title: 'ID', dataIndex: 'id', width: 60 },
|
||||||
{ title: '用户名', dataIndex: 'username' },
|
{ title: '用户名', dataIndex: 'username' },
|
||||||
{
|
{
|
||||||
title: '标签',
|
title: '标签',
|
||||||
dataIndex: 'tag',
|
dataIndex: 'tag',
|
||||||
width: 120,
|
width: 120,
|
||||||
render: (tag: string, record: any) => {
|
render: (tag: string, record: AccountItem) => {
|
||||||
if (!tag) {
|
if (!tag) {
|
||||||
if (canImport) {
|
if (canImport) {
|
||||||
return (
|
return (
|
||||||
@@ -224,7 +225,7 @@ export default function AccountsPage() {
|
|||||||
columns.push({
|
columns.push({
|
||||||
title: '分配给',
|
title: '分配给',
|
||||||
dataIndex: 'assigned_username',
|
dataIndex: 'assigned_username',
|
||||||
render: (_: any, record: any) => {
|
render: (_: unknown, record: AccountItem) => {
|
||||||
if (canAssign) {
|
if (canAssign) {
|
||||||
return (
|
return (
|
||||||
<Select
|
<Select
|
||||||
@@ -233,7 +234,7 @@ export default function AccountsPage() {
|
|||||||
placeholder="未分配"
|
placeholder="未分配"
|
||||||
value={record.assigned_to}
|
value={record.assigned_to}
|
||||||
onChange={(val) => handleAssign(record.id, val ?? null)}
|
onChange={(val) => handleAssign(record.id, val ?? null)}
|
||||||
options={users.map((u: any) => ({ value: u.id, label: u.username }))}
|
options={users.map((u) => ({ value: u.id, label: u.username }))}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -245,7 +246,7 @@ export default function AccountsPage() {
|
|||||||
columns.push({
|
columns.push({
|
||||||
title: '操作',
|
title: '操作',
|
||||||
width: 80,
|
width: 80,
|
||||||
render: (_: any, record: any) => (
|
render: (_: unknown, record: AccountItem) => (
|
||||||
<Popconfirm title="确认删除?" onConfirm={() => handleDelete(record.id)}>
|
<Popconfirm title="确认删除?" onConfirm={() => handleDelete(record.id)}>
|
||||||
<Button danger size="small" icon={<DeleteOutlined />}>删除</Button>
|
<Button danger size="small" icon={<DeleteOutlined />}>删除</Button>
|
||||||
</Popconfirm>
|
</Popconfirm>
|
||||||
|
|||||||
@@ -7,31 +7,18 @@ import {
|
|||||||
UserOutlined, CheckCircleOutlined, TeamOutlined,
|
UserOutlined, CheckCircleOutlined, TeamOutlined,
|
||||||
SwapOutlined, ClearOutlined, UsergroupAddOutlined,
|
SwapOutlined, ClearOutlined, UsergroupAddOutlined,
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import { accountApi } from '../api/modules';
|
import { accountApi, type AccountItem, type SupportUserItem } from '../api/modules';
|
||||||
|
import { getErrorMessage } from '../utils/error';
|
||||||
|
|
||||||
const { Text } = Typography;
|
const { Text } = Typography;
|
||||||
|
|
||||||
const STORAGE_KEY_SELECTED_USER = 'assignments_selected_user_id';
|
const STORAGE_KEY_SELECTED_USER = 'assignments_selected_user_id';
|
||||||
|
|
||||||
interface SupportUser {
|
|
||||||
id: number;
|
|
||||||
username: string;
|
|
||||||
assigned_count: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface AccountItem {
|
|
||||||
id: number;
|
|
||||||
username: string;
|
|
||||||
tag: string;
|
|
||||||
assigned_to: number | null;
|
|
||||||
assigned_username: string | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function AssignmentsPage() {
|
export default function AssignmentsPage() {
|
||||||
const { token } = theme.useToken();
|
const { token } = theme.useToken();
|
||||||
const [accounts, setAccounts] = useState<AccountItem[]>([]);
|
const [accounts, setAccounts] = useState<AccountItem[]>([]);
|
||||||
const [supportUsers, setSupportUsers] = useState<SupportUser[]>([]);
|
const [supportUsers, setSupportUsers] = useState<SupportUserItem[]>([]);
|
||||||
const [selectedUser, setSelectedUser] = useState<SupportUser | null>(null);
|
const [selectedUser, setSelectedUser] = useState<SupportUserItem | null>(null);
|
||||||
const [selectedRowKeys, setSelectedRowKeys] = useState<number[]>([]);
|
const [selectedRowKeys, setSelectedRowKeys] = useState<number[]>([]);
|
||||||
const [searchText, setSearchText] = useState('');
|
const [searchText, setSearchText] = useState('');
|
||||||
const [filterTag, setFilterTag] = useState<string | undefined>(undefined);
|
const [filterTag, setFilterTag] = useState<string | undefined>(undefined);
|
||||||
@@ -44,8 +31,8 @@ export default function AssignmentsPage() {
|
|||||||
const data = await accountApi.assignmentsSummary();
|
const data = await accountApi.assignmentsSummary();
|
||||||
setSupportUsers(data.support_users);
|
setSupportUsers(data.support_users);
|
||||||
return data.support_users as SupportUser[];
|
return data.support_users as SupportUser[];
|
||||||
} catch (e: any) {
|
} catch (e: unknown) {
|
||||||
message.error(e.message);
|
message.error(getErrorMessage(e));
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -55,8 +42,8 @@ export default function AssignmentsPage() {
|
|||||||
try {
|
try {
|
||||||
const all = await accountApi.list({ has_cookie: true });
|
const all = await accountApi.list({ has_cookie: true });
|
||||||
setAccounts(all);
|
setAccounts(all);
|
||||||
} catch (e: any) {
|
} catch (e: unknown) {
|
||||||
message.error(e.message);
|
message.error(getErrorMessage(e));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -67,7 +54,7 @@ export default function AssignmentsPage() {
|
|||||||
const users = await loadSummary();
|
const users = await loadSummary();
|
||||||
if (users.length > 0) {
|
if (users.length > 0) {
|
||||||
const savedId = localStorage.getItem(STORAGE_KEY_SELECTED_USER);
|
const savedId = localStorage.getItem(STORAGE_KEY_SELECTED_USER);
|
||||||
const savedUser = savedId ? users.find((u: SupportUser) => u.id === Number(savedId)) : null;
|
const savedUser = savedId ? users.find((u: SupportUserItemItem) => u.id === Number(savedId)) : null;
|
||||||
setSelectedUser(savedUser || users[0]);
|
setSelectedUser(savedUser || users[0]);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -105,7 +92,7 @@ export default function AssignmentsPage() {
|
|||||||
}, [activeTab, unassignedAccounts, assignedAccounts, searchText, filterTag]);
|
}, [activeTab, unassignedAccounts, assignedAccounts, searchText, filterTag]);
|
||||||
|
|
||||||
// 切换客服
|
// 切换客服
|
||||||
const handleSelectUser = (user: SupportUser) => {
|
const handleSelectUser = (user: SupportUserItem) => {
|
||||||
setSelectedUser(user);
|
setSelectedUser(user);
|
||||||
setSelectedRowKeys([]);
|
setSelectedRowKeys([]);
|
||||||
setActiveTab('unassigned');
|
setActiveTab('unassigned');
|
||||||
@@ -131,10 +118,10 @@ export default function AssignmentsPage() {
|
|||||||
setSelectedRowKeys([]);
|
setSelectedRowKeys([]);
|
||||||
await loadAccounts();
|
await loadAccounts();
|
||||||
const users = await loadSummary();
|
const users = await loadSummary();
|
||||||
const refreshed = users.find((u: SupportUser) => u.id === selectedUser.id);
|
const refreshed = users.find((u: SupportUserItem) => u.id === selectedUser.id);
|
||||||
if (refreshed) setSelectedUser(refreshed);
|
if (refreshed) setSelectedUser(refreshed);
|
||||||
} catch (e: any) {
|
} catch (e: unknown) {
|
||||||
message.error(e.message);
|
message.error(getErrorMessage(e));
|
||||||
} finally {
|
} finally {
|
||||||
setAssigning(false);
|
setAssigning(false);
|
||||||
}
|
}
|
||||||
@@ -153,10 +140,10 @@ export default function AssignmentsPage() {
|
|||||||
setSelectedRowKeys([]);
|
setSelectedRowKeys([]);
|
||||||
await loadAccounts();
|
await loadAccounts();
|
||||||
const users = await loadSummary();
|
const users = await loadSummary();
|
||||||
const refreshed = users.find((u: SupportUser) => u.id === selectedUser?.id);
|
const refreshed = users.find((u: SupportUserItem) => u.id === selectedUser?.id);
|
||||||
if (refreshed) setSelectedUser(refreshed);
|
if (refreshed) setSelectedUser(refreshed);
|
||||||
} catch (e: any) {
|
} catch (e: unknown) {
|
||||||
message.error(e.message);
|
message.error(getErrorMessage(e));
|
||||||
} finally {
|
} finally {
|
||||||
setAssigning(false);
|
setAssigning(false);
|
||||||
}
|
}
|
||||||
@@ -185,10 +172,10 @@ export default function AssignmentsPage() {
|
|||||||
message.success(`已将 ${ids.length} 个账号分配给 ${selectedUser.username}`);
|
message.success(`已将 ${ids.length} 个账号分配给 ${selectedUser.username}`);
|
||||||
await loadAccounts();
|
await loadAccounts();
|
||||||
const users = await loadSummary();
|
const users = await loadSummary();
|
||||||
const refreshed = users.find((u: SupportUser) => u.id === selectedUser.id);
|
const refreshed = users.find((u: SupportUserItem) => u.id === selectedUser.id);
|
||||||
if (refreshed) setSelectedUser(refreshed);
|
if (refreshed) setSelectedUser(refreshed);
|
||||||
} catch (e: any) {
|
} catch (e: unknown) {
|
||||||
message.error(e.message);
|
message.error(getErrorMessage(e));
|
||||||
} finally {
|
} finally {
|
||||||
setAssigning(false);
|
setAssigning(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { Table, Button, Card, Row, Col, Statistic, message, Tag, Popconfirm, Space, Typography, Input, theme, Dropdown } from 'antd';
|
import { Table, Button, Card, Row, Col, Statistic, message, Tag, Popconfirm, Space, Typography, Input, theme, Dropdown } from 'antd';
|
||||||
import { DownloadOutlined, DeleteOutlined, CopyOutlined, SearchOutlined } from '@ant-design/icons';
|
import { DownloadOutlined, DeleteOutlined, CopyOutlined, SearchOutlined } from '@ant-design/icons';
|
||||||
import { cookieApi } from '../api/modules';
|
import { cookieApi, type CookieItem } from '../api/modules';
|
||||||
import { getUser, hasPerm } from '../store/auth';
|
import { getUser, hasPerm } from '../store/auth';
|
||||||
import { formatTime } from '../utils/time';
|
import { formatTime } from '../utils/time';
|
||||||
|
import { getErrorMessage } from '../utils/error';
|
||||||
|
|
||||||
const { Text } = Typography;
|
const { Text } = Typography;
|
||||||
|
|
||||||
export default function CookiePage() {
|
export default function CookiePage() {
|
||||||
const { token } = theme.useToken();
|
const { token } = theme.useToken();
|
||||||
const [cookies, setCookies] = useState<any[]>([]);
|
const [cookies, setCookies] = useState<CookieItem[]>([]);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
|
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
|
||||||
const [searchText, setSearchText] = useState('');
|
const [searchText, setSearchText] = useState('');
|
||||||
@@ -23,8 +24,8 @@ export default function CookiePage() {
|
|||||||
try {
|
try {
|
||||||
const data = await cookieApi.list();
|
const data = await cookieApi.list();
|
||||||
setCookies(data);
|
setCookies(data);
|
||||||
} catch (e: any) {
|
} catch (e: unknown) {
|
||||||
message.error(e.message);
|
message.error(getErrorMessage(e));
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -37,15 +38,15 @@ export default function CookiePage() {
|
|||||||
const handleExport = async (format: string = 'csv') => {
|
const handleExport = async (format: string = 'csv') => {
|
||||||
try {
|
try {
|
||||||
const blob = await cookieApi.exportCsv(format);
|
const blob = await cookieApi.exportCsv(format);
|
||||||
const url = URL.createObjectURL(blob instanceof Blob ? blob : new Blob([blob as any]));
|
const url = URL.createObjectURL(blob instanceof Blob ? blob : new Blob([blob]));
|
||||||
const a = document.createElement('a');
|
const a = document.createElement('a');
|
||||||
a.href = url;
|
a.href = url;
|
||||||
a.download = format === 'custom' ? 'cookies_custom.txt' : 'cookies.csv';
|
a.download = format === 'custom' ? 'cookies_custom.txt' : 'cookies.csv';
|
||||||
a.click();
|
a.click();
|
||||||
URL.revokeObjectURL(url);
|
URL.revokeObjectURL(url);
|
||||||
message.success('已导出');
|
message.success('已导出');
|
||||||
} catch (e: any) {
|
} catch (e: unknown) {
|
||||||
message.error(e.message);
|
message.error(getErrorMessage(e));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -83,8 +84,8 @@ export default function CookiePage() {
|
|||||||
message.success('已删除');
|
message.success('已删除');
|
||||||
setSelectedRowKeys((prev) => prev.filter((k) => k !== id));
|
setSelectedRowKeys((prev) => prev.filter((k) => k !== id));
|
||||||
loadCookies();
|
loadCookies();
|
||||||
} catch (e: any) {
|
} catch (e: unknown) {
|
||||||
message.error(e.message);
|
message.error(getErrorMessage(e));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -98,8 +99,8 @@ export default function CookiePage() {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const columns: any[] = [
|
const columns = [
|
||||||
{ title: 'ID', dataIndex: 'id', width: 60, align: 'center' },
|
{ title: 'ID', dataIndex: 'id', width: 60, align: 'center' as const },
|
||||||
{
|
{
|
||||||
title: '账号',
|
title: '账号',
|
||||||
dataIndex: 'account_username',
|
dataIndex: 'account_username',
|
||||||
@@ -110,7 +111,7 @@ export default function CookiePage() {
|
|||||||
title: '分配',
|
title: '分配',
|
||||||
dataIndex: 'assigned_username',
|
dataIndex: 'assigned_username',
|
||||||
width: 100,
|
width: 100,
|
||||||
align: 'center',
|
align: 'center' as const,
|
||||||
render: (name: string | null) =>
|
render: (name: string | null) =>
|
||||||
name ? <Tag color="green">{name}</Tag> : <Text type="secondary" style={{ fontSize: 12 }}>未分配</Text>,
|
name ? <Tag color="green">{name}</Tag> : <Text type="secondary" style={{ fontSize: 12 }}>未分配</Text>,
|
||||||
},
|
},
|
||||||
@@ -127,15 +128,15 @@ export default function CookiePage() {
|
|||||||
title: '时间',
|
title: '时间',
|
||||||
dataIndex: 'created_at',
|
dataIndex: 'created_at',
|
||||||
width: 180,
|
width: 180,
|
||||||
align: 'center',
|
align: 'center' as const,
|
||||||
render: (val: string) => formatTime(val),
|
render: (val: string) => formatTime(val),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '操作',
|
title: '操作',
|
||||||
width: 130,
|
width: 130,
|
||||||
align: 'center',
|
align: 'center' as const,
|
||||||
fixed: 'right',
|
fixed: 'right' as const,
|
||||||
render: (_: any, record: any) => (
|
render: (_: unknown, record: CookieItem) => (
|
||||||
<Space size={4}>
|
<Space size={4}>
|
||||||
<Button
|
<Button
|
||||||
size="small"
|
size="small"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Card, Col, Row, Statistic, theme } from 'antd';
|
import { Card, Col, Row, Statistic, theme } from 'antd';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { accountApi, loginApi } from '../api/modules';
|
import { accountApi, loginApi, type LoginTaskItem } from '../api/modules';
|
||||||
|
|
||||||
export default function DashboardPage() {
|
export default function DashboardPage() {
|
||||||
const { token } = theme.useToken();
|
const { token } = theme.useToken();
|
||||||
@@ -12,8 +12,8 @@ export default function DashboardPage() {
|
|||||||
setStats({
|
setStats({
|
||||||
accounts: accounts.length,
|
accounts: accounts.length,
|
||||||
tasks: tasks.length,
|
tasks: tasks.length,
|
||||||
success: tasks.filter((t: any) => t.status === 'success').length,
|
success: tasks.filter((t: LoginTaskItem) => t.status === 'success').length,
|
||||||
failed: tasks.filter((t: any) => ['failed', 'error'].includes(t.status)).length,
|
failed: tasks.filter((t: LoginTaskItem) => ['failed', 'error'].includes(t.status)).length,
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch(() => {});
|
.catch(() => {});
|
||||||
|
|||||||
@@ -6,26 +6,12 @@ import {
|
|||||||
import {
|
import {
|
||||||
ReloadOutlined, DeleteOutlined, SearchOutlined, EyeOutlined,
|
ReloadOutlined, DeleteOutlined, SearchOutlined, EyeOutlined,
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import { logApi } from '../api/modules';
|
import { logApi, type HttpLogEntry } from '../api/modules';
|
||||||
import { hasPerm, getUser } from '../store/auth';
|
import { hasPerm, getUser } from '../store/auth';
|
||||||
|
import { getErrorMessage } from '../utils/error';
|
||||||
|
|
||||||
const { Text, Paragraph } = Typography;
|
const { Text, Paragraph } = Typography;
|
||||||
|
|
||||||
interface HttpLogEntry {
|
|
||||||
timestamp: string;
|
|
||||||
ts: number;
|
|
||||||
category: string;
|
|
||||||
tag: string;
|
|
||||||
method: string;
|
|
||||||
url: string;
|
|
||||||
proxy: string | null;
|
|
||||||
request: { headers: Record<string, string>; body: string };
|
|
||||||
response: { status_code: number | null; headers: Record<string, string>; body: string };
|
|
||||||
duration_ms: number | null;
|
|
||||||
error: string | null;
|
|
||||||
level: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const CATEGORY_LABELS: Record<string, string> = {
|
const CATEGORY_LABELS: Record<string, string> = {
|
||||||
douyu_login: '斗鱼登录',
|
douyu_login: '斗鱼登录',
|
||||||
geetest: '极验',
|
geetest: '极验',
|
||||||
@@ -63,8 +49,8 @@ export default function HttpLogsPage() {
|
|||||||
});
|
});
|
||||||
setLogs(res.items || []);
|
setLogs(res.items || []);
|
||||||
setTotal(res.total || 0);
|
setTotal(res.total || 0);
|
||||||
} catch (e: any) {
|
} catch (e: unknown) {
|
||||||
message.error(e.message || '获取日志失败');
|
message.error(getErrorMessage(e) || '获取日志失败');
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -87,8 +73,8 @@ export default function HttpLogsPage() {
|
|||||||
const res = await logApi.clearHttp();
|
const res = await logApi.clearHttp();
|
||||||
message.success(`已清空 ${res.cleared} 条日志`);
|
message.success(`已清空 ${res.cleared} 条日志`);
|
||||||
fetchLogs();
|
fetchLogs();
|
||||||
} catch (e: any) {
|
} catch (e: unknown) {
|
||||||
message.error(e.message || '清空失败');
|
message.error(getErrorMessage(e) || '清空失败');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -166,7 +152,7 @@ export default function HttpLogsPage() {
|
|||||||
{
|
{
|
||||||
title: '操作',
|
title: '操作',
|
||||||
width: 60,
|
width: 60,
|
||||||
render: (_: any, record: HttpLogEntry) => (
|
render: (_: unknown, record: HttpLogEntry) => (
|
||||||
<Button type="link" size="small" icon={<EyeOutlined />} onClick={() => setDetailEntry(record)} />
|
<Button type="link" size="small" icon={<EyeOutlined />} onClick={() => setDetailEntry(record)} />
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { LockOutlined, UserOutlined } from '@ant-design/icons';
|
|||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { authApi } from '../api/modules';
|
import { authApi } from '../api/modules';
|
||||||
import { setAuth, type AuthUser } from '../store/auth';
|
import { setAuth, type AuthUser } from '../store/auth';
|
||||||
|
import { getErrorMessage } from '../utils/error';
|
||||||
import { useTheme } from '../store/theme';
|
import { useTheme } from '../store/theme';
|
||||||
|
|
||||||
const { Title } = Typography;
|
const { Title } = Typography;
|
||||||
@@ -27,8 +28,8 @@ export default function LoginPage({ onLogin }: { onLogin?: () => void }) {
|
|||||||
message.success('登录成功');
|
message.success('登录成功');
|
||||||
onLogin?.(); // 触发 App 重渲染
|
onLogin?.(); // 触发 App 重渲染
|
||||||
navigate('/', { replace: true });
|
navigate('/', { replace: true });
|
||||||
} catch (e: any) {
|
} catch (e: unknown) {
|
||||||
message.error(e.message || '登录失败');
|
message.error(getErrorMessage(e) || '登录失败');
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,9 +3,10 @@ import {
|
|||||||
Table, Button, Select, message, Tag, Space, Spin, InputNumber, Tooltip, Popconfirm, theme,
|
Table, Button, Select, message, Tag, Space, Spin, InputNumber, Tooltip, Popconfirm, theme,
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
import { PlayCircleOutlined, StopOutlined, FilterOutlined, ThunderboltOutlined, ReloadOutlined, DownOutlined, UpOutlined, DeleteOutlined, SwapOutlined } from '@ant-design/icons';
|
import { PlayCircleOutlined, StopOutlined, FilterOutlined, ThunderboltOutlined, ReloadOutlined, DownOutlined, UpOutlined, DeleteOutlined, SwapOutlined } from '@ant-design/icons';
|
||||||
import { accountApi, loginApi } from '../api/modules';
|
import { accountApi, loginApi, type AccountItem, type LoginTaskItem } from '../api/modules';
|
||||||
import { getUser, hasPerm } from '../store/auth';
|
import { getUser, hasPerm } from '../store/auth';
|
||||||
import { formatTime } from '../utils/time';
|
import { formatTime } from '../utils/time';
|
||||||
|
import { getErrorMessage } from '../utils/error';
|
||||||
|
|
||||||
const STATUS_COLORS: Record<string, string> = {
|
const STATUS_COLORS: Record<string, string> = {
|
||||||
pending: 'default',
|
pending: 'default',
|
||||||
@@ -23,10 +24,15 @@ const STATUS_LABELS: Record<string, string> = {
|
|||||||
error: '异常',
|
error: '异常',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface SelectGroupOption {
|
||||||
|
label: string;
|
||||||
|
options: { value: number; label: string }[];
|
||||||
|
}
|
||||||
|
|
||||||
export default function LoginTasksPage() {
|
export default function LoginTasksPage() {
|
||||||
const [accounts, setAccounts] = useState<any[]>([]);
|
const [accounts, setAccounts] = useState<AccountItem[]>([]);
|
||||||
const [selectedIds, setSelectedIds] = useState<number[]>([]);
|
const [selectedIds, setSelectedIds] = useState<number[]>([]);
|
||||||
const [tasks, setTasks] = useState<any[]>([]);
|
const [tasks, setTasks] = useState<LoginTaskItem[]>([]);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [batchId, setBatchId] = useState<string | null>(null);
|
const [batchId, setBatchId] = useState<string | null>(null);
|
||||||
const [logs, setLogs] = useState<{ level: string; message: string }[]>([]);
|
const [logs, setLogs] = useState<{ level: string; message: string }[]>([]);
|
||||||
@@ -67,8 +73,8 @@ export default function LoginTasksPage() {
|
|||||||
try {
|
try {
|
||||||
const data = await accountApi.list();
|
const data = await accountApi.list();
|
||||||
setAccounts(data);
|
setAccounts(data);
|
||||||
} catch (e: any) {
|
} catch (e: unknown) {
|
||||||
message.error(e.message);
|
message.error(getErrorMessage(e));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -158,8 +164,8 @@ export default function LoginTasksPage() {
|
|||||||
ws.onerror = () => {
|
ws.onerror = () => {
|
||||||
setWsConnected(false);
|
setWsConnected(false);
|
||||||
};
|
};
|
||||||
} catch (e: any) {
|
} catch (e: unknown) {
|
||||||
message.error(e.message);
|
message.error(getErrorMessage(e));
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -191,8 +197,8 @@ export default function LoginTasksPage() {
|
|||||||
try {
|
try {
|
||||||
await loginApi.stop(batchId);
|
await loginApi.stop(batchId);
|
||||||
message.success('已发送停止信号');
|
message.success('已发送停止信号');
|
||||||
} catch (e: any) {
|
} catch (e: unknown) {
|
||||||
message.error(e.message);
|
message.error(getErrorMessage(e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -203,8 +209,8 @@ export default function LoginTasksPage() {
|
|||||||
message.success('已删除');
|
message.success('已删除');
|
||||||
loadTasks();
|
loadTasks();
|
||||||
setSelectedRowKeys((prev) => prev.filter((k) => k !== taskId));
|
setSelectedRowKeys((prev) => prev.filter((k) => k !== taskId));
|
||||||
} catch (e: any) {
|
} catch (e: unknown) {
|
||||||
message.error(e.message);
|
message.error(getErrorMessage(e));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -218,8 +224,8 @@ export default function LoginTasksPage() {
|
|||||||
message.success(`已删除 ${selectedRowKeys.length} 个任务`);
|
message.success(`已删除 ${selectedRowKeys.length} 个任务`);
|
||||||
setSelectedRowKeys([]);
|
setSelectedRowKeys([]);
|
||||||
loadTasks();
|
loadTasks();
|
||||||
} catch (e: any) {
|
} catch (e: unknown) {
|
||||||
message.error(e.message);
|
message.error(getErrorMessage(e));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -239,7 +245,7 @@ export default function LoginTasksPage() {
|
|||||||
{
|
{
|
||||||
title: '操作',
|
title: '操作',
|
||||||
width: 120,
|
width: 120,
|
||||||
render: (_: any, record: any) => (
|
render: (_: unknown, record: LoginTaskItem) => (
|
||||||
<Space size={4}>
|
<Space size={4}>
|
||||||
{['failed', 'error'].includes(record.status) && !wsConnected && (
|
{['failed', 'error'].includes(record.status) && !wsConnected && (
|
||||||
<Button
|
<Button
|
||||||
@@ -298,7 +304,7 @@ export default function LoginTasksPage() {
|
|||||||
noTag.push({ value: a.id, label: a.username });
|
noTag.push({ value: a.id, label: a.username });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const result: any[] = [];
|
const result: SelectGroupOption[] = [];
|
||||||
Object.keys(grouped).sort().forEach((tag) => {
|
Object.keys(grouped).sort().forEach((tag) => {
|
||||||
result.push({ label: tag, options: grouped[tag] });
|
result.push({ label: tag, options: grouped[tag] });
|
||||||
});
|
});
|
||||||
@@ -312,7 +318,7 @@ export default function LoginTasksPage() {
|
|||||||
size="small"
|
size="small"
|
||||||
filterOption={(input, option) => {
|
filterOption={(input, option) => {
|
||||||
if (!option) return false;
|
if (!option) return false;
|
||||||
const label = (option as any).label as string || '';
|
const label = (option as { label?: string }).label || '';
|
||||||
return label.toLowerCase().includes(input.toLowerCase());
|
return label.toLowerCase().includes(input.toLowerCase());
|
||||||
}}
|
}}
|
||||||
dropdownRender={(menu) => (
|
dropdownRender={(menu) => (
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useEffect, useState, useRef } from 'react';
|
import { useEffect, useState, useRef } from 'react';
|
||||||
import { Form, Input, Switch, Button, Card, message, Row, Col, theme } from 'antd';
|
import { Form, Input, Switch, Button, Card, message, Row, Col, theme } from 'antd';
|
||||||
import { proxyApi } from '../api/modules';
|
import { proxyApi, type ProxyConfig } from '../api/modules';
|
||||||
|
import { getErrorMessage } from '../utils/error';
|
||||||
|
|
||||||
const WS_BASE = `${window.location.protocol === 'https:' ? 'wss' : 'ws'}://${window.location.host}`;
|
const WS_BASE = `${window.location.protocol === 'https:' ? 'wss' : 'ws'}://${window.location.host}`;
|
||||||
|
|
||||||
@@ -26,8 +27,8 @@ export default function ProxyPage() {
|
|||||||
whitelist_uid: data.whitelist_uid ?? '',
|
whitelist_uid: data.whitelist_uid ?? '',
|
||||||
whitelist_ukey: data.whitelist_ukey ?? '',
|
whitelist_ukey: data.whitelist_ukey ?? '',
|
||||||
});
|
});
|
||||||
} catch (e: any) {
|
} catch (e: unknown) {
|
||||||
message.error(e.message);
|
message.error(getErrorMessage(e));
|
||||||
} finally {
|
} finally {
|
||||||
setConfigLoaded(true);
|
setConfigLoaded(true);
|
||||||
}
|
}
|
||||||
@@ -66,10 +67,10 @@ export default function ProxyPage() {
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const values = await form.validateFields();
|
const values = await form.validateFields();
|
||||||
await proxyApi.update(values);
|
await proxyApi.update(values as ProxyConfig);
|
||||||
message.success('已保存');
|
message.success('已保存');
|
||||||
} catch (e: any) {
|
} catch (e: unknown) {
|
||||||
message.error(e.message);
|
message.error(getErrorMessage(e));
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -81,8 +82,8 @@ export default function ProxyPage() {
|
|||||||
try {
|
try {
|
||||||
const result = await proxyApi.test();
|
const result = await proxyApi.test();
|
||||||
if (result.test_id) connectWs(result.test_id);
|
if (result.test_id) connectWs(result.test_id);
|
||||||
} catch (e: any) {
|
} catch (e: unknown) {
|
||||||
message.error(e.message);
|
message.error(getErrorMessage(e));
|
||||||
setTesting(false);
|
setTesting(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -93,8 +94,8 @@ export default function ProxyPage() {
|
|||||||
try {
|
try {
|
||||||
const result = await proxyApi.testWhitelist();
|
const result = await proxyApi.testWhitelist();
|
||||||
if (result.test_id) connectWs(result.test_id);
|
if (result.test_id) connectWs(result.test_id);
|
||||||
} catch (e: any) {
|
} catch (e: unknown) {
|
||||||
message.error(e.message);
|
message.error(getErrorMessage(e));
|
||||||
setTestingWl(false);
|
setTestingWl(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
import { PlusOutlined, EditOutlined, DeleteOutlined, SafetyOutlined } from '@ant-design/icons';
|
import { PlusOutlined, EditOutlined, DeleteOutlined, SafetyOutlined } from '@ant-design/icons';
|
||||||
import { userApi, type UserInfo } from '../api/modules';
|
import { userApi, type UserInfo } from '../api/modules';
|
||||||
import { getUser, hasPerm } from '../store/auth';
|
import { getUser, hasPerm } from '../store/auth';
|
||||||
|
import { getErrorMessage } from '../utils/error';
|
||||||
|
|
||||||
const ROLE_OPTIONS = [
|
const ROLE_OPTIONS = [
|
||||||
{ value: 'super_admin', label: '超级管理员' },
|
{ value: 'super_admin', label: '超级管理员' },
|
||||||
@@ -57,8 +58,8 @@ export default function UsersPage() {
|
|||||||
try {
|
try {
|
||||||
const data = await userApi.list();
|
const data = await userApi.list();
|
||||||
setUsers(data);
|
setUsers(data);
|
||||||
} catch (e: any) {
|
} catch (e: unknown) {
|
||||||
message.error(e.message);
|
message.error(getErrorMessage(e));
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -89,7 +90,7 @@ export default function UsersPage() {
|
|||||||
try {
|
try {
|
||||||
const values = await form.validateFields();
|
const values = await form.validateFields();
|
||||||
if (editing) {
|
if (editing) {
|
||||||
const updateData: any = { role: values.role, remark: values.remark };
|
const updateData: { role: string; remark: string; password?: string } = { role: values.role, remark: values.remark };
|
||||||
if (values.password) updateData.password = values.password;
|
if (values.password) updateData.password = values.password;
|
||||||
await userApi.update(editing.id, updateData);
|
await userApi.update(editing.id, updateData);
|
||||||
message.success('已更新');
|
message.success('已更新');
|
||||||
@@ -99,8 +100,8 @@ export default function UsersPage() {
|
|||||||
}
|
}
|
||||||
setModalOpen(false);
|
setModalOpen(false);
|
||||||
loadUsers();
|
loadUsers();
|
||||||
} catch (e: any) {
|
} catch (e: unknown) {
|
||||||
message.error(e.message);
|
message.error(getErrorMessage(e));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -109,8 +110,8 @@ export default function UsersPage() {
|
|||||||
await userApi.delete(id);
|
await userApi.delete(id);
|
||||||
message.success('已删除');
|
message.success('已删除');
|
||||||
loadUsers();
|
loadUsers();
|
||||||
} catch (e: any) {
|
} catch (e: unknown) {
|
||||||
message.error(e.message);
|
message.error(getErrorMessage(e));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -131,8 +132,8 @@ export default function UsersPage() {
|
|||||||
setUseCustom(false);
|
setUseCustom(false);
|
||||||
setSelectedPerms(data.role_permissions?.[user.role] || []);
|
setSelectedPerms(data.role_permissions?.[user.role] || []);
|
||||||
}
|
}
|
||||||
} catch (e: any) {
|
} catch (e: unknown) {
|
||||||
message.error(e.message);
|
message.error(getErrorMessage(e));
|
||||||
} finally {
|
} finally {
|
||||||
setPermLoading(false);
|
setPermLoading(false);
|
||||||
}
|
}
|
||||||
@@ -148,8 +149,8 @@ export default function UsersPage() {
|
|||||||
message.success('权限已更新');
|
message.success('权限已更新');
|
||||||
setPermModalOpen(false);
|
setPermModalOpen(false);
|
||||||
loadUsers();
|
loadUsers();
|
||||||
} catch (e: any) {
|
} catch (e: unknown) {
|
||||||
message.error(e.message);
|
message.error(getErrorMessage(e));
|
||||||
} finally {
|
} finally {
|
||||||
setPermLoading(false);
|
setPermLoading(false);
|
||||||
}
|
}
|
||||||
@@ -175,7 +176,7 @@ export default function UsersPage() {
|
|||||||
title: '权限模式',
|
title: '权限模式',
|
||||||
dataIndex: 'custom_permissions',
|
dataIndex: 'custom_permissions',
|
||||||
width: 100,
|
width: 100,
|
||||||
render: (_: any, record: UserInfo) =>
|
render: (_: unknown, record: UserInfo) =>
|
||||||
record.custom_permissions !== null && record.custom_permissions !== undefined
|
record.custom_permissions !== null && record.custom_permissions !== undefined
|
||||||
? <Tag color="orange">自定义</Tag>
|
? <Tag color="orange">自定义</Tag>
|
||||||
: <Tag>角色默认</Tag>,
|
: <Tag>角色默认</Tag>,
|
||||||
@@ -190,7 +191,7 @@ export default function UsersPage() {
|
|||||||
{
|
{
|
||||||
title: '操作',
|
title: '操作',
|
||||||
width: 200,
|
width: 200,
|
||||||
render: (_: any, record: UserInfo) => (
|
render: (_: unknown, record: UserInfo) => (
|
||||||
<Space>
|
<Space>
|
||||||
<Button size="small" icon={<EditOutlined />} onClick={() => handleEdit(record)}>编辑</Button>
|
<Button size="small" icon={<EditOutlined />} onClick={() => handleEdit(record)}>编辑</Button>
|
||||||
{canAssignPerm && (
|
{canAssignPerm && (
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
/** 从 unknown 类型的 catch 错误中安全提取 message */
|
||||||
|
export function getErrorMessage(e: unknown): string {
|
||||||
|
if (e instanceof Error) return e.message;
|
||||||
|
if (typeof e === 'string') return e;
|
||||||
|
return '未知错误';
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user