1
0
mirror of https://github.com/1Panel-dev/1Panel.git synced 2025-01-31 14:08:06 +08:00

feat: 完成面板日志功能

This commit is contained in:
ssongliu 2022-11-15 17:20:57 +08:00 committed by ssongliu
parent 0dfb9bd5c7
commit 3a3670f660
37 changed files with 838 additions and 358 deletions

View File

@ -5,10 +5,12 @@ import (
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper" "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/dto"
"github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/constant" "github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global" "github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/captcha" "github.com/1Panel-dev/1Panel/backend/utils/captcha"
"github.com/1Panel-dev/1Panel/backend/utils/encrypt" "github.com/1Panel-dev/1Panel/backend/utils/encrypt"
"github.com/1Panel-dev/1Panel/backend/utils/qqwry"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
@ -30,6 +32,7 @@ func (b *BaseApi) Login(c *gin.Context) {
} }
user, err := authService.Login(c, req) user, err := authService.Login(c, req)
go saveLoginLogs(c, err)
if err != nil { if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return return
@ -102,3 +105,22 @@ func (b *BaseApi) SafeEntrance(c *gin.Context) {
helper.SuccessWithData(c, nil) 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)
}

View File

@ -32,12 +32,13 @@ var (
settingService = service.ServiceGroupApp.SettingService settingService = service.ServiceGroupApp.SettingService
backupService = service.ServiceGroupApp.BackupService backupService = service.ServiceGroupApp.BackupService
operationService = service.ServiceGroupApp.OperationService commandService = service.ServiceGroupApp.CommandService
commandService = service.ServiceGroupApp.CommandService
websiteGroupService = service.ServiceGroupApp.WebsiteGroupService websiteGroupService = service.ServiceGroupApp.WebsiteGroupService
websiteService = service.ServiceGroupApp.WebsiteService websiteService = service.ServiceGroupApp.WebsiteService
websiteDnsAccountService = service.ServiceGroupApp.WebSiteDnsAccountService websiteDnsAccountService = service.ServiceGroupApp.WebSiteDnsAccountService
websiteSSLService = service.ServiceGroupApp.WebSiteSSLService websiteSSLService = service.ServiceGroupApp.WebSiteSSLService
websiteAcmeAccountService = service.ServiceGroupApp.WebSiteAcmeAccountService websiteAcmeAccountService = service.ServiceGroupApp.WebSiteAcmeAccountService
logService = service.ServiceGroupApp.LogService
) )

View File

@ -4,18 +4,17 @@ import (
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper" "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/dto"
"github.com/1Panel-dev/1Panel/backend/constant" "github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
func (b *BaseApi) GetOperationList(c *gin.Context) { func (b *BaseApi) GetLoginLogs(c *gin.Context) {
var req dto.PageInfo var req dto.PageInfo
if err := c.ShouldBindJSON(&req); err != nil { if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return return
} }
total, list, err := operationService.Page(req) total, list, err := logService.PageLoginLog(req)
if err != nil { if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return return
@ -27,20 +26,21 @@ func (b *BaseApi) GetOperationList(c *gin.Context) {
}) })
} }
func (b *BaseApi) DeleteOperation(c *gin.Context) { func (b *BaseApi) GetOperationLogs(c *gin.Context) {
var req dto.BatchDeleteReq var req dto.PageInfo
if err := c.ShouldBindJSON(&req); err != nil { if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return 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) helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return return
} }
helper.SuccessWithData(c, nil)
helper.SuccessWithData(c, dto.PageResult{
Items: list,
Total: total,
})
} }

View File

@ -4,7 +4,7 @@ import (
"time" "time"
) )
type OperationLogBack struct { type OperationLog struct {
ID uint `json:"id"` ID uint `json:"id"`
Group string `json:"group"` Group string `json:"group"`
Source string `json:"source"` Source string `json:"source"`
@ -24,3 +24,13 @@ type OperationLogBack struct {
Detail string `json:"detail"` Detail string `json:"detail"`
CreatedAt time.Time `json:"createdAt"` 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"`
}

View File

@ -21,3 +21,12 @@ type OperationLog struct {
Detail string `gorm:"type:longText" json:"detail"` 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"`
}

View File

@ -19,13 +19,13 @@ type RepoGroup struct {
GroupRepo GroupRepo
SettingRepo SettingRepo
BackupRepo BackupRepo
OperationRepo
WebSiteRepo WebSiteRepo
WebSiteDomainRepo WebSiteDomainRepo
WebSiteGroupRepo WebSiteGroupRepo
WebsiteDnsAccountRepo WebsiteDnsAccountRepo
WebsiteSSLRepo WebsiteSSLRepo
WebsiteAcmeAccountRepo WebsiteAcmeAccountRepo
LogRepo
} }
var RepoGroupApp = new(RepoGroup) var RepoGroupApp = new(RepoGroup)

52
backend/app/repo/logs.go Normal file
View 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
}

View File

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

View File

@ -27,12 +27,13 @@ type ServiceGroup struct {
SettingService SettingService
BackupService BackupService
OperationService
WebsiteGroupService WebsiteGroupService
WebsiteService WebsiteService
WebSiteDnsAccountService WebSiteDnsAccountService
WebSiteSSLService WebSiteSSLService
WebSiteAcmeAccountService WebSiteAcmeAccountService
LogService
} }
var ServiceGroupApp = new(ServiceGroup) var ServiceGroupApp = new(ServiceGroup)
@ -63,11 +64,12 @@ var (
settingRepo = repo.RepoGroupApp.SettingRepo settingRepo = repo.RepoGroupApp.SettingRepo
backupRepo = repo.RepoGroupApp.BackupRepo backupRepo = repo.RepoGroupApp.BackupRepo
operationRepo = repo.RepoGroupApp.OperationRepo
websiteRepo = repo.RepoGroupApp.WebSiteRepo websiteRepo = repo.RepoGroupApp.WebSiteRepo
websiteGroupRepo = repo.RepoGroupApp.WebSiteGroupRepo websiteGroupRepo = repo.RepoGroupApp.WebSiteGroupRepo
websiteDomainRepo = repo.RepoGroupApp.WebSiteDomainRepo websiteDomainRepo = repo.RepoGroupApp.WebSiteDomainRepo
websiteDnsRepo = repo.RepoGroupApp.WebsiteDnsAccountRepo websiteDnsRepo = repo.RepoGroupApp.WebsiteDnsAccountRepo
websiteSSLRepo = repo.RepoGroupApp.WebsiteSSLRepo websiteSSLRepo = repo.RepoGroupApp.WebsiteSSLRepo
websiteAcmeRepo = repo.RepoGroupApp.WebsiteAcmeAccountRepo websiteAcmeRepo = repo.RepoGroupApp.WebsiteAcmeAccountRepo
logRepo = repo.RepoGroupApp.LogRepo
) )

View File

@ -71,7 +71,7 @@ func (u *ImageRepoService) Create(imageRepoDto dto.ImageRepoCreate) error {
deamonMap["insecure-registries"] = k deamonMap["insecure-registries"] = k
} }
} }
newJson, err := json.Marshal(deamonMap) newJson, err := json.MarshalIndent(deamonMap, "", "\t")
if err != nil { if err != nil {
return err return err
} }

View 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)
}

View File

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

View File

@ -19,6 +19,7 @@ func Init() {
migrations.AddTableImageRepo, migrations.AddTableImageRepo,
migrations.AddTableWebsite, migrations.AddTableWebsite,
migrations.AddTableDatabaseMysql, migrations.AddTableDatabaseMysql,
migrations.AddTableLoginLog,
}) })
if err := m.Migrate(); err != nil { if err := m.Migrate(); err != nil {
global.LOG.Error(err) global.LOG.Error(err)

View File

@ -12,7 +12,7 @@ import (
var AddTableOperationLog = &gormigrate.Migration{ var AddTableOperationLog = &gormigrate.Migration{
ID: "20200809-add-table-operation-log", ID: "20200809-add-table-operation-log",
Migrate: func(tx *gorm.DB) error { Migrate: func(tx *gorm.DB) error {
return tx.AutoMigrate(&model.OperationLog{}) return tx.AutoMigrate(&model.OperationLog{}, &model.LoginLog{})
}, },
} }

View File

@ -74,7 +74,7 @@ func Routers() *gin.Engine {
systemRouter.InitCommandRouter(PrivateGroup) systemRouter.InitCommandRouter(PrivateGroup)
systemRouter.InitTerminalRouter(PrivateGroup) systemRouter.InitTerminalRouter(PrivateGroup)
systemRouter.InitMonitorRouter(PrivateGroup) systemRouter.InitMonitorRouter(PrivateGroup)
systemRouter.InitOperationLogRouter(PrivateGroup) systemRouter.InitLogRouter(PrivateGroup)
systemRouter.InitFileRouter(PrivateGroup) systemRouter.InitFileRouter(PrivateGroup)
systemRouter.InitCronjobRouter(PrivateGroup) systemRouter.InitCronjobRouter(PrivateGroup)
systemRouter.InitSettingRouter(PrivateGroup) systemRouter.InitSettingRouter(PrivateGroup)

View File

@ -70,7 +70,7 @@ func OperationRecord() gin.HandlerFunc {
record.Latency = latency record.Latency = latency
record.Resp = writer.body.String() 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) global.LOG.Errorf("create operation record failed, err: %v", err)
} }
} }

