Refresh tunnel actions promptly and clarify Feishu OIDC setup

This commit is contained in:
opsx committed 2026-10-22 00:01:12 +08:00
1 parent 41fb9d5631
commit 6a1bace3e9
10 files changed
+136 -32

No files matched your search

+3
View File
@@ -1040,12 +1040,14 @@ func (s *Server) startTunnel(c *gin.Context) {
t.State = "error"
t.LastError = err.Error()
store.DB.Save(t)
s.mgr.RemoveStatus(t.ID)
c.JSON(500, gin.H{"error": err.Error()})
return
}
t.State = "running"
t.LastError = ""
store.DB.Save(t)
s.mgr.RemoveStatus(t.ID) // the monitor's previous snapshot can lag behind this transition
store.Audit(c.GetString("username"), "start_tunnel", t.Name, "")
c.JSON(200, gin.H{"ok": true, "state": "running"})
}
@@ -1068,6 +1070,7 @@ func (s *Server) stopTunnel(c *gin.Context) {
}
t.State = "stopped"
store.DB.Save(t)
s.mgr.RemoveStatus(t.ID)
store.Audit(c.GetString("username"), "stop_tunnel", t.Name, "")
c.JSON(200, gin.H{"ok": true, "state": "stopped"})
}
+79
View File
@@ -0,0 +1,79 @@
package api
import (
"encoding/json"
"net/http"
"net/http/httptest"
"strconv"
"testing"
"github.com/gin-gonic/gin"
"vpn-platform/backend/internal/core"
"vpn-platform/backend/internal/model"
"vpn-platform/backend/internal/store"
)
type stateTestProtocol struct{}
func (stateTestProtocol) Name() string { return "state-test" }
func (stateTestProtocol) DisplayName() string { return "state-test" }
func (stateTestProtocol) Validate(cfg map[string]interface{}) (map[string]interface{}, error) {
return cfg, nil
}
func (stateTestProtocol) Create(*model.Tunnel) error { return nil }
func (stateTestProtocol) Start(*model.Tunnel) error { return nil }
func (stateTestProtocol) Stop(*model.Tunnel) error { return nil }
func (stateTestProtocol) Status(*model.Tunnel) (core.Status, error) { return core.Status{}, nil }
func (stateTestProtocol) GenerateClientConfig(*model.Tunnel) (string, error) { return "", nil }
func (stateTestProtocol) Delete(*model.Tunnel) error { return nil }
func (stateTestProtocol) Available() (bool, string) { return true, "" }
func TestStartStopDropsStaleRuntimeSnapshot(t *testing.T) {
oldDB := store.DB
t.Cleanup(func() { store.DB = oldDB })
if err := store.Init(t.TempDir()); err != nil {
t.Fatal(err)
}
line := model.Tunnel{Name: "snapshot", Protocol: "state-test", State: "running", Config: "{}"}
if err := store.DB.Create(&line).Error; err != nil {
t.Fatal(err)
}
mgr := core.NewManager()
mgr.Register(stateTestProtocol{})
s := NewServer(mgr)
for _, tc := range []struct{ operation, oldState, wantState string }{
{"stop", "running", "stopped"},
{"start", "stopped", "running"},
} {
mgr.SetStatus(line.ID, core.Status{State: tc.oldState})
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Params = gin.Params{{Key: "id", Value: strconv.FormatUint(uint64(line.ID), 10)}}
if tc.operation == "stop" {
s.stopTunnel(c)
} else {
s.startTunnel(c)
}
if w.Code != http.StatusOK {
t.Fatalf("%s returned %d: %s", tc.operation, w.Code, w.Body.String())
}
if _, ok := mgr.GetStatus(line.ID); ok {
t.Fatalf("%s left the old runtime status cached", tc.operation)
}
listResponse := httptest.NewRecorder()
listContext, _ := gin.CreateTestContext(listResponse)
s.listTunnels(listContext)
var rows []struct {
State string `json:"state"`
Runtime *core.Status `json:"runtime"`
}
if err := json.Unmarshal(listResponse.Body.Bytes(), &rows); err != nil {
t.Fatal(err)
}
if len(rows) != 1 || rows[0].State != tc.wantState || rows[0].Runtime != nil {
t.Fatalf("%s list returned stale state: %+v", tc.operation, rows)
}
}
}
+1 -1
View File
@@ -104,7 +104,7 @@ func prepareOIDC(t *model.Tunnel, cfg map[string]interface{}) error {
cfg["oidc_client_secret"] = old["oidc_client_secret"]
}
if service.Str(cfg, "oidc_client_secret") == "" {
return fmt.Errorf("oidc_client_secret 必填;请在飞书应用凭据中复制 Client Secret")
return fmt.Errorf("oidc_client_secret 必填;请从 OIDC 发行者对应的客户端凭据中复制 Client Secret")
}
for _, key := range []string{"oidc_http_secret", "oidc_management_password"} {
cfg[key] = old[key] // internal secrets are never supplied by the API caller