diff --git a/backend/app/api/v1/auth.go b/backend/app/api/v1/auth.go index 0fa084f31..0d387a3a8 100644 --- a/backend/app/api/v1/auth.go +++ b/backend/app/api/v1/auth.go @@ -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) +} diff --git a/backend/app/api/v1/entry.go b/backend/app/api/v1/entry.go index a7e59d34e..6810e2398 100644 --- a/backend/app/api/v1/entry.go +++ b/backend/app/api/v1/entry.go @@ -32,12 +32,13 @@ var ( settingService = service.ServiceGroupApp.SettingService backupService = service.ServiceGroupApp.BackupService - operationService = service.ServiceGroupApp.OperationService - commandService = service.ServiceGroupApp.CommandService + commandService = service.ServiceGroupApp.CommandService websiteGroupService = service.ServiceGroupApp.WebsiteGroupService websiteService = service.ServiceGroupApp.WebsiteService websiteDnsAccountService = service.ServiceGroupApp.WebSiteDnsAccountService websiteSSLService = service.ServiceGroupApp.WebSiteSSLService websiteAcmeAccountService = service.ServiceGroupApp.WebSiteAcmeAccountService + + logService = service.ServiceGroupApp.LogService ) diff --git a/backend/app/api/v1/operation_log.go b/backend/app/api/v1/logs.go similarity index 64% rename from backend/app/api/v1/operation_log.go rename to backend/app/api/v1/logs.go index bbec8ad1f..48b0eaeb2 100644 --- a/backend/app/api/v1/operation_log.go +++ b/backend/app/api/v1/logs.go @@ -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, + }) } diff --git a/backend/app/dto/operation_log.go b/backend/app/dto/logs.go similarity index 64% rename from backend/app/dto/operation_log.go rename to backend/app/dto/logs.go index 9e113c1a3..a7664e4da 100644 --- a/backend/app/dto/operation_log.go +++ b/backend/app/dto/logs.go @@ -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"` +} diff --git a/backend/app/model/operate_log.go b/backend/app/model/logs.go similarity index 68% rename from backend/app/model/operate_log.go rename to backend/app/model/logs.go index 7683b8a4f..de0378af0 100644 --- a/backend/app/model/operate_log.go +++ b/backend/app/model/logs.go @@ -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"` +} diff --git a/backend/app/repo/entry.go b/backend/app/repo/entry.go index 0134e3f66..6af031b57 100644 --- a/backend/app/repo/entry.go +++ b/backend/app/repo/entry.go @@ -19,13 +19,13 @@ type RepoGroup struct { GroupRepo SettingRepo BackupRepo - OperationRepo WebSiteRepo WebSiteDomainRepo WebSiteGroupRepo WebsiteDnsAccountRepo WebsiteSSLRepo WebsiteAcmeAccountRepo + LogRepo } var RepoGroupApp = new(RepoGroup) diff --git a/backend/app/repo/logs.go b/backend/app/repo/logs.go new file mode 100644 index 000000000..46ef67ebd --- /dev/null +++ b/backend/app/repo/logs.go @@ -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 +} diff --git a/backend/app/repo/operation_log.go b/backend/app/repo/operation_log.go deleted file mode 100644 index e5629e9d7..000000000 --- a/backend/app/repo/operation_log.go +++ /dev/null @@ -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 -} diff --git a/backend/app/service/entry.go b/backend/app/service/entry.go index c0e50aaec..abd35f9e1 100644 --- a/backend/app/service/entry.go +++ b/backend/app/service/entry.go @@ -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 ) diff --git a/backend/app/service/image_repo.go b/backend/app/service/image_repo.go index 633022410..b991b6cdf 100644 --- a/backend/app/service/image_repo.go +++ b/backend/app/service/image_repo.go @@ -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 } diff --git a/backend/app/service/logs.go b/backend/app/service/logs.go new file mode 100644 index 000000000..6b19b640e --- /dev/null +++ b/backend/app/service/logs.go @@ -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) +} diff --git a/backend/app/service/operation_log.go b/backend/app/service/operation_log.go deleted file mode 100644 index 7c5fd0947..000000000 --- a/backend/app/service/operation_log.go +++ /dev/null @@ -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) -} diff --git a/backend/init/migration/migrate.go b/backend/init/migration/migrate.go index 89fc76b44..f1b26160c 100644 --- a/backend/init/migration/migrate.go +++ b/backend/init/migration/migrate.go @@ -19,6 +19,7 @@ func Init() { migrations.AddTableImageRepo, migrations.AddTableWebsite, migrations.AddTableDatabaseMysql, + migrations.AddTableLoginLog, }) if err := m.Migrate(); err != nil { global.LOG.Error(err) diff --git a/backend/init/migration/migrations/init.go b/backend/init/migration/migrations/init.go index 797d28dba..fde3654ed 100644 --- a/backend/init/migration/migrations/init.go +++ b/backend/init/migration/migrations/init.go @@ -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{}) }, } diff --git a/backend/init/router/router.go b/backend/init/router/router.go index bd4d9967a..e913ec6fc 100644 --- a/backend/init/router/router.go +++ b/backend/init/router/router.go @@ -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) diff --git a/backend/middleware/operation.go b/backend/middleware/operation.go index 2060a9222..608b7e2e1 100644 --- a/backend/middleware/operation.go +++ b/backend/middleware/operation.go @@ -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) } } diff --git a/backend/router/entry.go b/backend/router/entry.go index 333debe7a..ae033ab12 100644 --- a/backend/router/entry.go +++ b/backend/router/entry.go @@ -8,7 +8,7 @@ type RouterGroup struct { ContainerRouter CommandRouter MonitorRouter - OperationLogRouter + LogRouter FileRouter TerminalRouter CronjobRouter diff --git a/backend/router/ro_base.go b/backend/router/ro_base.go index a18aba3af..b85a52569 100644 --- a/backend/router/ro_base.go +++ b/backend/router/ro_base.go @@ -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) } } diff --git a/backend/router/ro_operation_log.go b/backend/router/ro_log.go similarity index 53% rename from backend/router/ro_operation_log.go rename to backend/router/ro_log.go index 5ce95174c..236b2b68a 100644 --- a/backend/router/ro_operation_log.go +++ b/backend/router/ro_log.go @@ -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) } } diff --git a/backend/utils/qqwry/qqwry.go b/backend/utils/qqwry/qqwry.go new file mode 100644 index 000000000..9ec412bfa --- /dev/null +++ b/backend/utils/qqwry/qqwry.go @@ -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 +} diff --git a/frontend/src/api/interface/log.ts b/frontend/src/api/interface/log.ts new file mode 100644 index 000000000..3352ecbde --- /dev/null +++ b/frontend/src/api/interface/log.ts @@ -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; + } +} diff --git a/frontend/src/api/interface/operation-log.ts b/frontend/src/api/interface/operation-log.ts deleted file mode 100644 index 8fd03851c..000000000 --- a/frontend/src/api/interface/operation-log.ts +++ /dev/null @@ -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; -} diff --git a/frontend/src/api/modules/log.ts b/frontend/src/api/modules/log.ts new file mode 100644 index 000000000..9167e99d2 --- /dev/null +++ b/frontend/src/api/modules/log.ts @@ -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>(`/logs/operation`, info); +}; + +export const getLoginLogs = (info: ReqPage) => { + return http.post>(`/logs/login`, info); +}; diff --git a/frontend/src/api/modules/operation-log.ts b/frontend/src/api/modules/operation-log.ts deleted file mode 100644 index 539591916..000000000 --- a/frontend/src/api/modules/operation-log.ts +++ /dev/null @@ -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>(`/operations`, info); -}; diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index f1ece3c9e..4e90a360f 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -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', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index 8aa28a62c..5ed60b4c9 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -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: '删除', }, diff --git a/frontend/src/routers/modules/log.ts b/frontend/src/routers/modules/log.ts new file mode 100644 index 000000000..9b710015a --- /dev/null +++ b/frontend/src/routers/modules/log.ts @@ -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; diff --git a/frontend/src/routers/modules/operation-log.ts b/frontend/src/routers/modules/operation-log.ts deleted file mode 100644 index 704bd1ce3..000000000 --- a/frontend/src/routers/modules/operation-log.ts +++ /dev/null @@ -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; diff --git a/frontend/src/views/cronjob/record/index.vue b/frontend/src/views/cronjob/record/index.vue index 147996be6..675cdc0fd 100644 --- a/frontend/src/views/cronjob/record/index.vue +++ b/frontend/src/views/cronjob/record/index.vue @@ -332,7 +332,7 @@ const weekOptions = [ const timeRangeLoad = ref>([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)), diff --git a/frontend/src/views/host/terminal/command/index.vue b/frontend/src/views/host/terminal/command/index.vue index 3e9a8d208..6f6d3f59c 100644 --- a/frontend/src/views/host/terminal/command/index.vue +++ b/frontend/src/views/host/terminal/command/index.vue @@ -49,14 +49,11 @@ const data = ref(); const selects = ref([]); 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; const commandInfoRef = ref(); 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; }; diff --git a/frontend/src/views/log/index.vue b/frontend/src/views/log/index.vue new file mode 100644 index 000000000..847a6e700 --- /dev/null +++ b/frontend/src/views/log/index.vue @@ -0,0 +1,73 @@ + + + + + diff --git a/frontend/src/views/log/login/index.vue b/frontend/src/views/log/login/index.vue new file mode 100644 index 000000000..8925c2905 --- /dev/null +++ b/frontend/src/views/log/login/index.vue @@ -0,0 +1,69 @@ + + + + + diff --git a/frontend/src/views/log/operation/index.vue b/frontend/src/views/log/operation/index.vue new file mode 100644 index 000000000..aa97d8808 --- /dev/null +++ b/frontend/src/views/log/operation/index.vue @@ -0,0 +1,147 @@ + + + + + diff --git a/frontend/src/views/log/system/index.vue b/frontend/src/views/log/system/index.vue new file mode 100644 index 000000000..f1250aec9 --- /dev/null +++ b/frontend/src/views/log/system/index.vue @@ -0,0 +1,42 @@ + + + diff --git a/frontend/src/views/operation-log/index.vue b/frontend/src/views/operation-log/index.vue deleted file mode 100644 index f84b0ddbe..000000000 --- a/frontend/src/views/operation-log/index.vue +++ /dev/null @@ -1,141 +0,0 @@ - - - - - diff --git a/go.mod b/go.mod index 4a380e10b..d245a0b5f 100644 --- a/go.mod +++ b/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 diff --git a/go.sum b/go.sum index e1a605175..85be267b4 100644 --- a/go.sum +++ b/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=