mirror of
https://github.com/0xJacky/nginx-ui.git
synced 2025-05-12 02:45:49 +02:00
enhance: nginx log
This commit is contained in:
parent
2e284c5aa1
commit
56f4e5b87f
24 changed files with 1685 additions and 347 deletions
|
@ -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 (
|
||||
|
@ -23,10 +20,8 @@ const (
|
|||
)
|
||||
|
||||
type controlStruct struct {
|
||||
Type string `json:"type"`
|
||||
ConfName string `json:"conf_name"`
|
||||
ServerIdx int `json:"server_idx"`
|
||||
DirectiveIdx int `json:"directive_idx"`
|
||||
Type string `json:"type"`
|
||||
LogPath string `json:"log_path"`
|
||||
}
|
||||
|
||||
type nginxLogPageResp struct {
|
||||
|
@ -130,200 +125,35 @@ func GetNginxLogPage(c *gin.Context) {
|
|||
})
|
||||
}
|
||||
|
||||
func getLogPath(control *controlStruct) (logPath string, err error) {
|
||||
switch control.Type {
|
||||
case "site":
|
||||
var config *nginx.NgxConfig
|
||||
path := nginx.GetConfPath("sites-available", control.ConfName)
|
||||
config, err = nginx.ParseNgxConfig(path)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "error parsing ngx config")
|
||||
return
|
||||
}
|
||||
func GetLogList(c *gin.Context) {
|
||||
filters := []func(*cache.NginxLogCache) bool{}
|
||||
|
||||
if control.ServerIdx >= len(config.Servers) {
|
||||
err = nginx_log.ErrServerIdxOutOfRange
|
||||
return
|
||||
}
|
||||
|
||||
if control.DirectiveIdx >= len(config.Servers[control.ServerIdx].Directives) {
|
||||
err = nginx_log.ErrDirectiveIdxOutOfRange
|
||||
return
|
||||
}
|
||||
|
||||
directive := config.Servers[control.ServerIdx].Directives[control.DirectiveIdx]
|
||||
switch directive.Directive {
|
||||
case "access_log", "error_log":
|
||||
// ok
|
||||
default:
|
||||
err = nginx_log.ErrLogDirective
|
||||
return
|
||||
}
|
||||
|
||||
if directive.Params == "" {
|
||||
err = nginx_log.ErrDirectiveParamsIsEmpty
|
||||
return
|
||||
}
|
||||
|
||||
// fix: access_log /var/log/test.log main;
|
||||
p := strings.Split(directive.Params, " ")
|
||||
if len(p) > 0 {
|
||||
logPath = p[0]
|
||||
}
|
||||
|
||||
case "error":
|
||||
path := nginx.GetErrorLogPath()
|
||||
|
||||
if path == "" {
|
||||
err = nginx_log.ErrErrorLogPathIsEmpty
|
||||
return
|
||||
}
|
||||
|
||||
logPath = path
|
||||
default:
|
||||
path := nginx.GetAccessLogPath()
|
||||
|
||||
if path == "" {
|
||||
err = nginx_log.ErrAccessLogPathIsEmpty
|
||||
return
|
||||
}
|
||||
|
||||
logPath = path
|
||||
if c.Query("type") != "" {
|
||||
filters = append(filters, func(cache *cache.NginxLogCache) bool {
|
||||
return cache.Type == c.Query("type")
|
||||
})
|
||||
}
|
||||
|
||||
// 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
|
||||
if c.Query("name") != "" {
|
||||
filters = append(filters, func(cache *cache.NginxLogCache) bool {
|
||||
return strings.Contains(cache.Name, c.Query("name"))
|
||||
})
|
||||
}
|
||||
|
||||
if c.Query("path") != "" {
|
||||
filters = append(filters, func(cache *cache.NginxLogCache) bool {
|
||||
return strings.Contains(cache.Path, c.Query("path"))
|
||||
})
|
||||
}
|
||||
|
||||
data := cache.GetAllLogPaths(filters...)
|
||||
|
||||
orderBy := c.DefaultQuery("sort_by", "name")
|
||||
sort := c.DefaultQuery("order", "desc")
|
||||
|
||||
data = nginx_log.Sort(orderBy, sort, data)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"data": data,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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
50
api/nginx_log/sse.go
Normal 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
189
api/nginx_log/websocket.go
Normal 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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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 "عرض"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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 "보기"
|
||||
|
|
|
@ -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 ""
|
||||
|
|
|
@ -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 "Просмотр"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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)"
|
||||
|
||||
|
|
|
@ -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)"
|
||||
|
||||
|
|
|
@ -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'),
|
||||
},
|
||||
}],
|
||||
},
|
||||
]
|
||||
|
|
|
@ -11,16 +11,15 @@ let websocket: ReconnectingWebSocket | WebSocket
|
|||
const route = useRoute()
|
||||
const buffer = ref('')
|
||||
const page = ref(0)
|
||||
const auto_refresh = ref(true)
|
||||
const autoRefresh = ref(true)
|
||||
const router = useRouter()
|
||||
const loading = ref(false)
|
||||
const filter = ref('')
|
||||
|
||||
// Setup log control data based on route params
|
||||
const control = reactive<INginxLogData>({
|
||||
type: logType(),
|
||||
conf_name: route.query.conf_name as string,
|
||||
server_idx: Number.parseInt(route.query.server_idx as string),
|
||||
directive_idx: Number.parseInt(route.query.directive_idx as string),
|
||||
log_path: route.query.log_path as string,
|
||||
})
|
||||
|
||||
function logType() {
|
||||
|
@ -33,9 +32,7 @@ function openWs() {
|
|||
websocket = ws('/api/nginx_log')
|
||||
|
||||
websocket.onopen = () => {
|
||||
websocket.send(JSON.stringify({
|
||||
...control,
|
||||
}))
|
||||
websocket.send(JSON.stringify(control))
|
||||
}
|
||||
|
||||
websocket.onmessage = (m: { data: string }) => {
|
||||
|
@ -68,7 +65,7 @@ function init() {
|
|||
}
|
||||
|
||||
function clearLog() {
|
||||
logContainer.value!.innerHTML = ''
|
||||
buffer.value = ''
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
|
@ -79,10 +76,12 @@ onUnmounted(() => {
|
|||
websocket?.close()
|
||||
})
|
||||
|
||||
watch(auto_refresh, value => {
|
||||
watch(autoRefresh, async value => {
|
||||
if (value) {
|
||||
openWs()
|
||||
clearLog()
|
||||
await nextTick()
|
||||
await init()
|
||||
openWs()
|
||||
}
|
||||
else {
|
||||
websocket.close()
|
||||
|
@ -90,28 +89,24 @@ watch(auto_refresh, value => {
|
|||
})
|
||||
|
||||
watch(route, () => {
|
||||
init()
|
||||
|
||||
// Update control data when route changes
|
||||
control.type = logType()
|
||||
control.directive_idx = Number.parseInt(route.query.server_idx as string)
|
||||
control.server_idx = Number.parseInt(route.query.directive_idx as string)
|
||||
clearLog()
|
||||
control.log_path = route.query.log_path as string
|
||||
|
||||
nextTick(() => {
|
||||
websocket.send(JSON.stringify(control))
|
||||
})
|
||||
clearLog()
|
||||
init()
|
||||
})
|
||||
|
||||
watch(control, () => {
|
||||
clearLog()
|
||||
auto_refresh.value = true
|
||||
autoRefresh.value = true
|
||||
|
||||
nextTick(() => {
|
||||
websocket.send(JSON.stringify(control))
|
||||
})
|
||||
})
|
||||
|
||||
function on_scroll_log() {
|
||||
function onScrollLog() {
|
||||
if (!loading.value && page.value > 0) {
|
||||
loading.value = true
|
||||
|
||||
|
@ -131,8 +126,8 @@ function on_scroll_log() {
|
|||
}
|
||||
}
|
||||
|
||||
function debounce_scroll_log() {
|
||||
return debounce(on_scroll_log, 100)()
|
||||
function debounceScrollLog() {
|
||||
return debounce(onScrollLog, 100)()
|
||||
}
|
||||
|
||||
const computedBuffer = computed(() => {
|
||||
|
@ -148,10 +143,15 @@ const computedBuffer = computed(() => {
|
|||
:title="$gettext('Nginx Log')"
|
||||
:bordered="false"
|
||||
>
|
||||
<template #extra>
|
||||
<div class="flex items-center">
|
||||
<span class="mr-2">
|
||||
{{ $gettext('Auto Refresh') }}
|
||||
</span>
|
||||
<ASwitch v-model:checked="autoRefresh" />
|
||||
</div>
|
||||
</template>
|
||||
<AForm layout="vertical">
|
||||
<AFormItem :label="$gettext('Auto Refresh')">
|
||||
<ASwitch v-model:checked="auto_refresh" />
|
||||
</AFormItem>
|
||||
<AFormItem :label="$gettext('Filter')">
|
||||
<AInput
|
||||
v-model:value="filter"
|
||||
|
@ -165,10 +165,10 @@ const computedBuffer = computed(() => {
|
|||
ref="logContainer"
|
||||
v-dompurify-html="computedBuffer"
|
||||
class="nginx-log-container"
|
||||
@scroll="debounce_scroll_log"
|
||||
@scroll="debounceScrollLog"
|
||||
/>
|
||||
</ACard>
|
||||
<FooterToolBar v-if="control.type === 'site'">
|
||||
<FooterToolBar v-if="control.log_path">
|
||||
<AButton @click="router.go(-1)">
|
||||
{{ $gettext('Back') }}
|
||||
</AButton>
|
||||
|
|
152
app/src/views/nginx_log/NginxLogList.vue
Normal file
152
app/src/views/nginx_log/NginxLogList.vue
Normal 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>
|
|
@ -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,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
6
internal/cache/cache.go
vendored
6
internal/cache/cache.go
vendored
|
@ -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
585
internal/cache/nginx_log.go
vendored
Normal 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
|
||||
}
|
43
internal/nginx_log/log_list.go
Normal file
43
internal/nginx_log/log_list.go
Normal 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
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue