diff --git a/frontend/src/routes/index.ts b/frontend/src/routes/index.ts
index 68cce2cd..2c843213 100644
--- a/frontend/src/routes/index.ts
+++ b/frontend/src/routes/index.ts
@@ -74,7 +74,17 @@ export const routes = [
}
},
{
- path: 'config/:name',
+ path: 'config/:dir*',
+ name: () => $gettext('Manage Configs'),
+ component: () => import('@/views/config/Config.vue'),
+ meta: {
+ icon: FileOutlined,
+ hideChildren: true,
+ hiddenInSidebar: true
+ }
+ },
+ {
+ path: 'config/:name+/edit',
name: () => $gettext('Edit Configuration'),
component: () => import('@/views/config/ConfigEdit.vue'),
meta: {
diff --git a/frontend/src/views/config/Config.vue b/frontend/src/views/config/Config.vue
index ebe02dcf..6f09d4ae 100644
--- a/frontend/src/views/config/Config.vue
+++ b/frontend/src/views/config/Config.vue
@@ -2,43 +2,66 @@
import StdTable from '@/components/StdDataDisplay/StdTable.vue'
import gettext from '@/gettext'
import config from '@/api/config'
-import {datetime} from '@/components/StdDataDisplay/StdTableTransformer'
+import {customRender, datetime} from '@/components/StdDataDisplay/StdTableTransformer'
+import {computed, h, nextTick, ref, watch} from 'vue'
const {$gettext} = gettext
const api = config
-const columns = [{
- title: () => $gettext('Name'),
- dataIndex: 'name',
- sorter: true,
- pithy: true
-}, {
- title: () => $gettext('Updated at'),
- dataIndex: 'modify',
- customRender: datetime,
- datetime: true,
- sorter: true,
- pithy: true
-}, {
- title: () => $gettext('Action'),
- dataIndex: 'action'
-}]
+import configColumns from '@/views/config/config'
+import {useRoute} from 'vue-router'
+import FooterToolBar from '@/components/FooterToolbar/FooterToolBar.vue'
+import router from '@/routes'
+
+const table = ref(null)
+const route = useRoute()
+
+const basePath = computed(() => {
+ let dir = route?.params?.dir ? (route?.params?.dir as string[])?.join('/') : ''
+ if (dir) dir += '/'
+ return dir
+})
+
+const get_params = computed(() => {
+ return {
+ dir: basePath.value
+ }
+})
+
+const update = ref(1)
+
+watch(get_params, () => {
+ update.value++
+})
+
{
- $router.push({
- path: '/config/' + r
- })
+ :get_params="get_params"
+ @clickEdit="(r, row) => {
+ if (!row.is_dir) {
+ $router.push({
+ path: '/config/' + basePath + r + '/edit'
+ })
+ } else {
+ $router.push({
+ path: '/config/' + basePath + r
+ })
+ }
}"
/>
+
+ {{ $gettext('Back') }}
+
diff --git a/frontend/src/views/config/ConfigEdit.vue b/frontend/src/views/config/ConfigEdit.vue
index c3746edf..18c4dd96 100644
--- a/frontend/src/views/config/ConfigEdit.vue
+++ b/frontend/src/views/config/ConfigEdit.vue
@@ -2,7 +2,7 @@
import FooterToolBar from '@/components/FooterToolbar/FooterToolBar.vue'
import gettext from '@/gettext'
import {useRoute} from 'vue-router'
-import {ref} from 'vue'
+import {computed, ref} from 'vue'
import config from '@/api/config'
import {message} from 'ant-design-vue'
import CodeEditor from '@/components/CodeEditor/CodeEditor.vue'
@@ -10,7 +10,13 @@ import CodeEditor from '@/components/CodeEditor/CodeEditor.vue'
const {$gettext, interpolate} = gettext
const route = useRoute()
-const name = ref(route.params.name)
+const name = computed(() => {
+ const n = route.params.name
+ if (typeof n === 'string') {
+ return n
+ }
+ return n?.join('/')
+})
const configText = ref('')
@@ -46,7 +52,7 @@ function save() {
- Cancel
+ Back
Save
diff --git a/frontend/src/views/config/config.tsx b/frontend/src/views/config/config.tsx
new file mode 100644
index 00000000..662a77e7
--- /dev/null
+++ b/frontend/src/views/config/config.tsx
@@ -0,0 +1,41 @@
+import {customRender, datetime} from '@/components/StdDataDisplay/StdTableTransformer'
+import gettext from '@/gettext'
+
+const {$gettext} = gettext
+
+import {Badge} from 'ant-design-vue'
+import {h} from 'vue'
+
+const configColumns = [{
+ title: () => $gettext('Name'),
+ dataIndex: 'name',
+ sorter: true,
+ pithy: true
+}, {
+ title: () => $gettext('Type'),
+ dataIndex: 'is_dir',
+ customRender: (args: customRender) => {
+ const template: any = []
+ const {text, column} = args
+ if (text === true || text > 0) {
+ template.push($gettext('Dir'))
+ } else {
+ template.push($gettext('File'))
+ }
+ return h('div', template)
+ },
+ sorter: true,
+ pithy: true
+}, {
+ title: () => $gettext('Updated at'),
+ dataIndex: 'modify',
+ customRender: datetime,
+ datetime: true,
+ sorter: true,
+ pithy: true
+}, {
+ title: () => $gettext('Action'),
+ dataIndex: 'action'
+}]
+
+export default configColumns
diff --git a/server/api/config.go b/server/api/config.go
index cd2e395a..ebc24a92 100644
--- a/server/api/config.go
+++ b/server/api/config.go
@@ -1,184 +1,173 @@
package api
import (
- "github.com/0xJacky/Nginx-UI/server/pkg/config_list"
- "github.com/0xJacky/Nginx-UI/server/pkg/nginx"
- "github.com/gin-gonic/gin"
- "log"
- "net/http"
- "os"
- "path/filepath"
- "strings"
+ "github.com/0xJacky/Nginx-UI/server/pkg/config_list"
+ "github.com/0xJacky/Nginx-UI/server/pkg/nginx"
+ "github.com/gin-gonic/gin"
+ "log"
+ "net/http"
+ "os"
+ "path/filepath"
+ "strings"
)
func GetConfigs(c *gin.Context) {
- orderBy := c.Query("order_by")
- sort := c.DefaultQuery("sort", "desc")
+ orderBy := c.Query("order_by")
+ sort := c.DefaultQuery("sort", "desc")
+ dir := c.DefaultQuery("dir", "/")
- mySort := map[string]string{
- "name": "string",
- "modify": "time",
- }
+ mySort := map[string]string{
+ "name": "string",
+ "modify": "time",
+ "is_dir": "bool",
+ }
- configFiles, err := os.ReadDir(nginx.GetNginxConfPath("/"))
+ configFiles, err := os.ReadDir(nginx.GetNginxConfPath(dir))
- if err != nil {
- ErrHandler(c, err)
- return
- }
+ if err != nil {
+ ErrHandler(c, err)
+ return
+ }
- var configs []gin.H
+ var configs []gin.H
- for i := range configFiles {
- file := configFiles[i]
- fileInfo, _ := file.Info()
+ for i := range configFiles {
+ file := configFiles[i]
+ fileInfo, _ := file.Info()
- switch mode := fileInfo.Mode(); {
- case mode.IsRegular(): // regular file, not a hidden file
- if "." == file.Name()[0:1] {
- continue
- }
- case mode&os.ModeSymlink != 0: // is a symbol
- var targetPath string
- targetPath, err = os.Readlink(nginx.GetNginxConfPath(file.Name()))
- if err != nil {
- log.Println("GetConfigs Read Symlink Error", targetPath, err)
- continue
- }
+ switch mode := fileInfo.Mode(); {
+ case mode.IsRegular(): // regular file, not a hidden file
+ if "." == file.Name()[0:1] {
+ continue
+ }
+ case mode&os.ModeSymlink != 0: // is a symbol
+ var targetPath string
+ targetPath, err = os.Readlink(nginx.GetNginxConfPath(file.Name()))
+ if err != nil {
+ log.Println("GetConfigs Read Symlink Error", targetPath, err)
+ continue
+ }
+ }
- var targetInfo os.FileInfo
- targetInfo, err = os.Stat(targetPath)
- if err != nil {
- log.Println("GetConfigs Stat Error", targetPath, err)
- continue
- }
- // but target file is not a dir
- if targetInfo.IsDir() {
- continue
- }
- default:
- continue
- }
+ configs = append(configs, gin.H{
+ "name": file.Name(),
+ "size": fileInfo.Size(),
+ "modify": fileInfo.ModTime(),
+ "is_dir": file.IsDir(),
+ })
+ }
- configs = append(configs, gin.H{
- "name": file.Name(),
- "size": fileInfo.Size(),
- "modify": fileInfo.ModTime(),
- })
- }
+ configs = config_list.Sort(orderBy, sort, mySort[orderBy], configs)
- configs = config_list.Sort(orderBy, sort, mySort[orderBy], configs)
-
- c.JSON(http.StatusOK, gin.H{
- "data": configs,
- })
+ c.JSON(http.StatusOK, gin.H{
+ "data": configs,
+ })
}
func GetConfig(c *gin.Context) {
- name := c.Param("name")
- path := filepath.Join(nginx.GetNginxConfPath("/"), name)
+ name := c.Param("name")
+ path := filepath.Join(nginx.GetNginxConfPath("/"), name)
- content, err := os.ReadFile(path)
+ content, err := os.ReadFile(path)
- if err != nil {
- ErrHandler(c, err)
- return
- }
+ if err != nil {
+ ErrHandler(c, err)
+ return
+ }
- c.JSON(http.StatusOK, gin.H{
- "config": string(content),
- })
+ c.JSON(http.StatusOK, gin.H{
+ "config": string(content),
+ })
}
type AddConfigJson struct {
- Name string `json:"name" binding:"required"`
- Content string `json:"content" binding:"required"`
+ Name string `json:"name" binding:"required"`
+ Content string `json:"content" binding:"required"`
}
func AddConfig(c *gin.Context) {
- var request AddConfigJson
- err := c.BindJSON(&request)
- if err != nil {
- ErrHandler(c, err)
- return
- }
+ var request AddConfigJson
+ err := c.BindJSON(&request)
+ if err != nil {
+ ErrHandler(c, err)
+ return
+ }
- name := request.Name
- content := request.Content
+ name := request.Name
+ content := request.Content
- path := filepath.Join(nginx.GetNginxConfPath("/"), name)
+ path := filepath.Join(nginx.GetNginxConfPath("/"), name)
- log.Println(path)
- if _, err = os.Stat(path); err == nil {
- c.JSON(http.StatusNotAcceptable, gin.H{
- "message": "config exist",
- })
- return
- }
+ if _, err = os.Stat(path); err == nil {
+ c.JSON(http.StatusNotAcceptable, gin.H{
+ "message": "config exist",
+ })
+ return
+ }
- if content != "" {
- err = os.WriteFile(path, []byte(content), 0644)
- if err != nil {
- ErrHandler(c, err)
- return
- }
- }
+ if content != "" {
+ err = os.WriteFile(path, []byte(content), 0644)
+ if err != nil {
+ ErrHandler(c, err)
+ return
+ }
+ }
- output := nginx.ReloadNginx()
+ output := nginx.ReloadNginx()
- if output != "" && strings.Contains(output, "error") {
- c.JSON(http.StatusInternalServerError, gin.H{
- "message": output,
- })
- return
- }
+ if output != "" && strings.Contains(output, "error") {
+ c.JSON(http.StatusInternalServerError, gin.H{
+ "message": output,
+ })
+ return
+ }
- c.JSON(http.StatusOK, gin.H{
- "name": name,
- "content": content,
- })
+ c.JSON(http.StatusOK, gin.H{
+ "name": name,
+ "content": content,
+ })
}
type EditConfigJson struct {
- Content string `json:"content" binding:"required"`
+ Content string `json:"content" binding:"required"`
}
func EditConfig(c *gin.Context) {
- name := c.Param("name")
- var request EditConfigJson
- err := c.BindJSON(&request)
- if err != nil {
- ErrHandler(c, err)
- return
- }
- path := filepath.Join(nginx.GetNginxConfPath("/"), name)
- content := request.Content
+ name := c.Param("name")
+ var request EditConfigJson
+ err := c.BindJSON(&request)
+ if err != nil {
+ ErrHandler(c, err)
+ return
+ }
+ path := filepath.Join(nginx.GetNginxConfPath("/"), name)
+ content := request.Content
- origContent, err := os.ReadFile(path)
- if err != nil {
- ErrHandler(c, err)
- return
- }
+ origContent, err := os.ReadFile(path)
+ if err != nil {
+ ErrHandler(c, err)
+ return
+ }
- if content != "" && content != string(origContent) {
- // model.CreateBackup(path)
- err = os.WriteFile(path, []byte(content), 0644)
- if err != nil {
- ErrHandler(c, err)
- return
- }
- }
+ if content != "" && content != string(origContent) {
+ // model.CreateBackup(path)
+ err = os.WriteFile(path, []byte(content), 0644)
+ if err != nil {
+ ErrHandler(c, err)
+ return
+ }
+ }
- output := nginx.ReloadNginx()
+ output := nginx.ReloadNginx()
- if output != "" && strings.Contains(output, "error") {
- c.JSON(http.StatusInternalServerError, gin.H{
- "message": output,
- })
- return
- }
+ if output != "" && strings.Contains(output, "error") {
+ c.JSON(http.StatusInternalServerError, gin.H{
+ "message": output,
+ })
+ return
+ }
- GetConfig(c)
+ GetConfig(c)
}