Compare commits

...

2 Commits

Author SHA1 Message Date
langhuihui
ecb931ccb0 增加集群概览显示 2020-02-02 16:20:51 +08:00
langhuihui
13c357493f 更新控制台功能 2020-02-01 15:52:54 +08:00
31 changed files with 1329 additions and 584 deletions

View File

@@ -1 +0,0 @@
#app,body,html{height:100%}#app{font-family:Avenir,Helvetica,Arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-align:center;color:#184c18;position:relative}#app>div:first-child{position:absolute;top:10px;left:30px;font-size:x-large}.content{padding-top:60px}.feature-title[data-v-54efad41]{color:#eb5e46;font-weight:700;font-size:larger}p[data-v-54efad41]{margin:30px;font-size:20px}img[data-v-54efad41]{margin:20px}.root[data-v-e34eab40]{background:#d3d3d3}.root>img[data-v-e34eab40]{width:300px;margin:30px}.log-container{overflow-y:auto;max-height:360px}@-webkit-keyframes recording-data-v-1ed98600{0%{opacity:.2}50%{opacity:1}to{opacity:.2}}@keyframes recording-data-v-1ed98600{0%{opacity:.2}50%{opacity:1}to{opacity:.2}}.recording[data-v-1ed98600]{-webkit-animation:recording-data-v-1ed98600 1s infinite;animation:recording-data-v-1ed98600 1s infinite}.layout[data-v-1ed98600]{padding-bottom:30px;display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.room[data-v-1ed98600]{width:250px;margin:10px;text-align:left}.empty[data-v-1ed98600]{color:#eb5e46;width:100%;min-height:500px;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.empty[data-v-1ed98600],.status[data-v-1ed98600]{display:-webkit-box;display:-ms-flexbox;display:flex}.status[data-v-1ed98600]{position:fixed;left:5px;bottom:10px}.status>div[data-v-1ed98600]{margin:0 5px}

1
dashboard/dist/css/app.ea4656d8.css vendored Normal file
View File

@@ -0,0 +1 @@
#app,body,html{height:100%}#app{font-family:Avenir,Helvetica,Arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-align:center;color:#184c18;position:relative}#app>div:first-child{position:absolute;top:10px;left:30px;font-size:x-large}.content{padding-top:60px}.feature-title[data-v-54efad41]{color:#eb5e46;font-weight:700;font-size:larger}p[data-v-54efad41]{margin:30px;font-size:20px}img[data-v-54efad41]{margin:20px}.root[data-v-e34eab40]{background:#d3d3d3}.root>img[data-v-e34eab40]{width:300px;margin:30px}.records[data-v-4eee1624]{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding:0 15px}.records>[data-v-4eee1624]{width:200px}.log-container{overflow-y:auto;max-height:500px}@-webkit-keyframes recording-data-v-f6113870{0%{opacity:.2}50%{opacity:1}to{opacity:.2}}@keyframes recording-data-v-f6113870{0%{opacity:.2}50%{opacity:1}to{opacity:.2}}.recording[data-v-f6113870]{-webkit-animation:recording-data-v-f6113870 1s infinite;animation:recording-data-v-f6113870 1s infinite}.layout[data-v-f6113870]{padding-bottom:30px;display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.room[data-v-f6113870]{width:250px;margin:10px;text-align:left}.empty[data-v-f6113870]{color:#eb5e46;width:100%;min-height:500px;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.empty[data-v-f6113870],.status[data-v-f6113870]{display:-webkit-box;display:-ms-flexbox;display:flex}.status[data-v-f6113870]{position:fixed;left:5px;bottom:10px}.status>div[data-v-f6113870]{margin:0 5px}

View File

@@ -1 +1 @@
<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge"><meta name=viewport content="width=device-width,initial-scale=1"><link rel=icon href=/favicon.ico><title>Monibuca</title><script src=jessibuca/ajax.js></script><script src=jessibuca/renderer.js></script><link href=/css/app.93f68a59.css rel=preload as=style><link href=/css/chunk-vendors.22ebf426.css rel=preload as=style><link href=/js/app.00b4a97a.js rel=preload as=script><link href=/js/chunk-vendors.ae8ac63d.js rel=preload as=script><link href=/css/chunk-vendors.22ebf426.css rel=stylesheet><link href=/css/app.93f68a59.css rel=stylesheet></head><body><noscript><strong>We're sorry but dashboard doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id=app></div><script src=/js/chunk-vendors.ae8ac63d.js></script><script src=/js/app.00b4a97a.js></script></body></html>
<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge"><meta name=viewport content="width=device-width,initial-scale=1"><link rel=icon href=/favicon.ico><title>Monibuca</title><script src=jessibuca/ajax.js></script><script src=jessibuca/renderer.js></script><link href=/css/app.ea4656d8.css rel=preload as=style><link href=/css/chunk-vendors.22ebf426.css rel=preload as=style><link href=/js/app.af5e5ef3.js rel=preload as=script><link href=/js/chunk-vendors.ebc28a73.js rel=preload as=script><link href=/css/chunk-vendors.22ebf426.css rel=stylesheet><link href=/css/app.ea4656d8.css rel=stylesheet></head><body><noscript><strong>We're sorry but dashboard doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id=app></div><script src=/js/chunk-vendors.ebc28a73.js></script><script src=/js/app.af5e5ef3.js></script></body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2
dashboard/dist/js/app.af5e5ef3.js vendored Normal file

File diff suppressed because one or more lines are too long

