Kaynağa Gözat

feat(template): add selection box; add scheme and host field for reverse_proxy #608

Jacky 6 ay önce
ebeveyn
işleme
91799e64c4

+ 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", "toml", "po"]
+include_ext = ["go", "tpl", "tmpl", "html", "toml", "po", "conf"]
 # Ignore these filename extensions or directories.
 exclude_dir = ["assets", "tmp", "vendor", "app/node_modules", "upload", "docs", "resources", ".idea"]
 # Watch these directories if you specified.

+ 0 - 1
api/template/template.go

@@ -66,7 +66,6 @@ func GetTemplateConfList(c *gin.Context) {
 
 func GetTemplateBlockList(c *gin.Context) {
 	configList, err := template.GetTemplateList("block")
-
 	if err != nil {
 		api.ErrHandler(c, err)
 		return

+ 4 - 5
app/src/api/template.ts

@@ -4,19 +4,18 @@ import type { NgxDirective, NgxLocation, NgxServer } from '@/api/ngx'
 
 export interface Variable {
   type?: string
-  name?: { [key: string]: string }
+  name?: Record<string, string>
   // eslint-disable-next-line @typescript-eslint/no-explicit-any
   value?: any
+  mask?: Record<string, Record<string, string>>
 }
 
 export interface Template extends NgxServer {
   name: string
-  description: { [key: string]: string }
+  description: Record<string, string>
   author: string
   filename: string
-  variables: {
-    [key: string]: Variable
-  }
+  variables: Record<string, Variable>
   custom: string
   locations?: NgxLocation[]
   directives?: NgxDirective[]

+ 2 - 1
app/src/views/certificate/WildcardCertificate.vue

@@ -13,6 +13,7 @@ const step = ref(0)
 const visible = ref(false)
 const data = ref({}) as Ref<AutoCertOptions>
 const issuing_cert = ref(false)
+const domain = ref('')
 
 provide('issuing_cert', issuing_cert)
 function open() {
@@ -22,6 +23,7 @@ function open() {
     challenge_method: 'dns01',
     key_type: '2048',
   } as AutoCertOptions
+  domain.value = ''
 }
 
 defineExpose({
@@ -32,7 +34,6 @@ const modalVisible = ref(false)
 const modalClosable = ref(true)
 
 const refObtainCertLive = ref()
-const domain = ref('')
 
 const computedDomain = computed(() => {
   return `*.${domain.value}`

+ 5 - 0
app/src/views/domain/ngx_conf/LocationEditor.vue

@@ -173,4 +173,9 @@ function duplicate(index: number) {
 .ant-collapse-header {
   align-items: center;
 }
+
+:deep(.ant-collapse-header-text) {
+  width: 100%;
+  overflow: hidden;
+}
 </style>

+ 7 - 5
app/src/views/domain/ngx_conf/config_template/ConfigTemplate.vue

@@ -119,19 +119,21 @@ provide('ngx_directives', ngx_directives)
     >
       <p>{{ $gettext('Author') }}: {{ data.author }}</p>
       <p>{{ $gettext('Description') }}: {{ trans_description(data) }}</p>
-      <TemplateForm v-model:data="data.variables" />
-      <template v-if="data.custom">
-        <h2>{{ $gettext('Custom') }}</h2>
+      <TemplateForm v-model="data.variables" />
+      <div
+        v-if="data.custom"
+        class="mb-4"
+      >
+        <h3>{{ $gettext('Custom') }}</h3>
         <CodeEditor
           v-model:content="data.custom"
           default-height="150px"
         />
-      </template>
+      </div>
       <DirectiveEditor
         v-if="data.directives"
         readonly
       />
-      <br>
       <LocationEditor
         v-if="data.locations"
         :locations="data.locations"

+ 3 - 20
app/src/views/domain/ngx_conf/config_template/TemplateForm.vue

@@ -2,25 +2,8 @@
 import TemplateFormItem from '@/views/domain/ngx_conf/config_template/TemplateFormItem.vue'
 import type { Variable } from '@/api/template'
 
-const props = defineProps<{
-  data: {
-    [key: string]: Variable
-  }
-}>()
-
-const emit = defineEmits<{
-  'update:data': [data: {
-    [key: string]: Variable
-  }]
-}>()
-
-const data = computed({
-  get() {
-    return props.data
-  },
-  set(v) {
-    emit('update:data', v)
-  },
+const data = defineModel<Record<string, Variable>>({
+  default: () => {},
 })
 </script>
 
@@ -29,7 +12,7 @@ const data = computed({
     <TemplateFormItem
       v-for="(_, k) in data"
       :key="k"
-      v-model:data="data[k]"
+      v-model="data[k]"
       :name="k.toString()"
     />
   </AForm>

+ 20 - 18
app/src/views/domain/ngx_conf/config_template/TemplateFormItem.vue

@@ -4,35 +4,32 @@ import _ from 'lodash'
 import { useSettingsStore } from '@/pinia'
 import type { Variable } from '@/api/template'
 
-const props = defineProps<{
-  data: Variable
-  name: string
-}>()
-
-const emit = defineEmits<{
-  'update:data': [data: Variable]
-}>()
-
-const data = computed({
-  get() {
-    return props.data
-  },
-  set(v) {
-    emit('update:data', v)
-  },
+const data = defineModel<Variable>({
+  default: () => {},
 })
 
 const { language } = storeToRefs(useSettingsStore())
 
 const trans_name = computed(() => {
-  return props.data?.name?.[language.value] ?? props.data?.name?.en ?? ''
+  return data.value?.name?.[language.value] ?? data.value?.name?.en ?? ''
 })
 
 const build_template = inject('build_template') as () => void
 
-const value = computed(() => props.data.value)
+const value = computed(() => data.value.value)
 
 watch(value, _.throttle(build_template, 500))
+
+const selectOptions = computed(() => {
+  return Object.keys(data.value?.mask || {}).map(k => {
+    const label = data.value.mask?.[k]?.[language.value] ?? data.value.mask?.[k]?.en ?? ''
+
+    return {
+      label,
+      value: k,
+    }
+  })
+})
 </script>
 
 <template>
@@ -41,6 +38,11 @@ watch(value, _.throttle(build_template, 500))
       v-if="data.type === 'string'"
       v-model:value="data.value"
     />
+    <ASelect
+      v-else-if="data.type === 'select'"
+      v-model:value="data.value"
+      :options="selectOptions"
+    />
     <ASwitch
       v-else-if="data.type === 'boolean'"
       v-model:checked="data.value"

+ 1 - 0
docs/.vitepress/config/zh_TW.ts

@@ -31,6 +31,7 @@ export const zhTWConfig: LocaleSpecificConfig<DefaultTheme.Config> = {
           items: [
             {text: '構建', link: '/zh_TW/guide/build'},
             {text: '專案結構', link: '/zh_TW/guide/project-structure'},
+            {text: '配置模板', link: '/zh_TW/guide/nginx-ui-template'},
             {text: '貢獻程式碼', link: '/zh_TW/guide/contributing'}
           ]
         },

+ 108 - 35
docs/guide/nginx-ui-template.md

@@ -13,31 +13,71 @@ Please note, you need to recompile the backend after modifying or adding new con
 
 Nginx UI Template file consists of two parts: the file header and the actual Nginx configuration.
 
-Below is a configuration template for hotlink protection, which we will use as a basis to introduce the file format and related syntax of Nginx UI Template.
+Below is a configuration template for reverse proxy, which we will use as a basis to introduce the file format and related syntax of Nginx UI Template.
 
 ```nginx configuration
 # Nginx UI Template Start
-name = "Hotlink Protection"
+name = "Reverse Proxy"
 author = "@0xJacky"
-description = { en = "Hotlink Protection Config Template", zh_CN = "防盗链配置模板"}
+description = { en = "Reverse Proxy Config", zh_CN = "反向代理配置"}
 
-[variables.NoneReferer]
+[variables.enableWebSocket]
 type = "boolean"
-name = { en = "Allow Referer is None", zh_CN = "允许空 Referer"}
-value = false
+name = { en = "Enable WebSocket", zh_CN = "启用 WebSocket"}
+value = true
 
-[variables.AllowReferers]
+[variables.clientMaxBodySize]
 type = "string"
-name = { en = "Allow Referers", zh_CN = "允许的 Referers"}
-value = ""
+name = { en = "Client Max Body Size", zh_CN = "客户端最大请求内容大小"}
+value = "1000m"
+
+[variables.scheme]
+type = "select"
+name = { en = "Scheme", zh_CN = "协议"}
+value = "http"
+mask = { http = { en = "HTTP" }, https = { en = "HTTPS" } }
+
+[variables.host]
+type = "string"
+name = { en = "Host", zh_CN = "主机"}
+value = "127.0.0.1"
+
+[variables.port]
+type = "string"
+name = { en = "Port", zh_CN = "端口"}
+value = 9000
 # Nginx UI Template End
 
-location ~ .*\.(jpg|png|js|css)$ {
-    valid_referers {{- if .NoneReferer}} none {{- end}} blocked server_names {{if .AllowReferers}}{{.AllowReferers}}{{- end}};
-    if ($invalid_referer) {
-        return 403;
-    }
+# Nginx UI Custom Start
+{{- if .enableWebSocket }}
+map $http_upgrade $connection_upgrade {
+    default upgrade;
+    '' close;
 }
+{{- end }}
+# Nginx UI Custom End
+
+if ($host != $server_name) {
+    return 404;
+}
+
+location / {
+        {{ 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;
+
+        proxy_pass {{ .scheme }}://{{ .host }}:{{ .port }}/;
+ }
 ```
 
 ## File Header
@@ -51,27 +91,44 @@ The file header includes the following fields:
 |             `name`             |                       Name of the configuration                       |                    string                     |   Yes    |
 |            `author`            |                                Author                                 |                    string                     |   Yes    |
 |         `description`          |     Desciption, uses a toml dictionary for multi-language support     |                toml dictionary                |   Yes    |
-| `variables.VariableName.type`  |       Variable type, currently supports `boolean` and `string`        |                    string                     |    No    |
-| `variables.VariableName.name`  | Variable display name, is a toml dictionary to support multi-language |                toml dictionary                |    No    |
+| `variables.VariableName.type`  |  Variable type, currently supports `boolean`, `string` and `select`   |                    string                     |   Yes    |
+| `variables.VariableName.name`  | Variable display name, is a toml dictionary to support multi-language |                toml dictionary                |   Yes    |
 | `variables.VariableName.value` |                     Default value of the variable                     | boolean/string (according to type definition) |    No    |
+| `variables.VariableName.mask`  |                         The options of select                         |                toml dictionary                |    No    |
 
 Example:
 
 ```toml
 # Nginx UI Template Start
-name = "Hotlink Protection"
+name = "Reverse Proxy"
 author = "@0xJacky"
-description = { en = "Hotlink Protection Config Template", zh_CN = "防盗链配置模板"}
+description = { en = "Reverse Proxy Config", zh_CN = "反向代理配置"}
 
-[variables.NoneReferer]
+[variables.enableWebSocket]
 type = "boolean"
-name = { en = "Allow Referer is None", zh_CN = "允许空 Referer"}
-value = false
+name = { en = "Enable WebSocket", zh_CN = "启用 WebSocket"}
+value = true
 
-[variables.AllowReferers]
+[variables.clientMaxBodySize]
 type = "string"
-name = { en = "Allow Referers", zh_CN = "允许的 Referers"}
-value = ""
+name = { en = "Client Max Body Size", zh_CN = "客户端最大请求内容大小"}
+value = "1000m"
+
+[variables.scheme]
+type = "select"
+name = { en = "Scheme", zh_CN = "协议"}
+value = "http"
+mask = { http = { en = "HTTP" }, https = { en = "HTTPS" } }
+
+[variables.host]
+type = "string"
+name = { en = "Host", zh_CN = "主机"}
+value = "127.0.0.1"
+
+[variables.port]
+type = "string"
+name = { en = "Port", zh_CN = "端口"}
+value = 9000
 # Nginx UI Template End
 ```
 
@@ -83,29 +140,45 @@ When you click the "View" button, a dialog will appear, as shown below.
 
 <img src="/assets/nginx-ui-template/en/config-ui.png" width="350px" title="Config Modal" />
 
-The input boxes and switches in the interface correspond to the variable types `boolean` and `string`.
+The following table shows the relationship between the variable type and the UI element:
+
+| Variable Type | UI Element |
+|:-------------:|:----------:|
+| `boolean`     | switcher   |
+| `string`      | input      |
+| `select`      | select     |
 
 ## Nginx Configuration
 The Nginx configuration should be provided after the file header. This part will be parsed using the Go `text/template` library. This library provides powerful template generation capabilities, including conditional judgment, looping, and complex text processing, etc.
 For more information, please check [Go Documentation](https://pkg.go.dev/text/template).
 
-The variables defined in the header can be used in this part, such as `.NoneReferer` and `.AllowReferers`.
+The variables defined in the header can be used in this part, such as `.scheme`, `.host` and `.port`.
 Please note that you need to define the variables in the header in advance before using them in this part.
 
 Here is an example:
 
 ```nginx configuration
-location ~ .*\.(jpg|png|js|css)$ {
-    valid_referers {{- if .NoneReferer}} none {{- end}} blocked server_names {{if .AllowReferers}}{{.AllowReferers}}{{- end}};
-    if ($invalid_referer) {
-        return 403;
-    }
-}
+location / {
+        {{ 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;
+        proxy_set_header Forwarded $proxy_add_forwarded;
+
+        proxy_pass {{ .scheme }}://{{ .host }}:{{ .port }}/;
+ }
 ```
 
-When users input variable values in the app input boxes, the system will automatically generate new configuration content, as shown below:
-
-<img src="/assets/nginx-ui-template/en/config-ui-after-input.png" width="350px" title="Config Modal" />
+When users change the form, the system will automatically generate new configuration content based on the template and the input of user.
 
 In addition to the variables defined in the template header, we also provide macro-defined variables, as shown in the table below:
 

BIN
docs/public/assets/nginx-ui-template/en/config-ui-after-input.png


BIN
docs/public/assets/nginx-ui-template/en/config-ui.png


BIN
docs/public/assets/nginx-ui-template/zh_CN/config-ui-after-input.png


BIN
docs/public/assets/nginx-ui-template/zh_CN/config-ui.png


BIN
docs/public/assets/nginx-ui-template/zh_TW/config-ui-after-input.png


BIN
docs/public/assets/nginx-ui-template/zh_TW/config-ui.png


+ 114 - 39
docs/zh_CN/guide/nginx-ui-template.md

@@ -12,31 +12,71 @@ Nginx UI Template 提供了一种开箱即用的配置模板机制。在 NgxConf
 
 Nginx UI Template 文件由两部分组成:文件头部以及具体的 Nginx 配置。
 
-以下是一个关于防盗链的配置模板,我们将以这个模板为基础为您介绍 Nginx UI Template 的文件格式及相关语法。
+以下是一个关于反向代理的配置模板,我们将以这个模板为基础为您介绍 Nginx UI Template 的文件格式及相关语法。
 
 ```nginx configuration
 # Nginx UI Template Start
-name = "Hotlink Protection"
+name = "Reverse Proxy"
 author = "@0xJacky"
-description = { en = "Hotlink Protection Config Template", zh_CN = "防盗链配置模板"}
+description = { en = "Reverse Proxy Config", zh_CN = "反向代理配置"}
 
-[variables.NoneReferer]
+[variables.enableWebSocket]
 type = "boolean"
-name = { en = "Allow Referer is None", zh_CN = "允许空 Referer"}
-value = false
+name = { en = "Enable WebSocket", zh_CN = "启用 WebSocket"}
+value = true
 
-[variables.AllowReferers]
+[variables.clientMaxBodySize]
 type = "string"
-name = { en = "Allow Referers", zh_CN = "允许的 Referers"}
-value = ""
+name = { en = "Client Max Body Size", zh_CN = "客户端最大请求内容大小"}
+value = "1000m"
+
+[variables.scheme]
+type = "select"
+name = { en = "Scheme", zh_CN = "协议"}
+value = "http"
+mask = { http = { en = "HTTP" }, https = { en = "HTTPS" } }
+
+[variables.host]
+type = "string"
+name = { en = "Host", zh_CN = "主机"}
+value = "127.0.0.1"
+
+[variables.port]
+type = "string"
+name = { en = "Port", zh_CN = "端口"}
+value = 9000
 # Nginx UI Template End
 
-location ~ .*\.(jpg|png|js|css)$ {
-    valid_referers {{- if .NoneReferer}} none {{- end}} blocked server_names {{if .AllowReferers}}{{.AllowReferers}}{{- end}};
-    if ($invalid_referer) {
-        return 403;
-    }
+# Nginx UI Custom Start
+{{- if .enableWebSocket }}
+map $http_upgrade $connection_upgrade {
+    default upgrade;
+    '' close;
 }
+{{- end }}
+# Nginx UI Custom End
+
+if ($host != $server_name) {
+    return 404;
+}
+
+location / {
+        {{ 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;
+
+        proxy_pass {{ .scheme }}://{{ .host }}:{{ .port }}/;
+ }
 ```
 
 ## 文件头部
@@ -45,32 +85,49 @@ location ~ .*\.(jpg|png|js|css)$ {
 
 文件头部包含以下字段:
 
-|           字段           |               描述               |             类型              | 必要 |
-|:----------------------:|:------------------------------:|:---------------------------:|:--:|
-|         `name`         |             配置的名称              |           string            | 是  |
-|        `author`        |               作者               |           string            | 是  |
-|     `description`      |    描述,使用 toml 格式的字典来实现多语言描述    |           toml 字典           | 是  |
-| `variables.变量名称.type`  | 变量类型,目前支持 `boolean` 和 `string` |   string (boolean/string)   | 否  |
-| `variables.变量名称.name`  | 变量显示的名称,是一个 toml 格式的字典,用于支持多语言 |           toml 字典           | 否  |
-| `variables.变量名称.value` |             变量的默认值             | boolean/string (根据 type 定义) | 否  |
+|           字段           |                    描述                    |             类型              | 必要 |
+|:----------------------:|:----------------------------------------:|:---------------------------:|:--:|
+|         `name`         |                  配置的名称                   |           string            | 是  |
+|        `author`        |                    作者                    |           string            | 是  |
+|     `description`      |         描述,使用 toml 格式的字典来实现多语言描述         |           toml 字典           | 是  |
+| `variables.变量名称.type`  | 变量类型,目前支持 `boolean`, `string` 和 `select` |           string            | 是  |
+| `variables.变量名称.name`  |      变量显示的名称,是一个 toml 格式的字典,用于支持多语言      |           toml 字典           | 是  |
+| `variables.变量名称.value` |                  变量的默认值                  | boolean/string (根据 type 定义) | 否  |
+| `variables.变量名称.mask`  |                  选择框的选项                  |           toml 字典           | 否  |
 
 示例如下:
 
 ```toml
 # Nginx UI Template Start
-name = "Hotlink Protection"
+name = "Reverse Proxy"
 author = "@0xJacky"
-description = { en = "Hotlink Protection Config Template", zh_CN = "防盗链配置模板"}
+description = { en = "Reverse Proxy Config", zh_CN = "反向代理配置"}
 
-[variables.NoneReferer]
+[variables.enableWebSocket]
 type = "boolean"
-name = { en = "Allow Referer is None", zh_CN = "允许空 Referer"}
-value = false
+name = { en = "Enable WebSocket", zh_CN = "启用 WebSocket"}
+value = true
 
-[variables.AllowReferers]
+[variables.clientMaxBodySize]
 type = "string"
-name = { en = "Allow Referers", zh_CN = "允许的 Referers"}
-value = ""
+name = { en = "Client Max Body Size", zh_CN = "客户端最大请求内容大小"}
+value = "1000m"
+
+[variables.scheme]
+type = "select"
+name = { en = "Scheme", zh_CN = "协议"}
+value = "http"
+mask = { http = { en = "HTTP" }, https = { en = "HTTPS" } }
+
+[variables.host]
+type = "string"
+name = { en = "Host", zh_CN = "主机"}
+value = "127.0.0.1"
+
+[variables.port]
+type = "string"
+name = { en = "Port", zh_CN = "端口"}
+value = 9000
 # Nginx UI Template End
 ```
 
@@ -82,7 +139,14 @@ value = ""
 
 <img src="/assets/nginx-ui-template/zh_CN/config-ui.png" width="350px" title="配置 Modal" />
 
-界面中的输入框和开关对应着变量的类型 `boolean` 和 `string`。
+下表展示了变量类型与用户界面元素的关系:
+
+|    类型     | 用户界面元素 |
+|:---------:|:------:|
+| `boolean` |   开关   |
+| `string`  |  输入框   |
+| `select`  |  选择框   |
+
 
 ## Nginx 配置
 Nginx 配置应该在文件头部之后提供,这部分将使用 Go 的 `text/template` 库进行解析。这个库提供了强大的模板生成能力,包括条件判断、循环以及复杂的文本处理等。
@@ -93,16 +157,27 @@ Nginx 配置应该在文件头部之后提供,这部分将使用 Go 的 `text/
 示例如下:
 
 ```nginx configuration
-location ~ .*\.(jpg|png|js|css)$ {
-    valid_referers {{- if .NoneReferer}} none {{- end}} blocked server_names {{if .AllowReferers}}{{.AllowReferers}}{{- end}};
-    if ($invalid_referer) {
-        return 403;
-    }
-}
+location / {
+        {{ 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;
+        proxy_set_header Forwarded $proxy_add_forwarded;
+
+        proxy_pass {{ .scheme }}://{{ .host }}:{{ .port }}/;
+ }
 ```
 
-当用户在前端的输入框中输入变量的值后,系统将会自动生成新的配置内容,效果如下:
-<img src="/assets/nginx-ui-template/zh_CN/config-ui-after-input.png" width="350px" title="配置 Modal" />
+当用户修改前端的表单后,系统将会根据用户的输入和配置模板自动生成新的配置内容。
 
 除了模板头部定义的变量,我们还提供了宏定义的变量,如下表所示:
 

+ 189 - 0
docs/zh_TW/guide/nginx-ui-template.md

@@ -0,0 +1,189 @@
+# 配置模板
+
+Nginx UI Template 提供了一種開箱即用的配置模板機制。在 NgxConfigEditor 中,我們設計了一個可視化界面,使使用者能夠方便地將模板中的配置插入到當前的配置文件中。
+在本指南中,我們將介紹這種配置模板的文件格式和語法規則。
+配置模板文件存儲在 `template/block` 目錄中,我們歡迎並期待您通過提交 [PR](https://github.com/0xJacky/nginx-ui/pulls) 的形式分享您編寫的配置模板。
+
+::: tip
+請注意,每次修改或新增配置文件後,需要重新編譯後端以生效。
+:::
+
+## 文件格式
+
+Nginx UI Template 文件由兩部分組成:文件頭部以及具體的 Nginx 配置。
+
+以下是一個關於反向代理的配置模板,我們將以此模板為基礎向您介紹 Nginx UI Template 的文件格式及相關語法。
+
+```nginx configuration
+# Nginx UI Template Start
+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 = "Client Max Body Size", zh_CN = "客戶端最大請求內容大小"}
+value = "1000m"
+
+[variables.scheme]
+type = "select"
+name = { en = "Scheme", zh_CN = "協議"}
+value = "http"
+mask = { http = { en = "HTTP" }, https = { en = "HTTPS" } }
+
+[variables.host]
+type = "string"
+name = { en = "Host", zh_CN = "主機"}
+value = "127.0.0.1"
+
+[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
+
+if ($host != $server_name) {
+    return 404;
+}
+
+location / {
+        {{ 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;
+
+        proxy_pass {{ .scheme }}://{{ .host }}:{{ .port }}/;
+ }
+```
+
+## 文件頭部
+
+文件頭部應包含在 `# Nginx UI Template Start` 和 `# Nginx UI Template End` 之間,並遵循 toml 語法。
+
+文件頭部包含以下欄位:
+
+|           欄位           |                    描述                    |             類型              | 必要 |
+|:----------------------:|:----------------------------------------:|:---------------------------:|:--:|
+|         `name`         |                  配置的名稱                   |           string            | 是  |
+|        `author`        |                    作者                    |           string            | 是  |
+|     `description`      |         描述,使用 toml 格式的字典來實現多語言描述         |           toml 字典           | 是  |
+| `variables.變量名稱.type`  | 變量類型,目前支持 `boolean`, `string` 和 `select` |           string            | 是  |
+| `variables.變量名稱.name`  |      變量顯示的名稱,是一個 toml 格式的字典,用於支持多語言      |           toml 字典           | 是  |
+| `variables.變量名稱.value` |                  變量的默認值                  | boolean/string (根據 type 定義) | 否  |
+| `variables.變量名稱.mask`  |                  選擇框的選項                  |           toml 字典           | 否  |
+
+示例如下:
+
+```toml
+# Nginx UI Template Start
+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 = "Client Max Body Size", zh_CN = "客戶端最大請求內容大小"}
+value = "1000m"
+
+[variables.scheme]
+type = "select"
+name = { en = "Scheme", zh_CN = "協議"}
+value = "http"
+mask = { http = { en = "HTTP" }, https = { en = "HTTPS" } }
+
+[variables.host]
+type = "string"
+name = { en = "Host", zh_CN = "主機"}
+value = "127.0.0.1"
+
+[variables.port]
+type = "string"
+name = { en = "Port", zh_CN = "端口"}
+value = 9000
+# Nginx UI Template End
+```
+
+其中,名稱、作者及描述將會以摘要的形式在配置列表中顯示。
+
+![配置列表](/assets/nginx-ui-template/zh_TW/config-template-list.png)
+
+當您點擊「查看」按鈕,界面會顯示一個對話框,如下圖所示。
+
+<img src="/assets/nginx-ui-template/zh_TW/config-ui.png" width="350px" title="配置 Modal" />
+
+下表展示了變量類型與使用者界面元素的關係:
+
+|    類型     | 使用者界面元素 |
+|:---------:|:------:|
+| `boolean` |   開關   |
+| `string`  |  輸入框   |
+| `select`  |  選擇框   |
+
+
+## Nginx 配置
+Nginx 配置應該在文件頭部之後提供,這部分將使用 Go 的 `text/template` 庫進行解析。這個庫提供了強大的模板生成能力,包括條件判斷、循環以及複雜的文本處理等。
+具體語法可以參考 [Go 文件](https://pkg.go.dev/text/template)。
+
+在頭部中定義的變量可以在這部分中使用,如 `.NoneReferer` 和 `.AllowReferers`。請注意,需要預先在頭部定義變量,才能在這部分中使用。
+
+示例如下:
+
+```nginx configuration
+location / {
+        {{ 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;
+        proxy_set_header Forwarded $proxy_add_forwarded;
+
+        proxy_pass {{ .scheme }}://{{ .host }}:{{ .port }}/;
+ }
+```
+
+當使用者修改前端的表單後,系統將會根據使用者的輸入和配置模板自動生成新的配置內容。
+
+除了模板頭部定義的變量,我們還提供了宏定義的變量,如下表所示:
+
+|    變量名     |           描述            |
+|:----------:|:-----------------------:|
+|  HTTPPORT  |     Nginx UI 監聽的端口      |
+| HTTP01PORT | 用於 HTTP01 Challenge 的端口 |
+
+上述變量可以直接在配置部分使用,無需在頭部定義。

+ 8 - 9
internal/template/template.go

@@ -19,9 +19,10 @@ import (
 )
 
 type Variable struct {
-	Type  string            `json:"type"`
-	Name  map[string]string `json:"name"`
-	Value interface{}       `json:"value"`
+	Type  string                       `json:"type"` // string, bool, select
+	Name  map[string]string            `json:"name"`
+	Value interface{}                  `json:"value"`
+	Mask  map[string]map[string]string `json:"mask,omitempty"`
 }
 
 type ConfigInfoItem struct {
@@ -48,22 +49,22 @@ func GetTemplateInfo(path, name string) (configListItem ConfigInfoItem) {
 	}(file)
 
 	r := bufio.NewReader(file)
-	bytes, _, err := r.ReadLine()
+	lineBytes, _, err := r.ReadLine()
 	if err == io.EOF {
 		return
 	}
-	line := strings.TrimSpace(string(bytes))
+	line := strings.TrimSpace(string(lineBytes))
 
 	if line != "# Nginx UI Template Start" {
 		return
 	}
 	var content string
 	for {
-		bytes, _, err = r.ReadLine()
+		lineBytes, _, err = r.ReadLine()
 		if err == io.EOF {
 			break
 		}
-		line = strings.TrimSpace(string(bytes))
+		line = strings.TrimSpace(string(lineBytes))
 		if line == "# Nginx UI Template End" {
 			break
 		}
@@ -123,7 +124,6 @@ func ParseTemplate(path, name string, bindData map[string]Variable) (c ConfigDet
 	}
 
 	t, err := template.New(name).Parse(custom)
-
 	if err != nil {
 		err = errors.Wrap(err, "error parse template.custom")
 		return
@@ -147,7 +147,6 @@ func ParseTemplate(path, name string, bindData map[string]Variable) (c ConfigDet
 	content = templatePart[1]
 
 	t, err = template.New(name).Parse(content)
-
 	if err != nil {
 		err = errors.Wrap(err, "error parse template")
 		return

+ 12 - 2
template/block/reverse_proxy.conf

@@ -13,13 +13,23 @@ type = "string"
 name = { en = "Client Max Body Size", zh_CN = "客户端最大请求内容大小"}
 value = "1000m"
 
+[variables.scheme]
+type = "select"
+name = { en = "Scheme", zh_CN = "协议"}
+value = "http"
+mask = { http = { en = "HTTP" }, https = { en = "HTTPS" } }
+
+[variables.host]
+type = "string"
+name = { en = "Host", zh_CN = "主机"}
+value = "127.0.0.1"
+
 [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 {
@@ -67,5 +77,5 @@ location / {
         proxy_set_header X-Forwarded-Proto $scheme;
         proxy_set_header Forwarded $proxy_add_forwarded;
 
-        proxy_pass http://127.0.0.1:{{ .port }}/;
+        proxy_pass {{ .scheme }}://{{ .host }}:{{ .port }}/;
  }