Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 6 additions & 106 deletions api/api.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
package api

import (
"errors"
"github.com/0xJacky/Nginx-UI/model"
"github.com/gin-gonic/gin"
"github.com/go-playground/validator/v10"
"github.com/uozi-tech/cosy/logger"
"net/http"
"reflect"
"regexp"
"strings"
)

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

type ValidError struct {
Key string
Message string
}

func BindAndValid(c *gin.Context, target interface{}) bool {
err := c.ShouldBindJSON(target)
if err != nil {
logger.Error("bind err", err)

var verrs validator.ValidationErrors
ok := errors.As(err, &verrs)

if !ok {
c.JSON(http.StatusNotAcceptable, gin.H{
"message": "Requested with wrong parameters",
"code": http.StatusNotAcceptable,
})
return false
}

t := reflect.TypeOf(target).Elem()
errorsMap := make(map[string]interface{})
for _, value := range verrs {
var path []string

namespace := strings.Split(value.StructNamespace(), ".")
// logger.Debug(t.Name(), namespace)
if t.Name() != "" && len(namespace) > 1 {
namespace = namespace[1:]
}

getJsonPath(t, namespace, &path)
insertError(errorsMap, path, value.Tag())
}

c.JSON(http.StatusNotAcceptable, gin.H{
"errors": errorsMap,
"message": "Requested with wrong parameters",
"code": http.StatusNotAcceptable,
})

return false
}

return true
}

// findField recursively finds the field in a nested struct
func getJsonPath(t reflect.Type, fields []string, path *[]string) {
field := fields[0]
// used in case of array
var index string
if field[len(field)-1] == ']' {
re := regexp.MustCompile(`(\w+)\[(\d+)\]`)
matches := re.FindStringSubmatch(field)

if len(matches) > 2 {
field = matches[1]
index = matches[2]
}
}

f, ok := t.FieldByName(field)
if !ok {
return
}

*path = append(*path, f.Tag.Get("json"))

if index != "" {
*path = append(*path, index)
}

if len(fields) > 1 {
subFields := fields[1:]
getJsonPath(f.Type, subFields, path)
}
}

