subpage: enabled state

Track and surface a subscription's enabled state from backend to frontend so the UI can show inactive subscriptions and use it in active-state logic.

Changes:
- sub/subService.go: track hasEnabledClient, set traffic.Enable, add Enabled to PageData and populate it in BuildPageData.
- sub/subController.go: include enabled in the page context.
- web/html/settings/panel/subscription/subpage.html: emit data-enabled attribute and render an "inactive" tag when disabled.
- web/assets/js/subscription.js: read data-enabled and include it in isActive() checks.

This ensures subscriptions with no enabled clients are marked inactive in the UI and excluded from being considered active.
This commit is contained in:
MHSanaei
2026-05-04 23:27:57 +02:00
parent 6099a07ff0
commit 32b7ada549
4 changed files with 16 additions and 3 deletions

View File

@@ -138,6 +138,7 @@ func (a *SUBController) subs(c *gin.Context) {
"host": page.Host, "host": page.Host,
"base_path": page.BasePath, "base_path": page.BasePath,
"sId": page.SId, "sId": page.SId,
"enabled": page.Enabled,
"download": page.Download, "download": page.Download,
"upload": page.Upload, "upload": page.Upload,
"total": page.Total, "total": page.Total,

View File

@@ -46,6 +46,7 @@ func (s *SubService) GetSubs(subId string, host string) ([]string, int64, xray.C
var result []string var result []string
var traffic xray.ClientTraffic var traffic xray.ClientTraffic
var lastOnline int64 var lastOnline int64
var hasEnabledClient bool
var clientTraffics []xray.ClientTraffic var clientTraffics []xray.ClientTraffic
inbounds, err := s.getInboundsBySubId(subId) inbounds, err := s.getInboundsBySubId(subId)
if err != nil { if err != nil {
@@ -78,6 +79,9 @@ func (s *SubService) GetSubs(subId string, host string) ([]string, int64, xray.C
} }
for _, client := range clients { for _, client := range clients {
if client.SubID == subId { if client.SubID == subId {
if client.Enable {
hasEnabledClient = true
}
link := s.getLink(inbound, client.Email) link := s.getLink(inbound, client.Email)
result = append(result, link) result = append(result, link)
ct := s.getClientTraffics(inbound.ClientStats, client.Email) ct := s.getClientTraffics(inbound.ClientStats, client.Email)
@@ -111,6 +115,7 @@ func (s *SubService) GetSubs(subId string, host string) ([]string, int64, xray.C
} }
} }
} }
traffic.Enable = hasEnabledClient
return result, lastOnline, traffic, nil return result, lastOnline, traffic, nil
} }
@@ -1304,6 +1309,7 @@ type PageData struct {
Host string Host string
BasePath string BasePath string
SId string SId string
Enabled bool
Download string Download string
Upload string Upload string
Total string Total string
@@ -1453,6 +1459,7 @@ func (s *SubService) BuildPageData(subId string, hostHeader string, traffic xray
Host: hostHeader, Host: hostHeader,
BasePath: basePath, BasePath: basePath,
SId: subId, SId: subId,
Enabled: traffic.Enable,
Download: download, Download: download,
Upload: upload, Upload: upload,
Total: total, Total: total,

View File

@@ -7,6 +7,7 @@
const data = { const data = {
sId: el.getAttribute('data-sid') || '', sId: el.getAttribute('data-sid') || '',
enabled: (el.getAttribute('data-enabled') || '').toLowerCase() === 'true',
subUrl: el.getAttribute('data-sub-url') || '', subUrl: el.getAttribute('data-sub-url') || '',
subJsonUrl: el.getAttribute('data-subjson-url') || '', subJsonUrl: el.getAttribute('data-subjson-url') || '',
subClashUrl: el.getAttribute('data-subclash-url') || '', subClashUrl: el.getAttribute('data-subclash-url') || '',
@@ -128,9 +129,10 @@
}, },
isActive() { isActive() {
const now = Date.now(); const now = Date.now();
const enabledOk = this.app.enabled;
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 enabledOk && expiryOk && trafficOk;
}, },
shadowrocketUrl() { shadowrocketUrl() {
const rawUrl = this.app.subUrl + '?flag=shadowrocket'; const rawUrl = this.app.subUrl + '?flag=shadowrocket';

View File

@@ -153,7 +153,10 @@
app.sId app.sId
]]</a-descriptions-item> ]]</a-descriptions-item>
<a-descriptions-item label='{{ i18n "subscription.status" }}'> <a-descriptions-item label='{{ i18n "subscription.status" }}'>
<template v-if="isUnlimited"> <template v-if="!app.enabled">
<a-tag color="red">{{ i18n "subscription.inactive" }}</a-tag>
</template>
<template v-else-if="isUnlimited">
<a-tag color="purple">{{ i18n <a-tag color="purple">{{ i18n
"subscription.unlimited" }}</a-tag> "subscription.unlimited" }}</a-tag>
</template> </template>
@@ -275,7 +278,7 @@
data-subclash-url="{{ .subClashUrl }}" data-download="{{ .download }}" data-upload="{{ .upload }}" data-subclash-url="{{ .subClashUrl }}" data-download="{{ .download }}" data-upload="{{ .upload }}"
data-used="{{ .used }}" data-total="{{ .total }}" data-remained="{{ .remained }}" data-expire="{{ .expire }}" data-used="{{ .used }}" data-total="{{ .total }}" data-remained="{{ .remained }}" data-expire="{{ .expire }}"
data-lastonline="{{ .lastOnline }}" data-downloadbyte="{{ .downloadByte }}" data-uploadbyte="{{ .uploadByte }}" data-lastonline="{{ .lastOnline }}" data-downloadbyte="{{ .downloadByte }}" data-uploadbyte="{{ .uploadByte }}"
data-totalbyte="{{ .totalByte }}" data-datepicker="{{ .datepicker }}"></template> data-totalbyte="{{ .totalByte }}" data-datepicker="{{ .datepicker }}" data-enabled="{{ .enabled }}"></template>
<textarea id="subscription-links" style="display:none">{{ range .result }}{{ . }} <textarea id="subscription-links" style="display:none">{{ range .result }}{{ . }}
{{ end }}</textarea> {{ end }}</textarea>