فهرست منبع

docs: added docs of the syntax of config template

0xJacky 1 سال پیش
والد
کامیت
d3184ff8d0

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

@@ -26,6 +26,7 @@ export const enConfig: LocaleSpecificConfig<DefaultTheme.Config> = {
                     items: [
                         {text: 'Build', link: '/guide/build'},
                         {text: 'Project Structure', link: '/guide/project-structure'},
+                        {text: 'Config Template', link: '/guide/nginx-ui-template'},
                         {text: 'Contributing', link: '/guide/contributing'}
                     ]
                 },

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

@@ -31,6 +31,7 @@ export const zhCNConfig: LocaleSpecificConfig<DefaultTheme.Config> = {
                     items: [
                         {text: '构建', link: '/zh_CN/guide/build'},
                         {text: '项目结构', link: '/zh_CN/guide/project-structure'},
+                        {text: '配置模板', link: '/zh_CN/guide/nginx-ui-template'},
                         {text: '贡献代码', link: '/zh_CN/guide/contributing'}
                     ]
                 },

+ 13 - 0
docs/.vitepress/theme/styles/custom.less

@@ -121,3 +121,16 @@
 .VPNavBar {
     white-space: nowrap;
 }
+
+img {
+    margin: 0 auto;
+}
+
+.vp-doc table {
+    display: table;
+    margin: 10px auto;
+}
+
+.VPImage .logo {
+    margin-left: 0;
+}

+ 117 - 0
docs/guide/nginx-ui-template.md

