mirror of https://github.com/bjdgyc/anylink.git
Merge remote-tracking branch 'origin/dev' into dev
This commit is contained in:
commit
24fc03f378
|
@ -51,3 +51,29 @@ func SetAuditExport(w http.ResponseWriter, r *http.Request) {
|
|||
gocsv.Marshal(datas, w)
|
||||
|
||||
}
|
||||
|
||||
func UserActLogList(w http.ResponseWriter, r *http.Request) {
|
||||
_ = r.ParseForm()
|
||||
pageS := r.FormValue("page")
|
||||
page, _ := strconv.Atoi(pageS)
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
var datas []dbdata.UserActLog
|
||||
session := dbdata.UserActLogIns.GetSession(r.Form)
|
||||
count, err := dbdata.FindAndCount(session, &datas, dbdata.PageSize, page)
|
||||
if err != nil && !dbdata.CheckErrNotFound(err) {
|
||||
RespError(w, RespInternalErr, err)
|
||||
return
|
||||
}
|
||||
data := map[string]interface{}{
|
||||
"count": count,
|
||||
"page_size": dbdata.PageSize,
|
||||
"datas": datas,
|
||||
"statusOps": dbdata.UserActLogIns.GetStatusOpsWithTag(),
|
||||
"osOps": dbdata.UserActLogIns.OsOps,
|
||||
"clientOps": dbdata.UserActLogIns.ClientOps,
|
||||
}
|
||||
|
||||
RespSucess(w, data)
|
||||
}
|
||||
|
|
|
@ -178,7 +178,7 @@ func UserOnline(w http.ResponseWriter, r *http.Request) {
|
|||
func UserOffline(w http.ResponseWriter, r *http.Request) {
|
||||
_ = r.ParseForm()
|
||||
token := r.FormValue("token")
|
||||
sessdata.CloseSess(token)
|
||||
sessdata.CloseSess(token, dbdata.UserLogoutAdmin)
|
||||
RespSucess(w, nil)
|
||||
}
|
||||
|
||||
|
|
|
@ -45,6 +45,7 @@ func StartAdmin() {
|
|||
r.HandleFunc("/set/other/audit_log/edit", SetOtherAuditLogEdit)
|
||||
r.HandleFunc("/set/audit/list", SetAuditList)
|
||||
r.HandleFunc("/set/audit/export", SetAuditExport)
|
||||
r.HandleFunc("/set/audit/act_log_list", UserActLogList)
|
||||
|
||||
r.HandleFunc("/user/list", UserList)
|
||||
r.HandleFunc("/user/detail", UserDetail)
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
package cron
|
||||
|
||||
import (
|
||||
"github.com/bjdgyc/anylink/base"
|
||||
"github.com/bjdgyc/anylink/dbdata"
|
||||
)
|
||||
|
||||
// 清除用户活动日志
|
||||
func ClearUserActLog() {
|
||||
lifeDay, timesUp := isClearTime()
|
||||
if !timesUp {
|
||||
return
|
||||
}
|
||||
// 当审计日志永久保存时,则退出
|
||||
if lifeDay <= 0 {
|
||||
return
|
||||
}
|
||||
affected, err := dbdata.UserActLogIns.ClearUserActLog(getTimeAgo(lifeDay))
|
||||
base.Info("Cron ClearUserActLog: ", affected, err)
|
||||
}
|
|
@ -11,6 +11,7 @@ func Start() {
|
|||
s := gocron.NewScheduler(time.Local)
|
||||
s.Cron("0 * * * *").Do(ClearAudit)
|
||||
s.Cron("0 * * * *").Do(ClearStatsInfo)
|
||||
s.Cron("0 * * * *").Do(ClearUserActLog)
|
||||
s.Every(1).Day().At("00:00").Do(sessdata.CloseUserLimittimeSession)
|
||||
s.StartAsync()
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ type SearchCon struct {
|
|||
AccessProto string `json:"access_proto"`
|
||||
Date []string `json:"date"`
|
||||
Info string `json:"info"`
|
||||
Sort int `json:"sort"`
|
||||
}
|
||||
|
||||
func GetAuditSession(search string) *xorm.Session {
|
||||
|
@ -47,6 +48,11 @@ func GetAuditSession(search string) *xorm.Session {
|
|||
if searchData.Info != "" {
|
||||
session.And("info LIKE ?", "%"+searchData.Info+"%")
|
||||
}
|
||||
if searchData.Sort == 1 {
|
||||
session.OrderBy("id desc")
|
||||
} else {
|
||||
session.OrderBy("id asc")
|
||||
}
|
||||
return session
|
||||
}
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@ func initDb() {
|
|||
}
|
||||
|
||||
// 初始化数据库
|
||||
err = xdb.Sync2(&User{}, &Setting{}, &Group{}, &IpMap{}, &AccessAudit{}, &Policy{}, &StatsNetwork{}, &StatsCpu{}, &StatsMem{}, &StatsOnline{})
|
||||
err = xdb.Sync2(&User{}, &Setting{}, &Group{}, &IpMap{}, &AccessAudit{}, &Policy{}, &StatsNetwork{}, &StatsCpu{}, &StatsMem{}, &StatsOnline{}, &UserActLog{})
|
||||
if err != nil {
|
||||
base.Fatal(err)
|
||||
}
|
||||
|
|
|
@ -40,6 +40,20 @@ type User struct {
|
|||
UpdatedAt time.Time `json:"updated_at" xorm:"DateTime updated"`
|
||||
}
|
||||
|
||||
type UserActLog struct {
|
||||
Id int `json:"id" xorm:"pk autoincr not null"`
|
||||
Username string `json:"username" xorm:"varchar(60)"`
|
||||
GroupName string `json:"group_name" xorm:"varchar(60)"`
|
||||
IpAddr string `json:"ip_addr" xorm:"varchar(32)"`
|
||||
RemoteAddr string `json:"remote_addr" xorm:"varchar(32)"`
|
||||
Os uint8 `json:"os" xorm:"not null default 0 Int"`
|
||||
Client uint8 `json:"client" xorm:"not null default 0 Int"`
|
||||
Version string `json:"version" xorm:"varchar(15)"`
|
||||
Status uint8 `json:"status" xorm:"not null default 0 Int"`
|
||||
Info string `json:"info" xorm:"varchar(255) not null default ''"` // 详情
|
||||
CreatedAt time.Time `json:"created_at" xorm:"DateTime created"`
|
||||
}
|
||||
|
||||
type IpMap struct {
|
||||
Id int `json:"id" xorm:"pk autoincr not null"`
|
||||
IpAddr string `json:"ip_addr" xorm:"varchar(32) not null unique"`
|
||||
|
|
|
@ -0,0 +1,184 @@
|
|||
package dbdata
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/ivpusic/grpool"
|
||||
"github.com/spf13/cast"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
const (
|
||||
UserAuthFail = 0 // 认证失败
|
||||
UserAuthSuccess = 1 // 认证成功
|
||||
UserConnected = 2 // 连线成功
|
||||
UserLogout = 3 // 用户登出
|
||||
UserLogoutLose = 0 // 用户掉线
|
||||
UserLogoutBanner = 1 // 用户banner弹窗取消
|
||||
UserLogoutClient = 2 // 用户主动登出
|
||||
UserLogoutTimeout = 3 // 用户超时登出
|
||||
UserLogoutAdmin = 4 // 账号被管理员踢下线
|
||||
UserLogoutExpire = 5 // 账号过期被踢下线
|
||||
)
|
||||
|
||||
type UserActLogProcess struct {
|
||||
Pool *grpool.Pool
|
||||
StatusOps []string
|
||||
OsOps []string
|
||||
ClientOps []string
|
||||
InfoOps []string
|
||||
}
|
||||
|
||||
var (
|
||||
UserActLogIns = &UserActLogProcess{
|
||||
Pool: grpool.NewPool(1, 100),
|
||||
StatusOps: []string{ // 操作类型
|
||||
UserAuthFail: "认证失败",
|
||||
UserAuthSuccess: "认证成功",
|
||||
UserConnected: "连接成功",
|
||||
UserLogout: "用户登出",
|
||||
},
|
||||
OsOps: []string{ // 操作系统
|
||||
0: "Windows",
|
||||
1: "macOS",
|
||||
2: "Linux",
|
||||
3: "Android",
|
||||
4: "iOS",
|
||||
5: "Unknown",
|
||||
},
|
||||
ClientOps: []string{ // 客户端
|
||||
0: "AnyConnect",
|
||||
1: "OpenConnect",
|
||||
2: "Unknown",
|
||||
},
|
||||
InfoOps: []string{ // 信息
|
||||
UserLogoutLose: "用户掉线",
|
||||
UserLogoutBanner: "用户取消弹窗",
|
||||
UserLogoutClient: "用户/客户端主动断开",
|
||||
UserLogoutTimeout: "Session过期被踢下线",
|
||||
UserLogoutAdmin: "账号被管理员踢下线",
|
||||
UserLogoutExpire: "账号过期被踢下线",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
// 异步写入用户操作日志
|
||||
func (ua *UserActLogProcess) Add(u UserActLog, userAgent string) {
|
||||
// os, client, ver
|
||||
os_idx, client_idx, ver := ua.ParseUserAgent(userAgent)
|
||||
u.Os = os_idx
|
||||
u.Client = client_idx
|
||||
u.Version = ver
|
||||
u.RemoteAddr = strings.Split(u.RemoteAddr, ":")[0]
|
||||
// remove extra characters
|
||||
infoSlice := strings.Split(u.Info, " ")
|
||||
infoLen := len(infoSlice)
|
||||
if infoLen > 1 {
|
||||
if u.Username == infoSlice[0] {
|
||||
u.Info = strings.Join(infoSlice[1:], " ")
|
||||
}
|
||||
// delete - char
|
||||
if infoLen > 2 && infoSlice[1] == "-" {
|
||||
u.Info = u.Info[2:]
|
||||
}
|
||||
}
|
||||
UserActLogIns.Pool.JobQueue <- func() {
|
||||
_ = Add(u)
|
||||
}
|
||||
}
|
||||
|
||||
// 转义操作类型, 方便vue显示
|
||||
func (ua *UserActLogProcess) GetStatusOpsWithTag() interface{} {
|
||||
type StatusTag struct {
|
||||
Key int `json:"key"`
|
||||
Value string `json:"value"`
|
||||
Tag string `json:"tag"`
|
||||
}
|
||||
var res []StatusTag
|
||||
for k, v := range ua.StatusOps {
|
||||
tag := "info"
|
||||
switch k {
|
||||
case UserAuthFail:
|
||||
tag = "danger"
|
||||
case UserAuthSuccess:
|
||||
tag = "success"
|
||||
case UserConnected:
|
||||
tag = ""
|
||||
}
|
||||
res = append(res, StatusTag{k, v, tag})
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (ua *UserActLogProcess) GetInfoOpsById(id uint8) string {
|
||||
return ua.InfoOps[id]
|
||||
}
|
||||
|
||||
func (ua *UserActLogProcess) ParseUserAgent(userAgent string) (os_idx, client_idx uint8, ver string) {
|
||||
// Unknown
|
||||
if len(userAgent) == 0 {
|
||||
return 5, 2, ""
|
||||
}
|
||||
// os
|
||||
os_idx = 2
|
||||
if strings.Contains(userAgent, "windows") {
|
||||
os_idx = 0
|
||||
} else if strings.Contains(userAgent, "mac os") || strings.Contains(userAgent, "darwin_i386") {
|
||||
os_idx = 1
|
||||
} else if strings.Contains(userAgent, "darwin_arm") || strings.Contains(userAgent, "apple") {
|
||||
os_idx = 4
|
||||
} else if strings.Contains(userAgent, "android") {
|
||||
os_idx = 3
|
||||
}
|
||||
// client
|
||||
client_idx = 2
|
||||
if strings.Contains(userAgent, "anyconnect") {
|
||||
client_idx = 0
|
||||
} else if strings.Contains(userAgent, "openconnect") {
|
||||
client_idx = 1
|
||||
}
|
||||
// ver
|
||||
uaSlice := strings.Split(userAgent, " ")
|
||||
ver = uaSlice[len(uaSlice)-1]
|
||||
if ver[0] == 'v' {
|
||||
ver = ver[1:]
|
||||
}
|
||||
if !regexp.MustCompile(`^(\d+\.?)+$`).MatchString(ver) {
|
||||
ver = ""
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 清除用户操作日志
|
||||
func (ua *UserActLogProcess) ClearUserActLog(ts string) (int64, error) {
|
||||
affected, err := xdb.Where("created_at < '" + ts + "'").Delete(&UserActLog{})
|
||||
return affected, err
|
||||
}
|
||||
|
||||
// 后台筛选用户操作日志
|
||||
func (ua *UserActLogProcess) GetSession(values url.Values) *xorm.Session {
|
||||
session := xdb.Where("1=1")
|
||||
if values.Get("username") != "" {
|
||||
session.And("username = ?", values.Get("username"))
|
||||
}
|
||||
if values.Get("sdate") != "" {
|
||||
session.And("created_at >= ?", values.Get("sdate")+" 00:00:00'")
|
||||
}
|
||||
if values.Get("edate") != "" {
|
||||
session.And("created_at <= ?", values.Get("edate")+" 23:59:59'")
|
||||
}
|
||||
if values.Get("status") != "" {
|
||||
session.And("status = ?", cast.ToUint8(values.Get("status"))-1)
|
||||
}
|
||||
if values.Get("os") != "" {
|
||||
session.And("os = ?", cast.ToUint8(values.Get("os"))-1)
|
||||
}
|
||||
if values.Get("sort") == "1" {
|
||||
session.OrderBy("id desc")
|
||||
} else {
|
||||
session.OrderBy("id asc")
|
||||
}
|
||||
return session
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
package dbdata
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestParseUserAgent(t *testing.T) {
|
||||
type args struct {
|
||||
userAgent string
|
||||
}
|
||||
type res struct {
|
||||
os_idx uint8
|
||||
client_idx uint8
|
||||
ver string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want res
|
||||
}{
|
||||
{
|
||||
name: "mac os 1",
|
||||
args: args{userAgent: "cisco anyconnect vpn agent for mac os x 4.10.05085"},
|
||||
want: res{os_idx: 1, client_idx: 0, ver: "4.10.05085"},
|
||||
},
|
||||
{
|
||||
name: "mac os 2",
|
||||
args: args{userAgent: "anyconnect darwin_i386 4.10.05085"},
|
||||
want: res{os_idx: 1, client_idx: 0, ver: "4.10.05085"},
|
||||
},
|
||||
{
|
||||
name: "windows",
|
||||
args: args{userAgent: "cisco anyconnect vpn agent for windows 4.8.02042"},
|
||||
want: res{os_idx: 0, client_idx: 0, ver: "4.8.02042"},
|
||||
},
|
||||
{
|
||||
name: "iPad",
|
||||
args: args{userAgent: "anyconnect applesslvpn_darwin_arm (ipad) 4.10.04060"},
|
||||
want: res{os_idx: 4, client_idx: 0, ver: "4.10.04060"},
|
||||
},
|
||||
{
|
||||
name: "iPhone",
|
||||
args: args{userAgent: "cisco anyconnect vpn agent for apple iphone 4.10.04060"},
|
||||
want: res{os_idx: 4, client_idx: 0, ver: "4.10.04060"},
|
||||
},
|
||||
{
|
||||
name: "android",
|
||||
args: args{userAgent: "anyconnect android 4.10.05096"},
|
||||
want: res{os_idx: 3, client_idx: 0, ver: "4.10.05096"},
|
||||
},
|
||||
{
|
||||
name: "linux",
|
||||
args: args{userAgent: "open anyconnect vpn agent v7.08"},
|
||||
want: res{os_idx: 2, client_idx: 0, ver: "7.08"},
|
||||
},
|
||||
{
|
||||
name: "openconnect",
|
||||
args: args{userAgent: "openconnect-gui 1.5.3 v7.08"},
|
||||
want: res{os_idx: 2, client_idx: 1, ver: "7.08"},
|
||||
},
|
||||
{
|
||||
name: "unknown",
|
||||
args: args{userAgent: "unknown 1.4.3 aabcd"},
|
||||
want: res{os_idx: 2, client_idx: 2, ver: ""},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if os_idx, client_idx, ver := UserActLogIns.ParseUserAgent(tt.args.userAgent); os_idx != tt.want.os_idx || client_idx != tt.want.client_idx || ver != tt.want.ver {
|
||||
t.Errorf("ParseUserAgent() = %v, %v, %v, want %v, %v, %v", os_idx, client_idx, ver, tt.want.os_idx, tt.want.client_idx, tt.want.ver)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -43,7 +43,6 @@ func LinkAuth(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
// fmt.Printf("%+v \n", cr)
|
||||
|
||||
setCommonHeader(w)
|
||||
if cr.Type == "logout" {
|
||||
// 退出删除session信息
|
||||
|
@ -66,16 +65,27 @@ func LinkAuth(w http.ResponseWriter, r *http.Request) {
|
|||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// 用户活动日志
|
||||
ua := dbdata.UserActLog{
|
||||
Username: cr.Auth.Username,
|
||||
GroupName: cr.GroupSelect,
|
||||
RemoteAddr: r.RemoteAddr,
|
||||
Status: dbdata.UserAuthSuccess,
|
||||
}
|
||||
// TODO 用户密码校验
|
||||
err = dbdata.CheckUser(cr.Auth.Username, cr.Auth.Password, cr.GroupSelect)
|
||||
if err != nil {
|
||||
base.Warn(err)
|
||||
ua.Info = err.Error()
|
||||
ua.Status = dbdata.UserAuthFail
|
||||
dbdata.UserActLogIns.Add(ua, userAgent)
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
data := RequestData{Group: cr.GroupSelect, Groups: dbdata.GetGroupNames(), Error: "用户名或密码错误"}
|
||||
tplRequest(tpl_request, w, data)
|
||||
return
|
||||
}
|
||||
dbdata.UserActLogIns.Add(ua, userAgent)
|
||||
// if !ok {
|
||||
// w.WriteHeader(http.StatusOK)
|
||||
// data := RequestData{Group: cr.GroupSelect, Groups: base.Cfg.UserGroups, Error: "请先激活用户"}
|
||||
|
@ -89,6 +99,8 @@ func LinkAuth(w http.ResponseWriter, r *http.Request) {
|
|||
sess.Group = cr.GroupSelect
|
||||
sess.MacAddr = strings.ToLower(cr.MacAddressList.MacAddress)
|
||||
sess.UniqueIdGlobal = cr.DeviceId.UniqueIdGlobal
|
||||
sess.UserAgent = userAgent
|
||||
sess.RemoteAddr = r.RemoteAddr
|
||||
// 获取客户端mac地址
|
||||
macHw, err := net.ParseMAC(sess.MacAddr)
|
||||
if err != nil {
|
||||
|
@ -109,7 +121,7 @@ func LinkAuth(w http.ResponseWriter, r *http.Request) {
|
|||
Banner: other.Banner, ProfileHash: profileHash}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
tplRequest(tpl_complete, w, rd)
|
||||
base.Debug("login", cr.Auth.Username)
|
||||
base.Debug("login", cr.Auth.Username, userAgent)
|
||||
}
|
||||
|
||||
const (
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/bjdgyc/anylink/base"
|
||||
"github.com/bjdgyc/anylink/dbdata"
|
||||
"github.com/bjdgyc/anylink/pkg/utils"
|
||||
"github.com/bjdgyc/anylink/sessdata"
|
||||
)
|
||||
|
@ -55,6 +56,7 @@ func LinkCstp(conn net.Conn, bufRW *bufio.ReadWriter, cSess *sessdata.ConnSessio
|
|||
// do nothing
|
||||
// base.Debug("recv keepalive", cSess.IpAddr)
|
||||
case 0x05: // DISCONNECT
|
||||
cSess.UserLogoutCode = dbdata.UserLogoutClient
|
||||
base.Debug("DISCONNECT", cSess.IpAddr)
|
||||
return
|
||||
case 0x03: // DPD-REQ
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/bjdgyc/anylink/base"
|
||||
"github.com/bjdgyc/anylink/dbdata"
|
||||
"github.com/bjdgyc/anylink/pkg/utils"
|
||||
"github.com/bjdgyc/anylink/sessdata"
|
||||
)
|
||||
|
@ -57,6 +58,7 @@ func LinkDtls(conn net.Conn, cSess *sessdata.ConnSession) {
|
|||
// do nothing
|
||||
// base.Debug("recv keepalive", cSess.IpAddr)
|
||||
case 0x05: // DISCONNECT
|
||||
cSess.UserLogoutCode = dbdata.UserLogoutClient
|
||||
base.Debug("DISCONNECT DTLS", cSess.IpAddr)
|
||||
return
|
||||
case 0x03: // DPD-REQ
|
||||
|
|
|
@ -69,6 +69,7 @@ func LinkTunnel(w http.ResponseWriter, r *http.Request) {
|
|||
cSess.SetMtu(cstpMtu)
|
||||
cSess.MasterSecret = masterSecret
|
||||
cSess.RemoteAddr = r.RemoteAddr
|
||||
cSess.UserAgent = strings.ToLower(r.UserAgent())
|
||||
cSess.LocalIp = net.ParseIP(localIp)
|
||||
cstpKeepalive := base.Cfg.CstpKeepalive
|
||||
cstpDpd := base.Cfg.CstpDpd
|
||||
|
@ -194,6 +195,13 @@ func LinkTunnel(w http.ResponseWriter, r *http.Request) {
|
|||
base.Error(err)
|
||||
return
|
||||
}
|
||||
dbdata.UserActLogIns.Add(dbdata.UserActLog{
|
||||
Username: sess.Username,
|
||||
GroupName: sess.Group,
|
||||
IpAddr: cSess.IpAddr.String(),
|
||||
RemoteAddr: cSess.RemoteAddr,
|
||||
Status: dbdata.UserConnected,
|
||||
}, cSess.UserAgent)
|
||||
|
||||
go LinkCstp(conn, bufRW, cSess)
|
||||
}
|
||||
|
|
|
@ -36,6 +36,8 @@ type ConnSession struct {
|
|||
Mtu int
|
||||
IfName string
|
||||
Client string // 客户端 mobile pc
|
||||
UserAgent string // 客户端信息
|
||||
UserLogoutCode uint8 // 用户/客户端主动登出
|
||||
CstpDpd int
|
||||
Group *dbdata.Group
|
||||
Limit *LimitRater
|
||||
|
@ -73,6 +75,8 @@ type Session struct {
|
|||
Group string
|
||||
AuthStep string
|
||||
AuthPass string
|
||||
RemoteAddr string
|
||||
UserAgent string
|
||||
|
||||
LastLogin time.Time
|
||||
IsActive bool
|
||||
|
@ -110,7 +114,7 @@ func checkSession() {
|
|||
|
||||
// 删除过期session
|
||||
for _, v := range outToken {
|
||||
CloseSess(v)
|
||||
CloseSess(v, dbdata.UserLogoutTimeout)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
@ -130,7 +134,7 @@ func CloseUserLimittimeSession() {
|
|||
}
|
||||
sessMux.RUnlock()
|
||||
for _, v := range limitTimeToken {
|
||||
CloseSess(v)
|
||||
CloseSess(v, dbdata.UserLogoutExpire)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -247,6 +251,7 @@ func (cs *ConnSession) Close() {
|
|||
|
||||
ReleaseIp(cs.IpAddr, cs.Sess.MacAddr)
|
||||
LimitClient(cs.Username, true)
|
||||
AddUserActLog(cs)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -407,7 +412,7 @@ func DelSess(token string) {
|
|||
// sessions.Delete(token)
|
||||
}
|
||||
|
||||
func CloseSess(token string) {
|
||||
func CloseSess(token string, code ...uint8) {
|
||||
sessMux.Lock()
|
||||
defer sessMux.Unlock()
|
||||
sess, ok := sessions[token]
|
||||
|
@ -419,8 +424,13 @@ func CloseSess(token string) {
|
|||
delete(dtlsIds, sess.DtlsSid)
|
||||
|
||||
if sess.CSess != nil {
|
||||
sess.CSess.Close()
|
||||
if len(code) > 0 {
|
||||
sess.CSess.UserLogoutCode = code[0]
|
||||
}
|
||||
sess.CSess.Close()
|
||||
return
|
||||
}
|
||||
AddUserActLogBySess(sess)
|
||||
}
|
||||
|
||||
func CloseCSess(token string) {
|
||||
|
@ -440,5 +450,29 @@ func DelSessByStoken(stoken string) {
|
|||
stoken = strings.TrimSpace(stoken)
|
||||
sarr := strings.Split(stoken, "@")
|
||||
token := sarr[1]
|
||||
CloseSess(token)
|
||||
CloseSess(token, dbdata.UserLogoutBanner)
|
||||
}
|
||||
|
||||
func AddUserActLog(cs *ConnSession) {
|
||||
ua := dbdata.UserActLog{
|
||||
Username: cs.Sess.Username,
|
||||
GroupName: cs.Sess.Group,
|
||||
IpAddr: cs.IpAddr.String(),
|
||||
RemoteAddr: cs.RemoteAddr,
|
||||
Status: dbdata.UserLogout,
|
||||
}
|
||||
ua.Info = dbdata.UserActLogIns.GetInfoOpsById(cs.UserLogoutCode)
|
||||
dbdata.UserActLogIns.Add(ua, cs.UserAgent)
|
||||
}
|
||||
|
||||
func AddUserActLogBySess(sess *Session) {
|
||||
ua := dbdata.UserActLog{
|
||||
Username: sess.Username,
|
||||
GroupName: sess.Group,
|
||||
IpAddr: "",
|
||||
RemoteAddr: sess.RemoteAddr,
|
||||
Status: dbdata.UserLogout,
|
||||
}
|
||||
ua.Info = dbdata.UserActLogIns.GetInfoOpsById(1)
|
||||
dbdata.UserActLogIns.Add(ua, sess.UserAgent)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,321 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-form :model="searchForm" :rules="rules" ref="searchForm" :inline="true" class="search-form">
|
||||
<el-form-item label="用户名:" prop="username">
|
||||
<el-input size="mini" v-model="searchForm.username" clearable style="width: 130px" @keydown.enter.native="searchEnterFun"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="源IP地址:" prop="src">
|
||||
<el-input size="mini" v-model="searchForm.src" clearable style="width: 130px" @keydown.enter.native="searchEnterFun"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="目的IP地址:" prop="dst">
|
||||
<el-input size="mini" v-model="searchForm.dst" clearable style="width: 130px" @keydown.enter.native="searchEnterFun"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="目的端口:" prop="dst_port">
|
||||
<el-input size="mini" v-model="searchForm.dst_port" clearable style="width: 80px" @keydown.enter.native="searchEnterFun"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="访问协议:">
|
||||
<el-select size="mini" v-model="searchForm.access_proto" clearable placeholder="请选择" style="width: 100px">
|
||||
<el-option v-for="(item,index) in access_proto" :key="index" :label="item.text" :value="item.value">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="日期范围:">
|
||||
<el-date-picker
|
||||
v-model="searchForm.date"
|
||||
type="datetimerange"
|
||||
value-format="yyyy-MM-dd HH:mm:ss"
|
||||
size="mini"
|
||||
align="left"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
:default-time="['00:00:00', '23:59:59']">
|
||||
</el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item label="详情:">
|
||||
<el-input size="mini" v-model="searchForm.info" placeholder="请输入关键字" clearable style="width: 200px" @keydown.enter.native="searchEnterFun"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button
|
||||
size="mini"
|
||||
type="primary"
|
||||
icon="el-icon-search"
|
||||
@click="handleSearch">搜索
|
||||
</el-button>
|
||||
<el-button
|
||||
size="mini"
|
||||
icon="el-icon-refresh"
|
||||
@click="rest">重置搜索
|
||||
</el-button>
|
||||
<el-button
|
||||
size="mini"
|
||||
icon="el-icon-download"
|
||||
@click="handleExport">导出
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-table
|
||||
ref="multipleTable"
|
||||
:data="tableData"
|
||||
v-loading="loading"
|
||||
element-loading-text="玩命加载中"
|
||||
element-loading-spinner="el-icon-loading"
|
||||
:default-sort="{ prop: 'id', order: 'descending' }"
|
||||
@sort-change="sortChange"
|
||||
:header-cell-style="{backgroundColor:'#fcfcfc'}"
|
||||
border>
|
||||
|
||||
<el-table-column
|
||||
prop="id"
|
||||
label="ID"
|
||||
sortable="custom"
|
||||
width="100">
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column
|
||||
prop="username"
|
||||
label="用户名"
|
||||
width="140">
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column
|
||||
prop="src"
|
||||
label="源IP地址"
|
||||
width="140">
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column
|
||||
prop="dst"
|
||||
label="目的IP地址"
|
||||
width="140">
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column
|
||||
prop="dst_port"
|
||||
label="目的端口"
|
||||
width="85">
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column
|
||||
prop="access_proto"
|
||||
label="访问协议"
|
||||
width="80"
|
||||
:formatter="protoFormat">
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column
|
||||
prop="info"
|
||||
label="详情">
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column
|
||||
prop="created_at"
|
||||
label="创建时间"
|
||||
width="160"
|
||||
:formatter="tableDateFormat">
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="sh-20"></div>
|
||||
|
||||
<el-pagination
|
||||
background
|
||||
layout="prev, pager, next"
|
||||
:pager-count="11"
|
||||
:current-page.sync="currentPage"
|
||||
@current-change="pageChange"
|
||||
:total="count">
|
||||
</el-pagination>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from "axios";
|
||||
|
||||
export default {
|
||||
name: "auditAccess",
|
||||
mixins: [],
|
||||
data() {
|
||||
return {
|
||||
tableData: [],
|
||||
count: 10,
|
||||
currentPage: 1,
|
||||
idSort: 1,
|
||||
activeName: "first",
|
||||
accessProtoArr:["", "UDP", "TCP", "HTTPS", "HTTP"],
|
||||
defSearchForm: {username:'', src:'', dst:'', dst_port:'', access_proto:'', info:'', date:["",""]},
|
||||
searchForm: {},
|
||||
access_proto: [
|
||||
{ text: 'UDP', value: '1' },
|
||||
{ text: 'TCP', value: '2' },
|
||||
{ text: 'HTTPS', value: '3' },
|
||||
{ text: 'HTTP', value: '4' },
|
||||
],
|
||||
maxExportNum: 1000000,
|
||||
loading: false,
|
||||
rules: {
|
||||
username: [
|
||||
{max: 30, message: '长度小于 30 个字符', trigger: 'blur'}
|
||||
],
|
||||
src: [
|
||||
{ message: '请输入正确的IP地址', validator: this.validateIP, trigger: 'blur' },
|
||||
],
|
||||
dst: [
|
||||
{ message: '请输入正确的IP地址', validator: this.validateIP, trigger: 'blur' },
|
||||
],
|
||||
},
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
idSort: {
|
||||
handler(newValue, oldValue) {
|
||||
if (newValue != oldValue) {
|
||||
this.getData(1);
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
setSearchData() {
|
||||
this.searchForm = JSON.parse(JSON.stringify(this.defSearchForm));
|
||||
},
|
||||
handleSearch() {
|
||||
this.$refs["searchForm"].validate((valid) => {
|
||||
if (!valid) {
|
||||
console.log('error submit!!');
|
||||
return false;
|
||||
}
|
||||
this.getData(1)
|
||||
})
|
||||
},
|
||||
searchEnterFun(e) {
|
||||
var keyCode = window.event ? e.keyCode : e.which;
|
||||
if (keyCode == 13) {
|
||||
this.handleSearch()
|
||||
}
|
||||
},
|
||||
getData(p) {
|
||||
this.loading = true
|
||||
if (! this.searchForm.date) {
|
||||
this.searchForm.date = ["", ""];
|
||||
}
|
||||
this.searchForm.sort = this.idSort
|
||||
axios.get('/set/audit/list', {
|
||||
params: {
|
||||
page: p,
|
||||
search: this.searchForm,
|
||||
}
|
||||
}).then(resp => {
|
||||
var data = resp.data.data
|
||||
console.log(data);
|
||||
this.tableData = data.datas;
|
||||
this.count = data.count
|
||||
this.loading = false
|
||||
this.currentPage = p;
|
||||
}).catch(error => {
|
||||
this.$message.error('哦,请求出错');
|
||||
console.log(error);
|
||||
});
|
||||
},
|
||||
pageChange(p) {
|
||||
this.getData(p)
|
||||
},
|
||||
handleExport() {
|
||||
if (this.count > this.maxExportNum) {
|
||||
var formatNum = (this.maxExportNum + "").replace(/\d{1,3}(?=(\d{3})+$)/g,function(s){
|
||||
return s+','
|
||||
})
|
||||
this.$message.error("你导出的数据量超过" + formatNum + "条,请调整搜索条件,再导出");
|
||||
return ;
|
||||
}
|
||||
if (! this.searchForm.date) {
|
||||
this.searchForm.date = ["", ""];
|
||||
}
|
||||
const exporting = this.$loading({
|
||||
lock: true,
|
||||
text: '玩命导出中,请稍等片刻...',
|
||||
spinner: 'el-icon-loading',
|
||||
background: 'rgba(0, 0, 0, 0.7)'
|
||||
});
|
||||
axios.get('/set/audit/export', {
|
||||
params: {
|
||||
search: this.searchForm,
|
||||
}
|
||||
}).then(resp => {
|
||||
var rdata = resp.data
|
||||
if (rdata.code && rdata.code != 0) {
|
||||
exporting.close();
|
||||
this.$message.error(rdata.msg);
|
||||
return ;
|
||||
}
|
||||
exporting.close();
|
||||
this.$message.success("成功导出CSV文件")
|
||||
let csvData = 'data:text/csv;charset=utf-8,\uFEFF' + rdata
|
||||
this.createDownLoadClick(csvData, `anylink_audit_log_` + Date.parse(new Date()) + `.csv`)
|
||||
}).catch(error => {
|
||||
exporting.close();
|
||||
this.$message.error('哦,请求出错');
|
||||
console.log(error);
|
||||
});
|
||||
},
|
||||
createDownLoadClick(content, fileName) {
|
||||
const link = document.createElement('a')
|
||||
link.href = encodeURI(content)
|
||||
link.download = fileName
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
},
|
||||
protoFormat(row) {
|
||||
var access_proto = row.access_proto
|
||||
if (row.access_proto == 0) {
|
||||
switch (row.protocol) {
|
||||
case 6: access_proto = 2; break;
|
||||
case 17: access_proto = 1; break;
|
||||
}
|
||||
}
|
||||
return this.accessProtoArr[access_proto]
|
||||
},
|
||||
rest() {
|
||||
console.log("rest");
|
||||
this.setSearchData();
|
||||
this.handleSearch();
|
||||
},
|
||||
validateIP(rule, value, callback) {
|
||||
if (value === '' || typeof value === 'undefined' || value == null) {
|
||||
callback()
|
||||
} else {
|
||||
const reg = /^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/
|
||||
if ((!reg.test(value)) && value !== '') {
|
||||
callback(new Error('请输入正确的IP地址'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
},
|
||||
sortChange(column) {
|
||||
let { order } = column;
|
||||
if (order === 'ascending') {
|
||||
this.idSort = 2;
|
||||
} else {
|
||||
this.idSort = 1;
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.el-form-item {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.el-table {
|
||||
font-size: 12px;
|
||||
}
|
||||
.search-form >>> .el-form-item__label {
|
||||
font-size: 12px;
|
||||
}
|
||||
/deep/ .el-table th {
|
||||
padding: 5px 0;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,256 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-form :model="searchForm" ref="searchForm" :inline="true" class="search-form">
|
||||
<el-form-item>
|
||||
<el-input size="mini" v-model="searchForm.username" clearable placeholder="请输入用户名" style="width: 130px" @keydown.enter.native="searchEnterFun"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-date-picker
|
||||
v-model="searchForm.sdate"
|
||||
type="date"
|
||||
size="mini"
|
||||
placeholder="开始日期"
|
||||
format="yyyy-MM-dd"
|
||||
value-format="yyyy-MM-dd"
|
||||
style="width: 130px"
|
||||
>
|
||||
</el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-date-picker
|
||||
v-model="searchForm.edate"
|
||||
type="date"
|
||||
size="mini"
|
||||
placeholder="结束日期"
|
||||
format="yyyy-MM-dd"
|
||||
value-format="yyyy-MM-dd"
|
||||
style="width: 130px"
|
||||
>
|
||||
</el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item >
|
||||
<el-select size="mini" v-model="searchForm.status" clearable placeholder="操作类型" style="width: 130px">
|
||||
<el-option v-for="(item,index) in statusOps" :key="index" :label="item.value" :value="item.key+1">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-select size="mini" v-model="searchForm.os" clearable placeholder="操作系统" style="width: 130px">
|
||||
<el-option v-for="(value,item,index) in osOps" :key="index" :label="value" :value="item+1">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button
|
||||
size="mini"
|
||||
type="primary"
|
||||
icon="el-icon-search"
|
||||
@click="handleSearch">搜索
|
||||
</el-button>
|
||||
<el-button
|
||||
size="mini"
|
||||
icon="el-icon-refresh"
|
||||
@click="rest">重置搜索
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-table
|
||||
ref="multipleTable"
|
||||
:data="tableData"
|
||||
:default-sort="{ prop: 'id', order: 'descending' }"
|
||||
@sort-change="sortChange"
|
||||
:header-cell-style="{backgroundColor:'#fcfcfc'}"
|
||||
border>
|
||||
|
||||
<el-table-column
|
||||
prop="id"
|
||||
label="ID"
|
||||
sortable="custom"
|
||||
width="60">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="username"
|
||||
label="用户名"
|
||||
width="80">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="group_name"
|
||||
label="登陆组"
|
||||
width="100">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="status"
|
||||
label="操作类型"
|
||||
width="92">
|
||||
<template slot-scope="{ row }">
|
||||
<span v-for="(item, index) in statusOps" :key="index">
|
||||
<el-tag size="small" v-if="row.status == item.key" disable-transitions :type="item.tag">{{item.value}}</el-tag>
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="os"
|
||||
label="操作系统"
|
||||
width="82">
|
||||
<template slot-scope="{ row }">
|
||||
<span v-for="(value, item, index) in osOps" :key="index">
|
||||
{{ row.os == item? value: "" }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="client"
|
||||
label="客户端"
|
||||
width="100">
|
||||
<template slot-scope="{ row }">
|
||||
<span v-for="(value, item, index) in clientOps" :key="index">
|
||||
{{ row.client == item? value: "" }}
|
||||
</span>
|
||||
{{ row.version }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="ip_addr"
|
||||
label="内网IP"
|
||||
width="120">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="remote_addr"
|
||||
label="外网IP"
|
||||
width="120">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="info"
|
||||
label="详情">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="created_at"
|
||||
label="操作时间"
|
||||
width="130"
|
||||
:formatter="tableDateFormat">
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="sh-20"></div>
|
||||
<el-pagination
|
||||
background
|
||||
layout="prev, pager, next"
|
||||
:pager-count="11"
|
||||
@current-change="pageChange"
|
||||
:current-page="page"
|
||||
:total="count">
|
||||
</el-pagination>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from "axios";
|
||||
|
||||
export default {
|
||||
name: "List",
|
||||
components: {},
|
||||
mixins: [],
|
||||
created() {
|
||||
this.$emit('update:route_path', this.$route.path)
|
||||
this.$emit('update:route_name', ['用户信息', '登入日志'])
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
page: 1,
|
||||
grouNames: [],
|
||||
tableData: [],
|
||||
idSort: 1,
|
||||
count: 10,
|
||||
searchForm: {username:'', sdate:'', edate:'', status:'', os:''},
|
||||
statusOps:[],
|
||||
osOps:[],
|
||||
clientOps:[],
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
idSort: {
|
||||
handler(newValue, oldValue) {
|
||||
if (newValue != oldValue) {
|
||||
this.getData(1);
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleSearch() {
|
||||
this.getData(1)
|
||||
},
|
||||
pageChange(p) {
|
||||
this.getData(p)
|
||||
},
|
||||
searchEnterFun(e) {
|
||||
var keyCode = window.event ? e.keyCode : e.which;
|
||||
if (keyCode == 13) {
|
||||
this.handleSearch()
|
||||
}
|
||||
},
|
||||
getData(page) {
|
||||
console.log(this.searchForm)
|
||||
this.page = page
|
||||
axios.get('/set/audit/act_log_list', {
|
||||
params: {
|
||||
page: page,
|
||||
username: this.searchForm.username || '',
|
||||
sdate: this.searchForm.sdate || '',
|
||||
edate: this.searchForm.edate || '',
|
||||
status: this.searchForm.status || '',
|
||||
os: this.searchForm.os || '',
|
||||
sort: this.idSort,
|
||||
}
|
||||
}).then(resp => {
|
||||
var data = resp.data.data
|
||||
console.log(data);
|
||||
this.tableData = data.datas;
|
||||
this.count = data.count
|
||||
this.statusOps = data.statusOps
|
||||
this.osOps = data.osOps
|
||||
this.clientOps = data.clientOps
|
||||
}).catch(error => {
|
||||
this.$message.error('哦,请求出错');
|
||||
console.log(error);
|
||||
});
|
||||
},
|
||||
rest() {
|
||||
console.log("rest");
|
||||
this.searchForm.username = "";
|
||||
this.searchForm.sdate = "";
|
||||
this.searchForm.edate = "";
|
||||
this.searchForm.status = "";
|
||||
this.searchForm.os = "";
|
||||
this.handleSearch();
|
||||
},
|
||||
sortChange(column) {
|
||||
let { order } = column;
|
||||
if (order === 'ascending') {
|
||||
this.idSort = 2;
|
||||
} else {
|
||||
this.idSort = 1;
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.el-form-item {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.el-table {
|
||||
font-size: 12px;
|
||||
}
|
||||
.search-form >>> .el-form-item__label {
|
||||
font-size: 12px;
|
||||
}
|
||||
/deep/ .el-table th {
|
||||
padding: 5px 0;
|
||||
}
|
||||
/deep/ .el-table td {
|
||||
padding: 5px 0;
|
||||
}
|
||||
</style>
|
|
@ -56,7 +56,7 @@
|
|||
<el-menu-item index="/admin/group/list">用户组列表</el-menu-item>
|
||||
</el-submenu>
|
||||
|
||||
<el-submenu index="3">
|
||||
<el-submenu index="4">
|
||||
<template slot="title">
|
||||
<i class="el-icon-s-order"></i>
|
||||
<span slot="title">调试信息</span>
|
||||
|
@ -91,4 +91,9 @@ export default {
|
|||
.layout-menu {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.el-menu-item a {
|
||||
display: block;
|
||||
color: #fff;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,300 +1,61 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-card>
|
||||
<el-form :model="searchForm" :rules="rules" ref="searchForm" :inline="true" class="form-inner-error">
|
||||
<el-form-item label="用户名:" prop="username">
|
||||
<el-input size="small" v-model="searchForm.username" style="width: 130px" @keydown.enter.native="searchEnterFun"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="源IP地址:" prop="src">
|
||||
<el-input size="small" v-model="searchForm.src" style="width: 130px" @keydown.enter.native="searchEnterFun"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="目的IP地址:" prop="dst">
|
||||
<el-input size="small" v-model="searchForm.dst" style="width: 130px" @keydown.enter.native="searchEnterFun"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="目的端口:" prop="dst_port">
|
||||
<el-input size="small" v-model="searchForm.dst_port" style="width: 80px" @keydown.enter.native="searchEnterFun"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="访问协议:">
|
||||
<el-select size="small" v-model="searchForm.access_proto" style="width: 100px">
|
||||
<el-option v-for="(item,index) in access_proto" :key="index" :label="item.text" :value="item.value">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<div>
|
||||
<el-form-item label="日期范围:">
|
||||
<el-date-picker
|
||||
v-model="searchForm.date"
|
||||
type="datetimerange"
|
||||
size="small"
|
||||
value-format="yyyy-MM-dd HH:mm:ss"
|
||||
range-separator="~"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期">
|
||||
>
|
||||
</el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item label="详情:">
|
||||
<el-input size="small" v-model="searchForm.info" placeholder="请输入关键字" style="width: 200px" @keydown.enter.native="searchEnterFun"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button
|
||||
size="small"
|
||||
type="primary"
|
||||
icon="el-icon-search"
|
||||
@click="handleSearch">搜索
|
||||
</el-button>
|
||||
<el-button
|
||||
size="small"
|
||||
icon="el-icon-refresh"
|
||||
@click="rest">重置搜索
|
||||
</el-button>
|
||||
<el-button
|
||||
size="small"
|
||||
icon="el-icon-download"
|
||||
@click="handleExport">导出
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-form>
|
||||
|
||||
<el-table
|
||||
ref="multipleTable"
|
||||
:data="tableData"
|
||||
v-loading="loading"
|
||||
element-loading-text="玩命加载中"
|
||||
element-loading-spinner="el-icon-loading"
|
||||
border>
|
||||
|
||||
<el-table-column
|
||||
prop="id"
|
||||
label="ID"
|
||||
width="100">
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column
|
||||
prop="username"
|
||||
label="用户名"
|
||||
width="140">
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column
|
||||
prop="src"
|
||||
label="源IP地址"
|
||||
width="140">
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column
|
||||
prop="dst"
|
||||
label="目的IP地址"
|
||||
width="140">
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column
|
||||
prop="dst_port"
|
||||
label="目的端口"
|
||||
width="85">
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column
|
||||
prop="access_proto"
|
||||
label="访问协议"
|
||||
width="80"
|
||||
:formatter="protoFormat">
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column
|
||||
prop="info"
|
||||
label="详情">
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column
|
||||
prop="created_at"
|
||||
label="创建时间"
|
||||
width="150"
|
||||
:formatter="tableDateFormat">
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="sh-20"></div>
|
||||
|
||||
<el-pagination
|
||||
background
|
||||
layout="prev, pager, next"
|
||||
:pager-count="11"
|
||||
@current-change="pageChange"
|
||||
:total="count">
|
||||
</el-pagination>
|
||||
|
||||
<el-tabs v-model="activeName" @tab-click="handleClick">
|
||||
<el-tab-pane label="用户访问日志" name="access_audit">
|
||||
<AuditAccess ref="auditAccess"></AuditAccess>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="用户活动日志" name="act_log">
|
||||
<AuditActLog ref="auditActLog"></AuditActLog>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-card>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from "axios";
|
||||
import AuditAccess from "../../components/audit/Access";
|
||||
import AuditActLog from "../../components/audit/ActLog";
|
||||
|
||||
export default {
|
||||
name: "Audit",
|
||||
components: {},
|
||||
components:{
|
||||
AuditAccess,
|
||||
AuditActLog
|
||||
},
|
||||
mixins: [],
|
||||
mounted() {
|
||||
this.upTab();
|
||||
},
|
||||
created() {
|
||||
this.$emit('update:route_path', this.$route.path)
|
||||
this.$emit('update:route_name', ['基础信息', '审计日志'])
|
||||
},
|
||||
mounted() {
|
||||
this.getData(1)
|
||||
this.setSearchData()
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tableData: [],
|
||||
count: 10,
|
||||
nowIndex: 0,
|
||||
accessProtoArr:["", "UDP", "TCP", "HTTPS", "HTTP"],
|
||||
defSearchForm: {username:'', src:'', dst:'', dst_port:'', access_proto:'', info:'', date:["",""]},
|
||||
searchForm: {},
|
||||
access_proto: [
|
||||
{ text: '请选择', value: '' },
|
||||
{ text: 'UDP', value: '1' },
|
||||
{ text: 'TCP', value: '2' },
|
||||
{ text: 'HTTPS', value: '3' },
|
||||
{ text: 'HTTP', value: '4' },
|
||||
],
|
||||
maxExportNum: 1000000,
|
||||
loading: false,
|
||||
rules: {
|
||||
username: [
|
||||
{max: 30, message: '长度小于 30 个字符', trigger: 'blur'}
|
||||
],
|
||||
src: [
|
||||
{ message: '请输入正确的IP地址', validator: this.validateIP, trigger: 'blur' },
|
||||
],
|
||||
dst: [
|
||||
{ message: '请输入正确的IP地址', validator: this.validateIP, trigger: 'blur' },
|
||||
],
|
||||
},
|
||||
activeName: "access_audit",
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
setSearchData() {
|
||||
this.searchForm = JSON.parse(JSON.stringify(this.defSearchForm));
|
||||
upTab() {
|
||||
var tabname = this.$route.query.tabname
|
||||
if (tabname) {
|
||||
this.activeName = tabname
|
||||
}
|
||||
this.handleClick(this.activeName)
|
||||
},
|
||||
handleSearch() {
|
||||
this.$refs["searchForm"].validate((valid) => {
|
||||
if (!valid) {
|
||||
console.log('error submit!!');
|
||||
return false;
|
||||
handleClick() {
|
||||
switch (this.activeName) {
|
||||
case "access_audit":
|
||||
this.$refs.auditAccess.setSearchData()
|
||||
this.$refs.auditAccess.getData(1)
|
||||
break
|
||||
case "act_log":
|
||||
this.$refs.auditActLog.getData(1)
|
||||
break
|
||||
}
|
||||
this.getData(1)
|
||||
})
|
||||
this.$router.push({path: this.$route.path, query: {tabname: this.activeName}})
|
||||
},
|
||||
searchEnterFun(e) {
|
||||
var keyCode = window.event ? e.keyCode : e.which;
|
||||
if (keyCode == 13) {
|
||||
this.handleSearch()
|
||||
}
|
||||
},
|
||||
getData(p) {
|
||||
this.loading = true
|
||||
if (! this.searchForm.date) {
|
||||
this.searchForm.date = ["", ""];
|
||||
}
|
||||
axios.get('/set/audit/list', {
|
||||
params: {
|
||||
page: p,
|
||||
search: this.searchForm,
|
||||
}
|
||||
}).then(resp => {
|
||||
var data = resp.data.data
|
||||
console.log(data);
|
||||
this.tableData = data.datas;
|
||||
this.count = data.count
|
||||
this.loading = false
|
||||
}).catch(error => {
|
||||
this.$message.error('哦,请求出错');
|
||||
console.log(error);
|
||||
});
|
||||
},
|
||||
pageChange(p) {
|
||||
this.getData(p)
|
||||
},
|
||||
handleExport() {
|
||||
if (this.count > this.maxExportNum) {
|
||||
var formatNum = (this.maxExportNum + "").replace(/\d{1,3}(?=(\d{3})+$)/g,function(s){
|
||||
return s+','
|
||||
})
|
||||
this.$message.error("你导出的数据量超过" + formatNum + "条,请调整搜索条件,再导出");
|
||||
return ;
|
||||
}
|
||||
if (! this.searchForm.date) {
|
||||
this.searchForm.date = ["", ""];
|
||||
}
|
||||
const exporting = this.$loading({
|
||||
lock: true,
|
||||
text: '玩命导出中,请稍等片刻...',
|
||||
spinner: 'el-icon-loading',
|
||||
background: 'rgba(0, 0, 0, 0.7)'
|
||||
});
|
||||
axios.get('/set/audit/export', {
|
||||
params: {
|
||||
search: this.searchForm,
|
||||
}
|
||||
}).then(resp => {
|
||||
var rdata = resp.data
|
||||
if (rdata.code && rdata.code != 0) {
|
||||
exporting.close();
|
||||
this.$message.error(rdata.msg);
|
||||
return ;
|
||||
}
|
||||
exporting.close();
|
||||
this.$message.success("成功导出CSV文件")
|
||||
let csvData = 'data:text/csv;charset=utf-8,\uFEFF' + rdata
|
||||
this.createDownLoadClick(csvData, `anylink_audit_log_` + Date.parse(new Date()) + `.csv`)
|
||||
}).catch(error => {
|
||||
exporting.close();
|
||||
this.$message.error('哦,请求出错');
|
||||
console.log(error);
|
||||
});
|
||||
},
|
||||
createDownLoadClick(content, fileName) {
|
||||
const link = document.createElement('a')
|
||||
link.href = encodeURI(content)
|
||||
link.download = fileName
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
},
|
||||
protoFormat(row) {
|
||||
var access_proto = row.access_proto
|
||||
if (row.access_proto == 0) {
|
||||
switch (row.protocol) {
|
||||
case 6: access_proto = 2; break;
|
||||
case 17: access_proto = 1; break;
|
||||
}
|
||||
}
|
||||
return this.accessProtoArr[access_proto]
|
||||
},
|
||||
rest() {
|
||||
console.log("rest");
|
||||
this.setSearchData();
|
||||
this.handleSearch();
|
||||
},
|
||||
validateIP(rule, value, callback) {
|
||||
if (value === '' || typeof value === 'undefined' || value == null) {
|
||||
callback()
|
||||
} else {
|
||||
const reg = /^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/
|
||||
if ((!reg.test(value)) && value !== '') {
|
||||
callback(new Error('请输入正确的IP地址'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
|
|
Loading…
Reference in New Issue