ソースを参照

feat: add LogDirWhiteList option

Jacky 6 ヶ月 前
コミット
6d09ff5919

+ 1 - 0
.gitignore

@@ -11,3 +11,4 @@ nginx-ui
 resources/development/nginx
 app/.env
 app/.status_hash
+casdoor.pub

+ 51 - 5
api/nginx/nginx_log.go

@@ -2,9 +2,13 @@ package nginx
 
 import (
 	"encoding/json"
+	"fmt"
 	"github.com/0xJacky/Nginx-UI/api"
+	"github.com/0xJacky/Nginx-UI/internal/cache"
+	"github.com/0xJacky/Nginx-UI/internal/helper"
 	"github.com/0xJacky/Nginx-UI/internal/logger"
 	"github.com/0xJacky/Nginx-UI/internal/nginx"
+	"github.com/0xJacky/Nginx-UI/settings"
 	"github.com/gin-gonic/gin"
 	"github.com/gorilla/websocket"
 	"github.com/hpcloud/tail"
@@ -30,6 +34,7 @@ type controlStruct struct {
 type nginxLogPageResp struct {
 	Content string `json:"content"`
 	Page    int64  `json:"page"`
+	Error   string `json:"error,omitempty"`
 }
 
 func GetNginxLogPage(c *gin.Context) {
@@ -46,6 +51,9 @@ func GetNginxLogPage(c *gin.Context) {
 	logPath, err := getLogPath(&control)
 
 	if err != nil {
+		c.JSON(http.StatusInternalServerError, nginxLogPageResp{
+			Error: err.Error(),
+		})
 		logger.Error(err)
 		return
 	}
@@ -53,13 +61,17 @@ func GetNginxLogPage(c *gin.Context) {
 	logFileStat, err := os.Stat(logPath)
 
 	if err != nil {
-		c.JSON(http.StatusOK, nginxLogPageResp{})
+		c.JSON(http.StatusInternalServerError, nginxLogPageResp{
+			Error: err.Error(),
+		})
 		logger.Error(err)
 		return
 	}
 
 	if !logFileStat.Mode().IsRegular() {
-		c.JSON(http.StatusOK, nginxLogPageResp{})
+		c.JSON(http.StatusInternalServerError, nginxLogPageResp{
+			Error: "log file is not regular file",
+		})
 		logger.Error("log file is not regular file:", logPath)
 		return
 	}
@@ -67,7 +79,9 @@ func GetNginxLogPage(c *gin.Context) {
 	f, err := os.Open(logPath)
 
 	if err != nil {
-		c.JSON(http.StatusOK, nginxLogPageResp{})
+		c.JSON(http.StatusInternalServerError, nginxLogPageResp{
+			Error: err.Error(),
+		})
 		logger.Error(err)
 		return
 	}
@@ -90,7 +104,9 @@ func GetNginxLogPage(c *gin.Context) {
 	// seek
 	_, err = f.Seek(offset, io.SeekStart)
 	if err != nil && err != io.EOF {
-		c.JSON(http.StatusOK, nginxLogPageResp{})
+		c.JSON(http.StatusInternalServerError, nginxLogPageResp{
+			Error: err.Error(),
+		})
 		logger.Error(err)
 		return
 	}
@@ -98,7 +114,9 @@ func GetNginxLogPage(c *gin.Context) {
 	n, err := f.Read(buf)
 
 	if err != nil && err != io.EOF {
-		c.JSON(http.StatusOK, nginxLogPageResp{})
+		c.JSON(http.StatusInternalServerError, nginxLogPageResp{
+			Error: err.Error(),
+		})
 		logger.Error(err)
 		return
 	}
@@ -109,7 +127,30 @@ func GetNginxLogPage(c *gin.Context) {
 	})
 }
 
+// isLogPathUnderWhiteList checks if the log path is under one of the paths in LogDirWhiteList
+func isLogPathUnderWhiteList(path string) bool {
+	cacheKey := fmt.Sprintf("isLogPathUnderWhiteList:%s", path)
+	res, ok := cache.Get(cacheKey)
+	// no cache, check it
+	if !ok {
+		for _, whitePath := range settings.NginxSettings.LogDirWhiteList {
+			if helper.IsUnderDirectory(path, whitePath) {
+				cache.Set(cacheKey, true, 0)
+				return true
+			}
+		}
+		return false
+	}
+	return res.(bool)
+}
+
 func getLogPath(control *controlStruct) (logPath string, err error) {
+	if len(settings.NginxSettings.LogDirWhiteList) == 0 {
+		err = errors.New("The settings.NginxSettings.LogDirWhiteList has not been configured. " +
+			"For security reasons, please configure a whitelist of log directories. " +
+			"Please visit https://nginxui.com/guide/config-nginx.html for more information.")
+		return
+	}
 	switch control.Type {
 	case "site":
 		var config *nginx.NgxConfig
@@ -172,6 +213,11 @@ func getLogPath(control *controlStruct) (logPath string, err error) {
 		logPath = path
 	}
 
+	// check if logPath is under one of the paths in LogDirWhiteList
+	if !isLogPathUnderWhiteList(logPath) {
+		err = errors.New("The log path is not under the paths in LogDirWhiteList.")
+		return "", err
+	}
 	return
 }
 

+ 4 - 2
app/src/views/nginx_log/NginxLog.vue

@@ -59,6 +59,9 @@ function init() {
   nginx_log.page(0, control).then(r => {
     page.value = r.page - 1
     addLog(r.content)
+    openWs()
+  }).catch(e => {
+    addLog(e.error)
   })
 }
 
@@ -68,11 +71,10 @@ function clearLog() {
 
 onMounted(() => {
   init()
-  openWs()
 })
 
 onUnmounted(() => {
-  websocket.close()
+  websocket?.close()
 })
 
 watch(auto_refresh, value => {

+ 29 - 0
casdoor.pub

@@ -0,0 +1,29 @@
+-----BEGIN CERTIFICATE-----
+MIIE3TCCAsWgAwIBAgIDAeJAMA0GCSqGSIb3DQEBCwUAMCgxDjAMBgNVBAoTBWFk
+bWluMRYwFAYDVQQDEw1jZXJ0LWJ1aWx0LWluMB4XDTI0MDcyOTAzMDUzM1oXDTQ0
+MDcyOTAzMDUzM1owKDEOMAwGA1UEChMFYWRtaW4xFjAUBgNVBAMTDWNlcnQtYnVp
+bHQtaW4wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDh0c2zvM21NDNi
+xZmSQVPOtckiH/K80mHQ99e+xzdGrZugaw00tyTOMVRot+Bv1cggcJXmFcVaa9Da
+siIcIQ6jT3w7mINsrErYu4nz9tELd4BZUM6tytN+khVqo73p/NbRsnmX8ykMyrgx
+YCBknNoSxh7glLSmKcj4uQ12dYakRPr0QNnDwU7fpPfB7N4O88yXWpbqWaABwqBx
+S6+tYUp9Wx74mH8c917w5xXx8EI5eC+dJOAeVXrbzqD7OdC+uo8m7f06HAX1vRE/
+MQS0BDx8tDGtqOvdJbVxdnS6MQ2E3vmuusFseDcKVgqTF/7b76y5uNyBBux5zfpt
+G1O3bquMlQ8mSedK8BBcbI79gXYJRWazMBXvAdacaV93dF1s0EEjYXiBManAci2d
+lH8zzs4TNpE0t3adYiPbGPW1F6Q+HaV52MDVpFwG9Ld0kJmKhkvoWiSXl58Db3VN
+Ef8Jjo2xF+5o9685CQ2o9L0RalcHxMxy1+6wdKMSp7PReYpIiEgmkAhUsKePOVmr
+JwL46/4EulcXrh+ASjobmknHzdBQEK+MHapb/XWewX4mzq777gPmP8RdILTHsc1m
+/hbR8uW9iTfo9LQFvXwnIPVfX0wzFXSZzSg/zLb2tN5D7VlDenUAdCDT1zNOfz5m
+9vLYwfo5GzXIkp2py0G40vrZlv7C9wIDAQABoxAwDjAMBgNVHRMBAf8EAjAAMA0G
+CSqGSIb3DQEBCwUAA4ICAQBp1Bx+mShpumQiVb2hv0amSzAKADyYIX3Xcef68rPu
+Eb+7HSCmQf4yyI9eU1TyvQCLbjum3U3OhDWwAiRvxOwj0oO/Q+dOUEZxTjbL9UF3
+4LIrUUBMRhRgy3wK73qy3o8hAcRtQyexUW7eoFS+7L++6XQOvMkYAtLO0DQCHeKG
+loiQa5RuWbzQDdP7810DLvNF8IMA8t9KKaKGybYze9WTzRUMTDbXby8pVs8DG7JI
+zIW6neEmtsVbxufk/nthG1b83/yZxe0StL42xI7f4xgguhkfd68E4lpf/gp91EAM
+K6MbTmCqkB68c0wOSXpWYkte7EvXTTmMSKf6FnMgOtqdxqYYMknLk4ZdI7tMqS31
+rpb9XxjBgXFbB18oOSDbW64KPMjE7vuOx+o32BTHKsUWxOiDc8+0ELrbhG2Bm1Gj
+CYkx9bq5iTLDwtZZlPoA8O/T0TJzBTtC/tlEdpHSkkLoEaWsx0nT9ipRWck1Kj59
+NGJkArbrpq9Ee8tWJKqTN/pv0X8r+MxowIY2dKvwweokXb7R6k9nfXyGw8ji22Hv
+H4iibv9FEyVFQ16HPR6fIKg9yE9u0223UhJZEwohA4DylCxpmI/YSXbUmzQJwjBP
+27qvT4Y07xsdNqIbkwhb5yEQB5huivITD+SBwI5NwDfUeY6eF/BEHpRq+Uy3itx0
+SA==
+-----END CERTIFICATE-----

+ 14 - 0
docs/guide/config-nginx.md

@@ -33,6 +33,20 @@ In Nginx UI v2, we parse the output of the `nginx -V` command to get the default
 If you need to set a different path, you can use this option.
 :::
 
+### LogDirWhiteList
+
+- Type: `[]string`
+- Version:`>= v2.0.0-beta.36`
+- Example: `/var/log/nginx,/var/log/sites`
+
+This option is used to set the whitelist of directories for the Nginx logs viewer in Nginx UI.
+
+::: warning Warning
+For security reasons, you must specify the directories where the logs are stored. 
+
+Only logs within these directories can be viewed online.
+:::
+
 ## Service Monitoring and Control
 
 In this section, we will introduce configuration options in Nginx UI for monitoring and controlling Nginx services.

+ 2 - 2
docs/package.json

@@ -7,11 +7,11 @@
     "docs:preview": "vitepress preview"
   },
   "dependencies": {
-    "vitepress": "^1.3.4",
+    "vitepress": "^1.4.0",
     "vue": "^3.5.11"
   },
   "devDependencies": {
-    "@types/node": "^22.7.4",
+    "@types/node": "^22.7.5",
     "less": "^4.2.0"
   },
   "license": "AGPL-3.0",

+ 48 - 47
docs/pnpm-lock.yaml

@@ -9,15 +9,15 @@ importers:
   .:
     dependencies:
       vitepress:
-        specifier: ^1.3.4
-        version: 1.3.4(@algolia/client-search@4.24.0)(@types/node@22.7.4)(less@4.2.0)(postcss@8.4.47)(search-insights@2.13.0)
+        specifier: ^1.4.0
+        version: 1.4.0(@algolia/client-search@4.24.0)(@types/node@22.7.5)(less@4.2.0)(postcss@8.4.47)(search-insights@2.13.0)
       vue:
         specifier: ^3.5.11
         version: 3.5.11
     devDependencies:
       '@types/node':
-        specifier: ^22.7.4
-        version: 22.7.4
+        specifier: ^22.7.5
+        version: 22.7.5
       less:
         specifier: ^4.2.0
         version: 4.2.0
@@ -350,20 +350,20 @@ packages:
     cpu: [x64]
     os: [win32]
 
-  '@shikijs/core@1.21.1':
-    resolution: {integrity: sha512-scBQo4V4O4WZLEDg11e75UPmXoCMq4Ya2A16U6efi/aTiR4o7T/GMNWZs2rq1U8dEvFKGxJZxiUy+tXgmr/4vw==}
+  '@shikijs/core@1.22.0':
+    resolution: {integrity: sha512-S8sMe4q71TJAW+qG93s5VaiihujRK6rqDFqBnxqvga/3LvqHEnxqBIOPkt//IdXVtHkQWKu4nOQNk0uBGicU7Q==}
 
-  '@shikijs/engine-javascript@1.21.1':
-    resolution: {integrity: sha512-29EG4KYKlAona8yikEx8uoKbK7N2YoXUO26LS1GOIxpMMIAlQS9UFONg95lkGmIfp1rRcvCvSpYYIJ/blsQxvg==}
+  '@shikijs/engine-javascript@1.22.0':
+    resolution: {integrity: sha512-AeEtF4Gcck2dwBqCFUKYfsCq0s+eEbCEbkUuFou53NZ0sTGnJnJ/05KHQFZxpii5HMXbocV9URYVowOP2wH5kw==}
 
-  '@shikijs/engine-oniguruma@1.21.1':
-    resolution: {integrity: sha512-PvfEtXCDbQZc9ud0SC0bPiuMbul44Cv0Ky2go4SsvVkYAAKYJsMe/Hx7nxThW8yS0r+w8USa0WfOtQKsD9DU9A==}
+  '@shikijs/engine-oniguruma@1.22.0':
+    resolution: {integrity: sha512-5iBVjhu/DYs1HB0BKsRRFipRrD7rqjxlWTj4F2Pf+nQSPqc3kcyqFFeZXnBMzDf0HdqaFVvhDRAGiYNvyLP+Mw==}
 
-  '@shikijs/transformers@1.21.1':
-    resolution: {integrity: sha512-97csTb0Gv8eLbglPDhNZTuAI9eCXOujNqD4qK6H0cjFNK+NBhkRIU02RgmYHZ7yNyLary6cEzY6WmUWb+al3MQ==}
+  '@shikijs/transformers@1.22.0':
+    resolution: {integrity: sha512-k7iMOYuGQA62KwAuJOQBgH2IQb5vP8uiB3lMvAMGUgAMMurePOx3Z7oNqJdcpxqZP6I9cc7nc4DNqSKduCxmdg==}
 
-  '@shikijs/types@1.21.1':
-    resolution: {integrity: sha512-yLuTJTCHmYznerJ0nxF+f2rBKHQf2FMAd08QL/3du2xNBy/7yQ8CjuKN4Zc+Pk0vfIFzdBoxdzvEXE4JtXoR4Q==}
+  '@shikijs/types@1.22.0':
+    resolution: {integrity: sha512-Fw/Nr7FGFhlQqHfxzZY8Cwtwk5E9nKDUgeLjZgt3UuhcM3yJR9xj3ZGNravZZok8XmEZMiYkSMTPlPkULB8nww==}
 
   '@shikijs/vscode-textmate@9.3.0':
     resolution: {integrity: sha512-jn7/7ky30idSkd/O5yDBfAnVt+JJpepofP/POZ1iMOxK59cOfqIgg/Dj0eFsjOTMw+4ycJN0uhZH/Eb0bs/EUA==}
@@ -386,8 +386,8 @@ packages:
   '@types/mdurl@2.0.0':
     resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==}
 
-  '@types/node@22.7.4':
-    resolution: {integrity: sha512-y+NPi1rFzDs1NdQHHToqeiX2TIS79SWEAw9GYhkkx8bD0ChpfqC+n2j5OXOCpzfojBEBt6DnEnnG9MY0zk1XLg==}
+  '@types/node@22.7.5':
+    resolution: {integrity: sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==}
 
   '@types/unist@3.0.3':
     resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==}
@@ -691,8 +691,8 @@ packages:
     resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==}
     hasBin: true
 
-  shiki@1.21.1:
-    resolution: {integrity: sha512-jSOKRHyQJxGOW3kJflmwzHJbp/kjg6hP8LYuVbCPw5oyX+fSNNoCywvcCD3w9eHbj2rvNljt7YMa5BP5Xi+nHg==}
+  shiki@1.22.0:
+    resolution: {integrity: sha512-/t5LlhNs+UOKQCYBtl5ZsH/Vclz73GIqT2yQsCBygr8L/ppTdmpL4w3kPLoZJbMKVWtoG77Ue1feOjZfDxvMkw==}
 
   source-map-js@1.2.1:
     resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
@@ -784,8 +784,8 @@ packages:
       terser:
         optional: true
 
-  vitepress@1.3.4:
-    resolution: {integrity: sha512-I1/F6OW1xl3kW4PaIMC6snxjWgf3qfziq2aqsDoFc/Gt41WbcRv++z8zjw8qGRIJ+I4bUW7ZcKFDHHN/jkH9DQ==}
+  vitepress@1.4.0:
+    resolution: {integrity: sha512-JXCv4EsKTDyAFb6C/UjZr7nsGAzZ6mafVk2rx7rG5o8N+B/4QstIk+iEOe/9dKoU6V624UIC6g1pZ+K63rxhlw==}
     hasBin: true
     peerDependencies:
       markdown-it-mathjax3: ^4
@@ -1081,31 +1081,31 @@ snapshots:
   '@rollup/rollup-win32-x64-msvc@4.24.0':
     optional: true
 
-  '@shikijs/core@1.21.1':
+  '@shikijs/core@1.22.0':
     dependencies:
-      '@shikijs/engine-javascript': 1.21.1
-      '@shikijs/engine-oniguruma': 1.21.1
-      '@shikijs/types': 1.21.1
+      '@shikijs/engine-javascript': 1.22.0
+      '@shikijs/engine-oniguruma': 1.22.0
+      '@shikijs/types': 1.22.0
       '@shikijs/vscode-textmate': 9.3.0
       '@types/hast': 3.0.4
       hast-util-to-html: 9.0.3
 
-  '@shikijs/engine-javascript@1.21.1':
+  '@shikijs/engine-javascript@1.22.0':
     dependencies:
-      '@shikijs/types': 1.21.1
+      '@shikijs/types': 1.22.0
       '@shikijs/vscode-textmate': 9.3.0
       oniguruma-to-js: 0.4.3
 
-  '@shikijs/engine-oniguruma@1.21.1':
+  '@shikijs/engine-oniguruma@1.22.0':
     dependencies:
-      '@shikijs/types': 1.21.1
+      '@shikijs/types': 1.22.0
       '@shikijs/vscode-textmate': 9.3.0
 
-  '@shikijs/transformers@1.21.1':
+  '@shikijs/transformers@1.22.0':
     dependencies:
-      shiki: 1.21.1
+      shiki: 1.22.0
 
-  '@shikijs/types@1.21.1':
+  '@shikijs/types@1.22.0':
     dependencies:
       '@shikijs/vscode-textmate': 9.3.0
       '@types/hast': 3.0.4
@@ -1131,7 +1131,7 @@ snapshots:
 
   '@types/mdurl@2.0.0': {}
 
-  '@types/node@22.7.4':
+  '@types/node@22.7.5':
     dependencies:
       undici-types: 6.19.8
 
@@ -1141,9 +1141,9 @@ snapshots:
 
   '@ungap/structured-clone@1.2.0': {}
 
-  '@vitejs/plugin-vue@5.1.4(vite@5.4.8(@types/node@22.7.4)(less@4.2.0))(vue@3.5.11)':
+  '@vitejs/plugin-vue@5.1.4(vite@5.4.8(@types/node@22.7.5)(less@4.2.0))(vue@3.5.11)':
     dependencies:
-      vite: 5.4.8(@types/node@22.7.4)(less@4.2.0)
+      vite: 5.4.8(@types/node@22.7.5)(less@4.2.0)
       vue: 3.5.11
 
   '@vue/compiler-core@3.5.11':
@@ -1504,12 +1504,12 @@ snapshots:
   semver@5.7.2:
     optional: true
 
-  shiki@1.21.1:
+  shiki@1.22.0:
     dependencies:
-      '@shikijs/core': 1.21.1
-      '@shikijs/engine-javascript': 1.21.1
-      '@shikijs/engine-oniguruma': 1.21.1
-      '@shikijs/types': 1.21.1
+      '@shikijs/core': 1.22.0
+      '@shikijs/engine-javascript': 1.22.0
+      '@shikijs/engine-oniguruma': 1.22.0
+      '@shikijs/types': 1.22.0
       '@shikijs/vscode-textmate': 9.3.0
       '@types/hast': 3.0.4
 
@@ -1574,24 +1574,25 @@ snapshots:
       '@types/unist': 3.0.3
       vfile-message: 4.0.2
 
-  vite@5.4.8(@types/node@22.7.4)(less@4.2.0):
+  vite@5.4.8(@types/node@22.7.5)(less@4.2.0):
     dependencies:
       esbuild: 0.21.5
       postcss: 8.4.47
       rollup: 4.24.0
     optionalDependencies:
-      '@types/node': 22.7.4
+      '@types/node': 22.7.5
       fsevents: 2.3.3
       less: 4.2.0
 
-  vitepress@1.3.4(@algolia/client-search@4.24.0)(@types/node@22.7.4)(less@4.2.0)(postcss@8.4.47)(search-insights@2.13.0):
+  vitepress@1.4.0(@algolia/client-search@4.24.0)(@types/node@22.7.5)(less@4.2.0)(postcss@8.4.47)(search-insights@2.13.0):
     dependencies:
       '@docsearch/css': 3.6.2
       '@docsearch/js': 3.6.2(@algolia/client-search@4.24.0)(search-insights@2.13.0)
-      '@shikijs/core': 1.21.1
-      '@shikijs/transformers': 1.21.1
+      '@shikijs/core': 1.22.0
+      '@shikijs/transformers': 1.22.0
+      '@shikijs/types': 1.22.0
       '@types/markdown-it': 14.1.2
-      '@vitejs/plugin-vue': 5.1.4(vite@5.4.8(@types/node@22.7.4)(less@4.2.0))(vue@3.5.11)
+      '@vitejs/plugin-vue': 5.1.4(vite@5.4.8(@types/node@22.7.5)(less@4.2.0))(vue@3.5.11)
       '@vue/devtools-api': 7.4.6
       '@vue/shared': 3.5.11
       '@vueuse/core': 11.1.0(vue@3.5.11)
@@ -1599,8 +1600,8 @@ snapshots:
       focus-trap: 7.6.0
       mark.js: 8.11.1
       minisearch: 7.1.0
-      shiki: 1.21.1
-      vite: 5.4.8(@types/node@22.7.4)(less@4.2.0)
+      shiki: 1.22.0
+      vite: 5.4.8(@types/node@22.7.5)(less@4.2.0)
       vue: 3.5.11
     optionalDependencies:
       postcss: 8.4.47

+ 14 - 0
docs/zh_CN/guide/config-nginx.md

@@ -34,6 +34,20 @@ Nginx 日志对于监控、排查问题和维护您的 Web 服务器至关重要
 如果您需要设置不同的路径,您可以使用此选项。
 :::
 
+### LogDirWhiteList
+
+- 类型:`[]string`
+- 版本:`>= v2.0.0-beta.36`
+- 示例:`/var/log/nginx,/var/log/sites`
+
+此选项用于为 Nginx UI 设置日志查看器的目录白名单。
+
+::: warning 警告
+出于安全原因,您必须指定存储日志的目录。
+
+只有这些目录中的日志可以在线查看。
+:::
+
 ## 服务监控与控制
 
 在本节中,我们将会介绍 Nginx UI 中关于 Nginx 服务的监控和控制命令的配置选项。

+ 14 - 0
docs/zh_TW/guide/config-nginx.md

@@ -33,6 +33,20 @@ Nginx 日誌對於監控、排查問題和維護您的 Web 伺服器至關重要
 如果您需要設置不同的路徑,您可以使用此選項。
 :::
 
+### LogDirWhiteList
+
+- 類型:`[]string`
+- 版本:`>= v2.0.0-beta.36`
+- 示例:`/var/log/nginx,/var/log/sites`
+
+此選項用於為 Nginx UI 設置日誌查看器的目錄白名單。
+
+::: warning 警告
+出於安全原因,您必須指定存儲日誌的目錄。
+
+只有這些目錄中的日誌可以在線查看。
+:::
+
 ## 服務監控與控制
 
 在本節中,我們將會介紹 Nginx UI 中關於 Nginx 服務的監控和控制命令的配置選項。

+ 8 - 7
settings/nginx.go

@@ -1,13 +1,14 @@
 package settings
 
 type Nginx struct {
-	AccessLogPath string `json:"access_log_path" protected:"true"`
-	ErrorLogPath  string `json:"error_log_path" protected:"true"`
-	ConfigDir     string `json:"config_dir" protected:"true"`
-	PIDPath       string `json:"pid_path" protected:"true"`
-	TestConfigCmd string `json:"test_config_cmd" protected:"true"`
-	ReloadCmd     string `json:"reload_cmd" protected:"true"`
-	RestartCmd    string `json:"restart_cmd" protected:"true"`
+	AccessLogPath   string   `json:"access_log_path" protected:"true"`
+	ErrorLogPath    string   `json:"error_log_path" protected:"true"`
+	LogDirWhiteList []string `json:"log_dir_white_list" protected:"true"`
+	ConfigDir       string   `json:"config_dir" protected:"true"`
+	PIDPath         string   `json:"pid_path" protected:"true"`
+	TestConfigCmd   string   `json:"test_config_cmd" protected:"true"`
+	ReloadCmd       string   `json:"reload_cmd" protected:"true"`
+	RestartCmd      string   `json:"restart_cmd" protected:"true"`
 }
 
 var NginxSettings = Nginx{