mirror of
				https://github.com/bjdgyc/anylink.git
				synced 2025-11-04 11:06:22 +08:00 
			
		
		
		
	实现后台首页图表(用户在线数、网络吞量量、CPU、内存)
This commit is contained in:
		
							
								
								
									
										33
									
								
								server/admin/api_statsinfo.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								server/admin/api_statsinfo.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,33 @@
 | 
			
		||||
package admin
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"net/http"
 | 
			
		||||
 | 
			
		||||
	"github.com/bjdgyc/anylink/dbdata"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func StatsInfoList(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
	var ok bool
 | 
			
		||||
	_ = r.ParseForm()
 | 
			
		||||
	action := r.FormValue("action")
 | 
			
		||||
	scope := r.FormValue("scope")
 | 
			
		||||
	ok = dbdata.StatsInfoIns.ValidAction(action)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		RespError(w, RespParamErr, errors.New("不存在的图表类别"))
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	ok = dbdata.StatsInfoIns.ValidScope(scope)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		RespError(w, RespParamErr, errors.New("不存在的日期范围"))
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	datas, err := dbdata.StatsInfoIns.GetData(action, scope)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		RespError(w, RespInternalErr, err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	data := make(map[string]interface{})
 | 
			
		||||
	data["datas"] = datas
 | 
			
		||||
	RespSucess(w, data)
 | 
			
		||||
}
 | 
			
		||||
@@ -8,6 +8,7 @@ import (
 | 
			
		||||
	"net/http/pprof"
 | 
			
		||||
 | 
			
		||||
	"github.com/bjdgyc/anylink/base"
 | 
			
		||||
	"github.com/gorilla/handlers"
 | 
			
		||||
	"github.com/gorilla/mux"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -18,6 +19,7 @@ func StartAdmin() {
 | 
			
		||||
 | 
			
		||||
	r := mux.NewRouter()
 | 
			
		||||
	r.Use(authMiddleware)
 | 
			
		||||
	r.Use(handlers.CompressHandler)
 | 
			
		||||
 | 
			
		||||
	// 监控检测
 | 
			
		||||
	r.HandleFunc("/status.html", func(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
@@ -64,6 +66,8 @@ func StartAdmin() {
 | 
			
		||||
	r.HandleFunc("/group/set", GroupSet)
 | 
			
		||||
	r.HandleFunc("/group/del", GroupDel)
 | 
			
		||||
 | 
			
		||||
	r.HandleFunc("/statsinfo/list", StatsInfoList)
 | 
			
		||||
 | 
			
		||||
	// pprof
 | 
			
		||||
	if base.Cfg.Pprof {
 | 
			
		||||
		r.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline).Name("debug")
 | 
			
		||||
 
 | 
			
		||||
@@ -25,7 +25,7 @@ func initDb() {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 初始化数据库
 | 
			
		||||
	err = xdb.Sync2(&User{}, &Setting{}, &Group{}, &IpMap{}, &AccessAudit{}, &Policy{})
 | 
			
		||||
	err = xdb.Sync2(&User{}, &Setting{}, &Group{}, &IpMap{}, &AccessAudit{}, &Policy{}, &StatsNetwork{}, &StatsCpu{}, &StatsMem{}, &StatsOnline{})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		base.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -70,6 +70,15 @@ func Find(data interface{}, limit, page int) error {
 | 
			
		||||
	return xdb.Limit(limit, start).Find(data)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func FindWhere(data interface{}, limit int, page int, where string, args ...interface{}) error {
 | 
			
		||||
	if limit == 0 {
 | 
			
		||||
		return xdb.Where(where, args...).Find(data)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	start := (page - 1) * limit
 | 
			
		||||
	return xdb.Where(where, args...).Limit(limit, start).Find(data)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func CountPrefix(fieldName string, prefix string, data interface{}) int {
 | 
			
		||||
	n, _ := xdb.Where(fieldName+" like ?", prefix+"%").Count(data)
 | 
			
		||||
	return int(n)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										231
									
								
								server/dbdata/statsinfo.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										231
									
								
								server/dbdata/statsinfo.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,231 @@
 | 
			
		||||
package dbdata
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"container/list"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/bjdgyc/anylink/base"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	LayoutTimeFormat    = "2006-01-02 15:04:05"
 | 
			
		||||
	LayoutTimeFormatMin = "2006-01-02 15:04"
 | 
			
		||||
	RealTimeMaxSize     = 120 // 实时数据最大保存条数
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type StatsInfo struct {
 | 
			
		||||
	RealtimeData map[string]*list.List
 | 
			
		||||
	Actions      []string
 | 
			
		||||
	Scopes       []string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ScopeDetail struct {
 | 
			
		||||
	sTime   time.Time
 | 
			
		||||
	eTime   time.Time
 | 
			
		||||
	minutes int
 | 
			
		||||
	fsTime  string
 | 
			
		||||
	feTime  string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var StatsInfoIns *StatsInfo
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	StatsInfoIns = &StatsInfo{
 | 
			
		||||
		Actions:      []string{"online", "network", "cpu", "mem"},
 | 
			
		||||
		Scopes:       []string{"rt", "1h", "24h", "3d", "7d", "30d"},
 | 
			
		||||
		RealtimeData: make(map[string]*list.List),
 | 
			
		||||
	}
 | 
			
		||||
	for _, v := range StatsInfoIns.Actions {
 | 
			
		||||
		StatsInfoIns.RealtimeData[v] = list.New()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 校验统计类型值
 | 
			
		||||
func (s *StatsInfo) ValidAction(action string) bool {
 | 
			
		||||
	for _, item := range s.Actions {
 | 
			
		||||
		if item == action {
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 校验日期范围值
 | 
			
		||||
func (s *StatsInfo) ValidScope(scope string) bool {
 | 
			
		||||
	for _, item := range s.Scopes {
 | 
			
		||||
		if item == scope {
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 设置实时统计数据
 | 
			
		||||
func (s *StatsInfo) SetRealTime(action string, val interface{}) {
 | 
			
		||||
	if s.RealtimeData[action].Len() >= RealTimeMaxSize {
 | 
			
		||||
		ele := s.RealtimeData[action].Front()
 | 
			
		||||
		s.RealtimeData[action].Remove(ele)
 | 
			
		||||
	}
 | 
			
		||||
	s.RealtimeData[action].PushBack(val)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 获取实时统计数据
 | 
			
		||||
func (s *StatsInfo) GetRealTime(action string) (res []interface{}) {
 | 
			
		||||
	for e := s.RealtimeData[action].Front(); e != nil; e = e.Next() {
 | 
			
		||||
		res = append(res, e.Value)
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 保存数据至数据库
 | 
			
		||||
func (s *StatsInfo) SaveStatsInfo(so *StatsOnline, sn *StatsNetwork, sc *StatsCpu, sm *StatsMem) {
 | 
			
		||||
	if so.Num != 0 {
 | 
			
		||||
		_ = Add(so)
 | 
			
		||||
	}
 | 
			
		||||
	if sn.Up != 0 || sn.Down != 0 {
 | 
			
		||||
		_ = Add(sn)
 | 
			
		||||
	}
 | 
			
		||||
	if sc.Percent != 0 {
 | 
			
		||||
		_ = Add(sc)
 | 
			
		||||
	}
 | 
			
		||||
	if sm.Percent != 0 {
 | 
			
		||||
		_ = Add(sm)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 获取统计数据
 | 
			
		||||
func (s *StatsInfo) GetData(action string, scope string) (res []interface{}, err error) {
 | 
			
		||||
	if scope == "rt" {
 | 
			
		||||
		return s.GetRealTime(action), nil
 | 
			
		||||
	}
 | 
			
		||||
	statsMaps := make(map[string]interface{})
 | 
			
		||||
	currSec := fmt.Sprintf("%02d", time.Now().Second())
 | 
			
		||||
 | 
			
		||||
	// 获取时间段数据
 | 
			
		||||
	sd := s.getScopeDetail(scope)
 | 
			
		||||
	timeList := s.getTimeList(sd)
 | 
			
		||||
	res = make([]interface{}, len(timeList))
 | 
			
		||||
 | 
			
		||||
	// 获取数据库查询条件
 | 
			
		||||
	where := s.getStatsWhere(sd)
 | 
			
		||||
	if where == "" {
 | 
			
		||||
		return nil, errors.New("不支持的数据库类型: " + base.Cfg.DbType)
 | 
			
		||||
	}
 | 
			
		||||
	// 查询数据表
 | 
			
		||||
	switch action {
 | 
			
		||||
	case "online":
 | 
			
		||||
		statsRes := []StatsOnline{}
 | 
			
		||||
		FindWhere(&statsRes, 0, 0, where, sd.fsTime, sd.feTime)
 | 
			
		||||
		for _, v := range statsRes {
 | 
			
		||||
			t := v.CreatedAt.Format(LayoutTimeFormatMin)
 | 
			
		||||
			statsMaps[t] = v
 | 
			
		||||
		}
 | 
			
		||||
	case "network":
 | 
			
		||||
		statsRes := []StatsNetwork{}
 | 
			
		||||
		FindWhere(&statsRes, 0, 0, where, sd.fsTime, sd.feTime)
 | 
			
		||||
		for _, v := range statsRes {
 | 
			
		||||
			t := v.CreatedAt.Format(LayoutTimeFormatMin)
 | 
			
		||||
			statsMaps[t] = v
 | 
			
		||||
		}
 | 
			
		||||
	case "cpu":
 | 
			
		||||
		statsRes := []StatsCpu{}
 | 
			
		||||
		FindWhere(&statsRes, 0, 0, where, sd.fsTime, sd.feTime)
 | 
			
		||||
		for _, v := range statsRes {
 | 
			
		||||
			t := v.CreatedAt.Format(LayoutTimeFormatMin)
 | 
			
		||||
			statsMaps[t] = v
 | 
			
		||||
		}
 | 
			
		||||
	case "mem":
 | 
			
		||||
		statsRes := []StatsMem{}
 | 
			
		||||
		FindWhere(&statsRes, 0, 0, where, sd.fsTime, sd.feTime)
 | 
			
		||||
		for _, v := range statsRes {
 | 
			
		||||
			t := v.CreatedAt.Format(LayoutTimeFormatMin)
 | 
			
		||||
			statsMaps[t] = v
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	// 整合数据
 | 
			
		||||
	for i, v := range timeList {
 | 
			
		||||
		if mv, ok := statsMaps[v]; ok {
 | 
			
		||||
			res[i] = mv
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		t, _ := time.ParseInLocation(LayoutTimeFormat, v+":"+currSec, time.Local)
 | 
			
		||||
		switch action {
 | 
			
		||||
		case "online":
 | 
			
		||||
			res[i] = StatsOnline{CreatedAt: t}
 | 
			
		||||
		case "network":
 | 
			
		||||
			res[i] = StatsNetwork{CreatedAt: t}
 | 
			
		||||
		case "cpu":
 | 
			
		||||
			res[i] = StatsCpu{CreatedAt: t}
 | 
			
		||||
		case "mem":
 | 
			
		||||
			res[i] = StatsMem{CreatedAt: t}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 获取日期范围的明细值
 | 
			
		||||
func (s *StatsInfo) getScopeDetail(scope string) (sd *ScopeDetail) {
 | 
			
		||||
	sd = &ScopeDetail{}
 | 
			
		||||
	t := time.Now()
 | 
			
		||||
	sd.eTime = time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), 59, t.Nanosecond(), time.Local)
 | 
			
		||||
	sd.minutes = 0
 | 
			
		||||
	switch scope {
 | 
			
		||||
	case "1h":
 | 
			
		||||
		sd.sTime = sd.eTime.Add(-time.Minute * 60)
 | 
			
		||||
		sd.minutes = 1
 | 
			
		||||
	case "24h":
 | 
			
		||||
		sd.sTime = sd.eTime.AddDate(0, 0, -1)
 | 
			
		||||
		sd.minutes = 5
 | 
			
		||||
	case "7d":
 | 
			
		||||
		sd.sTime = sd.eTime.AddDate(0, 0, -7)
 | 
			
		||||
		sd.minutes = 30
 | 
			
		||||
	case "30d":
 | 
			
		||||
		sd.sTime = sd.eTime.AddDate(0, 0, -30)
 | 
			
		||||
		sd.minutes = 150
 | 
			
		||||
	}
 | 
			
		||||
	if sd.minutes != 0 {
 | 
			
		||||
		sd.sTime = sd.sTime.Add(-time.Minute * time.Duration(sd.minutes))
 | 
			
		||||
	}
 | 
			
		||||
	sd.fsTime = sd.sTime.Format(LayoutTimeFormat)
 | 
			
		||||
	sd.feTime = sd.eTime.Format(LayoutTimeFormat)
 | 
			
		||||
	// UTC
 | 
			
		||||
	switch base.Cfg.DbType {
 | 
			
		||||
	case "sqlite3", "postgres":
 | 
			
		||||
		sd.fsTime = sd.sTime.UTC().Format(LayoutTimeFormat)
 | 
			
		||||
		sd.feTime = sd.eTime.UTC().Format(LayoutTimeFormat)
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 针对日期范围进行拆解
 | 
			
		||||
func (s *StatsInfo) getTimeList(sd *ScopeDetail) []string {
 | 
			
		||||
	subSec := int64(60 * sd.minutes)
 | 
			
		||||
	count := (sd.eTime.Unix()-sd.sTime.Unix())/subSec - 1
 | 
			
		||||
	eTime := sd.eTime.Unix() - subSec
 | 
			
		||||
	timeLists := make([]string, count)
 | 
			
		||||
	for i := count - 1; i >= 0; i-- {
 | 
			
		||||
		timeLists[i] = time.Unix(eTime, 0).Format(LayoutTimeFormatMin)
 | 
			
		||||
		eTime = eTime - subSec
 | 
			
		||||
	}
 | 
			
		||||
	return timeLists
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 获取where条件
 | 
			
		||||
func (s *StatsInfo) getStatsWhere(sd *ScopeDetail) (where string) {
 | 
			
		||||
	where = "created_at BETWEEN ? AND ?"
 | 
			
		||||
	min := strconv.Itoa(sd.minutes)
 | 
			
		||||
	switch base.Cfg.DbType {
 | 
			
		||||
	case "mysql":
 | 
			
		||||
		where += " AND floor(TIMESTAMPDIFF(SECOND, created_at, '" + sd.feTime + "') / 60) % " + min + " = 0"
 | 
			
		||||
	case "sqlite3":
 | 
			
		||||
		where += " AND CAST(ROUND((JULIANDAY('" + sd.feTime + "') - JULIANDAY(created_at)) * 86400) / 60 as integer) % " + min + " = 0"
 | 
			
		||||
	case "postgres":
 | 
			
		||||
		where += " AND floor((EXTRACT(EPOCH FROM " + sd.feTime + ") - EXTRACT(EPOCH FROM created_at)) / 60) % " + min + " = 0"
 | 
			
		||||
	default:
 | 
			
		||||
		where = ""
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
@@ -84,3 +84,31 @@ type Policy struct {
 | 
			
		||||
	CreatedAt        time.Time `json:"created_at" xorm:"DateTime created"`
 | 
			
		||||
	UpdatedAt        time.Time `json:"updated_at" xorm:"DateTime updated"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type StatsOnline struct {
 | 
			
		||||
	Id        int       `json:"id" xorm:"pk autoincr not null"`
 | 
			
		||||
	Num       int       `json:"num" xorm:"Int"`
 | 
			
		||||
	NumGroups string    `json:"num_groups" xorm:"varchar(500) not null"`
 | 
			
		||||
	CreatedAt time.Time `json:"created_at" xorm:"DateTime created index"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type StatsNetwork struct {
 | 
			
		||||
	Id         int       `json:"id" xorm:"pk autoincr not null"`
 | 
			
		||||
	Up         uint32    `json:"up" xorm:"Int"`
 | 
			
		||||
	Down       uint32    `json:"down" xorm:"Int"`
 | 
			
		||||
	UpGroups   string    `json:"up_groups" xorm:"varchar(500) not null"`
 | 
			
		||||
	DownGroups string    `json:"down_groups" xorm:"varchar(500) not null"`
 | 
			
		||||
	CreatedAt  time.Time `json:"created_at" xorm:"DateTime created index"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type StatsCpu struct {
 | 
			
		||||
	Id        int       `json:"id" xorm:"pk autoincr not null"`
 | 
			
		||||
	Percent   float64   `json:"percent" xorm:"Float"`
 | 
			
		||||
	CreatedAt time.Time `json:"created_at" xorm:"DateTime created index"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type StatsMem struct {
 | 
			
		||||
	Id        int       `json:"id" xorm:"pk autoincr not null"`
 | 
			
		||||
	Percent   float64   `json:"percent" xorm:"Float"`
 | 
			
		||||
	CreatedAt time.Time `json:"created_at" xorm:"DateTime created index"`
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,16 +4,16 @@ go 1.16
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
	github.com/StackExchange/wmi v1.2.1 // indirect
 | 
			
		||||
	github.com/go-ldap/ldap v3.0.3+incompatible // indirect
 | 
			
		||||
	github.com/go-ldap/ldap/v3 v3.4.3 // indirect
 | 
			
		||||
	github.com/go-ldap/ldap v3.0.3+incompatible
 | 
			
		||||
	github.com/go-sql-driver/mysql v1.6.0
 | 
			
		||||
	github.com/gocarina/gocsv v0.0.0-20220712153207-8b2118da4570 // indirect
 | 
			
		||||
	github.com/gocarina/gocsv v0.0.0-20220712153207-8b2118da4570
 | 
			
		||||
	github.com/golang-jwt/jwt/v4 v4.0.0
 | 
			
		||||
	github.com/google/gopacket v1.1.19
 | 
			
		||||
	github.com/gorilla/handlers v1.5.1
 | 
			
		||||
	github.com/gorilla/mux v1.8.0
 | 
			
		||||
	github.com/lib/pq v1.10.2
 | 
			
		||||
	github.com/mattn/go-sqlite3 v1.14.8
 | 
			
		||||
	github.com/orcaman/concurrent-map v1.0.0 // indirect
 | 
			
		||||
	github.com/orcaman/concurrent-map v1.0.0
 | 
			
		||||
	github.com/pion/dtls/v2 v2.0.9
 | 
			
		||||
	github.com/pion/logging v0.2.2
 | 
			
		||||
	github.com/shirou/gopsutil v3.21.7+incompatible
 | 
			
		||||
 
 | 
			
		||||
@@ -3,4 +3,5 @@ package sessdata
 | 
			
		||||
func Start() {
 | 
			
		||||
	initIpPool()
 | 
			
		||||
	checkSession()
 | 
			
		||||
	saveStatsInfo()
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										102
									
								
								server/sessdata/statsinfo.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								server/sessdata/statsinfo.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,102 @@
 | 
			
		||||
package sessdata
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"sync/atomic"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/bjdgyc/anylink/dbdata"
 | 
			
		||||
	"github.com/shirou/gopsutil/cpu"
 | 
			
		||||
	"github.com/shirou/gopsutil/mem"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	StatsCycleSec = 5  // 统计周期(秒)
 | 
			
		||||
	AddCycleSec   = 60 // 记录到数据表周期(秒)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func saveStatsInfo() {
 | 
			
		||||
	go func() {
 | 
			
		||||
		tick := time.NewTicker(time.Second * StatsCycleSec)
 | 
			
		||||
		count := 0
 | 
			
		||||
		for range tick.C {
 | 
			
		||||
			up := uint32(0)
 | 
			
		||||
			down := uint32(0)
 | 
			
		||||
			upGroups := make(map[string]uint32)
 | 
			
		||||
			downGroups := make(map[string]uint32)
 | 
			
		||||
			numGroups := make(map[string]int)
 | 
			
		||||
			onlineNum := 0
 | 
			
		||||
			sessMux.Lock()
 | 
			
		||||
			for _, v := range sessions {
 | 
			
		||||
				v.mux.Lock()
 | 
			
		||||
				if v.IsActive {
 | 
			
		||||
					// 在线人数
 | 
			
		||||
					onlineNum += 1
 | 
			
		||||
					numGroups[v.Group] += 1
 | 
			
		||||
					// 网络吞吐
 | 
			
		||||
					userUp := atomic.LoadUint32(&v.CSess.BandwidthUpPeriod)
 | 
			
		||||
					userDown := atomic.LoadUint32(&v.CSess.BandwidthDownPeriod)
 | 
			
		||||
					upGroups[v.Group] += userUp
 | 
			
		||||
					downGroups[v.Group] += userDown
 | 
			
		||||
					up += userUp
 | 
			
		||||
					down += userDown
 | 
			
		||||
				}
 | 
			
		||||
				v.mux.Unlock()
 | 
			
		||||
			}
 | 
			
		||||
			sessMux.Unlock()
 | 
			
		||||
 | 
			
		||||
			tNow := time.Now()
 | 
			
		||||
			// online
 | 
			
		||||
			numData, _ := json.Marshal(numGroups)
 | 
			
		||||
			so := &dbdata.StatsOnline{Num: onlineNum, NumGroups: string(numData), CreatedAt: tNow}
 | 
			
		||||
			// network
 | 
			
		||||
			upData, _ := json.Marshal(upGroups)
 | 
			
		||||
			downData, _ := json.Marshal(downGroups)
 | 
			
		||||
			sn := &dbdata.StatsNetwork{Up: up, Down: down, UpGroups: string(upData), DownGroups: string(downData), CreatedAt: tNow}
 | 
			
		||||
			// cpu
 | 
			
		||||
			sc := &dbdata.StatsCpu{Percent: getCpuPercent(), CreatedAt: tNow}
 | 
			
		||||
			// mem
 | 
			
		||||
			sm := &dbdata.StatsMem{Percent: getMemPercent(), CreatedAt: tNow}
 | 
			
		||||
			count++
 | 
			
		||||
			// 是否保存至数据库
 | 
			
		||||
			save := count*StatsCycleSec >= AddCycleSec
 | 
			
		||||
			// 历史数据
 | 
			
		||||
			if save {
 | 
			
		||||
				count = 0
 | 
			
		||||
			}
 | 
			
		||||
			// 设置统计数据
 | 
			
		||||
			setStatsData(save, so, sn, sc, sm)
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func setStatsData(save bool, so *dbdata.StatsOnline, sn *dbdata.StatsNetwork, sc *dbdata.StatsCpu, sm *dbdata.StatsMem) {
 | 
			
		||||
	// 实时数据
 | 
			
		||||
	dbdata.StatsInfoIns.SetRealTime("online", so)
 | 
			
		||||
	dbdata.StatsInfoIns.SetRealTime("network", sn)
 | 
			
		||||
	dbdata.StatsInfoIns.SetRealTime("cpu", sc)
 | 
			
		||||
	dbdata.StatsInfoIns.SetRealTime("mem", sm)
 | 
			
		||||
	if !save {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	dbdata.StatsInfoIns.SaveStatsInfo(so, sn, sc, sm)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getCpuPercent() float64 {
 | 
			
		||||
	cpuUsedPercent, _ := cpu.Percent(0, false)
 | 
			
		||||
	percent := cpuUsedPercent[0]
 | 
			
		||||
	if percent == 0 {
 | 
			
		||||
		percent = 1
 | 
			
		||||
	}
 | 
			
		||||
	return decimal(percent)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getMemPercent() float64 {
 | 
			
		||||
	m, _ := mem.VirtualMemory()
 | 
			
		||||
	return decimal(m.UsedPercent)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func decimal(f float64) float64 {
 | 
			
		||||
	i := int(f * 100)
 | 
			
		||||
	return float64(i) / 100
 | 
			
		||||
}
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div id="line-chart" :style="{height:height,width:width}"/>
 | 
			
		||||
  <div class="line-chart" :style="{height:height,width:width}" />
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
@@ -14,7 +14,7 @@
 | 
			
		||||
      },
 | 
			
		||||
      height: {
 | 
			
		||||
        type: String,
 | 
			
		||||
        default: '350px'
 | 
			
		||||
        default: '240px'
 | 
			
		||||
      },
 | 
			
		||||
      // title,xAxis,series
 | 
			
		||||
      chartData: {
 | 
			
		||||
@@ -23,29 +23,43 @@
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    data() {
 | 
			
		||||
      return {}
 | 
			
		||||
      return {
 | 
			
		||||
        chart:null
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    mounted() {
 | 
			
		||||
      this.initChart()
 | 
			
		||||
    },
 | 
			
		||||
    beforeDestroy() {
 | 
			
		||||
    },
 | 
			
		||||
    watch: {
 | 
			
		||||
        chartData:{
 | 
			
		||||
            handler(){
 | 
			
		||||
                this.initChart()
 | 
			
		||||
            },
 | 
			
		||||
            deep:true
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    methods: {
 | 
			
		||||
      initChart() {
 | 
			
		||||
        let chart = echarts.init(this.$el)
 | 
			
		||||
        this.chart = echarts.init(this.$el)
 | 
			
		||||
 | 
			
		||||
        const option = {
 | 
			
		||||
        let option = {
 | 
			
		||||
          color: ['#2D5CF6','#50B142'],
 | 
			
		||||
          title: {
 | 
			
		||||
            text: this.chartData.title || '折线图'
 | 
			
		||||
            text: this.chartData.title || '折线图',
 | 
			
		||||
            textStyle:{fontWeight:'normal',fontSize:16, color:'#8C8C8C'}
 | 
			
		||||
          },
 | 
			
		||||
          tooltip: {
 | 
			
		||||
            trigger: 'axis'
 | 
			
		||||
          },
 | 
			
		||||
          legend: {},
 | 
			
		||||
          legend: {
 | 
			
		||||
            bottom: 0,
 | 
			
		||||
          },
 | 
			
		||||
          grid: {
 | 
			
		||||
            left: '3%',
 | 
			
		||||
            right: '4%',
 | 
			
		||||
            bottom: '3%',
 | 
			
		||||
            bottom: '10%',
 | 
			
		||||
            containLabel: true
 | 
			
		||||
          },
 | 
			
		||||
          // toolbox: {
 | 
			
		||||
@@ -56,27 +70,47 @@
 | 
			
		||||
          xAxis: {
 | 
			
		||||
            type: 'category',
 | 
			
		||||
            boundaryGap: false,
 | 
			
		||||
            data: this.chartData.xname
 | 
			
		||||
            data: this.chartData.xname, 
 | 
			
		||||
            splitLine: {
 | 
			
		||||
                show: false
 | 
			
		||||
            }                                                     
 | 
			
		||||
          },
 | 
			
		||||
          yAxis: {
 | 
			
		||||
            type: 'value'
 | 
			
		||||
            type: 'value',
 | 
			
		||||
            minInterval: undefined,
 | 
			
		||||
            name: this.chartData.yname,
 | 
			
		||||
            splitLine: {
 | 
			
		||||
                lineStyle: {
 | 
			
		||||
                    color: "#F0F0F0",
 | 
			
		||||
                },
 | 
			
		||||
            },            
 | 
			
		||||
            axisLabel: {
 | 
			
		||||
                // formatter: (value) => {
 | 
			
		||||
                //     value = value + " MB"
 | 
			
		||||
                //     return value
 | 
			
		||||
                // }
 | 
			
		||||
            }            
 | 
			
		||||
          },
 | 
			
		||||
          series: [],
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        if (this.chartData.yminInterval != undefined) {
 | 
			
		||||
            option.yAxis.minInterval = this.chartData.yminInterval
 | 
			
		||||
        }
 | 
			
		||||
        let xdata = this.chartData.xdata
 | 
			
		||||
        for (let key in xdata) {
 | 
			
		||||
          // window.console.log(key);
 | 
			
		||||
          let a = {
 | 
			
		||||
            name: key,
 | 
			
		||||
            type: 'line',
 | 
			
		||||
            showSymbol: false,
 | 
			
		||||
            data: xdata[key]
 | 
			
		||||
          };
 | 
			
		||||
          option.series.push(a)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        chart.setOption(option)
 | 
			
		||||
 | 
			
		||||
        this.chart.setOption(option)
 | 
			
		||||
      },
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
    }    
 | 
			
		||||
  } 
 | 
			
		||||
</script>
 | 
			
		||||
 
 | 
			
		||||
@@ -39,18 +39,63 @@
 | 
			
		||||
            <countTo :startVal='0' :endVal='counts.ip_map' :duration='2000' class="panel-num"></countTo>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
      </el-col>
 | 
			
		||||
      </el-col>    
 | 
			
		||||
 | 
			
		||||
    </el-row>
 | 
			
		||||
 | 
			
		||||
    <el-row class="line-chart">
 | 
			
		||||
      <LineChart :chart-data="lineChartUser"/>
 | 
			
		||||
    </el-row>
 | 
			
		||||
 | 
			
		||||
    <el-row class="line-chart">
 | 
			
		||||
      <LineChart :chart-data="lineChartOrder"/>
 | 
			
		||||
    <el-row class="line-chart-box" gutter="20">
 | 
			
		||||
        <el-col :span="12" class="line-chart-col">            
 | 
			
		||||
            <LineChart :chart-data="lineChart.online"/>
 | 
			
		||||
            <div class="time-range">
 | 
			
		||||
                <el-radio-group v-model="lineChartScope.online" size="mini" @change="((label)=>{lineChartScopeChange('online', label)})">
 | 
			
		||||
                    <el-radio-button label="rt" >实时</el-radio-button>
 | 
			
		||||
                    <el-radio-button label="1h">1小时</el-radio-button>
 | 
			
		||||
                    <el-radio-button label="24h">24小时</el-radio-button>
 | 
			
		||||
                    <el-radio-button label="7d">7天</el-radio-button>
 | 
			
		||||
                    <el-radio-button label="30d">30天</el-radio-button>
 | 
			
		||||
                </el-radio-group>
 | 
			
		||||
            </div>
 | 
			
		||||
        </el-col>
 | 
			
		||||
        <el-col :span="12" class="line-chart-col">
 | 
			
		||||
            <LineChart :chart-data="lineChart.network"/>
 | 
			
		||||
            <div class="time-range">
 | 
			
		||||
                <el-radio-group v-model="lineChartScope.network" size="mini" @change="((label)=>{lineChartScopeChange('network', label)})">
 | 
			
		||||
                    <el-radio-button label="rt" >实时</el-radio-button>
 | 
			
		||||
                    <el-radio-button label="1h">1小时</el-radio-button>
 | 
			
		||||
                    <el-radio-button label="24h">24小时</el-radio-button>
 | 
			
		||||
                    <el-radio-button label="7d">7天</el-radio-button>
 | 
			
		||||
                    <el-radio-button label="30d">30天</el-radio-button>
 | 
			
		||||
                </el-radio-group>
 | 
			
		||||
            </div>            
 | 
			
		||||
        </el-col>
 | 
			
		||||
    </el-row>
 | 
			
		||||
 | 
			
		||||
    <el-row class="line-chart-box" gutter="20">
 | 
			
		||||
        <el-col :span="12" class="line-chart-col">            
 | 
			
		||||
            <LineChart :chart-data="lineChart.cpu"/>
 | 
			
		||||
            <div class="time-range">
 | 
			
		||||
                <el-radio-group v-model="lineChartScope.cpu" size="mini" @change="((label)=>{lineChartScopeChange('cpu', label)})">
 | 
			
		||||
                    <el-radio-button label="rt" >实时</el-radio-button>
 | 
			
		||||
                    <el-radio-button label="1h">1小时</el-radio-button>
 | 
			
		||||
                    <el-radio-button label="24h">24小时</el-radio-button>
 | 
			
		||||
                    <el-radio-button label="7d">7天</el-radio-button>
 | 
			
		||||
                    <el-radio-button label="30d">30天</el-radio-button>
 | 
			
		||||
                </el-radio-group>
 | 
			
		||||
            </div>
 | 
			
		||||
        </el-col>
 | 
			
		||||
        <el-col :span="12" class="line-chart-col">
 | 
			
		||||
            <LineChart :chart-data="lineChart.mem"/>
 | 
			
		||||
            <div class="time-range">
 | 
			
		||||
                <el-radio-group v-model="lineChartScope.mem" size="mini" @change="((label)=>{lineChartScopeChange('mem', label)})">
 | 
			
		||||
                    <el-radio-button label="rt" >实时</el-radio-button>
 | 
			
		||||
                    <el-radio-button label="1h">1小时</el-radio-button>
 | 
			
		||||
                    <el-radio-button label="24h">24小时</el-radio-button>
 | 
			
		||||
                    <el-radio-button label="7d">7天</el-radio-button>
 | 
			
		||||
                    <el-radio-button label="30d">30天</el-radio-button>
 | 
			
		||||
                </el-radio-group>
 | 
			
		||||
            </div>            
 | 
			
		||||
        </el-col>
 | 
			
		||||
    </el-row>    
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
@@ -60,24 +105,6 @@ import countTo from 'vue-count-to';
 | 
			
		||||
import LineChart from "@/components/LineChart";
 | 
			
		||||
import axios from "axios";
 | 
			
		||||
 | 
			
		||||
const lineChartUser = {
 | 
			
		||||
  title: '每日在线统计',
 | 
			
		||||
  xname: ['2019-12-13', '2019-12-14', '2019-12-15', '2019-12-16', '2019-12-17', '2019-12-18', '2019-12-19'],
 | 
			
		||||
  xdata: {
 | 
			
		||||
    'test1': [10, 120, 11, 134, 105, 10, 15],
 | 
			
		||||
    'test2': [10, 82, 91, 14, 162, 10, 15]
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const lineChartOrder = {
 | 
			
		||||
  title: '每日流量统计',
 | 
			
		||||
  xname: ['2019-12-13', '2019-12-14', '2019-12-15', '2019-12-16', '2019-12-17', '2019-12-18', '2019-12-19'],
 | 
			
		||||
  xdata: {
 | 
			
		||||
    'test1': [100, 120, 161, 134, 105, 160, 165],
 | 
			
		||||
    'test2': [120, 82, 91, 154, 162, 140, 145]
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
  name: "Home",
 | 
			
		||||
  components: {
 | 
			
		||||
@@ -92,8 +119,48 @@ export default {
 | 
			
		||||
        group: 0,
 | 
			
		||||
        ip_map: 0,
 | 
			
		||||
      },
 | 
			
		||||
      lineChartUser: lineChartUser,
 | 
			
		||||
      lineChartOrder: lineChartOrder,
 | 
			
		||||
      lineChart: {
 | 
			
		||||
        online: {
 | 
			
		||||
            title: '用户在线数',
 | 
			
		||||
            xname: [],
 | 
			
		||||
            xdata: {
 | 
			
		||||
                '在线人数': [],
 | 
			
		||||
            },
 | 
			
		||||
            yminInterval: 1,
 | 
			
		||||
            yname:"人数"
 | 
			
		||||
        },
 | 
			
		||||
        network: {
 | 
			
		||||
            title: '网络吞吐量',
 | 
			
		||||
            xname: [],
 | 
			
		||||
            xdata: {
 | 
			
		||||
                '下行流量': [],
 | 
			
		||||
                '上行流量': [],                
 | 
			
		||||
            },
 | 
			
		||||
            yname:"Mbps"
 | 
			
		||||
        },
 | 
			
		||||
        cpu: {
 | 
			
		||||
            title: 'CPU占用比例',
 | 
			
		||||
            xname: [],
 | 
			
		||||
            xdata: {
 | 
			
		||||
                'CPU': [],
 | 
			
		||||
            },
 | 
			
		||||
            yname:"%"
 | 
			
		||||
        },
 | 
			
		||||
        mem: {
 | 
			
		||||
                title: '内存占用比例',
 | 
			
		||||
                xname: [],
 | 
			
		||||
                xdata: {
 | 
			
		||||
                    '内存': [],
 | 
			
		||||
                },
 | 
			
		||||
                yname:"%"
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      lineChartScope : { 
 | 
			
		||||
            online: "rt",
 | 
			
		||||
            network : "rt",
 | 
			
		||||
            cpu : "rt",
 | 
			
		||||
            mem : "rt"  
 | 
			
		||||
      },   
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  created() {
 | 
			
		||||
@@ -102,6 +169,13 @@ export default {
 | 
			
		||||
  },
 | 
			
		||||
  mounted() {
 | 
			
		||||
    this.getData()
 | 
			
		||||
    this.getAllStats() 
 | 
			
		||||
    const chartsTimer = setInterval(() => {
 | 
			
		||||
        this.getAllStats()                                      
 | 
			
		||||
    }, 5000);
 | 
			
		||||
    this.$once('hook:beforeDestroy', () => {
 | 
			
		||||
      clearInterval(chartsTimer);
 | 
			
		||||
    })    
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    getData() {
 | 
			
		||||
@@ -114,9 +188,108 @@ export default {
 | 
			
		||||
        console.log(error);
 | 
			
		||||
      });
 | 
			
		||||
    },
 | 
			
		||||
    getAllStats() {
 | 
			
		||||
        for (var action in this.lineChartScope){
 | 
			
		||||
           if (this.lineChartScope[action] == "rt") {
 | 
			
		||||
               this.getStatsData(action);
 | 
			
		||||
           }
 | 
			
		||||
        }   
 | 
			
		||||
    },
 | 
			
		||||
    getStatsData(action, scope) {
 | 
			
		||||
        if (!scope) {
 | 
			
		||||
            scope = "rt"
 | 
			
		||||
        }
 | 
			
		||||
        let getData = {params:{"action": action, "scope": scope}}
 | 
			
		||||
        axios.get('/statsinfo/list', getData).then(resp => {
 | 
			
		||||
            var data = resp.data.data
 | 
			
		||||
            if (! data.datas) return ;
 | 
			
		||||
            data.action = action
 | 
			
		||||
            data.scope = scope
 | 
			
		||||
            switch(action) {
 | 
			
		||||
                case "online": this.formatOnline(data); break;
 | 
			
		||||
                case "network": this.formatNetwork(data); break;
 | 
			
		||||
                case "cpu": this.formatCpu(data); break;
 | 
			
		||||
                case "mem": this.formatMem(data); break;
 | 
			
		||||
            }
 | 
			
		||||
        }).catch(error => {
 | 
			
		||||
            this.$message.error('哦,请求出错');
 | 
			
		||||
            console.log(error);
 | 
			
		||||
        });
 | 
			
		||||
    },
 | 
			
		||||
    formatOnline(data) {
 | 
			
		||||
        let timeFormat = data.scope == "rt" || data.scope == "1h" || data.scope == "24h" ? "h:i:s" : "m/d h:i:s"
 | 
			
		||||
        let datas = data.datas
 | 
			
		||||
        this.lineChart.online.xname = []
 | 
			
		||||
        this.lineChart.online.xdata["在线人数"] = []
 | 
			
		||||
        for(var i=0; i<datas.length;i++){
 | 
			
		||||
            this.lineChart.online.xname[i] = this.dateFormat(datas[i].created_at, timeFormat)
 | 
			
		||||
            this.lineChart.online.xdata["在线人数"][i] = datas[i].num
 | 
			
		||||
        }
 | 
			
		||||
        // 实时更新在线数
 | 
			
		||||
        if (data.scope == "rt") {
 | 
			
		||||
            this.counts.online = datas[datas.length - 1].num
 | 
			
		||||
        }
 | 
			
		||||
    },    
 | 
			
		||||
    formatNetwork(data) {
 | 
			
		||||
        let timeFormat = data.scope == "rt" || data.scope == "1h" || data.scope == "24h" ? "h:i:s" : "m/d h:i:s"
 | 
			
		||||
        let datas = data.datas
 | 
			
		||||
        this.lineChart.network.xname = []
 | 
			
		||||
        this.lineChart.network.xdata["上行流量"] = []
 | 
			
		||||
        this.lineChart.network.xdata["下行流量"] = []
 | 
			
		||||
        for(var i=0; i<datas.length;i++){
 | 
			
		||||
            this.lineChart.network.xname[i] = this.dateFormat(datas[i].created_at, timeFormat)
 | 
			
		||||
            this.lineChart.network.xdata["上行流量"][i] = this.toMbps(datas[i].up)
 | 
			
		||||
            this.lineChart.network.xdata["下行流量"][i] = this.toMbps(datas[i].down)
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    formatCpu(data) {
 | 
			
		||||
        let timeFormat = data.scope == "rt" || data.scope == "1h" || data.scope == "24h" ? "h:i:s" : "m/d h:i:s"
 | 
			
		||||
        let datas = data.datas
 | 
			
		||||
        this.lineChart.cpu.xname = []
 | 
			
		||||
        this.lineChart.cpu.xdata["CPU"] = []        
 | 
			
		||||
        for(var i=0; i<datas.length;i++){
 | 
			
		||||
            this.lineChart.cpu.xname[i] = this.dateFormat(datas[i].created_at, timeFormat)
 | 
			
		||||
            this.lineChart.cpu.xdata["CPU"][i] = this.toDecimal(datas[i].percent)
 | 
			
		||||
        }
 | 
			
		||||
    }, 
 | 
			
		||||
    formatMem(data) {
 | 
			
		||||
        let timeFormat = data.scope == "rt" || data.scope == "1h" || data.scope == "24h" ? "h:i:s" : "m/d h:i:s"
 | 
			
		||||
        let datas = data.datas
 | 
			
		||||
        this.lineChart.mem.xname = []
 | 
			
		||||
        this.lineChart.mem.xdata["内存"] = []        
 | 
			
		||||
        for(var i=0; i<datas.length;i++){
 | 
			
		||||
            this.lineChart.mem.xname[i] = this.dateFormat(datas[i].created_at, timeFormat)
 | 
			
		||||
            this.lineChart.mem.xdata["内存"][i] = this.toDecimal(datas[i].percent)
 | 
			
		||||
        }
 | 
			
		||||
    },        
 | 
			
		||||
    toMbps(bytes) {
 | 
			
		||||
        if (bytes == 0) return 0
 | 
			
		||||
        return (bytes / Math.pow(1024, 2) * 8).toFixed(2) * 1
 | 
			
		||||
    },
 | 
			
		||||
    toDecimal(f) {
 | 
			
		||||
        return Math.floor(f * 100) / 100
 | 
			
		||||
    },
 | 
			
		||||
    lineChartScopeChange(action, label) {
 | 
			
		||||
        this.lineChartScope[action] = label;
 | 
			
		||||
        this.getStatsData(action, label);  
 | 
			
		||||
    },     
 | 
			
		||||
    dateFormat(p, format) {
 | 
			
		||||
        var da = new Date(p);
 | 
			
		||||
        var year = da.getFullYear();
 | 
			
		||||
        var month = da.getMonth() + 1;
 | 
			
		||||
        var dt = da.getDate();
 | 
			
		||||
        var h = ('0'+da.getHours()).slice(-2);
 | 
			
		||||
        var m = ('0'+da.getMinutes()).slice(-2)
 | 
			
		||||
        var s = ('0'+da.getSeconds()).slice(-2);
 | 
			
		||||
        switch (format) {
 | 
			
		||||
            case "h:i:s":  return h + ':' + m + ':' + s;
 | 
			
		||||
            case "m/d h:i:s":  return month + '/' + dt + ' ' + h + ':' + m + ':' + s;
 | 
			
		||||
        }
 | 
			
		||||
        return year + '-' + month + '-' + dt + ' ' + h + ':' + m + ':' + s;
 | 
			
		||||
    },    
 | 
			
		||||
    jump(path) {
 | 
			
		||||
        this.$router.push(path);
 | 
			
		||||
    },    
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
@@ -124,6 +297,7 @@ export default {
 | 
			
		||||
<style scoped>
 | 
			
		||||
.card-panel {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  border-radius: 12px;
 | 
			
		||||
  justify-content: space-around;
 | 
			
		||||
  border: 1px solid red;
 | 
			
		||||
  padding: 30px 0;
 | 
			
		||||
@@ -158,11 +332,26 @@ export default {
 | 
			
		||||
  font-weight: 700;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.line-chart-box {
 | 
			
		||||
  padding: 0px 0px;
 | 
			
		||||
  margin-top: 10px; 
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.line-chart-col {
 | 
			
		||||
    position: relative;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.line-chart {
 | 
			
		||||
  border-radius: 12px;
 | 
			
		||||
  background: #fff;
 | 
			
		||||
  padding: 0 16px;
 | 
			
		||||
  margin-top: 40px;
 | 
			
		||||
  box-shadow: 0 1px 3px 0 rgba(0, 0, 0, .12), 0 0 3px 0 rgba(0, 0, 0, .04);
 | 
			
		||||
  border-color: rgba(0, 0, 0, .05);
 | 
			
		||||
  padding:5px 5px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.time-range {
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    right: 5px;
 | 
			
		||||
    top: 10px;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user