mirror of
https://github.com/0xJacky/nginx-ui.git
synced 2025-05-12 02:45:49 +02:00
Merge pull request #727 from 0xJacky/feat/node-selector-sse
feat: node list supports sse
This commit is contained in:
commit
865506d68a
31 changed files with 176 additions and 166 deletions
112
api/api.go
112
api/api.go
|
@ -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)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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")
|
||||||
{
|
{
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue