Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/MHSanaei/3x-ui.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorzhuzn <haimu0427@Outlook.com>2026-04-19 23:26:13 +0300
committerGitHub <noreply@github.com>2026-04-19 23:26:13 +0300
commitd580086361036f87af843d0f7386bdc54736720a (patch)
treef22944b1ab1ea8c29858d796cc248b7b16436427 /web/html/settings/panel
parent1e3b366fba3054698d55e36891d022a513e5a942 (diff)
feat add clash yaml convert (#3916)
* docs(agents): add AI agent guidance documentation * feat(sub): add Clash/Mihomo YAML subscription service Add SubClashService to convert subscription links to Clash/Mihomo YAML format for direct client compatibility. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(sub): integrate Clash YAML endpoint into subscription system - Add Clash route handler in SUBController - Update BuildURLs to include Clash URL - Pass Clash settings through subscription pipeline Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(web): add Clash settings to entity and service - Add SubClashEnable, SubClashPath, SubClashURI fields - Add getter methods for Clash configuration - Set default Clash path to /clash/ and enable by default Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(ui): add Clash settings to subscription panels - Add Clash enable switch in general subscription settings - Add Clash path/URI configuration in formats panel - Display Clash QR code on subscription page - Rename JSON tab to "Formats" for clarity Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(js): add Clash support to frontend models - Add subClashEnable, subClashPath, subClashURI to AllSetting - Generate and display Clash QR code on subscription page - Handle Clash URL in subscription data binding Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Sanaei <ho3ein.sanaei@gmail.com>
Diffstat (limited to 'web/html/settings/panel')
-rw-r--r--web/html/settings/panel/subscription/general.html86
-rw-r--r--web/html/settings/panel/subscription/json.html26
-rw-r--r--web/html/settings/panel/subscription/subpage.html17
3 files changed, 97 insertions, 32 deletions
diff --git a/web/html/settings/panel/subscription/general.html b/web/html/settings/panel/subscription/general.html
index 5d83aa37..725a9359 100644
--- a/web/html/settings/panel/subscription/general.html
+++ b/web/html/settings/panel/subscription/general.html
@@ -3,43 +3,58 @@
<a-collapse-panel key="1" header='{{ i18n "pages.xray.generalConfigs"}}'>
<a-setting-list-item paddings="small">
<template #title>{{ i18n "pages.settings.subEnable"}}</template>
- <template #description>{{ i18n "pages.settings.subEnableDesc"}}</template>
+ <template #description>{{ i18n
+ "pages.settings.subEnableDesc"}}</template>
<template #control>
<a-switch v-model="allSetting.subEnable"></a-switch>
</template>
</a-setting-list-item>
<a-setting-list-item paddings="small">
<template #title>JSON Subscription</template>
- <template #description>{{ i18n "pages.settings.subJsonEnable"}}</template>
+ <template #description>{{ i18n
+ "pages.settings.subJsonEnable"}}</template>
<template #control>
<a-switch v-model="allSetting.subJsonEnable"></a-switch>
</template>
</a-setting-list-item>
<a-setting-list-item paddings="small">
+ <template #title>Clash / Mihomo Subscription</template>
+ <template #description>Enable direct Clash and Mihomo YAML
+ subscriptions.</template>
+ <template #control>
+ <a-switch v-model="allSetting.subClashEnable"></a-switch>
+ </template>
+ </a-setting-list-item>
+ <a-setting-list-item paddings="small">
<template #title>{{ i18n "pages.settings.subListen"}}</template>
- <template #description>{{ i18n "pages.settings.subListenDesc"}}</template>
+ <template #description>{{ i18n
+ "pages.settings.subListenDesc"}}</template>
<template #control>
<a-input type="text" v-model="allSetting.subListen"></a-input>
</template>
</a-setting-list-item>
<a-setting-list-item paddings="small">
<template #title>{{ i18n "pages.settings.subDomain"}}</template>
- <template #description>{{ i18n "pages.settings.subDomainDesc"}}</template>
+ <template #description>{{ i18n
+ "pages.settings.subDomainDesc"}}</template>
<template #control>
<a-input type="text" v-model="allSetting.subDomain"></a-input>
</template>
</a-setting-list-item>
<a-setting-list-item paddings="small">
<template #title>{{ i18n "pages.settings.subPort"}}</template>
- <template #description>{{ i18n "pages.settings.subPortDesc"}}</template>
+ <template #description>{{ i18n
+ "pages.settings.subPortDesc"}}</template>
<template #control>
- <a-input-number v-model="allSetting.subPort" :min="1" :min="65535"
+ <a-input-number v-model="allSetting.subPort" :min="1"
+ :min="65535"
:style="{ width: '100%' }"></a-input-number>
</template>
</a-setting-list-item>
<a-setting-list-item paddings="small">
<template #title>{{ i18n "pages.settings.subPath"}}</template>
- <template #description>{{ i18n "pages.settings.subPathDesc"}}</template>
+ <template #description>{{ i18n
+ "pages.settings.subPathDesc"}}</template>
<template #control>
<a-input type="text" v-model="allSetting.subPath"
@input="allSetting.subPath = ((typeof $event === 'string' ? $event : ($event && $event.target ? $event.target.value : '')) || '').replace(/[:*]/g, '')"
@@ -49,9 +64,11 @@
</a-setting-list-item>
<a-setting-list-item paddings="small">
<template #title>{{ i18n "pages.settings.subURI"}}</template>
- <template #description>{{ i18n "pages.settings.subURIDesc"}}</template>
+ <template #description>{{ i18n
+ "pages.settings.subURIDesc"}}</template>
<template #control>
- <a-input type="text" placeholder="(http|https)://domain[:port]/path/"
+ <a-input type="text"
+ placeholder="(http|https)://domain[:port]/path/"
v-model="allSetting.subURI"></a-input>
</template>
</a-setting-list-item>
@@ -59,14 +76,16 @@
<a-collapse-panel key="2" header='{{ i18n "pages.settings.information" }}'>
<a-setting-list-item paddings="small">
<template #title>{{ i18n "pages.settings.subEncrypt"}}</template>
- <template #description>{{ i18n "pages.settings.subEncryptDesc"}}</template>
+ <template #description>{{ i18n
+ "pages.settings.subEncryptDesc"}}</template>
<template #control>
<a-switch v-model="allSetting.subEncrypt"></a-switch>
</template>
</a-setting-list-item>
<a-setting-list-item paddings="small">
<template #title>{{ i18n "pages.settings.subShowInfo"}}</template>
- <template #description>{{ i18n "pages.settings.subShowInfoDesc"}}</template>
+ <template #description>{{ i18n
+ "pages.settings.subShowInfoDesc"}}</template>
<template #control>
<a-switch v-model="allSetting.subShowInfo"></a-switch>
</template>
@@ -74,59 +93,72 @@
<a-divider>{{ i18n "pages.xray.basicTemplate"}}</a-divider>
<a-setting-list-item paddings="small">
<template #title>{{ i18n "pages.settings.subTitle"}}</template>
- <template #description>{{ i18n "pages.settings.subTitleDesc"}}</template>
+ <template #description>{{ i18n
+ "pages.settings.subTitleDesc"}}</template>
<template #control>
<a-input type="text" v-model="allSetting.subTitle"></a-input>
</template>
</a-setting-list-item>
<a-setting-list-item paddings="small">
<template #title>{{ i18n "pages.settings.subSupportUrl"}}</template>
- <template #description>{{ i18n "pages.settings.subSupportUrlDesc"}}</template>
+ <template #description>{{ i18n
+ "pages.settings.subSupportUrlDesc"}}</template>
<template #control>
- <a-input type="text" v-model="allSetting.subSupportUrl" placeholder="https://example.com"></a-input>
+ <a-input type="text" v-model="allSetting.subSupportUrl"
+ placeholder="https://example.com"></a-input>
</template>
</a-setting-list-item>
<a-setting-list-item paddings="small">
<template #title>{{ i18n "pages.settings.subProfileUrl"}}</template>
- <template #description>{{ i18n "pages.settings.subProfileUrlDesc"}}</template>
+ <template #description>{{ i18n
+ "pages.settings.subProfileUrlDesc"}}</template>
<template #control>
- <a-input type="text" v-model="allSetting.subProfileUrl" placeholder="https://example.com"></a-input>
+ <a-input type="text" v-model="allSetting.subProfileUrl"
+ placeholder="https://example.com"></a-input>
</template>
</a-setting-list-item>
<a-setting-list-item paddings="small">
<template #title>{{ i18n "pages.settings.subAnnounce"}}</template>
- <template #description>{{ i18n "pages.settings.subAnnounceDesc"}}</template>
+ <template #description>{{ i18n
+ "pages.settings.subAnnounceDesc"}}</template>
<template #control>
<a-textarea v-model="allSetting.subAnnounce"></a-textarea>
</template>
</a-setting-list-item>
<a-divider>{{ i18n "pages.xray.advancedTemplate"}} (Happ)</a-divider>
<a-setting-list-item paddings="small">
- <template #title>{{ i18n "pages.settings.subEnableRouting"}}</template>
- <template #description>{{ i18n "pages.settings.subEnableRoutingDesc"}}</template>
+ <template #title>{{ i18n
+ "pages.settings.subEnableRouting"}}</template>
+ <template #description>{{ i18n
+ "pages.settings.subEnableRoutingDesc"}}</template>
<template #control>
<a-switch v-model="allSetting.subEnableRouting"></a-switch>
</template>
</a-setting-list-item>
<a-setting-list-item paddings="small">
- <template #title>{{ i18n "pages.settings.subRoutingRules"}}</template>
- <template #description>{{ i18n "pages.settings.subRoutingRulesDesc"}}</template>
+ <template #title>{{ i18n
+ "pages.settings.subRoutingRules"}}</template>
+ <template #description>{{ i18n
+ "pages.settings.subRoutingRulesDesc"}}</template>
<template #control>
- <a-textarea v-model="allSetting.subRoutingRules" placeholder="happ://routing/add/..."></a-textarea>
+ <a-textarea v-model="allSetting.subRoutingRules"
+ placeholder="happ://routing/add/..."></a-textarea>
</template>
</a-setting-list-item>
</a-collapse-panel>
<a-collapse-panel key="3" header='{{ i18n "pages.settings.certs" }}'>
<a-setting-list-item paddings="small">
<template #title>{{ i18n "pages.settings.subCertPath"}}</template>
- <template #description>{{ i18n "pages.settings.subCertPathDesc"}}</template>
+ <template #description>{{ i18n
+ "pages.settings.subCertPathDesc"}}</template>
<template #control>
<a-input type="text" v-model="allSetting.subCertFile"></a-input>
</template>
</a-setting-list-item>
<a-setting-list-item paddings="small">
<template #title>{{ i18n "pages.settings.subKeyPath"}}</template>
- <template #description>{{ i18n "pages.settings.subKeyPathDesc"}}</template>
+ <template #description>{{ i18n
+ "pages.settings.subKeyPathDesc"}}</template>
<template #control>
<a-input type="text" v-model="allSetting.subKeyFile"></a-input>
</template>
@@ -135,9 +167,11 @@
<a-collapse-panel key="4" header='{{ i18n "pages.settings.intervals"}}'>
<a-setting-list-item paddings="small">
<template #title>{{ i18n "pages.settings.subUpdates"}}</template>
- <template #description>{{ i18n "pages.settings.subUpdatesDesc"}}</template>
+ <template #description>{{ i18n
+ "pages.settings.subUpdatesDesc"}}</template>
<template #control>
- <a-input-number :min="1" v-model="allSetting.subUpdates" :style="{ width: '100%' }"></a-input-number>
+ <a-input-number :min="1" v-model="allSetting.subUpdates"
+ :style="{ width: '100%' }"></a-input-number>
</template>
</a-setting-list-item>
</a-collapse-panel>
diff --git a/web/html/settings/panel/subscription/json.html b/web/html/settings/panel/subscription/json.html
index e8642305..9b83571a 100644
--- a/web/html/settings/panel/subscription/json.html
+++ b/web/html/settings/panel/subscription/json.html
@@ -1,8 +1,8 @@
{{define "settings/panel/subscription/json"}}
<a-collapse default-active-key="1">
<a-collapse-panel key="1" header='{{ i18n "pages.xray.generalConfigs"}}'>
- <a-setting-list-item paddings="small">
- <template #title>{{ i18n "pages.settings.subPath"}}</template>
+ <a-setting-list-item paddings="small" v-if="allSetting.subJsonEnable">
+ <template #title>{{ i18n "pages.settings.subPath"}} (JSON)</template>
<template #description>{{ i18n "pages.settings.subPathDesc"}}</template>
<template #control>
<a-input type="text" v-model="allSetting.subJsonPath"
@@ -11,14 +11,32 @@
placeholder="/json/"></a-input>
</template>
</a-setting-list-item>
- <a-setting-list-item paddings="small">
- <template #title>{{ i18n "pages.settings.subURI"}}</template>
+ <a-setting-list-item paddings="small" v-if="allSetting.subJsonEnable">
+ <template #title>{{ i18n "pages.settings.subURI"}} (JSON)</template>
<template #description>{{ i18n "pages.settings.subURIDesc"}}</template>
<template #control>
<a-input type="text" placeholder="(http|https)://domain[:port]/path/"
v-model="allSetting.subJsonURI"></a-input>
</template>
</a-setting-list-item>
+ <a-setting-list-item paddings="small" v-if="allSetting.subClashEnable">
+ <template #title>{{ i18n "pages.settings.subPath"}} (Clash)</template>
+ <template #description>{{ i18n "pages.settings.subPathDesc"}}</template>
+ <template #control>
+ <a-input type="text" v-model="allSetting.subClashPath"
+ @input="allSetting.subClashPath = ((typeof $event === 'string' ? $event : ($event && $event.target ? $event.target.value : '')) || '').replace(/[:*]/g, '')"
+ @blur="allSetting.subClashPath = (p => { p = p || '/'; if (!p.startsWith('/')) p='/' + p; if (!p.endsWith('/')) p += '/'; return p.replace(/\/+/g,'/'); })(allSetting.subClashPath)"
+ placeholder="/clash/"></a-input>
+ </template>
+ </a-setting-list-item>
+ <a-setting-list-item paddings="small" v-if="allSetting.subClashEnable">
+ <template #title>{{ i18n "pages.settings.subURI"}} (Clash)</template>
+ <template #description>{{ i18n "pages.settings.subURIDesc"}}</template>
+ <template #control>
+ <a-input type="text" placeholder="(http|https)://domain[:port]/path/"
+ v-model="allSetting.subClashURI"></a-input>
+ </template>
+ </a-setting-list-item>
</a-collapse-panel>
<a-collapse-panel key="2" header='{{ i18n "pages.settings.fragment"}}'>
<a-setting-list-item paddings="small">
diff --git a/web/html/settings/panel/subscription/subpage.html b/web/html/settings/panel/subscription/subpage.html
index 794c67c3..64c1224d 100644
--- a/web/html/settings/panel/subscription/subpage.html
+++ b/web/html/settings/panel/subscription/subpage.html
@@ -83,7 +83,7 @@
<a-form-item>
<a-space direction="vertical" align="center">
<a-row type="flex" :gutter="[8,8]" justify="center" style="width:100%">
- <a-col :xs="24" :sm="app.subJsonUrl ? 12 : 24" style="text-align:center;">
+ <a-col :xs="24" :sm="app.subJsonUrl || app.subClashUrl ? 12 : 24" style="text-align:center;">
<tr-qr-box class="qr-box">
<a-tag color="purple" class="qr-tag">
<span>{{ i18n
@@ -112,6 +112,19 @@
</tr-qr-bg>
</tr-qr-box>
</a-col>
+ <a-col v-if="app.subClashUrl" :xs="24" :sm="12" style="text-align:center;">
+ <tr-qr-box class="qr-box">
+ <a-tag color="purple" class="qr-tag">
+ <span>Clash / Mihomo</span>
+ </a-tag>
+ <tr-qr-bg class="qr-bg-sub">
+ <tr-qr-bg-inner class="qr-bg-sub-inner">
+ <canvas id="qrcode-subclash" class="qr-cv" title='{{ i18n "copy" }}'
+ @click="copy(app.subClashUrl)"></canvas>
+ </tr-qr-bg-inner>
+ </tr-qr-bg>
+ </tr-qr-box>
+ </a-col>
</a-row>
</a-space>
</a-form-item>
@@ -242,7 +255,7 @@
</a-layout>
<!-- Bootstrap data for external JS -->
-<template id="subscription-data" data-sid="{{ .sId }}" data-sub-url="{{ .subUrl }}" data-subjson-url="{{ .subJsonUrl }}"
+<template id="subscription-data" data-sid="{{ .sId }}" data-sub-url="{{ .subUrl }}" data-subjson-url="{{ .subJsonUrl }}" data-subclash-url="{{ .subClashUrl }}"
data-download="{{ .download }}" data-upload="{{ .upload }}" data-used="{{ .used }}" data-total="{{ .total }}"
data-remained="{{ .remained }}" data-expire="{{ .expire }}" data-lastonline="{{ .lastOnline }}"
data-downloadbyte="{{ .downloadByte }}" data-uploadbyte="{{ .uploadByte }}" data-totalbyte="{{ .totalByte }}"