Merge branch 'main' into fix/node-tag-unique-scope

This commit is contained in:
Sanaei 2026-05-13 19:26:40 +02:00 committed by GitHub
commit 4ec84cf56c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 161 additions and 55 deletions

View file

@ -36,7 +36,9 @@ export function useNodeList() {
return n != null && n.enable && n.status === 'online'; return n != null && n.enable && n.status === 'online';
} }
const hasActive = computed(() => nodes.value.some((n) => n.enable));
onMounted(refresh); onMounted(refresh);
return { nodes, fetched, refresh, byId, nameFor, isOnline }; return { nodes, fetched, refresh, byId, nameFor, isOnline, hasActive };
} }

View file

@ -57,6 +57,7 @@ export class AllSetting {
this.subUpdates = 12; this.subUpdates = 12;
this.subEncrypt = true; this.subEncrypt = true;
this.subShowInfo = true; this.subShowInfo = true;
this.subEmailInRemark = true;
this.subURI = ""; this.subURI = "";
this.subJsonURI = ""; this.subJsonURI = "";
this.subClashURI = ""; this.subClashURI = "";

View file

@ -70,6 +70,7 @@ const inbound = ref(null);
const dbForm = ref(null); const dbForm = ref(null);
const saving = ref(false); const saving = ref(false);
const advancedJson = ref({ stream: '', sniffing: '', settings: '' }); const advancedJson = ref({ stream: '', sniffing: '', settings: '' });
const activeTabKey = ref('basic');
// Cached default cert/key paths from /panel/setting/defaultSettings // Cached default cert/key paths from /panel/setting/defaultSettings
// powers the "Set default cert" button on the TLS form. // powers the "Set default cert" button on the TLS form.
const defaultCert = ref(''); const defaultCert = ref('');
@ -241,9 +242,60 @@ watch(() => props.open, (next) => {
dbForm.value = freshDbForm(); dbForm.value = freshDbForm();
primeAdvancedJson(); primeAdvancedJson();
} }
activeTabKey.value = 'basic';
fetchDefaultCertSettings(); fetchDefaultCertSettings();
}); });
function applyAdvancedJsonToBasic() {
if (!inbound.value) return true;
let parsedSettings;
let parsedStream;
let parsedSniffing;
try {
parsedSettings = advancedJson.value.settings.trim()
? JSON.parse(advancedJson.value.settings)
: inbound.value.settings?.toJson?.();
} catch (e) { message.error(`Settings JSON invalid: ${e.message}`); return false; }
try {
parsedStream = advancedJson.value.stream.trim()
? JSON.parse(advancedJson.value.stream)
: inbound.value.stream?.toJson?.();
} catch (e) { message.error(`Stream JSON invalid: ${e.message}`); return false; }
try {
parsedSniffing = advancedJson.value.sniffing.trim()
? JSON.parse(advancedJson.value.sniffing)
: inbound.value.sniffing?.toJson?.();
} catch (e) { message.error(`Sniffing JSON invalid: ${e.message}`); return false; }
try {
inbound.value = Inbound.fromJson({
port: inbound.value.port,
listen: inbound.value.listen,
protocol: inbound.value.protocol,
settings: parsedSettings,
streamSettings: parsedStream,
tag: inbound.value.tag,
sniffing: parsedSniffing,
clientStats: inbound.value.clientStats,
});
} catch (e) {
message.error(`Advanced JSON: ${e.message}`);
return false;
}
return true;
}
let isRevertingTab = false;
watch(activeTabKey, (next, prev) => {
if (isRevertingTab) { isRevertingTab = false; return; }
if (prev === 'advanced' && next !== 'advanced') {
if (!applyAdvancedJsonToBasic()) {
isRevertingTab = true;
activeTabKey.value = 'advanced';
}
}
});
// In add mode, switching protocol restamps settings + re-syncs port. // In add mode, switching protocol restamps settings + re-syncs port.
function onProtocolChange(next) { function onProtocolChange(next) {
if (props.mode === 'edit' || !inbound.value) return; if (props.mode === 'edit' || !inbound.value) return;
@ -572,7 +624,7 @@ watch(
<template> <template>
<a-modal :open="open" :title="title" :ok-text="okText" :cancel-text="t('close')" :confirm-loading="saving" <a-modal :open="open" :title="title" :ok-text="okText" :cancel-text="t('close')" :confirm-loading="saving"
:mask-closable="false" width="780px" @ok="submit" @cancel="close"> :mask-closable="false" width="780px" @ok="submit" @cancel="close">
<a-tabs v-if="inbound && dbForm" default-active-key="basic"> <a-tabs v-if="inbound && dbForm" v-model:active-key="activeTabKey">
<!-- ============================== BASICS ============================== --> <!-- ============================== BASICS ============================== -->
<a-tab-pane key="basic" :tab="t('pages.xray.basicTemplate')"> <a-tab-pane key="basic" :tab="t('pages.xray.basicTemplate')">
<a-form :colon="false" :label-col="{ sm: { span: 8 } }" :wrapper-col="{ sm: { span: 14 } }"> <a-form :colon="false" :label-col="{ sm: { span: 8 } }" :wrapper-col="{ sm: { span: 14 } }">
@ -582,7 +634,7 @@ watch(
<a-form-item :label="t('pages.inbounds.remark')"> <a-form-item :label="t('pages.inbounds.remark')">
<a-input v-model:value="dbForm.remark" /> <a-input v-model:value="dbForm.remark" />
</a-form-item> </a-form-item>
<a-form-item :label="t('pages.inbounds.deployTo')"> <a-form-item v-if="selectableNodes.length > 0" :label="t('pages.inbounds.deployTo')">
<a-select v-model:value="dbForm.nodeId" :disabled="mode === 'edit'" <a-select v-model:value="dbForm.nodeId" :disabled="mode === 'edit'"
:placeholder="t('pages.inbounds.localPanel')" allow-clear> :placeholder="t('pages.inbounds.localPanel')" allow-clear>
<a-select-option :value="null">{{ t('pages.inbounds.localPanel') }}</a-select-option> <a-select-option :value="null">{{ t('pages.inbounds.localPanel') }}</a-select-option>

View file

@ -49,6 +49,7 @@ const props = defineProps({
// Map node id -> node row, supplied by the parent page so each // Map node id -> node row, supplied by the parent page so each
// inbound row can render its node name without an extra fetch. // inbound row can render its node name without an extra fetch.
nodesById: { type: Map, default: () => new Map() }, nodesById: { type: Map, default: () => new Map() },
hasActiveNode: { type: Boolean, default: false },
}); });
const emit = defineEmits([ const emit = defineEmits([
@ -234,7 +235,7 @@ const desktopColumns = computed(() => {
if (hasAnyRemark.value) { if (hasAnyRemark.value) {
cols.push(sortableCol({ title: t('pages.inbounds.remark'), dataIndex: 'remark', key: 'remark', align: 'center', width: 60 }, 'remark')); cols.push(sortableCol({ title: t('pages.inbounds.remark'), dataIndex: 'remark', key: 'remark', align: 'center', width: 60 }, 'remark'));
} }
if (props.nodesById.size > 0) { if (props.hasActiveNode) {
cols.push(sortableCol({ title: t('pages.inbounds.node'), key: 'node', align: 'center', width: 60 }, 'node')); cols.push(sortableCol({ title: t('pages.inbounds.node'), key: 'node', align: 'center', width: 60 }, 'node'));
} }
cols.push( cols.push(
@ -374,7 +375,7 @@ function showQrCodeMenu(dbInbound) {
{{ protocol }} {{ protocol }}
</a-select-option> </a-select-option>
</a-select> </a-select>
<a-select v-if="nodeOptions.length > 0" v-model:value="nodeFilter" allow-clear <a-select v-if="hasActiveNode && nodeOptions.length > 0" v-model:value="nodeFilter" allow-clear
:placeholder="t('pages.inbounds.node')" :size="isMobile ? 'small' : 'middle'" :style="{ width: '170px' }"> :placeholder="t('pages.inbounds.node')" :size="isMobile ? 'small' : 'middle'" :style="{ width: '170px' }">
<a-select-option v-for="node in nodeOptions" :key="node.value" :value="node.value"> <a-select-option v-for="node in nodeOptions" :key="node.value" :value="node.value">
{{ node.label }} {{ node.label }}
@ -466,7 +467,7 @@ function showQrCodeMenu(dbInbound) {
<span class="stat-label">{{ t('pages.inbounds.port') }}</span> <span class="stat-label">{{ t('pages.inbounds.port') }}</span>
<a-tag>{{ record.port }}</a-tag> <a-tag>{{ record.port }}</a-tag>
</div> </div>
<div v-if="nodesById.size > 0" class="stat-row"> <div v-if="hasActiveNode" class="stat-row">
<span class="stat-label">{{ t('pages.inbounds.node') }}</span> <span class="stat-label">{{ t('pages.inbounds.node') }}</span>
<a-tag v-if="record.nodeId == null" color="default"> <a-tag v-if="record.nodeId == null" color="default">
{{ t('pages.inbounds.localPanel') }} {{ t('pages.inbounds.localPanel') }}

View file

@ -66,7 +66,7 @@ useWebSocket({
const { isMobile } = useMediaQuery(); const { isMobile } = useMediaQuery();
// Node list lives on the central panel; the Inbounds page consumes // Node list lives on the central panel; the Inbounds page consumes
// the idnode map for the new "Node" column. Fetched once on mount. // the idnode map for the new "Node" column. Fetched once on mount.
const { byId: nodesById } = useNodeList(); const { byId: nodesById, hasActive: hasActiveNode } = useNodeList();
const basePath = window.X_UI_BASE_PATH || ''; const basePath = window.X_UI_BASE_PATH || '';
const requestUri = window.location.pathname; const requestUri = window.location.pathname;
@ -647,7 +647,8 @@ function onRowAction({ key, dbInbound }) {
<InboundList :db-inbounds="dbInbounds" :client-count="clientCount" :online-clients="onlineClients" <InboundList :db-inbounds="dbInbounds" :client-count="clientCount" :online-clients="onlineClients"
:last-online-map="lastOnlineMap" :is-dark-theme="themeState.isDark" :expire-diff="expireDiff" :last-online-map="lastOnlineMap" :is-dark-theme="themeState.isDark" :expire-diff="expireDiff"
:traffic-diff="trafficDiff" :page-size="pageSize" :is-mobile="isMobile" :traffic-diff="trafficDiff" :page-size="pageSize" :is-mobile="isMobile"
:sub-enable="subSettings.enable" :nodes-by-id="nodesById" @refresh="refresh" :sub-enable="subSettings.enable" :nodes-by-id="nodesById" :has-active-node="hasActiveNode"
@refresh="refresh"
@add-inbound="onAddInbound" @general-action="onGeneralAction" @row-action="onRowAction" @add-inbound="onAddInbound" @general-action="onGeneralAction" @row-action="onRowAction"
@edit-client="onEditClient" @qrcode-client="onQrcodeClient" @info-client="onInfoClient" @edit-client="onEditClient" @qrcode-client="onQrcodeClient" @info-client="onInfoClient"
@reset-traffic-client="onResetTrafficClient" @delete-client="onDeleteClient" @reset-traffic-client="onResetTrafficClient" @delete-client="onDeleteClient"

View file

@ -112,6 +112,14 @@ function normalizeSubPath() {
</template> </template>
</SettingListItem> </SettingListItem>
<SettingListItem paddings="small">
<template #title>{{ t('pages.settings.subEmailInRemark') }}</template>
<template #description>{{ t('pages.settings.subEmailInRemarkDesc') }}</template>
<template #control>
<a-switch v-model:checked="allSetting.subEmailInRemark" />
</template>
</SettingListItem>
<a-divider>{{ t('pages.settings.subTitle') }}</a-divider> <a-divider>{{ t('pages.settings.subTitle') }}</a-divider>
<SettingListItem paddings="small"> <SettingListItem paddings="small">

View file

@ -186,9 +186,6 @@ function onRemoveRoutingRules({ prefix }) {
); );
} }
// `message` is used by some of the in-progress UX flows (kept around
// because future provisioning errors will surface through it).
void message;
const { isMobile } = useMediaQuery(); const { isMobile } = useMediaQuery();
const basePath = window.X_UI_BASE_PATH || ''; const basePath = window.X_UI_BASE_PATH || '';
@ -230,6 +227,17 @@ function onTabChange(key) {
} }
} }
function onSaveAll() {
try {
JSON.parse(xraySetting.value);
} catch (e) {
message.error(`Advanced JSON: ${e.message}`);
activeTabKey.value = 'tpl-advanced';
return;
}
saveAll();
}
function syncTabFromHash() { function syncTabFromHash() {
const key = keyBySlug[window.location.hash.slice(1)]; const key = keyBySlug[window.location.hash.slice(1)];
if (key) activeTabKey.value = key; if (key) activeTabKey.value = key;
@ -268,7 +276,7 @@ onBeforeUnmount(() => {
<a-row class="header-row"> <a-row class="header-row">
<a-col :xs="24" :sm="14" class="header-actions"> <a-col :xs="24" :sm="14" class="header-actions">
<a-space direction="horizontal"> <a-space direction="horizontal">
<a-button type="primary" :disabled="saveDisabled" @click="saveAll"> <a-button type="primary" :disabled="saveDisabled" @click="onSaveAll">
{{ t('pages.xray.save') }} {{ t('pages.xray.save') }}
</a-button> </a-button>
<a-button type="primary" danger :disabled="!saveDisabled" @click="confirmRestart"> <a-button type="primary" danger :disabled="!saveDisabled" @click="confirmRestart">

View file

@ -28,6 +28,7 @@ type SubService struct {
showInfo bool showInfo bool
remarkModel string remarkModel string
datepicker string datepicker string
emailInRemark bool
inboundService service.InboundService inboundService service.InboundService
settingService service.SettingService settingService service.SettingService
// nodesByID is populated per request from the Node table so // nodesByID is populated per request from the Node table so
@ -76,6 +77,12 @@ func (s *SubService) GetSubs(subId string, host string) ([]string, int64, xray.C
if err != nil { if err != nil {
s.datepicker = "gregorian" s.datepicker = "gregorian"
} }
s.emailInRemark, err = s.settingService.GetSubEmailInRemark()
if err != nil {
s.emailInRemark = true
}
seenEmails := make(map[string]struct{}) seenEmails := make(map[string]struct{})
for _, inbound := range inbounds { for _, inbound := range inbounds {
clients, err := s.inboundService.GetClients(inbound) clients, err := s.inboundService.GetClients(inbound)
@ -886,7 +893,7 @@ func (s *SubService) genRemark(inbound *model.Inbound, email string, extra strin
'e': "", 'e': "",
'o': "", 'o': "",
} }
if len(email) > 0 { if len(email) > 0 && s.emailInRemark {
orders['e'] = email orders['e'] = email
} }
if len(inbound.Remark) > 0 { if len(inbound.Remark) > 0 {

View file

@ -75,6 +75,7 @@ type AllSetting struct {
RestartXrayOnClientDisable bool `json:"restartXrayOnClientDisable" form:"restartXrayOnClientDisable"` // Restart Xray when clients are auto-disabled by expiry/traffic limit RestartXrayOnClientDisable bool `json:"restartXrayOnClientDisable" form:"restartXrayOnClientDisable"` // Restart Xray when clients are auto-disabled by expiry/traffic limit
SubEncrypt bool `json:"subEncrypt" form:"subEncrypt"` // Encrypt subscription responses SubEncrypt bool `json:"subEncrypt" form:"subEncrypt"` // Encrypt subscription responses
SubShowInfo bool `json:"subShowInfo" form:"subShowInfo"` // Show client information in subscriptions SubShowInfo bool `json:"subShowInfo" form:"subShowInfo"` // Show client information in subscriptions
SubEmailInRemark bool `json:"subEmailInRemark" form:"subEmailInRemark"` // Include email in subscription remark/name
SubURI string `json:"subURI" form:"subURI"` // Subscription server URI SubURI string `json:"subURI" form:"subURI"` // Subscription server URI
SubJsonPath string `json:"subJsonPath" form:"subJsonPath"` // Path for JSON subscription endpoint SubJsonPath string `json:"subJsonPath" form:"subJsonPath"` // Path for JSON subscription endpoint
SubJsonURI string `json:"subJsonURI" form:"subJsonURI"` // JSON subscription server URI SubJsonURI string `json:"subJsonURI" form:"subJsonURI"` // JSON subscription server URI

View file

@ -1,4 +1,21 @@
{ {
"api": {
"services": [
"HandlerService",
"LoggerService",
"StatsService"
],
"tag": "api"
},
"inbounds": [{
"listen": "127.0.0.1",
"port": 62789,
"protocol": "tunnel",
"settings": {
"address": "127.0.0.1"
},
"tag": "api"
}],
"log": { "log": {
"access": "none", "access": "none",
"dnsLog": false, "dnsLog": false,
@ -6,39 +23,21 @@
"loglevel": "warning", "loglevel": "warning",
"maskAddress": "" "maskAddress": ""
}, },
"api": { "metrics": {
"tag": "api", "listen": "127.0.0.1:11111",
"services": [ "tag": "metrics_out"
"HandlerService",
"LoggerService",
"StatsService"
]
}, },
"inbounds": [ "outbounds": [{
{
"tag": "api",
"listen": "127.0.0.1",
"port": 62789,
"protocol": "tunnel",
"settings": {
"address": "127.0.0.1"
}
}
],
"outbounds": [
{
"tag": "direct",
"protocol": "freedom", "protocol": "freedom",
"settings": { "settings": {
"domainStrategy": "AsIs", "domainStrategy": "AsIs"
"redirect": "", },
"noises": [] "tag": "direct"
}
}, },
{ {
"tag": "blocked",
"protocol": "blackhole", "protocol": "blackhole",
"settings": {} "settings": {},
"tag": "blocked"
} }
], ],
"policy": { "policy": {
@ -57,33 +56,28 @@
}, },
"routing": { "routing": {
"domainStrategy": "AsIs", "domainStrategy": "AsIs",
"rules": [ "rules": [{
{
"type": "field",
"inboundTag": [ "inboundTag": [
"api" "api"
], ],
"outboundTag": "api" "outboundTag": "api",
"type": "field"
}, },
{ {
"type": "field",
"outboundTag": "blocked",
"ip": [ "ip": [
"geoip:private" "geoip:private"
] ],
"outboundTag": "blocked",
"type": "field"
}, },
{ {
"type": "field",
"outboundTag": "blocked", "outboundTag": "blocked",
"protocol": [ "protocol": [
"bittorrent" "bittorrent"
] ],
"type": "field"
} }
] ]
}, },
"stats": {}, "stats": {}
"metrics": {
"tag": "metrics_out",
"listen": "127.0.0.1:11111"
}
} }

View file

@ -70,6 +70,7 @@ var defaultValueMap = map[string]string{
"subUpdates": "12", "subUpdates": "12",
"subEncrypt": "true", "subEncrypt": "true",
"subShowInfo": "true", "subShowInfo": "true",
"subEmailInRemark": "true",
"subURI": "", "subURI": "",
"subJsonPath": "/json/", "subJsonPath": "/json/",
"subJsonURI": "", "subJsonURI": "",
@ -592,6 +593,10 @@ func (s *SettingService) GetSubShowInfo() (bool, error) {
return s.getBool("subShowInfo") return s.getBool("subShowInfo")
} }
func (s *SettingService) GetSubEmailInRemark() (bool, error) {
return s.getBool("subEmailInRemark")
}
func (s *SettingService) GetPageSize() (int, error) { func (s *SettingService) GetPageSize() (int, error) {
return s.getInt("pageSize") return s.getInt("pageSize")
} }

View file

@ -547,6 +547,8 @@
"subEncryptDesc": "المحتوى اللي هيترجع من خدمة الاشتراك هيكون مشفر بـ Base64.", "subEncryptDesc": "المحتوى اللي هيترجع من خدمة الاشتراك هيكون مشفر بـ Base64.",
"subShowInfo": "اظهر معلومات الاستخدام", "subShowInfo": "اظهر معلومات الاستخدام",
"subShowInfoDesc": "هيظهر الترافيك المتبقي والتاريخ في تطبيقات العملاء.", "subShowInfoDesc": "هيظهر الترافيك المتبقي والتاريخ في تطبيقات العملاء.",
"subEmailInRemark": "تضمين البريد الإلكتروني في الاسم",
"subEmailInRemarkDesc": "تضمين بريد العميل الإلكتروني في اسم ملف تعريف الاشتراك.",
"subURI": "مسار البروكسي العكسي", "subURI": "مسار البروكسي العكسي",
"subURIDesc": "مسار URI لرابط الاشتراك عشان تستخدمه ورا البروكسي.", "subURIDesc": "مسار URI لرابط الاشتراك عشان تستخدمه ورا البروكسي.",
"externalTrafficInformEnable": "تنبيه الترافيك الخارجي", "externalTrafficInformEnable": "تنبيه الترافيك الخارجي",

View file

@ -547,6 +547,8 @@
"subEncryptDesc": "The returned content of subscription service will be Base64 encoded.", "subEncryptDesc": "The returned content of subscription service will be Base64 encoded.",
"subShowInfo": "Show Usage Info", "subShowInfo": "Show Usage Info",
"subShowInfoDesc": "The remaining traffic and date will be displayed in the client apps.", "subShowInfoDesc": "The remaining traffic and date will be displayed in the client apps.",
"subEmailInRemark": "Include Email in Name",
"subEmailInRemarkDesc": "Include the client email in the subscription profile name.",
"subURI": "Reverse Proxy URI", "subURI": "Reverse Proxy URI",
"subURIDesc": "The URI path of the subscription URL for use behind proxies.", "subURIDesc": "The URI path of the subscription URL for use behind proxies.",
"externalTrafficInformEnable": "External Traffic Inform", "externalTrafficInformEnable": "External Traffic Inform",

View file

@ -547,6 +547,8 @@
"subEncryptDesc": "Encriptar las configuraciones devueltas en la suscripción.", "subEncryptDesc": "Encriptar las configuraciones devueltas en la suscripción.",
"subShowInfo": "Mostrar información de uso", "subShowInfo": "Mostrar información de uso",
"subShowInfoDesc": "Mostrar tráfico restante y fecha después del nombre de configuración.", "subShowInfoDesc": "Mostrar tráfico restante y fecha después del nombre de configuración.",
"subEmailInRemark": "Incluir Email en el nombre",
"subEmailInRemarkDesc": "Incluir el correo del cliente en el nombre del perfil de suscripción.",
"subURI": "URI de proxy inverso", "subURI": "URI de proxy inverso",
"externalTrafficInformEnable": "Informe de tráfico externo", "externalTrafficInformEnable": "Informe de tráfico externo",
"externalTrafficInformEnableDesc": "Informar a la API externa sobre cada actualización de tráfico.", "externalTrafficInformEnableDesc": "Informar a la API externa sobre cada actualización de tráfico.",

View file

@ -547,6 +547,8 @@
"subEncryptDesc": "کدگذاری خواهدشد Base64 محتوای برگشتی سرویس سابسکریپشن برپایه", "subEncryptDesc": "کدگذاری خواهدشد Base64 محتوای برگشتی سرویس سابسکریپشن برپایه",
"subShowInfo": "نمایش اطلاعات مصرف", "subShowInfo": "نمایش اطلاعات مصرف",
"subShowInfoDesc": "ترافیک و زمان باقی‌مانده را در برنامه‌های کاربری نمایش می‌دهد", "subShowInfoDesc": "ترافیک و زمان باقی‌مانده را در برنامه‌های کاربری نمایش می‌دهد",
"subEmailInRemark": "گنجاندن ایمیل در نام",
"subEmailInRemarkDesc": "ایمیل کاربر در نام پروفایل اشتراک گنجانده می‌شود.",
"subURI": "پروکسی معکوس URI مسیر", "subURI": "پروکسی معکوس URI مسیر",
"subURIDesc": "سابسکریپشن را برای استفاده در پشت پراکسی‌ها تغییر می‌دهد URI مسیر", "subURIDesc": "سابسکریپشن را برای استفاده در پشت پراکسی‌ها تغییر می‌دهد URI مسیر",
"externalTrafficInformEnable": "اطلاع رسانی خارجی مصرف ترافیک", "externalTrafficInformEnable": "اطلاع رسانی خارجی مصرف ترافیک",

View file

@ -547,6 +547,8 @@
"subEncryptDesc": "Konten yang dikembalikan dari layanan langganan akan dienkripsi Base64.", "subEncryptDesc": "Konten yang dikembalikan dari layanan langganan akan dienkripsi Base64.",
"subShowInfo": "Tampilkan Info Penggunaan", "subShowInfo": "Tampilkan Info Penggunaan",
"subShowInfoDesc": "Sisa traffic dan tanggal akan ditampilkan di aplikasi klien.", "subShowInfoDesc": "Sisa traffic dan tanggal akan ditampilkan di aplikasi klien.",
"subEmailInRemark": "Sertakan Email dalam Nama",
"subEmailInRemarkDesc": "Sertakan email klien dalam nama profil langganan.",
"subURI": "URI Proxy Terbalik", "subURI": "URI Proxy Terbalik",
"subURIDesc": "Path URI dari URL langganan untuk digunakan di belakang proxy.", "subURIDesc": "Path URI dari URL langganan untuk digunakan di belakang proxy.",
"externalTrafficInformEnable": "Informasikan API eksternal pada setiap pembaruan lalu lintas.", "externalTrafficInformEnable": "Informasikan API eksternal pada setiap pembaruan lalu lintas.",

View file

@ -547,6 +547,8 @@
"subEncryptDesc": "サブスクリプションサービスが返す内容をBase64エンコードする", "subEncryptDesc": "サブスクリプションサービスが返す内容をBase64エンコードする",
"subShowInfo": "利用情報を表示", "subShowInfo": "利用情報を表示",
"subShowInfoDesc": "クライアントアプリで残りのトラフィックと日付情報を表示する", "subShowInfoDesc": "クライアントアプリで残りのトラフィックと日付情報を表示する",
"subEmailInRemark": "名前にメールを含める",
"subEmailInRemarkDesc": "サブスクリプションプロファイル名にクライアントのメールアドレスを含めます。",
"subURI": "リバースプロキシURI", "subURI": "リバースプロキシURI",
"subURIDesc": "プロキシ後ろのサブスクリプションURLのURIパスに使用する", "subURIDesc": "プロキシ後ろのサブスクリプションURLのURIパスに使用する",
"externalTrafficInformEnable": "外部トラフィック情報", "externalTrafficInformEnable": "外部トラフィック情報",

View file

@ -547,6 +547,8 @@
"subEncryptDesc": "O conteúdo retornado pelo serviço de assinatura será codificado em Base64.", "subEncryptDesc": "O conteúdo retornado pelo serviço de assinatura será codificado em Base64.",
"subShowInfo": "Mostrar Informações de Uso", "subShowInfo": "Mostrar Informações de Uso",
"subShowInfoDesc": "O tráfego restante e a data serão exibidos nos aplicativos de cliente.", "subShowInfoDesc": "O tráfego restante e a data serão exibidos nos aplicativos de cliente.",
"subEmailInRemark": "Incluir Email no nome",
"subEmailInRemarkDesc": "Incluir o email do cliente no nome do perfil de assinatura.",
"subURI": "URI de Proxy Reverso", "subURI": "URI de Proxy Reverso",
"subURIDesc": "O caminho URI da URL de assinatura para uso por trás de proxies.", "subURIDesc": "O caminho URI da URL de assinatura para uso por trás de proxies.",
"externalTrafficInformEnable": "Informações de tráfego externo", "externalTrafficInformEnable": "Informações de tráfego externo",

View file

@ -547,6 +547,8 @@
"subEncryptDesc": "Шифровать возвращенные конфиги в подписке", "subEncryptDesc": "Шифровать возвращенные конфиги в подписке",
"subShowInfo": "Показать информацию об использовании", "subShowInfo": "Показать информацию об использовании",
"subShowInfoDesc": "Отображать остаток трафика и дату окончания после имени конфигурации", "subShowInfoDesc": "Отображать остаток трафика и дату окончания после имени конфигурации",
"subEmailInRemark": "Включать Email в название",
"subEmailInRemarkDesc": "Включать email клиента в название профиля подписки.",
"subURI": "URI обратного прокси", "subURI": "URI обратного прокси",
"subURIDesc": "Изменить базовый URI URL-адреса подписки для использования за прокси-серверами", "subURIDesc": "Изменить базовый URI URL-адреса подписки для использования за прокси-серверами",
"externalTrafficInformEnable": "Информация о внешнем трафике", "externalTrafficInformEnable": "Информация о внешнем трафике",

View file

@ -547,6 +547,8 @@
"subEncryptDesc": "Abonelik hizmetinin döndürülen içeriği Base64 ile şifrelenir.", "subEncryptDesc": "Abonelik hizmetinin döndürülen içeriği Base64 ile şifrelenir.",
"subShowInfo": "Kullanım Bilgisini Göster", "subShowInfo": "Kullanım Bilgisini Göster",
"subShowInfoDesc": "Kalan trafik ve tarih müşteri uygulamalarında görüntülenir.", "subShowInfoDesc": "Kalan trafik ve tarih müşteri uygulamalarında görüntülenir.",
"subEmailInRemark": "Ada Email Ekle",
"subEmailInRemarkDesc": "Abonelik profil adına istemcinin e-postasını dahil edin.",
"subURI": "Ters Proxy URI", "subURI": "Ters Proxy URI",
"subURIDesc": "Proxy arkasında kullanılacak abonelik URL'sinin URI yolu.", "subURIDesc": "Proxy arkasında kullanılacak abonelik URL'sinin URI yolu.",
"externalTrafficInformEnable": "Harici Trafik Bilgisi", "externalTrafficInformEnable": "Harici Trafik Bilgisi",

View file

@ -547,6 +547,8 @@
"subEncryptDesc": "Повернений вміст послуги підписки матиме кодування Base64.", "subEncryptDesc": "Повернений вміст послуги підписки матиме кодування Base64.",
"subShowInfo": "Показати інформацію про використання", "subShowInfo": "Показати інформацію про використання",
"subShowInfoDesc": "Залишок трафіку та дата відображатимуться в клієнтських програмах.", "subShowInfoDesc": "Залишок трафіку та дата відображатимуться в клієнтських програмах.",
"subEmailInRemark": "Включати Email до назви",
"subEmailInRemarkDesc": "Включати email клієнта до назви профілю підписки.",
"subURI": "URI зворотного проксі", "subURI": "URI зворотного проксі",
"subURIDesc": "URI до URL-адреси підписки для використання за проксі.", "subURIDesc": "URI до URL-адреси підписки для використання за проксі.",
"externalTrafficInformEnable": "Інформація про зовнішній трафік", "externalTrafficInformEnable": "Інформація про зовнішній трафік",

View file

@ -547,6 +547,8 @@
"subEncryptDesc": "Mã hóa các cấu hình được trả về trong gói đăng ký", "subEncryptDesc": "Mã hóa các cấu hình được trả về trong gói đăng ký",
"subShowInfo": "Hiển thị thông tin sử dụng", "subShowInfo": "Hiển thị thông tin sử dụng",
"subShowInfoDesc": "Hiển thị lưu lượng truy cập còn lại và ngày sau tên cấu hình", "subShowInfoDesc": "Hiển thị lưu lượng truy cập còn lại và ngày sau tên cấu hình",
"subEmailInRemark": "Thêm Email vào tên",
"subEmailInRemarkDesc": "Thêm email của client vào tên hồ sơ đăng ký.",
"subURI": "URI proxy trung gian", "subURI": "URI proxy trung gian",
"subURIDesc": "Thay đổi URI cơ sở của URL gói đăng ký để sử dụng cho proxy trung gian", "subURIDesc": "Thay đổi URI cơ sở của URL gói đăng ký để sử dụng cho proxy trung gian",
"externalTrafficInformEnable": "Thông báo giao thông bên ngoài", "externalTrafficInformEnable": "Thông báo giao thông bên ngoài",

View file

@ -547,6 +547,8 @@
"subEncryptDesc": "订阅服务返回的内容将采用 Base64 编码", "subEncryptDesc": "订阅服务返回的内容将采用 Base64 编码",
"subShowInfo": "显示使用信息", "subShowInfo": "显示使用信息",
"subShowInfoDesc": "客户端应用中将显示剩余流量和日期信息", "subShowInfoDesc": "客户端应用中将显示剩余流量和日期信息",
"subEmailInRemark": "在名称中包含邮箱",
"subEmailInRemarkDesc": "在订阅配置名称中包含客户端邮箱。",
"subURI": "反向代理 URI", "subURI": "反向代理 URI",
"subURIDesc": "用于代理后面的订阅 URL 的 URI 路径", "subURIDesc": "用于代理后面的订阅 URL 的 URI 路径",
"externalTrafficInformEnable": "外部交通通知", "externalTrafficInformEnable": "外部交通通知",

View file

@ -547,6 +547,8 @@
"subEncryptDesc": "訂閱服務返回的內容將採用 Base64 編碼", "subEncryptDesc": "訂閱服務返回的內容將採用 Base64 編碼",
"subShowInfo": "顯示使用資訊", "subShowInfo": "顯示使用資訊",
"subShowInfoDesc": "客戶端應用中將顯示剩餘流量和日期資訊", "subShowInfoDesc": "客戶端應用中將顯示剩餘流量和日期資訊",
"subEmailInRemark": "在名稱中包含郵箱",
"subEmailInRemarkDesc": "在訂閱配置名稱中包含客戶端郵箱。",
"subURI": "反向代理 URI", "subURI": "反向代理 URI",
"subURIDesc": "用於代理後面的訂閱 URL 的 URI 路徑", "subURIDesc": "用於代理後面的訂閱 URL 的 URI 路徑",
"externalTrafficInformEnable": "外部交通通知", "externalTrafficInformEnable": "外部交通通知",