enhance: block template parser

This commit is contained in:
0xJacky 2023-04-10 23:43:34 +08:00
parent f36e582976
commit f15e66e0de
No known key found for this signature in database
GPG key ID: B6E4A6E4A561BAF0
20 changed files with 249 additions and 108 deletions

View file

@ -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')

View file

@ -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'

View file

@ -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"/>

View file

@ -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>

View file

@ -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
go.mod
View file

@ -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
go.sum
View file

@ -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=

View file

@ -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,
})
}

View file

@ -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)

View file

@ -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

View file

@ -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;

View file

@ -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(/|$)
{

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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}};
}

View file

@ -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}}/;
}

View file

@ -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 }}/;
}

View file

@ -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;
}

View file

@ -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;