3 Achegas db7e7dcd29 ... 99c79d4056

Autor SHA1 Mensaxe Data
  mhsanaei 99c79d4056 fix: online hai 19 horas
  RahGozar fcdeb1fc79 feat: add UUID to ClientTraffic (#3491) hai 22 horas
  Harry NG 0a58b5e745 Fix: Shadowrocket link using base64 encoding (#3489) hai 22 horas

+ 29 - 12
web/assets/js/subscription.js

@@ -50,7 +50,11 @@
   }
 
   function drawQR(value) {
-    try { new QRious({ element: document.getElementById('qrcode'), value, size: 220 }); } catch (e) { console.warn(e); }
+    try {
+      new QRious({ element: document.getElementById('qrcode'), value, size: 220 });
+    } catch (e) {
+      console.warn(e);
+    }
   }
 
   // Try to extract a human label (email/ps) from different link types
@@ -61,22 +65,18 @@
         if (json.ps) return json.ps;
         if (json.add && json.id) return json.add; // fallback host
       } else if (link.startsWith('vless://') || link.startsWith('trojan://')) {
-        // vless://<id>@host:port?...#name
         const hashIdx = link.indexOf('#');
         if (hashIdx !== -1) return decodeURIComponent(link.substring(hashIdx + 1));
-        // email sometimes in query params like sni or remark
         const qIdx = link.indexOf('?');
         if (qIdx !== -1) {
           const qs = new URL('http://x/?' + link.substring(qIdx + 1, hashIdx !== -1 ? hashIdx : undefined)).searchParams;
           if (qs.get('remark')) return qs.get('remark');
           if (qs.get('email')) return qs.get('email');
         }
-        // else take user@host
         const at = link.indexOf('@');
         const protSep = link.indexOf('://');
         if (at !== -1 && protSep !== -1) return link.substring(protSep + 3, at);
       } else if (link.startsWith('ss://')) {
-        // shadowsocks: label often after #
         const hashIdx = link.indexOf('#');
         if (hashIdx !== -1) return decodeURIComponent(link.substring(hashIdx + 1));
       }
@@ -96,14 +96,13 @@
     },
     async mounted() {
       this.lang = LanguageManager.getLanguage();
-      // Discover subJsonUrl if provided via template bootstrap
       const tpl = document.getElementById('subscription-data');
       const sj = tpl ? tpl.getAttribute('data-subjson-url') : '';
       if (sj) this.app.subJsonUrl = sj;
       drawQR(this.app.subUrl);
-      // Draw second QR if available
-      try { new QRious({ element: document.getElementById('qrcode-subjson'), value: this.app.subJsonUrl || '', size: 220 }); } catch (e) { /* ignore */ }
-      // Track viewport width for responsive behavior
+      try {
+        new QRious({ element: document.getElementById('qrcode-subjson'), value: this.app.subJsonUrl || '', size: 220 });
+      } catch (e) { /* ignore */ }
       this._onResize = () => { this.viewportWidth = window.innerWidth; };
       window.addEventListener('resize', this._onResize);
     },
@@ -111,15 +110,33 @@
       if (this._onResize) window.removeEventListener('resize', this._onResize);
     },
     computed: {
-      isMobile() { return this.viewportWidth < 576; },
-      isUnlimited() { return !this.app.totalByte; },
+      isMobile() {
+        return this.viewportWidth < 576;
+      },
+      isUnlimited() {
+        return !this.app.totalByte;
+      },
       isActive() {
         const now = Date.now();
         const expiryOk = !this.app.expireMs || this.app.expireMs >= now;
         const trafficOk = !this.app.totalByte || (this.app.uploadByte + this.app.downloadByte) <= this.app.totalByte;
         return expiryOk && trafficOk;
       },
+      shadowrocketUrl() {
+        const rawUrl = this.app.subUrl + '?flag=shadowrocket';
+        const base64Url = btoa(rawUrl);
+        const remark = encodeURIComponent(this.app.sId || 'Subscription');
+        return `shadowrocket://add/sub/${base64Url}?remark=${remark}`;
+      }
+    },
+    methods: {
+      renderLink,
+      copy,
+      open,
+      linkName,
+      i18nLabel(key) {
+        return '{{ i18n "' + key + '" }}';
+      },
     },
