A add log

This commit is contained in:
zhenorzz 2022-02-24 09:14:50 +08:00
parent 6e008443fb
commit 8d052f6b2e
44 changed files with 2143 additions and 90 deletions

View File

@ -11,7 +11,7 @@ type Agent Controller
func (a Agent) Routes() []core.Route {
return []core.Route{
core.NewRoute("/agent/report", http.MethodPost, a.Report).White(),
core.NewWhiteRoute("/agent/report", http.MethodPost, a.Report),
}
}

View File

@ -12,9 +12,9 @@ type Cron Controller
func (c Cron) Routes() []core.Route {
return []core.Route{
core.NewRoute("/cron/report", http.MethodPost, c.Report).White(),
core.NewRoute("/cron/getList", http.MethodPost, c.GetList).White(),
core.NewRoute("/cron/getLogs", http.MethodPost, c.GetLogs).White(),
core.NewWhiteRoute("/cron/report", http.MethodPost, c.Report),
core.NewWhiteRoute("/cron/getList", http.MethodPost, c.GetList),
core.NewWhiteRoute("/cron/getLogs", http.MethodPost, c.GetLogs),
core.NewRoute("/cron/add", http.MethodPost, c.Add).Roles(core.RoleAdmin, core.RoleManager),
core.NewRoute("/cron/edit", http.MethodPut, c.Edit).Roles(core.RoleAdmin, core.RoleManager),
core.NewRoute("/cron/remove", http.MethodDelete, c.Remove).Roles(core.RoleAdmin, core.RoleManager),

View File

@ -42,8 +42,8 @@ func (d Deploy) Routes() []core.Route {
core.NewRoute("/deploy/publish", http.MethodPost, d.Publish).Middleware(middleware.HasPublishAuth),
core.NewRoute("/deploy/rebuild", http.MethodPost, d.Rebuild).Middleware(middleware.HasPublishAuth),
core.NewRoute("/deploy/greyPublish", http.MethodPost, d.GreyPublish).Middleware(middleware.HasPublishAuth).Roles(core.RoleAdmin, core.RoleManager, core.RoleGroupManager),
core.NewRoute("/deploy/webhook", http.MethodPost, d.Webhook).Middleware(middleware.FilterEvent).White(),
core.NewRoute("/deploy/callback", http.MethodGet, d.Callback).White(),
core.NewWhiteRoute("/deploy/webhook", http.MethodPost, d.Webhook).Middleware(middleware.FilterEvent),
core.NewWhiteRoute("/deploy/callback", http.MethodGet, d.Callback),
core.NewRoute("/deploy/fileCompare", http.MethodPost, d.FileCompare).Roles(core.RoleAdmin, core.RoleManager, core.RoleGroupManager),
core.NewRoute("/deploy/fileDiff", http.MethodPost, d.FileDiff).Roles(core.RoleAdmin, core.RoleManager, core.RoleGroupManager),
core.NewRoute("/deploy/manageProcess", http.MethodPost, d.ManageProcess).Roles(core.RoleAdmin, core.RoleManager),

252
controller/LogController.go Normal file
View File

@ -0,0 +1,252 @@
package controller
import (
"github.com/zhenorzz/goploy/core"
"github.com/zhenorzz/goploy/model"
"github.com/zhenorzz/goploy/response"
"net/http"
)
// Log struct
type Log Controller
func (l Log) Routes() []core.Route {
return []core.Route{
core.NewRoute("/log/getLoginLogList", http.MethodGet, l.GetLoginLogList).Roles(core.RoleAdmin),
core.NewRoute("/log/getLoginLogTotal", http.MethodGet, l.GetLoginLogTotal).Roles(core.RoleAdmin),
core.NewRoute("/log/getSftpLogList", http.MethodGet, l.GetSftpLogList).Roles(core.RoleAdmin, core.RoleManager),
core.NewRoute("/log/getSftpLogTotal", http.MethodGet, l.GetSftpLogTotal).Roles(core.RoleAdmin, core.RoleManager),
core.NewRoute("/log/getTerminalLogList", http.MethodGet, l.GetTerminalLogList).Roles(core.RoleAdmin, core.RoleManager),
core.NewRoute("/log/getTerminalLogTotal", http.MethodGet, l.GetTerminalLogTotal).Roles(core.RoleAdmin, core.RoleManager),
core.NewRoute("/log/getTerminalRecord", http.MethodGet, l.GetTerminalRecord).Roles(core.RoleAdmin, core.RoleManager),
core.NewRoute("/log/getPublishLogList", http.MethodGet, l.GetPublishLogList).Roles(core.RoleAdmin, core.RoleManager),
core.NewRoute("/log/getPublishLogTotal", http.MethodGet, l.GetPublishLogTotal).Roles(core.RoleAdmin, core.RoleManager),
}
}
func (Log) GetLoginLogList(gp *core.Goploy) core.Response {
type ReqData struct {
Account string `schema:"account"`
Page uint64 `schema:"page" validate:"gt=0"`
Rows uint64 `schema:"rows" validate:"gt=0"`
}
var reqData ReqData
if err := decodeQuery(gp.URLQuery, &reqData); err != nil {
return response.JSON{Code: response.IllegalParam, Message: err.Error()}
}
list, err := model.LoginLog{Account: reqData.Account}.GetList(reqData.Page, reqData.Rows)
if err != nil {
return response.JSON{Code: response.Error, Message: err.Error()}
}
return response.JSON{
Data: struct {
List model.LoginLogs `json:"list"`
}{List: list},
}
}
func (Log) GetLoginLogTotal(gp *core.Goploy) core.Response {
type ReqData struct {
Account string `schema:"account"`
}
var reqData ReqData
if err := decodeQuery(gp.URLQuery, &reqData); err != nil {
return response.JSON{Code: response.IllegalParam, Message: err.Error()}
}
total, err := model.LoginLog{Account: reqData.Account}.GetTotal()
if err != nil {
return response.JSON{Code: response.Error, Message: err.Error()}
}
return response.JSON{
Data: struct {
Total int64 `json:"total"`
}{Total: total},
}
}
func (Log) GetSftpLogList(gp *core.Goploy) core.Response {
type ReqData struct {
Username string `schema:"username"`
ServerName string `schema:"serverName"`
Page uint64 `schema:"page" validate:"gt=0"`
Rows uint64 `schema:"rows" validate:"gt=0"`
}
var reqData ReqData
if err := decodeQuery(gp.URLQuery, &reqData); err != nil {
return response.JSON{Code: response.IllegalParam, Message: err.Error()}
}
sftpLog := model.SftpLog{Username: reqData.Username, ServerName: reqData.ServerName}
if gp.UserInfo.SuperManager != model.SuperManager {
sftpLog.NamespaceID = gp.Namespace.ID
}
list, err := sftpLog.GetList(reqData.Page, reqData.Rows)
if err != nil {
return response.JSON{Code: response.Error, Message: err.Error()}
}
return response.JSON{
Data: struct {
List model.SftpLogs `json:"list"`
}{List: list},
}
}
func (Log) GetSftpLogTotal(gp *core.Goploy) core.Response {
type ReqData struct {
Username string `schema:"username"`
ServerName string `schema:"serverName"`
}
var reqData ReqData
if err := decodeQuery(gp.URLQuery, &reqData); err != nil {
return response.JSON{Code: response.IllegalParam, Message: err.Error()}
}
sftpLog := model.SftpLog{Username: reqData.Username, ServerName: reqData.ServerName}
if gp.UserInfo.SuperManager != model.SuperManager {
sftpLog.NamespaceID = gp.Namespace.ID
}
total, err := sftpLog.GetTotal()
if err != nil {
return response.JSON{Code: response.Error, Message: err.Error()}
}
return response.JSON{
Data: struct {
Total int64 `json:"total"`
}{Total: total},
}
}
func (Log) GetTerminalLogList(gp *core.Goploy) core.Response {
type ReqData struct {
Username string `schema:"username"`
ServerName string `schema:"serverName"`
Page uint64 `schema:"page" validate:"gt=0"`
Rows uint64 `schema:"rows" validate:"gt=0"`
}
var reqData ReqData
if err := decodeQuery(gp.URLQuery, &reqData); err != nil {
return response.JSON{Code: response.IllegalParam, Message: err.Error()}
}
terminalLog := model.TerminalLog{Username: reqData.Username, ServerName: reqData.ServerName}
if gp.UserInfo.SuperManager != model.SuperManager {
terminalLog.NamespaceID = gp.Namespace.ID
}
list, err := terminalLog.GetList(reqData.Page, reqData.Rows)
if err != nil {
return response.JSON{Code: response.Error, Message: err.Error()}
}
return response.JSON{
Data: struct {
List model.TerminalLogs `json:"list"`
}{List: list},
}
}
func (Log) GetTerminalLogTotal(gp *core.Goploy) core.Response {
type ReqData struct {
Username string `schema:"username"`
ServerName string `schema:"serverName"`
}
var reqData ReqData
if err := decodeQuery(gp.URLQuery, &reqData); err != nil {
return response.JSON{Code: response.IllegalParam, Message: err.Error()}
}
terminalLog := model.TerminalLog{Username: reqData.Username, ServerName: reqData.ServerName}
if gp.UserInfo.SuperManager != model.SuperManager {
terminalLog.NamespaceID = gp.Namespace.ID
}
total, err := terminalLog.GetTotal()
if err != nil {
return response.JSON{Code: response.Error, Message: err.Error()}
}
return response.JSON{
Data: struct {
Total int64 `json:"total"`
}{Total: total},
}
}
func (Log) GetTerminalRecord(gp *core.Goploy) core.Response {
type ReqData struct {
RecordID int64 `schema:"recordId" validate:"gt=0"`
}
var reqData ReqData
if err := decodeQuery(gp.URLQuery, &reqData); err != nil {
return response.JSON{Code: response.IllegalParam, Message: err.Error()}
}
terminalLog, err := model.TerminalLog{ID: reqData.RecordID}.GetData()
if err != nil {
return response.JSON{Code: response.Error, Message: err.Error()}
}
if terminalLog.NamespaceID != gp.Namespace.ID {
return response.JSON{Code: response.Error, Message: "You have no access to enter this record"}
}
return response.File{Filename: core.GetTerminalLogPath(reqData.RecordID)}
}
func (Log) GetPublishLogList(gp *core.Goploy) core.Response {
type ReqData struct {
Username string `schema:"username"`
ProjectName string `schema:"projectName"`
Page uint64 `schema:"page" validate:"gt=0"`
Rows uint64 `schema:"rows" validate:"gt=0"`
}
var reqData ReqData
if err := decodeQuery(gp.URLQuery, &reqData); err != nil {
return response.JSON{Code: response.IllegalParam, Message: err.Error()}
}
publishLog := model.PublishTrace{PublisherName: reqData.Username, ProjectName: reqData.ProjectName}
if gp.UserInfo.SuperManager != model.SuperManager {
publishLog.NamespaceID = gp.Namespace.ID
}
list, err := publishLog.GetList(reqData.Page, reqData.Rows)
if err != nil {
return response.JSON{Code: response.Error, Message: err.Error()}
}
return response.JSON{
Data: struct {
List model.PublishTraces `json:"list"`
}{List: list},
}
}
func (Log) GetPublishLogTotal(gp *core.Goploy) core.Response {
type ReqData struct {
Username string `schema:"username"`
ProjectName string `schema:"projectName"`
}
var reqData ReqData
if err := decodeQuery(gp.URLQuery, &reqData); err != nil {
return response.JSON{Code: response.IllegalParam, Message: err.Error()}
}
publishLog := model.PublishTrace{PublisherName: reqData.Username, ProjectName: reqData.ProjectName}
if gp.UserInfo.SuperManager != model.SuperManager {
publishLog.NamespaceID = gp.Namespace.ID
}
total, err := publishLog.GetTotal()
if err != nil {
return response.JSON{Code: response.Error, Message: err.Error()}
}
return response.JSON{
Data: struct {
Total int64 `json:"total"`
}{Total: total},
}
}

View File

@ -3,13 +3,13 @@ package controller
import (
"github.com/pkg/sftp"
"github.com/zhenorzz/goploy/core"
"github.com/zhenorzz/goploy/middleware"
"github.com/zhenorzz/goploy/model"
"github.com/zhenorzz/goploy/response"
"github.com/zhenorzz/goploy/utils"
"io"
"io/ioutil"
"net/http"
"path"
"strconv"
"strings"
)
@ -23,14 +23,13 @@ func (s Server) Routes() []core.Route {
core.NewRoute("/server/getTotal", http.MethodGet, s.GetTotal),
core.NewRoute("/server/getOption", http.MethodGet, s.GetOption),
core.NewRoute("/server/getPublicKey", http.MethodGet, s.GetPublicKey),
core.NewRoute("/server/getTerminalRecord", http.MethodGet, s.GetTerminalRecord),
core.NewRoute("/server/check", http.MethodPost, s.Check).Roles(core.RoleAdmin, core.RoleManager),
core.NewRoute("/server/add", http.MethodPost, s.Add).Roles(core.RoleAdmin, core.RoleManager),
core.NewRoute("/server/edit", http.MethodPut, s.Edit).Roles(core.RoleAdmin, core.RoleManager),
core.NewRoute("/server/toggle", http.MethodPut, s.Toggle).Roles(core.RoleAdmin, core.RoleManager),
core.NewRoute("/server/previewFile", http.MethodGet, s.PreviewFile).Roles(core.RoleAdmin, core.RoleManager),
core.NewRoute("/server/downloadFile", http.MethodGet, s.DownloadFile).Roles(core.RoleAdmin, core.RoleManager),
core.NewRoute("/server/uploadFile", http.MethodPost, s.UploadFile).Roles(core.RoleAdmin, core.RoleManager),
core.NewRoute("/server/previewFile", http.MethodGet, s.PreviewFile).Roles(core.RoleAdmin, core.RoleManager).LogFunc(middleware.AddPreviewLog),
core.NewRoute("/server/downloadFile", http.MethodGet, s.DownloadFile).Roles(core.RoleAdmin, core.RoleManager).LogFunc(middleware.AddDownloadLog),
core.NewRoute("/server/uploadFile", http.MethodPost, s.UploadFile).Roles(core.RoleAdmin, core.RoleManager).LogFunc(middleware.AddUploadLog),
core.NewRoute("/server/report", http.MethodGet, s.Report).Roles(core.RoleAdmin, core.RoleManager),
core.NewRoute("/server/getAllMonitor", http.MethodGet, s.GetAllMonitor),
core.NewRoute("/server/addMonitor", http.MethodPost, s.AddMonitor).Roles(core.RoleAdmin, core.RoleManager),
@ -79,10 +78,6 @@ func (Server) GetOption(gp *core.Goploy) core.Response {
}
}
func (Server) GetTerminalRecord(*core.Goploy) core.Response {
return response.File{Filename: path.Join(core.GetLogPath(), "demo.cast")}
}
func (Server) GetPublicKey(gp *core.Goploy) core.Response {
publicKeyPath := gp.URLQuery.Get("path")
@ -289,20 +284,20 @@ func (Server) UploadFile(gp *core.Goploy) core.Response {
}
var reqData ReqData
if err := decodeQuery(gp.URLQuery, &reqData); err != nil {
return response.JSON{Code: response.Error, Message: err.Error()}
return response.JSON{Code: response.IllegalParam, Message: err.Error()}
}
file, fileHandler, err := gp.Request.FormFile("file")
if err != nil {
return response.JSON{Code: response.IllegalParam, Message: err.Error()}
}
defer file.Close()
server, err := (model.Server{ID: reqData.ID}).GetData()
if err != nil {
return response.JSON{Code: response.Error, Message: err.Error()}
}
file, fileHandler, err := gp.Request.FormFile("file")
if err != nil {
return response.JSON{Code: response.Error, Message: err.Error()}
}
defer file.Close()
client, err := server.Convert2SSHConfig().Dial()
if err != nil {
return response.JSON{Code: response.Error, Message: err.Error()}

View File

@ -4,6 +4,7 @@ import (
"database/sql"
"fmt"
"github.com/go-ldap/ldap/v3"
"github.com/zhenorzz/goploy/middleware"
"github.com/zhenorzz/goploy/response"
"net/http"
"time"
@ -17,7 +18,7 @@ type User Controller
func (u User) Routes() []core.Route {
return []core.Route{
core.NewRoute("/user/login", http.MethodPost, u.Login).White(),
core.NewWhiteRoute("/user/login", http.MethodPost, u.Login).LogFunc(middleware.AddLoginLog),
core.NewRoute("/user/info", http.MethodGet, u.Info),
core.NewRoute("/user/getList", http.MethodGet, u.GetList),
core.NewRoute("/user/getTotal", http.MethodGet, u.GetTotal),
@ -36,7 +37,7 @@ func (User) Login(gp *core.Goploy) core.Response {
}
var reqData ReqData
if err := decodeJson(gp.Body, &reqData); err != nil {
return response.JSON{Code: response.Error, Message: err.Error()}
return response.JSON{Code: response.IllegalParam, Message: err.Error()}
}
userData, err := model.User{Account: reqData.Account}.GetDataByAccount()
@ -77,7 +78,7 @@ func (User) Login(gp *core.Goploy) core.Response {
return response.JSON{Code: response.Deny, Message: err.Error()}
}
if len(sr.Entries) != 1 {
return response.JSON{Code: response.Deny, Message: err.Error()}
return response.JSON{Code: response.Deny, Message: fmt.Sprintf("No %s record in baseDN %s", reqData.Account, config.Toml.LDAP.BaseDN)}
}
if err := conn.Bind(sr.Entries[0].DN, reqData.Password); err != nil {
return response.JSON{Code: response.Deny, Message: err.Error()}
@ -91,9 +92,10 @@ func (User) Login(gp *core.Goploy) core.Response {
if userData.State == model.Disable {
return response.JSON{Code: response.AccountDisabled, Message: "Account is disabled"}
}
namespaceList, err := model.Namespace{UserID: userData.ID}.GetAllByUserID()
if err != nil {
return response.JSON{Code: response.Error, Message: err.Error()}
} else if len(namespaceList) == 0 {
return response.JSON{Code: response.Error, Message: "No space assigned, please contact the administrator"}
}

View File

@ -73,3 +73,7 @@ func GetProjectFilePath(projectID int64) string {
func GetProjectPath(projectID int64) string {
return path.Join(GetRepositoryPath(), "project_"+strconv.FormatInt(projectID, 10))
}
func GetTerminalLogPath(tlID int64) string {
return path.Join(GetRepositoryPath(), "terminal-log", strconv.FormatInt(tlID, 10)+".cast")
}

View File

@ -38,18 +38,19 @@ type Response interface {
}
type Route struct {
pattern string //
pattern string
method string // Method specifies the HTTP method (GET, POST, PUT, etc.).
roles map[string]struct{} // permission role
callback func(gp *Goploy) Response // Controller function
middlewares []func(gp *Goploy) error // Middlewares run before all callback
white bool // no need to login
middlewares []func(gp *Goploy) error // Middlewares run before callback, trigger error will end the request
callback func(gp *Goploy) Response // Controller function
logFunc func(gp *Goploy, resp Response)
}
// Router is Route slice and global middlewares
type Router struct {
routes map[string]Route
middlewares []func(gp *Goploy) error // Middlewares run before this Route
middlewares []func(gp *Goploy) error // Middlewares run before all Route
}
func NewRouter() Router {
@ -67,6 +68,16 @@ func NewRoute(pattern, method string, callback func(gp *Goploy) Response) Route
}
}
func NewWhiteRoute(pattern, method string, callback func(gp *Goploy) Response) Route {
return Route{
pattern: pattern,
method: method,
white: true,
roles: map[string]struct{}{},
callback: callback,
}
}
// Start a router
func (rt Router) Start() {
if config.Toml.Env == "production" {
@ -94,12 +105,6 @@ func (rt Router) Add(ra RouteApi) Router {
return rt
}
// White no need to check login
func (r Route) White() Route {
r.white = true
return r
}
// Roles Add much permission to the Route
func (r Route) Roles(roles ...string) Route {
for _, role := range roles {
@ -114,6 +119,12 @@ func (r Route) Middleware(middleware func(gp *Goploy) error) Route {
return r
}
// LogFunc callback finished
func (r Route) LogFunc(f func(gp *Goploy, resp Response)) Route {
r.logFunc = f
return r
}
func (rt Router) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// If in production env, serve file in go server,
// else serve file in npm
@ -236,7 +247,13 @@ func (rt Router) doRequest(w http.ResponseWriter, r *http.Request) (*Goploy, Res
}
}
return gp, route.callback(gp)
resp := route.callback(gp)
if route.logFunc != nil {
route.logFunc(gp, resp)
}
return gp, resp
}
func (r Route) hasRole(namespaceRole string) error {

View File

@ -1,7 +1,7 @@
# Import sql manually https://github.com/zhenorzz/goploy/blob/master/model/sql/goploy.sql
FROM alpine
LABEL maintainer="zhenorzz@gmail.com"
ARG GOPLOY_VER=v1.4.5
ARG GOPLOY_VER=v1.4.6
ENV GOPLOY_VER=${GOPLOY_VER}
ENV MYSQL_PORT=3306

View File

@ -1,5 +1,16 @@
# change log
## 1.4.6
*2022-02-24*
### New features
- web log
- sftp file preview
### Bug fixed
- web cookies undefined
## 1.4.5
*2022-01-26*
@ -31,6 +42,7 @@
### Optimization
- code
- select db
### Bug fixes
- fix exit deploy script
- fix tag refresh

View File

@ -38,7 +38,7 @@ var (
s string
)
const appVersion = "1.4.5"
const appVersion = "1.4.6"
func init() {
flag.StringVar(&core.AssetDir, "asset-dir", "", "default: ./")

View File

@ -0,0 +1,34 @@
package middleware
import (
"encoding/json"
"github.com/zhenorzz/goploy/core"
"github.com/zhenorzz/goploy/model"
"github.com/zhenorzz/goploy/response"
"time"
)
func AddLoginLog(gp *core.Goploy, resp core.Response) {
respJson := resp.(response.JSON)
account := ""
if respJson.Code != response.IllegalParam {
type ReqData struct {
Account string `json:"account"`
}
var reqData ReqData
_ = json.Unmarshal(gp.Body, &reqData)
account = reqData.Account
}
err := model.LoginLog{
Account: account,
RemoteAddr: gp.Request.RemoteAddr,
UserAgent: gp.Request.UserAgent(),
Referer: gp.Request.Referer(),
Reason: respJson.Message,
LoginTime: time.Now().Format("20060102150405"),
}.AddRow()
if err != nil {
core.Log(core.ERROR, err.Error())
}
}

View File

@ -0,0 +1,94 @@
package middleware
import (
"github.com/zhenorzz/goploy/core"
"github.com/zhenorzz/goploy/model"
"github.com/zhenorzz/goploy/response"
"strconv"
)
func AddUploadLog(gp *core.Goploy, resp core.Response) {
var serverID int64 = 0
var path = ""
respJson := resp.(response.JSON)
if respJson.Code != response.IllegalParam {
serverID, _ = strconv.ParseInt(gp.URLQuery.Get("id"), 10, 64)
file, fileHandler, _ := gp.Request.FormFile("file")
path = gp.URLQuery.Get("filePath") + "/" + fileHandler.Filename
_ = file.Close()
}
err := model.SftpLog{
NamespaceID: gp.Namespace.ID,
UserID: gp.UserInfo.ID,
ServerID: serverID,
RemoteAddr: gp.Request.RemoteAddr,
UserAgent: gp.Request.UserAgent(),
Type: model.SftpLogTypeUpload,
Path: path,
Reason: respJson.Message,
}.AddRow()
if err != nil {
core.Log(core.ERROR, err.Error())
}
}
func AddDownloadLog(gp *core.Goploy, resp core.Response) {
msg := ""
path := ""
var serverID int64 = 0
switch resp.(type) {
case response.JSON:
respJson := resp.(response.JSON)
if respJson.Code != response.IllegalParam {
msg = respJson.Message
serverID, _ = strconv.ParseInt(gp.URLQuery.Get("id"), 10, 64)
}
case response.SftpFile:
path = resp.(response.SftpFile).Filename
}
err := model.SftpLog{
NamespaceID: gp.Namespace.ID,
UserID: gp.UserInfo.ID,
ServerID: serverID,
RemoteAddr: gp.Request.RemoteAddr,
UserAgent: gp.Request.UserAgent(),
Type: model.SftpLogTypeDownload,
Path: path,
Reason: msg,
}.AddRow()
if err != nil {
core.Log(core.ERROR, err.Error())
}
}
func AddPreviewLog(gp *core.Goploy, resp core.Response) {
msg := ""
path := ""
var serverID int64 = 0
switch resp.(type) {
case response.JSON:
respJson := resp.(response.JSON)
if respJson.Code != response.IllegalParam {
msg = respJson.Message
serverID, _ = strconv.ParseInt(gp.URLQuery.Get("id"), 10, 64)
}
case response.SftpFile:
path = resp.(response.SftpFile).Filename
}
err := model.SftpLog{
NamespaceID: gp.Namespace.ID,
UserID: gp.UserInfo.ID,
ServerID: serverID,
RemoteAddr: gp.Request.RemoteAddr,
UserAgent: gp.Request.UserAgent(),
Type: model.SftpLogTypePreview,
Path: path,
Reason: msg,
}.AddRow()
if err != nil {
core.Log(core.ERROR, err.Error())
}
}

90
model/LoginLogModel.go Normal file
View File

@ -0,0 +1,90 @@
package model
import (
sq "github.com/Masterminds/squirrel"
)
const loginLogTable = "`login_log`"
type LoginLog struct {
ID int64 `json:"id"`
Account string `json:"account"`
RemoteAddr string `json:"remoteAddr"`
UserAgent string `json:"userAgent"`
Referer string `json:"referer"`
Reason string `json:"reason"`
LoginTime string `json:"loginTime"`
}
type LoginLogs []LoginLog
func (ll LoginLog) GetList(page, limit uint64) (LoginLogs, error) {
builder := sq.
Select(
"id",
"account",
"remote_addr",
"user_agent",
"referer",
"reason",
"login_time",
).
From(loginLogTable)
if len(ll.Account) > 0 {
builder = builder.Where(sq.Eq{"account": ll.Account})
}
rows, err := builder.
Limit(limit).
Offset((page - 1) * limit).
OrderBy("id DESC").
RunWith(DB).
Query()
if err != nil {
return nil, err
}
loginLogs := LoginLogs{}
for rows.Next() {
var loginLog LoginLog
if err := rows.Scan(
&loginLog.ID,
&loginLog.Account,
&loginLog.RemoteAddr,
&loginLog.UserAgent,
&loginLog.Referer,
&loginLog.Reason,
&loginLog.LoginTime,
); err != nil {
return nil, err
}
loginLogs = append(loginLogs, loginLog)
}
return loginLogs, nil
}
func (ll LoginLog) GetTotal() (int64, error) {
var total int64
builder := sq.Select("COUNT(*) AS count").From(loginLogTable)
if len(ll.Account) > 0 {
builder = builder.Where(sq.Eq{"account": ll.Account})
}
err := builder.RunWith(DB).
QueryRow().
Scan(&total)
if err != nil {
return 0, err
}
return total, nil
}
func (ll LoginLog) AddRow() error {
_, err := sq.
Insert(loginLogTable).
Columns("account", "remote_addr", "user_agent", "referer", "reason", "login_time").
Values(ll.Account, ll.RemoteAddr, ll.UserAgent, ll.Referer, ll.Reason, ll.LoginTime).
RunWith(DB).
Exec()
return err
}

View File

@ -1,6 +1,7 @@
package model
import (
"fmt"
sq "github.com/Masterminds/squirrel"
)
@ -10,6 +11,7 @@ const publishTraceTable = "`publish_trace`"
type PublishTrace struct {
ID int64 `json:"id"`
Token string `json:"token"`
NamespaceID int64 `json:"namespaceId"`
ProjectID int64 `json:"projectId"`
ProjectName string `json:"projectName"`
Detail string `json:"detail"`
@ -41,24 +43,86 @@ const (
AfterDeploy = 6
)
// AddRow return LastInsertId
func (pt PublishTrace) AddRow() (int64, error) {
result, err := sq.
Insert(publishTraceTable).
Columns("token", "project_id", "project_name", "detail", "state", "publisher_id", "publisher_name", "type", "ext").
Values(pt.Token, pt.ProjectID, pt.ProjectName, pt.Detail, pt.State, pt.PublisherID, pt.PublisherName, pt.Type, pt.Ext).
RunWith(DB).
Exec()
func (pt PublishTrace) GetList(page, limit uint64) (PublishTraces, error) {
builder := sq.
Select(
fmt.Sprintf("%s.token", publishTraceTable),
fmt.Sprintf("min(%s.project_id)", publishTraceTable),
fmt.Sprintf("min(%s.project_name)", publishTraceTable),
fmt.Sprintf("min(%s.publisher_id)", publishTraceTable),
fmt.Sprintf("min(%s.publisher_name)", publishTraceTable),
fmt.Sprintf("min(%s.state)", publishTraceTable),
fmt.Sprintf("IFNULL(GROUP_CONCAT(IF(%s.state = 0, %[1]s.detail, NULL)), '') as detail", publishTraceTable),
fmt.Sprintf("min(%s.insert_time) as insert_time", publishTraceTable),
).
From(publishTraceTable).
GroupBy("token")
if pt.NamespaceID > 0 {
builder = builder.
Join(fmt.Sprintf("%s ON %[1]s.id = %s.project_id", projectTable, publishTraceTable)).
Where(sq.Eq{projectTable + ".namespace_id": pt.NamespaceID})
}
if pt.PublisherName != "" {
builder = builder.Where(sq.Eq{publishTraceTable + ".publisher_name": pt.PublisherName})
}
if pt.ProjectName != "" {
builder = builder.Where(sq.Eq{publishTraceTable + ".project_name": pt.ProjectName})
}
rows, err := builder.RunWith(DB).
OrderBy("insert_time DESC").
Limit(limit).
Offset((page - 1) * limit).
Query()
if err != nil {
return nil, err
}
publishTraces := PublishTraces{}
for rows.Next() {
var publishTrace PublishTrace
if err := rows.Scan(
&publishTrace.Token,
&publishTrace.ProjectID,
&publishTrace.ProjectName,
&publishTrace.PublisherID,
&publishTrace.PublisherName,
&publishTrace.State,
&publishTrace.Detail,
&publishTrace.InsertTime); err != nil {
return nil, err
}
publishTraces = append(publishTraces, publishTrace)
}
return publishTraces, nil
}
func (pt PublishTrace) GetTotal() (int64, error) {
var total int64
builder := sq.Select("COUNT(distinct token) AS count").
From(publishTraceTable)
if pt.NamespaceID > 0 {
builder = builder.
Join(fmt.Sprintf("%s ON %[1]s.id = %s.project_id", projectTable, publishTraceTable)).
Where(sq.Eq{projectTable + ".namespace_id": pt.NamespaceID})
}
if pt.PublisherName != "" {
builder = builder.Where(sq.Eq{publishTraceTable + ".publisher_name": pt.PublisherName})
}
if pt.ProjectName != "" {
builder = builder.Where(sq.Eq{publishTraceTable + ".project_name": pt.ProjectName})
}
err := builder.RunWith(DB).
QueryRow().
Scan(&total)
if err != nil {
return 0, err
}
id, err := result.LastInsertId()
return id, err
return total, nil
}
// GetListByToken -
func (pt PublishTrace) GetListByToken() (PublishTraces, error) {
rows, err := sq.
Select(
@ -105,7 +169,6 @@ func (pt PublishTrace) GetListByToken() (PublishTraces, error) {
return publishTraces, nil
}
// GetPreview -
func (pt PublishTrace) GetPreview(
branch string,
commit string,
@ -229,6 +292,23 @@ func (pt PublishTrace) GetDetail() (string, error) {
return detail, nil
}
// AddRow return LastInsertId
func (pt PublishTrace) AddRow() (int64, error) {
result, err := sq.
Insert(publishTraceTable).
Columns("token", "project_id", "project_name", "detail", "state", "publisher_id", "publisher_name", "type", "ext").
Values(pt.Token, pt.ProjectID, pt.ProjectName, pt.Detail, pt.State, pt.PublisherID, pt.PublisherName, pt.Type, pt.Ext).
RunWith(DB).
Exec()
if err != nil {
return 0, err
}
id, err := result.LastInsertId()
return id, err
}
// EditUpdateTimeByToken -
func (pt PublishTrace) EditUpdateTimeByToken() error {
_, err := sq.

131
model/SftpLogModel.go Normal file
View File

@ -0,0 +1,131 @@
package model
import (
"fmt"
sq "github.com/Masterminds/squirrel"
)
const sftpLogTable = "`sftp_log`"
type SftpLog struct {
ID int64 `json:"id"`
NamespaceID int64 `json:"namespaceID"`
UserID int64 `json:"userID"`
Username string `json:"username"`
ServerID int64 `json:"serverID"`
ServerName string `json:"serverName"`
RemoteAddr string `json:"remoteAddr"`
UserAgent string `json:"userAgent"`
Type string `json:"type"`
Path string `json:"path"`
Reason string `json:"reason"`
InsertTime string `json:"insertTime"`
UpdateTime string `json:"updateTime"`
}
const (
SftpLogTypeDownload = "DOWNLOAD"
SftpLogTypeUpload = "UPLOAD"
SftpLogTypeRead = "READ"
SftpLogTypePreview = "PREVIEW"
)
type SftpLogs []SftpLog
func (sl SftpLog) GetList(page, limit uint64) (SftpLogs, error) {
builder := sq.
Select(
sftpLogTable+".id",
sftpLogTable+".namespace_id",
sftpLogTable+".user_id",
userTable+".name",
sftpLogTable+".server_id",
fmt.Sprintf("IFNULL(%s.name, '')", serverTable),
sftpLogTable+".remote_addr",
sftpLogTable+".user_agent",
sftpLogTable+".type",
sftpLogTable+".path",
sftpLogTable+".reason",
).
From(sftpLogTable).
LeftJoin(fmt.Sprintf("%s ON %[1]s.id = %s.user_id", userTable, sftpLogTable)).
LeftJoin(fmt.Sprintf("%s ON %[1]s.id = %s.server_id", serverTable, sftpLogTable))
if sl.NamespaceID > 0 {
builder = builder.Where(sq.Eq{sftpLogTable + ".namespace_id": sl.NamespaceID})
}
if sl.Username != "" {
builder = builder.Where(sq.Eq{userTable + ".name": sl.Username})
}
if sl.ServerName != "" {
builder = builder.Where(sq.Eq{serverTable + ".name": sl.ServerName})
}
rows, err := builder.
Limit(limit).
Offset((page - 1) * limit).
OrderBy("id DESC").
RunWith(DB).
Query()
if err != nil {
return nil, err
}
sftpLogs := SftpLogs{}
for rows.Next() {
var sftpLog SftpLog
if err := rows.Scan(
&sftpLog.ID,
&sftpLog.NamespaceID,
&sftpLog.UserID,
&sftpLog.Username,
&sftpLog.ServerID,
&sftpLog.ServerName,
&sftpLog.RemoteAddr,
&sftpLog.UserAgent,
&sftpLog.Type,
&sftpLog.Path,
&sftpLog.Reason,
); err != nil {
return nil, err
}
sftpLogs = append(sftpLogs, sftpLog)
}
return sftpLogs, nil
}
func (sl SftpLog) GetTotal() (int64, error) {
var total int64
builder := sq.Select("COUNT(*) AS count").From(sftpLogTable)
if sl.NamespaceID > 0 {
builder = builder.Where(sq.Eq{sftpLogTable + ".namespace_id": sl.NamespaceID})
}
if sl.Username != "" {
builder = builder.
LeftJoin(fmt.Sprintf("%s ON %[1]s.id = %s.user_id", userTable, sftpLogTable)).
Where(sq.Eq{userTable + ".name": sl.Username})
}
if sl.ServerName != "" {
builder = builder.
LeftJoin(fmt.Sprintf("%s ON %[1]s.id = %s.server_id", serverTable, sftpLogTable)).
Where(sq.Eq{serverTable + ".name": sl.ServerName})
}
err := builder.RunWith(DB).
QueryRow().
Scan(&total)
if err != nil {
return 0, err
}
return total, nil
}
func (sl SftpLog) AddRow() error {
_, err := sq.
Insert(sftpLogTable).
Columns("namespace_id", "user_id", "server_id", "remote_addr", "user_agent", "type", "path", "reason").
Values(sl.NamespaceID, sl.UserID, sl.ServerID, sl.RemoteAddr, sl.UserAgent, sl.Type, sl.Path, sl.Reason).
RunWith(DB).
Exec()
return err
}

165
model/TerminalLogModel.go Normal file
View File

@ -0,0 +1,165 @@
package model
import (
"fmt"
sq "github.com/Masterminds/squirrel"
)
const terminalLogTable = "`terminal_log`"
type TerminalLog struct {
ID int64 `json:"id"`
NamespaceID int64 `json:"namespaceID"`
UserID int64 `json:"userID"`
Username string `json:"username"`
ServerID int64 `json:"serverID"`
ServerName string `json:"serverName"`
RemoteAddr string `json:"remoteAddr"`
UserAgent string `json:"userAgent"`
StartTime string `json:"startTime"`
EndTime string `json:"endTime"`
InsertTime string `json:"insertTime"`
UpdateTime string `json:"updateTime"`
}
type TerminalLogs []TerminalLog
func (tl TerminalLog) GetData() (TerminalLog, error) {
var terminalLog TerminalLog
err := sq.
Select(
"id",
"namespace_id",
"user_id",
"server_id",
"remote_addr",
"user_agent",
"start_time",
"end_time",
).
From(terminalLogTable).
Where(sq.Eq{"id": tl.ID}).
OrderBy("id DESC").
RunWith(DB).
QueryRow().
Scan(&terminalLog.ID,
&terminalLog.NamespaceID,
&terminalLog.UserID,
&terminalLog.ServerID,
&terminalLog.RemoteAddr,
&terminalLog.UserAgent,
&terminalLog.StartTime,
&terminalLog.EndTime,
)
if err != nil {
return terminalLog, err
}
return terminalLog, nil
}
func (tl TerminalLog) GetList(page, limit uint64) (TerminalLogs, error) {
builder := sq.
Select(
terminalLogTable+".id",
terminalLogTable+".namespace_id",
terminalLogTable+".user_id",
userTable+".name",
terminalLogTable+".server_id",
fmt.Sprintf("IFNULL(%s.name, '')", serverTable),
terminalLogTable+".remote_addr",
terminalLogTable+".user_agent",
terminalLogTable+".start_time",
terminalLogTable+".end_time",
).
From(terminalLogTable).
LeftJoin(fmt.Sprintf("%s ON %[1]s.id = %s.user_id", userTable, terminalLogTable)).
LeftJoin(fmt.Sprintf("%s ON %[1]s.id = %s.server_id", serverTable, terminalLogTable))
if tl.NamespaceID > 0 {
builder = builder.Where(sq.Eq{terminalLogTable + ".namespace_id": tl.NamespaceID})
}
if tl.Username != "" {
builder = builder.Where(sq.Eq{userTable + ".name": tl.Username})
}
if tl.ServerName != "" {
builder = builder.Where(sq.Eq{serverTable + ".name": tl.ServerName})
}
rows, err := builder.Limit(limit).Offset((page - 1) * limit).OrderBy("id DESC").
RunWith(DB).Query()
if err != nil {
return nil, err
}
terminalLogs := TerminalLogs{}
for rows.Next() {
var terminalLog TerminalLog
if err := rows.Scan(
&terminalLog.ID,
&terminalLog.NamespaceID,
&terminalLog.UserID,
&terminalLog.Username,
&terminalLog.ServerID,
&terminalLog.ServerName,
&terminalLog.RemoteAddr,
&terminalLog.UserAgent,
&terminalLog.StartTime,
&terminalLog.EndTime,
); err != nil {
return nil, err
}
terminalLogs = append(terminalLogs, terminalLog)
}
return terminalLogs, nil
}
func (tl TerminalLog) GetTotal() (int64, error) {
var total int64
builder := sq.Select("COUNT(*) AS count").From(terminalLogTable)
if tl.NamespaceID > 0 {
builder = builder.Where(sq.Eq{terminalLogTable + ".namespace_id": tl.NamespaceID})
}
if tl.Username != "" {
builder = builder.
LeftJoin(fmt.Sprintf("%s ON %[1]s.id = %s.user_id", userTable, terminalLogTable)).
Where(sq.Eq{userTable + ".name": tl.Username})
}
if tl.ServerName != "" {
builder = builder.
LeftJoin(fmt.Sprintf("%s ON %[1]s.id = %s.server_id", serverTable, terminalLogTable)).
Where(sq.Eq{serverTable + ".name": tl.ServerName})
}
err := builder.RunWith(DB).QueryRow().Scan(&total)
if err != nil {
return 0, err
}
return total, nil
}
func (tl TerminalLog) AddRow() (int64, error) {
result, err := sq.
Insert(terminalLogTable).
Columns("namespace_id", "user_id", "server_id", "remote_addr", "user_agent", "start_time").
Values(tl.NamespaceID, tl.UserID, tl.ServerID, tl.RemoteAddr, tl.UserAgent, tl.StartTime).
RunWith(DB).
Exec()
if err != nil {
return 0, err
}
id, err := result.LastInsertId()
return id, err
}
func (tl TerminalLog) EditRow() error {
_, err := sq.
Update(terminalLogTable).
SetMap(sq.Eq{
"end_time": tl.EndTime,
}).
Where(sq.Eq{"id": tl.ID}).
RunWith(DB).
Exec()
return err
}

View File

@ -12,11 +12,11 @@ import (
const userTable = "`user`"
// SuperManager super manager
const SuperManager = 1
// GeneralUser general user
const GeneralUser = 0
// user role
const (
SuperManager = 1
GeneralUser = 0
)
// User -
type User struct {

40
model/sql/1.4.6.sql Normal file
View File

@ -0,0 +1,40 @@
CREATE TABLE IF NOT EXISTS `login_log` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`account` varchar(30) NOT NULL DEFAULT '',
`remote_addr` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '',
`user_agent` varchar(255) NOT NULL DEFAULT '',
`referer` varchar(255) NOT NULL DEFAULT '',
`reason` varchar(2555) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '',
`login_time` datetime NOT NULL,
`insert_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
CREATE TABLE IF NOT EXISTS `sftp_log` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`namespace_id` int(10) unsigned NOT NULL DEFAULT '0',
`user_id` int(10) unsigned NOT NULL DEFAULT '0',
`server_id` int(10) unsigned NOT NULL DEFAULT '0',
`remote_addr` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '',
`user_agent` varchar(255) NOT NULL DEFAULT '',
`type` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'READ|PREVIEW|DOWNLOAD|UPLOAD',
`path` varchar(255) NOT NULL DEFAULT '',
`reason` varchar(2555) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '',
`insert_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
CREATE TABLE IF NOT EXISTS `terminal_log` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`namespace_id` int(10) unsigned NOT NULL DEFAULT '0',
`user_id` int(10) unsigned NOT NULL DEFAULT '0',
`server_id` int(10) unsigned NOT NULL DEFAULT '0',
`remote_addr` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '',
`user_agent` varchar(255) NOT NULL DEFAULT '',
`start_time` datetime NOT NULL,
`end_time` datetime NOT NULL DEFAULT '1970-01-01 00:00:00',
`insert_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

View File

@ -2,17 +2,6 @@ CREATE DATABASE IF NOT EXISTS `goploy`;
use `goploy`;
CREATE TABLE IF NOT EXISTS `log` (
`id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`type` tinyint(3) UNSIGNED NOT NULL DEFAULT 1 COMMENT 'log type',
`ip` int(10) UNSIGNED NOT NULL DEFAULT 0,
`desc` varchar(30) NOT NULL DEFAULT '' COMMENT 'description',
`user_id` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '',
`create_time` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '',
PRIMARY KEY (`id`) USING BTREE,
INDEX `idx_create_time`(`create_time`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci;
CREATE TABLE IF NOT EXISTS `project` (
`id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`namespace_id` int(10) UNSIGNED NOT NULL DEFAULT 0,
@ -284,8 +273,49 @@ CREATE TABLE IF NOT EXISTS `system_config` (
UNIQUE KEY `udx_key` (`key`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
CREATE TABLE IF NOT EXISTS `login_log` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`account` varchar(30) NOT NULL DEFAULT '',
`remote_addr` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '',
`user_agent` varchar(255) NOT NULL DEFAULT '',
`referer` varchar(255) NOT NULL DEFAULT '',
`reason` varchar(2555) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '',
`login_time` datetime NOT NULL,
`insert_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
CREATE TABLE IF NOT EXISTS `sftp_log` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`namespace_id` int(10) unsigned NOT NULL DEFAULT '0',
`user_id` int(10) unsigned NOT NULL DEFAULT '0',
`server_id` int(10) unsigned NOT NULL DEFAULT '0',
`remote_addr` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '',
`user_agent` varchar(255) NOT NULL DEFAULT '',
`type` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'READ|PREVIEW|DOWNLOAD|UPLOAD',
`path` varchar(255) NOT NULL DEFAULT '',
`reason` varchar(2555) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '',
`insert_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
CREATE TABLE IF NOT EXISTS `terminal_log` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`namespace_id` int(10) unsigned NOT NULL DEFAULT '0',
`user_id` int(10) unsigned NOT NULL DEFAULT '0',
`server_id` int(10) unsigned NOT NULL DEFAULT '0',
`remote_addr` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '',
`user_agent` varchar(255) NOT NULL DEFAULT '',
`start_time` datetime NOT NULL,
`end_time` datetime NOT NULL DEFAULT '1970-01-01 00:00:00',
`insert_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
INSERT IGNORE INTO `user`(`id`, `account`, `password`, `name`, `contact`, `state`, `super_manager`) VALUES (1, 'admin', '$2a$10$89ZJ2xeJj35GOw11Qiucr.phaEZP4.kBX6aKTs7oWFp1xcGBBgijm', '超管', '', 1, 1);
INSERT IGNORE INTO `namespace`(`id`, `name`) VALUES (1, 'goploy');
INSERT IGNORE INTO `namespace_user`(`id`, `namespace_id`, `user_id`, `role`) VALUES (1, 1, 1, 'admin');
INSERT IGNORE INTO `system_config` (`id`, `key`, `value`) VALUES (1, 'version', '1.4.5');
INSERT IGNORE INTO `system_config` (`id`, `key`, `value`) VALUES (1, 'version', '1.4.6');

View File

@ -19,6 +19,7 @@ const (
AccountDisabled = 10000
IllegalRequest = 10001
NamespaceInvalid = 10002
IllegalParam = 10003
LoginExpired = 10086
)

View File

@ -45,8 +45,5 @@ func (sf SftpFile) Write(w http.ResponseWriter) error {
w.Header().Set("Content-Length", strconv.FormatInt(fileStat.Size(), 10))
_, err = io.Copy(w, srcFile)
if err != nil {
return err
}
return nil
return err
}

View File

@ -18,6 +18,7 @@ func Init() {
rt.Add(controller.Monitor{})
rt.Add(controller.Deploy{})
rt.Add(controller.Server{})
rt.Add(controller.Log{})
rt.Add(controller.Cron{})
rt.Add(controller.Agent{})

View File

@ -3,6 +3,7 @@ package utils
import (
"encoding/json"
"os"
"path"
"time"
)
@ -75,8 +76,14 @@ func (recorder *Recorder) WriteData(data string) (err error) {
func NewRecorder(recordingPath, term string, h int, w int) (recorder *Recorder, err error) {
recorder = &Recorder{}
var file *os.File
file, err = os.Create(recordingPath)
if _, err := os.Stat(path.Dir(recordingPath)); err != nil {
if err := os.MkdirAll(path.Dir(recordingPath), os.ModePerm); err != nil {
return recorder, err
}
}
file, err := os.Create(recordingPath)
if err != nil {
return nil, err
}

206
web/src/api/log.ts Normal file
View File

@ -0,0 +1,206 @@
import { Request, Pagination, Total } from './types'
export class LoginLogData {
public datagram!: {
id: number
account: string
remoteAddr: string
userAgent: string
referer: string
reason: string
loginTime: string
}
}
export class LoginLogList extends Request {
readonly url = '/log/getLoginLogList'
readonly method = 'get'
public pagination: Pagination
public param: {
account: string
}
public datagram!: {
list: LoginLogData['datagram'][]
}
constructor(param: LoginLogList['param'], pagination: Pagination) {
super()
this.pagination = pagination
this.param = { ...param, ...pagination }
}
}
export class LoginLogTotal extends Request {
readonly url = '/log/getLoginLogTotal'
readonly method = 'get'
public param: {
account: string
}
public datagram!: Total
constructor(param: LoginLogTotal['param']) {
super()
this.param = param
}
}
export class SftpLogData {
public datagram!: {
id: number
namespaceId: number
userId: number
username: string
serverId: number
serverName: string
remoteAddr: string
userAgent: string
type: string
path: string
reason: string
}
}
export class SftpLogList extends Request {
readonly url = '/log/getSftpLogList'
readonly method = 'get'
public pagination: Pagination
public param: {
username: string
serverName: string
}
public datagram!: {
list: SftpLogData['datagram'][]
}
constructor(param: SftpLogList['param'], pagination: Pagination) {
super()
this.pagination = pagination
this.param = { ...param, ...pagination }
}
}
export class SftpLogTotal extends Request {
readonly url = '/log/getSftpLogTotal'
readonly method = 'get'
public param: {
username: string
serverName: string
}
public datagram!: Total
constructor(param: SftpLogTotal['param']) {
super()
this.param = param
}
}
export class TerminalLogData {
public datagram!: {
id: number
namespaceId: number
userId: number
username: string
serverId: number
serverName: string
remoteAddr: string
userAgent: string
startTime: string
endTime: string
}
}
export class TerminalLogList extends Request {
readonly url = '/log/getTerminalLogList'
readonly method = 'get'
public pagination: Pagination
public param: {
username: string
serverName: string
}
public datagram!: {
list: TerminalLogData['datagram'][]
}
constructor(param: TerminalLogList['param'], pagination: Pagination) {
super()
this.pagination = pagination
this.param = { ...param, ...pagination }
}
}
export class TerminalLogTotal extends Request {
readonly url = '/log/getTerminalLogTotal'
readonly method = 'get'
public param: {
username: string
serverName: string
}
public datagram!: Total
constructor(param: TerminalLogTotal['param']) {
super()
this.param = param
}
}
export class PublishLogData {
public datagram!: {
token: string
publisherId: number
publisherName: string
projectId: number
projectName: string
state: number
insertTime: string
}
}
export class PublishLogList extends Request {
readonly url = '/log/getPublishLogList'
readonly method = 'get'
public pagination: Pagination
public param: {
username: string
projectName: string
}
public datagram!: {
list: PublishLogData['datagram'][]
}
constructor(param: PublishLogList['param'], pagination: Pagination) {
super()
this.pagination = pagination
this.param = { ...param, ...pagination }
}
}
export class PublishLogTotal extends Request {
readonly url = '/log/getPublishLogTotal'
readonly method = 'get'
public param: {
username: string
projectName: string
}
public datagram!: Total
constructor(param: PublishLogTotal['param']) {
super()
this.param = param
}
}

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1645410396659" class="icon" viewBox="0 0 1056 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2389" xmlns:xlink="http://www.w3.org/1999/xlink" width="206.25" height="200"><defs><style type="text/css"></style></defs><path d="M10.955762 512.623801h80.286179a10.95574 10.95574 0 0 1 10.95574 10.976708v12.150911a10.95574 10.95574 0 0 1-10.95574 10.913805H10.955762A10.95574 10.95574 0 0 1 0.000023 535.709485v-12.108976a10.95574 10.95574 0 0 1 10.955739-10.976708z m0-340.309396h80.286179a10.95574 10.95574 0 0 1 10.95574 10.95574v12.182363a10.95574 10.95574 0 0 1-10.95574 10.95574H10.955762A10.95574 10.95574 0 0 1 0.000023 195.452508V183.270145a10.95574 10.95574 0 0 1 10.955739-10.95574z m0 170.207118h80.286179a10.95574 10.95574 0 0 1 10.95574 10.95574v12.150911a10.95574 10.95574 0 0 1-10.95574 10.95574H10.955762A10.95574 10.95574 0 0 1 0.000023 365.628174v-12.140427a10.95574 10.95574 0 0 1 10.955739-10.95574z m0 340.183588h80.286179a10.95574 10.95574 0 0 1 10.95574 10.95574v12.150912a10.95574 10.95574 0 0 1-10.95574 10.95574H10.955762A10.95574 10.95574 0 0 1 0.000023 705.811763v-12.140428a10.95574 10.95574 0 0 1 10.955739-10.95574z m-10.955739 0" p-id="2390"></path><path d="M815.06514 68.019954H755.159783a8.554913 8.554913 0 0 1-8.523461-7.579904A68.082847 68.082847 0 0 0 679.035737 0.00001H108.980804a76.606308 76.606308 0 0 0-76.63776 76.616792v50.826247a8.586365 8.586365 0 0 0 8.586365 8.586364h50.83673a42.65924 42.65924 0 0 1 42.690692 42.648756v25.476027a34.135779 34.135779 0 0 1-34.062391 34.062392H40.971345a8.586365 8.586365 0 0 0-8.628301 8.607332v50.836731a8.586365 8.586365 0 0 0 8.586365 8.586364h50.83673a42.65924 42.65924 0 0 1 42.65924 42.65924v16.889663a42.65924 42.65924 0 0 1-42.65924 42.65924H40.971345a8.586365 8.586365 0 0 0-8.586365 8.586364v50.826247a8.586365 8.586365 0 0 0 8.586365 8.586364h50.83673a42.65924 42.65924 0 0 1 42.65924 42.65924v25.455059a34.135779 34.135779 0 0 1-34.062391 34.072876H40.971345a8.586365 8.586365 0 0 0-8.586365 8.586364v50.952054a8.586365 8.586365 0 0 0 8.586365 8.586365h50.83673a42.65924 42.65924 0 0 1 42.65924 42.65924v25.24538a34.135779 34.135779 0 0 1-34.062391 34.062391H40.971345a8.586365 8.586365 0 0 0-8.628301 8.659752v119.034901a76.606308 76.606308 0 0 0 76.606308 76.606308h697.529423a76.606308 76.606308 0 0 0 76.606308-76.606308V136.039897a68.072363 68.072363 0 0 0-68.019943-68.009459z m-295.867883 84.689442c8.387169-14.174316 33.118835-20.496145 47.639122-12.507366l81.240219 45.500394c14.530771 8.219426 21.209054 32.164794 12.863821 46.33911L404.230133 665.783997a8.586365 8.586365 0 0 1-11.584777 3.145189l-126.751097-70.934485a8.586365 8.586365 0 0 1-3.145189-11.867845zM365.009633 708.894048l-103.067828 38.297912a8.586365 8.586365 0 0 1-11.532358-7.338774l-8.387169-100.56216a8.586365 8.586365 0 0 1 12.748497-8.208942l111.423545 62.264248a8.586365 8.586365 0 0 1-1.184687 15.547716z m450.055507 141.806065c0 37.522099-2.096792 68.009459-68.00946 68.00946H134.456831a68.145751 68.145751 0 0 1-67.29655-58.1126 8.617817 8.617817 0 0 1 8.502493-9.89686h594.671275a76.606308 76.606308 0 0 0 76.606308-76.606308V110.825969a8.607333 8.607333 0 0 1 8.701688-8.596848c33.42287 0.345971 59.307771 4.990366 59.307771 67.988491v680.482501z m0 0" p-id="2391"></path></svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@ -132,7 +132,12 @@
"crontab": "Crontab",
"serverCron": "Cron",
"namespace": "Namespace",
"member": "Member"
"member": "Member",
"log": "Log",
"loginLog": "Login log",
"publishLog": "Publish log",
"sftpLog": "SFTP log",
"terminalLog": "Terminal log"
},
"tagsView": {
"refresh": "Refresh",

View File

@ -132,7 +132,12 @@
"template": "模板设置",
"crontab": "Crontab管理",
"namespace": "空间设置",
"member": "成员设置"
"member": "成员设置",
"log": "日志记录",
"loginLog": "登录日志",
"publishLog": "构建日志",
"sftpLog": "SFTP日志",
"terminalLog": "终端日志"
},
"tagsView": {
"refresh": "刷新",

View File

@ -261,6 +261,59 @@ export const asyncRoutes: RouteRecordRaw[] = [
},
],
},
{
path: '/log',
component: Layout,
redirect: '/log/loginLog',
name: 'log',
meta: {
title: 'log',
icon: 'log',
roles: ['admin'],
},
children: [
{
path: 'loginLog',
name: 'LoginLog',
component: () => import('@/views/log/loginLog.vue'),
meta: {
title: 'loginLog',
icon: 'log',
roles: ['admin'],
},
},
{
path: 'publishLog',
name: 'PublishLog',
component: () => import('@/views/log/publishLog.vue'),
meta: {
title: 'publishLog',
icon: 'log',
roles: ['admin'],
},
},
{
path: 'sftpLog',
name: 'SftpLog',
component: () => import('@/views/log/sftpLog.vue'),
meta: {
title: 'sftpLog',
icon: 'log',
roles: ['admin'],
},
},
{
path: 'terminalLog',
name: 'TerminalLog',
component: () => import('@/views/log/terminalLog.vue'),
meta: {
title: 'terminalLog',
icon: 'log',
roles: ['admin'],
},
},
],
},
// 404 page must be placed at the end !!!
{
path: '/:pathMatch(.*)*',

View File

@ -60,4 +60,9 @@
&__fixed-right{
height: 100% !important;
}
}
.el-pagination .btn-prev .el-icon,
.el-pagination .btn-next .el-icon {
width: 100%;
}

View File

@ -43,7 +43,12 @@ export function getRole() {
export function getNamespace(): Namespace {
const n =
sessionStorage.getItem(NamespaceKey) || localStorage.getItem(NamespaceKey)
return n ? JSON.parse(n) : { id: 0, name: '', role: '' }
try {
return n ? JSON.parse(n) : { id: 0, name: '', role: '' }
} catch (e) {
return { id: 0, name: '', role: '' }
}
}
export function getNamespaceId(): number {

View File

@ -0,0 +1,110 @@
<template>
<el-row class="app-container">
<el-row class="app-bar" type="flex" justify="space-between">
<el-row>
<el-input
v-model="account"
style="width: 200px"
placeholder="Filter the account"
/>
<el-button
:loading="tableLoading"
type="primary"
icon="el-icon-search"
@click="searchList"
/>
</el-row>
</el-row>
<el-table
:key="tableHeight"
v-loading="tableLoading"
border
stripe
highlight-current-row
:max-height="tableHeight"
:data="tableData"
style="width: 100%"
>
<el-table-column prop="id" label="ID" width="80" />
<el-table-column prop="account" label="Account" width="80" />
<el-table-column prop="remoteAddr" label="Remote addr" width="160" />
<el-table-column prop="referer" label="Referer" width="200" />
<el-table-column
prop="userAgent"
label="User agent"
width="160"
show-overflow-tooltip
/>
<el-table-column prop="reason" label="Reason" show-overflow-tooltip />
<el-table-column prop="loginTime" label="Login time" width="135" />
</el-table>
<el-row type="flex" justify="end" style="margin-top: 10px; width: 100%">
<el-pagination
style=""
:total="pagination.total"
:page-size="pagination.rows"
background
layout="prev, pager, next"
@current-change="handlePageChange"
/>
</el-row>
</el-row>
</template>
<script lang="ts">
import { LoginLogList, LoginLogTotal } from '@/api/log'
import tableHeight from '@/mixin/tableHeight'
import { defineComponent } from 'vue'
export default defineComponent({
name: 'LoginLog',
mixins: [tableHeight],
data() {
return {
tableLoading: false,
account: '',
tableData: [] as LoginLogList['datagram']['list'],
pagination: {
page: 1,
rows: 19,
total: 0,
},
}
},
created() {
this.getList()
this.getTotal()
},
methods: {
searchList() {
this.pagination.page = 1
this.getList()
this.getTotal()
},
getList() {
this.tableLoading = true
this.tableData = []
new LoginLogList({ account: this.account }, this.pagination)
.request()
.then((response) => {
this.tableData = response.data.list
})
.finally(() => {
this.tableLoading = false
})
},
getTotal() {
new LoginLogTotal({ account: this.account })
.request()
.then((response) => {
this.pagination.total = response.data.total
})
},
handlePageChange(val = 1) {
this.pagination.page = val
this.getList()
},
},
})
</script>

View File

@ -0,0 +1,371 @@
<template>
<el-row class="app-container">
<el-row class="app-bar" type="flex" justify="space-between">
<el-row>
<el-input
v-model="searchParam.username"
style="width: 200px"
placeholder="Filter the username"
/>
<el-input
v-model="searchParam.projectName"
style="width: 200px"
placeholder="Filter the project name"
/>
<el-button
:loading="tableLoading"
type="primary"
icon="el-icon-search"
@click="searchList"
/>
</el-row>
</el-row>
<el-table
:key="tableHeight"
v-loading="tableLoading"
border
stripe
highlight-current-row
:max-height="tableHeight"
:data="tableData"
style="width: 100%"
>
<el-table-column prop="token" label="Token" width="300" />
<el-table-column prop="publisherName" label="Username" width="80" />
<el-table-column prop="projectName" label="Project Name" width="160" />
<el-table-column prop="state" label="State" align="center" width="80">
<template #default="scope">
<span v-if="scope.row.state === 1" style="color: #67c23a">
{{ $t('success') }}
</span>
<span v-else style="color: #f56c6c">{{ $t('fail') }}</span>
</template>
</el-table-column>
<el-table-column prop="detail" label="Reason" show-overflow-tooltip />
<el-table-column prop="insertTime" label="insertTime" width="135" />
<el-table-column
prop="operation"
:label="$t('op')"
width="100"
align="center"
:fixed="$store.state.app.device === 'mobile' ? false : 'right'"
>
<template #default="scope">
<el-button type="text" @click="handleDetail(scope.row)">
{{ $t('detail') }}
</el-button>
</template>
</el-table-column>
</el-table>
<el-row type="flex" justify="end" style="margin-top: 10px; width: 100%">
<el-pagination
style=""
:total="pagination.total"
:page-size="pagination.rows"
background
layout="prev, pager, next"
@current-change="handlePageChange"
/>
</el-row>
<el-dialog
v-model="dialogVisible"
:title="$t('detail')"
:fullscreen="$store.state.app.device === 'mobile'"
>
<el-row v-loading="traceLoading" class="project-detail">
<div
v-for="(item, index) in publishLocalTraceList"
:key="index"
style="width: 100%"
>
<template v-if="item.type === 2">
<el-row style="margin: 5px 0">
<i v-if="item.state === 1" class="el-icon-check icon-success" />
<i v-else class="el-icon-close icon-fail" />
-------------GIT-------------
</el-row>
<el-row>Time: {{ item.updateTime }}</el-row>
<template v-if="item.state !== 0">
<el-row>Branch: {{ item['branch'] }}</el-row>
<el-row>Commit:{{ item['commit'] }}</el-row>
<el-row>Message: {{ item['message'] }}</el-row>
<el-row>Author: {{ item['author'] }}</el-row>
<el-row>
Datetime:
{{ item['timestamp'] ? parseTime(item['timestamp']) : '' }}
</el-row>
<el-row>
<span style="white-space: pre-line">{{ item['diff'] }}</span>
</el-row>
</template>
<el-row v-else style="margin: 5px 0">
<span style="white-space: pre-line; padding: 5px 0">
{{ item.detail }}
</span>
</el-row>
</template>
<div v-if="item.type === 3">
<hr />
<el-row align="middle">
<i v-if="item.state === 1" class="el-icon-check icon-success" />
<i v-else class="el-icon-close icon-fail" />
--------After pull--------
</el-row>
<el-row>Time: {{ item.updateTime }}</el-row>
<el-row style="width: 100%">
<div>Script:</div>
<pre style="white-space: pre-line">{{ item.script }}</pre>
</el-row>
<div v-loading="traceDetail[item.id] === ''" style="margin: 5px 0">
<span style="padding: 5px 0">[goploy ~]#</span>
<el-button
v-if="item.state === 1 && !(item.id in traceDetail)"
type="text"
@click="getPublishTraceDetail(item)"
>
{{ $t('deployPage.showDetail') }}
</el-button>
<div v-else style="white-space: pre-line; padding: 5px 0">
{{ traceDetail[item.id] }}
</div>
</div>
</div>
</div>
<el-tabs v-model="activeRomoteTracePane">
<el-tab-pane
v-for="(item, serverName) in publishRemoteTraceList"
:key="serverName"
:label="serverName"
:name="serverName"
>
<div v-for="(trace, key) in item" :key="key">
<template v-if="trace.type === 4">
<el-row style="margin: 5px 0">
<i
v-if="trace.state === 1"
class="el-icon-check icon-success"
/>
<i v-else class="el-icon-close icon-fail" />
---------Before deploy---------
</el-row>
<el-row style="margin: 5px 0">
Time: {{ trace.updateTime }}
</el-row>
<el-row>
Script:
<pre style="white-space: pre-line">{{ trace.script }}</pre>
</el-row>
<div v-loading="traceDetail[trace.id] === ''">
<span style="padding: 5px 0">[goploy ~]#</span>
<el-button
v-if="trace.state === 1 && !(trace.id in traceDetail)"
type="text"
@click="getPublishTraceDetail(trace)"
>
{{ $t('deployPage.showDetail') }}
</el-button>
<div v-else style="white-space: pre-line; padding: 5px 0">
{{ traceDetail[trace.id] }}
</div>
</div>
</template>
<template v-else-if="trace.type === 5">
<el-row style="margin: 5px 0">
<i
v-if="trace.state === 1"
class="el-icon-check icon-success"
/>
<i v-else class="el-icon-close icon-fail" />
-----------Rsync------------
</el-row>
<el-row style="margin: 5px 0">
Time: {{ trace.updateTime }}
</el-row>
<el-row>Command: {{ trace.command }}</el-row>
<div v-loading="traceDetail[trace.id] === ''">
<span style="padding: 5px 0">[goploy ~]#</span>
<el-button
v-if="trace.state === 1 && !(trace.id in traceDetail)"
type="text"
@click="getPublishTraceDetail(trace)"
>
{{ $t('deployPage.showDetail') }}
</el-button>
<div v-else style="white-space: pre-line; padding: 5px 0">
{{ traceDetail[trace.id] }}
</div>
</div>
</template>
<template v-else-if="trace.type === 6">
<el-row style="margin: 5px 0">
<i
v-if="trace.state === 1"
class="el-icon-check icon-success"
/>
<i v-else class="el-icon-close icon-fail" />
--------After deploy--------
</el-row>
<el-row style="margin: 5px 0">
Time: {{ trace.updateTime }}
</el-row>
<el-row>Script: {{ trace.script }}</el-row>
<div
v-loading="traceDetail[trace.id] === ''"
style="margin: 5px 0"
>
<span>[goploy ~]#</span>
<el-button
v-if="trace.state === 1 && !(trace.id in traceDetail)"
type="text"
@click="getPublishTraceDetail(trace)"
>
{{ $t('deployPage.showDetail') }}
</el-button>
<div v-else style="white-space: pre-line; padding: 5px 0">
{{ traceDetail[trace.id] }}
</div>
</div>
</template>
</div>
</el-tab-pane>
</el-tabs>
</el-row>
</el-dialog>
</el-row>
</template>
<script lang="ts">
import { PublishLogData, PublishLogList, PublishLogTotal } from '@/api/log'
import { DeployTrace, DeployTraceDetail, PublishTraceData } from '@/api/deploy'
import { parseTime } from '@/utils'
import tableHeight from '@/mixin/tableHeight'
import { defineComponent } from 'vue'
export default defineComponent({
name: 'PublishLog',
mixins: [tableHeight],
data() {
return {
dialogVisible: false,
tableLoading: false,
searchParam: {
username: '',
projectName: '',
},
tableData: [] as PublishLogList['datagram']['list'],
pagination: {
page: 1,
rows: 17,
total: 0,
},
traceLoading: false,
traceDetail: {} as Record<number, string>,
activeRomoteTracePane: '',
publishLocalTraceList: [] as DeployTrace['datagram']['list'],
publishRemoteTraceList: {} as Record<
string,
DeployTrace['datagram']['list']
>,
}
},
created() {
this.getList()
this.getTotal()
},
methods: {
parseTime,
searchList() {
this.pagination.page = 1
this.getList()
this.getTotal()
},
getList() {
this.tableLoading = true
this.tableData = []
new PublishLogList(this.searchParam, this.pagination)
.request()
.then((response) => {
this.tableData = response.data.list
})
.finally(() => {
this.tableLoading = false
})
},
getTotal() {
new PublishLogTotal(this.searchParam).request().then((response) => {
this.pagination.total = response.data.total
})
},
handlePageChange(val = 1) {
this.pagination.page = val
this.getList()
},
handleDetail(data: PublishLogData['datagram']) {
this.dialogVisible = true
this.traceLoading = true
new DeployTrace({ lastPublishToken: data.token })
.request()
.then((response) => {
const publishTraceList = response.data.list.map((element) => {
if (element.ext !== '') {
Object.assign(element, JSON.parse(element.ext))
}
return element
})
this.publishLocalTraceList = publishTraceList.filter(
(element) => element.type < 4
)
this.publishRemoteTraceList = {}
for (const trace of publishTraceList) {
if (trace.detail !== '') {
this.traceDetail[trace.id] = trace.detail
}
if (trace.type < 4) continue
if (!this.publishRemoteTraceList[trace.serverName]) {
this.publishRemoteTraceList[trace.serverName] = []
}
this.publishRemoteTraceList[trace.serverName].push(trace)
}
this.activeRomoteTracePane = Object.keys(
this.publishRemoteTraceList
)[0]
})
.finally(() => {
this.traceLoading = false
})
},
getPublishTraceDetail(data: PublishTraceData['datagram']) {
this.traceDetail[data.id] = ''
new DeployTraceDetail({ id: data.id }).request().then((response) => {
this.traceDetail[data.id] =
response.data.detail === ''
? this.$t('deployPage.noDetail')
: response.data.detail
})
},
},
})
</script>
<style scoped lang="scss">
@import '@/styles/mixin.scss';
.icon-success {
color: #67c23a;
font-size: 14px;
font-weight: 900;
}
.icon-fail {
color: #f56c6c;
font-size: 14px;
font-weight: 900;
}
.project-detail {
padding-left: 5px;
height: 470px;
overflow-y: auto;
@include scrollBar();
}
</style>

View File

@ -0,0 +1,117 @@
<template>
<el-row class="app-container">
<el-row class="app-bar" type="flex" justify="space-between">
<el-row>
<el-input
v-model="searchParam.username"
style="width: 200px"
placeholder="Filter the username"
/>
<el-input
v-model="searchParam.serverName"
style="width: 200px"
placeholder="Filter the server name"
/>
<el-button
:loading="tableLoading"
type="primary"
icon="el-icon-search"
@click="searchList"
/>
</el-row>
</el-row>
<el-table
:key="tableHeight"
v-loading="tableLoading"
border
stripe
highlight-current-row
:max-height="tableHeight"
:data="tableData"
style="width: 100%"
>
<el-table-column prop="id" label="ID" width="80" />
<el-table-column prop="username" label="Username" width="80" />
<el-table-column prop="serverName" label="Server Name" width="160" />
<el-table-column prop="remoteAddr" label="Remote addr" width="160" />
<el-table-column
prop="userAgent"
label="User agent"
width="160"
show-overflow-tooltip
/>
<el-table-column prop="type" label="Type" width="100" />
<el-table-column prop="path" label="Path" show-overflow-tooltip />
<el-table-column prop="reason" label="Reason" show-overflow-tooltip />
</el-table>
<el-row type="flex" justify="end" style="margin-top: 10px; width: 100%">
<el-pagination
style=""
:total="pagination.total"
:page-size="pagination.rows"
background
layout="prev, pager, next"
@current-change="handlePageChange"
/>
</el-row>
</el-row>
</template>
<script lang="ts">
import { SftpLogList, SftpLogTotal } from '@/api/log'
import tableHeight from '@/mixin/tableHeight'
import { defineComponent } from 'vue'
export default defineComponent({
name: 'SftpLog',
mixins: [tableHeight],
data() {
return {
tableLoading: false,
searchParam: {
username: '',
serverName: '',
},
tableData: [] as SftpLogList['datagram']['list'],
pagination: {
page: 1,
rows: 19,
total: 0,
},
}
},
created() {
this.getList()
this.getTotal()
},
methods: {
searchList() {
this.pagination.page = 1
this.getList()
this.getTotal()
},
getList() {
this.tableLoading = true
this.tableData = []
new SftpLogList(this.searchParam, this.pagination)
.request()
.then((response) => {
this.tableData = response.data.list
})
.finally(() => {
this.tableLoading = false
})
},
getTotal() {
new SftpLogTotal(this.searchParam).request().then((response) => {
this.pagination.total = response.data.total
})
},
handlePageChange(val = 1) {
this.pagination.page = val
this.getList()
},
},
})
</script>

View File

@ -0,0 +1,177 @@
<template>
<el-row class="app-container">
<el-row v-if="recordViewer" class="terminal">
<div class="close" @click="closeRecordViewer">×</div>
<div ref="record" style="width: 100%; height: 100%"></div>
</el-row>
<el-row v-show="!recordViewer">
<el-row class="app-bar" type="flex" justify="space-between">
<el-row>
<el-input
v-model="searchParam.username"
style="width: 200px"
placeholder="Filter the username"
/>
<el-input
v-model="searchParam.serverName"
style="width: 200px"
placeholder="Filter the server name"
/>
<el-button
:loading="tableLoading"
type="primary"
icon="el-icon-search"
@click="searchList"
/>
</el-row>
</el-row>
<el-table
:key="tableHeight"
v-loading="tableLoading"
border
stripe
highlight-current-row
:max-height="tableHeight"
:data="tableData"
style="width: 100%"
>
<el-table-column prop="id" label="ID" width="80" />
<el-table-column prop="username" label="Username" width="80" />
<el-table-column prop="serverName" label="Server Name" width="160" />
<el-table-column prop="remoteAddr" label="Remote addr" width="160" />
<el-table-column
prop="userAgent"
label="User agent"
show-overflow-tooltip
/>
<el-table-column prop="startTime" label="Start time" width="135" />
<el-table-column prop="endTime" label="End time" width="135" />
<el-table-column
prop="operation"
:label="$t('op')"
width="100"
align="center"
:fixed="$store.state.app.device === 'mobile' ? false : 'right'"
>
<template #default="scope">
<el-button type="text" @click="handleRecord(scope.row)">
Record
</el-button>
</template>
</el-table-column>
</el-table>
<el-row type="flex" justify="end" style="margin-top: 10px; width: 100%">
<el-pagination
style=""
:total="pagination.total"
:page-size="pagination.rows"
background
layout="prev, pager, next"
@current-change="handlePageChange"
/>
</el-row>
</el-row>
</el-row>
</template>
<script lang="ts">
import 'asciinema-player/dist/bundle/asciinema-player.css'
import * as AsciinemaPlayer from 'asciinema-player'
import { TerminalLogData, TerminalLogList, TerminalLogTotal } from '@/api/log'
import { NamespaceKey, getNamespaceId } from '@/utils/namespace'
import tableHeight from '@/mixin/tableHeight'
import { defineComponent } from 'vue'
export default defineComponent({
name: 'TerminalLog',
mixins: [tableHeight],
data() {
return {
recordViewer: false,
tableLoading: false,
searchParam: {
username: '',
serverName: '',
},
tableData: [] as TerminalLogList['datagram']['list'],
pagination: {
page: 1,
rows: 19,
total: 0,
},
}
},
created() {
this.getList()
this.getTotal()
},
methods: {
searchList() {
this.pagination.page = 1
this.getList()
this.getTotal()
},
getList() {
this.tableLoading = true
this.tableData = []
new TerminalLogList(this.searchParam, this.pagination)
.request()
.then((response) => {
this.tableData = response.data.list
})
.finally(() => {
this.tableLoading = false
})
},
getTotal() {
new TerminalLogTotal(this.searchParam).request().then((response) => {
this.pagination.total = response.data.total
})
},
handlePageChange(val = 1) {
this.pagination.page = val
this.getList()
},
handleRecord(data: TerminalLogData['datagram']) {
this.recordViewer = true
const castUrl = `${location.origin}${
import.meta.env.VITE_APP_BASE_API
}/log/getTerminalRecord?${NamespaceKey}=${getNamespaceId()}&recordId=${
data.id
}`
this.$nextTick(() => {
AsciinemaPlayer.create(castUrl, this.$refs['record'], {
fit: false,
fontSize: '14px',
})
})
},
closeRecordViewer() {
this.recordViewer = false
},
},
})
</script>
<style lang="scss" scoped>
.terminal {
height: calc(100vh - 124px);
width: 100%;
padding: 10px;
.close {
position: absolute;
top: 20px;
right: 30px;
color: #fff;
z-index: 1000;
font-size: 18px;
cursor: pointer;
}
}
</style>
<style>
.asciinema-player {
width: 100%;
}
</style>

View File

@ -63,7 +63,7 @@
</template>
</el-table-column>
</el-table>
<el-row type="flex" justify="end" style="margin-top: 10px">
<el-row type="flex" justify="end" style="margin-top: 10px; width: 100%">
<el-pagination
hide-on-single-page
:total="pagination.total"
@ -158,7 +158,7 @@ export default defineComponent({
tempFormData: {},
pagination: {
page: 1,
rows: 16,
rows: 18,
total: 0,
},
formProps: {

View File

@ -103,7 +103,7 @@
</template>
</el-table-column>
</el-table>
<el-row type="flex" justify="end" style="margin-top: 10px">
<el-row type="flex" justify="end" style="margin-top: 10px; width: 100%">
<el-pagination
hide-on-single-page
:total="pagination.total"

View File

@ -61,7 +61,7 @@
</template>
</el-table-column>
</el-table>
<el-row type="flex" justify="end" style="margin-top: 10px">
<el-row type="flex" justify="end" style="margin-top: 10px; width: 100%">
<el-pagination
hide-on-single-page
:total="pagination.total"

View File

@ -151,7 +151,7 @@
</template>
</el-table-column>
</el-table>
<el-row type="flex" justify="end" style="margin-top: 10px">
<el-row type="flex" justify="end" style="margin-top: 10px; width: 100%">
<el-pagination
hide-on-single-page
:total="pagination.total"

View File

@ -96,7 +96,7 @@
</template>
</el-table-column>
</el-table>
<el-row type="flex" justify="end" style="margin-top: 10px">
<el-row type="flex" justify="end" style="margin-top: 10px; width: 100%">
<el-pagination
hide-on-single-page
:total="pagination.total"

View File

@ -101,7 +101,7 @@
</template>
</el-table-column>
</el-table>
<el-row type="flex" justify="end" style="margin-top: 10px">
<el-row type="flex" justify="end" style="margin-top: 10px; width: 100%">
<el-pagination
hide-on-single-page
:total="pagination.total"

View File

@ -400,7 +400,7 @@ export default defineComponent({
this.fileListLoading = true
this.selectedFile = {} as file
this.dir = path.normalize(target)
this.ws?.send(target)
this.ws?.send(this.dir)
},
dirOpen(dir: string) {

View File

@ -123,6 +123,20 @@ func (hub *Hub) Sftp(gp *core.Goploy) core.Response {
core.Log(core.ERROR, err.Error())
break
}
// sftp log
if err := (model.SftpLog{
NamespaceID: gp.Namespace.ID,
UserID: gp.UserInfo.ID,
ServerID: serverID,
RemoteAddr: gp.Request.RemoteAddr,
UserAgent: gp.Request.UserAgent(),
Type: model.SftpLogTypeRead,
Path: string(message),
Reason: msg,
}.AddRow()); err != nil {
core.Log(core.ERROR, err.Error())
}
}
}

View File

@ -6,6 +6,7 @@ import (
"github.com/zhenorzz/goploy/core"
"github.com/zhenorzz/goploy/model"
"github.com/zhenorzz/goploy/response"
"github.com/zhenorzz/goploy/utils"
"golang.org/x/crypto/ssh"
"net/http"
"strconv"
@ -101,11 +102,31 @@ func (hub *Hub) Xterm(gp *core.Goploy) core.Response {
return response.Empty{}
}
//recorder, _ := utils.NewRecorder(path.Join(core.GetLogPath(), "demo.cast"), "xterm", rows, cols)
//defer recorder.Close()
// terminal log
tlID, err := model.TerminalLog{
NamespaceID: gp.Namespace.ID,
UserID: gp.UserInfo.ID,
ServerID: serverID,
RemoteAddr: gp.Request.RemoteAddr,
UserAgent: gp.Request.UserAgent(),
StartTime: time.Now().Format("20060102150405"),
}.AddRow()
if err != nil {
_ = c.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, err.Error()))
return response.Empty{}
}
var recorder *utils.Recorder
recorder, err = utils.NewRecorder(core.GetTerminalLogPath(tlID), "xterm", rows, cols)
if err != nil {
core.Log(core.ERROR, err.Error())
} else {
defer recorder.Close()
}
ticker := time.NewTicker(pingPeriod)
defer ticker.Stop()
flushMessageTick := time.NewTicker(time.Millisecond * time.Duration(120))
flushMessageTick := time.NewTicker(time.Millisecond * time.Duration(50))
defer flushMessageTick.Stop()
stop := make(chan bool, 1)
defer func() {
@ -126,7 +147,11 @@ func (hub *Hub) Xterm(gp *core.Goploy) core.Response {
c.Close()
return
}
//recorder.WriteData(comboWriter.buffer.String())
if recorder != nil {
if err := recorder.WriteData(comboWriter.buffer.String()); err != nil {
core.Log(core.ERROR, err.Error())
}
}
comboWriter.buffer.Reset()
}
case <-stop:
@ -151,5 +176,12 @@ func (hub *Hub) Xterm(gp *core.Goploy) core.Response {
}
}
if err := (model.TerminalLog{
ID: tlID,
EndTime: time.Now().Format("20060102150405"),
}.EditRow()); err != nil {
core.Log(core.ERROR, err.Error())
}
return response.Empty{}
}