mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-06-05 20:54:14 +00:00
Merge branch 'main' into fix/node-tag-unique-scope
This commit is contained in:
commit
4ec84cf56c
24 changed files with 161 additions and 55 deletions
|
|
@ -36,7 +36,9 @@ export function useNodeList() {
|
|||
return n != null && n.enable && n.status === 'online';
|
||||
}
|
||||
|
||||
const hasActive = computed(() => nodes.value.some((n) => n.enable));
|
||||
|
||||
onMounted(refresh);
|
||||
|
||||
return { nodes, fetched, refresh, byId, nameFor, isOnline };
|
||||
return { nodes, fetched, refresh, byId, nameFor, isOnline, hasActive };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ export class AllSetting {
|
|||
this.subUpdates = 12;
|
||||
this.subEncrypt = true;
|
||||
this.subShowInfo = true;
|
||||
this.subEmailInRemark = true;
|
||||
this.subURI = "";
|
||||
this.subJsonURI = "";
|
||||
this.subClashURI = "";
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ const inbound = ref(null);
|
|||
const dbForm = ref(null);
|
||||
const saving = ref(false);
|
||||
const advancedJson = ref({ stream: '', sniffing: '', settings: '' });
|
||||
const activeTabKey = ref('basic');
|
||||
// Cached default cert/key paths from /panel/setting/defaultSettings —
|
||||
// powers the "Set default cert" button on the TLS form.
|
||||
const defaultCert = ref('');
|
||||
|
|
@ -241,9 +242,60 @@ watch(() => props.open, (next) => {
|
|||
dbForm.value = freshDbForm();
|
||||
primeAdvancedJson();
|
||||
}
|
||||
activeTabKey.value = 'basic';
|
||||
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.
|
||||
function onProtocolChange(next) {
|
||||
if (props.mode === 'edit' || !inbound.value) return;
|
||||
|
|
@ -572,7 +624,7 @@ watch(
|
|||
<template>
|
||||
<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">
|
||||
<a-tabs v-if="inbound && dbForm" default-active-key="basic">
|
||||
<a-tabs v-if="inbound && dbForm" v-model:active-key="activeTabKey">
|
||||
<!-- ============================== BASICS ============================== -->
|
||||
<a-tab-pane key="basic" :tab="t('pages.xray.basicTemplate')">
|
||||
<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-input v-model:value="dbForm.remark" />
|
||||
</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'"
|
||||
:placeholder="t('pages.inbounds.localPanel')" allow-clear>
|
||||
<a-select-option :value="null">{{ t('pages.inbounds.localPanel') }}</a-select-option>
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ const props = defineProps({
|
|||
// Map node id -> node row, supplied by the parent page so each
|
||||
// inbound row can render its node name without an extra fetch.
|
||||
nodesById: { type: Map, default: () => new Map() },
|
||||
hasActiveNode: { type: Boolean, default: false },
|
||||
});
|
||||
|
||||
const emit = defineEmits([
|
||||
|
|
@ -234,7 +235,7 @@ const desktopColumns = computed(() => {
|
|||
if (hasAnyRemark.value) {
|
||||
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(
|
||||
|
|
@ -374,7 +375,7 @@ function showQrCodeMenu(dbInbound) {
|
|||
{{ protocol }}
|
||||
</a-select-option>
|
||||
</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' }">
|
||||
<a-select-option v-for="node in nodeOptions" :key="node.value" :value="node.value">
|
||||
{{ node.label }}
|
||||
|
|
@ -466,7 +467,7 @@ function showQrCodeMenu(dbInbound) {
|
|||
<span class="stat-label">{{ t('pages.inbounds.port') }}</span>
|
||||
<a-tag>{{ record.port }}</a-tag>
|
||||
</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>
|
||||
<a-tag v-if="record.nodeId == null" color="default">
|
||||
{{ t('pages.inbounds.localPanel') }}
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ useWebSocket({
|
|||
const { isMobile } = useMediaQuery();
|
||||
// Node list lives on the central panel; the Inbounds page consumes
|
||||
// the id→node 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 requestUri = window.location.pathname;
|
||||
|
|
@ -647,7 +647,8 @@ function onRowAction({ key, dbInbound }) {
|
|||
<InboundList :db-inbounds="dbInbounds" :client-count="clientCount" :online-clients="onlineClients"
|
||||
:last-online-map="lastOnlineMap" :is-dark-theme="themeState.isDark" :expire-diff="expireDiff"
|
||||
: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"
|
||||
@edit-client="onEditClient" @qrcode-client="onQrcodeClient" @info-client="onInfoClient"
|
||||
@reset-traffic-client="onResetTrafficClient" @delete-client="onDeleteClient"
|
||||
|
|
|
|||
|
|
@ -112,6 +112,14 @@ function normalizeSubPath() {
|
|||
</template>
|
||||
</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>
|
||||
|
||||
<SettingListItem paddings="small">
|
||||
|
|
|
|||
|
|
@ -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 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() {
|
||||
const key = keyBySlug[window.location.hash.slice(1)];
|
||||
if (key) activeTabKey.value = key;
|
||||
|
|
@ -268,7 +276,7 @@ onBeforeUnmount(() => {
|
|||
<a-row class="header-row">
|
||||
<a-col :xs="24" :sm="14" class="header-actions">
|
||||
<a-space direction="horizontal">
|
||||
<a-button type="primary" :disabled="saveDisabled" @click="saveAll">
|
||||
<a-button type="primary" :disabled="saveDisabled" @click="onSaveAll">
|
||||
{{ t('pages.xray.save') }}
|
||||
</a-button>
|
||||
<a-button type="primary" danger :disabled="!saveDisabled" @click="confirmRestart">
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ type SubService struct {
|
|||
showInfo bool
|
||||
remarkModel string
|
||||
datepicker string
|
||||
emailInRemark bool
|
||||
inboundService service.InboundService
|
||||
settingService service.SettingService
|
||||
// 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 {
|
||||
s.datepicker = "gregorian"
|
||||
}
|
||||
|
||||
s.emailInRemark, err = s.settingService.GetSubEmailInRemark()
|
||||
if err != nil {
|
||||
s.emailInRemark = true
|
||||
}
|
||||
|
||||
seenEmails := make(map[string]struct{})
|
||||
for _, inbound := range inbounds {
|
||||
clients, err := s.inboundService.GetClients(inbound)
|
||||
|
|
@ -886,7 +893,7 @@ func (s *SubService) genRemark(inbound *model.Inbound, email string, extra strin
|
|||
'e': "",
|
||||
'o': "",
|
||||
}
|
||||
if len(email) > 0 {
|
||||
if len(email) > 0 && s.emailInRemark {
|
||||
orders['e'] = email
|
||||
}
|
||||
if len(inbound.Remark) > 0 {
|
||||
|
|
|
|||
|
|
@ -75,6 +75,7 @@ type AllSetting struct {
|
|||
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
|
||||
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
|
||||
SubJsonPath string `json:"subJsonPath" form:"subJsonPath"` // Path for JSON subscription endpoint
|
||||
SubJsonURI string `json:"subJsonURI" form:"subJsonURI"` // JSON subscription server URI
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
"access": "none",
|
||||
"dnsLog": false,
|
||||
|
|
@ -6,39 +23,21 @@
|
|||
"loglevel": "warning",
|
||||
"maskAddress": ""
|
||||
},
|
||||
"api": {
|
||||
"tag": "api",
|
||||
"services": [
|
||||
"HandlerService",
|
||||
"LoggerService",
|
||||
"StatsService"
|
||||
]
|
||||
"metrics": {
|
||||
"listen": "127.0.0.1:11111",
|
||||
"tag": "metrics_out"
|
||||
},
|
||||
"inbounds": [
|
||||
{
|
||||
"tag": "api",
|
||||
"listen": "127.0.0.1",
|
||||
"port": 62789,
|
||||
"protocol": "tunnel",
|
||||
"settings": {
|
||||
"address": "127.0.0.1"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outbounds": [
|
||||
{
|
||||
"tag": "direct",
|
||||
"outbounds": [{
|
||||
"protocol": "freedom",
|
||||
"settings": {
|
||||
"domainStrategy": "AsIs",
|
||||
"redirect": "",
|
||||
"noises": []
|
||||
}
|
||||
"domainStrategy": "AsIs"
|
||||
},
|
||||
"tag": "direct"
|
||||
},
|
||||
{
|
||||
"tag": "blocked",
|
||||
"protocol": "blackhole",
|
||||
"settings": {}
|
||||
"settings": {},
|
||||
"tag": "blocked"
|
||||
}
|
||||
],
|
||||
"policy": {
|
||||
|
|
@ -57,33 +56,28 @@
|
|||
},
|
||||
"routing": {
|
||||
"domainStrategy": "AsIs",
|
||||
"rules": [
|
||||
{
|
||||
"type": "field",
|
||||
"rules": [{
|
||||
"inboundTag": [
|
||||
"api"
|
||||
],
|
||||
"outboundTag": "api"
|
||||
"outboundTag": "api",
|
||||
"type": "field"
|
||||
},
|
||||
{
|
||||
"type": "field",
|
||||
"outboundTag": "blocked",
|
||||
"ip": [
|
||||
"geoip:private"
|
||||
]
|
||||
],
|
||||
"outboundTag": "blocked",
|
||||
"type": "field"
|
||||
},
|
||||
{
|
||||
"type": "field",
|
||||
"outboundTag": "blocked",
|
||||
"protocol": [
|
||||
"bittorrent"
|
||||
]
|
||||
],
|
||||
"type": "field"
|
||||
}
|
||||
]
|
||||
},
|
||||
"stats": {},
|
||||
"metrics": {
|
||||
"tag": "metrics_out",
|
||||
"listen": "127.0.0.1:11111"
|
||||
}
|
||||
}
|
||||
"stats": {}
|
||||
}
|
||||
|
|
@ -70,6 +70,7 @@ var defaultValueMap = map[string]string{
|
|||
"subUpdates": "12",
|
||||
"subEncrypt": "true",
|
||||
"subShowInfo": "true",
|
||||
"subEmailInRemark": "true",
|
||||
"subURI": "",
|
||||
"subJsonPath": "/json/",
|
||||
"subJsonURI": "",
|
||||
|
|
@ -592,6 +593,10 @@ func (s *SettingService) GetSubShowInfo() (bool, error) {
|
|||
return s.getBool("subShowInfo")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetSubEmailInRemark() (bool, error) {
|
||||
return s.getBool("subEmailInRemark")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetPageSize() (int, error) {
|
||||
return s.getInt("pageSize")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -547,6 +547,8 @@
|
|||
"subEncryptDesc": "المحتوى اللي هيترجع من خدمة الاشتراك هيكون مشفر بـ Base64.",
|
||||
"subShowInfo": "اظهر معلومات الاستخدام",
|
||||
"subShowInfoDesc": "هيظهر الترافيك المتبقي والتاريخ في تطبيقات العملاء.",
|
||||
"subEmailInRemark": "تضمين البريد الإلكتروني في الاسم",
|
||||
"subEmailInRemarkDesc": "تضمين بريد العميل الإلكتروني في اسم ملف تعريف الاشتراك.",
|
||||
"subURI": "مسار البروكسي العكسي",
|
||||
"subURIDesc": "مسار URI لرابط الاشتراك عشان تستخدمه ورا البروكسي.",
|
||||
"externalTrafficInformEnable": "تنبيه الترافيك الخارجي",
|
||||
|
|
|
|||
|
|
@ -547,6 +547,8 @@
|
|||
"subEncryptDesc": "The returned content of subscription service will be Base64 encoded.",
|
||||
"subShowInfo": "Show Usage Info",
|
||||
"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",
|
||||
"subURIDesc": "The URI path of the subscription URL for use behind proxies.",
|
||||
"externalTrafficInformEnable": "External Traffic Inform",
|
||||
|
|
|
|||
|
|
@ -547,6 +547,8 @@
|
|||
"subEncryptDesc": "Encriptar las configuraciones devueltas en la suscripción.",
|
||||
"subShowInfo": "Mostrar información de uso",
|
||||
"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",
|
||||
"externalTrafficInformEnable": "Informe de tráfico externo",
|
||||
"externalTrafficInformEnableDesc": "Informar a la API externa sobre cada actualización de tráfico.",
|
||||
|
|
|
|||
|
|
@ -547,6 +547,8 @@
|
|||
"subEncryptDesc": "کدگذاری خواهدشد Base64 محتوای برگشتی سرویس سابسکریپشن برپایه",
|
||||
"subShowInfo": "نمایش اطلاعات مصرف",
|
||||
"subShowInfoDesc": "ترافیک و زمان باقیمانده را در برنامههای کاربری نمایش میدهد",
|
||||
"subEmailInRemark": "گنجاندن ایمیل در نام",
|
||||
"subEmailInRemarkDesc": "ایمیل کاربر در نام پروفایل اشتراک گنجانده میشود.",
|
||||
"subURI": "پروکسی معکوس URI مسیر",
|
||||
"subURIDesc": "سابسکریپشن را برای استفاده در پشت پراکسیها تغییر میدهد URI مسیر",
|
||||
"externalTrafficInformEnable": "اطلاع رسانی خارجی مصرف ترافیک",
|
||||
|
|
|
|||
|
|
@ -547,6 +547,8 @@
|
|||
"subEncryptDesc": "Konten yang dikembalikan dari layanan langganan akan dienkripsi Base64.",
|
||||
"subShowInfo": "Tampilkan Info Penggunaan",
|
||||
"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",
|
||||
"subURIDesc": "Path URI dari URL langganan untuk digunakan di belakang proxy.",
|
||||
"externalTrafficInformEnable": "Informasikan API eksternal pada setiap pembaruan lalu lintas.",
|
||||
|
|
|
|||
|
|
@ -547,6 +547,8 @@
|
|||
"subEncryptDesc": "サブスクリプションサービスが返す内容をBase64エンコードする",
|
||||
"subShowInfo": "利用情報を表示",
|
||||
"subShowInfoDesc": "クライアントアプリで残りのトラフィックと日付情報を表示する",
|
||||
"subEmailInRemark": "名前にメールを含める",
|
||||
"subEmailInRemarkDesc": "サブスクリプションプロファイル名にクライアントのメールアドレスを含めます。",
|
||||
"subURI": "リバースプロキシURI",
|
||||
"subURIDesc": "プロキシ後ろのサブスクリプションURLのURIパスに使用する",
|
||||
"externalTrafficInformEnable": "外部トラフィック情報",
|
||||
|
|
|
|||
|
|
@ -547,6 +547,8 @@
|
|||
"subEncryptDesc": "O conteúdo retornado pelo serviço de assinatura será codificado em Base64.",
|
||||
"subShowInfo": "Mostrar Informações de Uso",
|
||||
"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",
|
||||
"subURIDesc": "O caminho URI da URL de assinatura para uso por trás de proxies.",
|
||||
"externalTrafficInformEnable": "Informações de tráfego externo",
|
||||
|
|
|
|||
|
|
@ -547,6 +547,8 @@
|
|||
"subEncryptDesc": "Шифровать возвращенные конфиги в подписке",
|
||||
"subShowInfo": "Показать информацию об использовании",
|
||||
"subShowInfoDesc": "Отображать остаток трафика и дату окончания после имени конфигурации",
|
||||
"subEmailInRemark": "Включать Email в название",
|
||||
"subEmailInRemarkDesc": "Включать email клиента в название профиля подписки.",
|
||||
"subURI": "URI обратного прокси",
|
||||
"subURIDesc": "Изменить базовый URI URL-адреса подписки для использования за прокси-серверами",
|
||||
"externalTrafficInformEnable": "Информация о внешнем трафике",
|
||||
|
|
|
|||
|
|
@ -547,6 +547,8 @@
|
|||
"subEncryptDesc": "Abonelik hizmetinin döndürülen içeriği Base64 ile şifrelenir.",
|
||||
"subShowInfo": "Kullanım Bilgisini Göster",
|
||||
"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",
|
||||
"subURIDesc": "Proxy arkasında kullanılacak abonelik URL'sinin URI yolu.",
|
||||
"externalTrafficInformEnable": "Harici Trafik Bilgisi",
|
||||
|
|
|
|||
|
|
@ -547,6 +547,8 @@
|
|||
"subEncryptDesc": "Повернений вміст послуги підписки матиме кодування Base64.",
|
||||
"subShowInfo": "Показати інформацію про використання",
|
||||
"subShowInfoDesc": "Залишок трафіку та дата відображатимуться в клієнтських програмах.",
|
||||
"subEmailInRemark": "Включати Email до назви",
|
||||
"subEmailInRemarkDesc": "Включати email клієнта до назви профілю підписки.",
|
||||
"subURI": "URI зворотного проксі",
|
||||
"subURIDesc": "URI до URL-адреси підписки для використання за проксі.",
|
||||
"externalTrafficInformEnable": "Інформація про зовнішній трафік",
|
||||
|
|
|
|||
|
|
@ -547,6 +547,8 @@
|
|||
"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",
|
||||
"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",
|
||||
"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",
|
||||
|
|
|
|||
|
|
@ -547,6 +547,8 @@
|
|||
"subEncryptDesc": "订阅服务返回的内容将采用 Base64 编码",
|
||||
"subShowInfo": "显示使用信息",
|
||||
"subShowInfoDesc": "客户端应用中将显示剩余流量和日期信息",
|
||||
"subEmailInRemark": "在名称中包含邮箱",
|
||||
"subEmailInRemarkDesc": "在订阅配置名称中包含客户端邮箱。",
|
||||
"subURI": "反向代理 URI",
|
||||
"subURIDesc": "用于代理后面的订阅 URL 的 URI 路径",
|
||||
"externalTrafficInformEnable": "外部交通通知",
|
||||
|
|
|
|||
|
|
@ -547,6 +547,8 @@
|
|||
"subEncryptDesc": "訂閱服務返回的內容將採用 Base64 編碼",
|
||||
"subShowInfo": "顯示使用資訊",
|
||||
"subShowInfoDesc": "客戶端應用中將顯示剩餘流量和日期資訊",
|
||||
"subEmailInRemark": "在名稱中包含郵箱",
|
||||
"subEmailInRemarkDesc": "在訂閱配置名稱中包含客戶端郵箱。",
|
||||
"subURI": "反向代理 URI",
|
||||
"subURIDesc": "用於代理後面的訂閱 URL 的 URI 路徑",
|
||||
"externalTrafficInformEnable": "外部交通通知",
|
||||
|
|
|
|||
Loading…
Reference in a new issue