mirror of
https://github.com/langhuihui/monibuca.git
synced 2025-10-01 03:22:09 +08:00
Compare commits
1 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
13c357493f |
1
dashboard/dist/css/app.93f68a59.css
vendored
1
dashboard/dist/css/app.93f68a59.css
vendored
@@ -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.d549056a.css
vendored
Normal file
1
dashboard/dist/css/app.d549056a.css
vendored
Normal 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-7f7c92c8{0%{opacity:.2}50%{opacity:1}to{opacity:.2}}@keyframes recording-data-v-7f7c92c8{0%{opacity:.2}50%{opacity:1}to{opacity:.2}}.recording[data-v-7f7c92c8]{-webkit-animation:recording-data-v-7f7c92c8 1s infinite;animation:recording-data-v-7f7c92c8 1s infinite}.layout[data-v-7f7c92c8]{padding-bottom:30px;display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.room[data-v-7f7c92c8]{width:250px;margin:10px;text-align:left}.empty[data-v-7f7c92c8]{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-7f7c92c8],.status[data-v-7f7c92c8]{display:-webkit-box;display:-ms-flexbox;display:flex}.status[data-v-7f7c92c8]{position:fixed;left:5px;bottom:10px}.status>div[data-v-7f7c92c8]{margin:0 5px}
|
2
dashboard/dist/index.html
vendored
2
dashboard/dist/index.html
vendored
@@ -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.d549056a.css rel=preload as=style><link href=/css/chunk-vendors.22ebf426.css rel=preload as=style><link href=/js/app.d6cffcca.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.d549056a.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.d6cffcca.js></script></body></html>
|
2
dashboard/dist/js/app.00b4a97a.js
vendored
2
dashboard/dist/js/app.00b4a97a.js
vendored
File diff suppressed because one or more lines are too long
1
dashboard/dist/js/app.00b4a97a.js.map
vendored
1
dashboard/dist/js/app.00b4a97a.js.map
vendored
File diff suppressed because one or more lines are too long
2
dashboard/dist/js/app.d6cffcca.js
vendored
Normal file
2
dashboard/dist/js/app.d6cffcca.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
dashboard/dist/js/app.d6cffcca.js.map
vendored
Normal file
1
dashboard/dist/js/app.d6cffcca.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -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>
|
||||
|
@@ -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>
|
||||
|
||||
|
@@ -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>
|
@@ -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>
|
@@ -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: {
|
||||
|
@@ -1,273 +1,304 @@
|
||||
<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"
|
||||
>{{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>
|
||||
</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"></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>
|
||||
</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"
|
||||
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
|
||||
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,
|
||||
Config
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showPreview: false,
|
||||
currentTab: "",
|
||||
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.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) {
|
||||
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>
|
@@ -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)
|
||||
}
|
||||
}
|
||||
|
@@ -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 {
|
||||
|
@@ -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
139
monica/summary.go
Normal 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 {
|
||||
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
|
||||
}
|
@@ -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 {
|
||||
|
@@ -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) {
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
}
|
||||
|
Reference in New Issue
Block a user