mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-10-01 19:52:09 +03:00
feat(sub): add Incy app-management parameters (#6650)
* feat(sub): add Incy app-management parameters The panel already pushes a set of Happ headers, but INCY documents its own lowercase header names and its own value domains, so a Happ-shaped payload gets ignored by the client (per-app mode is bypass|proxy, not on|bypass, and per-app-proxy-enable has no Happ counterpart at all). Add a sibling Incy path that emits exactly the documented headers. Covered, per https://docs.incy.cc/en/app-management/: - profile-description, sort-order, support-email, announce-url, premium-url - banner text/button/URL and the two hex colours - hide-url, hide-check, no-limit-enabled - per-app split tunnelling (enable/mode/list) - TCP fragmentation (enable/length/interval/packets) - UDP noise packets (enable/type/packet/delay) - DoH pre-resolution (enable/domain/IP) Each string setting is tri-state: an empty value omits the header, so an untouched panel never overrides the subscriber's own choice in the app. Values are validated against the documented domains and dropped when they do not match, and non-ASCII text is base64-wrapped the way the docs require for Cyrillic. INCY identifies itself as INCY/<version>/<platform>, which gates the headers behind the same auto-detect switch the Happ path uses. Headers the panel already emits for every client (Profile-Title, Support-Url, Profile-Web-Page-Url, Announce, Profile-Update-Interval, Subscription-Userinfo) and Incy's routing line are left as they are. The Premium API (theme, defaultPingProtocol, fallbackHosts, ...) is a separate encrypted endpoint and stays out of scope here. * fix(sub): keep Incy per-app list entries separate on the wire The Incy settings textarea takes one package per line, as Incy documents for per-app-proxy-list, but the header path ran the value through sanitizeHeaderValue, which deletes CR/LF. "com.google.chrome\norg.telegram.messenger" reached the client as the single bogus package "com.google.chromeorg.telegram.messenger", so per-app split tunnelling silently matched no app. Join comma- or line-separated entries as CSV instead. Also drop three tests that could not fail: TestIncyExcludesHappOnlyHeaders (ApplyIncyHeaders has no path that emits Happ headers, and the non-Happ UA gate is already pinned by TestApplyHappHeaders_Gating) and two UI tests that only asserted updateSetting received the key the JSX passes it. --------- Co-authored-by: DIMFLIX <dimflix@users.noreply.github.com> Co-authored-by: MHSanaei <ho3ein.sanaei@gmail.com>
This commit is contained in:
@@ -98,8 +98,36 @@ export const EXAMPLES: Record<string, unknown> = {
|
||||
"subHappTunMode": "",
|
||||
"subHappTunType": "",
|
||||
"subHideSettings": false,
|
||||
"subIncyAnnounceUrl": "",
|
||||
"subIncyAppAutoDetect": false,
|
||||
"subIncyBannerBgColor": "",
|
||||
"subIncyBannerButtonColor": "",
|
||||
"subIncyBannerButtonText": "",
|
||||
"subIncyBannerButtonUrl": "",
|
||||
"subIncyBannerText": "",
|
||||
"subIncyEnableRouting": false,
|
||||
"subIncyFragmentInterval": "",
|
||||
"subIncyFragmentLength": "",
|
||||
"subIncyFragmentPackets": "",
|
||||
"subIncyFragmentationEnable": "",
|
||||
"subIncyHideCheck": "",
|
||||
"subIncyHideUrl": "",
|
||||
"subIncyNoLimitEnabled": "",
|
||||
"subIncyNoisesDelay": "",
|
||||
"subIncyNoisesEnable": "",
|
||||
"subIncyNoisesPacket": "",
|
||||
"subIncyNoisesType": "",
|
||||
"subIncyPerAppEnable": "",
|
||||
"subIncyPerAppList": "",
|
||||
"subIncyPerAppMode": "",
|
||||
"subIncyPremiumUrl": "",
|
||||
"subIncyProfileDescription": "",
|
||||
"subIncyResolveDnsDomain": "",
|
||||
"subIncyResolveDnsIp": "",
|
||||
"subIncyResolveEnable": "",
|
||||
"subIncyRoutingRules": "",
|
||||
"subIncySortOrder": "",
|
||||
"subIncySupportEmail": "",
|
||||
"subInfoNodeEnable": false,
|
||||
"subJsonAlwaysArray": false,
|
||||
"subJsonAutoDetect": false,
|
||||
@@ -257,8 +285,36 @@ export const EXAMPLES: Record<string, unknown> = {
|
||||
"subHappTunMode": "",
|
||||
"subHappTunType": "",
|
||||
"subHideSettings": false,
|
||||
"subIncyAnnounceUrl": "",
|
||||
"subIncyAppAutoDetect": false,
|
||||
"subIncyBannerBgColor": "",
|
||||
"subIncyBannerButtonColor": "",
|
||||
"subIncyBannerButtonText": "",
|
||||
"subIncyBannerButtonUrl": "",
|
||||
"subIncyBannerText": "",
|
||||
"subIncyEnableRouting": false,
|
||||
"subIncyFragmentInterval": "",
|
||||
"subIncyFragmentLength": "",
|
||||
"subIncyFragmentPackets": "",
|
||||
"subIncyFragmentationEnable": "",
|
||||
"subIncyHideCheck": "",
|
||||
"subIncyHideUrl": "",
|
||||
"subIncyNoLimitEnabled": "",
|
||||
"subIncyNoisesDelay": "",
|
||||
"subIncyNoisesEnable": "",
|
||||
"subIncyNoisesPacket": "",
|
||||
"subIncyNoisesType": "",
|
||||
"subIncyPerAppEnable": "",
|
||||
"subIncyPerAppList": "",
|
||||
"subIncyPerAppMode": "",
|
||||
"subIncyPremiumUrl": "",
|
||||
"subIncyProfileDescription": "",
|
||||
"subIncyResolveDnsDomain": "",
|
||||
"subIncyResolveDnsIp": "",
|
||||
"subIncyResolveEnable": "",
|
||||
"subIncyRoutingRules": "",
|
||||
"subIncySortOrder": "",
|
||||
"subIncySupportEmail": "",
|
||||
"subInfoNodeEnable": false,
|
||||
"subJsonAlwaysArray": false,
|
||||
"subJsonAutoDetect": false,
|
||||
|
||||
@@ -316,12 +316,97 @@ export const SCHEMAS: Record<string, unknown> = {
|
||||
"subHideSettings": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"subIncyAnnounceUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"subIncyAppAutoDetect": {
|
||||
"description": "Incy client customization settings (app-management). A \"\" value omits\nthe header so the subscriber's own app setting is left alone.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"subIncyBannerBgColor": {
|
||||
"type": "string"
|
||||
},
|
||||
"subIncyBannerButtonColor": {
|
||||
"type": "string"
|
||||
},
|
||||
"subIncyBannerButtonText": {
|
||||
"type": "string"
|
||||
},
|
||||
"subIncyBannerButtonUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"subIncyBannerText": {
|
||||
"type": "string"
|
||||
},
|
||||
"subIncyEnableRouting": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"subIncyFragmentInterval": {
|
||||
"type": "string"
|
||||
},
|
||||
"subIncyFragmentLength": {
|
||||
"type": "string"
|
||||
},
|
||||
"subIncyFragmentPackets": {
|
||||
"type": "string"
|
||||
},
|
||||
"subIncyFragmentationEnable": {
|
||||
"type": "string"
|
||||
},
|
||||
"subIncyHideCheck": {
|
||||
"type": "string"
|
||||
},
|
||||
"subIncyHideUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"subIncyNoLimitEnabled": {
|
||||
"type": "string"
|
||||
},
|
||||
"subIncyNoisesDelay": {
|
||||
"type": "string"
|
||||
},
|
||||
"subIncyNoisesEnable": {
|
||||
"type": "string"
|
||||
},
|
||||
"subIncyNoisesPacket": {
|
||||
"type": "string"
|
||||
},
|
||||
"subIncyNoisesType": {
|
||||
"type": "string"
|
||||
},
|
||||
"subIncyPerAppEnable": {
|
||||
"type": "string"
|
||||
},
|
||||
"subIncyPerAppList": {
|
||||
"type": "string"
|
||||
},
|
||||
"subIncyPerAppMode": {
|
||||
"type": "string"
|
||||
},
|
||||
"subIncyPremiumUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"subIncyProfileDescription": {
|
||||
"type": "string"
|
||||
},
|
||||
"subIncyResolveDnsDomain": {
|
||||
"type": "string"
|
||||
},
|
||||
"subIncyResolveDnsIp": {
|
||||
"type": "string"
|
||||
},
|
||||
"subIncyResolveEnable": {
|
||||
"type": "string"
|
||||
},
|
||||
"subIncyRoutingRules": {
|
||||
"type": "string"
|
||||
},
|
||||
"subIncySortOrder": {
|
||||
"type": "string"
|
||||
},
|
||||
"subIncySupportEmail": {
|
||||
"type": "string"
|
||||
},
|
||||
"subInfoNodeEnable": {
|
||||
"type": "boolean"
|
||||
},
|
||||
@@ -584,8 +669,36 @@ export const SCHEMAS: Record<string, unknown> = {
|
||||
"subHappTunMode",
|
||||
"subHappTunType",
|
||||
"subHideSettings",
|
||||
"subIncyAnnounceUrl",
|
||||
"subIncyAppAutoDetect",
|
||||
"subIncyBannerBgColor",
|
||||
"subIncyBannerButtonColor",
|
||||
"subIncyBannerButtonText",
|
||||
"subIncyBannerButtonUrl",
|
||||
"subIncyBannerText",
|
||||
"subIncyEnableRouting",
|
||||
"subIncyFragmentInterval",
|
||||
"subIncyFragmentLength",
|
||||
"subIncyFragmentPackets",
|
||||
"subIncyFragmentationEnable",
|
||||
"subIncyHideCheck",
|
||||
"subIncyHideUrl",
|
||||
"subIncyNoLimitEnabled",
|
||||
"subIncyNoisesDelay",
|
||||
"subIncyNoisesEnable",
|
||||
"subIncyNoisesPacket",
|
||||
"subIncyNoisesType",
|
||||
"subIncyPerAppEnable",
|
||||
"subIncyPerAppList",
|
||||
"subIncyPerAppMode",
|
||||
"subIncyPremiumUrl",
|
||||
"subIncyProfileDescription",
|
||||
"subIncyResolveDnsDomain",
|
||||
"subIncyResolveDnsIp",
|
||||
"subIncyResolveEnable",
|
||||
"subIncyRoutingRules",
|
||||
"subIncySortOrder",
|
||||
"subIncySupportEmail",
|
||||
"subInfoNodeEnable",
|
||||
"subJsonAlwaysArray",
|
||||
"subJsonAutoDetect",
|
||||
@@ -979,12 +1092,97 @@ export const SCHEMAS: Record<string, unknown> = {
|
||||
"subHideSettings": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"subIncyAnnounceUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"subIncyAppAutoDetect": {
|
||||
"description": "Incy client customization settings (app-management). A \"\" value omits\nthe header so the subscriber's own app setting is left alone.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"subIncyBannerBgColor": {
|
||||
"type": "string"
|
||||
},
|
||||
"subIncyBannerButtonColor": {
|
||||
"type": "string"
|
||||
},
|
||||
"subIncyBannerButtonText": {
|
||||
"type": "string"
|
||||
},
|
||||
"subIncyBannerButtonUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"subIncyBannerText": {
|
||||
"type": "string"
|
||||
},
|
||||
"subIncyEnableRouting": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"subIncyFragmentInterval": {
|
||||
"type": "string"
|
||||
},
|
||||
"subIncyFragmentLength": {
|
||||
"type": "string"
|
||||
},
|
||||
"subIncyFragmentPackets": {
|
||||
"type": "string"
|
||||
},
|
||||
"subIncyFragmentationEnable": {
|
||||
"type": "string"
|
||||
},
|
||||
"subIncyHideCheck": {
|
||||
"type": "string"
|
||||
},
|
||||
"subIncyHideUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"subIncyNoLimitEnabled": {
|
||||
"type": "string"
|
||||
},
|
||||
"subIncyNoisesDelay": {
|
||||
"type": "string"
|
||||
},
|
||||
"subIncyNoisesEnable": {
|
||||
"type": "string"
|
||||
},
|
||||
"subIncyNoisesPacket": {
|
||||
"type": "string"
|
||||
},
|
||||
"subIncyNoisesType": {
|
||||
"type": "string"
|
||||
},
|
||||
"subIncyPerAppEnable": {
|
||||
"type": "string"
|
||||
},
|
||||
"subIncyPerAppList": {
|
||||
"type": "string"
|
||||
},
|
||||
"subIncyPerAppMode": {
|
||||
"type": "string"
|
||||
},
|
||||
"subIncyPremiumUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"subIncyProfileDescription": {
|
||||
"type": "string"
|
||||
},
|
||||
"subIncyResolveDnsDomain": {
|
||||
"type": "string"
|
||||
},
|
||||
"subIncyResolveDnsIp": {
|
||||
"type": "string"
|
||||
},
|
||||
"subIncyResolveEnable": {
|
||||
"type": "string"
|
||||
},
|
||||
"subIncyRoutingRules": {
|
||||
"type": "string"
|
||||
},
|
||||
"subIncySortOrder": {
|
||||
"type": "string"
|
||||
},
|
||||
"subIncySupportEmail": {
|
||||
"type": "string"
|
||||
},
|
||||
"subInfoNodeEnable": {
|
||||
"type": "boolean"
|
||||
},
|
||||
@@ -1255,8 +1453,36 @@ export const SCHEMAS: Record<string, unknown> = {
|
||||
"subHappTunMode",
|
||||
"subHappTunType",
|
||||
"subHideSettings",
|
||||
"subIncyAnnounceUrl",
|
||||
"subIncyAppAutoDetect",
|
||||
"subIncyBannerBgColor",
|
||||
"subIncyBannerButtonColor",
|
||||
"subIncyBannerButtonText",
|
||||
"subIncyBannerButtonUrl",
|
||||
"subIncyBannerText",
|
||||
"subIncyEnableRouting",
|
||||
"subIncyFragmentInterval",
|
||||
"subIncyFragmentLength",
|
||||
"subIncyFragmentPackets",
|
||||
"subIncyFragmentationEnable",
|
||||
"subIncyHideCheck",
|
||||
"subIncyHideUrl",
|
||||
"subIncyNoLimitEnabled",
|
||||
"subIncyNoisesDelay",
|
||||
"subIncyNoisesEnable",
|
||||
"subIncyNoisesPacket",
|
||||
"subIncyNoisesType",
|
||||
"subIncyPerAppEnable",
|
||||
"subIncyPerAppList",
|
||||
"subIncyPerAppMode",
|
||||
"subIncyPremiumUrl",
|
||||
"subIncyProfileDescription",
|
||||
"subIncyResolveDnsDomain",
|
||||
"subIncyResolveDnsIp",
|
||||
"subIncyResolveEnable",
|
||||
"subIncyRoutingRules",
|
||||
"subIncySortOrder",
|
||||
"subIncySupportEmail",
|
||||
"subInfoNodeEnable",
|
||||
"subJsonAlwaysArray",
|
||||
"subJsonAutoDetect",
|
||||
|
||||
@@ -106,8 +106,36 @@ export interface AllSetting {
|
||||
subHappTunMode: string;
|
||||
subHappTunType: string;
|
||||
subHideSettings: boolean;
|
||||
subIncyAnnounceUrl: string;
|
||||
subIncyAppAutoDetect: boolean;
|
||||
subIncyBannerBgColor: string;
|
||||
subIncyBannerButtonColor: string;
|
||||
subIncyBannerButtonText: string;
|
||||
subIncyBannerButtonUrl: string;
|
||||
subIncyBannerText: string;
|
||||
subIncyEnableRouting: boolean;
|
||||
subIncyFragmentInterval: string;
|
||||
subIncyFragmentLength: string;
|
||||
subIncyFragmentPackets: string;
|
||||
subIncyFragmentationEnable: string;
|
||||
subIncyHideCheck: string;
|
||||
subIncyHideUrl: string;
|
||||
subIncyNoLimitEnabled: string;
|
||||
subIncyNoisesDelay: string;
|
||||
subIncyNoisesEnable: string;
|
||||
subIncyNoisesPacket: string;
|
||||
subIncyNoisesType: string;
|
||||
subIncyPerAppEnable: string;
|
||||
subIncyPerAppList: string;
|
||||
subIncyPerAppMode: string;
|
||||
subIncyPremiumUrl: string;
|
||||
subIncyProfileDescription: string;
|
||||
subIncyResolveDnsDomain: string;
|
||||
subIncyResolveDnsIp: string;
|
||||
subIncyResolveEnable: string;
|
||||
subIncyRoutingRules: string;
|
||||
subIncySortOrder: string;
|
||||
subIncySupportEmail: string;
|
||||
subInfoNodeEnable: boolean;
|
||||
subJsonAlwaysArray: boolean;
|
||||
subJsonAutoDetect: boolean;
|
||||
@@ -266,8 +294,36 @@ export interface AllSettingView {
|
||||
subHappTunMode: string;
|
||||
subHappTunType: string;
|
||||
subHideSettings: boolean;
|
||||
subIncyAnnounceUrl: string;
|
||||
subIncyAppAutoDetect: boolean;
|
||||
subIncyBannerBgColor: string;
|
||||
subIncyBannerButtonColor: string;
|
||||
subIncyBannerButtonText: string;
|
||||
subIncyBannerButtonUrl: string;
|
||||
subIncyBannerText: string;
|
||||
subIncyEnableRouting: boolean;
|
||||
subIncyFragmentInterval: string;
|
||||
subIncyFragmentLength: string;
|
||||
subIncyFragmentPackets: string;
|
||||
subIncyFragmentationEnable: string;
|
||||
subIncyHideCheck: string;
|
||||
subIncyHideUrl: string;
|
||||
subIncyNoLimitEnabled: string;
|
||||
subIncyNoisesDelay: string;
|
||||
subIncyNoisesEnable: string;
|
||||
subIncyNoisesPacket: string;
|
||||
subIncyNoisesType: string;
|
||||
subIncyPerAppEnable: string;
|
||||
subIncyPerAppList: string;
|
||||
subIncyPerAppMode: string;
|
||||
subIncyPremiumUrl: string;
|
||||
subIncyProfileDescription: string;
|
||||
subIncyResolveDnsDomain: string;
|
||||
subIncyResolveDnsIp: string;
|
||||
subIncyResolveEnable: string;
|
||||
subIncyRoutingRules: string;
|
||||
subIncySortOrder: string;
|
||||
subIncySupportEmail: string;
|
||||
subInfoNodeEnable: boolean;
|
||||
subJsonAlwaysArray: boolean;
|
||||
subJsonAutoDetect: boolean;
|
||||
|
||||
@@ -122,8 +122,36 @@ export const AllSettingSchema = z.object({
|
||||
subHappTunMode: z.string(),
|
||||
subHappTunType: z.string(),
|
||||
subHideSettings: z.boolean(),
|
||||
subIncyAnnounceUrl: z.string(),
|
||||
subIncyAppAutoDetect: z.boolean(),
|
||||
subIncyBannerBgColor: z.string(),
|
||||
subIncyBannerButtonColor: z.string(),
|
||||
subIncyBannerButtonText: z.string(),
|
||||
subIncyBannerButtonUrl: z.string(),
|
||||
subIncyBannerText: z.string(),
|
||||
subIncyEnableRouting: z.boolean(),
|
||||
subIncyFragmentInterval: z.string(),
|
||||
subIncyFragmentLength: z.string(),
|
||||
subIncyFragmentPackets: z.string(),
|
||||
subIncyFragmentationEnable: z.string(),
|
||||
subIncyHideCheck: z.string(),
|
||||
subIncyHideUrl: z.string(),
|
||||
subIncyNoLimitEnabled: z.string(),
|
||||
subIncyNoisesDelay: z.string(),
|
||||
subIncyNoisesEnable: z.string(),
|
||||
subIncyNoisesPacket: z.string(),
|
||||
subIncyNoisesType: z.string(),
|
||||
subIncyPerAppEnable: z.string(),
|
||||
subIncyPerAppList: z.string(),
|
||||
subIncyPerAppMode: z.string(),
|
||||
subIncyPremiumUrl: z.string(),
|
||||
subIncyProfileDescription: z.string(),
|
||||
subIncyResolveDnsDomain: z.string(),
|
||||
subIncyResolveDnsIp: z.string(),
|
||||
subIncyResolveEnable: z.string(),
|
||||
subIncyRoutingRules: z.string(),
|
||||
subIncySortOrder: z.string(),
|
||||
subIncySupportEmail: z.string(),
|
||||
subInfoNodeEnable: z.boolean(),
|
||||
subJsonAlwaysArray: z.boolean(),
|
||||
subJsonAutoDetect: z.boolean(),
|
||||
@@ -283,8 +311,36 @@ export const AllSettingViewSchema = z.object({
|
||||
subHappTunMode: z.string(),
|
||||
subHappTunType: z.string(),
|
||||
subHideSettings: z.boolean(),
|
||||
subIncyAnnounceUrl: z.string(),
|
||||
subIncyAppAutoDetect: z.boolean(),
|
||||
subIncyBannerBgColor: z.string(),
|
||||
subIncyBannerButtonColor: z.string(),
|
||||
subIncyBannerButtonText: z.string(),
|
||||
subIncyBannerButtonUrl: z.string(),
|
||||
subIncyBannerText: z.string(),
|
||||
subIncyEnableRouting: z.boolean(),
|
||||
subIncyFragmentInterval: z.string(),
|
||||
subIncyFragmentLength: z.string(),
|
||||
subIncyFragmentPackets: z.string(),
|
||||
subIncyFragmentationEnable: z.string(),
|
||||
subIncyHideCheck: z.string(),
|
||||
subIncyHideUrl: z.string(),
|
||||
subIncyNoLimitEnabled: z.string(),
|
||||
subIncyNoisesDelay: z.string(),
|
||||
subIncyNoisesEnable: z.string(),
|
||||
subIncyNoisesPacket: z.string(),
|
||||
subIncyNoisesType: z.string(),
|
||||
subIncyPerAppEnable: z.string(),
|
||||
subIncyPerAppList: z.string(),
|
||||
subIncyPerAppMode: z.string(),
|
||||
subIncyPremiumUrl: z.string(),
|
||||
subIncyProfileDescription: z.string(),
|
||||
subIncyResolveDnsDomain: z.string(),
|
||||
subIncyResolveDnsIp: z.string(),
|
||||
subIncyResolveEnable: z.string(),
|
||||
subIncyRoutingRules: z.string(),
|
||||
subIncySortOrder: z.string(),
|
||||
subIncySupportEmail: z.string(),
|
||||
subInfoNodeEnable: z.boolean(),
|
||||
subJsonAlwaysArray: z.boolean(),
|
||||
subJsonAutoDetect: z.boolean(),
|
||||
|
||||
@@ -107,6 +107,35 @@ export class AllSetting {
|
||||
subHappPerAppList = '';
|
||||
subHappLocalProxyAuth = 'auto';
|
||||
|
||||
subIncyAppAutoDetect = false;
|
||||
subIncyProfileDescription = '';
|
||||
subIncySortOrder = '';
|
||||
subIncySupportEmail = '';
|
||||
subIncyAnnounceUrl = '';
|
||||
subIncyPremiumUrl = '';
|
||||
subIncyBannerText = '';
|
||||
subIncyBannerButtonText = '';
|
||||
subIncyBannerButtonUrl = '';
|
||||
subIncyBannerBgColor = '';
|
||||
subIncyBannerButtonColor = '';
|
||||
subIncyHideUrl = '';
|
||||
subIncyHideCheck = '';
|
||||
subIncyNoLimitEnabled = '';
|
||||
subIncyPerAppEnable = '';
|
||||
subIncyPerAppMode = '';
|
||||
subIncyPerAppList = '';
|
||||
subIncyFragmentationEnable = '';
|
||||
subIncyFragmentLength = '';
|
||||
subIncyFragmentInterval = '';
|
||||
subIncyFragmentPackets = '';
|
||||
subIncyNoisesEnable = '';
|
||||
subIncyNoisesType = '';
|
||||
subIncyNoisesPacket = '';
|
||||
subIncyNoisesDelay = '';
|
||||
subIncyResolveEnable = '';
|
||||
subIncyResolveDnsDomain = '';
|
||||
subIncyResolveDnsIp = '';
|
||||
|
||||
timeLocation = 'Local';
|
||||
|
||||
ldapEnable = false;
|
||||
|
||||
@@ -0,0 +1,488 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Input, Select, Switch, Tabs } from 'antd';
|
||||
import {
|
||||
AppstoreOutlined,
|
||||
BranchesOutlined,
|
||||
NotificationOutlined,
|
||||
SafetyOutlined,
|
||||
ThunderboltOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import type { AllSetting } from '@/models/setting';
|
||||
import { SettingListItem } from '@/components/ui';
|
||||
import { catTabLabel } from './catTabLabel';
|
||||
|
||||
interface IncySettingsContentProps {
|
||||
allSetting: AllSetting;
|
||||
updateSetting: (patch: Partial<AllSetting>) => void;
|
||||
isMobile: boolean;
|
||||
remoteSourceBadge: (val: string) => React.ReactNode;
|
||||
}
|
||||
|
||||
// Incy documents every switch as `1`/`0`; an empty value omits the header so
|
||||
// the subscriber's own app choice wins.
|
||||
const onOff = (t: (key: string) => string) => [
|
||||
{ value: '', label: t('pages.settings.subIncyNotSet') },
|
||||
{ value: '1', label: t('pages.settings.subIncyOn') },
|
||||
{ value: '0', label: t('pages.settings.subIncyOff') },
|
||||
];
|
||||
|
||||
export default function IncySettingsContent({
|
||||
allSetting,
|
||||
updateSetting,
|
||||
isMobile,
|
||||
remoteSourceBadge,
|
||||
}: IncySettingsContentProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<>
|
||||
<SettingListItem
|
||||
paddings="small"
|
||||
title={t('pages.settings.subIncyAppAutoDetect')}
|
||||
description={t('pages.settings.subIncyAppAutoDetectDesc')}
|
||||
>
|
||||
<Switch
|
||||
checked={allSetting.subIncyAppAutoDetect}
|
||||
onChange={(v) => updateSetting({ subIncyAppAutoDetect: v })}
|
||||
/>
|
||||
</SettingListItem>
|
||||
|
||||
<Tabs
|
||||
type="card"
|
||||
size="small"
|
||||
items={[
|
||||
{
|
||||
key: 'app',
|
||||
label: catTabLabel(<AppstoreOutlined />, t('pages.settings.subIncyGroupApp'), isMobile),
|
||||
children: (
|
||||
<>
|
||||
<SettingListItem
|
||||
paddings="small"
|
||||
title={t('pages.settings.subIncyProfileDescription')}
|
||||
description={t('pages.settings.subIncyProfileDescriptionDesc')}
|
||||
>
|
||||
<Input
|
||||
value={allSetting.subIncyProfileDescription}
|
||||
maxLength={200}
|
||||
onChange={(e) => updateSetting({ subIncyProfileDescription: e.target.value })}
|
||||
/>
|
||||
</SettingListItem>
|
||||
|
||||
<SettingListItem
|
||||
paddings="small"
|
||||
title={t('pages.settings.subIncySortOrder')}
|
||||
description={t('pages.settings.subIncySortOrderDesc')}
|
||||
>
|
||||
<Select
|
||||
value={allSetting.subIncySortOrder}
|
||||
style={{ width: '100%' }}
|
||||
onChange={(v) => updateSetting({ subIncySortOrder: v })}
|
||||
options={[
|
||||
{ value: '', label: t('pages.settings.subIncyNotSet') },
|
||||
{ value: 'none', label: t('pages.settings.subIncySortNone') },
|
||||
{ value: 'ping', label: t('pages.settings.subIncySortPing') },
|
||||
{ value: 'name', label: t('pages.settings.subIncySortName') },
|
||||
]}
|
||||
/>
|
||||
</SettingListItem>
|
||||
|
||||
<SettingListItem
|
||||
paddings="small"
|
||||
title={t('pages.settings.subIncySupportEmail')}
|
||||
description={t('pages.settings.subIncySupportEmailDesc')}
|
||||
>
|
||||
<Input
|
||||
value={allSetting.subIncySupportEmail}
|
||||
placeholder="support@example.com"
|
||||
onChange={(e) => updateSetting({ subIncySupportEmail: e.target.value })}
|
||||
/>
|
||||
</SettingListItem>
|
||||
|
||||
<SettingListItem
|
||||
paddings="small"
|
||||
title={t('pages.settings.subIncyAnnounceUrl')}
|
||||
description={t('pages.settings.subIncyAnnounceUrlDesc')}
|
||||
>
|
||||
<Input
|
||||
value={allSetting.subIncyAnnounceUrl}
|
||||
placeholder="https://t.me/your_channel"
|
||||
onChange={(e) => updateSetting({ subIncyAnnounceUrl: e.target.value })}
|
||||
/>
|
||||
</SettingListItem>
|
||||
|
||||
<SettingListItem
|
||||
paddings="small"
|
||||
title={t('pages.settings.subIncyPremiumUrl')}
|
||||
description={t('pages.settings.subIncyPremiumUrlDesc')}
|
||||
>
|
||||
<Input
|
||||
value={allSetting.subIncyPremiumUrl}
|
||||
placeholder="https://example.com/buy"
|
||||
onChange={(e) => updateSetting({ subIncyPremiumUrl: e.target.value })}
|
||||
/>
|
||||
</SettingListItem>
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'banners',
|
||||
label: catTabLabel(
|
||||
<NotificationOutlined />,
|
||||
t('pages.settings.subIncyGroupBanners'),
|
||||
isMobile,
|
||||
),
|
||||
children: (
|
||||
<>
|
||||
<SettingListItem
|
||||
paddings="small"
|
||||
title={t('pages.settings.subIncyBannerText')}
|
||||
description={t('pages.settings.subIncyBannerTextDesc')}
|
||||
>
|
||||
<Input
|
||||
value={allSetting.subIncyBannerText}
|
||||
onChange={(e) => updateSetting({ subIncyBannerText: e.target.value })}
|
||||
/>
|
||||
</SettingListItem>
|
||||
|
||||
<SettingListItem
|
||||
paddings="small"
|
||||
title={t('pages.settings.subIncyBannerButtonText')}
|
||||
description={t('pages.settings.subIncyBannerButtonTextDesc')}
|
||||
>
|
||||
<Input
|
||||
value={allSetting.subIncyBannerButtonText}
|
||||
onChange={(e) => updateSetting({ subIncyBannerButtonText: e.target.value })}
|
||||
/>
|
||||
</SettingListItem>
|
||||
|
||||
<SettingListItem
|
||||
paddings="small"
|
||||
title={t('pages.settings.subIncyBannerButtonUrl')}
|
||||
description={t('pages.settings.subIncyBannerButtonUrlDesc')}
|
||||
>
|
||||
<Input
|
||||
value={allSetting.subIncyBannerButtonUrl}
|
||||
placeholder="https://example.com/sale"
|
||||
onChange={(e) => updateSetting({ subIncyBannerButtonUrl: e.target.value })}
|
||||
/>
|
||||
</SettingListItem>
|
||||
|
||||
<SettingListItem
|
||||
paddings="small"
|
||||
title={t('pages.settings.subIncyBannerBgColor')}
|
||||
description={t('pages.settings.subIncyBannerBgColorDesc')}
|
||||
>
|
||||
<Input
|
||||
value={allSetting.subIncyBannerBgColor}
|
||||
placeholder="#E53E3E"
|
||||
onChange={(e) => updateSetting({ subIncyBannerBgColor: e.target.value })}
|
||||
/>
|
||||
</SettingListItem>
|
||||
|
||||
<SettingListItem
|
||||
paddings="small"
|
||||
title={t('pages.settings.subIncyBannerButtonColor')}
|
||||
description={t('pages.settings.subIncyBannerButtonColorDesc')}
|
||||
>
|
||||
<Input
|
||||
value={allSetting.subIncyBannerButtonColor}
|
||||
placeholder="#38A169"
|
||||
onChange={(e) => updateSetting({ subIncyBannerButtonColor: e.target.value })}
|
||||
/>
|
||||
</SettingListItem>
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'privacy',
|
||||
label: catTabLabel(
|
||||
<SafetyOutlined />,
|
||||
t('pages.settings.subIncyGroupPrivacy'),
|
||||
isMobile,
|
||||
),
|
||||
children: (
|
||||
<>
|
||||
<SettingListItem
|
||||
paddings="small"
|
||||
title={t('pages.settings.subIncyHideUrl')}
|
||||
description={t('pages.settings.subIncyHideUrlDesc')}
|
||||
>
|
||||
<Select
|
||||
value={allSetting.subIncyHideUrl}
|
||||
style={{ width: '100%' }}
|
||||
onChange={(v) => updateSetting({ subIncyHideUrl: v })}
|
||||
options={onOff(t)}
|
||||
/>
|
||||
</SettingListItem>
|
||||
|
||||
<SettingListItem
|
||||
paddings="small"
|
||||
title={t('pages.settings.subIncyHideCheck')}
|
||||
description={t('pages.settings.subIncyHideCheckDesc')}
|
||||
>
|
||||
<Select
|
||||
value={allSetting.subIncyHideCheck}
|
||||
style={{ width: '100%' }}
|
||||
onChange={(v) => updateSetting({ subIncyHideCheck: v })}
|
||||
options={onOff(t)}
|
||||
/>
|
||||
</SettingListItem>
|
||||
|
||||
<SettingListItem
|
||||
paddings="small"
|
||||
title={t('pages.settings.subIncyNoLimit')}
|
||||
description={t('pages.settings.subIncyNoLimitDesc')}
|
||||
>
|
||||
<Select
|
||||
value={allSetting.subIncyNoLimitEnabled}
|
||||
style={{ width: '100%' }}
|
||||
onChange={(v) => updateSetting({ subIncyNoLimitEnabled: v })}
|
||||
options={onOff(t)}
|
||||
/>
|
||||
</SettingListItem>
|
||||
|
||||
<SettingListItem
|
||||
paddings="small"
|
||||
title={t('pages.settings.subIncyPerAppEnable')}
|
||||
description={t('pages.settings.subIncyPerAppEnableDesc')}
|
||||
>
|
||||
<Select
|
||||
value={allSetting.subIncyPerAppEnable}
|
||||
style={{ width: '100%' }}
|
||||
onChange={(v) => updateSetting({ subIncyPerAppEnable: v })}
|
||||
options={onOff(t)}
|
||||
/>
|
||||
</SettingListItem>
|
||||
|
||||
<SettingListItem
|
||||
paddings="small"
|
||||
title={t('pages.settings.subIncyPerAppMode')}
|
||||
description={t('pages.settings.subIncyPerAppModeDesc')}
|
||||
>
|
||||
<Select
|
||||
value={allSetting.subIncyPerAppMode}
|
||||
style={{ width: '100%' }}
|
||||
onChange={(v) => updateSetting({ subIncyPerAppMode: v })}
|
||||
options={[
|
||||
{ value: '', label: t('pages.settings.subIncyNotSet') },
|
||||
{ value: 'proxy', label: t('pages.settings.subIncyPerAppModeProxy') },
|
||||
{ value: 'bypass', label: t('pages.settings.subIncyPerAppModeBypass') },
|
||||
]}
|
||||
/>
|
||||
</SettingListItem>
|
||||
|
||||
<SettingListItem
|
||||
paddings="small"
|
||||
title={t('pages.settings.subIncyPerAppList')}
|
||||
description={t('pages.settings.subIncyPerAppListDesc')}
|
||||
>
|
||||
<Input.TextArea
|
||||
value={allSetting.subIncyPerAppList}
|
||||
rows={4}
|
||||
placeholder={
|
||||
'com.google.chrome\norg.telegram.messenger\n\nor https://.../apps.txt'
|
||||
}
|
||||
onChange={(e) => updateSetting({ subIncyPerAppList: e.target.value })}
|
||||
/>
|
||||
</SettingListItem>
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'network',
|
||||
label: catTabLabel(
|
||||
<ThunderboltOutlined />,
|
||||
t('pages.settings.subIncyGroupNetwork'),
|
||||
isMobile,
|
||||
),
|
||||
children: (
|
||||
<>
|
||||
<SettingListItem
|
||||
paddings="small"
|
||||
title={t('pages.settings.subIncyFragmentationEnable')}
|
||||
description={t('pages.settings.subIncyFragmentationEnableDesc')}
|
||||
>
|
||||
<Select
|
||||
value={allSetting.subIncyFragmentationEnable}
|
||||
style={{ width: '100%' }}
|
||||
onChange={(v) => updateSetting({ subIncyFragmentationEnable: v })}
|
||||
options={onOff(t)}
|
||||
/>
|
||||
</SettingListItem>
|
||||
|
||||
<SettingListItem
|
||||
paddings="small"
|
||||
title={t('pages.settings.subIncyFragmentLength')}
|
||||
description={t('pages.settings.subIncyFragmentLengthDesc')}
|
||||
>
|
||||
<Input
|
||||
value={allSetting.subIncyFragmentLength}
|
||||
placeholder="10-30"
|
||||
onChange={(e) => updateSetting({ subIncyFragmentLength: e.target.value })}
|
||||
/>
|
||||
</SettingListItem>
|
||||
|
||||
<SettingListItem
|
||||
paddings="small"
|
||||
title={t('pages.settings.subIncyFragmentInterval')}
|
||||
description={t('pages.settings.subIncyFragmentIntervalDesc')}
|
||||
>
|
||||
<Input
|
||||
value={allSetting.subIncyFragmentInterval}
|
||||
placeholder="20-40"
|
||||
onChange={(e) => updateSetting({ subIncyFragmentInterval: e.target.value })}
|
||||
/>
|
||||
</SettingListItem>
|
||||
|
||||
<SettingListItem
|
||||
paddings="small"
|
||||
title={t('pages.settings.subIncyFragmentPackets')}
|
||||
description={t('pages.settings.subIncyFragmentPacketsDesc')}
|
||||
>
|
||||
<Select
|
||||
value={allSetting.subIncyFragmentPackets}
|
||||
style={{ width: '100%' }}
|
||||
onChange={(v) => updateSetting({ subIncyFragmentPackets: v })}
|
||||
options={[
|
||||
{ value: '', label: t('pages.settings.subIncyNotSet') },
|
||||
{ value: 'tlshello', label: 'tlshello' },
|
||||
{ value: '1-3', label: '1-3' },
|
||||
{ value: '1', label: '1' },
|
||||
{ value: 'all', label: 'all' },
|
||||
]}
|
||||
/>
|
||||
</SettingListItem>
|
||||
|
||||
<SettingListItem
|
||||
paddings="small"
|
||||
title={t('pages.settings.subIncyNoisesEnable')}
|
||||
description={t('pages.settings.subIncyNoisesEnableDesc')}
|
||||
>
|
||||
<Select
|
||||
value={allSetting.subIncyNoisesEnable}
|
||||
style={{ width: '100%' }}
|
||||
onChange={(v) => updateSetting({ subIncyNoisesEnable: v })}
|
||||
options={onOff(t)}
|
||||
/>
|
||||
</SettingListItem>
|
||||
|
||||
<SettingListItem
|
||||
paddings="small"
|
||||
title={t('pages.settings.subIncyNoisesType')}
|
||||
description={t('pages.settings.subIncyNoisesTypeDesc')}
|
||||
>
|
||||
<Select
|
||||
value={allSetting.subIncyNoisesType}
|
||||
style={{ width: '100%' }}
|
||||
onChange={(v) => updateSetting({ subIncyNoisesType: v })}
|
||||
options={[
|
||||
{ value: '', label: t('pages.settings.subIncyNotSet') },
|
||||
{ value: 'rand', label: 'rand' },
|
||||
{ value: 'str', label: 'str' },
|
||||
{ value: 'hex', label: 'hex' },
|
||||
]}
|
||||
/>
|
||||
</SettingListItem>
|
||||
|
||||
<SettingListItem
|
||||
paddings="small"
|
||||
title={t('pages.settings.subIncyNoisesPacket')}
|
||||
description={t('pages.settings.subIncyNoisesPacketDesc')}
|
||||
>
|
||||
<Input
|
||||
value={allSetting.subIncyNoisesPacket}
|
||||
placeholder="10-20"
|
||||
onChange={(e) => updateSetting({ subIncyNoisesPacket: e.target.value })}
|
||||
/>
|
||||
</SettingListItem>
|
||||
|
||||
<SettingListItem
|
||||
paddings="small"
|
||||
title={t('pages.settings.subIncyNoisesDelay')}
|
||||
description={t('pages.settings.subIncyNoisesDelayDesc')}
|
||||
>
|
||||
<Input
|
||||
value={allSetting.subIncyNoisesDelay}
|
||||
placeholder="10-50"
|
||||
onChange={(e) => updateSetting({ subIncyNoisesDelay: e.target.value })}
|
||||
/>
|
||||
</SettingListItem>
|
||||
|
||||
<SettingListItem
|
||||
paddings="small"
|
||||
title={t('pages.settings.subIncyResolveEnable')}
|
||||
description={t('pages.settings.subIncyResolveEnableDesc')}
|
||||
>
|
||||
<Select
|
||||
value={allSetting.subIncyResolveEnable}
|
||||
style={{ width: '100%' }}
|
||||
onChange={(v) => updateSetting({ subIncyResolveEnable: v })}
|
||||
options={onOff(t)}
|
||||
/>
|
||||
</SettingListItem>
|
||||
|
||||
<SettingListItem
|
||||
paddings="small"
|
||||
title={t('pages.settings.subIncyResolveDnsDomain')}
|
||||
description={t('pages.settings.subIncyResolveDnsDomainDesc')}
|
||||
>
|
||||
<Input
|
||||
value={allSetting.subIncyResolveDnsDomain}
|
||||
placeholder="https://common.dot.dns.yandex.net/dns-query"
|
||||
onChange={(e) => updateSetting({ subIncyResolveDnsDomain: e.target.value })}
|
||||
/>
|
||||
</SettingListItem>
|
||||
|
||||
<SettingListItem
|
||||
paddings="small"
|
||||
title={t('pages.settings.subIncyResolveDnsIp')}
|
||||
description={t('pages.settings.subIncyResolveDnsIpDesc')}
|
||||
>
|
||||
<Input
|
||||
value={allSetting.subIncyResolveDnsIp}
|
||||
placeholder="77.88.8.8"
|
||||
onChange={(e) => updateSetting({ subIncyResolveDnsIp: e.target.value })}
|
||||
/>
|
||||
</SettingListItem>
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'routing',
|
||||
label: catTabLabel(
|
||||
<BranchesOutlined />,
|
||||
t('pages.settings.subIncyGroupRouting'),
|
||||
isMobile,
|
||||
),
|
||||
children: (
|
||||
<>
|
||||
<SettingListItem
|
||||
paddings="small"
|
||||
title={t('pages.settings.subIncyEnableRouting')}
|
||||
description={t('pages.settings.subIncyEnableRoutingDesc')}
|
||||
>
|
||||
<Switch
|
||||
checked={allSetting.subIncyEnableRouting}
|
||||
onChange={(v) => updateSetting({ subIncyEnableRouting: v })}
|
||||
/>
|
||||
</SettingListItem>
|
||||
<SettingListItem
|
||||
paddings="small"
|
||||
title={t('pages.settings.subIncyRoutingRules')}
|
||||
badge={remoteSourceBadge(allSetting.subIncyRoutingRules)}
|
||||
description={t('pages.settings.subIncyRoutingRulesDesc')}
|
||||
>
|
||||
<Input.TextArea
|
||||
value={allSetting.subIncyRoutingRules}
|
||||
placeholder="incy://routing/onadd/... or https://.../DEFAULT.JSON"
|
||||
onChange={(e) => updateSetting({ subIncyRoutingRules: e.target.value })}
|
||||
/>
|
||||
</SettingListItem>
|
||||
</>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -19,6 +19,7 @@ import { useMediaQuery } from '@/hooks/useMediaQuery';
|
||||
import { catTabLabel } from './catTabLabel';
|
||||
import { sanitizePath, normalizePath } from './uriPath';
|
||||
import HappSettingsContent from './HappSettingsContent';
|
||||
import IncySettingsContent from './IncySettingsContent';
|
||||
import { remoteSourceBadge } from './subscriptionShared';
|
||||
|
||||
interface SubscriptionGeneralTabProps {
|
||||
@@ -455,30 +456,12 @@ export default function SubscriptionGeneralTab({
|
||||
key: '7',
|
||||
label: catTabLabel(<CompassOutlined />, 'Incy', isMobile),
|
||||
children: (
|
||||
<>
|
||||
<SettingListItem
|
||||
paddings="small"
|
||||
title={t('pages.settings.subIncyEnableRouting')}
|
||||
description={t('pages.settings.subIncyEnableRoutingDesc')}
|
||||
>
|
||||
<Switch
|
||||
checked={allSetting.subIncyEnableRouting}
|
||||
onChange={(v) => updateSetting({ subIncyEnableRouting: v })}
|
||||
/>
|
||||
</SettingListItem>
|
||||
<SettingListItem
|
||||
paddings="small"
|
||||
title={t('pages.settings.subIncyRoutingRules')}
|
||||
badge={remoteSourceBadge(allSetting.subIncyRoutingRules)}
|
||||
description={t('pages.settings.subIncyRoutingRulesDesc')}
|
||||
>
|
||||
<Input.TextArea
|
||||
value={allSetting.subIncyRoutingRules}
|
||||
placeholder="incy://routing/onadd/... or https://.../DEFAULT.JSON"
|
||||
onChange={(e) => updateSetting({ subIncyRoutingRules: e.target.value })}
|
||||
/>
|
||||
</SettingListItem>
|
||||
</>
|
||||
<IncySettingsContent
|
||||
allSetting={allSetting}
|
||||
updateSetting={updateSetting}
|
||||
isMobile={isMobile}
|
||||
remoteSourceBadge={remoteSourceBadge}
|
||||
/>
|
||||
),
|
||||
},
|
||||
]}
|
||||
|
||||
@@ -111,6 +111,34 @@ export const AllSettingSchema = z
|
||||
subHappPerAppMode: z.string().optional(),
|
||||
subHappPerAppList: z.string().optional(),
|
||||
subHappLocalProxyAuth: z.string().optional(),
|
||||
subIncyAppAutoDetect: z.boolean().optional(),
|
||||
subIncyProfileDescription: z.string().optional(),
|
||||
subIncySortOrder: z.string().optional(),
|
||||
subIncySupportEmail: z.string().optional(),
|
||||
subIncyAnnounceUrl: z.string().optional(),
|
||||
subIncyPremiumUrl: z.string().optional(),
|
||||
subIncyBannerText: z.string().optional(),
|
||||
subIncyBannerButtonText: z.string().optional(),
|
||||
subIncyBannerButtonUrl: z.string().optional(),
|
||||
subIncyBannerBgColor: z.string().optional(),
|
||||
subIncyBannerButtonColor: z.string().optional(),
|
||||
subIncyHideUrl: z.string().optional(),
|
||||
subIncyHideCheck: z.string().optional(),
|
||||
subIncyNoLimitEnabled: z.string().optional(),
|
||||
subIncyPerAppEnable: z.string().optional(),
|
||||
subIncyPerAppMode: z.string().optional(),
|
||||
subIncyPerAppList: z.string().optional(),
|
||||
subIncyFragmentationEnable: z.string().optional(),
|
||||
subIncyFragmentLength: z.string().optional(),
|
||||
subIncyFragmentInterval: z.string().optional(),
|
||||
subIncyFragmentPackets: z.string().optional(),
|
||||
subIncyNoisesEnable: z.string().optional(),
|
||||
subIncyNoisesType: z.string().optional(),
|
||||
subIncyNoisesPacket: z.string().optional(),
|
||||
subIncyNoisesDelay: z.string().optional(),
|
||||
subIncyResolveEnable: z.string().optional(),
|
||||
subIncyResolveDnsDomain: z.string().optional(),
|
||||
subIncyResolveDnsIp: z.string().optional(),
|
||||
timeLocation: z.string().optional(),
|
||||
ldapEnable: z.boolean().optional(),
|
||||
ldapHost: z.string().optional(),
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { fireEvent } from '@testing-library/react';
|
||||
|
||||
import IncySettingsContent from '@/pages/settings/IncySettingsContent';
|
||||
import { AllSetting } from '@/models/setting';
|
||||
|
||||
import { renderWithProviders } from './test-utils';
|
||||
|
||||
function openTab(name: string) {
|
||||
const tab = Array.from(document.querySelectorAll('.ant-tabs-tab')).find((t) =>
|
||||
(t.textContent ?? '').includes(name),
|
||||
);
|
||||
if (!tab) throw new Error(`tab '${name}' not found`);
|
||||
fireEvent.click(tab);
|
||||
}
|
||||
|
||||
function settingRow(title: string): HTMLElement {
|
||||
const row = Array.from(document.querySelectorAll('li,div[class*="setting"]')).find((r) =>
|
||||
(r.textContent ?? '').includes(title),
|
||||
);
|
||||
if (!row) throw new Error(`setting row '${title}' not found`);
|
||||
return row as HTMLElement;
|
||||
}
|
||||
|
||||
function selectFor(title: string): HTMLElement {
|
||||
const select = settingRow(title).querySelector('.ant-select');
|
||||
if (!select) throw new Error(`select for '${title}' not found`);
|
||||
return select as HTMLElement;
|
||||
}
|
||||
|
||||
function selectOption(title: string, option: string) {
|
||||
const select = selectFor(title);
|
||||
fireEvent.mouseDown(select.querySelector('.ant-select-selector') ?? select);
|
||||
const match = Array.from(document.querySelectorAll('.ant-select-item-option')).find(
|
||||
(o) => (o.textContent ?? '').trim() === option,
|
||||
);
|
||||
if (!match) throw new Error(`option '${option}' for '${title}' not found`);
|
||||
fireEvent.click(match);
|
||||
}
|
||||
|
||||
function render(subIncy: Partial<AllSetting> = {}) {
|
||||
const updateSetting = vi.fn();
|
||||
const allSetting = Object.assign(new AllSetting(), subIncy);
|
||||
renderWithProviders(
|
||||
<IncySettingsContent
|
||||
allSetting={allSetting}
|
||||
updateSetting={updateSetting}
|
||||
isMobile={false}
|
||||
remoteSourceBadge={() => null}
|
||||
/>,
|
||||
);
|
||||
return updateSetting;
|
||||
}
|
||||
|
||||
describe('Incy app-management settings', () => {
|
||||
// Every Incy switch and enum is tri-state: "Not set" must store the empty
|
||||
// value so the panel sends no header and the subscriber's app choice wins.
|
||||
it('stores the empty value for "Not set" so no header is emitted', () => {
|
||||
const updateSetting = render({ subIncySortOrder: 'ping' });
|
||||
|
||||
openTab('App');
|
||||
selectOption('Server sort order', 'Not set');
|
||||
|
||||
expect(updateSetting).toHaveBeenCalledWith({ subIncySortOrder: '' });
|
||||
});
|
||||
|
||||
it('sends the documented per-app literal, not the Happ on/include spelling', () => {
|
||||
const updateSetting = render();
|
||||
|
||||
openTab('Privacy');
|
||||
selectOption('Per-app mode (Android)', 'On');
|
||||
|
||||
expect(updateSetting).toHaveBeenCalledWith({ subIncyPerAppEnable: '1' });
|
||||
});
|
||||
|
||||
it('offers bypass and proxy for the per-app mode', () => {
|
||||
const updateSetting = render();
|
||||
|
||||
openTab('Privacy');
|
||||
selectOption('Per-app mode type', 'Listed apps bypass');
|
||||
|
||||
expect(updateSetting).toHaveBeenCalledWith({ subIncyPerAppMode: 'bypass' });
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user