mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-09-16 15:17:14 +00:00
Merge remote-tracking branch 'upstream/main' into sync-3.6.0
# Conflicts: # .github/workflows/claude-bot.yml # .github/workflows/release.yml # DockerInit.sh # frontend/package-lock.json # frontend/package.json # frontend/src/hooks/useClients.ts # frontend/src/layouts/AppSidebar.tsx # frontend/src/main.tsx # internal/config/version # internal/database/model/model.go # internal/web/service/client_wireguard.go # internal/web/service/inbound.go
This commit is contained in:
@@ -55,6 +55,7 @@ type Inbound struct {
|
||||
Enable bool `json:"enable" form:"enable" gorm:"index:idx_enable_traffic_reset,priority:1" example:"true"` // Whether the inbound is enabled
|
||||
ExpiryTime int64 `json:"expiryTime" form:"expiryTime"` // Expiration timestamp
|
||||
TrafficReset string `json:"trafficReset" form:"trafficReset" gorm:"default:never;index:idx_enable_traffic_reset,priority:2" validate:"omitempty,oneof=never hourly daily weekly monthly"` // Traffic reset schedule
|
||||
TrafficResetDay int `json:"trafficResetDay" form:"trafficResetDay" gorm:"default:1" validate:"omitempty,gte=1,lte=31" example:"1"` // Day of month for monthly traffic resets
|
||||
LastTrafficResetTime int64 `json:"lastTrafficResetTime" form:"lastTrafficResetTime" gorm:"default:0"` // Last traffic reset timestamp
|
||||
ClientStats []xray.ClientTraffic `gorm:"foreignKey:InboundId;references:Id" json:"clientStats" form:"clientStats"` // Client traffic statistics
|
||||
|
||||
@@ -230,6 +231,57 @@ func jsonStringFieldFromRaw(r json.RawMessage) string {
|
||||
return string(trimmed)
|
||||
}
|
||||
|
||||
// hysteriaConfigVersion is the only hysteria version xray-core builds. Both
|
||||
// the protocol settings and the transport settings answer anything else with
|
||||
// "version != 2", and that error rejects the whole config — every other
|
||||
// inbound on the server goes down with it, not just the hysteria one.
|
||||
const hysteriaConfigVersion = 2
|
||||
|
||||
// HealHysteriaVersion pins a hysteria inbound's settings.version to the
|
||||
// version xray-core accepts. Rows written before the panel settled on v2, or
|
||||
// through the API and the raw JSON editor, can still carry the legacy 1 or no
|
||||
// version at all, either of which stops the core from starting.
|
||||
func HealHysteriaVersion(settings string) (string, bool) {
|
||||
return healVersionField(settings, nil)
|
||||
}
|
||||
|
||||
// HealHysteriaStreamVersion does the same for the transport half,
|
||||
// streamSettings.hysteriaSettings.version, which xray-core validates
|
||||
// separately. An absent hysteriaSettings object is left alone.
|
||||
func HealHysteriaStreamVersion(streamSettings string) (string, bool) {
|
||||
return healVersionField(streamSettings, []string{"hysteriaSettings"})
|
||||
}
|
||||
|
||||
// healVersionField rewrites the "version" key of the object reached by path to
|
||||
// hysteriaConfigVersion, reporting whether anything changed. A path that does
|
||||
// not resolve to an object leaves the input untouched.
|
||||
func healVersionField(raw string, path []string) (string, bool) {
|
||||
if raw == "" {
|
||||
return raw, false
|
||||
}
|
||||
var parsed map[string]any
|
||||
if err := json.Unmarshal([]byte(raw), &parsed); err != nil {
|
||||
return raw, false
|
||||
}
|
||||
target := parsed
|
||||
for _, key := range path {
|
||||
next, ok := target[key].(map[string]any)
|
||||
if !ok {
|
||||
return raw, false
|
||||
}
|
||||
target = next
|
||||
}
|
||||
if version, ok := target["version"].(float64); ok && version == hysteriaConfigVersion {
|
||||
return raw, false
|
||||
}
|
||||
target["version"] = hysteriaConfigVersion
|
||||
out, err := json.MarshalIndent(parsed, "", " ")
|
||||
if err != nil {
|
||||
return raw, false
|
||||
}
|
||||
return string(out), true
|
||||
}
|
||||
|
||||
// StripInboundXhttpClientFields removes xHTTP knobs that belong on the
|
||||
// client dialer and subscription share-link extras only. xray-core's XHTTP
|
||||
// inbound listener does not consume them; the panel still stores them on
|
||||
@@ -299,11 +351,20 @@ func (i *Inbound) GenXrayInboundConfig() *xray.InboundConfig {
|
||||
if converted, ok := WireguardClientsToPeers(settings); ok {
|
||||
settings = converted
|
||||
}
|
||||
case Hysteria:
|
||||
if healed, ok := HealHysteriaVersion(settings); ok {
|
||||
settings = healed
|
||||
}
|
||||
}
|
||||
streamSettings := i.StreamSettings
|
||||
if stripped, ok := StripInboundXhttpClientFields(streamSettings); ok {
|
||||
streamSettings = stripped
|
||||
}
|
||||
if i.Protocol == Hysteria {
|
||||
if healed, ok := HealHysteriaStreamVersion(streamSettings); ok {
|
||||
streamSettings = healed
|
||||
}
|
||||
}
|
||||
return &xray.InboundConfig{
|
||||
Listen: json_util.RawMessage(listen),
|
||||
Port: i.Port,
|
||||
@@ -847,12 +908,15 @@ type ClientRecord struct {
|
||||
TotalGB int64 `json:"totalGB" gorm:"column:total_gb"`
|
||||
ExpiryTime int64 `json:"expiryTime" gorm:"column:expiry_time"`
|
||||
Enable bool `json:"enable" gorm:"default:true"`
|
||||
TgID int64 `json:"tgId" gorm:"column:tg_id"`
|
||||
TgID int64 `json:"tgId" gorm:"column:tg_id;index:idx_clients_tg_id"`
|
||||
Group string `json:"group" gorm:"column:group_name;default:'';index:idx_client_record_group"`
|
||||
Comment string `json:"comment"`
|
||||
Reset int `json:"reset" gorm:"default:0"`
|
||||
CreatedAt int64 `json:"createdAt" gorm:"autoCreateTime:milli"`
|
||||
UpdatedAt int64 `json:"updatedAt" gorm:"autoUpdateTime:milli"`
|
||||
// Owned solely by the node-snapshot sweep, which soft-orphans instead of
|
||||
// deleting; orphans from any other cause stay at zero and are never reaped.
|
||||
SyncOrphanedAt int64 `json:"-" gorm:"column:sync_orphaned_at;default:0"`
|
||||
}
|
||||
|
||||
func (ClientRecord) TableName() string { return "clients" }
|
||||
|
||||
Reference in New Issue
Block a user