mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-09-22 00:00:50 +00:00
feat: add host system logs (#13285)
This commit is contained in:
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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": [],
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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": [],
|
||||
|
||||
@@ -124,6 +124,7 @@ var WebUrlMap = map[string]struct{}{
|
||||
|
||||
"/logs": {},
|
||||
"/logs/operation": {},
|
||||
"/logs/host": {},
|
||||
"/logs/login": {},
|
||||
"/logs/website": {},
|
||||
"/logs/system": {},
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -21,6 +21,16 @@ export const getSystemFiles = (node?: string) => {
|
||||
return http.get<Array<string>>(`/logs/system/files${params}`);
|
||||
};
|
||||
|
||||
export const readSystemLogs = (params: Log.SystemLogSearch, node?: string) => {
|
||||
const query = node ? `?operateNode=${node}` : '';
|
||||
return http.post<Log.SystemLog>(`/logs/system/read${query}`, params);
|
||||
};
|
||||
|
||||
export const listRunningServices = (node?: string) => {
|
||||
const query = node ? `?operateNode=${node}` : '';
|
||||
return http.get<string[]>(`/logs/system/services${query}`);
|
||||
};
|
||||
|
||||
export const cleanLogs = (param: Log.CleanLog) => {
|
||||
return http.post(`/core/logs/clean`, param);
|
||||
};
|
||||
|
||||
@@ -2122,6 +2122,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: {
|
||||
|
||||
@@ -2159,6 +2159,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: {
|
||||
|
||||
@@ -2107,6 +2107,21 @@ const message = {
|
||||
loginAgent: 'مرورگر ورود',
|
||||
loginStatus: 'وضعیت',
|
||||
system: 'لاگ سیستم',
|
||||
hostSystem: 'لاگهای میزبان',
|
||||
previousPage: 'صفحه قبل',
|
||||
nextPage: 'صفحه بعد',
|
||||
filter: 'فیلتر',
|
||||
service: 'سرویس',
|
||||
priority: 'اولویت',
|
||||
priorityEmergency: 'اضطراری',
|
||||
priorityAlert: 'هشدار',
|
||||
priorityCritical: 'بحرانی',
|
||||
priorityError: 'خطا',
|
||||
priorityWarning: 'اخطار',
|
||||
priorityNotice: 'اعلان',
|
||||
priorityInfo: 'اطلاعات',
|
||||
priorityDebug: 'اشکالزدایی',
|
||||
message: 'پیام',
|
||||
deleteLogs: 'پاکسازی لاگها',
|
||||
resource: 'منبع',
|
||||
detail: {
|
||||
|
||||
@@ -2127,6 +2127,21 @@ const message = {
|
||||
loginAgent: 'ログインエージェント',
|
||||
loginStatus: '状態',
|
||||
system: 'システムログ',
|
||||
hostSystem: 'ホストログ',
|
||||
previousPage: '前のページ',
|
||||
nextPage: '次のページ',
|
||||
filter: 'フィルター',
|
||||
service: 'サービス',
|
||||
priority: '優先度',
|
||||
priorityEmergency: '緊急',
|
||||
priorityAlert: '警報',
|
||||
priorityCritical: '重大',
|
||||
priorityError: 'エラー',
|
||||
priorityWarning: '警告',
|
||||
priorityNotice: '通知',
|
||||
priorityInfo: '情報',
|
||||
priorityDebug: 'デバッグ',
|
||||
message: 'メッセージ',
|
||||
deleteLogs: 'クリーンログ',
|
||||
resource: 'リソース',
|
||||
detail: {
|
||||
|
||||
@@ -2085,6 +2085,21 @@ const message = {
|
||||
loginAgent: '로그인 에이전트',
|
||||
loginStatus: '상태',
|
||||
system: '시스템 로그',
|
||||
hostSystem: '호스트 로그',
|
||||
previousPage: '이전 페이지',
|
||||
nextPage: '다음 페이지',
|
||||
filter: '필터',
|
||||
service: '서비스',
|
||||
priority: '우선순위',
|
||||
priorityEmergency: '긴급',
|
||||
priorityAlert: '경보',
|
||||
priorityCritical: '심각',
|
||||
priorityError: '오류',
|
||||
priorityWarning: '경고',
|
||||
priorityNotice: '알림',
|
||||
priorityInfo: '정보',
|
||||
priorityDebug: '디버그',
|
||||
message: '메시지',
|
||||
deleteLogs: '로그 정리',
|
||||
resource: '자원',
|
||||
detail: {
|
||||
|
||||
@@ -2154,6 +2154,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: {
|
||||
|
||||
@@ -2269,6 +2269,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: {
|
||||
|
||||
@@ -2141,6 +2141,21 @@ const message = {
|
||||
loginAgent: 'Агент входа',
|
||||
loginStatus: 'Статус',
|
||||
system: 'Системные логи',
|
||||
hostSystem: 'Журналы хоста',
|
||||
previousPage: 'Предыдущая страница',
|
||||
nextPage: 'Следующая страница',
|
||||
filter: 'Фильтр',
|
||||
service: 'Сервис',
|
||||
priority: 'Приоритет',
|
||||
priorityEmergency: 'Авария',
|
||||
priorityAlert: 'Тревога',
|
||||
priorityCritical: 'Критический',
|
||||
priorityError: 'Ошибка',
|
||||
priorityWarning: 'Предупреждение',
|
||||
priorityNotice: 'Уведомление',
|
||||
priorityInfo: 'Информация',
|
||||
priorityDebug: 'Отладка',
|
||||
message: 'Сообщение',
|
||||
deleteLogs: 'Очистить логи',
|
||||
resource: 'Ресурс',
|
||||
detail: {
|
||||
|
||||
@@ -2151,6 +2151,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: {
|
||||
|
||||
@@ -1986,6 +1986,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: {
|
||||
|
||||
@@ -1989,6 +1989,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: {
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -0,0 +1,256 @@
|
||||
<template>
|
||||
<LayoutContent v-loading="loading" :title="$t('logs.hostSystem')">
|
||||
<template #rightToolBar>
|
||||
<el-input v-model="keyword" class="p-w-200" clearable :placeholder="$t('logs.filter')" />
|
||||
<el-date-picker
|
||||
v-model="timeRange"
|
||||
class="p-w-360"
|
||||
type="datetimerange"
|
||||
range-separator="-"
|
||||
:start-placeholder="$t('commons.search.timeStart')"
|
||||
:end-placeholder="$t('commons.search.timeEnd')"
|
||||
:shortcuts="shortcuts"
|
||||
@change="changeTimeRange"
|
||||
/>
|
||||
<el-select v-model="priority" class="p-w-200" clearable>
|
||||
<template #prefix>{{ $t('logs.priority') }}</template>
|
||||
<el-option
|
||||
v-for="item in priorities"
|
||||
:key="item.value"
|
||||
:label="priorityLabel(item.value)"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
<el-select v-model="service" class="p-w-200" clearable filterable allow-create>
|
||||
<template #prefix>{{ $t('logs.service') }}</template>
|
||||
<el-option v-for="item in services" :key="item" :label="item" :value="item" />
|
||||
</el-select>
|
||||
<el-checkbox border v-model="watching" @change="changeWatch">
|
||||
{{ $t('commons.button.watch') }}
|
||||
</el-checkbox>
|
||||
<TableRefresh @search="resetAndLoadLogs" />
|
||||
</template>
|
||||
<template #main>
|
||||
<ComplexTable :data="logs" :pagination-config="paginationConfig" @row-click="openDetail">
|
||||
<el-table-column prop="time" :label="$t('commons.table.date')" width="220" show-overflow-tooltip />
|
||||
<el-table-column :label="$t('logs.priority')" width="120">
|
||||
<template #default="{ row }">
|
||||
<el-tag size="small" :type="priorityType(row.priority)">
|
||||
{{ priorityLabel(row.priority) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="service" :label="$t('logs.service')" width="220" show-overflow-tooltip />
|
||||
<el-table-column prop="message" :label="$t('logs.message')" min-width="360" show-overflow-tooltip />
|
||||
<template #pagination>
|
||||
<div class="flex items-center gap-2">
|
||||
<el-select v-model="paginationConfig.pageSize" class="p-w-100" @change="changePageSize">
|
||||
<el-option v-for="size in pageSizes" :key="size" :label="String(size)" :value="size" />
|
||||
</el-select>
|
||||
<el-button :disabled="cursorIndex === 0 || loading" @click="loadPreviousPage">
|
||||
{{ $t('logs.previousPage') }}
|
||||
</el-button>
|
||||
<el-button :disabled="!hasMore || loading" @click="loadNextPage">
|
||||
{{ $t('logs.nextPage') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</ComplexTable>
|
||||
</template>
|
||||
</LayoutContent>
|
||||
|
||||
<el-drawer v-model="detailOpen" :title="$t('logs.hostSystem')" size="50%">
|
||||
<el-descriptions v-if="selectedLog" :column="1" border>
|
||||
<el-descriptions-item :label="$t('commons.table.date')">{{ selectedLog.time || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item :label="$t('logs.priority')">
|
||||
{{ priorityLabel(selectedLog.priority) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item :label="$t('logs.service')">{{ selectedLog.service || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item :label="$t('logs.message')">{{ selectedLog.message }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<pre v-if="selectedLog" class="log-raw">{{ selectedLog.raw }}</pre>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, onUnmounted, reactive, ref, watch } from 'vue';
|
||||
import { Log } from '@/api/interface/log';
|
||||
import { listRunningServices, readSystemLogs } from '@/api/modules/log';
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
import i18n from '@/lang';
|
||||
import { shortcuts } from '@/utils/shortcuts';
|
||||
|
||||
const { currentNode } = useGlobalStore();
|
||||
const logs = ref<Log.SystemLogItem[]>([]);
|
||||
const loading = ref(false);
|
||||
const watching = ref(false);
|
||||
const keyword = ref('');
|
||||
const priority = ref('');
|
||||
const service = ref('');
|
||||
const services = ref<string[]>([]);
|
||||
const detailOpen = ref(false);
|
||||
const selectedLog = ref<Log.SystemLogItem>();
|
||||
const timeRange = ref<[Date, Date]>([new Date(new Date().setHours(0, 0, 0, 0)), new Date()]);
|
||||
const paginationConfig = reactive({
|
||||
pageSize: 100,
|
||||
cacheSizeKey: 'host-system-logs-page-size',
|
||||
});
|
||||
const pageSizes = [5, 10, 20, 50, 100, 200, 500];
|
||||
const cursorHistory = ref<string[]>(['']);
|
||||
const cursorIndex = ref(0);
|
||||
const hasMore = ref(false);
|
||||
let timer: ReturnType<typeof setTimeout> | undefined;
|
||||
let filterTimer: ReturnType<typeof setTimeout> | undefined;
|
||||
let requestID = 0;
|
||||
|
||||
const priorities = [
|
||||
{ value: '0', labelKey: 'logs.priorityEmergency' },
|
||||
{ value: '1', labelKey: 'logs.priorityAlert' },
|
||||
{ value: '2', labelKey: 'logs.priorityCritical' },
|
||||
{ value: '3', labelKey: 'logs.priorityError' },
|
||||
{ value: '4', labelKey: 'logs.priorityWarning' },
|
||||
{ value: '5', labelKey: 'logs.priorityNotice' },
|
||||
{ value: '6', labelKey: 'logs.priorityInfo' },
|
||||
{ value: '7', labelKey: 'logs.priorityDebug' },
|
||||
];
|
||||
|
||||
const loadLogs = async (cursor = cursorHistory.value[cursorIndex.value]) => {
|
||||
const currentRequestID = ++requestID;
|
||||
loading.value = true;
|
||||
try {
|
||||
const res = await readSystemLogs(
|
||||
{
|
||||
pageSize: paginationConfig.pageSize,
|
||||
cursor,
|
||||
startTime: timeRange.value?.[0],
|
||||
endTime: timeRange.value?.[1],
|
||||
keyword: keyword.value,
|
||||
priority: priority.value,
|
||||
service: service.value,
|
||||
},
|
||||
currentNode.value,
|
||||
);
|
||||
if (currentRequestID !== requestID) return;
|
||||
logs.value = res.data.items || [];
|
||||
hasMore.value = res.data.hasMore;
|
||||
if (res.data.nextCursor) {
|
||||
cursorHistory.value[cursorIndex.value + 1] = res.data.nextCursor;
|
||||
}
|
||||
} finally {
|
||||
if (currentRequestID === requestID) loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const resetAndLoadLogs = () => {
|
||||
requestID++;
|
||||
cursorHistory.value = [''];
|
||||
cursorIndex.value = 0;
|
||||
hasMore.value = false;
|
||||
return loadLogs('');
|
||||
};
|
||||
|
||||
const loadNextPage = () => {
|
||||
if (!hasMore.value) return;
|
||||
cursorIndex.value++;
|
||||
loadLogs();
|
||||
};
|
||||
|
||||
const loadPreviousPage = () => {
|
||||
if (cursorIndex.value === 0) return;
|
||||
cursorIndex.value--;
|
||||
loadLogs();
|
||||
};
|
||||
|
||||
const changePageSize = () => {
|
||||
localStorage.setItem(paginationConfig.cacheSizeKey, String(paginationConfig.pageSize));
|
||||
resetAndLoadLogs();
|
||||
};
|
||||
|
||||
const loadServices = async () => {
|
||||
try {
|
||||
const res = await listRunningServices(currentNode.value);
|
||||
services.value = res.data || [];
|
||||
} catch {
|
||||
services.value = [];
|
||||
}
|
||||
};
|
||||
|
||||
const scheduleWatch = () => {
|
||||
timer = setTimeout(async () => {
|
||||
if (!watching.value) return;
|
||||
try {
|
||||
if (!loading.value) {
|
||||
await resetAndLoadLogs();
|
||||
}
|
||||
} finally {
|
||||
if (watching.value) {
|
||||
scheduleWatch();
|
||||
}
|
||||
}
|
||||
}, 3000);
|
||||
};
|
||||
|
||||
const changeWatch = () => {
|
||||
if (timer) {
|
||||
clearTimeout(timer);
|
||||
timer = undefined;
|
||||
}
|
||||
if (watching.value) {
|
||||
scheduleWatch();
|
||||
}
|
||||
};
|
||||
|
||||
const changeTimeRange = () => {
|
||||
resetAndLoadLogs();
|
||||
};
|
||||
|
||||
const openDetail = (row: Log.SystemLogItem) => {
|
||||
selectedLog.value = row;
|
||||
detailOpen.value = true;
|
||||
};
|
||||
|
||||
const priorityLabel = (value: string) => {
|
||||
const priority = priorities.find((item) => item.value === value);
|
||||
return priority ? i18n.global.t(priority.labelKey) : value || '-';
|
||||
};
|
||||
const priorityType = (value: string) => {
|
||||
if (['0', '1', '2', '3'].includes(value)) return 'danger';
|
||||
if (value === '4') return 'warning';
|
||||
return 'info';
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
const cachedPageSize = Number(localStorage.getItem(paginationConfig.cacheSizeKey));
|
||||
if (pageSizes.includes(cachedPageSize)) {
|
||||
paginationConfig.pageSize = cachedPageSize;
|
||||
}
|
||||
resetAndLoadLogs();
|
||||
loadServices();
|
||||
});
|
||||
onUnmounted(() => {
|
||||
if (timer) clearTimeout(timer);
|
||||
if (filterTimer) clearTimeout(filterTimer);
|
||||
});
|
||||
|
||||
watch([keyword, priority, service], () => {
|
||||
requestID++;
|
||||
if (filterTimer) clearTimeout(filterTimer);
|
||||
filterTimer = setTimeout(resetAndLoadLogs, 300);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.log-raw {
|
||||
overflow: auto;
|
||||
padding: 12px;
|
||||
color: #f5f5f5;
|
||||
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', monospace;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
background: var(--panel-logs-bg-color);
|
||||
border: 1px solid var(--el-border-color-darker);
|
||||
border-radius: 4px;
|
||||
}
|
||||
</style>
|
||||
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user