Browse Source

pagination and sub URI support #1300

Alireza Ahmadi 1 năm trước cách đây
mục cha
commit
5e47b4e949

+ 4 - 37
web/controller/setting.go

@@ -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 := 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, err := a.settingService.GetDefaultSettings(c.Request.Host)
+	if err != nil {
+		jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
+		return
 	}
-	result["subTLS"] = subTLS
-
-	delete(result, "subKeyFile")
-	delete(result, "subCertFile")
-
 	jsonObj(c, result, nil)
 }
 

+ 8 - 0
web/entity/entity.go

@@ -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 {

+ 1 - 2
web/html/common/qrcode_modal.html

@@ -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() {

+ 3 - 3
web/html/xui/component/setting.html

@@ -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>

+ 1 - 2
web/html/xui/inbound_info_modal.html

@@ -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;
         }
     };
 

+ 28 - 12
web/html/xui/inbounds.html

@@ -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;
             }

+ 2 - 0
web/html/xui/settings.html

@@ -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>

+ 68 - 0
web/service/setting.go

@@ -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
+}

+ 4 - 0
web/translation/translate.en_US.toml

@@ -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"

+ 4 - 0
web/translation/translate.es_ES.toml

@@ -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"

+ 4 - 0
web/translation/translate.fa_IR.toml

@@ -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" = "الگوها"

+ 7 - 3
web/translation/translate.ru_RU.toml

@@ -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 Настройки"

+ 4 - 0
web/translation/translate.vi_VN.toml

@@ -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"

+ 4 - 0
web/translation/translate.zh_Hans.toml

@@ -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 设置"