mirror of https://github.com/bjdgyc/anylink.git
Merge pull request #135 from lanrenwo/home_charts
实现后台首页图表(用户在线数、网络吞吐量、CPU、内存)
This commit is contained in:
commit
b5c14e5f29
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue