mirror of
https://github.com/yaklang/yaklang-chrome-extension.git
synced 2026-09-25 20:51:52 +08:00
101 lines
7.0 KiB
TypeScript
101 lines
7.0 KiB
TypeScript
import { useEffect, useState } from 'react';
|
|
import { ChevronRight, KeyRound, Network, Plus, Power, Save, Trash2 } from 'lucide-react';
|
|
import { v7 as uuidv7 } from 'uuid';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Field } from '@/components/ui/field';
|
|
import { Switch } from '@/components/ui/switch';
|
|
import { request } from '@/platform/messaging/runtime';
|
|
import type { ProxyProfile } from '@/types/models';
|
|
import { PROXY_KIND_LABELS, proxyProfileDetail } from './presentation';
|
|
import type { ProxyViewProps } from './types';
|
|
import './proxy-workspace.css';
|
|
|
|
function createProfile(): ProxyProfile {
|
|
return {
|
|
id: uuidv7(), name: '新代理出口', kind: 'fixed_servers', scheme: 'http', host: '127.0.0.1', port: 8080, bypass: [],
|
|
};
|
|
}
|
|
|
|
export function ProxyProfilesView({ state, setState, run, busy }: ProxyViewProps) {
|
|
const [draft, setDraft] = useState<ProxyProfile>(() => state.proxyProfiles[0] || createProfile());
|
|
const [password, setPassword] = useState('');
|
|
const [passwordConfigured, setPasswordConfigured] = useState(false);
|
|
|
|
useEffect(() => {
|
|
setPassword('');
|
|
void request('proxy.auth.status', { profileId: draft.id }).then((result) => setPasswordConfigured(result.configured));
|
|
}, [draft.id]);
|
|
|
|
const save = () => run(async () => {
|
|
setState(await request('proxy.save', draft));
|
|
if (draft.authEnabled) {
|
|
if (password) await request('proxy.auth.set', { profileId: draft.id, password });
|
|
} else {
|
|
await request('proxy.auth.set', { profileId: draft.id, password: '' });
|
|
}
|
|
setPassword('');
|
|
setPasswordConfigured(Boolean(draft.authEnabled && (password || passwordConfigured)));
|
|
}, '代理出口已保存');
|
|
|
|
const remove = () => run(async () => {
|
|
setState(await request('proxy.delete', { id: draft.id }));
|
|
await request('proxy.auth.set', { profileId: draft.id, password: '' });
|
|
setDraft(state.proxyProfiles[0] || createProfile());
|
|
}, '代理出口已删除');
|
|
|
|
return <div className="section-view proxy-page">
|
|
<div className="page-heading proxy-page-heading">
|
|
<div><h1>代理出口</h1><p>维护浏览器可以使用的直连、HTTP、HTTPS、SOCKS 和 PAC 出口。</p></div>
|
|
<Button variant="primary" onClick={() => setDraft(createProfile())}><Plus size={16} />新建出口</Button>
|
|
</div>
|
|
|
|
<div className="proxy-profile-workspace">
|
|
<section className="proxy-profile-index" aria-label="代理出口列表">
|
|
<div className="proxy-panel-label"><span>出口</span><strong>{state.proxyProfiles.length}</strong></div>
|
|
<div className="proxy-profile-list">
|
|
{state.proxyProfiles.map((profile) => <button
|
|
key={profile.id}
|
|
className={`${draft.id === profile.id ? 'is-selected' : ''} ${state.activeProxyId === profile.id ? 'is-active' : ''}`}
|
|
onClick={() => setDraft({ ...profile, bypass: [...profile.bypass] })}
|
|
>
|
|
<span className="proxy-profile-icon"><Network size={16} /></span>
|
|
<span><strong>{profile.name}</strong><small>{proxyProfileDetail(profile)}</small></span>
|
|
{state.activeProxyId === profile.id && <i>使用中</i>}
|
|
<ChevronRight size={15} />
|
|
</button>)}
|
|
</div>
|
|
</section>
|
|
|
|
<section className="proxy-profile-editor">
|
|
<div className="proxy-editor-heading">
|
|
<div><span>{draft.builtin ? '内置出口' : '自定义出口'}</span><h2>{draft.name}</h2></div>
|
|
<span className={`proxy-live-state ${state.activeProxyId === draft.id ? 'is-live' : ''}`}><i />{state.activeProxyId === draft.id ? '当前生效' : '未使用'}</span>
|
|
</div>
|
|
<div className="proxy-form-grid">
|
|
<Field label="名称"><input value={draft.name} disabled={draft.id === 'direct' || draft.id === 'system'} onChange={(event) => setDraft({ ...draft, name: event.target.value })} /></Field>
|
|
<Field label="类型"><select value={draft.kind} disabled={draft.builtin} onChange={(event) => setDraft({ ...draft, kind: event.target.value as ProxyProfile['kind'] })}>{Object.entries(PROXY_KIND_LABELS).map(([value, label]) => <option key={value} value={value}>{label}</option>)}</select></Field>
|
|
{draft.kind === 'fixed_servers' && <>
|
|
<Field label="协议"><select value={draft.scheme || 'http'} onChange={(event) => setDraft({ ...draft, scheme: event.target.value as ProxyProfile['scheme'] })}><option value="http">HTTP</option><option value="https">HTTPS</option><option value="socks4">SOCKS4</option><option value="socks5">SOCKS5</option></select></Field>
|
|
<Field label="主机"><input value={draft.host || ''} onChange={(event) => setDraft({ ...draft, host: event.target.value })} /></Field>
|
|
<Field label="端口"><input type="number" min="1" max="65535" value={draft.port || ''} onChange={(event) => setDraft({ ...draft, port: Number(event.target.value) })} /></Field>
|
|
<Field label="绕过列表" hint="每行一个域名、IP 或 <local>"><textarea rows={5} value={draft.bypass.join('\n')} onChange={(event) => setDraft({ ...draft, bypass: event.target.value.split('\n').map((item) => item.trim()).filter(Boolean) })} /></Field>
|
|
</>}
|
|
{draft.kind === 'pac_script' && <>
|
|
<Field label="PAC URL"><input value={draft.pacUrl || ''} onChange={(event) => setDraft({ ...draft, pacUrl: event.target.value, pacScript: '' })} placeholder="https://example.com/proxy.pac" /></Field>
|
|
<Field label="内联 PAC"><textarea rows={10} value={draft.pacScript || ''} onChange={(event) => setDraft({ ...draft, pacScript: event.target.value, pacUrl: '' })} /></Field>
|
|
</>}
|
|
</div>
|
|
{draft.kind === 'fixed_servers' && <section className="proxy-auth-section">
|
|
<label><span><KeyRound size={16} /><span><strong>代理认证</strong><small>{passwordConfigured ? '已保存本次浏览器会话的凭据' : '凭据仅保存在浏览器 session'}</small></span></span><Switch checked={Boolean(draft.authEnabled)} onCheckedChange={(checked) => setDraft({ ...draft, authEnabled: checked })} /></label>
|
|
{draft.authEnabled && <div className="proxy-auth-fields"><Field label="用户名"><input value={draft.authUsername || ''} onChange={(event) => setDraft({ ...draft, authUsername: event.target.value })} /></Field><Field label="密码"><input type="password" value={password} onChange={(event) => setPassword(event.target.value)} placeholder={passwordConfigured ? '留空以保留当前密码' : '输入密码'} /></Field></div>}
|
|
</section>}
|
|
<div className="proxy-editor-actions">
|
|
<Button variant="primary" disabled={busy || !draft.name || (draft.kind === 'fixed_servers' && (!draft.host || !draft.port))} onClick={() => void save()}><Save size={16} />保存</Button>
|
|
<Button disabled={busy} onClick={() => void run(async () => setState(await request('proxy.switch', { id: draft.id })), `${draft.name} 已启用`)}><Power size={16} />立即使用</Button>
|
|
{!draft.builtin && <Button variant="danger" disabled={busy} onClick={() => void remove()}><Trash2 size={16} />删除</Button>}
|
|
</div>
|
|
</section>
|
|
</div>
|
|
</div>;
|
|
}
|