feat: 新增自定义配置合并到 Surge 输出
管理面板新增「自定义」面板,可按 Surge 格式书写 [Proxy]/[Rule]/ [General] 等配置片段,生成时合并进最终输出:[Proxy] 行追加到节点 之后,[Rule] 行注入到规则最顶部(优先级最高),其他段注入对应段 顶部且同名 key 覆盖模板取值(如 doh-server)。仅管理员链接生效, 游客链接不包含自定义内容。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1134
web/pnpm-lock.yaml
generated
Normal file
1134
web/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -4,6 +4,7 @@ import Subscriptions from './components/Subscriptions';
|
||||
import StaticNodes from './components/StaticNodes';
|
||||
import NodeSelector from './components/NodeSelector';
|
||||
import Rules from './components/Rules';
|
||||
import CustomConfig from './components/CustomConfig';
|
||||
import Output from './components/Output';
|
||||
import Guests from './components/Guests';
|
||||
import { auth, setToken } from './api';
|
||||
@@ -147,6 +148,7 @@ export default function App() {
|
||||
'static-nodes': <StaticNodes />,
|
||||
'node-selector': <NodeSelector />,
|
||||
rules: <Rules />,
|
||||
'custom-config': <CustomConfig />,
|
||||
output: <Output />,
|
||||
guests: <Guests />,
|
||||
};
|
||||
|
||||
@@ -124,6 +124,11 @@ export const config = {
|
||||
preview: () => request<{ config: string }>('/config/preview'),
|
||||
getSurgeToken: () => request<{ token: string }>('/config/surge-token'),
|
||||
regenerateSurgeToken: () => request<{ token: string }>('/config/surge-token', { method: 'POST' }),
|
||||
getCustom: () => request<{ content: string }>('/config/custom'),
|
||||
saveCustom: (content: string) => request<{ ok: boolean }>('/config/custom', {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({ content }),
|
||||
}),
|
||||
};
|
||||
|
||||
// Guest links
|
||||
|
||||
116
web/src/components/CustomConfig.tsx
Normal file
116
web/src/components/CustomConfig.tsx
Normal file
@@ -0,0 +1,116 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { config as configApi } from '../api';
|
||||
|
||||
const PLACEHOLDER = `[Proxy]
|
||||
EN5-DIRECT = direct, interface = en5, allow-other-interface = false, dns-follow-interface = true
|
||||
|
||||
[Rule]
|
||||
# 目标应用固定走 en5
|
||||
PROCESS-NAME,/Applications/YourApp.app/,EN5-DIRECT
|
||||
|
||||
# 其他所有流量按照 macOS 系统默认路由出网
|
||||
FINAL,DIRECT`;
|
||||
|
||||
export default function CustomConfig() {
|
||||
const [content, setContent] = useState('');
|
||||
const [saved, setSaved] = useState('');
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [message, setMessage] = useState('');
|
||||
const [error, setError] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
configApi.getCustom().then(data => {
|
||||
setContent(data.content);
|
||||
setSaved(data.content);
|
||||
}).catch(() => {});
|
||||
}, []);
|
||||
|
||||
const dirty = content !== saved;
|
||||
|
||||
const handleSave = async () => {
|
||||
setSaving(true);
|
||||
setMessage('');
|
||||
setError('');
|
||||
try {
|
||||
await configApi.saveCustom(content);
|
||||
setSaved(content);
|
||||
setMessage('已保存,将合并到 Surge 输出配置');
|
||||
setTimeout(() => setMessage(''), 3000);
|
||||
} catch (err: any) {
|
||||
setError(err.message || '保存失败');
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2 style={styles.title}>自定义配置</h2>
|
||||
<p style={styles.subtitle}>
|
||||
按 Surge 配置格式书写(如 [Proxy]、[Rule]),保存后合并到最终输出的 Surge 配置中
|
||||
</p>
|
||||
|
||||
<div style={styles.hints}>
|
||||
<div>· [Proxy] 段的自定义节点会追加到输出的 [Proxy] 末尾(不加入策略组,供规则直接引用)</div>
|
||||
<div>· [Rule] 段的自定义规则注入到输出 [Rule] 的最顶部,优先级高于「规则」面板中的规则</div>
|
||||
<div>· 其他段(如 [Host])注入到对应段顶部,模板中不存在的段会追加到文件末尾</div>
|
||||
<div>· 仅对管理员订阅链接生效,游客链接不包含自定义配置</div>
|
||||
</div>
|
||||
|
||||
<textarea
|
||||
value={content}
|
||||
onChange={e => setContent(e.target.value)}
|
||||
placeholder={PLACEHOLDER}
|
||||
spellCheck={false}
|
||||
style={styles.textarea}
|
||||
/>
|
||||
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 12, marginTop: 12 }}>
|
||||
<button className="primary" onClick={handleSave} disabled={saving || !dirty}>
|
||||
{saving ? '保存中...' : '保存'}
|
||||
</button>
|
||||
{dirty && <span style={{ fontSize: 12, color: 'var(--warning, #e0af68)' }}>有未保存的修改</span>}
|
||||
{message && <span style={{ fontSize: 12, color: 'var(--success)' }}>{message}</span>}
|
||||
{error && <span style={{ fontSize: 12, color: 'var(--danger)' }}>{error}</span>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = {
|
||||
title: {
|
||||
fontFamily: 'var(--font-mono)' as const,
|
||||
fontSize: 16,
|
||||
fontWeight: 600 as const,
|
||||
color: 'var(--text-primary)',
|
||||
marginBottom: 4,
|
||||
},
|
||||
subtitle: {
|
||||
fontSize: 12,
|
||||
color: 'var(--text-secondary)',
|
||||
marginBottom: 12,
|
||||
},
|
||||
hints: {
|
||||
background: 'var(--bg-input)',
|
||||
border: '1px solid var(--border)',
|
||||
borderRadius: 'var(--radius)',
|
||||
padding: '10px 14px',
|
||||
fontSize: 11,
|
||||
lineHeight: 1.8,
|
||||
color: 'var(--text-muted)',
|
||||
marginBottom: 16,
|
||||
},
|
||||
textarea: {
|
||||
width: '100%',
|
||||
minHeight: 'calc(100vh - 400px)',
|
||||
background: 'var(--bg-input)',
|
||||
border: '1px solid var(--border)',
|
||||
borderRadius: 'var(--radius)',
|
||||
padding: 16,
|
||||
fontFamily: 'var(--font-mono)' as const,
|
||||
fontSize: 12,
|
||||
lineHeight: 1.6,
|
||||
color: 'var(--text-primary)',
|
||||
resize: 'vertical' as const,
|
||||
},
|
||||
};
|
||||
@@ -1,13 +1,14 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { stats as statsApi } from '../api';
|
||||
|
||||
type Panel = 'subscriptions' | 'static-nodes' | 'node-selector' | 'rules' | 'output' | 'guests';
|
||||
type Panel = 'subscriptions' | 'static-nodes' | 'node-selector' | 'rules' | 'custom-config' | 'output' | 'guests';
|
||||
|
||||
const NAV_ITEMS: { key: Panel; label: string; icon: string }[] = [
|
||||
{ key: 'subscriptions', label: '订阅', icon: '⟐' },
|
||||
{ key: 'static-nodes', label: '节点', icon: '◈' },
|
||||
{ key: 'node-selector', label: '选择', icon: '☰' },
|
||||
{ key: 'rules', label: '规则', icon: '⧖' },
|
||||
{ key: 'custom-config', label: '自定义', icon: '✎' },
|
||||
{ key: 'output', label: '输出', icon: '▸' },
|
||||
{ key: 'guests', label: '游客', icon: '◐' },
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user