mirror of
https://github.com/yaklang/yaklang-chrome-extension.git
synced 2026-09-26 21:21:53 +08:00
add proxy switch demo
This commit is contained in:
+5
-3
@@ -4,6 +4,7 @@ import {ConfigProvider} from "antd";
|
||||
import {Contro} from "@components/Contro";
|
||||
import {Proxifier} from "@components/Proxifier";
|
||||
import {EvalInTab} from "@components/EvalInTab";
|
||||
import {ProxySwitch} from "@components/ProxySwitch";
|
||||
|
||||
function App() {
|
||||
return (
|
||||
@@ -15,10 +16,11 @@ function App() {
|
||||
}}
|
||||
>
|
||||
<div className="App">
|
||||
<Contro/>
|
||||
{/*<Proxifier/>*/}
|
||||
{/*<Contro/>*/}
|
||||
{/* <Proxifier/> */}
|
||||
<ProxySwitch/>
|
||||
|
||||
<EvalInTab/>
|
||||
{/* <EvalInTab/> */}
|
||||
</div>
|
||||
</ConfigProvider>
|
||||
);
|
||||
|
||||
@@ -32,6 +32,7 @@ export const EvalInTab: React.FC<EvalInTabProps> = () => {
|
||||
value: {mode: "CONTENT_CALL_FUNCTION", fn_name: funcName, args: inputArgsData},
|
||||
});
|
||||
} catch (error) {
|
||||
console.log("error:", error)
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
.proxy-switch {
|
||||
padding: 16px;
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.proxy-switch-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.proxy-switch .ant-select {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.proxy-switch .ant-space {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.proxy-switch-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.proxy-switch-title-text {
|
||||
color: #31343F;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.proxy-switch-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 12px 16px;
|
||||
background: #F8F8F8;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.proxy-switch-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.proxy-switch-status {
|
||||
color: #85899E;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.proxy-switch-proxy {
|
||||
color: #31343F;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.proxy-switch-status-active {
|
||||
color: var(--yakit-primary);
|
||||
}
|
||||
|
||||
.proxy-switch-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
@@ -0,0 +1,288 @@
|
||||
import React, {useEffect, useState} from "react";
|
||||
import {Select, Button, Tooltip, Modal, Form, Input, Radio, Space, message} from "antd";
|
||||
import {PlusOutlined, SettingOutlined} from "@ant-design/icons";
|
||||
import {ProxyConfig} from "@/types/proxy";
|
||||
import {StorageChanges} from "@/types/chrome";
|
||||
import "./index.css";
|
||||
|
||||
export const ProxySwitch: React.FC = () => {
|
||||
const [currentMode, setCurrentMode] = useState<string>("direct");
|
||||
const [proxyConfigs, setProxyConfigs] = useState<ProxyConfig[]>([]);
|
||||
const [isModalVisible, setIsModalVisible] = useState(false);
|
||||
const [form] = Form.useForm();
|
||||
const [proxyHost, setProxyHost] = useState('');
|
||||
const [proxyPort, setProxyPort] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
loadConfigs().catch(error => {
|
||||
console.error("Failed to load configs:", error);
|
||||
// 可以在这里添加错误提示
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (proxyConfigs.length > 0) {
|
||||
applyProxySettings(currentMode).catch(error => {
|
||||
console.error("Failed to apply proxy settings:", error);
|
||||
// 可以在这里添加错误提示
|
||||
});
|
||||
}
|
||||
}, [proxyConfigs, currentMode]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleStorageChange = (changes: StorageChanges) => {
|
||||
if (changes.proxyConfigs) {
|
||||
const newConfigs = changes.proxyConfigs.newValue;
|
||||
setProxyConfigs(newConfigs);
|
||||
const currentConfig = newConfigs.find((c: ProxyConfig) => c.id === currentMode);
|
||||
if (!currentConfig || !currentConfig.enabled) {
|
||||
setCurrentMode("direct");
|
||||
}
|
||||
}
|
||||
};
|
||||
chrome.storage.onChanged.addListener(handleStorageChange);
|
||||
return () => chrome.storage.onChanged.removeListener(handleStorageChange);
|
||||
}, [currentMode]);
|
||||
|
||||
const loadConfigs = async () => {
|
||||
try {
|
||||
const result = await chrome.storage.local.get('proxyConfigs');
|
||||
if (!result.proxyConfigs) {
|
||||
const defaultConfigs: ProxyConfig[] = [
|
||||
{
|
||||
id: "direct",
|
||||
name: "直接连接",
|
||||
proxyType: "direct" as const,
|
||||
enabled: true
|
||||
},
|
||||
{
|
||||
id: "default",
|
||||
name: "默认代理",
|
||||
proxyType: "fixed_server" as const,
|
||||
host: "127.0.0.1",
|
||||
port: 8080,
|
||||
scheme: "http",
|
||||
enabled: false
|
||||
}
|
||||
];
|
||||
await chrome.storage.local.set({proxyConfigs: defaultConfigs});
|
||||
setProxyConfigs(defaultConfigs);
|
||||
setCurrentMode("direct");
|
||||
} else {
|
||||
setProxyConfigs(result.proxyConfigs);
|
||||
const currentConfig = result.proxyConfigs.find((c: ProxyConfig) => c.id === currentMode);
|
||||
if (!currentConfig || !currentConfig.enabled) {
|
||||
setCurrentMode("direct");
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error("Error in loadConfigs:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const applyProxySettings = async (modeId: string) => {
|
||||
try {
|
||||
const config = proxyConfigs.find((c: ProxyConfig) => c.id === modeId);
|
||||
if (!config || !config.enabled) return false;
|
||||
|
||||
if (config.proxyType === "direct") {
|
||||
chrome.proxy.settings.clear({scope: 'regular'});
|
||||
return true;
|
||||
} else if (config.proxyType === "fixed_server") {
|
||||
chrome.proxy.settings.set({
|
||||
value: {
|
||||
mode: "fixed_servers",
|
||||
rules: {
|
||||
singleProxy: {
|
||||
scheme: config.scheme,
|
||||
host: config.host,
|
||||
port: Number(config.port)
|
||||
},
|
||||
bypassList: ["<-loopback>"]
|
||||
}
|
||||
},
|
||||
scope: "regular"
|
||||
});
|
||||
return true;
|
||||
} else if (config.proxyType === "pac_script") {
|
||||
chrome.proxy.settings.set({
|
||||
value: {
|
||||
mode: "pac_script",
|
||||
pacScript: {
|
||||
data: config.pacScript
|
||||
}
|
||||
},
|
||||
scope: "regular"
|
||||
});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} catch (error) {
|
||||
console.error("Failed to apply proxy settings:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const handleAddProxy = () => {
|
||||
form.resetFields();
|
||||
setIsModalVisible(true);
|
||||
};
|
||||
|
||||
const handleModalOk = () => {
|
||||
form.validateFields().then(async values => {
|
||||
try {
|
||||
const newConfig: ProxyConfig = {
|
||||
id: Date.now().toString(),
|
||||
name: values.name,
|
||||
proxyType: values.proxyType as "direct" | "fixed_server" | "pac_script" | "auto_detect",
|
||||
host: values.host,
|
||||
port: values.port ? parseInt(values.port) : undefined,
|
||||
scheme: values.scheme,
|
||||
enabled: true,
|
||||
};
|
||||
const updatedConfigs = [...proxyConfigs, newConfig];
|
||||
await chrome.storage.local.set({proxyConfigs: updatedConfigs});
|
||||
setProxyConfigs(updatedConfigs);
|
||||
setIsModalVisible(false);
|
||||
} catch (error) {
|
||||
console.error("Failed to save new proxy config:", error);
|
||||
// 可以在这里添加错误提示
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleSettingClick = () => {
|
||||
if (chrome.runtime.openOptionsPage) {
|
||||
chrome.runtime.openOptionsPage();
|
||||
} else {
|
||||
chrome.tabs.create({
|
||||
url: chrome.runtime.getURL('proxy/options.html')
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleApplyProxy = async () => {
|
||||
try {
|
||||
const response = await chrome.runtime.sendMessage({
|
||||
action: 'SET_PROXY_CONFIG',
|
||||
config: {
|
||||
host: proxyHost,
|
||||
port: parseInt(proxyPort),
|
||||
scheme: 'http',
|
||||
proxyType: 'fixed_server',
|
||||
id: 'default',
|
||||
name: '默认代理'
|
||||
}
|
||||
});
|
||||
|
||||
if (response.success) {
|
||||
message.success('代理设置已应用');
|
||||
} else {
|
||||
message.error(response.error || '代理设置失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error applying proxy:', error);
|
||||
message.error('应用代理设置时发生错误');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="proxy-switch">
|
||||
<div className="proxy-switch-header">
|
||||
<Select
|
||||
value={currentMode}
|
||||
onChange={setCurrentMode}
|
||||
style={{width: 200}}
|
||||
options={proxyConfigs.filter((config: ProxyConfig) => config.enabled).map((config: ProxyConfig) => ({
|
||||
label: config.name,
|
||||
value: config.id
|
||||
}))}
|
||||
/>
|
||||
<Space>
|
||||
<Tooltip title="添加代理">
|
||||
<Button
|
||||
icon={<PlusOutlined/>}
|
||||
onClick={handleAddProxy}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip title="代理设置">
|
||||
<Button
|
||||
icon={<SettingOutlined/>}
|
||||
onClick={handleSettingClick}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Space>
|
||||
</div>
|
||||
|
||||
<Modal
|
||||
title="添加代理配置"
|
||||
open={isModalVisible}
|
||||
onOk={handleModalOk}
|
||||
onCancel={() => setIsModalVisible(false)}
|
||||
>
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
>
|
||||
<Form.Item
|
||||
name="name"
|
||||
label="配置名称"
|
||||
rules={[{required: true}]}
|
||||
>
|
||||
<Input/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="proxyType"
|
||||
label="代理类型"
|
||||
rules={[{required: true}]}
|
||||
>
|
||||
<Radio.Group>
|
||||
<Radio value="direct">直接连接</Radio>
|
||||
<Radio value="fixed_server">代理服务器</Radio>
|
||||
<Radio value="pac_script">PAC 脚本</Radio>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
noStyle
|
||||
shouldUpdate={(prev, curr) => prev.proxyType !== curr.proxyType}
|
||||
>
|
||||
{({getFieldValue}) =>
|
||||
getFieldValue('proxyType') === 'fixed_server' && (
|
||||
<>
|
||||
<Form.Item
|
||||
name="scheme"
|
||||
label="代理协议"
|
||||
rules={[{required: true}]}
|
||||
>
|
||||
<Select>
|
||||
<Select.Option value="http">HTTP</Select.Option>
|
||||
<Select.Option value="https">HTTPS</Select.Option>
|
||||
<Select.Option value="socks4">SOCKS4</Select.Option>
|
||||
<Select.Option value="socks5">SOCKS5</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="host"
|
||||
label="代理服务器"
|
||||
rules={[{required: true}]}
|
||||
>
|
||||
<Input/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="port"
|
||||
label="端口"
|
||||
rules={[{required: true}]}
|
||||
>
|
||||
<Input type="number"/>
|
||||
</Form.Item>
|
||||
</>
|
||||
)
|
||||
}
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,100 @@
|
||||
.options-page {
|
||||
min-height: 100vh;
|
||||
background: #f0f2f5;
|
||||
}
|
||||
|
||||
.options-page .ant-layout-content {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.proxy-tabs {
|
||||
background: #fff;
|
||||
padding: 24px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.options-page .ant-tabs-nav::before {
|
||||
border-bottom-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.options-page .ant-tabs-tab.ant-tabs-tab-active .ant-tabs-tab-btn {
|
||||
color: var(--yakit-primary);
|
||||
}
|
||||
|
||||
.options-page .ant-tabs-ink-bar {
|
||||
background: var(--yakit-primary);
|
||||
}
|
||||
|
||||
.options-page .ant-card {
|
||||
margin-bottom: 16px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.options-page .ant-card-head {
|
||||
border-bottom: none;
|
||||
min-height: 48px;
|
||||
}
|
||||
|
||||
.options-page .ant-card-body {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.options-page .ant-btn-primary {
|
||||
background: var(--yakit-primary);
|
||||
border-color: var(--yakit-primary);
|
||||
}
|
||||
|
||||
.options-page .ant-btn-primary:hover {
|
||||
background: var(--yakit-primary-hover);
|
||||
border-color: var(--yakit-primary-hover);
|
||||
}
|
||||
|
||||
.proxy-action-btn.ant-btn-default {
|
||||
color: rgba(0, 0, 0, 0.65);
|
||||
border-color: #d9d9d9;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.proxy-action-btn.ant-btn-default:hover {
|
||||
color: var(--yakit-primary);
|
||||
border-color: var(--yakit-primary);
|
||||
}
|
||||
|
||||
.proxy-action-btn.ant-btn-primary {
|
||||
color: #fff;
|
||||
background: var(--yakit-primary);
|
||||
border-color: var(--yakit-primary);
|
||||
}
|
||||
|
||||
.proxy-action-btn.ant-btn-primary.ant-btn-dangerous {
|
||||
background: #ff4d4f;
|
||||
border-color: #ff4d4f;
|
||||
}
|
||||
|
||||
.proxy-action-btn.ant-btn-primary.ant-btn-dangerous:hover {
|
||||
background: #ff7875;
|
||||
border-color: #ff7875;
|
||||
}
|
||||
|
||||
.options-page .ant-table-wrapper {
|
||||
background: #fff;
|
||||
padding: 24px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.options-page .ant-input:focus,
|
||||
.options-page .ant-input-focused {
|
||||
border-color: var(--yakit-primary);
|
||||
box-shadow: 0 0 0 2px var(--yakit-primary-5);
|
||||
}
|
||||
|
||||
.options-page .ant-select-focused .ant-select-selector {
|
||||
border-color: var(--yakit-primary) !important;
|
||||
box-shadow: 0 0 0 2px var(--yakit-primary-5) !important;
|
||||
}
|
||||
|
||||
.options-page .ant-select-item-option-selected:not(.ant-select-item-option-disabled) {
|
||||
background-color: var(--yakit-primary-5);
|
||||
}
|
||||
@@ -0,0 +1,385 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Layout, Button, Card, Input, Select, InputNumber, Space, Typography, Modal, Switch, Tabs, Table, Form, App } from "antd";
|
||||
import { PlusOutlined, DeleteOutlined, ImportOutlined, ExportOutlined } from "@ant-design/icons";
|
||||
import { ProxyConfig } from "@/types/proxy";
|
||||
import { StorageChanges } from "@/types/chrome";
|
||||
import './index.css';
|
||||
import { ProxyActionType } from '@/types/action';
|
||||
|
||||
const { Header, Content } = Layout;
|
||||
const { Title } = Typography;
|
||||
const { TextArea } = Input;
|
||||
|
||||
const headerStyle = {
|
||||
background: '#fff',
|
||||
padding: '0 24px',
|
||||
borderBottom: '1px solid #f0f0f0'
|
||||
};
|
||||
|
||||
const contentStyle = {
|
||||
padding: '24px',
|
||||
background: '#f0f2f5',
|
||||
minHeight: '100vh'
|
||||
};
|
||||
|
||||
const titleStyle = {
|
||||
margin: '16px 0',
|
||||
color: '#31343F'
|
||||
};
|
||||
|
||||
interface ProxyLog {
|
||||
id: string;
|
||||
timestamp: number;
|
||||
url: string;
|
||||
proxyId: string;
|
||||
proxyName: string;
|
||||
status: 'success' | 'error';
|
||||
errorMessage?: string;
|
||||
}
|
||||
|
||||
export const OptionsPage: React.FC = () => {
|
||||
const { message } = App.useApp();
|
||||
const [proxyConfigs, setProxyConfigs] = useState<ProxyConfig[]>([]);
|
||||
const [proxyLogs, setProxyLogs] = useState<ProxyLog[]>([]);
|
||||
const [activeTab, setActiveTab] = useState('settings');
|
||||
const [currentConfigId, setCurrentConfigId] = useState<string>('');
|
||||
|
||||
useEffect(() => {
|
||||
loadConfigs();
|
||||
}, []);
|
||||
|
||||
const loadConfigs = async () => {
|
||||
const result = await chrome.storage.local.get('proxyConfigs');
|
||||
setProxyConfigs(result.proxyConfigs || []);
|
||||
};
|
||||
|
||||
const handleAddProxy = () => {
|
||||
const newConfig: ProxyConfig = {
|
||||
id: Date.now().toString(),
|
||||
name: '新建代理',
|
||||
proxyType: 'fixed_server',
|
||||
scheme: 'http',
|
||||
host: '127.0.0.1',
|
||||
port: 8080,
|
||||
enabled: false
|
||||
};
|
||||
const updatedConfigs = [...proxyConfigs, newConfig];
|
||||
chrome.storage.local.set({ proxyConfigs: updatedConfigs });
|
||||
setProxyConfigs(updatedConfigs);
|
||||
};
|
||||
|
||||
const handleConfigChange = (configId: string, field: keyof ProxyConfig, value: any) => {
|
||||
const updatedConfigs = proxyConfigs.map(config => {
|
||||
if (config.id === configId) {
|
||||
return { ...config, [field]: value };
|
||||
}
|
||||
return config;
|
||||
});
|
||||
chrome.storage.local.set({ proxyConfigs: updatedConfigs });
|
||||
setProxyConfigs(updatedConfigs);
|
||||
};
|
||||
|
||||
const handleDeleteProxy = (configId: string) => {
|
||||
Modal.confirm({
|
||||
title: '确认删除',
|
||||
content: '确定要删除这个代理配置吗?',
|
||||
onOk: () => {
|
||||
const updatedConfigs = proxyConfigs.filter(config => config.id !== configId);
|
||||
chrome.storage.local.set({ proxyConfigs: updatedConfigs });
|
||||
setProxyConfigs(updatedConfigs);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const handleStorageChange = (changes: StorageChanges) => {
|
||||
if (changes.proxyConfigs) {
|
||||
setProxyConfigs(changes.proxyConfigs.newValue || []);
|
||||
}
|
||||
};
|
||||
|
||||
chrome.storage.onChanged.addListener(handleStorageChange);
|
||||
return () => chrome.storage.onChanged.removeListener(handleStorageChange);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const loadLogs = async () => {
|
||||
const result = await chrome.storage.local.get('proxyLogs');
|
||||
setProxyLogs(result.proxyLogs || []);
|
||||
};
|
||||
loadLogs();
|
||||
|
||||
const handleStorageChange = (changes: StorageChanges) => {
|
||||
if (changes.proxyLogs) {
|
||||
setProxyLogs(changes.proxyLogs.newValue);
|
||||
}
|
||||
};
|
||||
chrome.storage.onChanged.addListener(handleStorageChange);
|
||||
return () => chrome.storage.onChanged.removeListener(handleStorageChange);
|
||||
}, []);
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: '时间',
|
||||
dataIndex: 'timestamp',
|
||||
key: 'timestamp',
|
||||
render: (timestamp: number) => new Date(timestamp).toLocaleString()
|
||||
},
|
||||
{
|
||||
title: 'URL',
|
||||
dataIndex: 'url',
|
||||
key: 'url',
|
||||
ellipsis: true,
|
||||
render: (url: string) => (
|
||||
<a
|
||||
href={url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
style={{
|
||||
color: '#1890ff',
|
||||
textDecoration: 'none',
|
||||
maxWidth: '400px',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
display: 'block'
|
||||
}}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
chrome.tabs.create({ url });
|
||||
}}
|
||||
>
|
||||
{url}
|
||||
</a>
|
||||
)
|
||||
},
|
||||
{
|
||||
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 handleApplyConfig = async (configId: string) => {
|
||||
const config = proxyConfigs.find(c => c.id === configId);
|
||||
if (config) {
|
||||
try {
|
||||
const response = await new Promise<any>((resolve) => {
|
||||
chrome.runtime.sendMessage({
|
||||
action: ProxyActionType.SET_PROXY_CONFIG,
|
||||
config: {
|
||||
...config,
|
||||
scheme: config.scheme || 'http',
|
||||
host: config.host || '127.0.0.1',
|
||||
port: Number(config.port) || 8080,
|
||||
}
|
||||
}, resolve);
|
||||
});
|
||||
|
||||
if (response && response.success) {
|
||||
const updatedConfigs = proxyConfigs.map(c => ({
|
||||
...c,
|
||||
enabled: c.id === configId
|
||||
}));
|
||||
await chrome.storage.local.set({ proxyConfigs: updatedConfigs });
|
||||
setProxyConfigs(updatedConfigs);
|
||||
message.success('代理设置已应用');
|
||||
} else {
|
||||
message.error((response && response.error) || '代理设置失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to apply proxy config:', error);
|
||||
message.error('操作失败');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleClearProxy = async (configId: string) => {
|
||||
try {
|
||||
const response = await new Promise<any>((resolve) => {
|
||||
chrome.runtime.sendMessage({
|
||||
action: ProxyActionType.CLEAR_PROXY_CONFIG
|
||||
}, resolve);
|
||||
});
|
||||
|
||||
if (response && response.success) {
|
||||
const updatedConfigs = proxyConfigs.map(c => ({
|
||||
...c,
|
||||
enabled: false
|
||||
}));
|
||||
await chrome.storage.local.set({ proxyConfigs: updatedConfigs });
|
||||
setProxyConfigs(updatedConfigs);
|
||||
message.success('代理已取消');
|
||||
} else {
|
||||
message.error((response && response.error) || '取消代理失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error clearing proxy:', error);
|
||||
message.error('操作失败');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Layout className="options-page">
|
||||
<Content style={contentStyle}>
|
||||
<Tabs
|
||||
activeKey={activeTab}
|
||||
onChange={setActiveTab}
|
||||
className="proxy-tabs"
|
||||
tabBarExtraContent={{
|
||||
right: (
|
||||
<Space>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<PlusOutlined />}
|
||||
onClick={handleAddProxy}
|
||||
>
|
||||
添加代理
|
||||
</Button>
|
||||
<Button icon={<ImportOutlined />}>导入</Button>
|
||||
<Button icon={<ExportOutlined />}>导出</Button>
|
||||
</Space>
|
||||
)
|
||||
}}
|
||||
items={[
|
||||
{
|
||||
key: 'settings',
|
||||
label: '代理设置',
|
||||
children: (
|
||||
<Space direction="vertical" style={{ width: '100%' }}>
|
||||
{proxyConfigs.map(config => (
|
||||
<Card
|
||||
key={config.id}
|
||||
size="small"
|
||||
title={
|
||||
<Input
|
||||
placeholder="代理名称"
|
||||
value={config.name}
|
||||
onChange={e => handleConfigChange(config.id, 'name', e.target.value)}
|
||||
disabled={config.id === 'direct'}
|
||||
variant="borderless"
|
||||
style={{ fontSize: '16px', padding: 0 }}
|
||||
/>
|
||||
}
|
||||
extra={
|
||||
<Space>
|
||||
<Button
|
||||
className="proxy-action-btn"
|
||||
type={config.enabled ? "primary" : "default"}
|
||||
danger={config.enabled}
|
||||
onClick={() => config.enabled ?
|
||||
handleClearProxy(config.id) :
|
||||
handleApplyConfig(config.id)
|
||||
}
|
||||
>
|
||||
{config.enabled ? '取消应用' : '应用选项'}
|
||||
</Button>
|
||||
{config.id !== 'direct' && (
|
||||
<Button
|
||||
danger
|
||||
icon={<DeleteOutlined />}
|
||||
onClick={() => handleDeleteProxy(config.id)}
|
||||
/>
|
||||
)}
|
||||
</Space>
|
||||
}
|
||||
style={{ borderRadius: '4px' }}
|
||||
>
|
||||
<Space direction="vertical" style={{ width: '100%' }}>
|
||||
<Select
|
||||
style={{ width: '100%' }}
|
||||
value={config.proxyType}
|
||||
onChange={value => handleConfigChange(config.id, 'proxyType', value)}
|
||||
disabled={config.id === 'direct'}
|
||||
>
|
||||
<Select.Option value="direct">直接连接</Select.Option>
|
||||
<Select.Option value="fixed_server">代理服务器</Select.Option>
|
||||
<Select.Option value="pac_script">PAC 脚本</Select.Option>
|
||||
<Select.Option value="bypass_list">代理规则列表</Select.Option>
|
||||
</Select>
|
||||
|
||||
{config.proxyType === 'bypass_list' && (
|
||||
<TextArea
|
||||
rows={4}
|
||||
value={config.bypassList?.join('\n')}
|
||||
onChange={e => handleConfigChange(config.id, 'bypassList', e.target.value.split('\n'))}
|
||||
placeholder="每行一个规则,例如:
|
||||
*.example.com
|
||||
[::1]
|
||||
127.0.0.1"
|
||||
/>
|
||||
)}
|
||||
|
||||
{config.proxyType === 'fixed_server' && (
|
||||
<Space style={{ width: '100%' }}>
|
||||
<Select
|
||||
style={{ width: 120 }}
|
||||
value={config.scheme}
|
||||
onChange={value => handleConfigChange(config.id, 'scheme', value)}
|
||||
>
|
||||
<Select.Option value="http">HTTP</Select.Option>
|
||||
<Select.Option value="https">HTTPS</Select.Option>
|
||||
<Select.Option value="socks4">SOCKS4</Select.Option>
|
||||
<Select.Option value="socks5">SOCKS5</Select.Option>
|
||||
</Select>
|
||||
<Input
|
||||
placeholder="代理服务器"
|
||||
value={config.host}
|
||||
onChange={e => handleConfigChange(config.id, 'host', e.target.value)}
|
||||
/>
|
||||
<InputNumber
|
||||
placeholder="端口"
|
||||
value={config.port}
|
||||
onChange={value => handleConfigChange(config.id, 'port', value)}
|
||||
style={{ width: 100 }}
|
||||
/>
|
||||
</Space>
|
||||
)}
|
||||
|
||||
{config.proxyType === 'pac_script' && (
|
||||
<TextArea
|
||||
rows={4}
|
||||
value={config.pacScript}
|
||||
onChange={e => handleConfigChange(config.id, 'pacScript', e.target.value)}
|
||||
placeholder="输入 PAC 脚本"
|
||||
/>
|
||||
)}
|
||||
</Space>
|
||||
</Card>
|
||||
))}
|
||||
</Space>
|
||||
)
|
||||
},
|
||||
{
|
||||
key: 'logs',
|
||||
label: '代理日志',
|
||||
children: (
|
||||
<Table
|
||||
dataSource={proxyLogs}
|
||||
columns={columns}
|
||||
pagination={{ pageSize: 50 }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</Content>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
import React from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import { App } from 'antd';
|
||||
import { OptionsPage } from './OptionsPage';
|
||||
import zhCN from 'antd/locale/zh_CN';
|
||||
import '@/styles/global.css';
|
||||
|
||||
const container = document.getElementById('root');
|
||||
const root = createRoot(container!);
|
||||
|
||||
root.render(
|
||||
<App>
|
||||
<OptionsPage />
|
||||
</App>
|
||||
);
|
||||
@@ -0,0 +1,37 @@
|
||||
:root {
|
||||
--yakit-primary: #F28B44;
|
||||
--yakit-primary-hover: #f4a061;
|
||||
--yakit-primary-active: #e87633;
|
||||
--yakit-primary-5: rgba(242, 139, 68, 0.05);
|
||||
--yakit-primary-10: rgba(242, 139, 68, 0.1);
|
||||
}
|
||||
|
||||
.ant-btn-primary {
|
||||
background-color: var(--yakit-primary) !important;
|
||||
}
|
||||
|
||||
.ant-btn-primary:hover {
|
||||
background-color: var(--yakit-primary-hover) !important;
|
||||
}
|
||||
|
||||
.ant-btn-primary:active {
|
||||
background-color: var(--yakit-primary-active) !important;
|
||||
}
|
||||
|
||||
.ant-select-item-option-selected:not(.ant-select-item-option-disabled) {
|
||||
background-color: var(--yakit-primary-5) !important;
|
||||
}
|
||||
|
||||
.ant-select-focused .ant-select-selector,
|
||||
.ant-input-focused,
|
||||
.ant-input:focus,
|
||||
.ant-input-number-focused,
|
||||
.ant-input-number:focus {
|
||||
border-color: var(--yakit-primary) !important;
|
||||
box-shadow: 0 0 0 2px var(--yakit-primary-10) !important;
|
||||
}
|
||||
|
||||
.ant-btn:not(.ant-btn-primary):hover {
|
||||
color: var(--yakit-primary) !important;
|
||||
border-color: var(--yakit-primary) !important;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
export const ProxyActionType = {
|
||||
CONNECT: "CONNECT",
|
||||
SEND_MESSAGE: "SEND_MESSAGE",
|
||||
DISCONNECT: "DISCONNECT",
|
||||
SET_PROXY: "SET_PROXY",
|
||||
CLEAR_PROXY: "CLEAR_PROXY",
|
||||
PROXY_STATUS: "PROXY_STATUS",
|
||||
INJECT_SCRIPT: "INJECT_SCRIPT",
|
||||
BADGE_COUNT: "BADGE_COUNT",
|
||||
SET_PROXY_CONFIG: "SET_PROXY_CONFIG",
|
||||
CLEAR_PROXY_CONFIG: "CLEAR_PROXY_CONFIG",
|
||||
GET_PROXY_STATUS: "GET_PROXY_STATUS"
|
||||
} as const;
|
||||
|
||||
export type ProxyActionType = typeof ProxyActionType[keyof typeof ProxyActionType];
|
||||
@@ -0,0 +1,19 @@
|
||||
export interface StorageChange<T = any> {
|
||||
oldValue?: T;
|
||||
newValue?: T;
|
||||
}
|
||||
|
||||
export interface StorageChanges {
|
||||
[key: string]: StorageChange;
|
||||
}
|
||||
|
||||
export interface ProxyConfig {
|
||||
host: string;
|
||||
port: number;
|
||||
scheme: 'http' | 'https' | 'socks5';
|
||||
proxyType: 'fixed_server';
|
||||
enabled?: boolean;
|
||||
id?: string;
|
||||
name?: string;
|
||||
timestamp?: number;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
export interface ProxyConfig {
|
||||
id: string;
|
||||
name: string;
|
||||
enabled: boolean;
|
||||
proxyType: "direct" | "fixed_server" | "pac_script" | "auto_detect" | "bypass_list";
|
||||
host?: string;
|
||||
port?: number;
|
||||
scheme?: "http" | "https" | "socks4" | "socks5";
|
||||
pacScript?: string;
|
||||
bypassList?: string[];
|
||||
}
|
||||
Reference in New Issue
Block a user