feat(wip): node selector supports sse

This commit is contained in:
Jacky 2024-11-14 20:14:02 +08:00
parent ed0dca6820
commit bc70567dc1
No known key found for this signature in database
GPG key ID: 215C21B10DF38B4D
31 changed files with 176 additions and 166 deletions

View file

@ -1,15 +1,10 @@
package api package api
import ( import (
"errors"
"github.com/0xJacky/Nginx-UI/model" "github.com/0xJacky/Nginx-UI/model"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/go-playground/validator/v10"
"github.com/uozi-tech/cosy/logger" "github.com/uozi-tech/cosy/logger"
"net/http" "net/http"
"reflect"
"regexp"
"strings"
) )
func CurrentUser(c *gin.Context) *model.User { func CurrentUser(c *gin.Context) *model.User {
@ -23,105 +18,10 @@ func ErrHandler(c *gin.Context, err error) {
}) })
} }
type ValidError struct { func SetSSEHeaders(c *gin.Context) {
Key string c.Header("Content-Type", "text/event-stream")
Message string 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
func BindAndValid(c *gin.Context, target interface{}) bool { c.Header("X-Accel-Buffering", "no")
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)
} }

View file

@ -97,7 +97,7 @@ type certJson struct {
func AddCert(c *gin.Context) { func AddCert(c *gin.Context) {
var json certJson var json certJson
if !api.BindAndValid(c, &json) { if !cosy.BindAndValid(c, &json) {
return return
} }
@ -145,7 +145,7 @@ func ModifyCert(c *gin.Context) {
var json certJson var json certJson
if !api.BindAndValid(c, &json) { if !cosy.BindAndValid(c, &json) {
return return
} }
@ -202,7 +202,7 @@ func RemoveCert(c *gin.Context) {
func SyncCertificate(c *gin.Context) { func SyncCertificate(c *gin.Context) {
var json cert.SyncCertificatePayload var json cert.SyncCertificatePayload
if !api.BindAndValid(c, &json) { if !cosy.BindAndValid(c, &json) {
return return
} }

View file

@ -47,7 +47,7 @@ type DnsCredentialManageJson struct {
func AddDnsCredential(c *gin.Context) { func AddDnsCredential(c *gin.Context) {
var json DnsCredentialManageJson var json DnsCredentialManageJson
if !api.BindAndValid(c, &json) { if !cosy.BindAndValid(c, &json) {
return return
} }
@ -73,7 +73,7 @@ func EditDnsCredential(c *gin.Context) {
id := cast.ToUint64(c.Param("id")) id := cast.ToUint64(c.Param("id"))
var json DnsCredentialManageJson var json DnsCredentialManageJson
if !api.BindAndValid(c, &json) { if !cosy.BindAndValid(c, &json) {
return return
} }

View file

@ -1,6 +1,9 @@
package cluster package cluster
import ( import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"github.com/0xJacky/Nginx-UI/api" "github.com/0xJacky/Nginx-UI/api"
"github.com/0xJacky/Nginx-UI/internal/analytic" "github.com/0xJacky/Nginx-UI/internal/analytic"
"github.com/0xJacky/Nginx-UI/internal/cluster" "github.com/0xJacky/Nginx-UI/internal/cluster"
@ -11,7 +14,9 @@ import (
"github.com/spf13/cast" "github.com/spf13/cast"
"github.com/uozi-tech/cosy" "github.com/uozi-tech/cosy"
"gorm.io/gorm" "gorm.io/gorm"
"io"
"net/http" "net/http"
"time"
) )
func GetEnvironment(c *gin.Context) { func GetEnvironment(c *gin.Context) {
@ -44,6 +49,85 @@ func GetEnvironmentList(c *gin.Context) {
}).PagingList() }).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) { func AddEnvironment(c *gin.Context) {
cosy.Core[model.Environment](c).SetValidRules(gin.H{ cosy.Core[model.Environment](c).SetValidRules(gin.H{
"name": "required", "name": "required",

View file

@ -5,6 +5,7 @@ import "github.com/gin-gonic/gin"
func InitRouter(r *gin.RouterGroup) { func InitRouter(r *gin.RouterGroup) {
// Environment // Environment
r.GET("environments", GetEnvironmentList) r.GET("environments", GetEnvironmentList)
r.GET("environments/enabled", GetAllEnabledEnvironment)
r.POST("environments/load_from_settings", LoadEnvironmentFromSettings) r.POST("environments/load_from_settings", LoadEnvironmentFromSettings)
envGroup := r.Group("environments") envGroup := r.Group("environments")
{ {

View file

@ -9,6 +9,7 @@ import (
"github.com/0xJacky/Nginx-UI/query" "github.com/0xJacky/Nginx-UI/query"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/sashabaranov/go-openai" "github.com/sashabaranov/go-openai"
"github.com/uozi-tech/cosy"
"net/http" "net/http"
"os" "os"
"path/filepath" "path/filepath"
@ -24,7 +25,7 @@ func AddConfig(c *gin.Context) {
SyncNodeIds []uint64 `json:"sync_node_ids"` SyncNodeIds []uint64 `json:"sync_node_ids"`
} }
if !api.BindAndValid(c, &json) { if !cosy.BindAndValid(c, &json) {
return return
} }

View file

@ -5,6 +5,7 @@ import (
"github.com/0xJacky/Nginx-UI/internal/helper" "github.com/0xJacky/Nginx-UI/internal/helper"
"github.com/0xJacky/Nginx-UI/internal/nginx" "github.com/0xJacky/Nginx-UI/internal/nginx"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/uozi-tech/cosy"
"net/http" "net/http"
"os" "os"
) )
@ -14,7 +15,7 @@ func Mkdir(c *gin.Context) {
BasePath string `json:"base_path"` BasePath string `json:"base_path"`
FolderName string `json:"folder_name"` FolderName string `json:"folder_name"`
} }
if !api.BindAndValid(c, &json) { if !cosy.BindAndValid(c, &json) {
return return
} }
fullPath := nginx.GetConfPath(json.BasePath, json.FolderName) fullPath := nginx.GetConfPath(json.BasePath, json.FolderName)

View file

@ -9,6 +9,7 @@ import (
"github.com/0xJacky/Nginx-UI/query" "github.com/0xJacky/Nginx-UI/query"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/sashabaranov/go-openai" "github.com/sashabaranov/go-openai"
"github.com/uozi-tech/cosy"
"net/http" "net/http"
"os" "os"
"path/filepath" "path/filepath"
@ -26,7 +27,7 @@ func EditConfig(c *gin.Context) {
SyncOverwrite bool `json:"sync_overwrite"` SyncOverwrite bool `json:"sync_overwrite"`
SyncNodeIds []uint64 `json:"sync_node_ids"` SyncNodeIds []uint64 `json:"sync_node_ids"`
} }
if !api.BindAndValid(c, &json) { if !cosy.BindAndValid(c, &json) {
return return
} }

View file

@ -8,6 +8,8 @@ import (
"github.com/0xJacky/Nginx-UI/model" "github.com/0xJacky/Nginx-UI/model"
"github.com/0xJacky/Nginx-UI/query" "github.com/0xJacky/Nginx-UI/query"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/uozi-tech/cosy"
"github.com/uozi-tech/cosy/logger"
"net/http" "net/http"
"os" "os"
"path/filepath" "path/filepath"
@ -21,8 +23,7 @@ func Rename(c *gin.Context) {
NewName string `json:"new_name"` NewName string `json:"new_name"`
SyncNodeIds []uint64 `json:"sync_node_ids" gorm:"serializer:json"` SyncNodeIds []uint64 `json:"sync_node_ids" gorm:"serializer:json"`
} }
if !cosy.BindAndValid(c, &json) {
if !api.BindAndValid(c, &json) {
return return
} }

View file

@ -4,12 +4,13 @@ import (
"github.com/0xJacky/Nginx-UI/api" "github.com/0xJacky/Nginx-UI/api"
"github.com/0xJacky/Nginx-UI/internal/nginx" "github.com/0xJacky/Nginx-UI/internal/nginx"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/uozi-tech/cosy"
"net/http" "net/http"
) )
func BuildNginxConfig(c *gin.Context) { func BuildNginxConfig(c *gin.Context) {
var ngxConf nginx.NgxConfig var ngxConf nginx.NgxConfig
if !api.BindAndValid(c, &ngxConf) { if !cosy.BindAndValid(c, &ngxConf) {
return return
} }
content, err := ngxConf.BuildConfig() content, err := ngxConf.BuildConfig()
@ -27,7 +28,7 @@ func TokenizeNginxConfig(c *gin.Context) {
Content string `json:"content" binding:"required"` Content string `json:"content" binding:"required"`
} }
if !api.BindAndValid(c, &json) { if !cosy.BindAndValid(c, &json) {
return return
} }
@ -45,7 +46,7 @@ func FormatNginxConfig(c *gin.Context) {
Content string `json:"content" binding:"required"` Content string `json:"content" binding:"required"`
} }
if !api.BindAndValid(c, &json) { if !cosy.BindAndValid(c, &json) {
return return
} }
content, err := nginx.FmtCode(json.Content) content, err := nginx.FmtCode(json.Content)

View file

@ -3,7 +3,6 @@ package nginx
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/0xJacky/Nginx-UI/api"
"github.com/0xJacky/Nginx-UI/internal/cache" "github.com/0xJacky/Nginx-UI/internal/cache"
"github.com/0xJacky/Nginx-UI/internal/helper" "github.com/0xJacky/Nginx-UI/internal/helper"
"github.com/0xJacky/Nginx-UI/internal/nginx" "github.com/0xJacky/Nginx-UI/internal/nginx"
@ -13,6 +12,7 @@ import (
"github.com/hpcloud/tail" "github.com/hpcloud/tail"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/cast" "github.com/spf13/cast"
"github.com/uozi-tech/cosy"
"github.com/uozi-tech/cosy/logger" "github.com/uozi-tech/cosy/logger"
"io" "io"
"net/http" "net/http"
@ -44,7 +44,7 @@ func GetNginxLogPage(c *gin.Context) {
} }
var control controlStruct var control controlStruct
if !api.BindAndValid(c, &control) { if !cosy.BindAndValid(c, &control) {
return return
} }

View file

@ -1,6 +1,7 @@
package notification package notification
import ( import (
"github.com/0xJacky/Nginx-UI/api"
"github.com/0xJacky/Nginx-UI/internal/notification" "github.com/0xJacky/Nginx-UI/internal/notification"
"github.com/0xJacky/Nginx-UI/model" "github.com/0xJacky/Nginx-UI/model"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@ -9,11 +10,7 @@ import (
) )
func Live(c *gin.Context) { func Live(c *gin.Context) {
c.Header("Content-Type", "text/event-stream") api.SetSSEHeaders(c)
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")
evtChan := make(chan *model.Notification) evtChan := make(chan *model.Notification)

View file

@ -3,13 +3,13 @@ package openai
import ( import (
"context" "context"
"fmt" "fmt"
"github.com/0xJacky/Nginx-UI/api"
"github.com/0xJacky/Nginx-UI/internal/chatbot" "github.com/0xJacky/Nginx-UI/internal/chatbot"
"github.com/0xJacky/Nginx-UI/internal/transport" "github.com/0xJacky/Nginx-UI/internal/transport"
"github.com/0xJacky/Nginx-UI/settings" "github.com/0xJacky/Nginx-UI/settings"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/sashabaranov/go-openai" "github.com/sashabaranov/go-openai"
"github.com/uozi-tech/cosy"
"io" "io"
"net/http" "net/http"
) )
@ -26,7 +26,7 @@ func MakeChatCompletionRequest(c *gin.Context) {
Messages []openai.ChatCompletionMessage `json:"messages"` Messages []openai.ChatCompletionMessage `json:"messages"`
} }
if !api.BindAndValid(c, &json) { if !cosy.BindAndValid(c, &json) {
return return
} }

View file

@ -6,6 +6,7 @@ import (
"github.com/0xJacky/Nginx-UI/query" "github.com/0xJacky/Nginx-UI/query"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/sashabaranov/go-openai" "github.com/sashabaranov/go-openai"
"github.com/uozi-tech/cosy"
"net/http" "net/http"
) )
@ -15,7 +16,7 @@ func StoreChatGPTRecord(c *gin.Context) {
Messages []openai.ChatCompletionMessage `json:"messages"` Messages []openai.ChatCompletionMessage `json:"messages"`
} }
if !api.BindAndValid(c, &json) { if !cosy.BindAndValid(c, &json) {
return return
} }

View file

@ -1,10 +1,10 @@
package settings package settings
import ( import (
"github.com/0xJacky/Nginx-UI/api"
"github.com/0xJacky/Nginx-UI/query" "github.com/0xJacky/Nginx-UI/query"
"github.com/0xJacky/Nginx-UI/settings" "github.com/0xJacky/Nginx-UI/settings"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/uozi-tech/cosy"
"net/http" "net/http"
"time" "time"
) )
@ -19,7 +19,7 @@ func GetBanLoginIP(c *gin.Context) {
b.ExpiredAt.Gte(time.Now().Unix()), b.ExpiredAt.Gte(time.Now().Unix()),
b.Attempts.Gte(settings.AuthSettings.MaxAttempts)).Find() b.Attempts.Gte(settings.AuthSettings.MaxAttempts)).Find()
if err != nil { if err != nil {
api.ErrHandler(c, err) cosy.ErrHandler(c, err)
return return
} }
c.JSON(http.StatusOK, banIps) c.JSON(http.StatusOK, banIps)
@ -29,7 +29,7 @@ func RemoveBannedIP(c *gin.Context) {
var json struct { var json struct {
IP string `json:"ip"` IP string `json:"ip"`
} }
if !api.BindAndValid(c, &json) { if !cosy.BindAndValid(c, &json) {
return return
} }
@ -37,7 +37,7 @@ func RemoveBannedIP(c *gin.Context) {
_, err := b.Where(b.IP.Eq(json.IP)).Delete() _, err := b.Where(b.IP.Eq(json.IP)).Delete()
if err != nil { if err != nil {
api.ErrHandler(c, err) cosy.ErrHandler(c, err)
return return
} }

View file

@ -7,6 +7,7 @@ import (
"github.com/0xJacky/Nginx-UI/internal/nginx" "github.com/0xJacky/Nginx-UI/internal/nginx"
"github.com/0xJacky/Nginx-UI/settings" "github.com/0xJacky/Nginx-UI/settings"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/uozi-tech/cosy"
cSettings "github.com/uozi-tech/cosy/settings" cSettings "github.com/uozi-tech/cosy/settings"
"net/http" "net/http"
) )
@ -68,7 +69,7 @@ func SaveSettings(c *gin.Context) {
Logrotate settings.Logrotate `json:"logrotate"` Logrotate settings.Logrotate `json:"logrotate"`
} }
if !api.BindAndValid(c, &json) { if !cosy.BindAndValid(c, &json) {
return return
} }

View file

@ -5,6 +5,7 @@ import (
"github.com/0xJacky/Nginx-UI/internal/nginx" "github.com/0xJacky/Nginx-UI/internal/nginx"
"github.com/0xJacky/Nginx-UI/query" "github.com/0xJacky/Nginx-UI/query"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/uozi-tech/cosy"
"net/http" "net/http"
) )
@ -13,7 +14,7 @@ func DomainEditByAdvancedMode(c *gin.Context) {
Advanced bool `json:"advanced"` Advanced bool `json:"advanced"`
} }
if !api.BindAndValid(c, &json) { if !cosy.BindAndValid(c, &json) {
return return
} }

View file

@ -6,6 +6,7 @@ import (
"github.com/0xJacky/Nginx-UI/model" "github.com/0xJacky/Nginx-UI/model"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/go-acme/lego/v4/certcrypto" "github.com/go-acme/lego/v4/certcrypto"
"github.com/uozi-tech/cosy"
"net/http" "net/http"
) )
@ -19,7 +20,7 @@ func AddDomainToAutoCert(c *gin.Context) {
KeyType certcrypto.KeyType `json:"key_type"` KeyType certcrypto.KeyType `json:"key_type"`
} }
if !api.BindAndValid(c, &json) { if !cosy.BindAndValid(c, &json) {
return return
} }

View file

@ -4,6 +4,7 @@ import (
"github.com/0xJacky/Nginx-UI/api" "github.com/0xJacky/Nginx-UI/api"
"github.com/0xJacky/Nginx-UI/internal/site" "github.com/0xJacky/Nginx-UI/internal/site"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/uozi-tech/cosy"
"net/http" "net/http"
) )
@ -16,7 +17,7 @@ func DuplicateSite(c *gin.Context) {
Name string `json:"name" binding:"required"` Name string `json:"name" binding:"required"`
} }
if !api.BindAndValid(c, &json) { if !cosy.BindAndValid(c, &json) {
return return
} }

View file

@ -120,7 +120,7 @@ func SaveSite(c *gin.Context) {
Overwrite bool `json:"overwrite"` Overwrite bool `json:"overwrite"`
} }
if !api.BindAndValid(c, &json) { if !cosy.BindAndValid(c, &json) {
return return
} }

View file

@ -5,6 +5,7 @@ import (
"github.com/0xJacky/Nginx-UI/internal/nginx" "github.com/0xJacky/Nginx-UI/internal/nginx"
"github.com/0xJacky/Nginx-UI/query" "github.com/0xJacky/Nginx-UI/query"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/uozi-tech/cosy"
"net/http" "net/http"
) )
@ -13,7 +14,7 @@ func AdvancedEdit(c *gin.Context) {
Advanced bool `json:"advanced"` Advanced bool `json:"advanced"`
} }
if !api.BindAndValid(c, &json) { if !cosy.BindAndValid(c, &json) {
return return
} }

View file

@ -5,6 +5,7 @@ import (
"github.com/0xJacky/Nginx-UI/internal/helper" "github.com/0xJacky/Nginx-UI/internal/helper"
"github.com/0xJacky/Nginx-UI/internal/nginx" "github.com/0xJacky/Nginx-UI/internal/nginx"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/uozi-tech/cosy"
"net/http" "net/http"
) )
@ -17,7 +18,7 @@ func Duplicate(c *gin.Context) {
Name string `json:"name" binding:"required"` Name string `json:"name" binding:"required"`
} }
if !api.BindAndValid(c, &json) { if !cosy.BindAndValid(c, &json) {
return return
} }

View file

@ -8,6 +8,7 @@ import (
"github.com/0xJacky/Nginx-UI/query" "github.com/0xJacky/Nginx-UI/query"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/sashabaranov/go-openai" "github.com/sashabaranov/go-openai"
"github.com/uozi-tech/cosy"
"net/http" "net/http"
"os" "os"
"strings" "strings"
@ -174,7 +175,7 @@ func SaveStream(c *gin.Context) {
Overwrite bool `json:"overwrite"` Overwrite bool `json:"overwrite"`
} }
if !api.BindAndValid(c, &json) { if !cosy.BindAndValid(c, &json) {
return return
} }

View file

@ -8,6 +8,7 @@ import (
"github.com/0xJacky/Nginx-UI/settings" "github.com/0xJacky/Nginx-UI/settings"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/uozi-tech/cosy"
cSettings "github.com/uozi-tech/cosy/settings" cSettings "github.com/uozi-tech/cosy/settings"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
"net/http" "net/http"
@ -39,7 +40,7 @@ func InstallNginxUI(c *gin.Context) {
return return
} }
var json InstallJson var json InstallJson
ok := api.BindAndValid(c, &json) ok := cosy.BindAndValid(c, &json)
if !ok { if !ok {
return return
} }

View file

@ -12,6 +12,7 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/go-webauthn/webauthn/webauthn" "github.com/go-webauthn/webauthn/webauthn"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/uozi-tech/cosy"
"net/http" "net/http"
"strings" "strings"
"time" "time"
@ -71,7 +72,7 @@ func Start2FASecureSessionByOTP(c *gin.Context) {
OTP string `json:"otp"` OTP string `json:"otp"`
RecoveryCode string `json:"recovery_code"` RecoveryCode string `json:"recovery_code"`
} }
if !api.BindAndValid(c, &json) { if !cosy.BindAndValid(c, &json) {
return return
} }
u := api.CurrentUser(c) u := api.CurrentUser(c)

View file

@ -7,6 +7,7 @@ import (
"github.com/0xJacky/Nginx-UI/settings" "github.com/0xJacky/Nginx-UI/settings"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/uozi-tech/cosy"
"github.com/uozi-tech/cosy/logger" "github.com/uozi-tech/cosy/logger"
"math/rand/v2" "math/rand/v2"
"net/http" "net/http"
@ -61,7 +62,7 @@ func Login(c *gin.Context) {
} }
var json LoginUser var json LoginUser
ok := api.BindAndValid(c, &json) ok := cosy.BindAndValid(c, &json)
if !ok { if !ok {
return return
} }

View file

@ -8,6 +8,7 @@ import (
"github.com/casdoor/casdoor-go-sdk/casdoorsdk" "github.com/casdoor/casdoor-go-sdk/casdoorsdk"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/uozi-tech/cosy"
"gorm.io/gorm" "gorm.io/gorm"
"net/http" "net/http"
"net/url" "net/url"
@ -22,7 +23,7 @@ type CasdoorLoginUser struct {
func CasdoorCallback(c *gin.Context) { func CasdoorCallback(c *gin.Context) {
var loginUser CasdoorLoginUser var loginUser CasdoorLoginUser
ok := api.BindAndValid(c, &loginUser) ok := cosy.BindAndValid(c, &loginUser)
if !ok { if !ok {
return return
} }

View file

@ -13,6 +13,7 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/pquerna/otp" "github.com/pquerna/otp"
"github.com/pquerna/otp/totp" "github.com/pquerna/otp/totp"
"github.com/uozi-tech/cosy"
"image/jpeg" "image/jpeg"
"net/http" "net/http"
"strings" "strings"
@ -81,7 +82,7 @@ func EnrollTOTP(c *gin.Context) {
Secret string `json:"secret" binding:"required"` Secret string `json:"secret" binding:"required"`
Passcode string `json:"passcode" binding:"required"` Passcode string `json:"passcode" binding:"required"`
} }
if !api.BindAndValid(c, &json) { if !cosy.BindAndValid(c, &json) {
return return
} }
@ -117,7 +118,7 @@ func ResetOTP(c *gin.Context) {
var json struct { var json struct {
RecoveryCode string `json:"recovery_code"` RecoveryCode string `json:"recovery_code"`
} }
if !api.BindAndValid(c, &json) { if !cosy.BindAndValid(c, &json) {
return return
} }
recoverCode, err := hex.DecodeString(json.RecoveryCode) recoverCode, err := hex.DecodeString(json.RecoveryCode)

View file

@ -38,7 +38,7 @@ type UserJson struct {
func AddUser(c *gin.Context) { func AddUser(c *gin.Context) {
var json UserJson var json UserJson
ok := api.BindAndValid(c, &json) ok := cosy.BindAndValid(c, &json)
if !ok { if !ok {
return return
} }
@ -79,7 +79,7 @@ func EditUser(c *gin.Context) {
} }
var json UserJson var json UserJson
ok := api.BindAndValid(c, &json) ok := cosy.BindAndValid(c, &json)
if !ok { if !ok {
return return
} }

View file

@ -1,7 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
import type { Environment } from '@/api/environment' import type { Environment } from '@/api/environment'
import type { Ref } from 'vue' import type { Ref } from 'vue'
import environment from '@/api/environment' import { useUserStore } from '@/pinia'
import { SSE, type SSEvent } from 'sse.js'
const props = defineProps<{ const props = defineProps<{
hiddenLocal?: boolean hiddenLocal?: boolean
@ -9,26 +10,35 @@ const props = defineProps<{
const target = defineModel<number[]>('target') const target = defineModel<number[]>('target')
const map = defineModel<Record<number, string>>('map') const map = defineModel<Record<number, string>>('map')
const { token } = storeToRefs(useUserStore())
const data = ref([]) as Ref<Environment[]> const data = ref([]) as Ref<Environment[]>
const data_map = ref({}) as Ref<Record<number, Environment>> const data_map = ref({}) as Ref<Record<number, Environment>>
onMounted(async () => { const sse = shallowRef(newSSE())
let hasMore = true
let page = 1 function reconnect() {
while (hasMore) { setTimeout(() => {
await environment.get_list({ page, enabled: true }).then(r => { sse.value = newSSE()
data.value.push(...r.data) }, 5000)
r.data?.forEach(node => { }
data_map.value[node.id] = node
}) function newSSE() {
hasMore = r.data.length === r.pagination?.per_page const s = new SSE('/api/environments/enabled', {
page++ headers: {
}).catch(() => { Authorization: token.value,
hasMore = false },
}) })
s.onmessage = (e: SSEvent) => {
data.value = JSON.parse(e.data)
} }
})
// reconnect
s.onerror = reconnect
return s
}
const value = computed({ const value = computed({
get() { get() {

View file

@ -35,7 +35,7 @@ function batchUpgrade() {
:api="environment" :api="environment"
:columns="envColumns" :columns="envColumns"
> >
<template #extra> <template #beforeAdd>
<a @click="loadFromSettings">{{ $gettext('Load from settings') }}</a> <a @click="loadFromSettings">{{ $gettext('Load from settings') }}</a>
</template> </template>
</StdCurd> </StdCurd>