mirror of https://github.com/bjdgyc/anylink.git
commit
5a60398435
|
@ -51,3 +51,29 @@ func SetAuditExport(w http.ResponseWriter, r *http.Request) {
|
||||||
gocsv.Marshal(datas, w)
|
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) {
|
func UserOffline(w http.ResponseWriter, r *http.Request) {
|
||||||
_ = r.ParseForm()
|
_ = r.ParseForm()
|
||||||
token := r.FormValue("token")
|
token := r.FormValue("token")
|
||||||
sessdata.CloseSess(token)
|
sessdata.CloseSess(token, dbdata.UserLogoutAdmin)
|
||||||
RespSucess(w, nil)
|
RespSucess(w, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -45,6 +45,7 @@ func StartAdmin() {
|
||||||
r.HandleFunc("/set/other/audit_log/edit", SetOtherAuditLogEdit)
|
r.HandleFunc("/set/other/audit_log/edit", SetOtherAuditLogEdit)
|
||||||
r.HandleFunc("/set/audit/list", SetAuditList)
|
r.HandleFunc("/set/audit/list", SetAuditList)
|
||||||
r.HandleFunc("/set/audit/export", SetAuditExport)
|
r.HandleFunc("/set/audit/export", SetAuditExport)
|
||||||
|
r.HandleFunc("/set/audit/act_log_list", UserActLogList)
|
||||||
|
|
||||||
r.HandleFunc("/user/list", UserList)
|
r.HandleFunc("/user/list", UserList)
|
||||||
r.HandleFunc("/user/detail", UserDetail)
|
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 := gocron.NewScheduler(time.Local)
|
||||||
s.Cron("0 * * * *").Do(ClearAudit)
|
s.Cron("0 * * * *").Do(ClearAudit)
|
||||||
s.Cron("0 * * * *").Do(ClearStatsInfo)
|
s.Cron("0 * * * *").Do(ClearStatsInfo)
|
||||||
|
s.Cron("0 * * * *").Do(ClearUserActLog)
|
||||||
s.Every(1).Day().At("00:00").Do(sessdata.CloseUserLimittimeSession)
|
s.Every(1).Day().At("00:00").Do(sessdata.CloseUserLimittimeSession)
|
||||||
s.StartAsync()
|
s.StartAsync()
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ type SearchCon struct {
|
||||||
AccessProto string `json:"access_proto"`
|
AccessProto string `json:"access_proto"`
|
||||||
Date []string `json:"date"`
|
Date []string `json:"date"`
|
||||||
Info string `json:"info"`
|
Info string `json:"info"`
|
||||||
|
Sort int `json:"sort"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetAuditSession(search string) *xorm.Session {
|
func GetAuditSession(search string) *xorm.Session {
|
||||||
|
@ -47,6 +48,11 @@ func GetAuditSession(search string) *xorm.Session {
|
||||||
if searchData.Info != "" {
|
if searchData.Info != "" {
|
||||||
session.And("info LIKE ?", "%"+searchData.Info+"%")
|
session.And("info LIKE ?", "%"+searchData.Info+"%")
|
||||||
}
|
}
|
||||||
|
if searchData.Sort == 1 {
|
||||||
|
session.OrderBy("id desc")
|
||||||
|
} else {
|
||||||
|
session.OrderBy("id asc")
|
||||||
|
}
|
||||||
return session
|
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 {
|
if err != nil {
|
||||||
base.Fatal(err)
|
base.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,6 +40,20 @@ type User struct {
|
||||||
UpdatedAt time.Time `json:"updated_at" xorm:"DateTime updated"`
|
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 {
|
type IpMap struct {
|
||||||
Id int `json:"id" xorm:"pk autoincr not null"`
|
Id int `json:"id" xorm:"pk autoincr not null"`
|
||||||
IpAddr string `json:"ip_addr" xorm:"varchar(32) not null unique"`
|
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
|
return
|
||||||
}
|
}
|
||||||
// fmt.Printf("%+v \n", cr)
|
// fmt.Printf("%+v \n", cr)
|
||||||
|
|
||||||
setCommonHeader(w)
|
setCommonHeader(w)
|
||||||
if cr.Type == "logout" {
|
if cr.Type == "logout" {
|
||||||
// 退出删除session信息
|
// 退出删除session信息
|
||||||
|
@ -66,16 +65,27 @@ func LinkAuth(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// 用户活动日志
|
||||||
|
ua := dbdata.UserActLog{
|
||||||
|
Username: cr.Auth.Username,
|
||||||
|
GroupName: cr.GroupSelect,
|
||||||
|
RemoteAddr: r.RemoteAddr,
|
||||||
|
Status: dbdata.UserAuthSuccess,
|
||||||
|
}
|
||||||
// TODO 用户密码校验
|
// TODO 用户密码校验
|
||||||
err = dbdata.CheckUser(cr.Auth.Username, cr.Auth.Password, cr.GroupSelect)
|
err = dbdata.CheckUser(cr.Auth.Username, cr.Auth.Password, cr.GroupSelect)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
base.Warn(err)
|
base.Warn(err)
|
||||||
|
ua.Info = err.Error()
|
||||||
|
ua.Status = dbdata.UserAuthFail
|
||||||
|
dbdata.UserActLogIns.Add(ua, userAgent)
|
||||||
|
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
data := RequestData{Group: cr.GroupSelect, Groups: dbdata.GetGroupNames(), Error: "用户名或密码错误"}
|
data := RequestData{Group: cr.GroupSelect, Groups: dbdata.GetGroupNames(), Error: "用户名或密码错误"}
|
||||||
tplRequest(tpl_request, w, data)
|
tplRequest(tpl_request, w, data)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
dbdata.UserActLogIns.Add(ua, userAgent)
|
||||||
// if !ok {
|
// if !ok {
|
||||||
// w.WriteHeader(http.StatusOK)
|
// w.WriteHeader(http.StatusOK)
|
||||||
// data := RequestData{Group: cr.GroupSelect, Groups: base.Cfg.UserGroups, Error: "请先激活用户"}
|
// 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.Group = cr.GroupSelect
|
||||||
sess.MacAddr = strings.ToLower(cr.MacAddressList.MacAddress)
|
sess.MacAddr = strings.ToLower(cr.MacAddressList.MacAddress)
|
||||||
sess.UniqueIdGlobal = cr.DeviceId.UniqueIdGlobal
|
sess.UniqueIdGlobal = cr.DeviceId.UniqueIdGlobal
|
||||||
|
sess.UserAgent = userAgent
|
||||||
|
sess.RemoteAddr = r.RemoteAddr
|
||||||
// 获取客户端mac地址
|
// 获取客户端mac地址
|
||||||
macHw, err := net.ParseMAC(sess.MacAddr)
|
macHw, err := net.ParseMAC(sess.MacAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -109,7 +121,7 @@ func LinkAuth(w http.ResponseWriter, r *http.Request) {
|
||||||
Banner: other.Banner, ProfileHash: profileHash}
|
Banner: other.Banner, ProfileHash: profileHash}
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
tplRequest(tpl_complete, w, rd)
|
tplRequest(tpl_complete, w, rd)
|
||||||
base.Debug("login", cr.Auth.Username)
|
base.Debug("login", cr.Auth.Username, userAgent)
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bjdgyc/anylink/base"
|
"github.com/bjdgyc/anylink/base"
|
||||||
|
"github.com/bjdgyc/anylink/dbdata"
|
||||||
"github.com/bjdgyc/anylink/pkg/utils"
|
"github.com/bjdgyc/anylink/pkg/utils"
|
||||||
"github.com/bjdgyc/anylink/sessdata"
|
"github.com/bjdgyc/anylink/sessdata"
|
||||||
)
|
)
|
||||||
|
@ -55,6 +56,7 @@ func LinkCstp(conn net.Conn, bufRW *bufio.ReadWriter, cSess *sessdata.ConnSessio
|
||||||
// do nothing
|
// do nothing
|
||||||
// base.Debug("recv keepalive", cSess.IpAddr)
|
// base.Debug("recv keepalive", cSess.IpAddr)
|
||||||
case 0x05: // DISCONNECT
|
case 0x05: // DISCONNECT
|
||||||
|
cSess.UserLogoutCode = dbdata.UserLogoutClient
|
||||||
base.Debug("DISCONNECT", cSess.IpAddr)
|
base.Debug("DISCONNECT", cSess.IpAddr)
|
||||||
return
|
return
|
||||||
case 0x03: // DPD-REQ
|
case 0x03: // DPD-REQ
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bjdgyc/anylink/base"
|
"github.com/bjdgyc/anylink/base"
|
||||||
|
"github.com/bjdgyc/anylink/dbdata"
|
||||||
"github.com/bjdgyc/anylink/pkg/utils"
|
"github.com/bjdgyc/anylink/pkg/utils"
|
||||||
"github.com/bjdgyc/anylink/sessdata"
|
"github.com/bjdgyc/anylink/sessdata"
|
||||||
)
|
)
|
||||||
|
@ -57,6 +58,7 @@ func LinkDtls(conn net.Conn, cSess *sessdata.ConnSession) {
|
||||||
// do nothing
|
// do nothing
|
||||||
// base.Debug("recv keepalive", cSess.IpAddr)
|
// base.Debug("recv keepalive", cSess.IpAddr)
|
||||||
case 0x05: // DISCONNECT
|
case 0x05: // DISCONNECT
|
||||||
|
cSess.UserLogoutCode = dbdata.UserLogoutClient
|
||||||
base.Debug("DISCONNECT DTLS", cSess.IpAddr)
|
base.Debug("DISCONNECT DTLS", cSess.IpAddr)
|
||||||
return
|
return
|
||||||
case 0x03: // DPD-REQ
|
case 0x03: // DPD-REQ
|
||||||
|
|
|
@ -69,6 +69,7 @@ func LinkTunnel(w http.ResponseWriter, r *http.Request) {
|
||||||
cSess.SetMtu(cstpMtu)
|
cSess.SetMtu(cstpMtu)
|
||||||
cSess.MasterSecret = masterSecret
|
cSess.MasterSecret = masterSecret
|
||||||
cSess.RemoteAddr = r.RemoteAddr
|
cSess.RemoteAddr = r.RemoteAddr
|
||||||
|
cSess.UserAgent = strings.ToLower(r.UserAgent())
|
||||||
cSess.LocalIp = net.ParseIP(localIp)
|
cSess.LocalIp = net.ParseIP(localIp)
|
||||||
cstpKeepalive := base.Cfg.CstpKeepalive
|
cstpKeepalive := base.Cfg.CstpKeepalive
|
||||||
cstpDpd := base.Cfg.CstpDpd
|
cstpDpd := base.Cfg.CstpDpd
|
||||||
|
@ -194,6 +195,13 @@ func LinkTunnel(w http.ResponseWriter, r *http.Request) {
|
||||||
base.Error(err)
|
base.Error(err)
|
||||||
return
|
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)
|
go LinkCstp(conn, bufRW, cSess)
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,6 +36,8 @@ type ConnSession struct {
|
||||||
Mtu int
|
Mtu int
|
||||||
IfName string
|
IfName string
|
||||||
Client string // 客户端 mobile pc
|
Client string // 客户端 mobile pc
|
||||||
|
UserAgent string // 客户端信息
|
||||||
|
UserLogoutCode uint8 // 用户/客户端主动登出
|
||||||
CstpDpd int
|
CstpDpd int
|
||||||
Group *dbdata.Group
|
Group *dbdata.Group
|
||||||
Limit *LimitRater
|
Limit *LimitRater
|
||||||
|
@ -73,6 +75,8 @@ type Session struct {
|
||||||
Group string
|
Group string
|
||||||
AuthStep string
|
AuthStep string
|
||||||
AuthPass string
|
AuthPass string
|
||||||
|
RemoteAddr string
|
||||||
|
UserAgent string
|
||||||
|
|
||||||
LastLogin time.Time
|
LastLogin time.Time
|
||||||
IsActive bool
|
IsActive bool
|
||||||
|
@ -110,7 +114,7 @@ func checkSession() {
|
||||||
|
|
||||||
// 删除过期session
|
// 删除过期session
|
||||||
for _, v := range outToken {
|
for _, v := range outToken {
|
||||||
CloseSess(v)
|
CloseSess(v, dbdata.UserLogoutTimeout)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
@ -130,7 +134,7 @@ func CloseUserLimittimeSession() {
|
||||||
}
|
}
|
||||||
sessMux.RUnlock()
|
sessMux.RUnlock()
|
||||||
for _, v := range limitTimeToken {
|
for _, v := range limitTimeToken {
|
||||||
CloseSess(v)
|
CloseSess(v, dbdata.UserLogoutExpire)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -247,6 +251,7 @@ func (cs *ConnSession) Close() {
|
||||||
|
|
||||||
ReleaseIp(cs.IpAddr, cs.Sess.MacAddr)
|
ReleaseIp(cs.IpAddr, cs.Sess.MacAddr)
|
||||||
LimitClient(cs.Username, true)
|
LimitClient(cs.Username, true)
|
||||||
|
AddUserActLog(cs)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -407,7 +412,7 @@ func DelSess(token string) {
|
||||||
// sessions.Delete(token)
|
// sessions.Delete(token)
|
||||||
}
|
}
|
||||||
|
|
||||||
func CloseSess(token string) {
|
func CloseSess(token string, code ...uint8) {
|
||||||
sessMux.Lock()
|
sessMux.Lock()
|
||||||
defer sessMux.Unlock()
|
defer sessMux.Unlock()
|
||||||
sess, ok := sessions[token]
|
sess, ok := sessions[token]
|
||||||
|
@ -419,8 +424,13 @@ func CloseSess(token string) {
|
||||||
delete(dtlsIds, sess.DtlsSid)
|
delete(dtlsIds, sess.DtlsSid)
|
||||||
|
|
||||||
if sess.CSess != nil {
|
if sess.CSess != nil {
|
||||||
|
if len(code) > 0 {
|
||||||
|
sess.CSess.UserLogoutCode = code[0]
|
||||||
|
}
|
||||||
sess.CSess.Close()
|
sess.CSess.Close()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
AddUserActLogBySess(sess)
|
||||||
}
|
}
|
||||||
|
|
||||||
func CloseCSess(token string) {
|
func CloseCSess(token string) {
|
||||||
|
@ -440,5 +450,29 @@ func DelSessByStoken(stoken string) {
|
||||||
stoken = strings.TrimSpace(stoken)
|
stoken = strings.TrimSpace(stoken)
|
||||||
sarr := strings.Split(stoken, "@")
|
sarr := strings.Split(stoken, "@")
|
||||||
token := sarr[1]
|
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>
|
|
@ -1,300 +1,61 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<el-card>
|
<el-card>
|
||||||
<el-form :model="searchForm" :rules="rules" ref="searchForm" :inline="true" class="form-inner-error">
|
<el-tabs v-model="activeName" @tab-click="handleClick">
|
||||||
<el-form-item label="用户名:" prop="username">
|
<el-tab-pane label="用户访问日志" name="access_audit">
|
||||||
<el-input size="small" v-model="searchForm.username" style="width: 130px" @keydown.enter.native="searchEnterFun"></el-input>
|
<AuditAccess ref="auditAccess"></AuditAccess>
|
||||||
</el-form-item>
|
</el-tab-pane>
|
||||||
<el-form-item label="源IP地址:" prop="src">
|
<el-tab-pane label="用户活动日志" name="act_log">
|
||||||
<el-input size="small" v-model="searchForm.src" style="width: 130px" @keydown.enter.native="searchEnterFun"></el-input>
|
<AuditActLog ref="auditActLog"></AuditActLog>
|
||||||
</el-form-item>
|
</el-tab-pane>
|
||||||
<el-form-item label="目的IP地址:" prop="dst">
|
</el-tabs>
|
||||||
<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-card>
|
</el-card>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import axios from "axios";
|
import AuditAccess from "../../components/audit/Access";
|
||||||
|
import AuditActLog from "../../components/audit/ActLog";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "Audit",
|
name: "Audit",
|
||||||
components: {},
|
components:{
|
||||||
|
AuditAccess,
|
||||||
|
AuditActLog
|
||||||
|
},
|
||||||
mixins: [],
|
mixins: [],
|
||||||
|
mounted() {
|
||||||
|
this.upTab();
|
||||||
|
},
|
||||||
created() {
|
created() {
|
||||||
this.$emit('update:route_path', this.$route.path)
|
this.$emit('update:route_path', this.$route.path)
|
||||||
this.$emit('update:route_name', ['基础信息', '审计日志'])
|
this.$emit('update:route_name', ['基础信息', '审计日志'])
|
||||||
},
|
},
|
||||||
mounted() {
|
|
||||||
this.getData(1)
|
|
||||||
this.setSearchData()
|
|
||||||
},
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
tableData: [],
|
activeName: "access_audit",
|
||||||
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' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
setSearchData() {
|
upTab() {
|
||||||
this.searchForm = JSON.parse(JSON.stringify(this.defSearchForm));
|
var tabname = this.$route.query.tabname
|
||||||
},
|
if (tabname) {
|
||||||
handleSearch() {
|
this.activeName = tabname
|
||||||
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 = ["", ""];
|
|
||||||
}
|
}
|
||||||
axios.get('/set/audit/list', {
|
this.handleClick(this.activeName)
|
||||||
params: {
|
},
|
||||||
page: p,
|
handleClick() {
|
||||||
search: this.searchForm,
|
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
|
||||||
}
|
}
|
||||||
}).then(resp => {
|
this.$router.push({path: this.$route.path, query: {tabname: this.activeName}})
|
||||||
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>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
</style>
|
|
||||||
|
|
Loading…
Reference in New Issue