3 Commits 3827d7d061 ... 9f80cfedab

Author SHA1 Message Date
  MHSanaei 9f80cfedab fix(sub): use standard sub://BASE64#REMARK scheme for Shadowrocket 22 hours ago
  MHSanaei 1b436bb3e0 fix(clients): honor global pageSize and widen size-changer dropdown 22 hours ago
  MHSanaei 5b5ac3f04b fix(migrate): include hysteria, hysteria2, shadowsocks in client sync 22 hours ago

+ 42 - 11
frontend/src/pages/clients/ClientsPage.vue

@@ -43,6 +43,7 @@ const {
   tgBotEnable,
   tgBotEnable,
   expireDiff,
   expireDiff,
   trafficDiff,
   trafficDiff,
+  pageSize,
   create,
   create,
   update,
   update,
   remove,
   remove,
@@ -442,6 +443,10 @@ function expiryColor(row) {
 const sortState = ref({ column: null, order: null });
 const sortState = ref({ column: null, order: null });
 const paginationState = ref({ current: 1, pageSize: 20 });
 const paginationState = ref({ current: 1, pageSize: 20 });
 
 
+watch(pageSize, (next) => {
+  if (next > 0) paginationState.value.pageSize = next;
+}, { immediate: true });
+
 function sortableCol(col, key) {
 function sortableCol(col, key) {
   return {
   return {
     ...col,
     ...col,
@@ -670,8 +675,9 @@ const columns = computed(() => [
                     </a-select>
                     </a-select>
                   </div>
                   </div>
 
 
-                  <a-table v-if="!isMobile" :columns="columns" :data-source="sortedClients" :loading="loading" row-key="email"
-                    :row-selection="rowSelection" :pagination="tablePagination" size="small" @change="onTableChange">
+                  <a-table v-if="!isMobile" :columns="columns" :data-source="sortedClients" :loading="loading"
+                    row-key="email" :row-selection="rowSelection" :pagination="tablePagination" size="small"
+                    @change="onTableChange">
                     <template #bodyCell="{ column, record }">
                     <template #bodyCell="{ column, record }">
                       <template v-if="column.key === 'email'">
                       <template v-if="column.key === 'email'">
                         <div class="email-cell">
                         <div class="email-cell">
@@ -842,6 +848,11 @@ const columns = computed(() => [
   background: var(--bg-page);
   background: var(--bg-page);
 }
 }
 
 
+.clients-page :deep(.ant-pagination-options-size-changer),
+.clients-page :deep(.ant-pagination-options-size-changer .ant-select-selector) {
+  min-width: 100px !important;
+}
+
 .clients-page.is-dark {
 .clients-page.is-dark {
   --bg-page: #1e1e1e;
   --bg-page: #1e1e1e;
   --bg-card: #252526;
   --bg-card: #252526;
@@ -874,7 +885,7 @@ const columns = computed(() => [
   margin-bottom: 8px;
   margin-bottom: 8px;
 }
 }
 
 
-.filter-bar.mobile > * {
+.filter-bar.mobile>* {
   flex: 0 0 auto;
   flex: 0 0 auto;
 }
 }
 
 
@@ -911,11 +922,25 @@ const columns = computed(() => [
   vertical-align: middle;
   vertical-align: middle;
 }
 }
 
 
-.dot-green { background: #52c41a; }
-.dot-blue { background: #1677ff; }
-.dot-red { background: #ff4d4f; }
-.dot-orange { background: #fa8c16; }
-.dot-gray { background: rgba(128, 128, 128, 0.6); }
+.dot-green {
+  background: #52c41a;
+}
+
+.dot-blue {
+  background: #1677ff;
+}
+
+.dot-red {
+  background: #ff4d4f;
+}
+
+.dot-orange {
+  background: #fa8c16;
+}
+
+.dot-gray {
+  background: rgba(128, 128, 128, 0.6);
+}
 
 
 .status-tag {
 .status-tag {
   margin: 0 0 0 4px;
   margin: 0 0 0 4px;
@@ -1050,8 +1075,6 @@ const columns = computed(() => [
 </style>
 </style>
 
 
 <style>
 <style>
-/* AD-Vue popovers teleport their content to <body>, so scoped styles
-   don't reach them — this block has to be unscoped. */
 .client-email-list {
 .client-email-list {
   max-height: 280px;
   max-height: 280px;
   min-width: 160px;
   min-width: 160px;
@@ -1059,9 +1082,17 @@ const columns = computed(() => [
   padding-right: 4px;
   padding-right: 4px;
 }
 }
 
 
-.client-email-list > div {
+.client-email-list>div {
   padding: 2px 0;
   padding: 2px 0;
   font-size: 12px;
   font-size: 12px;
   white-space: nowrap;
   white-space: nowrap;
 }
 }
+
+.ant-select-dropdown:has(.ant-select-item-option[title$="/ page"]) {
+  min-width: 110px !important;
+}
+
+.ant-select-dropdown:has(.ant-select-item-option[title$="/ page"]) .ant-select-item-option-content {
+  white-space: nowrap;
+}
 </style>
 </style>

+ 3 - 0
frontend/src/pages/clients/useClients.js

@@ -14,6 +14,7 @@ export function useClients() {
   const tgBotEnable = ref(false);
   const tgBotEnable = ref(false);
   const expireDiff = ref(0);
   const expireDiff = ref(0);
   const trafficDiff = ref(0);
   const trafficDiff = ref(0);
+  const pageSize = ref(0);
 
 
   async function refresh() {
   async function refresh() {
     loading.value = true;
     loading.value = true;
@@ -48,6 +49,7 @@ export function useClients() {
     tgBotEnable.value = !!s.tgBotEnable;
     tgBotEnable.value = !!s.tgBotEnable;
     expireDiff.value = (s.expireDiff ?? 0) * 86400000;
     expireDiff.value = (s.expireDiff ?? 0) * 86400000;
     trafficDiff.value = (s.trafficDiff ?? 0) * 1073741824;
     trafficDiff.value = (s.trafficDiff ?? 0) * 1073741824;
+    pageSize.value = s.pageSize ?? 0;
   }
   }
 
 
   async function create(payload) {
   async function create(payload) {
@@ -199,6 +201,7 @@ export function useClients() {
     tgBotEnable,
     tgBotEnable,
     expireDiff,
     expireDiff,
     trafficDiff,
     trafficDiff,
+    pageSize,
     refresh,
     refresh,
     create,
     create,
     update,
     update,

+ 1 - 1
frontend/src/pages/sub/SubPage.vue

@@ -125,7 +125,7 @@ const shadowrocketUrl = computed(() => {
   if (!subUrl) return '';
   if (!subUrl) return '';
   const separator = subUrl.includes('?') ? '&' : '?';
   const separator = subUrl.includes('?') ? '&' : '?';
   const rawUrl = subUrl + separator + 'flag=shadowrocket';
   const rawUrl = subUrl + separator + 'flag=shadowrocket';
-  const base64Url = encodeURIComponent(btoa(rawUrl));
+  const base64Url = btoa(rawUrl).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
   const remark = encodeURIComponent(subTitle || sId || 'Subscription');
   const remark = encodeURIComponent(subTitle || sId || 'Subscription');
   return `shadowrocket://add/sub/${base64Url}?remark=${remark}`;
   return `shadowrocket://add/sub/${base64Url}?remark=${remark}`;
 });
 });

+ 1 - 1
web/service/inbound.go

@@ -2845,7 +2845,7 @@ func (s *InboundService) MigrationRequirements() {
 
 
 	// Fix inbounds based problems
 	// Fix inbounds based problems
 	var inbounds []*model.Inbound
 	var inbounds []*model.Inbound
-	err = tx.Model(model.Inbound{}).Where("protocol IN (?)", []string{"vmess", "vless", "trojan"}).Find(&inbounds).Error
+	err = tx.Model(model.Inbound{}).Where("protocol IN (?)", []string{"vmess", "vless", "trojan", "shadowsocks", "hysteria", "hysteria2"}).Find(&inbounds).Error
 	if err != nil && err != gorm.ErrRecordNotFound {
 	if err != nil && err != gorm.ErrRecordNotFound {
 		return
 		return
 	}
 	}