mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-06-05 11:59:35 +00:00
feat(Clash): Add routing rules and enable routing option for Clash subscriptions (#4904)
* feat(clash): add routing rules and enable routing option for Clash/Mihomo subscriptions Allows adding custom YAML blocks and placeholders to Clash exports. Why: Shifting routing to the client prevents server IP exposure for DIRECT traffic and reduces unnecessary server bandwidth/CPU usage. * fix --------- Co-authored-by: Misfit-s <>
This commit is contained in:
@@ -34,7 +34,9 @@ export interface AllSetting {
|
||||
subAnnounce: string;
|
||||
subCertFile: string;
|
||||
subClashEnable: boolean;
|
||||
subClashEnableRouting: boolean;
|
||||
subClashPath: string;
|
||||
subClashRules: string;
|
||||
subClashURI: string;
|
||||
subDomain: string;
|
||||
subEmailInRemark: boolean;
|
||||
@@ -121,7 +123,9 @@ export interface AllSettingView {
|
||||
subAnnounce: string;
|
||||
subCertFile: string;
|
||||
subClashEnable: boolean;
|
||||
subClashEnableRouting: boolean;
|
||||
subClashPath: string;
|
||||
subClashRules: string;
|
||||
subClashURI: string;
|
||||
subDomain: string;
|
||||
subEmailInRemark: boolean;
|
||||
|
||||
@@ -36,7 +36,9 @@ export const AllSettingSchema = z.object({
|
||||
subAnnounce: z.string(),
|
||||
subCertFile: z.string(),
|
||||
subClashEnable: z.boolean(),
|
||||
subClashEnableRouting: z.boolean(),
|
||||
subClashPath: z.string(),
|
||||
subClashRules: z.string(),
|
||||
subClashURI: z.string(),
|
||||
subDomain: z.string(),
|
||||
subEmailInRemark: z.boolean(),
|
||||
@@ -124,7 +126,9 @@ export const AllSettingViewSchema = z.object({
|
||||
subAnnounce: z.string(),
|
||||
subCertFile: z.string(),
|
||||
subClashEnable: z.boolean(),
|
||||
subClashEnableRouting: z.boolean(),
|
||||
subClashPath: z.string(),
|
||||
subClashRules: z.string(),
|
||||
subClashURI: z.string(),
|
||||
subDomain: z.string(),
|
||||
subEmailInRemark: z.boolean(),
|
||||
|
||||
@@ -55,6 +55,8 @@ export class AllSetting {
|
||||
subURI = '';
|
||||
subJsonURI = '';
|
||||
subClashURI = '';
|
||||
subClashEnableRouting = false;
|
||||
subClashRules = '';
|
||||
subJsonFragment = '';
|
||||
subJsonNoises = '';
|
||||
subJsonMux = '';
|
||||
|
||||
@@ -1114,7 +1114,7 @@ export const sections: readonly Section[] = [
|
||||
{
|
||||
method: 'GET',
|
||||
path: '/{clashPath}:subid',
|
||||
summary: 'Return subscription as a Clash/Mihomo-compatible YAML config. Only when Clash subscription is enabled in settings. Default path: /clash/:subid.',
|
||||
summary: 'Return subscription as a Clash/Mihomo-compatible YAML config, including configured global Clash routing rules. Only when Clash subscription is enabled in settings. Default path: /clash/:subid.',
|
||||
params: [
|
||||
{ name: 'subid', in: 'path', type: 'string', desc: 'Client subscription ID.' },
|
||||
],
|
||||
|
||||
@@ -166,6 +166,20 @@ export default function SubscriptionGeneralTab({ allSetting, updateSetting }: Su
|
||||
<Input.TextArea value={allSetting.subRoutingRules} placeholder="happ://routing/add/..."
|
||||
onChange={(e) => updateSetting({ subRoutingRules: e.target.value })} />
|
||||
</SettingListItem>
|
||||
|
||||
<Divider>Clash / Mihomo</Divider>
|
||||
|
||||
<SettingListItem paddings="small" title={t('pages.settings.subClashEnableRouting')} description={t('pages.settings.subClashEnableRoutingDesc')}>
|
||||
<Switch checked={allSetting.subClashEnableRouting} onChange={(v) => updateSetting({ subClashEnableRouting: v })} />
|
||||
</SettingListItem>
|
||||
<SettingListItem paddings="small" title={t('pages.settings.subClashRoutingRules')} description={t('pages.settings.subClashRoutingRulesDesc')}>
|
||||
<Input.TextArea
|
||||
value={allSetting.subClashRules}
|
||||
rows={8}
|
||||
placeholder={'GEOSITE,category-ir,DIRECT\nGEOIP,private,DIRECT'}
|
||||
onChange={(e) => updateSetting({ subClashRules: e.target.value })}
|
||||
/>
|
||||
</SettingListItem>
|
||||
</>
|
||||
),
|
||||
},
|
||||
|
||||
@@ -59,6 +59,8 @@ export const AllSettingSchema = z.object({
|
||||
subURI: z.string().optional(),
|
||||
subJsonURI: z.string().optional(),
|
||||
subClashURI: z.string().optional(),
|
||||
subClashEnableRouting: z.boolean().optional(),
|
||||
subClashRules: z.string().optional(),
|
||||
subJsonFragment: z.string().optional(),
|
||||
subJsonNoises: z.string().optional(),
|
||||
subJsonMux: z.string().optional(),
|
||||
|
||||
12
sub/sub.go
12
sub/sub.go
@@ -140,6 +140,16 @@ func (s *Server) initRouter() (*gin.Engine, error) {
|
||||
SubJsonRules = ""
|
||||
}
|
||||
|
||||
SubClashEnableRouting, err := s.settingService.GetSubClashEnableRouting()
|
||||
if err != nil {
|
||||
SubClashEnableRouting = false
|
||||
}
|
||||
|
||||
SubClashRules, err := s.settingService.GetSubClashRules()
|
||||
if err != nil {
|
||||
SubClashRules = ""
|
||||
}
|
||||
|
||||
SubTitle, err := s.settingService.GetSubTitle()
|
||||
if err != nil {
|
||||
SubTitle = ""
|
||||
@@ -226,7 +236,7 @@ func (s *Server) initRouter() (*gin.Engine, error) {
|
||||
|
||||
s.sub = NewSUBController(
|
||||
g, LinksPath, JsonPath, ClashPath, subJsonEnable, subClashEnable, Encrypt, ShowInfo, RemarkModel, SubUpdates,
|
||||
SubJsonFragment, SubJsonNoises, SubJsonMux, SubJsonRules, SubTitle, SubSupportUrl,
|
||||
SubJsonFragment, SubJsonNoises, SubJsonMux, SubJsonRules, SubClashEnableRouting, SubClashRules, SubTitle, SubSupportUrl,
|
||||
SubProfileUrl, SubAnnounce, SubEnableRouting, SubRoutingRules)
|
||||
|
||||
return engine, nil
|
||||
|
||||
@@ -15,17 +15,13 @@ import (
|
||||
|
||||
type SubClashService struct {
|
||||
inboundService service.InboundService
|
||||
enableRouting bool
|
||||
clashRules string
|
||||
SubService *SubService
|
||||
}
|
||||
|
||||
type ClashConfig struct {
|
||||
Proxies []map[string]any `yaml:"proxies"`
|
||||
ProxyGroups []map[string]any `yaml:"proxy-groups"`
|
||||
Rules []string `yaml:"rules"`
|
||||
}
|
||||
|
||||
func NewSubClashService(subService *SubService) *SubClashService {
|
||||
return &SubClashService{SubService: subService}
|
||||
func NewSubClashService(enableRouting bool, clashRules string, subService *SubService) *SubClashService {
|
||||
return &SubClashService{enableRouting: enableRouting, clashRules: clashRules, SubService: subService}
|
||||
}
|
||||
|
||||
func (s *SubClashService) GetClash(subId string, host string) (string, string, error) {
|
||||
@@ -76,14 +72,20 @@ func (s *SubClashService) GetClash(subId string, host string) (string, string, e
|
||||
}
|
||||
proxyNames = append(proxyNames, "DIRECT")
|
||||
|
||||
config := ClashConfig{
|
||||
Proxies: proxies,
|
||||
ProxyGroups: []map[string]any{{
|
||||
config := map[string]any{
|
||||
"proxies": proxies,
|
||||
"proxy-groups": []map[string]any{{
|
||||
"name": "PROXY",
|
||||
"type": "select",
|
||||
"proxies": proxyNames,
|
||||
}},
|
||||
Rules: []string{"MATCH,PROXY"},
|
||||
"rules": []string{"MATCH,PROXY"},
|
||||
}
|
||||
|
||||
if s.enableRouting {
|
||||
if err := mergeClashRulesYAML(config, s.clashRules); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
}
|
||||
|
||||
finalYAML, err := yaml.Marshal(config)
|
||||
@@ -554,3 +556,96 @@ func cloneMap(src map[string]any) map[string]any {
|
||||
maps.Copy(dst, src)
|
||||
return dst
|
||||
}
|
||||
|
||||
func mergeClashRulesYAML(base map[string]any, raw string) error {
|
||||
raw = strings.TrimSpace(raw)
|
||||
if raw == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
var custom any
|
||||
if err := yaml.Unmarshal([]byte(raw), &custom); err != nil {
|
||||
mergeClashRules(base, linesToClashRules(raw))
|
||||
return nil
|
||||
}
|
||||
|
||||
switch typed := custom.(type) {
|
||||
case []any:
|
||||
mergeClashRules(base, typed)
|
||||
case map[string]any:
|
||||
if rules, ok := typed["rules"]; ok {
|
||||
if ruleList, ok := asAnySlice(rules); ok {
|
||||
mergeClashRules(base, ruleList)
|
||||
}
|
||||
}
|
||||
default:
|
||||
mergeClashRules(base, linesToClashRules(raw))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func mergeClashRules(base map[string]any, customRules []any) {
|
||||
if len(customRules) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
baseRules, _ := asAnySlice(base["rules"])
|
||||
if hasClashMatchRule(customRules) {
|
||||
base["rules"] = customRules
|
||||
return
|
||||
}
|
||||
|
||||
merged := make([]any, 0, len(customRules)+len(baseRules))
|
||||
merged = append(merged, customRules...)
|
||||
merged = append(merged, baseRules...)
|
||||
base["rules"] = merged
|
||||
}
|
||||
|
||||
func asAnySlice(value any) ([]any, bool) {
|
||||
switch typed := value.(type) {
|
||||
case []any:
|
||||
return typed, true
|
||||
case []string:
|
||||
out := make([]any, 0, len(typed))
|
||||
for _, item := range typed {
|
||||
out = append(out, item)
|
||||
}
|
||||
return out, true
|
||||
case []map[string]any:
|
||||
out := make([]any, 0, len(typed))
|
||||
for _, item := range typed {
|
||||
out = append(out, item)
|
||||
}
|
||||
return out, true
|
||||
default:
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
|
||||
func hasClashMatchRule(rules []any) bool {
|
||||
for _, rule := range rules {
|
||||
ruleText, ok := rule.(string)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
parts := strings.SplitN(ruleText, ",", 2)
|
||||
if strings.EqualFold(strings.TrimSpace(parts[0]), "MATCH") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func linesToClashRules(raw string) []any {
|
||||
lines := strings.Split(raw, "\n")
|
||||
rules := make([]any, 0, len(lines))
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" || strings.HasPrefix(line, "#") {
|
||||
continue
|
||||
}
|
||||
rules = append(rules, line)
|
||||
}
|
||||
return rules
|
||||
}
|
||||
|
||||
@@ -66,6 +66,8 @@ func NewSUBController(
|
||||
jsonNoise string,
|
||||
jsonMux string,
|
||||
jsonRules string,
|
||||
clashEnableRouting bool,
|
||||
clashRules string,
|
||||
subTitle string,
|
||||
subSupportUrl string,
|
||||
subProfileUrl string,
|
||||
@@ -91,7 +93,7 @@ func NewSUBController(
|
||||
|
||||
subService: sub,
|
||||
subJsonService: NewSubJsonService(jsonFragment, jsonNoise, jsonMux, jsonRules, sub),
|
||||
subClashService: NewSubClashService(sub),
|
||||
subClashService: NewSubClashService(clashEnableRouting, clashRules, sub),
|
||||
}
|
||||
a.initRouter(g)
|
||||
return a
|
||||
|
||||
@@ -83,6 +83,8 @@ type AllSetting struct {
|
||||
SubClashEnable bool `json:"subClashEnable" form:"subClashEnable"` // Enable Clash/Mihomo subscription endpoint
|
||||
SubClashPath string `json:"subClashPath" form:"subClashPath"` // Path for Clash/Mihomo subscription endpoint
|
||||
SubClashURI string `json:"subClashURI" form:"subClashURI"` // Clash/Mihomo subscription server URI
|
||||
SubClashEnableRouting bool `json:"subClashEnableRouting" form:"subClashEnableRouting"` // Enable global routing rules for Clash/Mihomo
|
||||
SubClashRules string `json:"subClashRules" form:"subClashRules"` // Clash/Mihomo global routing rules
|
||||
SubJsonFragment string `json:"subJsonFragment" form:"subJsonFragment"` // JSON subscription fragment configuration
|
||||
SubJsonNoises string `json:"subJsonNoises" form:"subJsonNoises"` // JSON subscription noise configuration
|
||||
SubJsonMux string `json:"subJsonMux" form:"subJsonMux"` // JSON subscription mux configuration
|
||||
|
||||
@@ -79,6 +79,8 @@ var defaultValueMap = map[string]string{
|
||||
"subClashEnable": "false",
|
||||
"subClashPath": "/clash/",
|
||||
"subClashURI": "",
|
||||
"subClashEnableRouting": "false",
|
||||
"subClashRules": "",
|
||||
"subJsonFragment": "",
|
||||
"subJsonNoises": "",
|
||||
"subJsonMux": "",
|
||||
@@ -658,6 +660,14 @@ func (s *SettingService) GetSubClashURI() (string, error) {
|
||||
return s.getString("subClashURI")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetSubClashEnableRouting() (bool, error) {
|
||||
return s.getBool("subClashEnableRouting")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetSubClashRules() (string, error) {
|
||||
return s.getString("subClashRules")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetSubJsonFragment() (string, error) {
|
||||
return s.getString("subJsonFragment")
|
||||
}
|
||||
|
||||
@@ -1004,6 +1004,10 @@
|
||||
"subEnableRoutingDesc": "إعداد عام لتمكين التوجيه (Routing) في عميل VPN. (فقط لـ Happ)",
|
||||
"subRoutingRules": "قواعد التوجيه",
|
||||
"subRoutingRulesDesc": "قواعد التوجيه العامة لعميل VPN. (فقط لـ Happ)",
|
||||
"subClashEnableRouting": "تفعيل التوجيه",
|
||||
"subClashEnableRoutingDesc": "تضمين قواعد توجيه Clash/Mihomo العامة في اشتراكات YAML المُنشأة.",
|
||||
"subClashRoutingRules": "قواعد التوجيه العامة",
|
||||
"subClashRoutingRulesDesc": "قواعد Clash/Mihomo التي تُضاف في بداية كل اشتراك YAML قبل MATCH,PROXY.",
|
||||
"subListen": "IP الاستماع",
|
||||
"subListenDesc": "عنوان IP لخدمة الاشتراك. (سيبه فاضي عشان يستمع على كل الـ IPs)",
|
||||
"subPort": "بورت الاستماع",
|
||||
|
||||
@@ -1004,6 +1004,10 @@
|
||||
"subEnableRoutingDesc": "Global setting to enable routing in the VPN client. (Only for Happ)",
|
||||
"subRoutingRules": "Routing rules",
|
||||
"subRoutingRulesDesc": "Global routing rules for the VPN client. (Only for Happ)",
|
||||
"subClashEnableRouting": "Enable routing",
|
||||
"subClashEnableRoutingDesc": "Include global Clash/Mihomo routing rules in generated YAML subscriptions.",
|
||||
"subClashRoutingRules": "Global routing rules",
|
||||
"subClashRoutingRulesDesc": "Default Clash/Mihomo rules prepended to every generated YAML subscription before MATCH,PROXY.",
|
||||
"subListen": "Listen IP",
|
||||
"subListenDesc": "The IP address for the subscription service. (leave blank to listen on all IPs)",
|
||||
"subPort": "Listen Port",
|
||||
|
||||
@@ -1004,6 +1004,10 @@
|
||||
"subEnableRoutingDesc": "Configuración global para habilitar el enrutamiento en el cliente VPN. (Solo para Happ)",
|
||||
"subRoutingRules": "Reglas de enrutamiento",
|
||||
"subRoutingRulesDesc": "Reglas de enrutamiento globales para el cliente VPN. (Solo para Happ)",
|
||||
"subClashEnableRouting": "Habilitar enrutamiento",
|
||||
"subClashEnableRoutingDesc": "Incluir reglas globales de enrutamiento Clash/Mihomo en las suscripciones YAML generadas.",
|
||||
"subClashRoutingRules": "Reglas globales de enrutamiento",
|
||||
"subClashRoutingRulesDesc": "Reglas Clash/Mihomo agregadas al inicio de cada suscripción YAML antes de MATCH,PROXY.",
|
||||
"subListen": "Listening IP",
|
||||
"subListenDesc": "Dejar en blanco por defecto para monitorear todas las IPs.",
|
||||
"subPort": "Puerto de Suscripción",
|
||||
|
||||
@@ -1004,6 +1004,10 @@
|
||||
"subEnableRoutingDesc": "تنظیمات سراسری برای فعالسازی مسیریابی در کلاینت VPN. (فقط برای Happ)",
|
||||
"subRoutingRules": "قوانین مسیریابی",
|
||||
"subRoutingRulesDesc": "قوانین مسیریابی سراسری برای کلاینت VPN. (فقط برای Happ)",
|
||||
"subClashEnableRouting": "فعالسازی مسیریابی",
|
||||
"subClashEnableRoutingDesc": "قوانین مسیریابی سراسری Clash/Mihomo را در اشتراکهای YAML تولیدشده وارد کن.",
|
||||
"subClashRoutingRules": "قوانین مسیریابی سراسری",
|
||||
"subClashRoutingRulesDesc": "قوانین Clash/Mihomo که پیش از MATCH,PROXY به ابتدای هر اشتراک YAML افزوده میشوند.",
|
||||
"subListen": "آدرس آیپی",
|
||||
"subListenDesc": "آدرس آیپی برای سرویس سابسکریپشن. برای گوش دادن بهتمام آیپیها خالیبگذارید",
|
||||
"subPort": "پورت",
|
||||
|
||||
@@ -1004,6 +1004,10 @@
|
||||
"subEnableRoutingDesc": "Pengaturan global untuk mengaktifkan perutean (routing) di klien VPN. (Hanya untuk Happ)",
|
||||
"subRoutingRules": "Aturan routing",
|
||||
"subRoutingRulesDesc": "Aturan routing global untuk klien VPN. (Hanya untuk Happ)",
|
||||
"subClashEnableRouting": "Aktifkan routing",
|
||||
"subClashEnableRoutingDesc": "Sertakan aturan routing global Clash/Mihomo dalam langganan YAML yang dibuat.",
|
||||
"subClashRoutingRules": "Aturan routing global",
|
||||
"subClashRoutingRulesDesc": "Aturan Clash/Mihomo yang ditambahkan di awal setiap langganan YAML sebelum MATCH,PROXY.",
|
||||
"subListen": "IP Pendengar",
|
||||
"subListenDesc": "Alamat IP untuk layanan langganan. (biarkan kosong untuk mendengarkan semua IP)",
|
||||
"subPort": "Port Pendengar",
|
||||
|
||||
@@ -1004,6 +1004,10 @@
|
||||
"subEnableRoutingDesc": "VPNクライアントでルーティングを有効にするためのグローバル設定。(Happのみ)",
|
||||
"subRoutingRules": "ルーティングルール",
|
||||
"subRoutingRulesDesc": "VPNクライアントのグローバルルーティングルール。(Happのみ)",
|
||||
"subClashEnableRouting": "ルーティングを有効化",
|
||||
"subClashEnableRoutingDesc": "生成されたYAMLサブスクリプションにClash/Mihomoのグローバルルーティングルールを含めます。",
|
||||
"subClashRoutingRules": "グローバルルーティングルール",
|
||||
"subClashRoutingRulesDesc": "各YAMLサブスクリプションのMATCH,PROXYより前に追加されるClash/Mihomoルール。",
|
||||
"subListen": "監視IP",
|
||||
"subListenDesc": "サブスクリプションサービスが監視するIPアドレス(空白にするとすべてのIPを監視)",
|
||||
"subPort": "監視ポート",
|
||||
|
||||
@@ -1004,6 +1004,10 @@
|
||||
"subEnableRoutingDesc": "Configuração global para habilitar o roteamento no cliente VPN. (Apenas para Happ)",
|
||||
"subRoutingRules": "Regras de roteamento",
|
||||
"subRoutingRulesDesc": "Regras de roteamento globais para o cliente VPN. (Apenas para Happ)",
|
||||
"subClashEnableRouting": "Ativar roteamento",
|
||||
"subClashEnableRoutingDesc": "Incluir regras globais de roteamento Clash/Mihomo nas assinaturas YAML geradas.",
|
||||
"subClashRoutingRules": "Regras globais de roteamento",
|
||||
"subClashRoutingRulesDesc": "Regras Clash/Mihomo adicionadas ao início de cada assinatura YAML antes de MATCH,PROXY.",
|
||||
"subListen": "IP de Escuta",
|
||||
"subListenDesc": "O endereço IP para o serviço de assinatura. (deixe em branco para escutar em todos os IPs)",
|
||||
"subPort": "Porta de Escuta",
|
||||
|
||||
@@ -1004,6 +1004,10 @@
|
||||
"subEnableRoutingDesc": "Глобальная настройка для включения маршрутизации в VPN-клиенте. (Только для Happ)",
|
||||
"subRoutingRules": "Правила маршрутизации",
|
||||
"subRoutingRulesDesc": "Глобальные правила маршрутизации для VPN-клиента. (Только для Happ)",
|
||||
"subClashEnableRouting": "Включить маршрутизацию",
|
||||
"subClashEnableRoutingDesc": "Добавлять глобальные правила маршрутизации Clash/Mihomo в сгенерированные YAML-подписки.",
|
||||
"subClashRoutingRules": "Глобальные правила маршрутизации",
|
||||
"subClashRoutingRulesDesc": "Правила Clash/Mihomo, добавляемые в начало каждой YAML-подписки перед MATCH,PROXY.",
|
||||
"subListen": "Прослушивание IP",
|
||||
"subListenDesc": "Оставьте пустым по умолчанию, чтобы отслеживать все IP-адреса",
|
||||
"subPort": "Порт подписки",
|
||||
|
||||
@@ -1004,6 +1004,10 @@
|
||||
"subEnableRoutingDesc": "VPN istemcisinde yönlendirmeyi etkinleştirmek için genel ayar. (Yalnızca Happ için)",
|
||||
"subRoutingRules": "Yönlendirme kuralları",
|
||||
"subRoutingRulesDesc": "VPN istemcisi için genel yönlendirme kuralları. (Yalnızca Happ için)",
|
||||
"subClashEnableRouting": "Yönlendirmeyi etkinleştir",
|
||||
"subClashEnableRoutingDesc": "Oluşturulan YAML aboneliklerine genel Clash/Mihomo yönlendirme kurallarını ekle.",
|
||||
"subClashRoutingRules": "Genel yönlendirme kuralları",
|
||||
"subClashRoutingRulesDesc": "Her YAML aboneliğinin başına MATCH,PROXY öncesinde eklenen Clash/Mihomo kuralları.",
|
||||
"subListen": "Dinleme IP",
|
||||
"subListenDesc": "Abonelik hizmeti için IP adresi. (tüm IP'leri dinlemek için boş bırakın)",
|
||||
"subPort": "Dinleme Portu",
|
||||
|
||||
@@ -1004,6 +1004,10 @@
|
||||
"subEnableRoutingDesc": "Глобальне налаштування для увімкнення маршрутизації у VPN-клієнті. (Тільки для Happ)",
|
||||
"subRoutingRules": "Правила маршрутизації",
|
||||
"subRoutingRulesDesc": "Глобальні правила маршрутизації для VPN-клієнта. (Тільки для Happ)",
|
||||
"subClashEnableRouting": "Увімкнути маршрутизацію",
|
||||
"subClashEnableRoutingDesc": "Додавати глобальні правила маршрутизації Clash/Mihomo до згенерованих YAML-підписок.",
|
||||
"subClashRoutingRules": "Глобальні правила маршрутизації",
|
||||
"subClashRoutingRulesDesc": "Правила Clash/Mihomo, що додаються на початок кожної YAML-підписки перед MATCH,PROXY.",
|
||||
"subListen": "Слухати IP",
|
||||
"subListenDesc": "IP-адреса для служби підписки. (залиште порожнім, щоб слухати всі IP-адреси)",
|
||||
"subPort": "Слухати порт",
|
||||
|
||||
@@ -1004,6 +1004,10 @@
|
||||
"subEnableRoutingDesc": "Cài đặt toàn cục để bật định tuyến trong ứng dụng khách VPN. (Chỉ dành cho Happ)",
|
||||
"subRoutingRules": "Quy tắc định tuyến",
|
||||
"subRoutingRulesDesc": "Quy tắc định tuyến toàn cầu cho client VPN. (Chỉ dành cho Happ)",
|
||||
"subClashEnableRouting": "Bật định tuyến",
|
||||
"subClashEnableRoutingDesc": "Bao gồm quy tắc định tuyến Clash/Mihomo toàn cầu trong các đăng ký YAML được tạo.",
|
||||
"subClashRoutingRules": "Quy tắc định tuyến toàn cầu",
|
||||
"subClashRoutingRulesDesc": "Quy tắc Clash/Mihomo được thêm vào đầu mỗi đăng ký YAML trước MATCH,PROXY.",
|
||||
"subListen": "Listening IP",
|
||||
"subListenDesc": "Mặc định để trống để nghe tất cả các IP",
|
||||
"subPort": "Cổng gói đăng ký",
|
||||
|
||||
@@ -1004,6 +1004,10 @@
|
||||
"subEnableRoutingDesc": "在 VPN 客户端中启用路由的全局设置。(僅限 Happ)",
|
||||
"subRoutingRules": "路由規則",
|
||||
"subRoutingRulesDesc": "VPN 用戶端的全域路由規則。(僅限 Happ)",
|
||||
"subClashEnableRouting": "启用路由",
|
||||
"subClashEnableRoutingDesc": "在生成的 YAML 订阅中包含 Clash/Mihomo 全局路由规则。",
|
||||
"subClashRoutingRules": "全局路由规则",
|
||||
"subClashRoutingRulesDesc": "添加到每个 YAML 订阅开头、MATCH,PROXY 之前的 Clash/Mihomo 规则。",
|
||||
"subListen": "监听 IP",
|
||||
"subListenDesc": "订阅服务监听的 IP 地址(留空表示监听所有 IP)",
|
||||
"subPort": "监听端口",
|
||||
|
||||
@@ -1004,6 +1004,10 @@
|
||||
"subEnableRoutingDesc": "在 VPN 用戶端中啟用路由的全域設定。(僅限 Happ)",
|
||||
"subRoutingRules": "路由規則",
|
||||
"subRoutingRulesDesc": "VPN 用戶端的全域路由規則。(僅限 Happ)",
|
||||
"subClashEnableRouting": "啟用路由",
|
||||
"subClashEnableRoutingDesc": "在產生的 YAML 訂閱中包含 Clash/Mihomo 全域路由規則。",
|
||||
"subClashRoutingRules": "全域路由規則",
|
||||
"subClashRoutingRulesDesc": "加入到每個 YAML 訂閱開頭、MATCH,PROXY 之前的 Clash/Mihomo 規則。",
|
||||
"subListen": "監聽 IP",
|
||||
"subListenDesc": "訂閱服務監聽的 IP 地址(留空表示監聽所有 IP)",
|
||||
"subPort": "監聽埠",
|
||||
|
||||
Reference in New Issue
Block a user