Browse Source

enhance: nginx log

Jacky 1 month ago
parent
commit
56f4e5b87f

+ 29 - 199
api/nginx_log/nginx_log.go

@@ -1,21 +1,18 @@
 package nginx_log
 package nginx_log
 
 
 import (
 import (
-	"encoding/json"
-	"github.com/0xJacky/Nginx-UI/internal/helper"
-	"github.com/0xJacky/Nginx-UI/internal/nginx"
+	"io"
+	"net/http"
+	"os"
+	"strings"
+
+	"github.com/0xJacky/Nginx-UI/internal/cache"
 	"github.com/0xJacky/Nginx-UI/internal/nginx_log"
 	"github.com/0xJacky/Nginx-UI/internal/nginx_log"
 	"github.com/gin-gonic/gin"
 	"github.com/gin-gonic/gin"
-	"github.com/gorilla/websocket"
-	"github.com/hpcloud/tail"
 	"github.com/pkg/errors"
 	"github.com/pkg/errors"
 	"github.com/spf13/cast"
 	"github.com/spf13/cast"
 	"github.com/uozi-tech/cosy"
 	"github.com/uozi-tech/cosy"
 	"github.com/uozi-tech/cosy/logger"
 	"github.com/uozi-tech/cosy/logger"
-	"io"
-	"net/http"
-	"os"
-	"strings"
 )
 )
 
 
 const (
 const (
@@ -23,10 +20,8 @@ const (
 )
 )
 
 
 type controlStruct struct {
 type controlStruct struct {
-	Type         string `json:"type"`
-	ConfName     string `json:"conf_name"`
-	ServerIdx    int    `json:"server_idx"`
-	DirectiveIdx int    `json:"directive_idx"`
+	Type    string `json:"type"`
+	LogPath string `json:"log_path"`
 }
 }
 
 
 type nginxLogPageResp struct {
 type nginxLogPageResp struct {
@@ -130,200 +125,35 @@ func GetNginxLogPage(c *gin.Context) {
 	})
 	})
 }
 }
 
 
-func getLogPath(control *controlStruct) (logPath string, err error) {
-	switch control.Type {
-	case "site":
-		var config *nginx.NgxConfig
-		path := nginx.GetConfPath("sites-available", control.ConfName)
-		config, err = nginx.ParseNgxConfig(path)
-		if err != nil {
-			err = errors.Wrap(err, "error parsing ngx config")
-			return
-		}
-
-		if control.ServerIdx >= len(config.Servers) {
-			err = nginx_log.ErrServerIdxOutOfRange
-			return
-		}
-
-		if control.DirectiveIdx >= len(config.Servers[control.ServerIdx].Directives) {
-			err = nginx_log.ErrDirectiveIdxOutOfRange
-			return
-		}
-
-		directive := config.Servers[control.ServerIdx].Directives[control.DirectiveIdx]
-		switch directive.Directive {
-		case "access_log", "error_log":
-			// ok
-		default:
-			err = nginx_log.ErrLogDirective
-			return
-		}
-
-		if directive.Params == "" {
-			err = nginx_log.ErrDirectiveParamsIsEmpty
-			return
-		}
-
-		// fix: access_log /var/log/test.log main;
-		p := strings.Split(directive.Params, " ")
-		if len(p) > 0 {
-			logPath = p[0]
-		}
-
-	case "error":
-		path := nginx.GetErrorLogPath()
-
-		if path == "" {
-			err = nginx_log.ErrErrorLogPathIsEmpty
-			return
-		}
-
-		logPath = path
-	default:
-		path := nginx.GetAccessLogPath()
-
-		if path == "" {
-			err = nginx_log.ErrAccessLogPathIsEmpty
-			return
-		}
-
-		logPath = path
-	}
-
-	// check if logPath is under one of the paths in LogDirWhiteList
-	if !nginx_log.IsLogPathUnderWhiteList(logPath) {
-		return "", nginx_log.ErrLogPathIsNotUnderTheLogDirWhiteList
-	}
-	return
-}
-
-func tailNginxLog(ws *websocket.Conn, controlChan chan controlStruct, errChan chan error) {
-	defer func() {
-		if err := recover(); err != nil {
-			logger.Error(err)
-			return
-		}
-	}()
-
-	control := <-controlChan
+func GetLogList(c *gin.Context) {
+	filters := []func(*cache.NginxLogCache) bool{}
 
 
-	for {
-		logPath, err := getLogPath(&control)
-
-		if err != nil {
-			errChan <- err
-			return
-		}
-
-		seek := tail.SeekInfo{
-			Offset: 0,
-			Whence: io.SeekEnd,
-		}
-
-		stat, err := os.Stat(logPath)
-		if os.IsNotExist(err) {
-			errChan <- errors.New("[error] log path not exists " + logPath)
-			return
-		}
-
-		if !stat.Mode().IsRegular() {
-			errChan <- errors.New("[error] " + logPath + " is not a regular file. " +
-				"If you are using nginx-ui in docker container, please refer to " +
-				"https://nginxui.com/zh_CN/guide/config-nginx-log.html for more information.")
-			return
-		}
-
-		// Create a tail
-		t, err := tail.TailFile(logPath, tail.Config{Follow: true,
-			ReOpen: true, Location: &seek})
-
-		if err != nil {
-			errChan <- errors.Wrap(err, "error tailing log")
-			return
-		}
-
-		for {
-			var next = false
-			select {
-			case line := <-t.Lines:
-				// Print the text of each received line
-				if line == nil {
-					continue
-				}
-
-				err = ws.WriteMessage(websocket.TextMessage, []byte(line.Text))
-				if err != nil {
-					if helper.IsUnexpectedWebsocketError(err) {
-						errChan <- errors.Wrap(err, "error tailNginxLog write message")
-					}
-					return
-				}
-			case control = <-controlChan:
-				next = true
-				break
-			}
-			if next {
-				break
-			}
-		}
+	if c.Query("type") != "" {
+		filters = append(filters, func(cache *cache.NginxLogCache) bool {
+			return cache.Type == c.Query("type")
+		})
 	}
 	}
-}
-
-func handleLogControl(ws *websocket.Conn, controlChan chan controlStruct, errChan chan error) {
-	defer func() {
-		if err := recover(); err != nil {
-			logger.Error(err)
-			return
-		}
-	}()
-
-	for {
-		msgType, payload, err := ws.ReadMessage()
-		if err != nil && websocket.IsUnexpectedCloseError(err, websocket.CloseNormalClosure) {
-			errChan <- errors.Wrap(err, "error handleLogControl read message")
-			return
-		}
 
 
-		if msgType != websocket.TextMessage {
-			errChan <- errors.New("error handleLogControl message type")
-			return
-		}
-
-		var msg controlStruct
-		err = json.Unmarshal(payload, &msg)
-		if err != nil {
-			errChan <- errors.Wrap(err, "error ReadWsAndWritePty json.Unmarshal")
-			return
-		}
-		controlChan <- msg
+	if c.Query("name") != "" {
+		filters = append(filters, func(cache *cache.NginxLogCache) bool {
+			return strings.Contains(cache.Name, c.Query("name"))
+		})
 	}
 	}
-}
 
 
-func Log(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
+	if c.Query("path") != "" {
+		filters = append(filters, func(cache *cache.NginxLogCache) bool {
+			return strings.Contains(cache.Path, c.Query("path"))
+		})
 	}
 	}
 
 
-	defer ws.Close()
+	data := cache.GetAllLogPaths(filters...)
 
 
-	errChan := make(chan error, 1)
-	controlChan := make(chan controlStruct, 1)
+	orderBy := c.DefaultQuery("sort_by", "name")
+	sort := c.DefaultQuery("order", "desc")
 
 
-	go tailNginxLog(ws, controlChan, errChan)
-	go handleLogControl(ws, controlChan, errChan)
+	data = nginx_log.Sort(orderBy, sort, data)
 
 
-	if err = <-errChan; err != nil {
-		logger.Error(err)
-		_ = ws.WriteMessage(websocket.TextMessage, []byte(err.Error()))
-		return
-	}
+	c.JSON(http.StatusOK, gin.H{
+		"data": data,
+	})
 }
 }

+ 2 - 0
api/nginx_log/router.go

@@ -4,4 +4,6 @@ import "github.com/gin-gonic/gin"
 
 
 func InitRouter(r *gin.RouterGroup) {
 func InitRouter(r *gin.RouterGroup) {
 	r.GET("nginx_log", Log)
 	r.GET("nginx_log", Log)
+	r.GET("nginx_logs", GetLogList)
+	r.GET("nginx_logs/index_status", GetNginxLogsLive)
 }
 }

+ 50 - 0
api/nginx_log/sse.go

@@ -0,0 +1,50 @@
+package nginx_log
+
+import (
+	"io"
+	"time"
+
+	"github.com/0xJacky/Nginx-UI/api"
+	"github.com/0xJacky/Nginx-UI/internal/cache"
+	"github.com/gin-gonic/gin"
+)
+
+// GetNginxLogsLive is an SSE endpoint that sends real-time log scanning status updates
+func GetNginxLogsLive(c *gin.Context) {
+	api.SetSSEHeaders(c)
+	notify := c.Writer.CloseNotify()
+
+	// Subscribe to scanner status changes
+	statusChan := cache.SubscribeStatusChanges()
+
+	// Ensure we unsubscribe when the handler exits
+	defer cache.UnsubscribeStatusChanges(statusChan)
+
+	// Main event loop
+	for {
+		select {
+		case status, ok := <-statusChan:
+			// If channel closed, exit
+			if !ok {
+				return
+			}
+
+			// Send status update
+			c.Stream(func(w io.Writer) bool {
+				c.SSEvent("message", gin.H{
+					"scanning": status,
+				})
+				return false
+			})
+		case <-time.After(30 * time.Second):
+			// Send heartbeat to keep connection alive
+			c.Stream(func(w io.Writer) bool {
+				c.SSEvent("heartbeat", "")
+				return false
+			})
+		case <-notify:
+			// Client disconnected
+			return
+		}
+	}
+}

+ 189 - 0
api/nginx_log/websocket.go

@@ -0,0 +1,189 @@
+package nginx_log
+
+import (
+	"encoding/json"
+	"io"
+	"net/http"
+	"os"
+
+	"github.com/0xJacky/Nginx-UI/internal/helper"
+	"github.com/0xJacky/Nginx-UI/internal/nginx"
+	"github.com/0xJacky/Nginx-UI/internal/nginx_log"
+	"github.com/gin-gonic/gin"
+	"github.com/gorilla/websocket"
+	"github.com/nxadm/tail"
+	"github.com/pkg/errors"
+	"github.com/uozi-tech/cosy/logger"
+)
+
+func getLogPath(control *controlStruct) (logPath string, err error) {
+	// If direct log path is provided, use it
+	if control.LogPath != "" {
+		logPath = control.LogPath
+		// Check if logPath is under one of the paths in LogDirWhiteList
+		if !nginx_log.IsLogPathUnderWhiteList(logPath) {
+			return "", nginx_log.ErrLogPathIsNotUnderTheLogDirWhiteList
+		}
+		return
+	}
+
+	// Otherwise, use default log path based on type
+	switch control.Type {
+	case "error":
+		path := nginx.GetErrorLogPath()
+
+		if path == "" {
+			err = nginx_log.ErrErrorLogPathIsEmpty
+			return
+		}
+
+		logPath = path
+	case "access":
+		fallthrough
+	default:
+		path := nginx.GetAccessLogPath()
+
+		if path == "" {
+			err = nginx_log.ErrAccessLogPathIsEmpty
+			return
+		}
+
+		logPath = path
+	}
+
+	// check if logPath is under one of the paths in LogDirWhiteList
+	if !nginx_log.IsLogPathUnderWhiteList(logPath) {
+		return "", nginx_log.ErrLogPathIsNotUnderTheLogDirWhiteList
+	}
+	return
+}
+
+func tailNginxLog(ws *websocket.Conn, controlChan chan controlStruct, errChan chan error) {
+	defer func() {
+		if err := recover(); err != nil {
+			logger.Error(err)
+			return
+		}
+	}()
+
+	control := <-controlChan
+
+	for {
+		logPath, err := getLogPath(&control)
+
+		if err != nil {
+			errChan <- err
+			return
+		}
+
+		seek := tail.SeekInfo{
+			Offset: 0,
+			Whence: io.SeekEnd,
+		}
+
+		stat, err := os.Stat(logPath)
+		if os.IsNotExist(err) {
+			errChan <- errors.New("[error] log path not exists " + logPath)
+			return
+		}
+
+		if !stat.Mode().IsRegular() {
+			errChan <- errors.New("[error] " + logPath + " is not a regular file. " +
+				"If you are using nginx-ui in docker container, please refer to " +
+				"https://nginxui.com/zh_CN/guide/config-nginx-log.html for more information.")
+			return
+		}
+
+		// Create a tail
+		t, err := tail.TailFile(logPath, tail.Config{Follow: true,
+			ReOpen: true, Location: &seek})
+
+		if err != nil {
+			errChan <- errors.Wrap(err, "error tailing log")
+			return
+		}
+
+		for {
+			var next = false
+			select {
+			case line := <-t.Lines:
+				// Print the text of each received line
+				if line == nil {
+					continue
+				}
+
+				err = ws.WriteMessage(websocket.TextMessage, []byte(line.Text))
+				if err != nil {
+					if helper.IsUnexpectedWebsocketError(err) {
+						errChan <- errors.Wrap(err, "error tailNginxLog write message")
+					}
+					return
+				}
+			case control = <-controlChan:
+				next = true
+				break
+			}
+			if next {
+				break
+			}
+		}
+	}
+}
+
+func handleLogControl(ws *websocket.Conn, controlChan chan controlStruct, errChan chan error) {
+	defer func() {
+		if err := recover(); err != nil {
+			logger.Error(err)
+			return
+		}
+	}()
+
+	for {
+		msgType, payload, err := ws.ReadMessage()
+		if err != nil && websocket.IsUnexpectedCloseError(err, websocket.CloseNormalClosure) {
+			errChan <- errors.Wrap(err, "error handleLogControl read message")
+			return
+		}
+
+		if msgType != websocket.TextMessage {
+			errChan <- errors.New("error handleLogControl message type")
+			return
+		}
+
+		var msg controlStruct
+		err = json.Unmarshal(payload, &msg)
+		if err != nil {
+			errChan <- errors.Wrap(err, "error ReadWsAndWritePty json.Unmarshal")
+			return
+		}
+		controlChan <- msg
+	}
+}
+
+func Log(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()
+
+	errChan := make(chan error, 1)
+	controlChan := make(chan controlStruct, 1)
+
+	go tailNginxLog(ws, controlChan, errChan)
+	go handleLogControl(ws, controlChan, errChan)
+
+	if err = <-errChan; err != nil {
+		logger.Error(err)
+		_ = ws.WriteMessage(websocket.TextMessage, []byte(err.Error()))
+		return
+	}
+}

+ 22 - 3
app/src/api/nginx_log.ts

@@ -1,16 +1,35 @@
 import http from '@/lib/http'
 import http from '@/lib/http'
+import { useUserStore } from '@/pinia'
+import { SSE } from 'sse.js'
 
 
 export interface INginxLogData {
 export interface INginxLogData {
   type: string
   type: string
-  conf_name: string
-  server_idx: number
-  directive_idx: number
+  log_path?: string
 }
 }
 
 
 const nginx_log = {
 const nginx_log = {
   page(page = 0, data: INginxLogData | undefined = undefined) {
   page(page = 0, data: INginxLogData | undefined = undefined) {
     return http.post(`/nginx_log?page=${page}`, data)
     return http.post(`/nginx_log?page=${page}`, data)
   },
   },
+
+  get_list(params: {
+    type?: string
+    name?: string
+    path?: string
+  }) {
+    return http.get(`/nginx_logs`, { params })
+  },
+
+  logs_live() {
+    const { token } = useUserStore()
+    const url = `/api/nginx_logs/index_status`
+
+    return new SSE(url, {
+      headers: {
+        Authorization: token,
+      },
+    })
+  },
 }
 }
 
 
 export default nginx_log
 export default nginx_log

+ 46 - 8
app/src/language/ar/app.po

@@ -25,7 +25,12 @@ msgstr "إعدادات المصادقة الثنائية"
 msgid "About"
 msgid "About"
 msgstr "عن"
 msgstr "عن"
 
 
-#: src/routes/modules/nginx_log.ts:17 src/views/site/ngx_conf/LogEntry.vue:75
+#: src/views/nginx_log/NginxLogList.vue:29
+#, fuzzy
+msgid "Access Log"
+msgstr "سجلات الدخول"
+
+#: src/routes/modules/nginx_log.ts:17 src/views/site/ngx_conf/LogEntry.vue:91
 msgid "Access Logs"
 msgid "Access Logs"
 msgstr "سجلات الدخول"
 msgstr "سجلات الدخول"
 
 
@@ -39,7 +44,8 @@ msgstr "مستخدم ACME"
 #: src/views/certificate/DNSCredential.vue:33
 #: src/views/certificate/DNSCredential.vue:33
 #: src/views/config/configColumns.tsx:42
 #: src/views/config/configColumns.tsx:42
 #: src/views/environment/envColumns.tsx:97
 #: src/views/environment/envColumns.tsx:97
-#: src/views/notification/notificationColumns.tsx:65
+#: src/views/nginx_log/NginxLogList.vue:51
+#: src/views/notification/notificationColumns.tsx:66
 #: src/views/preference/AuthSettings.vue:30
 #: src/views/preference/AuthSettings.vue:30
 #: src/views/site/site_category/columns.ts:28
 #: src/views/site/site_category/columns.ts:28
 #: src/views/site/site_list/columns.tsx:76 src/views/stream/StreamList.vue:49
 #: src/views/site/site_list/columns.tsx:76 src/views/stream/StreamList.vue:49
@@ -244,7 +250,7 @@ msgstr "إعدادات المصادقة"
 msgid "Author"
 msgid "Author"
 msgstr "الكاتب"
 msgstr "الكاتب"
 
 
-#: src/views/nginx_log/NginxLog.vue:152
+#: src/views/nginx_log/NginxLog.vue:149
 msgid "Auto Refresh"
 msgid "Auto Refresh"
 msgstr "التحديث التلقائي"
 msgstr "التحديث التلقائي"
 
 
@@ -260,6 +266,10 @@ msgstr "تم تمكين التجديد التلقائي لـ‏%{name}"
 msgid "Automatic Restart"
 msgid "Automatic Restart"
 msgstr ""
 msgstr ""
 
 
+#: src/views/nginx_log/NginxLogList.vue:125
+msgid "Automatically indexed from site and stream configurations."
+msgstr ""
+
 #: src/views/certificate/CertificateEditor.vue:255
 #: src/views/certificate/CertificateEditor.vue:255
 #: src/views/config/ConfigEditor.vue:213 src/views/config/ConfigList.vue:106
 #: src/views/config/ConfigEditor.vue:213 src/views/config/ConfigList.vue:106
 #: src/views/config/ConfigList.vue:180 src/views/nginx_log/NginxLog.vue:173
 #: src/views/config/ConfigList.vue:180 src/views/nginx_log/NginxLog.vue:173
@@ -627,7 +637,7 @@ msgid ""
 "Backup files will be automatically downloaded to your computer."
 "Backup files will be automatically downloaded to your computer."
 msgstr ""
 msgstr ""
 
 
-#: src/views/notification/notificationColumns.tsx:58
+#: src/views/notification/notificationColumns.tsx:59
 #: src/views/preference/components/Passkey.vue:95
 #: src/views/preference/components/Passkey.vue:95
 #: src/views/site/site_category/columns.ts:16 src/views/user/userColumns.tsx:48
 #: src/views/site/site_category/columns.ts:16 src/views/user/userColumns.tsx:48
 msgid "Created at"
 msgid "Created at"
@@ -771,7 +781,7 @@ msgstr "وصف"
 msgid "Destination file already exists"
 msgid "Destination file already exists"
 msgstr ""
 msgstr ""
 
 
-#: src/views/notification/notificationColumns.tsx:52
+#: src/views/notification/notificationColumns.tsx:53
 msgid "Details"
 msgid "Details"
 msgstr "تفاصيل"
 msgstr "تفاصيل"
 
 
@@ -1113,7 +1123,12 @@ msgstr "البيئات"
 msgid "Error"
 msgid "Error"
 msgstr "خطأ"
 msgstr "خطأ"
 
 
-#: src/routes/modules/nginx_log.ts:24 src/views/site/ngx_conf/LogEntry.vue:83
+#: src/views/nginx_log/NginxLogList.vue:30
+#, fuzzy
+msgid "Error Log"
+msgstr "سجلات الأخطاء"
+
+#: src/routes/modules/nginx_log.ts:24 src/views/site/ngx_conf/LogEntry.vue:99
 msgid "Error Logs"
 msgid "Error Logs"
 msgstr "سجلات الأخطاء"
 msgstr "سجلات الأخطاء"
 
 
@@ -1542,6 +1557,12 @@ msgstr ""
 msgid "If left blank, the default CA Dir will be used."
 msgid "If left blank, the default CA Dir will be used."
 msgstr "إذا تُرك فارغًا، سيتم استخدام دليل CA الافتراضي."
 msgstr "إذا تُرك فارغًا، سيتم استخدام دليل CA الافتراضي."
 
 
+#: src/views/nginx_log/NginxLogList.vue:127
+msgid ""
+"If logs are not indexed, please check if the log file is under the directory "
+"in Nginx.LogDirWhiteList."
+msgstr ""
+
 #: src/views/preference/AuthSettings.vue:145
 #: src/views/preference/AuthSettings.vue:145
 msgid ""
 msgid ""
 "If the number of login failed attempts from a ip reach the max attempts in "
 "If the number of login failed attempts from a ip reach the max attempts in "
@@ -1571,6 +1592,14 @@ msgstr "استيراد"
 msgid "Import Certificate"
 msgid "Import Certificate"
 msgstr "استيراد شهادة"
 msgstr "استيراد شهادة"
 
 
+#: src/views/nginx_log/NginxLogList.vue:135
+msgid "Indexed"
+msgstr ""
+
+#: src/views/nginx_log/NginxLogList.vue:132
+msgid "Indexing..."
+msgstr ""
+
 #: src/components/StdDesign/StdDetail/StdDetail.vue:81
 #: src/components/StdDesign/StdDetail/StdDetail.vue:81
 #: src/constants/index.ts:18 src/views/notification/notificationColumns.tsx:29
 #: src/constants/index.ts:18 src/views/notification/notificationColumns.tsx:29
 msgid "Info"
 msgid "Info"
@@ -1793,6 +1822,11 @@ msgstr "أماكن"
 msgid "Log"
 msgid "Log"
 msgstr "سجل"
 msgstr "سجل"
 
 
+#: src/routes/modules/nginx_log.ts:39 src/views/nginx_log/NginxLogList.vue:113
+#, fuzzy
+msgid "Log List"
+msgstr "قائمة"
+
 #: src/routes/modules/auth.ts:14 src/views/other/Login.vue:222
 #: src/routes/modules/auth.ts:14 src/views/other/Login.vue:222
 msgid "Login"
 msgid "Login"
 msgstr "تسجيل الدخول"
 msgstr "تسجيل الدخول"
@@ -1906,6 +1940,7 @@ msgstr "توجيه متعدد الأسطر"
 #: src/views/config/components/Mkdir.vue:64
 #: src/views/config/components/Mkdir.vue:64
 #: src/views/config/configColumns.tsx:7 src/views/config/ConfigEditor.vue:256
 #: src/views/config/configColumns.tsx:7 src/views/config/ConfigEditor.vue:256
 #: src/views/environment/envColumns.tsx:9
 #: src/views/environment/envColumns.tsx:9
+#: src/views/nginx_log/NginxLogList.vue:35
 #: src/views/preference/components/AddPasskey.vue:75
 #: src/views/preference/components/AddPasskey.vue:75
 #: src/views/site/ngx_conf/NgxUpstream.vue:177
 #: src/views/site/ngx_conf/NgxUpstream.vue:177
 #: src/views/site/site_category/columns.ts:7
 #: src/views/site/site_category/columns.ts:7
@@ -2025,7 +2060,7 @@ msgstr "مسار سجل أخطاء Nginx"
 msgid "Nginx is not running"
 msgid "Nginx is not running"
 msgstr "Nginx لا يعمل"
 msgstr "Nginx لا يعمل"
 
 
-#: src/routes/modules/nginx_log.ts:9 src/views/nginx_log/NginxLog.vue:148
+#: src/routes/modules/nginx_log.ts:9 src/views/nginx_log/NginxLog.vue:143
 msgid "Nginx Log"
 msgid "Nginx Log"
 msgstr "سجل Nginx"
 msgstr "سجل Nginx"
 
 
@@ -2282,6 +2317,7 @@ msgid "Password length cannot exceed 20 characters"
 msgstr ""
 msgstr ""
 
 
 #: src/views/config/ConfigEditor.vue:263
 #: src/views/config/ConfigEditor.vue:263
+#: src/views/nginx_log/NginxLogList.vue:43
 #: src/views/site/ngx_conf/LocationEditor.vue:109
 #: src/views/site/ngx_conf/LocationEditor.vue:109
 #: src/views/site/ngx_conf/LocationEditor.vue:137
 #: src/views/site/ngx_conf/LocationEditor.vue:137
 msgid "Path"
 msgid "Path"
@@ -3369,7 +3405,7 @@ msgstr "كبح"
 msgid "Tips"
 msgid "Tips"
 msgstr "نصائح"
 msgstr "نصائح"
 
 
-#: src/views/notification/notificationColumns.tsx:44
+#: src/views/notification/notificationColumns.tsx:45
 msgid "Title"
 msgid "Title"
 msgstr "عنوان"
 msgstr "عنوان"
 
 
@@ -3447,6 +3483,7 @@ msgid "Two-factor authentication required"
 msgstr "يتطلب المصادقة الثنائية"
 msgstr "يتطلب المصادقة الثنائية"
 
 
 #: src/views/certificate/CertificateList/certColumns.tsx:25
 #: src/views/certificate/CertificateList/certColumns.tsx:25
+#: src/views/nginx_log/NginxLogList.vue:20
 #: src/views/notification/notificationColumns.tsx:9
 #: src/views/notification/notificationColumns.tsx:9
 msgid "Type"
 msgid "Type"
 msgstr "نوع"
 msgstr "نوع"
@@ -3549,6 +3586,7 @@ msgid "Version"
 msgstr "إصدار"
 msgstr "إصدار"
 
 
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:488
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:488
+#: src/views/nginx_log/NginxLogList.vue:143
 #: src/views/site/ngx_conf/config_template/ConfigTemplate.vue:103
 #: src/views/site/ngx_conf/config_template/ConfigTemplate.vue:103
 msgid "View"
 msgid "View"
 msgstr "عرض"
 msgstr "عرض"

+ 46 - 8
app/src/language/de_DE/app.po

@@ -21,7 +21,12 @@ msgstr "2FA-Einstellungen"
 msgid "About"
 msgid "About"
 msgstr "Über"
 msgstr "Über"
 
 
-#: src/routes/modules/nginx_log.ts:17 src/views/site/ngx_conf/LogEntry.vue:75
+#: src/views/nginx_log/NginxLogList.vue:29
+#, fuzzy
+msgid "Access Log"
+msgstr "Zugriffslog"
+
+#: src/routes/modules/nginx_log.ts:17 src/views/site/ngx_conf/LogEntry.vue:91
 msgid "Access Logs"
 msgid "Access Logs"
 msgstr "Zugriffslog"
 msgstr "Zugriffslog"
 
 
@@ -36,7 +41,8 @@ msgstr "Benutzername"
 #: src/views/certificate/DNSCredential.vue:33
 #: src/views/certificate/DNSCredential.vue:33
 #: src/views/config/configColumns.tsx:42
 #: src/views/config/configColumns.tsx:42
 #: src/views/environment/envColumns.tsx:97
 #: src/views/environment/envColumns.tsx:97
-#: src/views/notification/notificationColumns.tsx:65
+#: src/views/nginx_log/NginxLogList.vue:51
+#: src/views/notification/notificationColumns.tsx:66
 #: src/views/preference/AuthSettings.vue:30
 #: src/views/preference/AuthSettings.vue:30
 #: src/views/site/site_category/columns.ts:28
 #: src/views/site/site_category/columns.ts:28
 #: src/views/site/site_list/columns.tsx:76 src/views/stream/StreamList.vue:49
 #: src/views/site/site_list/columns.tsx:76 src/views/stream/StreamList.vue:49
@@ -258,7 +264,7 @@ msgstr "Authentifizierungseinstellungen"
 msgid "Author"
 msgid "Author"
 msgstr "Autor"
 msgstr "Autor"
 
 
-#: src/views/nginx_log/NginxLog.vue:152
+#: src/views/nginx_log/NginxLog.vue:149
 msgid "Auto Refresh"
 msgid "Auto Refresh"
 msgstr "Automatische Aktualisierung"
 msgstr "Automatische Aktualisierung"
 
 
@@ -274,6 +280,10 @@ msgstr "Automatische Verlängerung aktiviert für %{name}"
 msgid "Automatic Restart"
 msgid "Automatic Restart"
 msgstr ""
 msgstr ""
 
 
+#: src/views/nginx_log/NginxLogList.vue:125
+msgid "Automatically indexed from site and stream configurations."
+msgstr ""
+
 #: src/views/certificate/CertificateEditor.vue:255
 #: src/views/certificate/CertificateEditor.vue:255
 #: src/views/config/ConfigEditor.vue:213 src/views/config/ConfigList.vue:106
 #: src/views/config/ConfigEditor.vue:213 src/views/config/ConfigList.vue:106
 #: src/views/config/ConfigList.vue:180 src/views/nginx_log/NginxLog.vue:173
 #: src/views/config/ConfigList.vue:180 src/views/nginx_log/NginxLog.vue:173
@@ -650,7 +660,7 @@ msgid ""
 "Backup files will be automatically downloaded to your computer."
 "Backup files will be automatically downloaded to your computer."
 msgstr ""
 msgstr ""
 
 
-#: src/views/notification/notificationColumns.tsx:58
+#: src/views/notification/notificationColumns.tsx:59
 #: src/views/preference/components/Passkey.vue:95
 #: src/views/preference/components/Passkey.vue:95
 #: src/views/site/site_category/columns.ts:16 src/views/user/userColumns.tsx:48
 #: src/views/site/site_category/columns.ts:16 src/views/user/userColumns.tsx:48
 msgid "Created at"
 msgid "Created at"
@@ -799,7 +809,7 @@ msgstr "Beschreibung"
 msgid "Destination file already exists"
 msgid "Destination file already exists"
 msgstr ""
 msgstr ""
 
 
-#: src/views/notification/notificationColumns.tsx:52
+#: src/views/notification/notificationColumns.tsx:53
 msgid "Details"
 msgid "Details"
 msgstr "Details"
 msgstr "Details"
 
 
@@ -1161,7 +1171,12 @@ msgstr "Kommentare"
 msgid "Error"
 msgid "Error"
 msgstr "Fehler"
 msgstr "Fehler"
 
 
-#: src/routes/modules/nginx_log.ts:24 src/views/site/ngx_conf/LogEntry.vue:83
+#: src/views/nginx_log/NginxLogList.vue:30
+#, fuzzy
+msgid "Error Log"
+msgstr "Feherlogs"
+
+#: src/routes/modules/nginx_log.ts:24 src/views/site/ngx_conf/LogEntry.vue:99
 msgid "Error Logs"
 msgid "Error Logs"
 msgstr "Feherlogs"
 msgstr "Feherlogs"
 
 
@@ -1592,6 +1607,12 @@ msgstr "ICP-Nummer"
 msgid "If left blank, the default CA Dir will be used."
 msgid "If left blank, the default CA Dir will be used."
 msgstr "Wenn leer, wird das Standard-CA-Verzeichnis verwendet."
 msgstr "Wenn leer, wird das Standard-CA-Verzeichnis verwendet."
 
 
+#: src/views/nginx_log/NginxLogList.vue:127
+msgid ""
+"If logs are not indexed, please check if the log file is under the directory "
+"in Nginx.LogDirWhiteList."
+msgstr ""
+
 #: src/views/preference/AuthSettings.vue:145
 #: src/views/preference/AuthSettings.vue:145
 msgid ""
 msgid ""
 "If the number of login failed attempts from a ip reach the max attempts in "
 "If the number of login failed attempts from a ip reach the max attempts in "
@@ -1625,6 +1646,14 @@ msgstr "Import"
 msgid "Import Certificate"
 msgid "Import Certificate"
 msgstr "Zertifikatsstatus"
 msgstr "Zertifikatsstatus"
 
 
+#: src/views/nginx_log/NginxLogList.vue:135
+msgid "Indexed"
+msgstr ""
+
+#: src/views/nginx_log/NginxLogList.vue:132
+msgid "Indexing..."
+msgstr ""
+
 #: src/components/StdDesign/StdDetail/StdDetail.vue:81
 #: src/components/StdDesign/StdDetail/StdDetail.vue:81
 #: src/constants/index.ts:18 src/views/notification/notificationColumns.tsx:29
 #: src/constants/index.ts:18 src/views/notification/notificationColumns.tsx:29
 msgid "Info"
 msgid "Info"
@@ -1859,6 +1888,11 @@ msgstr "Orte"
 msgid "Log"
 msgid "Log"
 msgstr "Login"
 msgstr "Login"
 
 
+#: src/routes/modules/nginx_log.ts:39 src/views/nginx_log/NginxLogList.vue:113
+#, fuzzy
+msgid "Log List"
+msgstr "Liste"
+
 #: src/routes/modules/auth.ts:14 src/views/other/Login.vue:222
 #: src/routes/modules/auth.ts:14 src/views/other/Login.vue:222
 msgid "Login"
 msgid "Login"
 msgstr "Login"
 msgstr "Login"
@@ -1981,6 +2015,7 @@ msgstr "Einzelne Anweisung"
 #: src/views/config/components/Mkdir.vue:64
 #: src/views/config/components/Mkdir.vue:64
 #: src/views/config/configColumns.tsx:7 src/views/config/ConfigEditor.vue:256
 #: src/views/config/configColumns.tsx:7 src/views/config/ConfigEditor.vue:256
 #: src/views/environment/envColumns.tsx:9
 #: src/views/environment/envColumns.tsx:9
+#: src/views/nginx_log/NginxLogList.vue:35
 #: src/views/preference/components/AddPasskey.vue:75
 #: src/views/preference/components/AddPasskey.vue:75
 #: src/views/site/ngx_conf/NgxUpstream.vue:177
 #: src/views/site/ngx_conf/NgxUpstream.vue:177
 #: src/views/site/site_category/columns.ts:7
 #: src/views/site/site_category/columns.ts:7
@@ -2104,7 +2139,7 @@ msgstr "Nginx Fehlerlog-Pfad"
 msgid "Nginx is not running"
 msgid "Nginx is not running"
 msgstr "Nginx läuft nicht"
 msgstr "Nginx läuft nicht"
 
 
-#: src/routes/modules/nginx_log.ts:9 src/views/nginx_log/NginxLog.vue:148
+#: src/routes/modules/nginx_log.ts:9 src/views/nginx_log/NginxLog.vue:143
 msgid "Nginx Log"
 msgid "Nginx Log"
 msgstr "Nginx-Log"
 msgstr "Nginx-Log"
 
 
@@ -2370,6 +2405,7 @@ msgid "Password length cannot exceed 20 characters"
 msgstr "Passwort darf nicht länger als 20 Zeichen sein"
 msgstr "Passwort darf nicht länger als 20 Zeichen sein"
 
 
 #: src/views/config/ConfigEditor.vue:263
 #: src/views/config/ConfigEditor.vue:263
+#: src/views/nginx_log/NginxLogList.vue:43
 #: src/views/site/ngx_conf/LocationEditor.vue:109
 #: src/views/site/ngx_conf/LocationEditor.vue:109
 #: src/views/site/ngx_conf/LocationEditor.vue:137
 #: src/views/site/ngx_conf/LocationEditor.vue:137
 msgid "Path"
 msgid "Path"
@@ -3511,7 +3547,7 @@ msgstr "Begrenzung"
 msgid "Tips"
 msgid "Tips"
 msgstr "Tipps"
 msgstr "Tipps"
 
 
-#: src/views/notification/notificationColumns.tsx:44
+#: src/views/notification/notificationColumns.tsx:45
 msgid "Title"
 msgid "Title"
 msgstr "Titel"
 msgstr "Titel"
 
 
@@ -3585,6 +3621,7 @@ msgid "Two-factor authentication required"
 msgstr "Zwei-Faktor-Authentifizierung erforderlich"
 msgstr "Zwei-Faktor-Authentifizierung erforderlich"
 
 
 #: src/views/certificate/CertificateList/certColumns.tsx:25
 #: src/views/certificate/CertificateList/certColumns.tsx:25
+#: src/views/nginx_log/NginxLogList.vue:20
 #: src/views/notification/notificationColumns.tsx:9
 #: src/views/notification/notificationColumns.tsx:9
 msgid "Type"
 msgid "Type"
 msgstr "Typ"
 msgstr "Typ"
@@ -3692,6 +3729,7 @@ msgid "Version"
 msgstr ""
 msgstr ""
 
 
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:488
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:488
+#: src/views/nginx_log/NginxLogList.vue:143
 #: src/views/site/ngx_conf/config_template/ConfigTemplate.vue:103
 #: src/views/site/ngx_conf/config_template/ConfigTemplate.vue:103
 msgid "View"
 msgid "View"
 msgstr "Anzeigen"
 msgstr "Anzeigen"

+ 44 - 8
app/src/language/en/app.po

@@ -21,7 +21,12 @@ msgstr ""
 msgid "About"
 msgid "About"
 msgstr "About"
 msgstr "About"
 
 
-#: src/routes/modules/nginx_log.ts:17 src/views/site/ngx_conf/LogEntry.vue:75
+#: src/views/nginx_log/NginxLogList.vue:29
+#, fuzzy
+msgid "Access Log"
+msgstr "Sites List"
+
+#: src/routes/modules/nginx_log.ts:17 src/views/site/ngx_conf/LogEntry.vue:91
 #, fuzzy
 #, fuzzy
 msgid "Access Logs"
 msgid "Access Logs"
 msgstr "Sites List"
 msgstr "Sites List"
@@ -37,7 +42,8 @@ msgstr "Username"
 #: src/views/certificate/DNSCredential.vue:33
 #: src/views/certificate/DNSCredential.vue:33
 #: src/views/config/configColumns.tsx:42
 #: src/views/config/configColumns.tsx:42
 #: src/views/environment/envColumns.tsx:97
 #: src/views/environment/envColumns.tsx:97
-#: src/views/notification/notificationColumns.tsx:65
+#: src/views/nginx_log/NginxLogList.vue:51
+#: src/views/notification/notificationColumns.tsx:66
 #: src/views/preference/AuthSettings.vue:30
 #: src/views/preference/AuthSettings.vue:30
 #: src/views/site/site_category/columns.ts:28
 #: src/views/site/site_category/columns.ts:28
 #: src/views/site/site_list/columns.tsx:76 src/views/stream/StreamList.vue:49
 #: src/views/site/site_list/columns.tsx:76 src/views/stream/StreamList.vue:49
@@ -255,7 +261,7 @@ msgstr ""
 msgid "Author"
 msgid "Author"
 msgstr ""
 msgstr ""
 
 
-#: src/views/nginx_log/NginxLog.vue:152
+#: src/views/nginx_log/NginxLog.vue:149
 msgid "Auto Refresh"
 msgid "Auto Refresh"
 msgstr ""
 msgstr ""
 
 
@@ -271,6 +277,10 @@ msgstr "Auto-renewal enabled for %{name}"
 msgid "Automatic Restart"
 msgid "Automatic Restart"
 msgstr ""
 msgstr ""
 
 
+#: src/views/nginx_log/NginxLogList.vue:125
+msgid "Automatically indexed from site and stream configurations."
+msgstr ""
+
 #: src/views/certificate/CertificateEditor.vue:255
 #: src/views/certificate/CertificateEditor.vue:255
 #: src/views/config/ConfigEditor.vue:213 src/views/config/ConfigList.vue:106
 #: src/views/config/ConfigEditor.vue:213 src/views/config/ConfigList.vue:106
 #: src/views/config/ConfigList.vue:180 src/views/nginx_log/NginxLog.vue:173
 #: src/views/config/ConfigList.vue:180 src/views/nginx_log/NginxLog.vue:173
@@ -642,7 +652,7 @@ msgid ""
 "Backup files will be automatically downloaded to your computer."
 "Backup files will be automatically downloaded to your computer."
 msgstr ""
 msgstr ""
 
 
-#: src/views/notification/notificationColumns.tsx:58
+#: src/views/notification/notificationColumns.tsx:59
 #: src/views/preference/components/Passkey.vue:95
 #: src/views/preference/components/Passkey.vue:95
 #: src/views/site/site_category/columns.ts:16 src/views/user/userColumns.tsx:48
 #: src/views/site/site_category/columns.ts:16 src/views/user/userColumns.tsx:48
 msgid "Created at"
 msgid "Created at"
@@ -790,7 +800,7 @@ msgstr ""
 msgid "Destination file already exists"
 msgid "Destination file already exists"
 msgstr ""
 msgstr ""
 
 
-#: src/views/notification/notificationColumns.tsx:52
+#: src/views/notification/notificationColumns.tsx:53
 msgid "Details"
 msgid "Details"
 msgstr ""
 msgstr ""
 
 
@@ -1147,7 +1157,11 @@ msgstr "Comments"
 msgid "Error"
 msgid "Error"
 msgstr ""
 msgstr ""
 
 
-#: src/routes/modules/nginx_log.ts:24 src/views/site/ngx_conf/LogEntry.vue:83
+#: src/views/nginx_log/NginxLogList.vue:30
+msgid "Error Log"
+msgstr ""
+
+#: src/routes/modules/nginx_log.ts:24 src/views/site/ngx_conf/LogEntry.vue:99
 msgid "Error Logs"
 msgid "Error Logs"
 msgstr ""
 msgstr ""
 
 
@@ -1580,6 +1594,12 @@ msgstr ""
 msgid "If left blank, the default CA Dir will be used."
 msgid "If left blank, the default CA Dir will be used."
 msgstr ""
 msgstr ""
 
 
+#: src/views/nginx_log/NginxLogList.vue:127
+msgid ""
+"If logs are not indexed, please check if the log file is under the directory "
+"in Nginx.LogDirWhiteList."
+msgstr ""
+
 #: src/views/preference/AuthSettings.vue:145
 #: src/views/preference/AuthSettings.vue:145
 msgid ""
 msgid ""
 "If the number of login failed attempts from a ip reach the max attempts in "
 "If the number of login failed attempts from a ip reach the max attempts in "
@@ -1606,6 +1626,14 @@ msgstr ""
 msgid "Import Certificate"
 msgid "Import Certificate"
 msgstr "Certificate Status"
 msgstr "Certificate Status"
 
 
+#: src/views/nginx_log/NginxLogList.vue:135
+msgid "Indexed"
+msgstr ""
+
+#: src/views/nginx_log/NginxLogList.vue:132
+msgid "Indexing..."
+msgstr ""
+
 #: src/components/StdDesign/StdDetail/StdDetail.vue:81
 #: src/components/StdDesign/StdDetail/StdDetail.vue:81
 #: src/constants/index.ts:18 src/views/notification/notificationColumns.tsx:29
 #: src/constants/index.ts:18 src/views/notification/notificationColumns.tsx:29
 msgid "Info"
 msgid "Info"
@@ -1845,6 +1873,10 @@ msgstr "Locations"
 msgid "Log"
 msgid "Log"
 msgstr "Login"
 msgstr "Login"
 
 
+#: src/routes/modules/nginx_log.ts:39 src/views/nginx_log/NginxLogList.vue:113
+msgid "Log List"
+msgstr ""
+
 #: src/routes/modules/auth.ts:14 src/views/other/Login.vue:222
 #: src/routes/modules/auth.ts:14 src/views/other/Login.vue:222
 msgid "Login"
 msgid "Login"
 msgstr "Login"
 msgstr "Login"
@@ -1960,6 +1992,7 @@ msgstr "Single Directive"
 #: src/views/config/components/Mkdir.vue:64
 #: src/views/config/components/Mkdir.vue:64
 #: src/views/config/configColumns.tsx:7 src/views/config/ConfigEditor.vue:256
 #: src/views/config/configColumns.tsx:7 src/views/config/ConfigEditor.vue:256
 #: src/views/environment/envColumns.tsx:9
 #: src/views/environment/envColumns.tsx:9
+#: src/views/nginx_log/NginxLogList.vue:35
 #: src/views/preference/components/AddPasskey.vue:75
 #: src/views/preference/components/AddPasskey.vue:75
 #: src/views/site/ngx_conf/NgxUpstream.vue:177
 #: src/views/site/ngx_conf/NgxUpstream.vue:177
 #: src/views/site/site_category/columns.ts:7
 #: src/views/site/site_category/columns.ts:7
@@ -2083,7 +2116,7 @@ msgstr ""
 msgid "Nginx is not running"
 msgid "Nginx is not running"
 msgstr ""
 msgstr ""
 
 
-#: src/routes/modules/nginx_log.ts:9 src/views/nginx_log/NginxLog.vue:148
+#: src/routes/modules/nginx_log.ts:9 src/views/nginx_log/NginxLog.vue:143
 msgid "Nginx Log"
 msgid "Nginx Log"
 msgstr ""
 msgstr ""
 
 
@@ -2340,6 +2373,7 @@ msgid "Password length cannot exceed 20 characters"
 msgstr ""
 msgstr ""
 
 
 #: src/views/config/ConfigEditor.vue:263
 #: src/views/config/ConfigEditor.vue:263
+#: src/views/nginx_log/NginxLogList.vue:43
 #: src/views/site/ngx_conf/LocationEditor.vue:109
 #: src/views/site/ngx_conf/LocationEditor.vue:109
 #: src/views/site/ngx_conf/LocationEditor.vue:137
 #: src/views/site/ngx_conf/LocationEditor.vue:137
 msgid "Path"
 msgid "Path"
@@ -3459,7 +3493,7 @@ msgstr ""
 msgid "Tips"
 msgid "Tips"
 msgstr ""
 msgstr ""
 
 
-#: src/views/notification/notificationColumns.tsx:44
+#: src/views/notification/notificationColumns.tsx:45
 msgid "Title"
 msgid "Title"
 msgstr ""
 msgstr ""
 
 
@@ -3520,6 +3554,7 @@ msgid "Two-factor authentication required"
 msgstr ""
 msgstr ""
 
 
 #: src/views/certificate/CertificateList/certColumns.tsx:25
 #: src/views/certificate/CertificateList/certColumns.tsx:25
+#: src/views/nginx_log/NginxLogList.vue:20
 #: src/views/notification/notificationColumns.tsx:9
 #: src/views/notification/notificationColumns.tsx:9
 msgid "Type"
 msgid "Type"
 msgstr ""
 msgstr ""
@@ -3628,6 +3663,7 @@ msgid "Version"
 msgstr ""
 msgstr ""
 
 
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:488
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:488
+#: src/views/nginx_log/NginxLogList.vue:143
 #: src/views/site/ngx_conf/config_template/ConfigTemplate.vue:103
 #: src/views/site/ngx_conf/config_template/ConfigTemplate.vue:103
 #, fuzzy
 #, fuzzy
 msgid "View"
 msgid "View"

+ 46 - 8
app/src/language/es/app.po

@@ -28,7 +28,12 @@ msgstr "Configuración de 2FA"
 msgid "About"
 msgid "About"
 msgstr "Acerca de"
 msgstr "Acerca de"
 
 
-#: src/routes/modules/nginx_log.ts:17 src/views/site/ngx_conf/LogEntry.vue:75
+#: src/views/nginx_log/NginxLogList.vue:29
+#, fuzzy
+msgid "Access Log"
+msgstr "Logs de acceso"
+
+#: src/routes/modules/nginx_log.ts:17 src/views/site/ngx_conf/LogEntry.vue:91
 msgid "Access Logs"
 msgid "Access Logs"
 msgstr "Logs de acceso"
 msgstr "Logs de acceso"
 
 
@@ -42,7 +47,8 @@ msgstr "Usuario ACME"
 #: src/views/certificate/DNSCredential.vue:33
 #: src/views/certificate/DNSCredential.vue:33
 #: src/views/config/configColumns.tsx:42
 #: src/views/config/configColumns.tsx:42
 #: src/views/environment/envColumns.tsx:97
 #: src/views/environment/envColumns.tsx:97
-#: src/views/notification/notificationColumns.tsx:65
+#: src/views/nginx_log/NginxLogList.vue:51
+#: src/views/notification/notificationColumns.tsx:66
 #: src/views/preference/AuthSettings.vue:30
 #: src/views/preference/AuthSettings.vue:30
 #: src/views/site/site_category/columns.ts:28
 #: src/views/site/site_category/columns.ts:28
 #: src/views/site/site_list/columns.tsx:76 src/views/stream/StreamList.vue:49
 #: src/views/site/site_list/columns.tsx:76 src/views/stream/StreamList.vue:49
@@ -249,7 +255,7 @@ msgstr "Configuración de autenticación"
 msgid "Author"
 msgid "Author"
 msgstr "Autor"
 msgstr "Autor"
 
 
-#: src/views/nginx_log/NginxLog.vue:152
+#: src/views/nginx_log/NginxLog.vue:149
 msgid "Auto Refresh"
 msgid "Auto Refresh"
 msgstr "Actualización automática"
 msgstr "Actualización automática"
 
 
@@ -265,6 +271,10 @@ msgstr "Renovación automática habilitada por %{name}"
 msgid "Automatic Restart"
 msgid "Automatic Restart"
 msgstr ""
 msgstr ""
 
 
+#: src/views/nginx_log/NginxLogList.vue:125
+msgid "Automatically indexed from site and stream configurations."
+msgstr ""
+
 #: src/views/certificate/CertificateEditor.vue:255
 #: src/views/certificate/CertificateEditor.vue:255
 #: src/views/config/ConfigEditor.vue:213 src/views/config/ConfigList.vue:106
 #: src/views/config/ConfigEditor.vue:213 src/views/config/ConfigList.vue:106
 #: src/views/config/ConfigList.vue:180 src/views/nginx_log/NginxLog.vue:173
 #: src/views/config/ConfigList.vue:180 src/views/nginx_log/NginxLog.vue:173
@@ -626,7 +636,7 @@ msgid ""
 "Backup files will be automatically downloaded to your computer."
 "Backup files will be automatically downloaded to your computer."
 msgstr ""
 msgstr ""
 
 
-#: src/views/notification/notificationColumns.tsx:58
+#: src/views/notification/notificationColumns.tsx:59
 #: src/views/preference/components/Passkey.vue:95
 #: src/views/preference/components/Passkey.vue:95
 #: src/views/site/site_category/columns.ts:16 src/views/user/userColumns.tsx:48
 #: src/views/site/site_category/columns.ts:16 src/views/user/userColumns.tsx:48
 msgid "Created at"
 msgid "Created at"
@@ -772,7 +782,7 @@ msgstr "Descripción"
 msgid "Destination file already exists"
 msgid "Destination file already exists"
 msgstr ""
 msgstr ""
 
 
-#: src/views/notification/notificationColumns.tsx:52
+#: src/views/notification/notificationColumns.tsx:53
 msgid "Details"
 msgid "Details"
 msgstr "Detalles"
 msgstr "Detalles"
 
 
@@ -1116,7 +1126,12 @@ msgstr "Entornos"
 msgid "Error"
 msgid "Error"
 msgstr "Error"
 msgstr "Error"
 
 
-#: src/routes/modules/nginx_log.ts:24 src/views/site/ngx_conf/LogEntry.vue:83
+#: src/views/nginx_log/NginxLogList.vue:30
+#, fuzzy
+msgid "Error Log"
+msgstr "Logs de error"
+
+#: src/routes/modules/nginx_log.ts:24 src/views/site/ngx_conf/LogEntry.vue:99
 msgid "Error Logs"
 msgid "Error Logs"
 msgstr "Logs de error"
 msgstr "Logs de error"
 
 
@@ -1543,6 +1558,12 @@ msgstr ""
 msgid "If left blank, the default CA Dir will be used."
 msgid "If left blank, the default CA Dir will be used."
 msgstr "Si se deja en blanco, se utilizará el directorio CA predeterminado."
 msgstr "Si se deja en blanco, se utilizará el directorio CA predeterminado."
 
 
+#: src/views/nginx_log/NginxLogList.vue:127
+msgid ""
+"If logs are not indexed, please check if the log file is under the directory "
+"in Nginx.LogDirWhiteList."
+msgstr ""
+
 #: src/views/preference/AuthSettings.vue:145
 #: src/views/preference/AuthSettings.vue:145
 msgid ""
 msgid ""
 "If the number of login failed attempts from a ip reach the max attempts in "
 "If the number of login failed attempts from a ip reach the max attempts in "
@@ -1574,6 +1595,14 @@ msgstr "Importar"
 msgid "Import Certificate"
 msgid "Import Certificate"
 msgstr "Importar Certificado"
 msgstr "Importar Certificado"
 
 
+#: src/views/nginx_log/NginxLogList.vue:135
+msgid "Indexed"
+msgstr ""
+
+#: src/views/nginx_log/NginxLogList.vue:132
+msgid "Indexing..."
+msgstr ""
+
 #: src/components/StdDesign/StdDetail/StdDetail.vue:81
 #: src/components/StdDesign/StdDetail/StdDetail.vue:81
 #: src/constants/index.ts:18 src/views/notification/notificationColumns.tsx:29
 #: src/constants/index.ts:18 src/views/notification/notificationColumns.tsx:29
 msgid "Info"
 msgid "Info"
@@ -1795,6 +1824,11 @@ msgstr "Ubicaciones"
 msgid "Log"
 msgid "Log"
 msgstr "Registro"
 msgstr "Registro"
 
 
+#: src/routes/modules/nginx_log.ts:39 src/views/nginx_log/NginxLogList.vue:113
+#, fuzzy
+msgid "Log List"
+msgstr "Lista"
+
 #: src/routes/modules/auth.ts:14 src/views/other/Login.vue:222
 #: src/routes/modules/auth.ts:14 src/views/other/Login.vue:222
 msgid "Login"
 msgid "Login"
 msgstr "Acceso"
 msgstr "Acceso"
@@ -1909,6 +1943,7 @@ msgstr "Directiva multilínea"
 #: src/views/config/components/Mkdir.vue:64
 #: src/views/config/components/Mkdir.vue:64
 #: src/views/config/configColumns.tsx:7 src/views/config/ConfigEditor.vue:256
 #: src/views/config/configColumns.tsx:7 src/views/config/ConfigEditor.vue:256
 #: src/views/environment/envColumns.tsx:9
 #: src/views/environment/envColumns.tsx:9
+#: src/views/nginx_log/NginxLogList.vue:35
 #: src/views/preference/components/AddPasskey.vue:75
 #: src/views/preference/components/AddPasskey.vue:75
 #: src/views/site/ngx_conf/NgxUpstream.vue:177
 #: src/views/site/ngx_conf/NgxUpstream.vue:177
 #: src/views/site/site_category/columns.ts:7
 #: src/views/site/site_category/columns.ts:7
@@ -2029,7 +2064,7 @@ msgstr "Ruta de registro de errores de Nginx"
 msgid "Nginx is not running"
 msgid "Nginx is not running"
 msgstr "Nginx no se está ejecutando"
 msgstr "Nginx no se está ejecutando"
 
 
-#: src/routes/modules/nginx_log.ts:9 src/views/nginx_log/NginxLog.vue:148
+#: src/routes/modules/nginx_log.ts:9 src/views/nginx_log/NginxLog.vue:143
 msgid "Nginx Log"
 msgid "Nginx Log"
 msgstr "Registro Nginx"
 msgstr "Registro Nginx"
 
 
@@ -2291,6 +2326,7 @@ msgid "Password length cannot exceed 20 characters"
 msgstr ""
 msgstr ""
 
 
 #: src/views/config/ConfigEditor.vue:263
 #: src/views/config/ConfigEditor.vue:263
+#: src/views/nginx_log/NginxLogList.vue:43
 #: src/views/site/ngx_conf/LocationEditor.vue:109
 #: src/views/site/ngx_conf/LocationEditor.vue:109
 #: src/views/site/ngx_conf/LocationEditor.vue:137
 #: src/views/site/ngx_conf/LocationEditor.vue:137
 msgid "Path"
 msgid "Path"
@@ -3412,7 +3448,7 @@ msgstr "Acelerador"
 msgid "Tips"
 msgid "Tips"
 msgstr "Consejos"
 msgstr "Consejos"
 
 
-#: src/views/notification/notificationColumns.tsx:44
+#: src/views/notification/notificationColumns.tsx:45
 msgid "Title"
 msgid "Title"
 msgstr "Título"
 msgstr "Título"
 
 
@@ -3488,6 +3524,7 @@ msgid "Two-factor authentication required"
 msgstr "Se requiere autenticación de dos factores"
 msgstr "Se requiere autenticación de dos factores"
 
 
 #: src/views/certificate/CertificateList/certColumns.tsx:25
 #: src/views/certificate/CertificateList/certColumns.tsx:25
+#: src/views/nginx_log/NginxLogList.vue:20
 #: src/views/notification/notificationColumns.tsx:9
 #: src/views/notification/notificationColumns.tsx:9
 msgid "Type"
 msgid "Type"
 msgstr "Tipo"
 msgstr "Tipo"
@@ -3591,6 +3628,7 @@ msgid "Version"
 msgstr "Versión"
 msgstr "Versión"
 
 
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:488
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:488
+#: src/views/nginx_log/NginxLogList.vue:143
 #: src/views/site/ngx_conf/config_template/ConfigTemplate.vue:103
 #: src/views/site/ngx_conf/config_template/ConfigTemplate.vue:103
 msgid "View"
 msgid "View"
 msgstr "Ver"
 msgstr "Ver"

+ 46 - 8
app/src/language/fr_FR/app.po

@@ -26,7 +26,12 @@ msgstr "Options 2FA"
 msgid "About"
 msgid "About"
 msgstr "À propos"
 msgstr "À propos"
 
 
-#: src/routes/modules/nginx_log.ts:17 src/views/site/ngx_conf/LogEntry.vue:75
+#: src/views/nginx_log/NginxLogList.vue:29
+#, fuzzy
+msgid "Access Log"
+msgstr "Journaux d'accès"
+
+#: src/routes/modules/nginx_log.ts:17 src/views/site/ngx_conf/LogEntry.vue:91
 msgid "Access Logs"
 msgid "Access Logs"
 msgstr "Journaux d'accès"
 msgstr "Journaux d'accès"
 
 
@@ -41,7 +46,8 @@ msgstr "Nom d'utilisateur"
 #: src/views/certificate/DNSCredential.vue:33
 #: src/views/certificate/DNSCredential.vue:33
 #: src/views/config/configColumns.tsx:42
 #: src/views/config/configColumns.tsx:42
 #: src/views/environment/envColumns.tsx:97
 #: src/views/environment/envColumns.tsx:97
-#: src/views/notification/notificationColumns.tsx:65
+#: src/views/nginx_log/NginxLogList.vue:51
+#: src/views/notification/notificationColumns.tsx:66
 #: src/views/preference/AuthSettings.vue:30
 #: src/views/preference/AuthSettings.vue:30
 #: src/views/site/site_category/columns.ts:28
 #: src/views/site/site_category/columns.ts:28
 #: src/views/site/site_list/columns.tsx:76 src/views/stream/StreamList.vue:49
 #: src/views/site/site_list/columns.tsx:76 src/views/stream/StreamList.vue:49
@@ -262,7 +268,7 @@ msgstr "Options d'authentification"
 msgid "Author"
 msgid "Author"
 msgstr "Autheur"
 msgstr "Autheur"
 
 
-#: src/views/nginx_log/NginxLog.vue:152
+#: src/views/nginx_log/NginxLog.vue:149
 msgid "Auto Refresh"
 msgid "Auto Refresh"
 msgstr "Actualisation automatique"
 msgstr "Actualisation automatique"
 
 
@@ -278,6 +284,10 @@ msgstr "Renouvellement automatique activé pour %{name}"
 msgid "Automatic Restart"
 msgid "Automatic Restart"
 msgstr ""
 msgstr ""
 
 
+#: src/views/nginx_log/NginxLogList.vue:125
+msgid "Automatically indexed from site and stream configurations."
+msgstr ""
+
 #: src/views/certificate/CertificateEditor.vue:255
 #: src/views/certificate/CertificateEditor.vue:255
 #: src/views/config/ConfigEditor.vue:213 src/views/config/ConfigList.vue:106
 #: src/views/config/ConfigEditor.vue:213 src/views/config/ConfigList.vue:106
 #: src/views/config/ConfigList.vue:180 src/views/nginx_log/NginxLog.vue:173
 #: src/views/config/ConfigList.vue:180 src/views/nginx_log/NginxLog.vue:173
@@ -654,7 +664,7 @@ msgid ""
 "Backup files will be automatically downloaded to your computer."
 "Backup files will be automatically downloaded to your computer."
 msgstr ""
 msgstr ""
 
 
-#: src/views/notification/notificationColumns.tsx:58
+#: src/views/notification/notificationColumns.tsx:59
 #: src/views/preference/components/Passkey.vue:95
 #: src/views/preference/components/Passkey.vue:95
 #: src/views/site/site_category/columns.ts:16 src/views/user/userColumns.tsx:48
 #: src/views/site/site_category/columns.ts:16 src/views/user/userColumns.tsx:48
 msgid "Created at"
 msgid "Created at"
@@ -805,7 +815,7 @@ msgstr "Description"
 msgid "Destination file already exists"
 msgid "Destination file already exists"
 msgstr "Le fichier de destination existe déjà"
 msgstr "Le fichier de destination existe déjà"
 
 
-#: src/views/notification/notificationColumns.tsx:52
+#: src/views/notification/notificationColumns.tsx:53
 msgid "Details"
 msgid "Details"
 msgstr "Détails"
 msgstr "Détails"
 
 
@@ -1166,7 +1176,12 @@ msgstr "Commentaires"
 msgid "Error"
 msgid "Error"
 msgstr "Erreur"
 msgstr "Erreur"
 
 
-#: src/routes/modules/nginx_log.ts:24 src/views/site/ngx_conf/LogEntry.vue:83
+#: src/views/nginx_log/NginxLogList.vue:30
+#, fuzzy
+msgid "Error Log"
+msgstr "Journaux d'erreurs"
+
+#: src/routes/modules/nginx_log.ts:24 src/views/site/ngx_conf/LogEntry.vue:99
 msgid "Error Logs"
 msgid "Error Logs"
 msgstr "Journaux d'erreurs"
 msgstr "Journaux d'erreurs"
 
 
@@ -1604,6 +1619,12 @@ msgstr ""
 msgid "If left blank, the default CA Dir will be used."
 msgid "If left blank, the default CA Dir will be used."
 msgstr "Si vide, le répertoire CA sera utilisé."
 msgstr "Si vide, le répertoire CA sera utilisé."
 
 
+#: src/views/nginx_log/NginxLogList.vue:127
+msgid ""
+"If logs are not indexed, please check if the log file is under the directory "
+"in Nginx.LogDirWhiteList."
+msgstr ""
+
 #: src/views/preference/AuthSettings.vue:145
 #: src/views/preference/AuthSettings.vue:145
 #, fuzzy
 #, fuzzy
 msgid ""
 msgid ""
@@ -1640,6 +1661,14 @@ msgstr "Exporter"
 msgid "Import Certificate"
 msgid "Import Certificate"
 msgstr "État du certificat"
 msgstr "État du certificat"
 
 
+#: src/views/nginx_log/NginxLogList.vue:135
+msgid "Indexed"
+msgstr ""
+
+#: src/views/nginx_log/NginxLogList.vue:132
+msgid "Indexing..."
+msgstr ""
+
 #: src/components/StdDesign/StdDetail/StdDetail.vue:81
 #: src/components/StdDesign/StdDetail/StdDetail.vue:81
 #: src/constants/index.ts:18 src/views/notification/notificationColumns.tsx:29
 #: src/constants/index.ts:18 src/views/notification/notificationColumns.tsx:29
 msgid "Info"
 msgid "Info"
@@ -1876,6 +1905,11 @@ msgstr "Localisations"
 msgid "Log"
 msgid "Log"
 msgstr "Connexion"
 msgstr "Connexion"
 
 
+#: src/routes/modules/nginx_log.ts:39 src/views/nginx_log/NginxLogList.vue:113
+#, fuzzy
+msgid "Log List"
+msgstr "Liste"
+
 #: src/routes/modules/auth.ts:14 src/views/other/Login.vue:222
 #: src/routes/modules/auth.ts:14 src/views/other/Login.vue:222
 msgid "Login"
 msgid "Login"
 msgstr "Connexion"
 msgstr "Connexion"
@@ -1989,6 +2023,7 @@ msgstr "Directive multiligne"
 #: src/views/config/components/Mkdir.vue:64
 #: src/views/config/components/Mkdir.vue:64
 #: src/views/config/configColumns.tsx:7 src/views/config/ConfigEditor.vue:256
 #: src/views/config/configColumns.tsx:7 src/views/config/ConfigEditor.vue:256
 #: src/views/environment/envColumns.tsx:9
 #: src/views/environment/envColumns.tsx:9
+#: src/views/nginx_log/NginxLogList.vue:35
 #: src/views/preference/components/AddPasskey.vue:75
 #: src/views/preference/components/AddPasskey.vue:75
 #: src/views/site/ngx_conf/NgxUpstream.vue:177
 #: src/views/site/ngx_conf/NgxUpstream.vue:177
 #: src/views/site/site_category/columns.ts:7
 #: src/views/site/site_category/columns.ts:7
@@ -2112,7 +2147,7 @@ msgstr "Chemin du journal des erreurs Nginx"
 msgid "Nginx is not running"
 msgid "Nginx is not running"
 msgstr ""
 msgstr ""
 
 
-#: src/routes/modules/nginx_log.ts:9 src/views/nginx_log/NginxLog.vue:148
+#: src/routes/modules/nginx_log.ts:9 src/views/nginx_log/NginxLog.vue:143
 msgid "Nginx Log"
 msgid "Nginx Log"
 msgstr "Journal Nginx"
 msgstr "Journal Nginx"
 
 
@@ -2368,6 +2403,7 @@ msgid "Password length cannot exceed 20 characters"
 msgstr ""
 msgstr ""
 
 
 #: src/views/config/ConfigEditor.vue:263
 #: src/views/config/ConfigEditor.vue:263
+#: src/views/nginx_log/NginxLogList.vue:43
 #: src/views/site/ngx_conf/LocationEditor.vue:109
 #: src/views/site/ngx_conf/LocationEditor.vue:109
 #: src/views/site/ngx_conf/LocationEditor.vue:137
 #: src/views/site/ngx_conf/LocationEditor.vue:137
 msgid "Path"
 msgid "Path"
@@ -3499,7 +3535,7 @@ msgstr ""
 msgid "Tips"
 msgid "Tips"
 msgstr ""
 msgstr ""
 
 
-#: src/views/notification/notificationColumns.tsx:44
+#: src/views/notification/notificationColumns.tsx:45
 msgid "Title"
 msgid "Title"
 msgstr ""
 msgstr ""
 
 
@@ -3564,6 +3600,7 @@ msgid "Two-factor authentication required"
 msgstr ""
 msgstr ""
 
 
 #: src/views/certificate/CertificateList/certColumns.tsx:25
 #: src/views/certificate/CertificateList/certColumns.tsx:25
+#: src/views/nginx_log/NginxLogList.vue:20
 #: src/views/notification/notificationColumns.tsx:9
 #: src/views/notification/notificationColumns.tsx:9
 msgid "Type"
 msgid "Type"
 msgstr "Type"
 msgstr "Type"
@@ -3670,6 +3707,7 @@ msgid "Version"
 msgstr "Version actuelle"
 msgstr "Version actuelle"
 
 
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:488
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:488
+#: src/views/nginx_log/NginxLogList.vue:143
 #: src/views/site/ngx_conf/config_template/ConfigTemplate.vue:103
 #: src/views/site/ngx_conf/config_template/ConfigTemplate.vue:103
 msgid "View"
 msgid "View"
 msgstr "Voir"
 msgstr "Voir"

+ 45 - 8
app/src/language/ko_KR/app.po

@@ -26,7 +26,12 @@ msgstr "2FA 설정"
 msgid "About"
 msgid "About"
 msgstr "대하여"
 msgstr "대하여"
 
 
-#: src/routes/modules/nginx_log.ts:17 src/views/site/ngx_conf/LogEntry.vue:75
+#: src/views/nginx_log/NginxLogList.vue:29
+#, fuzzy
+msgid "Access Log"
+msgstr "접근 로그"
+
+#: src/routes/modules/nginx_log.ts:17 src/views/site/ngx_conf/LogEntry.vue:91
 msgid "Access Logs"
 msgid "Access Logs"
 msgstr "접근 로그"
 msgstr "접근 로그"
 
 
@@ -40,7 +45,8 @@ msgstr "ACME 사용자"
 #: src/views/certificate/DNSCredential.vue:33
 #: src/views/certificate/DNSCredential.vue:33
 #: src/views/config/configColumns.tsx:42
 #: src/views/config/configColumns.tsx:42
 #: src/views/environment/envColumns.tsx:97
 #: src/views/environment/envColumns.tsx:97
-#: src/views/notification/notificationColumns.tsx:65
+#: src/views/nginx_log/NginxLogList.vue:51
+#: src/views/notification/notificationColumns.tsx:66
 #: src/views/preference/AuthSettings.vue:30
 #: src/views/preference/AuthSettings.vue:30
 #: src/views/site/site_category/columns.ts:28
 #: src/views/site/site_category/columns.ts:28
 #: src/views/site/site_list/columns.tsx:76 src/views/stream/StreamList.vue:49
 #: src/views/site/site_list/columns.tsx:76 src/views/stream/StreamList.vue:49
@@ -246,7 +252,7 @@ msgstr ""
 msgid "Author"
 msgid "Author"
 msgstr "저자"
 msgstr "저자"
 
 
-#: src/views/nginx_log/NginxLog.vue:152
+#: src/views/nginx_log/NginxLog.vue:149
 msgid "Auto Refresh"
 msgid "Auto Refresh"
 msgstr "자동 새로고침"
 msgstr "자동 새로고침"
 
 
@@ -262,6 +268,10 @@ msgstr "%{name}에 대한 자동 갱신 활성화됨"
 msgid "Automatic Restart"
 msgid "Automatic Restart"
 msgstr ""
 msgstr ""
 
 
+#: src/views/nginx_log/NginxLogList.vue:125
+msgid "Automatically indexed from site and stream configurations."
+msgstr ""
+
 #: src/views/certificate/CertificateEditor.vue:255
 #: src/views/certificate/CertificateEditor.vue:255
 #: src/views/config/ConfigEditor.vue:213 src/views/config/ConfigList.vue:106
 #: src/views/config/ConfigEditor.vue:213 src/views/config/ConfigList.vue:106
 #: src/views/config/ConfigList.vue:180 src/views/nginx_log/NginxLog.vue:173
 #: src/views/config/ConfigList.vue:180 src/views/nginx_log/NginxLog.vue:173
@@ -623,7 +633,7 @@ msgid ""
 "Backup files will be automatically downloaded to your computer."
 "Backup files will be automatically downloaded to your computer."
 msgstr ""
 msgstr ""
 
 
-#: src/views/notification/notificationColumns.tsx:58
+#: src/views/notification/notificationColumns.tsx:59
 #: src/views/preference/components/Passkey.vue:95
 #: src/views/preference/components/Passkey.vue:95
 #: src/views/site/site_category/columns.ts:16 src/views/user/userColumns.tsx:48
 #: src/views/site/site_category/columns.ts:16 src/views/user/userColumns.tsx:48
 msgid "Created at"
 msgid "Created at"
@@ -770,7 +780,7 @@ msgstr "설명"
 msgid "Destination file already exists"
 msgid "Destination file already exists"
 msgstr ""
 msgstr ""
 
 
-#: src/views/notification/notificationColumns.tsx:52
+#: src/views/notification/notificationColumns.tsx:53
 msgid "Details"
 msgid "Details"
 msgstr "세부 사항"
 msgstr "세부 사항"
 
 
@@ -1115,7 +1125,12 @@ msgstr "환경"
 msgid "Error"
 msgid "Error"
 msgstr "오류"
 msgstr "오류"
 
 
-#: src/routes/modules/nginx_log.ts:24 src/views/site/ngx_conf/LogEntry.vue:83
+#: src/views/nginx_log/NginxLogList.vue:30
+#, fuzzy
+msgid "Error Log"
+msgstr "오류 로그"
+
+#: src/routes/modules/nginx_log.ts:24 src/views/site/ngx_conf/LogEntry.vue:99
 msgid "Error Logs"
 msgid "Error Logs"
 msgstr "오류 로그"
 msgstr "오류 로그"
 
 
@@ -1543,6 +1558,12 @@ msgstr ""
 msgid "If left blank, the default CA Dir will be used."
 msgid "If left blank, the default CA Dir will be used."
 msgstr ""
 msgstr ""
 
 
+#: src/views/nginx_log/NginxLogList.vue:127
+msgid ""
+"If logs are not indexed, please check if the log file is under the directory "
+"in Nginx.LogDirWhiteList."
+msgstr ""
+
 #: src/views/preference/AuthSettings.vue:145
 #: src/views/preference/AuthSettings.vue:145
 msgid ""
 msgid ""
 "If the number of login failed attempts from a ip reach the max attempts in "
 "If the number of login failed attempts from a ip reach the max attempts in "
@@ -1569,6 +1590,14 @@ msgstr "가져오기"
 msgid "Import Certificate"
 msgid "Import Certificate"
 msgstr "인증서 상태"
 msgstr "인증서 상태"
 
 
+#: src/views/nginx_log/NginxLogList.vue:135
+msgid "Indexed"
+msgstr ""
+
+#: src/views/nginx_log/NginxLogList.vue:132
+msgid "Indexing..."
+msgstr ""
+
 #: src/components/StdDesign/StdDetail/StdDetail.vue:81
 #: src/components/StdDesign/StdDetail/StdDetail.vue:81
 #: src/constants/index.ts:18 src/views/notification/notificationColumns.tsx:29
 #: src/constants/index.ts:18 src/views/notification/notificationColumns.tsx:29
 msgid "Info"
 msgid "Info"
@@ -1801,6 +1830,10 @@ msgstr "위치들"
 msgid "Log"
 msgid "Log"
 msgstr "로그인"
 msgstr "로그인"
 
 
+#: src/routes/modules/nginx_log.ts:39 src/views/nginx_log/NginxLogList.vue:113
+msgid "Log List"
+msgstr ""
+
 #: src/routes/modules/auth.ts:14 src/views/other/Login.vue:222
 #: src/routes/modules/auth.ts:14 src/views/other/Login.vue:222
 msgid "Login"
 msgid "Login"
 msgstr "로그인"
 msgstr "로그인"
@@ -1921,6 +1954,7 @@ msgstr "단일 지시문"
 #: src/views/config/components/Mkdir.vue:64
 #: src/views/config/components/Mkdir.vue:64
 #: src/views/config/configColumns.tsx:7 src/views/config/ConfigEditor.vue:256
 #: src/views/config/configColumns.tsx:7 src/views/config/ConfigEditor.vue:256
 #: src/views/environment/envColumns.tsx:9
 #: src/views/environment/envColumns.tsx:9
+#: src/views/nginx_log/NginxLogList.vue:35
 #: src/views/preference/components/AddPasskey.vue:75
 #: src/views/preference/components/AddPasskey.vue:75
 #: src/views/site/ngx_conf/NgxUpstream.vue:177
 #: src/views/site/ngx_conf/NgxUpstream.vue:177
 #: src/views/site/site_category/columns.ts:7
 #: src/views/site/site_category/columns.ts:7
@@ -2044,7 +2078,7 @@ msgstr "Nginx 오류 로그 경로"
 msgid "Nginx is not running"
 msgid "Nginx is not running"
 msgstr ""
 msgstr ""
 
 
-#: src/routes/modules/nginx_log.ts:9 src/views/nginx_log/NginxLog.vue:148
+#: src/routes/modules/nginx_log.ts:9 src/views/nginx_log/NginxLog.vue:143
 msgid "Nginx Log"
 msgid "Nginx Log"
 msgstr "Nginx 로그"
 msgstr "Nginx 로그"
 
 
@@ -2301,6 +2335,7 @@ msgid "Password length cannot exceed 20 characters"
 msgstr ""
 msgstr ""
 
 
 #: src/views/config/ConfigEditor.vue:263
 #: src/views/config/ConfigEditor.vue:263
+#: src/views/nginx_log/NginxLogList.vue:43
 #: src/views/site/ngx_conf/LocationEditor.vue:109
 #: src/views/site/ngx_conf/LocationEditor.vue:109
 #: src/views/site/ngx_conf/LocationEditor.vue:137
 #: src/views/site/ngx_conf/LocationEditor.vue:137
 msgid "Path"
 msgid "Path"
@@ -3421,7 +3456,7 @@ msgstr ""
 msgid "Tips"
 msgid "Tips"
 msgstr "팁"
 msgstr "팁"
 
 
-#: src/views/notification/notificationColumns.tsx:44
+#: src/views/notification/notificationColumns.tsx:45
 msgid "Title"
 msgid "Title"
 msgstr "제목"
 msgstr "제목"
 
 
@@ -3485,6 +3520,7 @@ msgid "Two-factor authentication required"
 msgstr ""
 msgstr ""
 
 
 #: src/views/certificate/CertificateList/certColumns.tsx:25
 #: src/views/certificate/CertificateList/certColumns.tsx:25
+#: src/views/nginx_log/NginxLogList.vue:20
 #: src/views/notification/notificationColumns.tsx:9
 #: src/views/notification/notificationColumns.tsx:9
 msgid "Type"
 msgid "Type"
 msgstr "유형"
 msgstr "유형"
@@ -3593,6 +3629,7 @@ msgid "Version"
 msgstr "현재 버전"
 msgstr "현재 버전"
 
 
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:488
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:488
+#: src/views/nginx_log/NginxLogList.vue:143
 #: src/views/site/ngx_conf/config_template/ConfigTemplate.vue:103
 #: src/views/site/ngx_conf/config_template/ConfigTemplate.vue:103
 msgid "View"
 msgid "View"
 msgstr "보기"
 msgstr "보기"

+ 42 - 8
app/src/language/messages.pot

@@ -14,8 +14,12 @@ msgstr ""
 msgid "About"
 msgid "About"
 msgstr ""
 msgstr ""
 
 
+#: src/views/nginx_log/NginxLogList.vue:29
+msgid "Access Log"
+msgstr ""
+
 #: src/routes/modules/nginx_log.ts:17
 #: src/routes/modules/nginx_log.ts:17
-#: src/views/site/ngx_conf/LogEntry.vue:75
+#: src/views/site/ngx_conf/LogEntry.vue:91
 msgid "Access Logs"
 msgid "Access Logs"
 msgstr ""
 msgstr ""
 
 
@@ -30,7 +34,8 @@ msgstr ""
 #: src/views/certificate/DNSCredential.vue:33
 #: src/views/certificate/DNSCredential.vue:33
 #: src/views/config/configColumns.tsx:42
 #: src/views/config/configColumns.tsx:42
 #: src/views/environment/envColumns.tsx:97
 #: src/views/environment/envColumns.tsx:97
-#: src/views/notification/notificationColumns.tsx:65
+#: src/views/nginx_log/NginxLogList.vue:51
+#: src/views/notification/notificationColumns.tsx:66
 #: src/views/preference/AuthSettings.vue:30
 #: src/views/preference/AuthSettings.vue:30
 #: src/views/site/site_category/columns.ts:28
 #: src/views/site/site_category/columns.ts:28
 #: src/views/site/site_list/columns.tsx:76
 #: src/views/site/site_list/columns.tsx:76
@@ -232,7 +237,7 @@ msgstr ""
 msgid "Author"
 msgid "Author"
 msgstr ""
 msgstr ""
 
 
-#: src/views/nginx_log/NginxLog.vue:152
+#: src/views/nginx_log/NginxLog.vue:149
 msgid "Auto Refresh"
 msgid "Auto Refresh"
 msgstr ""
 msgstr ""
 
 
@@ -248,6 +253,10 @@ msgstr ""
 msgid "Automatic Restart"
 msgid "Automatic Restart"
 msgstr ""
 msgstr ""
 
 
+#: src/views/nginx_log/NginxLogList.vue:125
+msgid "Automatically indexed from site and stream configurations."
+msgstr ""
+
 #: src/views/certificate/CertificateEditor.vue:255
 #: src/views/certificate/CertificateEditor.vue:255
 #: src/views/config/ConfigEditor.vue:213
 #: src/views/config/ConfigEditor.vue:213
 #: src/views/config/ConfigList.vue:106
 #: src/views/config/ConfigList.vue:106
@@ -590,7 +599,7 @@ msgstr ""
 msgid "Create system backups including Nginx configuration and Nginx UI settings. Backup files will be automatically downloaded to your computer."
 msgid "Create system backups including Nginx configuration and Nginx UI settings. Backup files will be automatically downloaded to your computer."
 msgstr ""
 msgstr ""
 
 
-#: src/views/notification/notificationColumns.tsx:58
+#: src/views/notification/notificationColumns.tsx:59
 #: src/views/preference/components/Passkey.vue:95
 #: src/views/preference/components/Passkey.vue:95
 #: src/views/site/site_category/columns.ts:16
 #: src/views/site/site_category/columns.ts:16
 #: src/views/user/userColumns.tsx:48
 #: src/views/user/userColumns.tsx:48
@@ -731,7 +740,7 @@ msgstr ""
 msgid "Destination file already exists"
 msgid "Destination file already exists"
 msgstr ""
 msgstr ""
 
 
-#: src/views/notification/notificationColumns.tsx:52
+#: src/views/notification/notificationColumns.tsx:53
 msgid "Details"
 msgid "Details"
 msgstr ""
 msgstr ""
 
 
@@ -1060,8 +1069,12 @@ msgstr ""
 msgid "Error"
 msgid "Error"
 msgstr ""
 msgstr ""
 
 
+#: src/views/nginx_log/NginxLogList.vue:30
+msgid "Error Log"
+msgstr ""
+
 #: src/routes/modules/nginx_log.ts:24
 #: src/routes/modules/nginx_log.ts:24
-#: src/views/site/ngx_conf/LogEntry.vue:83
+#: src/views/site/ngx_conf/LogEntry.vue:99
 msgid "Error Logs"
 msgid "Error Logs"
 msgstr ""
 msgstr ""
 
 
@@ -1449,6 +1462,10 @@ msgstr ""
 msgid "If left blank, the default CA Dir will be used."
 msgid "If left blank, the default CA Dir will be used."
 msgstr ""
 msgstr ""
 
 
+#: src/views/nginx_log/NginxLogList.vue:127
+msgid "If logs are not indexed, please check if the log file is under the directory in Nginx.LogDirWhiteList."
+msgstr ""
+
 #: src/views/preference/AuthSettings.vue:145
 #: src/views/preference/AuthSettings.vue:145
 msgid "If the number of login failed attempts from a ip reach the max attempts in ban threshold minutes, the ip will be banned for a period of time."
 msgid "If the number of login failed attempts from a ip reach the max attempts in ban threshold minutes, the ip will be banned for a period of time."
 msgstr ""
 msgstr ""
@@ -1470,6 +1487,14 @@ msgstr ""
 msgid "Import Certificate"
 msgid "Import Certificate"
 msgstr ""
 msgstr ""
 
 
+#: src/views/nginx_log/NginxLogList.vue:135
+msgid "Indexed"
+msgstr ""
+
+#: src/views/nginx_log/NginxLogList.vue:132
+msgid "Indexing..."
+msgstr ""
+
 #: src/components/StdDesign/StdDetail/StdDetail.vue:81
 #: src/components/StdDesign/StdDetail/StdDetail.vue:81
 #: src/constants/index.ts:18
 #: src/constants/index.ts:18
 #: src/views/notification/notificationColumns.tsx:29
 #: src/views/notification/notificationColumns.tsx:29
@@ -1682,6 +1707,11 @@ msgstr ""
 msgid "Log"
 msgid "Log"
 msgstr ""
 msgstr ""
 
 
+#: src/routes/modules/nginx_log.ts:39
+#: src/views/nginx_log/NginxLogList.vue:113
+msgid "Log List"
+msgstr ""
+
 #: src/routes/modules/auth.ts:14
 #: src/routes/modules/auth.ts:14
 #: src/views/other/Login.vue:222
 #: src/views/other/Login.vue:222
 msgid "Login"
 msgid "Login"
@@ -1787,6 +1817,7 @@ msgstr ""
 #: src/views/config/configColumns.tsx:7
 #: src/views/config/configColumns.tsx:7
 #: src/views/config/ConfigEditor.vue:256
 #: src/views/config/ConfigEditor.vue:256
 #: src/views/environment/envColumns.tsx:9
 #: src/views/environment/envColumns.tsx:9
+#: src/views/nginx_log/NginxLogList.vue:35
 #: src/views/preference/components/AddPasskey.vue:75
 #: src/views/preference/components/AddPasskey.vue:75
 #: src/views/site/ngx_conf/NgxUpstream.vue:177
 #: src/views/site/ngx_conf/NgxUpstream.vue:177
 #: src/views/site/site_category/columns.ts:7
 #: src/views/site/site_category/columns.ts:7
@@ -1904,7 +1935,7 @@ msgid "Nginx is not running"
 msgstr ""
 msgstr ""
 
 
 #: src/routes/modules/nginx_log.ts:9
 #: src/routes/modules/nginx_log.ts:9
-#: src/views/nginx_log/NginxLog.vue:148
+#: src/views/nginx_log/NginxLog.vue:143
 msgid "Nginx Log"
 msgid "Nginx Log"
 msgstr ""
 msgstr ""
 
 
@@ -2139,6 +2170,7 @@ msgid "Password length cannot exceed 20 characters"
 msgstr ""
 msgstr ""
 
 
 #: src/views/config/ConfigEditor.vue:263
 #: src/views/config/ConfigEditor.vue:263
+#: src/views/nginx_log/NginxLogList.vue:43
 #: src/views/site/ngx_conf/LocationEditor.vue:109
 #: src/views/site/ngx_conf/LocationEditor.vue:109
 #: src/views/site/ngx_conf/LocationEditor.vue:137
 #: src/views/site/ngx_conf/LocationEditor.vue:137
 msgid "Path"
 msgid "Path"
@@ -3125,7 +3157,7 @@ msgstr ""
 msgid "Tips"
 msgid "Tips"
 msgstr ""
 msgstr ""
 
 
-#: src/views/notification/notificationColumns.tsx:44
+#: src/views/notification/notificationColumns.tsx:45
 msgid "Title"
 msgid "Title"
 msgstr ""
 msgstr ""
 
 
@@ -3172,6 +3204,7 @@ msgid "Two-factor authentication required"
 msgstr ""
 msgstr ""
 
 
 #: src/views/certificate/CertificateList/certColumns.tsx:25
 #: src/views/certificate/CertificateList/certColumns.tsx:25
+#: src/views/nginx_log/NginxLogList.vue:20
 #: src/views/notification/notificationColumns.tsx:9
 #: src/views/notification/notificationColumns.tsx:9
 msgid "Type"
 msgid "Type"
 msgstr ""
 msgstr ""
@@ -3279,6 +3312,7 @@ msgid "Version"
 msgstr ""
 msgstr ""
 
 
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:488
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:488
+#: src/views/nginx_log/NginxLogList.vue:143
 #: src/views/site/ngx_conf/config_template/ConfigTemplate.vue:103
 #: src/views/site/ngx_conf/config_template/ConfigTemplate.vue:103
 msgid "View"
 msgid "View"
 msgstr ""
 msgstr ""

+ 46 - 8
app/src/language/ru_RU/app.po

@@ -28,7 +28,12 @@ msgstr "Настройки 2FA"
 msgid "About"
 msgid "About"
 msgstr "О проекте"
 msgstr "О проекте"
 
 
-#: src/routes/modules/nginx_log.ts:17 src/views/site/ngx_conf/LogEntry.vue:75
+#: src/views/nginx_log/NginxLogList.vue:29
+#, fuzzy
+msgid "Access Log"
+msgstr "Журналы доступа"
+
+#: src/routes/modules/nginx_log.ts:17 src/views/site/ngx_conf/LogEntry.vue:91
 msgid "Access Logs"
 msgid "Access Logs"
 msgstr "Журналы доступа"
 msgstr "Журналы доступа"
 
 
@@ -42,7 +47,8 @@ msgstr "Пользователь ACME"
 #: src/views/certificate/DNSCredential.vue:33
 #: src/views/certificate/DNSCredential.vue:33
 #: src/views/config/configColumns.tsx:42
 #: src/views/config/configColumns.tsx:42
 #: src/views/environment/envColumns.tsx:97
 #: src/views/environment/envColumns.tsx:97
-#: src/views/notification/notificationColumns.tsx:65
+#: src/views/nginx_log/NginxLogList.vue:51
+#: src/views/notification/notificationColumns.tsx:66
 #: src/views/preference/AuthSettings.vue:30
 #: src/views/preference/AuthSettings.vue:30
 #: src/views/site/site_category/columns.ts:28
 #: src/views/site/site_category/columns.ts:28
 #: src/views/site/site_list/columns.tsx:76 src/views/stream/StreamList.vue:49
 #: src/views/site/site_list/columns.tsx:76 src/views/stream/StreamList.vue:49
@@ -243,7 +249,7 @@ msgstr "Настройки аутентификации"
 msgid "Author"
 msgid "Author"
 msgstr "Автор"
 msgstr "Автор"
 
 
-#: src/views/nginx_log/NginxLog.vue:152
+#: src/views/nginx_log/NginxLog.vue:149
 msgid "Auto Refresh"
 msgid "Auto Refresh"
 msgstr "Автообновление"
 msgstr "Автообновление"
 
 
@@ -259,6 +265,10 @@ msgstr "Автообновление включено для %{name}"
 msgid "Automatic Restart"
 msgid "Automatic Restart"
 msgstr ""
 msgstr ""
 
 
+#: src/views/nginx_log/NginxLogList.vue:125
+msgid "Automatically indexed from site and stream configurations."
+msgstr ""
+
 #: src/views/certificate/CertificateEditor.vue:255
 #: src/views/certificate/CertificateEditor.vue:255
 #: src/views/config/ConfigEditor.vue:213 src/views/config/ConfigList.vue:106
 #: src/views/config/ConfigEditor.vue:213 src/views/config/ConfigList.vue:106
 #: src/views/config/ConfigList.vue:180 src/views/nginx_log/NginxLog.vue:173
 #: src/views/config/ConfigList.vue:180 src/views/nginx_log/NginxLog.vue:173
@@ -613,7 +623,7 @@ msgid ""
 "Backup files will be automatically downloaded to your computer."
 "Backup files will be automatically downloaded to your computer."
 msgstr ""
 msgstr ""
 
 
-#: src/views/notification/notificationColumns.tsx:58
+#: src/views/notification/notificationColumns.tsx:59
 #: src/views/preference/components/Passkey.vue:95
 #: src/views/preference/components/Passkey.vue:95
 #: src/views/site/site_category/columns.ts:16 src/views/user/userColumns.tsx:48
 #: src/views/site/site_category/columns.ts:16 src/views/user/userColumns.tsx:48
 msgid "Created at"
 msgid "Created at"
@@ -757,7 +767,7 @@ msgstr "Описание"
 msgid "Destination file already exists"
 msgid "Destination file already exists"
 msgstr ""
 msgstr ""
 
 
-#: src/views/notification/notificationColumns.tsx:52
+#: src/views/notification/notificationColumns.tsx:53
 msgid "Details"
 msgid "Details"
 msgstr "Детали"
 msgstr "Детали"
 
 
@@ -1100,7 +1110,12 @@ msgstr "Окружения"
 msgid "Error"
 msgid "Error"
 msgstr "Ошибка"
 msgstr "Ошибка"
 
 
-#: src/routes/modules/nginx_log.ts:24 src/views/site/ngx_conf/LogEntry.vue:83
+#: src/views/nginx_log/NginxLogList.vue:30
+#, fuzzy
+msgid "Error Log"
+msgstr "Ошибка логирования"
+
+#: src/routes/modules/nginx_log.ts:24 src/views/site/ngx_conf/LogEntry.vue:99
 msgid "Error Logs"
 msgid "Error Logs"
 msgstr "Ошибка логирования"
 msgstr "Ошибка логирования"
 
 
@@ -1526,6 +1541,12 @@ msgstr "ICP номер"
 msgid "If left blank, the default CA Dir will be used."
 msgid "If left blank, the default CA Dir will be used."
 msgstr "Если оставить пустым, будет использоваться каталог CA по умолчанию."
 msgstr "Если оставить пустым, будет использоваться каталог CA по умолчанию."
 
 
+#: src/views/nginx_log/NginxLogList.vue:127
+msgid ""
+"If logs are not indexed, please check if the log file is under the directory "
+"in Nginx.LogDirWhiteList."
+msgstr ""
+
 #: src/views/preference/AuthSettings.vue:145
 #: src/views/preference/AuthSettings.vue:145
 msgid ""
 msgid ""
 "If the number of login failed attempts from a ip reach the max attempts in "
 "If the number of login failed attempts from a ip reach the max attempts in "
@@ -1557,6 +1578,14 @@ msgstr "Импорт"
 msgid "Import Certificate"
 msgid "Import Certificate"
 msgstr "Импортировать сертификат"
 msgstr "Импортировать сертификат"
 
 
+#: src/views/nginx_log/NginxLogList.vue:135
+msgid "Indexed"
+msgstr ""
+
+#: src/views/nginx_log/NginxLogList.vue:132
+msgid "Indexing..."
+msgstr ""
+
 #: src/components/StdDesign/StdDetail/StdDetail.vue:81
 #: src/components/StdDesign/StdDetail/StdDetail.vue:81
 #: src/constants/index.ts:18 src/views/notification/notificationColumns.tsx:29
 #: src/constants/index.ts:18 src/views/notification/notificationColumns.tsx:29
 msgid "Info"
 msgid "Info"
@@ -1776,6 +1805,11 @@ msgstr "Локации"
 msgid "Log"
 msgid "Log"
 msgstr "Журнал"
 msgstr "Журнал"
 
 
+#: src/routes/modules/nginx_log.ts:39 src/views/nginx_log/NginxLogList.vue:113
+#, fuzzy
+msgid "Log List"
+msgstr "Список"
+
 #: src/routes/modules/auth.ts:14 src/views/other/Login.vue:222
 #: src/routes/modules/auth.ts:14 src/views/other/Login.vue:222
 msgid "Login"
 msgid "Login"
 msgstr "Логин"
 msgstr "Логин"
@@ -1889,6 +1923,7 @@ msgstr "Многострочная директива"
 #: src/views/config/components/Mkdir.vue:64
 #: src/views/config/components/Mkdir.vue:64
 #: src/views/config/configColumns.tsx:7 src/views/config/ConfigEditor.vue:256
 #: src/views/config/configColumns.tsx:7 src/views/config/ConfigEditor.vue:256
 #: src/views/environment/envColumns.tsx:9
 #: src/views/environment/envColumns.tsx:9
+#: src/views/nginx_log/NginxLogList.vue:35
 #: src/views/preference/components/AddPasskey.vue:75
 #: src/views/preference/components/AddPasskey.vue:75
 #: src/views/site/ngx_conf/NgxUpstream.vue:177
 #: src/views/site/ngx_conf/NgxUpstream.vue:177
 #: src/views/site/site_category/columns.ts:7
 #: src/views/site/site_category/columns.ts:7
@@ -2009,7 +2044,7 @@ msgstr "Путь для Nginx Error Log"
 msgid "Nginx is not running"
 msgid "Nginx is not running"
 msgstr "Nginx не работает"
 msgstr "Nginx не работает"
 
 
-#: src/routes/modules/nginx_log.ts:9 src/views/nginx_log/NginxLog.vue:148
+#: src/routes/modules/nginx_log.ts:9 src/views/nginx_log/NginxLog.vue:143
 msgid "Nginx Log"
 msgid "Nginx Log"
 msgstr "Журнал"
 msgstr "Журнал"
 
 
@@ -2263,6 +2298,7 @@ msgid "Password length cannot exceed 20 characters"
 msgstr ""
 msgstr ""
 
 
 #: src/views/config/ConfigEditor.vue:263
 #: src/views/config/ConfigEditor.vue:263
+#: src/views/nginx_log/NginxLogList.vue:43
 #: src/views/site/ngx_conf/LocationEditor.vue:109
 #: src/views/site/ngx_conf/LocationEditor.vue:109
 #: src/views/site/ngx_conf/LocationEditor.vue:137
 #: src/views/site/ngx_conf/LocationEditor.vue:137
 msgid "Path"
 msgid "Path"
@@ -3375,7 +3411,7 @@ msgstr ""
 msgid "Tips"
 msgid "Tips"
 msgstr "Советы"
 msgstr "Советы"
 
 
-#: src/views/notification/notificationColumns.tsx:44
+#: src/views/notification/notificationColumns.tsx:45
 msgid "Title"
 msgid "Title"
 msgstr "Заголовок"
 msgstr "Заголовок"
 
 
@@ -3444,6 +3480,7 @@ msgid "Two-factor authentication required"
 msgstr "Требуется двухфакторная аутентификация"
 msgstr "Требуется двухфакторная аутентификация"
 
 
 #: src/views/certificate/CertificateList/certColumns.tsx:25
 #: src/views/certificate/CertificateList/certColumns.tsx:25
+#: src/views/nginx_log/NginxLogList.vue:20
 #: src/views/notification/notificationColumns.tsx:9
 #: src/views/notification/notificationColumns.tsx:9
 msgid "Type"
 msgid "Type"
 msgstr "Тип"
 msgstr "Тип"
@@ -3547,6 +3584,7 @@ msgid "Version"
 msgstr "Версия"
 msgstr "Версия"
 
 
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:488
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:488
+#: src/views/nginx_log/NginxLogList.vue:143
 #: src/views/site/ngx_conf/config_template/ConfigTemplate.vue:103
 #: src/views/site/ngx_conf/config_template/ConfigTemplate.vue:103
 msgid "View"
 msgid "View"
 msgstr "Просмотр"
 msgstr "Просмотр"

+ 46 - 8
app/src/language/tr_TR/app.po

@@ -24,7 +24,12 @@ msgstr "2FA Ayarları"
 msgid "About"
 msgid "About"
 msgstr "Hakkında"
 msgstr "Hakkında"
 
 
-#: src/routes/modules/nginx_log.ts:17 src/views/site/ngx_conf/LogEntry.vue:75
+#: src/views/nginx_log/NginxLogList.vue:29
+#, fuzzy
+msgid "Access Log"
+msgstr "Erişim Günlükleri"
+
+#: src/routes/modules/nginx_log.ts:17 src/views/site/ngx_conf/LogEntry.vue:91
 msgid "Access Logs"
 msgid "Access Logs"
 msgstr "Erişim Günlükleri"
 msgstr "Erişim Günlükleri"
 
 
@@ -38,7 +43,8 @@ msgstr "ACME Kullanıcısı"
 #: src/views/certificate/DNSCredential.vue:33
 #: src/views/certificate/DNSCredential.vue:33
 #: src/views/config/configColumns.tsx:42
 #: src/views/config/configColumns.tsx:42
 #: src/views/environment/envColumns.tsx:97
 #: src/views/environment/envColumns.tsx:97
-#: src/views/notification/notificationColumns.tsx:65
+#: src/views/nginx_log/NginxLogList.vue:51
+#: src/views/notification/notificationColumns.tsx:66
 #: src/views/preference/AuthSettings.vue:30
 #: src/views/preference/AuthSettings.vue:30
 #: src/views/site/site_category/columns.ts:28
 #: src/views/site/site_category/columns.ts:28
 #: src/views/site/site_list/columns.tsx:76 src/views/stream/StreamList.vue:49
 #: src/views/site/site_list/columns.tsx:76 src/views/stream/StreamList.vue:49
@@ -244,7 +250,7 @@ msgstr "Kimlik Doğrulama Ayarları"
 msgid "Author"
 msgid "Author"
 msgstr "Yazar"
 msgstr "Yazar"
 
 
-#: src/views/nginx_log/NginxLog.vue:152
+#: src/views/nginx_log/NginxLog.vue:149
 msgid "Auto Refresh"
 msgid "Auto Refresh"
 msgstr "Otomatik Yenileme"
 msgstr "Otomatik Yenileme"
 
 
@@ -260,6 +266,10 @@ msgstr "Otomatik yenileme %{name} için etkinleştirildi"
 msgid "Automatic Restart"
 msgid "Automatic Restart"
 msgstr ""
 msgstr ""
 
 
+#: src/views/nginx_log/NginxLogList.vue:125
+msgid "Automatically indexed from site and stream configurations."
+msgstr ""
+
 #: src/views/certificate/CertificateEditor.vue:255
 #: src/views/certificate/CertificateEditor.vue:255
 #: src/views/config/ConfigEditor.vue:213 src/views/config/ConfigList.vue:106
 #: src/views/config/ConfigEditor.vue:213 src/views/config/ConfigList.vue:106
 #: src/views/config/ConfigList.vue:180 src/views/nginx_log/NginxLog.vue:173
 #: src/views/config/ConfigList.vue:180 src/views/nginx_log/NginxLog.vue:173
@@ -619,7 +629,7 @@ msgid ""
 "Backup files will be automatically downloaded to your computer."
 "Backup files will be automatically downloaded to your computer."
 msgstr ""
 msgstr ""
 
 
-#: src/views/notification/notificationColumns.tsx:58
+#: src/views/notification/notificationColumns.tsx:59
 #: src/views/preference/components/Passkey.vue:95
 #: src/views/preference/components/Passkey.vue:95
 #: src/views/site/site_category/columns.ts:16 src/views/user/userColumns.tsx:48
 #: src/views/site/site_category/columns.ts:16 src/views/user/userColumns.tsx:48
 msgid "Created at"
 msgid "Created at"
@@ -768,7 +778,7 @@ msgstr "Açıklama"
 msgid "Destination file already exists"
 msgid "Destination file already exists"
 msgstr ""
 msgstr ""
 
 
-#: src/views/notification/notificationColumns.tsx:52
+#: src/views/notification/notificationColumns.tsx:53
 msgid "Details"
 msgid "Details"
 msgstr "Detaylar"
 msgstr "Detaylar"
 
 
@@ -1129,7 +1139,12 @@ msgstr "Ortamlar"
 msgid "Error"
 msgid "Error"
 msgstr "Hata"
 msgstr "Hata"
 
 
-#: src/routes/modules/nginx_log.ts:24 src/views/site/ngx_conf/LogEntry.vue:83
+#: src/views/nginx_log/NginxLogList.vue:30
+#, fuzzy
+msgid "Error Log"
+msgstr "Hata Günlükleri"
+
+#: src/routes/modules/nginx_log.ts:24 src/views/site/ngx_conf/LogEntry.vue:99
 msgid "Error Logs"
 msgid "Error Logs"
 msgstr "Hata Günlükleri"
 msgstr "Hata Günlükleri"
 
 
@@ -1555,6 +1570,12 @@ msgstr ""
 msgid "If left blank, the default CA Dir will be used."
 msgid "If left blank, the default CA Dir will be used."
 msgstr "Boş bırakılırsa, varsayılan CA Dir kullanılır."
 msgstr "Boş bırakılırsa, varsayılan CA Dir kullanılır."
 
 
+#: src/views/nginx_log/NginxLogList.vue:127
+msgid ""
+"If logs are not indexed, please check if the log file is under the directory "
+"in Nginx.LogDirWhiteList."
+msgstr ""
+
 #: src/views/preference/AuthSettings.vue:145
 #: src/views/preference/AuthSettings.vue:145
 msgid ""
 msgid ""
 "If the number of login failed attempts from a ip reach the max attempts in "
 "If the number of login failed attempts from a ip reach the max attempts in "
@@ -1587,6 +1608,14 @@ msgstr "İçe Aktar"
 msgid "Import Certificate"
 msgid "Import Certificate"
 msgstr "Sertifika İçe Aktar"
 msgstr "Sertifika İçe Aktar"
 
 
+#: src/views/nginx_log/NginxLogList.vue:135
+msgid "Indexed"
+msgstr ""
+
+#: src/views/nginx_log/NginxLogList.vue:132
+msgid "Indexing..."
+msgstr ""
+
 #: src/components/StdDesign/StdDetail/StdDetail.vue:81
 #: src/components/StdDesign/StdDetail/StdDetail.vue:81
 #: src/constants/index.ts:18 src/views/notification/notificationColumns.tsx:29
 #: src/constants/index.ts:18 src/views/notification/notificationColumns.tsx:29
 msgid "Info"
 msgid "Info"
@@ -1807,6 +1836,11 @@ msgstr "Konumlar"
 msgid "Log"
 msgid "Log"
 msgstr "Günlük"
 msgstr "Günlük"
 
 
+#: src/routes/modules/nginx_log.ts:39 src/views/nginx_log/NginxLogList.vue:113
+#, fuzzy
+msgid "Log List"
+msgstr "Liste"
+
 #: src/routes/modules/auth.ts:14 src/views/other/Login.vue:222
 #: src/routes/modules/auth.ts:14 src/views/other/Login.vue:222
 msgid "Login"
 msgid "Login"
 msgstr "Giriş"
 msgstr "Giriş"
@@ -1936,6 +1970,7 @@ msgstr "Çok Hatlı Direktif"
 #: src/views/config/components/Mkdir.vue:64
 #: src/views/config/components/Mkdir.vue:64
 #: src/views/config/configColumns.tsx:7 src/views/config/ConfigEditor.vue:256
 #: src/views/config/configColumns.tsx:7 src/views/config/ConfigEditor.vue:256
 #: src/views/environment/envColumns.tsx:9
 #: src/views/environment/envColumns.tsx:9
+#: src/views/nginx_log/NginxLogList.vue:35
 #: src/views/preference/components/AddPasskey.vue:75
 #: src/views/preference/components/AddPasskey.vue:75
 #: src/views/site/ngx_conf/NgxUpstream.vue:177
 #: src/views/site/ngx_conf/NgxUpstream.vue:177
 #: src/views/site/site_category/columns.ts:7
 #: src/views/site/site_category/columns.ts:7
@@ -2071,7 +2106,7 @@ msgstr "Nginx Hata Günlüğü Yolu"
 msgid "Nginx is not running"
 msgid "Nginx is not running"
 msgstr "Nginx çalışmıyor"
 msgstr "Nginx çalışmıyor"
 
 
-#: src/routes/modules/nginx_log.ts:9 src/views/nginx_log/NginxLog.vue:148
+#: src/routes/modules/nginx_log.ts:9 src/views/nginx_log/NginxLog.vue:143
 #, fuzzy
 #, fuzzy
 msgid "Nginx Log"
 msgid "Nginx Log"
 msgstr "Nginx Günlüğü"
 msgstr "Nginx Günlüğü"
@@ -2360,6 +2395,7 @@ msgid "Password length cannot exceed 20 characters"
 msgstr ""
 msgstr ""
 
 
 #: src/views/config/ConfigEditor.vue:263
 #: src/views/config/ConfigEditor.vue:263
+#: src/views/nginx_log/NginxLogList.vue:43
 #: src/views/site/ngx_conf/LocationEditor.vue:109
 #: src/views/site/ngx_conf/LocationEditor.vue:109
 #: src/views/site/ngx_conf/LocationEditor.vue:137
 #: src/views/site/ngx_conf/LocationEditor.vue:137
 #, fuzzy
 #, fuzzy
@@ -3603,7 +3639,7 @@ msgstr ""
 msgid "Tips"
 msgid "Tips"
 msgstr "İpuçları"
 msgstr "İpuçları"
 
 
-#: src/views/notification/notificationColumns.tsx:44
+#: src/views/notification/notificationColumns.tsx:45
 #, fuzzy
 #, fuzzy
 msgid "Title"
 msgid "Title"
 msgstr "Başlık"
 msgstr "Başlık"
@@ -3688,6 +3724,7 @@ msgid "Two-factor authentication required"
 msgstr "İki faktörlü kimlik doğrulama gerekiyor"
 msgstr "İki faktörlü kimlik doğrulama gerekiyor"
 
 
 #: src/views/certificate/CertificateList/certColumns.tsx:25
 #: src/views/certificate/CertificateList/certColumns.tsx:25
+#: src/views/nginx_log/NginxLogList.vue:20
 #: src/views/notification/notificationColumns.tsx:9
 #: src/views/notification/notificationColumns.tsx:9
 #, fuzzy
 #, fuzzy
 msgid "Type"
 msgid "Type"
@@ -3808,6 +3845,7 @@ msgid "Version"
 msgstr "Versiyon"
 msgstr "Versiyon"
 
 
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:488
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:488
+#: src/views/nginx_log/NginxLogList.vue:143
 #: src/views/site/ngx_conf/config_template/ConfigTemplate.vue:103
 #: src/views/site/ngx_conf/config_template/ConfigTemplate.vue:103
 #, fuzzy
 #, fuzzy
 msgid "View"
 msgid "View"

+ 45 - 8
app/src/language/vi_VN/app.po

@@ -21,7 +21,12 @@ msgstr ""
 msgid "About"
 msgid "About"
 msgstr "Tác giả"
 msgstr "Tác giả"
 
 
-#: src/routes/modules/nginx_log.ts:17 src/views/site/ngx_conf/LogEntry.vue:75
+#: src/views/nginx_log/NginxLogList.vue:29
+#, fuzzy
+msgid "Access Log"
+msgstr "Log truy cập"
+
+#: src/routes/modules/nginx_log.ts:17 src/views/site/ngx_conf/LogEntry.vue:91
 msgid "Access Logs"
 msgid "Access Logs"
 msgstr "Log truy cập"
 msgstr "Log truy cập"
 
 
@@ -36,7 +41,8 @@ msgstr "Người dùng"
 #: src/views/certificate/DNSCredential.vue:33
 #: src/views/certificate/DNSCredential.vue:33
 #: src/views/config/configColumns.tsx:42
 #: src/views/config/configColumns.tsx:42
 #: src/views/environment/envColumns.tsx:97
 #: src/views/environment/envColumns.tsx:97
-#: src/views/notification/notificationColumns.tsx:65
+#: src/views/nginx_log/NginxLogList.vue:51
+#: src/views/notification/notificationColumns.tsx:66
 #: src/views/preference/AuthSettings.vue:30
 #: src/views/preference/AuthSettings.vue:30
 #: src/views/site/site_category/columns.ts:28
 #: src/views/site/site_category/columns.ts:28
 #: src/views/site/site_list/columns.tsx:76 src/views/stream/StreamList.vue:49
 #: src/views/site/site_list/columns.tsx:76 src/views/stream/StreamList.vue:49
@@ -255,7 +261,7 @@ msgstr ""
 msgid "Author"
 msgid "Author"
 msgstr "Tác giả"
 msgstr "Tác giả"
 
 
-#: src/views/nginx_log/NginxLog.vue:152
+#: src/views/nginx_log/NginxLog.vue:149
 msgid "Auto Refresh"
 msgid "Auto Refresh"
 msgstr "Tự động làm mới"
 msgstr "Tự động làm mới"
 
 
@@ -271,6 +277,10 @@ msgstr "Đã bật tự động gia hạn SSL cho %{name}"
 msgid "Automatic Restart"
 msgid "Automatic Restart"
 msgstr ""
 msgstr ""
 
 
+#: src/views/nginx_log/NginxLogList.vue:125
+msgid "Automatically indexed from site and stream configurations."
+msgstr ""
+
 #: src/views/certificate/CertificateEditor.vue:255
 #: src/views/certificate/CertificateEditor.vue:255
 #: src/views/config/ConfigEditor.vue:213 src/views/config/ConfigList.vue:106
 #: src/views/config/ConfigEditor.vue:213 src/views/config/ConfigList.vue:106
 #: src/views/config/ConfigList.vue:180 src/views/nginx_log/NginxLog.vue:173
 #: src/views/config/ConfigList.vue:180 src/views/nginx_log/NginxLog.vue:173
@@ -645,7 +655,7 @@ msgid ""
 "Backup files will be automatically downloaded to your computer."
 "Backup files will be automatically downloaded to your computer."
 msgstr ""
 msgstr ""
 
 
-#: src/views/notification/notificationColumns.tsx:58
+#: src/views/notification/notificationColumns.tsx:59
 #: src/views/preference/components/Passkey.vue:95
 #: src/views/preference/components/Passkey.vue:95
 #: src/views/site/site_category/columns.ts:16 src/views/user/userColumns.tsx:48
 #: src/views/site/site_category/columns.ts:16 src/views/user/userColumns.tsx:48
 msgid "Created at"
 msgid "Created at"
@@ -794,7 +804,7 @@ msgstr "Mô tả"
 msgid "Destination file already exists"
 msgid "Destination file already exists"
 msgstr ""
 msgstr ""
 
 
-#: src/views/notification/notificationColumns.tsx:52
+#: src/views/notification/notificationColumns.tsx:53
 msgid "Details"
 msgid "Details"
 msgstr "Chi tiết"
 msgstr "Chi tiết"
 
 
@@ -1152,7 +1162,12 @@ msgstr "Environments"
 msgid "Error"
 msgid "Error"
 msgstr "Lỗi"
 msgstr "Lỗi"
 
 
-#: src/routes/modules/nginx_log.ts:24 src/views/site/ngx_conf/LogEntry.vue:83
+#: src/views/nginx_log/NginxLogList.vue:30
+#, fuzzy
+msgid "Error Log"
+msgstr "Log lỗi"
+
+#: src/routes/modules/nginx_log.ts:24 src/views/site/ngx_conf/LogEntry.vue:99
 msgid "Error Logs"
 msgid "Error Logs"
 msgstr "Log lỗi"
 msgstr "Log lỗi"
 
 
@@ -1579,6 +1594,12 @@ msgstr ""
 msgid "If left blank, the default CA Dir will be used."
 msgid "If left blank, the default CA Dir will be used."
 msgstr ""
 msgstr ""
 
 
+#: src/views/nginx_log/NginxLogList.vue:127
+msgid ""
+"If logs are not indexed, please check if the log file is under the directory "
+"in Nginx.LogDirWhiteList."
+msgstr ""
+
 #: src/views/preference/AuthSettings.vue:145
 #: src/views/preference/AuthSettings.vue:145
 msgid ""
 msgid ""
 "If the number of login failed attempts from a ip reach the max attempts in "
 "If the number of login failed attempts from a ip reach the max attempts in "
@@ -1606,6 +1627,14 @@ msgstr "Xuất"
 msgid "Import Certificate"
 msgid "Import Certificate"
 msgstr "Chứng chỉ"
 msgstr "Chứng chỉ"
 
 
+#: src/views/nginx_log/NginxLogList.vue:135
+msgid "Indexed"
+msgstr ""
+
+#: src/views/nginx_log/NginxLogList.vue:132
+msgid "Indexing..."
+msgstr ""
+
 #: src/components/StdDesign/StdDetail/StdDetail.vue:81
 #: src/components/StdDesign/StdDetail/StdDetail.vue:81
 #: src/constants/index.ts:18 src/views/notification/notificationColumns.tsx:29
 #: src/constants/index.ts:18 src/views/notification/notificationColumns.tsx:29
 msgid "Info"
 msgid "Info"
@@ -1839,6 +1868,10 @@ msgstr "Locations"
 msgid "Log"
 msgid "Log"
 msgstr "Log"
 msgstr "Log"
 
 
+#: src/routes/modules/nginx_log.ts:39 src/views/nginx_log/NginxLogList.vue:113
+msgid "Log List"
+msgstr ""
+
 #: src/routes/modules/auth.ts:14 src/views/other/Login.vue:222
 #: src/routes/modules/auth.ts:14 src/views/other/Login.vue:222
 msgid "Login"
 msgid "Login"
 msgstr "Đăng nhập"
 msgstr "Đăng nhập"
@@ -1953,6 +1986,7 @@ msgstr "Single Directive"
 #: src/views/config/components/Mkdir.vue:64
 #: src/views/config/components/Mkdir.vue:64
 #: src/views/config/configColumns.tsx:7 src/views/config/ConfigEditor.vue:256
 #: src/views/config/configColumns.tsx:7 src/views/config/ConfigEditor.vue:256
 #: src/views/environment/envColumns.tsx:9
 #: src/views/environment/envColumns.tsx:9
+#: src/views/nginx_log/NginxLogList.vue:35
 #: src/views/preference/components/AddPasskey.vue:75
 #: src/views/preference/components/AddPasskey.vue:75
 #: src/views/site/ngx_conf/NgxUpstream.vue:177
 #: src/views/site/ngx_conf/NgxUpstream.vue:177
 #: src/views/site/site_category/columns.ts:7
 #: src/views/site/site_category/columns.ts:7
@@ -2076,7 +2110,7 @@ msgstr "Vị trí lưu log lỗi (Error log) của Nginx"
 msgid "Nginx is not running"
 msgid "Nginx is not running"
 msgstr ""
 msgstr ""
 
 
-#: src/routes/modules/nginx_log.ts:9 src/views/nginx_log/NginxLog.vue:148
+#: src/routes/modules/nginx_log.ts:9 src/views/nginx_log/NginxLog.vue:143
 msgid "Nginx Log"
 msgid "Nginx Log"
 msgstr ""
 msgstr ""
 
 
@@ -2331,6 +2365,7 @@ msgid "Password length cannot exceed 20 characters"
 msgstr ""
 msgstr ""
 
 
 #: src/views/config/ConfigEditor.vue:263
 #: src/views/config/ConfigEditor.vue:263
+#: src/views/nginx_log/NginxLogList.vue:43
 #: src/views/site/ngx_conf/LocationEditor.vue:109
 #: src/views/site/ngx_conf/LocationEditor.vue:109
 #: src/views/site/ngx_conf/LocationEditor.vue:137
 #: src/views/site/ngx_conf/LocationEditor.vue:137
 msgid "Path"
 msgid "Path"
@@ -3443,7 +3478,7 @@ msgstr ""
 msgid "Tips"
 msgid "Tips"
 msgstr ""
 msgstr ""
 
 
-#: src/views/notification/notificationColumns.tsx:44
+#: src/views/notification/notificationColumns.tsx:45
 msgid "Title"
 msgid "Title"
 msgstr "Tiêu đề"
 msgstr "Tiêu đề"
 
 
@@ -3508,6 +3543,7 @@ msgid "Two-factor authentication required"
 msgstr ""
 msgstr ""
 
 
 #: src/views/certificate/CertificateList/certColumns.tsx:25
 #: src/views/certificate/CertificateList/certColumns.tsx:25
+#: src/views/nginx_log/NginxLogList.vue:20
 #: src/views/notification/notificationColumns.tsx:9
 #: src/views/notification/notificationColumns.tsx:9
 msgid "Type"
 msgid "Type"
 msgstr "Loại"
 msgstr "Loại"
@@ -3616,6 +3652,7 @@ msgid "Version"
 msgstr "Phiên bản hiện tại"
 msgstr "Phiên bản hiện tại"
 
 
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:488
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:488
+#: src/views/nginx_log/NginxLogList.vue:143
 #: src/views/site/ngx_conf/config_template/ConfigTemplate.vue:103
 #: src/views/site/ngx_conf/config_template/ConfigTemplate.vue:103
 msgid "View"
 msgid "View"
 msgstr "Xem"
 msgstr "Xem"

+ 45 - 12
app/src/language/zh_CN/app.po

@@ -3,7 +3,7 @@ msgid ""
 msgstr ""
 msgstr ""
 "Project-Id-Version: \n"
 "Project-Id-Version: \n"
 "POT-Creation-Date: \n"
 "POT-Creation-Date: \n"
-"PO-Revision-Date: 2025-04-02 11:07+0800\n"
+"PO-Revision-Date: 2025-04-02 18:55+0800\n"
 "Last-Translator: 0xJacky <me@jackyu.cn>\n"
 "Last-Translator: 0xJacky <me@jackyu.cn>\n"
 "Language-Team: Chinese (Simplified Han script) <https://weblate.nginxui.com/"
 "Language-Team: Chinese (Simplified Han script) <https://weblate.nginxui.com/"
 "projects/nginx-ui/frontend/zh_Hans/>\n"
 "projects/nginx-ui/frontend/zh_Hans/>\n"
@@ -27,7 +27,11 @@ msgstr "2FA 设置"
 msgid "About"
 msgid "About"
 msgstr "关于"
 msgstr "关于"
 
 
-#: src/routes/modules/nginx_log.ts:17 src/views/site/ngx_conf/LogEntry.vue:75
+#: src/views/nginx_log/NginxLogList.vue:29
+msgid "Access Log"
+msgstr "访问日志"
+
+#: src/routes/modules/nginx_log.ts:17 src/views/site/ngx_conf/LogEntry.vue:91
 msgid "Access Logs"
 msgid "Access Logs"
 msgstr "访问日志"
 msgstr "访问日志"
 
 
@@ -41,7 +45,8 @@ msgstr "ACME 用户"
 #: src/views/certificate/DNSCredential.vue:33
 #: src/views/certificate/DNSCredential.vue:33
 #: src/views/config/configColumns.tsx:42
 #: src/views/config/configColumns.tsx:42
 #: src/views/environment/envColumns.tsx:97
 #: src/views/environment/envColumns.tsx:97
-#: src/views/notification/notificationColumns.tsx:65
+#: src/views/nginx_log/NginxLogList.vue:51
+#: src/views/notification/notificationColumns.tsx:66
 #: src/views/preference/AuthSettings.vue:30
 #: src/views/preference/AuthSettings.vue:30
 #: src/views/site/site_category/columns.ts:28
 #: src/views/site/site_category/columns.ts:28
 #: src/views/site/site_list/columns.tsx:76 src/views/stream/StreamList.vue:49
 #: src/views/site/site_list/columns.tsx:76 src/views/stream/StreamList.vue:49
@@ -240,7 +245,7 @@ msgstr "认证设置"
 msgid "Author"
 msgid "Author"
 msgstr "作者"
 msgstr "作者"
 
 
-#: src/views/nginx_log/NginxLog.vue:152
+#: src/views/nginx_log/NginxLog.vue:149
 msgid "Auto Refresh"
 msgid "Auto Refresh"
 msgstr "自动刷新"
 msgstr "自动刷新"
 
 
@@ -256,6 +261,10 @@ msgstr "成功启用 %{name} 自动续签"
 msgid "Automatic Restart"
 msgid "Automatic Restart"
 msgstr "自动重启"
 msgstr "自动重启"
 
 
+#: src/views/nginx_log/NginxLogList.vue:125
+msgid "Automatically indexed from site and stream configurations."
+msgstr "自动索引站点和 Stream 的配置文件。"
+
 #: src/views/certificate/CertificateEditor.vue:255
 #: src/views/certificate/CertificateEditor.vue:255
 #: src/views/config/ConfigEditor.vue:213 src/views/config/ConfigList.vue:106
 #: src/views/config/ConfigEditor.vue:213 src/views/config/ConfigList.vue:106
 #: src/views/config/ConfigList.vue:180 src/views/nginx_log/NginxLog.vue:173
 #: src/views/config/ConfigList.vue:180 src/views/nginx_log/NginxLog.vue:173
@@ -604,7 +613,7 @@ msgid ""
 msgstr ""
 msgstr ""
 "创建系统备份,包括 Nginx 配置和 Nginx UI 设置。备份文件将自动下载到你的电脑。"
 "创建系统备份,包括 Nginx 配置和 Nginx UI 设置。备份文件将自动下载到你的电脑。"
 
 
-#: src/views/notification/notificationColumns.tsx:58
+#: src/views/notification/notificationColumns.tsx:59
 #: src/views/preference/components/Passkey.vue:95
 #: src/views/preference/components/Passkey.vue:95
 #: src/views/site/site_category/columns.ts:16 src/views/user/userColumns.tsx:48
 #: src/views/site/site_category/columns.ts:16 src/views/user/userColumns.tsx:48
 msgid "Created at"
 msgid "Created at"
@@ -741,7 +750,7 @@ msgstr "描述"
 msgid "Destination file already exists"
 msgid "Destination file already exists"
 msgstr "目标文件已存在"
 msgstr "目标文件已存在"
 
 
-#: src/views/notification/notificationColumns.tsx:52
+#: src/views/notification/notificationColumns.tsx:53
 msgid "Details"
 msgid "Details"
 msgstr "详情"
 msgstr "详情"
 
 
@@ -1063,7 +1072,11 @@ msgstr "环境"
 msgid "Error"
 msgid "Error"
 msgstr "错误"
 msgstr "错误"
 
 
-#: src/routes/modules/nginx_log.ts:24 src/views/site/ngx_conf/LogEntry.vue:83
+#: src/views/nginx_log/NginxLogList.vue:30
+msgid "Error Log"
+msgstr "错误日志"
+
+#: src/routes/modules/nginx_log.ts:24 src/views/site/ngx_conf/LogEntry.vue:99
 msgid "Error Logs"
 msgid "Error Logs"
 msgstr "错误日志"
 msgstr "错误日志"
 
 
@@ -1451,6 +1464,13 @@ msgstr "ICP备案号"
 msgid "If left blank, the default CA Dir will be used."
 msgid "If left blank, the default CA Dir will be used."
 msgstr "如果留空,则使用默认 CA Dir。"
 msgstr "如果留空,则使用默认 CA Dir。"
 
 
+#: src/views/nginx_log/NginxLogList.vue:127
+msgid ""
+"If logs are not indexed, please check if the log file is under the directory "
+"in Nginx.LogDirWhiteList."
+msgstr ""
+"如果日志未被索引,请检查日志文件是否位于 Nginx.LogDirWhiteList 中的目录下。"
+
 #: src/views/preference/AuthSettings.vue:145
 #: src/views/preference/AuthSettings.vue:145
 msgid ""
 msgid ""
 "If the number of login failed attempts from a ip reach the max attempts in "
 "If the number of login failed attempts from a ip reach the max attempts in "
@@ -1478,6 +1498,14 @@ msgstr "导入"
 msgid "Import Certificate"
 msgid "Import Certificate"
 msgstr "导入证书"
 msgstr "导入证书"
 
 
+#: src/views/nginx_log/NginxLogList.vue:135
+msgid "Indexed"
+msgstr "已索引"
+
+#: src/views/nginx_log/NginxLogList.vue:132
+msgid "Indexing..."
+msgstr "索引中..."
+
 #: src/components/StdDesign/StdDetail/StdDetail.vue:81
 #: src/components/StdDesign/StdDetail/StdDetail.vue:81
 #: src/constants/index.ts:18 src/views/notification/notificationColumns.tsx:29
 #: src/constants/index.ts:18 src/views/notification/notificationColumns.tsx:29
 msgid "Info"
 msgid "Info"
@@ -1693,6 +1721,10 @@ msgstr "Locations"
 msgid "Log"
 msgid "Log"
 msgstr "日志"
 msgstr "日志"
 
 
+#: src/routes/modules/nginx_log.ts:39 src/views/nginx_log/NginxLogList.vue:113
+msgid "Log List"
+msgstr "日志列表"
+
 #: src/routes/modules/auth.ts:14 src/views/other/Login.vue:222
 #: src/routes/modules/auth.ts:14 src/views/other/Login.vue:222
 msgid "Login"
 msgid "Login"
 msgstr "登录"
 msgstr "登录"
@@ -1804,6 +1836,7 @@ msgstr "多行指令"
 #: src/views/config/components/Mkdir.vue:64
 #: src/views/config/components/Mkdir.vue:64
 #: src/views/config/configColumns.tsx:7 src/views/config/ConfigEditor.vue:256
 #: src/views/config/configColumns.tsx:7 src/views/config/ConfigEditor.vue:256
 #: src/views/environment/envColumns.tsx:9
 #: src/views/environment/envColumns.tsx:9
+#: src/views/nginx_log/NginxLogList.vue:35
 #: src/views/preference/components/AddPasskey.vue:75
 #: src/views/preference/components/AddPasskey.vue:75
 #: src/views/site/ngx_conf/NgxUpstream.vue:177
 #: src/views/site/ngx_conf/NgxUpstream.vue:177
 #: src/views/site/site_category/columns.ts:7
 #: src/views/site/site_category/columns.ts:7
@@ -1919,7 +1952,7 @@ msgstr "Nginx 错误日志路径"
 msgid "Nginx is not running"
 msgid "Nginx is not running"
 msgstr "Nginx 未启动"
 msgstr "Nginx 未启动"
 
 
-#: src/routes/modules/nginx_log.ts:9 src/views/nginx_log/NginxLog.vue:148
+#: src/routes/modules/nginx_log.ts:9 src/views/nginx_log/NginxLog.vue:143
 msgid "Nginx Log"
 msgid "Nginx Log"
 msgstr "Nginx 日志"
 msgstr "Nginx 日志"
 
 
@@ -2163,6 +2196,7 @@ msgid "Password length cannot exceed 20 characters"
 msgstr "密码长度不能超过 20 个字符"
 msgstr "密码长度不能超过 20 个字符"
 
 
 #: src/views/config/ConfigEditor.vue:263
 #: src/views/config/ConfigEditor.vue:263
+#: src/views/nginx_log/NginxLogList.vue:43
 #: src/views/site/ngx_conf/LocationEditor.vue:109
 #: src/views/site/ngx_conf/LocationEditor.vue:109
 #: src/views/site/ngx_conf/LocationEditor.vue:137
 #: src/views/site/ngx_conf/LocationEditor.vue:137
 msgid "Path"
 msgid "Path"
@@ -3198,7 +3232,7 @@ msgstr "限流"
 msgid "Tips"
 msgid "Tips"
 msgstr "提示"
 msgstr "提示"
 
 
-#: src/views/notification/notificationColumns.tsx:44
+#: src/views/notification/notificationColumns.tsx:45
 msgid "Title"
 msgid "Title"
 msgstr "标题"
 msgstr "标题"
 
 
@@ -3265,6 +3299,7 @@ msgid "Two-factor authentication required"
 msgstr "需要两步验证"
 msgstr "需要两步验证"
 
 
 #: src/views/certificate/CertificateList/certColumns.tsx:25
 #: src/views/certificate/CertificateList/certColumns.tsx:25
+#: src/views/nginx_log/NginxLogList.vue:20
 #: src/views/notification/notificationColumns.tsx:9
 #: src/views/notification/notificationColumns.tsx:9
 msgid "Type"
 msgid "Type"
 msgstr "类型"
 msgstr "类型"
@@ -3366,6 +3401,7 @@ msgid "Version"
 msgstr "版本"
 msgstr "版本"
 
 
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:488
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:488
+#: src/views/nginx_log/NginxLogList.vue:143
 #: src/views/site/ngx_conf/config_template/ConfigTemplate.vue:103
 #: src/views/site/ngx_conf/config_template/ConfigTemplate.vue:103
 msgid "View"
 msgid "View"
 msgstr "查看"
 msgstr "查看"
@@ -3797,9 +3833,6 @@ msgstr "你的 Passkeys"
 #~ msgid "HTTPS Listen Port"
 #~ msgid "HTTPS Listen Port"
 #~ msgstr "HTTPS 监听端口"
 #~ msgstr "HTTPS 监听端口"
 
 
-#~ msgid "Index (index)"
-#~ msgstr "网站首页 (index)"
-
 #~ msgid "Private Key Path (ssl_certificate_key)"
 #~ msgid "Private Key Path (ssl_certificate_key)"
 #~ msgstr "私钥路径 (ssl_certificate_key)"
 #~ msgstr "私钥路径 (ssl_certificate_key)"
 
 

+ 47 - 11
app/src/language/zh_TW/app.po

@@ -31,7 +31,12 @@ msgstr "多重要素驗證設定"
 msgid "About"
 msgid "About"
 msgstr "關於"
 msgstr "關於"
 
 
-#: src/routes/modules/nginx_log.ts:17 src/views/site/ngx_conf/LogEntry.vue:75
+#: src/views/nginx_log/NginxLogList.vue:29
+#, fuzzy
+msgid "Access Log"
+msgstr "訪問日誌"
+
+#: src/routes/modules/nginx_log.ts:17 src/views/site/ngx_conf/LogEntry.vue:91
 msgid "Access Logs"
 msgid "Access Logs"
 msgstr "訪問日誌"
 msgstr "訪問日誌"
 
 
@@ -45,7 +50,8 @@ msgstr "ACME 用戶"
 #: src/views/certificate/DNSCredential.vue:33
 #: src/views/certificate/DNSCredential.vue:33
 #: src/views/config/configColumns.tsx:42
 #: src/views/config/configColumns.tsx:42
 #: src/views/environment/envColumns.tsx:97
 #: src/views/environment/envColumns.tsx:97
-#: src/views/notification/notificationColumns.tsx:65
+#: src/views/nginx_log/NginxLogList.vue:51
+#: src/views/notification/notificationColumns.tsx:66
 #: src/views/preference/AuthSettings.vue:30
 #: src/views/preference/AuthSettings.vue:30
 #: src/views/site/site_category/columns.ts:28
 #: src/views/site/site_category/columns.ts:28
 #: src/views/site/site_list/columns.tsx:76 src/views/stream/StreamList.vue:49
 #: src/views/site/site_list/columns.tsx:76 src/views/stream/StreamList.vue:49
@@ -245,7 +251,7 @@ msgstr "認證設定"
 msgid "Author"
 msgid "Author"
 msgstr "作者"
 msgstr "作者"
 
 
-#: src/views/nginx_log/NginxLog.vue:152
+#: src/views/nginx_log/NginxLog.vue:149
 msgid "Auto Refresh"
 msgid "Auto Refresh"
 msgstr "自動重新整理"
 msgstr "自動重新整理"
 
 
@@ -261,6 +267,10 @@ msgstr "已啟用 %{name} 的自動續簽"
 msgid "Automatic Restart"
 msgid "Automatic Restart"
 msgstr ""
 msgstr ""
 
 
+#: src/views/nginx_log/NginxLogList.vue:125
+msgid "Automatically indexed from site and stream configurations."
+msgstr ""
+
 #: src/views/certificate/CertificateEditor.vue:255
 #: src/views/certificate/CertificateEditor.vue:255
 #: src/views/config/ConfigEditor.vue:213 src/views/config/ConfigList.vue:106
 #: src/views/config/ConfigEditor.vue:213 src/views/config/ConfigList.vue:106
 #: src/views/config/ConfigList.vue:180 src/views/nginx_log/NginxLog.vue:173
 #: src/views/config/ConfigList.vue:180 src/views/nginx_log/NginxLog.vue:173
@@ -614,7 +624,7 @@ msgid ""
 "Backup files will be automatically downloaded to your computer."
 "Backup files will be automatically downloaded to your computer."
 msgstr ""
 msgstr ""
 
 
-#: src/views/notification/notificationColumns.tsx:58
+#: src/views/notification/notificationColumns.tsx:59
 #: src/views/preference/components/Passkey.vue:95
 #: src/views/preference/components/Passkey.vue:95
 #: src/views/site/site_category/columns.ts:16 src/views/user/userColumns.tsx:48
 #: src/views/site/site_category/columns.ts:16 src/views/user/userColumns.tsx:48
 msgid "Created at"
 msgid "Created at"
@@ -751,7 +761,7 @@ msgstr "描述"
 msgid "Destination file already exists"
 msgid "Destination file already exists"
 msgstr "目的檔案已存在"
 msgstr "目的檔案已存在"
 
 
-#: src/views/notification/notificationColumns.tsx:52
+#: src/views/notification/notificationColumns.tsx:53
 msgid "Details"
 msgid "Details"
 msgstr "詳細資料"
 msgstr "詳細資料"
 
 
@@ -1084,7 +1094,12 @@ msgstr "環境"
 msgid "Error"
 msgid "Error"
 msgstr "錯誤"
 msgstr "錯誤"
 
 
-#: src/routes/modules/nginx_log.ts:24 src/views/site/ngx_conf/LogEntry.vue:83
+#: src/views/nginx_log/NginxLogList.vue:30
+#, fuzzy
+msgid "Error Log"
+msgstr "錯誤日誌"
+
+#: src/routes/modules/nginx_log.ts:24 src/views/site/ngx_conf/LogEntry.vue:99
 msgid "Error Logs"
 msgid "Error Logs"
 msgstr "錯誤日誌"
 msgstr "錯誤日誌"
 
 
@@ -1509,6 +1524,12 @@ msgstr "ICP 編號"
 msgid "If left blank, the default CA Dir will be used."
 msgid "If left blank, the default CA Dir will be used."
 msgstr "如果留空,將使用默認的 CA Dir。"
 msgstr "如果留空,將使用默認的 CA Dir。"
 
 
+#: src/views/nginx_log/NginxLogList.vue:127
+msgid ""
+"If logs are not indexed, please check if the log file is under the directory "
+"in Nginx.LogDirWhiteList."
+msgstr ""
+
 #: src/views/preference/AuthSettings.vue:145
 #: src/views/preference/AuthSettings.vue:145
 msgid ""
 msgid ""
 "If the number of login failed attempts from a ip reach the max attempts in "
 "If the number of login failed attempts from a ip reach the max attempts in "
@@ -1536,6 +1557,15 @@ msgstr "導入"
 msgid "Import Certificate"
 msgid "Import Certificate"
 msgstr "導入憑證"
 msgstr "導入憑證"
 
 
+#: src/views/nginx_log/NginxLogList.vue:135
+#, fuzzy
+msgid "Indexed"
+msgstr "網站首頁 (index)"
+
+#: src/views/nginx_log/NginxLogList.vue:132
+msgid "Indexing..."
+msgstr ""
+
 #: src/components/StdDesign/StdDetail/StdDetail.vue:81
 #: src/components/StdDesign/StdDetail/StdDetail.vue:81
 #: src/constants/index.ts:18 src/views/notification/notificationColumns.tsx:29
 #: src/constants/index.ts:18 src/views/notification/notificationColumns.tsx:29
 msgid "Info"
 msgid "Info"
@@ -1756,6 +1786,11 @@ msgstr "Locations"
 msgid "Log"
 msgid "Log"
 msgstr "日誌"
 msgstr "日誌"
 
 
+#: src/routes/modules/nginx_log.ts:39 src/views/nginx_log/NginxLogList.vue:113
+#, fuzzy
+msgid "Log List"
+msgstr "列表"
+
 #: src/routes/modules/auth.ts:14 src/views/other/Login.vue:222
 #: src/routes/modules/auth.ts:14 src/views/other/Login.vue:222
 msgid "Login"
 msgid "Login"
 msgstr "登入"
 msgstr "登入"
@@ -1866,6 +1901,7 @@ msgstr "多行指令"
 #: src/views/config/components/Mkdir.vue:64
 #: src/views/config/components/Mkdir.vue:64
 #: src/views/config/configColumns.tsx:7 src/views/config/ConfigEditor.vue:256
 #: src/views/config/configColumns.tsx:7 src/views/config/ConfigEditor.vue:256
 #: src/views/environment/envColumns.tsx:9
 #: src/views/environment/envColumns.tsx:9
+#: src/views/nginx_log/NginxLogList.vue:35
 #: src/views/preference/components/AddPasskey.vue:75
 #: src/views/preference/components/AddPasskey.vue:75
 #: src/views/site/ngx_conf/NgxUpstream.vue:177
 #: src/views/site/ngx_conf/NgxUpstream.vue:177
 #: src/views/site/site_category/columns.ts:7
 #: src/views/site/site_category/columns.ts:7
@@ -1985,7 +2021,7 @@ msgstr "Nginx 錯誤日誌路徑"
 msgid "Nginx is not running"
 msgid "Nginx is not running"
 msgstr "Nginx 未執行"
 msgstr "Nginx 未執行"
 
 
-#: src/routes/modules/nginx_log.ts:9 src/views/nginx_log/NginxLog.vue:148
+#: src/routes/modules/nginx_log.ts:9 src/views/nginx_log/NginxLog.vue:143
 msgid "Nginx Log"
 msgid "Nginx Log"
 msgstr "Nginx 日誌"
 msgstr "Nginx 日誌"
 
 
@@ -2235,6 +2271,7 @@ msgid "Password length cannot exceed 20 characters"
 msgstr "密碼長度不能超過 20 個字元"
 msgstr "密碼長度不能超過 20 個字元"
 
 
 #: src/views/config/ConfigEditor.vue:263
 #: src/views/config/ConfigEditor.vue:263
+#: src/views/nginx_log/NginxLogList.vue:43
 #: src/views/site/ngx_conf/LocationEditor.vue:109
 #: src/views/site/ngx_conf/LocationEditor.vue:109
 #: src/views/site/ngx_conf/LocationEditor.vue:137
 #: src/views/site/ngx_conf/LocationEditor.vue:137
 msgid "Path"
 msgid "Path"
@@ -3306,7 +3343,7 @@ msgstr "節流"
 msgid "Tips"
 msgid "Tips"
 msgstr "提示"
 msgstr "提示"
 
 
-#: src/views/notification/notificationColumns.tsx:44
+#: src/views/notification/notificationColumns.tsx:45
 msgid "Title"
 msgid "Title"
 msgstr "標題"
 msgstr "標題"
 
 
@@ -3373,6 +3410,7 @@ msgid "Two-factor authentication required"
 msgstr "需要多重因素驗證"
 msgstr "需要多重因素驗證"
 
 
 #: src/views/certificate/CertificateList/certColumns.tsx:25
 #: src/views/certificate/CertificateList/certColumns.tsx:25
+#: src/views/nginx_log/NginxLogList.vue:20
 #: src/views/notification/notificationColumns.tsx:9
 #: src/views/notification/notificationColumns.tsx:9
 msgid "Type"
 msgid "Type"
 msgstr "類型"
 msgstr "類型"
@@ -3474,6 +3512,7 @@ msgid "Version"
 msgstr "版本"
 msgstr "版本"
 
 
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:488
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:488
+#: src/views/nginx_log/NginxLogList.vue:143
 #: src/views/site/ngx_conf/config_template/ConfigTemplate.vue:103
 #: src/views/site/ngx_conf/config_template/ConfigTemplate.vue:103
 msgid "View"
 msgid "View"
 msgstr "檢視"
 msgstr "檢視"
@@ -3866,9 +3905,6 @@ msgstr "您的通行密鑰"
 #~ msgid "HTTPS Listen Port"
 #~ msgid "HTTPS Listen Port"
 #~ msgstr "HTTPS 監聽埠"
 #~ msgstr "HTTPS 監聽埠"
 
 
-#~ msgid "Index (index)"
-#~ msgstr "網站首頁 (index)"
-
 #~ msgid "Private Key Path (ssl_certificate_key)"
 #~ msgid "Private Key Path (ssl_certificate_key)"
 #~ msgstr "私鑰路徑 (ssl_certificate_key)"
 #~ msgstr "私鑰路徑 (ssl_certificate_key)"
 
 

+ 7 - 0
app/src/routes/modules/nginx_log.ts

@@ -31,6 +31,13 @@ export const nginxLogRoutes: RouteRecordRaw[] = [
         name: () => $gettext('Site Logs'),
         name: () => $gettext('Site Logs'),
         hiddenInSidebar: true,
         hiddenInSidebar: true,
       },
       },
+    }, {
+      path: 'list',
+      name: 'Log List',
+      component: () => import('@/views/nginx_log/NginxLogList.vue'),
+      meta: {
+        name: () => $gettext('Log List'),
+      },
     }],
     }],
   },
   },
 ]
 ]

+ 27 - 27
app/src/views/nginx_log/NginxLog.vue

@@ -11,16 +11,15 @@ let websocket: ReconnectingWebSocket | WebSocket
 const route = useRoute()
 const route = useRoute()
 const buffer = ref('')
 const buffer = ref('')
 const page = ref(0)
 const page = ref(0)
-const auto_refresh = ref(true)
+const autoRefresh = ref(true)
 const router = useRouter()
 const router = useRouter()
 const loading = ref(false)
 const loading = ref(false)
 const filter = ref('')
 const filter = ref('')
 
 