@@ -0,0 +1,117 @@
+# Config Template
+
+Nginx UI Template provides out-of-the-box configuration templates for users. In `NgxConfigEditor`, we offer a UI where users can quickly insert configurations from the template into the current configuration file.
+In this document, we will describe the file format and syntax of it.
+
+The configuration templates are stored in `template/block`, and we welcome you to share your own configuration templates by open a [PR](https://github.com/0xJacky/nginx-ui/pulls).
+
+::: tip
+Please note, you need to recompile the backend after modifying or adding new configuration files.
+:::
+
+## File Format
+
+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.
+
+```nginx configuration
+# Nginx UI Template Start
+name = "Hotlink Protection"
+author = "@0xJacky"
+description = { en = "Hotlink Protection Config Template", zh_CN = "防盗链配置模板"}
+
+[variables.NoneReferer]
+type = "boolean"
+name = { en = "Allow Referer is None", zh_CN = "允许空 Referer"}
+value = false
+
+[variables.AllowReferers]
+type = "string"
+name = { en = "Allow Referers", zh_CN = "允许的 Referers"}
+value = ""
+# 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;
+    }
+}
+```
+
+## File Header
+
+The file header should be placed between `# Nginx UI Template Start` and `# Nginx UI Template End`, and should follow the toml syntax.
+
+The file header includes the following fields:
+
+|             Field              |                              Description                              |                     Type                      | Required |
+|:------------------------------:|:---------------------------------------------------------------------:|:---------------------------------------------:|:--------:|
+|             `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.value` |                     Default value of the variable                     | boolean/string (according to type definition) |    No    |
+
+示例如下:
+
+```toml
+# Nginx UI Template Start
+name = "Hotlink Protection"
+author = "@0xJacky"
+description = { en = "Hotlink Protection Config Template", zh_CN = "防盗链配置模板"}
+
+[variables.NoneReferer]
+type = "boolean"
+name = { en = "Allow Referer is None", zh_CN = "允许空 Referer"}
+value = false
+
+[variables.AllowReferers]
+type = "string"
+name = { en = "Allow Referers", zh_CN = "允许的 Referers"}
+value = ""
+# Nginx UI Template End
+```
+
+The name, author, and description will be displayed in the configuration list as a summary.
+
+![Config template list](/assets/nginx-ui-template/zh_CN/config-template-list.png)
+
+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`.
+
+## 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`.
+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;
+    }
+}
+```
+
+When users input variable values in the frontend 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" />
+
+In addition to the variables defined in the template header, we also provide macro-defined variables, as shown in the table below:
+
+| Variable Name |        Description        |
+|:-------------:|:-------------------------:|
+|   HTTPPORT    |  Nginx UI listening port  |
+|  HTTP01PORT   | Port for HTTP01 Challenge |
+
+The variables above can be used directly in the configuration part without definition in the header.

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


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-template-list.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-template-list.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


+ 201 - 134
docs/yarn.lock

@@ -179,7 +179,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@babel/parser@npm:^7.16.4":
+"@babel/parser@npm:^7.20.15, @babel/parser@npm:^7.21.3":
   version: 7.21.8
   resolution: "@babel/parser@npm:7.21.8"
   bin:
@@ -199,30 +199,30 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@docsearch/css@npm:3.3.4, @docsearch/css@npm:^3.3.4":
-  version: 3.3.4
-  resolution: "@docsearch/css@npm:3.3.4"
-  checksum: 56e3ae677423fa4cf508ffb964d0616862a4af22affad308f47edf5c1ad097a2b21187c53d240f83463c4e7add3cd60e3630022a68e2089bb3066bfbaded64a0
+"@docsearch/css@npm:3.3.5, @docsearch/css@npm:^3.3.5":
+  version: 3.3.5
+  resolution: "@docsearch/css@npm:3.3.5"
+  checksum: 4ed57fe5014d80d516095c5d1d2aa6cf809c3b0ea730a905651d45cd1492dcc8703b2d18ca013e45eb60b1b05676b857117fa579e784e678f11026bbee6aad3d
   languageName: node
   linkType: hard
 
-"@docsearch/js@npm:^3.3.4":
-  version: 3.3.4
-  resolution: "@docsearch/js@npm:3.3.4"
+"@docsearch/js@npm:^3.3.5":
+  version: 3.3.5
+  resolution: "@docsearch/js@npm:3.3.5"
   dependencies:
-    "@docsearch/react": 3.3.4
+    "@docsearch/react": 3.3.5
     preact: ^10.0.0
-  checksum: 4a865e6fede00b5d7d957d1e640d0114235b0217cfb21d6d6de02ede11bc6081ddf82fb5a96284bc1f47642eea35211a7ba0cf7541e8d356913c56f8b1408cf5
+  checksum: 95950b46094c28acdc3a613ac25a5e4fd71175a4cdc33a8f97190adc4864c4db1b9a3604593db07716cf2f1ae3ec23eed5014490a79df12af0538522df9689a6
   languageName: node
   linkType: hard
 
-"@docsearch/react@npm:3.3.4":
-  version: 3.3.4
-  resolution: "@docsearch/react@npm:3.3.4"
+"@docsearch/react@npm:3.3.5":
+  version: 3.3.5
+  resolution: "@docsearch/react@npm:3.3.5"
   dependencies:
     "@algolia/autocomplete-core": 1.8.2
     "@algolia/autocomplete-preset-algolia": 1.8.2
-    "@docsearch/css": 3.3.4
+    "@docsearch/css": 3.3.5
     algoliasearch: ^4.0.0
   peerDependencies:
     "@types/react": ">= 16.8.0 < 19.0.0"
@@ -235,7 +235,7 @@ __metadata:
       optional: true
     react-dom:
       optional: true
-  checksum: 50f122f08c543711fffe8ba3b507311a01defef6db5d47401bd2b5c7759512357fa26d2a88a68b50916b9084fd922f7340ad03e479b4d60ac2e22b4a198dc06d
+  checksum: 9786fa969a273c5d427b79b8470fd565bcb78b90c9bd6d7ddb42ccdd0f2244790f8d51d56b7f4ff6fc0f41ce67fadd99925272eb5bb4f5bca3b55960398bd956
   languageName: node
   linkType: hard
 
@@ -400,6 +400,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@jridgewell/sourcemap-codec@npm:^1.4.13":
+  version: 1.4.15
+  resolution: "@jridgewell/sourcemap-codec@npm:1.4.15"
+  checksum: b881c7e503db3fc7f3c1f35a1dd2655a188cc51a3612d76efc8a6eb74728bef5606e6758ee77423e564092b4a518aba569bbb21c9bac5ab7a35b0c6ae7e344c8
+  languageName: node
+  linkType: hard
+
 "@npmcli/fs@npm:^2.1.0":
   version: 2.1.2
   resolution: "@npmcli/fs@npm:2.1.2"
@@ -441,63 +448,63 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@vitejs/plugin-vue@npm:^4.2.1":
-  version: 4.2.1
-  resolution: "@vitejs/plugin-vue@npm:4.2.1"
+"@vitejs/plugin-vue@npm:^4.2.3":
+  version: 4.2.3
+  resolution: "@vitejs/plugin-vue@npm:4.2.3"
   peerDependencies:
     vite: ^4.0.0
     vue: ^3.2.25
-  checksum: a7266a45378d5fd7813699cf97dfac0e313dc63bd245a1c932848b2b5dc58aede922231d5a534baeeb7e2842c7172f17194a4833bd6a7a6ed61dc0a31d60c7c9
+  checksum: 1c70c1cd18f6ba3ed6cdf1391a0d441dd8e9a89c728f7eb20d74c84e75fef1fdc651836cce9bf59a8a48e5b2caebf6ca60a908fdd8527a476a750afd2b458592
   languageName: node
   linkType: hard
 
-"@vue/compiler-core@npm:3.2.47":
-  version: 3.2.47
-  resolution: "@vue/compiler-core@npm:3.2.47"
+"@vue/compiler-core@npm:3.3.4":
+  version: 3.3.4
+  resolution: "@vue/compiler-core@npm:3.3.4"
   dependencies:
-    "@babel/parser": ^7.16.4
-    "@vue/shared": 3.2.47
+    "@babel/parser": ^7.21.3
+    "@vue/shared": 3.3.4
     estree-walker: ^2.0.2
-    source-map: ^0.6.1
-  checksum: 9ccc2a0b897b59eea56ca4f92ed29c14cd1184f68532edf5fb0fe5cb2833bcf9e4836029effb6eb9a7c872e9e0350fafdcd96ff00c0b5b79e17ded0c068b5f84
+    source-map-js: ^1.0.2
+  checksum: 5437942ea6575b316c9cd84f4f128a44939713da8b6958060e152c599e6d771d5db056c398d7574ee706ff8092e0d99ac4f14e7eef8712a8dd923d2323201b9e
   languageName: node
   linkType: hard
 
-"@vue/compiler-dom@npm:3.2.47":
-  version: 3.2.47
-  resolution: "@vue/compiler-dom@npm:3.2.47"
+"@vue/compiler-dom@npm:3.3.4":
+  version: 3.3.4
+  resolution: "@vue/compiler-dom@npm:3.3.4"
   dependencies:
-    "@vue/compiler-core": 3.2.47
-    "@vue/shared": 3.2.47
-  checksum: 1eced735f865e6df0c2d7fa041f9f27996ff4c0d4baf5fad0f67e65e623215f4394c49bba337b78427c6e71f2cc2db12b19ec6b865b4c057c0a15ccedeb20752
+    "@vue/compiler-core": 3.3.4
+    "@vue/shared": 3.3.4
+  checksum: 1c2ac0c89de8eef7be1c568d57504e6245adaaec40c2c4d9717bc231ca10bf682d918a3b358d24c786eeaf8e0d7eb8a65f57d9044775a304783fde1d069a1896
   languageName: node
   linkType: hard
 
-"@vue/compiler-sfc@npm:3.2.47":
-  version: 3.2.47
-  resolution: "@vue/compiler-sfc@npm:3.2.47"
-  dependencies:
-    "@babel/parser": ^7.16.4
-    "@vue/compiler-core": 3.2.47
-    "@vue/compiler-dom": 3.2.47
-    "@vue/compiler-ssr": 3.2.47
-    "@vue/reactivity-transform": 3.2.47
-    "@vue/shared": 3.2.47
+"@vue/compiler-sfc@npm:3.3.4":
+  version: 3.3.4
+  resolution: "@vue/compiler-sfc@npm:3.3.4"
+  dependencies:
+    "@babel/parser": ^7.20.15
+    "@vue/compiler-core": 3.3.4
+    "@vue/compiler-dom": 3.3.4
+    "@vue/compiler-ssr": 3.3.4
+    "@vue/reactivity-transform": 3.3.4
+    "@vue/shared": 3.3.4
     estree-walker: ^2.0.2
-    magic-string: ^0.25.7
+    magic-string: ^0.30.0
     postcss: ^8.1.10
-    source-map: ^0.6.1
-  checksum: 4588a513310b9319a00adfdbe789cfe60d5ec19c51e8f2098152b9e81f54be170e16c40463f6b5e4c7ab79796fc31e2de93587a9dd1af136023fa03712b62e68
+    source-map-js: ^1.0.2
+  checksum: 0a0adfdd3e812f528e25e4b3bbf14b2296b719a8aac609eca42035295527cc253b918a552dc15218e917efef26b7ca94054dc8784a1a18c06c3d4bb4d18ab8b9
   languageName: node
   linkType: hard
 
-"@vue/compiler-ssr@npm:3.2.47":
-  version: 3.2.47
-  resolution: "@vue/compiler-ssr@npm:3.2.47"
+"@vue/compiler-ssr@npm:3.3.4":
+  version: 3.3.4
+  resolution: "@vue/compiler-ssr@npm:3.3.4"
   dependencies:
-    "@vue/compiler-dom": 3.2.47
-    "@vue/shared": 3.2.47
-  checksum: 91bc6e46744d5405713c08d8e576971aa6d13a0cde84ec592d3221bf6ee228e49ce12233af8c18dc39723455b420df2951f3616ceb99758eb432485475fa7bc2
+    "@vue/compiler-dom": 3.3.4
+    "@vue/shared": 3.3.4
+  checksum: 5d1875d55ea864080dd90e5d81a29f93308e312faf00163db5b391b38c2fe799fd3eb58955823dc632f2f8bdd271a4534cc0020646b7f82717be1a8d30dc16e7
   languageName: node
   linkType: hard
 
@@ -508,69 +515,69 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@vue/reactivity-transform@npm:3.2.47":
-  version: 3.2.47
-  resolution: "@vue/reactivity-transform@npm:3.2.47"
+"@vue/reactivity-transform@npm:3.3.4":
+  version: 3.3.4
+  resolution: "@vue/reactivity-transform@npm:3.3.4"
   dependencies:
-    "@babel/parser": ^7.16.4
-    "@vue/compiler-core": 3.2.47
-    "@vue/shared": 3.2.47
+    "@babel/parser": ^7.20.15
+    "@vue/compiler-core": 3.3.4
+    "@vue/shared": 3.3.4
     estree-walker: ^2.0.2
-    magic-string: ^0.25.7
-  checksum: 6fe54374aa8c080c0c421e18134e84e723e2d3e53178cf084c1cd75bc8b1ffaaf07756801f3aa4e1e7ad1ba76356c28bbab4bc1b676159db8fc10f10f2cbd405
+    magic-string: ^0.30.0
+  checksum: b425e78b2084ac7037887fbe012dcad5e5963ac9714ae15a04fda1c6766ec8c53ef231de1cfdc4d3cf46bd5d84bfec8ebdccf48da4ff5ee2f4b5084e54f0a1b1
   languageName: node
   linkType: hard
 
-"@vue/reactivity@npm:3.2.47":
-  version: 3.2.47
-  resolution: "@vue/reactivity@npm:3.2.47"
+"@vue/reactivity@npm:3.3.4":
+  version: 3.3.4
+  resolution: "@vue/reactivity@npm:3.3.4"
   dependencies:
-    "@vue/shared": 3.2.47
-  checksum: bd61134e4b2f281067e78b23b34d17f1290a0b3fdb0cd7f06424570b4428c899389451a396694144b0af2fc6dbb1459c38e79151119f12c4f818f4c5004b7d16
+    "@vue/shared": 3.3.4
+  checksum: 81c3d0c587d23656a57a7a31afb51357274f6512b51baffc67cda183b2361a7e65e646029c26a8bc28587f26b65bba808dcd93cdd3bacab48d2b99d11ad0ec97
   languageName: node
   linkType: hard
 
-"@vue/runtime-core@npm:3.2.47":
-  version: 3.2.47
-  resolution: "@vue/runtime-core@npm:3.2.47"
+"@vue/runtime-core@npm:3.3.4":
+  version: 3.3.4
+  resolution: "@vue/runtime-core@npm:3.3.4"
   dependencies:
-    "@vue/reactivity": 3.2.47
-    "@vue/shared": 3.2.47
-  checksum: 56fd41368cb56ac83d88ef93ffb9e8d2cdad30adf8395351c38ac744691dfe50c0add0bd14e8aa817cd59afd2534213eb2aaabf46b03aacce9a4af7497af2e69
+    "@vue/reactivity": 3.3.4
+    "@vue/shared": 3.3.4
+  checksum: d402da51269658cba5d857d65fbe322121160bcb1a6fcf03601d5183705e92505c6e90418f491a331ca3e27628f457a6ca7158b9add25f5b0cf5cf53664b8011
   languageName: node
   linkType: hard
 
-"@vue/runtime-dom@npm:3.2.47":
-  version: 3.2.47
-  resolution: "@vue/runtime-dom@npm:3.2.47"
+"@vue/runtime-dom@npm:3.3.4":
+  version: 3.3.4
+  resolution: "@vue/runtime-dom@npm:3.3.4"
   dependencies:
-    "@vue/runtime-core": 3.2.47
-    "@vue/shared": 3.2.47
-    csstype: ^2.6.8
-  checksum: 8987d7276174120ed346c456752cdb3633c400432ae89a1a866e342c964b7ed2f9e3e09926f2b4b81fe175b1e4fb3935fe3920c113bcc71464ff39b4e50931f5
+    "@vue/runtime-core": 3.3.4
+    "@vue/shared": 3.3.4
+    csstype: ^3.1.1
+  checksum: dac9ada7f6128bcccc031fe5c25d00db94ffb7c011fcb70bada22fa4d889ff842eeb139ab9304bcc52cb5ae9030911a52cb3510b691bb190bbe5fab680b4411a
   languageName: node
   linkType: hard
 
-"@vue/server-renderer@npm:3.2.47":
-  version: 3.2.47
-  resolution: "@vue/server-renderer@npm:3.2.47"
+"@vue/server-renderer@npm:3.3.4":
+  version: 3.3.4
+  resolution: "@vue/server-renderer@npm:3.3.4"
   dependencies:
-    "@vue/compiler-ssr": 3.2.47
-    "@vue/shared": 3.2.47
+    "@vue/compiler-ssr": 3.3.4
+    "@vue/shared": 3.3.4
   peerDependencies:
-    vue: 3.2.47
-  checksum: 482fe3c9bbb0e154864259a976bac50f5a6cbbb86bc4376c5942a6108919466e57d9830c65060abff6ecb6a9ff1d5e3e4fede165f4312edf25b0088b8c0e26af
+    vue: 3.3.4
+  checksum: e8598ed1a44df70edaea0ad6786aea6443b9b3d9266249eec5690401859d72d45a1e29ba3eef20e37a95f020abd5e763088b79070ee848af436a4390a253a37a
   languageName: node
   linkType: hard
 
-"@vue/shared@npm:3.2.47":
-  version: 3.2.47
-  resolution: "@vue/shared@npm:3.2.47"
-  checksum: 0aa711dc9160fa0e476e6e94eea4e019398adf2211352d0e4a672cfb6b65b104bbd5d234807d1c091107bdc0f5d818d0f12378987eb7861d39be3aa9f6cd6e3e
+"@vue/shared@npm:3.3.4":
+  version: 3.3.4
+  resolution: "@vue/shared@npm:3.3.4"
+  checksum: 12fe53ff816bfa29ea53f89212067a86512c626b8d30149ff28b36705820f6150e1fb4e4e46897ad9eddb1d1cfc02d8941053939910eed69a905f7a5509baabe
   languageName: node
   linkType: hard
 
-"@vueuse/core@npm:^10.1.0":
+"@vueuse/core@npm:10.1.2, @vueuse/core@npm:^10.1.2":
   version: 10.1.2
   resolution: "@vueuse/core@npm:10.1.2"
   dependencies:
@@ -582,6 +589,55 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@vueuse/integrations@npm:^10.1.2":
+  version: 10.1.2
+  resolution: "@vueuse/integrations@npm:10.1.2"
+  dependencies:
+    "@vueuse/core": 10.1.2
+    "@vueuse/shared": 10.1.2
+    vue-demi: ">=0.14.0"
+  peerDependencies:
+    async-validator: "*"
+    axios: "*"
+    change-case: "*"
+    drauu: "*"
+    focus-trap: "*"
+    fuse.js: "*"
+    idb-keyval: "*"
+    jwt-decode: "*"
+    nprogress: "*"
+    qrcode: "*"
+    sortablejs: "*"
+    universal-cookie: "*"
+  peerDependenciesMeta:
+    async-validator:
+      optional: true
+    axios:
+      optional: true
+    change-case:
+      optional: true
+    drauu:
+      optional: true
+    focus-trap:
+      optional: true
+    fuse.js:
+      optional: true
+    idb-keyval:
+      optional: true
+    jwt-decode:
+      optional: true
+    nprogress:
+      optional: true
+    qrcode:
+      optional: true
+    sortablejs:
+      optional: true
+    universal-cookie:
+      optional: true
+  checksum: 6f4a1f5edaf213b2cc494fabb56ffbdee67a302b5c1062d8121e9454666f5bc8d8d7b68ccea8ad8721d6c45dfd43954a88d5633bece0d90be3938f449b8c2171
+  languageName: node
+  linkType: hard
+
 "@vueuse/metadata@npm:10.1.2":
   version: 10.1.2
   resolution: "@vueuse/metadata@npm:10.1.2"
@@ -793,10 +849,10 @@ __metadata:
   languageName: node
   linkType: hard
 
-"csstype@npm:^2.6.8":
-  version: 2.6.21
-  resolution: "csstype@npm:2.6.21"
-  checksum: 2ce8bc832375146eccdf6115a1f8565a27015b74cce197c35103b4494955e9516b246140425ad24103864076aa3e1257ac9bab25a06c8d931dd87a6428c9dccf
+"csstype@npm:^3.1.1":
+  version: 3.1.2
+  resolution: "csstype@npm:3.1.2"
+  checksum: e1a52e6c25c1314d6beef5168da704ab29c5186b877c07d822bd0806717d9a265e8493a2e35ca7e68d0f5d472d43fac1cdce70fd79fd0853dff81f3028d857b5
   languageName: node
   linkType: hard
 
@@ -960,6 +1016,15 @@ __metadata:
   languageName: node
   linkType: hard
 
+"focus-trap@npm:^7.4.2":
+  version: 7.4.3
+  resolution: "focus-trap@npm:7.4.3"
+  dependencies:
+    tabbable: ^6.1.2
+  checksum: 3426a46b6e3c487e4fc6d6ddfd53b1cb2f9ec4e64857bb502a9661bb765276b4be89172477177a1569812e2b733e541f6780da72cdead2f7f0961a6a5c430a85
+  languageName: node
+  linkType: hard
+
 "fs-minipass@npm:^2.0.0, fs-minipass@npm:^2.1.0":
   version: 2.1.0
   resolution: "fs-minipass@npm:2.1.0"
@@ -1238,12 +1303,12 @@ __metadata:
   languageName: node
   linkType: hard
 
-"magic-string@npm:^0.25.7":
-  version: 0.25.9
-  resolution: "magic-string@npm:0.25.9"
+"magic-string@npm:^0.30.0":
+  version: 0.30.0
+  resolution: "magic-string@npm:0.30.0"
   dependencies:
-    sourcemap-codec: ^1.4.8
-  checksum: 9a0e55a15c7303fc360f9572a71cffba1f61451bc92c5602b1206c9d17f492403bf96f946dfce7483e66822d6b74607262e24392e87b0ac27b786e69a40e9b1a
+    "@jridgewell/sourcemap-codec": ^1.4.13
+  checksum: 7bdf22e27334d8a393858a16f5f840af63a7c05848c000fd714da5aa5eefa09a1bc01d8469362f25cc5c4a14ec01b46557b7fff8751365522acddf21e57c488d
   languageName: node
   linkType: hard
 
@@ -1382,10 +1447,10 @@ __metadata:
   languageName: node
   linkType: hard
 
-"minisearch@npm:^6.0.1":
-  version: 6.0.1
-  resolution: "minisearch@npm:6.0.1"
-  checksum: 0ce329173720c2c75b36507a0fde4d45ef0dc80b4b41da39b2c6236416700956f61e6f80b7f0080cc94819bcd7c28358c45fb38bdc29f11ae266ada52161bf7a
+"minisearch@npm:^6.1.0":
+  version: 6.1.0
+  resolution: "minisearch@npm:6.1.0"
+  checksum: c169e9d917afc2812c2302d2291e6b12f3c6fcd8fbbbaa11ac524a0fd220a0e8e0c5fc82845a6b9e13a14de0908d9810f3c0affca200a02bf4abfa485241940f
   languageName: node
   linkType: hard
 
@@ -1737,20 +1802,13 @@ __metadata:
   languageName: node
   linkType: hard
 
-"source-map@npm:^0.6.1, source-map@npm:~0.6.0":
+"source-map@npm:~0.6.0":
   version: 0.6.1
   resolution: "source-map@npm:0.6.1"
   checksum: 59ce8640cf3f3124f64ac289012c2b8bd377c238e316fb323ea22fbfe83da07d81e000071d7242cad7a23cd91c7de98e4df8830ec3f133cb6133a5f6e9f67bc2
   languageName: node
   linkType: hard
 
-"sourcemap-codec@npm:^1.4.8":
-  version: 1.4.8
-  resolution: "sourcemap-codec@npm:1.4.8"
-  checksum: b57981c05611afef31605732b598ccf65124a9fcb03b833532659ac4d29ac0f7bfacbc0d6c5a28a03e84c7510e7e556d758d0bb57786e214660016fb94279316
-  languageName: node
-  linkType: hard
-
 "ssri@npm:^9.0.0":
   version: 9.0.1
   resolution: "ssri@npm:9.0.1"
@@ -1789,6 +1847,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"tabbable@npm:^6.1.2":
+  version: 6.1.2
+  resolution: "tabbable@npm:6.1.2"
+  checksum: 1e2d9af4f172a3793f491bab10263c26903c2be6a5c4ab723d69d1ba2054828d22a50add9ba7fc42735468871d40054d906fa4f0a9dc2fbd4feff875e84c1a89
+  languageName: node
+  linkType: hard
+
 "tar@npm:^6.1.11, tar@npm:^6.1.2":
   version: 6.1.14
   resolution: "tar@npm:6.1.14"
@@ -1842,9 +1907,9 @@ __metadata:
   languageName: node
   linkType: hard
 
-"vite@npm:^4.3.3":
-  version: 4.3.5
-  resolution: "vite@npm:4.3.5"
+"vite@npm:^4.3.8":
+  version: 4.3.8
+  resolution: "vite@npm:4.3.8"
   dependencies:
     esbuild: ^0.17.5
     fsevents: ~2.3.2
@@ -1875,28 +1940,30 @@ __metadata:
       optional: true
   bin:
     vite: bin/vite.js
-  checksum: 6b7f2189f097110846e49a3f1d463bd620dfe9e4f433b1967e5b99f0789610ff4475e85e3e71476e6fa40be25449bb6f1c45edb95c79deba6f3f173699bff948
+  checksum: 454a7c0c1bd1fd5611c9df28c62e3adbe75f48e87fc787179c5af60c4ab9a87aa0eda44be446d898851a135766d36f65f8e7d56317556aa807d30e561de369c4
   languageName: node
   linkType: hard
 
 "vitepress@npm:^1.0.0-alpha.75":
-  version: 1.0.0-alpha.75
-  resolution: "vitepress@npm:1.0.0-alpha.75"
+  version: 1.0.0-alpha.76
+  resolution: "vitepress@npm:1.0.0-alpha.76"
   dependencies:
-    "@docsearch/css": ^3.3.4
-    "@docsearch/js": ^3.3.4
-    "@vitejs/plugin-vue": ^4.2.1
+    "@docsearch/css": ^3.3.5
+    "@docsearch/js": ^3.3.5
+    "@vitejs/plugin-vue": ^4.2.3
     "@vue/devtools-api": ^6.5.0
-    "@vueuse/core": ^10.1.0
+    "@vueuse/core": ^10.1.2
+    "@vueuse/integrations": ^10.1.2
     body-scroll-lock: 4.0.0-beta.0
+    focus-trap: ^7.4.2
     mark.js: 8.11.1
-    minisearch: ^6.0.1
+    minisearch: ^6.1.0
     shiki: ^0.14.2
-    vite: ^4.3.3
-    vue: ^3.2.47
+    vite: ^4.3.8
+    vue: ^3.3.4
   bin:
     vitepress: bin/vitepress.js
-  checksum: eb146a65477ed90033ee9dd04ef18bb2188178a7f44a22b00715f325508ec78ec1cb771ca87cd2d1a05ed156b8dd214800c3b24567a9054ca2ad84b1d76d5b55
+  checksum: 6ebcd0eebde7aa6f9a1016fadaf98ac8b2007893b5aad2fe3b2c59e18a0b6ffd4f08de7017af7b53b0c05d40888d33d57c065ad4d8567c0c10a61ed5d9cd9199
   languageName: node
   linkType: hard
 
@@ -1930,16 +1997,16 @@ __metadata:
   languageName: node
   linkType: hard
 
-"vue@npm:^3.2.47":
-  version: 3.2.47
-  resolution: "vue@npm:3.2.47"
-  dependencies:
-    "@vue/compiler-dom": 3.2.47
-    "@vue/compiler-sfc": 3.2.47
-    "@vue/runtime-dom": 3.2.47
-    "@vue/server-renderer": 3.2.47
-    "@vue/shared": 3.2.47
-  checksum: 3b586f61fd2cdfdd24459a946a2831c0f164056993cc005badde4fea10d53e430f1cedf1b62d52a6992b58fe656fc5fc67b32f594a2832dfe440c6db46d6f158
+"vue@npm:^3.3.4":
+  version: 3.3.4
+  resolution: "vue@npm:3.3.4"
+  dependencies:
+    "@vue/compiler-dom": 3.3.4
+    "@vue/compiler-sfc": 3.3.4
+    "@vue/runtime-dom": 3.3.4
+    "@vue/server-renderer": 3.3.4
+    "@vue/shared": 3.3.4
+  checksum: 58b6c62a66a375ce5df460fcb7ba41b37c8637c635faf06ef472ae4197f412cf9ad83586cd8e3f66c486404fbe8550e694f90ff724a571d1ba78830791099c59
   languageName: node
   linkType: hard
 

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

@@ -0,0 +1,114 @@
+# 配置模板
+
+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 = "Hotlink Protection"
+author = "@0xJacky"
+description = { en = "Hotlink Protection Config Template", zh_CN = "防盗链配置模板"}
+
+[variables.NoneReferer]
+type = "boolean"
+name = { en = "Allow Referer is None", zh_CN = "允许空 Referer"}
+value = false
+
+[variables.AllowReferers]
+type = "string"
+name = { en = "Allow Referers", zh_CN = "允许的 Referers"}
+value = ""
+# 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 Template Start` 和 `# Nginx UI Template End` 之间,遵循 toml 语法。
+
+文件头部包含以下字段:
+
+|           字段           |               描述               |             类型              | 必要 |
+|:----------------------:|:------------------------------:|:---------------------------:|:--:|
+|         `name`         |             配置的名称              |           string            | 是  |
+|        `author`        |               作者               |           string            | 是  |
+|     `description`      |    描述,使用 toml 格式的字典来实现多语言描述    |           toml 字典           | 是  |
+| `variables.变量名称.type`  | 变量类型,目前支持 `boolean` 和 `string` |   string (boolean/string)   | 否  |
+| `variables.变量名称.name`  | 变量显示的名称,是一个 toml 格式的字典,用于支持多语言 |           toml 字典           | 否  |
+| `variables.变量名称.value` |             变量的默认值             | boolean/string (根据 type 定义) | 否  |
+
+示例如下:
+
+```toml
+# Nginx UI Template Start
+name = "Hotlink Protection"
+author = "@0xJacky"
+description = { en = "Hotlink Protection Config Template", zh_CN = "防盗链配置模板"}
+
+[variables.NoneReferer]
+type = "boolean"
+name = { en = "Allow Referer is None", zh_CN = "允许空 Referer"}
+value = false
+
+[variables.AllowReferers]
+type = "string"
+name = { en = "Allow Referers", zh_CN = "允许的 Referers"}
+value = ""
+# Nginx UI Template End
+```
+
+其中,名称、作者及描述将会以摘要的形式在配置列表中显示。
+
+![配置列表](/assets/nginx-ui-template/zh_CN/config-template-list.png)
+
+当您点击「查看」按钮,界面会显示一个对话框,如下图所示。
+
+<img src="/assets/nginx-ui-template/zh_CN/config-ui.png" width="350px" title="配置 Modal" />
+
+界面中的输入框和开关对应着变量的类型 `boolean` 和 `string`。
+
+## Nginx 配置
+Nginx 配置应该在文件头部之后提供,这部分将使用 Go 的 `text/template` 库进行解析。这个库提供了强大的模板生成能力,包括条件判断、循环以及复杂的文本处理等。
+具体语法可以参考 [Go 文档](https://pkg.go.dev/text/template)。
+
+在头部中定义的变量可以在这部分中使用,如 `.NoneReferer` 和 `.AllowReferers`。请注意,需要预先在头部定义变量,才能在这部分中使用。
+
+示例如下:
+
+```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;
+    }
+}
+```
+
+当用户在前端的输入框中输入变量的值后,系统将会自动生成新的配置内容,效果如下:
+<img src="/assets/nginx-ui-template/zh_CN/config-ui-after-input.png" width="350px" title="配置 Modal" />
+
+除了模板头部定义的变量,我们还提供了宏定义的变量,如下表所示:
+
+|    变量名     |           描述            |
+|:----------:|:-----------------------:|
+|  HTTPPORT  |     Nginx UI 监听的端口      |
+| HTTP01PORT | 用于 HTTP01 Challenge 的端口 |
+
+上述变量可以直接在配置部分使用,无需在头部定义。

+ 121 - 121
server/api/upgrade.go

@@ -1,145 +1,145 @@
 package api
 
 import (
-    "github.com/0xJacky/Nginx-UI/server/internal/logger"
-    "github.com/0xJacky/Nginx-UI/server/internal/upgrader"
-    "github.com/0xJacky/Nginx-UI/server/settings"
-    "github.com/gin-gonic/gin"
-    "github.com/gorilla/websocket"
-    "net/http"
-    "os"
+	"github.com/0xJacky/Nginx-UI/server/internal/logger"
+	"github.com/0xJacky/Nginx-UI/server/internal/upgrader"
+	"github.com/0xJacky/Nginx-UI/server/settings"
+	"github.com/gin-gonic/gin"
+	"github.com/gorilla/websocket"
+	"net/http"
+	"os"
 )
 
 func GetRelease(c *gin.Context) {
-    data, err := upgrader.GetRelease(c.Query("channel"))
-    if err != nil {
-        ErrHandler(c, err)
-        return
-    }
-    runtimeInfo, err := upgrader.GetRuntimeInfo()
-    if err != nil {
-        ErrHandler(c, err)
-        return
-    }
-    type resp struct {
-        upgrader.TRelease
-        upgrader.RuntimeInfo
-    }
-    c.JSON(http.StatusOK, resp{
-        data, runtimeInfo,
-    })
+	data, err := upgrader.GetRelease(c.Query("channel"))
+	if err != nil {
+		ErrHandler(c, err)
+		return
+	}
+	runtimeInfo, err := upgrader.GetRuntimeInfo()
+	if err != nil {
+		ErrHandler(c, err)
+		return
+	}
+	type resp struct {
+		upgrader.TRelease
+		upgrader.RuntimeInfo
+	}
+	c.JSON(http.StatusOK, resp{
+		data, runtimeInfo,
+	})
 }
 
 func GetCurrentVersion(c *gin.Context) {
-    curVer, err := upgrader.GetCurrentVersion()
-    if err != nil {
-        ErrHandler(c, err)
-        return
-    }
+	curVer, err := upgrader.GetCurrentVersion()
+	if err != nil {
+		ErrHandler(c, err)
+		return
+	}
 
-    c.JSON(http.StatusOK, curVer)
+	c.JSON(http.StatusOK, curVer)
 }
 
 func PerformCoreUpgrade(c *gin.Context) {
-    var upGrader = websocket.Upgrader{
-        CheckOrigin: func(r *http.Request) bool {
-            return true
-        },
-    }
-    // upgrade http to websocket
-    ws, err := upGrader.Upgrade(c.Writer, c.Request, nil)
-    if err != nil {
-        logger.Error(err)
-        return
-    }
-    defer ws.Close()
+	var upGrader = websocket.Upgrader{
+		CheckOrigin: func(r *http.Request) bool {
+			return true
+		},
+	}
+	// upgrade http to websocket
+	ws, err := upGrader.Upgrade(c.Writer, c.Request, nil)
+	if err != nil {
+		logger.Error(err)
+		return
+	}
+	defer ws.Close()
 
-    var control struct {
-        DryRun  bool   `json:"dry_run"`
-        Channel string `json:"channel"`
-    }
+	var control struct {
+		DryRun  bool   `json:"dry_run"`
+		Channel string `json:"channel"`
+	}
 
-    err = ws.ReadJSON(&control)
+	err = ws.ReadJSON(&control)
 
-    if err != nil {
-        logger.Error(err)
-        return
-    }
+	if err != nil {
+		logger.Error(err)
+		return
+	}
 
-    _ = ws.WriteJSON(gin.H{
-        "status":  "info",
-        "message": "Initialing core upgrader",
-    })
+	_ = ws.WriteJSON(gin.H{
+		"status":  "info",
+		"message": "Initialing core upgrader",
+	})
 
-    u, err := upgrader.NewUpgrader(control.Channel)
+	u, err := upgrader.NewUpgrader(control.Channel)
 
-    if err != nil {
-        _ = ws.WriteJSON(gin.H{
-            "status":  "error",
-            "message": "Initial core upgrader error",
-        })
-        _ = ws.WriteJSON(gin.H{
-            "status":  "error",
-            "message": err.Error(),
-        })
-        logger.Error(err)
-        return
-    }
-    _ = ws.WriteJSON(gin.H{
-        "status":  "info",
-        "message": "Downloading latest release",
-    })
-    progressChan := make(chan float64)
-    go func() {
-        for progress := range progressChan {
-            _ = ws.WriteJSON(gin.H{
-                "status":   "progress",
-                "progress": progress,
-            })
-        }
-    }()
+	if err != nil {
+		_ = ws.WriteJSON(gin.H{
+			"status":  "error",
+			"message": "Initial core upgrader error",
+		})
+		_ = ws.WriteJSON(gin.H{
+			"status":  "error",
+			"message": err.Error(),
+		})
+		logger.Error(err)
+		return
+	}
+	_ = ws.WriteJSON(gin.H{
+		"status":  "info",
+		"message": "Downloading latest release",
+	})
+	progressChan := make(chan float64)
+	go func() {
+		for progress := range progressChan {
+			_ = ws.WriteJSON(gin.H{
+				"status":   "progress",
+				"progress": progress,
+			})
+		}
+	}()
 
-    tarName, err := u.DownloadLatestRelease(progressChan)
+	tarName, err := u.DownloadLatestRelease(progressChan)
 
-    if err != nil {
-        _ = ws.WriteJSON(gin.H{
-            "status":  "error",
-            "message": "Download latest release error",
-        })
-        _ = ws.WriteJSON(gin.H{
-            "status":  "error",
-            "message": err.Error(),
-        })
-        logger.Error(err)
-        return
-    }
+	if err != nil {
+		_ = ws.WriteJSON(gin.H{
+			"status":  "error",
+			"message": "Download latest release error",
+		})
+		_ = ws.WriteJSON(gin.H{
+			"status":  "error",
+			"message": err.Error(),
+		})
+		logger.Error(err)
+		return
+	}
 
-    defer func() {
-        _ = os.Remove(tarName)
-        _ = os.Remove(tarName + ".digest")
-    }()
-    _ = ws.WriteJSON(gin.H{
-        "status":  "info",
-        "message": "Performing core upgrade",
-    })
-    // dry run
-    if control.DryRun || settings.ServerSettings.Demo {
-        return
-    }
+	defer func() {
+		_ = os.Remove(tarName)
+		_ = os.Remove(tarName + ".digest")
+	}()
+	_ = ws.WriteJSON(gin.H{
+		"status":  "info",
+		"message": "Performing core upgrade",
+	})
+	// dry run
+	if control.DryRun || settings.ServerSettings.Demo {
+		return
+	}
 
-    _ = os.Remove(u.ExPath)
-    // bye, overseer will restart nginx-ui
-    err = u.PerformCoreUpgrade(u.ExPath, tarName)
-    if err != nil {
-        _ = ws.WriteJSON(gin.H{
-            "status":  "error",
-            "message": "Perform core upgrade error",
-        })
-        _ = ws.WriteJSON(gin.H{
-            "status":  "error",
-            "message": err.Error(),
-        })
-        logger.Error(err)
-        return
-    }
+	_ = os.Remove(u.ExPath)
+	// bye, overseer will restart nginx-ui
+	err = u.PerformCoreUpgrade(u.ExPath, tarName)
+	if err != nil {
+		_ = ws.WriteJSON(gin.H{
+			"status":  "error",
+			"message": "Perform core upgrade error",
+		})
+		_ = ws.WriteJSON(gin.H{
+			"status":  "error",
+			"message": err.Error(),
+		})
+		logger.Error(err)
+		return
+	}
 }