mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-01-31 14:08:06 +08:00
feat: 完成面板日志功能
This commit is contained in:
parent
0dfb9bd5c7
commit
3a3670f660
@ -5,10 +5,12 @@ import (
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/captcha"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/encrypt"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/qqwry"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
@ -30,6 +32,7 @@ func (b *BaseApi) Login(c *gin.Context) {
|
||||
}
|
||||
|
||||
user, err := authService.Login(c, req)
|
||||
go saveLoginLogs(c, err)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
@ -102,3 +105,22 @@ func (b *BaseApi) SafeEntrance(c *gin.Context) {
|
||||
|
||||
helper.SuccessWithData(c, nil)
|
||||
}
|
||||
|
||||
func saveLoginLogs(c *gin.Context, err error) {
|
||||
var logs model.LoginLog
|
||||
if err != nil {
|
||||
logs.Status = constant.StatusFailed
|
||||
logs.Message = err.Error()
|
||||
} else {
|
||||
logs.Status = constant.StatusSuccess
|
||||
}
|
||||
logs.IP = c.ClientIP()
|
||||
qqWry, err := qqwry.NewQQwry()
|
||||
if err != nil {
|
||||
global.LOG.Errorf("load qqwry datas failed: %s", err)
|
||||
}
|
||||
res := qqWry.Find(logs.IP)
|
||||
logs.Agent = c.GetHeader("User-Agent")
|
||||
logs.Address = res.Area
|
||||
_ = logService.CreateLoginLog(logs)
|
||||
}
|
||||
|
@ -32,7 +32,6 @@ var (
|
||||
settingService = service.ServiceGroupApp.SettingService
|
||||
backupService = service.ServiceGroupApp.BackupService
|
||||
|
||||
operationService = service.ServiceGroupApp.OperationService
|
||||
commandService = service.ServiceGroupApp.CommandService
|
||||
|
||||
websiteGroupService = service.ServiceGroupApp.WebsiteGroupService
|
||||
@ -40,4 +39,6 @@ var (
|
||||
websiteDnsAccountService = service.ServiceGroupApp.WebSiteDnsAccountService
|
||||
websiteSSLService = service.ServiceGroupApp.WebSiteSSLService
|
||||
websiteAcmeAccountService = service.ServiceGroupApp.WebSiteAcmeAccountService
|
||||
|
||||
logService = service.ServiceGroupApp.LogService
|
||||
)
|
||||
|
@ -4,18 +4,17 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func (b *BaseApi) GetOperationList(c *gin.Context) {
|
||||
func (b *BaseApi) GetLoginLogs(c *gin.Context) {
|
||||
var req dto.PageInfo
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
|
||||
total, list, err := operationService.Page(req)
|
||||
total, list, err := logService.PageLoginLog(req)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
@ -27,20 +26,21 @@ func (b *BaseApi) GetOperationList(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
func (b *BaseApi) DeleteOperation(c *gin.Context) {
|
||||
var req dto.BatchDeleteReq
|
||||
func (b *BaseApi) GetOperationLogs(c *gin.Context) {
|
||||
var req dto.PageInfo
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
if err := global.VALID.Struct(req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := operationService.BatchDelete(req.Ids); err != nil {
|
||||
total, list, err := logService.PageOperationLog(req)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, nil)
|
||||
|
||||
helper.SuccessWithData(c, dto.PageResult{
|
||||
Items: list,
|
||||
Total: total,
|
||||
})
|
||||
}
|
@ -4,7 +4,7 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type OperationLogBack struct {
|
||||
type OperationLog struct {
|
||||
ID uint `json:"id"`
|
||||
Group string `json:"group"`
|
||||
Source string `json:"source"`
|
||||
@ -24,3 +24,13 @@ type OperationLogBack struct {
|
||||
Detail string `json:"detail"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
}
|
||||
|
||||
type LoginLog struct {
|
||||
ID uint `json:"id"`
|
||||
IP string `json:"ip"`
|
||||
Address string `json:"address"`
|
||||
Agent string `json:"agent"`
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
}
|
@ -21,3 +21,12 @@ type OperationLog struct {
|
||||
|
||||
Detail string `gorm:"type:longText" json:"detail"`
|
||||
}
|
||||
|
||||
type LoginLog struct {
|
||||
BaseModel
|
||||
IP string `gorm:"type:varchar(64)" json:"ip"`
|
||||
Address string `gorm:"type:varchar(64)" json:"address"`
|
||||
Agent string `gorm:"type:varchar(256)" json:"agent"`
|
||||
Status string `gorm:"type:varchar(64)" json:"status"`
|
||||
Message string `gorm:"type:longText" json:"message"`
|
||||
}
|
@ -19,13 +19,13 @@ type RepoGroup struct {
|
||||
GroupRepo
|
||||
SettingRepo
|
||||
BackupRepo
|
||||
OperationRepo
|
||||
WebSiteRepo
|
||||
WebSiteDomainRepo
|
||||
WebSiteGroupRepo
|
||||
WebsiteDnsAccountRepo
|
||||
WebsiteSSLRepo
|
||||
WebsiteAcmeAccountRepo
|
||||
LogRepo
|
||||
}
|
||||
|
||||
var RepoGroupApp = new(RepoGroup)
|
||||
|
52
backend/app/repo/logs.go
Normal file
52
backend/app/repo/logs.go
Normal file
@ -0,0 +1,52 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
)
|
||||
|
||||
type LogRepo struct{}
|
||||
|
||||
type ILogRepo interface {
|
||||
CreateLoginLog(user *model.LoginLog) error
|
||||
PageLoginLog(limit, offset int, opts ...DBOption) (int64, []model.LoginLog, error)
|
||||
|
||||
CreateOperationLog(user *model.OperationLog) error
|
||||
PageOperationLog(limit, offset int, opts ...DBOption) (int64, []model.OperationLog, error)
|
||||
}
|
||||
|
||||
func NewILogRepo() ILogRepo {
|
||||
return &LogRepo{}
|
||||
}
|
||||
|
||||
func (u *LogRepo) CreateLoginLog(log *model.LoginLog) error {
|
||||
return global.DB.Create(log).Error
|
||||
}
|
||||
|
||||
func (u *LogRepo) PageLoginLog(page, size int, opts ...DBOption) (int64, []model.LoginLog, error) {
|
||||
var ops []model.LoginLog
|
||||
db := global.DB.Model(&model.LoginLog{})
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
count := int64(0)
|
||||
db = db.Count(&count)
|
||||
err := db.Limit(size).Offset(size * (page - 1)).Find(&ops).Error
|
||||
return count, ops, err
|
||||
}
|
||||
|
||||
func (u *LogRepo) CreateOperationLog(log *model.OperationLog) error {
|
||||
return global.DB.Create(log).Error
|
||||
}
|
||||
|
||||
func (u *LogRepo) PageOperationLog(page, size int, opts ...DBOption) (int64, []model.OperationLog, error) {
|
||||
var ops []model.OperationLog
|
||||
db := global.DB.Model(&model.OperationLog{})
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
count := int64(0)
|
||||
db = db.Count(&count)
|
||||
err := db.Limit(size).Offset(size * (page - 1)).Find(&ops).Error
|
||||
return count, ops, err
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
)
|
||||
|
||||
type OperationRepo struct{}
|
||||
|
||||
type IOperationRepo interface {
|
||||
Create(user *model.OperationLog) error
|
||||
Page(limit, offset int, opts ...DBOption) (int64, []model.OperationLog, error)
|
||||
Delete(opts ...DBOption) error
|
||||
}
|
||||
|
||||
func NewIOperationRepo() IOperationRepo {
|
||||
return &OperationRepo{}
|
||||
}
|
||||
|
||||
func (u *OperationRepo) Create(user *model.OperationLog) error {
|
||||
return global.DB.Create(user).Error
|
||||
}
|
||||
|
||||
func (u *OperationRepo) Page(page, size int, opts ...DBOption) (int64, []model.OperationLog, error) {
|
||||
var ops []model.OperationLog
|
||||
db := global.DB.Model(&model.OperationLog{})
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
count := int64(0)
|
||||
db = db.Count(&count)
|
||||
err := db.Limit(size).Offset(size * (page - 1)).Find(&ops).Error
|
||||
return count, ops, err
|
||||
}
|
||||
|
||||
func (u *OperationRepo) Delete(opts ...DBOption) error {
|
||||
db := global.DB
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
|
||||
return db.Delete(&model.OperationLog{}).Error
|
||||
}
|
@ -27,12 +27,13 @@ type ServiceGroup struct {
|
||||
SettingService
|
||||
BackupService
|
||||
|
||||
OperationService
|
||||
WebsiteGroupService
|
||||
WebsiteService
|
||||
WebSiteDnsAccountService
|
||||
WebSiteSSLService
|
||||
WebSiteAcmeAccountService
|
||||
|
||||
LogService
|
||||
}
|
||||
|
||||
var ServiceGroupApp = new(ServiceGroup)
|
||||
@ -63,11 +64,12 @@ var (
|
||||
settingRepo = repo.RepoGroupApp.SettingRepo
|
||||
backupRepo = repo.RepoGroupApp.BackupRepo
|
||||
|
||||
operationRepo = repo.RepoGroupApp.OperationRepo
|
||||
websiteRepo = repo.RepoGroupApp.WebSiteRepo
|
||||
websiteGroupRepo = repo.RepoGroupApp.WebSiteGroupRepo
|
||||
websiteDomainRepo = repo.RepoGroupApp.WebSiteDomainRepo
|
||||
websiteDnsRepo = repo.RepoGroupApp.WebsiteDnsAccountRepo
|
||||
websiteSSLRepo = repo.RepoGroupApp.WebsiteSSLRepo
|
||||
websiteAcmeRepo = repo.RepoGroupApp.WebsiteAcmeAccountRepo
|
||||
|
||||
logRepo = repo.RepoGroupApp.LogRepo
|
||||
)
|
||||
|
@ -71,7 +71,7 @@ func (u *ImageRepoService) Create(imageRepoDto dto.ImageRepoCreate) error {
|
||||
deamonMap["insecure-registries"] = k
|
||||
}
|
||||
}
|
||||
newJson, err := json.Marshal(deamonMap)
|
||||
newJson, err := json.MarshalIndent(deamonMap, "", "\t")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
92
backend/app/service/logs.go
Normal file
92
backend/app/service/logs.go
Normal file
@ -0,0 +1,92 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type LogService struct{}
|
||||
|
||||
type ILogService interface {
|
||||
CreateLoginLog(operation model.LoginLog) error
|
||||
PageLoginLog(search dto.PageInfo) (int64, interface{}, error)
|
||||
|
||||
CreateOperationLog(operation model.OperationLog) error
|
||||
PageOperationLog(search dto.PageInfo) (int64, interface{}, error)
|
||||
}
|
||||
|
||||
func NewILogService() ILogService {
|
||||
return &LogService{}
|
||||
}
|
||||
|
||||
func (u *LogService) CreateLoginLog(operation model.LoginLog) error {
|
||||
return logRepo.CreateLoginLog(&operation)
|
||||
}
|
||||
|
||||
func (u *LogService) PageLoginLog(search dto.PageInfo) (int64, interface{}, error) {
|
||||
total, ops, err := logRepo.PageLoginLog(search.Page, search.PageSize, commonRepo.WithOrderBy("created_at desc"))
|
||||
var dtoOps []dto.LoginLog
|
||||
for _, op := range ops {
|
||||
var item dto.LoginLog
|
||||
if err := copier.Copy(&item, &op); err != nil {
|
||||
return 0, nil, errors.WithMessage(constant.ErrStructTransform, err.Error())
|
||||
}
|
||||
dtoOps = append(dtoOps, item)
|
||||
}
|
||||
return total, dtoOps, err
|
||||
}
|
||||
|
||||
func (u *LogService) CreateOperationLog(operation model.OperationLog) error {
|
||||
return logRepo.CreateOperationLog(&operation)
|
||||
}
|
||||
|
||||
func (u *LogService) PageOperationLog(search dto.PageInfo) (int64, interface{}, error) {
|
||||
total, ops, err := logRepo.PageOperationLog(search.Page, search.PageSize, commonRepo.WithOrderBy("created_at desc"))
|
||||
var dtoOps []dto.OperationLog
|
||||
for _, op := range ops {
|
||||
var item dto.OperationLog
|
||||
if err := copier.Copy(&item, &op); err != nil {
|
||||
return 0, nil, errors.WithMessage(constant.ErrStructTransform, err.Error())
|
||||
}
|
||||
item.Body = filterSensitive(item.Body)
|
||||
var res dto.Response
|
||||
if err := json.Unmarshal([]byte(item.Resp), &res); err != nil {
|
||||
global.LOG.Errorf("unmarshal failed, err: %+v", err)
|
||||
dtoOps = append(dtoOps, item)
|
||||
continue
|
||||
}
|
||||
item.Status = res.Code
|
||||
if item.Status != 200 {
|
||||
item.ErrorMessage = res.Msg
|
||||
}
|
||||
dtoOps = append(dtoOps, item)
|
||||
}
|
||||
return total, dtoOps, err
|
||||
}
|
||||
|
||||
func filterSensitive(vars string) string {
|
||||
var Sensitives = []string{"password", "Password", "credential", "privateKey"}
|
||||
ops := make(map[string]interface{})
|
||||
if err := json.Unmarshal([]byte(vars), &ops); err != nil {
|
||||
return vars
|
||||
}
|
||||
for k := range ops {
|
||||
for _, sen := range Sensitives {
|
||||
if k == sen {
|
||||
delete(ops, k)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
backStr, err := json.Marshal(ops)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return string(backStr)
|
||||
}
|
@ -1,77 +0,0 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type OperationService struct{}
|
||||
|
||||
type IOperationService interface {
|
||||
Page(search dto.PageInfo) (int64, interface{}, error)
|
||||
Create(operation model.OperationLog) error
|
||||
BatchDelete(ids []uint) error
|
||||
}
|
||||
|
||||
func NewIOperationService() IOperationService {
|
||||
return &OperationService{}
|
||||
}
|
||||
|
||||
func (u *OperationService) Create(operation model.OperationLog) error {
|
||||
return operationRepo.Create(&operation)
|
||||
}
|
||||
|
||||
func (u *OperationService) Page(search dto.PageInfo) (int64, interface{}, error) {
|
||||
total, ops, err := operationRepo.Page(search.Page, search.PageSize, commonRepo.WithOrderBy("created_at desc"))
|
||||
var dtoOps []dto.OperationLogBack
|
||||
for _, op := range ops {
|
||||
var item dto.OperationLogBack
|
||||
if err := copier.Copy(&item, &op); err != nil {
|
||||
return 0, nil, errors.WithMessage(constant.ErrStructTransform, err.Error())
|
||||
}
|
||||
item.Body = filterSensitive(item.Body)
|
||||
var res dto.Response
|
||||
if err := json.Unmarshal([]byte(item.Resp), &res); err != nil {
|
||||
global.LOG.Errorf("unmarshal failed, err: %+v", err)
|
||||
dtoOps = append(dtoOps, item)
|
||||
continue
|
||||
}
|
||||
item.Status = res.Code
|
||||
if item.Status != 200 {
|
||||
item.ErrorMessage = res.Msg
|
||||
}
|
||||
dtoOps = append(dtoOps, item)
|
||||
}
|
||||
return total, dtoOps, err
|
||||
}
|
||||
|
||||
func (u *OperationService) BatchDelete(ids []uint) error {
|
||||
return operationRepo.Delete(commonRepo.WithIdsIn(ids))
|
||||
}
|
||||
|
||||
func filterSensitive(vars string) string {
|
||||
var Sensitives = []string{"password", "Password", "credential", "privateKey"}
|
||||
ops := make(map[string]interface{})
|
||||
if err := json.Unmarshal([]byte(vars), &ops); err != nil {
|
||||
return vars
|
||||
}
|
||||
for k := range ops {
|
||||
for _, sen := range Sensitives {
|
||||
if k == sen {
|
||||
delete(ops, k)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
backStr, err := json.Marshal(ops)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return string(backStr)
|
||||
}
|
@ -19,6 +19,7 @@ func Init() {
|
||||
migrations.AddTableImageRepo,
|
||||
migrations.AddTableWebsite,
|
||||
migrations.AddTableDatabaseMysql,
|
||||
migrations.AddTableLoginLog,
|
||||
})
|
||||
if err := m.Migrate(); err != nil {
|
||||
global.LOG.Error(err)
|
||||
|
@ -12,7 +12,7 @@ import (
|
||||
var AddTableOperationLog = &gormigrate.Migration{
|
||||
ID: "20200809-add-table-operation-log",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
return tx.AutoMigrate(&model.OperationLog{})
|
||||
return tx.AutoMigrate(&model.OperationLog{}, &model.LoginLog{})
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -74,7 +74,7 @@ func Routers() *gin.Engine {
|
||||
systemRouter.InitCommandRouter(PrivateGroup)
|
||||
systemRouter.InitTerminalRouter(PrivateGroup)
|
||||
systemRouter.InitMonitorRouter(PrivateGroup)
|
||||
systemRouter.InitOperationLogRouter(PrivateGroup)
|
||||
systemRouter.InitLogRouter(PrivateGroup)
|
||||
systemRouter.InitFileRouter(PrivateGroup)
|
||||
systemRouter.InitCronjobRouter(PrivateGroup)
|
||||
systemRouter.InitSettingRouter(PrivateGroup)
|
||||
|
@ -70,7 +70,7 @@ func OperationRecord() gin.HandlerFunc {
|
||||
record.Latency = latency
|
||||
record.Resp = writer.body.String()
|
||||
|
||||
if err := service.NewIOperationService().Create(record); err != nil {
|
||||
if err := service.NewILogService().CreateOperationLog(record); err != nil {
|
||||
global.LOG.Errorf("create operation record failed, err: %v", err)
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ type RouterGroup struct {
|
||||
ContainerRouter
|
||||
CommandRouter
|
||||
MonitorRouter
|
||||
OperationLogRouter
|
||||
LogRouter
|
||||
FileRouter
|
||||
TerminalRouter
|
||||
CronjobRouter
|
||||
|
@ -2,7 +2,6 @@ package router
|
||||
|
||||
import (
|
||||
v1 "github.com/1Panel-dev/1Panel/backend/app/api/v1"
|
||||
"github.com/1Panel-dev/1Panel/backend/middleware"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
@ -10,12 +9,11 @@ type BaseRouter struct{}
|
||||
|
||||
func (s *BaseRouter) InitBaseRouter(Router *gin.RouterGroup) {
|
||||
baseRouter := Router.Group("auth")
|
||||
withRecordRouter := Router.Group("auth").Use(middleware.OperationRecord())
|
||||
baseApi := v1.ApiGroupApp.BaseApi
|
||||
{
|
||||
baseRouter.GET("captcha", baseApi.Captcha)
|
||||
baseRouter.POST("mfalogin", baseApi.MFALogin)
|
||||
withRecordRouter.POST("login", baseApi.Login)
|
||||
withRecordRouter.POST("logout", baseApi.LogOut)
|
||||
baseRouter.POST("login", baseApi.Login)
|
||||
baseRouter.POST("logout", baseApi.LogOut)
|
||||
}
|
||||
}
|
||||
|
@ -7,14 +7,14 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type OperationLogRouter struct{}
|
||||
type LogRouter struct{}
|
||||
|
||||
func (s *OperationLogRouter) InitOperationLogRouter(Router *gin.RouterGroup) {
|
||||
operationRouter := Router.Group("operations")
|
||||
func (s *LogRouter) InitLogRouter(Router *gin.RouterGroup) {
|
||||
operationRouter := Router.Group("logs")
|
||||
operationRouter.Use(middleware.JwtAuth()).Use(middleware.SessionAuth()).Use(middleware.PasswordExpired())
|
||||
baseApi := v1.ApiGroupApp.BaseApi
|
||||
{
|
||||
operationRouter.POST("", baseApi.GetOperationList)
|
||||
operationRouter.POST("/del", baseApi.DeleteOperation)
|
||||
operationRouter.POST("login", baseApi.GetLoginLogs)
|
||||
operationRouter.POST("operation", baseApi.GetOperationLogs)
|
||||
}
|
||||
}
|
168
backend/utils/qqwry/qqwry.go
Normal file
168
backend/utils/qqwry/qqwry.go
Normal file
@ -0,0 +1,168 @@
|
||||
package qqwry
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/text/encoding/simplifiedchinese"
|
||||
)
|
||||
|
||||
const (
|
||||
indexLen = 7
|
||||
redirectMode1 = 0x01
|
||||
redirectMode2 = 0x02
|
||||
)
|
||||
|
||||
var IpCommonDictionary []byte
|
||||
|
||||
type QQwry struct {
|
||||
Data []byte
|
||||
Offset int64
|
||||
}
|
||||
|
||||
func NewQQwry() (*QQwry, error) {
|
||||
IpCommonDictionary, err := ioutil.ReadFile("/opt/1Panel/conf/qqwry.dat")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &QQwry{Data: IpCommonDictionary}, nil
|
||||
}
|
||||
|
||||
// readData 从文件中读取数据
|
||||
func (q *QQwry) readData(num int, offset ...int64) (rs []byte) {
|
||||
if len(offset) > 0 {
|
||||
q.setOffset(offset[0])
|
||||
}
|
||||
nums := int64(num)
|
||||
end := q.Offset + nums
|
||||
dataNum := int64(len(q.Data))
|
||||
if q.Offset > dataNum {
|
||||
return nil
|
||||
}
|
||||
|
||||
if end > dataNum {
|
||||
end = dataNum
|
||||
}
|
||||
rs = q.Data[q.Offset:end]
|
||||
q.Offset = end
|
||||
return
|
||||
}
|
||||
|
||||
// setOffset 设置偏移量
|
||||
func (q *QQwry) setOffset(offset int64) {
|
||||
q.Offset = offset
|
||||
}
|
||||
|
||||
// Find ip地址查询对应归属地信息
|
||||
func (q *QQwry) Find(ip string) (res ResultQQwry) {
|
||||
res = ResultQQwry{}
|
||||
res.IP = ip
|
||||
if strings.Count(ip, ".") != 3 {
|
||||
return res
|
||||
}
|
||||
offset := q.searchIndex(binary.BigEndian.Uint32(net.ParseIP(ip).To4()))
|
||||
if offset <= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
var area []byte
|
||||
mode := q.readMode(offset + 4)
|
||||
if mode == redirectMode1 {
|
||||
countryOffset := q.readUInt24()
|
||||
mode = q.readMode(countryOffset)
|
||||
if mode == redirectMode2 {
|
||||
c := q.readUInt24()
|
||||
area = q.readString(c)
|
||||
} else {
|
||||
area = q.readString(countryOffset)
|
||||
}
|
||||
} else if mode == redirectMode2 {
|
||||
countryOffset := q.readUInt24()
|
||||
area = q.readString(countryOffset)
|
||||
} else {
|
||||
area = q.readString(offset + 4)
|
||||
}
|
||||
|
||||
enc := simplifiedchinese.GBK.NewDecoder()
|
||||
res.Area, _ = enc.String(string(area))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type ResultQQwry struct {
|
||||
IP string `json:"ip"`
|
||||
Area string `json:"area"`
|
||||
}
|
||||
|
||||
// readMode 获取偏移值类型
|
||||
func (q *QQwry) readMode(offset uint32) byte {
|
||||
mode := q.readData(1, int64(offset))
|
||||
return mode[0]
|
||||
}
|
||||
|
||||
// readString 获取字符串
|
||||
func (q *QQwry) readString(offset uint32) []byte {
|
||||
q.setOffset(int64(offset))
|
||||
data := make([]byte, 0, 30)
|
||||
for {
|
||||
buf := q.readData(1)
|
||||
if buf[0] == 0 {
|
||||
break
|
||||
}
|
||||
data = append(data, buf[0])
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// searchIndex 查找索引位置
|
||||
func (q *QQwry) searchIndex(ip uint32) uint32 {
|
||||
header := q.readData(8, 0)
|
||||
|
||||
start := binary.LittleEndian.Uint32(header[:4])
|
||||
end := binary.LittleEndian.Uint32(header[4:])
|
||||
|
||||
for {
|
||||
mid := q.getMiddleOffset(start, end)
|
||||
buf := q.readData(indexLen, int64(mid))
|
||||
_ip := binary.LittleEndian.Uint32(buf[:4])
|
||||
|
||||
if end-start == indexLen {
|
||||
offset := byteToUInt32(buf[4:])
|
||||
buf = q.readData(indexLen)
|
||||
if ip < binary.LittleEndian.Uint32(buf[:4]) {
|
||||
return offset
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
if _ip > ip {
|
||||
end = mid
|
||||
} else if _ip < ip {
|
||||
start = mid
|
||||
} else if _ip == ip {
|
||||
return byteToUInt32(buf[4:])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// readUInt24
|
||||
func (q *QQwry) readUInt24() uint32 {
|
||||
buf := q.readData(3)
|
||||
return byteToUInt32(buf)
|
||||
}
|
||||
|
||||
// getMiddleOffset
|
||||
func (q *QQwry) getMiddleOffset(start uint32, end uint32) uint32 {
|
||||
records := ((end - start) / indexLen) >> 1
|
||||
return start + records*indexLen
|
||||
}
|
||||
|
||||
// byteToUInt32 将 byte 转换为uint32
|
||||
func byteToUInt32(data []byte) uint32 {
|
||||
i := uint32(data[0]) & 0xff
|
||||
i |= (uint32(data[1]) << 8) & 0xff00
|
||||
i |= (uint32(data[2]) << 16) & 0xff0000
|
||||
return i
|
||||
}
|
31
frontend/src/api/interface/log.ts
Normal file
31
frontend/src/api/interface/log.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import { DateTimeFormats } from '@intlify/core-base';
|
||||
|
||||
export namespace Log {
|
||||
export interface OperationLog {
|
||||
id: number;
|
||||
group: string;
|
||||
source: string;
|
||||
action: string;
|
||||
ip: string;
|
||||
path: string;
|
||||
method: string;
|
||||
userAgent: string;
|
||||
body: string;
|
||||
resp: string;
|
||||
|
||||
status: number;
|
||||
latency: number;
|
||||
errorMessage: string;
|
||||
|
||||
detail: string;
|
||||
createdAt: DateTimeFormats;
|
||||
}
|
||||
export interface LoginLogs {
|
||||
ip: string;
|
||||
address: string;
|
||||
agent: string;
|
||||
status: string;
|
||||
message: string;
|
||||
createdAt: DateTimeFormats;
|
||||
}
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
import { DateTimeFormats } from '@intlify/core-base';
|
||||
|
||||
export interface ResOperationLog {
|
||||
id: number;
|
||||
group: string;
|
||||
source: string;
|
||||
action: string;
|
||||
ip: string;
|
||||
path: string;
|
||||
method: string;
|
||||
userAgent: string;
|
||||
body: string;
|
||||
resp: string;
|
||||
|
||||
status: number;
|
||||
latency: number;
|
||||
errorMessage: string;
|
||||
|
||||
detail: string;
|
||||
createdAt: DateTimeFormats;
|
||||
}
|
11
frontend/src/api/modules/log.ts
Normal file
11
frontend/src/api/modules/log.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import http from '@/api';
|
||||
import { ResPage, ReqPage } from '../interface';
|
||||
import { Log } from '../interface/log';
|
||||
|
||||
export const getOperationLogs = (info: ReqPage) => {
|
||||
return http.post<ResPage<Log.OperationLog>>(`/logs/operation`, info);
|
||||
};
|
||||
|
||||
export const getLoginLogs = (info: ReqPage) => {
|
||||
return http.post<ResPage<Log.OperationLog>>(`/logs/login`, info);
|
||||
};
|
@ -1,7 +0,0 @@
|
||||
import http from '@/api';
|
||||
import { ResPage, ReqPage } from '../interface';
|
||||
import { ResOperationLog } from '../interface/operation-log';
|
||||
|
||||
export const getOperationList = (info: ReqPage) => {
|
||||
return http.post<ResPage<ResOperationLog>>(`/operations`, info);
|
||||
};
|
@ -142,7 +142,7 @@ export default {
|
||||
terminal: 'Terminal',
|
||||
settings: 'Setting',
|
||||
toolbox: 'Toolbox',
|
||||
operations: 'Operation Records',
|
||||
logs: 'Log',
|
||||
},
|
||||
home: {
|
||||
welcome: 'Welcome',
|
||||
@ -475,6 +475,13 @@ export default {
|
||||
emptyTerminal: 'No terminal is currently connected',
|
||||
},
|
||||
operations: {
|
||||
operation: 'Operation logs',
|
||||
login: 'Login logs',
|
||||
system: 'System logs',
|
||||
loginIP: 'Login IP',
|
||||
loginAddress: 'Login address',
|
||||
loginAgent: 'Login agent',
|
||||
loginStatus: 'Login status',
|
||||
detail: {
|
||||
users: 'User',
|
||||
hosts: 'Host',
|
||||
@ -483,6 +490,7 @@ export default {
|
||||
backups: 'Backup Account',
|
||||
settings: 'Panel Setting',
|
||||
cronjobs: 'Cronjob',
|
||||
databases: 'Database',
|
||||
status: ' Update status',
|
||||
auth: 'User',
|
||||
login: ' login',
|
||||
@ -493,7 +501,6 @@ export default {
|
||||
delete: ' delete',
|
||||
del: 'delete',
|
||||
},
|
||||
operatoin: 'operatoin',
|
||||
status: 'status',
|
||||
request: 'request',
|
||||
response: 'response',
|
||||
|
@ -142,7 +142,7 @@ export default {
|
||||
terminal: '终端',
|
||||
settings: '面板设置',
|
||||
toolbox: '工具箱',
|
||||
operations: '操作日志',
|
||||
logs: '面板日志',
|
||||
},
|
||||
home: {
|
||||
welcome: '欢迎使用',
|
||||
@ -487,7 +487,14 @@ export default {
|
||||
key: '密钥',
|
||||
emptyTerminal: '暂无终端连接',
|
||||
},
|
||||
operations: {
|
||||
logs: {
|
||||
operation: '操作日志',
|
||||
login: '登录日志',
|
||||
loginIP: '登录 IP',
|
||||
loginAddress: '登录地址',
|
||||
loginAgent: '用户代理',
|
||||
loginStatus: '登录状态',
|
||||
system: '系统日志',
|
||||
detail: {
|
||||
users: '用户',
|
||||
hosts: '主机',
|
||||
@ -496,6 +503,7 @@ export default {
|
||||
backups: '备份账号',
|
||||
settings: '面板设置',
|
||||
cronjobs: '计划任务',
|
||||
databases: '数据库',
|
||||
status: '状态修改',
|
||||
auth: '用户',
|
||||
post: '创建',
|
||||
@ -503,6 +511,8 @@ export default {
|
||||
update: '更新',
|
||||
delete: '删除',
|
||||
login: '登录',
|
||||
backup: '备份',
|
||||
recover: '恢复',
|
||||
logout: '退出',
|
||||
del: '删除',
|
||||
},
|
||||
|
43
frontend/src/routers/modules/log.ts
Normal file
43
frontend/src/routers/modules/log.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import { Layout } from '@/routers/constant';
|
||||
|
||||
const logsRouter = {
|
||||
sort: 10,
|
||||
path: '/logs',
|
||||
component: Layout,
|
||||
redirect: '/logs',
|
||||
meta: {
|
||||
title: 'menu.logs',
|
||||
icon: 'p-log',
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
name: 'LoginLog',
|
||||
component: () => import('@/views/log/login/index.vue'),
|
||||
hidden: true,
|
||||
meta: {
|
||||
activeMenu: '/logs',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'operation',
|
||||
name: 'OperationLog',
|
||||
component: () => import('@/views/log/operation/index.vue'),
|
||||
hidden: true,
|
||||
meta: {
|
||||
activeMenu: '/logs',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'system',
|
||||
name: 'SystemLog',
|
||||
component: () => import('@/views/log/system/index.vue'),
|
||||
hidden: true,
|
||||
meta: {
|
||||
activeMenu: '/logs',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default logsRouter;
|
@ -1,22 +0,0 @@
|
||||
import { Layout } from '@/routers/constant';
|
||||
|
||||
const operationRouter = {
|
||||
sort: 10,
|
||||
path: '/operations',
|
||||
component: Layout,
|
||||
redirect: '/operation',
|
||||
meta: {
|
||||
title: 'menu.operations',
|
||||
icon: 'p-log',
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '/operation',
|
||||
name: 'OperationLog',
|
||||
component: () => import('@/views/operation-log/index.vue'),
|
||||
meta: {},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default operationRouter;
|
@ -332,7 +332,7 @@ const weekOptions = [
|
||||
const timeRangeLoad = ref<Array<any>>([new Date(new Date().setHours(0, 0, 0, 0)), new Date()]);
|
||||
const searchInfo = reactive({
|
||||
page: 1,
|
||||
pageSize: 5,
|
||||
pageSize: 10,
|
||||
recordTotal: 0,
|
||||
cronjobID: 0,
|
||||
startTime: new Date(new Date().setHours(0, 0, 0, 0)),
|
||||
|
@ -49,14 +49,11 @@ const data = ref();
|
||||
const selects = ref<any>([]);
|
||||
const paginationConfig = reactive({
|
||||
page: 1,
|
||||
pageSize: 5,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
});
|
||||
const commandSearch = reactive({
|
||||
page: 1,
|
||||
pageSize: 5,
|
||||
info: '',
|
||||
});
|
||||
const info = ref();
|
||||
|
||||
type FormInstance = InstanceType<typeof ElForm>;
|
||||
const commandInfoRef = ref<FormInstance>();
|
||||
const rules = reactive({
|
||||
@ -136,9 +133,12 @@ const buttons = [
|
||||
];
|
||||
|
||||
const search = async () => {
|
||||
commandSearch.page = paginationConfig.page;
|
||||
commandSearch.pageSize = paginationConfig.pageSize;
|
||||
const res = await getCommandPage(commandSearch);
|
||||
let params = {
|
||||
page: paginationConfig.page,
|
||||
pageSize: paginationConfig.pageSize,
|
||||
info: info.value,
|
||||
};
|
||||
const res = await getCommandPage(params);
|
||||
data.value = res.data.items;
|
||||
paginationConfig.total = res.data.total;
|
||||
};
|
||||
|
73
frontend/src/views/log/index.vue
Normal file
73
frontend/src/views/log/index.vue
Normal file
@ -0,0 +1,73 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-card class="topCard">
|
||||
<el-radio-group v-model="active">
|
||||
<el-radio-button class="topButton" size="large" @click="routerTo('/logs')" label="login">
|
||||
{{ $t('logs.login') }}
|
||||
</el-radio-button>
|
||||
<el-radio-button class="topButton" size="large" @click="routerTo('/logs/operation')" label="operation">
|
||||
{{ $t('logs.operation') }}
|
||||
</el-radio-button>
|
||||
<el-radio-button class="topButton" size="large" @click="routerTo('/logs/system')" label="system">
|
||||
{{ $t('logs.system') }}
|
||||
</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
const router = useRouter();
|
||||
interface MenuProps {
|
||||
activeName: string;
|
||||
}
|
||||
const props = withDefaults(defineProps<MenuProps>(), {
|
||||
activeName: 'operation',
|
||||
});
|
||||
|
||||
const active = ref('operation');
|
||||
|
||||
onMounted(() => {
|
||||
if (props.activeName) {
|
||||
active.value = props.activeName;
|
||||
}
|
||||
});
|
||||
|
||||
const routerTo = (path: string) => {
|
||||
router.push({ path: path });
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.topCard {
|
||||
--el-card-border-color: var(--el-border-color-light);
|
||||
--el-card-border-radius: 4px;
|
||||
--el-card-padding: 0px;
|
||||
--el-card-bg-color: var(--el-fill-color-blank);
|
||||
}
|
||||
.topButton .el-radio-button__inner {
|
||||
display: inline-block;
|
||||
line-height: 1;
|
||||
white-space: nowrap;
|
||||
vertical-align: middle;
|
||||
background: var(--el-button-bg-color, var(--el-fill-color-blank));
|
||||
border: 0;
|
||||
font-weight: 350;
|
||||
border-left: 0;
|
||||
color: var(--el-button-text-color, var(--el-text-color-regular));
|
||||
text-align: center;
|
||||
box-sizing: border-box;
|
||||
outline: 0;
|
||||
margin: 0;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
transition: var(--el-transition-all);
|
||||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
padding: 8px 15px;
|
||||
font-size: var(--el-font-size-base);
|
||||
border-radius: 0;
|
||||
}
|
||||
</style>
|
69
frontend/src/views/log/login/index.vue
Normal file
69
frontend/src/views/log/login/index.vue
Normal file
@ -0,0 +1,69 @@
|
||||
<template>
|
||||
<div>
|
||||
<Submenu activeName="login" />
|
||||
<el-card style="margin-top: 20px">
|
||||
<ComplexTable :pagination-config="paginationConfig" :data="data" @search="search">
|
||||
<el-table-column min-width="40" :label="$t('logs.loginIP')" prop="ip" />
|
||||
<el-table-column min-width="40" :label="$t('logs.loginAddress')" prop="address" />
|
||||
<el-table-column :label="$t('logs.loginAgent')" show-overflow-tooltip prop="agent" />
|
||||
<el-table-column min-width="40" :label="$t('logs.loginStatus')" prop="status">
|
||||
<template #default="{ row }">
|
||||
<div v-if="row.status === 'Success'">
|
||||
<el-tag type="success">{{ row.status }}</el-tag>
|
||||
</div>
|
||||
<div v-else>
|
||||
<el-tooltip class="box-item" effect="dark" :content="row.message" placement="top-start">
|
||||
<el-tag type="danger">{{ row.status }}</el-tag>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="createdAt"
|
||||
:label="$t('commons.table.date')"
|
||||
:formatter="dateFromat"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
</ComplexTable>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import ComplexTable from '@/components/complex-table/index.vue';
|
||||
import { dateFromat } from '@/utils/util';
|
||||
import { getLoginLogs } from '@/api/modules/log';
|
||||
import Submenu from '@/views/log/index.vue';
|
||||
import { onMounted, reactive, ref } from '@vue/runtime-core';
|
||||
|
||||
const data = ref();
|
||||
const paginationConfig = reactive({
|
||||
currentPage: 1,
|
||||
pageSize: 15,
|
||||
total: 0,
|
||||
});
|
||||
|
||||
const search = async () => {
|
||||
let params = {
|
||||
page: paginationConfig.currentPage,
|
||||
pageSize: paginationConfig.pageSize,
|
||||
};
|
||||
const res = await getLoginLogs(params);
|
||||
data.value = res.data.items;
|
||||
paginationConfig.total = res.data.total;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
search();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.pre {
|
||||
white-space: pre-wrap;
|
||||
white-space: -moz-pre-wrap;
|
||||
white-space: -pre-wrap;
|
||||
white-space: -o-pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
</style>
|
147
frontend/src/views/log/operation/index.vue
Normal file
147
frontend/src/views/log/operation/index.vue
Normal file
@ -0,0 +1,147 @@
|
||||
<template>
|
||||
<div>
|
||||
<Submenu activeName="operation" />
|
||||
<el-card style="margin-top: 20px">
|
||||
<ComplexTable :pagination-config="paginationConfig" :data="data" @search="search">
|
||||
<el-table-column :label="$t('logs.operatoin')" fix>
|
||||
<template #default="{ row }">
|
||||
{{ fmtOperation(row) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('logs.status')" prop="status">
|
||||
<template #default="{ row }">
|
||||
<el-tag v-if="row.status == '200'" class="ml-2" type="success">{{ row.status }}</el-tag>
|
||||
<div v-else>
|
||||
<el-popover
|
||||
placement="top-start"
|
||||
:title="$t('commons.table.message')"
|
||||
:width="400"
|
||||
trigger="hover"
|
||||
:content="row.errorMessage"
|
||||
>
|
||||
<template #reference>
|
||||
<el-tag class="ml-2" type="warning">{{ row.status }}</el-tag>
|
||||
</template>
|
||||
</el-popover>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="IP" prop="ip" />
|
||||
<el-table-column :label="$t('logs.request')" prop="path">
|
||||
<template #default="{ row }">
|
||||
<div>
|
||||
<el-popover :width="500" v-if="row.body" placement="left-start" trigger="click">
|
||||
<div style="word-wrap: break-word; font-size: 12px; white-space: normal">
|
||||
<pre class="pre">{{ fmtBody(row.body) }}</pre>
|
||||
</div>
|
||||
<template #reference>
|
||||
<el-icon style="cursor: pointer"><warning /></el-icon>
|
||||
</template>
|
||||
</el-popover>
|
||||
<span v-else>-</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('logs.response')" prop="path">
|
||||
<template #default="{ row }">
|
||||
<div>
|
||||
<el-popover :width="500" v-if="row.resp" placement="left-start" trigger="click">
|
||||
<div style="word-wrap: break-word; font-size: 12px; white-space: normal">
|
||||
<pre class="pre">{{ fmtBody(row.resp) }}</pre>
|
||||
</div>
|
||||
<template #reference>
|
||||
<el-icon style="cursor: pointer"><warning /></el-icon>
|
||||
</template>
|
||||
</el-popover>
|
||||
<span v-else>-</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="createdAt"
|
||||
:label="$t('commons.table.date')"
|
||||
:formatter="dateFromat"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
</ComplexTable>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import ComplexTable from '@/components/complex-table/index.vue';
|
||||
import { dateFromat } from '@/utils/util';
|
||||
import { getOperationLogs } from '@/api/modules/log';
|
||||
import Submenu from '@/views/log/index.vue';
|
||||
import { onMounted, reactive, ref } from '@vue/runtime-core';
|
||||
import { Log } from '@/api/interface/log';
|
||||
import i18n from '@/lang';
|
||||
|
||||
const data = ref();
|
||||
const paginationConfig = reactive({
|
||||
currentPage: 1,
|
||||
pageSize: 15,
|
||||
total: 0,
|
||||
});
|
||||
|
||||
const search = async () => {
|
||||
let params = {
|
||||
page: paginationConfig.currentPage,
|
||||
pageSize: paginationConfig.pageSize,
|
||||
};
|
||||
const res = await getOperationLogs(params);
|
||||
data.value = res.data.items;
|
||||
paginationConfig.total = res.data.total;
|
||||
};
|
||||
|
||||
const fmtOperation = (row: Log.OperationLog) => {
|
||||
if (row.method.toLocaleLowerCase() === 'post') {
|
||||
if (row.source == '' && row.action == '') {
|
||||
return (
|
||||
i18n.global.t('logs.detail.' + row.group.toLocaleLowerCase()) +
|
||||
i18n.global.t('logs.detail.' + row.method.toLocaleLowerCase())
|
||||
);
|
||||
}
|
||||
if (row.action == '') {
|
||||
return (
|
||||
i18n.global.t('logs.detail.' + row.group.toLocaleLowerCase()) +
|
||||
i18n.global.t('logs.detail.' + row.source.toLocaleLowerCase())
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (row.action == '') {
|
||||
return (
|
||||
i18n.global.t('logs.detail.' + row.group.toLocaleLowerCase()) +
|
||||
i18n.global.t('logs.detail.' + row.method.toLocaleLowerCase())
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
i18n.global.t('logs.detail.' + row.group.toLocaleLowerCase()) +
|
||||
i18n.global.t('logs.detail.' + row.source.toLocaleLowerCase())
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const fmtBody = (value: string) => {
|
||||
try {
|
||||
return JSON.parse(value);
|
||||
} catch (err) {
|
||||
return value;
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
search();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.pre {
|
||||
white-space: pre-wrap;
|
||||
white-space: -moz-pre-wrap;
|
||||
white-space: -pre-wrap;
|
||||
white-space: -o-pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
</style>
|
42
frontend/src/views/log/system/index.vue
Normal file
42
frontend/src/views/log/system/index.vue
Normal file
@ -0,0 +1,42 @@
|
||||
<template>
|
||||
<div>
|
||||
<Submenu activeName="system" />
|
||||
<el-card style="margin-top: 20px">
|
||||
<codemirror
|
||||
:autofocus="true"
|
||||
placeholder="None data"
|
||||
:indent-with-tab="true"
|
||||
:tabSize="4"
|
||||
style="height: calc(100vh - 150px)"
|
||||
:lineWrapping="true"
|
||||
:matchBrackets="true"
|
||||
theme="cobalt"
|
||||
:styleActiveLine="true"
|
||||
:extensions="extensions"
|
||||
v-model="logs"
|
||||
:readOnly="true"
|
||||
/>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Codemirror } from 'vue-codemirror';
|
||||
import { javascript } from '@codemirror/lang-javascript';
|
||||
import { oneDark } from '@codemirror/theme-one-dark';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import Submenu from '@/views/log/index.vue';
|
||||
import { LoadFile } from '@/api/modules/files';
|
||||
|
||||
const extensions = [javascript(), oneDark];
|
||||
const logs = ref();
|
||||
|
||||
const loadSystemlogs = async () => {
|
||||
const res = await LoadFile({ path: '/opt/1Panel/log/1Panel.log' });
|
||||
logs.value = res.data;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
loadSystemlogs();
|
||||
});
|
||||
</script>
|
@ -1,141 +0,0 @@
|
||||
<template>
|
||||
<ComplexTable :pagination-config="paginationConfig" :data="data" @search="search">
|
||||
<el-table-column :label="$t('operations.operatoin')" fix>
|
||||
<template #default="{ row }">
|
||||
{{ fmtOperation(row) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('operations.status')" prop="status">
|
||||
<template #default="{ row }">
|
||||
<el-tag v-if="row.status == '200'" class="ml-2" type="success">{{ row.status }}</el-tag>
|
||||
<div v-else>
|
||||
<el-popover
|
||||
placement="top-start"
|
||||
:title="$t('commons.table.message')"
|
||||
:width="400"
|
||||
trigger="hover"
|
||||
:content="row.errorMessage"
|
||||
>
|
||||
<template #reference>
|
||||
<el-tag class="ml-2" type="warning">{{ row.status }}</el-tag>
|
||||
</template>
|
||||
</el-popover>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="IP" prop="ip" />
|
||||
<el-table-column :label="$t('operations.request')" prop="path">
|
||||
<template #default="{ row }">
|
||||
<div>
|
||||
<el-popover :width="500" v-if="row.body" placement="left-start" trigger="click">
|
||||
<div style="word-wrap: break-word; font-size: 12px; white-space: normal">
|
||||
<pre class="pre">{{ fmtBody(row.body) }}</pre>
|
||||
</div>
|
||||
<template #reference>
|
||||
<el-icon style="cursor: pointer"><warning /></el-icon>
|
||||
</template>
|
||||
</el-popover>
|
||||
<span v-else>无</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('operations.response')" prop="path">
|
||||
<template #default="{ row }">
|
||||
<div>
|
||||
<el-popover :width="500" v-if="row.resp" placement="left-start" trigger="click">
|
||||
<div style="word-wrap: break-word; font-size: 12px; white-space: normal">
|
||||
<pre class="pre">{{ fmtBody(row.resp) }}</pre>
|
||||
</div>
|
||||
<template #reference>
|
||||
<el-icon style="cursor: pointer"><warning /></el-icon>
|
||||
</template>
|
||||
</el-popover>
|
||||
<span v-else>无</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="createdAt"
|
||||
:label="$t('commons.table.date')"
|
||||
:formatter="dateFromat"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
</ComplexTable>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import ComplexTable from '@/components/complex-table/index.vue';
|
||||
import { dateFromat } from '@/utils/util';
|
||||
import { getOperationList } from '@/api/modules/operation-log';
|
||||
import { onMounted, reactive, ref } from '@vue/runtime-core';
|
||||
import { ResOperationLog } from '@/api/interface/operation-log';
|
||||
import i18n from '@/lang';
|
||||
|
||||
const data = ref();
|
||||
const paginationConfig = reactive({
|
||||
currentPage: 1,
|
||||
pageSize: 5,
|
||||
total: 0,
|
||||
});
|
||||
|
||||
const search = async () => {
|
||||
let params = {
|
||||
page: paginationConfig.currentPage,
|
||||
pageSize: paginationConfig.pageSize,
|
||||
};
|
||||
const res = await getOperationList(params);
|
||||
data.value = res.data.items;
|
||||
paginationConfig.total = res.data.total;
|
||||
};
|
||||
|
||||
const fmtOperation = (row: ResOperationLog) => {
|
||||
if (row.method.toLocaleLowerCase() === 'post') {
|
||||
if (row.source == '' && row.action == '') {
|
||||
return (
|
||||
i18n.global.t('operations.detail.' + row.group.toLocaleLowerCase()) +
|
||||
i18n.global.t('operations.detail.' + row.method.toLocaleLowerCase())
|
||||
);
|
||||
}
|
||||
if (row.action == '') {
|
||||
return (
|
||||
i18n.global.t('operations.detail.' + row.group.toLocaleLowerCase()) +
|
||||
i18n.global.t('operations.detail.' + row.source.toLocaleLowerCase())
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (row.action == '') {
|
||||
return (
|
||||
i18n.global.t('operations.detail.' + row.group.toLocaleLowerCase()) +
|
||||
i18n.global.t('operations.detail.' + row.method.toLocaleLowerCase())
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
i18n.global.t('operations.detail.' + row.group.toLocaleLowerCase()) +
|
||||
i18n.global.t('operations.detail.' + row.source.toLocaleLowerCase())
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const fmtBody = (value: string) => {
|
||||
try {
|
||||
return JSON.parse(value);
|
||||
} catch (err) {
|
||||
return value;
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
search();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.pre {
|
||||
white-space: pre-wrap;
|
||||
white-space: -moz-pre-wrap;
|
||||
white-space: -pre-wrap;
|
||||
white-space: -o-pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
</style>
|
3
go.mod
3
go.mod
@ -25,7 +25,7 @@ require (
|
||||
github.com/gwatts/gin-adapter v1.0.0
|
||||
github.com/jinzhu/copier v0.3.5
|
||||
github.com/joho/godotenv v1.4.0
|
||||
github.com/kr/pty v1.1.5
|
||||
github.com/kr/pty v1.1.8
|
||||
github.com/mholt/archiver/v4 v4.0.0-alpha.7
|
||||
github.com/minio/minio-go/v7 v7.0.36
|
||||
github.com/mojocn/base64Captcha v1.3.5
|
||||
@ -69,6 +69,7 @@ require (
|
||||
github.com/containerd/cgroups v1.0.3 // indirect
|
||||
github.com/containerd/containerd v1.6.8 // indirect
|
||||
github.com/containerd/continuity v0.3.0 // indirect
|
||||
github.com/creack/pty v1.1.11 // indirect
|
||||
github.com/dgraph-io/ristretto v0.1.0 // indirect
|
||||
github.com/distribution/distribution/v3 v3.0.0-20220725133111-4bf3547399eb // indirect
|
||||
github.com/docker/distribution v2.8.1+incompatible // indirect
|
||||
|
4
go.sum
4
go.sum
@ -314,6 +314,7 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:ma
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw=
|
||||
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4=
|
||||
github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
|
||||
@ -709,8 +710,9 @@ github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfn
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/pty v1.1.5 h1:hyz3dwM5QLc1Rfoz4FuWJQG5BN7tc6K1MndAUnGpQr4=
|
||||
github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
|
||||
github.com/kr/pty v1.1.8 h1:AkaSdXYQOWeaO3neb8EM634ahkXXe3jYbVh/F9lq+GI=
|
||||
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
|
Loading…
x
Reference in New Issue
Block a user