Pārlūkot izejas kodu

feat: config templates #53, #34, #33

0xJacky 2 gadi atpakaļ
vecāks
revīzija
f5a2a634a1

+ 1 - 1
.air.toml

@@ -13,7 +13,7 @@ bin = "tmp/main"
 # Customize binary.
 full_bin = "APP_ENV=dev APP_USER=air ./tmp/main"
 # Watch these filename extensions.
-include_ext = ["go", "tpl", "tmpl", "html"]
+include_ext = ["go", "tpl", "tmpl", "html", "conf"]
 # Ignore these filename extensions or directories.
 exclude_dir = ["assets", "tmp", "vendor", "frontend/node_modules", "upload"]
 # Watch these directories if you specified.

+ 2 - 0
frontend/components.d.ts

@@ -28,6 +28,8 @@ declare module '@vue/runtime-core' {
     ALayoutFooter: typeof import('ant-design-vue/es')['LayoutFooter']
     ALayoutHeader: typeof import('ant-design-vue/es')['LayoutHeader']
     ALayoutSider: typeof import('ant-design-vue/es')['LayoutSider']
+    AList: typeof import('ant-design-vue/es')['List']
+    AListItem: typeof import('ant-design-vue/es')['ListItem']
     AMenu: typeof import('ant-design-vue/es')['Menu']
     AMenuItem: typeof import('ant-design-vue/es')['MenuItem']
     AModal: typeof import('ant-design-vue/es')['Modal']

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

@@ -0,0 +1,25 @@
+import Curd from '@/api/curd'
+import http from '@/lib/http'
+
+class Template extends Curd {
+    get_config_list() {
+        return http.get('template/configs')
+    }
+
+    get_block_list() {
+        return http.get('template/blocks')
+    }
+
+    get_config(name: string) {
+        return http.get('template/config/' + name)
+    }
+
+    get_block(name: string) {
+        return http.get('template/block/' + name)
+    }
+
+}
+
+const template = new Template('/template')
+
+export default template

+ 0 - 1
frontend/src/components/SetLanguage/SetLanguage.vue

@@ -1,7 +1,6 @@
 <script setup lang="ts">
 import gettext from '@/gettext'
 
-
 import {ref, watch} from 'vue'
 
 import {useSettingsStore} from '@/pinia'

+ 1 - 1
frontend/src/layouts/BaseLayout.vue

@@ -39,7 +39,7 @@ const lang = computed(() => {
 
 </script>
 <template>
-    <a-config-provider :locale="lang">
+    <a-config-provider :locale="lang" :autoInsertSpaceInButton="false">
         <a-layout style="min-height: 100%;">
             <div class="drawer-sidebar">
                 <a-drawer

+ 2 - 3
frontend/src/views/domain/DomainAdd.vue

@@ -8,6 +8,7 @@ import ngx from '@/api/ngx'
 import {computed, reactive, ref} from 'vue'
 import {message} from 'ant-design-vue'
 import {useRouter} from 'vue-router'
+import template from '@/api/template'
 
 const {$gettext, interpolate} = useGettext()
 
@@ -39,7 +40,7 @@ function init() {
 
 function save() {
     ngx.build_config(ngx_config).then(r => {
-        domain.save(config.name, {content: r.content, enabled: true}).then(() => {
+        domain.save(config.name, {name: config.name, content: r.content, enabled: true}).then(() => {
             message.success($gettext('Saved successfully'))
 
             domain.enable(config.name).then(() => {
@@ -89,7 +90,6 @@ const has_server_name = computed(() => {
                 <a-step :title="$gettext('Configure SSL')"/>
                 <a-step :title="$gettext('Finished')"/>
             </a-steps>
-
             <template v-if="current_step===0">
                 <a-form layout="vertical">
                     <a-form-item :label="$gettext('Configuration Name')">
@@ -97,7 +97,6 @@ const has_server_name = computed(() => {
                     </a-form-item>
                 </a-form>
 
-
                 <directive-editor :ngx_directives="ngx_config.servers[0].directives"/>
                 <br/>
                 <location-editor :locations="ngx_config.servers[0].locations"/>

+ 1 - 0
frontend/src/views/domain/cert/IssueCert.vue

@@ -166,6 +166,7 @@ const modalClosable = ref(false)
     <a-modal
         :title="$gettext('Obtaining certificate')"
         v-model:visible="modalVisible"
+        :mask-closable="modalClosable"
         :footer="null" :closable="modalClosable" force-render>
         <a-progress
             :stroke-color="progressStrokeColor"

+ 106 - 0
frontend/src/views/domain/ngx_conf/ConfigTemplate.vue

@@ -0,0 +1,106 @@
+<script setup lang="ts">
+import {useGettext} from 'vue3-gettext'
+import template from '@/api/template'
+import {computed, ref} 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'
+
+const {$gettext} = useGettext()
+const {language} = storeToRefs(useSettingsStore())
+const props = defineProps(['ngx_config', 'current_server_index'])
+
+const blocks = ref([])
+const data: any = ref({})
+const visible = ref(false)
+
+function get_block_list() {
+    template.get_block_list().then(r => {
+        blocks.value = r.data
+    })
+}
+
+get_block_list()
+
+function view(name: string) {
+    visible.value = true
+    template.get_block(name).then(r => {
+        data.value = r
+    })
+}
+
+const trans_description = computed(() => {
+    return (item: any) => item.description?.[language.value] ?? item.description?.en ?? ''
+})
+
+async function add() {
+
+    if (data.value.custom) {
+        props.ngx_config.custom += '\n' + data.value.custom
+    }
+
+    props.ngx_config.custom = props.ngx_config.custom.trim()
+
+    if (data.value.locations) {
+        props.ngx_config.servers[props.current_server_index].locations.push(...data.value.locations)
+    }
+
+    if (data.value.directives) {
+        props.ngx_config.servers[props.current_server_index].directives.push(...data.value.directives)
+    }
+
+    visible.value = false
+}
+</script>
+
+<template>
+    <div>
+        <h2 v-translate>Config Templates</h2>
+        <div class="config-list-wrapper">
+            <a-list
+                :grid="{ gutter: 16, xs: 1, sm: 2, md: 2, lg: 2, xl: 2, xxl: 2, xxxl: 2 }"
+                :data-source="blocks"
+            >
+                <template #renderItem="{ item }">
+                    <a-list-item>
+                        <a-card size="small" :title="item.name">
+                            <template #extra>
+                                <a-button type="link" @click="view(item.filename)">View</a-button>
+                            </template>
+                            <p>{{ $gettext('Author') }}: {{ item.author }}</p>
+                            <p>{{ $gettext('Description') }}: {{ trans_description(item) }}</p>
+                        </a-card>
+                    </a-list-item>
+                </template>
+            </a-list>
+        </div>
+        <a-modal
+            :title="data.name"
+            v-model:visible="visible"
+            :mask="false"
+            :ok-text="$gettext('Add')"
+            @ok="add"
+        >
+            <p>{{ $gettext('Author') }}: {{ data.author }}</p>
+            <p>{{ $gettext('Description') }}: {{ trans_description(data) }}</p>
+            <template v-if="data.custom">
+                <h2>{{ $gettext('Custom') }}</h2>
+                <code-editor v-model:content="data.custom" default-height="150px"/>
+            </template>
+            <directive-editor v-if="data.directives" :ngx_directives="data.directives" :readonly="true"/>
+            <br/>
+            <location-editor v-if="data.locations" :locations="data.locations" :readonly="true"/>
+        </a-modal>
+    </div>
+</template>
+
+<style lang="less" scoped>
+.config-list-wrapper {
+    max-height: 200px;
+    overflow-y: scroll;
+    overflow-x: hidden;
+}
+</style>

+ 3 - 3
frontend/src/views/domain/ngx_conf/LocationEditor.vue

@@ -7,7 +7,7 @@ import draggable from 'vuedraggable'
 
 const {$gettext} = useGettext()
 
-const props = defineProps(['locations'])
+const props = defineProps(['locations', 'readonly'])
 
 let location = reactive({
     comments: '',
@@ -52,7 +52,7 @@ function remove(index: number) {
                     <HolderOutlined/>
                     {{ $gettext('Location') }}
                 </template>
-                <template #extra>
+                <template #extra v-if="!readonly">
                     <a-popconfirm @confirm="remove(index)"
                                   :title="$gettext('Are you sure you want to remove this location?')"
                                   :ok-text="$gettext('Yes')"
@@ -94,7 +94,7 @@ function remove(index: number) {
         </a-form>
     </a-modal>
 
-    <div>
+    <div v-if="!readonly">
         <a-button block @click="add">{{ $gettext('Add Location') }}</a-button>
     </div>
 </template>

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

@@ -6,6 +6,8 @@ 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 CodeEditor from '@/components/CodeEditor/CodeEditor.vue'
 
 const {$gettext} = useGettext()
 
@@ -151,6 +153,9 @@ watch(current_server_index, () => {
             <a-switch @change="change_tls"/>
         </a-form-item>
 
+        <h2>{{ $gettext('Custom') }}</h2>
+        <code-editor v-model:content="ngx_config.custom" default-height="150px"/>
+
         <a-tabs v-model:activeKey="current_server_index">
             <a-tab-pane :tab="'Server '+(k+1)" v-for="(v,k) in props.ngx_config.servers" :key="k">
                 <log-entry
@@ -175,9 +180,11 @@ watch(current_server_index, () => {
                         <h3 v-translate>Comments</h3>
                         <a-textarea v-model:value="v.comments" :bordered="false"/>
                     </template>
-
                     <directive-editor :ngx_directives="v.directives"/>
                     <br/>
+                    <config-template :ngx_config="ngx_config"
+                                     :current_server_index="current_server_index"/>
+                    <br/>
                     <location-editor :locations="v.locations"/>
                 </div>
 

+ 5 - 5
frontend/src/views/domain/ngx_conf/directive/DirectiveEditor.vue

@@ -7,9 +7,7 @@ import DirectiveEditorItem from '@/views/domain/ngx_conf/directive/DirectiveEdit
 
 const {$gettext} = useGettext()
 
-const props = defineProps<{
-    ngx_directives: any[]
-}>()
+const props = defineProps(['ngx_directives', 'readonly'])
 
 const adding = ref(false)
 
@@ -38,11 +36,13 @@ function onSave(idx: number) {
             <directive-editor-item @click="current_idx=index"
                                    :directive="directive"
                                    :current_idx="current_idx" :index="index"
-                                   :ngx_directives="ngx_directives"/>
+                                   :ngx_directives="ngx_directives"
+                                   :readonly="readonly"
+            />
         </template>
     </draggable>
 
-    <directive-add :ngx_directives="ngx_directives"/>
+    <directive-add v-if="!readonly" :ngx_directives="ngx_directives"/>
 </template>
 
 <style lang="less" scoped>

+ 3 - 2
frontend/src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue

@@ -10,7 +10,7 @@ import {message} from 'ant-design-vue'
 
 const {$gettext, interpolate} = useGettext()
 
-const props = defineProps(['directive', 'current_idx', 'index', 'ngx_directives'])
+const props = defineProps(['directive', 'current_idx', 'index', 'ngx_directives', 'readonly'])
 
 function remove(index: number) {
     props.ngx_directives.splice(index, 1)
@@ -56,7 +56,8 @@ function save() {
                 </template>
             </a-input>
 
-            <a-popconfirm @confirm="remove(index)"
+            <a-popconfirm v-if="!readonly"
+                          @confirm="remove(index)"
                           :title="$gettext('Are you sure you want to remove this directive?')"
                           :ok-text="$gettext('Yes')"
                           :cancel-text="$gettext('No')">

+ 28 - 68
server/api/template.go

@@ -1,15 +1,11 @@
 package api
 
 import (
-	"bufio"
 	"github.com/0xJacky/Nginx-UI/server/pkg/nginx"
+	"github.com/0xJacky/Nginx-UI/server/service"
 	"github.com/0xJacky/Nginx-UI/server/settings"
-	"github.com/0xJacky/Nginx-UI/template"
 	"github.com/gin-gonic/gin"
-	"io"
 	"net/http"
-	"path/filepath"
-	"regexp"
 	"strings"
 )
 
@@ -64,79 +60,43 @@ proxy_pass http://127.0.0.1:{{ HTTP01PORT }};
 }
 
 func GetTemplateConfList(c *gin.Context) {
-	configs, err := template.DistFS.ReadDir("conf")
+	configList, err := service.GetTemplateList("conf")
+
 	if err != nil {
 		ErrHandler(c, err)
 		return
 	}
-	type configItem struct {
-		Name        string            `json:"name"`
-		Description map[string]string `json:"description"`
-		Author      string            `json:"author"`
-	}
-
-	var configList []configItem
-	for _, config := range configs {
-		func() {
-			configListItem := configItem{
-				Description: make(map[string]string),
-			}
 
-			file, _ := template.DistFS.Open(filepath.Join("conf", config.Name()))
-			defer file.Close()
-			r := bufio.NewReader(file)
-			bytes, _, err := r.ReadLine()
-			if err == io.EOF {
-				return
-			}
-			line := strings.TrimSpace(string(bytes))
-
-			if line != "# Nginx UI Template Start" {
-				return
-			}
-			var content string
-			for {
-				bytes, _, err = r.ReadLine()
-				if err == io.EOF {
-					break
-				}
-				line = strings.TrimSpace(string(bytes))
-				if line == "# Nginx UI Template End" {
-					break
-				}
-				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]
-					}
-				}
-			}
+	c.JSON(http.StatusOK, gin.H{
+		"data": configList,
+	})
+}
 
-			configList = append(configList, configListItem)
-		}()
+func GetTemplateBlockList(c *gin.Context) {
+	configList, err := service.GetTemplateList("block")
 
+	if err != nil {
+		ErrHandler(c, err)
+		return
 	}
 
 	c.JSON(http.StatusOK, gin.H{
 		"data": configList,
 	})
 }
+
+func GetTemplateBlock(c *gin.Context) {
+	type resp struct {
+		service.ConfigInfoItem
+		service.ConfigDetail
+	}
+	detail, err := service.ParseTemplate("block", c.Param("name"))
+	if err != nil {
+		ErrHandler(c, err)
+		return
+	}
+	c.JSON(http.StatusOK, resp{
+		service.GetTemplateInfo("block", c.Param("name")),
+		detail,
+	})
+}

+ 12 - 1
server/pkg/nginx/parse.go

@@ -13,6 +13,10 @@ const (
 	Upstream = "upstream"
 )
 
+func (s *NgxServer) ParseServer(directive gonginx.IDirective) {
+	s.parseServer(directive)
+}
+
 func (s *NgxServer) parseServer(directive gonginx.IDirective) {
 	if directive.GetBlock() == nil {
 		return
@@ -36,7 +40,9 @@ func (s *NgxServer) parseServer(directive gonginx.IDirective) {
 		}
 	}
 }
-
+func (l *NgxLocation) ParseLocation(directive gonginx.IDirective, deep int) {
+	l.parseLocation(directive, deep)
+}
 func (l *NgxLocation) parseLocation(directive gonginx.IDirective, deep int) {
 	if directive.GetBlock() == nil {
 		return
@@ -52,6 +58,10 @@ func (l *NgxLocation) parseLocation(directive gonginx.IDirective, deep int) {
 	}
 }
 
+func (d *NgxDirective) ParseDirective(directive gonginx.IDirective, deep int) {
+	d.parseDirective(directive, deep)
+}
+
 func (d *NgxDirective) parseDirective(directive gonginx.IDirective, deep int) {
 	if directive.GetBlock() != nil {
 		d.Params += directive.GetName() + " "
@@ -134,6 +144,7 @@ func parse(block gonginx.IBlock, ngxConfig *NgxConfig) {
 			ngxConfig.parseCustom(v)
 		}
 	}
+	ngxConfig.Custom = FmtCode(ngxConfig.Custom)
 }
 
 func ParseNgxConfigByContent(content string) (ngxConfig *NgxConfig) {

+ 2 - 0
server/router/routers.go

@@ -77,6 +77,8 @@ func InitRouter() *gin.Engine {
 
 			g.GET("template", api.GetTemplate)
 			g.GET("template/configs", api.GetTemplateConfList)
+			g.GET("template/blocks", api.GetTemplateBlockList)
+			g.GET("template/block/:name", api.GetTemplateBlock)
 
 			g.GET("cert/issue", api.IssueCert)
 

+ 145 - 0
server/service/template.go

@@ -0,0 +1,145 @@
+package service
+
+import (
+	"bufio"
+	"github.com/0xJacky/Nginx-UI/server/pkg/nginx"
+	"github.com/0xJacky/Nginx-UI/template"
+	"github.com/pkg/errors"
+	"github.com/tufanbarisyildirim/gonginx/parser"
+	"io"
+	"path/filepath"
+	"regexp"
+	"strings"
+)
+
+type ConfigInfoItem struct {
+	Name        string            `json:"name"`
+	Description map[string]string `json:"description"`
+	Author      string            `json:"author"`
+	Filename    string            `json:"filename"`
+}
+
+func GetTemplateInfo(path, name string) (configListItem ConfigInfoItem) {
+	configListItem = ConfigInfoItem{
+		Description: make(map[string]string),
+		Filename:    name,
+	}
+
+	file, _ := template.DistFS.Open(filepath.Join(path, name))
+	defer file.Close()
+	r := bufio.NewReader(file)
+	bytes, _, err := r.ReadLine()
+	if err == io.EOF {
+		return
+	}
+	line := strings.TrimSpace(string(bytes))
+
+	if line != "# Nginx UI Template Start" {
+		return
+	}
+	var content string
+	for {
+		bytes, _, err = r.ReadLine()
+		if err == io.EOF {
+			break
+		}
+		line = strings.TrimSpace(string(bytes))
+		if line == "# Nginx UI Template End" {
+			break
+		}
+		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]
+			}
+		}
+	}
+
+	return
+}
+
+type ConfigDetail struct {
+	Custom string `json:"custom"`
+	nginx.NgxServer
+}
+
+func ParseTemplate(path, name string) (c ConfigDetail, err error) {
+	file, err := template.DistFS.Open(filepath.Join(path, name))
+	if err != nil {
+		err = errors.Wrap(err, "error tokenized template")
+		return
+	}
+	defer file.Close()
+
+	r := bufio.NewReader(file)
+	var flag bool
+	custom := ""
+	content := ""
+	for {
+		bytes, _, err := r.ReadLine()
+		if err == io.EOF {
+			break
+		}
+		orig := string(bytes)
+		line := strings.TrimSpace(orig)
+		switch {
+		case line == "# Nginx UI Custom Start":
+			flag = true
+		case line == "# Nginx UI Custom End":
+			flag = false
+		case flag == true:
+			custom += orig + "\n"
+		case flag == false:
+			content += orig + "\n"
+		}
+	}
+	p := parser.NewStringParser(content)
+	config := p.Parse()
+	c.Custom = custom
+	for _, d := range config.GetDirectives() {
+		switch d.GetName() {
+		case nginx.Location:
+			l := &nginx.NgxLocation{}
+			l.ParseLocation(d, 0)
+			c.NgxServer.Locations = append(c.NgxServer.Locations, l)
+		default:
+			dir := &nginx.NgxDirective{}
+			dir.ParseDirective(d, 0)
+			c.NgxServer.Directives = append(c.NgxServer.Directives, dir)
+		}
+	}
+	return
+}
+
+func GetTemplateList(path string) (configList []ConfigInfoItem, err error) {
+	configs, err := template.DistFS.ReadDir(path)
+	if err != nil {
+		err = errors.Wrap(err, "error get template list")
+		return
+	}
+
+	for _, config := range configs {
+		configList = append(configList, GetTemplateInfo(path, config.Name()))
+	}
+
+	return
+}

+ 9 - 3
template/block/codeigniter.conf

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

+ 13 - 0
template/block/enable-php-8.conf

@@ -0,0 +1,13 @@
+# Nginx UI Template Start
+# Name: PHP8.1
+# Description[en]: Enabled PHP 8.1 Config
+# Description[zh_CN]: 启用 PHP 8.1 配置
+# Author: @0xJacky
+# Nginx UI Template End
+location ~ [^/]\.php(/|$)
+{
+    try_files $uri =404;
+    fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
+    fastcgi_index index.php;
+    include fastcgi.conf;
+}

+ 9 - 3
template/block/laravel.conf

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

+ 15 - 0
template/block/reverse_proxy.conf

@@ -0,0 +1,15 @@
+# Nginx UI Template Start
+# Name: Reverse Proxy
+# Description[en]: Reverse Proxy Config
+# Description[zh_CN]: 反向代理配置
+# Author: @0xJacky
+# Nginx UI Template End
+location / {
+        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;
+ }

+ 26 - 0
template/block/reverse_proxy_ws.conf

@@ -0,0 +1,26 @@
+# 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;
+ }