+// Setup log control data based on route params
 const control = reactive<INginxLogData>({
 const control = reactive<INginxLogData>({
   type: logType(),
   type: logType(),
-  conf_name: route.query.conf_name as string,
-  server_idx: Number.parseInt(route.query.server_idx as string),
-  directive_idx: Number.parseInt(route.query.directive_idx as string),
+  log_path: route.query.log_path as string,
 })
 })
 
 
 function logType() {
 function logType() {
@@ -33,9 +32,7 @@ function openWs() {
   websocket = ws('/api/nginx_log')
   websocket = ws('/api/nginx_log')
 
 
   websocket.onopen = () => {
   websocket.onopen = () => {
-    websocket.send(JSON.stringify({
-      ...control,
-    }))
+    websocket.send(JSON.stringify(control))
   }
   }
 
 
   websocket.onmessage = (m: { data: string }) => {
   websocket.onmessage = (m: { data: string }) => {
@@ -68,7 +65,7 @@ function init() {
 }
 }
 
 
 function clearLog() {
 function clearLog() {
-  logContainer.value!.innerHTML = ''
+  buffer.value = ''
 }
 }
 
 
 onMounted(() => {
 onMounted(() => {
@@ -79,10 +76,12 @@ onUnmounted(() => {
   websocket?.close()
   websocket?.close()
 })
 })
 
 
-watch(auto_refresh, value => {
+watch(autoRefresh, async value => {
   if (value) {
   if (value) {
-    openWs()
     clearLog()
     clearLog()
+    await nextTick()
+    await init()
+    openWs()
   }
   }
   else {
   else {
     websocket.close()
     websocket.close()
@@ -90,28 +89,24 @@ watch(auto_refresh, value => {
 })
 })
 
 
 watch(route, () => {
 watch(route, () => {
-  init()
-
+  // Update control data when route changes
   control.type = logType()
   control.type = logType()
-  control.directive_idx = Number.parseInt(route.query.server_idx as string)
-  control.server_idx = Number.parseInt(route.query.directive_idx as string)
-  clearLog()
+  control.log_path = route.query.log_path as string
 
 
-  nextTick(() => {
-    websocket.send(JSON.stringify(control))
-  })
+  clearLog()
+  init()
 })
 })
 
 
 watch(control, () => {
 watch(control, () => {
   clearLog()
   clearLog()
-  auto_refresh.value = true
+  autoRefresh.value = true
 
 
   nextTick(() => {
   nextTick(() => {
     websocket.send(JSON.stringify(control))
     websocket.send(JSON.stringify(control))
   })
   })
 })
 })
 
 
-function on_scroll_log() {
+function onScrollLog() {
   if (!loading.value && page.value > 0) {
   if (!loading.value && page.value > 0) {
     loading.value = true
     loading.value = true
 
 
@@ -131,8 +126,8 @@ function on_scroll_log() {
   }
   }
 }
 }
 
 
-function debounce_scroll_log() {
-  return debounce(on_scroll_log, 100)()
+function debounceScrollLog() {
+  return debounce(onScrollLog, 100)()
 }
 }
 
 
 const computedBuffer = computed(() => {
 const computedBuffer = computed(() => {
@@ -148,10 +143,15 @@ const computedBuffer = computed(() => {
     :title="$gettext('Nginx Log')"
     :title="$gettext('Nginx Log')"
     :bordered="false"
     :bordered="false"
   >
   >
+    <template #extra>
+      <div class="flex items-center">
+        <span class="mr-2">
+          {{ $gettext('Auto Refresh') }}
+        </span>
+        <ASwitch v-model:checked="autoRefresh" />
+      </div>
+    </template>
     <AForm layout="vertical">
     <AForm layout="vertical">
-      <AFormItem :label="$gettext('Auto Refresh')">
-        <ASwitch v-model:checked="auto_refresh" />
-      </AFormItem>
       <AFormItem :label="$gettext('Filter')">
       <AFormItem :label="$gettext('Filter')">
         <AInput
         <AInput
           v-model:value="filter"
           v-model:value="filter"
@@ -165,10 +165,10 @@ const computedBuffer = computed(() => {
         ref="logContainer"
         ref="logContainer"
         v-dompurify-html="computedBuffer"
         v-dompurify-html="computedBuffer"
         class="nginx-log-container"
         class="nginx-log-container"
-        @scroll="debounce_scroll_log"
+        @scroll="debounceScrollLog"
       />
       />
     </ACard>
     </ACard>
-    <FooterToolBar v-if="control.type === 'site'">
+    <FooterToolBar v-if="control.log_path">
       <AButton @click="router.go(-1)">
       <AButton @click="router.go(-1)">
         {{ $gettext('Back') }}
         {{ $gettext('Back') }}
       </AButton>
       </AButton>

+ 152 - 0
app/src/views/nginx_log/NginxLogList.vue

@@ -0,0 +1,152 @@
+<script setup lang="tsx">
+import type { CustomRender } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
+import type { Column } from '@/components/StdDesign/types'
+import type { SSE, SSEvent } from 'sse.js'
+import nginxLog from '@/api/nginx_log'
+import StdCurd from '@/components/StdDesign/StdDataDisplay/StdCurd.vue'
+import { input, select } from '@/components/StdDesign/StdDataEntry'
+import { CheckCircleOutlined, LoadingOutlined } from '@ant-design/icons-vue'
+import { Tag } from 'ant-design-vue'
+import { onMounted, onUnmounted, ref } from 'vue'
+import { useRouter } from 'vue-router'
+
+const router = useRouter()
+const isScanning = ref(false)
+const stdCurdRef = ref()
+const sse = ref<SSE | null>(null)
+
+const columns: Column[] = [
+  {
+    title: () => $gettext('Type'),
+    dataIndex: 'type',
+    customRender: (args: CustomRender) => {
+      return args.record?.type === 'access' ? <Tag color="success">{ $gettext('Access Log') }</Tag> : <Tag color="orange">{ $gettext('Error Log') }</Tag>
+    },
+    sorter: true,
+    search: {
+      type: select,
+      mask: {
+        access: () => $gettext('Access Log'),
+        error: () => $gettext('Error Log'),
+      },
+    },
+    width: 200,
+  },
+  {
+    title: () => $gettext('Name'),
+    dataIndex: 'name',
+    sorter: true,
+    search: {
+      type: input,
+    },
+  },
+  {
+    title: () => $gettext('Path'),
+    dataIndex: 'path',
+    sorter: true,
+    search: {
+      type: input,
+    },
+  },
+  {
+    title: () => $gettext('Action'),
+    dataIndex: 'action',
+  },
+]
+
+function viewLog(record: { type: string, path: string }) {
+  router.push({
+    path: `/nginx_log/${record.type}`,
+    query: {
+      log_path: record.path,
+    },
+  })
+}
+
+// Connect to SSE endpoint and setup handlers
+function setupSSE() {
+  if (sse.value) {
+    sse.value.close()
+  }
+
+  sse.value = nginxLog.logs_live()
+
+  // Handle incoming messages
+  if (sse.value) {
+    sse.value.onmessage = (e: SSEvent) => {
+      try {
+        if (!e.data)
+          return
+
+        const data = JSON.parse(e.data)
+        isScanning.value = data.scanning
+
+        stdCurdRef.value.get_list()
+      }
+      catch (error) {
+        console.error('Error parsing SSE message:', error)
+      }
+    }
+
+    sse.value.onerror = () => {
+      // Reconnect on error
+      setTimeout(() => {
+        setupSSE()
+      }, 5000)
+    }
+  }
+}
+
+onMounted(() => {
+  setupSSE()
+})
+
+onUnmounted(() => {
+  if (sse.value) {
+    sse.value.close()
+  }
+})
+</script>
+
+<template>
+  <StdCurd
+    ref="stdCurdRef"
+    :title="$gettext('Log List')"
+    :columns="columns"
+    :api="nginxLog"
+    disable-add
+    disable-delete
+    disable-view
+    disable-modify
+  >
+    <template #extra>
+      <APopover placement="bottomRight">
+        <template #content>
+          <div>
+            {{ $gettext('Automatically indexed from site and stream configurations.') }}
+            <br>
+            {{ $gettext('If logs are not indexed, please check if the log file is under the directory in Nginx.LogDirWhiteList.') }}
+          </div>
+        </template>
+        <div class="flex items-center cursor-pointer">
+          <template v-if="isScanning">
+            <LoadingOutlined class="mr-2" spin />{{ $gettext('Indexing...') }}
+          </template>
+          <template v-else>
+            <CheckCircleOutlined class="mr-2" />{{ $gettext('Indexed') }}
+          </template>
+        </div>
+      </APopover>
+    </template>
+
+    <template #actions="{ record }">
+      <AButton type="link" size="small" @click="viewLog(record)">
+        {{ $gettext('View') }}
+      </AButton>
+    </template>
+  </StdCurd>
+</template>
+
+<style scoped lang="less">
+
+</style>

+ 22 - 6
app/src/views/site/ngx_conf/LogEntry.vue

@@ -11,6 +11,8 @@ const props = defineProps<{
 
 
 const accessIdx = ref<number>()
 const accessIdx = ref<number>()
 const errorIdx = ref<number>()
 const errorIdx = ref<number>()
+const accessLogPath = ref<string>()
+const errorLogPath = ref<string>()
 
 
 const hasAccessLog = computed(() => {
 const hasAccessLog = computed(() => {
   let flag = false
   let flag = false
@@ -18,6 +20,14 @@ const hasAccessLog = computed(() => {
     if (v.directive === 'access_log') {
     if (v.directive === 'access_log') {
       flag = true
       flag = true
       accessIdx.value = k
       accessIdx.value = k
+
+      // Extract log path from directive params
+      if (v.params) {
+        const params = v.params.split(' ')
+        if (params.length > 0) {
+          accessLogPath.value = params[0]
+        }
+      }
     }
     }
   })
   })
 
 
@@ -30,6 +40,14 @@ const hasErrorLog = computed(() => {
     if (v.directive === 'error_log') {
     if (v.directive === 'error_log') {
       flag = true
       flag = true
       errorIdx.value = k
       errorIdx.value = k
+
+      // Extract log path from directive params
+      if (v.params) {
+        const params = v.params.split(' ')
+        if (params.length > 0) {
+          errorLogPath.value = params[0]
+        }
+      }
     }
     }
   })
   })
 
 
@@ -42,9 +60,8 @@ function on_click_access_log() {
   router.push({
   router.push({
     path: '/nginx_log/site',
     path: '/nginx_log/site',
     query: {
     query: {
-      server_idx: props.currentServerIdx,
-      directive_idx: accessIdx.value,
-      conf_name: props.name,
+      type: 'site',
+      log_path: accessLogPath.value,
     },
     },
   })
   })
 }
 }
@@ -53,9 +70,8 @@ function on_click_error_log() {
   router.push({
   router.push({
     path: '/nginx_log/site',
     path: '/nginx_log/site',
     query: {
     query: {
-      server_idx: props.currentServerIdx,
-      directive_idx: errorIdx.value,
-      conf_name: props.name,
+      type: 'site',
+      log_path: errorLogPath.value,
     },
     },
   })
   })
 }
 }

+ 5 - 1
internal/cache/cache.go

@@ -1,9 +1,10 @@
 package cache
 package cache
 
 
 import (
 import (
+	"time"
+
 	"github.com/dgraph-io/ristretto/v2"
 	"github.com/dgraph-io/ristretto/v2"
 	"github.com/uozi-tech/cosy/logger"
 	"github.com/uozi-tech/cosy/logger"
-	"time"
 )
 )
 
 
 var cache *ristretto.Cache[string, any]
 var cache *ristretto.Cache[string, any]
@@ -19,6 +20,9 @@ func Init() {
 	if err != nil {
 	if err != nil {
 		logger.Fatal("initializing local cache err", err)
 		logger.Fatal("initializing local cache err", err)
 	}
 	}
+
+	// Initialize the nginx log scanner
+	InitNginxLogScanner()
 }
 }
 
 
 func Set(key string, value interface{}, ttl time.Duration) {
 func Set(key string, value interface{}, ttl time.Duration) {

+ 585 - 0
internal/cache/nginx_log.go

@@ -0,0 +1,585 @@
+package cache
+
+import (
+	"os"
+	"path/filepath"
+	"regexp"
+	"strings"
+	"sync"
+	"time"
+
+	"github.com/0xJacky/Nginx-UI/internal/helper"
+	"github.com/0xJacky/Nginx-UI/internal/nginx"
+	"github.com/0xJacky/Nginx-UI/settings"
+	"github.com/fsnotify/fsnotify"
+	"github.com/uozi-tech/cosy/logger"
+)
+
+// NginxLogCache represents a cached log entry from nginx configuration
+type NginxLogCache struct {
+	Path string `json:"path"` // Path to the log file
+	Type string `json:"type"` // Type of log: "access" or "error"
+	Name string `json:"name"` // Name of the log file
+}
+
+// NginxLogScanner is responsible for scanning and watching nginx config files for log directives
+type NginxLogScanner struct {
+	logCache      map[string]*NginxLogCache // Map of log path to cache entry
+	cacheMutex    sync.RWMutex              // Mutex for protecting the cache
+	watcher       *fsnotify.Watcher         // File system watcher
+	scanTicker    *time.Ticker              // Ticker for periodic scanning
+	initialized   bool                      // Whether the scanner has been initialized
+	scanning      bool                      // Whether a scan is currently in progress
+	scanMutex     sync.RWMutex              // Mutex for protecting the scanning state
+	statusChan    chan bool                 // Channel to broadcast scanning status changes
+	subscribers   map[chan bool]struct{}    // Set of subscribers
+	subscriberMux sync.RWMutex              // Mutex for protecting the subscribers map
+}
+
+// Add regex constants at package level
+var (
+	// logScanner is the singleton instance of NginxLogScanner
+	logScanner     *NginxLogScanner
+	scannerInitMux sync.Mutex
+)
+
+// Compile the regular expressions for matching log directives
+var (
+	// This regex matches: access_log or error_log, followed by a path, and optional parameters ending with semicolon
+	logDirectiveRegex = regexp.MustCompile(`(?m)(access_log|error_log)\s+([^\s;]+)(?:\s+[^;]+)?;`)
+)
+
+// InitNginxLogScanner initializes the nginx log scanner
+func InitNginxLogScanner() {
+	scanner := GetNginxLogScanner()
+	err := scanner.Initialize()
+	if err != nil {
+		logger.Error("Failed to initialize nginx log scanner:", err)
+	}
+}
+
+// GetNginxLogScanner returns the singleton instance of NginxLogScanner
+func GetNginxLogScanner() *NginxLogScanner {
+	scannerInitMux.Lock()
+	defer scannerInitMux.Unlock()
+
+	if logScanner == nil {
+		logScanner = &NginxLogScanner{
+			logCache:    make(map[string]*NginxLogCache),
+			statusChan:  make(chan bool, 10), // Buffer to prevent blocking
+			subscribers: make(map[chan bool]struct{}),
+		}
+
+		// Start broadcaster goroutine
+		go logScanner.broadcastStatus()
+	}
+	return logScanner
+}
+
+// broadcastStatus listens for status changes and broadcasts to all subscribers
+func (s *NginxLogScanner) broadcastStatus() {
+	for status := range s.statusChan {
+		s.subscriberMux.RLock()
+		for ch := range s.subscribers {
+			// Non-blocking send to prevent slow subscribers from blocking others
+			select {
+			case ch <- status:
+			default:
+				// Skip if channel buffer is full
+			}
+		}
+		s.subscriberMux.RUnlock()
+	}
+}
+
+// SubscribeStatusChanges allows a client to subscribe to scanning status changes
+func SubscribeStatusChanges() chan bool {
+	s := GetNginxLogScanner()
+	ch := make(chan bool, 5) // Buffer to prevent blocking
+
+	// Add to subscribers
+	s.subscriberMux.Lock()
+	s.subscribers[ch] = struct{}{}
+	s.subscriberMux.Unlock()
+
+	// Send current status immediately
+	s.scanMutex.RLock()
+	currentStatus := s.scanning
+	s.scanMutex.RUnlock()
+
+	// Non-blocking send
+	select {
+	case ch <- currentStatus:
+	default:
+	}
+
+	return ch
+}
+
+// UnsubscribeStatusChanges removes a subscriber from receiving status updates
+func UnsubscribeStatusChanges(ch chan bool) {
+	s := GetNginxLogScanner()
+
+	s.subscriberMux.Lock()
+	delete(s.subscribers, ch)
+	s.subscriberMux.Unlock()
+
+	// Close the channel so the client knows it's unsubscribed
+	close(ch)
+}
+
+// Initialize sets up the log scanner and starts watching for file changes
+func (s *NginxLogScanner) Initialize() error {
+	if s.initialized {
+		return nil
+	}
+
+	// Create a new watcher
+	watcher, err := fsnotify.NewWatcher()
+	if err != nil {
+		return err
+	}
+	s.watcher = watcher
+
+	// Scan for the first time
+	err = s.ScanAllConfigs()
+	if err != nil {
+		return err
+	}
+
+	// Setup watcher for config directory
+	configDir := filepath.Dir(nginx.GetConfPath("", ""))
+	availableDir := nginx.GetConfPath("sites-available", "")
+	enabledDir := nginx.GetConfPath("sites-enabled", "")
+	streamAvailableDir := nginx.GetConfPath("stream-available", "")
+	streamEnabledDir := nginx.GetConfPath("stream-enabled", "")
+
+	// Watch the main directories
+	err = s.watcher.Add(configDir)
+	if err != nil {
+		logger.Error("Failed to watch config directory:", err)
+	}
+
+	// Watch sites-available and sites-enabled if they exist
+	if _, err := os.Stat(availableDir); err == nil {
+		err = s.watcher.Add(availableDir)
+		if err != nil {
+			logger.Error("Failed to watch sites-available directory:", err)
+		}
+	}
+
+	if _, err := os.Stat(enabledDir); err == nil {
+		err = s.watcher.Add(enabledDir)
+		if err != nil {
+			logger.Error("Failed to watch sites-enabled directory:", err)
+		}
+	}
+
+	// Watch stream-available and stream-enabled if they exist
+	if _, err := os.Stat(streamAvailableDir); err == nil {
+		err = s.watcher.Add(streamAvailableDir)
+		if err != nil {
+			logger.Error("Failed to watch stream-available directory:", err)
+		}
+	}
+
+	if _, err := os.Stat(streamEnabledDir); err == nil {
+		err = s.watcher.Add(streamEnabledDir)
+		if err != nil {
+			logger.Error("Failed to watch stream-enabled directory:", err)
+		}
+	}
+
+	// Start the watcher goroutine
+	go s.watchForChanges()
+
+	// Setup a ticker for periodic scanning (every 5 minutes)
+	s.scanTicker = time.NewTicker(5 * time.Minute)
+	go func() {
+		for range s.scanTicker.C {
+			err := s.ScanAllConfigs()
+			if err != nil {
+				logger.Error("Periodic config scan failed:", err)
+			}
+		}
+	}()
+
+	s.initialized = true
+	return nil
+}
+
+// watchForChanges handles the fsnotify events and triggers rescans when necessary
+func (s *NginxLogScanner) watchForChanges() {
+	for {
+		select {
+		case event, ok := <-s.watcher.Events:
+			if !ok {
+				return
+			}
+
+			// Check if this is a relevant event (create, write, rename, remove)
+			if event.Has(fsnotify.Create) || event.Has(fsnotify.Write) ||
+				event.Has(fsnotify.Rename) || event.Has(fsnotify.Remove) {
+				// If it's a directory, add it to the watch list
+				if event.Has(fsnotify.Create) {
+					fi, err := os.Stat(event.Name)
+					if err == nil && fi.IsDir() {
+						_ = s.watcher.Add(event.Name)
+					}
+				}
+
+				// Process file changes - no .conf restriction anymore
+				if !event.Has(fsnotify.Remove) {
+					logger.Debug("Config file changed:", event.Name)
+					// Give the system a moment to finish writing the file
+					time.Sleep(100 * time.Millisecond)
+					// Only scan the changed file instead of all configs
+					err := s.scanSingleFile(event.Name)
+					if err != nil {
+						logger.Error("Failed to scan changed file:", err)
+					}
+				} else {
+					// For removed files, we need to clean up any log entries that came from this file
+					// This would require tracking which logs came from which config files
+					// For now, we'll do a full rescan which is simpler but less efficient
+					err := s.ScanAllConfigs()
+					if err != nil {
+						logger.Error("Failed to rescan configs after file removal:", err)
+					}
+				}
+			}
+		case err, ok := <-s.watcher.Errors:
+			if !ok {
+				return
+			}
+			logger.Error("Watcher error:", err)
+		}
+	}
+}
+
+// scanSingleFile scans a single file and updates the log cache accordingly
+func (s *NginxLogScanner) scanSingleFile(filePath string) error {
+	// Set scanning state to true
+	s.scanMutex.Lock()
+	wasScanning := s.scanning
+	s.scanning = true
+	if !wasScanning {
+		// Only broadcast if status changed from not scanning to scanning
+		s.statusChan <- true
+	}
+	s.scanMutex.Unlock()
+
+	// Ensure we reset scanning state when done
+	defer func() {
+		s.scanMutex.Lock()
+		s.scanning = false
+		// Broadcast the completion
+		s.statusChan <- false
+		s.scanMutex.Unlock()
+	}()
+
+	// Create a temporary cache for new entries from this file
+	newEntries := make(map[string]*NginxLogCache)
+
+	// Scan the file
+	err := s.scanConfigFile(filePath, newEntries)
+	if err != nil {
+		return err
+	}
+
+	// Update the main cache with new entries
+	s.cacheMutex.Lock()
+	for path, entry := range newEntries {
+		s.logCache[path] = entry
+	}
+	s.cacheMutex.Unlock()
+
+	return nil
+}
+
+// ScanAllConfigs scans all nginx config files for log directives
+func (s *NginxLogScanner) ScanAllConfigs() error {
+	// Set scanning state to true
+	s.scanMutex.Lock()
+	wasScanning := s.scanning
+	s.scanning = true
+	if !wasScanning {
+		// Only broadcast if status changed from not scanning to scanning
+		s.statusChan <- true
+	}
+	s.scanMutex.Unlock()
+
+	// Ensure we reset scanning state when done
+	defer func() {
+		s.scanMutex.Lock()
+		s.scanning = false
+		// Broadcast the completion
+		s.statusChan <- false
+		s.scanMutex.Unlock()
+	}()
+
+	// Initialize a new cache to replace the old one
+	newCache := make(map[string]*NginxLogCache)
+
+	// Get the main config file
+	mainConfigPath := nginx.GetConfPath("", "nginx.conf")
+	err := s.scanConfigFile(mainConfigPath, newCache)
+	if err != nil {
+		logger.Error("Failed to scan main config:", err)
+	}
+
+	// Scan sites-available directory - no .conf restriction anymore
+	sitesAvailablePath := nginx.GetConfPath("sites-available", "")
+	sitesAvailableFiles, err := os.ReadDir(sitesAvailablePath)
+	if err == nil {
+		for _, file := range sitesAvailableFiles {
+			if !file.IsDir() {
+				configPath := filepath.Join(sitesAvailablePath, file.Name())
+				err := s.scanConfigFile(configPath, newCache)
+				if err != nil {
+					logger.Error("Failed to scan config:", configPath, err)
+				}
+			}
+		}
+	}
+
+	// Scan stream-available directory if it exists
+	streamAvailablePath := nginx.GetConfPath("stream-available", "")
+	streamAvailableFiles, err := os.ReadDir(streamAvailablePath)
+	if err == nil {
+		for _, file := range streamAvailableFiles {
+			if !file.IsDir() {
+				configPath := filepath.Join(streamAvailablePath, file.Name())
+				err := s.scanConfigFile(configPath, newCache)
+				if err != nil {
+					logger.Error("Failed to scan stream config:", configPath, err)
+				}
+			}
+		}
+	}
+
+	// Replace the old cache with the new one
+	s.cacheMutex.Lock()
+	s.logCache = newCache
+	s.cacheMutex.Unlock()
+
+	return nil
+}
+
+// scanConfigFile scans a single config file for log directives using regex
+func (s *NginxLogScanner) scanConfigFile(configPath string, cache map[string]*NginxLogCache) error {
+	// Open the file
+	file, err := os.Open(configPath)
+	if err != nil {
+		return err
+	}
+	defer file.Close()
+
+	// Read the entire file content
+	content, err := os.ReadFile(configPath)
+	if err != nil {
+		return err
+	}
+
+	// Find all matches of log directives
+	matches := logDirectiveRegex.FindAllSubmatch(content, -1)
+	for _, match := range matches {
+		if len(match) >= 3 {
+			directiveType := string(match[1]) // "access_log" or "error_log"
+			logPath := string(match[2])       // The log file path
+
+			// Validate the log path
+			if isValidLogPath(logPath) {
+				logType := "access"
+				if directiveType == "error_log" {
+					logType = "error"
+				}
+
+				cache[logPath] = &NginxLogCache{
+					Path: logPath,
+					Type: logType,
+					Name: filepath.Base(logPath),
+				}
+			}
+		}
+	}
+
+	// Look for include directives to process included files
+	includeRegex := regexp.MustCompile(`include\s+([^;]+);`)
+	includeMatches := includeRegex.FindAllSubmatch(content, -1)
+
+	for _, match := range includeMatches {
+		if len(match) >= 2 {
+			includePath := string(match[1])
+			// Handle glob patterns in include directives
+			if strings.Contains(includePath, "*") {
+				// If it's a relative path, make it absolute based on nginx config dir
+				if !filepath.IsAbs(includePath) {
+					configDir := filepath.Dir(nginx.GetConfPath("", ""))
+					includePath = filepath.Join(configDir, includePath)
+				}
+
+				// Expand the glob pattern
+				matchedFiles, err := filepath.Glob(includePath)
+				if err != nil {
+					logger.Error("Error expanding glob pattern:", includePath, err)
+					continue
+				}
+
+				// Process each matched file
+				for _, matchedFile := range matchedFiles {
+					fileInfo, err := os.Stat(matchedFile)
+					if err == nil && !fileInfo.IsDir() {
+						err = s.scanConfigFile(matchedFile, cache)
+						if err != nil {
+							logger.Error("Failed to scan included file:", matchedFile, err)
+						}
+					}
+				}
+			} else {
+				// Handle single file include
+				// If it's a relative path, make it absolute based on nginx config dir
+				if !filepath.IsAbs(includePath) {
+					configDir := filepath.Dir(nginx.GetConfPath("", ""))
+					includePath = filepath.Join(configDir, includePath)
+				}
+
+				fileInfo, err := os.Stat(includePath)
+				if err == nil && !fileInfo.IsDir() {
+					err = s.scanConfigFile(includePath, cache)
+					if err != nil {
+						logger.Error("Failed to scan included file:", includePath, err)
+					}
+				}
+			}
+		}
+	}
+
+	return nil
+}
+
+// isLogPathUnderWhiteList checks if the log path is under one of the paths in LogDirWhiteList
+// This is a duplicate of the function in nginx_log package to avoid import cycle
+func isLogPathUnderWhiteList(path string) bool {
+	// deep copy
+	logDirWhiteList := append([]string{}, settings.NginxSettings.LogDirWhiteList...)
+
+	accessLogPath := nginx.GetAccessLogPath()
+	errorLogPath := nginx.GetErrorLogPath()
+
+	if accessLogPath != "" {
+		logDirWhiteList = append(logDirWhiteList, filepath.Dir(accessLogPath))
+	}
+	if errorLogPath != "" {
+		logDirWhiteList = append(logDirWhiteList, filepath.Dir(errorLogPath))
+	}
+
+	for _, whitePath := range logDirWhiteList {
+		if helper.IsUnderDirectory(path, whitePath) {
+			return true
+		}
+	}
+	return false
+}
+
+// isValidLogPath checks if a log path is valid:
+// 1. It must be a regular file or a symlink to a regular file
+// 2. It must not point to a console or special device
+// 3. It must be under the whitelist directories
+func isValidLogPath(logPath string) bool {
+	// First check if the path is under the whitelist
+	if !isLogPathUnderWhiteList(logPath) {
+		logger.Warn("Log path is not under whitelist:", logPath)
+		return false
+	}
+
+	// Check if the path exists
+	fileInfo, err := os.Lstat(logPath)
+	if err != nil {
+		// If file doesn't exist, it might be created later
+		// We'll assume it's valid for now
+		return true
+	}
+
+	// If it's a symlink, follow it
+	if fileInfo.Mode()&os.ModeSymlink != 0 {
+		linkTarget, err := os.Readlink(logPath)
+		if err != nil {
+			return false
+		}
+
+		// Make absolute path if the link target is relative
+		if !filepath.IsAbs(linkTarget) {
+			linkTarget = filepath.Join(filepath.Dir(logPath), linkTarget)
+		}
+
+		// Check the target file
+		targetInfo, err := os.Stat(linkTarget)
+		if err != nil {
+			return false
+		}
+
+		// Only accept regular files as targets
+		return targetInfo.Mode().IsRegular()
+	}
+
+	// For non-symlinks, just check if it's a regular file
+	return fileInfo.Mode().IsRegular()
+}
+
+// Shutdown cleans up resources used by the scanner
+func (s *NginxLogScanner) Shutdown() {
+	if s.watcher != nil {
+		s.watcher.Close()
+	}
+
+	if s.scanTicker != nil {
+		s.scanTicker.Stop()
+	}
+
+	// Clean up subscriber resources
+	s.subscriberMux.Lock()
+	// Close all subscriber channels
+	for ch := range s.subscribers {
+		close(ch)
+	}
+	// Clear the map
+	s.subscribers = make(map[chan bool]struct{})
+	s.subscriberMux.Unlock()
+
+	// Close the status channel
+	close(s.statusChan)
+}
+
+// GetAllLogPaths returns all cached log paths
+func GetAllLogPaths(filters ...func(*NginxLogCache) bool) []*NginxLogCache {
+	s := GetNginxLogScanner()
+	s.cacheMutex.RLock()
+	defer s.cacheMutex.RUnlock()
+
+	result := make([]*NginxLogCache, 0, len(s.logCache))
+	for _, cache := range s.logCache {
+		flag := true
+		if len(filters) > 0 {
+			for _, filter := range filters {
+				if !filter(cache) {
+					flag = false
+					break
+				}
+			}
+		}
+		if flag {
+			result = append(result, cache)
+		}
+	}
+
+	return result
+}
+
+// IsScanning returns whether a scan is currently in progress
+func IsScanning() bool {
+	s := GetNginxLogScanner()
+	s.scanMutex.RLock()
+	defer s.scanMutex.RUnlock()
+	return s.scanning
+}

+ 43 - 0
internal/nginx_log/log_list.go

@@ -0,0 +1,43 @@
+package nginx_log
+
+import (
+	"slices"
+
+	"github.com/0xJacky/Nginx-UI/internal/cache"
+)
+
+func typeToInt(t string) int {
+	if t == "access" {
+		return 0
+	}
+	return 1
+}
+
+func sortCompare(i, j *cache.NginxLogCache, key string, order string) bool {
+	flag := false
+
+	switch key {
+	case "type":
+		flag = typeToInt(i.Type) > typeToInt(j.Type)
+	default:
+		fallthrough
+	case "name":
+		flag = i.Name > j.Name
+	}
+
+	if order == "asc" {
+		flag = !flag
+	}
+
+	return flag
+}
+
+func Sort(key string, order string, configs []*cache.NginxLogCache) []*cache.NginxLogCache {
+	slices.SortStableFunc(configs, func(i, j *cache.NginxLogCache) int {
+		if sortCompare(i, j, key, order) {
+			return 1
+		}
+		return -1
+	})
+	return configs
+}