Skip to content

Commit 865506d

Browse files
authored
Merge pull request #727 from 0xJacky/feat/node-selector-sse
feat: node list supports sse
2 parents ed0dca6 + bc70567 commit 865506d

File tree

31 files changed

+176
-166
lines changed

31 files changed

+176
-166
lines changed

api/api.go

Lines changed: 6 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,10 @@
11
package api
22

33
import (
4-
"errors"
54
"github.com/0xJacky/Nginx-UI/model"
65
"github.com/gin-gonic/gin"
7-
"github.com/go-playground/validator/v10"
86
"github.com/uozi-tech/cosy/logger"
97
"net/http"
10-
"reflect"
11-
"regexp"
12-
"strings"
138
)
149

1510
func CurrentUser(c *gin.Context) *model.User {
@@ -23,105 +18,10 @@ func ErrHandler(c *gin.Context, err error) {
2318
})
2419
}
2520

26-
type ValidError struct {
27-
Key string
28-
Message string
29-
}
30-
31-
func BindAndValid(c *gin.Context, target interface{}) bool {
32-
err := c.ShouldBindJSON(target)
33-
if err != nil {
34-
logger.Error("bind err", err)
35-
36-
var verrs validator.ValidationErrors
37-
ok := errors.As(err, &verrs)
38-
39-
if !ok {
40-
c.JSON(http.StatusNotAcceptable, gin.H{
41-
"message": "Requested with wrong parameters",
42-
"code": http.StatusNotAcceptable,
43-
})
44-
return false
45-
}
46-
47-
t := reflect.TypeOf(target).Elem()
48-
errorsMap := make(map[string]interface{})
49-
for _, value := range verrs {
50-
var path []string
51-
52-
namespace := strings.Split(value.StructNamespace(), ".")
53-
// logger.Debug(t.Name(), namespace)
54-
if t.Name() != "" && len(namespace) > 1 {
55-
namespace = namespace[1:]
56-
}
57-
58-
getJsonPath(t, namespace, &path)
59-
insertError(errorsMap, path, value.Tag())
60-
}
61-
62-
c.JSON(http.StatusNotAcceptable, gin.H{
63-
"errors": errorsMap,
64-
"message": "Requested with wrong parameters",
65-
"code": http.StatusNotAcceptable,
66-
})
67-
68-
return false
69-
}
70-
71-
return true
72-
}
73-
74-
// findField recursively finds the field in a nested struct
75-
func getJsonPath(t reflect.Type, fields []string, path *[]string) {
76-
field := fields[0]
77-
// used in case of array
78-
var index string
79-
if field[len(field)-1] == ']' {
80-
re := regexp.MustCompile(`(\w+)\[(\d+)\]`)
81-
matches := re.FindStringSubmatch(field)
82-
83-
if len(matches) > 2 {
84-
field = matches[1]
85-
index = matches[2]
86-
}
87-
}
88-
89-
f, ok := t.FieldByName(field)
90-
if !ok {
91-
return
92-
}
93-
94-
*path = append(*path, f.Tag.Get("json"))
95-
96-
if index != "" {
97-
*path = append(*path, index)
98-
}
99-
100-
if len(fields) > 1 {
101-
subFields := fields[1:]
102-
getJsonPath(f.Type, subFields, path)
103-
}
104-
}
105-
106-
// insertError inserts an error into the errors map
107-
func insertError(errorsMap map[string]interface{}, path []string, errorTag string) {
108-
if len(path) == 0 {
109-
return
110-
}
111-
112-
jsonTag := path[0]
113-
if len(path) == 1 {
114-
// Last element in the path, set the error
115-
errorsMap[jsonTag] = errorTag
116-
return
117-
}
118-
119-
// Create a new map if necessary
120-
if _, ok := errorsMap[jsonTag]; !ok {
121-
errorsMap[jsonTag] = make(map[string]interface{})
122-
}
123-
124-
// Recursively insert into the nested map
125-
subMap, _ := errorsMap[jsonTag].(map[string]interface{})
126-
insertError(subMap, path[1:], errorTag)
21+
func SetSSEHeaders(c *gin.Context) {
22+
c.Header("Content-Type", "text/event-stream")
23+
c.Header("Cache-Control", "no-cache")
24+
c.Header("Connection", "keep-alive")
25+
// https://stackoverflow.com/questions/27898622/server-sent-events-stopped-work-after-enabling-ssl-on-proxy/27960243#27960243
26+
c.Header("X-Accel-Buffering", "no")
12727
}

api/certificate/certificate.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ type certJson struct {
9797
func AddCert(c *gin.Context) {
9898
var json certJson
9999

100-
if !api.BindAndValid(c, &json) {
100+
if !cosy.BindAndValid(c, &json) {
101101
return
102102
}
103103

@@ -145,7 +145,7 @@ func ModifyCert(c *gin.Context) {
145145

146146
var json certJson
147147

148-
if !api.BindAndValid(c, &json) {
148+
if !cosy.BindAndValid(c, &json) {
149149
return
150150
}
151151

@@ -202,7 +202,7 @@ func RemoveCert(c *gin.Context) {
202202
func SyncCertificate(c *gin.Context) {
203203
var json cert.SyncCertificatePayload
204204

205-
if !api.BindAndValid(c, &json) {
205+
if !cosy.BindAndValid(c, &json) {
206206
return
207207
}
208208

api/certificate/dns_credential.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ type DnsCredentialManageJson struct {
4747

4848
func AddDnsCredential(c *gin.Context) {
4949
var json DnsCredentialManageJson
50-
if !api.BindAndValid(c, &json) {
50+
if !cosy.BindAndValid(c, &json) {
5151
return
5252
}
5353

@@ -73,7 +73,7 @@ func EditDnsCredential(c *gin.Context) {
7373
id := cast.ToUint64(c.Param("id"))
7474

7575
var json DnsCredentialManageJson
76-
if !api.BindAndValid(c, &json) {
76+
if !cosy.BindAndValid(c, &json) {
7777
return
7878
}
7979

api/cluster/environment.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package cluster
22

33
import (
4+
"crypto/sha256"
5+
"encoding/hex"
6+
"encoding/json"
47
"github.com/0xJacky/Nginx-UI/api"
58
"github.com/0xJacky/Nginx-UI/internal/analytic"
69
"github.com/0xJacky/Nginx-UI/internal/cluster"
@@ -11,7 +14,9 @@ import (
1114
"github.com/spf13/cast"
1215
"github.com/uozi-tech/cosy"
1316
"gorm.io/gorm"
17+
"io"
1418
"net/http"
19+
"time"
1520
)
1621

1722
func GetEnvironment(c *gin.Context) {
@@ -44,6 +49,85 @@ func GetEnvironmentList(c *gin.Context) {
4449
}).PagingList()
4550
}
4651

52+
func GetAllEnabledEnvironment(c *gin.Context) {
53+
api.SetSSEHeaders(c)
54+
notify := c.Writer.CloseNotify()
55+
56+
interval := 10
57+
58+
type respEnvironment struct {
59+
*model.Environment
60+
Status bool `json:"status"`
61+
}
62+
63+
f := func() (any, bool) {
64+
return cosy.Core[model.Environment](c).
65+
SetFussy("name").
66+
SetTransformer(func(m *model.Environment) any {
67+
resp := respEnvironment{
68+
Environment: m,
69+
Status: analytic.GetNode(m).Status,
70+
}
71+
return resp
72+
}).ListAllData()
73+
}
74+
75+
getHash := func(data any) string {
76+
bytes, _ := json.Marshal(data)
77+
hash := sha256.New()
78+
hash.Write(bytes)
79+
hashSum := hash.Sum(nil)
80+
return hex.EncodeToString(hashSum)
81+
}
82+
83+
dataHash := ""
84+
85+
{
86+
data, ok := f()
87+
if !ok {
88+
return
89+
}
90+
91+
c.Stream(func(w io.Writer) bool {
92+
c.SSEvent("message", data)
93+
dataHash = getHash(data)
94+
return false
95+
})
96+
}
97+
98+
for {
99+
select {
100+
case <-time.After(time.Duration(interval) * time.Second):
101+
data, ok := f()
102+
if !ok {
103+
return
104+
}
105+
// if data is not changed, send heartbeat
106+
if dataHash == getHash(data) {
107+
c.Stream(func(w io.Writer) bool {
108+
c.SSEvent("heartbeat", "")
109+
return false
110+
})
111+
return
112+
}
113+
114+
dataHash = getHash(data)
115+
116+
c.Stream(func(w io.Writer) bool {
117+
c.SSEvent("message", data)
118+
return false
119+
})
120+
case <-time.After(30 * time.Second):
121+
c.Stream(func(w io.Writer) bool {
122+
c.SSEvent("heartbeat", "")
123+
return false
124+
})
125+
case <-notify:
126+
return
127+
}
128+
}
129+
}
130+
47131
func AddEnvironment(c *gin.Context) {
48132
cosy.Core[model.Environment](c).SetValidRules(gin.H{
49133
"name": "required",

api/cluster/router.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import "github.com/gin-gonic/gin"
55
func InitRouter(r *gin.RouterGroup) {
66
// Environment
77
r.GET("environments", GetEnvironmentList)
8+
r.GET("environments/enabled", GetAllEnabledEnvironment)
89
r.POST("environments/load_from_settings", LoadEnvironmentFromSettings)
910
envGroup := r.Group("environments")
1011
{

api/config/add.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"github.com/0xJacky/Nginx-UI/query"
1010
"github.com/gin-gonic/gin"
1111
"github.com/sashabaranov/go-openai"
12+
"github.com/uozi-tech/cosy"
1213
"net/http"
1314
"os"
1415
"path/filepath"
@@ -24,7 +25,7 @@ func AddConfig(c *gin.Context) {
2425
SyncNodeIds []uint64 `json:"sync_node_ids"`
2526
}
2627

27-
if !api.BindAndValid(c, &json) {
28+
if !cosy.BindAndValid(c, &json) {
2829
return
2930
}
3031

api/config/mkdir.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"github.com/0xJacky/Nginx-UI/internal/helper"
66
"github.com/0xJacky/Nginx-UI/internal/nginx"
77
"github.com/gin-gonic/gin"
8+
"github.com/uozi-tech/cosy"
89
"net/http"
910
"os"
1011
)
@@ -14,7 +15,7 @@ func Mkdir(c *gin.Context) {
1415
BasePath string `json:"base_path"`
1516
FolderName string `json:"folder_name"`
1617
}
17-
if !api.BindAndValid(c, &json) {
18+
if !cosy.BindAndValid(c, &json) {
1819
return
1920
}
2021
fullPath := nginx.GetConfPath(json.BasePath, json.FolderName)

api/config/modify.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"github.com/0xJacky/Nginx-UI/query"
1010
"github.com/gin-gonic/gin"
1111
"github.com/sashabaranov/go-openai"
12+
"github.com/uozi-tech/cosy"
1213
"net/http"
1314
"os"
1415
"path/filepath"
@@ -26,7 +27,7 @@ func EditConfig(c *gin.Context) {
2627
SyncOverwrite bool `json:"sync_overwrite"`
2728
SyncNodeIds []uint64 `json:"sync_node_ids"`
2829
}
29-
if !api.BindAndValid(c, &json) {
30+
if !cosy.BindAndValid(c, &json) {
3031
return
3132
}
3233

api/config/rename.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import (
88
"github.com/0xJacky/Nginx-UI/model"
99
"github.com/0xJacky/Nginx-UI/query"
1010
"github.com/gin-gonic/gin"
11+
"github.com/uozi-tech/cosy"
12+
"github.com/uozi-tech/cosy/logger"
1113
"net/http"
1214
"os"
1315
"path/filepath"
@@ -21,8 +23,7 @@ func Rename(c *gin.Context) {
2123
NewName string `json:"new_name"`
2224
SyncNodeIds []uint64 `json:"sync_node_ids" gorm:"serializer:json"`
2325
}
24-
25-
if !api.BindAndValid(c, &json) {
26+
if !cosy.BindAndValid(c, &json) {
2627
return
2728
}
2829

api/nginx/nginx.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@ import (
44
"github.com/0xJacky/Nginx-UI/api"
55
"github.com/0xJacky/Nginx-UI/internal/nginx"
66
"github.com/gin-gonic/gin"
7+
"github.com/uozi-tech/cosy"
78
"net/http"
89
)
910

1011
func BuildNginxConfig(c *gin.Context) {
1112
var ngxConf nginx.NgxConfig
12-
if !api.BindAndValid(c, &ngxConf) {
13+
if !cosy.BindAndValid(c, &ngxConf) {
1314
return
1415
}
1516
content, err := ngxConf.BuildConfig()
@@ -27,7 +28,7 @@ func TokenizeNginxConfig(c *gin.Context) {
2728
Content string `json:"content" binding:"required"`
2829
}
2930

30-
if !api.BindAndValid(c, &json) {
31+
if !cosy.BindAndValid(c, &json) {
3132
return
3233
}
3334

@@ -45,7 +46,7 @@ func FormatNginxConfig(c *gin.Context) {
4546
Content string `json:"content" binding:"required"`
4647
}
4748

48-
if !api.BindAndValid(c, &json) {
49+
if !cosy.BindAndValid(c, &json) {
4950
return
5051
}
5152
content, err := nginx.FmtCode(json.Content)

0 commit comments

Comments
 (0)