mirror of
https://github.com/0xJacky/nginx-ui.git
synced 2025-05-12 02:45:49 +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
|
@ -5,10 +5,13 @@ import (
|
||||||
"github.com/0xJacky/Nginx-UI/internal/config"
|
"github.com/0xJacky/Nginx-UI/internal/config"
|
||||||
"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/0xJacky/Nginx-UI/model"
|
||||||
|
"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"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -18,6 +21,7 @@ func AddConfig(c *gin.Context) {
|
||||||
NewFilepath string `json:"new_filepath" binding:"required"`
|
NewFilepath string `json:"new_filepath" binding:"required"`
|
||||||
Content string `json:"content"`
|
Content string `json:"content"`
|
||||||
Overwrite bool `json:"overwrite"`
|
Overwrite bool `json:"overwrite"`
|
||||||
|
SyncNodeIds []int `json:"sync_node_ids"`
|
||||||
}
|
}
|
||||||
|
|
||||||
if !api.BindAndValid(c, &json) {
|
if !api.BindAndValid(c, &json) {
|
||||||
|
@ -41,6 +45,16 @@ func AddConfig(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check if the dir exists, if not, use mkdirAll to create the dir
|
||||||
|
dir := filepath.Dir(path)
|
||||||
|
if !helper.FileExists(dir) {
|
||||||
|
err := os.MkdirAll(dir, 0755)
|
||||||
|
if err != nil {
|
||||||
|
api.ErrHandler(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
err := os.WriteFile(path, []byte(content), 0644)
|
err := os.WriteFile(path, []byte(content), 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
api.ErrHandler(c, err)
|
api.ErrHandler(c, err)
|
||||||
|
@ -55,6 +69,24 @@ func AddConfig(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
q := query.Config
|
||||||
|
_, err = q.Where(q.Filepath.Eq(path)).Delete()
|
||||||
|
if err != nil {
|
||||||
|
api.ErrHandler(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = q.Create(&model.Config{
|
||||||
|
Name: name,
|
||||||
|
Filepath: path,
|
||||||
|
SyncNodeIds: json.SyncNodeIds,
|
||||||
|
SyncOverwrite: json.Overwrite,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
api.ErrHandler(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusOK, config.Config{
|
c.JSON(http.StatusOK, config.Config{
|
||||||
Name: name,
|
Name: name,
|
||||||
Content: content,
|
Content: content,
|
||||||
|
|
|
@ -12,6 +12,12 @@ import (
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type APIConfigResp struct {
|
||||||
|
config.Config
|
||||||
|
SyncNodeIds []int `json:"sync_node_ids" gorm:"serializer:json"`
|
||||||
|
SyncOverwrite bool `json:"sync_overwrite"`
|
||||||
|
}
|
||||||
|
|
||||||
func GetConfig(c *gin.Context) {
|
func GetConfig(c *gin.Context) {
|
||||||
name := c.Param("name")
|
name := c.Param("name")
|
||||||
|
|
||||||
|
@ -34,7 +40,7 @@ func GetConfig(c *gin.Context) {
|
||||||
api.ErrHandler(c, err)
|
api.ErrHandler(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
q := query.Config
|
||||||
g := query.ChatGPTLog
|
g := query.ChatGPTLog
|
||||||
chatgpt, err := g.Where(g.Name.Eq(path)).FirstOrCreate()
|
chatgpt, err := g.Where(g.Name.Eq(path)).FirstOrCreate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -46,11 +52,21 @@ func GetConfig(c *gin.Context) {
|
||||||
chatgpt.Content = make([]openai.ChatCompletionMessage, 0)
|
chatgpt.Content = make([]openai.ChatCompletionMessage, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusOK, config.Config{
|
cfg, err := q.Where(q.Filepath.Eq(path)).FirstOrInit()
|
||||||
|
if err != nil {
|
||||||
|
api.ErrHandler(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, APIConfigResp{
|
||||||
|
Config: config.Config{
|
||||||
Name: stat.Name(),
|
Name: stat.Name(),
|
||||||
Content: string(content),
|
Content: string(content),
|
||||||
ChatGPTMessages: chatgpt.Content,
|
ChatGPTMessages: chatgpt.Content,
|
||||||
FilePath: path,
|
FilePath: path,
|
||||||
ModifiedAt: stat.ModTime(),
|
ModifiedAt: stat.ModTime(),
|
||||||
|
},
|
||||||
|
SyncNodeIds: cfg.SyncNodeIds,
|
||||||
|
SyncOverwrite: cfg.SyncOverwrite,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,7 @@ func Mkdir(c *gin.Context) {
|
||||||
api.ErrHandler(c, err)
|
api.ErrHandler(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"message": "ok",
|
"message": "ok",
|
||||||
})
|
})
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"github.com/0xJacky/Nginx-UI/internal/config"
|
"github.com/0xJacky/Nginx-UI/internal/config"
|
||||||
"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/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/sashabaranov/go-openai"
|
"github.com/sashabaranov/go-openai"
|
||||||
|
@ -24,6 +25,8 @@ func EditConfig(c *gin.Context) {
|
||||||
Filepath string `json:"filepath" binding:"required"`
|
Filepath string `json:"filepath" binding:"required"`
|
||||||
NewFilepath string `json:"new_filepath" binding:"required"`
|
NewFilepath string `json:"new_filepath" binding:"required"`
|
||||||
Content string `json:"content"`
|
Content string `json:"content"`
|
||||||
|
Overwrite bool `json:"overwrite"`
|
||||||
|
SyncNodeIds []int `json:"sync_node_ids"`
|
||||||
}
|
}
|
||||||
if !api.BindAndValid(c, &json) {
|
if !api.BindAndValid(c, &json) {
|
||||||
return
|
return
|
||||||
|
@ -66,8 +69,25 @@ func EditConfig(c *gin.Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
g := query.ChatGPTLog
|
q := query.Config
|
||||||
|
cfg, err := q.Where(q.Filepath.Eq(json.Filepath)).FirstOrCreate()
|
||||||
|
if err != nil {
|
||||||
|
api.ErrHandler(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = q.Where(q.Filepath.Eq(json.Filepath)).Updates(&model.Config{
|
||||||
|
Name: json.Name,
|
||||||
|
Filepath: json.NewFilepath,
|
||||||
|
SyncNodeIds: json.SyncNodeIds,
|
||||||
|
SyncOverwrite: json.Overwrite,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
api.ErrHandler(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
g := query.ChatGPTLog
|
||||||
// handle rename
|
// handle rename
|
||||||
if path != json.NewFilepath {
|
if path != json.NewFilepath {
|
||||||
if helper.FileExists(json.NewFilepath) {
|
if helper.FileExists(json.NewFilepath) {
|
||||||
|
@ -87,6 +107,12 @@ func EditConfig(c *gin.Context) {
|
||||||
_, _ = g.Where(g.Name.Eq(path)).Update(g.Name, json.NewFilepath)
|
_, _ = g.Where(g.Name.Eq(path)).Update(g.Name, json.NewFilepath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = config.SyncToRemoteServer(cfg, json.NewFilepath)
|
||||||
|
if err != nil {
|
||||||
|
api.ErrHandler(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
output := nginx.Reload()
|
output := nginx.Reload()
|
||||||
if nginx.GetLogLevel(output) >= nginx.Warn {
|
if nginx.GetLogLevel(output) >= nginx.Warn {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
|
|
@ -2,7 +2,9 @@ package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/0xJacky/Nginx-UI/api"
|
"github.com/0xJacky/Nginx-UI/api"
|
||||||
|
"github.com/0xJacky/Nginx-UI/internal/config"
|
||||||
"github.com/0xJacky/Nginx-UI/internal/helper"
|
"github.com/0xJacky/Nginx-UI/internal/helper"
|
||||||
|
"github.com/0xJacky/Nginx-UI/internal/logger"
|
||||||
"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"
|
||||||
|
@ -15,11 +17,13 @@ func Rename(c *gin.Context) {
|
||||||
BasePath string `json:"base_path"`
|
BasePath string `json:"base_path"`
|
||||||
OrigName string `json:"orig_name"`
|
OrigName string `json:"orig_name"`
|
||||||
NewName string `json:"new_name"`
|
NewName string `json:"new_name"`
|
||||||
|
SyncNodeIds []int `json:"sync_node_ids" gorm:"serializer:json"`
|
||||||
}
|
}
|
||||||
if !api.BindAndValid(c, &json) {
|
if !api.BindAndValid(c, &json) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if json.OrigName == json.OrigName {
|
logger.Debug(json)
|
||||||
|
if json.OrigName == json.NewName {
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"message": "ok",
|
"message": "ok",
|
||||||
})
|
})
|
||||||
|
@ -55,11 +59,36 @@ func Rename(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !stat.IsDir() {
|
|
||||||
// update ChatGPT records
|
// update ChatGPT records
|
||||||
g := query.ChatGPTLog
|
g := query.ChatGPTLog
|
||||||
|
q := query.Config
|
||||||
|
cfg, err := q.Where(q.Filepath.Eq(origFullPath)).FirstOrInit()
|
||||||
|
if err != nil {
|
||||||
|
api.ErrHandler(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !stat.IsDir() {
|
||||||
_, _ = g.Where(g.Name.Eq(newFullPath)).Delete()
|
_, _ = g.Where(g.Name.Eq(newFullPath)).Delete()
|
||||||
_, _ = g.Where(g.Name.Eq(origFullPath)).Update(g.Name, newFullPath)
|
_, _ = g.Where(g.Name.Eq(origFullPath)).Update(g.Name, newFullPath)
|
||||||
|
// for file, the sync policy for this file is used
|
||||||
|
json.SyncNodeIds = cfg.SyncNodeIds
|
||||||
|
} else {
|
||||||
|
// is directory, update all records under the directory
|
||||||
|
_, _ = g.Where(g.Name.Like(origFullPath+"%")).Update(g.Name, g.Name.Replace(origFullPath, newFullPath))
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = q.Where(q.Filepath.Eq(origFullPath)).Update(q.Filepath, newFullPath)
|
||||||
|
if err != nil {
|
||||||
|
api.ErrHandler(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(json.SyncNodeIds) > 0 {
|
||||||
|
err = config.SyncRenameOnRemoteServer(origFullPath, newFullPath, json.SyncNodeIds)
|
||||||
|
if err != nil {
|
||||||
|
api.ErrHandler(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import "github.com/gin-gonic/gin"
|
import (
|
||||||
|
"github.com/0xJacky/Nginx-UI/internal/middleware"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
func InitRouter(r *gin.RouterGroup) {
|
func InitRouter(r *gin.RouterGroup) {
|
||||||
r.GET("config_base_path", GetBasePath)
|
r.GET("config_base_path", GetBasePath)
|
||||||
|
@ -9,6 +12,10 @@ func InitRouter(r *gin.RouterGroup) {
|
||||||
r.GET("config/*name", GetConfig)
|
r.GET("config/*name", GetConfig)
|
||||||
r.POST("config", AddConfig)
|
r.POST("config", AddConfig)
|
||||||
r.POST("config/*name", EditConfig)
|
r.POST("config/*name", EditConfig)
|
||||||
r.POST("config_mkdir", Mkdir)
|
|
||||||
r.POST("config_rename", Rename)
|
o := r.Group("", middleware.RequireSecureSession())
|
||||||
|
{
|
||||||
|
o.POST("config_mkdir", Mkdir)
|
||||||
|
o.POST("config_rename", Rename)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -170,6 +170,13 @@ func OTPStatus(c *gin.Context) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SecureSessionStatus(c *gin.Context) {
|
||||||
|
// if you can visit this endpoint, you are already in a secure session
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"status": true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func StartSecure2FASession(c *gin.Context) {
|
func StartSecure2FASession(c *gin.Context) {
|
||||||
var json struct {
|
var json struct {
|
||||||
OTP string `json:"otp"`
|
OTP string `json:"otp"`
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
package user
|
package user
|
||||||
|
|
||||||
import "github.com/gin-gonic/gin"
|
import (
|
||||||
|
"github.com/0xJacky/Nginx-UI/internal/middleware"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
func InitAuthRouter(r *gin.RouterGroup) {
|
func InitAuthRouter(r *gin.RouterGroup) {
|
||||||
r.POST("/login", Login)
|
r.POST("/login", Login)
|
||||||
|
@ -23,5 +26,8 @@ func InitUserRouter(r *gin.RouterGroup) {
|
||||||
r.GET("/otp_secret", GenerateTOTP)
|
r.GET("/otp_secret", GenerateTOTP)
|
||||||
r.POST("/otp_enroll", EnrollTOTP)
|
r.POST("/otp_enroll", EnrollTOTP)
|
||||||
r.POST("/otp_reset", ResetOTP)
|
r.POST("/otp_reset", ResetOTP)
|
||||||
|
|
||||||
|
r.GET("/otp_secure_session_status",
|
||||||
|
middleware.RequireSecureSession(), SecureSessionStatus)
|
||||||
r.POST("/otp_secure_session", StartSecure2FASession)
|
r.POST("/otp_secure_session", StartSecure2FASession)
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,8 @@ export interface Config {
|
||||||
chatgpt_messages: ChatComplicationMessage[]
|
chatgpt_messages: ChatComplicationMessage[]
|
||||||
filepath: string
|
filepath: string
|
||||||
modified_at: string
|
modified_at: string
|
||||||
|
sync_node_ids?: number[]
|
||||||
|
sync_overwrite?: false
|
||||||
}
|
}
|
||||||
|
|
||||||
class ConfigCurd extends Curd<Config> {
|
class ConfigCurd extends Curd<Config> {
|
||||||
|
@ -23,8 +25,13 @@ class ConfigCurd extends Curd<Config> {
|
||||||
return http.post('/config_mkdir', { base_path: basePath, folder_name: name })
|
return http.post('/config_mkdir', { base_path: basePath, folder_name: name })
|
||||||
}
|
}
|
||||||
|
|
||||||
rename(basePath: string, origName: string, newName: string) {
|
rename(basePath: string, origName: string, newName: string, syncNodeIds: number[]) {
|
||||||
return http.post('/config_rename', { base_path: basePath, orig_name: origName, new_name: newName })
|
return http.post('/config_rename', {
|
||||||
|
base_path: basePath,
|
||||||
|
orig_name: origName,
|
||||||
|
new_name: newName,
|
||||||
|
sync_node_ids: syncNodeIds,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,9 @@ const otp = {
|
||||||
recovery_code,
|
recovery_code,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
secure_session_status() {
|
||||||
|
return http.get('/otp_secure_session_status')
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export default otp
|
export default otp
|
||||||
|
|
18
app/src/components/Notification/cert.ts
Normal file
18
app/src/components/Notification/cert.ts
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
export function syncCertificateSuccess(text: string) {
|
||||||
|
const data = JSON.parse(text)
|
||||||
|
|
||||||
|
return $gettext('Sync Certificate %{cert_name} to %{env_name} successfully',
|
||||||
|
{ cert_name: data.cert_name, env_name: data.env_name })
|
||||||
|
}
|
||||||
|
|
||||||
|
export function syncCertificateError(text: string) {
|
||||||
|
const data = JSON.parse(text)
|
||||||
|
|
||||||
|
if (data.status_code === 404) {
|
||||||
|
return $gettext('Sync Certificate %{cert_name} to %{env_name} failed, please upgrade the remote Nginx UI to the latest version',
|
||||||
|
{ cert_name: data.cert_name, env_name: data.env_name }, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
return $gettext('Sync Certificate %{cert_name} to %{env_name} failed, response: %{resp}',
|
||||||
|
{ cert_name: data.cert_name, env_name: data.env_name, resp: data.resp_body }, true)
|
||||||
|
}
|
37
app/src/components/Notification/config.ts
Normal file
37
app/src/components/Notification/config.ts
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
export function syncConfigSuccess(text: string) {
|
||||||
|
const data = JSON.parse(text)
|
||||||
|
|
||||||
|
return $gettext('Sync Config %{config_name} to %{env_name} successfully',
|
||||||
|
{ config_name: data.config_name, env_name: data.env_name })
|
||||||
|
}
|
||||||
|
|
||||||
|
export function syncConfigError(text: string) {
|
||||||
|
const data = JSON.parse(text)
|
||||||
|
|
||||||
|
if (data.status_code === 404) {
|
||||||
|
return $gettext('Sync config %{cert_name} to %{env_name} failed, please upgrade the remote Nginx UI to the latest version',
|
||||||
|
{ config_name: data.config_name, env_name: data.env_name }, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
return $gettext('Sync config %{config_name} to %{env_name} failed, response: %{resp}',
|
||||||
|
{ cert_name: data.cert_name, env_name: data.env_name, resp: data.resp_body }, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function syncRenameConfigSuccess(text: string) {
|
||||||
|
const data = JSON.parse(text)
|
||||||
|
|
||||||
|
return $gettext('Rename %{orig_path} to %{new_path} on %{env_name} successfully',
|
||||||
|
{ orig_path: data.orig_path, new_path: data.orig_path, env_name: data.env_name })
|
||||||
|
}
|
||||||
|
|
||||||
|
export function syncRenameConfigError(text: string) {
|
||||||
|
const data = JSON.parse(text)
|
||||||
|
|
||||||
|
if (data.status_code === 404) {
|
||||||
|
return $gettext('Rename %{orig_path} to %{new_path} on %{env_name} failed, please upgrade the remote Nginx UI to the latest version',
|
||||||
|
{ orig_path: data.orig_path, new_path: data.orig_path, env_name: data.env_name }, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
return $gettext('Rename %{orig_path} to %{new_path} on %{env_name} failed, response: %{resp}',
|
||||||
|
{ orig_path: data.orig_path, new_path: data.orig_path, resp: data.resp_body, env_name: data.env_name }, true)
|
||||||
|
}
|
|
@ -1,4 +1,11 @@
|
||||||
import type { customRender } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
|
import type { customRender } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
|
||||||
|
import { syncCertificateError, syncCertificateSuccess } from '@/components/Notification/cert'
|
||||||
|
import {
|
||||||
|
syncConfigError,
|
||||||
|
syncConfigSuccess,
|
||||||
|
syncRenameConfigError,
|
||||||
|
syncRenameConfigSuccess,
|
||||||
|
} from '@/components/Notification/config'
|
||||||
|
|
||||||
export const detailRender = (args: customRender) => {
|
export const detailRender = (args: customRender) => {
|
||||||
switch (args.record.title) {
|
switch (args.record.title) {
|
||||||
|
@ -6,26 +13,15 @@ export const detailRender = (args: customRender) => {
|
||||||
return syncCertificateSuccess(args.text)
|
return syncCertificateSuccess(args.text)
|
||||||
case 'Sync Certificate Error':
|
case 'Sync Certificate Error':
|
||||||
return syncCertificateError(args.text)
|
return syncCertificateError(args.text)
|
||||||
|
case 'Sync Rename Configuration Success':
|
||||||
|
return syncRenameConfigSuccess(args.text)
|
||||||
|
case 'Sync Rename Configuration Error':
|
||||||
|
return syncRenameConfigError(args.text)
|
||||||
|
case 'Sync Configuration Success':
|
||||||
|
return syncConfigSuccess(args.text)
|
||||||
|
case 'Sync Configuration Error':
|
||||||
|
return syncConfigError(args.text)
|
||||||
default:
|
default:
|
||||||
return args.text
|
return args.text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function syncCertificateSuccess(text: string) {
|
|
||||||
const data = JSON.parse(text)
|
|
||||||
|
|
||||||
return $gettext('Sync Certificate %{cert_name} to %{env_name} successfully',
|
|
||||||
{ cert_name: data.cert_name, env_name: data.env_name })
|
|
||||||
}
|
|
||||||
|
|
||||||
function syncCertificateError(text: string) {
|
|
||||||
const data = JSON.parse(text)
|
|
||||||
|
|
||||||
if (data.status_code === 404) {
|
|
||||||
return $gettext('Sync Certificate %{cert_name} to %{env_name} failed, please upgrade the remote Nginx UI to the latest version',
|
|
||||||
{ cert_name: data.cert_name, env_name: data.env_name }, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
return $gettext('Sync Certificate %{cert_name} to %{env_name} failed, response: %{resp}',
|
|
||||||
{ cert_name: data.cert_name, env_name: data.env_name, resp: data.resp_body }, true)
|
|
||||||
}
|
|
||||||
|
|
|
@ -5,11 +5,6 @@ import OTPAuthorization from '@/components/OTP/OTPAuthorization.vue'
|
||||||
import otp from '@/api/otp'
|
import otp from '@/api/otp'
|
||||||
import { useUserStore } from '@/pinia'
|
import { useUserStore } from '@/pinia'
|
||||||
|
|
||||||
export interface OTPModalProps {
|
|
||||||
onOk?: (secureSessionId: string) => void
|
|
||||||
onCancel?: () => void
|
|
||||||
}
|
|
||||||
|
|
||||||
const useOTPModal = () => {
|
const useOTPModal = () => {
|
||||||
const refOTPAuthorization = ref<typeof OTPAuthorization>()
|
const refOTPAuthorization = ref<typeof OTPAuthorization>()
|
||||||
const randomId = Math.random().toString(36).substring(2, 8)
|
const randomId = Math.random().toString(36).substring(2, 8)
|
||||||
|
@ -26,10 +21,12 @@ const useOTPModal = () => {
|
||||||
document.head.appendChild(style)
|
document.head.appendChild(style)
|
||||||
}
|
}
|
||||||
|
|
||||||
const open = async ({ onOk, onCancel }: OTPModalProps) => {
|
const open = async (): Promise<string> => {
|
||||||
const { status } = await otp.status()
|
const { status } = await otp.status()
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
if (!status) {
|
if (!status) {
|
||||||
onOk?.('')
|
resolve('')
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -37,7 +34,7 @@ const useOTPModal = () => {
|
||||||
const cookies = useCookies(['nginx-ui-2fa'])
|
const cookies = useCookies(['nginx-ui-2fa'])
|
||||||
const ssid = cookies.get('secure_session_id')
|
const ssid = cookies.get('secure_session_id')
|
||||||
if (ssid) {
|
if (ssid) {
|
||||||
onOk?.(ssid)
|
resolve(ssid)
|
||||||
secureSessionId.value = ssid
|
secureSessionId.value = ssid
|
||||||
|
|
||||||
return
|
return
|
||||||
|
@ -56,7 +53,7 @@ const useOTPModal = () => {
|
||||||
const verify = (passcode: string, recovery: string) => {
|
const verify = (passcode: string, recovery: string) => {
|
||||||
otp.start_secure_session(passcode, recovery).then(r => {
|
otp.start_secure_session(passcode, recovery).then(r => {
|
||||||
cookies.set('secure_session_id', r.session_id, { maxAge: 60 * 3 })
|
cookies.set('secure_session_id', r.session_id, { maxAge: 60 * 3 })
|
||||||
onOk?.(r.session_id)
|
resolve(r.session_id)
|
||||||
close()
|
close()
|
||||||
secureSessionId.value = r.session_id
|
secureSessionId.value = r.session_id
|
||||||
}).catch(async () => {
|
}).catch(async () => {
|
||||||
|
@ -74,7 +71,8 @@ const useOTPModal = () => {
|
||||||
footer: false,
|
footer: false,
|
||||||
onCancel: () => {
|
onCancel: () => {
|
||||||
close()
|
close()
|
||||||
onCancel?.()
|
// eslint-disable-next-line prefer-promise-reject-errors
|
||||||
|
reject()
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
default: () => h(
|
default: () => h(
|
||||||
|
@ -87,7 +85,8 @@ const useOTPModal = () => {
|
||||||
),
|
),
|
||||||
})
|
})
|
||||||
|
|
||||||
render(vnode, container)
|
render(vnode, container!)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return { open }
|
return { open }
|
||||||
|
|
|
@ -194,9 +194,9 @@ msgid "Auto-renewal enabled for %{name}"
|
||||||
msgstr "Auto-renewal enabled for %{name}"
|
msgstr "Auto-renewal enabled for %{name}"
|
||||||
|
|
||||||
#: src/views/certificate/CertificateEditor.vue:247
|
#: src/views/certificate/CertificateEditor.vue:247
|
||||||
#: src/views/config/Config.vue:143 src/views/config/ConfigEditor.vue:196
|
#: src/views/config/ConfigEditor.vue:196 src/views/config/ConfigList.vue:173
|
||||||
#: src/views/domain/DomainEdit.vue:253 src/views/nginx_log/NginxLog.vue:168
|
#: src/views/config/ConfigList.vue:99 src/views/domain/DomainEdit.vue:253
|
||||||
#: src/views/stream/StreamEdit.vue:245
|
#: src/views/nginx_log/NginxLog.vue:168 src/views/stream/StreamEdit.vue:245
|
||||||
msgid "Back"
|
msgid "Back"
|
||||||
msgstr "Back"
|
msgstr "Back"
|
||||||
|
|
||||||
|
@ -369,7 +369,7 @@ msgstr ""
|
||||||
msgid "Configuration Name"
|
msgid "Configuration Name"
|
||||||
msgstr "Configuration Name"
|
msgstr "Configuration Name"
|
||||||
|
|
||||||
#: src/views/config/Config.vue:91
|
#: src/views/config/ConfigList.vue:91
|
||||||
msgid "Configurations"
|
msgid "Configurations"
|
||||||
msgstr "Configurations"
|
msgstr "Configurations"
|
||||||
|
|
||||||
|
@ -420,12 +420,12 @@ msgstr "Created at"
|
||||||
msgid "Create Another"
|
msgid "Create Another"
|
||||||
msgstr "Create Another"
|
msgstr "Create Another"
|
||||||
|
|
||||||
#: src/views/config/Config.vue:99
|
#: src/views/config/ConfigList.vue:109
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
msgid "Create File"
|
msgid "Create File"
|
||||||
msgstr "Created at"
|
msgstr "Created at"
|
||||||
|
|
||||||
#: src/views/config/components/Mkdir.vue:50 src/views/config/Config.vue:100
|
#: src/views/config/components/Mkdir.vue:50 src/views/config/ConfigList.vue:116
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
msgid "Create Folder"
|
msgid "Create Folder"
|
||||||
msgstr "Create Another"
|
msgstr "Create Another"
|
||||||
|
@ -474,8 +474,8 @@ msgid ""
|
||||||
"indicator."
|
"indicator."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/routes/index.ts:39 src/views/config/Config.vue:57
|
#: src/routes/index.ts:39 src/views/config/ConfigEditor.vue:118
|
||||||
#: src/views/config/ConfigEditor.vue:118 src/views/config/ConfigEditor.vue:79
|
#: src/views/config/ConfigEditor.vue:79 src/views/config/ConfigList.vue:57
|
||||||
msgid "Dashboard"
|
msgid "Dashboard"
|
||||||
msgstr "Dashboard"
|
msgstr "Dashboard"
|
||||||
|
|
||||||
|
@ -803,6 +803,10 @@ msgstr "Enabled successfully"
|
||||||
msgid "Encrypt website with Let's Encrypt"
|
msgid "Encrypt website with Let's Encrypt"
|
||||||
msgstr "Encrypt website with Let's Encrypt"
|
msgstr "Encrypt website with Let's Encrypt"
|
||||||
|
|
||||||
|
#: src/views/config/ConfigList.vue:151
|
||||||
|
msgid "Enter"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/routes/index.ts:228 src/views/environment/Environment.vue:34
|
#: src/routes/index.ts:228 src/views/environment/Environment.vue:34
|
||||||
msgid "Environment"
|
msgid "Environment"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
@ -1191,8 +1195,8 @@ msgstr ""
|
||||||
"Make sure you have configured a reverse proxy for .well-known directory to "
|
"Make sure you have configured a reverse proxy for .well-known directory to "
|
||||||
"HTTPChallengePort (default: 9180) before getting the certificate."
|
"HTTPChallengePort (default: 9180) before getting the certificate."
|
||||||
|
|
||||||
#: src/routes/index.ts:102 src/views/config/Config.vue:62
|
#: src/routes/index.ts:102 src/views/config/ConfigEditor.vue:123
|
||||||
#: src/views/config/ConfigEditor.vue:123 src/views/config/ConfigEditor.vue:84
|
#: src/views/config/ConfigEditor.vue:84 src/views/config/ConfigList.vue:62
|
||||||
msgid "Manage Configs"
|
msgid "Manage Configs"
|
||||||
msgstr "Manage Configs"
|
msgstr "Manage Configs"
|
||||||
|
|
||||||
|
@ -1239,6 +1243,7 @@ msgstr "Advance Mode"
|
||||||
#: src/components/ChatGPT/ChatGPT.vue:248
|
#: src/components/ChatGPT/ChatGPT.vue:248
|
||||||
#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:181
|
#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:181
|
||||||
#: src/components/StdDesign/StdDataDisplay/StdTable.vue:532
|
#: src/components/StdDesign/StdDataDisplay/StdTable.vue:532
|
||||||
|
#: src/views/config/ConfigList.vue:151
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
msgid "Modify"
|
msgid "Modify"
|
||||||
msgstr "Modify Config"
|
msgstr "Modify Config"
|
||||||
|
@ -1703,7 +1708,8 @@ msgstr "Saved successfully"
|
||||||
msgid "Removed successfully"
|
msgid "Removed successfully"
|
||||||
msgstr "Saved successfully"
|
msgstr "Saved successfully"
|
||||||
|
|
||||||
#: src/views/config/components/Rename.vue:52 src/views/config/Config.vue:130
|
#: src/views/config/components/Rename.vue:52
|
||||||
|
#: src/views/config/ConfigList.vue:159
|
||||||
#: src/views/domain/ngx_conf/NgxUpstream.vue:123
|
#: src/views/domain/ngx_conf/NgxUpstream.vue:123
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
msgid "Rename"
|
msgid "Rename"
|
||||||
|
|
|
@ -194,9 +194,9 @@ msgid "Auto-renewal enabled for %{name}"
|
||||||
msgstr "Renovación automática habilitada por %{name}"
|
msgstr "Renovación automática habilitada por %{name}"
|
||||||
|
|
||||||
#: src/views/certificate/CertificateEditor.vue:247
|
#: src/views/certificate/CertificateEditor.vue:247
|
||||||
#: src/views/config/Config.vue:143 src/views/config/ConfigEditor.vue:196
|
#: src/views/config/ConfigEditor.vue:196 src/views/config/ConfigList.vue:173
|
||||||
#: src/views/domain/DomainEdit.vue:253 src/views/nginx_log/NginxLog.vue:168
|
#: src/views/config/ConfigList.vue:99 src/views/domain/DomainEdit.vue:253
|
||||||
#: src/views/stream/StreamEdit.vue:245
|
#: src/views/nginx_log/NginxLog.vue:168 src/views/stream/StreamEdit.vue:245
|
||||||
msgid "Back"
|
msgid "Back"
|
||||||
msgstr "Volver"
|
msgstr "Volver"
|
||||||
|
|
||||||
|
@ -362,7 +362,7 @@ msgstr "El archivo de configuración se probó exitosamente"
|
||||||
msgid "Configuration Name"
|
msgid "Configuration Name"
|
||||||
msgstr "Nombre de la configuración"
|
msgstr "Nombre de la configuración"
|
||||||
|
|
||||||
#: src/views/config/Config.vue:91
|
#: src/views/config/ConfigList.vue:91
|
||||||
msgid "Configurations"
|
msgid "Configurations"
|
||||||
msgstr "Configuraciones"
|
msgstr "Configuraciones"
|
||||||
|
|
||||||
|
@ -412,12 +412,12 @@ msgstr "Crear"
|
||||||
msgid "Create Another"
|
msgid "Create Another"
|
||||||
msgstr "Crear otro"
|
msgstr "Crear otro"
|
||||||
|
|
||||||
#: src/views/config/Config.vue:99
|
#: src/views/config/ConfigList.vue:109
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
msgid "Create File"
|
msgid "Create File"
|
||||||
msgstr "Crear"
|
msgstr "Crear"
|
||||||
|
|
||||||
#: src/views/config/components/Mkdir.vue:50 src/views/config/Config.vue:100
|
#: src/views/config/components/Mkdir.vue:50 src/views/config/ConfigList.vue:116
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
msgid "Create Folder"
|
msgid "Create Folder"
|
||||||
msgstr "Crear otro"
|
msgstr "Crear otro"
|
||||||
|
@ -466,8 +466,8 @@ msgid ""
|
||||||
"indicator."
|
"indicator."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/routes/index.ts:39 src/views/config/Config.vue:57
|
#: src/routes/index.ts:39 src/views/config/ConfigEditor.vue:118
|
||||||
#: src/views/config/ConfigEditor.vue:118 src/views/config/ConfigEditor.vue:79
|
#: src/views/config/ConfigEditor.vue:79 src/views/config/ConfigList.vue:57
|
||||||
msgid "Dashboard"
|
msgid "Dashboard"
|
||||||
msgstr "Panel"
|
msgstr "Panel"
|
||||||
|
|
||||||
|
@ -778,6 +778,10 @@ msgstr "Habilitado con éxito"
|
||||||
msgid "Encrypt website with Let's Encrypt"
|
msgid "Encrypt website with Let's Encrypt"
|
||||||
msgstr "Encriptar sitio web con Let's Encrypt"
|
msgstr "Encriptar sitio web con Let's Encrypt"
|
||||||
|
|
||||||
|
#: src/views/config/ConfigList.vue:151
|
||||||
|
msgid "Enter"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/routes/index.ts:228 src/views/environment/Environment.vue:34
|
#: src/routes/index.ts:228 src/views/environment/Environment.vue:34
|
||||||
msgid "Environment"
|
msgid "Environment"
|
||||||
msgstr "Entorno"
|
msgstr "Entorno"
|
||||||
|
@ -1152,8 +1156,8 @@ msgstr ""
|
||||||
"Asegúrese de haber configurado un proxy reverso para el directorio .well-"
|
"Asegúrese de haber configurado un proxy reverso para el directorio .well-"
|
||||||
"known en HTTPChallengePort antes de obtener el certificado."
|
"known en HTTPChallengePort antes de obtener el certificado."
|
||||||
|
|
||||||
#: src/routes/index.ts:102 src/views/config/Config.vue:62
|
#: src/routes/index.ts:102 src/views/config/ConfigEditor.vue:123
|
||||||
#: src/views/config/ConfigEditor.vue:123 src/views/config/ConfigEditor.vue:84
|
#: src/views/config/ConfigEditor.vue:84 src/views/config/ConfigList.vue:62
|
||||||
msgid "Manage Configs"
|
msgid "Manage Configs"
|
||||||
msgstr "Administrar configuraciones"
|
msgstr "Administrar configuraciones"
|
||||||
|
|
||||||
|
@ -1198,6 +1202,7 @@ msgstr "Modo de ejecución"
|
||||||
#: src/components/ChatGPT/ChatGPT.vue:248
|
#: src/components/ChatGPT/ChatGPT.vue:248
|
||||||
#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:181
|
#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:181
|
||||||
#: src/components/StdDesign/StdDataDisplay/StdTable.vue:532
|
#: src/components/StdDesign/StdDataDisplay/StdTable.vue:532
|
||||||
|
#: src/views/config/ConfigList.vue:151
|
||||||
msgid "Modify"
|
msgid "Modify"
|
||||||
msgstr "Modificar"
|
msgstr "Modificar"
|
||||||
|
|
||||||
|
@ -1660,7 +1665,8 @@ msgstr "Eliminado con éxito"
|
||||||
msgid "Removed successfully"
|
msgid "Removed successfully"
|
||||||
msgstr "Eliminado con éxito"
|
msgstr "Eliminado con éxito"
|
||||||
|
|
||||||
#: src/views/config/components/Rename.vue:52 src/views/config/Config.vue:130
|
#: src/views/config/components/Rename.vue:52
|
||||||
|
#: src/views/config/ConfigList.vue:159
|
||||||
#: src/views/domain/ngx_conf/NgxUpstream.vue:123
|
#: src/views/domain/ngx_conf/NgxUpstream.vue:123
|
||||||
msgid "Rename"
|
msgid "Rename"
|
||||||
msgstr "Renombrar"
|
msgstr "Renombrar"
|
||||||
|
|
|
@ -197,9 +197,9 @@ msgid "Auto-renewal enabled for %{name}"
|
||||||
msgstr "Renouvellement automatique activé pour %{name}"
|
msgstr "Renouvellement automatique activé pour %{name}"
|
||||||
|
|
||||||
#: src/views/certificate/CertificateEditor.vue:247
|
#: src/views/certificate/CertificateEditor.vue:247
|
||||||
#: src/views/config/Config.vue:143 src/views/config/ConfigEditor.vue:196
|
#: src/views/config/ConfigEditor.vue:196 src/views/config/ConfigList.vue:173
|
||||||
#: src/views/domain/DomainEdit.vue:253 src/views/nginx_log/NginxLog.vue:168
|
#: src/views/config/ConfigList.vue:99 src/views/domain/DomainEdit.vue:253
|
||||||
#: src/views/stream/StreamEdit.vue:245
|
#: src/views/nginx_log/NginxLog.vue:168 src/views/stream/StreamEdit.vue:245
|
||||||
msgid "Back"
|
msgid "Back"
|
||||||
msgstr "Retour"
|
msgstr "Retour"
|
||||||
|
|
||||||
|
@ -369,7 +369,7 @@ msgstr "Le fichier de configuration est testé avec succès"
|
||||||
msgid "Configuration Name"
|
msgid "Configuration Name"
|
||||||
msgstr "Nom de la configuration"
|
msgstr "Nom de la configuration"
|
||||||
|
|
||||||
#: src/views/config/Config.vue:91
|
#: src/views/config/ConfigList.vue:91
|
||||||
msgid "Configurations"
|
msgid "Configurations"
|
||||||
msgstr "Configurations"
|
msgstr "Configurations"
|
||||||
|
|
||||||
|
@ -420,12 +420,12 @@ msgstr "Créé le"
|
||||||
msgid "Create Another"
|
msgid "Create Another"
|
||||||
msgstr "Créer un autre"
|
msgstr "Créer un autre"
|
||||||
|
|
||||||
#: src/views/config/Config.vue:99
|
#: src/views/config/ConfigList.vue:109
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
msgid "Create File"
|
msgid "Create File"
|
||||||
msgstr "Créé le"
|
msgstr "Créé le"
|
||||||
|
|
||||||
#: src/views/config/components/Mkdir.vue:50 src/views/config/Config.vue:100
|
#: src/views/config/components/Mkdir.vue:50 src/views/config/ConfigList.vue:116
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
msgid "Create Folder"
|
msgid "Create Folder"
|
||||||
msgstr "Créer un autre"
|
msgstr "Créer un autre"
|
||||||
|
@ -474,8 +474,8 @@ msgid ""
|
||||||
"indicator."
|
"indicator."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/routes/index.ts:39 src/views/config/Config.vue:57
|
#: src/routes/index.ts:39 src/views/config/ConfigEditor.vue:118
|
||||||
#: src/views/config/ConfigEditor.vue:118 src/views/config/ConfigEditor.vue:79
|
#: src/views/config/ConfigEditor.vue:79 src/views/config/ConfigList.vue:57
|
||||||
msgid "Dashboard"
|
msgid "Dashboard"
|
||||||
msgstr "Dashboard"
|
msgstr "Dashboard"
|
||||||
|
|
||||||
|
@ -803,6 +803,10 @@ msgstr "Activé avec succès"
|
||||||
msgid "Encrypt website with Let's Encrypt"
|
msgid "Encrypt website with Let's Encrypt"
|
||||||
msgstr "Crypter le site Web avec Let's Encrypt"
|
msgstr "Crypter le site Web avec Let's Encrypt"
|
||||||
|
|
||||||
|
#: src/views/config/ConfigList.vue:151
|
||||||
|
msgid "Enter"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/routes/index.ts:228 src/views/environment/Environment.vue:34
|
#: src/routes/index.ts:228 src/views/environment/Environment.vue:34
|
||||||
msgid "Environment"
|
msgid "Environment"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
@ -1193,8 +1197,8 @@ msgstr ""
|
||||||
"Assurez vous d'avoir configuré un reverse proxy pour le répertoire .well-"
|
"Assurez vous d'avoir configuré un reverse proxy pour le répertoire .well-"
|
||||||
"known vers HTTPChallengePort avant d'obtenir le certificat."
|
"known vers HTTPChallengePort avant d'obtenir le certificat."
|
||||||
|
|
||||||
#: src/routes/index.ts:102 src/views/config/Config.vue:62
|
#: src/routes/index.ts:102 src/views/config/ConfigEditor.vue:123
|
||||||
#: src/views/config/ConfigEditor.vue:123 src/views/config/ConfigEditor.vue:84
|
#: src/views/config/ConfigEditor.vue:84 src/views/config/ConfigList.vue:62
|
||||||
msgid "Manage Configs"
|
msgid "Manage Configs"
|
||||||
msgstr "Gérer les configurations"
|
msgstr "Gérer les configurations"
|
||||||
|
|
||||||
|
@ -1241,6 +1245,7 @@ msgstr "Mode d'exécution"
|
||||||
#: src/components/ChatGPT/ChatGPT.vue:248
|
#: src/components/ChatGPT/ChatGPT.vue:248
|
||||||
#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:181
|
#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:181
|
||||||
#: src/components/StdDesign/StdDataDisplay/StdTable.vue:532
|
#: src/components/StdDesign/StdDataDisplay/StdTable.vue:532
|
||||||
|
#: src/views/config/ConfigList.vue:151
|
||||||
msgid "Modify"
|
msgid "Modify"
|
||||||
msgstr "Modifier"
|
msgstr "Modifier"
|
||||||
|
|
||||||
|
@ -1710,7 +1715,8 @@ msgstr "Enregistré avec succès"
|
||||||
msgid "Removed successfully"
|
msgid "Removed successfully"
|
||||||
msgstr "Enregistré avec succès"
|
msgstr "Enregistré avec succès"
|
||||||
|
|
||||||
#: src/views/config/components/Rename.vue:52 src/views/config/Config.vue:130
|
#: src/views/config/components/Rename.vue:52
|
||||||
|
#: src/views/config/ConfigList.vue:159
|
||||||
#: src/views/domain/ngx_conf/NgxUpstream.vue:123
|
#: src/views/domain/ngx_conf/NgxUpstream.vue:123
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
msgid "Rename"
|
msgid "Rename"
|
||||||
|
|
|
@ -193,9 +193,9 @@ msgid "Auto-renewal enabled for %{name}"
|
||||||
msgstr "%{name}에 대한 자동 갱신 활성화됨"
|
msgstr "%{name}에 대한 자동 갱신 활성화됨"
|
||||||
|
|
||||||
#: src/views/certificate/CertificateEditor.vue:247
|
#: src/views/certificate/CertificateEditor.vue:247
|
||||||
#: src/views/config/Config.vue:143 src/views/config/ConfigEditor.vue:196
|
#: src/views/config/ConfigEditor.vue:196 src/views/config/ConfigList.vue:173
|
||||||
#: src/views/domain/DomainEdit.vue:253 src/views/nginx_log/NginxLog.vue:168
|
#: src/views/config/ConfigList.vue:99 src/views/domain/DomainEdit.vue:253
|
||||||
#: src/views/stream/StreamEdit.vue:245
|
#: src/views/nginx_log/NginxLog.vue:168 src/views/stream/StreamEdit.vue:245
|
||||||
msgid "Back"
|
msgid "Back"
|
||||||
msgstr "뒤로"
|
msgstr "뒤로"
|
||||||
|
|
||||||
|
@ -360,7 +360,7 @@ msgstr "구성 파일 테스트 성공"
|
||||||
msgid "Configuration Name"
|
msgid "Configuration Name"
|
||||||
msgstr "구성 이름"
|
msgstr "구성 이름"
|
||||||
|
|
||||||
#: src/views/config/Config.vue:91
|
#: src/views/config/ConfigList.vue:91
|
||||||
msgid "Configurations"
|
msgid "Configurations"
|
||||||
msgstr "구성들"
|
msgstr "구성들"
|
||||||
|
|
||||||
|
@ -410,12 +410,12 @@ msgstr "생성"
|
||||||
msgid "Create Another"
|
msgid "Create Another"
|
||||||
msgstr "다른 것 생성하기"
|
msgstr "다른 것 생성하기"
|
||||||
|
|
||||||
#: src/views/config/Config.vue:99
|
#: src/views/config/ConfigList.vue:109
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
msgid "Create File"
|
msgid "Create File"
|
||||||
msgstr "생성"
|
msgstr "생성"
|
||||||
|
|
||||||
#: src/views/config/components/Mkdir.vue:50 src/views/config/Config.vue:100
|
#: src/views/config/components/Mkdir.vue:50 src/views/config/ConfigList.vue:116
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
msgid "Create Folder"
|
msgid "Create Folder"
|
||||||
msgstr "다른 것 생성하기"
|
msgstr "다른 것 생성하기"
|
||||||
|
@ -464,8 +464,8 @@ msgid ""
|
||||||
"indicator."
|
"indicator."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/routes/index.ts:39 src/views/config/Config.vue:57
|
#: src/routes/index.ts:39 src/views/config/ConfigEditor.vue:118
|
||||||
#: src/views/config/ConfigEditor.vue:118 src/views/config/ConfigEditor.vue:79
|
#: src/views/config/ConfigEditor.vue:79 src/views/config/ConfigList.vue:57
|
||||||
msgid "Dashboard"
|
msgid "Dashboard"
|
||||||
msgstr "대시보드"
|
msgstr "대시보드"
|
||||||
|
|
||||||
|
@ -776,6 +776,11 @@ msgstr "성공적으로 활성화됨"
|
||||||
msgid "Encrypt website with Let's Encrypt"
|
msgid "Encrypt website with Let's Encrypt"
|
||||||
msgstr "Let's Encrypt로 웹사이트 암호화"
|
msgstr "Let's Encrypt로 웹사이트 암호화"
|
||||||
|
|
||||||
|
#: src/views/config/ConfigList.vue:151
|
||||||
|
#, fuzzy
|
||||||
|
msgid "Enter"
|
||||||
|
msgstr "간격"
|
||||||
|
|
||||||
#: src/routes/index.ts:228 src/views/environment/Environment.vue:34
|
#: src/routes/index.ts:228 src/views/environment/Environment.vue:34
|
||||||
msgid "Environment"
|
msgid "Environment"
|
||||||
msgstr "환경"
|
msgstr "환경"
|
||||||
|
@ -1170,8 +1175,8 @@ msgstr ""
|
||||||
"인증서를 획득하기 전에 .well-known 디렉토리에 대한역방향 프록시를 "
|
"인증서를 획득하기 전에 .well-known 디렉토리에 대한역방향 프록시를 "
|
||||||
"HTTPChallengePort(기본값: 9180)로 구성했는지 확인하세요."
|
"HTTPChallengePort(기본값: 9180)로 구성했는지 확인하세요."
|
||||||
|
|
||||||
#: src/routes/index.ts:102 src/views/config/Config.vue:62
|
#: src/routes/index.ts:102 src/views/config/ConfigEditor.vue:123
|
||||||
#: src/views/config/ConfigEditor.vue:123 src/views/config/ConfigEditor.vue:84
|
#: src/views/config/ConfigEditor.vue:84 src/views/config/ConfigList.vue:62
|
||||||
msgid "Manage Configs"
|
msgid "Manage Configs"
|
||||||
msgstr "구성 관리"
|
msgstr "구성 관리"
|
||||||
|
|
||||||
|
@ -1218,6 +1223,7 @@ msgstr "실행 모드"
|
||||||
#: src/components/ChatGPT/ChatGPT.vue:248
|
#: src/components/ChatGPT/ChatGPT.vue:248
|
||||||
#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:181
|
#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:181
|
||||||
#: src/components/StdDesign/StdDataDisplay/StdTable.vue:532
|
#: src/components/StdDesign/StdDataDisplay/StdTable.vue:532
|
||||||
|
#: src/views/config/ConfigList.vue:151
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
msgid "Modify"
|
msgid "Modify"
|
||||||
msgstr "설정 수정"
|
msgstr "설정 수정"
|
||||||
|
@ -1686,7 +1692,8 @@ msgstr "성공적으로 제거됨"
|
||||||
msgid "Removed successfully"
|
msgid "Removed successfully"
|
||||||
msgstr "성공적으로 제거됨"
|
msgstr "성공적으로 제거됨"
|
||||||
|
|
||||||
#: src/views/config/components/Rename.vue:52 src/views/config/Config.vue:130
|
#: src/views/config/components/Rename.vue:52
|
||||||
|
#: src/views/config/ConfigList.vue:159
|
||||||
#: src/views/domain/ngx_conf/NgxUpstream.vue:123
|
#: src/views/domain/ngx_conf/NgxUpstream.vue:123
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
msgid "Rename"
|
msgid "Rename"
|
||||||
|
|
|
@ -181,8 +181,9 @@ msgid "Auto-renewal enabled for %{name}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/views/certificate/CertificateEditor.vue:247
|
#: src/views/certificate/CertificateEditor.vue:247
|
||||||
#: src/views/config/Config.vue:143
|
|
||||||
#: src/views/config/ConfigEditor.vue:196
|
#: src/views/config/ConfigEditor.vue:196
|
||||||
|
#: src/views/config/ConfigList.vue:173
|
||||||
|
#: src/views/config/ConfigList.vue:99
|
||||||
#: src/views/domain/DomainEdit.vue:253
|
#: src/views/domain/DomainEdit.vue:253
|
||||||
#: src/views/nginx_log/NginxLog.vue:168
|
#: src/views/nginx_log/NginxLog.vue:168
|
||||||
#: src/views/stream/StreamEdit.vue:245
|
#: src/views/stream/StreamEdit.vue:245
|
||||||
|
@ -347,7 +348,7 @@ msgstr ""
|
||||||
msgid "Configuration Name"
|
msgid "Configuration Name"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/views/config/Config.vue:91
|
#: src/views/config/ConfigList.vue:91
|
||||||
msgid "Configurations"
|
msgid "Configurations"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -397,12 +398,12 @@ msgstr ""
|
||||||
msgid "Create Another"
|
msgid "Create Another"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/views/config/Config.vue:99
|
#: src/views/config/ConfigList.vue:109
|
||||||
msgid "Create File"
|
msgid "Create File"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/views/config/components/Mkdir.vue:50
|
#: src/views/config/components/Mkdir.vue:50
|
||||||
#: src/views/config/Config.vue:100
|
#: src/views/config/ConfigList.vue:116
|
||||||
msgid "Create Folder"
|
msgid "Create Folder"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -449,9 +450,9 @@ msgid "Customize the name of local server to be displayed in the environment ind
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/routes/index.ts:39
|
#: src/routes/index.ts:39
|
||||||
#: src/views/config/Config.vue:57
|
|
||||||
#: src/views/config/ConfigEditor.vue:118
|
#: src/views/config/ConfigEditor.vue:118
|
||||||
#: src/views/config/ConfigEditor.vue:79
|
#: src/views/config/ConfigEditor.vue:79
|
||||||
|
#: src/views/config/ConfigList.vue:57
|
||||||
msgid "Dashboard"
|
msgid "Dashboard"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -768,6 +769,10 @@ msgstr ""
|
||||||
msgid "Encrypt website with Let's Encrypt"
|
msgid "Encrypt website with Let's Encrypt"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/views/config/ConfigList.vue:151
|
||||||
|
msgid "Enter"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/routes/index.ts:228
|
#: src/routes/index.ts:228
|
||||||
#: src/views/environment/Environment.vue:34
|
#: src/views/environment/Environment.vue:34
|
||||||
msgid "Environment"
|
msgid "Environment"
|
||||||
|
@ -1129,9 +1134,9 @@ msgid "Make sure you have configured a reverse proxy for .well-known directory t
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/routes/index.ts:102
|
#: src/routes/index.ts:102
|
||||||
#: src/views/config/Config.vue:62
|
|
||||||
#: src/views/config/ConfigEditor.vue:123
|
#: src/views/config/ConfigEditor.vue:123
|
||||||
#: src/views/config/ConfigEditor.vue:84
|
#: src/views/config/ConfigEditor.vue:84
|
||||||
|
#: src/views/config/ConfigList.vue:62
|
||||||
msgid "Manage Configs"
|
msgid "Manage Configs"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -1178,6 +1183,7 @@ msgstr ""
|
||||||
#: src/components/ChatGPT/ChatGPT.vue:248
|
#: src/components/ChatGPT/ChatGPT.vue:248
|
||||||
#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:181
|
#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:181
|
||||||
#: src/components/StdDesign/StdDataDisplay/StdTable.vue:532
|
#: src/components/StdDesign/StdDataDisplay/StdTable.vue:532
|
||||||
|
#: src/views/config/ConfigList.vue:151
|
||||||
msgid "Modify"
|
msgid "Modify"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -1623,7 +1629,7 @@ msgid "Removed successfully"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/views/config/components/Rename.vue:52
|
#: src/views/config/components/Rename.vue:52
|
||||||
#: src/views/config/Config.vue:130
|
#: src/views/config/ConfigList.vue:159
|
||||||
#: src/views/domain/ngx_conf/NgxUpstream.vue:123
|
#: src/views/domain/ngx_conf/NgxUpstream.vue:123
|
||||||
msgid "Rename"
|
msgid "Rename"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
|
@ -195,9 +195,9 @@ msgid "Auto-renewal enabled for %{name}"
|
||||||
msgstr "Автообновление включено для %{name}"
|
msgstr "Автообновление включено для %{name}"
|
||||||
|
|
||||||
#: src/views/certificate/CertificateEditor.vue:247
|
#: src/views/certificate/CertificateEditor.vue:247
|
||||||
#: src/views/config/Config.vue:143 src/views/config/ConfigEditor.vue:196
|
#: src/views/config/ConfigEditor.vue:196 src/views/config/ConfigList.vue:173
|
||||||
#: src/views/domain/DomainEdit.vue:253 src/views/nginx_log/NginxLog.vue:168
|
#: src/views/config/ConfigList.vue:99 src/views/domain/DomainEdit.vue:253
|
||||||
#: src/views/stream/StreamEdit.vue:245
|
#: src/views/nginx_log/NginxLog.vue:168 src/views/stream/StreamEdit.vue:245
|
||||||
msgid "Back"
|
msgid "Back"
|
||||||
msgstr "Назад"
|
msgstr "Назад"
|
||||||
|
|
||||||
|
@ -371,7 +371,7 @@ msgstr "Проверка конфигурации успешна"
|
||||||
msgid "Configuration Name"
|
msgid "Configuration Name"
|
||||||
msgstr "Название конфигурации"
|
msgstr "Название конфигурации"
|
||||||
|
|
||||||
#: src/views/config/Config.vue:91
|
#: src/views/config/ConfigList.vue:91
|
||||||
msgid "Configurations"
|
msgid "Configurations"
|
||||||
msgstr "Конфигурации"
|
msgstr "Конфигурации"
|
||||||
|
|
||||||
|
@ -422,12 +422,12 @@ msgstr "Создан в"
|
||||||
msgid "Create Another"
|
msgid "Create Another"
|
||||||
msgstr "Создать еще"
|
msgstr "Создать еще"
|
||||||
|
|
||||||
#: src/views/config/Config.vue:99
|
#: src/views/config/ConfigList.vue:109
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
msgid "Create File"
|
msgid "Create File"
|
||||||
msgstr "Создан в"
|
msgstr "Создан в"
|
||||||
|
|
||||||
#: src/views/config/components/Mkdir.vue:50 src/views/config/Config.vue:100
|
#: src/views/config/components/Mkdir.vue:50 src/views/config/ConfigList.vue:116
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
msgid "Create Folder"
|
msgid "Create Folder"
|
||||||
msgstr "Создать еще"
|
msgstr "Создать еще"
|
||||||
|
@ -476,8 +476,8 @@ msgid ""
|
||||||
"indicator."
|
"indicator."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/routes/index.ts:39 src/views/config/Config.vue:57
|
#: src/routes/index.ts:39 src/views/config/ConfigEditor.vue:118
|
||||||
#: src/views/config/ConfigEditor.vue:118 src/views/config/ConfigEditor.vue:79
|
#: src/views/config/ConfigEditor.vue:79 src/views/config/ConfigList.vue:57
|
||||||
msgid "Dashboard"
|
msgid "Dashboard"
|
||||||
msgstr "Доска"
|
msgstr "Доска"
|
||||||
|
|
||||||
|
@ -807,6 +807,10 @@ msgstr "Активировано успешно"
|
||||||
msgid "Encrypt website with Let's Encrypt"
|
msgid "Encrypt website with Let's Encrypt"
|
||||||
msgstr "Использовать для сайта Let's Encrypt"
|
msgstr "Использовать для сайта Let's Encrypt"
|
||||||
|
|
||||||
|
#: src/views/config/ConfigList.vue:151
|
||||||
|
msgid "Enter"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/routes/index.ts:228 src/views/environment/Environment.vue:34
|
#: src/routes/index.ts:228 src/views/environment/Environment.vue:34
|
||||||
msgid "Environment"
|
msgid "Environment"
|
||||||
msgstr "Окружение"
|
msgstr "Окружение"
|
||||||
|
@ -1199,8 +1203,8 @@ msgstr ""
|
||||||
"Убедитесь, что вы настроили обратный прокси-сервер для каталога .well-known "
|
"Убедитесь, что вы настроили обратный прокси-сервер для каталога .well-known "
|
||||||
"на HTTPChallengePort перед получением сертификата»."
|
"на HTTPChallengePort перед получением сертификата»."
|
||||||
|
|
||||||
#: src/routes/index.ts:102 src/views/config/Config.vue:62
|
#: src/routes/index.ts:102 src/views/config/ConfigEditor.vue:123
|
||||||
#: src/views/config/ConfigEditor.vue:123 src/views/config/ConfigEditor.vue:84
|
#: src/views/config/ConfigEditor.vue:84 src/views/config/ConfigList.vue:62
|
||||||
msgid "Manage Configs"
|
msgid "Manage Configs"
|
||||||
msgstr "Конфигурации"
|
msgstr "Конфигурации"
|
||||||
|
|
||||||
|
@ -1247,6 +1251,7 @@ msgstr "Расширенный режим"
|
||||||
#: src/components/ChatGPT/ChatGPT.vue:248
|
#: src/components/ChatGPT/ChatGPT.vue:248
|
||||||
#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:181
|
#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:181
|
||||||
#: src/components/StdDesign/StdDataDisplay/StdTable.vue:532
|
#: src/components/StdDesign/StdDataDisplay/StdTable.vue:532
|
||||||
|
#: src/views/config/ConfigList.vue:151
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
msgid "Modify"
|
msgid "Modify"
|
||||||
msgstr "Изменить"
|
msgstr "Изменить"
|
||||||
|
@ -1716,7 +1721,8 @@ msgstr "Успешно сохранено"
|
||||||
msgid "Removed successfully"
|
msgid "Removed successfully"
|
||||||
msgstr "Успешно сохранено"
|
msgstr "Успешно сохранено"
|
||||||
|
|
||||||
#: src/views/config/components/Rename.vue:52 src/views/config/Config.vue:130
|
#: src/views/config/components/Rename.vue:52
|
||||||
|
#: src/views/config/ConfigList.vue:159
|
||||||
#: src/views/domain/ngx_conf/NgxUpstream.vue:123
|
#: src/views/domain/ngx_conf/NgxUpstream.vue:123
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
msgid "Rename"
|
msgid "Rename"
|
||||||
|
|
|
@ -195,9 +195,9 @@ msgid "Auto-renewal enabled for %{name}"
|
||||||
msgstr "Đã bật tự động gia hạn SSL cho %{name}"
|
msgstr "Đã bật tự động gia hạn SSL cho %{name}"
|
||||||
|
|
||||||
#: src/views/certificate/CertificateEditor.vue:247
|
#: src/views/certificate/CertificateEditor.vue:247
|
||||||
#: src/views/config/Config.vue:143 src/views/config/ConfigEditor.vue:196
|
#: src/views/config/ConfigEditor.vue:196 src/views/config/ConfigList.vue:173
|
||||||
#: src/views/domain/DomainEdit.vue:253 src/views/nginx_log/NginxLog.vue:168
|
#: src/views/config/ConfigList.vue:99 src/views/domain/DomainEdit.vue:253
|
||||||
#: src/views/stream/StreamEdit.vue:245
|
#: src/views/nginx_log/NginxLog.vue:168 src/views/stream/StreamEdit.vue:245
|
||||||
msgid "Back"
|
msgid "Back"
|
||||||
msgstr "Quay lại"
|
msgstr "Quay lại"
|
||||||
|
|
||||||
|
@ -371,7 +371,7 @@ msgstr "Tệp cấu hình được kiểm tra thành công"
|
||||||
msgid "Configuration Name"
|
msgid "Configuration Name"
|
||||||
msgstr "Tên cấu hình"
|
msgstr "Tên cấu hình"
|
||||||
|
|
||||||
#: src/views/config/Config.vue:91
|
#: src/views/config/ConfigList.vue:91
|
||||||
msgid "Configurations"
|
msgid "Configurations"
|
||||||
msgstr "Cấu hình"
|
msgstr "Cấu hình"
|
||||||
|
|
||||||
|
@ -422,12 +422,12 @@ msgstr "Ngày tạo"
|
||||||
msgid "Create Another"
|
msgid "Create Another"
|
||||||
msgstr "Tạo thêm"
|
msgstr "Tạo thêm"
|
||||||
|
|
||||||
#: src/views/config/Config.vue:99
|
#: src/views/config/ConfigList.vue:109
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
msgid "Create File"
|
msgid "Create File"
|
||||||
msgstr "Ngày tạo"
|
msgstr "Ngày tạo"
|
||||||
|
|
||||||
#: src/views/config/components/Mkdir.vue:50 src/views/config/Config.vue:100
|
#: src/views/config/components/Mkdir.vue:50 src/views/config/ConfigList.vue:116
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
msgid "Create Folder"
|
msgid "Create Folder"
|
||||||
msgstr "Tạo thêm"
|
msgstr "Tạo thêm"
|
||||||
|
@ -476,8 +476,8 @@ msgid ""
|
||||||
"indicator."
|
"indicator."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/routes/index.ts:39 src/views/config/Config.vue:57
|
#: src/routes/index.ts:39 src/views/config/ConfigEditor.vue:118
|
||||||
#: src/views/config/ConfigEditor.vue:118 src/views/config/ConfigEditor.vue:79
|
#: src/views/config/ConfigEditor.vue:79 src/views/config/ConfigList.vue:57
|
||||||
msgid "Dashboard"
|
msgid "Dashboard"
|
||||||
msgstr "Bảng điều khiển"
|
msgstr "Bảng điều khiển"
|
||||||
|
|
||||||
|
@ -808,6 +808,10 @@ msgstr "Đã bật"
|
||||||
msgid "Encrypt website with Let's Encrypt"
|
msgid "Encrypt website with Let's Encrypt"
|
||||||
msgstr "Bảo mật trang web với Let's Encrypt"
|
msgstr "Bảo mật trang web với Let's Encrypt"
|
||||||
|
|
||||||
|
#: src/views/config/ConfigList.vue:151
|
||||||
|
msgid "Enter"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/routes/index.ts:228 src/views/environment/Environment.vue:34
|
#: src/routes/index.ts:228 src/views/environment/Environment.vue:34
|
||||||
msgid "Environment"
|
msgid "Environment"
|
||||||
msgstr "Environment"
|
msgstr "Environment"
|
||||||
|
@ -1201,8 +1205,8 @@ msgstr ""
|
||||||
"Đảm bảo rằng bạn đã định cấu hình proxy ngược (reverse proxy) thư mục .well-"
|
"Đảm bảo rằng bạn đã định cấu hình proxy ngược (reverse proxy) thư mục .well-"
|
||||||
"known tới HTTPChallengePort (default: 9180) trước khi ký chứng chỉ SSL."
|
"known tới HTTPChallengePort (default: 9180) trước khi ký chứng chỉ SSL."
|
||||||
|
|
||||||
#: src/routes/index.ts:102 src/views/config/Config.vue:62
|
#: src/routes/index.ts:102 src/views/config/ConfigEditor.vue:123
|
||||||
#: src/views/config/ConfigEditor.vue:123 src/views/config/ConfigEditor.vue:84
|
#: src/views/config/ConfigEditor.vue:84 src/views/config/ConfigList.vue:62
|
||||||
msgid "Manage Configs"
|
msgid "Manage Configs"
|
||||||
msgstr "Quản lý cấu hình"
|
msgstr "Quản lý cấu hình"
|
||||||
|
|
||||||
|
@ -1248,6 +1252,7 @@ msgstr "Run Mode"
|
||||||
#: src/components/ChatGPT/ChatGPT.vue:248
|
#: src/components/ChatGPT/ChatGPT.vue:248
|
||||||
#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:181
|
#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:181
|
||||||
#: src/components/StdDesign/StdDataDisplay/StdTable.vue:532
|
#: src/components/StdDesign/StdDataDisplay/StdTable.vue:532
|
||||||
|
#: src/views/config/ConfigList.vue:151
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
msgid "Modify"
|
msgid "Modify"
|
||||||
msgstr "Sửa"
|
msgstr "Sửa"
|
||||||
|
@ -1718,7 +1723,8 @@ msgstr "Xoá thành công"
|
||||||
msgid "Removed successfully"
|
msgid "Removed successfully"
|
||||||
msgstr "Xoá thành công"
|
msgstr "Xoá thành công"
|
||||||
|
|
||||||
#: src/views/config/components/Rename.vue:52 src/views/config/Config.vue:130
|
#: src/views/config/components/Rename.vue:52
|
||||||
|
#: src/views/config/ConfigList.vue:159
|
||||||
#: src/views/domain/ngx_conf/NgxUpstream.vue:123
|
#: src/views/domain/ngx_conf/NgxUpstream.vue:123
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
msgid "Rename"
|
msgid "Rename"
|
||||||
|
|
Binary file not shown.
|
@ -184,9 +184,9 @@ msgid "Auto-renewal enabled for %{name}"
|
||||||
msgstr "成功启用 %{name} 自动续签"
|
msgstr "成功启用 %{name} 自动续签"
|
||||||
|
|
||||||
#: src/views/certificate/CertificateEditor.vue:247
|
#: src/views/certificate/CertificateEditor.vue:247
|
||||||
#: src/views/config/Config.vue:143 src/views/config/ConfigEditor.vue:196
|
#: src/views/config/ConfigEditor.vue:196 src/views/config/ConfigList.vue:173
|
||||||
#: src/views/domain/DomainEdit.vue:253 src/views/nginx_log/NginxLog.vue:168
|
#: src/views/config/ConfigList.vue:99 src/views/domain/DomainEdit.vue:253
|
||||||
#: src/views/stream/StreamEdit.vue:245
|
#: src/views/nginx_log/NginxLog.vue:168 src/views/stream/StreamEdit.vue:245
|
||||||
msgid "Back"
|
msgid "Back"
|
||||||
msgstr "返回"
|
msgstr "返回"
|
||||||
|
|
||||||
|
@ -344,7 +344,7 @@ msgstr "配置文件测试成功"
|
||||||
msgid "Configuration Name"
|
msgid "Configuration Name"
|
||||||
msgstr "配置名称"
|
msgstr "配置名称"
|
||||||
|
|
||||||
#: src/views/config/Config.vue:91
|
#: src/views/config/ConfigList.vue:91
|
||||||
msgid "Configurations"
|
msgid "Configurations"
|
||||||
msgstr "配置"
|
msgstr "配置"
|
||||||
|
|
||||||
|
@ -394,11 +394,11 @@ msgstr "创建"
|
||||||
msgid "Create Another"
|
msgid "Create Another"
|
||||||
msgstr "再创建一个"
|
msgstr "再创建一个"
|
||||||
|
|
||||||
#: src/views/config/Config.vue:99
|
#: src/views/config/ConfigList.vue:109
|
||||||
msgid "Create File"
|
msgid "Create File"
|
||||||
msgstr "创建文件"
|
msgstr "创建文件"
|
||||||
|
|
||||||
#: src/views/config/components/Mkdir.vue:50 src/views/config/Config.vue:100
|
#: src/views/config/components/Mkdir.vue:50 src/views/config/ConfigList.vue:116
|
||||||
msgid "Create Folder"
|
msgid "Create Folder"
|
||||||
msgstr "创建文件夹"
|
msgstr "创建文件夹"
|
||||||
|
|
||||||
|
@ -445,8 +445,8 @@ msgid ""
|
||||||
"indicator."
|
"indicator."
|
||||||
msgstr "自定义显示在环境指示器中的本地服务器名称。"
|
msgstr "自定义显示在环境指示器中的本地服务器名称。"
|
||||||
|
|
||||||
#: src/routes/index.ts:39 src/views/config/Config.vue:57
|
#: src/routes/index.ts:39 src/views/config/ConfigEditor.vue:118
|
||||||
#: src/views/config/ConfigEditor.vue:118 src/views/config/ConfigEditor.vue:79
|
#: src/views/config/ConfigEditor.vue:79 src/views/config/ConfigList.vue:57
|
||||||
msgid "Dashboard"
|
msgid "Dashboard"
|
||||||
msgstr "仪表盘"
|
msgstr "仪表盘"
|
||||||
|
|
||||||
|
@ -751,6 +751,10 @@ msgstr "启用成功"
|
||||||
msgid "Encrypt website with Let's Encrypt"
|
msgid "Encrypt website with Let's Encrypt"
|
||||||
msgstr "用 Let's Encrypt 对网站进行加密"
|
msgstr "用 Let's Encrypt 对网站进行加密"
|
||||||
|
|
||||||
|
#: src/views/config/ConfigList.vue:151
|
||||||
|
msgid "Enter"
|
||||||
|
msgstr "进入"
|
||||||
|
|
||||||
#: src/routes/index.ts:228 src/views/environment/Environment.vue:34
|
#: src/routes/index.ts:228 src/views/environment/Environment.vue:34
|
||||||
msgid "Environment"
|
msgid "Environment"
|
||||||
msgstr "环境"
|
msgstr "环境"
|
||||||
|
@ -1123,8 +1127,8 @@ msgstr ""
|
||||||
"在获取签发证书前,请确保配置文件中已将 .well-known 目录反向代理到 "
|
"在获取签发证书前,请确保配置文件中已将 .well-known 目录反向代理到 "
|
||||||
"HTTPChallengePort。"
|
"HTTPChallengePort。"
|
||||||
|
|
||||||
#: src/routes/index.ts:102 src/views/config/Config.vue:62
|
#: src/routes/index.ts:102 src/views/config/ConfigEditor.vue:123
|
||||||
#: src/views/config/ConfigEditor.vue:123 src/views/config/ConfigEditor.vue:84
|
#: src/views/config/ConfigEditor.vue:84 src/views/config/ConfigList.vue:62
|
||||||
msgid "Manage Configs"
|
msgid "Manage Configs"
|
||||||
msgstr "配置管理"
|
msgstr "配置管理"
|
||||||
|
|
||||||
|
@ -1168,6 +1172,7 @@ msgstr "模型"
|
||||||
#: src/components/ChatGPT/ChatGPT.vue:248
|
#: src/components/ChatGPT/ChatGPT.vue:248
|
||||||
#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:181
|
#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:181
|
||||||
#: src/components/StdDesign/StdDataDisplay/StdTable.vue:532
|
#: src/components/StdDesign/StdDataDisplay/StdTable.vue:532
|
||||||
|
#: src/views/config/ConfigList.vue:151
|
||||||
msgid "Modify"
|
msgid "Modify"
|
||||||
msgstr "修改"
|
msgstr "修改"
|
||||||
|
|
||||||
|
@ -1612,7 +1617,8 @@ msgstr "移除成功"
|
||||||
msgid "Removed successfully"
|
msgid "Removed successfully"
|
||||||
msgstr "删除成功"
|
msgstr "删除成功"
|
||||||
|
|
||||||
#: src/views/config/components/Rename.vue:52 src/views/config/Config.vue:130
|
#: src/views/config/components/Rename.vue:52
|
||||||
|
#: src/views/config/ConfigList.vue:159
|
||||||
#: src/views/domain/ngx_conf/NgxUpstream.vue:123
|
#: src/views/domain/ngx_conf/NgxUpstream.vue:123
|
||||||
msgid "Rename"
|
msgid "Rename"
|
||||||
msgstr "重命名"
|
msgstr "重命名"
|
||||||
|
|
|
@ -197,9 +197,9 @@ msgid "Auto-renewal enabled for %{name}"
|
||||||
msgstr "已啟用 %{name} 的自動續簽"
|
msgstr "已啟用 %{name} 的自動續簽"
|
||||||
|
|
||||||
#: src/views/certificate/CertificateEditor.vue:247
|
#: src/views/certificate/CertificateEditor.vue:247
|
||||||
#: src/views/config/Config.vue:143 src/views/config/ConfigEditor.vue:196
|
#: src/views/config/ConfigEditor.vue:196 src/views/config/ConfigList.vue:173
|
||||||
#: src/views/domain/DomainEdit.vue:253 src/views/nginx_log/NginxLog.vue:168
|
#: src/views/config/ConfigList.vue:99 src/views/domain/DomainEdit.vue:253
|
||||||
#: src/views/stream/StreamEdit.vue:245
|
#: src/views/nginx_log/NginxLog.vue:168 src/views/stream/StreamEdit.vue:245
|
||||||
msgid "Back"
|
msgid "Back"
|
||||||
msgstr "返回"
|
msgstr "返回"
|
||||||
|
|
||||||
|
@ -366,7 +366,7 @@ msgstr "設定檔案測試成功"
|
||||||
msgid "Configuration Name"
|
msgid "Configuration Name"
|
||||||
msgstr "設定名稱"
|
msgstr "設定名稱"
|
||||||
|
|
||||||
#: src/views/config/Config.vue:91
|
#: src/views/config/ConfigList.vue:91
|
||||||
msgid "Configurations"
|
msgid "Configurations"
|
||||||
msgstr "設定"
|
msgstr "設定"
|
||||||
|
|
||||||
|
@ -417,12 +417,12 @@ msgstr "建立時間"
|
||||||
msgid "Create Another"
|
msgid "Create Another"
|
||||||
msgstr "再建立一個"
|
msgstr "再建立一個"
|
||||||
|
|
||||||
#: src/views/config/Config.vue:99
|
#: src/views/config/ConfigList.vue:109
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
msgid "Create File"
|
msgid "Create File"
|
||||||
msgstr "建立時間"
|
msgstr "建立時間"
|
||||||
|
|
||||||
#: src/views/config/components/Mkdir.vue:50 src/views/config/Config.vue:100
|
#: src/views/config/components/Mkdir.vue:50 src/views/config/ConfigList.vue:116
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
msgid "Create Folder"
|
msgid "Create Folder"
|
||||||
msgstr "再建立一個"
|
msgstr "再建立一個"
|
||||||
|
@ -471,8 +471,8 @@ msgid ""
|
||||||
"indicator."
|
"indicator."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/routes/index.ts:39 src/views/config/Config.vue:57
|
#: src/routes/index.ts:39 src/views/config/ConfigEditor.vue:118
|
||||||
#: src/views/config/ConfigEditor.vue:118 src/views/config/ConfigEditor.vue:79
|
#: src/views/config/ConfigEditor.vue:79 src/views/config/ConfigList.vue:57
|
||||||
msgid "Dashboard"
|
msgid "Dashboard"
|
||||||
msgstr "儀表板"
|
msgstr "儀表板"
|
||||||
|
|
||||||
|
@ -788,6 +788,10 @@ msgstr "成功啟用"
|
||||||
msgid "Encrypt website with Let's Encrypt"
|
msgid "Encrypt website with Let's Encrypt"
|
||||||
msgstr "用 Let's Encrypt 對網站進行加密"
|
msgstr "用 Let's Encrypt 對網站進行加密"
|
||||||
|
|
||||||
|
#: src/views/config/ConfigList.vue:151
|
||||||
|
msgid "Enter"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/routes/index.ts:228 src/views/environment/Environment.vue:34
|
#: src/routes/index.ts:228 src/views/environment/Environment.vue:34
|
||||||
msgid "Environment"
|
msgid "Environment"
|
||||||
msgstr "環境"
|
msgstr "環境"
|
||||||
|
@ -1172,8 +1176,8 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"在取得憑證前,請確保您已將 .well-known 目錄反向代理到 HTTPChallengePort。"
|
"在取得憑證前,請確保您已將 .well-known 目錄反向代理到 HTTPChallengePort。"
|
||||||
|
|
||||||
#: src/routes/index.ts:102 src/views/config/Config.vue:62
|
#: src/routes/index.ts:102 src/views/config/ConfigEditor.vue:123
|
||||||
#: src/views/config/ConfigEditor.vue:123 src/views/config/ConfigEditor.vue:84
|
#: src/views/config/ConfigEditor.vue:84 src/views/config/ConfigList.vue:62
|
||||||
msgid "Manage Configs"
|
msgid "Manage Configs"
|
||||||
msgstr "管理設定"
|
msgstr "管理設定"
|
||||||
|
|
||||||
|
@ -1220,6 +1224,7 @@ msgstr "執行模式"
|
||||||
#: src/components/ChatGPT/ChatGPT.vue:248
|
#: src/components/ChatGPT/ChatGPT.vue:248
|
||||||
#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:181
|
#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:181
|
||||||
#: src/components/StdDesign/StdDataDisplay/StdTable.vue:532
|
#: src/components/StdDesign/StdDataDisplay/StdTable.vue:532
|
||||||
|
#: src/views/config/ConfigList.vue:151
|
||||||
msgid "Modify"
|
msgid "Modify"
|
||||||
msgstr "修改"
|
msgstr "修改"
|
||||||
|
|
||||||
|
@ -1681,7 +1686,8 @@ msgstr "儲存成功"
|
||||||
msgid "Removed successfully"
|
msgid "Removed successfully"
|
||||||
msgstr "儲存成功"
|
msgstr "儲存成功"
|
||||||
|
|
||||||
#: src/views/config/components/Rename.vue:52 src/views/config/Config.vue:130
|
#: src/views/config/components/Rename.vue:52
|
||||||
|
#: src/views/config/ConfigList.vue:159
|
||||||
#: src/views/domain/ngx_conf/NgxUpstream.vue:123
|
#: src/views/domain/ngx_conf/NgxUpstream.vue:123
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
msgid "Rename"
|
msgid "Rename"
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
import type { AxiosRequestConfig } from 'axios'
|
import type { AxiosRequestConfig } from 'axios'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
|
import { useCookies } from '@vueuse/integrations/useCookies'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import NProgress from 'nprogress'
|
import NProgress from 'nprogress'
|
||||||
import { useSettingsStore, useUserStore } from '@/pinia'
|
import { useSettingsStore, useUserStore } from '@/pinia'
|
||||||
import 'nprogress/nprogress.css'
|
import 'nprogress/nprogress.css'
|
||||||
|
|
||||||
import router from '@/routes'
|
import router from '@/routes'
|
||||||
|
import useOTPModal from '@/components/OTP/useOTPModal'
|
||||||
|
|
||||||
const user = useUserStore()
|
const user = useUserStore()
|
||||||
const settings = useSettingsStore()
|
const settings = useSettingsStore()
|
||||||
|
@ -58,8 +60,14 @@ instance.interceptors.response.use(
|
||||||
},
|
},
|
||||||
async error => {
|
async error => {
|
||||||
NProgress.done()
|
NProgress.done()
|
||||||
|
|
||||||
|
const otpModal = useOTPModal()
|
||||||
|
const cookies = useCookies(['nginx-ui-2fa'])
|
||||||
switch (error.response.status) {
|
switch (error.response.status) {
|
||||||
case 401:
|
case 401:
|
||||||
|
cookies.remove('secure_session_id')
|
||||||
|
await otpModal.open()
|
||||||
|
break
|
||||||
case 403:
|
case 403:
|
||||||
user.logout()
|
user.logout()
|
||||||
await router.push('/login')
|
await router.push('/login')
|
||||||
|
|
|
@ -97,7 +97,7 @@ export const routes: RouteRecordRaw[] = [
|
||||||
{
|
{
|
||||||
path: 'config',
|
path: 'config',
|
||||||
name: 'Manage Configs',
|
name: 'Manage Configs',
|
||||||
component: () => import('@/views/config/Config.vue'),
|
component: () => import('@/views/config/ConfigList.vue'),
|
||||||
meta: {
|
meta: {
|
||||||
name: () => $gettext('Manage Configs'),
|
name: () => $gettext('Manage Configs'),
|
||||||
icon: FileOutlined,
|
icon: FileOutlined,
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
{"version":"2.0.0-beta.28","build_id":147,"total_build":351}
|
{"version":"2.0.0-beta.28","build_id":149,"total_build":353}
|
|
@ -1,8 +1,10 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { message } from 'ant-design-vue'
|
import { message } from 'ant-design-vue'
|
||||||
import type { Ref } from 'vue'
|
import type { Ref } from 'vue'
|
||||||
|
import { InfoCircleOutlined } from '@ant-design/icons-vue'
|
||||||
import { formatDateTime } from '@/lib/helper'
|
import { formatDateTime } from '@/lib/helper'
|
||||||
import FooterToolBar from '@/components/FooterToolbar/FooterToolBar.vue'
|
import FooterToolBar from '@/components/FooterToolbar/FooterToolBar.vue'
|
||||||
|
import type { Config } from '@/api/config'
|
||||||
import config from '@/api/config'
|
import config from '@/api/config'
|
||||||
import CodeEditor from '@/components/CodeEditor/CodeEditor.vue'
|
import CodeEditor from '@/components/CodeEditor/CodeEditor.vue'
|
||||||
import ngx from '@/api/ngx'
|
import ngx from '@/api/ngx'
|
||||||
|
@ -10,7 +12,10 @@ import InspectConfig from '@/views/config/InspectConfig.vue'
|
||||||
import ChatGPT from '@/components/ChatGPT/ChatGPT.vue'
|
import ChatGPT from '@/components/ChatGPT/ChatGPT.vue'
|
||||||
import type { ChatComplicationMessage } from '@/api/openai'
|
import type { ChatComplicationMessage } from '@/api/openai'
|
||||||
import { useBreadcrumbs } from '@/composables/useBreadcrumbs'
|
import { useBreadcrumbs } from '@/composables/useBreadcrumbs'
|
||||||
|
import NodeSelector from '@/components/NodeSelector/NodeSelector.vue'
|
||||||
|
import { useSettingsStore } from '@/pinia'
|
||||||
|
|
||||||
|
const settings = useSettingsStore()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const refForm = ref()
|
const refForm = ref()
|
||||||
|
@ -32,10 +37,12 @@ const data = ref({
|
||||||
name: '',
|
name: '',
|
||||||
content: '',
|
content: '',
|
||||||
filepath: '',
|
filepath: '',
|
||||||
})
|
sync_node_ids: [] as number[],
|
||||||
|
sync_overwrite: false,
|
||||||
|
} as Config)
|
||||||
|
|
||||||
const historyChatgptRecord = ref([]) as Ref<ChatComplicationMessage[]>
|
const historyChatgptRecord = ref([]) as Ref<ChatComplicationMessage[]>
|
||||||
const activeKey = ref(['basic', 'chatgpt'])
|
const activeKey = ref(['basic', 'deploy', 'chatgpt'])
|
||||||
const modifiedAt = ref('')
|
const modifiedAt = ref('')
|
||||||
const nginxConfigBase = ref('')
|
const nginxConfigBase = ref('')
|
||||||
|
|
||||||
|
@ -145,6 +152,8 @@ function save() {
|
||||||
filepath: data.value.filepath,
|
filepath: data.value.filepath,
|
||||||
new_filepath: newPath.value,
|
new_filepath: newPath.value,
|
||||||
content: data.value.content,
|
content: data.value.content,
|
||||||
|
sync_node_ids: data.value.sync_node_ids,
|
||||||
|
sync_overwrite: data.value.sync_overwrite,
|
||||||
}).then(r => {
|
}).then(r => {
|
||||||
data.value.content = r.content
|
data.value.content = r.content
|
||||||
message.success($gettext('Saved successfully'))
|
message.success($gettext('Saved successfully'))
|
||||||
|
@ -261,6 +270,29 @@ function goBack() {
|
||||||
</AFormItem>
|
</AFormItem>
|
||||||
</AForm>
|
</AForm>
|
||||||
</ACollapsePanel>
|
</ACollapsePanel>
|
||||||
|
<ACollapsePanel
|
||||||
|
v-if="!settings.is_remote"
|
||||||
|
key="deploy"
|
||||||
|
:header="$gettext('Deploy')"
|
||||||
|
>
|
||||||
|
<NodeSelector
|
||||||
|
v-model:target="data.sync_node_ids"
|
||||||
|
hidden-local
|
||||||
|
/>
|
||||||
|
<div class="node-deploy-control">
|
||||||
|
<div class="overwrite">
|
||||||
|
<ACheckbox v-model:checked="data.sync_overwrite">
|
||||||
|
{{ $gettext('Overwrite') }}
|
||||||
|
</ACheckbox>
|
||||||
|
<ATooltip placement="bottom">
|
||||||
|
<template #title>
|
||||||
|
{{ $gettext('Overwrite exist file') }}
|
||||||
|
</template>
|
||||||
|
<InfoCircleOutlined />
|
||||||
|
</ATooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ACollapsePanel>
|
||||||
<ACollapsePanel
|
<ACollapsePanel
|
||||||
key="chatgpt"
|
key="chatgpt"
|
||||||
header="ChatGPT"
|
header="ChatGPT"
|
||||||
|
@ -295,4 +327,19 @@ function goBack() {
|
||||||
:deep(.ant-collapse > .ant-collapse-item > .ant-collapse-header) {
|
:deep(.ant-collapse > .ant-collapse-item > .ant-collapse-header) {
|
||||||
padding: 0 0 10px 0;
|
padding: 0 0 10px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.overwrite {
|
||||||
|
margin-right: 15px;
|
||||||
|
|
||||||
|
span {
|
||||||
|
color: #9b9b9b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-deploy-control {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-top: 10px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -90,14 +90,31 @@ const refRename = ref()
|
||||||
<template>
|
<template>
|
||||||
<ACard :title="$gettext('Configurations')">
|
<ACard :title="$gettext('Configurations')">
|
||||||
<template #extra>
|
<template #extra>
|
||||||
<a
|
<AButton
|
||||||
class="mr-4"
|
v-if="basePath"
|
||||||
|
type="link"
|
||||||
|
size="small"
|
||||||
|
@click="goBack"
|
||||||
|
>
|
||||||
|
{{ $gettext('Back') }}
|
||||||
|
</AButton>
|
||||||
|
<AButton
|
||||||
|
type="link"
|
||||||
|
size="small"
|
||||||
@click="router.push({
|
@click="router.push({
|
||||||
path: '/config/add',
|
path: '/config/add',
|
||||||
query: { basePath: basePath || undefined },
|
query: { basePath: basePath || undefined },
|
||||||
})"
|
})"
|
||||||
>{{ $gettext('Create File') }}</a>
|
>
|
||||||
<a @click="() => refMkdir.open(basePath)">{{ $gettext('Create Folder') }}</a>
|
{{ $gettext('Create File') }}
|
||||||
|
</AButton>
|
||||||
|
<AButton
|
||||||
|
type="link"
|
||||||
|
size="small"
|
||||||
|
@click="() => refMkdir.open(basePath)"
|
||||||
|
>
|
||||||
|
{{ $gettext('Create Folder') }}
|
||||||
|
</AButton>
|
||||||
</template>
|
</template>
|
||||||
<InspectConfig ref="refInspectConfig" />
|
<InspectConfig ref="refInspectConfig" />
|
||||||
<StdTable
|
<StdTable
|
||||||
|
@ -110,24 +127,37 @@ const refRename = ref()
|
||||||
row-key="name"
|
row-key="name"
|
||||||
:get-params="getParams"
|
:get-params="getParams"
|
||||||
disable-query-params
|
disable-query-params
|
||||||
@click-edit="(r, row) => {
|
disable-modify
|
||||||
if (!row.is_dir) {
|
>
|
||||||
|
<template #actions="{ record }">
|
||||||
|
<AButton
|
||||||
|
type="link"
|
||||||
|
size="small"
|
||||||
|
@click="() => {
|
||||||
|
if (!record.is_dir) {
|
||||||
$router.push({
|
$router.push({
|
||||||
path: `/config/${basePath}${r}/edit`,
|
path: `/config/${basePath}${record.name}/edit`,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$router.push({
|
$router.push({
|
||||||
query: {
|
query: {
|
||||||
dir: basePath + r,
|
dir: basePath + record.name,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<template #actions="{ record }">
|
{{ $gettext('Modify') }}
|
||||||
|
</AButton>
|
||||||
<ADivider type="vertical" />
|
<ADivider type="vertical" />
|
||||||
<a @click="() => refRename.open(basePath, record.name)">{{ $gettext('Rename') }}</a>
|
<AButton
|
||||||
|
type="link"
|
||||||
|
size="small"
|
||||||
|
@click="() => refRename.open(basePath, record.name, record.is_dir)"
|
||||||
|
>
|
||||||
|
{{ $gettext('Rename') }}
|
||||||
|
</AButton>
|
||||||
</template>
|
</template>
|
||||||
</StdTable>
|
</StdTable>
|
||||||
<Mkdir
|
<Mkdir
|
|
@ -27,8 +27,7 @@ function ok() {
|
||||||
refForm.value.validate().then(() => {
|
refForm.value.validate().then(() => {
|
||||||
const otpModal = useOTPModal()
|
const otpModal = useOTPModal()
|
||||||
|
|
||||||
otpModal.open({
|
otpModal.open().then(() => {
|
||||||
onOk() {
|
|
||||||
config.mkdir(data.value.basePath, data.value.name).then(() => {
|
config.mkdir(data.value.basePath, data.value.name).then(() => {
|
||||||
visible.value = false
|
visible.value = false
|
||||||
|
|
||||||
|
@ -37,7 +36,6 @@ function ok() {
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
message.error(`${$gettext('Server error')} ${e?.message}`)
|
message.error(`${$gettext('Server error')} ${e?.message}`)
|
||||||
})
|
})
|
||||||
},
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,22 +2,27 @@
|
||||||
import { message } from 'ant-design-vue'
|
import { message } from 'ant-design-vue'
|
||||||
import config from '@/api/config'
|
import config from '@/api/config'
|
||||||
import useOTPModal from '@/components/OTP/useOTPModal'
|
import useOTPModal from '@/components/OTP/useOTPModal'
|
||||||
|
import NodeSelector from '@/components/NodeSelector/NodeSelector.vue'
|
||||||
|
|
||||||
const emit = defineEmits(['renamed'])
|
const emit = defineEmits(['renamed'])
|
||||||
const visible = ref(false)
|
const visible = ref(false)
|
||||||
|
const isDirFlag = ref(false)
|
||||||
|
|
||||||
const data = ref({
|
const data = ref({
|
||||||
basePath: '',
|
basePath: '',
|
||||||
orig_name: '',
|
orig_name: '',
|
||||||
new_name: '',
|
new_name: '',
|
||||||
|
sync_node_ids: [] as number[],
|
||||||
})
|
})
|
||||||
|
|
||||||
const refForm = ref()
|
const refForm = ref()
|
||||||
function open(basePath: string, origName: string) {
|
|
||||||
|
function open(basePath: string, origName: string, isDir: boolean) {
|
||||||
visible.value = true
|
visible.value = true
|
||||||
data.value.orig_name = origName
|
data.value.orig_name = origName
|
||||||
data.value.new_name = origName
|
data.value.new_name = origName
|
||||||
data.value.basePath = basePath
|
data.value.basePath = basePath
|
||||||
|
isDirFlag.value = isDir
|
||||||
}
|
}
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
|
@ -26,20 +31,18 @@ defineExpose({
|
||||||
|
|
||||||
function ok() {
|
function ok() {
|
||||||
refForm.value.validate().then(() => {
|
refForm.value.validate().then(() => {
|
||||||
const { basePath, orig_name, new_name } = data.value
|
const { basePath, orig_name, new_name, sync_node_ids } = data.value
|
||||||
|
|
||||||
const otpModal = useOTPModal()
|
const otpModal = useOTPModal()
|
||||||
|
|
||||||
otpModal.open({
|
otpModal.open().then(() => {
|
||||||
onOk() {
|
config.rename(basePath, orig_name, new_name, sync_node_ids).then(() => {
|
||||||
config.rename(basePath, orig_name, new_name).then(() => {
|
|
||||||
visible.value = false
|
visible.value = false
|
||||||
message.success($gettext('Rename successfully'))
|
message.success($gettext('Rename successfully'))
|
||||||
emit('renamed')
|
emit('renamed')
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
message.error(`${$gettext('Server error')} ${e?.message}`)
|
message.error(`${$gettext('Server error')} ${e?.message}`)
|
||||||
})
|
})
|
||||||
},
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -72,6 +75,15 @@ function ok() {
|
||||||
>
|
>
|
||||||
<AInput v-model:value="data.new_name" />
|
<AInput v-model:value="data.new_name" />
|
||||||
</AFormItem>
|
</AFormItem>
|
||||||
|
<AFormItem
|
||||||
|
v-if="isDirFlag"
|
||||||
|
:label="$gettext('Sync')"
|
||||||
|
>
|
||||||
|
<NodeSelector
|
||||||
|
v-model:target="data.sync_node_ids"
|
||||||
|
hidden-local
|
||||||
|
/>
|
||||||
|
</AFormItem>
|
||||||
</AForm>
|
</AForm>
|
||||||
</AModal>
|
</AModal>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { FitAddon } from '@xterm/addon-fit'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import ws from '@/lib/websocket'
|
import ws from '@/lib/websocket'
|
||||||
import useOTPModal from '@/components/OTP/useOTPModal'
|
import useOTPModal from '@/components/OTP/useOTPModal'
|
||||||
|
import otp from '@/api/otp'
|
||||||
|
|
||||||
let term: Terminal | null
|
let term: Terminal | null
|
||||||
let ping: NodeJS.Timeout
|
let ping: NodeJS.Timeout
|
||||||
|
@ -14,10 +15,11 @@ const websocket = shallowRef()
|
||||||
const lostConnection = ref(false)
|
const lostConnection = ref(false)
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
otp.secure_session_status()
|
||||||
|
|
||||||
const otpModal = useOTPModal()
|
const otpModal = useOTPModal()
|
||||||
|
|
||||||
otpModal.open({
|
otpModal.open().then(secureSessionId => {
|
||||||
onOk(secureSessionId: string) {
|
|
||||||
websocket.value = ws(`/api/pty?X-Secure-Session-ID=${secureSessionId}`, false)
|
websocket.value = ws(`/api/pty?X-Secure-Session-ID=${secureSessionId}`, false)
|
||||||
|
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
|
@ -31,13 +33,11 @@ onMounted(() => {
|
||||||
lostConnection.value = true
|
lostConnection.value = true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
}).catch(() => {
|
||||||
onCancel() {
|
|
||||||
if (window.history.length > 1)
|
if (window.history.length > 1)
|
||||||
router.go(-1)
|
router.go(-1)
|
||||||
else
|
else
|
||||||
router.push('/')
|
router.push('/')
|
||||||
},
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
{"version":"2.0.0-beta.28","build_id":147,"total_build":351}
|
{"version":"2.0.0-beta.28","build_id":149,"total_build":353}
|
292
internal/config/sync.go
Normal file
292
internal/config/sync.go
Normal file
|
@ -0,0 +1,292 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/tls"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/0xJacky/Nginx-UI/internal/helper"
|
||||||
|
"github.com/0xJacky/Nginx-UI/internal/logger"
|
||||||
|
"github.com/0xJacky/Nginx-UI/internal/nginx"
|
||||||
|
"github.com/0xJacky/Nginx-UI/internal/notification"
|
||||||
|
"github.com/0xJacky/Nginx-UI/model"
|
||||||
|
"github.com/0xJacky/Nginx-UI/query"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SyncConfigPayload struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Filepath string `json:"filepath"`
|
||||||
|
NewFilepath string `json:"new_filepath"`
|
||||||
|
Content string `json:"content"`
|
||||||
|
Overwrite bool `json:"overwrite"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func SyncToRemoteServer(c *model.Config, newFilepath string) (err error) {
|
||||||
|
if c.Filepath == "" || len(c.SyncNodeIds) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
nginxConfPath := nginx.GetConfPath()
|
||||||
|
if !helper.IsUnderDirectory(c.Filepath, nginxConfPath) {
|
||||||
|
return fmt.Errorf("config: %s is not under the nginx conf path: %s",
|
||||||
|
c.Filepath, nginxConfPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
if newFilepath != "" && !helper.IsUnderDirectory(newFilepath, nginxConfPath) {
|
||||||
|
return fmt.Errorf("config: %s is not under the nginx conf path: %s",
|
||||||
|
c.Filepath, nginxConfPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
currentPath := c.Filepath
|
||||||
|
if newFilepath != "" {
|
||||||
|
currentPath = newFilepath
|
||||||
|
}
|
||||||
|
configBytes, err := os.ReadFile(currentPath)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
payload := &SyncConfigPayload{
|
||||||
|
Name: c.Name,
|
||||||
|
Filepath: c.Filepath,
|
||||||
|
NewFilepath: newFilepath,
|
||||||
|
Content: string(configBytes),
|
||||||
|
Overwrite: c.SyncOverwrite,
|
||||||
|
}
|
||||||
|
payloadBytes, err := json.Marshal(payload)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
q := query.Environment
|
||||||
|
envs, _ := q.Where(q.ID.In(c.SyncNodeIds...)).Find()
|
||||||
|
for _, env := range envs {
|
||||||
|
go func() {
|
||||||
|
err := payload.deploy(env, c, payloadBytes)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func SyncRenameOnRemoteServer(origPath, newPath string, syncNodeIds []int) (err error) {
|
||||||
|
if origPath == "" || newPath == "" || len(syncNodeIds) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
nginxConfPath := nginx.GetConfPath()
|
||||||
|
if !helper.IsUnderDirectory(origPath, nginxConfPath) {
|
||||||
|
return fmt.Errorf("config: %s is not under the nginx conf path: %s",
|
||||||
|
origPath, nginxConfPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !helper.IsUnderDirectory(newPath, nginxConfPath) {
|
||||||
|
return fmt.Errorf("config: %s is not under the nginx conf path: %s",
|
||||||
|
newPath, nginxConfPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
payload := &SyncConfigPayload{
|
||||||
|
Filepath: origPath,
|
||||||
|
NewFilepath: newPath,
|
||||||
|
}
|
||||||
|
|
||||||
|
q := query.Environment
|
||||||
|
envs, _ := q.Where(q.ID.In(syncNodeIds...)).Find()
|
||||||
|
for _, env := range envs {
|
||||||
|
go func() {
|
||||||
|
err := payload.rename(env)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type SyncNotificationPayload struct {
|
||||||
|
StatusCode int `json:"status_code"`
|
||||||
|
ConfigName string `json:"config_name"`
|
||||||
|
EnvName string `json:"env_name"`
|
||||||
|
RespBody string `json:"resp_body"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *SyncConfigPayload) deploy(env *model.Environment, c *model.Config, payloadBytes []byte) (err error) {
|
||||||
|
client := http.Client{
|
||||||
|
Transport: &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
url, err := env.GetUrl("/api/config")
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(payloadBytes))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
req.Header.Set("X-Node-Secret", env.Token)
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
respBody, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
notificationPayload := &SyncNotificationPayload{
|
||||||
|
StatusCode: resp.StatusCode,
|
||||||
|
ConfigName: c.Name,
|
||||||
|
EnvName: env.Name,
|
||||||
|
RespBody: string(respBody),
|
||||||
|
}
|
||||||
|
|
||||||
|
notificationPayloadBytes, err := json.Marshal(notificationPayload)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
notification.Error("Sync Configuration Error", string(notificationPayloadBytes))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
notification.Success("Sync Configuration Success", string(notificationPayloadBytes))
|
||||||
|
|
||||||
|
// handle rename
|
||||||
|
if p.NewFilepath == "" || p.Filepath == p.NewFilepath {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
payloadBytes, err = json.Marshal(gin.H{
|
||||||
|
"base_path": filepath.Dir(p.Filepath),
|
||||||
|
"old_filepath": filepath.Base(p.Filepath),
|
||||||
|
"new_filepath": filepath.Base(p.NewFilepath),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
url, err = env.GetUrl("/api/config_rename")
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
req, err = http.NewRequest(http.MethodPost, url, bytes.NewBuffer(payloadBytes))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
req.Header.Set("X-Node-Secret", env.Token)
|
||||||
|
resp, err = client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
respBody, err = io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
notificationPayload = &SyncNotificationPayload{
|
||||||
|
StatusCode: resp.StatusCode,
|
||||||
|
ConfigName: c.Name,
|
||||||
|
EnvName: env.Name,
|
||||||
|
RespBody: string(respBody),
|
||||||
|
}
|
||||||
|
|
||||||
|
notificationPayloadBytes, err = json.Marshal(notificationPayload)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
notification.Error("Sync Rename Configuration Error", string(notificationPayloadBytes))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
notification.Success("Sync Rename Configuration Success", string(notificationPayloadBytes))
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type SyncRenameNotificationPayload struct {
|
||||||
|
StatusCode int `json:"status_code"`
|
||||||
|
OrigPath string `json:"orig_path"`
|
||||||
|
NewPath string `json:"new_path"`
|
||||||
|
EnvName string `json:"env_name"`
|
||||||
|
RespBody string `json:"resp_body"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *SyncConfigPayload) rename(env *model.Environment) (err error) {
|
||||||
|
// handle rename
|
||||||
|
if p.NewFilepath == "" || p.Filepath == p.NewFilepath {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
client := http.Client{
|
||||||
|
Transport: &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
payloadBytes, err := json.Marshal(gin.H{
|
||||||
|
"base_path": strings.ReplaceAll(filepath.Dir(p.Filepath), nginx.GetConfPath(), ""),
|
||||||
|
"orig_name": filepath.Base(p.Filepath),
|
||||||
|
"new_name": filepath.Base(p.NewFilepath),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
url, err := env.GetUrl("/api/config_rename")
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(payloadBytes))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
req.Header.Set("X-Node-Secret", env.Token)
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
respBody, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
notificationPayload := &SyncRenameNotificationPayload{
|
||||||
|
StatusCode: resp.StatusCode,
|
||||||
|
OrigPath: p.Filepath,
|
||||||
|
NewPath: p.NewFilepath,
|
||||||
|
EnvName: env.Name,
|
||||||
|
RespBody: string(respBody),
|
||||||
|
}
|
||||||
|
|
||||||
|
notificationPayloadBytes, err := json.Marshal(notificationPayload)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
notification.Error("Sync Rename Configuration Error", string(notificationPayloadBytes))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
notification.Success("Sync Rename Configuration Success", string(notificationPayloadBytes))
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package router
|
package middleware
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/0xJacky/Nginx-UI/settings"
|
"github.com/0xJacky/Nginx-UI/settings"
|
||||||
|
@ -7,7 +7,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ipWhiteList() gin.HandlerFunc {
|
func IPWhiteList() gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
clientIP := c.ClientIP()
|
clientIP := c.ClientIP()
|
||||||
if len(settings.AuthSettings.IPWhiteList) == 0 || clientIP == "127.0.0.1" {
|
if len(settings.AuthSettings.IPWhiteList) == 0 || clientIP == "127.0.0.1" {
|
|
@ -1,11 +1,10 @@
|
||||||
package router
|
package middleware
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"github.com/0xJacky/Nginx-UI/app"
|
"github.com/0xJacky/Nginx-UI/app"
|
||||||
"github.com/0xJacky/Nginx-UI/internal/logger"
|
"github.com/0xJacky/Nginx-UI/internal/logger"
|
||||||
"github.com/0xJacky/Nginx-UI/internal/user"
|
"github.com/0xJacky/Nginx-UI/internal/user"
|
||||||
"github.com/0xJacky/Nginx-UI/model"
|
|
||||||
"github.com/0xJacky/Nginx-UI/settings"
|
"github.com/0xJacky/Nginx-UI/settings"
|
||||||
"github.com/gin-contrib/static"
|
"github.com/gin-contrib/static"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
@ -16,7 +15,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func recovery() gin.HandlerFunc {
|
func Recovery() gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := recover(); err != nil {
|
if err := recover(); err != nil {
|
||||||
|
@ -34,7 +33,7 @@ func recovery() gin.HandlerFunc {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func authRequired() gin.HandlerFunc {
|
func AuthRequired() gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
abortWithAuthFailure := func() {
|
abortWithAuthFailure := func() {
|
||||||
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{
|
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{
|
||||||
|
@ -75,46 +74,11 @@ func authRequired() gin.HandlerFunc {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func required2FA() gin.HandlerFunc {
|
type ServerFileSystemType struct {
|
||||||
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
|
http.FileSystem
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f serverFileSystemType) Exists(prefix string, _path string) bool {
|
func (f ServerFileSystemType) Exists(prefix string, _path string) bool {
|
||||||
file, err := f.Open(path.Join(prefix, _path))
|
file, err := f.Open(path.Join(prefix, _path))
|
||||||
if file != nil {
|
if file != nil {
|
||||||
defer func(file http.File) {
|
defer func(file http.File) {
|
||||||
|
@ -127,7 +91,7 @@ func (f serverFileSystemType) Exists(prefix string, _path string) bool {
|
||||||
return err == nil
|
return err == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func mustFS(dir string) (serverFileSystem static.ServeFileSystem) {
|
func MustFs(dir string) (serverFileSystem static.ServeFileSystem) {
|
||||||
|
|
||||||
sub, err := fs.Sub(app.DistFS, path.Join("dist", dir))
|
sub, err := fs.Sub(app.DistFS, path.Join("dist", dir))
|
||||||
|
|
||||||
|
@ -136,14 +100,14 @@ func mustFS(dir string) (serverFileSystem static.ServeFileSystem) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
serverFileSystem = serverFileSystemType{
|
serverFileSystem = ServerFileSystemType{
|
||||||
http.FS(sub),
|
http.FS(sub),
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func cacheJs() gin.HandlerFunc {
|
func CacheJs() gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
if strings.Contains(c.Request.URL.String(), "js") {
|
if strings.Contains(c.Request.URL.String(), "js") {
|
||||||
c.Header("Cache-Control", "max-age: 1296000")
|
c.Header("Cache-Control", "max-age: 1296000")
|
|
@ -1,4 +1,4 @@
|
||||||
package router
|
package middleware
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
@ -11,7 +11,7 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
)
|
)
|
||||||
|
|
||||||
func proxy() gin.HandlerFunc {
|
func Proxy() gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
nodeID, ok := c.Get("ProxyNodeID")
|
nodeID, ok := c.Get("ProxyNodeID")
|
||||||
if !ok {
|
if !ok {
|
|
@ -1,4 +1,4 @@
|
||||||
package router
|
package middleware
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/0xJacky/Nginx-UI/internal/logger"
|
"github.com/0xJacky/Nginx-UI/internal/logger"
|
||||||
|
@ -9,7 +9,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
func proxyWs() gin.HandlerFunc {
|
func ProxyWs() gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
nodeID, ok := c.Get("ProxyNodeID")
|
nodeID, ok := c.Get("ProxyNodeID")
|
||||||
if !ok {
|
if !ok {
|
43
internal/middleware/secure_session.go
Normal file
43
internal/middleware/secure_session.go
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/0xJacky/Nginx-UI/internal/user"
|
||||||
|
"github.com/0xJacky/Nginx-UI/model"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RequireSecureSession() 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.StatusUnauthorized, gin.H{
|
||||||
|
"message": "Secure Session ID is empty",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.VerifySecureSessionID(ssid, cUser.ID) {
|
||||||
|
c.Next()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
|
||||||
|
"message": "Secure Session ID is invalid",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
9
model/config.go
Normal file
9
model/config.go
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Model
|
||||||
|
Name string `json:"name"`
|
||||||
|
Filepath string `json:"filepath"`
|
||||||
|
SyncNodeIds []int `json:"sync_node_ids" gorm:"serializer:json"`
|
||||||
|
SyncOverwrite bool `json:"sync_overwrite"`
|
||||||
|
}
|
|
@ -36,6 +36,7 @@ func GenerateAllModel() []any {
|
||||||
Notification{},
|
Notification{},
|
||||||
AcmeUser{},
|
AcmeUser{},
|
||||||
BanIP{},
|
BanIP{},
|
||||||
|
Config{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -45,6 +45,8 @@ func newCert(db *gorm.DB, opts ...gen.DOOption) cert {
|
||||||
_cert.Log = field.NewString(tableName, "log")
|
_cert.Log = field.NewString(tableName, "log")
|
||||||
_cert.Resource = field.NewField(tableName, "resource")
|
_cert.Resource = field.NewField(tableName, "resource")
|
||||||
_cert.SyncNodeIds = field.NewField(tableName, "sync_node_ids")
|
_cert.SyncNodeIds = field.NewField(tableName, "sync_node_ids")
|
||||||
|
_cert.MustStaple = field.NewBool(tableName, "must_staple")
|
||||||
|
_cert.LegoDisableCNAMESupport = field.NewBool(tableName, "lego_disable_cname_support")
|
||||||
_cert.DnsCredential = certBelongsToDnsCredential{
|
_cert.DnsCredential = certBelongsToDnsCredential{
|
||||||
db: db.Session(&gorm.Session{}),
|
db: db.Session(&gorm.Session{}),
|
||||||
|
|
||||||
|
@ -83,6 +85,8 @@ type cert struct {
|
||||||
Log field.String
|
Log field.String
|
||||||
Resource field.Field
|
Resource field.Field
|
||||||
SyncNodeIds field.Field
|
SyncNodeIds field.Field
|
||||||
|
MustStaple field.Bool
|
||||||
|
LegoDisableCNAMESupport field.Bool
|
||||||
DnsCredential certBelongsToDnsCredential
|
DnsCredential certBelongsToDnsCredential
|
||||||
|
|
||||||
ACMEUser certBelongsToACMEUser
|
ACMEUser certBelongsToACMEUser
|
||||||
|
@ -119,6 +123,8 @@ func (c *cert) updateTableName(table string) *cert {
|
||||||
c.Log = field.NewString(table, "log")
|
c.Log = field.NewString(table, "log")
|
||||||
c.Resource = field.NewField(table, "resource")
|
c.Resource = field.NewField(table, "resource")
|
||||||
c.SyncNodeIds = field.NewField(table, "sync_node_ids")
|
c.SyncNodeIds = field.NewField(table, "sync_node_ids")
|
||||||
|
c.MustStaple = field.NewBool(table, "must_staple")
|
||||||
|
c.LegoDisableCNAMESupport = field.NewBool(table, "lego_disable_cname_support")
|
||||||
|
|
||||||
c.fillFieldMap()
|
c.fillFieldMap()
|
||||||
|
|
||||||
|
@ -135,7 +141,7 @@ func (c *cert) GetFieldByName(fieldName string) (field.OrderExpr, bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *cert) fillFieldMap() {
|
func (c *cert) fillFieldMap() {
|
||||||
c.fieldMap = make(map[string]field.Expr, 19)
|
c.fieldMap = make(map[string]field.Expr, 21)
|
||||||
c.fieldMap["id"] = c.ID
|
c.fieldMap["id"] = c.ID
|
||||||
c.fieldMap["created_at"] = c.CreatedAt
|
c.fieldMap["created_at"] = c.CreatedAt
|
||||||
c.fieldMap["updated_at"] = c.UpdatedAt
|
c.fieldMap["updated_at"] = c.UpdatedAt
|
||||||
|
@ -153,6 +159,8 @@ func (c *cert) fillFieldMap() {
|
||||||
c.fieldMap["log"] = c.Log
|
c.fieldMap["log"] = c.Log
|
||||||
c.fieldMap["resource"] = c.Resource
|
c.fieldMap["resource"] = c.Resource
|
||||||
c.fieldMap["sync_node_ids"] = c.SyncNodeIds
|
c.fieldMap["sync_node_ids"] = c.SyncNodeIds
|
||||||
|
c.fieldMap["must_staple"] = c.MustStaple
|
||||||
|
c.fieldMap["lego_disable_cname_support"] = c.LegoDisableCNAMESupport
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
370
query/configs.gen.go
Normal file
370
query/configs.gen.go
Normal file
|
@ -0,0 +1,370 @@
|
||||||
|
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||||
|
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||||
|
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package query
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"gorm.io/gorm/clause"
|
||||||
|
"gorm.io/gorm/schema"
|
||||||
|
|
||||||
|
"gorm.io/gen"
|
||||||
|
"gorm.io/gen/field"
|
||||||
|
|
||||||
|
"gorm.io/plugin/dbresolver"
|
||||||
|
|
||||||
|
"github.com/0xJacky/Nginx-UI/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newConfig(db *gorm.DB, opts ...gen.DOOption) config {
|
||||||
|
_config := config{}
|
||||||
|
|
||||||
|
_config.configDo.UseDB(db, opts...)
|
||||||
|
_config.configDo.UseModel(&model.Config{})
|
||||||
|
|
||||||
|
tableName := _config.configDo.TableName()
|
||||||
|
_config.ALL = field.NewAsterisk(tableName)
|
||||||
|
_config.ID = field.NewInt(tableName, "id")
|
||||||
|
_config.CreatedAt = field.NewTime(tableName, "created_at")
|
||||||
|
_config.UpdatedAt = field.NewTime(tableName, "updated_at")
|
||||||
|
_config.DeletedAt = field.NewField(tableName, "deleted_at")
|
||||||
|
_config.Filepath = field.NewString(tableName, "filepath")
|
||||||
|
_config.SyncNodeIds = field.NewField(tableName, "sync_node_ids")
|
||||||
|
|
||||||
|
_config.fillFieldMap()
|
||||||
|
|
||||||
|
return _config
|
||||||
|
}
|
||||||
|
|
||||||
|
type config struct {
|
||||||
|
configDo
|
||||||
|
|
||||||
|
ALL field.Asterisk
|
||||||
|
ID field.Int
|
||||||
|
CreatedAt field.Time
|
||||||
|
UpdatedAt field.Time
|
||||||
|
DeletedAt field.Field
|
||||||
|
Filepath field.String
|
||||||
|
SyncNodeIds field.Field
|
||||||
|
|
||||||
|
fieldMap map[string]field.Expr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c config) Table(newTableName string) *config {
|
||||||
|
c.configDo.UseTable(newTableName)
|
||||||
|
return c.updateTableName(newTableName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c config) As(alias string) *config {
|
||||||
|
c.configDo.DO = *(c.configDo.As(alias).(*gen.DO))
|
||||||
|
return c.updateTableName(alias)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *config) updateTableName(table string) *config {
|
||||||
|
c.ALL = field.NewAsterisk(table)
|
||||||
|
c.ID = field.NewInt(table, "id")
|
||||||
|
c.CreatedAt = field.NewTime(table, "created_at")
|
||||||
|
c.UpdatedAt = field.NewTime(table, "updated_at")
|
||||||
|
c.DeletedAt = field.NewField(table, "deleted_at")
|
||||||
|
c.Filepath = field.NewString(table, "filepath")
|
||||||
|
c.SyncNodeIds = field.NewField(table, "sync_node_ids")
|
||||||
|
|
||||||
|
c.fillFieldMap()
|
||||||
|
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *config) GetFieldByName(fieldName string) (field.OrderExpr, bool) {
|
||||||
|
_f, ok := c.fieldMap[fieldName]
|
||||||
|
if !ok || _f == nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
_oe, ok := _f.(field.OrderExpr)
|
||||||
|
return _oe, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *config) fillFieldMap() {
|
||||||
|
c.fieldMap = make(map[string]field.Expr, 6)
|
||||||
|
c.fieldMap["id"] = c.ID
|
||||||
|
c.fieldMap["created_at"] = c.CreatedAt
|
||||||
|
c.fieldMap["updated_at"] = c.UpdatedAt
|
||||||
|
c.fieldMap["deleted_at"] = c.DeletedAt
|
||||||
|
c.fieldMap["filepath"] = c.Filepath
|
||||||
|
c.fieldMap["sync_node_ids"] = c.SyncNodeIds
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c config) clone(db *gorm.DB) config {
|
||||||
|
c.configDo.ReplaceConnPool(db.Statement.ConnPool)
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c config) replaceDB(db *gorm.DB) config {
|
||||||
|
c.configDo.ReplaceDB(db)
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
type configDo struct{ gen.DO }
|
||||||
|
|
||||||
|
// FirstByID Where("id=@id")
|
||||||
|
func (c configDo) FirstByID(id int) (result *model.Config, err error) {
|
||||||
|
var params []interface{}
|
||||||
|
|
||||||
|
var generateSQL strings.Builder
|
||||||
|
params = append(params, id)
|
||||||
|
generateSQL.WriteString("id=? ")
|
||||||
|
|
||||||
|
var executeSQL *gorm.DB
|
||||||
|
executeSQL = c.UnderlyingDB().Where(generateSQL.String(), params...).Take(&result) // ignore_security_alert
|
||||||
|
err = executeSQL.Error
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteByID update @@table set deleted_at=strftime('%Y-%m-%d %H:%M:%S','now') where id=@id
|
||||||
|
func (c configDo) DeleteByID(id int) (err error) {
|
||||||
|
var params []interface{}
|
||||||
|
|
||||||
|
var generateSQL strings.Builder
|
||||||
|
params = append(params, id)
|
||||||
|
generateSQL.WriteString("update configs set deleted_at=strftime('%Y-%m-%d %H:%M:%S','now') where id=? ")
|
||||||
|
|
||||||
|
var executeSQL *gorm.DB
|
||||||
|
executeSQL = c.UnderlyingDB().Exec(generateSQL.String(), params...) // ignore_security_alert
|
||||||
|
err = executeSQL.Error
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c configDo) Debug() *configDo {
|
||||||
|
return c.withDO(c.DO.Debug())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c configDo) WithContext(ctx context.Context) *configDo {
|
||||||
|
return c.withDO(c.DO.WithContext(ctx))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c configDo) ReadDB() *configDo {
|
||||||
|
return c.Clauses(dbresolver.Read)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c configDo) WriteDB() *configDo {
|
||||||
|
return c.Clauses(dbresolver.Write)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c configDo) Session(config *gorm.Session) *configDo {
|
||||||
|
return c.withDO(c.DO.Session(config))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c configDo) Clauses(conds ...clause.Expression) *configDo {
|
||||||
|
return c.withDO(c.DO.Clauses(conds...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c configDo) Returning(value interface{}, columns ...string) *configDo {
|
||||||
|
return c.withDO(c.DO.Returning(value, columns...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c configDo) Not(conds ...gen.Condition) *configDo {
|
||||||
|
return c.withDO(c.DO.Not(conds...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c configDo) Or(conds ...gen.Condition) *configDo {
|
||||||
|
return c.withDO(c.DO.Or(conds...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c configDo) Select(conds ...field.Expr) *configDo {
|
||||||
|
return c.withDO(c.DO.Select(conds...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c configDo) Where(conds ...gen.Condition) *configDo {
|
||||||
|
return c.withDO(c.DO.Where(conds...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c configDo) Order(conds ...field.Expr) *configDo {
|
||||||
|
return c.withDO(c.DO.Order(conds...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c configDo) Distinct(cols ...field.Expr) *configDo {
|
||||||
|
return c.withDO(c.DO.Distinct(cols...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c configDo) Omit(cols ...field.Expr) *configDo {
|
||||||
|
return c.withDO(c.DO.Omit(cols...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c configDo) Join(table schema.Tabler, on ...field.Expr) *configDo {
|
||||||
|
return c.withDO(c.DO.Join(table, on...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c configDo) LeftJoin(table schema.Tabler, on ...field.Expr) *configDo {
|
||||||
|
return c.withDO(c.DO.LeftJoin(table, on...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c configDo) RightJoin(table schema.Tabler, on ...field.Expr) *configDo {
|
||||||
|
return c.withDO(c.DO.RightJoin(table, on...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c configDo) Group(cols ...field.Expr) *configDo {
|
||||||
|
return c.withDO(c.DO.Group(cols...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c configDo) Having(conds ...gen.Condition) *configDo {
|
||||||
|
return c.withDO(c.DO.Having(conds...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c configDo) Limit(limit int) *configDo {
|
||||||
|
return c.withDO(c.DO.Limit(limit))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c configDo) Offset(offset int) *configDo {
|
||||||
|
return c.withDO(c.DO.Offset(offset))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c configDo) Scopes(funcs ...func(gen.Dao) gen.Dao) *configDo {
|
||||||
|
return c.withDO(c.DO.Scopes(funcs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c configDo) Unscoped() *configDo {
|
||||||
|
return c.withDO(c.DO.Unscoped())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c configDo) Create(values ...*model.Config) error {
|
||||||
|
if len(values) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return c.DO.Create(values)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c configDo) CreateInBatches(values []*model.Config, batchSize int) error {
|
||||||
|
return c.DO.CreateInBatches(values, batchSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save : !!! underlying implementation is different with GORM
|
||||||
|
// The method is equivalent to executing the statement: db.Clauses(clause.OnConflict{UpdateAll: true}).Create(values)
|
||||||
|
func (c configDo) Save(values ...*model.Config) error {
|
||||||
|
if len(values) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return c.DO.Save(values)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c configDo) First() (*model.Config, error) {
|
||||||
|
if result, err := c.DO.First(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
return result.(*model.Config), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c configDo) Take() (*model.Config, error) {
|
||||||
|
if result, err := c.DO.Take(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
return result.(*model.Config), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c configDo) Last() (*model.Config, error) {
|
||||||
|
if result, err := c.DO.Last(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
return result.(*model.Config), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c configDo) Find() ([]*model.Config, error) {
|
||||||
|
result, err := c.DO.Find()
|
||||||
|
return result.([]*model.Config), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c configDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.Config, err error) {
|
||||||
|
buf := make([]*model.Config, 0, batchSize)
|
||||||
|
err = c.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error {
|
||||||
|
defer func() { results = append(results, buf...) }()
|
||||||
|
return fc(tx, batch)
|
||||||
|
})
|
||||||
|
return results, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c configDo) FindInBatches(result *[]*model.Config, batchSize int, fc func(tx gen.Dao, batch int) error) error {
|
||||||
|
return c.DO.FindInBatches(result, batchSize, fc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c configDo) Attrs(attrs ...field.AssignExpr) *configDo {
|
||||||
|
return c.withDO(c.DO.Attrs(attrs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c configDo) Assign(attrs ...field.AssignExpr) *configDo {
|
||||||
|
return c.withDO(c.DO.Assign(attrs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c configDo) Joins(fields ...field.RelationField) *configDo {
|
||||||
|
for _, _f := range fields {
|
||||||
|
c = *c.withDO(c.DO.Joins(_f))
|
||||||
|
}
|
||||||
|
return &c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c configDo) Preload(fields ...field.RelationField) *configDo {
|
||||||
|
for _, _f := range fields {
|
||||||
|
c = *c.withDO(c.DO.Preload(_f))
|
||||||
|
}
|
||||||
|
return &c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c configDo) FirstOrInit() (*model.Config, error) {
|
||||||
|
if result, err := c.DO.FirstOrInit(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
return result.(*model.Config), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c configDo) FirstOrCreate() (*model.Config, error) {
|
||||||
|
if result, err := c.DO.FirstOrCreate(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
return result.(*model.Config), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c configDo) FindByPage(offset int, limit int) (result []*model.Config, count int64, err error) {
|
||||||
|
result, err = c.Offset(offset).Limit(limit).Find()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if size := len(result); 0 < limit && 0 < size && size < limit {
|
||||||
|
count = int64(size + offset)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
count, err = c.Offset(-1).Limit(-1).Count()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c configDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) {
|
||||||
|
count, err = c.Count()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = c.Offset(offset).Limit(limit).Scan(result)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c configDo) Scan(result interface{}) (err error) {
|
||||||
|
return c.DO.Scan(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c configDo) Delete(models ...*model.Config) (result gen.ResultInfo, err error) {
|
||||||
|
return c.DO.Delete(models)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *configDo) withDO(do gen.Dao) *configDo {
|
||||||
|
c.DO = *do.(*gen.DO)
|
||||||
|
return c
|
||||||
|
}
|
|
@ -23,6 +23,7 @@ var (
|
||||||
BanIP *banIP
|
BanIP *banIP
|
||||||
Cert *cert
|
Cert *cert
|
||||||
ChatGPTLog *chatGPTLog
|
ChatGPTLog *chatGPTLog
|
||||||
|
Config *config
|
||||||
ConfigBackup *configBackup
|
ConfigBackup *configBackup
|
||||||
DnsCredential *dnsCredential
|
DnsCredential *dnsCredential
|
||||||
Environment *environment
|
Environment *environment
|
||||||
|
@ -39,6 +40,7 @@ func SetDefault(db *gorm.DB, opts ...gen.DOOption) {
|
||||||
BanIP = &Q.BanIP
|
BanIP = &Q.BanIP
|
||||||
Cert = &Q.Cert
|
Cert = &Q.Cert
|
||||||
ChatGPTLog = &Q.ChatGPTLog
|
ChatGPTLog = &Q.ChatGPTLog
|
||||||
|
Config = &Q.Config
|
||||||
ConfigBackup = &Q.ConfigBackup
|
ConfigBackup = &Q.ConfigBackup
|
||||||
DnsCredential = &Q.DnsCredential
|
DnsCredential = &Q.DnsCredential
|
||||||
Environment = &Q.Environment
|
Environment = &Q.Environment
|
||||||
|
@ -56,6 +58,7 @@ func Use(db *gorm.DB, opts ...gen.DOOption) *Query {
|
||||||
BanIP: newBanIP(db, opts...),
|
BanIP: newBanIP(db, opts...),
|
||||||
Cert: newCert(db, opts...),
|
Cert: newCert(db, opts...),
|
||||||
ChatGPTLog: newChatGPTLog(db, opts...),
|
ChatGPTLog: newChatGPTLog(db, opts...),
|
||||||
|
Config: newConfig(db, opts...),
|
||||||
ConfigBackup: newConfigBackup(db, opts...),
|
ConfigBackup: newConfigBackup(db, opts...),
|
||||||
DnsCredential: newDnsCredential(db, opts...),
|
DnsCredential: newDnsCredential(db, opts...),
|
||||||
Environment: newEnvironment(db, opts...),
|
Environment: newEnvironment(db, opts...),
|
||||||
|
@ -74,6 +77,7 @@ type Query struct {
|
||||||
BanIP banIP
|
BanIP banIP
|
||||||
Cert cert
|
Cert cert
|
||||||
ChatGPTLog chatGPTLog
|
ChatGPTLog chatGPTLog
|
||||||
|
Config config
|
||||||
ConfigBackup configBackup
|
ConfigBackup configBackup
|
||||||
DnsCredential dnsCredential
|
DnsCredential dnsCredential
|
||||||
Environment environment
|
Environment environment
|
||||||
|
@ -93,6 +97,7 @@ func (q *Query) clone(db *gorm.DB) *Query {
|
||||||
BanIP: q.BanIP.clone(db),
|
BanIP: q.BanIP.clone(db),
|
||||||
Cert: q.Cert.clone(db),
|
Cert: q.Cert.clone(db),
|
||||||
ChatGPTLog: q.ChatGPTLog.clone(db),
|
ChatGPTLog: q.ChatGPTLog.clone(db),
|
||||||
|
Config: q.Config.clone(db),
|
||||||
ConfigBackup: q.ConfigBackup.clone(db),
|
ConfigBackup: q.ConfigBackup.clone(db),
|
||||||
DnsCredential: q.DnsCredential.clone(db),
|
DnsCredential: q.DnsCredential.clone(db),
|
||||||
Environment: q.Environment.clone(db),
|
Environment: q.Environment.clone(db),
|
||||||
|
@ -119,6 +124,7 @@ func (q *Query) ReplaceDB(db *gorm.DB) *Query {
|
||||||
BanIP: q.BanIP.replaceDB(db),
|
BanIP: q.BanIP.replaceDB(db),
|
||||||
Cert: q.Cert.replaceDB(db),
|
Cert: q.Cert.replaceDB(db),
|
||||||
ChatGPTLog: q.ChatGPTLog.replaceDB(db),
|
ChatGPTLog: q.ChatGPTLog.replaceDB(db),
|
||||||
|
Config: q.Config.replaceDB(db),
|
||||||
ConfigBackup: q.ConfigBackup.replaceDB(db),
|
ConfigBackup: q.ConfigBackup.replaceDB(db),
|
||||||
DnsCredential: q.DnsCredential.replaceDB(db),
|
DnsCredential: q.DnsCredential.replaceDB(db),
|
||||||
Environment: q.Environment.replaceDB(db),
|
Environment: q.Environment.replaceDB(db),
|
||||||
|
@ -135,6 +141,7 @@ type queryCtx struct {
|
||||||
BanIP *banIPDo
|
BanIP *banIPDo
|
||||||
Cert *certDo
|
Cert *certDo
|
||||||
ChatGPTLog *chatGPTLogDo
|
ChatGPTLog *chatGPTLogDo
|
||||||
|
Config *configDo
|
||||||
ConfigBackup *configBackupDo
|
ConfigBackup *configBackupDo
|
||||||
DnsCredential *dnsCredentialDo
|
DnsCredential *dnsCredentialDo
|
||||||
Environment *environmentDo
|
Environment *environmentDo
|
||||||
|
@ -151,6 +158,7 @@ func (q *Query) WithContext(ctx context.Context) *queryCtx {
|
||||||
BanIP: q.BanIP.WithContext(ctx),
|
BanIP: q.BanIP.WithContext(ctx),
|
||||||
Cert: q.Cert.WithContext(ctx),
|
Cert: q.Cert.WithContext(ctx),
|
||||||
ChatGPTLog: q.ChatGPTLog.WithContext(ctx),
|
ChatGPTLog: q.ChatGPTLog.WithContext(ctx),
|
||||||
|
Config: q.Config.WithContext(ctx),
|
||||||
ConfigBackup: q.ConfigBackup.WithContext(ctx),
|
ConfigBackup: q.ConfigBackup.WithContext(ctx),
|
||||||
DnsCredential: q.DnsCredential.WithContext(ctx),
|
DnsCredential: q.DnsCredential.WithContext(ctx),
|
||||||
Environment: q.Environment.WithContext(ctx),
|
Environment: q.Environment.WithContext(ctx),
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -16,6 +16,7 @@ import (
|
||||||
"github.com/0xJacky/Nginx-UI/api/terminal"
|
"github.com/0xJacky/Nginx-UI/api/terminal"
|
||||||
"github.com/0xJacky/Nginx-UI/api/upstream"
|
"github.com/0xJacky/Nginx-UI/api/upstream"
|
||||||
"github.com/0xJacky/Nginx-UI/api/user"
|
"github.com/0xJacky/Nginx-UI/api/user"
|
||||||
|
"github.com/0xJacky/Nginx-UI/internal/middleware"
|
||||||
"github.com/gin-contrib/static"
|
"github.com/gin-contrib/static"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -23,14 +24,13 @@ import (
|
||||||
|
|
||||||
func InitRouter() *gin.Engine {
|
func InitRouter() *gin.Engine {
|
||||||
r := gin.New()
|
r := gin.New()
|
||||||
r.Use(gin.Logger())
|
r.Use(
|
||||||
r.Use(recovery())
|
gin.Logger(),
|
||||||
r.Use(cacheJs())
|
middleware.Recovery(),
|
||||||
r.Use(ipWhiteList())
|
middleware.CacheJs(),
|
||||||
|
middleware.IPWhiteList(),
|
||||||
//r.Use(OperationSync())
|
static.Serve("/", middleware.MustFs("")),
|
||||||
|
)
|
||||||
r.Use(static.Serve("/", mustFS("")))
|
|
||||||
|
|
||||||
r.NoRoute(func(c *gin.Context) {
|
r.NoRoute(func(c *gin.Context) {
|
||||||
c.JSON(http.StatusNotFound, gin.H{
|
c.JSON(http.StatusNotFound, gin.H{
|
||||||
|
@ -44,7 +44,7 @@ func InitRouter() *gin.Engine {
|
||||||
user.InitAuthRouter(root)
|
user.InitAuthRouter(root)
|
||||||
|
|
||||||
// Authorization required not websocket request
|
// Authorization required not websocket request
|
||||||
g := root.Group("/", authRequired(), proxy())
|
g := root.Group("/", middleware.AuthRequired(), middleware.Proxy())
|
||||||
{
|
{
|
||||||
user.InitUserRouter(g)
|
user.InitUserRouter(g)
|
||||||
analytic.InitRouter(g)
|
analytic.InitRouter(g)
|
||||||
|
@ -65,11 +65,11 @@ func InitRouter() *gin.Engine {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Authorization required and websocket request
|
// Authorization required and websocket request
|
||||||
w := root.Group("/", authRequired(), proxyWs())
|
w := root.Group("/", middleware.AuthRequired(), middleware.ProxyWs())
|
||||||
{
|
{
|
||||||
analytic.InitWebSocketRouter(w)
|
analytic.InitWebSocketRouter(w)
|
||||||
certificate.InitCertificateWebSocketRouter(w)
|
certificate.InitCertificateWebSocketRouter(w)
|
||||||
o := w.Group("", required2FA())
|
o := w.Group("", middleware.RequireSecureSession())
|
||||||
{
|
{
|
||||||
terminal.InitRouter(o)
|
terminal.InitRouter(o)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue