Browse Source

enhance: block template parser

0xJacky 2 years ago
parent
commit
f15e66e0de

+ 4 - 0
frontend/src/api/template.ts

@@ -18,6 +18,10 @@ class Template extends Curd {
         return http.get('template/block/' + name)
     }
 
+    build_block(name: string, data: any) {
+        return http.post('template/block/' + name, data)
+    }
+
 }
 
 const template = new Template('/template')

+ 1 - 1
frontend/src/views/domain/ngx_conf/NgxConfigEditor.vue

@@ -6,7 +6,7 @@ import {useRoute, useRouter} from 'vue-router'
 import {useGettext} from 'vue3-gettext'
 import Cert from '@/views/domain/cert/Cert.vue'
 import LogEntry from '@/views/domain/ngx_conf/LogEntry.vue'
-import ConfigTemplate from '@/views/domain/ngx_conf/ConfigTemplate.vue'
+import ConfigTemplate from '@/views/domain/ngx_conf/config_template/ConfigTemplate.vue'
 import CodeEditor from '@/components/CodeEditor/CodeEditor.vue'
 import {PlusOutlined} from '@ant-design/icons-vue'
 

+ 24 - 3
frontend/src/views/domain/ngx_conf/ConfigTemplate.vue → frontend/src/views/domain/ngx_conf/config_template/ConfigTemplate.vue

@@ -1,13 +1,16 @@
 <script setup lang="ts">
 import {useGettext} from 'vue3-gettext'
 import template from '@/api/template'
-import {computed, ref} from 'vue'
+import {computed, provide, ref, watch} from 'vue'
 import {storeToRefs} from 'pinia'
 import {useSettingsStore} from '@/pinia'
 import Template from '@/views/template/Template.vue'
 import DirectiveEditor from '@/views/domain/ngx_conf/directive/DirectiveEditor.vue'
 import LocationEditor from '@/views/domain/ngx_conf/LocationEditor.vue'
 import CodeEditor from '@/components/CodeEditor/CodeEditor.vue'
+import TemplateForm from '@/views/domain/ngx_conf/config_template/TemplateForm.vue'
+import * as wasi from 'wasi'
+import _ from 'lodash'
 
 const {$gettext} = useGettext()
 const {language} = storeToRefs(useSettingsStore())
@@ -16,6 +19,7 @@ const props = defineProps(['ngx_config', 'current_server_index'])
 const blocks = ref([])
 const data: any = ref({})
 const visible = ref(false)
+const name = ref('')
 
 function get_block_list() {
     template.get_block_list().then(r => {
@@ -25,9 +29,11 @@ function get_block_list() {
 
 get_block_list()
 
-function view(name: string) {
+
+function view(n: string) {
     visible.value = true
-    template.get_block(name).then(r => {
+    name.value = n
+    template.get_block(n).then(r => {
         data.value = r
     })
 }
@@ -54,6 +60,20 @@ async function add() {
 
     visible.value = false
 }
+
+const variables = computed(() => {
+    return data.value.variables
+})
+
+function build_template() {
+    template.build_block(name.value, variables.value).then(r => {
+        data.value.directives = r.directives
+        data.value.locations = r.locations
+        data.value.custom = r.custom
+    })
+}
+
+provide('build_template', build_template)
 </script>
 
 <template>
@@ -88,6 +108,7 @@ async function add() {
         >
             <p>{{ $gettext('Author') }}: {{ data.author }}</p>
             <p>{{ $gettext('Description') }}: {{ trans_description(data) }}</p>
+            <template-form :data="data.variables"/>
             <template v-if="data.custom">
                 <h2>{{ $gettext('Custom') }}</h2>
                 <code-editor v-model:content="data.custom" default-height="150px"/>

+ 15 - 0
frontend/src/views/domain/ngx_conf/config_template/TemplateForm.vue

@@ -0,0 +1,15 @@
+<script setup lang="ts">
+import TemplateFormItem from '@/views/domain/ngx_conf/config_template/TemplateFormItem.vue'
+
+const props = defineProps(['data'])
+</script>
+
+<template>
+    <a-form layout="vertical">
+        <template-form-item v-for="(v,k) in data" :name="k" :data="v"/>
+    </a-form>
+</template>
+
+<style lang="less" scoped>
+
+</style>

+ 33 - 0
frontend/src/views/domain/ngx_conf/config_template/TemplateFormItem.vue

@@ -0,0 +1,33 @@
+<script setup lang="ts">
+import {computed, inject, watch} from 'vue'
+import {storeToRefs} from 'pinia'
+import {useSettingsStore} from '@/pinia'
+import {useGettext} from 'vue3-gettext'
+import _ from 'lodash'
+
+const {$gettext} = useGettext()
+
+const {language} = storeToRefs(useSettingsStore())
+const props = defineProps(['data', 'name'])
+
+const trans_name = computed(() => {
+    return props.data?.name?.[language.value] ?? props.data?.name?.en ?? ''
+})
+
+const build_template: any = inject('build_template')!
+
+const value = computed(() => props.data.value)
+
+watch(value, _.throttle(build_template, 500))
+</script>
+
+<template>
+    <a-form-item :label="trans_name">
+        <a-input v-if="data.type === 'string'" v-model:value="data.value"/>
+        <a-switch v-else-if="data.type === 'boolean'" v-model:checked="data.value"/>
+    </a-form-item>
+</template>
+
+<style lang="less" scoped>
+
+</style>

+ 1 - 0
go.mod

@@ -33,6 +33,7 @@ require (
 )
 
 require (
+	github.com/BurntSushi/toml v1.2.1 // indirect
 	github.com/StackExchange/wmi v1.2.1 // indirect
 	github.com/bytedance/sonic v1.8.7 // indirect
 	github.com/cenkalti/backoff/v4 v4.2.0 // indirect

+ 4 - 0
go.sum

@@ -1,3 +1,5 @@
+github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
+github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
 github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
 github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA=
 github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8=
@@ -71,6 +73,8 @@ github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWm
 github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
 github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
+github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM=
+github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
 github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
 github.com/jackc/pgconn v1.13.0 h1:3L1XMNV2Zvca/8BYhzcRFS70Lr0WlDg16Di6SFGAbys=
 github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=

+ 11 - 2
server/api/template.go

@@ -74,13 +74,22 @@ func GetTemplateBlock(c *gin.Context) {
 		service.ConfigInfoItem
 		service.ConfigDetail
 	}
-	detail, err := service.ParseTemplate("block", c.Param("name"))
+	var bindData map[string]service.TVariable
+	_ = c.ShouldBindJSON(&bindData)
+	info := service.GetTemplateInfo("block", c.Param("name"))
+
+	if bindData == nil {
+		bindData = info.Variables
+	}
+
+	detail, err := service.ParseTemplate("block", c.Param("name"), bindData)
 	if err != nil {
 		ErrHandler(c, err)
 		return
 	}
+	info.Variables = bindData
 	c.JSON(http.StatusOK, resp{
-		service.GetTemplateInfo("block", c.Param("name")),
+		info,
 		detail,
 	})
 }

+ 1 - 0
server/router/routers.go

@@ -80,6 +80,7 @@ func InitRouter() *gin.Engine {
 			g.GET("template/configs", api.GetTemplateConfList)
 			g.GET("template/blocks", api.GetTemplateBlockList)
 			g.GET("template/block/:name", api.GetTemplateBlock)
+			g.POST("template/block/:name", api.GetTemplateBlock)
 
 			g.GET("certs", api.GetCertList)
 			g.GET("cert/:id", api.GetCert)

+ 78 - 35
server/service/template.go

@@ -2,22 +2,33 @@ package service
 
 import (
 	"bufio"
+	"bytes"
 	"github.com/0xJacky/Nginx-UI/server/pkg/nginx"
 	"github.com/0xJacky/Nginx-UI/server/settings"
-	"github.com/0xJacky/Nginx-UI/template"
+	templ "github.com/0xJacky/Nginx-UI/template"
+	"github.com/BurntSushi/toml"
+	"github.com/gin-gonic/gin"
 	"github.com/pkg/errors"
 	"github.com/tufanbarisyildirim/gonginx/parser"
 	"io"
+	"log"
 	"path/filepath"
-	"regexp"
 	"strings"
+	"text/template"
 )
 
+type TVariable struct {
+	Type  string            `json:"type"`
+	Name  map[string]string `json:"name"`
+	Value interface{}       `json:"value"`
+}
+
 type ConfigInfoItem struct {
-	Name        string            `json:"name"`
-	Description map[string]string `json:"description"`
-	Author      string            `json:"author"`
-	Filename    string            `json:"filename"`
+	Name        string               `json:"name"`
+	Description map[string]string    `json:"description"`
+	Author      string               `json:"author"`
+	Filename    string               `json:"filename"`
+	Variables   map[string]TVariable `json:"variables"`
 }
 
 func GetTemplateInfo(path, name string) (configListItem ConfigInfoItem) {
@@ -26,7 +37,7 @@ func GetTemplateInfo(path, name string) (configListItem ConfigInfoItem) {
 		Filename:    name,
 	}
 
-	file, _ := template.DistFS.Open(filepath.Join(path, name))
+	file, _ := templ.DistFS.Open(filepath.Join(path, name))
 	defer file.Close()
 	r := bufio.NewReader(file)
 	bytes, _, err := r.ReadLine()
@@ -50,31 +61,11 @@ func GetTemplateInfo(path, name string) (configListItem ConfigInfoItem) {
 		}
 		content += line + "\n"
 	}
-	re := regexp.MustCompile(`# (\S+): (.*)`)
-	matches := re.FindAllStringSubmatch(content, -1)
-	for _, match := range matches {
-		if len(match) < 3 {
-			continue
-		}
-		key := match[1]
-		switch {
-		case key == "Name":
-			configListItem.Name = match[2]
-		case key == "Author":
-			configListItem.Author = match[2]
-		case strings.Contains(key, "Description"):
-			re = regexp.MustCompile(`(\w+)\[(\w+)\]`)
-			matches = re.FindAllStringSubmatch(key, -1)
-			for _, m := range matches {
-				if len(m) < 3 {
-					continue
-				}
-				// lang => description
-				configListItem.Description[m[2]] = match[2]
-			}
-		}
-	}
 
+	_, err = toml.Decode(content, &configListItem)
+	if err != nil {
+		log.Println("toml.Decode", err.Error())
+	}
 	return
 }
 
@@ -83,8 +74,8 @@ type ConfigDetail struct {
 	nginx.NgxServer
 }
 
-func ParseTemplate(path, name string) (c ConfigDetail, err error) {
-	file, err := template.DistFS.Open(filepath.Join(path, name))
+func ParseTemplate(path, name string, bindData map[string]TVariable) (c ConfigDetail, err error) {
+	file, err := templ.DistFS.Open(filepath.Join(path, name))
 	if err != nil {
 		err = errors.Wrap(err, "error tokenized template")
 		return
@@ -113,7 +104,59 @@ func ParseTemplate(path, name string) (c ConfigDetail, err error) {
 			content += orig + "\n"
 		}
 	}
-	content = strings.ReplaceAll(content, "{{ HTTP01PORT }}", settings.ServerSettings.HTTPChallengePort)
+
+	data := gin.H{
+		"HTTPPORT":   settings.ServerSettings.HttpPort,
+		"HTTP01PORT": settings.ServerSettings.HTTPChallengePort,
+	}
+
+	for k, v := range bindData {
+		data[k] = v.Value
+	}
+
+	t, err := template.New(name).Parse(custom)
+
+	if err != nil {
+		err = errors.Wrap(err, "error parse template.custom")
+		return
+	}
+
+	var buf bytes.Buffer
+
+	err = t.Execute(&buf, data)
+	if err != nil {
+		err = errors.Wrap(err, "error execute template")
+		return
+	}
+
+	custom = strings.TrimSpace(buf.String())
+
+	templatePart := strings.Split(content, "# Nginx UI Template End")
+	if len(templatePart) < 2 {
+		return
+	}
+
+	content = templatePart[1]
+
+	t, err = template.New(name).Parse(content)
+
+	if err != nil {
+		err = errors.Wrap(err, "error parse template")
+		return
+	}
+
+	buf.Reset()
+
+	err = t.Execute(&buf, data)
+	if err != nil {
+		err = errors.Wrap(err, "error execute template")
+		return
+	}
+
+	content = buf.String()
+
+	log.Println(content)
+
 	p := parser.NewStringParser(content)
 	config := p.Parse()
 	c.Custom = custom
@@ -137,7 +180,7 @@ func ParseTemplate(path, name string) (c ConfigDetail, err error) {
 }
 
 func GetTemplateList(path string) (configList []ConfigInfoItem, err error) {
-	configs, err := template.DistFS.ReadDir(path)
+	configs, err := templ.DistFS.ReadDir(path)
 	if err != nil {
 		err = errors.Wrap(err, "error get template list")
 		return

+ 3 - 4
template/block/codeigniter.conf

@@ -1,8 +1,7 @@
 # Nginx UI Template Start
-# Name: Codeigniter Rewrite
-# Description[en]: Codeigniter URL Rewrite Config
-# Description[zh_CN]: Codeigniter 伪静态配置
-# Author: @0xJacky
+name = "Codeigniter Rewrite"
+author = "@0xJacky"
+description = { en = "Codeigniter URL Rewrite Config", zh_CN = "Codeigniter 伪静态配置"}
 # Nginx UI Template End
 location / {
     try_files $uri $uri/ /index.php;

+ 3 - 4
template/block/enable-php-8.conf

@@ -1,8 +1,7 @@
 # Nginx UI Template Start
-# Name: PHP8.1
-# Description[en]: Enabled PHP 8.1 Config
-# Description[zh_CN]: 启用 PHP 8.1 配置
-# Author: @0xJacky
+name = "PHP8.1"
+author = "@0xJacky"
+description = { en = "Enabled PHP 8.1 Config", zh_CN = "启用 PHP 8.1 配置"}
 # Nginx UI Template End
 location ~ [^/]\.php(/|$)
 {

+ 15 - 5
template/block/hsts.conf

@@ -1,7 +1,17 @@
 # Nginx UI Template Start
-# Name: HSTS
-# Description[en]: Enable HTTP Strict Transport Security
-# Description[zh_CN]: 启用 HTTP 严格传输安全
-# Author: @0xJacky
+name = "HSTS"
+author = "@0xJacky"
+description = { en = "Enable HTTP Strict Transport Security", zh_CN = "启用 HTTP 严格传输安全"}
+
+[variables.maxAge]
+type = "string"
+name = { en = "Max Age", zh_CN = "有效时间"}
+value = "31536000"
+
+[variables.includeSubDomains]
+type = "boolean"
+name = { en = "Include sub domains", zh_CN = "包括子域名"}
+value = true
 # Nginx UI Template End
-add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
+
+add_header Strict-Transport-Security "max-age={{.maxAge}}; {{if .includeSubDomains}} includeSubDomains {{end}}" always;

+ 3 - 4
template/block/http-to-https.conf

@@ -1,7 +1,6 @@
 # Nginx UI Template Start
-# Name: HTTP to HTTPS
-# Description[en]: HTTP force redirect to HTTPS Config
-# Description[zh_CN]: HTTP 强制跳转 HTTPS 配置
-# Author: @0xJacky
+name = "HTTP to HTTPS"
+author = "@0xJacky"
+description = { en = "HTTP force redirect to HTTPS Config", zh_CN = "HTTP 强制跳转 HTTPS 配置"}
 # Nginx UI Template End
 return 307 https://$server_name$request_uri;

+ 3 - 4
template/block/laravel.conf

@@ -1,8 +1,7 @@
 # Nginx UI Template Start
-# Name: Laravel Rewrite
-# Description[en]: Laravel URL Rewrite Config
-# Description[zh_CN]: Laravel 伪静态配置
-# Author: @0xJacky
+name = "Laravel Rewrite"
+author = "@0xJacky"
+description = { en = "Laravel URL Rewrite Config", zh_CN = "Laravel 伪静态配置"}
 # Nginx UI Template End
 location / {
     try_files $uri $uri/ /server.php?$query_string;

+ 4 - 5
template/block/letsencrypt.conf

@@ -1,13 +1,12 @@
 # Nginx UI Template Start
-# Name: Let's Encrypt
-# Description[en]: Let's Encrypt HTTPChallange
-# Description[zh_CN]: Let's Encrypt HTTP 鉴权
-# Author: @0xJacky
+name = "Let's Encrypt"
+author = "@0xJacky"
+description = { en = "Let's Encrypt HTTPChallange", zh_CN = "Let's Encrypt HTTP 鉴权"}
 # Nginx UI Template End
 
 location /.well-known/acme-challenge {
     proxy_set_header Host $host;
     proxy_set_header X-Real_IP $remote_addr;
     proxy_set_header X-Forwarded-For $remote_addr:$remote_port;
-    proxy_pass http://127.0.0.1:{{ HTTP01PORT }};
+    proxy_pass http://127.0.0.1:{{.HTTP01PORT}};
 }

+ 4 - 5
template/block/nginx-ui.conf

@@ -1,8 +1,7 @@
 # Nginx UI Template Start
-# Name: Nginx UI
-# Description[en]: Nginx UI Config Template
-# Description[zh_CN]: Nginx UI 配置模板
-# Author: @0xJacky
+name = "Nginx UI"
+author = "@0xJacky"
+description = { en = "Nginx UI Config Templateg", zh_CN = "Nginx UI 配置模板"}
 # Nginx UI Template End
 
 # Nginx UI Custom Start
@@ -19,5 +18,5 @@ location / {
         proxy_http_version 1.1;
         proxy_set_header Upgrade $http_upgrade;
         proxy_set_header Connection $connection_upgrade;
-        proxy_pass http://127.0.0.1:9000/;
+        proxy_pass http://127.0.0.1:{{.HTTPPORT}}/;
 }

+ 39 - 6
template/block/reverse_proxy.conf

@@ -1,15 +1,48 @@
 # Nginx UI Template Start
-# Name: Reverse Proxy
-# Description[en]: Reverse Proxy Config
-# Description[zh_CN]: 反向代理配置
-# Author: @0xJacky
+name = "Reverse Proxy"
+author = "@0xJacky"
+description = { en = "Reverse Proxy Config", zh_CN = "反向代理配置"}
+
+[variables.enableWebSocket]
+type = "boolean"
+name = { en = "Enable WebSocket", zh_CN = "启用 WebSocket"}
+value = true
+
+[variables.clientMaxBodySize]
+type = "string"
+name = { en = "Include sub domains", zh_CN = "客户端最大请求内容大小"}
+value = "1000m"
+
+[variables.port]
+type = "string"
+name = { en = "Port", zh_CN = "端口"}
+value = 9000
 # Nginx UI Template End
+
+
+# Nginx UI Custom Start
+{{- if .enableWebSocket }}
+map $http_upgrade $connection_upgrade {
+    default upgrade;
+    '' close;
+}
+{{- end }}
+# Nginx UI Custom End
+
 location / {
-        proxy_pass http://127.0.0.1:9000/;
+        {{ if .enableWebSocket }}
+        proxy_http_version 1.1;
+        proxy_set_header Upgrade $http_upgrade;
+        proxy_set_header Connection $connection_upgrade;
+        {{ end }}
+
+        client_max_body_size {{ .clientMaxBodySize }};
+
         proxy_redirect off;
         proxy_set_header Host $host;
         proxy_set_header X-Real-IP $remote_addr;
         proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
         proxy_set_header X-Forwarded-Proto $scheme;
-        client_max_body_size 1000m;
+
+        proxy_pass http://127.0.0.1:{{ .port }}/;
  }

+ 0 - 26
template/block/reverse_proxy_ws.conf

@@ -1,26 +0,0 @@
-# Nginx UI Template Start
-# Name: Reverse Proxy WebSocket
-# Description[en]: Reverse Proxy with WebSocket Config
-# Description[zh_CN]: 反向代理 WebSocket 配置
-# Author: @0xJacky
-# Nginx UI Template End
-
-# Nginx UI Custom Start
-map $http_upgrade $connection_upgrade {
-    default upgrade;
-    '' close;
-}
-# Nginx UI Custom End
-
-location / {
-        proxy_http_version 1.1;
-        proxy_set_header Upgrade $http_upgrade;
-        proxy_set_header Connection $connection_upgrade;
-        proxy_pass http://127.0.0.1:9000/;
-        proxy_redirect off;
-        proxy_set_header Host $host;
-        proxy_set_header X-Real-IP $remote_addr;
-        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
-        proxy_set_header X-Forwarded-Proto $scheme;
-        client_max_body_size 1000m;
- }

+ 3 - 4
template/block/wordpress.conf

@@ -1,8 +1,7 @@
 # Nginx UI Template Start
-# Name: WordPress
-# Description[en]: WordPress Config Template
-# Description[zh_CN]: WordPress  配置模板
-# Author: @0xJacky
+name = "WordPress"
+author = "@0xJacky"
+description = { en = "WordPress Config Template", zh_CN = "WordPress  配置模板"}
 # Nginx UI Template End
 location / {
 		try_files $uri $uri/ /index.php?$args;