mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2025-04-20 05:52:24 +00:00
pagination and sub URI support #1300
This commit is contained in:
parent
549f230221
commit
5e47b4e949
14 changed files with 142 additions and 59 deletions
|
@ -56,44 +56,11 @@ func (a *SettingController) getAllSetting(c *gin.Context) {
|
|||
}
|
||||
|
||||
func (a *SettingController) getDefaultSettings(c *gin.Context) {
|
||||
type settingFunc func() (interface{}, error)
|
||||
|
||||
settings := map[string]settingFunc{
|
||||
"expireDiff": func() (interface{}, error) { return a.settingService.GetExpireDiff() },
|
||||
"trafficDiff": func() (interface{}, error) { return a.settingService.GetTrafficDiff() },
|
||||
"defaultCert": func() (interface{}, error) { return a.settingService.GetCertFile() },
|
||||
"defaultKey": func() (interface{}, error) { return a.settingService.GetKeyFile() },
|
||||
"tgBotEnable": func() (interface{}, error) { return a.settingService.GetTgbotenabled() },
|
||||
"subEnable": func() (interface{}, error) { return a.settingService.GetSubEnable() },
|
||||
"subPort": func() (interface{}, error) { return a.settingService.GetSubPort() },
|
||||
"subPath": func() (interface{}, error) { return a.settingService.GetSubPath() },
|
||||
"subDomain": func() (interface{}, error) { return a.settingService.GetSubDomain() },
|
||||
"subKeyFile": func() (interface{}, error) { return a.settingService.GetSubKeyFile() },
|
||||
"subCertFile": func() (interface{}, error) { return a.settingService.GetSubCertFile() },
|
||||
"subEncrypt": func() (interface{}, error) { return a.settingService.GetSubEncrypt() },
|
||||
"subShowInfo": func() (interface{}, error) { return a.settingService.GetSubShowInfo() },
|
||||
result, err := a.settingService.GetDefaultSettings(c.Request.Host)
|
||||
if err != nil {
|
||||
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
|
||||
return
|
||||
}
|
||||
|
||||
result := make(map[string]interface{})
|
||||
|
||||
for key, fn := range settings {
|
||||
value, err := fn()
|
||||
if err != nil {
|
||||
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
|
||||
return
|
||||
}
|
||||
result[key] = value
|
||||
}
|
||||
|
||||
subTLS := false
|
||||
if result["subKeyFile"] != "" || result["subCertFile"] != "" {
|
||||
subTLS = true
|
||||
}
|
||||
result["subTLS"] = subTLS
|
||||
|
||||
delete(result, "subKeyFile")
|
||||
delete(result, "subCertFile")
|
||||
|
||||
jsonObj(c, result, nil)
|
||||
}
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@ type AllSetting struct {
|
|||
WebKeyFile string `json:"webKeyFile" form:"webKeyFile"`
|
||||
WebBasePath string `json:"webBasePath" form:"webBasePath"`
|
||||
SessionMaxAge int `json:"sessionMaxAge" form:"sessionMaxAge"`
|
||||
PageSize int `json:"pageSize" form:"pageSize"`
|
||||
ExpireDiff int `json:"expireDiff" form:"expireDiff"`
|
||||
TrafficDiff int `json:"trafficDiff" form:"trafficDiff"`
|
||||
TgBotEnable bool `json:"tgBotEnable" form:"tgBotEnable"`
|
||||
|
@ -44,6 +45,7 @@ type AllSetting struct {
|
|||
SubUpdates int `json:"subUpdates" form:"subUpdates"`
|
||||
SubEncrypt bool `json:"subEncrypt" form:"subEncrypt"`
|
||||
SubShowInfo bool `json:"subShowInfo" form:"subShowInfo"`
|
||||
SubURI string `json:"subURI" form:"subURI"`
|
||||
}
|
||||
|
||||
func (s *AllSetting) CheckValid() error {
|
||||
|
@ -93,6 +95,12 @@ func (s *AllSetting) CheckValid() error {
|
|||
if !strings.HasSuffix(s.WebBasePath, "/") {
|
||||
s.WebBasePath += "/"
|
||||
}
|
||||
if !strings.HasPrefix(s.SubPath, "/") {
|
||||
s.SubPath = "/" + s.SubPath
|
||||
}
|
||||
if !strings.HasSuffix(s.SubPath, "/") {
|
||||
s.SubPath += "/"
|
||||
}
|
||||
|
||||
_, err := time.LoadLocation(s.TimeLocation)
|
||||
if err != nil {
|
||||
|
|
|
@ -86,8 +86,7 @@
|
|||
});
|
||||
},
|
||||
genSubLink(subID) {
|
||||
const { domain: host, port, tls: isTLS, path: base } = app.subSettings;
|
||||
return buildURL({ host, port, isTLS, base, path: subID+'?name='+remark });
|
||||
return app.subSettings.subURI+subID+'?name='+subID;
|
||||
}
|
||||
},
|
||||
updated() {
|
||||
|
|
|
@ -13,10 +13,10 @@
|
|||
</a-col>
|
||||
<a-col :lg="24" :xl="12">
|
||||
<template v-if="type === 'text'">
|
||||
<a-input :value="value" @input="$emit('input', $event.target.value)"></a-input>
|
||||
<a-input :value="value" @input="$emit('input', $event.target.value)" :placeholder="placeholder"></a-input>
|
||||
</template>
|
||||
<template v-else-if="type === 'number'">
|
||||
<a-input-number :value="value" @change="value => $emit('input', value)" :min="min" style="width: 100%;"></a-input-number>
|
||||
<a-input-number :value="value" :step="step" @change="value => $emit('input', value)" :min="min" style="width: 100%;"></a-input-number>
|
||||
</template>
|
||||
<template v-else-if="type === 'switch'">
|
||||
<a-switch :checked="value" @change="value => $emit('input', value)"></a-switch>
|
||||
|
@ -29,7 +29,7 @@
|
|||
{{define "component/setting"}}
|
||||
<script>
|
||||
Vue.component('setting-list-item', {
|
||||
props: ["type", "title", "desc", "value", "min"],
|
||||
props: ["type", "title", "desc", "value", "min", "step", "placeholder"],
|
||||
template: `{{template "component/settingListItem"}}`,
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -300,8 +300,7 @@
|
|||
infoModal.visible = false;
|
||||
},
|
||||
genSubLink(subID) {
|
||||
const { domain: host, port, tls: isTLS, path: base } = app.subSettings;
|
||||
return buildURL({ host, port, isTLS, base, path: subID+'?name='+remark });
|
||||
return app.subSettings.subURI+subID+'?name='+subID;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -173,14 +173,13 @@
|
|||
<a-table :columns="isMobile ? mobileColums : columns" :row-key="dbInbound => dbInbound.id"
|
||||
:data-source="searchedInbounds"
|
||||
:scroll="isMobile ? {} : { x: 1000 }"
|
||||
:pagination="false"
|
||||
:pagination=pagination(searchedInbounds)
|
||||
:expand-icon-as-cell="false"
|
||||
:expand-row-by-click="false"
|
||||
:expand-icon-column-index="0"
|
||||
:indent-size="0"
|
||||
:row-class-name="dbInbound => (dbInbound.isMultiUser() ? '' : 'hideExpandIcon')"
|
||||
style="margin-top: 10px"
|
||||
@change="() => getDBInbounds()">
|
||||
style="margin-top: 10px">
|
||||
<template slot="action" slot-scope="text, dbInbound">
|
||||
<a-icon type="edit" style="font-size: 22px" @click="openEditInbound(dbInbound.id);"></a-icon>
|
||||
<a-dropdown :trigger="['click']">
|
||||
|
@ -416,7 +415,7 @@
|
|||
:row-key="client => client.id"
|
||||
:columns="isMobile ? innerMobileColumns : innerColumns"
|
||||
:data-source="getInboundClients(record)"
|
||||
:pagination="false"
|
||||
:pagination=pagination(getInboundClients(record))
|
||||
:style="isMobile ? 'margin: -16px -5px -17px;' : 'margin-left: 10px;'">
|
||||
{{template "client_table"}}
|
||||
</a-table>
|
||||
|
@ -547,12 +546,10 @@
|
|||
refreshInterval: Number(localStorage.getItem("refreshInterval")) || 5000,
|
||||
subSettings: {
|
||||
enable : false,
|
||||
port: 0,
|
||||
path: '',
|
||||
domain: '',
|
||||
tls: false
|
||||
subURI : ''
|
||||
},
|
||||
tgBotEnable: false,
|
||||
pageSize: 0,
|
||||
isMobile: window.innerWidth <= 768,
|
||||
},
|
||||
methods: {
|
||||
|
@ -592,11 +589,9 @@
|
|||
this.tgBotEnable = tgBotEnable;
|
||||
this.subSettings = {
|
||||
enable : subEnable,
|
||||
port: subPort,
|
||||
path: subPath,
|
||||
domain: subDomain,
|
||||
tls: subTLS
|
||||
subURI: subURI
|
||||
};
|
||||
this.pageSize = pageSize;
|
||||
}
|
||||
},
|
||||
setInbounds(dbInbounds) {
|
||||
|
@ -1216,6 +1211,27 @@
|
|||
this.spinning = false;
|
||||
}
|
||||
},
|
||||
pagination(obj){
|
||||
if (this.pageSize > 0 && obj.length>this.pageSize) {
|
||||
// Set page options based on object size
|
||||
sizeOptions = []
|
||||
for (i=this.pageSize;i<=obj.length;i=i+this.pageSize) {
|
||||
sizeOptions.push(i.toString());
|
||||
}
|
||||
// Add option to see all in one page
|
||||
sizeOptions.push(i.toString());
|
||||
|
||||
p = {
|
||||
showSizeChanger: true,
|
||||
size: 'small',
|
||||
position: 'bottom',
|
||||
pageSize: this.pageSize,
|
||||
pageSizeOptions: sizeOptions
|
||||
}
|
||||
return p
|
||||
}
|
||||
return false
|
||||
},
|
||||
onResize() {
|
||||
this.isMobile = window.innerWidth <= 768;
|
||||
}
|
||||
|
|
|
@ -112,6 +112,7 @@
|
|||
<setting-list-item type="text" title='{{ i18n "pages.settings.privateKeyPath"}}' desc='{{ i18n "pages.settings.privateKeyPathDesc"}}' v-model="allSetting.webKeyFile"></setting-list-item>
|
||||
<setting-list-item type="text" title='{{ i18n "pages.settings.panelUrlPath"}}' desc='{{ i18n "pages.settings.panelUrlPathDesc"}}' v-model="allSetting.webBasePath"></setting-list-item>
|
||||
<setting-list-item type="number" title='{{ i18n "pages.settings.sessionMaxAge" }}' desc='{{ i18n "pages.settings.sessionMaxAgeDesc" }}' v-model="allSetting.sessionMaxAge" :min="0"></setting-list-item>
|
||||
<setting-list-item type="number" title='{{ i18n "pages.settings.pageSize" }}' desc='{{ i18n "pages.settings.pageSizeDesc" }}' v-model="allSetting.pageSize" :min="0" :step="5"></setting-list-item>
|
||||
<setting-list-item type="number" title='{{ i18n "pages.settings.expireTimeDiff" }}' desc='{{ i18n "pages.settings.expireTimeDiffDesc" }}' v-model="allSetting.expireDiff" :min="0"></setting-list-item>
|
||||
<setting-list-item type="number" title='{{ i18n "pages.settings.trafficDiff" }}' desc='{{ i18n "pages.settings.trafficDiffDesc" }}' v-model="allSetting.trafficDiff" :min="0"></setting-list-item>
|
||||
<setting-list-item type="text" title='{{ i18n "pages.settings.timeZone"}}' desc='{{ i18n "pages.settings.timeZoneDesc"}}' v-model="allSetting.timeLocation"></setting-list-item>
|
||||
|
@ -243,6 +244,7 @@
|
|||
<setting-list-item type="text" title='{{ i18n "pages.settings.subPath"}}' desc='{{ i18n "pages.settings.subPathDesc"}}' v-model="allSetting.subPath"></setting-list-item>
|
||||
<setting-list-item type="text" title='{{ i18n "pages.settings.subCertPath"}}' desc='{{ i18n "pages.settings.subCertPathDesc"}}' v-model="allSetting.subCertFile"></setting-list-item>
|
||||
<setting-list-item type="text" title='{{ i18n "pages.settings.subKeyPath"}}' desc='{{ i18n "pages.settings.subKeyPathDesc"}}' v-model="allSetting.subKeyFile"></setting-list-item>
|
||||
<setting-list-item type="text" title='{{ i18n "pages.settings.subURI"}}' desc='{{ i18n "pages.settings.subURIDesc"}}' v-model="allSetting.subURI" placeholder="(http|https)://domain[:port]/path/"></setting-list-item>
|
||||
<setting-list-item type="number" title='{{ i18n "pages.settings.subUpdates"}}' desc='{{ i18n "pages.settings.subUpdatesDesc"}}' v-model="allSetting.subUpdates"></setting-list-item>
|
||||
</a-list>
|
||||
</a-tab-pane>
|
||||
|
|
|
@ -31,6 +31,7 @@ var defaultValueMap = map[string]string{
|
|||
"secret": random.Seq(32),
|
||||
"webBasePath": "/",
|
||||
"sessionMaxAge": "0",
|
||||
"pageSize": "0",
|
||||
"expireDiff": "0",
|
||||
"trafficDiff": "0",
|
||||
"timeLocation": "Asia/Tehran",
|
||||
|
@ -53,6 +54,7 @@ var defaultValueMap = map[string]string{
|
|||
"subUpdates": "12",
|
||||
"subEncrypt": "true",
|
||||
"subShowInfo": "true",
|
||||
"subURI": "",
|
||||
}
|
||||
|
||||
type SettingService struct {
|
||||
|
@ -406,6 +408,14 @@ func (s *SettingService) GetSubShowInfo() (bool, error) {
|
|||
return s.getBool("subShowInfo")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetSubURI() (string, error) {
|
||||
return s.getString("subURI")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetPageSize() (int, error) {
|
||||
return s.getInt("pageSize")
|
||||
}
|
||||
|
||||
func (s *SettingService) UpdateAllSetting(allSetting *entity.AllSetting) error {
|
||||
if err := allSetting.CheckValid(); err != nil {
|
||||
return err
|
||||
|
@ -435,3 +445,61 @@ func (s *SettingService) GetDefaultXrayConfig() (interface{}, error) {
|
|||
}
|
||||
return jsonData, nil
|
||||
}
|
||||
|
||||
func (s *SettingService) GetDefaultSettings(host string) (interface{}, error) {
|
||||
type settingFunc func() (interface{}, error)
|
||||
settings := map[string]settingFunc{
|
||||
"expireDiff": func() (interface{}, error) { return s.GetExpireDiff() },
|
||||
"trafficDiff": func() (interface{}, error) { return s.GetTrafficDiff() },
|
||||
"pageSize": func() (interface{}, error) { return s.GetPageSize() },
|
||||
"defaultCert": func() (interface{}, error) { return s.GetCertFile() },
|
||||
"defaultKey": func() (interface{}, error) { return s.GetKeyFile() },
|
||||
"tgBotEnable": func() (interface{}, error) { return s.GetTgbotenabled() },
|
||||
"subEnable": func() (interface{}, error) { return s.GetSubEnable() },
|
||||
"subURI": func() (interface{}, error) { return s.GetSubURI() },
|
||||
}
|
||||
|
||||
result := make(map[string]interface{})
|
||||
|
||||
for key, fn := range settings {
|
||||
value, err := fn()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
result[key] = value
|
||||
}
|
||||
|
||||
if result["subEnable"].(bool) && result["subURI"].(string) == "" {
|
||||
subURI := ""
|
||||
subPort, _ := s.GetSubPort()
|
||||
subPath, _ := s.GetSubPath()
|
||||
subDomain, _ := s.GetSubDomain()
|
||||
subKeyFile, _ := s.GetSubKeyFile()
|
||||
subCertFile, _ := s.GetSubCertFile()
|
||||
subTLS := false
|
||||
if subKeyFile != "" && subCertFile != "" {
|
||||
subTLS = true
|
||||
}
|
||||
if subDomain == "" {
|
||||
subDomain = strings.Split(host, ":")[0]
|
||||
}
|
||||
if subTLS {
|
||||
subURI = "https://"
|
||||
} else {
|
||||
subURI = "http://"
|
||||
}
|
||||
if (subPort == 443 && subTLS) || (subPort == 80 && !subTLS) {
|
||||
subURI += subDomain
|
||||
} else {
|
||||
subURI += fmt.Sprintf("%s:%d", subDomain, subPort)
|
||||
}
|
||||
if subPath[0] == byte('/') {
|
||||
subURI += subPath
|
||||
} else {
|
||||
subURI += "/" + subPath
|
||||
}
|
||||
result["subURI"] = subURI
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
|
|
@ -238,6 +238,8 @@
|
|||
"privateKeyPathDesc" = "Fill in an absolute path starting with."
|
||||
"panelUrlPath" = "Panel URL Root Path"
|
||||
"panelUrlPathDesc" = "Must start with '/' and end with."
|
||||
"pageSize" = "Pagination size"
|
||||
"pageSizeDesc" = "Define page size for inbounds table. Set 0 to disable"
|
||||
"oldUsername" = "Current Username"
|
||||
"currentPassword" = "Current Password"
|
||||
"newUsername" = "New Username"
|
||||
|
@ -285,6 +287,8 @@
|
|||
"subEncryptDesc" = "Encrypt the returned configs in subscription"
|
||||
"subShowInfo" = "Show usage info"
|
||||
"subShowInfoDesc" = "Show remianed traffic and date after config name"
|
||||
"subURI" = "Reverse Proxy URI"
|
||||
"subURIDesc" = "Change base URI of subscription URL for using on behind of proxies"
|
||||
|
||||
[pages.xray]
|
||||
"title" = "Xray Settings"
|
||||
|
|
|
@ -238,6 +238,8 @@
|
|||
"privateKeyPathDesc" = "Complete con una ruta absoluta que comience con."
|
||||
"panelUrlPath" = "Ruta Raíz de la URL del Panel"
|
||||
"panelUrlPathDesc" = "Debe empezar con '/' y terminar con."
|
||||
"pageSize" = "Tamaño de paginación"
|
||||
"pageSizeDesc" = "Defina el tamaño de página para la tabla de entradas. Establezca 0 para desactivar"
|
||||
"oldUsername" = "Nombre de Usuario Actual"
|
||||
"currentPassword" = "Contraseña Actual"
|
||||
"newUsername" = "Nuevo Nombre de Usuario"
|
||||
|
@ -285,6 +287,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."
|
||||
"subURI" = "URI de proxy inverso"
|
||||
"subURIDesc" = "Cambiar el URI base de la URL de suscripción para usar detrás de los servidores proxy"
|
||||
|
||||
[pages.xray]
|
||||
"title" = "Xray Configuración"
|
||||
|
|
|
@ -238,6 +238,8 @@
|
|||
"privateKeyPathDesc" = "باید یک مسیر مطلق باشد که با / شروع می شود "
|
||||
"panelUrlPath" = "آدرس روت پنل"
|
||||
"panelUrlPathDesc" = "باید با '/' شروع شود و با '/' تمام شود"
|
||||
"pageSize" = "اندازه صفحه بندی جدول"
|
||||
"pageSizeDesc" = "اندازه صفحه را برای جدول سرویس ها تعریف کنید. 0: غیرفعال"
|
||||
"oldUsername" = "نام کاربری فعلی"
|
||||
"currentPassword" = "رمز عبور فعلی"
|
||||
"newUsername" = "نام کاربری جدید"
|
||||
|
@ -285,6 +287,8 @@
|
|||
"subEncryptDesc" = "رمزگذاری کانفیگ های بازگشتی سابسکریپشن"
|
||||
"subShowInfo" = "نمایش اطلاعات مصرف"
|
||||
"subShowInfoDesc" = "ترافیک و زمان باقیمانده را در هر کانفیگ نمایش میدهد"
|
||||
"subURI" = "آدرس پایه پروکسی معکوس"
|
||||
"subURIDesc" = "آدرس پایه سابسکریپشن را برای استفاده در پشت پراکسی ها تغییر میدهد"
|
||||
|
||||
[pages.xray]
|
||||
"title" = "الگوها"
|
||||
|
|
|
@ -233,11 +233,13 @@
|
|||
"panelPort" = "Порт панели"
|
||||
"panelPortDesc" = "Порт, используемый для отображения этой панели"
|
||||
"publicKeyPath" = "Путь к файлу публичного ключа сертификата панели"
|
||||
"publicKeyPathDesc" = "Введите полный путь, начинающийся с "
|
||||
"publicKeyPathDesc" = "Введите полный путь, начинающийся с"
|
||||
"privateKeyPath" = "Путь к файлу приватного ключа сертификата панели"
|
||||
"privateKeyPathDesc" = "Введите полный путь, начинающийся с "
|
||||
"privateKeyPathDesc" = "Введите полный путь, начинающийся с"
|
||||
"panelUrlPath" = "Корневой путь URL адреса панели"
|
||||
"panelUrlPathDesc" = "Должен начинаться с '/' и заканчиваться на "
|
||||
"panelUrlPathDesc" = "Должен начинаться с '/' и заканчиваться на"
|
||||
"pageSize" = "Размер нумерации страниц"
|
||||
"pageSizeDesc" = "Определить размер страницы для входящей таблицы. Установите 0, чтобы отключить"
|
||||
"oldUsername" = "Текущее имя пользователя"
|
||||
"currentPassword" = "Текущий пароль"
|
||||
"newUsername" = "Новое имя пользователя"
|
||||
|
@ -285,6 +287,8 @@
|
|||
"subEncryptDesc" = "Шифровать возвращенные конфиги в подписке"
|
||||
"subShowInfo" = "Показать информацию об использовании"
|
||||
"subShowInfoDesc" = "Показывать восстановленный трафик и дату после имени конфигурации"
|
||||
"subURI" = "URI обратного прокси"
|
||||
"subURIDesc" = "Изменить базовый URI URL-адреса подписки для использования за прокси-серверами"
|
||||
|
||||
[pages.xray]
|
||||
"title" = "Xray Настройки"
|
||||
|
|
|
@ -238,6 +238,8 @@
|
|||
"privateKeyPathDesc" = "Điền vào đường dẫn tuyệt đối bắt đầu với."
|
||||
"panelUrlPath" = "Đường dẫn gốc URL Bảng điều khiển"
|
||||
"panelUrlPathDesc" = "Phải bắt đầu bằng '/' và kết thúc bằng."
|
||||
"pageSize" = "Kích thước phân trang"
|
||||
"pageSizeDesc" = "Xác định kích thước trang cho bảng gửi đến. Đặt 0 để tắt"
|
||||
"oldUsername" = "Tên người dùng hiện tại"
|
||||
"currentPassword" = "Mật khẩu hiện tại"
|
||||
"newUsername" = "Tên người dùng mới"
|
||||
|
@ -285,6 +287,8 @@
|
|||
"subEncryptDesc" = "Mã hóa các cấu hình được trả về trong đă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"
|
||||
"subURI" = "URI proxy ngược"
|
||||
"subURIDesc" = "Thay đổi URI cơ sở của URL đăng ký để sử dụng ở phía sau proxy"
|
||||
|
||||
[pages.xray]
|
||||
"title" = "Xray Cài đặt"
|
||||
|
|
|
@ -238,6 +238,8 @@
|
|||
"privateKeyPathDesc" = "填写一个 '/' 开头的绝对路径"
|
||||
"panelUrlPath" = "面板 url 根路径"
|
||||
"panelUrlPathDesc" = "必须以 '/' 开头,以 '/' 结尾"
|
||||
"pageSize" = "分页大小"
|
||||
"pageSizeDesc" = "定义入站表的页面大小。设置 0 表示禁用"
|
||||
"oldUsername" = "原用户名"
|
||||
"currentPassword" = "原密码"
|
||||
"newUsername" = "新用户名"
|
||||
|
@ -285,6 +287,8 @@
|
|||
"subEncryptDesc" = "在订阅中加密返回的配置"
|
||||
"subShowInfo" = "显示使用信息"
|
||||
"subShowInfoDesc" = "在配置名称后显示剩余流量和日期"
|
||||
"subURI" = "反向代理 URI"
|
||||
"subURIDesc" = "更改订阅 URL 的基本 URI 以在代理后面使用"
|
||||
|
||||
[pages.xray]
|
||||
"title" = "Xray 设置"
|
||||
|
|
Loading…
Reference in a new issue