diff --git a/api/config/add.go b/api/config/add.go index f2acd817..c8fac389 100644 --- a/api/config/add.go +++ b/api/config/add.go @@ -3,42 +3,48 @@ package config import ( "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/nginx" "github.com/gin-gonic/gin" + "github.com/sashabaranov/go-openai" "net/http" "os" + "time" ) func AddConfig(c *gin.Context) { - var request struct { - Name string `json:"name" binding:"required"` - Content string `json:"content" binding:"required"` + var json struct { + Name string `json:"name" binding:"required"` + NewFilepath string `json:"new_filepath" binding:"required"` + Content string `json:"content"` + Overwrite bool `json:"overwrite"` } - err := c.BindJSON(&request) - if err != nil { - api.ErrHandler(c, err) + if !api.BindAndValid(c, &json) { return } - name := request.Name - content := request.Content - - path := nginx.GetConfPath("/", name) - - if _, err = os.Stat(path); err == nil { - c.JSON(http.StatusNotAcceptable, gin.H{ - "message": "config exist", + name := json.Name + content := json.Content + path := json.NewFilepath + if !helper.IsUnderDirectory(path, nginx.GetConfPath()) { + c.JSON(http.StatusForbidden, gin.H{ + "message": "new filepath is not under the nginx conf path", }) return } - if content != "" { - err = os.WriteFile(path, []byte(content), 0644) - if err != nil { - api.ErrHandler(c, err) - return - } + if !json.Overwrite && helper.FileExists(path) { + c.JSON(http.StatusNotAcceptable, gin.H{ + "message": "File exists", + }) + return + } + + err := os.WriteFile(path, []byte(content), 0644) + if err != nil { + api.ErrHandler(c, err) + return } output := nginx.Reload() @@ -50,7 +56,10 @@ func AddConfig(c *gin.Context) { } c.JSON(http.StatusOK, config.Config{ - Name: name, - Content: content, + Name: name, + Content: content, + ChatGPTMessages: make([]openai.ChatCompletionMessage, 0), + FilePath: path, + ModifiedAt: time.Now(), }) } diff --git a/api/config/base_path.go b/api/config/base_path.go new file mode 100644 index 00000000..e44c92c8 --- /dev/null +++ b/api/config/base_path.go @@ -0,0 +1,13 @@ +package config + +import ( + "github.com/0xJacky/Nginx-UI/internal/nginx" + "github.com/gin-gonic/gin" + "net/http" +) + +func GetBasePath(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{ + "base_path": nginx.GetConfPath(), + }) +} diff --git a/api/config/get.go b/api/config/get.go index dc9f8aa4..a19aac9b 100644 --- a/api/config/get.go +++ b/api/config/get.go @@ -3,6 +3,7 @@ package config import ( "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/nginx" "github.com/0xJacky/Nginx-UI/query" "github.com/gin-gonic/gin" @@ -15,16 +16,20 @@ func GetConfig(c *gin.Context) { name := c.Param("name") path := nginx.GetConfPath("/", name) + if !helper.IsUnderDirectory(path, nginx.GetConfPath()) { + c.JSON(http.StatusForbidden, gin.H{ + "message": "path is not under the nginx conf path", + }) + return + } stat, err := os.Stat(path) - if err != nil { api.ErrHandler(c, err) return } content, err := os.ReadFile(path) - if err != nil { api.ErrHandler(c, err) return @@ -32,7 +37,6 @@ func GetConfig(c *gin.Context) { g := query.ChatGPTLog chatgpt, err := g.Where(g.Name.Eq(path)).FirstOrCreate() - if err != nil { api.ErrHandler(c, err) return diff --git a/api/config/modify.go b/api/config/modify.go index 9d6c8b73..b1cdb60f 100644 --- a/api/config/modify.go +++ b/api/config/modify.go @@ -2,10 +2,15 @@ package config import ( "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/nginx" + "github.com/0xJacky/Nginx-UI/query" "github.com/gin-gonic/gin" + "github.com/sashabaranov/go-openai" "net/http" "os" + "time" ) type EditConfigJson struct { @@ -14,15 +19,39 @@ type EditConfigJson struct { func EditConfig(c *gin.Context) { name := c.Param("name") - var request EditConfigJson - err := c.BindJSON(&request) - if err != nil { - api.ErrHandler(c, err) + var json struct { + Name string `json:"name" binding:"required"` + Filepath string `json:"filepath" binding:"required"` + NewFilepath string `json:"new_filepath" binding:"required"` + Content string `json:"content"` + } + if !api.BindAndValid(c, &json) { return } - path := nginx.GetConfPath("/", name) - content := request.Content + path := json.Filepath + if !helper.IsUnderDirectory(path, nginx.GetConfPath()) { + c.JSON(http.StatusForbidden, gin.H{ + "message": "filepath is not under the nginx conf path", + }) + return + } + + if !helper.IsUnderDirectory(json.NewFilepath, nginx.GetConfPath()) { + c.JSON(http.StatusForbidden, gin.H{ + "message": "new filepath is not under the nginx conf path", + }) + return + } + + if _, err := os.Stat(path); os.IsNotExist(err) { + c.JSON(http.StatusNotFound, gin.H{ + "message": "file not found", + }) + return + } + + content := json.Content origContent, err := os.ReadFile(path) if err != nil { api.ErrHandler(c, err) @@ -37,8 +66,28 @@ func EditConfig(c *gin.Context) { } } - output := nginx.Reload() + g := query.ChatGPTLog + // handle rename + if path != json.NewFilepath { + if helper.FileExists(json.NewFilepath) { + c.JSON(http.StatusNotAcceptable, gin.H{ + "message": "File exists", + }) + return + } + err := os.Rename(json.Filepath, json.NewFilepath) + if err != nil { + api.ErrHandler(c, err) + return + } + + // update ChatGPT record + _, _ = g.Where(g.Name.Eq(json.NewFilepath)).Delete() + _, _ = g.Where(g.Name.Eq(path)).Update(g.Name, json.NewFilepath) + } + + output := nginx.Reload() if nginx.GetLogLevel(output) >= nginx.Warn { c.JSON(http.StatusInternalServerError, gin.H{ "message": output, @@ -46,5 +95,21 @@ func EditConfig(c *gin.Context) { return } - GetConfig(c) + chatgpt, err := g.Where(g.Name.Eq(json.NewFilepath)).FirstOrCreate() + if err != nil { + api.ErrHandler(c, err) + return + } + + if chatgpt.Content == nil { + chatgpt.Content = make([]openai.ChatCompletionMessage, 0) + } + + c.JSON(http.StatusOK, config.Config{ + Name: name, + Content: content, + ChatGPTMessages: chatgpt.Content, + FilePath: json.NewFilepath, + ModifiedAt: time.Now(), + }) } diff --git a/api/config/router.go b/api/config/router.go index 594744fc..bac3775b 100644 --- a/api/config/router.go +++ b/api/config/router.go @@ -7,4 +7,5 @@ func InitRouter(r *gin.RouterGroup) { r.GET("config/*name", GetConfig) r.POST("config", AddConfig) r.POST("config/*name", EditConfig) + r.GET("config_base_path", GetBasePath) } diff --git a/app/src/api/config.ts b/app/src/api/config.ts index f23230f8..2416e658 100644 --- a/app/src/api/config.ts +++ b/app/src/api/config.ts @@ -1,5 +1,6 @@ import Curd from '@/api/curd' import type { ChatComplicationMessage } from '@/api/openai' +import http from '@/lib/http' export interface Config { name: string @@ -9,6 +10,16 @@ export interface Config { modified_at: string } -const config: Curd = new Curd('/config') +class ConfigCurd extends Curd { + constructor() { + super('/config') + } + + get_base_path() { + return http.get('/config_base_path') + } +} + +const config: ConfigCurd = new ConfigCurd() export default config diff --git a/app/src/routes/index.ts b/app/src/routes/index.ts index ea2f5b2a..e29d5130 100644 --- a/app/src/routes/index.ts +++ b/app/src/routes/index.ts @@ -104,13 +104,24 @@ export const routes: RouteRecordRaw[] = [ hideChildren: true, }, }, + { + path: 'config/add', + name: 'Add Configuration', + component: () => import('@/views/config/ConfigEditor.vue'), + meta: { + name: () => $gettext('Add Configuration'), + hiddenInSidebar: true, + lastRouteName: 'Manage Configs', + }, + }, { path: 'config/:name+/edit', name: 'Edit Configuration', - component: () => import('@/views/config/ConfigEdit.vue'), + component: () => import('@/views/config/ConfigEditor.vue'), meta: { name: () => $gettext('Edit Configuration'), hiddenInSidebar: true, + lastRouteName: 'Manage Configs', }, }, { diff --git a/app/src/views/config/Config.vue b/app/src/views/config/Config.vue index 2e8364d5..b14dd19e 100644 --- a/app/src/views/config/Config.vue +++ b/app/src/views/config/Config.vue @@ -3,20 +3,18 @@ import StdTable from '@/components/StdDesign/StdDataDisplay/StdTable.vue' import config from '@/api/config' import configColumns from '@/views/config/config' import FooterToolBar from '@/components/FooterToolbar/FooterToolBar.vue' -import router from '@/routes' import InspectConfig from '@/views/config/InspectConfig.vue' -const api = config - -const table = ref(null) +const table = ref() const route = useRoute() +const router = useRouter() const basePath = computed(() => { let dir = route?.query?.dir ?? '' if (dir) dir += '/' - return dir + return dir as string }) const getParams = computed(() => { @@ -36,15 +34,32 @@ const refInspectConfig = ref() watch(route, () => { refInspectConfig.value?.test() }) + +function goBack() { + router.push({ + path: '/config', + query: { + dir: `${basePath.value.split('/').slice(0, -2).join('/')}` || undefined, + }, + }) +}