CI / go-test (push) Waiting to run
CI / postgres-durable-first (push) Waiting to run
CI / codegen (push) Waiting to run
CI / govulncheck (push) Waiting to run
CI / race (push) Waiting to run
CI / fuzz-smoke (push) Waiting to run
CI / golangci (push) Waiting to run
CI / frontend (push) Waiting to run
Docs CI / build (push) Waiting to run
Docs Deploy (GitHub Pages) / build (push) Waiting to run
Docs Deploy (GitHub Pages) / deploy (push) Blocked by required conditions
Release 3X-UI / build (386) (push) Waiting to run
Release 3X-UI / build (amd64) (push) Waiting to run
Release 3X-UI / build (arm64) (push) Waiting to run
Release 3X-UI / build (armv5) (push) Waiting to run
Release 3X-UI / build (armv6) (push) Waiting to run
Release 3X-UI / build (armv7) (push) Waiting to run
Release 3X-UI / build (s390x) (push) Waiting to run
Release 3X-UI / Build for Windows (amd64) (push) Waiting to run
Release 3X-UI / Publish rolling dev release (push) Blocked by required conditions
CodeQL Advanced / Analyze (go) (push) Waiting to run
CodeQL Advanced / Analyze (actions) (push) Waiting to run
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
- 0: sing-box gen overrides fragment listen/port from the inbound row (empty listen -> "::"), Update/Add recompute the stored fragment; non-loopback E2E against the real core binary. - 1: agent fetches the sing-box core from the Gitea release when missing, self-checks it, and only then latches the switch. - 2: node list shows the sing-box core version (heartbeat field, persisted on the node row), xray version as fallback. - 3: public-page fallback returns 404 to anonymous visitors when the page is disabled. - 4: settings restart hint bar removed, dead i18n keys cleaned. - 5: two PUT calls missing JSON headers fixed (audit: only those two), batch probe-task creation endpoint + UI. - 6: cf-probe retirement card and coverage pipeline removed. - 7: generated vless links no longer carry spx (panel export + sub); parser still accepts spx; golden fixtures updated. - probe batch: dedicated probe settings tab (public-page settings, tasks, per-node meta), retention_days editable, per-node traffic calibration + stat-interface pinning, runtime metrics split into a lightweight fast report (default 3s, 1-60s adjustable). - 8: sing-box core processes start with GOMEMLIMIT=32MiB, GOGC=50. - 9: dead /getTwoFactorEnable docs entries removed (frontend call and contract entry already gone at base; verified by grep).
70 lines
2.2 KiB
Go
70 lines
2.2 KiB
Go
package controller
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
|
|
"github.com/mhsanaei/3x-ui/v3/internal/web/entity"
|
|
"github.com/mhsanaei/3x-ui/v3/internal/web/service/probenotify"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
// Item 7 B6: the notification settings surface — the notify_* block
|
|
// (offline grace, traffic step, expiry window, login alerts, the
|
|
// webhook channel and the two message templates) and a test send.
|
|
// Like the rest of the probe surfaces these endpoints answer in the
|
|
// panel's Msg envelope and take effect immediately; validation
|
|
// refusals are operator-facing text, shown verbatim. (The cf-probe
|
|
// retirement coverage card that originally rode this payload was
|
|
// removed in V2.0.1 ⑥ — the checklist lives in the operator docs.)
|
|
|
|
type ProbeNotifyController struct{}
|
|
|
|
func NewProbeNotifyController(g *gin.RouterGroup) *ProbeNotifyController {
|
|
pc := &ProbeNotifyController{}
|
|
g.GET("/probe/notify", pc.get)
|
|
g.PUT("/probe/notify", pc.put)
|
|
g.POST("/probe/notify/test", pc.test)
|
|
return pc
|
|
}
|
|
|
|
type probeNotifyPayload struct {
|
|
Settings probenotify.SettingsView `json:"settings"`
|
|
}
|
|
|
|
func (pc *ProbeNotifyController) get(c *gin.Context) {
|
|
jsonObj(c, probeNotifyPayload{
|
|
Settings: probenotify.GetSettings(),
|
|
}, nil)
|
|
}
|
|
|
|
func (pc *ProbeNotifyController) put(c *gin.Context) {
|
|
var body map[string]json.RawMessage
|
|
if err := c.ShouldBindJSON(&body); err != nil {
|
|
c.JSON(http.StatusOK, entity.Msg{Success: false, Msg: "请求格式不对"})
|
|
return
|
|
}
|
|
if err := probenotify.SaveSettings(body); err != nil {
|
|
// SaveSettings validates everything before writing anything;
|
|
// its errors are the operator's answer (a range, a malformed
|
|
// header block), not machine faults.
|
|
c.JSON(http.StatusOK, entity.Msg{Success: false, Msg: err.Error()})
|
|
return
|
|
}
|
|
// Answer with the fresh view, like the GET: the tab re-renders
|
|
// the channel status and secret-set flags from it.
|
|
jsonObj(c, probeNotifyPayload{
|
|
Settings: probenotify.GetSettings(),
|
|
}, nil)
|
|
}
|
|
|
|
func (pc *ProbeNotifyController) test(c *gin.Context) {
|
|
sent, err := probenotify.Test()
|
|
if err != nil {
|
|
c.JSON(http.StatusOK, entity.Msg{Success: false, Msg: err.Error()})
|
|
return
|
|
}
|
|
jsonObj(c, gin.H{"sent": sent}, nil)
|
|
}
|