mirror of
				https://github.com/MHSanaei/3x-ui.git
				synced 2025-10-27 10:30:08 +00:00 
			
		
		
		
	Compare commits
	
		
			No commits in common. "99c79d4056a1efb636010739f56bb1c623f47634" and "db7e7dcd2956722a7c39306d59be55f4bc0c1a42" have entirely different histories.
		
	
	
		
			99c79d4056
			...
			db7e7dcd29
		
	
		
					 5 changed files with 15 additions and 35 deletions
				
			
		|  | @ -50,11 +50,7 @@ | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   function drawQR(value) { |   function drawQR(value) { | ||||||
|     try { |     try { new QRious({ element: document.getElementById('qrcode'), value, size: 220 }); } catch (e) { console.warn(e); } | ||||||
|       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
 |   // Try to extract a human label (email/ps) from different link types
 | ||||||
|  | @ -65,18 +61,22 @@ | ||||||
|         if (json.ps) return json.ps; |         if (json.ps) return json.ps; | ||||||
|         if (json.add && json.id) return json.add; // fallback host
 |         if (json.add && json.id) return json.add; // fallback host
 | ||||||
|       } else if (link.startsWith('vless://') || link.startsWith('trojan://')) { |       } else if (link.startsWith('vless://') || link.startsWith('trojan://')) { | ||||||
|  |         // vless://<id>@host:port?...#name
 | ||||||
|         const hashIdx = link.indexOf('#'); |         const hashIdx = link.indexOf('#'); | ||||||
|         if (hashIdx !== -1) return decodeURIComponent(link.substring(hashIdx + 1)); |         if (hashIdx !== -1) return decodeURIComponent(link.substring(hashIdx + 1)); | ||||||
|  |         // email sometimes in query params like sni or remark
 | ||||||
|         const qIdx = link.indexOf('?'); |         const qIdx = link.indexOf('?'); | ||||||
|         if (qIdx !== -1) { |         if (qIdx !== -1) { | ||||||
|           const qs = new URL('http://x/?' + link.substring(qIdx + 1, hashIdx !== -1 ? hashIdx : undefined)).searchParams; |           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('remark')) return qs.get('remark'); | ||||||
|           if (qs.get('email')) return qs.get('email'); |           if (qs.get('email')) return qs.get('email'); | ||||||
|         } |         } | ||||||
|  |         // else take user@host
 | ||||||
|         const at = link.indexOf('@'); |         const at = link.indexOf('@'); | ||||||
|         const protSep = link.indexOf('://'); |         const protSep = link.indexOf('://'); | ||||||
|         if (at !== -1 && protSep !== -1) return link.substring(protSep + 3, at); |         if (at !== -1 && protSep !== -1) return link.substring(protSep + 3, at); | ||||||
|       } else if (link.startsWith('ss://')) { |       } else if (link.startsWith('ss://')) { | ||||||
|  |         // shadowsocks: label often after #
 | ||||||
|         const hashIdx = link.indexOf('#'); |         const hashIdx = link.indexOf('#'); | ||||||
|         if (hashIdx !== -1) return decodeURIComponent(link.substring(hashIdx + 1)); |         if (hashIdx !== -1) return decodeURIComponent(link.substring(hashIdx + 1)); | ||||||
|       } |       } | ||||||
|  | @ -96,13 +96,14 @@ | ||||||
|     }, |     }, | ||||||
|     async mounted() { |     async mounted() { | ||||||
|       this.lang = LanguageManager.getLanguage(); |       this.lang = LanguageManager.getLanguage(); | ||||||
|  |       // Discover subJsonUrl if provided via template bootstrap
 | ||||||
|       const tpl = document.getElementById('subscription-data'); |       const tpl = document.getElementById('subscription-data'); | ||||||
|       const sj = tpl ? tpl.getAttribute('data-subjson-url') : ''; |       const sj = tpl ? tpl.getAttribute('data-subjson-url') : ''; | ||||||
|       if (sj) this.app.subJsonUrl = sj; |       if (sj) this.app.subJsonUrl = sj; | ||||||
|       drawQR(this.app.subUrl); |       drawQR(this.app.subUrl); | ||||||
|       try { |       // Draw second QR if available
 | ||||||
|         new QRious({ element: document.getElementById('qrcode-subjson'), value: this.app.subJsonUrl || '', size: 220 }); |       try { new QRious({ element: document.getElementById('qrcode-subjson'), value: this.app.subJsonUrl || '', size: 220 }); } catch (e) { /* ignore */ } | ||||||
|       } catch (e) { /* ignore */ } |       // Track viewport width for responsive behavior
 | ||||||
|       this._onResize = () => { this.viewportWidth = window.innerWidth; }; |       this._onResize = () => { this.viewportWidth = window.innerWidth; }; | ||||||
|       window.addEventListener('resize', this._onResize); |       window.addEventListener('resize', this._onResize); | ||||||
|     }, |     }, | ||||||
|  | @ -110,33 +111,15 @@ | ||||||
|       if (this._onResize) window.removeEventListener('resize', this._onResize); |       if (this._onResize) window.removeEventListener('resize', this._onResize); | ||||||
|     }, |     }, | ||||||
|     computed: { |     computed: { | ||||||
|       isMobile() { |       isMobile() { return this.viewportWidth < 576; }, | ||||||
|         return this.viewportWidth < 576; |       isUnlimited() { return !this.app.totalByte; }, | ||||||
|       }, |  | ||||||
|       isUnlimited() { |  | ||||||
|         return !this.app.totalByte; |  | ||||||
|       }, |  | ||||||
|       isActive() { |       isActive() { | ||||||
|         const now = Date.now(); |         const now = Date.now(); | ||||||
|         const expiryOk = !this.app.expireMs || this.app.expireMs >= now; |         const expiryOk = !this.app.expireMs || this.app.expireMs >= now; | ||||||
|         const trafficOk = !this.app.totalByte || (this.app.uploadByte + this.app.downloadByte) <= this.app.totalByte; |         const trafficOk = !this.app.totalByte || (this.app.uploadByte + this.app.downloadByte) <= this.app.totalByte; | ||||||
|         return expiryOk && trafficOk; |         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 + '" }}'; } }, | ||||||
|   }); |   }); | ||||||
| })(); | })(); | ||||||
|  |  | ||||||
|  | @ -37,7 +37,7 @@ | ||||||
|     <template slot="content" > |     <template slot="content" > | ||||||
|       {{ i18n "lastOnline" }}: [[ formatLastOnline(client.email) ]] |       {{ i18n "lastOnline" }}: [[ formatLastOnline(client.email) ]] | ||||||
|     </template> |     </template> | ||||||
|     <template v-if="client.enable && isClientOnline(client.email)"> |     <template v-if="client.enable && isClientOnline(client.email) && !isClientDepleted"> | ||||||
|       <a-tag color="green">{{ i18n "online" }}</a-tag> |       <a-tag color="green">{{ i18n "online" }}</a-tag> | ||||||
|     </template> |     </template> | ||||||
|     <template v-else> |     <template v-else> | ||||||
|  | @ -51,7 +51,7 @@ | ||||||
|       <template slot="title"> |       <template slot="title"> | ||||||
|         <template v-if="isClientDepleted">{{ i18n "depleted" }}</template> |         <template v-if="isClientDepleted">{{ i18n "depleted" }}</template> | ||||||
|         <template v-if="!isClientDepleted && !client.enable">{{ i18n "disabled" }}</template> |         <template v-if="!isClientDepleted && !client.enable">{{ i18n "disabled" }}</template> | ||||||
|         <template v-if="client.enable && isClientOnline(client.email)">{{ i18n "online" }}</template> |         <template v-if="!isClientDepleted && client.enable && isClientOnline(client.email)">{{ i18n "online" }}</template> | ||||||
|       </template> |       </template> | ||||||
|       <a-badge :class="isClientOnline(client.email)? 'online-animation' : ''" :color="client.enable ? statsExpColor(record, client.email) : themeSwitcher.isDarkTheme ? '#2c3950' : '#bcbcbc'"></a-badge> |       <a-badge :class="isClientOnline(client.email)? 'online-animation' : ''" :color="client.enable ? statsExpColor(record, client.email) : themeSwitcher.isDarkTheme ? '#2c3950' : '#bcbcbc'"></a-badge> | ||||||
|     </a-tooltip> |     </a-tooltip> | ||||||
|  |  | ||||||
|  | @ -233,7 +233,7 @@ | ||||||
|                                         <a-menu slot="overlay" |                                         <a-menu slot="overlay" | ||||||
|                                             :class="themeSwitcher.currentTheme"> |                                             :class="themeSwitcher.currentTheme"> | ||||||
|                                             <a-menu-item key="ios-shadowrocket" |                                             <a-menu-item key="ios-shadowrocket" | ||||||
|                                                 @click="open(shadowrocketUrl)">Shadowrocket</a-menu-item> |                                                 @click="open('shadowrocket://add/subscription?url=' + encodeURIComponent(app.subUrl) + '&remark=' + encodeURIComponent(app.sId))">Shadowrocket</a-menu-item> | ||||||
|                                             <a-menu-item key="ios-v2box" |                                             <a-menu-item key="ios-v2box" | ||||||
|                                                 @click="open('v2box://install-sub?url=' + encodeURIComponent(app.subUrl) + '&name=' + encodeURIComponent(app.sId))">V2Box</a-menu-item> |                                                 @click="open('v2box://install-sub?url=' + encodeURIComponent(app.subUrl) + '&name=' + encodeURIComponent(app.sId))">V2Box</a-menu-item> | ||||||
|                                             <a-menu-item key="ios-streisand" |                                             <a-menu-item key="ios-streisand" | ||||||
|  |  | ||||||
|  | @ -1953,7 +1953,6 @@ func (s *InboundService) GetClientTrafficByEmail(email string) (traffic *xray.Cl | ||||||
| 	if t != nil && client != nil { | 	if t != nil && client != nil { | ||||||
| 		t.Enable = client.Enable | 		t.Enable = client.Enable | ||||||
| 		t.SubId = client.SubID | 		t.SubId = client.SubID | ||||||
| 		t.UUID = client.ID |  | ||||||
| 		return t, nil | 		return t, nil | ||||||
| 	} | 	} | ||||||
| 	return nil, nil | 	return nil, nil | ||||||
|  | @ -1995,7 +1994,6 @@ func (s *InboundService) GetClientTrafficByID(id string) ([]xray.ClientTraffic, | ||||||
| 		if ct, client, e := s.GetClientByEmail(traffics[i].Email); e == nil && ct != nil && client != nil { | 		if ct, client, e := s.GetClientByEmail(traffics[i].Email); e == nil && ct != nil && client != nil { | ||||||
| 			traffics[i].Enable = client.Enable | 			traffics[i].Enable = client.Enable | ||||||
| 			traffics[i].SubId = client.SubID | 			traffics[i].SubId = client.SubID | ||||||
| 			traffics[i].UUID = client.ID |  | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	return traffics, err | 	return traffics, err | ||||||
|  |  | ||||||
|  | @ -5,7 +5,6 @@ type ClientTraffic struct { | ||||||
| 	InboundId  int    `json:"inboundId" form:"inboundId"` | 	InboundId  int    `json:"inboundId" form:"inboundId"` | ||||||
| 	Enable     bool   `json:"enable" form:"enable"` | 	Enable     bool   `json:"enable" form:"enable"` | ||||||
| 	Email      string `json:"email" form:"email" gorm:"unique"` | 	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:"-"` | 	SubId      string `json:"subId" form:"subId" gorm:"-"` | ||||||
| 	Up         int64  `json:"up" form:"up"` | 	Up         int64  `json:"up" form:"up"` | ||||||
| 	Down       int64  `json:"down" form:"down"` | 	Down       int64  `json:"down" form:"down"` | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue