diff --git a/server/admin/api_statsinfo.go b/server/admin/api_statsinfo.go
new file mode 100644
index 0000000..2ef347a
--- /dev/null
+++ b/server/admin/api_statsinfo.go
@@ -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)
+}
diff --git a/server/admin/server.go b/server/admin/server.go
index 807efbd..482ddf0 100644
--- a/server/admin/server.go
+++ b/server/admin/server.go
@@ -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")
diff --git a/server/dbdata/db.go b/server/dbdata/db.go
index 994651b..00c992c 100644
--- a/server/dbdata/db.go
+++ b/server/dbdata/db.go
@@ -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)
 	}
diff --git a/server/dbdata/db_orm.go b/server/dbdata/db_orm.go
index c120b69..656ff01 100644
--- a/server/dbdata/db_orm.go
+++ b/server/dbdata/db_orm.go
@@ -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)
diff --git a/server/dbdata/statsinfo.go b/server/dbdata/statsinfo.go
new file mode 100644
index 0000000..dd53b2c
--- /dev/null
+++ b/server/dbdata/statsinfo.go
@@ -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
+}
diff --git a/server/dbdata/tables.go b/server/dbdata/tables.go
index 6bb0bea..9249643 100644
--- a/server/dbdata/tables.go
+++ b/server/dbdata/tables.go
@@ -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"`
+}
diff --git a/server/go.mod b/server/go.mod
index 1ef67ff..e6fa303 100644
--- a/server/go.mod
+++ b/server/go.mod
@@ -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
diff --git a/server/sessdata/start.go b/server/sessdata/start.go
index 73773ce..1d4243c 100644
--- a/server/sessdata/start.go
+++ b/server/sessdata/start.go
@@ -3,4 +3,5 @@ package sessdata
 func Start() {
 	initIpPool()
 	checkSession()
+	saveStatsInfo()
 }
diff --git a/server/sessdata/statsinfo.go b/server/sessdata/statsinfo.go
new file mode 100644
index 0000000..5a583b4
--- /dev/null
+++ b/server/sessdata/statsinfo.go
@@ -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
+}
diff --git a/web/src/components/LineChart.vue b/web/src/components/LineChart.vue
index 09cd46a..ac881cd 100644
--- a/web/src/components/LineChart.vue
+++ b/web/src/components/LineChart.vue
@@ -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>
diff --git a/web/src/pages/Home.vue b/web/src/pages/Home.vue
index 10b7629..e66a75a 100644
--- a/web/src/pages/Home.vue
+++ b/web/src/pages/Home.vue
@@ -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>