Files
Ant-Browser/frontend/src/modules/browser/pages/proxyPool/ProxyPoolModals.tsx
T

342 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { Button, FormItem, Input, Modal, Select, Table, Textarea } from '../../../../shared/components'
import type { TableColumn } from '../../../../shared/components/Table'
import type { ProxyIPHealthResult } from '../../types'
import {
type ChainImportForm,
type ProxyDisplayInfo,
type ProxyImportMode,
} from './helpers'
export interface ProxyEditFormValue {
proxyName: string
proxyConfig: string
preferredKernel: string
dnsServers: string
groupName: string
}
export { ProxyPoolImportModal } from './ProxyPoolImportModal'
interface ProxyPoolPreviewModalProps {
open: boolean
importMode: ProxyImportMode
importDnsServers: string
previewList: ProxyDisplayInfo[]
removedPreviewProxyNames: string[]
importing: boolean
onClose: () => void
onBack: () => void
onConfirm: () => void
onRemoveProxy: (proxyId: string) => void
}
export function ProxyPoolPreviewModal({
open,
importMode,
importDnsServers,
previewList,
removedPreviewProxyNames,
importing,
onClose,
onBack,
onConfirm,
onRemoveProxy,
}: ProxyPoolPreviewModalProps) {
const previewColumns: TableColumn<ProxyDisplayInfo>[] = [
{ key: 'proxyName', title: '代理名称', width: '200px' },
{ key: 'type', title: '类型', width: '100px' },
{ key: 'server', title: '服务器', width: '200px' },
{ key: 'port', title: '端口', width: '100px', render: (value) => value || '-' },
{
key: 'actions',
title: '操作',
width: '96px',
render: (_, record) => (
<Button size="sm" variant="danger" onClick={() => onRemoveProxy(record.proxyId)}>
删除
</Button>
),
},
]
return (
<Modal
open={open}
onClose={onClose}
title="确认导入以下代理"
width="700px"
footer={
<>
<Button variant="secondary" onClick={onBack}>
返回修改
</Button>
<Button onClick={onConfirm} loading={importing} disabled={previewList.length === 0}>
确认导入
</Button>
</>
}
>
<div className="space-y-3">
{importMode === 'clash' && importDnsServers.trim() && (
<p className="text-xs text-[var(--color-text-muted)] bg-[var(--color-bg-secondary)] px-3 py-2 rounded">
已配置批量 DNS,将应用到以下所有代理
</p>
)}
<p className="text-xs text-[var(--color-text-muted)]">
保留 {previewList.length} 条,删除 {removedPreviewProxyNames.length} 条。删除项不会进入后续比较环节。
</p>
<Table columns={previewColumns} data={previewList} rowKey="proxyId" maxHeight="380px" emptyText="无代理数据" />
</div>
</Modal>
)
}
interface ProxyPoolEditModalProps {
open: boolean
saving: boolean
groups: string[]
editForm: ProxyEditFormValue
chainEditMode: boolean
chainEditForm: ChainImportForm
onClose: () => void
onSave: () => void
onChange: (patch: Partial<ProxyEditFormValue>) => void
onChainEditFormChange: (patch: Partial<ChainImportForm>) => void
onChainEditHopChange: (hop: 'first' | 'second', field: keyof ChainImportForm['first'], value: string) => void
}
export function ProxyPoolEditModal({
open,
saving,
groups,
editForm,
chainEditMode,
chainEditForm,
onClose,
onSave,
onChange,
onChainEditFormChange,
onChainEditHopChange,
}: ProxyPoolEditModalProps) {
return (
<Modal
open={open}
onClose={onClose}
title="编辑代理"
width="500px"
footer={
<>
<Button variant="secondary" onClick={onClose}>
取消
</Button>
<Button onClick={onSave} loading={saving}>
保存
</Button>
</>
}
>
<div className="space-y-4">
<FormItem label="代理名称" required>
<Input
value={chainEditMode ? chainEditForm.proxyName : editForm.proxyName}
onChange={(event) => {
if (chainEditMode) {
onChainEditFormChange({ proxyName: event.target.value })
return
}
onChange({ proxyName: event.target.value })
}}
placeholder="节点名称"
/>
</FormItem>
<FormItem label="分组名称(可选)">
<Input
value={editForm.groupName}
onChange={(event) => onChange({ groupName: event.target.value })}
placeholder="分组名称"
list="edit-proxy-groups-datalist"
/>
<datalist id="edit-proxy-groups-datalist">
{groups.map((group) => (
<option key={group} value={group} />
))}
</datalist>
</FormItem>
<FormItem label="代理内核">
<Select
value={editForm.preferredKernel || 'auto'}
onChange={(event) => onChange({ preferredKernel: event.target.value })}
options={[
{ value: 'auto', label: '自动' },
{ value: 'xray', label: 'Xray' },
{ value: 'sing-box', label: 'sing-box' },
{ value: 'mihomo', label: 'Mihomo' },
]}
/>
</FormItem>
{chainEditMode ? (
<div className="space-y-4">
<FormItem label="本地监听端口(可选)">
<Input
type="number"
min={1}
max={65535}
value={chainEditForm.localPort}
onChange={(event) => onChainEditFormChange({ localPort: event.target.value })}
placeholder="留空自动分配"
/>
</FormItem>
<div className="rounded-md border border-[var(--color-border)] p-3 space-y-3">
<h4 className="text-sm font-medium text-[var(--color-text-primary)]">第一层代理</h4>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
<FormItem label="协议">
<Select
value={chainEditForm.first.protocol}
onChange={(event) => onChainEditHopChange('first', 'protocol', event.target.value)}
options={[
{ value: 'http', label: 'HTTP' },
{ value: 'socks5', label: 'SOCKS5' },
]}
/>
</FormItem>
<FormItem label="代理地址" required>
<Input
value={chainEditForm.first.server}
onChange={(event) => onChainEditHopChange('first', 'server', event.target.value)}
/>
</FormItem>
<FormItem label="代理端口" required>
<Input
type="number"
min={1}
max={65535}
value={chainEditForm.first.port}
onChange={(event) => onChainEditHopChange('first', 'port', event.target.value)}
/>
</FormItem>
<FormItem label="账号(可选)">
<Input
value={chainEditForm.first.username}
onChange={(event) => onChainEditHopChange('first', 'username', event.target.value)}
/>
</FormItem>
<FormItem label="密码(可选)">
<Input
type="password"
value={chainEditForm.first.password}
onChange={(event) => onChainEditHopChange('first', 'password', event.target.value)}
/>
</FormItem>
</div>
</div>
<div className="rounded-md border border-[var(--color-border)] p-3 space-y-3">
<h4 className="text-sm font-medium text-[var(--color-text-primary)]">第二层代理</h4>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
<FormItem label="协议">
<Select
value={chainEditForm.second.protocol}
onChange={(event) => onChainEditHopChange('second', 'protocol', event.target.value)}
options={[
{ value: 'http', label: 'HTTP' },
{ value: 'socks5', label: 'SOCKS5' },
]}
/>
</FormItem>
<FormItem label="代理地址" required>
<Input
value={chainEditForm.second.server}
onChange={(event) => onChainEditHopChange('second', 'server', event.target.value)}
/>
</FormItem>
<FormItem label="代理端口" required>
<Input
type="number"
min={1}
max={65535}
value={chainEditForm.second.port}
onChange={(event) => onChainEditHopChange('second', 'port', event.target.value)}
/>
</FormItem>
<FormItem label="账号(可选)">
<Input
value={chainEditForm.second.username}
onChange={(event) => onChainEditHopChange('second', 'username', event.target.value)}
/>
</FormItem>
<FormItem label="密码(可选)">
<Input
type="password"
value={chainEditForm.second.password}
onChange={(event) => onChainEditHopChange('second', 'password', event.target.value)}
/>
</FormItem>
</div>
</div>
</div>
) : (
<FormItem label="代理配置">
<Textarea
value={editForm.proxyConfig}
onChange={(event) => onChange({ proxyConfig: event.target.value })}
rows={10}
placeholder="支持 Clash YAML、http://、https://、socks5://、chain+socks5://"
/>
</FormItem>
)}
<FormItem label="DNS 服务器(可选)">
<Textarea
value={editForm.dnsServers}
onChange={(event) => onChange({ dnsServers: event.target.value })}
rows={6}
placeholder={`dns:\n enable: true\n nameserver:\n - 119.29.29.29\n - 223.5.5.5`}
/>
</FormItem>
</div>
</Modal>
)
}
interface ProxyPoolIPHealthDetailModalProps {
open: boolean
detail: ProxyIPHealthResult | null
onClose: () => void
}
export function ProxyPoolIPHealthDetailModal({
open,
detail,
onClose,
}: ProxyPoolIPHealthDetailModalProps) {
return (
<Modal
open={open}
onClose={onClose}
title="IP健康原始返回"
width="760px"
footer={
<Button variant="secondary" onClick={onClose}>
关闭
</Button>
}
>
<div className="space-y-3">
{detail && (
<>
<div className="text-xs text-[var(--color-text-muted)]">
代理ID{detail.proxyId} | 来源:{detail.source} | 时间:{detail.updatedAt}
</div>
{!detail.ok && <div className="text-sm text-red-500">{detail.error || '检测失败'}</div>}
<pre className="max-h-[420px] overflow-auto text-xs leading-5 rounded-lg bg-[var(--color-bg-secondary)] border border-[var(--color-border)] p-3">
{JSON.stringify(detail.rawData || {}, null, 2)}
</pre>
</>
)}
</div>
</Modal>
)
}