From 017eb3814b55518d1ce9208a48d2918371052f06 Mon Sep 17 00:00:00 2001 From: ssongliu <73214554+ssongliu@users.noreply.github.com> Date: Wed, 20 Sep 2023 14:18:20 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20ssh=20=E7=99=BB=E5=BD=95=E6=97=A5?= =?UTF-8?q?=E5=BF=97=E5=A2=9E=E5=8A=A0=E6=A6=82=E8=A7=88=E6=98=BE=E7=A4=BA?= =?UTF-8?q?=20(#2347)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/api/v1/ssh.go | 53 +++- backend/app/dto/ssh.go | 13 + backend/app/service/firewall.go | 18 ++ backend/app/service/ssh.go | 106 ++++++++ backend/constant/errs.go | 4 + backend/i18n/lang/en.yaml | 5 +- backend/i18n/lang/zh-Hant.yaml | 3 + backend/i18n/lang/zh.yaml | 3 + backend/router/ro_host.go | 1 + backend/utils/firewall/client.go | 5 +- cmd/server/docs/docs.go | 109 +++++++- cmd/server/docs/swagger.json | 109 +++++++- cmd/server/docs/swagger.yaml | 79 +++++- frontend/src/api/interface/host.ts | 7 + frontend/src/api/modules/host.ts | 3 + frontend/src/lang/modules/en.ts | 12 +- frontend/src/lang/modules/tw.ts | 8 + frontend/src/lang/modules/zh.ts | 8 + .../src/views/host/ssh/log/analysis/index.vue | 244 ++++++++++++++++++ frontend/src/views/host/ssh/log/log.vue | 70 ++--- .../setting/panel/default-network/index.vue | 1 - 21 files changed, 756 insertions(+), 105 deletions(-) create mode 100644 frontend/src/views/host/ssh/log/analysis/index.vue diff --git a/backend/app/api/v1/ssh.go b/backend/app/api/v1/ssh.go index 644216dd2..e724bdb44 100644 --- a/backend/app/api/v1/ssh.go +++ b/backend/app/api/v1/ssh.go @@ -9,7 +9,7 @@ import ( ) // @Tags SSH -// @Summary Load host ssh setting info +// @Summary Load host SSH setting info // @Description 加载 SSH 配置信息 // @Success 200 {object} dto.SSHInfo // @Security ApiKeyAuth @@ -24,7 +24,7 @@ func (b *BaseApi) GetSSHInfo(c *gin.Context) { } // @Tags SSH -// @Summary Operate ssh +// @Summary Operate SSH // @Description 修改 SSH 服务状态 // @Accept json // @Param request body dto.Operate true "request" @@ -50,7 +50,7 @@ func (b *BaseApi) OperateSSH(c *gin.Context) { } // @Tags SSH -// @Summary Update host ssh setting +// @Summary Update host SSH setting // @Description 更新 SSH 配置 // @Accept json // @Param request body dto.SettingUpdate true "request" @@ -77,7 +77,7 @@ func (b *BaseApi) UpdateSSH(c *gin.Context) { } // @Tags SSH -// @Summary Update host ssh setting by file +// @Summary Update host SSH setting by file // @Description 上传文件更新 SSH 配置 // @Accept json // @Param request body dto.SSHConf true "request" @@ -104,8 +104,8 @@ func (b *BaseApi) UpdateSSHByfile(c *gin.Context) { } // @Tags SSH -// @Summary Generate host ssh secret -// @Description 生成 ssh 密钥 +// @Summary Generate host SSH secret +// @Description 生成 SSH 密钥 // @Accept json // @Param request body dto.GenerateSSH true "request" // @Success 200 @@ -131,8 +131,8 @@ func (b *BaseApi) GenerateSSH(c *gin.Context) { } // @Tags SSH -// @Summary Load host ssh secret -// @Description 获取 ssh 密钥 +// @Summary Load host SSH secret +// @Description 获取 SSH 密钥 // @Accept json // @Param request body dto.GenerateLoad true "request" // @Success 200 @@ -158,13 +158,40 @@ func (b *BaseApi) LoadSSHSecret(c *gin.Context) { } // @Tags SSH -// @Summary Load host ssh logs -// @Description 获取 ssh 登录日志 +// @Summary Analysis host SSH logs +// @Description 分析 SSH 登录日志 +// @Accept json +// @Param request body dto.SearchForAnalysis true "request" +// @Success 200 {array} dto.SSHLogAnalysis +// @Security ApiKeyAuth +// @Router /host/ssh/log/analysis [post] +func (b *BaseApi) AnalysisLog(c *gin.Context) { + var req dto.SearchForAnalysis + 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 + } + + data, err := sshService.AnalysisLog(req) + if err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + helper.SuccessWithData(c, data) +} + +// @Tags SSH +// @Summary Load host SSH logs +// @Description 获取 SSH 登录日志 // @Accept json // @Param request body dto.SearchSSHLog true "request" // @Success 200 {object} dto.SSHLog // @Security ApiKeyAuth -// @Router /host/ssh/logs [post] +// @Router /host/ssh/log [post] func (b *BaseApi) LoadSSHLogs(c *gin.Context) { var req dto.SearchSSHLog if err := c.ShouldBindJSON(&req); err != nil { @@ -185,8 +212,8 @@ func (b *BaseApi) LoadSSHLogs(c *gin.Context) { } // @Tags SSH -// @Summary Load host ssh conf -// @Description 获取 ssh 配置文件 +// @Summary Load host SSH conf +// @Description 获取 SSH 配置文件 // @Success 200 // @Security ApiKeyAuth // @Router /host/ssh/conf [get] diff --git a/backend/app/dto/ssh.go b/backend/app/dto/ssh.go index 15eb4926d..b7e42eef7 100644 --- a/backend/app/dto/ssh.go +++ b/backend/app/dto/ssh.go @@ -36,6 +36,19 @@ type SSHLog struct { SuccessfulCount int `json:"successfulCount"` FailedCount int `json:"failedCount"` } + +type SearchForAnalysis struct { + OrderBy string `json:"orderBy" validate:"required,oneof=Success Failed"` +} + +type SSHLogAnalysis struct { + Address string `json:"address"` + Area string `json:"area"` + SuccessfulCount int `json:"successfulCount"` + FailedCount int `json:"failedCount"` + Status string `json:"status"` +} + type SSHHistory struct { Date time.Time `json:"date"` DateStr string `json:"dateStr"` diff --git a/backend/app/service/firewall.go b/backend/app/service/firewall.go index c9f94463c..51c4bcead 100644 --- a/backend/app/service/firewall.go +++ b/backend/app/service/firewall.go @@ -608,3 +608,21 @@ func (u *FirewallService) addAddressRecord(req dto.AddrRuleOperate) error { Description: req.Description, }) } + +func listIpRules(strategy string) ([]string, error) { + client, err := firewall.NewFirewallClient() + if err != nil { + return nil, err + } + addrs, err := client.ListAddress() + if err != nil { + return nil, err + } + var rules []string + for _, addr := range addrs { + if addr.Strategy == strategy { + rules = append(rules, addr.Address) + } + } + return rules, nil +} diff --git a/backend/app/service/ssh.go b/backend/app/service/ssh.go index 90c407dcd..9db48ad35 100644 --- a/backend/app/service/ssh.go +++ b/backend/app/service/ssh.go @@ -32,6 +32,7 @@ type ISSHService interface { UpdateByFile(value string) error Update(key, value string) error GenerateSSH(req dto.GenerateSSH) error + AnalysisLog(req dto.SearchForAnalysis) ([]dto.SSHLogAnalysis, error) LoadSSHSecret(mode string) (string, error) LoadLog(req dto.SearchSSHLog) (*dto.SSHLog, error) @@ -303,6 +304,70 @@ func (u *SSHService) LoadLog(req dto.SearchSSHLog) (*dto.SSHLog, error) { return &data, nil } +func (u *SSHService) AnalysisLog(req dto.SearchForAnalysis) ([]dto.SSHLogAnalysis, error) { + var fileList []string + baseDir := "/var/log" + if err := filepath.Walk(baseDir, func(pathItem string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if !info.IsDir() && strings.HasPrefix(info.Name(), "secure") || strings.HasPrefix(info.Name(), "auth") { + if !strings.HasSuffix(info.Name(), ".gz") { + fileList = append(fileList, pathItem) + return nil + } + itemFileName := strings.TrimSuffix(pathItem, ".gz") + if _, err := os.Stat(itemFileName); err != nil && os.IsNotExist(err) { + if err := handleGunzip(pathItem); err == nil { + fileList = append(fileList, itemFileName) + } + } + } + return nil + }); err != nil { + return nil, err + } + + command := "" + sortMap := make(map[string]dto.SSHLogAnalysis) + for _, file := range fileList { + commandItem := "" + if strings.HasPrefix(path.Base(file), "secure") { + commandItem = fmt.Sprintf("cat %s | grep -aE '(Failed password for|Accepted)' | grep -v 'invalid' %s", file, command) + } + if strings.HasPrefix(path.Base(file), "auth.log") { + commandItem = fmt.Sprintf("cat %s | grep -aE \"(Connection closed by authenticating user|Accepted)\" | grep -v 'invalid' %s", file, command) + } + loadSSHDataForAnalysis(sortMap, commandItem) + } + var sortSlice []dto.SSHLogAnalysis + for key, value := range sortMap { + sortSlice = append(sortSlice, dto.SSHLogAnalysis{Address: key, SuccessfulCount: value.SuccessfulCount, FailedCount: value.FailedCount, Status: "accept"}) + } + if req.OrderBy == constant.StatusSuccess { + sort.Slice(sortSlice, func(i, j int) bool { + return sortSlice[i].SuccessfulCount > sortSlice[j].SuccessfulCount + }) + } else { + sort.Slice(sortSlice, func(i, j int) bool { + return sortSlice[i].FailedCount > sortSlice[j].FailedCount + }) + } + qqWry, _ := qqwry.NewQQwry() + rules, _ := listIpRules("drop") + for i := 0; i < len(sortSlice); i++ { + sortSlice[i].Area = qqWry.Find(sortSlice[i].Address).Area + for _, rule := range rules { + if sortSlice[i].Address == rule { + sortSlice[i].Status = "drop" + break + } + } + } + + return sortSlice, nil +} + func (u *SSHService) LoadSSHConf() (string, error) { if _, err := os.Stat("/etc/ssh/sshd_config"); err != nil { return "", buserr.New("ErrHttpReqNotFound") @@ -412,6 +477,47 @@ func loadSSHData(command string, showCountFrom, showCountTo, currentYear int, qq return datas, successCount, failedCount } +func loadSSHDataForAnalysis(analysisMap map[string]dto.SSHLogAnalysis, commandItem string) { + stdout, err := cmd.Exec(commandItem) + if err != nil { + return + } + lines := strings.Split(string(stdout), "\n") + for i := len(lines) - 1; i >= 0; i-- { + var itemData dto.SSHHistory + switch { + case strings.Contains(lines[i], "Failed password for"): + itemData = loadFailedSecureDatas(lines[i]) + case strings.Contains(lines[i], "Connection closed by authenticating user"): + itemData = loadFailedAuthDatas(lines[i]) + case strings.Contains(lines[i], "Accepted "): + itemData = loadSuccessDatas(lines[i]) + } + if len(itemData.Address) != 0 { + if val, ok := analysisMap[itemData.Address]; ok { + if itemData.Status == constant.StatusSuccess { + val.SuccessfulCount++ + } else { + val.FailedCount++ + } + analysisMap[itemData.Address] = val + } else { + item := dto.SSHLogAnalysis{ + Address: itemData.Address, + SuccessfulCount: 0, + FailedCount: 0, + } + if itemData.Status == constant.StatusSuccess { + item.SuccessfulCount = 1 + } else { + item.FailedCount = 1 + } + analysisMap[itemData.Address] = item + } + } + } +} + func loadSuccessDatas(line string) dto.SSHHistory { var data dto.SSHHistory parts := strings.Fields(line) diff --git a/backend/constant/errs.go b/backend/constant/errs.go index 02b3d2c23..114101885 100644 --- a/backend/constant/errs.go +++ b/backend/constant/errs.go @@ -126,3 +126,7 @@ var ( ErrOSSConn = "ErrOSSConn" ErrEntrance = "ErrEntrance" ) + +var ( + ErrFirewall = "ErrFirewall" +) diff --git a/backend/i18n/lang/en.yaml b/backend/i18n/lang/en.yaml index 7f98ee4bd..d8718d7e9 100644 --- a/backend/i18n/lang/en.yaml +++ b/backend/i18n/lang/en.yaml @@ -111,4 +111,7 @@ ErrConfigParse: "Configuration file format error" ErrConfigIsNull: "The configuration file is not allowed to be empty" ErrConfigDirNotFound: "The running directory does not exist" ErrConfigAlreadyExist: "A configuration file with the same name already exists" -ErrUserFindErr: "Failed to find user {{ .name }} {{ .err }}" \ No newline at end of file +ErrUserFindErr: "Failed to find user {{ .name }} {{ .err }}" + +#ssh +ErrFirewall: "No firewalld or ufw service is detected. Please check and try again!" \ No newline at end of file diff --git a/backend/i18n/lang/zh-Hant.yaml b/backend/i18n/lang/zh-Hant.yaml index 5372c71b3..b0993ba82 100644 --- a/backend/i18n/lang/zh-Hant.yaml +++ b/backend/i18n/lang/zh-Hant.yaml @@ -112,3 +112,6 @@ ErrConfigIsNull: "配置文件不允許為空" ErrConfigDirNotFound: "運行目錄不存在" ErrConfigAlreadyExist: "已存在同名配置文件" ErrUserFindErr: "用戶 {{ .name }} 查找失敗 {{ .err }}" + +#ssh +ErrFirewall: "當前未檢測到系統 firewalld 或 ufw 服務,請檢查後重試!" \ No newline at end of file diff --git a/backend/i18n/lang/zh.yaml b/backend/i18n/lang/zh.yaml index 4b46094b1..d65565924 100644 --- a/backend/i18n/lang/zh.yaml +++ b/backend/i18n/lang/zh.yaml @@ -112,3 +112,6 @@ ErrConfigIsNull: "配置文件不允许为空" ErrConfigDirNotFound: "运行目录不存在" ErrConfigAlreadyExist: "已存在同名配置文件" ErrUserFindErr: "用户 {{ .name }} 查找失败 {{ .err }}" + +#ssh +ErrFirewall: "当前未检测到系统 firewalld 或 ufw 服务,请检查后重试!" diff --git a/backend/router/ro_host.go b/backend/router/ro_host.go index b4217b155..7c16ac56d 100644 --- a/backend/router/ro_host.go +++ b/backend/router/ro_host.go @@ -41,6 +41,7 @@ func (s *HostRouter) InitHostRouter(Router *gin.RouterGroup) { hostRouter.POST("/ssh/generate", baseApi.GenerateSSH) hostRouter.POST("/ssh/secret", baseApi.LoadSSHSecret) hostRouter.POST("/ssh/log", baseApi.LoadSSHLogs) + hostRouter.POST("/ssh/log/analysis", baseApi.AnalysisLog) hostRouter.POST("/ssh/conffile/update", baseApi.UpdateSSHByfile) hostRouter.POST("/ssh/operate", baseApi.OperateSSH) diff --git a/backend/utils/firewall/client.go b/backend/utils/firewall/client.go index 9080b657d..18ac3825a 100644 --- a/backend/utils/firewall/client.go +++ b/backend/utils/firewall/client.go @@ -1,9 +1,10 @@ package firewall import ( - "errors" "os" + "github.com/1Panel-dev/1Panel/backend/buserr" + "github.com/1Panel-dev/1Panel/backend/constant" "github.com/1Panel-dev/1Panel/backend/utils/firewall/client" ) @@ -30,5 +31,5 @@ func NewFirewallClient() (FirewallClient, error) { if _, err := os.Stat("/usr/sbin/ufw"); err == nil { return client.NewUfw() } - return nil, errors.New("no such type") + return nil, buserr.New(constant.ErrFirewall) } diff --git a/cmd/server/docs/docs.go b/cmd/server/docs/docs.go index 32f986881..283de3f00 100644 --- a/cmd/server/docs/docs.go +++ b/cmd/server/docs/docs.go @@ -5907,7 +5907,7 @@ const docTemplate = `{ "tags": [ "SSH" ], - "summary": "Update host ssh setting by file", + "summary": "Update host SSH setting by file", "parameters": [ { "description": "request", @@ -5940,11 +5940,11 @@ const docTemplate = `{ "ApiKeyAuth": [] } ], - "description": "获取 ssh 配置文件", + "description": "获取 SSH 配置文件", "tags": [ "SSH" ], - "summary": "Load host ssh conf", + "summary": "Load host SSH conf", "responses": { "200": { "description": "OK" @@ -5959,14 +5959,14 @@ const docTemplate = `{ "ApiKeyAuth": [] } ], - "description": "生成 ssh 密钥", + "description": "生成 SSH 密钥", "consumes": [ "application/json" ], "tags": [ "SSH" ], - "summary": "Generate host ssh secret", + "summary": "Generate host SSH secret", "parameters": [ { "description": "request", @@ -5992,21 +5992,21 @@ const docTemplate = `{ } } }, - "/host/ssh/logs": { + "/host/ssh/log": { "post": { "security": [ { "ApiKeyAuth": [] } ], - "description": "获取 ssh 登录日志", + "description": "获取 SSH 登录日志", "consumes": [ "application/json" ], "tags": [ "SSH" ], - "summary": "Load host ssh logs", + "summary": "Load host SSH logs", "parameters": [ { "description": "request", @@ -6028,6 +6028,45 @@ const docTemplate = `{ } } }, + "/host/ssh/log/analysis": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "分析 SSH 登录日志", + "consumes": [ + "application/json" + ], + "tags": [ + "SSH" + ], + "summary": "Analysis host SSH logs", + "parameters": [ + { + "description": "request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SearchForAnalysis" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/dto.SSHLogAnalysis" + } + } + } + } + } + }, "/host/ssh/operate": { "post": { "security": [ @@ -6042,7 +6081,7 @@ const docTemplate = `{ "tags": [ "SSH" ], - "summary": "Operate ssh", + "summary": "Operate SSH", "parameters": [ { "description": "request", @@ -6077,7 +6116,7 @@ const docTemplate = `{ "tags": [ "SSH" ], - "summary": "Load host ssh setting info", + "summary": "Load host SSH setting info", "responses": { "200": { "description": "OK", @@ -6095,14 +6134,14 @@ const docTemplate = `{ "ApiKeyAuth": [] } ], - "description": "获取 ssh 密钥", + "description": "获取 SSH 密钥", "consumes": [ "application/json" ], "tags": [ "SSH" ], - "summary": "Load host ssh secret", + "summary": "Load host SSH secret", "parameters": [ { "description": "request", @@ -6135,7 +6174,7 @@ const docTemplate = `{ "tags": [ "SSH" ], - "summary": "Update host ssh setting", + "summary": "Update host SSH setting", "parameters": [ { "description": "request", @@ -14456,6 +14495,26 @@ const docTemplate = `{ } } }, + "dto.SSHLogAnalysis": { + "type": "object", + "properties": { + "address": { + "type": "string" + }, + "area": { + "type": "string" + }, + "failedCount": { + "type": "integer" + }, + "status": { + "type": "string" + }, + "successfulCount": { + "type": "integer" + } + } + }, "dto.SSLUpdate": { "type": "object", "required": [ @@ -14486,6 +14545,21 @@ const docTemplate = `{ } } }, + "dto.SearchForAnalysis": { + "type": "object", + "required": [ + "orderBy" + ], + "properties": { + "orderBy": { + "type": "string", + "enum": [ + "Success", + "Failed" + ] + } + } + }, "dto.SearchForTree": { "type": "object", "properties": { @@ -14656,6 +14730,9 @@ const docTemplate = `{ "complexityVerification": { "type": "string" }, + "defaultNetwork": { + "type": "string" + }, "dingVars": { "type": "string" }, @@ -16305,6 +16382,9 @@ const docTemplate = `{ "resource": { "type": "string" }, + "source": { + "type": "string" + }, "type": { "type": "string" }, @@ -16364,6 +16444,9 @@ const docTemplate = `{ "rebuild": { "type": "boolean" }, + "source": { + "type": "string" + }, "version": { "type": "string" } diff --git a/cmd/server/docs/swagger.json b/cmd/server/docs/swagger.json index 052e7994c..2e53860c5 100644 --- a/cmd/server/docs/swagger.json +++ b/cmd/server/docs/swagger.json @@ -5900,7 +5900,7 @@ "tags": [ "SSH" ], - "summary": "Update host ssh setting by file", + "summary": "Update host SSH setting by file", "parameters": [ { "description": "request", @@ -5933,11 +5933,11 @@ "ApiKeyAuth": [] } ], - "description": "获取 ssh 配置文件", + "description": "获取 SSH 配置文件", "tags": [ "SSH" ], - "summary": "Load host ssh conf", + "summary": "Load host SSH conf", "responses": { "200": { "description": "OK" @@ -5952,14 +5952,14 @@ "ApiKeyAuth": [] } ], - "description": "生成 ssh 密钥", + "description": "生成 SSH 密钥", "consumes": [ "application/json" ], "tags": [ "SSH" ], - "summary": "Generate host ssh secret", + "summary": "Generate host SSH secret", "parameters": [ { "description": "request", @@ -5985,21 +5985,21 @@ } } }, - "/host/ssh/logs": { + "/host/ssh/log": { "post": { "security": [ { "ApiKeyAuth": [] } ], - "description": "获取 ssh 登录日志", + "description": "获取 SSH 登录日志", "consumes": [ "application/json" ], "tags": [ "SSH" ], - "summary": "Load host ssh logs", + "summary": "Load host SSH logs", "parameters": [ { "description": "request", @@ -6021,6 +6021,45 @@ } } }, + "/host/ssh/log/analysis": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "分析 SSH 登录日志", + "consumes": [ + "application/json" + ], + "tags": [ + "SSH" + ], + "summary": "Analysis host SSH logs", + "parameters": [ + { + "description": "request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SearchForAnalysis" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/dto.SSHLogAnalysis" + } + } + } + } + } + }, "/host/ssh/operate": { "post": { "security": [ @@ -6035,7 +6074,7 @@ "tags": [ "SSH" ], - "summary": "Operate ssh", + "summary": "Operate SSH", "parameters": [ { "description": "request", @@ -6070,7 +6109,7 @@ "tags": [ "SSH" ], - "summary": "Load host ssh setting info", + "summary": "Load host SSH setting info", "responses": { "200": { "description": "OK", @@ -6088,14 +6127,14 @@ "ApiKeyAuth": [] } ], - "description": "获取 ssh 密钥", + "description": "获取 SSH 密钥", "consumes": [ "application/json" ], "tags": [ "SSH" ], - "summary": "Load host ssh secret", + "summary": "Load host SSH secret", "parameters": [ { "description": "request", @@ -6128,7 +6167,7 @@ "tags": [ "SSH" ], - "summary": "Update host ssh setting", + "summary": "Update host SSH setting", "parameters": [ { "description": "request", @@ -14449,6 +14488,26 @@ } } }, + "dto.SSHLogAnalysis": { + "type": "object", + "properties": { + "address": { + "type": "string" + }, + "area": { + "type": "string" + }, + "failedCount": { + "type": "integer" + }, + "status": { + "type": "string" + }, + "successfulCount": { + "type": "integer" + } + } + }, "dto.SSLUpdate": { "type": "object", "required": [ @@ -14479,6 +14538,21 @@ } } }, + "dto.SearchForAnalysis": { + "type": "object", + "required": [ + "orderBy" + ], + "properties": { + "orderBy": { + "type": "string", + "enum": [ + "Success", + "Failed" + ] + } + } + }, "dto.SearchForTree": { "type": "object", "properties": { @@ -14649,6 +14723,9 @@ "complexityVerification": { "type": "string" }, + "defaultNetwork": { + "type": "string" + }, "dingVars": { "type": "string" }, @@ -16298,6 +16375,9 @@ "resource": { "type": "string" }, + "source": { + "type": "string" + }, "type": { "type": "string" }, @@ -16357,6 +16437,9 @@ "rebuild": { "type": "boolean" }, + "source": { + "type": "string" + }, "version": { "type": "string" } diff --git a/cmd/server/docs/swagger.yaml b/cmd/server/docs/swagger.yaml index 6146c10e4..12f7a1b7a 100644 --- a/cmd/server/docs/swagger.yaml +++ b/cmd/server/docs/swagger.yaml @@ -1855,6 +1855,19 @@ definitions: totalCount: type: integer type: object + dto.SSHLogAnalysis: + properties: + address: + type: string + area: + type: string + failedCount: + type: integer + status: + type: string + successfulCount: + type: integer + type: object dto.SSLUpdate: properties: cert: @@ -1875,6 +1888,16 @@ definitions: required: - ssl type: object + dto.SearchForAnalysis: + properties: + orderBy: + enum: + - Success + - Failed + type: string + required: + - orderBy + type: object dto.SearchForTree: properties: info: @@ -1989,6 +2012,8 @@ definitions: type: string complexityVerification: type: string + defaultNetwork: + type: string dingVars: type: string email: @@ -3089,6 +3114,8 @@ definitions: type: object resource: type: string + source: + type: string type: type: string version: @@ -3128,6 +3155,8 @@ definitions: type: object rebuild: type: boolean + source: + type: string version: type: string type: object @@ -7804,7 +7833,7 @@ paths: description: OK security: - ApiKeyAuth: [] - summary: Update host ssh setting by file + summary: Update host SSH setting by file tags: - SSH x-panel-log: @@ -7815,20 +7844,20 @@ paths: paramKeys: [] /host/ssh/conf: get: - description: 获取 ssh 配置文件 + description: 获取 SSH 配置文件 responses: "200": description: OK security: - ApiKeyAuth: [] - summary: Load host ssh conf + summary: Load host SSH conf tags: - SSH /host/ssh/generate: post: consumes: - application/json - description: 生成 ssh 密钥 + description: 生成 SSH 密钥 parameters: - description: request in: body @@ -7841,7 +7870,7 @@ paths: description: OK security: - ApiKeyAuth: [] - summary: Generate host ssh secret + summary: Generate host SSH secret tags: - SSH x-panel-log: @@ -7850,11 +7879,11 @@ paths: formatEN: generate SSH secret formatZH: '生成 SSH 密钥 ' paramKeys: [] - /host/ssh/logs: + /host/ssh/log: post: consumes: - application/json - description: 获取 ssh 登录日志 + description: 获取 SSH 登录日志 parameters: - description: request in: body @@ -7869,7 +7898,31 @@ paths: $ref: '#/definitions/dto.SSHLog' security: - ApiKeyAuth: [] - summary: Load host ssh logs + summary: Load host SSH logs + tags: + - SSH + /host/ssh/log/analysis: + post: + consumes: + - application/json + description: 分析 SSH 登录日志 + parameters: + - description: request + in: body + name: request + required: true + schema: + $ref: '#/definitions/dto.SearchForAnalysis' + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/dto.SSHLogAnalysis' + type: array + security: + - ApiKeyAuth: [] + summary: Analysis host SSH logs tags: - SSH /host/ssh/operate: @@ -7887,7 +7940,7 @@ paths: responses: {} security: - ApiKeyAuth: [] - summary: Operate ssh + summary: Operate SSH tags: - SSH x-panel-log: @@ -7907,14 +7960,14 @@ paths: $ref: '#/definitions/dto.SSHInfo' security: - ApiKeyAuth: [] - summary: Load host ssh setting info + summary: Load host SSH setting info tags: - SSH /host/ssh/secret: post: consumes: - application/json - description: 获取 ssh 密钥 + description: 获取 SSH 密钥 parameters: - description: request in: body @@ -7927,7 +7980,7 @@ paths: description: OK security: - ApiKeyAuth: [] - summary: Load host ssh secret + summary: Load host SSH secret tags: - SSH /host/ssh/update: @@ -7947,7 +8000,7 @@ paths: description: OK security: - ApiKeyAuth: [] - summary: Update host ssh setting + summary: Update host SSH setting tags: - SSH x-panel-log: diff --git a/frontend/src/api/interface/host.ts b/frontend/src/api/interface/host.ts index a6bd723a8..be5661e41 100644 --- a/frontend/src/api/interface/host.ts +++ b/frontend/src/api/interface/host.ts @@ -141,6 +141,13 @@ export namespace Host { 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 88ec5ee96..ab1922f41 100644 --- a/frontend/src/api/modules/host.ts +++ b/frontend/src/api/modules/host.ts @@ -123,3 +123,6 @@ export const loadSecret = (mode: string) => { export const loadSSHLogs = (params: Host.searchSSHLog) => { return http.post(`/hosts/ssh/log`, params); }; +export const loadAnalysis = (orderBy: string) => { + return http.post>(`/hosts/ssh/log/analysis`, { orderBy: orderBy }, TimeoutEnum.T_40S); +}; diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index 9b2c078cd..34cc23d5c 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -221,7 +221,9 @@ const message = { upgrading: 'Upgrading', upgradeerr: 'Upgrade Error', pullerr: 'Pull Image Error', - rebuilding: '重建中', + rebuilding: 'ReBuilding', + deny: 'Denied', + accept: 'Accepted', }, units: { second: 'Second', @@ -950,6 +952,14 @@ const message = { useDNS: 'useDNS', dnsHelper: 'Controls whether the DNS resolution function is enabled on the SSH server to verify the identity of the connection.', + analysis: 'Statistical information', + denyHelper: + "Performing a 'deny' operation on the following addresses. After setting, the IP will be prohibited from accessing the server. Do you want to continue?", + acceptHelper: + "Performing an 'accept' operation on the following addresses. After setting, the IP will regain normal access. Do you want to continue?", + noAddrWarning: 'No [{0}] addresses are currently selected. Please check and try again!', + successful: 'Success', + failed: 'Failed', loginLogs: 'SSH login log', loginMode: 'Login mode', authenticating: 'Key', diff --git a/frontend/src/lang/modules/tw.ts b/frontend/src/lang/modules/tw.ts index 7aaa8379f..800cdf236 100644 --- a/frontend/src/lang/modules/tw.ts +++ b/frontend/src/lang/modules/tw.ts @@ -220,6 +220,8 @@ const message = { upgradeerr: '升級失敗', pullerr: '鏡像拉取失敗', rebuilding: '重建中', + deny: '已屏蔽', + accept: '已放行', }, units: { second: '秒', @@ -912,6 +914,12 @@ const message = { keyAuthHelper: '是否啟用密鑰認證,默認啟用。', useDNS: '反向解析', dnsHelper: '控製 SSH 服務器是否啟用 DNS 解析功能,從而驗證連接方的身份。', + analysis: '統計信息', + denyHelper: '將對下列地址進行【屏蔽】操作,設置後該 IP 將禁止訪問服務器,是否繼續?', + acceptHelper: '將對下列地址進行【放行】操作,設置後該 IP 將恢復正常訪問,是否繼續?', + noAddrWarning: '當前未選中任何可【{0}】地址,請檢查後重試!', + successful: '成功', + failed: '失敗', loginLogs: 'SSH 登錄日誌', loginMode: '登錄方式', authenticating: '密鑰', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index f4a3ce5ed..c5a2c28fd 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -220,6 +220,8 @@ const message = { upgradeerr: '升级失败', pullerr: '镜像拉取失败', rebuilding: '重建中', + deny: '已屏蔽', + accept: '已放行', }, units: { second: '秒', @@ -912,6 +914,12 @@ const message = { keyAuthHelper: '是否启用密钥认证,默认启用。', useDNS: '反向解析', dnsHelper: '控制 SSH 服务器是否启用 DNS 解析功能,从而验证连接方的身份。', + analysis: '统计信息', + denyHelper: '将对下列地址进行【屏蔽】操作,设置后该 IP 将禁止访问服务器,是否继续?', + acceptHelper: '将对下列地址进行【放行】操作,设置后该 IP 将恢复正常访问,是否继续?', + noAddrWarning: '当前未选中任何可【{0}】地址,请检查后重试!', + successful: '成功', + failed: '失败', loginLogs: '登录日志', loginMode: '登录方式', authenticating: '密钥', diff --git a/frontend/src/views/host/ssh/log/analysis/index.vue b/frontend/src/views/host/ssh/log/analysis/index.vue new file mode 100644 index 000000000..3e309e2da --- /dev/null +++ b/frontend/src/views/host/ssh/log/analysis/index.vue @@ -0,0 +1,244 @@ + + + + diff --git a/frontend/src/views/host/ssh/log/log.vue b/frontend/src/views/host/ssh/log/log.vue index c80345d05..0912884d5 100644 --- a/frontend/src/views/host/ssh/log/log.vue +++ b/frontend/src/views/host/ssh/log/log.vue @@ -4,23 +4,24 @@ + + + @@ -86,9 +83,7 @@ import TableSetting from '@/components/table-setting/index.vue'; import { dateFormat } from '@/utils/util'; import { onMounted, reactive, ref } from '@vue/runtime-core'; import { loadSSHLogs } from '@/api/modules/host'; -import { operateIPRule } from '@/api/modules/host'; -import { MsgSuccess } from '@/utils/message'; -import i18n from '@/lang'; +import Analysis from '@/views/host/ssh/log/analysis/index.vue'; const loading = ref(); const data = ref(); @@ -100,29 +95,10 @@ const paginationConfig = reactive({ }); const searchInfo = ref(); const searchStatus = ref('All'); -const successfulCount = ref(0); -const faliedCount = ref(0); -const selects = ref([]); +const analysisRef = ref(); -function selectable(row: any): boolean { - return row.address !== '127.0.0.1' && row.address !== '::1'; -} - -function select2address(): string { - let res = []; - selects.value.forEach((item: any) => { - if (!res.includes(item.address)) res.push(item.address); - }); - return res.join(','); -} - -const onDeny = async () => { - let address = select2address(); - if (!address) return; - await operateIPRule({ operation: 'add', address: address, strategy: 'drop', description: '' }).then(() => { - MsgSuccess(i18n.global.t('commons.msg.operationSuccess')); - search(); - }); +const onLoadAnalysis = () => { + analysisRef.value.acceptParams(); }; const search = async () => { @@ -137,8 +113,6 @@ const search = async () => { .then((res) => { loading.value = false; data.value = res.data?.logs || []; - faliedCount.value = res.data.failedCount; - successfulCount.value = res.data.successfulCount; if (searchStatus.value === 'Success') { paginationConfig.total = res.data.successfulCount; } diff --git a/frontend/src/views/setting/panel/default-network/index.vue b/frontend/src/views/setting/panel/default-network/index.vue index 53638e1e9..ddc271d0c 100644 --- a/frontend/src/views/setting/panel/default-network/index.vue +++ b/frontend/src/views/setting/panel/default-network/index.vue @@ -64,7 +64,6 @@ const formRef = ref(); const acceptParams = (params: DialogProps): void => { form.defaultNetwork = params.defaultNetwork; - console.log(form.defaultNetwork); loadNetworkOptions(); drawerVisiable.value = true; };