1
dashboard/dist/js/app.af5e5ef3.js.map vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -123,6 +123,44 @@
"tslib": "1.10.0"
}
},
"@antv/g6": {
"version": "3.2.9",
"resolved": "https://registry.npm.taobao.org/@antv/g6/download/@antv/g6-3.2.9.tgz",
"integrity": "sha1-V4k4YKrE3iZUTz1dSoG4jWXVK9k=",
"requires": {
"@antv/g": "3.4.7",
"@antv/hierarchy": "0.6.1",
"@antv/util": "1.3.1",
"d3": "5.15.0",
"d3-force": "2.0.1",
"d3-sankey": "0.12.3",
"dagre": "0.8.5",
"numeric": "1.2.6",
"numericjs": "1.2.6"
},
"dependencies": {
"@antv/g": {
"version": "3.4.7",
"resolved": "https://registry.npm.taobao.org/@antv/g/download/@antv/g-3.4.7.tgz",
"integrity": "sha1-1CCM07f4XVEvp19l0YXIoFoYHuU=",
"requires": {
"@antv/gl-matrix": "2.7.1",
"@antv/util": "1.3.1",
"d3-ease": "1.0.6",
"d3-interpolate": "1.1.6",
"d3-timer": "1.0.10"
}
},
"@antv/util": {
"version": "1.3.1",
"resolved": "https://registry.npm.taobao.org/@antv/util/download/@antv/util-1.3.1.tgz?cache=0&sync_timestamp=1572438102677&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40antv%2Futil%2Fdownload%2F%40antv%2Futil-1.3.1.tgz",
"integrity": "sha1-MKNLIB/5Em7A1YxyyBZqnD5kTM0=",
"requires": {
"@antv/gl-matrix": "2.7.1"
}
}
}
},
"@antv/gl-matrix": {
"version": "2.7.1",
"resolved": "https://registry.npm.taobao.org/@antv/gl-matrix/download/@antv/gl-matrix-2.7.1.tgz",
@@ -141,6 +179,24 @@
"tslib": "1.10.0"
}
},
"@antv/hierarchy": {
"version": "0.6.1",
"resolved": "https://registry.npm.taobao.org/@antv/hierarchy/download/@antv/hierarchy-0.6.1.tgz",
"integrity": "sha1-2oH6FUTNTj/17D+WAfv4FC9h7yw=",
"requires": {
"@antv/util": "1.3.1"
},
"dependencies": {
"@antv/util": {
"version": "1.3.1",
"resolved": "https://registry.npm.taobao.org/@antv/util/download/@antv/util-1.3.1.tgz?cache=0&sync_timestamp=1572438102677&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40antv%2Futil%2Fdownload%2F%40antv%2Futil-1.3.1.tgz",
"integrity": "sha1-MKNLIB/5Em7A1YxyyBZqnD5kTM0=",
"requires": {
"@antv/gl-matrix": "2.7.1"
}
}
}
},
"@antv/matrix-util": {
"version": "2.0.4",
"resolved": "https://registry.npm.taobao.org/@antv/matrix-util/download/@antv/matrix-util-2.0.4.tgz",
@@ -4409,8 +4465,7 @@
"commander": {
"version": "2.20.3",
"resolved": "https://registry.npm.taobao.org/commander/download/commander-2.20.3.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcommander%2Fdownload%2Fcommander-2.20.3.tgz",
"integrity": "sha1-/UhehMA+tIgcIHIrpIA16FMa6zM=",
"dev": true
"integrity": "sha1-/UhehMA+tIgcIHIrpIA16FMa6zM="
},
"common-tags": {
"version": "1.8.0",
@@ -5130,16 +5185,171 @@
"type": "1.2.0"
}
},
"d3": {
"version": "5.15.0",
"resolved": "https://registry.npm.taobao.org/d3/download/d3-5.15.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fd3%2Fdownload%2Fd3-5.15.0.tgz",
"integrity": "sha1-/9RJWOajy4pZqEQpxFQpuLylZ3o=",
"requires": {
"d3-array": "1.2.4",
"d3-axis": "1.0.12",
"d3-brush": "1.1.5",
"d3-chord": "1.0.6",
"d3-collection": "1.0.7",
"d3-color": "1.4.0",
"d3-contour": "1.3.2",
"d3-dispatch": "1.0.6",
"d3-drag": "1.2.5",
"d3-dsv": "1.2.0",
"d3-ease": "1.0.6",
"d3-fetch": "1.1.2",
"d3-force": "1.2.1",
"d3-format": "1.4.3",
"d3-geo": "1.11.9",
"d3-hierarchy": "1.1.9",
"d3-interpolate": "1.1.6",
"d3-path": "1.0.9",
"d3-polygon": "1.0.6",
"d3-quadtree": "1.0.7",
"d3-random": "1.1.2",
"d3-scale": "2.2.2",
"d3-scale-chromatic": "1.5.0",
"d3-selection": "1.4.1",
"d3-shape": "1.3.7",
"d3-time": "1.1.0",
"d3-time-format": "2.2.3",
"d3-timer": "1.0.10",
"d3-transition": "1.3.2",
"d3-voronoi": "1.1.4",
"d3-zoom": "1.8.3"
},
"dependencies": {
"d3-force": {
"version": "1.2.1",
"resolved": "https://registry.npm.taobao.org/d3-force/download/d3-force-1.2.1.tgz",
"integrity": "sha1-/Sml0f8YHJ5/BmnkvXK9sOkU7As=",
"requires": {
"d3-collection": "1.0.7",
"d3-dispatch": "1.0.6",
"d3-quadtree": "1.0.7",
"d3-timer": "1.0.10"
}
}
}
},
"d3-array": {
"version": "1.2.4",
"resolved": "https://registry.npm.taobao.org/d3-array/download/d3-array-1.2.4.tgz",
"integrity": "sha1-Y1zk1e6nWfb2BYY9vPww7cc39x8="
},
"d3-axis": {
"version": "1.0.12",
"resolved": "https://registry.npm.taobao.org/d3-axis/download/d3-axis-1.0.12.tgz",
"integrity": "sha1-zfILohDPu0N5WvM3Vohvs2ONqsk="
},
"d3-brush": {
"version": "1.1.5",
"resolved": "https://registry.npm.taobao.org/d3-brush/download/d3-brush-1.1.5.tgz",
"integrity": "sha1-BmuOhNF7GSmGAwRGyXwPun4brNw=",
"requires": {
"d3-dispatch": "1.0.6",
"d3-drag": "1.2.5",
"d3-interpolate": "1.1.6",
"d3-selection": "1.4.1",
"d3-transition": "1.3.2"
}
},
"d3-chord": {
"version": "1.0.6",
"resolved": "https://registry.npm.taobao.org/d3-chord/download/d3-chord-1.0.6.tgz?cache=0&sync_timestamp=1575418695102&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fd3-chord%2Fdownload%2Fd3-chord-1.0.6.tgz",
"integrity": "sha1-MJFX4/LbLHUvAoD+3TXyBnzLsV8=",
"requires": {
"d3-array": "1.2.4",
"d3-path": "1.0.9"
}
},
"d3-collection": {
"version": "1.0.7",
"resolved": "https://registry.npm.taobao.org/d3-collection/download/d3-collection-1.0.7.tgz",
"integrity": "sha1-NJvSqpl32wcQkcExRNXk8WtbMQ4="
},
"d3-color": {
"version": "1.4.0",
"resolved": "https://registry.npm.taobao.org/d3-color/download/d3-color-1.4.0.tgz",
"integrity": "sha1-icRamV7Xc7EzFPBkYN8m1gug7K8="
},
"d3-contour": {
"version": "1.3.2",
"resolved": "https://registry.npm.taobao.org/d3-contour/download/d3-contour-1.3.2.tgz",
"integrity": "sha1-ZSqs1QDSJkyzQjzuENtp9vWb6tM=",
"requires": {
"d3-array": "1.2.4"
}
},
"d3-dispatch": {
"version": "1.0.6",
"resolved": "https://registry.npm.taobao.org/d3-dispatch/download/d3-dispatch-1.0.6.tgz",
"integrity": "sha1-ANN7zuTdjNl3Kd2JOgrCnKq6XVg="
},
"d3-drag": {
"version": "1.2.5",
"resolved": "https://registry.npm.taobao.org/d3-drag/download/d3-drag-1.2.5.tgz",
"integrity": "sha1-JTf0UazTnTFAZne33HfIL32Yj3A=",
"requires": {
"d3-dispatch": "1.0.6",
"d3-selection": "1.4.1"
}
},
"d3-dsv": {
"version": "1.2.0",
"resolved": "https://registry.npm.taobao.org/d3-dsv/download/d3-dsv-1.2.0.tgz?cache=0&sync_timestamp=1573934576301&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fd3-dsv%2Fdownload%2Fd3-dsv-1.2.0.tgz",
"integrity": "sha1-nV91w6X4q9YR900/WEew1DOLiFw=",
"requires": {
"commander": "2.20.3",
"iconv-lite": "0.4.24",
"rw": "1.3.3"
}
},
"d3-ease": {
"version": "1.0.6",
"resolved": "https://registry.npm.taobao.org/d3-ease/download/d3-ease-1.0.6.tgz",
"integrity": "sha1-69ttoi36wKIiIvLU2gb2bEFqDsA="
},
"d3-fetch": {
"version": "1.1.2",
"resolved": "https://registry.npm.taobao.org/d3-fetch/download/d3-fetch-1.1.2.tgz",
"integrity": "sha1-lXyPvG1EgFmboZGxslGL+Gs+G+I=",
"requires": {
"d3-dsv": "1.2.0"
}
},
"d3-force": {
"version": "2.0.1",
"resolved": "https://registry.npm.taobao.org/d3-force/download/d3-force-2.0.1.tgz",
"integrity": "sha1-MXUO7oxDU1MB1XEZW/loO+2lNOI=",
"requires": {
"d3-dispatch": "1.0.6",
"d3-quadtree": "1.0.7",
"d3-timer": "1.0.10"
}
},
"d3-format": {
"version": "1.4.3",
"resolved": "https://registry.npm.taobao.org/d3-format/download/d3-format-1.4.3.tgz",
"integrity": "sha1-To603/P9y4kahInsbmmGAcQblvE="
},
"d3-geo": {
"version": "1.11.9",
"resolved": "https://registry.npm.taobao.org/d3-geo/download/d3-geo-1.11.9.tgz",
"integrity": "sha1-d+rtFLpi/CwK71XNKUOEnIZveuY=",
"requires": {
"d3-array": "1.2.4"
}
},
"d3-hierarchy": {
"version": "1.1.9",
"resolved": "https://registry.npm.taobao.org/d3-hierarchy/download/d3-hierarchy-1.1.9.tgz?cache=0&sync_timestamp=1574108571076&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fd3-hierarchy%2Fdownload%2Fd3-hierarchy-1.1.9.tgz",
"integrity": "sha1-L2vuJMqupD+Nw3VF+gFihVlkeoM="
},
"d3-interpolate": {
"version": "1.1.6",
"resolved": "https://registry.npm.taobao.org/d3-interpolate/download/d3-interpolate-1.1.6.tgz",
@@ -5148,16 +5358,132 @@
"d3-color": "1.4.0"
}
},
"d3-path": {
"version": "1.0.9",
"resolved": "https://registry.npm.taobao.org/d3-path/download/d3-path-1.0.9.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fd3-path%2Fdownload%2Fd3-path-1.0.9.tgz",
"integrity": "sha1-SMBQux/owmJJOoyvVSTj6VkXAc8="
},
"d3-polygon": {
"version": "1.0.6",
"resolved": "https://registry.npm.taobao.org/d3-polygon/download/d3-polygon-1.0.6.tgz",
"integrity": "sha1-C/jLgYCm3BB/UY3feXXhKrv7044="
},
"d3-quadtree": {
"version": "1.0.7",
"resolved": "https://registry.npm.taobao.org/d3-quadtree/download/d3-quadtree-1.0.7.tgz",
"integrity": "sha1-youE33u1N2P+PC8kvUNRN/TlMTU="
},
"d3-random": {
"version": "1.1.2",
"resolved": "https://registry.npm.taobao.org/d3-random/download/d3-random-1.1.2.tgz",
"integrity": "sha1-KDO+fBJDYL+eLT/U8zhHz+bKspE="
},
"d3-regression": {
"version": "1.3.4",
"resolved": "https://registry.npm.taobao.org/d3-regression/download/d3-regression-1.3.4.tgz",
"integrity": "sha1-o0FLGr5aINRqII2V5WWkGL6aDiM="
},
"d3-sankey": {
"version": "0.12.3",
"resolved": "https://registry.npm.taobao.org/d3-sankey/download/d3-sankey-0.12.3.tgz",
"integrity": "sha1-s8JoYnvXLl2AM26N5qy/7J0V0B0=",
"requires": {
"d3-array": "1.2.4",
"d3-shape": "1.3.7"
}
},
"d3-scale": {
"version": "2.2.2",
"resolved": "https://registry.npm.taobao.org/d3-scale/download/d3-scale-2.2.2.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fd3-scale%2Fdownload%2Fd3-scale-2.2.2.tgz",
"integrity": "sha1-TogOCydFrKrd0+3iap6Qip4XuB8=",
"requires": {
"d3-array": "1.2.4",
"d3-collection": "1.0.7",
"d3-format": "1.4.3",
"d3-interpolate": "1.1.6",
"d3-time": "1.1.0",
"d3-time-format": "2.2.3"
}
},
"d3-scale-chromatic": {
"version": "1.5.0",
"resolved": "https://registry.npm.taobao.org/d3-scale-chromatic/download/d3-scale-chromatic-1.5.0.tgz",
"integrity": "sha1-VOMz/HghL0ObFGQftVgB3YETWpg=",
"requires": {
"d3-color": "1.4.0",
"d3-interpolate": "1.1.6"
}
},
"d3-selection": {
"version": "1.4.1",
"resolved": "https://registry.npm.taobao.org/d3-selection/download/d3-selection-1.4.1.tgz",
"integrity": "sha1-mO7bvghfvaW6+i+ePzovTX1iKpg="
},
"d3-shape": {
"version": "1.3.7",
"resolved": "https://registry.npm.taobao.org/d3-shape/download/d3-shape-1.3.7.tgz",
"integrity": "sha1-32OAG+B7yYa8VPY3ibT+UCmStdc=",
"requires": {
"d3-path": "1.0.9"
}
},
"d3-time": {
"version": "1.1.0",
"resolved": "https://registry.npm.taobao.org/d3-time/download/d3-time-1.1.0.tgz",
"integrity": "sha1-seGdMH2unJALflsl/8XcwkmooPE="
},
"d3-time-format": {
"version": "2.2.3",
"resolved": "https://registry.npm.taobao.org/d3-time-format/download/d3-time-format-2.2.3.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fd3-time-format%2Fdownload%2Fd3-time-format-2.2.3.tgz",
"integrity": "sha1-DJoS7ig0KyA35eoc8LnrTddfKcs=",
"requires": {
"d3-time": "1.1.0"
}
},
"d3-timer": {
"version": "1.0.10",
"resolved": "https://registry.npm.taobao.org/d3-timer/download/d3-timer-1.0.10.tgz?cache=0&sync_timestamp=1573934576216&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fd3-timer%2Fdownload%2Fd3-timer-1.0.10.tgz",
"integrity": "sha1-3+dripF0iDGxO22ceT/71QjdneU="
},
"d3-transition": {
"version": "1.3.2",
"resolved": "https://registry.npm.taobao.org/d3-transition/download/d3-transition-1.3.2.tgz",
"integrity": "sha1-qY7yFRvo2GAFQ0NMHKgBQK4js5g=",
"requires": {
"d3-color": "1.4.0",
"d3-dispatch": "1.0.6",
"d3-ease": "1.0.6",
"d3-interpolate": "1.1.6",
"d3-selection": "1.4.1",
"d3-timer": "1.0.10"
}
},
"d3-voronoi": {
"version": "1.1.4",
"resolved": "https://registry.npm.taobao.org/d3-voronoi/download/d3-voronoi-1.1.4.tgz",
"integrity": "sha1-3Tx412U9K7NZKErkeGRdlZRMgpc="
},
"d3-zoom": {
"version": "1.8.3",
"resolved": "https://registry.npm.taobao.org/d3-zoom/download/d3-zoom-1.8.3.tgz",
"integrity": "sha1-tqPb5zjHdjEhzQW4p3lf/hf0/Ao=",
"requires": {
"d3-dispatch": "1.0.6",
"d3-drag": "1.2.5",
"d3-interpolate": "1.1.6",
"d3-selection": "1.4.1",
"d3-transition": "1.3.2"
}
},
"dagre": {
"version": "0.8.5",
"resolved": "https://registry.npm.taobao.org/dagre/download/dagre-0.8.5.tgz",
"integrity": "sha1-ujCwBV2sErbB/MJHgXRCd30Gr+4=",
"requires": {
"graphlib": "2.1.8",
"lodash": "4.17.15"
}
},
"dashdash": {
"version": "1.14.1",
"resolved": "https://registry.npm.taobao.org/dashdash/download/dashdash-1.14.1.tgz",
@@ -7568,6 +7894,14 @@
"integrity": "sha1-ShL/G2A3bvCYYsIJPt2Qgyi+hCM=",
"dev": true
},
"graphlib": {
"version": "2.1.8",
"resolved": "https://registry.npm.taobao.org/graphlib/download/graphlib-2.1.8.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fgraphlib%2Fdownload%2Fgraphlib-2.1.8.tgz",
"integrity": "sha1-V2HUFHN4cAhMkux7XbywWSydNdo=",
"requires": {
"lodash": "4.17.15"
}
},
"gray-matter": {
"version": "4.0.2",
"resolved": "https://registry.npm.taobao.org/gray-matter/download/gray-matter-4.0.2.tgz",
@@ -8023,7 +8357,6 @@
"version": "0.4.24",
"resolved": "https://registry.npm.taobao.org/iconv-lite/download/iconv-lite-0.4.24.tgz?cache=0&sync_timestamp=1579333981154&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ficonv-lite%2Fdownload%2Ficonv-lite-0.4.24.tgz",
"integrity": "sha1-ICK0sl+93CHS9SSXSkdKr+czkIs=",
"dev": true,
"requires": {
"safer-buffer": "2.1.2"
}
@@ -9306,8 +9639,7 @@
"lodash": {
"version": "4.17.15",
"resolved": "https://registry.npm.taobao.org/lodash/download/lodash-4.17.15.tgz?cache=0&sync_timestamp=1563508077056&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Flodash%2Fdownload%2Flodash-4.17.15.tgz",
"integrity": "sha1-tEf2ZwoEVbv+7dETku/zMOoJdUg=",
"dev": true
"integrity": "sha1-tEf2ZwoEVbv+7dETku/zMOoJdUg="
},
"lodash._reinterpolate": {
"version": "3.0.0",
@@ -10296,6 +10628,16 @@
"integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=",
"dev": true
},
"numeric": {
"version": "1.2.6",
"resolved": "https://registry.npm.taobao.org/numeric/download/numeric-1.2.6.tgz",
"integrity": "sha1-dlsCvvl5iPz4gNTrPza4D6MTNao="
},
"numericjs": {
"version": "1.2.6",
"resolved": "https://registry.npm.taobao.org/numericjs/download/numericjs-1.2.6.tgz",
"integrity": "sha1-wNryXEvLIuBDv4NEP5856LM2eYs="
},
"oauth-sign": {
"version": "0.9.0",
"resolved": "https://registry.npm.taobao.org/oauth-sign/download/oauth-sign-0.9.0.tgz",
@@ -13155,6 +13497,11 @@
"aproba": "1.2.0"
}
},
"rw": {
"version": "1.3.3",
"resolved": "https://registry.npm.taobao.org/rw/download/rw-1.3.3.tgz",
"integrity": "sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q="
},
"rxjs": {
"version": "6.5.4",
"resolved": "https://registry.npm.taobao.org/rxjs/download/rxjs-6.5.4.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Frxjs%2Fdownload%2Frxjs-6.5.4.tgz",
@@ -13182,8 +13529,7 @@
"safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npm.taobao.org/safer-buffer/download/safer-buffer-2.1.2.tgz",
"integrity": "sha1-RPoWGwGHuVSd2Eu5GAL5vYOFzWo=",
"dev": true
"integrity": "sha1-RPoWGwGHuVSd2Eu5GAL5vYOFzWo="
},
"sax": {
"version": "1.2.4",

View File

@@ -11,6 +11,7 @@
},
"dependencies": {
"@antv/g2plot": "^0.11.22",
"@antv/g6": "^3.2.9",
"core-js": "^3.4.4",
"view-design": "^4.0.0",
"vue": "^2.6.10",

View File

@@ -1,80 +1,71 @@
<template>
<div id="app">
<div>Monibuca</div>
<Menu mode="horizontal" :active-name="selectedMenu" style="position: absolute;top: 0;right: 0;">
<MenuItem name="home" to="/">首页</MenuItem>
<MenuItem name="docs" to="/docs" target="_blank">
文档
</MenuItem>
<MenuItem name="console" to="console">
控制台
</MenuItem>
<Submenu name="plugins">
<template slot="title">
内置插件
</template>
<MenuGroup title="发布者/订阅者">
<MenuItem name="cluster" to="/docs/plugins.html#cluster插件">集群</MenuItem>
<MenuItem name="rtmp" to="/docs/plugins.html#rtmp插件">RTMP</MenuItem>
</MenuGroup>
<MenuGroup title="订阅者">
<MenuItem name="jessica" to="/docs/plugins.html#jessica插件">Jessica</MenuItem>
<MenuItem name="HDL" to="/docs/plugins.html#http-flv插件">Http-Flv</MenuItem>
<MenuItem name="record" to="/docs/plugins.html#recordflv插件">录制Flv</MenuItem>
</MenuGroup>
<MenuGroup title="发布者">
<MenuItem name="HLS" to="/docs/plugins.html#hls插件">HLS</MenuItem>
<MenuItem name="TS" to="/docs/plugins.html#hls插件">TS</MenuItem>
</MenuGroup>
<MenuGroup title="钩子">
<MenuItem name="Auth" to="/docs/plugins.html#校验插件">验证</MenuItem>
<MenuItem name="QoS">QoS</MenuItem>
<MenuItem name="gateway" to="/docs/plugins.html#网关插件">网关</MenuItem>
</MenuGroup>
</Submenu>
<MenuItem name="4" to="about">
支持
</MenuItem>
</Menu>
<router-view class="content"></router-view>
<div>Copyright © 2019-2020 dexter 苏ICP备20001212号</div>
</div>
<div id="app">
<div>Monibuca</div>
<Menu mode="horizontal" :active-name="selectedMenu" style="position: absolute;top: 0;right: 0;">
<MenuItem name="home" to="/">首页</MenuItem>
<MenuItem name="docs" to="/docs" target="_blank">文档</MenuItem>
<MenuItem name="console" to="console">控制台</MenuItem>
<Submenu name="plugins">
<template slot="title">内置插件</template>
<MenuGroup title="发布者/订阅者">
<MenuItem name="cluster" target="_blank" to="/docs/plugins.html#cluster插件">集群</MenuItem>
<MenuItem name="rtmp" target="_blank" to="/docs/plugins.html#rtmp插件">RTMP</MenuItem>
</MenuGroup>
<MenuGroup title="订阅者">
<MenuItem name="jessica" target="_blank" to="/docs/plugins.html#jessica插件">Jessica</MenuItem>
<MenuItem name="HDL" target="_blank" to="/docs/plugins.html#http-flv插件">Http-Flv</MenuItem>
<MenuItem name="record" target="_blank" to="/docs/plugins.html#recordflv插件">录制Flv</MenuItem>
</MenuGroup>
<MenuGroup title="发布者">
<MenuItem name="HLS" target="_blank" to="/docs/plugins.html#hls插件">HLS</MenuItem>
<MenuItem name="TS" target="_blank" to="/docs/plugins.html#hls插件">TS</MenuItem>
</MenuGroup>
<MenuGroup title="钩子">
<MenuItem name="Auth" target="_blank" to="/docs/plugins.html#校验插件">验证</MenuItem>
<MenuItem name="QoS">QoS</MenuItem>
<MenuItem name="gateway" target="_blank" to="/docs/plugins.html#网关插件">网关</MenuItem>
</MenuGroup>
</Submenu>
<MenuItem name="4" to="about">支持</MenuItem>
</Menu>
<router-view class="content"></router-view>
<div>Copyright © 2019-2020 dexter 苏ICP备20001212号</div>
</div>
</template>
<script>
export default {
name: 'app',
data(){
return {
selectedMenu:"home"
}
}
}
export default {
name: "app",
data() {
return {
selectedMenu: "home"
};
}
};
</script>
<style>
body,html{
height: 100%;
}
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #184c18;
position: relative;
height: 100%;
}
body,
html {
height: 100%;
}
#app {
font-family: "Avenir", Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #184c18;
position: relative;
height: 100%;
}
#app > div:first-child {
position: absolute;
top: 10px;
left: 30px;
font-size: x-large;
}
.content{
padding-top: 60px;
}
#app > div:first-child {
position: absolute;
top: 10px;
left: 30px;
font-size: x-large;
}
.content {
padding-top: 60px;
}
</style>

View File

@@ -0,0 +1,97 @@
<template>
<div id="mountNode"></div>
</template>
<script>
import { mapState } from "vuex";
import G6 from "@antv/g6";
var graph = null;
export default {
computed: {
...mapState({
data(state) {
let summary = state.summary;
// 点集
let nodes = [];
// 边集
let edges = [];
this.addServer(summary, nodes, edges);
return {
nodes,
edges
};
}
})
},
methods: {
addServer(node, nodes, edges) {
let result = {
id: node.Address,
label: node.Address,
description: `cpu:${node.CPUUsage >> 0}% mem:${node.Memory.Usage >>
0}%`,
shape: "modelRect",
logoIcon: {
show: false
}
};
nodes.push(result);
if (node.Rooms) {
for (let i = 0; i < node.Rooms.length; i++) {
let room = node.Rooms[i];
let roomId = result.id + room.StreamPath;
nodes.push({
id: roomId,
label: room.StreamPath,
shape: "rect"
});
edges.push({ source: result.id, target: roomId });
if (room.SubscriberInfo) {
for (let j = 0; j < room.SubscriberInfo.length; j++) {
let subId = roomId + room.SubscriberInfo[j].ID;
nodes.push({
id: subId,
label: room.SubscriberInfo[j].ID
});
edges.push({ source: roomId, target: subId });
}
}
}
}
if (node.Children && node.Children.length > 0) {
for (let i = 0; i < node.Children.length; i++) {
let child = this.addServer(node.Children[i], nodes, edges);
edges.push({
source: result.id,
target: child.id
});
}
}
return result;
}
},
watch: {
data(v) {
if (graph) {
graph.read(v); // 加载数据
}
}
},
mounted() {
graph = new G6.Graph({
renderer: "svg",
container: "mountNode", // 指定挂载容器
width: 800, // 图的宽度
height: 500, // 图的高度
layout: {
type: "radial"
},
defaultNode: {}
});
graph.read(this.data); // 加载数据
}
};
</script>
<style>
</style>

