diff --git a/PANEL-LITE-SPEC.md b/PANEL-LITE-SPEC.md index 6676955..5fcc7b0 100644 --- a/PANEL-LITE-SPEC.md +++ b/PANEL-LITE-SPEC.md @@ -24,11 +24,12 @@ - 安全设置:双重验证(2FA,用户不用) - 邮件模块(SMTP 发送:到期/流量提醒等,用户不用) - 分组功能(客户端归类标签 + 按组批量操作/聚合订阅;用户从未用过,裁功能不影响已建客户端与订阅) +- 本地核心实测三件套(P9 第 4 条,用户 2026-10-21 晚改口):出站测速(临时 xray 实例批量探测 `testOutbound(s)` 接口 + 前端测试按钮/模式切换/结果弹层)、出口探测(同一动作的 egress IP/国家展示)、重启/停止核心接口(`restartXrayService`/`stopXrayService`,与首页已摘按钮同族);出站与路由的模板编辑(增删改存)保留不动 ## 范围确认(2026-10-21 用户拍板:四项全部保留,不裁) - 协议范围:保留全部协议模板(VLESS、SS2022、VMess、Trojan、WireGuard、TUIC、Hysteria、MTProto 等),不收窄 -- IP 检查 / 测速类后台任务:保留 +- IP 检查 / 测速类后台任务:保留 → **2026-10-21 晚用户改口推翻本项**:出站测速 + 出口探测(同一动作,测的是主控本地网络位置;精简主控不跑本机核心、无本地出站,测速结果无意义)连同重启/停止核心控件与接口,已于 P9 第 4 条摘除。其余三项(协议范围 / ACME / 多用户)保留不变,不要再翻出来问。 - 面板内置 ACME 签证书:保留 - 多用户功能:保留 - 用户理由:这四块以静态代码为主,裁掉省不了多少内存。此节关闭,后续轮次不要再翻出来问。 diff --git a/frontend/src/generated/examples.ts b/frontend/src/generated/examples.ts index a66eeec..8e98e71 100644 --- a/frontend/src/generated/examples.ts +++ b/frontend/src/generated/examples.ts @@ -3,64 +3,24 @@ export const EXAMPLES: Record = { "AllSetting": { "datepicker": "", "expireDiff": 0, - "externalTrafficInformEnable": false, - "externalTrafficInformURI": "", "ipLimitAllowlist": "", - "ldapAutoCreate": false, - "ldapAutoDelete": false, - "ldapBaseDN": "", - "ldapBindDN": "", - "ldapDefaultExpiryDays": 0, - "ldapDefaultLimitIP": 0, - "ldapDefaultTotalGB": 0, - "ldapEnable": false, - "ldapFlagField": "", - "ldapHost": "", - "ldapInboundTags": "", - "ldapInsecureSkipVerify": false, - "ldapInvertFlag": false, - "ldapPassword": "", - "ldapPort": 0, - "ldapSyncCron": "", - "ldapTruthyValues": "", - "ldapUseTLS": false, - "ldapUserAttr": "", - "ldapUserFilter": "", - "ldapVlessField": "", "outboundDownThreshold": 1, "pageSize": 0, "panelOutbound": "", "remarkTemplate": "", "restartXrayOnClientDisable": false, "sessionMaxAge": 1, - "smtpCpu": 0, - "smtpEnable": false, - "smtpEnabledEvents": "", - "smtpEncryptionType": "", - "smtpFrom": "", - "smtpFromName": "", - "smtpHost": "", - "smtpMemory": 0, - "smtpPassword": "", - "smtpPort": 1, - "smtpTo": "", - "smtpUsername": "", "subAnnounce": "", "subCertFile": "", "subClashAutoDetect": false, "subClashEnable": false, - "subClashEnableRouting": false, "subClashPath": "", - "subClashRules": "", "subClashURI": "", "subClashUserAgentRegex": "", "subDomain": "", "subEnable": false, - "subEnableRouting": false, "subEncrypt": false, "subHideSettings": false, - "subIncyEnableRouting": false, - "subIncyRoutingRules": "", "subJsonAlwaysArray": false, "subJsonAutoDetect": false, "subJsonEnable": false, @@ -68,37 +28,22 @@ export const EXAMPLES: Record = { "subJsonMux": "", "subJsonObservatory": "", "subJsonPath": "", - "subJsonRules": "", "subJsonURI": "", "subJsonUserAgentRegex": "", "subKeyFile": "", "subListen": "", "subPath": "", - "subPort": 1, + "subPort": 0, "subProfileUrl": "", - "subRoutingRules": "", "subShowIdentityOnAllLinks": false, "subSupportUrl": "", "subThemeDir": "", "subTitle": "", "subURI": "", "subUpdates": 0, - "tgBotAPIServer": "", - "tgBotBackup": false, - "tgBotChatId": "", - "tgBotEnable": false, - "tgBotProxy": "", - "tgBotToken": "", - "tgCpu": 0, - "tgEnabledEvents": "", - "tgLang": "", - "tgMemory": 0, - "tgRunTime": "", "timeLocation": "", "trafficDiff": 0, "trustedProxyCIDRs": "", - "twoFactorEnable": false, - "twoFactorToken": "", "warpUpdateInterval": 0, "webBasePath": "", "webCertFile": "", @@ -110,71 +55,27 @@ export const EXAMPLES: Record = { "AllSettingView": { "datepicker": "", "expireDiff": 0, - "externalTrafficInformEnable": false, - "externalTrafficInformURI": "", "hasApiToken": false, - "hasLdapPassword": false, "hasNordSecret": false, - "hasSmtpPassword": false, - "hasTgBotToken": false, - "hasTwoFactorToken": false, "hasWarpSecret": false, "ipLimitAllowlist": "", - "ldapAutoCreate": false, - "ldapAutoDelete": false, - "ldapBaseDN": "", - "ldapBindDN": "", - "ldapDefaultExpiryDays": 0, - "ldapDefaultLimitIP": 0, - "ldapDefaultTotalGB": 0, - "ldapEnable": false, - "ldapFlagField": "", - "ldapHost": "", - "ldapInboundTags": "", - "ldapInsecureSkipVerify": false, - "ldapInvertFlag": false, - "ldapPassword": "", - "ldapPort": 0, - "ldapSyncCron": "", - "ldapTruthyValues": "", - "ldapUseTLS": false, - "ldapUserAttr": "", - "ldapUserFilter": "", - "ldapVlessField": "", "outboundDownThreshold": 1, "pageSize": 0, "panelOutbound": "", "remarkTemplate": "", "restartXrayOnClientDisable": false, "sessionMaxAge": 1, - "smtpCpu": 0, - "smtpEnable": false, - "smtpEnabledEvents": "", - "smtpEncryptionType": "", - "smtpFrom": "", - "smtpFromName": "", - "smtpHost": "", - "smtpMemory": 0, - "smtpPassword": "", - "smtpPort": 1, - "smtpTo": "", - "smtpUsername": "", "subAnnounce": "", "subCertFile": "", "subClashAutoDetect": false, "subClashEnable": false, - "subClashEnableRouting": false, "subClashPath": "", - "subClashRules": "", "subClashURI": "", "subClashUserAgentRegex": "", "subDomain": "", "subEnable": false, - "subEnableRouting": false, "subEncrypt": false, "subHideSettings": false, - "subIncyEnableRouting": false, - "subIncyRoutingRules": "", "subJsonAlwaysArray": false, "subJsonAutoDetect": false, "subJsonEnable": false, @@ -182,37 +83,22 @@ export const EXAMPLES: Record = { "subJsonMux": "", "subJsonObservatory": "", "subJsonPath": "", - "subJsonRules": "", "subJsonURI": "", "subJsonUserAgentRegex": "", "subKeyFile": "", "subListen": "", "subPath": "", - "subPort": 1, + "subPort": 0, "subProfileUrl": "", - "subRoutingRules": "", "subShowIdentityOnAllLinks": false, "subSupportUrl": "", "subThemeDir": "", "subTitle": "", "subURI": "", "subUpdates": 0, - "tgBotAPIServer": "", - "tgBotBackup": false, - "tgBotChatId": "", - "tgBotEnable": false, - "tgBotProxy": "", - "tgBotToken": "", - "tgCpu": 0, - "tgEnabledEvents": "", - "tgLang": "", - "tgMemory": 0, - "tgRunTime": "", "timeLocation": "", "trafficDiff": 0, "trustedProxyCIDRs": "", - "twoFactorEnable": false, - "twoFactorToken": "", "warpUpdateInterval": 0, "webBasePath": "", "webCertFile": "", @@ -221,26 +107,6 @@ export const EXAMPLES: Record = { "webListen": "", "webPort": 1 }, - "AmneziaWGLogs": { - "events": [ - "2025/01/01 12:00:00 amneziawg: started interface awg1 for inbound 1" - ], - "peers": [ - { - "allowedIPs": "10.8.1.2/32", - "down": 4194304, - "email": "peer@example.com", - "endpoint": "203.0.113.9:51820", - "handshake": 1735732800000, - "inboundId": 1, - "interface": "awg1", - "online": true, - "tag": "inbound-51820", - "up": 1048576 - } - ], - "running": true - }, "ApiToken": { "createdAt": 0, "enabled": false, @@ -648,9 +514,10 @@ export const EXAMPLES: Record = { "" ], "name": "", + "nodeType": "traditional", "outboundTag": "", "pinnedCertSha256": "", - "port": 1, + "port": 0, "remark": "", "scheme": "http", "tlsVerifyMode": "verify" @@ -683,6 +550,7 @@ export const EXAMPLES: Record = { "name": "edge-1", "netDown": 1048576, "netUp": 2097152, + "nodeType": "traditional", "onlineCount": 5, "outboundTag": "direct", "panelVersion": "v3.x.x", @@ -713,18 +581,6 @@ export const EXAMPLES: Record = { "runId": "1735689600123456789", "state": "success" }, - "PeerActivity": { - "allowedIPs": "10.8.1.2/32", - "down": 4194304, - "email": "peer@example.com", - "endpoint": "203.0.113.9:51820", - "handshake": 1735732800000, - "inboundId": 1, - "interface": "awg1", - "online": true, - "tag": "inbound-51820", - "up": 1048576 - }, "ProbeResultUI": { "cpuPct": 12.5, "error": "", diff --git a/frontend/src/generated/schemas.ts b/frontend/src/generated/schemas.ts index 84c9820..825fea4 100644 --- a/frontend/src/generated/schemas.ts +++ b/frontend/src/generated/schemas.ts @@ -9,83 +9,9 @@ export const SCHEMAS: Record = { "minimum": 0, "type": "integer" }, - "externalTrafficInformEnable": { - "type": "boolean" - }, - "externalTrafficInformURI": { - "type": "string" - }, "ipLimitAllowlist": { "type": "string" }, - "ldapAutoCreate": { - "type": "boolean" - }, - "ldapAutoDelete": { - "type": "boolean" - }, - "ldapBaseDN": { - "type": "string" - }, - "ldapBindDN": { - "type": "string" - }, - "ldapDefaultExpiryDays": { - "minimum": 0, - "type": "integer" - }, - "ldapDefaultLimitIP": { - "minimum": 0, - "type": "integer" - }, - "ldapDefaultTotalGB": { - "minimum": 0, - "type": "integer" - }, - "ldapEnable": { - "type": "boolean" - }, - "ldapFlagField": { - "type": "string" - }, - "ldapHost": { - "type": "string" - }, - "ldapInboundTags": { - "type": "string" - }, - "ldapInsecureSkipVerify": { - "type": "boolean" - }, - "ldapInvertFlag": { - "type": "boolean" - }, - "ldapPassword": { - "type": "string" - }, - "ldapPort": { - "maximum": 65535, - "minimum": 0, - "type": "integer" - }, - "ldapSyncCron": { - "type": "string" - }, - "ldapTruthyValues": { - "type": "string" - }, - "ldapUseTLS": { - "type": "boolean" - }, - "ldapUserAttr": { - "type": "string" - }, - "ldapUserFilter": { - "type": "string" - }, - "ldapVlessField": { - "type": "string" - }, "outboundDownThreshold": { "maximum": 100, "minimum": 1, @@ -110,48 +36,6 @@ export const SCHEMAS: Record = { "minimum": 1, "type": "integer" }, - "smtpCpu": { - "maximum": 100, - "minimum": 0, - "type": "integer" - }, - "smtpEnable": { - "type": "boolean" - }, - "smtpEnabledEvents": { - "type": "string" - }, - "smtpEncryptionType": { - "type": "string" - }, - "smtpFrom": { - "type": "string" - }, - "smtpFromName": { - "type": "string" - }, - "smtpHost": { - "type": "string" - }, - "smtpMemory": { - "maximum": 100, - "minimum": 0, - "type": "integer" - }, - "smtpPassword": { - "type": "string" - }, - "smtpPort": { - "maximum": 65535, - "minimum": 1, - "type": "integer" - }, - "smtpTo": { - "type": "string" - }, - "smtpUsername": { - "type": "string" - }, "subAnnounce": { "type": "string" }, @@ -164,15 +48,9 @@ export const SCHEMAS: Record = { "subClashEnable": { "type": "boolean" }, - "subClashEnableRouting": { - "type": "boolean" - }, "subClashPath": { "type": "string" }, - "subClashRules": { - "type": "string" - }, "subClashURI": { "type": "string" }, @@ -185,21 +63,12 @@ export const SCHEMAS: Record = { "subEnable": { "type": "boolean" }, - "subEnableRouting": { - "type": "boolean" - }, "subEncrypt": { "type": "boolean" }, "subHideSettings": { "type": "boolean" }, - "subIncyEnableRouting": { - "type": "boolean" - }, - "subIncyRoutingRules": { - "type": "string" - }, "subJsonAlwaysArray": { "type": "boolean" }, @@ -221,9 +90,6 @@ export const SCHEMAS: Record = { "subJsonPath": { "type": "string" }, - "subJsonRules": { - "type": "string" - }, "subJsonURI": { "type": "string" }, @@ -240,16 +106,14 @@ export const SCHEMAS: Record = { "type": "string" }, "subPort": { + "description": "SubPort 0 means \"no explicit port\": subscription links are generated\nwithout a port (standard scheme port behind a reverse proxy) while the\nsub server still binds the default port. See SettingService.BuildSubURIBase\nand the sub server bootstrap for the two sides of that split.", "maximum": 65535, - "minimum": 1, + "minimum": 0, "type": "integer" }, "subProfileUrl": { "type": "string" }, - "subRoutingRules": { - "type": "string" - }, "subShowIdentityOnAllLinks": { "type": "boolean" }, @@ -270,43 +134,6 @@ export const SCHEMAS: Record = { "minimum": 0, "type": "integer" }, - "tgBotAPIServer": { - "type": "string" - }, - "tgBotBackup": { - "type": "boolean" - }, - "tgBotChatId": { - "type": "string" - }, - "tgBotEnable": { - "type": "boolean" - }, - "tgBotProxy": { - "type": "string" - }, - "tgBotToken": { - "type": "string" - }, - "tgCpu": { - "maximum": 100, - "minimum": 0, - "type": "integer" - }, - "tgEnabledEvents": { - "type": "string" - }, - "tgLang": { - "type": "string" - }, - "tgMemory": { - "maximum": 100, - "minimum": 0, - "type": "integer" - }, - "tgRunTime": { - "type": "string" - }, "timeLocation": { "type": "string" }, @@ -318,12 +145,6 @@ export const SCHEMAS: Record = { "trustedProxyCIDRs": { "type": "string" }, - "twoFactorEnable": { - "type": "boolean" - }, - "twoFactorToken": { - "type": "string" - }, "warpUpdateInterval": { "minimum": 0, "type": "integer" @@ -352,64 +173,24 @@ export const SCHEMAS: Record = { "required": [ "datepicker", "expireDiff", - "externalTrafficInformEnable", - "externalTrafficInformURI", "ipLimitAllowlist", - "ldapAutoCreate", - "ldapAutoDelete", - "ldapBaseDN", - "ldapBindDN", - "ldapDefaultExpiryDays", - "ldapDefaultLimitIP", - "ldapDefaultTotalGB", - "ldapEnable", - "ldapFlagField", - "ldapHost", - "ldapInboundTags", - "ldapInsecureSkipVerify", - "ldapInvertFlag", - "ldapPassword", - "ldapPort", - "ldapSyncCron", - "ldapTruthyValues", - "ldapUseTLS", - "ldapUserAttr", - "ldapUserFilter", - "ldapVlessField", "outboundDownThreshold", "pageSize", "panelOutbound", "remarkTemplate", "restartXrayOnClientDisable", "sessionMaxAge", - "smtpCpu", - "smtpEnable", - "smtpEnabledEvents", - "smtpEncryptionType", - "smtpFrom", - "smtpFromName", - "smtpHost", - "smtpMemory", - "smtpPassword", - "smtpPort", - "smtpTo", - "smtpUsername", "subAnnounce", "subCertFile", "subClashAutoDetect", "subClashEnable", - "subClashEnableRouting", "subClashPath", - "subClashRules", "subClashURI", "subClashUserAgentRegex", "subDomain", "subEnable", - "subEnableRouting", "subEncrypt", "subHideSettings", - "subIncyEnableRouting", - "subIncyRoutingRules", "subJsonAlwaysArray", "subJsonAutoDetect", "subJsonEnable", @@ -417,7 +198,6 @@ export const SCHEMAS: Record = { "subJsonMux", "subJsonObservatory", "subJsonPath", - "subJsonRules", "subJsonURI", "subJsonUserAgentRegex", "subKeyFile", @@ -425,29 +205,15 @@ export const SCHEMAS: Record = { "subPath", "subPort", "subProfileUrl", - "subRoutingRules", "subShowIdentityOnAllLinks", "subSupportUrl", "subThemeDir", "subTitle", "subURI", "subUpdates", - "tgBotAPIServer", - "tgBotBackup", - "tgBotChatId", - "tgBotEnable", - "tgBotProxy", - "tgBotToken", - "tgCpu", - "tgEnabledEvents", - "tgLang", - "tgMemory", - "tgRunTime", "timeLocation", "trafficDiff", "trustedProxyCIDRs", - "twoFactorEnable", - "twoFactorToken", "warpUpdateInterval", "webBasePath", "webCertFile", @@ -467,104 +233,18 @@ export const SCHEMAS: Record = { "minimum": 0, "type": "integer" }, - "externalTrafficInformEnable": { - "type": "boolean" - }, - "externalTrafficInformURI": { - "type": "string" - }, "hasApiToken": { "type": "boolean" }, - "hasLdapPassword": { - "type": "boolean" - }, "hasNordSecret": { "type": "boolean" }, - "hasSmtpPassword": { - "type": "boolean" - }, - "hasTgBotToken": { - "type": "boolean" - }, - "hasTwoFactorToken": { - "type": "boolean" - }, "hasWarpSecret": { "type": "boolean" }, "ipLimitAllowlist": { "type": "string" }, - "ldapAutoCreate": { - "type": "boolean" - }, - "ldapAutoDelete": { - "type": "boolean" - }, - "ldapBaseDN": { - "type": "string" - }, - "ldapBindDN": { - "type": "string" - }, - "ldapDefaultExpiryDays": { - "minimum": 0, - "type": "integer" - }, - "ldapDefaultLimitIP": { - "minimum": 0, - "type": "integer" - }, - "ldapDefaultTotalGB": { - "minimum": 0, - "type": "integer" - }, - "ldapEnable": { - "type": "boolean" - }, - "ldapFlagField": { - "type": "string" - }, - "ldapHost": { - "type": "string" - }, - "ldapInboundTags": { - "type": "string" - }, - "ldapInsecureSkipVerify": { - "type": "boolean" - }, - "ldapInvertFlag": { - "type": "boolean" - }, - "ldapPassword": { - "type": "string" - }, - "ldapPort": { - "maximum": 65535, - "minimum": 0, - "type": "integer" - }, - "ldapSyncCron": { - "type": "string" - }, - "ldapTruthyValues": { - "type": "string" - }, - "ldapUseTLS": { - "type": "boolean" - }, - "ldapUserAttr": { - "type": "string" - }, - "ldapUserFilter": { - "type": "string" - }, - "ldapVlessField": { - "type": "string" - }, "outboundDownThreshold": { "maximum": 100, "minimum": 1, @@ -589,48 +269,6 @@ export const SCHEMAS: Record = { "minimum": 1, "type": "integer" }, - "smtpCpu": { - "maximum": 100, - "minimum": 0, - "type": "integer" - }, - "smtpEnable": { - "type": "boolean" - }, - "smtpEnabledEvents": { - "type": "string" - }, - "smtpEncryptionType": { - "type": "string" - }, - "smtpFrom": { - "type": "string" - }, - "smtpFromName": { - "type": "string" - }, - "smtpHost": { - "type": "string" - }, - "smtpMemory": { - "maximum": 100, - "minimum": 0, - "type": "integer" - }, - "smtpPassword": { - "type": "string" - }, - "smtpPort": { - "maximum": 65535, - "minimum": 1, - "type": "integer" - }, - "smtpTo": { - "type": "string" - }, - "smtpUsername": { - "type": "string" - }, "subAnnounce": { "type": "string" }, @@ -643,15 +281,9 @@ export const SCHEMAS: Record = { "subClashEnable": { "type": "boolean" }, - "subClashEnableRouting": { - "type": "boolean" - }, "subClashPath": { "type": "string" }, - "subClashRules": { - "type": "string" - }, "subClashURI": { "type": "string" }, @@ -664,21 +296,12 @@ export const SCHEMAS: Record = { "subEnable": { "type": "boolean" }, - "subEnableRouting": { - "type": "boolean" - }, "subEncrypt": { "type": "boolean" }, "subHideSettings": { "type": "boolean" }, - "subIncyEnableRouting": { - "type": "boolean" - }, - "subIncyRoutingRules": { - "type": "string" - }, "subJsonAlwaysArray": { "type": "boolean" }, @@ -700,9 +323,6 @@ export const SCHEMAS: Record = { "subJsonPath": { "type": "string" }, - "subJsonRules": { - "type": "string" - }, "subJsonURI": { "type": "string" }, @@ -719,16 +339,14 @@ export const SCHEMAS: Record = { "type": "string" }, "subPort": { + "description": "SubPort 0 means \"no explicit port\": subscription links are generated\nwithout a port (standard scheme port behind a reverse proxy) while the\nsub server still binds the default port. See SettingService.BuildSubURIBase\nand the sub server bootstrap for the two sides of that split.", "maximum": 65535, - "minimum": 1, + "minimum": 0, "type": "integer" }, "subProfileUrl": { "type": "string" }, - "subRoutingRules": { - "type": "string" - }, "subShowIdentityOnAllLinks": { "type": "boolean" }, @@ -749,43 +367,6 @@ export const SCHEMAS: Record = { "minimum": 0, "type": "integer" }, - "tgBotAPIServer": { - "type": "string" - }, - "tgBotBackup": { - "type": "boolean" - }, - "tgBotChatId": { - "type": "string" - }, - "tgBotEnable": { - "type": "boolean" - }, - "tgBotProxy": { - "type": "string" - }, - "tgBotToken": { - "type": "string" - }, - "tgCpu": { - "maximum": 100, - "minimum": 0, - "type": "integer" - }, - "tgEnabledEvents": { - "type": "string" - }, - "tgLang": { - "type": "string" - }, - "tgMemory": { - "maximum": 100, - "minimum": 0, - "type": "integer" - }, - "tgRunTime": { - "type": "string" - }, "timeLocation": { "type": "string" }, @@ -797,12 +378,6 @@ export const SCHEMAS: Record = { "trustedProxyCIDRs": { "type": "string" }, - "twoFactorEnable": { - "type": "boolean" - }, - "twoFactorToken": { - "type": "string" - }, "warpUpdateInterval": { "minimum": 0, "type": "integer" @@ -831,71 +406,27 @@ export const SCHEMAS: Record = { "required": [ "datepicker", "expireDiff", - "externalTrafficInformEnable", - "externalTrafficInformURI", "hasApiToken", - "hasLdapPassword", "hasNordSecret", - "hasSmtpPassword", - "hasTgBotToken", - "hasTwoFactorToken", "hasWarpSecret", "ipLimitAllowlist", - "ldapAutoCreate", - "ldapAutoDelete", - "ldapBaseDN", - "ldapBindDN", - "ldapDefaultExpiryDays", - "ldapDefaultLimitIP", - "ldapDefaultTotalGB", - "ldapEnable", - "ldapFlagField", - "ldapHost", - "ldapInboundTags", - "ldapInsecureSkipVerify", - "ldapInvertFlag", - "ldapPassword", - "ldapPort", - "ldapSyncCron", - "ldapTruthyValues", - "ldapUseTLS", - "ldapUserAttr", - "ldapUserFilter", - "ldapVlessField", "outboundDownThreshold", "pageSize", "panelOutbound", "remarkTemplate", "restartXrayOnClientDisable", "sessionMaxAge", - "smtpCpu", - "smtpEnable", - "smtpEnabledEvents", - "smtpEncryptionType", - "smtpFrom", - "smtpFromName", - "smtpHost", - "smtpMemory", - "smtpPassword", - "smtpPort", - "smtpTo", - "smtpUsername", "subAnnounce", "subCertFile", "subClashAutoDetect", "subClashEnable", - "subClashEnableRouting", "subClashPath", - "subClashRules", "subClashURI", "subClashUserAgentRegex", "subDomain", "subEnable", - "subEnableRouting", "subEncrypt", "subHideSettings", - "subIncyEnableRouting", - "subIncyRoutingRules", "subJsonAlwaysArray", "subJsonAutoDetect", "subJsonEnable", @@ -903,7 +434,6 @@ export const SCHEMAS: Record = { "subJsonMux", "subJsonObservatory", "subJsonPath", - "subJsonRules", "subJsonURI", "subJsonUserAgentRegex", "subKeyFile", @@ -911,29 +441,15 @@ export const SCHEMAS: Record = { "subPath", "subPort", "subProfileUrl", - "subRoutingRules", "subShowIdentityOnAllLinks", "subSupportUrl", "subThemeDir", "subTitle", "subURI", "subUpdates", - "tgBotAPIServer", - "tgBotBackup", - "tgBotChatId", - "tgBotEnable", - "tgBotProxy", - "tgBotToken", - "tgCpu", - "tgEnabledEvents", - "tgLang", - "tgMemory", - "tgRunTime", "timeLocation", "trafficDiff", "trustedProxyCIDRs", - "twoFactorEnable", - "twoFactorToken", "warpUpdateInterval", "webBasePath", "webCertFile", @@ -944,36 +460,6 @@ export const SCHEMAS: Record = { ], "type": "object" }, - "AmneziaWGLogs": { - "description": "AmneziaWGLogs is what the overview's AmneziaWG log view renders: the live\nper-peer activity of every running embedded interface, plus the panel's\nown recent AmneziaWG lifecycle log lines that explain a peer being absent\nfrom Peers at all.", - "properties": { - "events": { - "example": [ - "2025/01/01 12:00:00 amneziawg: started interface awg1 for inbound 1" - ], - "items": { - "type": "string" - }, - "type": "array" - }, - "peers": { - "items": { - "$ref": "#/components/schemas/PeerActivity" - }, - "type": "array" - }, - "running": { - "example": true, - "type": "boolean" - } - }, - "required": [ - "events", - "peers", - "running" - ], - "type": "object" - }, "ApiToken": { "properties": { "createdAt": { @@ -2617,6 +2103,14 @@ export const SCHEMAS: Record = { "name": { "type": "string" }, + "nodeType": { + "description": "NodeType distinguishes traditional panel-to-panel nodes from lightweight\nagent nodes. Agent nodes skip the panel reachability probe and the API\ntoken requirement; an agent token is issued on creation instead.", + "enum": [ + "traditional", + "agent" + ], + "type": "string" + }, "outboundTag": { "type": "string" }, @@ -2625,7 +2119,7 @@ export const SCHEMAS: Record = { }, "port": { "maximum": 65535, - "minimum": 1, + "minimum": 0, "type": "integer" }, "remark": { @@ -2778,6 +2272,11 @@ export const SCHEMAS: Record = { "format": "int64", "type": "integer" }, + "nodeType": { + "description": "NodeType is \"agent\" for lightweight agent-managed nodes (an agent is\nbound to the node) and \"traditional\" otherwise. It is derived from the\nagent binding, not stored on the node row; read paths that serve the\npanel UI annotate it via annotateNodeTypes. Empty on views built\noutside those paths.", + "example": "traditional", + "type": "string" + }, "onlineCount": { "example": 5, "type": "integer" @@ -2945,68 +2444,6 @@ export const SCHEMAS: Record = { ], "type": "object" }, - "PeerActivity": { - "description": "PeerActivity is one peer's live embedded-Device-reported state, the\ncounterpart of an Xray access-log entry: a tunnel logs no requests, only\nhandshakes and bytes.", - "properties": { - "allowedIPs": { - "example": "10.8.1.2/32", - "type": "string" - }, - "down": { - "example": 4194304, - "format": "int64", - "type": "integer" - }, - "email": { - "example": "peer@example.com", - "type": "string" - }, - "endpoint": { - "example": "203.0.113.9:51820", - "type": "string" - }, - "handshake": { - "description": "Handshake is unix milliseconds, 0 when the peer has never connected.", - "example": 1735732800000, - "format": "int64", - "type": "integer" - }, - "inboundId": { - "example": 1, - "type": "integer" - }, - "interface": { - "example": "awg1", - "type": "string" - }, - "online": { - "example": true, - "type": "boolean" - }, - "tag": { - "example": "inbound-51820", - "type": "string" - }, - "up": { - "example": 1048576, - "format": "int64", - "type": "integer" - } - }, - "required": [ - "allowedIPs", - "down", - "email", - "endpoint", - "handshake", - "inboundId", - "interface", - "online", - "tag", - "up" - ], - "type": "object" - }, "ProbeResultUI": { "properties": { "cpuPct": { diff --git a/frontend/src/generated/types.ts b/frontend/src/generated/types.ts index 2157722..2077527 100644 --- a/frontend/src/generated/types.ts +++ b/frontend/src/generated/types.ts @@ -4,6 +4,7 @@ export type OnlineAPISupport = number; export type ProcessState = string; export type Protocol = string; export type SubLinkProvider = unknown; +export type rawBytes = number[]; export type staticEgressResolver = string; export type trafficLocalApplyAction = number; export type transportBits = number; @@ -11,64 +12,24 @@ export type transportBits = number; export interface AllSetting { datepicker: string; expireDiff: number; - externalTrafficInformEnable: boolean; - externalTrafficInformURI: string; ipLimitAllowlist: string; - ldapAutoCreate: boolean; - ldapAutoDelete: boolean; - ldapBaseDN: string; - ldapBindDN: string; - ldapDefaultExpiryDays: number; - ldapDefaultLimitIP: number; - ldapDefaultTotalGB: number; - ldapEnable: boolean; - ldapFlagField: string; - ldapHost: string; - ldapInboundTags: string; - ldapInsecureSkipVerify: boolean; - ldapInvertFlag: boolean; - ldapPassword: string; - ldapPort: number; - ldapSyncCron: string; - ldapTruthyValues: string; - ldapUseTLS: boolean; - ldapUserAttr: string; - ldapUserFilter: string; - ldapVlessField: string; outboundDownThreshold: number; pageSize: number; panelOutbound: string; remarkTemplate: string; restartXrayOnClientDisable: boolean; sessionMaxAge: number; - smtpCpu: number; - smtpEnable: boolean; - smtpEnabledEvents: string; - smtpEncryptionType: string; - smtpFrom: string; - smtpFromName: string; - smtpHost: string; - smtpMemory: number; - smtpPassword: string; - smtpPort: number; - smtpTo: string; - smtpUsername: string; subAnnounce: string; subCertFile: string; subClashAutoDetect: boolean; subClashEnable: boolean; - subClashEnableRouting: boolean; subClashPath: string; - subClashRules: string; subClashURI: string; subClashUserAgentRegex: string; subDomain: string; subEnable: boolean; - subEnableRouting: boolean; subEncrypt: boolean; subHideSettings: boolean; - subIncyEnableRouting: boolean; - subIncyRoutingRules: string; subJsonAlwaysArray: boolean; subJsonAutoDetect: boolean; subJsonEnable: boolean; @@ -76,7 +37,6 @@ export interface AllSetting { subJsonMux: string; subJsonObservatory: string; subJsonPath: string; - subJsonRules: string; subJsonURI: string; subJsonUserAgentRegex: string; subKeyFile: string; @@ -84,29 +44,15 @@ export interface AllSetting { subPath: string; subPort: number; subProfileUrl: string; - subRoutingRules: string; subShowIdentityOnAllLinks: boolean; subSupportUrl: string; subThemeDir: string; subTitle: string; subURI: string; subUpdates: number; - tgBotAPIServer: string; - tgBotBackup: boolean; - tgBotChatId: string; - tgBotEnable: boolean; - tgBotProxy: string; - tgBotToken: string; - tgCpu: number; - tgEnabledEvents: string; - tgLang: string; - tgMemory: number; - tgRunTime: string; timeLocation: string; trafficDiff: number; trustedProxyCIDRs: string; - twoFactorEnable: boolean; - twoFactorToken: string; warpUpdateInterval: number; webBasePath: string; webCertFile: string; @@ -119,71 +65,27 @@ export interface AllSetting { export interface AllSettingView { datepicker: string; expireDiff: number; - externalTrafficInformEnable: boolean; - externalTrafficInformURI: string; hasApiToken: boolean; - hasLdapPassword: boolean; hasNordSecret: boolean; - hasSmtpPassword: boolean; - hasTgBotToken: boolean; - hasTwoFactorToken: boolean; hasWarpSecret: boolean; ipLimitAllowlist: string; - ldapAutoCreate: boolean; - ldapAutoDelete: boolean; - ldapBaseDN: string; - ldapBindDN: string; - ldapDefaultExpiryDays: number; - ldapDefaultLimitIP: number; - ldapDefaultTotalGB: number; - ldapEnable: boolean; - ldapFlagField: string; - ldapHost: string; - ldapInboundTags: string; - ldapInsecureSkipVerify: boolean; - ldapInvertFlag: boolean; - ldapPassword: string; - ldapPort: number; - ldapSyncCron: string; - ldapTruthyValues: string; - ldapUseTLS: boolean; - ldapUserAttr: string; - ldapUserFilter: string; - ldapVlessField: string; outboundDownThreshold: number; pageSize: number; panelOutbound: string; remarkTemplate: string; restartXrayOnClientDisable: boolean; sessionMaxAge: number; - smtpCpu: number; - smtpEnable: boolean; - smtpEnabledEvents: string; - smtpEncryptionType: string; - smtpFrom: string; - smtpFromName: string; - smtpHost: string; - smtpMemory: number; - smtpPassword: string; - smtpPort: number; - smtpTo: string; - smtpUsername: string; subAnnounce: string; subCertFile: string; subClashAutoDetect: boolean; subClashEnable: boolean; - subClashEnableRouting: boolean; subClashPath: string; - subClashRules: string; subClashURI: string; subClashUserAgentRegex: string; subDomain: string; subEnable: boolean; - subEnableRouting: boolean; subEncrypt: boolean; subHideSettings: boolean; - subIncyEnableRouting: boolean; - subIncyRoutingRules: string; subJsonAlwaysArray: boolean; subJsonAutoDetect: boolean; subJsonEnable: boolean; @@ -191,7 +93,6 @@ export interface AllSettingView { subJsonMux: string; subJsonObservatory: string; subJsonPath: string; - subJsonRules: string; subJsonURI: string; subJsonUserAgentRegex: string; subKeyFile: string; @@ -199,29 +100,15 @@ export interface AllSettingView { subPath: string; subPort: number; subProfileUrl: string; - subRoutingRules: string; subShowIdentityOnAllLinks: boolean; subSupportUrl: string; subThemeDir: string; subTitle: string; subURI: string; subUpdates: number; - tgBotAPIServer: string; - tgBotBackup: boolean; - tgBotChatId: string; - tgBotEnable: boolean; - tgBotProxy: string; - tgBotToken: string; - tgCpu: number; - tgEnabledEvents: string; - tgLang: string; - tgMemory: number; - tgRunTime: string; timeLocation: string; trafficDiff: number; trustedProxyCIDRs: string; - twoFactorEnable: boolean; - twoFactorToken: string; warpUpdateInterval: number; webBasePath: string; webCertFile: string; @@ -231,12 +118,6 @@ export interface AllSettingView { webPort: number; } -export interface AmneziaWGLogs { - events: string[]; - peers: PeerActivity[]; - running: boolean; -} - export interface ApiToken { createdAt: number; enabled: boolean; @@ -601,6 +482,7 @@ export interface NodeMutationRequest { inboundSyncMode: string; inboundTags: string[]; name: string; + nodeType?: string; outboundTag: string; pinnedCertSha256: string; port: number; @@ -635,6 +517,7 @@ export interface NodeView { name: string; netDown: number; netUp: number; + nodeType?: string; onlineCount: number; outboundTag: string; panelVersion: string; @@ -668,19 +551,6 @@ export interface PanelUpdateStatus { state: string; } -export interface PeerActivity { - allowedIPs: string; - down: number; - email: string; - endpoint: string; - handshake: number; - inboundId: number; - interface: string; - online: boolean; - tag: string; - up: number; -} - export interface ProbeResultUI { cpuPct: number; error: string; diff --git a/frontend/src/generated/zod.ts b/frontend/src/generated/zod.ts index b9b3c7b..fbbf24c 100644 --- a/frontend/src/generated/zod.ts +++ b/frontend/src/generated/zod.ts @@ -15,6 +15,9 @@ export type Protocol = z.infer; export const SubLinkProviderSchema = z.unknown(); export type SubLinkProvider = z.infer; +export const rawBytesSchema = z.array(z.number().int()); +export type rawBytes = z.infer; + export const staticEgressResolverSchema = z.string(); export type staticEgressResolver = z.infer; @@ -27,64 +30,24 @@ export type transportBits = z.infer; export const AllSettingSchema = z.object({ datepicker: z.string(), expireDiff: z.number().int().min(0), - externalTrafficInformEnable: z.boolean(), - externalTrafficInformURI: z.string(), ipLimitAllowlist: z.string(), - ldapAutoCreate: z.boolean(), - ldapAutoDelete: z.boolean(), - ldapBaseDN: z.string(), - ldapBindDN: z.string(), - ldapDefaultExpiryDays: z.number().int().min(0), - ldapDefaultLimitIP: z.number().int().min(0), - ldapDefaultTotalGB: z.number().int().min(0), - ldapEnable: z.boolean(), - ldapFlagField: z.string(), - ldapHost: z.string(), - ldapInboundTags: z.string(), - ldapInsecureSkipVerify: z.boolean(), - ldapInvertFlag: z.boolean(), - ldapPassword: z.string(), - ldapPort: z.number().int().min(0).max(65535), - ldapSyncCron: z.string(), - ldapTruthyValues: z.string(), - ldapUseTLS: z.boolean(), - ldapUserAttr: z.string(), - ldapUserFilter: z.string(), - ldapVlessField: z.string(), outboundDownThreshold: z.number().int().min(1).max(100), pageSize: z.number().int().min(0).max(1000), panelOutbound: z.string(), remarkTemplate: z.string(), restartXrayOnClientDisable: z.boolean(), sessionMaxAge: z.number().int().min(1).max(525600), - smtpCpu: z.number().int().min(0).max(100), - smtpEnable: z.boolean(), - smtpEnabledEvents: z.string(), - smtpEncryptionType: z.string(), - smtpFrom: z.string(), - smtpFromName: z.string(), - smtpHost: z.string(), - smtpMemory: z.number().int().min(0).max(100), - smtpPassword: z.string(), - smtpPort: z.number().int().min(1).max(65535), - smtpTo: z.string(), - smtpUsername: z.string(), subAnnounce: z.string(), subCertFile: z.string(), subClashAutoDetect: z.boolean(), subClashEnable: z.boolean(), - subClashEnableRouting: z.boolean(), subClashPath: z.string(), - subClashRules: z.string(), subClashURI: z.string(), subClashUserAgentRegex: z.string(), subDomain: z.string(), subEnable: z.boolean(), - subEnableRouting: z.boolean(), subEncrypt: z.boolean(), subHideSettings: z.boolean(), - subIncyEnableRouting: z.boolean(), - subIncyRoutingRules: z.string(), subJsonAlwaysArray: z.boolean(), subJsonAutoDetect: z.boolean(), subJsonEnable: z.boolean(), @@ -92,7 +55,6 @@ export const AllSettingSchema = z.object({ subJsonMux: z.string(), subJsonObservatory: z.string(), subJsonPath: z.string(), - subJsonRules: z.string(), subJsonURI: z.string(), subJsonUserAgentRegex: z.string(), subKeyFile: z.string(), @@ -100,29 +62,15 @@ export const AllSettingSchema = z.object({ subPath: z.string(), subPort: z.number().int().min(0).max(65535), subProfileUrl: z.string(), - subRoutingRules: z.string(), subShowIdentityOnAllLinks: z.boolean(), subSupportUrl: z.string(), subThemeDir: z.string(), subTitle: z.string(), subURI: z.string(), subUpdates: z.number().int().min(0).max(525600), - tgBotAPIServer: z.string(), - tgBotBackup: z.boolean(), - tgBotChatId: z.string(), - tgBotEnable: z.boolean(), - tgBotProxy: z.string(), - tgBotToken: z.string(), - tgCpu: z.number().int().min(0).max(100), - tgEnabledEvents: z.string(), - tgLang: z.string(), - tgMemory: z.number().int().min(0).max(100), - tgRunTime: z.string(), timeLocation: z.string(), trafficDiff: z.number().int().min(0).max(100), trustedProxyCIDRs: z.string(), - twoFactorEnable: z.boolean(), - twoFactorToken: z.string(), warpUpdateInterval: z.number().int().min(0), webBasePath: z.string(), webCertFile: z.string(), @@ -136,71 +84,27 @@ export type AllSetting = z.infer; export const AllSettingViewSchema = z.object({ datepicker: z.string(), expireDiff: z.number().int().min(0), - externalTrafficInformEnable: z.boolean(), - externalTrafficInformURI: z.string(), hasApiToken: z.boolean(), - hasLdapPassword: z.boolean(), hasNordSecret: z.boolean(), - hasSmtpPassword: z.boolean(), - hasTgBotToken: z.boolean(), - hasTwoFactorToken: z.boolean(), hasWarpSecret: z.boolean(), ipLimitAllowlist: z.string(), - ldapAutoCreate: z.boolean(), - ldapAutoDelete: z.boolean(), - ldapBaseDN: z.string(), - ldapBindDN: z.string(), - ldapDefaultExpiryDays: z.number().int().min(0), - ldapDefaultLimitIP: z.number().int().min(0), - ldapDefaultTotalGB: z.number().int().min(0), - ldapEnable: z.boolean(), - ldapFlagField: z.string(), - ldapHost: z.string(), - ldapInboundTags: z.string(), - ldapInsecureSkipVerify: z.boolean(), - ldapInvertFlag: z.boolean(), - ldapPassword: z.string(), - ldapPort: z.number().int().min(0).max(65535), - ldapSyncCron: z.string(), - ldapTruthyValues: z.string(), - ldapUseTLS: z.boolean(), - ldapUserAttr: z.string(), - ldapUserFilter: z.string(), - ldapVlessField: z.string(), outboundDownThreshold: z.number().int().min(1).max(100), pageSize: z.number().int().min(0).max(1000), panelOutbound: z.string(), remarkTemplate: z.string(), restartXrayOnClientDisable: z.boolean(), sessionMaxAge: z.number().int().min(1).max(525600), - smtpCpu: z.number().int().min(0).max(100), - smtpEnable: z.boolean(), - smtpEnabledEvents: z.string(), - smtpEncryptionType: z.string(), - smtpFrom: z.string(), - smtpFromName: z.string(), - smtpHost: z.string(), - smtpMemory: z.number().int().min(0).max(100), - smtpPassword: z.string(), - smtpPort: z.number().int().min(1).max(65535), - smtpTo: z.string(), - smtpUsername: z.string(), subAnnounce: z.string(), subCertFile: z.string(), subClashAutoDetect: z.boolean(), subClashEnable: z.boolean(), - subClashEnableRouting: z.boolean(), subClashPath: z.string(), - subClashRules: z.string(), subClashURI: z.string(), subClashUserAgentRegex: z.string(), subDomain: z.string(), subEnable: z.boolean(), - subEnableRouting: z.boolean(), subEncrypt: z.boolean(), subHideSettings: z.boolean(), - subIncyEnableRouting: z.boolean(), - subIncyRoutingRules: z.string(), subJsonAlwaysArray: z.boolean(), subJsonAutoDetect: z.boolean(), subJsonEnable: z.boolean(), @@ -208,7 +112,6 @@ export const AllSettingViewSchema = z.object({ subJsonMux: z.string(), subJsonObservatory: z.string(), subJsonPath: z.string(), - subJsonRules: z.string(), subJsonURI: z.string(), subJsonUserAgentRegex: z.string(), subKeyFile: z.string(), @@ -216,29 +119,15 @@ export const AllSettingViewSchema = z.object({ subPath: z.string(), subPort: z.number().int().min(0).max(65535), subProfileUrl: z.string(), - subRoutingRules: z.string(), subShowIdentityOnAllLinks: z.boolean(), subSupportUrl: z.string(), subThemeDir: z.string(), subTitle: z.string(), subURI: z.string(), subUpdates: z.number().int().min(0).max(525600), - tgBotAPIServer: z.string(), - tgBotBackup: z.boolean(), - tgBotChatId: z.string(), - tgBotEnable: z.boolean(), - tgBotProxy: z.string(), - tgBotToken: z.string(), - tgCpu: z.number().int().min(0).max(100), - tgEnabledEvents: z.string(), - tgLang: z.string(), - tgMemory: z.number().int().min(0).max(100), - tgRunTime: z.string(), timeLocation: z.string(), trafficDiff: z.number().int().min(0).max(100), trustedProxyCIDRs: z.string(), - twoFactorEnable: z.boolean(), - twoFactorToken: z.string(), warpUpdateInterval: z.number().int().min(0), webBasePath: z.string(), webCertFile: z.string(), @@ -249,13 +138,6 @@ export const AllSettingViewSchema = z.object({ }); export type AllSettingView = z.infer; -export const AmneziaWGLogsSchema = z.object({ - events: z.array(z.string()), - peers: z.array(z.lazy(() => PeerActivitySchema)), - running: z.boolean(), -}); -export type AmneziaWGLogs = z.infer; - export const ApiTokenSchema = z.object({ createdAt: z.number().int(), enabled: z.boolean(), @@ -643,9 +525,10 @@ export const NodeMutationRequestSchema = z.object({ inboundSyncMode: z.enum(['all', 'selected']), inboundTags: z.array(z.string()), name: z.string(), + nodeType: z.enum(['traditional', 'agent']).optional(), outboundTag: z.string(), pinnedCertSha256: z.string(), - port: z.number().int().min(1).max(65535), + port: z.number().int().min(0).max(65535), remark: z.string(), scheme: z.enum(['http', 'https']), tlsVerifyMode: z.enum(['verify', 'skip', 'pin', 'mtls']), @@ -678,6 +561,7 @@ export const NodeViewSchema = z.object({ name: z.string(), netDown: z.number().int(), netUp: z.number().int(), + nodeType: z.string().optional(), onlineCount: z.number().int(), outboundTag: z.string(), panelVersion: z.string(), @@ -714,20 +598,6 @@ export const PanelUpdateStatusSchema = z.object({ }); export type PanelUpdateStatus = z.infer; -export const PeerActivitySchema = z.object({ - allowedIPs: z.string(), - down: z.number().int(), - email: z.string(), - endpoint: z.string(), - handshake: z.number().int(), - inboundId: z.number().int(), - interface: z.string(), - online: z.boolean(), - tag: z.string(), - up: z.number().int(), -}); -export type PeerActivity = z.infer; - export const ProbeResultUISchema = z.object({ cpuPct: z.number(), error: z.string(), diff --git a/frontend/src/hooks/useXraySetting.ts b/frontend/src/hooks/useXraySetting.ts index af614d7..f7ed765 100644 --- a/frontend/src/hooks/useXraySetting.ts +++ b/frontend/src/hooks/useXraySetting.ts @@ -7,46 +7,15 @@ import { parseMsg } from '@/utils/zodValidate'; import { keys } from '@/api/queryKeys'; import { OutboundTrafficListSchema, - OutboundTestResultListSchema, XrayConfigPayloadSchema, XraySettingsValueSchema, - type OutboundTestResult, type OutboundTrafficRow, } from '@/schemas/xray'; -const DEFAULT_TEST_URL = 'https://www.google.com/generate_204'; -// One HTTP-mode batch request tests this many outbounds through a single -// shared temp xray instance; chunking keeps responses bounded (~30s worst -// case — each probe is a cold plus a warm request) and lands Test All -// results progressively. -const HTTP_BATCH_CHUNK = 16; - -function normalizeOutboundTestUrl(url: string) { - return url || DEFAULT_TEST_URL; -} - -export function isUdpOutbound(outbound: unknown): boolean { - const o = outbound as - | { protocol?: string; streamSettings?: { network?: string } } - | null - | undefined; - const p = o?.protocol; - const n = o?.streamSettings?.network; - return p === 'wireguard' || p === 'hysteria' || n === 'hysteria' || n === 'kcp' || n === 'quic'; -} - -export type OutboundTestMode = 'tcp' | 'http' | 'real'; - -export type { OutboundTrafficRow, OutboundTestResult }; +export type { OutboundTrafficRow }; export type XraySettingsValue = z.infer; -export interface OutboundTestState { - testing?: boolean; - result?: OutboundTestResult | null; - mode?: string; -} - export type SetTemplate = ( next: XraySettingsValue | null | ((prev: XraySettingsValue | null) => XraySettingsValue | null), ) => void; @@ -60,30 +29,14 @@ export interface UseXraySettingResult { setXraySetting: (next: string) => void; templateSettings: XraySettingsValue | null; setTemplateSettings: SetTemplate; - outboundTestUrl: string; - setOutboundTestUrl: (v: string) => void; inboundTags: string[]; clientReverseTags: string[]; subscriptionOutbounds: unknown[]; subscriptionOutboundTags: string[]; outboundsTraffic: OutboundTrafficRow[]; - outboundTestStates: Record; - subscriptionTestStates: Record; - testingAll: boolean; fetchAll: () => Promise; fetchOutboundsTraffic: () => Promise; resetOutboundsTraffic: (tag: string) => Promise; - testOutbound: ( - index: number, - outbound: unknown, - mode?: string, - ) => Promise; - testSubscriptionOutbound: ( - tag: string, - outbound: unknown, - mode?: string, - ) => Promise; - testAllOutbounds: (mode?: string) => Promise; saveAll: () => Promise; resetToDefault: () => Promise; } @@ -136,9 +89,7 @@ export function useXraySetting(): UseXraySettingResult { const [xraySetting, setXraySettingState] = useState(''); const [templateSettings, setTemplateSettingsState] = useState(null); - const [outboundTestUrl, setOutboundTestUrlState] = useState(DEFAULT_TEST_URL); const [savedXraySetting, setSavedXraySetting] = useState(''); - const [savedOutboundTestUrl, setSavedOutboundTestUrl] = useState(DEFAULT_TEST_URL); const config = configQuery.data; const inboundTags = useMemo(() => config?.inboundTags || [], [config]); const clientReverseTags = useMemo(() => config?.clientReverseTags || [], [config]); @@ -147,21 +98,10 @@ export function useXraySetting(): UseXraySettingResult { [config], ); const subscriptionOutboundTags = useMemo(() => config?.subscriptionOutboundTags || [], [config]); - const [outboundTestStates, setOutboundTestStates] = useState>( - {}, - ); - // Subscription outbounds aren't in templateSettings.outbounds, so their test - // results are keyed by tag rather than by index. - const [subscriptionTestStates, setSubscriptionTestStates] = useState< - Record - >({}); - const [testingAll, setTestingAll] = useState(false); const syncingRef = useRef(false); const xraySettingRef = useRef(''); - const outboundTestUrlRef = useRef(outboundTestUrl); const savedXraySettingRef = useRef(savedXraySetting); - const savedOutboundTestUrlRef = useRef(savedOutboundTestUrl); const templateSettingsRef = useRef(null); const subscriptionOutboundsRef = useRef([]); @@ -169,9 +109,7 @@ export function useXraySetting(): UseXraySettingResult { useEffect(() => { xraySettingRef.current = xraySetting; - outboundTestUrlRef.current = outboundTestUrl; savedXraySettingRef.current = savedXraySetting; - savedOutboundTestUrlRef.current = savedOutboundTestUrl; templateSettingsRef.current = templateSettings; subscriptionOutboundsRef.current = subscriptionOutbounds; }); @@ -180,17 +118,12 @@ export function useXraySetting(): UseXraySettingResult { // of the previous config after a refetch. Local edits win over the refetch. if (config && config !== syncedConfig) { setSyncedConfig(config); - const isDirty = - savedXraySetting !== xraySetting || - savedOutboundTestUrl !== normalizeOutboundTestUrl(outboundTestUrl); + const isDirty = savedXraySetting !== xraySetting; if (!isDirty) { const pretty = JSON.stringify(config.xraySetting, null, 2); - const nextUrl = normalizeOutboundTestUrl(config.outboundTestUrl || ''); setXraySettingState(pretty); setTemplateSettingsState(config.xraySetting); setSavedXraySetting(pretty); - setOutboundTestUrlState(nextUrl); - setSavedOutboundTestUrl(nextUrl); } } @@ -226,10 +159,6 @@ export function useXraySetting(): UseXraySettingResult { }); }, []); - const setOutboundTestUrl = useCallback((v: string) => { - setOutboundTestUrlState(v); - }, []); - const fetchAll = useCallback(async () => { await queryClient.invalidateQueries({ queryKey: keys.xray.config() }); }, [queryClient]); @@ -241,17 +170,14 @@ export function useXraySetting(): UseXraySettingResult { const saveMut = useMutation({ mutationFn: async () => { const sentXraySetting = xraySettingRef.current; - const sentTestUrl = normalizeOutboundTestUrl(outboundTestUrlRef.current); const msg = await HttpUtil.post('/panel/api/xray/update', { xraySetting: sentXraySetting, - outboundTestUrl: sentTestUrl, }); - return { msg, sentXraySetting, sentTestUrl }; + return { msg, sentXraySetting }; }, - onSuccess: ({ msg, sentXraySetting, sentTestUrl }) => { + onSuccess: ({ msg, sentXraySetting }) => { if (!msg?.success) return; setSavedXraySetting(sentXraySetting); - setSavedOutboundTestUrl(sentTestUrl); queryClient.invalidateQueries({ queryKey: keys.xray.config() }); }, }); @@ -291,193 +217,7 @@ export function useXraySetting(): UseXraySettingResult { const spinning = saveMut.isPending || resetDefaultMut.isPending; - // Shared POST + parse for a batch of outbound tests. The backend probes the - // whole batch through one shared temp xray instance and returns results in - // request order; this aligns them by index and shapes failures so every - // input gets an OutboundTestResult. - const postOutboundTestBatch = useCallback( - async (outbounds: unknown[], effMode: string): Promise => { - const failAll = (error: string): OutboundTestResult[] => - outbounds.map(() => ({ success: false, error, mode: effMode })); - try { - const raw = await HttpUtil.post('/panel/api/xray/testOutbounds', { - outbounds: JSON.stringify(outbounds), - allOutbounds: JSON.stringify(templateSettingsRef.current?.outbounds || []), - mode: effMode, - }); - const msg = parseMsg(raw, OutboundTestResultListSchema, 'xray/testOutbounds'); - if (!msg?.success || !Array.isArray(msg.obj)) return failAll(msg?.msg || 'Unknown error'); - const list = msg.obj; - return outbounds.map( - (_ob, i) => list[i] ?? { success: false, error: 'Missing result', mode: effMode }, - ); - } catch (e) { - return failAll(String(e)); - } - }, - [], - ); - - const testOutbound = useCallback( - async (index: number, outbound: unknown, mode = 'tcp'): Promise => { - if (!outbound) return null; - const effMode = mode === 'tcp' && isUdpOutbound(outbound) ? 'http' : mode; - setOutboundTestStates((prev) => ({ - ...prev, - [index]: { testing: true, result: null, mode: effMode }, - })); - const [result] = await postOutboundTestBatch([outbound], effMode); - setOutboundTestStates((prev) => ({ ...prev, [index]: { testing: false, result } })); - return result.success ? result : null; - }, - [postOutboundTestBatch], - ); - - // Test a subscription outbound (not present in templateSettings.outbounds); - // results are keyed by tag in subscriptionTestStates. - const testSubscriptionOutbound = useCallback( - async (tag: string, outbound: unknown, mode = 'tcp'): Promise => { - if (!outbound || !tag) return null; - const effMode = mode === 'tcp' && isUdpOutbound(outbound) ? 'http' : mode; - setSubscriptionTestStates((prev) => ({ - ...prev, - [tag]: { testing: true, result: null, mode: effMode }, - })); - const [result] = await postOutboundTestBatch([outbound], effMode); - setSubscriptionTestStates((prev) => ({ ...prev, [tag]: { testing: false, result } })); - return result.success ? result : null; - }, - [postOutboundTestBatch], - ); - - const testAllOutbounds = useCallback( - async (mode = 'tcp') => { - // Template outbounds key their results by index (outboundTestStates); - // subscription outbounds aren't in the template, so they key by tag - // (subscriptionTestStates). Both go through the same probe endpoint. - const templateList = templateSettingsRef.current?.outbounds || []; - const subList = (subscriptionOutboundsRef.current || []) as Array<{ - tag?: string; - protocol?: string; - }>; - if ((templateList.length === 0 && subList.length === 0) || testingAll) return; - setTestingAll(true); - try { - type TcpEntry = - | { kind: 'tpl'; index: number; outbound: unknown } - | { kind: 'sub'; tag: string; outbound: unknown }; - const tcpQueue: TcpEntry[] = []; - // HTTP batches stay homogeneous (all template or all subscription) so a - // tag shared between a template and a subscription outbound can't collide - // inside one batch, and each batch's results route to one state map. - const probeMode = mode === 'real' ? 'real' : 'http'; - const httpTplQueue: { index: number; outbound: unknown }[] = []; - const httpSubQueue: { tag: string; outbound: unknown }[] = []; - const enqueue = ( - ob: { tag?: string; protocol?: string }, - kind: 'tpl' | 'sub', - index: number, - tag: string, - ) => { - const proto = ob?.protocol; - if (proto === 'blackhole' || proto === 'loopback' || ob?.tag === 'blocked') return; - // freedom ("direct") and dns aren't proxies — skip them in every mode. - if (proto === 'freedom' || proto === 'dns') return; - if (kind === 'sub' && !tag) return; - const toHttp = mode !== 'tcp' || isUdpOutbound(ob); - if (kind === 'tpl') { - if (toHttp) httpTplQueue.push({ index, outbound: ob }); - else tcpQueue.push({ kind: 'tpl', index, outbound: ob }); - } else if (toHttp) { - httpSubQueue.push({ tag, outbound: ob }); - } else { - tcpQueue.push({ kind: 'sub', tag, outbound: ob }); - } - }; - templateList.forEach((ob, i) => enqueue(ob, 'tpl', i, '')); - subList.forEach((ob) => enqueue(ob, 'sub', -1, typeof ob?.tag === 'string' ? ob.tag : '')); - - // TCP probes are dial-only and cheap server-side; per-item requests - // keep results landing one by one, each routed to its own state map. - const runTcpLane = async () => { - const queue = [...tcpQueue]; - const worker = async () => { - while (queue.length > 0) { - const item = queue.shift(); - if (!item) break; - if (item.kind === 'sub') - await testSubscriptionOutbound(item.tag, item.outbound, mode); - else await testOutbound(item.index, item.outbound, mode); - } - }; - await Promise.all(Array.from({ length: Math.min(8, queue.length) }, () => worker())); - }; - // HTTP probes go out as chunked batches — one temp xray spawn per - // chunk instead of one per outbound, with results landing per chunk. - const runTplHttpLane = async () => { - for (let at = 0; at < httpTplQueue.length; at += HTTP_BATCH_CHUNK) { - const chunk = httpTplQueue.slice(at, at + HTTP_BATCH_CHUNK); - setOutboundTestStates((prev) => { - const next = { ...prev }; - for (const item of chunk) - next[item.index] = { testing: true, result: null, mode: probeMode }; - return next; - }); - const results = await postOutboundTestBatch( - chunk.map((c) => c.outbound), - probeMode, - ); - setOutboundTestStates((prev) => { - const next = { ...prev }; - chunk.forEach((item, i) => { - next[item.index] = { testing: false, result: results[i] }; - }); - return next; - }); - } - }; - const runSubHttpLane = async () => { - for (let at = 0; at < httpSubQueue.length; at += HTTP_BATCH_CHUNK) { - const chunk = httpSubQueue.slice(at, at + HTTP_BATCH_CHUNK); - setSubscriptionTestStates((prev) => { - const next = { ...prev }; - for (const item of chunk) - next[item.tag] = { testing: true, result: null, mode: probeMode }; - return next; - }); - const results = await postOutboundTestBatch( - chunk.map((c) => c.outbound), - probeMode, - ); - setSubscriptionTestStates((prev) => { - const next = { ...prev }; - chunk.forEach((item, i) => { - next[item.tag] = { testing: false, result: results[i] }; - }); - return next; - }); - } - }; - // HTTP batches must not overlap: the backend serialises them with a - // non-blocking lock and rejects a second concurrent batch ("Another - // outbound test is already running"). Run the template and subscription - // HTTP lanes one after the other; TCP probes don't take that lock, so - // they still run alongside. - const runHttpLane = async () => { - await runTplHttpLane(); - await runSubHttpLane(); - }; - await Promise.all([runTcpLane(), runHttpLane()]); - } finally { - setTestingAll(false); - } - }, - [testingAll, testOutbound, testSubscriptionOutbound, postOutboundTestBatch], - ); - - const saveDisabled = - savedXraySetting === xraySetting && - savedOutboundTestUrl === normalizeOutboundTestUrl(outboundTestUrl); + const saveDisabled = savedXraySetting === xraySetting; const outboundsTraffic = useMemo(() => trafficQuery.data ?? [], [trafficQuery.data]); @@ -491,22 +231,14 @@ export function useXraySetting(): UseXraySettingResult { setXraySetting, templateSettings, setTemplateSettings, - outboundTestUrl, - setOutboundTestUrl, inboundTags, clientReverseTags, subscriptionOutbounds, subscriptionOutboundTags, outboundsTraffic, - outboundTestStates, - subscriptionTestStates, - testingAll, fetchAll, fetchOutboundsTraffic: fetchOutboundsTrafficCb, resetOutboundsTraffic, - testOutbound, - testSubscriptionOutbound, - testAllOutbounds, saveAll, resetToDefault, }), @@ -519,22 +251,14 @@ export function useXraySetting(): UseXraySettingResult { setXraySetting, templateSettings, setTemplateSettings, - outboundTestUrl, - setOutboundTestUrl, inboundTags, clientReverseTags, subscriptionOutbounds, subscriptionOutboundTags, outboundsTraffic, - outboundTestStates, - subscriptionTestStates, - testingAll, fetchAll, fetchOutboundsTrafficCb, resetOutboundsTraffic, - testOutbound, - testSubscriptionOutbound, - testAllOutbounds, saveAll, resetToDefault, ], diff --git a/frontend/src/pages/xray/XrayPage.css b/frontend/src/pages/xray/XrayPage.css index f9af696..4e5bc03 100644 --- a/frontend/src/pages/xray/XrayPage.css +++ b/frontend/src/pages/xray/XrayPage.css @@ -1,16 +1,3 @@ -.xray-page .restart-icon { - font-size: 16px; - cursor: pointer; - color: var(--ant-color-primary); -} - -.xray-page .restart-result { - max-width: 480px; - white-space: pre-wrap; - font-size: 12px; - margin: 0; -} - .xray-page .advanced-meta h4 { margin: 0 0 4px; } diff --git a/frontend/src/pages/xray/XrayPage.tsx b/frontend/src/pages/xray/XrayPage.tsx index 73be980..b4cd16f 100644 --- a/frontend/src/pages/xray/XrayPage.tsx +++ b/frontend/src/pages/xray/XrayPage.tsx @@ -55,21 +55,13 @@ export default function XrayPage() { setXraySetting, templateSettings, setTemplateSettings, - outboundTestUrl, - setOutboundTestUrl, inboundTags, clientReverseTags, subscriptionOutbounds, subscriptionOutboundTags, outboundsTraffic, - outboundTestStates, - subscriptionTestStates, - testingAll, fetchAll, resetOutboundsTraffic, - testOutbound, - testSubscriptionOutbound, - testAllOutbounds, saveAll, resetToDefault, } = xs; @@ -99,16 +91,6 @@ export default function XrayPage() { [setTemplateSettings], ); - async function onTestOutbound(idx: number, mode: string) { - const outbound = templateSettings?.outbounds?.[idx]; - if (outbound) await testOutbound(idx, outbound, mode); - } - - async function onTestSubscription(outbound: Record, mode: string) { - const tag = typeof outbound?.tag === 'string' ? outbound.tag : ''; - if (tag) await testSubscriptionOutbound(tag, outbound, mode); - } - function onAddOutbound(outbound: Record) { mutate((tt) => { if (!Array.isArray(tt.outbounds)) tt.outbounds = []; @@ -196,17 +178,11 @@ export default function XrayPage() { templateSettings={templateSettings} setTemplateSettings={setTemplateSettings} outboundsTraffic={outboundsTraffic} - outboundTestStates={outboundTestStates} - subscriptionTestStates={subscriptionTestStates} - testingAll={testingAll} inboundTags={inboundTags} subscriptionOutbounds={subscriptionOutbounds} subscriptionOutboundTags={subscriptionOutboundTags} isMobile={isMobile} onResetTraffic={resetOutboundsTraffic} - onTest={onTestOutbound} - onTestSubscription={onTestSubscription} - onTestAll={testAllOutbounds} onShowWarp={() => setWarpOpen(true)} onShowNord={() => setNordOpen(true)} onShowPia={() => setPiaOpen(true)} @@ -218,8 +194,6 @@ export default function XrayPage() { ); diff --git a/frontend/src/pages/xray/basics/BasicsTab.tsx b/frontend/src/pages/xray/basics/BasicsTab.tsx index 4a464f8..13777ab 100644 --- a/frontend/src/pages/xray/basics/BasicsTab.tsx +++ b/frontend/src/pages/xray/basics/BasicsTab.tsx @@ -29,16 +29,12 @@ import { interface BasicsTabProps { templateSettings: XraySettingsValue | null; setTemplateSettings: SetTemplate; - outboundTestUrl: string; - onChangeOutboundTestUrl: (v: string) => void; onResetDefault: () => void; } export default function BasicsTab({ templateSettings, setTemplateSettings, - outboundTestUrl, - onChangeOutboundTestUrl, onResetDefault, }: BasicsTabProps) { const { t } = useTranslation(); @@ -276,18 +272,6 @@ export default function BasicsTab({ /> } /> - onChangeOutboundTestUrl(e.target.value)} - placeholder="https://www.google.com/generate_204" - /> - } - /> ), }, diff --git a/frontend/src/pages/xray/outbounds/CountryPill.tsx b/frontend/src/pages/xray/outbounds/CountryPill.tsx deleted file mode 100644 index 9923604..0000000 --- a/frontend/src/pages/xray/outbounds/CountryPill.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { CloudOutlined } from '@ant-design/icons'; - -interface CountryPillProps { - flag: string; - name: string; - warp?: string; -} - -export default function CountryPill({ flag, name, warp }: CountryPillProps) { - const isWarp = !!warp && warp.toLowerCase() !== 'off'; - return ( - - {isWarp && } - {flag && {flag}} - {name} - - ); -} diff --git a/frontend/src/pages/xray/outbounds/OutboundCardList.tsx b/frontend/src/pages/xray/outbounds/OutboundCardList.tsx index 158c11f..ad59e32 100644 --- a/frontend/src/pages/xray/outbounds/OutboundCardList.tsx +++ b/frontend/src/pages/xray/outbounds/OutboundCardList.tsx @@ -1,4 +1,3 @@ -import { useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Button, Dropdown, Tag, Tooltip } from 'antd'; import { @@ -7,127 +6,34 @@ import { EditOutlined, DeleteOutlined, VerticalAlignTopOutlined, - ThunderboltOutlined, - LoadingOutlined, ExportOutlined, - EyeInvisibleOutlined, - EyeOutlined, } from '@ant-design/icons'; import { SizeFormatter } from '@/utils'; -import { activateOnKey } from '@/utils/a11y'; import { OutboundProtocols as Protocols } from '@/schemas/primitives'; -import type { - OutboundTestMode, - OutboundTestState, - OutboundTrafficRow, -} from '@/hooks/useXraySetting'; +import type { OutboundTrafficRow } from '@/hooks/useXraySetting'; import type { OutboundRow } from './outbounds-tab-types'; -import CountryPill from './CountryPill'; -import TestResultPopover from './TestResultPopover'; -import { - countryFlag, - countryName, - isTesting, - isUntestable, - outboundAddresses, - showSecurity, - testResult, - trafficFor, -} from './outbounds-tab-helpers'; +import { outboundAddresses, showSecurity, trafficFor } from './outbounds-tab-helpers'; interface OutboundCardListProps { rows: OutboundRow[]; - testMode: OutboundTestMode; outboundsTraffic: OutboundTrafficRow[]; - outboundTestStates: Record; setFirst: (idx: number) => void; openEdit: (idx: number) => void; onResetTraffic: (tag: string) => void; confirmDelete: (idx: number) => void; - onTest: (index: number, mode: string) => void; } export default function OutboundCardList({ rows, - testMode, outboundsTraffic, - outboundTestStates, setFirst, openEdit, onResetTraffic, confirmDelete, - onTest, }: OutboundCardListProps) { - const { t, i18n } = useTranslation(); - const [showEgressIp, setShowEgressIp] = useState>({}); - - const setCardEgressVisible = (key: string, visible: boolean) => { - setShowEgressIp((prev) => ({ ...prev, [key]: visible })); - }; - - const renderEgress = (testKey: number, rowKey: string) => { - const result = testResult(outboundTestStates, testKey); - const egress = result?.egress; - const isEgressVisible = !!showEgressIp[rowKey]; - const flag = countryFlag(egress?.country); - const name = countryName(egress?.country, i18n.language); - const addresses = [ - egress?.ipv4 ? { label: 'v4', value: egress.ipv4 } : null, - egress?.ipv6 ? { label: 'v6', value: egress.ipv6 } : null, - ].filter((item): item is { label: string; value: string } => Boolean(item)); - - if (!egress || (addresses.length === 0 && !egress.country)) { - return null; - } - - return ( -
-
- {t('pages.xray.outbound.egress')}: - - {isEgressVisible ? ( - setCardEgressVisible(rowKey, false)} - onKeyDown={activateOnKey(() => setCardEgressVisible(rowKey, false))} - /> - ) : ( - setCardEgressVisible(rowKey, true)} - onKeyDown={activateOnKey(() => setCardEgressVisible(rowKey, true))} - /> - )} - - {egress.country && ( - - )} -
- {addresses.map((addr) => ( - -
- {addr.label}: - - {addr.value} - -
-
- ))} -
- ); - }; + const { t } = useTranslation(); if (rows.length === 0) { return ( @@ -219,7 +125,6 @@ export default function OutboundCardList({ ))} )} - {renderEgress(record.key, String(record.key))}
↑ {SizeFormatter.sizeFormat(trafficFor(outboundsTraffic, record).up)} @@ -228,23 +133,6 @@ export default function OutboundCardList({ ↓ {SizeFormatter.sizeFormat(trafficFor(outboundsTraffic, record).down)} - - {testResult(outboundTestStates, record.key) ? ( - - ) : isTesting(outboundTestStates, record.key) ? ( - - ) : null} -
))} diff --git a/frontend/src/pages/xray/outbounds/OutboundsTab.css b/frontend/src/pages/xray/outbounds/OutboundsTab.css index abbe1c9..35befa6 100644 --- a/frontend/src/pages/xray/outbounds/OutboundsTab.css +++ b/frontend/src/pages/xray/outbounds/OutboundsTab.css @@ -75,51 +75,8 @@ background: var(--ant-color-fill-tertiary); } -.egress-header { - display: inline-flex; - align-items: center; - gap: 4px; -} -.egress-ip { - font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; - font-size: 12px; -} -.egress-stack { - display: inline-flex; - flex-direction: column; - gap: 3px; -} - -.egress-address { - display: inline-flex; - align-items: center; - gap: 4px; - min-width: 0; -} - -.egress-family { - color: var(--ant-color-text-secondary); - font-size: 10px; - text-transform: uppercase; -} - -.country-pill { - display: inline-flex; - align-items: center; - gap: 4px; - border: 1px solid var(--ant-color-border); - border-radius: 4px; - padding: 1px 6px; - line-height: 20px; - font-size: 12px; - white-space: nowrap; -} - -.country-pill.warp-on { - border-color: #f48120; -} .warp-cloud-icon { width: 1em; @@ -185,30 +142,6 @@ flex-wrap: wrap; } -.card-egress { - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 3px; - font-size: 12px; - opacity: 0.85; -} - -.card-egress-row { - display: inline-flex; - align-items: center; - gap: 6px; - min-width: 0; - max-width: 100%; -} - -.card-test { - margin-left: auto; - display: inline-flex; - align-items: center; - gap: 8px; -} - .traffic-up { color: #008771; font-size: 12px; diff --git a/frontend/src/pages/xray/outbounds/OutboundsTab.tsx b/frontend/src/pages/xray/outbounds/OutboundsTab.tsx index fd4d223..3618593 100644 --- a/frontend/src/pages/xray/outbounds/OutboundsTab.tsx +++ b/frontend/src/pages/xray/outbounds/OutboundsTab.tsx @@ -9,7 +9,6 @@ import { InputNumber, Modal, Popconfirm, - Radio, Row, Space, Switch, @@ -24,7 +23,6 @@ import { ApiOutlined, MoreOutlined, RetweetOutlined, - PlayCircleOutlined, ReloadOutlined, DeleteOutlined, EditOutlined, @@ -47,13 +45,7 @@ import { propagateOutboundTagRename } from '../basics/helpers'; import { planOutboundDeletion, applyOutboundDeletion } from '../reference-cleanup'; import DeletionImpactList from '../DeletionImpactList'; import { isBalancerLoopbackTag } from '../balancers/balancer-loopback'; -import type { - XraySettingsValue, - SetTemplate, - OutboundTestMode, - OutboundTestState, - OutboundTrafficRow, -} from '@/hooks/useXraySetting'; +import type { XraySettingsValue, SetTemplate, OutboundTrafficRow } from '@/hooks/useXraySetting'; import './OutboundsTab.css'; import type { OutboundRow } from './outbounds-tab-types'; @@ -82,17 +74,11 @@ interface OutboundsTabProps { templateSettings: XraySettingsValue | null; setTemplateSettings: SetTemplate; outboundsTraffic: OutboundTrafficRow[]; - outboundTestStates: Record; - subscriptionTestStates: Record; - testingAll: boolean; inboundTags: string[]; subscriptionOutbounds?: unknown[]; subscriptionOutboundTags?: string[]; isMobile: boolean; onResetTraffic: (tag: string) => void; - onTest: (index: number, mode: string) => void; - onTestSubscription: (outbound: Record, mode: string) => void; - onTestAll: (mode: string) => void; onShowWarp: () => void; onShowNord: () => void; onShowPia: () => void; @@ -103,17 +89,11 @@ export default function OutboundsTab({ templateSettings, setTemplateSettings, outboundsTraffic, - outboundTestStates, - subscriptionTestStates, - testingAll, inboundTags: _inboundTags, subscriptionOutbounds, subscriptionOutboundTags, isMobile, onResetTraffic, - onTest, - onTestSubscription, - onTestAll, onShowWarp, onShowNord, onShowPia, @@ -122,7 +102,6 @@ export default function OutboundsTab({ const { t } = useTranslation(); const [modal, modalContextHolder] = Modal.useModal(); const [messageApi, messageContextHolder] = message.useMessage(); - const [testMode, setTestMode] = useState('tcp'); const [modalOpen, setModalOpen] = useState(false); const [editingOutbound, setEditingOutbound] = useState | null>(null); const [editingIndex, setEditingIndex] = useState(null); @@ -519,17 +498,14 @@ export default function OutboundsTab({ } const columns = useOutboundColumns({ - testMode, rows, outboundsTraffic, - outboundTestStates, openEdit, setFirst, moveUp, moveDown, confirmDelete, onResetTraffic, - onTest, }); return ( @@ -581,26 +557,6 @@ export default function OutboundsTab({ - - setTestMode(e.target.value)} - buttonStyle="solid" - size="small" - > - TCP - HTTP - {t('pages.xray.outbound.modeRealDelay')} - - - ) : ( )} diff --git a/frontend/src/pages/xray/outbounds/SubscriptionOutbounds.tsx b/frontend/src/pages/xray/outbounds/SubscriptionOutbounds.tsx index 16ce608..fb686e6 100644 --- a/frontend/src/pages/xray/outbounds/SubscriptionOutbounds.tsx +++ b/frontend/src/pages/xray/outbounds/SubscriptionOutbounds.tsx @@ -1,49 +1,28 @@ import { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; -import { Button, Table, Tag, Tooltip } from 'antd'; -import { ThunderboltOutlined, LoadingOutlined } from '@ant-design/icons'; +import { Table, Tag, Tooltip } from 'antd'; import type { ColumnsType } from 'antd/es/table'; import { SizeFormatter } from '@/utils'; import { OutboundProtocols as Protocols } from '@/schemas/primitives'; -import type { - OutboundTestMode, - OutboundTestState, - OutboundTrafficRow, -} from '@/hooks/useXraySetting'; +import type { OutboundTrafficRow } from '@/hooks/useXraySetting'; import type { OutboundRow } from './outbounds-tab-types'; -import TestResultPopover from './TestResultPopover'; -import { - effectiveTestMode, - isTesting, - isUntestable, - outboundAddresses, - showSecurity, - testModeLabel, - testResult, - trafficFor, -} from './outbounds-tab-helpers'; +import { outboundAddresses, showSecurity, trafficFor } from './outbounds-tab-helpers'; interface SubscriptionOutboundsProps { subscriptionOutbounds: unknown[]; outboundsTraffic: OutboundTrafficRow[]; - subscriptionTestStates: Record; - testMode: OutboundTestMode; isMobile: boolean; - onTestSubscription: (outbound: Record, mode: string) => void; } // Read-only view of outbounds imported from active subscriptions. They are not // part of the editable template (so no edit/delete/move), but traffic is matched -// by tag and they can be latency-tested via the same backend endpoint. +// by tag. export default function SubscriptionOutbounds({ subscriptionOutbounds, outboundsTraffic, - subscriptionTestStates, - testMode, isMobile, - onTestSubscription, }: SubscriptionOutboundsProps) { const { t } = useTranslation(); @@ -104,36 +83,6 @@ export default function SubscriptionOutbounds({ ); }; - const latencyCell = (record: OutboundRow) => { - const key = record.tag || ''; - const r = testResult(subscriptionTestStates, key); - if (!r) - return isTesting(subscriptionTestStates, key) ? ( - - ) : ( - — - ); - return ; - }; - - const testButton = (record: OutboundRow) => { - const key = record.tag || ''; - return ( - -