From e3e5f8fd966d288b5cb5cd13636f8af4bf420742 Mon Sep 17 00:00:00 2001 From: ssongliu Date: Fri, 17 Jul 2026 16:23:24 +0800 Subject: [PATCH] feat: add host system logs --- agent/app/api/v2/logs.go | 39 ++ agent/app/dto/logs.go | 28 ++ agent/app/service/logs.go | 387 +++++++++++++++++++ agent/cmd/server/docs/x-log.json | 12 +- agent/router/ro_log.go | 2 + agent/utils/re/re.go | 8 + core/cmd/server/docs/docs.go | 162 ++++++++ core/cmd/server/docs/swagger.json | 162 ++++++++ core/cmd/server/docs/x-log.json | 12 +- core/constant/common.go | 1 + frontend/src/api/interface/log.ts | 25 ++ frontend/src/api/modules/log.ts | 10 + frontend/src/lang/modules/en.ts | 15 + frontend/src/lang/modules/es-es.ts | 15 + frontend/src/lang/modules/fa.ts | 15 + frontend/src/lang/modules/ja.ts | 15 + frontend/src/lang/modules/ko.ts | 15 + frontend/src/lang/modules/ms.ts | 15 + frontend/src/lang/modules/pt-br.ts | 15 + frontend/src/lang/modules/ru.ts | 15 + frontend/src/lang/modules/tr.ts | 15 + frontend/src/lang/modules/zh-Hant.ts | 15 + frontend/src/lang/modules/zh.ts | 15 + frontend/src/routers/modules/log.ts | 12 + frontend/src/views/log/host-system/index.vue | 256 ++++++++++++ frontend/src/views/log/index.vue | 4 + 26 files changed, 1283 insertions(+), 2 deletions(-) create mode 100644 frontend/src/views/log/host-system/index.vue diff --git a/agent/app/api/v2/logs.go b/agent/app/api/v2/logs.go index 1058a79cd8f1..37dcf6481260 100644 --- a/agent/app/api/v2/logs.go +++ b/agent/app/api/v2/logs.go @@ -2,6 +2,7 @@ package v2 import ( "github.com/1Panel-dev/1Panel/agent/app/api/v2/helper" + "github.com/1Panel-dev/1Panel/agent/app/dto" "github.com/gin-gonic/gin" ) @@ -20,3 +21,41 @@ func (b *BaseApi) GetSystemFiles(c *gin.Context) { helper.SuccessWithData(c, data) } + +// @Tags Logs +// @Summary Read host logs +// @Accept json +// @Param request body dto.SystemLogReq true "request" +// @Produce json +// @Success 200 {object} dto.SystemLogRes +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /logs/system/read [post] +func (b *BaseApi) ReadSystemLog(c *gin.Context) { + var req dto.SystemLogReq + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + data, err := logService.ReadSystemLog(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, data) +} + +// @Tags Logs +// @Summary List running host services +// @Produce json +// @Success 200 {array} string +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /logs/system/services [get] +func (b *BaseApi) ListRunningServices(c *gin.Context) { + data, err := logService.ListRunningServices() + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, data) +} diff --git a/agent/app/dto/logs.go b/agent/app/dto/logs.go index 1cf6686715f2..3bf287e31946 100644 --- a/agent/app/dto/logs.go +++ b/agent/app/dto/logs.go @@ -1,6 +1,8 @@ package dto import ( + "time" + "github.com/1Panel-dev/1Panel/agent/app/model" ) @@ -14,3 +16,29 @@ type SearchTaskLogReq struct { type TaskDTO struct { model.Task } + +type SystemLogReq struct { + PageSize int `json:"pageSize" validate:"omitempty,min=1,max=500"` + Cursor string `json:"cursor"` + StartTime time.Time `json:"startTime"` + EndTime time.Time `json:"endTime"` + Keyword string `json:"keyword"` + Priority string `json:"priority"` + Service string `json:"service"` +} + +type SystemLogRes struct { + Source string `json:"source"` + Items []SystemLogItem `json:"items"` + HasMore bool `json:"hasMore"` + NextCursor string `json:"nextCursor"` +} + +type SystemLogItem struct { + Timestamp int64 `json:"-"` + Time string `json:"time"` + Priority string `json:"priority"` + Service string `json:"service"` + Message string `json:"message"` + Raw string `json:"raw"` +} diff --git a/agent/app/service/logs.go b/agent/app/service/logs.go index de27b8ba3e74..57de9a91f534 100644 --- a/agent/app/service/logs.go +++ b/agent/app/service/logs.go @@ -1,18 +1,405 @@ package service import ( + "context" + "encoding/base64" + "encoding/json" + "fmt" "os" + "os/exec" "sort" + "strconv" "strings" "time" + "github.com/1Panel-dev/1Panel/agent/app/dto" "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/re" ) type LogService struct{} +const maxSystemLogCursorSkip = 100 +const maxFileSystemLogLines = 1000 + +var ( + syslogPriorityKeywords = []struct { + keyword string + priority string + }{ + {"emerg", "0"}, {"panic", "0"}, {"alert", "1"}, {"crit", "2"}, {"fatal", "2"}, + {"error", "3"}, {"err", "3"}, {"warn", "4"}, {"notice", "5"}, {"debug", "7"}, {"info", "6"}, + } +) + +type systemLogCursor struct { + EndTime int64 `json:"endTime"` + Skipped int `json:"skipped"` +} + type ILogService interface { ListSystemLogFile() ([]string, error) + ReadSystemLog(req dto.SystemLogReq) (dto.SystemLogRes, error) + ListRunningServices() ([]string, error) +} + +func (u *LogService) ReadSystemLog(req dto.SystemLogReq) (dto.SystemLogRes, error) { + pageSize := req.PageSize + if pageSize == 0 { + pageSize = 100 + } + now := time.Now() + startTime := req.StartTime + if startTime.IsZero() { + startTime = time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()) + } + endTime := req.EndTime + if endTime.IsZero() { + endTime = now + } + cursor, err := decodeSystemLogCursor(req.Cursor) + if err != nil { + return dto.SystemLogRes{}, err + } + if cursor != nil { + endTime = time.Unix(0, cursor.EndTime*int64(time.Microsecond)) + } + journalctl, err := exec.LookPath("journalctl") + if err != nil { + return u.readFileSystemLog(req, startTime, endTime, pageSize, cursor) + } + args := []string{ + "--no-pager", "--reverse", "--output=json", + "--since", formatJournalQueryTime(startTime), + "--until", formatJournalQueryTime(endTime), + } + if service := strings.TrimSpace(req.Service); service != "" { + args = append(args, "-u", service) + } + if priority := strings.TrimSpace(req.Priority); priority != "" { + args = append(args, "--priority", priority) + } + if keyword := strings.TrimSpace(req.Keyword); keyword != "" { + args = append(args, "--grep", keyword) + } + queryLines := pageSize + 1 + if cursor != nil { + queryLines += cursor.Skipped + } + queryArgs := append(args, "--lines="+strconv.Itoa(queryLines)) + ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) + output, err := exec.CommandContext(ctx, journalctl, queryArgs...).CombinedOutput() + cancel() + if err != nil { + return dto.SystemLogRes{}, fmt.Errorf("read host system logs failed: %s", strings.TrimSpace(string(output))) + } + content := strings.TrimSpace(string(output)) + if content == "" || strings.HasPrefix(content, "-- No entries --") { + return dto.SystemLogRes{Source: "journalctl", Items: []dto.SystemLogItem{}}, nil + } + items := parseJournalLogItems(content) + if cursor != nil { + items, err = skipSystemLogCursorItems(items, *cursor) + if err != nil { + return dto.SystemLogRes{}, err + } + } + return buildSystemLogResponse("journalctl", items, pageSize, cursor) +} + +func (u *LogService) readFileSystemLog(req dto.SystemLogReq, startTime, endTime time.Time, pageSize int, cursor *systemLogCursor) (dto.SystemLogRes, error) { + items := make([]dto.SystemLogItem, 0) + for _, logFile := range []string{"/var/log/syslog", "/var/log/messages", "/var/log/system.log"} { + if _, err := os.Stat(logFile); err != nil { + continue + } + content, err := readLastSystemLogLines(logFile, maxFileSystemLogLines) + if err != nil { + return dto.SystemLogRes{}, err + } + for _, line := range strings.Split(content, "\n") { + item, ok := parseFileSystemLogItem(line) + if !ok || !matchSystemLogItem(item, req, startTime, endTime) { + continue + } + items = append(items, item) + } + } + sort.SliceStable(items, func(i, j int) bool { return items[i].Timestamp > items[j].Timestamp }) + if cursor != nil { + var err error + items, err = skipSystemLogCursorItems(items, *cursor) + if err != nil { + return dto.SystemLogRes{}, err + } + } + return buildSystemLogResponse("file", items, pageSize, cursor) +} + +func readLastSystemLogLines(logFile string, lines int) (string, error) { + ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) + defer cancel() + output, err := exec.CommandContext(ctx, "tail", "-n", strconv.Itoa(lines), logFile).Output() + if err != nil { + return "", err + } + return string(output), nil +} + +func buildSystemLogResponse(source string, items []dto.SystemLogItem, pageSize int, cursor *systemLogCursor) (dto.SystemLogRes, error) { + hasMore := len(items) > pageSize + if hasMore { + items = items[:pageSize] + } + res := dto.SystemLogRes{Source: source, Items: items, HasMore: hasMore} + if !hasMore || len(items) == 0 { + return res, nil + } + lastItem := items[len(items)-1] + skipped := countSystemLogTimestamp(items, lastItem.Timestamp) + if cursor != nil && cursor.EndTime == lastItem.Timestamp { + skipped += cursor.Skipped + } + nextCursor, err := encodeSystemLogCursor(systemLogCursor{EndTime: lastItem.Timestamp, Skipped: skipped}) + if err != nil { + return dto.SystemLogRes{}, err + } + res.NextCursor = nextCursor + return res, nil +} + +func decodeSystemLogCursor(value string) (*systemLogCursor, error) { + value = strings.TrimSpace(value) + if value == "" { + return nil, nil + } + decoded, err := base64.RawURLEncoding.DecodeString(value) + if err != nil { + return nil, fmt.Errorf("invalid system log cursor") + } + var cursor systemLogCursor + if err := json.Unmarshal(decoded, &cursor); err != nil || cursor.EndTime <= 0 || cursor.Skipped < 0 || cursor.Skipped > maxSystemLogCursorSkip { + return nil, fmt.Errorf("invalid system log cursor") + } + return &cursor, nil +} + +func encodeSystemLogCursor(cursor systemLogCursor) (string, error) { + value, err := json.Marshal(cursor) + if err != nil { + return "", err + } + return base64.RawURLEncoding.EncodeToString(value), nil +} + +func skipSystemLogCursorItems(items []dto.SystemLogItem, cursor systemLogCursor) ([]dto.SystemLogItem, error) { + skipped := 0 + for len(items) > 0 && skipped < cursor.Skipped && items[0].Timestamp == cursor.EndTime { + items = items[1:] + skipped++ + } + if skipped != cursor.Skipped { + return nil, fmt.Errorf("system log cursor has expired") + } + return items, nil +} + +func countSystemLogTimestamp(items []dto.SystemLogItem, timestamp int64) int { + count := 0 + for _, item := range items { + if item.Timestamp == timestamp { + count++ + } + } + return count +} + +func formatJournalQueryTime(value time.Time) string { + return value.In(time.Local).Format("2006-01-02 15:04:05.000000") +} + +func parseFileSystemLogItem(line string) (dto.SystemLogItem, bool) { + raw := strings.TrimSpace(line) + if raw == "" { + return dto.SystemLogItem{}, false + } + priority := "6" + if matches := re.GetRegex(re.SyslogPRIPattern).FindStringSubmatch(raw); len(matches) == 2 { + value, _ := strconv.Atoi(matches[1]) + priority = strconv.Itoa(value % 8) + raw = strings.TrimSpace(strings.TrimPrefix(raw, matches[0])) + } + logTime, rest, ok := parseFileSystemLogTime(raw) + if !ok { + return dto.SystemLogItem{}, false + } + service, message := parseFileSystemLogService(rest) + if priority == "6" { + priority = detectFileSystemLogPriority(message) + } + return dto.SystemLogItem{ + Timestamp: logTime.UnixMicro(), + Time: logTime.Format("2006-01-02 15:04:05"), + Priority: priority, + Service: service, + Message: message, + Raw: line, + }, true +} + +func parseFileSystemLogTime(value string) (time.Time, string, bool) { + if matches := re.GetRegex(re.SyslogRFC3339Pattern).FindStringSubmatch(value); len(matches) == 3 { + for _, layout := range []string{time.RFC3339Nano, "2006-01-02 15:04:05.999999999Z07:00"} { + if parsed, err := time.Parse(layout, matches[1]); err == nil { + return parsed.Local(), matches[2], true + } + } + for _, layout := range []string{"2006-01-02T15:04:05.999999999", "2006-01-02 15:04:05.999999999"} { + if parsed, err := time.ParseInLocation(layout, matches[1], time.Local); err == nil { + return parsed, matches[2], true + } + } + } + if matches := re.GetRegex(re.SyslogRFC3164Pattern).FindStringSubmatch(value); len(matches) == 3 { + parsed, err := time.ParseInLocation("Jan _2 15:04:05", matches[1], time.Local) + if err != nil { + return time.Time{}, "", false + } + now := time.Now() + parsed = time.Date(now.Year(), parsed.Month(), parsed.Day(), parsed.Hour(), parsed.Minute(), parsed.Second(), 0, time.Local) + if parsed.After(now.Add(24 * time.Hour)) { + parsed = parsed.AddDate(-1, 0, 0) + } + return parsed, matches[2], true + } + return time.Time{}, "", false +} + +func parseFileSystemLogService(value string) (string, string) { + if matches := re.GetRegex(re.SyslogServicePattern).FindStringSubmatch(value); len(matches) == 3 { + return matches[1], matches[2] + } + // RFC5424 records have the form HOST APP-NAME PROCID MSGID STRUCTURED-DATA MSG. + fields := strings.Fields(value) + if len(fields) >= 6 { + return fields[1], strings.Join(fields[5:], " ") + } + return "", value +} + +func detectFileSystemLogPriority(message string) string { + lowerMessage := strings.ToLower(message) + for _, item := range syslogPriorityKeywords { + if strings.Contains(lowerMessage, item.keyword) { + return item.priority + } + } + return "6" +} + +func matchSystemLogItem(item dto.SystemLogItem, req dto.SystemLogReq, startTime, endTime time.Time) bool { + itemTime := time.UnixMicro(item.Timestamp) + if itemTime.Before(startTime) || itemTime.After(endTime) { + return false + } + if priority := strings.TrimSpace(req.Priority); priority != "" && item.Priority != priority { + return false + } + if service := strings.TrimSpace(req.Service); service != "" && !matchFileSystemLogService(item.Service, service) { + return false + } + if keyword := strings.TrimSpace(req.Keyword); keyword != "" && !strings.Contains(strings.ToLower(item.Raw), strings.ToLower(keyword)) { + return false + } + return true +} + +func matchFileSystemLogService(itemService, requestedService string) bool { + itemService = strings.TrimSpace(itemService) + requestedService = strings.TrimSpace(requestedService) + if itemService == "" || requestedService == "" { + return itemService == requestedService + } + return strings.EqualFold(strings.TrimSuffix(itemService, ".service"), strings.TrimSuffix(requestedService, ".service")) +} + +func (u *LogService) ListRunningServices() ([]string, error) { + systemctl, err := exec.LookPath("systemctl") + if err != nil { + return []string{}, nil + } + ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) + defer cancel() + args := []string{"list-units", "--type=service", "--state=running", "--no-legend", "--no-pager", "--plain"} + output, err := exec.CommandContext(ctx, systemctl, args...).Output() + if err != nil { + return []string{}, nil + } + + services := make([]string, 0) + for _, line := range strings.Split(string(output), "\n") { + fields := strings.Fields(line) + if len(fields) == 0 || !strings.HasSuffix(fields[0], ".service") { + continue + } + services = append(services, fields[0]) + } + sort.Strings(services) + return services, nil +} + +func parseJournalLogItems(content string) []dto.SystemLogItem { + entries := strings.Split(content, "\n") + items := make([]dto.SystemLogItem, 0, len(entries)) + for _, entry := range entries { + var fields map[string]interface{} + if err := json.Unmarshal([]byte(entry), &fields); err != nil { + continue + } + items = append(items, dto.SystemLogItem{ + Timestamp: journalFieldMicroseconds(fields), + Time: formatJournalTimestamp(journalFieldString(fields, "__REALTIME_TIMESTAMP")), + Priority: journalFieldString(fields, "PRIORITY"), + Service: journalFieldString(fields, "_SYSTEMD_UNIT"), + Message: journalFieldString(fields, "MESSAGE"), + Raw: entry, + }) + } + return items +} + +func journalFieldMicroseconds(fields map[string]interface{}) int64 { + value, _ := strconv.ParseInt(journalFieldString(fields, "__REALTIME_TIMESTAMP"), 10, 64) + return value +} + +func journalFieldString(fields map[string]interface{}, key string) string { + value, ok := fields[key] + if !ok || value == nil { + return "" + } + switch item := value.(type) { + case string: + return item + case float64: + return strconv.FormatInt(int64(item), 10) + case []interface{}: + values := make([]string, 0, len(item)) + for _, value := range item { + values = append(values, fmt.Sprint(value)) + } + return strings.Join(values, " ") + default: + return fmt.Sprint(item) + } +} + +func formatJournalTimestamp(value string) string { + microseconds, err := strconv.ParseInt(value, 10, 64) + if err != nil { + return value + } + return time.Unix(0, microseconds*int64(time.Microsecond)).Local().Format("2006-01-02 15:04:05") } func NewILogService() ILogService { diff --git a/agent/cmd/server/docs/x-log.json b/agent/cmd/server/docs/x-log.json index 08488774b3d9..1b53872a4d56 100644 --- a/agent/cmd/server/docs/x-log.json +++ b/agent/cmd/server/docs/x-log.json @@ -5174,6 +5174,15 @@ "formatZH": "更新 CDN 配置", "formatEN": "update CDN config" }, + "/xpack/waf/config/global/spider": { + "bodyKeys": [ + "rules" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "更新全局蜘蛛允许列表", + "formatEN": "update global spider allowlist" + }, "/xpack/waf/config/global/state": { "bodyKeys": [ "scope", @@ -5187,7 +5196,8 @@ "/xpack/waf/config/website/state": { "bodyKeys": [ "scope", - "state" + "state", + "mode" ], "paramKeys": [], "beforeFunctions": [], diff --git a/agent/router/ro_log.go b/agent/router/ro_log.go index b3f9ee14b4e8..af4feb8bd64f 100644 --- a/agent/router/ro_log.go +++ b/agent/router/ro_log.go @@ -12,6 +12,8 @@ func (s *LogRouter) InitRouter(Router *gin.RouterGroup) { baseApi := v2.ApiGroupApp.BaseApi { operationRouter.GET("/system/files", baseApi.GetSystemFiles) + operationRouter.POST("/system/read", baseApi.ReadSystemLog) + operationRouter.GET("/system/services", baseApi.ListRunningServices) operationRouter.POST("/tasks/search", baseApi.PageTasks) operationRouter.POST("/tasks/read", baseApi.ReadTaskLogByLine) operationRouter.GET("/tasks/executing/count", baseApi.CountExecutingTasks) diff --git a/agent/utils/re/re.go b/agent/utils/re/re.go index 92e34edb9ef3..e3fb6e93b9b8 100644 --- a/agent/utils/re/re.go +++ b/agent/utils/re/re.go @@ -44,6 +44,10 @@ const ( SSHDisconnectPattern = `^Received disconnect from ([0-9a-fA-F:.]+) port (\d+)` SSHMaxAuthPattern = `^error: maximum authentication attempts exceeded for (?:(?:invalid user) )?(.+?) from ([0-9a-fA-F:.]+) port (\d+)` SSHNotAllowedPattern = `^User (.+?) from ([0-9a-fA-F:.]+) not allowed` + SyslogPRIPattern = `^<(\d{1,3})>(?:\d\s+)?` + SyslogRFC3164Pattern = `^([A-Z][a-z]{2}\s{1,2}\d{1,2}\s\d{2}:\d{2}:\d{2})\s+(.*)$` + SyslogRFC3339Pattern = `^(\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:?\d{2})?)\s+(.*)$` + SyslogServicePattern = `^(?:\S+\s+)?([[:alnum:]_.@/-]+)(?:\[\d+\])?:\s*(.*)$` ) var regexMap = make(map[string]*regexp.Regexp) @@ -88,6 +92,10 @@ func Init() { SSHDisconnectPattern, SSHMaxAuthPattern, SSHNotAllowedPattern, + SyslogPRIPattern, + SyslogRFC3164Pattern, + SyslogRFC3339Pattern, + SyslogServicePattern, } for _, pattern := range patterns { diff --git a/core/cmd/server/docs/docs.go b/core/cmd/server/docs/docs.go index 37cbf213dded..d613b146feca 100644 --- a/core/cmd/server/docs/docs.go +++ b/core/cmd/server/docs/docs.go @@ -20119,6 +20119,77 @@ const docTemplate = `{ ] } }, + "/logs/system/read": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SystemLogReq" + } + } + ], + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.SystemLogRes" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Read host logs", + "tags": [ + "Logs" + ] + } + }, + "/logs/system/services": { + "get": { + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "type": "string" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "List running host services", + "tags": [ + "Logs" + ] + } + }, "/logs/tasks/executing/count": { "get": { "responses": { @@ -38445,6 +38516,74 @@ const docTemplate = `{ ], "type": "object" }, + "dto.SystemLogItem": { + "properties": { + "message": { + "type": "string" + }, + "priority": { + "type": "string" + }, + "raw": { + "type": "string" + }, + "service": { + "type": "string" + }, + "time": { + "type": "string" + } + }, + "type": "object" + }, + "dto.SystemLogReq": { + "properties": { + "cursor": { + "type": "string" + }, + "endTime": { + "type": "string" + }, + "keyword": { + "type": "string" + }, + "pageSize": { + "maximum": 500, + "minimum": 1, + "type": "integer" + }, + "priority": { + "type": "string" + }, + "service": { + "type": "string" + }, + "startTime": { + "type": "string" + } + }, + "type": "object" + }, + "dto.SystemLogRes": { + "properties": { + "hasMore": { + "type": "boolean" + }, + "items": { + "items": { + "$ref": "#/definitions/dto.SystemLogItem" + }, + "type": "array" + }, + "nextCursor": { + "type": "string" + }, + "source": { + "type": "string" + } + }, + "type": "object" + }, "dto.Tag": { "properties": { "key": { @@ -38769,6 +38908,9 @@ const docTemplate = `{ "group": { "type": "string" }, + "isAppendOnly": { + "type": "boolean" + }, "isDetail": { "type": "boolean" }, @@ -38778,6 +38920,9 @@ const docTemplate = `{ "isHidden": { "type": "boolean" }, + "isImmutable": { + "type": "boolean" + }, "isSymlink": { "type": "boolean" }, @@ -41066,6 +41211,10 @@ const docTemplate = `{ }, "type": "array" }, + "gatewayArgs": { + "maxLength": 4096, + "type": "string" + }, "gatewayImage": { "type": "string" }, @@ -41198,6 +41347,10 @@ const docTemplate = `{ }, "type": "array" }, + "gatewayArgs": { + "maxLength": 4096, + "type": "string" + }, "gatewayImage": { "type": "string" }, @@ -44612,6 +44765,9 @@ const docTemplate = `{ "group": { "type": "string" }, + "isAppendOnly": { + "type": "boolean" + }, "isDetail": { "type": "boolean" }, @@ -44621,6 +44777,9 @@ const docTemplate = `{ "isHidden": { "type": "boolean" }, + "isImmutable": { + "type": "boolean" + }, "isSymlink": { "type": "boolean" }, @@ -44881,6 +45040,9 @@ const docTemplate = `{ }, "type": "array" }, + "gatewayArgs": { + "type": "string" + }, "gatewayImage": { "type": "string" }, diff --git a/core/cmd/server/docs/swagger.json b/core/cmd/server/docs/swagger.json index 61813364e014..40b62bc11aae 100644 --- a/core/cmd/server/docs/swagger.json +++ b/core/cmd/server/docs/swagger.json @@ -20115,6 +20115,77 @@ ] } }, + "/logs/system/read": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SystemLogReq" + } + } + ], + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.SystemLogRes" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Read host logs", + "tags": [ + "Logs" + ] + } + }, + "/logs/system/services": { + "get": { + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "type": "string" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "List running host services", + "tags": [ + "Logs" + ] + } + }, "/logs/tasks/executing/count": { "get": { "responses": { @@ -38441,6 +38512,74 @@ ], "type": "object" }, + "dto.SystemLogItem": { + "properties": { + "message": { + "type": "string" + }, + "priority": { + "type": "string" + }, + "raw": { + "type": "string" + }, + "service": { + "type": "string" + }, + "time": { + "type": "string" + } + }, + "type": "object" + }, + "dto.SystemLogReq": { + "properties": { + "cursor": { + "type": "string" + }, + "endTime": { + "type": "string" + }, + "keyword": { + "type": "string" + }, + "pageSize": { + "maximum": 500, + "minimum": 1, + "type": "integer" + }, + "priority": { + "type": "string" + }, + "service": { + "type": "string" + }, + "startTime": { + "type": "string" + } + }, + "type": "object" + }, + "dto.SystemLogRes": { + "properties": { + "hasMore": { + "type": "boolean" + }, + "items": { + "items": { + "$ref": "#/definitions/dto.SystemLogItem" + }, + "type": "array" + }, + "nextCursor": { + "type": "string" + }, + "source": { + "type": "string" + } + }, + "type": "object" + }, "dto.Tag": { "properties": { "key": { @@ -38765,6 +38904,9 @@ "group": { "type": "string" }, + "isAppendOnly": { + "type": "boolean" + }, "isDetail": { "type": "boolean" }, @@ -38774,6 +38916,9 @@ "isHidden": { "type": "boolean" }, + "isImmutable": { + "type": "boolean" + }, "isSymlink": { "type": "boolean" }, @@ -41062,6 +41207,10 @@ }, "type": "array" }, + "gatewayArgs": { + "maxLength": 4096, + "type": "string" + }, "gatewayImage": { "type": "string" }, @@ -41194,6 +41343,10 @@ }, "type": "array" }, + "gatewayArgs": { + "maxLength": 4096, + "type": "string" + }, "gatewayImage": { "type": "string" }, @@ -44608,6 +44761,9 @@ "group": { "type": "string" }, + "isAppendOnly": { + "type": "boolean" + }, "isDetail": { "type": "boolean" }, @@ -44617,6 +44773,9 @@ "isHidden": { "type": "boolean" }, + "isImmutable": { + "type": "boolean" + }, "isSymlink": { "type": "boolean" }, @@ -44877,6 +45036,9 @@ }, "type": "array" }, + "gatewayArgs": { + "type": "string" + }, "gatewayImage": { "type": "string" }, diff --git a/core/cmd/server/docs/x-log.json b/core/cmd/server/docs/x-log.json index 08488774b3d9..1b53872a4d56 100644 --- a/core/cmd/server/docs/x-log.json +++ b/core/cmd/server/docs/x-log.json @@ -5174,6 +5174,15 @@ "formatZH": "更新 CDN 配置", "formatEN": "update CDN config" }, + "/xpack/waf/config/global/spider": { + "bodyKeys": [ + "rules" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "更新全局蜘蛛允许列表", + "formatEN": "update global spider allowlist" + }, "/xpack/waf/config/global/state": { "bodyKeys": [ "scope", @@ -5187,7 +5196,8 @@ "/xpack/waf/config/website/state": { "bodyKeys": [ "scope", - "state" + "state", + "mode" ], "paramKeys": [], "beforeFunctions": [], diff --git a/core/constant/common.go b/core/constant/common.go index 95066809dfda..2e7090f887c9 100644 --- a/core/constant/common.go +++ b/core/constant/common.go @@ -124,6 +124,7 @@ var WebUrlMap = map[string]struct{}{ "/logs": {}, "/logs/operation": {}, + "/logs/host": {}, "/logs/login": {}, "/logs/website": {}, "/logs/system": {}, diff --git a/frontend/src/api/interface/log.ts b/frontend/src/api/interface/log.ts index 3209abe52365..7778b1fa2f75 100644 --- a/frontend/src/api/interface/log.ts +++ b/frontend/src/api/interface/log.ts @@ -43,6 +43,31 @@ export namespace Log { logType: string; } + export interface SystemLog { + source: string; + items: SystemLogItem[]; + hasMore: boolean; + nextCursor: string; + } + + export interface SystemLogSearch { + pageSize: number; + cursor?: string; + startTime?: Date; + endTime?: Date; + keyword?: string; + priority?: string; + service?: string; + } + + export interface SystemLogItem { + time: string; + priority: string; + service: string; + message: string; + raw: string; + } + export interface SearchTaskReq extends ReqPage { type: string; status: string; diff --git a/frontend/src/api/modules/log.ts b/frontend/src/api/modules/log.ts index 4481e734f6c8..ec767d3f7950 100644 --- a/frontend/src/api/modules/log.ts +++ b/frontend/src/api/modules/log.ts @@ -21,6 +21,16 @@ export const getSystemFiles = (node?: string) => { return http.get>(`/logs/system/files${params}`); }; +export const readSystemLogs = (params: Log.SystemLogSearch, node?: string) => { + const query = node ? `?operateNode=${node}` : ''; + return http.post(`/logs/system/read${query}`, params); +}; + +export const listRunningServices = (node?: string) => { + const query = node ? `?operateNode=${node}` : ''; + return http.get(`/logs/system/services${query}`); +}; + export const cleanLogs = (param: Log.CleanLog) => { return http.post(`/core/logs/clean`, param); }; diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index 8e7800bdfbb4..c44e34434031 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -2116,6 +2116,21 @@ const message = { loginAgent: 'Login agent', loginStatus: 'Status', system: 'System logs', + hostSystem: 'Host logs', + previousPage: 'Previous page', + nextPage: 'Next page', + filter: 'Filter', + service: 'Service', + priority: 'Priority', + priorityEmergency: 'Emergency', + priorityAlert: 'Alert', + priorityCritical: 'Critical', + priorityError: 'Error', + priorityWarning: 'Warning', + priorityNotice: 'Notice', + priorityInfo: 'Info', + priorityDebug: 'Debug', + message: 'Message', deleteLogs: 'Clean logs', resource: 'Resource', detail: { diff --git a/frontend/src/lang/modules/es-es.ts b/frontend/src/lang/modules/es-es.ts index 637d575ecc18..289e0b1788e5 100644 --- a/frontend/src/lang/modules/es-es.ts +++ b/frontend/src/lang/modules/es-es.ts @@ -2153,6 +2153,21 @@ const message = { loginAgent: 'Agente de acceso', loginStatus: 'Estado', system: 'Logs del sistema', + hostSystem: 'Logs del host', + previousPage: 'Página anterior', + nextPage: 'Página siguiente', + filter: 'Filtrar', + service: 'Servicio', + priority: 'Prioridad', + priorityEmergency: 'Emergencia', + priorityAlert: 'Alerta', + priorityCritical: 'Crítico', + priorityError: 'Error', + priorityWarning: 'Advertencia', + priorityNotice: 'Aviso', + priorityInfo: 'Información', + priorityDebug: 'Depuración', + message: 'Mensaje', deleteLogs: 'Limpiar logs', resource: 'Recurso', detail: { diff --git a/frontend/src/lang/modules/fa.ts b/frontend/src/lang/modules/fa.ts index 5a4602cb0598..464939644a83 100644 --- a/frontend/src/lang/modules/fa.ts +++ b/frontend/src/lang/modules/fa.ts @@ -2098,6 +2098,21 @@ const message = { loginAgent: 'مرورگر ورود', loginStatus: 'وضعیت', system: 'لاگ سیستم', + hostSystem: 'لاگ‌های میزبان', + previousPage: 'صفحه قبل', + nextPage: 'صفحه بعد', + filter: 'فیلتر', + service: 'سرویس', + priority: 'اولویت', + priorityEmergency: 'اضطراری', + priorityAlert: 'هشدار', + priorityCritical: 'بحرانی', + priorityError: 'خطا', + priorityWarning: 'اخطار', + priorityNotice: 'اعلان', + priorityInfo: 'اطلاعات', + priorityDebug: 'اشکال‌زدایی', + message: 'پیام', deleteLogs: 'پاک‌سازی لاگ‌ها', resource: 'منبع', detail: { diff --git a/frontend/src/lang/modules/ja.ts b/frontend/src/lang/modules/ja.ts index 74c14c580de6..b51ff62c05f5 100644 --- a/frontend/src/lang/modules/ja.ts +++ b/frontend/src/lang/modules/ja.ts @@ -2121,6 +2121,21 @@ const message = { loginAgent: 'ログインエージェント', loginStatus: '状態', system: 'システムログ', + hostSystem: 'ホストログ', + previousPage: '前のページ', + nextPage: '次のページ', + filter: 'フィルター', + service: 'サービス', + priority: '優先度', + priorityEmergency: '緊急', + priorityAlert: '警報', + priorityCritical: '重大', + priorityError: 'エラー', + priorityWarning: '警告', + priorityNotice: '通知', + priorityInfo: '情報', + priorityDebug: 'デバッグ', + message: 'メッセージ', deleteLogs: 'クリーンログ', resource: 'リソース', detail: { diff --git a/frontend/src/lang/modules/ko.ts b/frontend/src/lang/modules/ko.ts index 79086671a7cb..607199a971ef 100644 --- a/frontend/src/lang/modules/ko.ts +++ b/frontend/src/lang/modules/ko.ts @@ -2079,6 +2079,21 @@ const message = { loginAgent: '로그인 에이전트', loginStatus: '상태', system: '시스템 로그', + hostSystem: '호스트 로그', + previousPage: '이전 페이지', + nextPage: '다음 페이지', + filter: '필터', + service: '서비스', + priority: '우선순위', + priorityEmergency: '긴급', + priorityAlert: '경보', + priorityCritical: '심각', + priorityError: '오류', + priorityWarning: '경고', + priorityNotice: '알림', + priorityInfo: '정보', + priorityDebug: '디버그', + message: '메시지', deleteLogs: '로그 정리', resource: '자원', detail: { diff --git a/frontend/src/lang/modules/ms.ts b/frontend/src/lang/modules/ms.ts index 1e13bd7d8bf5..e927bfac8e2d 100644 --- a/frontend/src/lang/modules/ms.ts +++ b/frontend/src/lang/modules/ms.ts @@ -2147,6 +2147,21 @@ const message = { loginAgent: 'Ejen Log Masuk', loginStatus: 'Status', system: 'Log Sistem', + hostSystem: 'Log hos', + previousPage: 'Halaman sebelumnya', + nextPage: 'Halaman seterusnya', + filter: 'Tapis', + service: 'Perkhidmatan', + priority: 'Keutamaan', + priorityEmergency: 'Kecemasan', + priorityAlert: 'Amaran', + priorityCritical: 'Kritikal', + priorityError: 'Ralat', + priorityWarning: 'Peringatan', + priorityNotice: 'Notis', + priorityInfo: 'Maklumat', + priorityDebug: 'Nyahpepijat', + message: 'Mesej', deleteLogs: 'Bersihkan Log', resource: 'Sumber', detail: { diff --git a/frontend/src/lang/modules/pt-br.ts b/frontend/src/lang/modules/pt-br.ts index e1fdfee7b7cb..f628f9d077c9 100644 --- a/frontend/src/lang/modules/pt-br.ts +++ b/frontend/src/lang/modules/pt-br.ts @@ -2263,6 +2263,21 @@ const message = { loginAgent: 'Agente de login', loginStatus: 'Status', system: 'Logs do sistema', + hostSystem: 'Logs do host', + previousPage: 'Página anterior', + nextPage: 'Próxima página', + filter: 'Filtrar', + service: 'Serviço', + priority: 'Prioridade', + priorityEmergency: 'Emergência', + priorityAlert: 'Alerta', + priorityCritical: 'Crítico', + priorityError: 'Erro', + priorityWarning: 'Aviso', + priorityNotice: 'Notificação', + priorityInfo: 'Informação', + priorityDebug: 'Depuração', + message: 'Mensagem', deleteLogs: 'Limpar logs', resource: 'Recurso', detail: { diff --git a/frontend/src/lang/modules/ru.ts b/frontend/src/lang/modules/ru.ts index 7dfeaa94a066..d614ea93e4cb 100644 --- a/frontend/src/lang/modules/ru.ts +++ b/frontend/src/lang/modules/ru.ts @@ -2136,6 +2136,21 @@ const message = { loginAgent: 'Агент входа', loginStatus: 'Статус', system: 'Системные логи', + hostSystem: 'Журналы хоста', + previousPage: 'Предыдущая страница', + nextPage: 'Следующая страница', + filter: 'Фильтр', + service: 'Сервис', + priority: 'Приоритет', + priorityEmergency: 'Авария', + priorityAlert: 'Тревога', + priorityCritical: 'Критический', + priorityError: 'Ошибка', + priorityWarning: 'Предупреждение', + priorityNotice: 'Уведомление', + priorityInfo: 'Информация', + priorityDebug: 'Отладка', + message: 'Сообщение', deleteLogs: 'Очистить логи', resource: 'Ресурс', detail: { diff --git a/frontend/src/lang/modules/tr.ts b/frontend/src/lang/modules/tr.ts index a6e60734dbb5..65b5cd0623f0 100644 --- a/frontend/src/lang/modules/tr.ts +++ b/frontend/src/lang/modules/tr.ts @@ -2145,6 +2145,21 @@ const message = { loginAgent: 'Giriş aracısı', loginStatus: 'Durum', system: 'Sistem logları', + hostSystem: 'Ana makine günlükleri', + previousPage: 'Önceki sayfa', + nextPage: 'Sonraki sayfa', + filter: 'Filtrele', + service: 'Servis', + priority: 'Öncelik', + priorityEmergency: 'Acil', + priorityAlert: 'Alarm', + priorityCritical: 'Kritik', + priorityError: 'Hata', + priorityWarning: 'Uyarı', + priorityNotice: 'Bildirim', + priorityInfo: 'Bilgi', + priorityDebug: 'Hata ayıklama', + message: 'Mesaj', deleteLogs: 'Logları temizle', resource: 'Kaynak', detail: { diff --git a/frontend/src/lang/modules/zh-Hant.ts b/frontend/src/lang/modules/zh-Hant.ts index 5d3a64a5d408..66e433b0d6c0 100644 --- a/frontend/src/lang/modules/zh-Hant.ts +++ b/frontend/src/lang/modules/zh-Hant.ts @@ -1981,6 +1981,21 @@ const message = { loginAgent: '使用者代理', loginStatus: '登入狀態', system: '系統日誌', + hostSystem: '主機日誌', + previousPage: '上一頁', + nextPage: '下一頁', + filter: '篩選', + service: '服務', + priority: '優先級', + priorityEmergency: '緊急 Emergency', + priorityAlert: '警報 Alert', + priorityCritical: '嚴重 Critical', + priorityError: '錯誤 Error', + priorityWarning: '警告 Warning', + priorityNotice: '通知 Notice', + priorityInfo: '資訊 Info', + priorityDebug: '除錯 Debug', + message: '訊息', deleteLogs: '清空日誌', resource: '資源', detail: { diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index 0bf59477d4c7..b5ae962f4ab9 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -1984,6 +1984,21 @@ const message = { loginAgent: '用户代理', loginStatus: '登录状态', system: '系统日志', + hostSystem: '主机日志', + previousPage: '上一页', + nextPage: '下一页', + filter: '过滤', + service: '服务', + priority: '优先级', + priorityEmergency: '紧急 Emergency', + priorityAlert: '警报 Alert', + priorityCritical: '严重 Critical', + priorityError: '错误 Error', + priorityWarning: '警告 Warning', + priorityNotice: '通知 Notice', + priorityInfo: '信息 Info', + priorityDebug: '调试 Debug', + message: '消息', deleteLogs: '清空日志', resource: '资源', detail: { diff --git a/frontend/src/routers/modules/log.ts b/frontend/src/routers/modules/log.ts index cdab2fa4bb19..9cf9edb85203 100644 --- a/frontend/src/routers/modules/log.ts +++ b/frontend/src/routers/modules/log.ts @@ -67,6 +67,18 @@ const logsRouter = { permission: 'log_view', }, }, + { + path: 'host', + name: 'HostSystemLog', + component: () => import('@/views/log/host-system/index.vue'), + hidden: true, + meta: { + parent: 'menu.logs', + title: 'logs.hostSystem', + activeMenu: '/logs', + permission: 'log_view', + }, + }, { path: 'ssh', name: 'SSHLog2', diff --git a/frontend/src/views/log/host-system/index.vue b/frontend/src/views/log/host-system/index.vue new file mode 100644 index 000000000000..ac37afac3ae2 --- /dev/null +++ b/frontend/src/views/log/host-system/index.vue @@ -0,0 +1,256 @@ + + + + + diff --git a/frontend/src/views/log/index.vue b/frontend/src/views/log/index.vue index 5590ce6b8c67..371c7dac5dd5 100644 --- a/frontend/src/views/log/index.vue +++ b/frontend/src/views/log/index.vue @@ -15,6 +15,10 @@ const buttons = [ label: i18n.global.t('logs.panelLog'), path: '/logs/operation', }, + { + label: i18n.global.t('logs.hostSystem'), + path: '/logs/host', + }, { label: i18n.global.t('ssh.loginLogs'), path: '/logs/ssh',