// insertError inserts an error into the errors map
func insertError(errorsMap map[string]interface{}, path []string, errorTag string) {
if len(path) == 0 {
return
}

jsonTag := path[0]
if len(path) == 1 {
// Last element in the path, set the error
errorsMap[jsonTag] = errorTag
return
}

// Create a new map if necessary
if _, ok := errorsMap[jsonTag]; !ok {
errorsMap[jsonTag] = make(map[string]interface{})
}

// Recursively insert into the nested map
subMap, _ := errorsMap[jsonTag].(map[string]interface{})
insertError(subMap, path[1:], errorTag)
func SetSSEHeaders(c *gin.Context) {
c.Header("Content-Type", "text/event-stream")
c.Header("Cache-Control", "no-cache")
c.Header("Connection", "keep-alive")
// https://stackoverflow.com/questions/27898622/server-sent-events-stopped-work-after-enabling-ssl-on-proxy/27960243#27960243
c.Header("X-Accel-Buffering", "no")
}
6 changes: 3 additions & 3 deletions api/certificate/certificate.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ type certJson struct {
func AddCert(c *gin.Context) {
var json certJson

if !api.BindAndValid(c, &json) {
if !cosy.BindAndValid(c, &json) {
return
}

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

var json certJson

if !api.BindAndValid(c, &json) {
if !cosy.BindAndValid(c, &json) {
return
}

Expand Down Expand Up @@ -202,7 +202,7 @@ func RemoveCert(c *gin.Context) {
func SyncCertificate(c *gin.Context) {
var json cert.SyncCertificatePayload

if !api.BindAndValid(c, &json) {
if !cosy.BindAndValid(c, &json) {
return
}

Expand Down
4 changes: 2 additions & 2 deletions api/certificate/dns_credential.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ type DnsCredentialManageJson struct {

func AddDnsCredential(c *gin.Context) {
var json DnsCredentialManageJson
if !api.BindAndValid(c, &json) {
if !cosy.BindAndValid(c, &json) {
return
}

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

var json DnsCredentialManageJson
if !api.BindAndValid(c, &json) {
if !cosy.BindAndValid(c, &json) {
return
}

Expand Down
84 changes: 84 additions & 0 deletions api/cluster/environment.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package cluster

import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"github.com/0xJacky/Nginx-UI/api"
"github.com/0xJacky/Nginx-UI/internal/analytic"
"github.com/0xJacky/Nginx-UI/internal/cluster"
Expand All @@ -11,7 +14,9 @@ import (
"github.com/spf13/cast"
"github.com/uozi-tech/cosy"
"gorm.io/gorm"
"io"
"net/http"
"time"
)

func GetEnvironment(c *gin.Context) {
Expand Down Expand Up @@ -44,6 +49,85 @@ func GetEnvironmentList(c *gin.Context) {
}).PagingList()
}

func GetAllEnabledEnvironment(c *gin.Context) {
api.SetSSEHeaders(c)
notify := c.Writer.CloseNotify()

interval := 10

type respEnvironment struct {
*model.Environment
Status bool `json:"status"`
}

f := func() (any, bool) {
return cosy.Core[model.Environment](c).
SetFussy("name").
SetTransformer(func(m *model.Environment) any {
resp := respEnvironment{
Environment: m,
Status: analytic.GetNode(m).Status,
}
return resp
}).ListAllData()
}

getHash := func(data any) string {
bytes, _ := json.Marshal(data)
hash := sha256.New()
hash.Write(bytes)
hashSum := hash.Sum(nil)
return hex.EncodeToString(hashSum)
}

dataHash := ""

{
data, ok := f()
if !ok {
return
}

c.Stream(func(w io.Writer) bool {
c.SSEvent("message", data)
dataHash = getHash(data)
return false
})
}

for {
select {
case <-time.After(time.Duration(interval) * time.Second):
data, ok := f()
if !ok {
return
}
// if data is not changed, send heartbeat
if dataHash == getHash(data) {
c.Stream(func(w io.Writer) bool {
c.SSEvent("heartbeat", "")
return false
})
return
}

dataHash = getHash(data)

c.Stream(func(w io.Writer) bool {
c.SSEvent("message", data)
return false
})
case <-time.After(30 * time.Second):
c.Stream(func(w io.Writer) bool {
c.SSEvent("heartbeat", "")
return false
})
case <-notify:
return
}
}
}

func AddEnvironment(c *gin.Context) {
cosy.Core[model.Environment](c).SetValidRules(gin.H{
"name": "required",
Expand Down
1 change: 1 addition & 0 deletions api/cluster/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import "github.com/gin-gonic/gin"
func InitRouter(r *gin.RouterGroup) {
// Environment
r.GET("environments", GetEnvironmentList)
r.GET("environments/enabled", GetAllEnabledEnvironment)
r.POST("environments/load_from_settings", LoadEnvironmentFromSettings)
envGroup := r.Group("environments")
{
Expand Down
3 changes: 2 additions & 1 deletion api/config/add.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/0xJacky/Nginx-UI/query"
"github.com/gin-gonic/gin"
"github.com/sashabaranov/go-openai"
"github.com/uozi-tech/cosy"
"net/http"
"os"
"path/filepath"
Expand All @@ -24,7 +25,7 @@ func AddConfig(c *gin.Context) {
SyncNodeIds []uint64 `json:"sync_node_ids"`
}

if !api.BindAndValid(c, &json) {
if !cosy.BindAndValid(c, &json) {
return
}

Expand Down
3 changes: 2 additions & 1 deletion api/config/mkdir.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"github.com/0xJacky/Nginx-UI/internal/helper"
"github.com/0xJacky/Nginx-UI/internal/nginx"
"github.com/gin-gonic/gin"
"github.com/uozi-tech/cosy"
"net/http"
"os"
)
Expand All @@ -14,7 +15,7 @@ func Mkdir(c *gin.Context) {
BasePath string `json:"base_path"`
FolderName string `json:"folder_name"`
}
if !api.BindAndValid(c, &json) {
if !cosy.BindAndValid(c, &json) {
return
}
fullPath := nginx.GetConfPath(json.BasePath, json.FolderName)
Expand Down
3 changes: 2 additions & 1 deletion api/config/modify.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/0xJacky/Nginx-UI/query"
"github.com/gin-gonic/gin"
"github.com/sashabaranov/go-openai"
"github.com/uozi-tech/cosy"
"net/http"
"os"
"path/filepath"
Expand All @@ -26,7 +27,7 @@ func EditConfig(c *gin.Context) {
SyncOverwrite bool `json:"sync_overwrite"`
SyncNodeIds []uint64 `json:"sync_node_ids"`
}
if !api.BindAndValid(c, &json) {
if !cosy.BindAndValid(c, &json) {
return
}

Expand Down
5 changes: 3 additions & 2 deletions api/config/rename.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
"github.com/0xJacky/Nginx-UI/model"
"github.com/0xJacky/Nginx-UI/query"
"github.com/gin-gonic/gin"
"github.com/uozi-tech/cosy"
"github.com/uozi-tech/cosy/logger"

Check failure on line 12 in api/config/rename.go

View workflow job for this annotation

GitHub Actions / build (linux, arm, 6)

"github.com/uozi-tech/cosy/logger" imported and not used

Check failure on line 12 in api/config/rename.go

View workflow job for this annotation

GitHub Actions / build (linux, arm, 6)

"github.com/uozi-tech/cosy/logger" imported and not used
"net/http"
"os"
"path/filepath"
Expand All @@ -21,8 +23,7 @@
NewName string `json:"new_name"`
SyncNodeIds []uint64 `json:"sync_node_ids" gorm:"serializer:json"`
}

if !api.BindAndValid(c, &json) {
if !cosy.BindAndValid(c, &json) {
return
}

Expand Down
7 changes: 4 additions & 3 deletions api/nginx/nginx.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ import (
"github.com/0xJacky/Nginx-UI/api"
"github.com/0xJacky/Nginx-UI/internal/nginx"
"github.com/gin-gonic/gin"
"github.com/uozi-tech/cosy"
"net/http"
)

func BuildNginxConfig(c *gin.Context) {
var ngxConf nginx.NgxConfig
if !api.BindAndValid(c, &ngxConf) {
if !cosy.BindAndValid(c, &ngxConf) {
return
}
content, err := ngxConf.BuildConfig()
Expand All @@ -27,7 +28,7 @@ func TokenizeNginxConfig(c *gin.Context) {
Content string `json:"content" binding:"required"`
}

if !api.BindAndValid(c, &json) {
if !cosy.BindAndValid(c, &json) {
return
}

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

if !api.BindAndValid(c, &json) {
if !cosy.BindAndValid(c, &json) {
return
}
content, err := nginx.FmtCode(json.Content)
Expand Down
Loading
Loading