mirror of
https://github.com/0xJacky/nginx-ui.git
synced 2025-05-11 18:35:51 +02:00
feat: manage config support dir
This commit is contained in:
parent
6bcb575442
commit
65b192c8be
5 changed files with 231 additions and 162 deletions
|
@ -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'),
|
name: () => $gettext('Edit Configuration'),
|
||||||
component: () => import('@/views/config/ConfigEdit.vue'),
|
component: () => import('@/views/config/ConfigEdit.vue'),
|
||||||
meta: {
|
meta: {
|
||||||
|
|
|
@ -2,43 +2,66 @@
|
||||||
import StdTable from '@/components/StdDataDisplay/StdTable.vue'
|
import StdTable from '@/components/StdDataDisplay/StdTable.vue'
|
||||||
import gettext from '@/gettext'
|
import gettext from '@/gettext'
|
||||||
import config from '@/api/config'
|
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 {$gettext} = gettext
|
||||||
|
|
||||||
const api = config
|
const api = config
|
||||||
|
|
||||||
const columns = [{
|
import configColumns from '@/views/config/config'
|
||||||
title: () => $gettext('Name'),
|
import {useRoute} from 'vue-router'
|
||||||
dataIndex: 'name',
|
import FooterToolBar from '@/components/FooterToolbar/FooterToolBar.vue'
|
||||||
sorter: true,
|
import router from '@/routes'
|
||||||
pithy: true
|
|
||||||
}, {
|
const table = ref(null)
|
||||||
title: () => $gettext('Updated at'),
|
const route = useRoute()
|
||||||
dataIndex: 'modify',
|
|
||||||
customRender: datetime,
|
const basePath = computed(() => {
|
||||||
datetime: true,
|
let dir = route?.params?.dir ? (route?.params?.dir as string[])?.join('/') : ''
|
||||||
sorter: true,
|
if (dir) dir += '/'
|
||||||
pithy: true
|
return dir
|
||||||
}, {
|
})
|
||||||
title: () => $gettext('Action'),
|
|
||||||
dataIndex: 'action'
|
const get_params = computed(() => {
|
||||||
}]
|
return {
|
||||||
|
dir: basePath.value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const update = ref(1)
|
||||||
|
|
||||||
|
watch(get_params, () => {
|
||||||
|
update.value++
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<a-card :title="$gettext('Configurations')">
|
<a-card :title="$gettext('Configurations')">
|
||||||
<std-table
|
<std-table
|
||||||
|
:key="update"
|
||||||
|
ref="table"
|
||||||
:api="api"
|
:api="api"
|
||||||
:columns="columns"
|
:columns="configColumns"
|
||||||
:deletable="false"
|
:deletable="false"
|
||||||
:disable_search="true"
|
:disable_search="true"
|
||||||
row-key="name"
|
row-key="name"
|
||||||
@clickEdit="r => {
|
:get_params="get_params"
|
||||||
$router.push({
|
@clickEdit="(r, row) => {
|
||||||
path: '/config/' + r
|
if (!row.is_dir) {
|
||||||
})
|
$router.push({
|
||||||
|
path: '/config/' + basePath + r + '/edit'
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
$router.push({
|
||||||
|
path: '/config/' + basePath + r
|
||||||
|
})
|
||||||
|
}
|
||||||
}"
|
}"
|
||||||
/>
|
/>
|
||||||
|
<footer-tool-bar v-if="basePath">
|
||||||
|
<a-button @click="router.go(-1)">{{ $gettext('Back') }}</a-button>
|
||||||
|
</footer-tool-bar>
|
||||||
</a-card>
|
</a-card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
import FooterToolBar from '@/components/FooterToolbar/FooterToolBar.vue'
|
import FooterToolBar from '@/components/FooterToolbar/FooterToolBar.vue'
|
||||||
import gettext from '@/gettext'
|
import gettext from '@/gettext'
|
||||||
import {useRoute} from 'vue-router'
|
import {useRoute} from 'vue-router'
|
||||||
import {ref} from 'vue'
|
import {computed, ref} from 'vue'
|
||||||
import config from '@/api/config'
|
import config from '@/api/config'
|
||||||
import {message} from 'ant-design-vue'
|
import {message} from 'ant-design-vue'
|
||||||
import CodeEditor from '@/components/CodeEditor/CodeEditor.vue'
|
import CodeEditor from '@/components/CodeEditor/CodeEditor.vue'
|
||||||
|
@ -10,7 +10,13 @@ import CodeEditor from '@/components/CodeEditor/CodeEditor.vue'
|
||||||
const {$gettext, interpolate} = gettext
|
const {$gettext, interpolate} = gettext
|
||||||
const route = useRoute()
|
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('')
|
const configText = ref('')
|
||||||
|
|
||||||
|
@ -46,7 +52,7 @@ function save() {
|
||||||
<footer-tool-bar>
|
<footer-tool-bar>
|
||||||
<a-space>
|
<a-space>
|
||||||
<a-button @click="$router.go(-1)">
|
<a-button @click="$router.go(-1)">
|
||||||
<translate>Cancel</translate>
|
<translate>Back</translate>
|
||||||
</a-button>
|
</a-button>
|
||||||
<a-button type="primary" @click="save">
|
<a-button type="primary" @click="save">
|
||||||
<translate>Save</translate>
|
<translate>Save</translate>
|
||||||
|
|
41
frontend/src/views/config/config.tsx
Normal file
41
frontend/src/views/config/config.tsx
Normal file
|
@ -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
|
|
@ -1,184 +1,173 @@
|
||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/0xJacky/Nginx-UI/server/pkg/config_list"
|
"github.com/0xJacky/Nginx-UI/server/pkg/config_list"
|
||||||
"github.com/0xJacky/Nginx-UI/server/pkg/nginx"
|
"github.com/0xJacky/Nginx-UI/server/pkg/nginx"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetConfigs(c *gin.Context) {
|
func GetConfigs(c *gin.Context) {
|
||||||
orderBy := c.Query("order_by")
|
orderBy := c.Query("order_by")
|
||||||
sort := c.DefaultQuery("sort", "desc")
|
sort := c.DefaultQuery("sort", "desc")
|
||||||
|
dir := c.DefaultQuery("dir", "/")
|
||||||
|
|
||||||
mySort := map[string]string{
|
mySort := map[string]string{
|
||||||
"name": "string",
|
"name": "string",
|
||||||
"modify": "time",
|
"modify": "time",
|
||||||
}
|
"is_dir": "bool",
|
||||||
|
}
|
||||||
|
|
||||||
configFiles, err := os.ReadDir(nginx.GetNginxConfPath("/"))
|
configFiles, err := os.ReadDir(nginx.GetNginxConfPath(dir))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ErrHandler(c, err)
|
ErrHandler(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var configs []gin.H
|
var configs []gin.H
|
||||||
|
|
||||||
for i := range configFiles {
|
for i := range configFiles {
|
||||||
file := configFiles[i]
|
file := configFiles[i]
|
||||||
fileInfo, _ := file.Info()
|
fileInfo, _ := file.Info()
|
||||||
|
|
||||||
switch mode := fileInfo.Mode(); {
|
switch mode := fileInfo.Mode(); {
|
||||||
case mode.IsRegular(): // regular file, not a hidden file
|
case mode.IsRegular(): // regular file, not a hidden file
|
||||||
if "." == file.Name()[0:1] {
|
if "." == file.Name()[0:1] {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
case mode&os.ModeSymlink != 0: // is a symbol
|
case mode&os.ModeSymlink != 0: // is a symbol
|
||||||
var targetPath string
|
var targetPath string
|
||||||
targetPath, err = os.Readlink(nginx.GetNginxConfPath(file.Name()))
|
targetPath, err = os.Readlink(nginx.GetNginxConfPath(file.Name()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("GetConfigs Read Symlink Error", targetPath, err)
|
log.Println("GetConfigs Read Symlink Error", targetPath, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var targetInfo os.FileInfo
|
configs = append(configs, gin.H{
|
||||||
targetInfo, err = os.Stat(targetPath)
|
"name": file.Name(),
|
||||||
if err != nil {
|
"size": fileInfo.Size(),
|
||||||
log.Println("GetConfigs Stat Error", targetPath, err)
|
"modify": fileInfo.ModTime(),
|
||||||
continue
|
"is_dir": file.IsDir(),
|
||||||
}
|
})
|
||||||
// but target file is not a dir
|
}
|
||||||
if targetInfo.IsDir() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
configs = append(configs, gin.H{
|
configs = config_list.Sort(orderBy, sort, mySort[orderBy], configs)
|
||||||
"name": file.Name(),
|
|
||||||
"size": fileInfo.Size(),
|
|
||||||
"modify": fileInfo.ModTime(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
func GetConfig(c *gin.Context) {
|
||||||
name := c.Param("name")
|
name := c.Param("name")
|
||||||
path := filepath.Join(nginx.GetNginxConfPath("/"), name)
|
path := filepath.Join(nginx.GetNginxConfPath("/"), name)
|
||||||
|
|
||||||
content, err := os.ReadFile(path)
|
content, err := os.ReadFile(path)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ErrHandler(c, err)
|
ErrHandler(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"config": string(content),
|
"config": string(content),
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type AddConfigJson struct {
|
type AddConfigJson struct {
|
||||||
Name string `json:"name" binding:"required"`
|
Name string `json:"name" binding:"required"`
|
||||||
Content string `json:"content" binding:"required"`
|
Content string `json:"content" binding:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func AddConfig(c *gin.Context) {
|
func AddConfig(c *gin.Context) {
|
||||||
var request AddConfigJson
|
var request AddConfigJson
|
||||||
err := c.BindJSON(&request)
|
err := c.BindJSON(&request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ErrHandler(c, err)
|
ErrHandler(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
name := request.Name
|
name := request.Name
|
||||||
content := request.Content
|
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 {
|
||||||
if _, err = os.Stat(path); err == nil {
|
c.JSON(http.StatusNotAcceptable, gin.H{
|
||||||
c.JSON(http.StatusNotAcceptable, gin.H{
|
"message": "config exist",
|
||||||
"message": "config exist",
|
})
|
||||||
})
|
return
|
||||||
return
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if content != "" {
|
if content != "" {
|
||||||
err = os.WriteFile(path, []byte(content), 0644)
|
err = os.WriteFile(path, []byte(content), 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ErrHandler(c, err)
|
ErrHandler(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
output := nginx.ReloadNginx()
|
output := nginx.ReloadNginx()
|
||||||
|
|
||||||
if output != "" && strings.Contains(output, "error") {
|
if output != "" && strings.Contains(output, "error") {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"message": output,
|
"message": output,
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"name": name,
|
"name": name,
|
||||||
"content": content,
|
"content": content,
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type EditConfigJson struct {
|
type EditConfigJson struct {
|
||||||
Content string `json:"content" binding:"required"`
|
Content string `json:"content" binding:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func EditConfig(c *gin.Context) {
|
func EditConfig(c *gin.Context) {
|
||||||
name := c.Param("name")
|
name := c.Param("name")
|
||||||
var request EditConfigJson
|
var request EditConfigJson
|
||||||
err := c.BindJSON(&request)
|
err := c.BindJSON(&request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ErrHandler(c, err)
|
ErrHandler(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
path := filepath.Join(nginx.GetNginxConfPath("/"), name)
|
path := filepath.Join(nginx.GetNginxConfPath("/"), name)
|
||||||
content := request.Content
|
content := request.Content
|
||||||
|
|
||||||
origContent, err := os.ReadFile(path)
|
origContent, err := os.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ErrHandler(c, err)
|
ErrHandler(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if content != "" && content != string(origContent) {
|
if content != "" && content != string(origContent) {
|
||||||
// model.CreateBackup(path)
|
// model.CreateBackup(path)
|
||||||
err = os.WriteFile(path, []byte(content), 0644)
|
err = os.WriteFile(path, []byte(content), 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ErrHandler(c, err)
|
ErrHandler(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
output := nginx.ReloadNginx()
|
output := nginx.ReloadNginx()
|
||||||
|
|
||||||
if output != "" && strings.Contains(output, "error") {
|
if output != "" && strings.Contains(output, "error") {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"message": output,
|
"message": output,
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
GetConfig(c)
|
GetConfig(c)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue