mirror of
https://github.com/yaklang/yaklang-chrome-extension.git
synced 2026-09-27 05:31:53 +08:00
use indexedDB manager proxySwitch
This commit is contained in:
@@ -0,0 +1,166 @@
|
||||
import React from 'react';
|
||||
import { Modal, Button, Space, Descriptions, Tabs, Card, Typography, message } from 'antd';
|
||||
import { ProxyLog } from '@/types/proxy';
|
||||
|
||||
const { Text, Paragraph } = Typography;
|
||||
|
||||
interface LogDetailProps {
|
||||
log: ProxyLog | null;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export const LogDetail: React.FC<LogDetailProps> = ({ log, onClose }) => {
|
||||
const renderHttpRequest = (log: ProxyLog) => {
|
||||
if (!log) return '';
|
||||
|
||||
// 构建请求头
|
||||
const headers = Object.entries(log.requestHeaders || {})
|
||||
.map(([key, value]) => `${key}: ${value}`)
|
||||
.join('\n');
|
||||
|
||||
// 构建完整的 HTTP 请求
|
||||
return `${log.method || 'GET'} ${log.url} ${log.protocol || 'HTTP/1.1'}
|
||||
${headers}
|
||||
|
||||
${log.requestBody || ''}`;
|
||||
};
|
||||
|
||||
const renderHttpResponse = (log: ProxyLog) => {
|
||||
if (!log || !log.responseHeaders) return '';
|
||||
|
||||
// 构建响应头
|
||||
const headers = Object.entries(log.responseHeaders)
|
||||
.map(([key, value]) => `${key}: ${value}`)
|
||||
.join('\n');
|
||||
|
||||
// 构建完整的 HTTP 响应
|
||||
return `HTTP/1.1 ${log.status === 'success' ? '200 OK' : '500 Error'}
|
||||
${headers}
|
||||
|
||||
${log.responseBody || ''}`;
|
||||
};
|
||||
|
||||
const handleCopyRaw = () => {
|
||||
if (!log) return;
|
||||
navigator.clipboard.writeText(renderHttpRequest(log))
|
||||
.then(() => message.success('已复制到剪贴板'))
|
||||
.catch(() => message.error('复制失败'));
|
||||
};
|
||||
|
||||
const formatProxyInfo = (log: ProxyLog) => {
|
||||
if (!log) return '';
|
||||
return `${log.proxyName}${log.host ? ` - ${log.host}:${log.port}` : ''}`;
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title="请求详情"
|
||||
open={!!log}
|
||||
onCancel={onClose}
|
||||
width={800}
|
||||
footer={[
|
||||
<Button key="copy" onClick={handleCopyRaw}>
|
||||
复制原始数据
|
||||
</Button>,
|
||||
<Button key="close" onClick={onClose}>
|
||||
关闭
|
||||
</Button>
|
||||
]}
|
||||
>
|
||||
{log && (
|
||||
<Space direction="vertical" style={{ width: '100%' }}>
|
||||
<Descriptions bordered column={2}>
|
||||
<Descriptions.Item label="请求时间">
|
||||
{new Date(log.timestamp).toLocaleString()}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="代理服务器">
|
||||
{formatProxyInfo(log)}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="状态">
|
||||
<Text type={log.status === 'success' ? 'success' : 'danger'}>
|
||||
{log.status === 'success' ? '成功' : '失败'}
|
||||
</Text>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="响应时间">
|
||||
{log.timing?.duration}ms
|
||||
</Descriptions.Item>
|
||||
{log.errorMessage && (
|
||||
<Descriptions.Item label="错误信息" span={2}>
|
||||
<Text type="danger">{log.errorMessage}</Text>
|
||||
</Descriptions.Item>
|
||||
)}
|
||||
</Descriptions>
|
||||
|
||||
<Tabs
|
||||
items={[
|
||||
{
|
||||
key: 'request',
|
||||
label: '请求数据',
|
||||
children: (
|
||||
<Card size="small">
|
||||
<pre style={{
|
||||
background: '#f5f5f5',
|
||||
padding: 16,
|
||||
borderRadius: 4,
|
||||
maxHeight: 400,
|
||||
overflow: 'auto',
|
||||
margin: 0
|
||||
}}>
|
||||
{renderHttpRequest(log)}
|
||||
</pre>
|
||||
</Card>
|
||||
)
|
||||
},
|
||||
{
|
||||
key: 'response',
|
||||
label: '响应数据',
|
||||
children: (
|
||||
<Card size="small">
|
||||
<pre style={{
|
||||
background: '#f5f5f5',
|
||||
padding: 16,
|
||||
borderRadius: 4,
|
||||
maxHeight: 400,
|
||||
overflow: 'auto',
|
||||
margin: 0
|
||||
}}>
|
||||
{renderHttpResponse(log)}
|
||||
</pre>
|
||||
</Card>
|
||||
)
|
||||
},
|
||||
{
|
||||
key: 'timing',
|
||||
label: '性能数据',
|
||||
children: (
|
||||
<Card size="small">
|
||||
<Descriptions bordered>
|
||||
<Descriptions.Item label="开始时间">
|
||||
{new Date(log.timing?.startTime || 0).toLocaleString()}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="结束时间">
|
||||
{new Date(log.timing?.endTime || 0).toLocaleString()}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="总耗时">
|
||||
{log.timing?.duration}ms
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="IP地址">
|
||||
{log.ip || '-'}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="协议">
|
||||
{log.protocol || '-'}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="缓存">
|
||||
{log.fromCache ? '是' : '否'}
|
||||
</Descriptions.Item>
|
||||
</Descriptions>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</Space>
|
||||
)}
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,131 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Table, Button, Space } from 'antd';
|
||||
import type { ColumnsType } from 'antd/es/table';
|
||||
import { DeleteOutlined, FilterFilled } from '@ant-design/icons';
|
||||
import { ProxyLog } from '@/types/proxy';
|
||||
import { LogDetail } from './LogDetail';
|
||||
|
||||
interface ProxyLogsProps {
|
||||
logs: ProxyLog[];
|
||||
onClearLogs: () => void;
|
||||
}
|
||||
|
||||
export const ProxyLogs: React.FC<ProxyLogsProps> = ({
|
||||
logs,
|
||||
onClearLogs
|
||||
}) => {
|
||||
const [selectedLog, setSelectedLog] = useState<ProxyLog | null>(null);
|
||||
const [resourceFilter, setResourceFilter] = useState<string[]>([]);
|
||||
|
||||
const columns: ColumnsType<ProxyLog> = [
|
||||
{
|
||||
title: '时间',
|
||||
dataIndex: 'timestamp',
|
||||
key: 'timestamp',
|
||||
render: (timestamp: number) => new Date(timestamp).toLocaleString()
|
||||
},
|
||||
{
|
||||
title: 'URL',
|
||||
dataIndex: 'url',
|
||||
key: 'url',
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: (
|
||||
<Space>
|
||||
类型
|
||||
{resourceFilter.length > 0 && <FilterFilled style={{ color: '#f50' }} />}
|
||||
</Space>
|
||||
),
|
||||
dataIndex: 'resourceType',
|
||||
key: 'resourceType',
|
||||
render: (type: string) => {
|
||||
const typeMap: Record<string, string> = {
|
||||
xhr: 'XHR',
|
||||
fetch: 'Fetch',
|
||||
script: 'JS',
|
||||
stylesheet: 'CSS',
|
||||
image: 'Image',
|
||||
other: 'Other'
|
||||
};
|
||||
return typeMap[type] || 'Other';
|
||||
},
|
||||
filters: [
|
||||
{ text: 'XHR', value: 'xhr' },
|
||||
{ text: 'Fetch', value: 'fetch' },
|
||||
{ text: 'JS', value: 'script' },
|
||||
{ text: 'CSS', value: 'stylesheet' },
|
||||
{ text: 'Image', value: 'image' },
|
||||
{ text: 'Other', value: 'other' }
|
||||
],
|
||||
filterMode: 'menu' as const,
|
||||
filtered: resourceFilter.length > 0,
|
||||
onFilter: (value: string, record: ProxyLog) => record.resourceType === value
|
||||
},
|
||||
{
|
||||
title: '使用代理',
|
||||
dataIndex: 'proxyName',
|
||||
key: 'proxyName'
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
render: (status: string) => (
|
||||
<span style={{ color: status === 'success' ? '#52c41a' : '#ff4d4f' }}>
|
||||
{status === 'success' ? '成功' : '失败'}
|
||||
</span>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: '错误信息',
|
||||
dataIndex: 'errorMessage',
|
||||
key: 'errorMessage',
|
||||
ellipsis: true
|
||||
}
|
||||
];
|
||||
|
||||
const filteredLogs = logs.filter(log => {
|
||||
if (resourceFilter.length === 0) return true;
|
||||
return resourceFilter.includes(log.resourceType || 'other');
|
||||
});
|
||||
|
||||
return (
|
||||
<Space direction="vertical" style={{ width: '100%' }}>
|
||||
<div style={{
|
||||
marginBottom: 16,
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-end'
|
||||
}}>
|
||||
<Button
|
||||
danger
|
||||
onClick={onClearLogs}
|
||||
icon={<DeleteOutlined />}
|
||||
>
|
||||
清除日志
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Table
|
||||
dataSource={filteredLogs}
|
||||
columns={columns}
|
||||
onRow={(record) => ({
|
||||
onClick: () => setSelectedLog(record),
|
||||
style: { cursor: 'pointer' }
|
||||
})}
|
||||
pagination={{
|
||||
pageSize: 10,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
showTotal: (total) => `共 ${total} 条`,
|
||||
pageSizeOptions: ['10', '20', '50', '100']
|
||||
}}
|
||||
rowKey="id"
|
||||
/>
|
||||
<LogDetail
|
||||
log={selectedLog}
|
||||
onClose={() => setSelectedLog(null)}
|
||||
/>
|
||||
</Space>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,165 @@
|
||||
import React from 'react';
|
||||
import { Card, Input, Space, Button, Select, InputNumber, Form, Table } from 'antd';
|
||||
import type { ColumnsType } from 'antd/es/table';
|
||||
import { DeleteOutlined, PlusOutlined } from '@ant-design/icons';
|
||||
import { ProxyConfig } from '@/types/proxy';
|
||||
|
||||
interface ProxySettingsProps {
|
||||
proxyConfigs: ProxyConfig[];
|
||||
onAdd: () => void;
|
||||
onChange: (configId: string, field: keyof ProxyConfig, value: any) => void;
|
||||
onDelete: (configId: string) => void;
|
||||
onApply: (configId: string) => Promise<void>;
|
||||
onClear: (configId: string) => Promise<void>;
|
||||
}
|
||||
|
||||
export const ProxySettings: React.FC<ProxySettingsProps> = ({
|
||||
proxyConfigs,
|
||||
onAdd,
|
||||
onChange,
|
||||
onDelete,
|
||||
onApply,
|
||||
onClear
|
||||
}) => {
|
||||
const columns: ColumnsType<ProxyConfig> = [
|
||||
{
|
||||
title: '名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
render: (text: string, record: ProxyConfig) => (
|
||||
<Input
|
||||
value={text}
|
||||
onChange={e => onChange(record.id, 'name', e.target.value)}
|
||||
disabled={record.id === 'direct'}
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: '类型',
|
||||
dataIndex: 'proxyType',
|
||||
key: 'proxyType',
|
||||
render: (text: string, record: ProxyConfig) => (
|
||||
<Select
|
||||
value={text}
|
||||
onChange={value => onChange(record.id, 'proxyType', value)}
|
||||
style={{ width: 120 }}
|
||||
disabled={record.id === 'direct'}
|
||||
options={[
|
||||
{ label: '直接连接', value: 'direct' },
|
||||
{ label: '代理服务器', value: 'fixed_server' },
|
||||
{ label: 'PAC 脚本', value: 'pac_script' }
|
||||
]}
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: '协议',
|
||||
dataIndex: 'scheme',
|
||||
key: 'scheme',
|
||||
render: (text: string, record: ProxyConfig) => (
|
||||
record.proxyType === 'fixed_server' && (
|
||||
<Select
|
||||
value={text}
|
||||
onChange={value => onChange(record.id, 'scheme', value)}
|
||||
style={{ width: 100 }}
|
||||
options={[
|
||||
{ label: 'HTTP', value: 'http' },
|
||||
{ label: 'HTTPS', value: 'https' },
|
||||
{ label: 'SOCKS4', value: 'socks4' },
|
||||
{ label: 'SOCKS5', value: 'socks5' }
|
||||
]}
|
||||
/>
|
||||
)
|
||||
)
|
||||
},
|
||||
{
|
||||
title: '主机',
|
||||
dataIndex: 'host',
|
||||
key: 'host',
|
||||
render: (text: string, record: ProxyConfig) => (
|
||||
record.proxyType === 'fixed_server' && (
|
||||
<Input
|
||||
value={text}
|
||||
onChange={e => onChange(record.id, 'host', e.target.value)}
|
||||
placeholder="127.0.0.1"
|
||||
/>
|
||||
)
|
||||
)
|
||||
},
|
||||
{
|
||||
title: '端口',
|
||||
dataIndex: 'port',
|
||||
key: 'port',
|
||||
render: (text: number, record: ProxyConfig) => (
|
||||
record.proxyType === 'fixed_server' && (
|
||||
<InputNumber
|
||||
value={text}
|
||||
onChange={value => onChange(record.id, 'port', value)}
|
||||
min={1}
|
||||
max={65535}
|
||||
/>
|
||||
)
|
||||
)
|
||||
},
|
||||
{
|
||||
title: 'PAC 脚本',
|
||||
dataIndex: 'pacScript',
|
||||
key: 'pacScript',
|
||||
render: (text: string, record: ProxyConfig) => (
|
||||
record.proxyType === 'pac_script' && (
|
||||
<Input.TextArea
|
||||
value={text}
|
||||
onChange={e => onChange(record.id, 'pacScript', e.target.value)}
|
||||
rows={4}
|
||||
placeholder="输入 PAC 脚本"
|
||||
/>
|
||||
)
|
||||
)
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
render: (_, record: ProxyConfig) => (
|
||||
<Space>
|
||||
<Button
|
||||
type={record.enabled ? "primary" : "default"}
|
||||
danger={record.enabled}
|
||||
onClick={() => record.enabled ?
|
||||
onClear(record.id) :
|
||||
onApply(record.id)
|
||||
}
|
||||
>
|
||||
{record.enabled ? '取消应用' : '应用选项'}
|
||||
</Button>
|
||||
{record.id !== 'direct' && (
|
||||
<Button
|
||||
danger
|
||||
icon={<DeleteOutlined />}
|
||||
onClick={() => onDelete(record.id)}
|
||||
/>
|
||||
)}
|
||||
</Space>
|
||||
)
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Space style={{ marginBottom: 16, justifyContent: 'flex-end', width: '100%' }}>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<PlusOutlined />}
|
||||
onClick={onAdd}
|
||||
>
|
||||
添加代理
|
||||
</Button>
|
||||
</Space>
|
||||
<Table
|
||||
dataSource={proxyConfigs}
|
||||
columns={columns}
|
||||
rowKey="id"
|
||||
pagination={false}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user