View File

@@ -1,9 +1,7 @@
<template>
<Modal v-bind="$attrs" draggable v-on="$listeners" @on-visible-change="onVisible" title="配置">
<div>
<pre>{{config}}</pre>
</div>
</Modal>
<div style="padding:0 15px">
<pre>{{config}}</pre>
</div>
</template>
<script>

View File

@@ -1,50 +1,60 @@
<template>
<Modal
v-bind="$attrs" draggable
v-on="$listeners"
:title="url"
@on-ok="onClosePreview"
@on-cancel="onClosePreview">
<canvas id="canvas" width="488" height="275" style="background: black"/>
</Modal>
<Modal
v-bind="$attrs"
draggable
v-on="$listeners"
:title="url"
@on-ok="onClosePreview"
@on-cancel="onClosePreview"
>
<canvas id="canvas" width="488" height="275" style="background: black" />
<div slot="footer">
<Button v-if="audioEnabled" @click="turnOff" icon="md-volume-off" />
<Button v-else @click="turnOn" icon="md-volume-up"></Button>
</div>
</Modal>
</template>
<script>
let h5lc = null;
export default {
name: 'Jessibuca',
props: {
audioEnabled: Boolean,
},
data(){
return {
url:""
}
},
watch: {
audioEnabled(value){
h5lc.audioEnabled(value)
}
},
mounted() {
h5lc = new window.Jessibuca({
canvas: document.getElementById("canvas"),
decoder: "jessibuca/ff.js"
});
},
destroyed() {
this.onClosePreview()
h5lc.destroy()
},
methods: {
play(url){
this.url = url
h5lc.play(url)
},
onClosePreview() {
h5lc.close();
},
}
let h5lc = null;
export default {
name: "Jessibuca",
data() {
return {
audioEnabled: false,
url: ""
};
},
watch: {
audioEnabled(value) {
h5lc.audioEnabled(value);
}
},
mounted() {
h5lc = new window.Jessibuca({
canvas: document.getElementById("canvas"),
decoder: "jessibuca/ff.js"
});
},
destroyed() {
this.onClosePreview();
h5lc.destroy();
},
methods: {
play(url) {
this.url = url;
h5lc.play(url);
},
onClosePreview() {
h5lc.close();
},
turnOn() {
this.audioEnabled = true;
},
turnOff() {
this.audioEnabled = false;
}
}
};
</script>

View File

@@ -1,13 +1,13 @@
<template>
<Modal v-bind="$attrs" draggable v-on="$listeners" @on-visible-change="onVisible" title="日志跟踪">
<div ref="logContainer" class="log-container">
<pre><template v-for="item in $store.state.logs">{{item+"\n"}}</template></pre>
</div>
<div slot="footer">
<div style="padding:0 15px">
<div>
自动滚动
<Switch v-model="autoScroll" />
</div>
</Modal>
<div ref="logContainer" class="log-container">
<pre><template v-for="item in $store.state.logs">{{item+"\n"}}</template></pre>
</div>
</div>
</template>
<script>
@@ -18,15 +18,14 @@ export default {
autoScroll: true
};
},
mounted() {
this.fetchLogs();
},
destroyed() {
this.stopFetchLogs();
},
methods: {
...mapActions(["fetchLogs", "stopFetchLogs"]),
onVisible(visible) {
if (visible) {
this.fetchLogs();
} else {
this.stopFetchLogs();
}
}
...mapActions(["fetchLogs", "stopFetchLogs"])
},
updated() {
if (this.autoScroll) {
@@ -39,6 +38,6 @@ export default {
<style>
.log-container {
overflow-y: auto;
max-height: 360px;
max-height: 500px;
}
</style>

View File

@@ -1,21 +1,14 @@
<template>
<Modal v-bind="$attrs" draggable v-on="$listeners" title="录制的视频" @on-visible-change="onVisible" :z-index="900">
<List>
<ListItem v-for="item in data" :key="item">
<ListItemMeta :title="item.Path">
<template slot="description">{{toSizeStr(item.Size)}} {{toDurationStr(item.Duration)}}</template>
</ListItemMeta>
<template slot="action">
<li>
<a href="javascript:void(0)" @click="play(item)">Play</a>
</li>
<li>
<a href="javascript:void(0)" @click="deleteFlv(item)">Delete</a>
</li>
</template>
</ListItem>
</List>
</Modal>
<div class="records">
<Card v-for="item in data" :key="item">
<p slot="title">{{item.Path}}</p>
<div slot="extra">
<Button @click="play(item)" icon="md-play" size="small"></Button>
<Button @click="deleteFlv(item)" icon="ios-trash" size="small"></Button>
</div>
{{toSizeStr(item.Size)}} {{toDurationStr(item.Duration)}}
</Card>
</div>
</template>
<script>
@@ -39,7 +32,7 @@ export default {
{ streamPath: item.Path.replace(".flv", "") },
x => {
if (x == "success") {
this.onVisible(true)
this.onVisible(true);
this.$Message.success("删除成功");
} else {
this.$Message.error(x);
@@ -92,14 +85,14 @@ export default {
return value + "ms";
}
},
onVisible(visible){
if(visible){
onVisible(visible) {
if (visible) {
window.ajax.getJSON(
"//" + location.host + "/api/record/flv/list",
{},
x => {
this.data = x;
}
"//" + location.host + "/api/record/flv/list",
{},
x => {
this.data = x;
}
);
}
}
@@ -107,5 +100,13 @@ export default {
};
</script>
<style>
<style scoped>
.records {
display: flex;
flex-wrap: wrap;
padding: 0 15px;
}
.records > * {
width: 200px;
}
</style>

View File

@@ -0,0 +1,62 @@
<template>
<Modal v-bind="$attrs" draggable v-on="$listeners" title="查看订阅者">
<Table :columns="subtableColumns" :data="data"></Table>
</Modal>
</template>
<script>
export default {
props: {
data: Array
},
data() {
return {
subtableColumns: [
{
title: "类型",
key: "Type"
},
{
title: "Name",
key: "ID"
},
{
title: "订阅时间",
render(h, { row }) {
return h("StartTime", {
props: {
value: row.SubscribeTime
}
});
}
},
{
title: "丢帧",
render(h, { row }) {
return h(
"span",
row.TotalPacket ? row.TotalDrop + "/" + row.TotalPacket : ""
);
}
},
{
title: "Buffer",
render(h, { row }) {
return h("Progress", {
props: {
percent: Math.floor((row.BufferLength * 99) / 1024),
"text-inside": true,
"stroke-width": 20,
"stroke-color": ["#87d068", "#ff0000"]
}
});
}
}
]
};
}
};
</script>
<style>
</style>

View File

@@ -7,6 +7,7 @@ let logsES = null
export default new Vuex.Store({
state: {
summary: {
Address: location.hostname,
NetWork: [],
Rooms: [],
Memory: {
@@ -17,7 +18,8 @@ export default new Vuex.Store({
HardDisk: {
Used: 0,
Usage: 0
}
},
Children: {}
}, logs: []
},
mutations: {
@@ -36,6 +38,7 @@ export default new Vuex.Store({
summaryES.onmessage = evt => {
if (!evt.data) return
let summary = JSON.parse(evt.data)
summary.Address = location.hostname
commit("update", { summary })
}
},

View File

@@ -1,273 +1,325 @@
<template>
<div class="layout">
<ButtonGroup vertical>
<Button icon="ios-folder" @click="showRecords=true"></Button>
<Button icon="md-bug" @click="showLogs=true"></Button>
<Button icon="md-settings" @click="showConfig=true"></Button>
</ButtonGroup>
<Card v-for="item in Rooms" :key="item.StreamPath" class="room">
<div style="text-align:left;">
<Tabs v-model="currentTab" @on-click="onChangeTab">
<TabPane label="直播流" icon="md-videocam">
<div class="layout">
<Card v-for="item in Rooms" :key="item.StreamPath" class="room">
<p slot="title">{{typeMap[item.Type]||item.Type}}{{item.StreamPath}}</p>
<StartTime slot="extra" :value="item.StartTime"></StartTime>
<p>
{{SoundFormat(item.AudioInfo.SoundFormat)}} {{item.AudioInfo.PacketCount}}
{{SoundRate(item.AudioInfo.SoundRate)}} 声道:{{item.AudioInfo.SoundType}}
{{SoundFormat(item.AudioInfo.SoundFormat)}} {{item.AudioInfo.PacketCount}}
{{SoundRate(item.AudioInfo.SoundRate)}} 声道:{{item.AudioInfo.SoundType}}
</p>
<p>
{{CodecID(item.VideoInfo.CodecID)}} {{item.VideoInfo.PacketCount}}
{{item.VideoInfo.SPSInfo.Width}}x{{item.VideoInfo.SPSInfo.Height}}
{{CodecID(item.VideoInfo.CodecID)}} {{item.VideoInfo.PacketCount}}
{{item.VideoInfo.SPSInfo.Width}}x{{item.VideoInfo.SPSInfo.Height}}
</p>
<ButtonGroup size="small">
<Button
@click="onShowDetail(item)"
icon="ios-people"
>{{item.SubscriberInfo?item.SubscriberInfo.length:0}}
</Button>
<Button v-if="item.Type" @click="preview(item)" icon="md-eye"></Button>
<Button
@click="stopRecord(item)"
class="recording"
v-if="isRecording(item)"
icon="ios-radio-button-on"
></Button>
<Button @click="record(item)" v-else icon="ios-radio-button-on"></Button>
<Button @click="onShowDetail(item)" icon="ios-people">{{getSubscriberCount(item)}}</Button>
<Button v-if="item.Type" @click="preview(item)" icon="md-eye"></Button>
<Button
@click="stopRecord(item)"
class="recording"
v-if="isRecording(item)"
icon="ios-radio-button-on"
></Button>
<Button @click="record(item)" v-else icon="ios-radio-button-on"></Button>
</ButtonGroup>
</Card>
<div v-if="Rooms.length==0" class="empty">
<Icon type="md-wine" size="50"/>
没有任何房间
</Card>
<div v-if="Rooms.length==0" class="empty">
<Icon type="md-wine" size="50" />没有任何房间
</div>
</div>
<div class="status">
<Alert>带宽消耗 📥{{totalInNetSpeed}} 📤{{totalOutNetSpeed}}</Alert>
<Alert
:type="memoryStatus"
>内存使用{{networkFormat(Memory.Used,"M")}} 占比{{Memory.Usage.toFixed(2)}}%
</Alert>
<Alert :type="cpuStatus">CPU使用{{CPUUsage.toFixed(2)}}%</Alert>
<Alert
:type="hardDiskStatus"
>磁盘使用{{networkFormat(HardDisk.Used,"M")}} 占比{{HardDisk.Usage.toFixed(2)}}%
</Alert>
</div>
<Jessibuca ref="jessibuca" v-model="showPreview"></Jessibuca>
<Records v-model="showRecords"/>
<Logs v-model="showLogs"/>
<Config v-model="showConfig"/>
</TabPane>
<TabPane label="集群总览" icon="ios-cloud">
<Cluster />
</TabPane>
<TabPane label="录制的视频" icon="ios-folder" name="recordsPanel">
<Records ref="recordsPanel" />
</TabPane>
<TabPane label="日志跟踪" icon="md-bug">
<Logs />
</TabPane>
<TabPane label="查看配置" icon="md-settings" name="configPanel">
<Config ref="configPanel" />
</TabPane>
</Tabs>
<div class="status">
<Alert>带宽消耗 📥{{totalInNetSpeed}} 📤{{totalOutNetSpeed}}</Alert>
<Alert
:type="memoryStatus"
>内存使用{{networkFormat(Memory.Used,"M")}} 占比{{Memory.Usage.toFixed(2)}}%</Alert>
<Alert :type="cpuStatus">CPU使用{{CPUUsage.toFixed(2)}}%</Alert>
<Alert
:type="hardDiskStatus"
>磁盘使用{{networkFormat(HardDisk.Used,"M")}} 占比{{HardDisk.Usage.toFixed(2)}}%</Alert>
</div>
<Jessibuca ref="jessibuca" v-model="showPreview"></Jessibuca>
<Subscribers :data="currentStream && currentStream.SubscriberInfo" v-model="showSubscribers" />
</div>
</template>
<script>
import {mapActions, mapState} from "vuex";
import Jessibuca from "../components/Jessibuca";
import StartTime from "../components/StartTime";
import Records from "../components/Records";
import Logs from "../components/Logs";
import Config from "../components/Config"
const uintInc = {
"": "K",
K: "M",
M: "G",
G: null
import { mapActions, mapState } from "vuex";
import Jessibuca from "../components/Jessibuca";
import StartTime from "../components/StartTime";
import Records from "../components/Records";
import Logs from "../components/Logs";
import Config from "../components/Config";
import Subscribers from "../components/Subscribers";
import Cluster from "../components/Cluster";
const uintInc = {
"": "K",
K: "M",
M: "G",
G: null
};
const SoundFormat = {
0: "Linear PCM, platform endian",
1: "ADPCM",
2: "MP3",
3: "Linear PCM, little endian",
4: "Nellymoser 16kHz mono",
5: "Nellymoser 8kHz mono",
6: "Nellymoser",
7: "G.711 A-law logarithmic PCM",
8: "G.711 mu-law logarithmic PCM",
9: "reserved",
10: "AAC",
11: "Speex",
14: "MP3 8Khz",
15: "Device-specific sound"
};
const CodecID = {
1: "JPEG (currently unused)",
2: "Sorenson H.263",
3: "Screen video",
4: "On2 VP6",
5: "On2 VP6 with alpha channel",
6: "Screen video version 2",
7: "AVC",
12: "H265"
};
export default {
name: "Console",
components: {
Jessibuca,
StartTime,
Records,
Logs,
Subscribers,
Config,
Cluster
},
data() {
return {
showPreview: false,
showSubscribers: false,
currentTab: "",
currentStream: [],
typeMap: {
FlvFile: "🎥",
TS: "🎬",
HLS: "🍎",
"": "⏳",
Match365: "🏆",
RTMP: "🚠"
}
};
const SoundFormat = {
0: "Linear PCM, platform endian",
1: "ADPCM",
2: "MP3",
3: "Linear PCM, little endian",
4: "Nellymoser 16kHz mono",
5: "Nellymoser 8kHz mono",
6: "Nellymoser",
7: "G.711 A-law logarithmic PCM",
8: "G.711 mu-law logarithmic PCM",
9: "reserved",
10: "AAC",
11: "Speex",
14: "MP3 8Khz",
15: "Device-specific sound"
};
const CodecID = {
1: "JPEG (currently unused)",
2: "Sorenson H.263",
3: "Screen video",
4: "On2 VP6",
5: "On2 VP6 with alpha channel",
6: "Screen video version 2",
7: "AVC",
12: "H265"
};
export default {
name: "Console",
components: {
Jessibuca,
StartTime,
Records,
Logs, Config
},
data() {
return {
showPreview: false,
showRecords: false,
showLogs: false,
showConfig: false,
typeMap: {
FlvFile: "🎥",
TS: "🎬",
HLS: "🍎",
"": "⏳",
Match365: "🏆",
RTMP: "🚠"
}
};
},
computed: {
...mapState({
Rooms: state => state.summary.Rooms || [],
Memory: state => state.summary.Memory,
CPUUsage: state => state.summary.CPUUsage,
HardDisk: state => state.summary.HardDisk,
cpuStatus: state => {
if (state.summary.CPUUsage > 99) return "error";
return state.summary.CPUUsage > 50 ? "warning" : "success";
},
memoryStatus(state) {
if (state.summary.CPUUsage > 99) return "error";
return state.summary.CPUUsage > 50 ? "warning" : "success";
},
hardDiskStatus(state) {
if (state.summary.CPUUsage > 99) return "error";
return state.summary.CPUUsage > 50 ? "warning" : "success";
},
totalInNetSpeed(state) {
return (
this.networkFormat(
state.summary.NetWork.reduce((aac, c) => aac + c.ReceiveSpeed, 0)
) + "/S"
);
},
totalOutNetSpeed(state) {
return (
this.networkFormat(
state.summary.NetWork.reduce((aac, c) => aac + c.SentSpeed, 0)
) + "/S"
);
}
})
},
methods: {
...mapActions(["fetchSummary", "stopFetchSummary"]),
preview(item) {
this.$refs.jessibuca.play(
"ws://" + location.hostname + ":8080/" + item.StreamPath
);
this.showPreview = true;
},
onShowDetail() {
// this.showDetail = true
// this.currentSub = item
},
networkFormat(value, unit = "") {
if (value > 1024 && uintInc[unit]) {
return this.networkFormat(value / 1024, uintInc[unit]);
}
return value.toFixed(2).replace(".00", "") + unit + "B";
},
SoundFormat(soundFormat) {
return SoundFormat[soundFormat];
},
CodecID(codec) {
return CodecID[codec];
},
SoundRate(rate) {
return rate > 1000 ? rate / 1000 + "kHz" : rate + "Hz";
},
record(item) {
window.ajax.get(
"//" + location.host + "/api/record/flv",
{streamPath: item.StreamPath},
x => {
if (x == "success") {
this.$Message.success("开始录制");
} else {
this.$Message.error(x);
}
}
);
},
stopRecord(item) {
window.ajax.get(
"//" + location.host + "/api/record/flv/stop",
{streamPath: item.StreamPath},
x => {
if (x == "success") {
this.$Message.success("停止录制");
} else {
this.$Message.error(x);
}
}
);
},
isRecording(item) {
return (
item.SubscriberInfo &&
item.SubscriberInfo.find(x => x.Type == "FlvRecord")
);
},
computed: {
...mapState({
Rooms: state => state.summary.Rooms || [],
Memory: state => state.summary.Memory,
CPUUsage: state => state.summary.CPUUsage,
HardDisk: state => state.summary.HardDisk,
cpuStatus: state => {
if (state.summary.CPUUsage > 99) return "error";
return state.summary.CPUUsage > 50 ? "warning" : "success";
},
memoryStatus(state) {
if (state.summary.CPUUsage > 99) return "error";
return state.summary.CPUUsage > 50 ? "warning" : "success";
},
hardDiskStatus(state) {
if (state.summary.CPUUsage > 99) return "error";
return state.summary.CPUUsage > 50 ? "warning" : "success";
},
totalInNetSpeed(state) {
return (
this.networkFormat(
state.summary.NetWork
? state.summary.NetWork.reduce(
(aac, c) => aac + c.ReceiveSpeed,
0
)
: 0
) + "/S"
);
},
totalOutNetSpeed(state) {
return (
this.networkFormat(
state.summary.NetWork
? state.summary.NetWork.reduce((aac, c) => aac + c.SentSpeed, 0)
: 0
) + "/S"
);
}
})
},
methods: {
...mapActions(["fetchSummary", "stopFetchSummary"]),
getSubscriberCount(item) {
if (
this.currentStream &&
this.currentStream.StreamPath == item.StreamPath
) {
this.currentStream = item;
}
return item.SubscriberInfo ? item.SubscriberInfo.length : 0;
},
preview(item) {
this.$refs.jessibuca.play(
"ws://" + location.hostname + ":8080/" + item.StreamPath
);
this.showPreview = true;
},
onShowDetail(item) {
this.showSubscribers = true;
this.currentStream = item;
},
networkFormat(value, unit = "") {
if (value > 1024 && uintInc[unit]) {
return this.networkFormat(value / 1024, uintInc[unit]);
}
return value.toFixed(2).replace(".00", "") + unit + "B";
},
SoundFormat(soundFormat) {
return SoundFormat[soundFormat];
},
CodecID(codec) {
return CodecID[codec];
},
SoundRate(rate) {
return rate > 1000 ? rate / 1000 + "kHz" : rate + "Hz";
},
record(item) {
this.$Modal.confirm({
title: "提示",
content: "<p>是否使用追加模式</p><small>选择取消将覆盖已有文件</small>",
onOk: () => {
window.ajax.get(
"//" + location.host + "/api/record/flv?append=true",
{ streamPath: item.StreamPath },
x => {
if (x == "success") {
this.$Message.success("开始录制(追加模式)");
} else {
this.$Message.error(x);
}
}
);
},
mounted() {
this.fetchSummary();
},
destroyed() {
this.stopFetchSummary();
onCancel: () => {
window.ajax.get(
"//" + location.host + "/api/record/flv",
{ streamPath: item.StreamPath },
x => {
if (x == "success") {
this.$Message.success("开始录制");
} else {
this.$Message.error(x);
}
}
);
}
};
});
},
stopRecord(item) {
window.ajax.get(
"//" + location.host + "/api/record/flv/stop",
{ streamPath: item.StreamPath },
x => {
if (x == "success") {
this.$Message.success("停止录制");
} else {
this.$Message.error(x);
}
}
);
},
isRecording(item) {
return (
item.SubscriberInfo &&
item.SubscriberInfo.find(x => x.Type == "FlvRecord")
);
},
onChangeTab(name) {
switch (name) {
case "recordsPanel":
this.$refs.recordsPanel.onVisible(true);
break;
case "configPanel":
this.$refs.configPanel.onVisible(true);
}
}
},
mounted() {
this.fetchSummary();
},
destroyed() {
this.stopFetchSummary();
}
};
</script>
<style scoped>
@keyframes recording {
0% {
opacity: 0.2;
}
50% {
opacity: 1;
}
100% {
opacity: 0.2;
}
}
@keyframes recording {
0% {
opacity: 0.2;
}
50% {
opacity: 1;
}
100% {
opacity: 0.2;
}
}
.recording {
animation: recording 1s infinite;
}
.recording {
animation: recording 1s infinite;
}
.layout {
padding-bottom: 30px;
display: flex;
flex-wrap: wrap;
}
.layout {
padding-bottom: 30px;
display: flex;
flex-wrap: wrap;
}
.room {
width: 250px;
margin: 10px;
text-align: left;
}
.room {
width: 250px;
margin: 10px;
text-align: left;
}
.empty {
color: #eb5e46;
width: 100%;
min-height: 500px;
display: flex;
justify-content: center;
align-items: center;
}
.empty {
color: #eb5e46;
width: 100%;
min-height: 500px;
display: flex;
justify-content: center;
align-items: center;
}
.status {
position: fixed;
display: flex;
left: 5px;
bottom: 10px;
}
.status {
position: fixed;
display: flex;
left: 5px;
bottom: 10px;
}
.status > div {
margin: 0 5px;
}
.status > div {
margin: 0 5px;
}
</style>

View File

@@ -54,3 +54,16 @@ func (h OnDropHook) Trigger(s *OutputStream) {
h(s)
}
}
var OnSummaryHooks = make(OnSummaryHook, 0)
type OnSummaryHook []func(bool)
func (h OnSummaryHook) AddHook(hook func(bool)) {
OnSummaryHooks = append(h, hook)
}
func (h OnSummaryHook) Trigger(v bool) {
for _, h := range h {
h(v)
}
}

View File

@@ -13,6 +13,7 @@ func Run(configFile string) (err error) {
if ConfigRaw, err = ioutil.ReadFile(configFile); err != nil {
return
}
go Summary.StartSummary()
if _, err = toml.Decode(string(ConfigRaw), cg); err == nil {
for name, config := range plugins {
if cfg, ok := cg.Plugins[name]; ok {

View File

@@ -111,11 +111,13 @@ func (r *Room) Run() {
case <-r.Done():
return
case <-update.C:
r.SubscriberInfo = make([]*SubscriberInfo, len(r.Subscribers))
i := 0
for _, v := range r.Subscribers {
r.SubscriberInfo[i] = &v.SubscriberInfo
i++
if Summary.Running() {
r.SubscriberInfo = make([]*SubscriberInfo, len(r.Subscribers))
i := 0
for _, v := range r.Subscribers {
r.SubscriberInfo[i] = &v.SubscriberInfo
i++
}
}
case s := <-r.Control:
switch v := s.(type) {

139
monica/summary.go Normal file
View File

@@ -0,0 +1,139 @@
package monica
import (
"time"
"github.com/shirou/gopsutil/cpu"
"github.com/shirou/gopsutil/disk"
"github.com/shirou/gopsutil/mem"
"github.com/shirou/gopsutil/net"
)
var (
Summary = ServerSummary{}
)
type ServerSummary struct {
Address string
Memory struct {
Total uint64
Free uint64
Used uint64
Usage float64
}
CPUUsage float64
HardDisk struct {
Total uint64
Free uint64
Used uint64
Usage float64
}
NetWork []NetWorkInfo
Rooms []*RoomInfo
lastNetWork []NetWorkInfo
ref int
control chan bool
reportChan chan *ServerSummary
Children map[string]*ServerSummary
}
type NetWorkInfo struct {
Name string
Receive uint64
Sent uint64
ReceiveSpeed uint64
SentSpeed uint64
}
func (s *ServerSummary) StartSummary() {
ticker := time.NewTicker(time.Second)
s.control = make(chan bool)
s.reportChan = make(chan *ServerSummary)
for {
select {
case <-ticker.C:
if s.ref > 0 {
Summary.collect()
}
case v := <-s.control:
if v {
if s.ref++; s.ref == 1 {
OnSummaryHooks.Trigger(true)
}
} else {
if s.ref--; s.ref == 0 {
s.lastNetWork = nil
OnSummaryHooks.Trigger(false)
}
}
case report := <-s.reportChan:
s.Children[report.Address] = report
}
}
}
func (s *ServerSummary) Running() bool {
return s.ref > 0
}
func (s *ServerSummary) Add() {
s.control <- true
}
func (s *ServerSummary) Done() {
s.control <- false
}
func (s *ServerSummary) Report(slave *ServerSummary) {
s.reportChan <- slave
}
func (s *ServerSummary) collect() {
v, _ := mem.VirtualMemory()
//c, _ := cpu.Info()
cc, _ := cpu.Percent(time.Second, false)
d, _ := disk.Usage("/")
//n, _ := host.Info()
nv, _ := net.IOCounters(true)
//boottime, _ := host.BootTime()
//btime := time.Unix(int64(boottime), 0).Format("2006-01-02 15:04:05")
s.Memory.Total = v.Total / 1024 / 1024
s.Memory.Free = v.Available / 1024 / 1024
s.Memory.Used = v.Used / 1024 / 1024
s.Memory.Usage = v.UsedPercent
//fmt.Printf(" Mem : %v MB Free: %v MB Used:%v Usage:%f%%\n", v.Total/1024/1024, v.Available/1024/1024, v.Used/1024/1024, v.UsedPercent)
//if len(c) > 1 {
// for _, sub_cpu := range c {
// modelname := sub_cpu.ModelName
// cores := sub_cpu.Cores
// fmt.Printf(" CPU : %v %v cores \n", modelname, cores)
// }
//} else {
// sub_cpu := c[0]
// modelname := sub_cpu.ModelName
// cores := sub_cpu.Cores
// fmt.Printf(" CPU : %v %v cores \n", modelname, cores)
//}
s.CPUUsage = cc[0]
s.HardDisk.Free = d.Free / 1024 / 1024 / 1024
s.HardDisk.Total = d.Total / 1024 / 1024 / 1024
s.HardDisk.Used = d.Used / 1024 / 1024 / 1024
s.HardDisk.Usage = d.UsedPercent
s.NetWork = make([]NetWorkInfo, len(nv))
for i, n := range nv {
s.NetWork[i].Name = n.Name
s.NetWork[i].Receive = n.BytesRecv
s.NetWork[i].Sent = n.BytesSent
if s.lastNetWork != nil && len(s.lastNetWork) > i {
s.NetWork[i].ReceiveSpeed = n.BytesRecv - s.lastNetWork[i].Receive
s.NetWork[i].SentSpeed = n.BytesSent - s.lastNetWork[i].Sent
}
}
s.lastNetWork = s.NetWork
//fmt.Printf(" Network: %v bytes / %v bytes\n", nv[0].BytesRecv, nv[0].BytesSent)
//fmt.Printf(" SystemBoot:%v\n", btime)
//fmt.Printf(" CPU Used : used %f%% \n", cc[0])
//fmt.Printf(" HD : %v GB Free: %v GB Usage:%f%%\n", d.Total/1024/1024/1024, d.Free/1024/1024/1024, d.UsedPercent)
//fmt.Printf(" OS : %v(%v) %v \n", n.Platform, n.PlatformFamily, n.PlatformVersion)
//fmt.Printf(" Hostname : %v \n", n.Hostname)
s.Rooms = nil
AllRoom.Range(func(key interface{}, v interface{}) bool {
s.Rooms = append(s.Rooms, &v.(*Room).RoomInfo)
return true
})
return
}

View File

@@ -4,14 +4,15 @@ import (
"bytes"
"compress/gzip"
"context"
. "github.com/langhuihui/monibuca/monica"
"github.com/quangngotan95/go-m3u8/m3u8"
"io"
"io/ioutil"
"log"
"net/http"
"path/filepath"
"time"
. "github.com/langhuihui/monibuca/monica"
"github.com/quangngotan95/go-m3u8/m3u8"
)
type HLS struct {
@@ -94,8 +95,8 @@ func (p *HLS) run(info *M3u8Info) {
if len(tsItems) > 3 {
tsItems = tsItems[len(tsItems)-3:]
}
info.M3u8Info = make([]TSCost, len(tsItems))
for i, v := range tsItems {
info.M3u8Info = nil
for _, v := range tsItems {
tsCost := TSCost{}
tsUrl, _ := info.Req.URL.Parse(v.Segment)
tsReq, _ := http.NewRequest("GET", tsUrl.String(), nil)
@@ -122,7 +123,7 @@ func (p *HLS) run(info *M3u8Info) {
} else if err != nil {
log.Printf("%s reqTs:%v", p.StreamPath, err)
}
info.M3u8Info[i] = tsCost
info.M3u8Info = append(info.M3u8Info, tsCost)
}
time.Sleep(time.Second * time.Duration(playlist.Target) * 2)

View File

@@ -1,8 +1,15 @@
package cluster
import (
. "github.com/langhuihui/monibuca/monica"
"bufio"
"encoding/json"
"log"
"math/rand"
"net"
"sync"
"time"
. "github.com/langhuihui/monibuca/monica"
)
const (
@@ -11,12 +18,18 @@ const (
MSG_VIDEO
MSG_SUBSCRIBE
MSG_AUTH
MSG_SUMMARY
MSG_LOG
)
var config = struct {
Master string
ListenAddr string
}{}
var (
config = struct {
Master string
ListenAddr string
}{}
slaves = sync.Map{}
masterConn *net.TCPConn
)
func init() {
InstallPlugin(&PluginConfig{
@@ -29,12 +42,88 @@ func init() {
func run() {
if config.Master != "" {
OnSubscribeHooks.AddHook(onSubscribe)
addr, err := net.ResolveTCPAddr("tcp", config.Master)
if MayBeError(err) {
return
}
masterConn, err = net.DialTCP("tcp", nil, addr)
if MayBeError(err) {
return
}
go readMaster()
}
if config.ListenAddr != "" {
OnSummaryHooks.AddHook(onSummary)
log.Printf("server bare start at %s", config.ListenAddr)
log.Fatal(ListenBare(config.ListenAddr))
}
}
func readMaster() {
var err error
defer func() {
for {
time.Sleep(time.Second*5 + time.Duration(rand.Int63n(5))*time.Second)
addr, _ := net.ResolveTCPAddr("tcp", config.Master)
if masterConn, err = net.DialTCP("tcp", nil, addr); err == nil {
go readMaster()
return
}
}
}()
brw := bufio.NewReadWriter(bufio.NewReader(masterConn), bufio.NewWriter(masterConn))
//首次报告
if b, err := json.Marshal(Summary); err == nil {
_, err = masterConn.Write(b)
}
for {
cmd, err := brw.ReadByte()
if err != nil {
return
}
switch cmd {
case MSG_SUMMARY: //收到主服务器指令,进行采集和上报
if cmd, err = brw.ReadByte(); err != nil {
return
}
if cmd == 1 {
Summary.Add()
go onReport()
} else {
Summary.Done()
}
}
}
}
//定时上报
func onReport() {
for range time.NewTicker(time.Second).C {
if Summary.Running() {
if b, err := json.Marshal(Summary); err == nil {
data := make([]byte, len(b)+2)
data[0] = MSG_SUMMARY
copy(data[1:], b)
data[len(data)-1] = 0
_, err = masterConn.Write(data)
}
} else {
return
}
}
}
//通知从服务器需要上报或者关闭上报
func onSummary(start bool) {
slaves.Range(func(k, v interface{}) bool {
conn := v.(*net.TCPConn)
b := []byte{MSG_SUMMARY, 0}
if start {
b[1] = 1
}
conn.Write(b)
return true
})
}
func onSubscribe(s *OutputStream) {
if s.Publisher == nil {

View File

@@ -45,6 +45,7 @@ func (p *Receiver) readAVPacket(avType byte) (av *pool.AVPacket, err error) {
pool.RecycleSlice(buf)
return
}
func PullUpStream(streamPath string) {
addr, err := net.ResolveTCPAddr("tcp", config.Master)
if MayBeError(err) {

View File

@@ -3,13 +3,15 @@ package cluster
import (
"bufio"
"encoding/binary"
"encoding/json"
"fmt"
. "github.com/langhuihui/monibuca/monica"
"github.com/langhuihui/monibuca/monica/pool"
"net"
"strconv"
"strings"
"time"
. "github.com/langhuihui/monibuca/monica"
"github.com/langhuihui/monibuca/monica/pool"
)
func ListenBare(addr string) error {
@@ -49,6 +51,7 @@ func ListenBare(addr string) error {
func process(conn net.Conn) {
defer conn.Close()
reader := bufio.NewReader(conn)
connAddr := conn.RemoteAddr().String()
stream := OutputStream{
SendHandler: func(p *pool.SendPacket) error {
head := pool.GetSlice(9)
@@ -64,7 +67,7 @@ func process(conn net.Conn) {
}
return nil
}, SubscriberInfo: SubscriberInfo{
ID: conn.RemoteAddr().String(),
ID: connAddr,
Type: "Bare",
},
}
@@ -73,31 +76,36 @@ func process(conn net.Conn) {
if err != nil {
return
}
bytes, err := reader.ReadBytes(0)
if err != nil {
return
}
switch cmd {
case MSG_SUBSCRIBE:
if stream.Room != nil {
fmt.Printf("bare stream already exist from %s", conn.RemoteAddr())
return
}
bytes, err := reader.ReadBytes(0)
if MayBeError(err) {
return
}
streamName := string(bytes[0 : len(bytes)-1])
stream.Play(streamName)
go stream.Play(streamName)
case MSG_AUTH:
bytes, err := reader.ReadBytes(0)
if err != nil {
print(err)
return
}
sign := strings.Split(string(bytes[0:len(bytes)-1]), ",")
head := []byte{MSG_AUTH, 0}
head := []byte{MSG_AUTH, 2}
if len(sign) > 1 && AuthHooks.Trigger(sign[1]) == nil {
head[1] = 1
}
conn.Write(head)
conn.Write(bytes)
case MSG_SUMMARY: //收到从服务器发来报告,加入摘要中
var summary *ServerSummary
if err = json.Unmarshal(bytes, summary); err == nil {
summary.Address = connAddr
Summary.Report(summary)
if _, ok := slaves.Load(connAddr); !ok {
slaves.Store(connAddr, conn)
defer slaves.Delete(connAddr)
}
}
default:
fmt.Printf("bare receive unknown cmd:%d from %s", cmd, conn.RemoteAddr())
return

View File

@@ -3,11 +3,6 @@ package gateway
import (
"context"
"encoding/json"
. "github.com/langhuihui/monibuca/monica"
"github.com/shirou/gopsutil/cpu"
"github.com/shirou/gopsutil/disk"
"github.com/shirou/gopsutil/mem"
"github.com/shirou/gopsutil/net"
"io/ioutil"
"log"
"mime"
@@ -16,6 +11,8 @@ import (
"path"
"runtime"
"time"
. "github.com/langhuihui/monibuca/monica"
)
var (
@@ -133,93 +130,19 @@ func website(w http.ResponseWriter, r *http.Request) {
}
func summary(w http.ResponseWriter, r *http.Request) {
sse := NewSSE(w, r.Context())
s := collect()
sse.WriteJSON(&s)
for range time.NewTicker(time.Second).C {
old := s
s = collect()
for i, v := range s.NetWork {
s.NetWork[i].ReceiveSpeed = v.Receive - old.NetWork[i].Receive
s.NetWork[i].SentSpeed = v.Sent - old.NetWork[i].Sent
}
AllRoom.Range(func(key interface{}, v interface{}) bool {
s.Rooms = append(s.Rooms, &v.(*Room).RoomInfo)
return true
})
if sse.WriteJSON(&s) != nil {
break
Summary.Add()
defer Summary.Done()
sse.WriteJSON(&Summary)
ticker := time.NewTicker(time.Second)
defer ticker.Stop()
for {
select {
case <-ticker.C:
if sse.WriteJSON(&Summary) != nil {
return
}
case <-r.Context().Done():
return
}
}
}
type Summary struct {
Memory struct {
Total uint64
Free uint64
Used uint64
Usage float64
}
CPUUsage float64
HardDisk struct {
Total uint64
Free uint64
Used uint64
Usage float64
}
NetWork []NetWorkInfo
Rooms []*RoomInfo
}
type NetWorkInfo struct {
Name string
Receive uint64
Sent uint64
ReceiveSpeed uint64
SentSpeed uint64
}
func collect() (s Summary) {
v, _ := mem.VirtualMemory()
//c, _ := cpu.Info()
cc, _ := cpu.Percent(time.Second, false)
d, _ := disk.Usage("/")
//n, _ := host.Info()
nv, _ := net.IOCounters(true)
//boottime, _ := host.BootTime()
//btime := time.Unix(int64(boottime), 0).Format("2006-01-02 15:04:05")
s.Memory.Total = v.Total / 1024 / 1024
s.Memory.Free = v.Available / 1024 / 1024
s.Memory.Used = v.Used / 1024 / 1024
s.Memory.Usage = v.UsedPercent
//fmt.Printf(" Mem : %v MB Free: %v MB Used:%v Usage:%f%%\n", v.Total/1024/1024, v.Available/1024/1024, v.Used/1024/1024, v.UsedPercent)
//if len(c) > 1 {
// for _, sub_cpu := range c {
// modelname := sub_cpu.ModelName
// cores := sub_cpu.Cores
// fmt.Printf(" CPU : %v %v cores \n", modelname, cores)
// }
//} else {
// sub_cpu := c[0]
// modelname := sub_cpu.ModelName
// cores := sub_cpu.Cores
// fmt.Printf(" CPU : %v %v cores \n", modelname, cores)
//}
s.CPUUsage = cc[0]
s.HardDisk.Free = d.Free / 1024 / 1024 / 1024
s.HardDisk.Total = d.Total / 1024 / 1024 / 1024
s.HardDisk.Used = d.Used / 1024 / 1024 / 1024
s.HardDisk.Usage = d.UsedPercent
s.NetWork = make([]NetWorkInfo, len(nv))
for i, n := range nv {
s.NetWork[i].Name = n.Name
s.NetWork[i].Receive = n.BytesRecv
s.NetWork[i].Sent = n.BytesSent
}
//fmt.Printf(" Network: %v bytes / %v bytes\n", nv[0].BytesRecv, nv[0].BytesSent)
//fmt.Printf(" SystemBoot:%v\n", btime)
//fmt.Printf(" CPU Used : used %f%% \n", cc[0])
//fmt.Printf(" HD : %v GB Free: %v GB Usage:%f%%\n", d.Total/1024/1024/1024, d.Free/1024/1024/1024, d.UsedPercent)
//fmt.Printf(" OS : %v(%v) %v \n", n.Platform, n.PlatformFamily, n.PlatformVersion)
//fmt.Printf(" Hostname : %v \n", n.Hostname)
return
}