View File

@ -8,7 +8,7 @@ type RouterGroup struct {
ContainerRouter ContainerRouter
CommandRouter CommandRouter
MonitorRouter MonitorRouter
OperationLogRouter LogRouter
FileRouter FileRouter
TerminalRouter TerminalRouter
CronjobRouter CronjobRouter

View File

@ -2,7 +2,6 @@ package router
import ( import (
v1 "github.com/1Panel-dev/1Panel/backend/app/api/v1" v1 "github.com/1Panel-dev/1Panel/backend/app/api/v1"
"github.com/1Panel-dev/1Panel/backend/middleware"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
@ -10,12 +9,11 @@ type BaseRouter struct{}
func (s *BaseRouter) InitBaseRouter(Router *gin.RouterGroup) { func (s *BaseRouter) InitBaseRouter(Router *gin.RouterGroup) {
baseRouter := Router.Group("auth") baseRouter := Router.Group("auth")
withRecordRouter := Router.Group("auth").Use(middleware.OperationRecord())
baseApi := v1.ApiGroupApp.BaseApi baseApi := v1.ApiGroupApp.BaseApi
{ {
baseRouter.GET("captcha", baseApi.Captcha) baseRouter.GET("captcha", baseApi.Captcha)
baseRouter.POST("mfalogin", baseApi.MFALogin) baseRouter.POST("mfalogin", baseApi.MFALogin)
withRecordRouter.POST("login", baseApi.Login) baseRouter.POST("login", baseApi.Login)
withRecordRouter.POST("logout", baseApi.LogOut) baseRouter.POST("logout", baseApi.LogOut)
} }
} }

View File

@ -7,14 +7,14 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
type OperationLogRouter struct{} type LogRouter struct{}
func (s *OperationLogRouter) InitOperationLogRouter(Router *gin.RouterGroup) { func (s *LogRouter) InitLogRouter(Router *gin.RouterGroup) {
operationRouter := Router.Group("operations") operationRouter := Router.Group("logs")
operationRouter.Use(middleware.JwtAuth()).Use(middleware.SessionAuth()).Use(middleware.PasswordExpired()) operationRouter.Use(middleware.JwtAuth()).Use(middleware.SessionAuth()).Use(middleware.PasswordExpired())
baseApi := v1.ApiGroupApp.BaseApi baseApi := v1.ApiGroupApp.BaseApi
{ {
operationRouter.POST("", baseApi.GetOperationList) operationRouter.POST("login", baseApi.GetLoginLogs)
operationRouter.POST("/del", baseApi.DeleteOperation) operationRouter.POST("operation", baseApi.GetOperationLogs)
} }
} }

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

View 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;
}
}

View File

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

View 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);
};

View File

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

View File

@ -142,7 +142,7 @@ export default {
terminal: 'Terminal', terminal: 'Terminal',
settings: 'Setting', settings: 'Setting',
toolbox: 'Toolbox', toolbox: 'Toolbox',
operations: 'Operation Records', logs: 'Log',
}, },
home: { home: {
welcome: 'Welcome', welcome: 'Welcome',
@ -475,6 +475,13 @@ export default {
emptyTerminal: 'No terminal is currently connected', emptyTerminal: 'No terminal is currently connected',
}, },
operations: { operations: {
operation: 'Operation logs',
login: 'Login logs',
system: 'System logs',
loginIP: 'Login IP',
loginAddress: 'Login address',
loginAgent: 'Login agent',
loginStatus: 'Login status',
detail: { detail: {
users: 'User', users: 'User',
hosts: 'Host', hosts: 'Host',
@ -483,6 +490,7 @@ export default {
backups: 'Backup Account', backups: 'Backup Account',
settings: 'Panel Setting', settings: 'Panel Setting',
cronjobs: 'Cronjob', cronjobs: 'Cronjob',
databases: 'Database',
status: ' Update status', status: ' Update status',
auth: 'User', auth: 'User',
login: ' login', login: ' login',
@ -493,7 +501,6 @@ export default {
delete: ' delete', delete: ' delete',
del: 'delete', del: 'delete',
}, },
operatoin: 'operatoin',
status: 'status', status: 'status',
request: 'request', request: 'request',
response: 'response', response: 'response',

View File

@ -142,7 +142,7 @@ export default {
terminal: '终端', terminal: '终端',
settings: '面板设置', settings: '面板设置',
toolbox: '工具箱', toolbox: '工具箱',
operations: '操作日志', logs: '面板日志',
}, },
home: { home: {
welcome: '欢迎使用', welcome: '欢迎使用',
@ -487,7 +487,14 @@ export default {
key: '密钥', key: '密钥',
emptyTerminal: '暂无终端连接', emptyTerminal: '暂无终端连接',
}, },
operations: { logs: {
operation: '操作日志',
login: '登录日志',
loginIP: '登录 IP',
loginAddress: '登录地址',
loginAgent: '用户代理',
loginStatus: '登录状态',
system: '系统日志',
detail: { detail: {
users: '用户', users: '用户',
hosts: '主机', hosts: '主机',
@ -496,6 +503,7 @@ export default {
backups: '备份账号', backups: '备份账号',
settings: '面板设置', settings: '面板设置',
cronjobs: '计划任务', cronjobs: '计划任务',
databases: '数据库',
status: '状态修改', status: '状态修改',
auth: '用户', auth: '用户',
post: '创建', post: '创建',
@ -503,6 +511,8 @@ export default {
update: '更新', update: '更新',
delete: '删除', delete: '删除',
login: '登录', login: '登录',
backup: '备份',
recover: '恢复',
logout: '退出', logout: '退出',
del: '删除', del: '删除',
}, },

View 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;

View File

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

View File

@ -332,7 +332,7 @@ const weekOptions = [
const timeRangeLoad = ref<Array<any>>([new Date(new Date().setHours(0, 0, 0, 0)), new Date()]); const timeRangeLoad = ref<Array<any>>([new Date(new Date().setHours(0, 0, 0, 0)), new Date()]);
const searchInfo = reactive({ const searchInfo = reactive({
page: 1, page: 1,
pageSize: 5, pageSize: 10,
recordTotal: 0, recordTotal: 0,
cronjobID: 0, cronjobID: 0,
startTime: new Date(new Date().setHours(0, 0, 0, 0)), startTime: new Date(new Date().setHours(0, 0, 0, 0)),

View File

@ -49,14 +49,11 @@ const data = ref();
const selects = ref<any>([]); const selects = ref<any>([]);
const paginationConfig = reactive({ const paginationConfig = reactive({
page: 1, page: 1,
pageSize: 5, pageSize: 10,
total: 0, total: 0,
}); });
const commandSearch = reactive({ const info = ref();
page: 1,
pageSize: 5,
info: '',
});
type FormInstance = InstanceType<typeof ElForm>; type FormInstance = InstanceType<typeof ElForm>;
const commandInfoRef = ref<FormInstance>(); const commandInfoRef = ref<FormInstance>();
const rules = reactive({ const rules = reactive({
@ -136,9 +133,12 @@ const buttons = [
]; ];
const search = async () => { const search = async () => {
commandSearch.page = paginationConfig.page; let params = {
commandSearch.pageSize = paginationConfig.pageSize; page: paginationConfig.page,
const res = await getCommandPage(commandSearch); pageSize: paginationConfig.pageSize,
info: info.value,
};
const res = await getCommandPage(params);
data.value = res.data.items; data.value = res.data.items;
paginationConfig.total = res.data.total; paginationConfig.total = res.data.total;
}; };

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

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

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

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

View File

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

@ -25,7 +25,7 @@ require (
github.com/gwatts/gin-adapter v1.0.0 github.com/gwatts/gin-adapter v1.0.0
github.com/jinzhu/copier v0.3.5 github.com/jinzhu/copier v0.3.5
github.com/joho/godotenv v1.4.0 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/mholt/archiver/v4 v4.0.0-alpha.7
github.com/minio/minio-go/v7 v7.0.36 github.com/minio/minio-go/v7 v7.0.36
github.com/mojocn/base64Captcha v1.3.5 github.com/mojocn/base64Captcha v1.3.5
@ -69,6 +69,7 @@ require (
github.com/containerd/cgroups v1.0.3 // indirect github.com/containerd/cgroups v1.0.3 // indirect
github.com/containerd/containerd v1.6.8 // indirect github.com/containerd/containerd v1.6.8 // indirect
github.com/containerd/continuity v0.3.0 // 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/dgraph-io/ristretto v0.1.0 // indirect
github.com/distribution/distribution/v3 v3.0.0-20220725133111-4bf3547399eb // indirect github.com/distribution/distribution/v3 v3.0.0-20220725133111-4bf3547399eb // indirect
github.com/docker/distribution v2.8.1+incompatible // indirect github.com/docker/distribution v2.8.1+incompatible // indirect

4
go.sum
View File

@ -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/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.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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/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.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4=
github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= 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 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= 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.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.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.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 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=