feat(sub): add per-client subscription HWID limits (#5802)

* feat(sub): add per-client subscription HWID limits

* fix(sub): address HWID review on shared subId and bulk create

* fix(sub): store HWID devices by sub_id and drop anchor client workaround

* fix(sub): restore UA auto-detect and HTML page routing in subs()

The cherry-pick of the HWID gate onto main's refactored SUBController
had dropped main's UA-based format auto-detection and sub-page handling
from subs(). Restore those branches, slotting enforceHwid after the
HTML page and before format detection so the gate only applies to
machine-readable subscription bodies.

Also adapt tests to main's options-struct constructor and to the
ClientService.Update signature extended with limitHwid.

* fix(frontend): drop axios from HttpUtil.delete

The bulk-delete rework's committed version still referenced axios,
which this file no longer imports, breaking typecheck in CI. Use the
httpRequest wrapper like the other verbs.

---------

Co-authored-by: Sanaei <ho3ein.sanaei@gmail.com>
This commit is contained in:
Rouzbeh†
2026-08-15 18:20:20 +03:30
committed by GitHub
parent 1793a9b8b4
commit 694ad6deae
45 changed files with 1212 additions and 50 deletions
+25 -15
View File
@@ -24,6 +24,7 @@ type ClientSlim struct {
TotalGB int64 `json:"totalGB"`
ExpiryTime int64 `json:"expiryTime"`
LimitIP int `json:"limitIp"`
LimitHwid int `json:"limitHwid"`
Reset int `json:"reset"`
Group string `json:"group,omitempty"`
Comment string `json:"comment,omitempty"`
@@ -457,21 +458,11 @@ func (q clientQuery) pageRows(params ClientPageParams, onlines []string, offset,
if rec == nil {
continue
}
items = append(items, ClientSlim{
Email: rec.Email,
SubID: rec.SubID,
Enable: rec.Enable,
TotalGB: rec.TotalGB,
ExpiryTime: rec.ExpiryTime,
LimitIP: rec.LimitIP,
Reset: rec.Reset,
Group: rec.Group,
Comment: rec.Comment,
InboundIds: attachments[rec.Id],
Traffic: trafficByEmail[rec.Email],
CreatedAt: rec.CreatedAt,
UpdatedAt: rec.UpdatedAt,
})
items = append(items, toClientSlim(ClientWithAttachments{
ClientRecord: *rec,
InboundIds: attachments[rec.Id],
Traffic: trafficByEmail[rec.Email],
}))
}
return items, nil
}
@@ -604,6 +595,25 @@ func sqlInt(v int64) string {
return strconv.FormatInt(v, 10)
}
func toClientSlim(c ClientWithAttachments) ClientSlim {
return ClientSlim{
Email: c.Email,
SubID: c.SubID,
Enable: c.Enable,
TotalGB: c.TotalGB,
ExpiryTime: c.ExpiryTime,
LimitIP: c.LimitIP,
LimitHwid: c.LimitHwid,
Reset: c.Reset,
Group: c.Group,
Comment: c.Comment,
InboundIds: c.InboundIds,
Traffic: c.Traffic,
CreatedAt: c.CreatedAt,
UpdatedAt: c.UpdatedAt,
}
}
// escapeLikeLiteral neutralises LIKE wildcards so searching for "a_b" keeps
// matching literally, the way strings.Contains did.
func escapeLikeLiteral(s string) string {