mirror of
https://github.com/0xJacky/nginx-ui.git
synced 2025-05-10 18:05:48 +02:00
feat: deploy config to remote nodes #359
This commit is contained in:
parent
e75dce92ad
commit
1c1da92363
46 changed files with 1480 additions and 605 deletions
25
router/ip.go
25
router/ip.go
|
@ -1,25 +0,0 @@
|
|||
package router
|
||||
|
||||
import (
|
||||
"github.com/0xJacky/Nginx-UI/settings"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/samber/lo"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func ipWhiteList() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
clientIP := c.ClientIP()
|
||||
if len(settings.AuthSettings.IPWhiteList) == 0 || clientIP == "127.0.0.1" {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
if !lo.Contains(settings.AuthSettings.IPWhiteList, clientIP) {
|
||||
c.AbortWithStatus(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
|
@ -1,156 +0,0 @@
|
|||
package router
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"github.com/0xJacky/Nginx-UI/app"
|
||||
"github.com/0xJacky/Nginx-UI/internal/logger"
|
||||
"github.com/0xJacky/Nginx-UI/internal/user"
|
||||
"github.com/0xJacky/Nginx-UI/model"
|
||||
"github.com/0xJacky/Nginx-UI/settings"
|
||||
"github.com/gin-contrib/static"
|
||||
"github.com/gin-gonic/gin"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"path"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func recovery() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
buf := make([]byte, 1024)
|
||||
runtime.Stack(buf, false)
|
||||
logger.Errorf("%s\n%s", err, buf)
|
||||
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{
|
||||
"message": err.(error).Error(),
|
||||
})
|
||||
}
|
||||
}()
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func authRequired() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
abortWithAuthFailure := func() {
|
||||
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{
|
||||
"message": "Authorization failed",
|
||||
})
|
||||
}
|
||||
|
||||
token := c.GetHeader("Authorization")
|
||||
if token == "" {
|
||||
if token = c.GetHeader("X-Node-Secret"); token != "" && token == settings.ServerSettings.NodeSecret {
|
||||
c.Set("NodeSecret", token)
|
||||
c.Next()
|
||||
return
|
||||
} else {
|
||||
c.Set("ProxyNodeID", c.Query("x_node_id"))
|
||||
tokenBytes, _ := base64.StdEncoding.DecodeString(c.Query("token"))
|
||||
token = string(tokenBytes)
|
||||
if token == "" {
|
||||
abortWithAuthFailure()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
u, ok := user.GetTokenUser(token)
|
||||
if !ok {
|
||||
abortWithAuthFailure()
|
||||
return
|
||||
}
|
||||
|
||||
c.Set("user", u)
|
||||
|
||||
if nodeID := c.GetHeader("X-Node-ID"); nodeID != "" {
|
||||
c.Set("ProxyNodeID", nodeID)
|
||||
}
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func required2FA() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
u, ok := c.Get("user")
|
||||
if !ok {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
cUser := u.(*model.Auth)
|
||||
if !cUser.EnabledOTP() {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
ssid := c.GetHeader("X-Secure-Session-ID")
|
||||
if ssid == "" {
|
||||
ssid = c.Query("X-Secure-Session-ID")
|
||||
}
|
||||
if ssid == "" {
|
||||
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{
|
||||
"message": "Secure Session ID is empty",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if user.VerifySecureSessionID(ssid, cUser.ID) {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{
|
||||
"message": "Secure Session ID is invalid",
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
type serverFileSystemType struct {
|
||||
http.FileSystem
|
||||
}
|
||||
|
||||
func (f serverFileSystemType) Exists(prefix string, _path string) bool {
|
||||
file, err := f.Open(path.Join(prefix, _path))
|
||||
if file != nil {
|
||||
defer func(file http.File) {
|
||||
err = file.Close()
|
||||
if err != nil {
|
||||
logger.Error("file not found", err)
|
||||
}
|
||||
}(file)
|
||||
}
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func mustFS(dir string) (serverFileSystem static.ServeFileSystem) {
|
||||
|
||||
sub, err := fs.Sub(app.DistFS, path.Join("dist", dir))
|
||||
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
serverFileSystem = serverFileSystemType{
|
||||
http.FS(sub),
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func cacheJs() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
if strings.Contains(c.Request.URL.String(), "js") {
|
||||
c.Header("Cache-Control", "max-age: 1296000")
|
||||
if c.Request.Header.Get("If-Modified-Since") == settings.LastModified {
|
||||
c.AbortWithStatus(http.StatusNotModified)
|
||||
}
|
||||
c.Header("Last-Modified", settings.LastModified)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,154 +0,0 @@
|
|||
package router
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/0xJacky/Nginx-UI/internal/analytic"
|
||||
"github.com/0xJacky/Nginx-UI/internal/logger"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/pkg/errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type ErrorRes struct {
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
type toolBodyWriter struct {
|
||||
gin.ResponseWriter
|
||||
body *bytes.Buffer
|
||||
}
|
||||
|
||||
func (r toolBodyWriter) Write(b []byte) (int, error) {
|
||||
return r.body.Write(b)
|
||||
}
|
||||
|
||||
// OperationSync 针对配置了vip的环境操作进行同步
|
||||
func OperationSync() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
bodyBytes, _ := PeekRequest(c.Request)
|
||||
wb := &toolBodyWriter{
|
||||
body: &bytes.Buffer{},
|
||||
ResponseWriter: c.Writer,
|
||||
}
|
||||
c.Writer = wb
|
||||
|
||||
c.Next()
|
||||
if c.Request.Method == "GET" || !statusValid(c.Writer.Status()) { // 请求有问题,无需执行同步操作
|
||||
return
|
||||
}
|
||||
|
||||
totalCount := 0
|
||||
successCount := 0
|
||||
detailMsg := ""
|
||||
// 后置处理操作同步
|
||||
wg := sync.WaitGroup{}
|
||||
for _, node := range analytic.NodeMap {
|
||||
wg.Add(1)
|
||||
node := node
|
||||
go func(data analytic.Node) {
|
||||
defer wg.Done()
|
||||
if node.OperationSync && node.Status && requestUrlMatch(c.Request.URL.Path, data) { // 开启操作同步且当前状态正常
|
||||
totalCount++
|
||||
if err := syncNodeOperation(c, data, bodyBytes); err != nil {
|
||||
detailMsg += fmt.Sprintf("node_name: %s, err_msg: %s; ", data.Name, err)
|
||||
return
|
||||
}
|
||||
successCount++
|
||||
}
|
||||
}(*node)
|
||||
}
|
||||
wg.Wait()
|
||||
if successCount < totalCount { // 如果有错误,替换原来的消息内容
|
||||
originBytes := wb.body
|
||||
logger.Infof("origin response body: %s", originBytes)
|
||||
// clear Origin Buffer
|
||||
wb.body = &bytes.Buffer{}
|
||||
wb.ResponseWriter.WriteHeader(http.StatusInternalServerError)
|
||||
|
||||
errorRes := ErrorRes{
|
||||
Message: fmt.Sprintf("operation sync failed, total: %d, success: %d, fail: %d, detail: %s", totalCount, successCount, totalCount-successCount, detailMsg),
|
||||
}
|
||||
byts, _ := json.Marshal(errorRes)
|
||||
_, err := wb.Write(byts)
|
||||
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
}
|
||||
}
|
||||
_, err := wb.ResponseWriter.Write(wb.body.Bytes())
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func PeekRequest(request *http.Request) ([]byte, error) {
|
||||
if request.Body != nil {
|
||||
byts, err := io.ReadAll(request.Body) // io.ReadAll as Go 1.16, below please use ioutil.ReadAll
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
request.Body = io.NopCloser(bytes.NewReader(byts))
|
||||
return byts, nil
|
||||
}
|
||||
return make([]byte, 0), nil
|
||||
}
|
||||
|
||||
func requestUrlMatch(url string, node analytic.Node) bool {
|
||||
p, _ := regexp.Compile(node.SyncApiRegex)
|
||||
result := p.FindAllString(url, -1)
|
||||
if len(result) > 0 && result[0] == url {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func statusValid(code int) bool {
|
||||
return code < http.StatusMultipleChoices
|
||||
}
|
||||
|
||||
func syncNodeOperation(c *gin.Context, node analytic.Node, bodyBytes []byte) error {
|
||||
u, err := url.JoinPath(node.URL, c.Request.RequestURI)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
decodedUri, err := url.QueryUnescape(u)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
logger.Debugf("syncNodeOperation request: %s, node_id: %d, node_name: %s", decodedUri, node.ID, node.Name)
|
||||
client := http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
},
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(c.Request.Method, decodedUri, bytes.NewReader(bodyBytes))
|
||||
req.Header.Set("X-Node-Secret", node.Token)
|
||||
|
||||
res, err := client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
byts, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !statusValid(res.StatusCode) {
|
||||
errRes := ErrorRes{}
|
||||
if err = json.Unmarshal(byts, &errRes); err != nil {
|
||||
return err
|
||||
}
|
||||
return errors.New(errRes.Message)
|
||||
}
|
||||
logger.Debug("syncNodeOperation result: ", string(byts))
|
||||
return nil
|
||||
}
|
102
router/proxy.go
102
router/proxy.go
|
@ -1,102 +0,0 @@
|
|||
package router
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"github.com/0xJacky/Nginx-UI/internal/logger"
|
||||
"github.com/0xJacky/Nginx-UI/query"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/spf13/cast"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
func proxy() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
nodeID, ok := c.Get("ProxyNodeID")
|
||||
if !ok {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
id := cast.ToInt(nodeID)
|
||||
if id == 0 {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
defer c.Abort()
|
||||
|
||||
env := query.Environment
|
||||
|
||||
environment, err := env.Where(env.ID.Eq(id)).First()
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
c.AbortWithStatusJSON(http.StatusServiceUnavailable, gin.H{
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
baseUrl, err := url.Parse(environment.URL)
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
proxyUrl, err := baseUrl.Parse(c.Request.RequestURI)
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
logger.Debug("Proxy request", proxyUrl.String())
|
||||
client := http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
},
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(c.Request.Method, proxyUrl.String(), c.Request.Body)
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
req.Header.Set("X-Node-Secret", environment.Token)
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
// rewrite status code to fix https://github.com/0xJacky/nginx-ui/issues/342
|
||||
if resp.StatusCode == http.StatusForbidden {
|
||||
resp.StatusCode = http.StatusServiceUnavailable
|
||||
}
|
||||
|
||||
c.Writer.WriteHeader(resp.StatusCode)
|
||||
|
||||
c.Writer.Header().Add("Content-Type", resp.Header.Get("Content-Type"))
|
||||
|
||||
_, err = io.Copy(c.Writer, resp.Body)
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,56 +0,0 @@
|
|||
package router
|
||||
|
||||
import (
|
||||
"github.com/0xJacky/Nginx-UI/internal/logger"
|
||||
"github.com/0xJacky/Nginx-UI/query"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/pretty66/websocketproxy"
|
||||
"github.com/spf13/cast"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func proxyWs() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
nodeID, ok := c.Get("ProxyNodeID")
|
||||
if !ok {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
id := cast.ToInt(nodeID)
|
||||
if id == 0 {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
defer c.Abort()
|
||||
|
||||
env := query.Environment
|
||||
environment, err := env.Where(env.ID.Eq(id)).First()
|
||||
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
decodedUri, err := environment.GetWebSocketURL(c.Request.RequestURI)
|
||||
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
logger.Debug("Proxy request", decodedUri)
|
||||
|
||||
wp, err := websocketproxy.NewProxy(decodedUri, func(r *http.Request) error {
|
||||
r.Header.Set("X-Node-Secret", environment.Token)
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
wp.Proxy(c.Writer, c.Request)
|
||||
}
|
||||
}
|
|
@ -1,83 +1,83 @@
|
|||
package router
|
||||
|
||||
import (
|
||||
"github.com/0xJacky/Nginx-UI/api/analytic"
|
||||
"github.com/0xJacky/Nginx-UI/api/certificate"
|
||||
"github.com/0xJacky/Nginx-UI/api/cluster"
|
||||
"github.com/0xJacky/Nginx-UI/api/config"
|
||||
"github.com/0xJacky/Nginx-UI/api/nginx"
|
||||
"github.com/0xJacky/Nginx-UI/api/notification"
|
||||
"github.com/0xJacky/Nginx-UI/api/openai"
|
||||
"github.com/0xJacky/Nginx-UI/api/settings"
|
||||
"github.com/0xJacky/Nginx-UI/api/sites"
|
||||
"github.com/0xJacky/Nginx-UI/api/streams"
|
||||
"github.com/0xJacky/Nginx-UI/api/system"
|
||||
"github.com/0xJacky/Nginx-UI/api/template"
|
||||
"github.com/0xJacky/Nginx-UI/api/terminal"
|
||||
"github.com/0xJacky/Nginx-UI/api/upstream"
|
||||
"github.com/0xJacky/Nginx-UI/api/user"
|
||||
"github.com/gin-contrib/static"
|
||||
"github.com/gin-gonic/gin"
|
||||
"net/http"
|
||||
"github.com/0xJacky/Nginx-UI/api/analytic"
|
||||
"github.com/0xJacky/Nginx-UI/api/certificate"
|
||||
"github.com/0xJacky/Nginx-UI/api/cluster"
|
||||
"github.com/0xJacky/Nginx-UI/api/config"
|
||||
"github.com/0xJacky/Nginx-UI/api/nginx"
|
||||
"github.com/0xJacky/Nginx-UI/api/notification"
|
||||
"github.com/0xJacky/Nginx-UI/api/openai"
|
||||
"github.com/0xJacky/Nginx-UI/api/settings"
|
||||
"github.com/0xJacky/Nginx-UI/api/sites"
|
||||
"github.com/0xJacky/Nginx-UI/api/streams"
|
||||
"github.com/0xJacky/Nginx-UI/api/system"
|
||||
"github.com/0xJacky/Nginx-UI/api/template"
|
||||
"github.com/0xJacky/Nginx-UI/api/terminal"
|
||||
"github.com/0xJacky/Nginx-UI/api/upstream"
|
||||
"github.com/0xJacky/Nginx-UI/api/user"
|
||||
"github.com/0xJacky/Nginx-UI/internal/middleware"
|
||||
"github.com/gin-contrib/static"
|
||||
"github.com/gin-gonic/gin"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func InitRouter() *gin.Engine {
|
||||
r := gin.New()
|
||||
r.Use(gin.Logger())
|
||||
r.Use(recovery())
|
||||
r.Use(cacheJs())
|
||||
r.Use(ipWhiteList())
|
||||
r := gin.New()
|
||||
r.Use(
|
||||
gin.Logger(),
|
||||
middleware.Recovery(),
|
||||
middleware.CacheJs(),
|
||||
middleware.IPWhiteList(),
|
||||
static.Serve("/", middleware.MustFs("")),
|
||||
)
|
||||
|
||||
//r.Use(OperationSync())
|
||||
r.NoRoute(func(c *gin.Context) {
|
||||
c.JSON(http.StatusNotFound, gin.H{
|
||||
"message": "not found",
|
||||
})
|
||||
})
|
||||
|
||||
r.Use(static.Serve("/", mustFS("")))
|
||||
root := r.Group("/api")
|
||||
{
|
||||
system.InitPublicRouter(root)
|
||||
user.InitAuthRouter(root)
|
||||
|
||||
r.NoRoute(func(c *gin.Context) {
|
||||
c.JSON(http.StatusNotFound, gin.H{
|
||||
"message": "not found",
|
||||
})
|
||||
})
|
||||
// Authorization required not websocket request
|
||||
g := root.Group("/", middleware.AuthRequired(), middleware.Proxy())
|
||||
{
|
||||
user.InitUserRouter(g)
|
||||
analytic.InitRouter(g)
|
||||
user.InitManageUserRouter(g)
|
||||
nginx.InitRouter(g)
|
||||
sites.InitRouter(g)
|
||||
streams.InitRouter(g)
|
||||
config.InitRouter(g)
|
||||
template.InitRouter(g)
|
||||
certificate.InitCertificateRouter(g)
|
||||
certificate.InitDNSCredentialRouter(g)
|
||||
certificate.InitAcmeUserRouter(g)
|
||||
system.InitPrivateRouter(g)
|
||||
settings.InitRouter(g)
|
||||
openai.InitRouter(g)
|
||||
cluster.InitRouter(g)
|
||||
notification.InitRouter(g)
|
||||
}
|
||||
|
||||
root := r.Group("/api")
|
||||
{
|
||||
system.InitPublicRouter(root)
|
||||
user.InitAuthRouter(root)
|
||||
// Authorization required and websocket request
|
||||
w := root.Group("/", middleware.AuthRequired(), middleware.ProxyWs())
|
||||
{
|
||||
analytic.InitWebSocketRouter(w)
|
||||
certificate.InitCertificateWebSocketRouter(w)
|
||||
o := w.Group("", middleware.RequireSecureSession())
|
||||
{
|
||||
terminal.InitRouter(o)
|
||||
}
|
||||
nginx.InitNginxLogRouter(w)
|
||||
upstream.InitRouter(w)
|
||||
system.InitWebSocketRouter(w)
|
||||
}
|
||||
}
|
||||
|
||||
// Authorization required not websocket request
|
||||
g := root.Group("/", authRequired(), proxy())
|
||||
{
|
||||
user.InitUserRouter(g)
|
||||
analytic.InitRouter(g)
|
||||
user.InitManageUserRouter(g)
|
||||
nginx.InitRouter(g)
|
||||
sites.InitRouter(g)
|
||||
streams.InitRouter(g)
|
||||
config.InitRouter(g)
|
||||
template.InitRouter(g)
|
||||
certificate.InitCertificateRouter(g)
|
||||
certificate.InitDNSCredentialRouter(g)
|
||||
certificate.InitAcmeUserRouter(g)
|
||||
system.InitPrivateRouter(g)
|
||||
settings.InitRouter(g)
|
||||
openai.InitRouter(g)
|
||||
cluster.InitRouter(g)
|
||||
notification.InitRouter(g)
|
||||
}
|
||||
|
||||
// Authorization required and websocket request
|
||||
w := root.Group("/", authRequired(), proxyWs())
|
||||
{
|
||||
analytic.InitWebSocketRouter(w)
|
||||
certificate.InitCertificateWebSocketRouter(w)
|
||||
o := w.Group("", required2FA())
|
||||
{
|
||||
terminal.InitRouter(o)
|
||||
}
|
||||
nginx.InitNginxLogRouter(w)
|
||||
upstream.InitRouter(w)
|
||||
system.InitWebSocketRouter(w)
|
||||
}
|
||||
}
|
||||
|
||||
return r
|
||||
return r
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue