enhance: nginx log

This commit is contained in:
Jacky 2025-04-02 18:56:15 +08:00
parent 2e284c5aa1
commit 56f4e5b87f
No known key found for this signature in database
GPG key ID: 215C21B10DF38B4D
24 changed files with 1685 additions and 347 deletions

View file

@ -1,21 +1,18 @@
package nginx_log
import (
"encoding/json"
"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/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"
"github.com/0xJacky/Nginx-UI/internal/cache"
"github.com/0xJacky/Nginx-UI/internal/nginx_log"
"github.com/gin-gonic/gin"
"github.com/pkg/errors"
"github.com/spf13/cast"
"github.com/uozi-tech/cosy"
"github.com/uozi-tech/cosy/logger"
)
const (
@ -24,9 +21,7 @@ const (
type controlStruct struct {
Type string `json:"type"`
ConfName string `json:"conf_name"`
ServerIdx int `json:"server_idx"`
DirectiveIdx int `json:"directive_idx"`
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
func GetLogList(c *gin.Context) {
filters := []func(*cache.NginxLogCache) bool{}
if c.Query("type") != "" {
filters = append(filters, func(cache *cache.NginxLogCache) bool {
return cache.Type == c.Query("type")
})
}
if control.ServerIdx >= len(config.Servers) {
err = nginx_log.ErrServerIdxOutOfRange
return
if c.Query("name") != "" {
filters = append(filters, func(cache *cache.NginxLogCache) bool {
return strings.Contains(cache.Name, c.Query("name"))
})
}
if control.DirectiveIdx >= len(config.Servers[control.ServerIdx].Directives) {
err = nginx_log.ErrDirectiveIdxOutOfRange
return
if c.Query("path") != "" {
filters = append(filters, func(cache *cache.NginxLogCache) bool {
return strings.Contains(cache.Path, c.Query("path"))
})
}
directive := config.Servers[control.ServerIdx].Directives[control.DirectiveIdx]
switch directive.Directive {
case "access_log", "error_log":
// ok
default:
err = nginx_log.ErrLogDirective
return
}
data := cache.GetAllLogPaths(filters...)
if directive.Params == "" {
err = nginx_log.ErrDirectiveParamsIsEmpty
return
}
orderBy := c.DefaultQuery("sort_by", "name")
sort := c.DefaultQuery("order", "desc")
// fix: access_log /var/log/test.log main;
p := strings.Split(directive.Params, " ")
if len(p) > 0 {
logPath = p[0]
}
data = nginx_log.Sort(orderBy, sort, data)
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
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
}
c.JSON(http.StatusOK, gin.H{
"data": data,
})
}

View file

@ -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
api/nginx_log/sse.go Normal file
View file

@ -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
api/nginx_log/websocket.go Normal file
View file

@ -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
}
}

View file

@ -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

View file

@ -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 "عرض"

View file

@ -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"

View file

@ -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"

View file

@ -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"

View file

@ -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"

View file

@ -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 "보기"

View file

@ -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 ""

View file

@ -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 "Просмотр"

View file

@ -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"

View file

@ -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"

View file

@ -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)"

View file

@ -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)"

View file

@ -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'),
},
}],
},
]

View file

@ -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>

View file

@ -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>

View file

@ -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,
},
})
}

View file

@ -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
internal/cache/nginx_log.go vendored Normal file
View file

@ -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
}

View file

@ -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
}