import view from "view"; import uci from "uci"; import { CORE_TYPE, LuciFlied, HijpassValues, PROXY_TYPE, ProxyType } from "../enum/hijpass"; import form from "form"; import { SaveApplyUtils } from "../utils/actions/save-apply/index"; import { ServiceUtils } from "../utils/base/luci/service"; import { FormUtils } from "../utils/base/luci/form"; import { ValidationUtils } from "../utils/base/luci/validation"; import { FilePathUtils } from "../utils/base/files/paths"; import { UciUtils } from "../utils/base/luci/uci"; import { ProxyFormUtils } from "../utils/feature/proxy/form"; import { ProxyChainUtils } from "../utils/feature/proxy/chain"; import { findProxyDependencyCycleFrom } from "../utils/feature/proxy/validation"; import { NotificationUtils } from "../utils/base/luci/notification"; function getProxyReferrers(nodeName: string): string[] { return ProxyChainUtils.getProxySections() .filter((section: any) => ProxyChainUtils.getProxyDependencies(section).includes(nodeName)) .map((section: any) => ProxyChainUtils.getProxyName(section)); } function getFirewallReferrers(listenPort: string): string[] { if (!listenPort) return []; const referrers: string[] = []; if (uci.get_first(LuciFlied.CONF_NAME, LuciFlied.FIREWALL_SECTION_TYPE, 'proxy_port') === listenPort) { referrers.push(_('Proxy Port')); } if (uci.get_first(LuciFlied.CONF_NAME, LuciFlied.FIREWALL_SECTION_TYPE, 'shunt_port') === listenPort) { referrers.push(_('Default Shunt Port')); } return referrers; } const v = view.extend({ load: function () { return Promise.all([ uci.load(LuciFlied.CONF_NAME), ServiceUtils.getServiceInstances(LuciFlied.CONF_NAME) ]); }, handleSaveApply: SaveApplyUtils.genHandleSaveApply(), handleSave: function (ev?: Event) { return view.prototype.handleSave.call(this, ev); }, render: async function (data: any[]) { let m = new form.Map(LuciFlied.CONF_NAME, _('Proxy Nodes'), _('Configure Proxy Nodes')); ProxyFormUtils.createShareLinkImportSection(m); let s = m.section(form.GridSection, LuciFlied.PROXY_NODE_TYPE) FormUtils.setAnonymousGridSectionMembers(s); // 重写 handleAdd 方法,使用自定义的唯一 ID 生成器 s.handleAdd = function (ev: Event, name?: string) { let sectionId = UciUtils.generateUniqueSectionId(LuciFlied.CONF_NAME); return L.bind(form.GridSection.prototype.handleAdd, this, ev, sectionId)(); }; s.handleRemove = function (sectionId: string, ev: Event) { const nodeName = uci.get(LuciFlied.CONF_NAME, sectionId, 'name') || sectionId; const listenPort = uci.get(LuciFlied.CONF_NAME, sectionId, 'listen_port'); const firewallReferrers = getFirewallReferrers(listenPort); if (firewallReferrers.length > 0) { NotificationUtils.warning( _('Cannot Delete Proxy Node'), _('Proxy node "%s" listen port %s is referenced by firewall settings: %s. Remove the references first.') .format(nodeName, listenPort, firewallReferrers.join(', ')), 8000 ); return; } const referrers = getProxyReferrers(nodeName); if (referrers.length > 0) { NotificationUtils.warning( _('Cannot Delete Proxy Node'), _('Proxy node "%s" is referenced by proxy nodes: %s. Remove the references first.') .format(nodeName, referrers.join(', ')), 8000 ); return; } return form.GridSection.prototype.handleRemove.call(this, sectionId, ev); }; const instances = data[1] ?? {}; createProxyOptions(s, instances); return m.render(); }, }); /** * 创建启用状态选项 */ function createEnabledOption(s: LuCI.form.AbstractSection) { let o = s.option(form.Flag, 'enabled', _('Enable'), ''); o.modalonly = true; o.default = '1'; o.rmempty = false; return o; } /** * 创建节点名称编辑选项 */ function createNodeNameOption(s: LuCI.form.AbstractSection) { let o = s.option(form.Value, 'name', _('Node Name'), _('Modify Proxy Node Name')); o.modalonly = true; o.validate = ValidationUtils.createTagValidator(LuciFlied.CONF_NAME, LuciFlied.PROXY_NODE_TYPE, 'name') return o; } /** * 创建Grid表格显示选项 */ function createGridDisplayOptions(s: LuCI.form.AbstractSection, instances: any) { let typeOpt = s.option(form.TextValue, 'type', _('Type'), _('Proxy Type')); typeOpt.modalonly = false; typeOpt.textvalue = function (section_id: string) { return ProxyFormUtils.renderProxyType(section_id); }; // 显示服务器信息(不同类型显示不同内容) let serverInfoOpt = s.option(form.TextValue, 'server_info', _('Configuration Info'), ''); serverInfoOpt.modalonly = false; serverInfoOpt.textvalue = function (section_id: string) { return ProxyFormUtils.renderProxyServerInfo(section_id); }; // 运行状态列 let statusOpt = s.option(form.TextValue, 'run_status', _('Status'), ''); statusOpt.modalonly = false; statusOpt.textvalue = function (section_id: string) { return ProxyFormUtils.renderRunStatus(section_id, instances); }; // 启用状态(在表格中显示并可直接修改) let enabledOpt = createEnabledOption(s); enabledOpt.editable = true; enabledOpt.modalonly = false; // 操作列:预览 / 日志 / 测速,三个按钮合并在一列 let actionsOpt = s.option(form.TextValue, 'actions', ''); actionsOpt.modalonly = false; actionsOpt.textvalue = function (section_id: string) { return ProxyFormUtils.createGridActionButtons(section_id); }; } /** * 创建自定义代理的特定选项 */ function createCustomProxyOptions(s: LuCI.form.AbstractSection) { // 运行命令,{conf_path} 为配置文件路径占位符 let cmdOpt = s.option(form.Value, 'command', _('Run Command'), _('Proxy Run Command')); cmdOpt.default = '/usr/bin/xray -c {conf_path}'; cmdOpt.rmempty = false; cmdOpt.modalonly = true; cmdOpt.depends('core', CORE_TYPE.CUSTOM); // 配置文件内容编辑器,路径自动生成为 $client_dir/$name-$section_id.json let confOpt = s.option(form.TextValue, 'custom_conf', _('Configuration File Content'), _('Edit Configuration File')); confOpt.modalonly = true; confOpt.depends('core', CORE_TYPE.CUSTOM); FormUtils.setConfContentOptionMembers(confOpt, LuciFlied.CONF_NAME, FilePathUtils.getFilePath('client_dir')); } /** * 创建自定义代理的环境变量和日志选项 */ function createProxyEnvironmentOptions(s: LuCI.form.AbstractSection) { let procdEnvOpt = s.option(form.DynamicList, 'procd_env', _('Procd Environment Variables'), _('Procd Environment Variables')); procdEnvOpt.modalonly = true; let logLevelOpt = s.option(form.ListValue, 'log_level', _('Log Level'), _('Core Log Level')); logLevelOpt.modalonly = true; logLevelOpt.default = 'info'; logLevelOpt.rmempty = false; logLevelOpt.depends('core', CORE_TYPE.XRAY); logLevelOpt.depends('core', CORE_TYPE.SING_BOX); HijpassValues.PROXY_LOG_LEVELS.forEach(level => logLevelOpt.value(level)); FormUtils.createLogFlagOption(s, LuciFlied.CONF_NAME, undefined) } function addLoadBalanceDepends(o: LuCI.form.AbstractValue, core?: string) { if (core) { o.depends({type: PROXY_TYPE.LOAD_BALANCE, core}); return; } o.depends({type: PROXY_TYPE.LOAD_BALANCE, core: CORE_TYPE.SING_BOX}); o.depends({type: PROXY_TYPE.LOAD_BALANCE, core: CORE_TYPE.XRAY}); } function createLoadBalanceOptions(s: LuCI.form.AbstractSection) { const members = s.option(form.DynamicList, 'member_node', _('Load Balancing Nodes'), _('Select proxy nodes to connect through their local SOCKS ports')); members.modalonly = true; members.rmempty = false; addLoadBalanceDepends(members); members.load = function (sectionId: string) { delete this.keylist; delete this.vallist; const currentName = this.section.formvalue(sectionId, 'name') || uci.get(LuciFlied.CONF_NAME, sectionId, 'name') || sectionId; ProxyChainUtils.getProxySections().forEach((section: any) => { const name = ProxyChainUtils.getProxyName(section); if (!name || name === currentName || section['.name'] === sectionId || section.type === PROXY_TYPE.LOAD_BALANCE) { return; } let label = name; if (section.socks_port) label += ' (' + section.socks_port + ')'; if (section.enabled !== '1') label += ' - ' + _('Disabled'); this.value(name, label); }); return form.DynamicList.prototype.load.apply(this, [sectionId]); }; members.validate = function (sectionId: string, value: string) { if (!value) return true; const currentName = this.section.formvalue(sectionId, 'name') || uci.get(LuciFlied.CONF_NAME, sectionId, 'name') || sectionId; if (value === currentName) return _('Load balancing node cannot include itself'); const sections = ProxyChainUtils.getProxySectionMap(); const member = sections.get(value); if (!member) return _('Load balancing member node not found'); if (member.type === PROXY_TYPE.LOAD_BALANCE) { return _('Nested load balancing nodes are not supported'); } const dependencyCycle = findProxyDependencyCycleFrom(currentName, [value], sections); if (dependencyCycle) { return _('Proxy node dependency chain has a cycle: %s').format(dependencyCycle.join(' -> ')); } if (member.enabled !== '1') return _('Load balancing member node is not enabled'); if (!ProxyChainUtils.isPort(member.socks_port)) { return _('Load balancing member node has no valid SOCKS port configured'); } return true; }; const url = s.option(form.Value, 'url', _('Test URL'), _('Health Check URL')); url.modalonly = true; url.default = 'https://www.gstatic.com/generate_204'; addLoadBalanceDepends(url, CORE_TYPE.SING_BOX); const interval = s.option(form.Value, 'interval', _('Test Interval'), _('Health Check Interval')); interval.modalonly = true; interval.default = '3m'; addLoadBalanceDepends(interval, CORE_TYPE.SING_BOX); const tolerance = s.option(form.Value, 'tolerance', _('Tolerance (ms)'), _('Tolerance')); tolerance.modalonly = true; tolerance.default = '50'; tolerance.datatype = 'uinteger'; addLoadBalanceDepends(tolerance, CORE_TYPE.SING_BOX); const idleTimeout = s.option(form.Value, 'idle_timeout', _('Idle Timeout'), _('Node Idle Timeout')); idleTimeout.modalonly = true; idleTimeout.default = '30m'; addLoadBalanceDepends(idleTimeout, CORE_TYPE.SING_BOX); const interrupt = s.option(form.Flag, 'interrupt_exist_connections', _('Interrupt Existing Connections'), _('Interrupt on Outbound Change')); interrupt.modalonly = true; interrupt.default = '0'; addLoadBalanceDepends(interrupt, CORE_TYPE.SING_BOX); const strategy = s.option(form.ListValue, 'strategy', _('Load Strategy'), _('Load Balancing Strategy')); strategy.modalonly = true; strategy.default = 'leastLoad'; strategy.value('random', _('Random')); strategy.value('roundRobin', _('Round Robin')); strategy.value('leastPing', _('Lowest Ping')); strategy.value('leastLoad', _('Lowest Load')); addLoadBalanceDepends(strategy, CORE_TYPE.XRAY); const expected = s.option(form.Value, 'strategy_expected', _('Best Nodes'), _('Best Nodes Count')); expected.modalonly = true; expected.default = '1'; expected.datatype = 'uinteger'; expected.depends({type: PROXY_TYPE.LOAD_BALANCE, core: CORE_TYPE.XRAY, strategy: 'leastLoad'}); const maxRtt = s.option(form.Value, 'strategy_max_rtt', _('Max Acceptable RTT'), _('Max Acceptable RTT')); maxRtt.modalonly = true; maxRtt.default = '1s'; maxRtt.depends({type: PROXY_TYPE.LOAD_BALANCE, core: CORE_TYPE.XRAY, strategy: 'leastLoad'}); const strategyTolerance = s.option(form.Value, 'strategy_tolerance', _('Failure Rate Tolerance'), _('Failure Rate Tolerance')); strategyTolerance.modalonly = true; strategyTolerance.default = '0.05'; strategyTolerance.depends({type: PROXY_TYPE.LOAD_BALANCE, core: CORE_TYPE.XRAY, strategy: 'leastLoad'}); const fallback = s.option(form.ListValue, 'fallback_tag', _('Fallback Node'), _('Fallback Outbound')); fallback.modalonly = true; fallback.value('', _('First member node')); fallback.load = function (sectionId: string) { delete this.keylist; delete this.vallist; this.value('', _('First member node')); const currentName = this.section.formvalue(sectionId, 'name') || uci.get(LuciFlied.CONF_NAME, sectionId, 'name') || sectionId; ProxyChainUtils.getProxySections().forEach((section: any) => { const name = ProxyChainUtils.getProxyName(section); if (!name || name === currentName || section.type === PROXY_TYPE.LOAD_BALANCE) return; this.value(name, name); }); return form.ListValue.prototype.load.apply(this, [sectionId]); }; fallback.validate = function (sectionId: string, value: string) { if (!value) return true; const selected = ProxyChainUtils.normalizeProxyNodeList(this.section.formvalue(sectionId, 'member_node')); return selected.indexOf(value) >= 0 ? true : _('Fallback node must be a load balancing member'); }; addLoadBalanceDepends(fallback, CORE_TYPE.XRAY); const probeFields: Array<[string, string, string, string]> = [ ['probe_url', _('Probe URL'), _('Health Check Probe URL'), 'https://connectivitycheck.gstatic.com/generate_204'], ['probe_connectivity', _('Local Connectivity Check URL'), _('Local Connectivity Check URL'), ''], ['probe_interval', _('Probe Interval'), _('Probe Interval'), '1m'], ['probe_timeout', _('Probe Timeout'), _('Probe Timeout'), '5s'], ]; probeFields.forEach(([name, title, description, defaultValue]) => { const option = s.option(form.Value, name, title, description); option.modalonly = true; if (defaultValue) option.default = defaultValue; option.depends({type: PROXY_TYPE.LOAD_BALANCE, core: CORE_TYPE.XRAY, strategy: 'leastPing'}); option.depends({type: PROXY_TYPE.LOAD_BALANCE, core: CORE_TYPE.XRAY, strategy: 'leastLoad'}); }); const sampling = s.option(form.Value, 'probe_sampling', _('Results to Retain'), _('Retain Recent Probe Results')); sampling.modalonly = true; sampling.default = '10'; sampling.datatype = 'uinteger'; sampling.depends({type: PROXY_TYPE.LOAD_BALANCE, core: CORE_TYPE.XRAY, strategy: 'leastPing'}); sampling.depends({type: PROXY_TYPE.LOAD_BALANCE, core: CORE_TYPE.XRAY, strategy: 'leastLoad'}); const method = s.option(form.ListValue, 'probe_http_method', _('Probe HTTP Method'), _('Probe HTTP Method')); method.modalonly = true; method.value('', _('Default (HEAD)')); method.value('HEAD', 'HEAD'); method.value('GET', 'GET'); method.depends({type: PROXY_TYPE.LOAD_BALANCE, core: CORE_TYPE.XRAY, strategy: 'leastPing'}); method.depends({type: PROXY_TYPE.LOAD_BALANCE, core: CORE_TYPE.XRAY, strategy: 'leastLoad'}); } /** * 创建通用的服务器地址和端口选项(用于所有需要server/server_port的协议) */ function createCommonServerOptions(s: LuCI.form.AbstractSection) { let serverAddrOpt = s.option(form.Value, 'server', _('Server Address'), _('Proxy Server Address')); serverAddrOpt.modalonly = true; serverAddrOpt.rmempty = false; ProxyFormUtils.forProtocols(serverAddrOpt, [PROXY_TYPE.HYSTERIA2, PROXY_TYPE.SHADOWSOCKS, PROXY_TYPE.TUIC, PROXY_TYPE.SHADOWTLS, PROXY_TYPE.VLESS]); let serverPortOpt = s.option(form.Value, 'server_port', _('Server Port'), _('Proxy Server Port')); serverPortOpt.datatype = 'port'; serverPortOpt.modalonly = true; serverPortOpt.rmempty = false; ProxyFormUtils.forProtocols(serverPortOpt, [PROXY_TYPE.HYSTERIA2, PROXY_TYPE.SHADOWSOCKS, PROXY_TYPE.TUIC, PROXY_TYPE.SHADOWTLS, PROXY_TYPE.VLESS]); } /** * 创建Hysteria2代理的高级选项 */ function createHysteria2AdvancedOptions(s: LuCI.form.AbstractSection) { let serverPortsOpt = s.option(form.DynamicList, 'server_ports', _('Port Hopping'), _('Port Hopping Range')); serverPortsOpt.modalonly = true; serverPortsOpt.datatype = 'portrange'; serverPortsOpt.depends({ type: PROXY_TYPE.HYSTERIA2, core: CORE_TYPE.SING_BOX }); let hopIntervalOpt = s.option(form.Value, 'hop_interval', _('Port Hop Interval'), _('Port Hop Interval')); hopIntervalOpt.modalonly = true; // hop_interval 依赖于类型是 Hysteria2 且 server_ports 有值 hopIntervalOpt.depends({ type: PROXY_TYPE.HYSTERIA2, core: CORE_TYPE.SING_BOX, server_ports: /./ }); let upMbpsOpt = s.option(form.Value, 'up_mbps', _('Uplink Speed'), _('Maximum Uplink Bandwidth')); upMbpsOpt.datatype = 'uinteger'; upMbpsOpt.modalonly = true; upMbpsOpt.depends({ type: PROXY_TYPE.HYSTERIA2, core: CORE_TYPE.SING_BOX }); upMbpsOpt.depends({ type: PROXY_TYPE.HYSTERIA2, core: CORE_TYPE.XRAY }); let downMbpsOpt = s.option(form.Value, 'down_mbps', _('Downlink Speed'), _('Maximum Downlink Bandwidth')); downMbpsOpt.datatype = 'uinteger'; downMbpsOpt.modalonly = true; downMbpsOpt.depends({ type: PROXY_TYPE.HYSTERIA2, core: CORE_TYPE.SING_BOX }); downMbpsOpt.depends({ type: PROXY_TYPE.HYSTERIA2, core: CORE_TYPE.XRAY }); } /** * 创建Hysteria2代理的Xray核心独有选项 */ function createHysteria2XrayOptions(s: LuCI.form.AbstractSection) { let udpIdleTimeoutOpt = s.option(form.Value, 'udp_idle_timeout', _('UDP Idle Timeout'), _('UDP connection idle timeout in seconds')); udpIdleTimeoutOpt.datatype = 'uinteger'; udpIdleTimeoutOpt.modalonly = true; udpIdleTimeoutOpt.placeholder = '60'; udpIdleTimeoutOpt.depends({ type: PROXY_TYPE.HYSTERIA2, core: CORE_TYPE.XRAY }); } /** * 创建Hysteria2代理的OBFS选项(sing-box 核心独有) */ function createHysteria2ObfsOptions(s: LuCI.form.AbstractSection) { let obfsTypeOpt = s.option(form.ListValue, 'obfs_type', _('OBFS Type'), _('QUIC Obfuscation Type')); obfsTypeOpt.modalonly = true; obfsTypeOpt.depends({ type: PROXY_TYPE.HYSTERIA2, core: CORE_TYPE.SING_BOX }); HijpassValues.HYSTERIA2_OBFS_TYPES.forEach((item) => { obfsTypeOpt.value(item.value, _(item.label)); }); let obfsPasswordOpt = s.option(form.Value, 'obfs_password', _('OBFS Password'), _('QUIC Obfuscation Password')); obfsPasswordOpt.modalonly = true; obfsPasswordOpt.depends({ type: PROXY_TYPE.HYSTERIA2, core: CORE_TYPE.SING_BOX, obfs_type: 'salamander' }); } /** * 创建VLESS代理的网络和packet_encoding选项 * packet_encoding 只对 sing-box core 显示(xray 的 VLESS 不支持该字段) */ function createVlessNetworkOptions(s: LuCI.form.AbstractSection) { let packetEncodingOpt = s.option(form.ListValue, 'packet_encoding', _('UDP Packet Encoding'), _('UDP Packet Encoding Method')); packetEncodingOpt.modalonly = true; // packet_encoding 只对 sing-box core 有意义 packetEncodingOpt.depends({ type: PROXY_TYPE.VLESS, core: CORE_TYPE.SING_BOX }); HijpassValues.VLESS_PACKET_ENCODINGS.forEach((item) => { packetEncodingOpt.value(item.value, _(item.label)); }); } /** * 创建通用传输层类型选择(用于支持Transport的协议:VLESS等) * - 所有传输类型统一注册(含 xhttp) * - sing-box 不支持 xhttp:通过 validate 校验阻止 sing-box+xhttp 的非法组合 * - xhttp 相关子字段仅在 core:xray 时显示 */ function createTransportTypeOption(s: LuCI.form.AbstractSection) { let transportOpt = s.option(form.ListValue, 'transport_type', _('Transport Protocol'), _('Proxy Transport Protocol')); transportOpt.modalonly = true; ProxyFormUtils.forProtocols(transportOpt, [PROXY_TYPE.VLESS]); HijpassValues.V2RAY_TRANSPORT_TYPES_XRAY.forEach((item) => { transportOpt.value(item.value, _(item.label)); }); // sing-box 不支持 xhttp // 用 this.section.formvalue 读取表单中 core 字段的当前值(未保存的实时值), // 而非 uci.get(那只能读到上次保存的旧值) transportOpt.validate = function (sectionId: string, value: string) { const core = this.section.formvalue(sectionId, 'core'); const realityEnabled = this.section.formvalue(sectionId, 'tls_reality_enabled'); if (value === 'xhttp' && core !== CORE_TYPE.XRAY) { return _('XHTTP transport is only supported by Xray core'); } if (core === CORE_TYPE.XRAY && realityEnabled === '1' && (value === 'ws' || value === 'httpupgrade')) { return _('Xray REALITY only supports RAW, XHTTP and gRPC transports'); } return true; }; } /** * 创建通用的HTTP传输选项(用于支持Transport的协议:VLESS等) */ function createTransportHttpOptions(s: LuCI.form.AbstractSection) { let hostOpt = s.option(form.Value, 'transport_http_host', _('HTTP Host'), _('HTTP Host List')); hostOpt.modalonly = true; ProxyFormUtils.forProtocols(hostOpt, [PROXY_TYPE.VLESS], { transport_type: 'http' }); let pathOpt = s.option(form.Value, 'transport_http_path', _('HTTP Path'), _('HTTP Request Path')); pathOpt.modalonly = true; ProxyFormUtils.forProtocols(pathOpt, [PROXY_TYPE.VLESS], { transport_type: 'http' }); let methodOpt = s.option(form.Value, 'transport_http_method', _('HTTP Method'), _('HTTP Request Method')); methodOpt.modalonly = true; ProxyFormUtils.forProtocols(methodOpt, [PROXY_TYPE.VLESS], { transport_type: 'http' }); let idleOpt = s.option(form.Value, 'transport_http_idle_timeout', _('Idle Timeout'), _('Idle Timeout')); idleOpt.modalonly = true; idleOpt.default = '15s'; ProxyFormUtils.forProtocols(idleOpt, [PROXY_TYPE.VLESS], { transport_type: 'http' }); let pingOpt = s.option(form.Value, 'transport_http_ping_timeout', _('PING Timeout'), _('PING Response Timeout')); pingOpt.modalonly = true; pingOpt.default = '15s'; ProxyFormUtils.forProtocols(pingOpt, [PROXY_TYPE.VLESS], { transport_type: 'http' }); } /** * 创建通用的WebSocket传输选项(用于支持Transport的协议:VLESS等) */ function createTransportWsOptions(s: LuCI.form.AbstractSection) { let hostOpt = s.option(form.Value, 'transport_ws_host', _('WebSocket Host'), _('WebSocket Host Domain')); hostOpt.modalonly = true; ProxyFormUtils.forProtocols(hostOpt, [PROXY_TYPE.VLESS], { transport_type: 'ws' }); let pathOpt = s.option(form.Value, 'transport_ws_path', _('WebSocket Path'), _('HTTP Request Path')); pathOpt.modalonly = true; ProxyFormUtils.forProtocols(pathOpt, [PROXY_TYPE.VLESS], { transport_type: 'ws' }); let earlyDataOpt = s.option(form.Value, 'transport_ws_max_early_data', _('Max Early Data'), _('Maximum allowed payload size in requests')); earlyDataOpt.datatype = 'uinteger'; earlyDataOpt.modalonly = true; ProxyFormUtils.forProtocols(earlyDataOpt, [PROXY_TYPE.VLESS], { transport_type: 'ws' }); let headerNameOpt = s.option(form.Value, 'transport_ws_early_data_header_name', _('Early Data Header Name'), _('Early Data Header Name')); headerNameOpt.modalonly = true; ProxyFormUtils.forProtocols(headerNameOpt, [PROXY_TYPE.VLESS], { transport_type: 'ws' }); } /** * 创建通用的gRPC传输选项(用于支持Transport的协议:VLESS等) */ function createTransportGrpcOptions(s: LuCI.form.AbstractSection) { let serviceOpt = s.option(form.Value, 'transport_grpc_service_name', _('gRPC Service Name'), _('gRPC Service Name')); serviceOpt.modalonly = true; serviceOpt.default = 'TunService'; ProxyFormUtils.forProtocols(serviceOpt, [PROXY_TYPE.VLESS], { transport_type: 'grpc' }); let idleOpt = s.option(form.Value, 'transport_grpc_idle_timeout', _('Idle Timeout'), _('Idle Timeout')); idleOpt.modalonly = true; idleOpt.default = '15s'; ProxyFormUtils.forProtocols(idleOpt, [PROXY_TYPE.VLESS], { transport_type: 'grpc' }); let pingOpt = s.option(form.Value, 'transport_grpc_ping_timeout', _('PING Timeout'), _('PING Response Timeout')); pingOpt.modalonly = true; pingOpt.default = '15s'; ProxyFormUtils.forProtocols(pingOpt, [PROXY_TYPE.VLESS], { transport_type: 'grpc' }); let permitOpt = s.option(form.Flag, 'transport_grpc_permit_without_stream', _('Permit Without Stream'), _('Send Keepalive Ping')); permitOpt.modalonly = true; ProxyFormUtils.forProtocols(permitOpt, [PROXY_TYPE.VLESS], { transport_type: 'grpc' }); } /** * 创建通用的HTTPUpgrade传输选项(用于支持Transport的协议:VLESS等) */ function createTransportHttpupgradeOptions(s: LuCI.form.AbstractSection) { let hostOpt = s.option(form.Value, 'transport_httpupgrade_host', _('HTTPUpgrade Host'), _('Host Domain')); hostOpt.modalonly = true; ProxyFormUtils.forProtocols(hostOpt, [PROXY_TYPE.VLESS], { transport_type: 'httpupgrade' }); let pathOpt = s.option(form.Value, 'transport_httpupgrade_path', _('HTTPUpgrade Path'), _('HTTP Request Path')); pathOpt.modalonly = true; ProxyFormUtils.forProtocols(pathOpt, [PROXY_TYPE.VLESS], { transport_type: 'httpupgrade' }); } /** * 创建通用TLS配置选项(用于支持TLS的协议:Hysteria2、TUIC、ShadowTLS、VLESS) */ function createCommonTlsOptions(s: LuCI.form.AbstractSection) { let serverNameOpt = s.option(form.Value, 'tls_server_name', _('TLS Server Name'), _('TLS Server Name')); serverNameOpt.modalonly = true; serverNameOpt.rmempty = false; ProxyFormUtils.forProtocols(serverNameOpt, [PROXY_TYPE.HYSTERIA2, PROXY_TYPE.TUIC, PROXY_TYPE.SHADOWTLS, PROXY_TYPE.VLESS]); let insecureOpt = s.option(form.Flag, 'tls_insecure', _('Ignore Certificate Verification'), _('Accept Any Certificate')); insecureOpt.modalonly = true; insecureOpt.depends({ type: PROXY_TYPE.HYSTERIA2, core: CORE_TYPE.SING_BOX }); insecureOpt.depends({ type: PROXY_TYPE.VLESS, core: CORE_TYPE.SING_BOX }); ProxyFormUtils.forProtocols(insecureOpt, [PROXY_TYPE.TUIC, PROXY_TYPE.SHADOWTLS]); } /** * 创建TLS Reality配置选项(用于支持Reality的协议:VLESS等) */ function createTlsRealityOptions(s: LuCI.form.AbstractSection) { let realityEnabledOpt = s.option(form.Flag, 'tls_reality_enabled', _('Enable TLS Reality'), _('Enable TLS Reality Obfuscation')); realityEnabledOpt.modalonly = true; ProxyFormUtils.forProtocols(realityEnabledOpt, [PROXY_TYPE.VLESS]); let publicKeyOpt = s.option(form.Value, 'tls_reality_public_key', _('Reality Public Key'), _('Server Reality Public Key')); publicKeyOpt.modalonly = true; ProxyFormUtils.forProtocols(publicKeyOpt, [PROXY_TYPE.VLESS], { tls_reality_enabled: '1' }); let shortIdOpt = s.option(form.Value, 'tls_reality_short_id', _('Reality Short ID'), _('Server Reality Short ID')); shortIdOpt.modalonly = true; ProxyFormUtils.forProtocols(shortIdOpt, [PROXY_TYPE.VLESS], { tls_reality_enabled: '1' }); } /** * 创建Hysteria2代理的调试选项(sing-box 核心独有) */ function createHysteria2DebugOptions(s: LuCI.form.AbstractSection) { let o = s.option(form.Flag, 'brutal_debug', _('Enable Brutal CC Debug'), _('Enable Hysteria Brutal CC Debug')); o.modalonly = true; o.depends({ type: PROXY_TYPE.HYSTERIA2, core: CORE_TYPE.SING_BOX }); } function createNetworkOption(s: LuCI.form.AbstractSection) { let networkOpt = s.option(form.ListValue, 'network', _('Network Protocol'), _('Enabled Network Protocols')); networkOpt.modalonly = true; networkOpt.default = ''; networkOpt.value('', _('Default')); networkOpt.value('tcp', 'TCP'); networkOpt.value('udp', 'UDP'); ProxyFormUtils.forProtocols(networkOpt, [PROXY_TYPE.HYSTERIA2, PROXY_TYPE.SHADOWSOCKS, PROXY_TYPE.TUIC, PROXY_TYPE.VLESS]); } function createPasswordOption(s: LuCI.form.AbstractSection) { let passwordOpt = s.option(form.Value, 'password', _('Password'), _('Authentication Password')); passwordOpt.modalonly = true; passwordOpt.password = true; passwordOpt.rmempty = false; ProxyFormUtils.forProtocols(passwordOpt, [PROXY_TYPE.HYSTERIA2, PROXY_TYPE.SHADOWSOCKS, PROXY_TYPE.TUIC]); // ShadowTLS v2/v3 only — compound depends, must stay separate ProxyFormUtils.forProtocols(passwordOpt, [PROXY_TYPE.SHADOWTLS], { version: '2' }); ProxyFormUtils.forProtocols(passwordOpt, [PROXY_TYPE.SHADOWTLS], { version: '3' }); } function createUuidOption(s: LuCI.form.AbstractSection) { let uuidOpt = s.option(form.Value, 'uuid', _('UUID'), _('User UUID')); uuidOpt.modalonly = true; uuidOpt.rmempty = false; ProxyFormUtils.forProtocols(uuidOpt, [PROXY_TYPE.VLESS, PROXY_TYPE.TUIC]); } function createMultiplexOptions(s: LuCI.form.AbstractSection) { const muxTypes: ProxyType[] = [PROXY_TYPE.SHADOWSOCKS, PROXY_TYPE.VLESS]; let enabledOpt = s.option(form.Flag, 'multiplex_enabled', _('Enable Multiplexing'), _('Enable Multiplexing for Better Efficiency')); enabledOpt.modalonly = true; ProxyFormUtils.forCoreProtocols(enabledOpt, CORE_TYPE.SING_BOX, muxTypes); let protocolOpt = s.option(form.ListValue, 'multiplex_protocol', _('Multiplexing Protocol'), _('Multiplexing Protocol')); protocolOpt.modalonly = true; protocolOpt.default = 'h2mux'; muxTypes.forEach(t => protocolOpt.depends({ type: t, core: CORE_TYPE.SING_BOX, multiplex_enabled: '1' })); HijpassValues.MULTIPLEX_PROTOCOLS.forEach(item => protocolOpt.value(item.value, item.label)); let maxConnOpt = s.option(form.Value, 'multiplex_max_connections', _('Max Connections'), _('Max Connections')); maxConnOpt.datatype = 'uinteger'; maxConnOpt.modalonly = true; muxTypes.forEach(t => maxConnOpt.depends({ type: t, core: CORE_TYPE.SING_BOX, multiplex_enabled: '1' })); let minStreamsOpt = s.option(form.Value, 'multiplex_min_streams', _('Min Streams'), _('Min Concurrent Streams')); minStreamsOpt.datatype = 'uinteger'; minStreamsOpt.modalonly = true; muxTypes.forEach(t => minStreamsOpt.depends({ type: t, core: CORE_TYPE.SING_BOX, multiplex_enabled: '1' })); let maxStreamsOpt = s.option(form.Value, 'multiplex_max_streams', _('Max Streams'), _('Max Concurrent Streams')); maxStreamsOpt.datatype = 'uinteger'; maxStreamsOpt.modalonly = true; muxTypes.forEach(t => maxStreamsOpt.depends({ type: t, core: CORE_TYPE.SING_BOX, multiplex_enabled: '1' })); let brutalEnabledOpt = s.option(form.Flag, 'multiplex_brutal_enabled', _('Enable TCP Brutal'), _('Enable TCP Brutal Congestion Control')); brutalEnabledOpt.modalonly = true; muxTypes.forEach(t => brutalEnabledOpt.depends({ type: t, core: CORE_TYPE.SING_BOX, multiplex_enabled: '1' })); let brutalUpOpt = s.option(form.Value, 'multiplex_brutal_up_mbps', _('Uplink Bandwidth'), _('Uplink Bandwidth')); brutalUpOpt.datatype = 'uinteger'; brutalUpOpt.modalonly = true; muxTypes.forEach(t => brutalUpOpt.depends({ type: t, core: CORE_TYPE.SING_BOX, multiplex_enabled: '1', multiplex_brutal_enabled: '1' })); let brutalDownOpt = s.option(form.Value, 'multiplex_brutal_down_mbps', _('Downlink Bandwidth'), _('Downlink Bandwidth')); brutalDownOpt.datatype = 'uinteger'; brutalDownOpt.modalonly = true; muxTypes.forEach(t => brutalDownOpt.depends({ type: t, core: CORE_TYPE.SING_BOX, multiplex_enabled: '1', multiplex_brutal_enabled: '1' })); } /** * 创建Shadowsocks代理的加密和密码选项 */ function createShadowsocksEncryptionOptions(s: LuCI.form.AbstractSection) { let methodOpt = s.option(form.ListValue, 'method', _('Encryption Method'), _('Shadowsocks Encryption Method')); methodOpt.modalonly = true; methodOpt.rmempty = false; methodOpt.default = '2022-blake3-aes-256-gcm'; ProxyFormUtils.forProtocols(methodOpt, [PROXY_TYPE.SHADOWSOCKS]); HijpassValues.SHADOWSOCKS_METHODS.forEach((item) => { methodOpt.value(item.value, item.label); }); } /** * 创建Shadowsocks代理的插件选项 */ function createShadowsocksPluginOptions(s: LuCI.form.AbstractSection) { let pluginOpt = s.option(form.ListValue, 'plugin', _('Plugin'), _('Shadowsocks SIP003 Plugin')); pluginOpt.modalonly = true; pluginOpt.value('', _('None')); pluginOpt.value('obfs-local', 'obfs-local'); pluginOpt.value('v2ray-plugin', 'v2ray-plugin'); ProxyFormUtils.forProtocols(pluginOpt, [PROXY_TYPE.SHADOWSOCKS]); let pluginOptsOpt = s.option(form.Value, 'plugin_opts', _('Plugin Options'), _('Shadowsocks SIP003 Plugin Options')); pluginOptsOpt.modalonly = true; // plugin_opts 依赖于类型是 Shadowsocks 且 plugin 有值 ProxyFormUtils.forProtocols(pluginOptsOpt, [PROXY_TYPE.SHADOWSOCKS], { plugin: /./ }); } /** * 创建Shadowsocks代理的网络选项 */ function createShadowsocksNetworkOptions(s: LuCI.form.AbstractSection) { let udpOverTcpOpt = s.option(form.Flag, 'udp_over_tcp', _('UDP over TCP'), _('Enable UDP over TCP')); udpOverTcpOpt.modalonly = true; ProxyFormUtils.forProtocols(udpOverTcpOpt, [PROXY_TYPE.SHADOWSOCKS]); } /** * 创建TUIC代理的高级选项 */ function createTuicAdvancedOptions(s: LuCI.form.AbstractSection) { let congestionOpt = s.option(form.ListValue, 'congestion_control', _('Congestion Control'), _('QUIC Congestion Control')); congestionOpt.modalonly = true; congestionOpt.default = 'cubic'; ProxyFormUtils.forProtocols(congestionOpt, [PROXY_TYPE.TUIC]); HijpassValues.TUIC_CONGESTION_CONTROLS.forEach((item) => { congestionOpt.value(item.value, item.label); }); let udpRelayOpt = s.option(form.ListValue, 'udp_relay_mode', _('UDP Relay Mode'), _('UDP Packet Relay Mode')); udpRelayOpt.modalonly = true; udpRelayOpt.default = ''; // udp_relay_mode 依赖于 udp_over_stream 未启用 ProxyFormUtils.forProtocols(udpRelayOpt, [PROXY_TYPE.TUIC], { udp_over_stream: '0' }); udpRelayOpt.value('', _('None')); HijpassValues.TUIC_UDP_RELAY_MODES.forEach((item) => { udpRelayOpt.value(item.value, _(item.label)); }); let udpOverStreamOpt = s.option(form.Flag, 'udp_over_stream', _('UDP over Stream'), _('Use UDP Stream Relay')); udpOverStreamOpt.modalonly = true; ProxyFormUtils.forProtocols(udpOverStreamOpt, [PROXY_TYPE.TUIC]); let zero_rttOpt = s.option(form.Flag, 'zero_rtt_handshake', _('0-RTT Handshake'), _('Enable 0-RTT QUIC Handshake')); zero_rttOpt.modalonly = true; ProxyFormUtils.forProtocols(zero_rttOpt, [PROXY_TYPE.TUIC]); let heartbeatOpt = s.option(form.Value, 'heartbeat', _('Heartbeat Interval'), _('Heartbeat Interval')); heartbeatOpt.modalonly = true; heartbeatOpt.default = '10s'; ProxyFormUtils.forProtocols(heartbeatOpt, [PROXY_TYPE.TUIC]); } /** * 创建ShadowTLS代理的版本和密码选项 */ function createShadowTLSAuthOptions(s: LuCI.form.AbstractSection) { let versionOpt = s.option(form.ListValue, 'version', _('Protocol Version'), _('ShadowTLS Protocol Version')); versionOpt.modalonly = true; versionOpt.default = '1'; ProxyFormUtils.forProtocols(versionOpt, [PROXY_TYPE.SHADOWTLS]); HijpassValues.SHADOWTLS_VERSIONS.forEach((item) => { versionOpt.value(item.value, item.label); }); } /** * 创建VLESS代理的认证选项 * flow (XTLS Vision) 对 sing-box 和 xray 均支持 */ function createVlessAuthOptions(s: LuCI.form.AbstractSection) { let flowOpt = s.option(form.ListValue, 'flow', _('VLESS Flow Control'), _('VLESS Flow Control')); flowOpt.modalonly = true; // sing-box 和 xray 均支持 flow: xtls-rprx-vision ProxyFormUtils.forProtocols(flowOpt, [PROXY_TYPE.VLESS]); HijpassValues.VLESS_FLOWS.forEach((item) => { flowOpt.value(item.value, _(item.label)); }); } /** * 创建通用的XHTTP传输选项(Xray 核心专用,sing-box 不支持 xhttp transport) */ function createTransportXhttpOptions(s: LuCI.form.AbstractSection) { let hostOpt = s.option(form.Value, 'transport_xhttp_host', _('XHTTP Host'), _('XHTTP Host Domain')); hostOpt.modalonly = true; hostOpt.depends({ type: PROXY_TYPE.VLESS, core: CORE_TYPE.XRAY, transport_type: 'xhttp' }); let pathOpt = s.option(form.Value, 'transport_xhttp_path', _('XHTTP Path'), _('XHTTP Request Path')); pathOpt.modalonly = true; pathOpt.default = '/'; pathOpt.depends({ type: PROXY_TYPE.VLESS, core: CORE_TYPE.XRAY, transport_type: 'xhttp' }); let modeOpt = s.option(form.ListValue, 'transport_xhttp_mode', _('XHTTP Mode'), _('XHTTP Transport Mode')); modeOpt.modalonly = true; modeOpt.default = 'auto'; modeOpt.value('auto', _('Auto')); modeOpt.value('packet-up', 'packet-up'); modeOpt.value('stream-one', 'stream-one'); modeOpt.value('stream-up', 'stream-up'); modeOpt.depends({ type: PROXY_TYPE.VLESS, core: CORE_TYPE.XRAY, transport_type: 'xhttp' }); } /** * 创建所有代理配置选项 */ function createProxyOptions(s: LuCI.form.AbstractSection, instances: any) { // Grid表格显示选项(所有类型都显示) createGridDisplayOptions(s, instances); // 通用选项(所有类型都需要) createNodeNameOption(s); createEnabledOption(s); ProxyFormUtils.createCoreAndTypeOptions(s); ProxyFormUtils.createUpstreamProxyNodeOption(s); createLoadBalanceOptions(s); // 类型特定的选项 - 服务器地址和端口 createCommonServerOptions(s); // 自定义代理选项 createCustomProxyOptions(s); createUuidOption(s); createPasswordOption(s); // 通用TLS配置(供所有支持TLS的协议使用) createCommonTlsOptions(s); createNetworkOption(s); createHysteria2AdvancedOptions(s); createHysteria2XrayOptions(s); createHysteria2ObfsOptions(s); createHysteria2DebugOptions(s); // Shadowsocks代理选项 createShadowsocksEncryptionOptions(s); createShadowsocksPluginOptions(s); createShadowsocksNetworkOptions(s); createTuicAdvancedOptions(s); // ShadowTLS代理选项 createShadowTLSAuthOptions(s); // VLESS代理选项 createVlessAuthOptions(s); createVlessNetworkOptions(s); // TLS Reality配置(供支持Reality的协议使用:VLESS等) createTlsRealityOptions(s); // 通用传输层配置(供支持Transport的协议使用:VLESS等) createTransportTypeOption(s); createTransportHttpOptions(s); createTransportWsOptions(s); createTransportGrpcOptions(s); createTransportHttpupgradeOptions(s); createTransportXhttpOptions(s); // 通用多路复用配置(供支持多路复用的协议使用:Shadowsocks、VLESS) createMultiplexOptions(s); // 通用的监听端口选项(所有代理类型都需要) ProxyFormUtils.createProxyPortOptions(s); createProxyEnvironmentOptions(s); } export default v;