Jacky пре 1 месец
родитељ
комит
56f4e5b87f

+ 29 - 199
api/nginx_log/nginx_log.go

@@ -1,21 +1,18 @@
 package nginx_log
 
 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/gin-gonic/gin"
-	"github.com/gorilla/websocket"
-	"github.com/hpcloud/tail"
 	"github.com/pkg/errors"
 	"github.com/spf13/cast"
 	"github.com/uozi-tech/cosy"
 	"github.com/uozi-tech/cosy/logger"
-	"io"
-	"net/http"
-	"os"
-	"strings"
 )
 
 const (
@@ -23,10 +20,8 @@ const (
 )
 
 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 {
@@ -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) {
 	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 { useUserStore } from '@/pinia'
+import { SSE } from 'sse.js'
 
 export interface INginxLogData {
   type: string
-  conf_name: string
-  server_idx: number
-  directive_idx: number
+  log_path?: string
 }
 
 const nginx_log = {
   page(page = 0, data: INginxLogData | undefined = undefined) {
     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

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

@@ -25,7 +25,12 @@ msgstr "إعدادات المصادقة الثنائية"
 msgid "About"
 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"
 msgstr "سجلات الدخول"
 
@@ -39,7 +44,8 @@ msgstr "مستخدم ACME"
 #: src/views/certificate/DNSCredential.vue:33
 #: src/views/config/configColumns.tsx:42
 #: 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/site/site_category/columns.ts:28
 #: src/views/site/site_list/columns.tsx:76 src/views/stream/StreamList.vue:49
@@ -244,7 +250,7 @@ msgstr "إعدادات المصادقة"
 msgid "Author"
 msgstr "الكاتب"
 
-#: src/views/nginx_log/NginxLog.vue:152
+#: src/views/nginx_log/NginxLog.vue:149
 msgid "Auto Refresh"
 msgstr "التحديث التلقائي"
 
@@ -260,6 +266,10 @@ msgstr "تم تمكين التجديد التلقائي لـ‏%{name}"
 msgid "Automatic Restart"
 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/config/ConfigEditor.vue:213 src/views/config/ConfigList.vue:106
 #: 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."
 msgstr ""
 
-#: src/views/notification/notificationColumns.tsx:58
+#: src/views/notification/notificationColumns.tsx:59
 #: src/views/preference/components/Passkey.vue:95
 #: src/views/site/site_category/columns.ts:16 src/views/user/userColumns.tsx:48
 msgid "Created at"
@@ -771,7 +781,7 @@ msgstr "وصف"
 msgid "Destination file already exists"
 msgstr ""
 
-#: src/views/notification/notificationColumns.tsx:52
+#: src/views/notification/notificationColumns.tsx:53
 msgid "Details"
 msgstr "تفاصيل"
 
@@ -1113,7 +1123,12 @@ msgstr "البيئات"
 msgid "Error"
 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"
 msgstr "سجلات الأخطاء"
 
@@ -1542,6 +1557,12 @@ msgstr ""
 msgid "If left blank, the default CA Dir will be used."
 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
 msgid ""
 "If the number of login failed attempts from a ip reach the max attempts in "
@@ -1571,6 +1592,14 @@ msgstr "استيراد"
 msgid "Import Certificate"
 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/constants/index.ts:18 src/views/notification/notificationColumns.tsx:29
 msgid "Info"
@@ -1793,6 +1822,11 @@ msgstr "أماكن"
 msgid "Log"
 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
 msgid "Login"
 msgstr "تسجيل الدخول"
@@ -1906,6 +1940,7 @@ msgstr "توجيه متعدد الأسطر"
 #: src/views/config/components/Mkdir.vue:64
 #: src/views/config/configColumns.tsx:7 src/views/config/ConfigEditor.vue:256
 #: src/views/environment/envColumns.tsx:9
+#: src/views/nginx_log/NginxLogList.vue:35
 #: src/views/preference/components/AddPasskey.vue:75
 #: src/views/site/ngx_conf/NgxUpstream.vue:177
 #: src/views/site/site_category/columns.ts:7
@@ -2025,7 +2060,7 @@ msgstr "مسار سجل أخطاء Nginx"
 msgid "Nginx is not running"
 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"
 msgstr "سجل Nginx"
 
@@ -2282,6 +2317,7 @@ msgid "Password length cannot exceed 20 characters"
 msgstr ""
 
 #: 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:137
 msgid "Path"
@@ -3369,7 +3405,7 @@ msgstr "كبح"
 msgid "Tips"
 msgstr "نصائح"
 
-#: src/views/notification/notificationColumns.tsx:44
+#: src/views/notification/notificationColumns.tsx:45
 msgid "Title"
 msgstr "عنوان"
 
@@ -3447,6 +3483,7 @@ msgid "Two-factor authentication required"
 msgstr "يتطلب المصادقة الثنائية"
 
 #: src/views/certificate/CertificateList/certColumns.tsx:25
+#: src/views/nginx_log/NginxLogList.vue:20
 #: src/views/notification/notificationColumns.tsx:9
 msgid "Type"
 msgstr "نوع"
@@ -3549,6 +3586,7 @@ msgid "Version"
 msgstr "إصدار"
 
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:488
+#: src/views/nginx_log/NginxLogList.vue:143
 #: src/views/site/ngx_conf/config_template/ConfigTemplate.vue:103
 msgid "View"
 msgstr "عرض"

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

@@ -21,7 +21,12 @@ msgstr "2FA-Einstellungen"
 msgid "About"
 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"
 msgstr "Zugriffslog"
 
@@ -36,7 +41,8 @@ msgstr "Benutzername"
 #: src/views/certificate/DNSCredential.vue:33
 #: src/views/config/configColumns.tsx:42
 #: 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/site/site_category/columns.ts:28
 #: src/views/site/site_list/columns.tsx:76 src/views/stream/StreamList.vue:49
@@ -258,7 +264,7 @@ msgstr "Authentifizierungseinstellungen"
 msgid "Author"
 msgstr "Autor"
 
-#: src/views/nginx_log/NginxLog.vue:152
+#: src/views/nginx_log/NginxLog.vue:149
 msgid "Auto Refresh"
 msgstr "Automatische Aktualisierung"
 
@@ -274,6 +280,10 @@ msgstr "Automatische Verlängerung aktiviert für %{name}"
 msgid "Automatic Restart"
 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/config/ConfigEditor.vue:213 src/views/config/ConfigList.vue:106
 #: 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."
 msgstr ""
 
-#: src/views/notification/notificationColumns.tsx:58
+#: src/views/notification/notificationColumns.tsx:59
 #: src/views/preference/components/Passkey.vue:95
 #: src/views/site/site_category/columns.ts:16 src/views/user/userColumns.tsx:48
 msgid "Created at"
@@ -799,7 +809,7 @@ msgstr "Beschreibung"
 msgid "Destination file already exists"
 msgstr ""
 
-#: src/views/notification/notificationColumns.tsx:52
+#: src/views/notification/notificationColumns.tsx:53
 msgid "Details"
 msgstr "Details"
 
@@ -1161,7 +1171,12 @@ msgstr "Kommentare"
 msgid "Error"
 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"
 msgstr "Feherlogs"
 
@@ -1592,6 +1607,12 @@ msgstr "ICP-Nummer"
 msgid "If left blank, the default CA Dir will be used."
 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
 msgid ""
 "If the number of login failed attempts from a ip reach the max attempts in "
@@ -1625,6 +1646,14 @@ msgstr "Import"
 msgid "Import Certificate"
 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/constants/index.ts:18 src/views/notification/notificationColumns.tsx:29
 msgid "Info"
@@ -1859,6 +1888,11 @@ msgstr "Orte"
 msgid "Log"
 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
 msgid "Login"
 msgstr "Login"
@@ -1981,6 +2015,7 @@ msgstr "Einzelne Anweisung"
 #: src/views/config/components/Mkdir.vue:64
 #: src/views/config/configColumns.tsx:7 src/views/config/ConfigEditor.vue:256
 #: src/views/environment/envColumns.tsx:9
+#: src/views/nginx_log/NginxLogList.vue:35
 #: src/views/preference/components/AddPasskey.vue:75
 #: src/views/site/ngx_conf/NgxUpstream.vue:177
 #: src/views/site/site_category/columns.ts:7
@@ -2104,7 +2139,7 @@ msgstr "Nginx Fehlerlog-Pfad"
 msgid "Nginx is not running"
 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"
 msgstr "Nginx-Log"
 
@@ -2370,6 +2405,7 @@ msgid "Password length cannot exceed 20 characters"
 msgstr "Passwort darf nicht länger als 20 Zeichen sein"
 
 #: 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:137
 msgid "Path"
@@ -3511,7 +3547,7 @@ msgstr "Begrenzung"
 msgid "Tips"
 msgstr "Tipps"
 
-#: src/views/notification/notificationColumns.tsx:44
+#: src/views/notification/notificationColumns.tsx:45
 msgid "Title"
 msgstr "Titel"
 
@@ -3585,6 +3621,7 @@ msgid "Two-factor authentication required"
 msgstr "Zwei-Faktor-Authentifizierung erforderlich"
 
 #: src/views/certificate/CertificateList/certColumns.tsx:25
+#: src/views/nginx_log/NginxLogList.vue:20
 #: src/views/notification/notificationColumns.tsx:9
 msgid "Type"
 msgstr "Typ"
@@ -3692,6 +3729,7 @@ msgid "Version"
 msgstr ""
 
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:488
+#: src/views/nginx_log/NginxLogList.vue:143
 #: src/views/site/ngx_conf/config_template/ConfigTemplate.vue:103
 msgid "View"
 msgstr "Anzeigen"

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

@@ -21,7 +21,12 @@ msgstr ""
 msgid "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
 msgid "Access Logs"
 msgstr "Sites List"
@@ -37,7 +42,8 @@ msgstr "Username"
 #: src/views/certificate/DNSCredential.vue:33
 #: src/views/config/configColumns.tsx:42
 #: 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/site/site_category/columns.ts:28
 #: src/views/site/site_list/columns.tsx:76 src/views/stream/StreamList.vue:49
@@ -255,7 +261,7 @@ msgstr ""
 msgid "Author"
 msgstr ""
 
-#: src/views/nginx_log/NginxLog.vue:152
+#: src/views/nginx_log/NginxLog.vue:149
 msgid "Auto Refresh"
 msgstr ""
 
@@ -271,6 +277,10 @@ msgstr "Auto-renewal enabled for %{name}"
 msgid "Automatic Restart"
 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/config/ConfigEditor.vue:213 src/views/config/ConfigList.vue:106
 #: 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."
 msgstr ""
 
-#: src/views/notification/notificationColumns.tsx:58
+#: src/views/notification/notificationColumns.tsx:59
 #: src/views/preference/components/Passkey.vue:95
 #: src/views/site/site_category/columns.ts:16 src/views/user/userColumns.tsx:48
 msgid "Created at"
@@ -790,7 +800,7 @@ msgstr ""
 msgid "Destination file already exists"
 msgstr ""
 
-#: src/views/notification/notificationColumns.tsx:52
+#: src/views/notification/notificationColumns.tsx:53
 msgid "Details"
 msgstr ""
 
@@ -1147,7 +1157,11 @@ msgstr "Comments"
 msgid "Error"
 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"
 msgstr ""
 
@@ -1580,6 +1594,12 @@ msgstr ""
 msgid "If left blank, the default CA Dir will be used."
 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
 msgid ""
 "If the number of login failed attempts from a ip reach the max attempts in "
@@ -1606,6 +1626,14 @@ msgstr ""
 msgid "Import Certificate"
 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/constants/index.ts:18 src/views/notification/notificationColumns.tsx:29
 msgid "Info"
@@ -1845,6 +1873,10 @@ msgstr "Locations"
 msgid "Log"
 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
 msgid "Login"
 msgstr "Login"
@@ -1960,6 +1992,7 @@ msgstr "Single Directive"
 #: src/views/config/components/Mkdir.vue:64
 #: src/views/config/configColumns.tsx:7 src/views/config/ConfigEditor.vue:256
 #: src/views/environment/envColumns.tsx:9
+#: src/views/nginx_log/NginxLogList.vue:35
 #: src/views/preference/components/AddPasskey.vue:75
 #: src/views/site/ngx_conf/NgxUpstream.vue:177
 #: src/views/site/site_category/columns.ts:7
@@ -2083,7 +2116,7 @@ msgstr ""
 msgid "Nginx is not running"
 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"
 msgstr ""
 
@@ -2340,6 +2373,7 @@ msgid "Password length cannot exceed 20 characters"
 msgstr ""
 
 #: 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:137
 msgid "Path"
@@ -3459,7 +3493,7 @@ msgstr ""
 msgid "Tips"
 msgstr ""
 
-#: src/views/notification/notificationColumns.tsx:44
+#: src/views/notification/notificationColumns.tsx:45
 msgid "Title"
 msgstr ""
 
@@ -3520,6 +3554,7 @@ msgid "Two-factor authentication required"
 msgstr ""
 
 #: src/views/certificate/CertificateList/certColumns.tsx:25
+#: src/views/nginx_log/NginxLogList.vue:20
 #: src/views/notification/notificationColumns.tsx:9
 msgid "Type"
 msgstr ""
@@ -3628,6 +3663,7 @@ msgid "Version"
 msgstr ""
 
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:488
+#: src/views/nginx_log/NginxLogList.vue:143
 #: src/views/site/ngx_conf/config_template/ConfigTemplate.vue:103
 #, fuzzy
 msgid "View"

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

@@ -28,7 +28,12 @@ msgstr "Configuración de 2FA"
 msgid "About"
 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"
 msgstr "Logs de acceso"
 
@@ -42,7 +47,8 @@ msgstr "Usuario ACME"
 #: src/views/certificate/DNSCredential.vue:33
 #: src/views/config/configColumns.tsx:42
 #: 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/site/site_category/columns.ts:28
 #: 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"
 msgstr "Autor"
 
-#: src/views/nginx_log/NginxLog.vue:152
+#: src/views/nginx_log/NginxLog.vue:149
 msgid "Auto Refresh"
 msgstr "Actualización automática"
 
@@ -265,6 +271,10 @@ msgstr "Renovación automática habilitada por %{name}"
 msgid "Automatic Restart"
 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/config/ConfigEditor.vue:213 src/views/config/ConfigList.vue:106
 #: 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."
 msgstr ""
 
-#: src/views/notification/notificationColumns.tsx:58
+#: src/views/notification/notificationColumns.tsx:59
 #: src/views/preference/components/Passkey.vue:95
 #: src/views/site/site_category/columns.ts:16 src/views/user/userColumns.tsx:48
 msgid "Created at"
@@ -772,7 +782,7 @@ msgstr "Descripción"
 msgid "Destination file already exists"
 msgstr ""
 
-#: src/views/notification/notificationColumns.tsx:52
+#: src/views/notification/notificationColumns.tsx:53
 msgid "Details"
 msgstr "Detalles"
 
@@ -1116,7 +1126,12 @@ msgstr "Entornos"
 msgid "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"
 msgstr "Logs de error"
 
@@ -1543,6 +1558,12 @@ msgstr ""
 msgid "If left blank, the default CA Dir will be used."
 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
 msgid ""
 "If the number of login failed attempts from a ip reach the max attempts in "
@@ -1574,6 +1595,14 @@ msgstr "Importar"
 msgid "Import Certificate"
 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/constants/index.ts:18 src/views/notification/notificationColumns.tsx:29
 msgid "Info"
@@ -1795,6 +1824,11 @@ msgstr "Ubicaciones"
 msgid "Log"
 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
 msgid "Login"
 msgstr "Acceso"
@@ -1909,6 +1943,7 @@ msgstr "Directiva multilínea"
 #: src/views/config/components/Mkdir.vue:64
 #: src/views/config/configColumns.tsx:7 src/views/config/ConfigEditor.vue:256
 #: src/views/environment/envColumns.tsx:9
+#: src/views/nginx_log/NginxLogList.vue:35
 #: src/views/preference/components/AddPasskey.vue:75
 #: src/views/site/ngx_conf/NgxUpstream.vue:177
 #: 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"
 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"
 msgstr "Registro Nginx"
 
@@ -2291,6 +2326,7 @@ msgid "Password length cannot exceed 20 characters"
 msgstr ""
 
 #: 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:137
 msgid "Path"
@@ -3412,7 +3448,7 @@ msgstr "Acelerador"
 msgid "Tips"
 msgstr "Consejos"
 
-#: src/views/notification/notificationColumns.tsx:44
+#: src/views/notification/notificationColumns.tsx:45
 msgid "Title"
 msgstr "Título"
 
@@ -3488,6 +3524,7 @@ msgid "Two-factor authentication required"
 msgstr "Se requiere autenticación de dos factores"
 
 #: src/views/certificate/CertificateList/certColumns.tsx:25
+#: src/views/nginx_log/NginxLogList.vue:20
 #: src/views/notification/notificationColumns.tsx:9
 msgid "Type"
 msgstr "Tipo"
@@ -3591,6 +3628,7 @@ msgid "Version"
 msgstr "Versión"
 
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:488
+#: src/views/nginx_log/NginxLogList.vue:143
 #: src/views/site/ngx_conf/config_template/ConfigTemplate.vue:103
 msgid "View"
 msgstr "Ver"

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

@@ -26,7 +26,12 @@ msgstr "Options 2FA"
 msgid "About"
 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"
 msgstr "Journaux d'accès"
 
@@ -41,7 +46,8 @@ msgstr "Nom d'utilisateur"
 #: src/views/certificate/DNSCredential.vue:33
 #: src/views/config/configColumns.tsx:42
 #: 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/site/site_category/columns.ts:28
 #: src/views/site/site_list/columns.tsx:76 src/views/stream/StreamList.vue:49
@@ -262,7 +268,7 @@ msgstr "Options d'authentification"
 msgid "Author"
 msgstr "Autheur"
 
-#: src/views/nginx_log/NginxLog.vue:152
+#: src/views/nginx_log/NginxLog.vue:149
 msgid "Auto Refresh"
 msgstr "Actualisation automatique"
 
@@ -278,6 +284,10 @@ msgstr "Renouvellement automatique activé pour %{name}"
 msgid "Automatic Restart"
 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/config/ConfigEditor.vue:213 src/views/config/ConfigList.vue:106
 #: 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."
 msgstr ""
 
-#: src/views/notification/notificationColumns.tsx:58
+#: src/views/notification/notificationColumns.tsx:59
 #: src/views/preference/components/Passkey.vue:95
 #: src/views/site/site_category/columns.ts:16 src/views/user/userColumns.tsx:48
 msgid "Created at"
@@ -805,7 +815,7 @@ msgstr "Description"
 msgid "Destination file already exists"
 msgstr "Le fichier de destination existe déjà"
 
-#: src/views/notification/notificationColumns.tsx:52
+#: src/views/notification/notificationColumns.tsx:53
 msgid "Details"
 msgstr "Détails"
 
@@ -1166,7 +1176,12 @@ msgstr "Commentaires"
 msgid "Error"
 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"
 msgstr "Journaux d'erreurs"
 
@@ -1604,6 +1619,12 @@ msgstr ""
 msgid "If left blank, the default CA Dir will be used."
 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
 #, fuzzy
 msgid ""
@@ -1640,6 +1661,14 @@ msgstr "Exporter"
 msgid "Import Certificate"
 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/constants/index.ts:18 src/views/notification/notificationColumns.tsx:29
 msgid "Info"
@@ -1876,6 +1905,11 @@ msgstr "Localisations"
 msgid "Log"
 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
 msgid "Login"
 msgstr "Connexion"
@@ -1989,6 +2023,7 @@ msgstr "Directive multiligne"
 #: src/views/config/components/Mkdir.vue:64
 #: src/views/config/configColumns.tsx:7 src/views/config/ConfigEditor.vue:256
 #: src/views/environment/envColumns.tsx:9
+#: src/views/nginx_log/NginxLogList.vue:35
 #: src/views/preference/components/AddPasskey.vue:75
 #: src/views/site/ngx_conf/NgxUpstream.vue:177
 #: src/views/site/site_category/columns.ts:7
@@ -2112,7 +2147,7 @@ msgstr "Chemin du journal des erreurs Nginx"
 msgid "Nginx is not running"
 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"
 msgstr "Journal Nginx"
 
@@ -2368,6 +2403,7 @@ msgid "Password length cannot exceed 20 characters"
 msgstr ""
 
 #: 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:137
 msgid "Path"
@@ -3499,7 +3535,7 @@ msgstr ""
 msgid "Tips"
 msgstr ""
 
-#: src/views/notification/notificationColumns.tsx:44
+#: src/views/notification/notificationColumns.tsx:45
 msgid "Title"
 msgstr ""
 
@@ -3564,6 +3600,7 @@ msgid "Two-factor authentication required"
 msgstr ""
 
 #: src/views/certificate/CertificateList/certColumns.tsx:25
+#: src/views/nginx_log/NginxLogList.vue:20
 #: src/views/notification/notificationColumns.tsx:9
 msgid "Type"
 msgstr "Type"
@@ -3670,6 +3707,7 @@ msgid "Version"
 msgstr "Version actuelle"
 
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:488
+#: src/views/nginx_log/NginxLogList.vue:143
 #: src/views/site/ngx_conf/config_template/ConfigTemplate.vue:103
 msgid "View"
 msgstr "Voir"

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

@@ -26,7 +26,12 @@ msgstr "2FA 설정"
 msgid "About"
 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"
 msgstr "접근 로그"
 
@@ -40,7 +45,8 @@ msgstr "ACME 사용자"
 #: src/views/certificate/DNSCredential.vue:33
 #: src/views/config/configColumns.tsx:42
 #: 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/site/site_category/columns.ts:28
 #: src/views/site/site_list/columns.tsx:76 src/views/stream/StreamList.vue:49
@@ -246,7 +252,7 @@ msgstr ""
 msgid "Author"
 msgstr "저자"
 
-#: src/views/nginx_log/NginxLog.vue:152
+#: src/views/nginx_log/NginxLog.vue:149
 msgid "Auto Refresh"
 msgstr "자동 새로고침"
 
@@ -262,6 +268,10 @@ msgstr "%{name}에 대한 자동 갱신 활성화됨"
 msgid "Automatic Restart"
 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/config/ConfigEditor.vue:213 src/views/config/ConfigList.vue:106
 #: 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."
 msgstr ""
 
-#: src/views/notification/notificationColumns.tsx:58
+#: src/views/notification/notificationColumns.tsx:59
 #: src/views/preference/components/Passkey.vue:95
 #: src/views/site/site_category/columns.ts:16 src/views/user/userColumns.tsx:48
 msgid "Created at"
@@ -770,7 +780,7 @@ msgstr "설명"
 msgid "Destination file already exists"
 msgstr ""
 
-#: src/views/notification/notificationColumns.tsx:52
+#: src/views/notification/notificationColumns.tsx:53
 msgid "Details"
 msgstr "세부 사항"
 
@@ -1115,7 +1125,12 @@ msgstr "환경"
 msgid "Error"
 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"
 msgstr "오류 로그"
 
@@ -1543,6 +1558,12 @@ msgstr ""
 msgid "If left blank, the default CA Dir will be used."
 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
 msgid ""
 "If the number of login failed attempts from a ip reach the max attempts in "
@@ -1569,6 +1590,14 @@ msgstr "가져오기"
 msgid "Import Certificate"
 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/constants/index.ts:18 src/views/notification/notificationColumns.tsx:29
 msgid "Info"
@@ -1801,6 +1830,10 @@ msgstr "위치들"
 msgid "Log"
 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
 msgid "Login"
 msgstr "로그인"
@@ -1921,6 +1954,7 @@ msgstr "단일 지시문"
 #: src/views/config/components/Mkdir.vue:64
 #: src/views/config/configColumns.tsx:7 src/views/config/ConfigEditor.vue:256
 #: src/views/environment/envColumns.tsx:9
+#: src/views/nginx_log/NginxLogList.vue:35
 #: src/views/preference/components/AddPasskey.vue:75
 #: src/views/site/ngx_conf/NgxUpstream.vue:177
 #: src/views/site/site_category/columns.ts:7
@@ -2044,7 +2078,7 @@ msgstr "Nginx 오류 로그 경로"
 msgid "Nginx is not running"
 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"
 msgstr "Nginx 로그"
 
@@ -2301,6 +2335,7 @@ msgid "Password length cannot exceed 20 characters"
 msgstr ""
 
 #: 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:137
 msgid "Path"
@@ -3421,7 +3456,7 @@ msgstr ""
 msgid "Tips"
 msgstr "팁"
 
-#: src/views/notification/notificationColumns.tsx:44
+#: src/views/notification/notificationColumns.tsx:45
 msgid "Title"
 msgstr "제목"
 
@@ -3485,6 +3520,7 @@ msgid "Two-factor authentication required"
 msgstr ""
 
 #: src/views/certificate/CertificateList/certColumns.tsx:25
+#: src/views/nginx_log/NginxLogList.vue:20
 #: src/views/notification/notificationColumns.tsx:9
 msgid "Type"
 msgstr "유형"
@@ -3593,6 +3629,7 @@ msgid "Version"
 msgstr "현재 버전"
 
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:488
+#: src/views/nginx_log/NginxLogList.vue:143
 #: src/views/site/ngx_conf/config_template/ConfigTemplate.vue:103
 msgid "View"
 msgstr "보기"

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

@@ -14,8 +14,12 @@ msgstr ""
 msgid "About"
 msgstr ""
 
+#: 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:75
+#: src/views/site/ngx_conf/LogEntry.vue:91
 msgid "Access Logs"
 msgstr ""
 
@@ -30,7 +34,8 @@ msgstr ""
 #: src/views/certificate/DNSCredential.vue:33
 #: src/views/config/configColumns.tsx:42
 #: 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/site/site_category/columns.ts:28
 #: src/views/site/site_list/columns.tsx:76
@@ -232,7 +237,7 @@ msgstr ""
 msgid "Author"
 msgstr ""
 
-#: src/views/nginx_log/NginxLog.vue:152
+#: src/views/nginx_log/NginxLog.vue:149
 msgid "Auto Refresh"
 msgstr ""
 
@@ -248,6 +253,10 @@ msgstr ""
 msgid "Automatic Restart"
 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/config/ConfigEditor.vue:213
 #: 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."
 msgstr ""
 
-#: src/views/notification/notificationColumns.tsx:58
+#: src/views/notification/notificationColumns.tsx:59
 #: src/views/preference/components/Passkey.vue:95
 #: src/views/site/site_category/columns.ts:16
 #: src/views/user/userColumns.tsx:48
@@ -731,7 +740,7 @@ msgstr ""
 msgid "Destination file already exists"
 msgstr ""
 
-#: src/views/notification/notificationColumns.tsx:52
+#: src/views/notification/notificationColumns.tsx:53
 msgid "Details"
 msgstr ""
 
@@ -1060,8 +1069,12 @@ msgstr ""
 msgid "Error"
 msgstr ""
 
+#: 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:83
+#: src/views/site/ngx_conf/LogEntry.vue:99
 msgid "Error Logs"
 msgstr ""
 
@@ -1449,6 +1462,10 @@ msgstr ""
 msgid "If left blank, the default CA Dir will be used."
 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
 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 ""
@@ -1470,6 +1487,14 @@ msgstr ""
 msgid "Import Certificate"
 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/constants/index.ts:18
 #: src/views/notification/notificationColumns.tsx:29
@@ -1682,6 +1707,11 @@ msgstr ""
 msgid "Log"
 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
 msgid "Login"
@@ -1787,6 +1817,7 @@ msgstr ""
 #: src/views/config/configColumns.tsx:7
 #: src/views/config/ConfigEditor.vue:256
 #: src/views/environment/envColumns.tsx:9
+#: src/views/nginx_log/NginxLogList.vue:35
 #: src/views/preference/components/AddPasskey.vue:75
 #: src/views/site/ngx_conf/NgxUpstream.vue:177
 #: src/views/site/site_category/columns.ts:7
@@ -1904,7 +1935,7 @@ msgid "Nginx is not running"
 msgstr ""
 
 #: src/routes/modules/nginx_log.ts:9
-#: src/views/nginx_log/NginxLog.vue:148
+#: src/views/nginx_log/NginxLog.vue:143
 msgid "Nginx Log"
 msgstr ""
 
@@ -2139,6 +2170,7 @@ msgid "Password length cannot exceed 20 characters"
 msgstr ""
 
 #: 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:137
 msgid "Path"
@@ -3125,7 +3157,7 @@ msgstr ""
 msgid "Tips"
 msgstr ""
 
-#: src/views/notification/notificationColumns.tsx:44
+#: src/views/notification/notificationColumns.tsx:45
 msgid "Title"
 msgstr ""
 
@@ -3172,6 +3204,7 @@ msgid "Two-factor authentication required"
 msgstr ""
 
 #: src/views/certificate/CertificateList/certColumns.tsx:25
+#: src/views/nginx_log/NginxLogList.vue:20
 #: src/views/notification/notificationColumns.tsx:9
 msgid "Type"
 msgstr ""
@@ -3279,6 +3312,7 @@ msgid "Version"
 msgstr ""
 
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:488
+#: src/views/nginx_log/NginxLogList.vue:143
 #: src/views/site/ngx_conf/config_template/ConfigTemplate.vue:103
 msgid "View"
 msgstr ""

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

@@ -28,7 +28,12 @@ msgstr "Настройки 2FA"
 msgid "About"
 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"
 msgstr "Журналы доступа"
 
@@ -42,7 +47,8 @@ msgstr "Пользователь ACME"
 #: src/views/certificate/DNSCredential.vue:33
 #: src/views/config/configColumns.tsx:42
 #: 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/site/site_category/columns.ts:28
 #: src/views/site/site_list/columns.tsx:76 src/views/stream/StreamList.vue:49
@@ -243,7 +249,7 @@ msgstr "Настройки аутентификации"
 msgid "Author"
 msgstr "Автор"
 
-#: src/views/nginx_log/NginxLog.vue:152
+#: src/views/nginx_log/NginxLog.vue:149
 msgid "Auto Refresh"
 msgstr "Автообновление"
 
@@ -259,6 +265,10 @@ msgstr "Автообновление включено для %{name}"
 msgid "Automatic Restart"
 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/config/ConfigEditor.vue:213 src/views/config/ConfigList.vue:106
 #: 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."
 msgstr ""
 
-#: src/views/notification/notificationColumns.tsx:58
+#: src/views/notification/notificationColumns.tsx:59
 #: src/views/preference/components/Passkey.vue:95
 #: src/views/site/site_category/columns.ts:16 src/views/user/userColumns.tsx:48
 msgid "Created at"
@@ -757,7 +767,7 @@ msgstr "Описание"
 msgid "Destination file already exists"
 msgstr ""
 
-#: src/views/notification/notificationColumns.tsx:52
+#: src/views/notification/notificationColumns.tsx:53
 msgid "Details"
 msgstr "Детали"
 
@@ -1100,7 +1110,12 @@ msgstr "Окружения"
 msgid "Error"
 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"
 msgstr "Ошибка логирования"
 
@@ -1526,6 +1541,12 @@ msgstr "ICP номер"
 msgid "If left blank, the default CA Dir will be used."
 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
 msgid ""
 "If the number of login failed attempts from a ip reach the max attempts in "
@@ -1557,6 +1578,14 @@ msgstr "Импорт"
 msgid "Import Certificate"
 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/constants/index.ts:18 src/views/notification/notificationColumns.tsx:29
 msgid "Info"
@@ -1776,6 +1805,11 @@ msgstr "Локации"
 msgid "Log"
 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
 msgid "Login"
 msgstr "Логин"
@@ -1889,6 +1923,7 @@ msgstr "Многострочная директива"
 #: src/views/config/components/Mkdir.vue:64
 #: src/views/config/configColumns.tsx:7 src/views/config/ConfigEditor.vue:256
 #: src/views/environment/envColumns.tsx:9
+#: src/views/nginx_log/NginxLogList.vue:35
 #: src/views/preference/components/AddPasskey.vue:75
 #: src/views/site/ngx_conf/NgxUpstream.vue:177
 #: src/views/site/site_category/columns.ts:7
@@ -2009,7 +2044,7 @@ msgstr "Путь для Nginx Error Log"
 msgid "Nginx is not running"
 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"
 msgstr "Журнал"
 
@@ -2263,6 +2298,7 @@ msgid "Password length cannot exceed 20 characters"
 msgstr ""
 
 #: 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:137
 msgid "Path"
@@ -3375,7 +3411,7 @@ msgstr ""
 msgid "Tips"
 msgstr "Советы"
 
-#: src/views/notification/notificationColumns.tsx:44
+#: src/views/notification/notificationColumns.tsx:45
 msgid "Title"
 msgstr "Заголовок"
 
@@ -3444,6 +3480,7 @@ msgid "Two-factor authentication required"
 msgstr "Требуется двухфакторная аутентификация"
 
 #: src/views/certificate/CertificateList/certColumns.tsx:25
+#: src/views/nginx_log/NginxLogList.vue:20
 #: src/views/notification/notificationColumns.tsx:9
 msgid "Type"
 msgstr "Тип"
@@ -3547,6 +3584,7 @@ msgid "Version"
 msgstr "Версия"
 
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:488
+#: src/views/nginx_log/NginxLogList.vue:143
 #: src/views/site/ngx_conf/config_template/ConfigTemplate.vue:103
 msgid "View"
 msgstr "Просмотр"

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

@@ -24,7 +24,12 @@ msgstr "2FA Ayarları"
 msgid "About"
 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"
 msgstr "Erişim Günlükleri"
 
@@ -38,7 +43,8 @@ msgstr "ACME Kullanıcısı"
 #: src/views/certificate/DNSCredential.vue:33
 #: src/views/config/configColumns.tsx:42
 #: 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/site/site_category/columns.ts:28
 #: 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"
 msgstr "Yazar"
 
-#: src/views/nginx_log/NginxLog.vue:152
+#: src/views/nginx_log/NginxLog.vue:149
 msgid "Auto Refresh"
 msgstr "Otomatik Yenileme"
 
@@ -260,6 +266,10 @@ msgstr "Otomatik yenileme %{name} için etkinleştirildi"
 msgid "Automatic Restart"
 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/config/ConfigEditor.vue:213 src/views/config/ConfigList.vue:106
 #: 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."
 msgstr ""
 
-#: src/views/notification/notificationColumns.tsx:58
+#: src/views/notification/notificationColumns.tsx:59
 #: src/views/preference/components/Passkey.vue:95
 #: src/views/site/site_category/columns.ts:16 src/views/user/userColumns.tsx:48
 msgid "Created at"
@@ -768,7 +778,7 @@ msgstr "Açıklama"
 msgid "Destination file already exists"
 msgstr ""
 
-#: src/views/notification/notificationColumns.tsx:52
+#: src/views/notification/notificationColumns.tsx:53
 msgid "Details"
 msgstr "Detaylar"
 
@@ -1129,7 +1139,12 @@ msgstr "Ortamlar"
 msgid "Error"
 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"
 msgstr "Hata Günlükleri"
 
@@ -1555,6 +1570,12 @@ msgstr ""
 msgid "If left blank, the default CA Dir will be used."
 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
 msgid ""
 "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"
 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/constants/index.ts:18 src/views/notification/notificationColumns.tsx:29
 msgid "Info"
@@ -1807,6 +1836,11 @@ msgstr "Konumlar"
 msgid "Log"
 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
 msgid "Login"
 msgstr "Giriş"
@@ -1936,6 +1970,7 @@ msgstr "Çok Hatlı Direktif"
 #: src/views/config/components/Mkdir.vue:64
 #: src/views/config/configColumns.tsx:7 src/views/config/ConfigEditor.vue:256
 #: src/views/environment/envColumns.tsx:9
+#: src/views/nginx_log/NginxLogList.vue:35
 #: src/views/preference/components/AddPasskey.vue:75
 #: src/views/site/ngx_conf/NgxUpstream.vue:177
 #: src/views/site/site_category/columns.ts:7
@@ -2071,7 +2106,7 @@ msgstr "Nginx Hata Günlüğü Yolu"
 msgid "Nginx is not running"
 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
 msgid "Nginx Log"
 msgstr "Nginx Günlüğü"
@@ -2360,6 +2395,7 @@ msgid "Password length cannot exceed 20 characters"
 msgstr ""
 
 #: 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:137
 #, fuzzy
@@ -3603,7 +3639,7 @@ msgstr ""
 msgid "Tips"
 msgstr "İpuçları"
 
-#: src/views/notification/notificationColumns.tsx:44
+#: src/views/notification/notificationColumns.tsx:45
 #, fuzzy
 msgid "Title"
 msgstr "Başlık"
@@ -3688,6 +3724,7 @@ msgid "Two-factor authentication required"
 msgstr "İki faktörlü kimlik doğrulama gerekiyor"
 
 #: src/views/certificate/CertificateList/certColumns.tsx:25
+#: src/views/nginx_log/NginxLogList.vue:20
 #: src/views/notification/notificationColumns.tsx:9
 #, fuzzy
 msgid "Type"
@@ -3808,6 +3845,7 @@ msgid "Version"
 msgstr "Versiyon"
 
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:488
+#: src/views/nginx_log/NginxLogList.vue:143
 #: src/views/site/ngx_conf/config_template/ConfigTemplate.vue:103
 #, fuzzy
 msgid "View"

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

@@ -21,7 +21,12 @@ msgstr ""
 msgid "About"
 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"
 msgstr "Log truy cập"
 
@@ -36,7 +41,8 @@ msgstr "Người dùng"
 #: src/views/certificate/DNSCredential.vue:33
 #: src/views/config/configColumns.tsx:42
 #: 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/site/site_category/columns.ts:28
 #: src/views/site/site_list/columns.tsx:76 src/views/stream/StreamList.vue:49
@@ -255,7 +261,7 @@ msgstr ""
 msgid "Author"
 msgstr "Tác giả"
 
-#: src/views/nginx_log/NginxLog.vue:152
+#: src/views/nginx_log/NginxLog.vue:149
 msgid "Auto Refresh"
 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"
 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/config/ConfigEditor.vue:213 src/views/config/ConfigList.vue:106
 #: 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."
 msgstr ""
 
-#: src/views/notification/notificationColumns.tsx:58
+#: src/views/notification/notificationColumns.tsx:59
 #: src/views/preference/components/Passkey.vue:95
 #: src/views/site/site_category/columns.ts:16 src/views/user/userColumns.tsx:48
 msgid "Created at"
@@ -794,7 +804,7 @@ msgstr "Mô tả"
 msgid "Destination file already exists"
 msgstr ""
 
-#: src/views/notification/notificationColumns.tsx:52
+#: src/views/notification/notificationColumns.tsx:53
 msgid "Details"
 msgstr "Chi tiết"
 
@@ -1152,7 +1162,12 @@ msgstr "Environments"
 msgid "Error"
 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"
 msgstr "Log lỗi"
 
@@ -1579,6 +1594,12 @@ msgstr ""
 msgid "If left blank, the default CA Dir will be used."
 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
 msgid ""
 "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"
 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/constants/index.ts:18 src/views/notification/notificationColumns.tsx:29
 msgid "Info"
@@ -1839,6 +1868,10 @@ msgstr "Locations"
 msgid "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
 msgid "Login"
 msgstr "Đăng nhập"
@@ -1953,6 +1986,7 @@ msgstr "Single Directive"
 #: src/views/config/components/Mkdir.vue:64
 #: src/views/config/configColumns.tsx:7 src/views/config/ConfigEditor.vue:256
 #: src/views/environment/envColumns.tsx:9
+#: src/views/nginx_log/NginxLogList.vue:35
 #: src/views/preference/components/AddPasskey.vue:75
 #: src/views/site/ngx_conf/NgxUpstream.vue:177
 #: 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"
 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"
 msgstr ""
 
@@ -2331,6 +2365,7 @@ msgid "Password length cannot exceed 20 characters"
 msgstr ""
 
 #: 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:137
 msgid "Path"
@@ -3443,7 +3478,7 @@ msgstr ""
 msgid "Tips"
 msgstr ""
 
-#: src/views/notification/notificationColumns.tsx:44
+#: src/views/notification/notificationColumns.tsx:45
 msgid "Title"
 msgstr "Tiêu đề"
 
@@ -3508,6 +3543,7 @@ msgid "Two-factor authentication required"
 msgstr ""
 
 #: src/views/certificate/CertificateList/certColumns.tsx:25
+#: src/views/nginx_log/NginxLogList.vue:20
 #: src/views/notification/notificationColumns.tsx:9
 msgid "Type"
 msgstr "Loại"
@@ -3616,6 +3652,7 @@ msgid "Version"
 msgstr "Phiên bản hiện tại"
 
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:488
+#: src/views/nginx_log/NginxLogList.vue:143
 #: src/views/site/ngx_conf/config_template/ConfigTemplate.vue:103
 msgid "View"
 msgstr "Xem"

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

@@ -3,7 +3,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: \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"
 "Language-Team: Chinese (Simplified Han script) <https://weblate.nginxui.com/"
 "projects/nginx-ui/frontend/zh_Hans/>\n"
@@ -27,7 +27,11 @@ msgstr "2FA 设置"
 msgid "About"
 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"
 msgstr "访问日志"
 
@@ -41,7 +45,8 @@ msgstr "ACME 用户"
 #: src/views/certificate/DNSCredential.vue:33
 #: src/views/config/configColumns.tsx:42
 #: 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/site/site_category/columns.ts:28
 #: src/views/site/site_list/columns.tsx:76 src/views/stream/StreamList.vue:49
@@ -240,7 +245,7 @@ msgstr "认证设置"
 msgid "Author"
 msgstr "作者"
 
-#: src/views/nginx_log/NginxLog.vue:152
+#: src/views/nginx_log/NginxLog.vue:149
 msgid "Auto Refresh"
 msgstr "自动刷新"
 
@@ -256,6 +261,10 @@ msgstr "成功启用 %{name} 自动续签"
 msgid "Automatic Restart"
 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/config/ConfigEditor.vue:213 src/views/config/ConfigList.vue:106
 #: src/views/config/ConfigList.vue:180 src/views/nginx_log/NginxLog.vue:173
@@ -604,7 +613,7 @@ msgid ""
 msgstr ""
 "创建系统备份,包括 Nginx 配置和 Nginx UI 设置。备份文件将自动下载到你的电脑。"
 
-#: src/views/notification/notificationColumns.tsx:58
+#: src/views/notification/notificationColumns.tsx:59
 #: src/views/preference/components/Passkey.vue:95
 #: src/views/site/site_category/columns.ts:16 src/views/user/userColumns.tsx:48
 msgid "Created at"
@@ -741,7 +750,7 @@ msgstr "描述"
 msgid "Destination file already exists"
 msgstr "目标文件已存在"
 
-#: src/views/notification/notificationColumns.tsx:52
+#: src/views/notification/notificationColumns.tsx:53
 msgid "Details"
 msgstr "详情"
 
@@ -1063,7 +1072,11 @@ msgstr "环境"
 msgid "Error"
 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"
 msgstr "错误日志"
 
@@ -1451,6 +1464,13 @@ msgstr "ICP备案号"
 msgid "If left blank, the default CA Dir will be used."
 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
 msgid ""
 "If the number of login failed attempts from a ip reach the max attempts in "
@@ -1478,6 +1498,14 @@ msgstr "导入"
 msgid "Import Certificate"
 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/constants/index.ts:18 src/views/notification/notificationColumns.tsx:29
 msgid "Info"
@@ -1693,6 +1721,10 @@ msgstr "Locations"
 msgid "Log"
 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
 msgid "Login"
 msgstr "登录"
@@ -1804,6 +1836,7 @@ msgstr "多行指令"
 #: src/views/config/components/Mkdir.vue:64
 #: src/views/config/configColumns.tsx:7 src/views/config/ConfigEditor.vue:256
 #: src/views/environment/envColumns.tsx:9
+#: src/views/nginx_log/NginxLogList.vue:35
 #: src/views/preference/components/AddPasskey.vue:75
 #: src/views/site/ngx_conf/NgxUpstream.vue:177
 #: src/views/site/site_category/columns.ts:7
@@ -1919,7 +1952,7 @@ msgstr "Nginx 错误日志路径"
 msgid "Nginx is not running"
 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"
 msgstr "Nginx 日志"
 
@@ -2163,6 +2196,7 @@ msgid "Password length cannot exceed 20 characters"
 msgstr "密码长度不能超过 20 个字符"
 
 #: 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:137
 msgid "Path"
@@ -3198,7 +3232,7 @@ msgstr "限流"
 msgid "Tips"
 msgstr "提示"
 
-#: src/views/notification/notificationColumns.tsx:44
+#: src/views/notification/notificationColumns.tsx:45
 msgid "Title"
 msgstr "标题"
 
@@ -3265,6 +3299,7 @@ msgid "Two-factor authentication required"
 msgstr "需要两步验证"
 
 #: src/views/certificate/CertificateList/certColumns.tsx:25
+#: src/views/nginx_log/NginxLogList.vue:20
 #: src/views/notification/notificationColumns.tsx:9
 msgid "Type"
 msgstr "类型"
@@ -3366,6 +3401,7 @@ msgid "Version"
 msgstr "版本"
 
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:488
+#: src/views/nginx_log/NginxLogList.vue:143
 #: src/views/site/ngx_conf/config_template/ConfigTemplate.vue:103
 msgid "View"
 msgstr "查看"
@@ -3797,9 +3833,6 @@ msgstr "你的 Passkeys"
 #~ msgid "HTTPS Listen Port"
 #~ msgstr "HTTPS 监听端口"
 
-#~ msgid "Index (index)"
-#~ msgstr "网站首页 (index)"
-
 #~ msgid "Private Key Path (ssl_certificate_key)"
 #~ msgstr "私钥路径 (ssl_certificate_key)"
 

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

@@ -31,7 +31,12 @@ msgstr "多重要素驗證設定"
 msgid "About"
 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"
 msgstr "訪問日誌"
 
@@ -45,7 +50,8 @@ msgstr "ACME 用戶"
 #: src/views/certificate/DNSCredential.vue:33
 #: src/views/config/configColumns.tsx:42
 #: 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/site/site_category/columns.ts:28
 #: src/views/site/site_list/columns.tsx:76 src/views/stream/StreamList.vue:49
@@ -245,7 +251,7 @@ msgstr "認證設定"
 msgid "Author"
 msgstr "作者"
 
-#: src/views/nginx_log/NginxLog.vue:152
+#: src/views/nginx_log/NginxLog.vue:149
 msgid "Auto Refresh"
 msgstr "自動重新整理"
 
@@ -261,6 +267,10 @@ msgstr "已啟用 %{name} 的自動續簽"
 msgid "Automatic Restart"
 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/config/ConfigEditor.vue:213 src/views/config/ConfigList.vue:106
 #: 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."
 msgstr ""
 
-#: src/views/notification/notificationColumns.tsx:58
+#: src/views/notification/notificationColumns.tsx:59
 #: src/views/preference/components/Passkey.vue:95
 #: src/views/site/site_category/columns.ts:16 src/views/user/userColumns.tsx:48
 msgid "Created at"
@@ -751,7 +761,7 @@ msgstr "描述"
 msgid "Destination file already exists"
 msgstr "目的檔案已存在"
 
-#: src/views/notification/notificationColumns.tsx:52
+#: src/views/notification/notificationColumns.tsx:53
 msgid "Details"
 msgstr "詳細資料"
 
@@ -1084,7 +1094,12 @@ msgstr "環境"
 msgid "Error"
 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"
 msgstr "錯誤日誌"
 
@@ -1509,6 +1524,12 @@ msgstr "ICP 編號"
 msgid "If left blank, the default CA Dir will be used."
 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
 msgid ""
 "If the number of login failed attempts from a ip reach the max attempts in "
@@ -1536,6 +1557,15 @@ msgstr "導入"
 msgid "Import Certificate"
 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/constants/index.ts:18 src/views/notification/notificationColumns.tsx:29
 msgid "Info"
@@ -1756,6 +1786,11 @@ msgstr "Locations"
 msgid "Log"
 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
 msgid "Login"
 msgstr "登入"
@@ -1866,6 +1901,7 @@ msgstr "多行指令"
 #: src/views/config/components/Mkdir.vue:64
 #: src/views/config/configColumns.tsx:7 src/views/config/ConfigEditor.vue:256
 #: src/views/environment/envColumns.tsx:9
+#: src/views/nginx_log/NginxLogList.vue:35
 #: src/views/preference/components/AddPasskey.vue:75
 #: src/views/site/ngx_conf/NgxUpstream.vue:177
 #: src/views/site/site_category/columns.ts:7
@@ -1985,7 +2021,7 @@ msgstr "Nginx 錯誤日誌路徑"
 msgid "Nginx is not running"
 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"
 msgstr "Nginx 日誌"
 
@@ -2235,6 +2271,7 @@ msgid "Password length cannot exceed 20 characters"
 msgstr "密碼長度不能超過 20 個字元"
 
 #: 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:137
 msgid "Path"
@@ -3306,7 +3343,7 @@ msgstr "節流"
 msgid "Tips"
 msgstr "提示"
 
-#: src/views/notification/notificationColumns.tsx:44
+#: src/views/notification/notificationColumns.tsx:45
 msgid "Title"
 msgstr "標題"
 
@@ -3373,6 +3410,7 @@ msgid "Two-factor authentication required"
 msgstr "需要多重因素驗證"
 
 #: src/views/certificate/CertificateList/certColumns.tsx:25
+#: src/views/nginx_log/NginxLogList.vue:20
 #: src/views/notification/notificationColumns.tsx:9
 msgid "Type"
 msgstr "類型"
@@ -3474,6 +3512,7 @@ msgid "Version"
 msgstr "版本"
 
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:488
+#: src/views/nginx_log/NginxLogList.vue:143
 #: src/views/site/ngx_conf/config_template/ConfigTemplate.vue:103
 msgid "View"
 msgstr "檢視"
@@ -3866,9 +3905,6 @@ msgstr "您的通行密鑰"
 #~ msgid "HTTPS Listen Port"
 #~ msgstr "HTTPS 監聽埠"
 
-#~ msgid "Index (index)"
-#~ msgstr "網站首頁 (index)"
-
 #~ msgid "Private Key Path (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'),
         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 buffer = ref('')
 const page = ref(0)
-const auto_refresh = ref(true)
+const autoRefresh = ref(true)
 const router = useRouter()
 const loading = ref(false)
 const filter = ref('')
 
+// Setup log control data based on route params
 const control = reactive<INginxLogData>({
   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() {
@@ -33,9 +32,7 @@ function openWs() {
   websocket = ws('/api/nginx_log')
 
   websocket.onopen = () => {
-    websocket.send(JSON.stringify({
-      ...control,
-    }))
+    websocket.send(JSON.stringify(control))
   }
 
   websocket.onmessage = (m: { data: string }) => {
@@ -68,7 +65,7 @@ function init() {
 }
 
 function clearLog() {
-  logContainer.value!.innerHTML = ''
+  buffer.value = ''
 }
 
 onMounted(() => {
@@ -79,10 +76,12 @@ onUnmounted(() => {
   websocket?.close()
 })
 
-watch(auto_refresh, value => {
+watch(autoRefresh, async value => {
   if (value) {
-    openWs()
     clearLog()
+    await nextTick()
+    await init()
+    openWs()
   }
   else {
     websocket.close()
@@ -90,28 +89,24 @@ watch(auto_refresh, value => {
 })
 
 watch(route, () => {
-  init()
-
+  // Update control data when route changes
   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, () => {
   clearLog()
-  auto_refresh.value = true
+  autoRefresh.value = true
 
   nextTick(() => {
     websocket.send(JSON.stringify(control))
   })
 })
 
-function on_scroll_log() {
+function onScrollLog() {
   if (!loading.value && page.value > 0) {
     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(() => {
@@ -148,10 +143,15 @@ const computedBuffer = computed(() => {
     :title="$gettext('Nginx Log')"
     :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">
-      <AFormItem :label="$gettext('Auto Refresh')">
-        <ASwitch v-model:checked="auto_refresh" />
-      </AFormItem>
       <AFormItem :label="$gettext('Filter')">
         <AInput
           v-model:value="filter"
@@ -165,10 +165,10 @@ const computedBuffer = computed(() => {
         ref="logContainer"
         v-dompurify-html="computedBuffer"
         class="nginx-log-container"
-        @scroll="debounce_scroll_log"
+        @scroll="debounceScrollLog"
       />
     </ACard>
-    <FooterToolBar v-if="control.type === 'site'">
+    <FooterToolBar v-if="control.log_path">
       <AButton @click="router.go(-1)">
         {{ $gettext('Back') }}
       </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 errorIdx = ref<number>()
+const accessLogPath = ref<string>()
+const errorLogPath = ref<string>()
 
 const hasAccessLog = computed(() => {
   let flag = false
@@ -18,6 +20,14 @@ const hasAccessLog = computed(() => {
     if (v.directive === 'access_log') {
       flag = true
       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') {
       flag = true
       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({
     path: '/nginx_log/site',
     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({
     path: '/nginx_log/site',
     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
 
 import (
+	"time"
+
 	"github.com/dgraph-io/ristretto/v2"
 	"github.com/uozi-tech/cosy/logger"
-	"time"
 )
 
 var cache *ristretto.Cache[string, any]
@@ -19,6 +20,9 @@ func Init() {
 	if err != nil {
 		logger.Fatal("initializing local cache err", err)
 	}
+
+	// Initialize the nginx log scanner
+	InitNginxLogScanner()
 }
 
 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
+}