-    methods: { renderLink, copy, open, linkName, i18nLabel(key) { return '{{ i18n "' + key + '" }}'; } },
   });
 })();

+ 2 - 2
web/html/component/aClientTable.html

@@ -37,7 +37,7 @@
     <template slot="content" >
       {{ i18n "lastOnline" }}: [[ formatLastOnline(client.email) ]]
     </template>
-    <template v-if="client.enable && isClientOnline(client.email) && !isClientDepleted">
+    <template v-if="client.enable && isClientOnline(client.email)">
       <a-tag color="green">{{ i18n "online" }}</a-tag>
     </template>
     <template v-else>
@@ -51,7 +51,7 @@
       <template slot="title">
         <template v-if="isClientDepleted">{{ i18n "depleted" }}</template>
         <template v-if="!isClientDepleted && !client.enable">{{ i18n "disabled" }}</template>
-        <template v-if="!isClientDepleted && client.enable && isClientOnline(client.email)">{{ i18n "online" }}</template>
+        <template v-if="client.enable && isClientOnline(client.email)">{{ i18n "online" }}</template>
       </template>
       <a-badge :class="isClientOnline(client.email)? 'online-animation' : ''" :color="client.enable ? statsExpColor(record, client.email) : themeSwitcher.isDarkTheme ? '#2c3950' : '#bcbcbc'"></a-badge>
     </a-tooltip>

+ 1 - 1
web/html/subscription.html

@@ -233,7 +233,7 @@
                                         <a-menu slot="overlay"
                                             :class="themeSwitcher.currentTheme">
                                             <a-menu-item key="ios-shadowrocket"
-                                                @click="open('shadowrocket://add/subscription?url=' + encodeURIComponent(app.subUrl) + '&remark=' + encodeURIComponent(app.sId))">Shadowrocket</a-menu-item>
+                                                @click="open(shadowrocketUrl)">Shadowrocket</a-menu-item>
                                             <a-menu-item key="ios-v2box"
                                                 @click="open('v2box://install-sub?url=' + encodeURIComponent(app.subUrl) + '&name=' + encodeURIComponent(app.sId))">V2Box</a-menu-item>
                                             <a-menu-item key="ios-streisand"

+ 2 - 0
web/service/inbound.go

@@ -1953,6 +1953,7 @@ func (s *InboundService) GetClientTrafficByEmail(email string) (traffic *xray.Cl
 	if t != nil && client != nil {
 		t.Enable = client.Enable
 		t.SubId = client.SubID
+		t.UUID = client.ID
 		return t, nil
 	}
 	return nil, nil
@@ -1994,6 +1995,7 @@ func (s *InboundService) GetClientTrafficByID(id string) ([]xray.ClientTraffic,
 		if ct, client, e := s.GetClientByEmail(traffics[i].Email); e == nil && ct != nil && client != nil {
 			traffics[i].Enable = client.Enable
 			traffics[i].SubId = client.SubID
+			traffics[i].UUID = client.ID
 		}
 	}
 	return traffics, err

+ 1 - 0
xray/client_traffic.go

@@ -5,6 +5,7 @@ type ClientTraffic struct {
 	InboundId  int    `json:"inboundId" form:"inboundId"`
 	Enable     bool   `json:"enable" form:"enable"`
 	Email      string `json:"email" form:"email" gorm:"unique"`
+	UUID       string `json:"uuid" form:"uuid" gorm:"unique;type:char(36)"`
 	SubId      string `json:"subId" form:"subId" gorm:"-"`
 	Up         int64  `json:"up" form:"up"`
 	Down       int64  `json:"down" form:"down"`