diff --git a/agent/app/api/v2/ssh.go b/agent/app/api/v2/ssh.go index acd271b37..8d413e9d3 100644 --- a/agent/app/api/v2/ssh.go +++ b/agent/app/api/v2/ssh.go @@ -2,6 +2,10 @@ package v2 import ( "encoding/base64" + "net/http" + "net/url" + "os" + "strconv" "github.com/1Panel-dev/1Panel/agent/app/api/v2/helper" "github.com/1Panel-dev/1Panel/agent/app/dto" @@ -201,7 +205,7 @@ func (b *BaseApi) DeleteRootCert(c *gin.Context) { // @Summary Load host SSH logs // @Accept json // @Param request body dto.SearchSSHLog true "request" -// @Success 200 {object} dto.SSHLog +// @Success 200 {object} dto.PageResult // @Security ApiKeyAuth // @Security Timestamp // @Router /hosts/ssh/log [post] @@ -211,12 +215,49 @@ func (b *BaseApi) LoadSSHLogs(c *gin.Context) { return } - data, err := sshService.LoadLog(c, req) + total, data, err := sshService.LoadLog(c, req) if err != nil { helper.InternalServer(c, err) return } - helper.SuccessWithData(c, data) + + helper.SuccessWithData(c, dto.PageResult{ + Total: total, + Items: data, + }) +} + +// @Tags SSH +// @Summary Export host SSH logs +// @Accept json +// @Param request body dto.SearchSSHLog true "request" +// @Success 200 {object} dto.PageResult +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /hosts/ssh/log/export [post] +func (b *BaseApi) ExportSSHLogs(c *gin.Context) { + var req dto.SearchSSHLog + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + tmpFile, err := sshService.ExportLog(c, req) + if err != nil { + helper.InternalServer(c, err) + return + } + file, err := os.Open(tmpFile) + if err != nil { + helper.InternalServer(c, err) + return + } + defer func() { + _ = file.Close() + _ = os.RemoveAll(tmpFile) + }() + info, _ := file.Stat() + c.Header("Content-Length", strconv.FormatInt(info.Size(), 10)) + c.Header("Content-Disposition", "attachment; filename*=utf-8''"+url.PathEscape(info.Name())) + http.ServeContent(c.Writer, c.Request, info.Name(), info.ModTime(), file) } // @Tags SSH diff --git a/agent/app/dto/ssh.go b/agent/app/dto/ssh.go index 7a5018117..2b977135c 100644 --- a/agent/app/dto/ssh.go +++ b/agent/app/dto/ssh.go @@ -54,12 +54,6 @@ type SearchSSHLog struct { Info string `json:"info"` Status string `json:"Status" validate:"required,oneof=Success Failed All"` } -type SSHLog struct { - Logs []SSHHistory `json:"logs"` - TotalCount int `json:"totalCount"` - SuccessfulCount int `json:"successfulCount"` - FailedCount int `json:"failedCount"` -} type SSHHistory struct { Date time.Time `json:"date"` diff --git a/agent/app/service/ssh.go b/agent/app/service/ssh.go index ee0f9a4b1..c9d2ff11c 100644 --- a/agent/app/service/ssh.go +++ b/agent/app/service/ssh.go @@ -13,6 +13,7 @@ import ( "time" "github.com/1Panel-dev/1Panel/agent/utils/copier" + csvexport "github.com/1Panel-dev/1Panel/agent/utils/csv_export" "github.com/1Panel-dev/1Panel/agent/utils/encrypt" "github.com/1Panel-dev/1Panel/agent/utils/geo" "github.com/gin-gonic/gin" @@ -38,9 +39,11 @@ type ISSHService interface { OperateSSH(operation string) error UpdateByFile(value string) error Update(req dto.SSHUpdate) error - LoadLog(ctx *gin.Context, req dto.SearchSSHLog) (*dto.SSHLog, error) LoadSSHConf() (string, error) + LoadLog(ctx *gin.Context, req dto.SearchSSHLog) (int64, []dto.SSHHistory, error) + ExportLog(ctx *gin.Context, req dto.SearchSSHLog) (string, error) + SyncRootCert() error CreateRootCert(req dto.CreateRootCert) error SearchRootCerts(req dto.SearchWithPage) (int64, interface{}, error) @@ -392,13 +395,13 @@ type sshFileItem struct { Year int } -func (u *SSHService) LoadLog(ctx *gin.Context, req dto.SearchSSHLog) (*dto.SSHLog, error) { +func (u *SSHService) LoadLog(ctx *gin.Context, req dto.SearchSSHLog) (int64, []dto.SSHHistory, error) { var fileList []sshFileItem - var data dto.SSHLog + var data []dto.SSHHistory baseDir := "/var/log" fileItems, err := os.ReadDir(baseDir) if err != nil { - return &data, err + return 0, data, err } for _, item := range fileItems { if item.IsDir() || (!strings.HasPrefix(item.Name(), "secure") && !strings.HasPrefix(item.Name(), "auth")) { @@ -427,6 +430,7 @@ func (u *SSHService) LoadLog(ctx *gin.Context, req dto.SearchSSHLog) (*dto.SSHLo showCountFrom := (req.Page - 1) * req.PageSize showCountTo := req.Page * req.PageSize nyc, _ := time.LoadLocation(common.LoadTimeZoneByCmd()) + itemFailed, itemTotal := 0, 0 for _, file := range fileList { commandItem := "" if strings.HasPrefix(path.Base(file.Name), "secure") { @@ -450,15 +454,38 @@ func (u *SSHService) LoadLog(ctx *gin.Context, req dto.SearchSSHLog) (*dto.SSHLo } } dataItem, successCount, failedCount := loadSSHData(ctx, commandItem, showCountFrom, showCountTo, file.Year, nyc) - data.FailedCount += failedCount - data.TotalCount += successCount + failedCount + itemFailed += failedCount + itemTotal += successCount + failedCount showCountFrom = showCountFrom - (successCount + failedCount) - showCountTo = showCountTo - (successCount + failedCount) - data.Logs = append(data.Logs, dataItem...) + if showCountTo != -1 { + showCountTo = showCountTo - (successCount + failedCount) + } + data = append(data, dataItem...) } - data.SuccessfulCount = data.TotalCount - data.FailedCount - return &data, nil + total := itemTotal + if req.Status == constant.StatusFailed { + total = itemFailed + } + if req.Status == constant.StatusSuccess { + total = itemTotal - itemFailed + } + return int64(total), data, nil +} + +func (u *SSHService) ExportLog(ctx *gin.Context, req dto.SearchSSHLog) (string, error) { + _, logs, err := u.LoadLog(ctx, req) + if err != nil { + return "", err + } + tmpFileName := path.Join(global.Dir.TmpDir, "export/ssh-log", fmt.Sprintf("1panel-ssh-log-%s.csv", time.Now().Format(constant.DateTimeSlimLayout))) + if _, err := os.Stat(path.Dir(tmpFileName)); err != nil { + _ = os.MkdirAll(path.Dir(tmpFileName), constant.DirPerm) + } + if err := csvexport.ExportSSHLogs(tmpFileName, logs); err != nil { + return "", err + } + return tmpFileName, nil } func (u *SSHService) LoadSSHConf() (string, error) { @@ -543,7 +570,7 @@ func loadSSHData(ctx *gin.Context, command string, showCountFrom, showCountTo, c case strings.Contains(lines[i], "Failed password for"): itemData = loadFailedSecureDatas(lines[i]) if checkIsStandard(itemData) { - if successCount+failedCount >= showCountFrom && successCount+failedCount < showCountTo { + if successCount+failedCount >= showCountFrom && (showCountTo == -1 || successCount+failedCount < showCountTo) { itemData.Area, _ = geo.GetIPLocation(getLoc, itemData.Address, common.GetLang(ctx)) itemData.Date = loadDate(currentYear, itemData.DateStr, nyc) datas = append(datas, itemData) @@ -553,7 +580,7 @@ func loadSSHData(ctx *gin.Context, command string, showCountFrom, showCountTo, c case strings.Contains(lines[i], "Connection closed by authenticating user"): itemData = loadFailedAuthDatas(lines[i]) if checkIsStandard(itemData) { - if successCount+failedCount >= showCountFrom && successCount+failedCount < showCountTo { + if successCount+failedCount >= showCountFrom && (showCountTo == -1 || successCount+failedCount < showCountTo) { itemData.Area, _ = geo.GetIPLocation(getLoc, itemData.Address, common.GetLang(ctx)) itemData.Date = loadDate(currentYear, itemData.DateStr, nyc) datas = append(datas, itemData) @@ -563,7 +590,7 @@ func loadSSHData(ctx *gin.Context, command string, showCountFrom, showCountTo, c case strings.Contains(lines[i], "Accepted "): itemData = loadSuccessDatas(lines[i]) if checkIsStandard(itemData) { - if successCount+failedCount >= showCountFrom && successCount+failedCount < showCountTo { + if successCount+failedCount >= showCountFrom && (showCountTo == -1 || successCount+failedCount < showCountTo) { itemData.Area, _ = geo.GetIPLocation(getLoc, itemData.Address, common.GetLang(ctx)) itemData.Date = loadDate(currentYear, itemData.DateStr, nyc) datas = append(datas, itemData) diff --git a/agent/router/ro_host.go b/agent/router/ro_host.go index 47a3763ef..b70a21f41 100644 --- a/agent/router/ro_host.go +++ b/agent/router/ro_host.go @@ -33,6 +33,7 @@ func (s *HostRouter) InitRouter(Router *gin.RouterGroup) { hostRouter.POST("/ssh/search", baseApi.GetSSHInfo) hostRouter.POST("/ssh/update", baseApi.UpdateSSH) hostRouter.POST("/ssh/log", baseApi.LoadSSHLogs) + hostRouter.POST("/ssh/log/export", baseApi.ExportSSHLogs) hostRouter.POST("/ssh/conffile/update", baseApi.UpdateSSHByfile) hostRouter.POST("/ssh/operate", baseApi.OperateSSH) diff --git a/agent/utils/csv_export/ssh_log.go b/agent/utils/csv_export/ssh_log.go new file mode 100644 index 000000000..6751fca26 --- /dev/null +++ b/agent/utils/csv_export/ssh_log.go @@ -0,0 +1,41 @@ +package csvexport + +import ( + "encoding/csv" + "os" + + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/constant" +) + +func ExportSSHLogs(filename string, logs []dto.SSHHistory) error { + file, err := os.Create(filename) + if err != nil { + return err + } + defer file.Close() + + writer := csv.NewWriter(file) + defer writer.Flush() + + if err := writer.Write([]string{"IP", "Area", "Port", "AuthMode", "User", "Status", "Date"}); err != nil { + return err + } + + for _, log := range logs { + record := []string{ + log.Address, + log.Area, + log.Port, + log.AuthMode, + log.User, + log.Status, + log.Date.Format(constant.DateTimeLayout), + } + if err := writer.Write(record); err != nil { + return err + } + } + + return nil +} diff --git a/frontend/src/api/interface/host.ts b/frontend/src/api/interface/host.ts index 78ce46000..b11646189 100644 --- a/frontend/src/api/interface/host.ts +++ b/frontend/src/api/interface/host.ts @@ -194,24 +194,6 @@ export namespace Host { export interface analysisSSHLog extends ReqPage { orderBy: string; } - export interface logAnalysisRes { - total: number; - items: Array; - successfulCount: number; - failedCount: number; - } - export interface sshLog { - logs: Array; - successfulCount: number; - failedCount: number; - } - export interface logAnalysis { - address: string; - area: string; - successfulCount: number; - failedCount: number; - status: string; - } export interface sshHistory { date: Date; area: string; diff --git a/frontend/src/api/modules/host.ts b/frontend/src/api/modules/host.ts index 8ffc1927b..2f4b55c65 100644 --- a/frontend/src/api/modules/host.ts +++ b/frontend/src/api/modules/host.ts @@ -96,5 +96,11 @@ export const syncCert = () => { return http.post(`/hosts/ssh/cert/sync`); }; export const loadSSHLogs = (params: Host.searchSSHLog) => { - return http.post(`/hosts/ssh/log`, params); + return http.post>(`/hosts/ssh/log`, params); +}; +export const exportSSHLogs = (params: Host.searchSSHLog) => { + return http.download('/hosts/ssh/log/export', params, { + responseType: 'blob', + timeout: TimeoutEnum.T_40S, + }); }; diff --git a/frontend/src/views/host/ssh/log/log.vue b/frontend/src/views/host/ssh/log/log.vue index a8039cf24..1aada499d 100644 --- a/frontend/src/views/host/ssh/log/log.vue +++ b/frontend/src/views/host/ssh/log/log.vue @@ -5,6 +5,11 @@
+ + + + + + + + + + + + + + + + + + + + + + +