Compare commits

..

1 Commits

Author SHA1 Message Date
langhuihui
87776b9540 增加log查看和config查看 2020-01-31 14:22:21 +08:00
14 changed files with 410 additions and 289 deletions

1
dashboard/dist/css/app.93f68a59.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}.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}

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}@-webkit-keyframes recording-data-v-65ce2c22{0%{opacity:.2}50%{opacity:1}to{opacity:.2}}@keyframes recording-data-v-65ce2c22{0%{opacity:.2}50%{opacity:1}to{opacity:.2}}.recording[data-v-65ce2c22]{-webkit-animation:recording-data-v-65ce2c22 1s infinite;animation:recording-data-v-65ce2c22 1s infinite}.layout[data-v-65ce2c22]{padding-bottom:30px;display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.room[data-v-65ce2c22]{width:250px;margin:10px;text-align:left}.empty[data-v-65ce2c22]{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-65ce2c22],.status[data-v-65ce2c22]{display:-webkit-box;display:-ms-flexbox;display:flex}.status[data-v-65ce2c22]{position:fixed;left:5px;bottom:10px}.status>div[data-v-65ce2c22]{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.b2f5eee5.css rel=preload as=style><link href=/css/chunk-vendors.22ebf426.css rel=preload as=style><link href=/js/app.2d98a4c2.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.b2f5eee5.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.2d98a4c2.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.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>

2
dashboard/dist/js/app.00b4a97a.js vendored Normal file

File diff suppressed because one or more lines are too long

1
dashboard/dist/js/app.00b4a97a.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

View File

@@ -0,0 +1,31 @@
<template>
<Modal v-bind="$attrs" draggable v-on="$listeners" @on-visible-change="onVisible" title="配置">
<div>
<pre>{{config}}</pre>
</div>
</Modal>
</template>
<script>
export default {
data() {
return {
config: ""
};
},
methods: {
onVisible(visible) {
if (visible) {
window.ajax.get(
"//" + location.host + "/api/config",
{},
x => (this.config = x)
);
}
}
}
};
</script>
<style>
</style>

View File

@@ -0,0 +1,44 @@
<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">
自动滚动
<Switch v-model="autoScroll" />
</div>
</Modal>
</template>
<script>
import { mapActions } from "vuex";
export default {
data() {
return {
autoScroll: true
};
},
methods: {
...mapActions(["fetchLogs", "stopFetchLogs"]),
onVisible(visible) {
if (visible) {
this.fetchLogs();
} else {
this.stopFetchLogs();
}
}
},
updated() {
if (this.autoScroll) {
this.$refs.logContainer.scrollTop = this.$refs.logContainer.offsetHeight;
}
}
};
</script>
<style>
.log-container {
overflow-y: auto;
max-height: 360px;
}
</style>

View File

@@ -1,5 +1,5 @@
<template>
<Modal v-bind="$attrs" draggable v-on="$listeners" title="录制的视频">
<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">
@@ -39,7 +39,8 @@ export default {
{ streamPath: item.Path.replace(".flv", "") },
x => {
if (x == "success") {
this.$Message.success("开始发布");
this.onVisible(true)
this.$Message.success("删除成功");
} else {
this.$Message.error(x);
}
@@ -90,16 +91,18 @@ export default {
} else {
return value + "ms";
}
}
},
mounted() {
window.ajax.getJSON(
"//" + location.host + "/api/record/flv/list",
{},
x => {
this.data = x;
},
onVisible(visible){
if(visible){
window.ajax.getJSON(
"//" + location.host + "/api/record/flv/list",
{},
x => {
this.data = x;
}
);
}
);
}
}
};
</script>

View File

@@ -3,40 +3,55 @@ import Vuex from 'vuex'
Vue.use(Vuex)
let summaryES = null
let logsES = null
export default new Vuex.Store({
state: {
summary:{
NetWork:[],
Rooms:[],
Memory:{
summary: {
NetWork: [],
Rooms: [],
Memory: {
Used: 0,
Usage: 0
},
CPUUsage:0,
HardDisk:{
CPUUsage: 0,
HardDisk: {
Used: 0,
Usage: 0
}
}
}, logs: []
},
mutations: {
update(state,payload){
Object.assign(state,payload)
update(state, payload) {
Object.assign(state, payload)
},
addLog(state, payload) {
state.logs.push(payload)
}
},
actions: {
fetchSummary({commit}){
fetchSummary({ commit }) {
summaryES = new EventSource(
"//" + location.host + "/api/summary"
"//" + location.host + "/api/summary"
);
summaryES.onmessage = evt=>{
summaryES.onmessage = evt => {
if (!evt.data) return
let summary = JSON.parse(evt.data)
commit("update",{summary})
commit("update", { summary })
}
},
stopFetchSummary(){
fetchLogs({ commit }) {
logsES = new EventSource(
"//" + location.host + "/api/logs"
)
logsES.onmessage = evt => {
if (!evt.data) return
commit("addLog", evt.data)
}
},
stopFetchLogs() {
logsES.close()
},
stopFetchSummary() {
summaryES.close()
}
},

View File

@@ -1,261 +1,273 @@
<template>
<div class="layout">
<ButtonGroup vertical>
<Button icon="ios-folder" @click="showRecords=true"></Button>
<Button icon="md-bug"></Button>
<Button icon="md-settings"></Button>
</ButtonGroup>
<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}}
</p>
<p>
{{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>
</ButtonGroup>
</Card>
<div v-if="Rooms.length==0" class="empty">
<Icon type="md-wine" size="50" />没有任何房间
<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">
<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}}
</p>
<p>
{{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>
</ButtonGroup>
</Card>
<div v-if="Rooms.length==0" class="empty">
<Icon type="md-wine" size="50"/>
没有任何房间
</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"/>
</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" />
</div>
</template>
<script>
import { mapActions, mapState } from "vuex";
import Jessibuca from "../components/Jessibuca";
import StartTime from "../components/StartTime";
import Records from "../components/Records";
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
},
data() {
return {
showPreview: false,
showRecords: false,
typeMap: {
FlvFile:"🎥",
TS: "🎬",
HLS: "🍎",
"": "⏳",
Match365: "🏆",
RTMP: "🚠"
}
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
};
},
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);
}
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")
);
}
},
mounted() {
this.fetchSummary();
},
destroyed() {
this.stopFetchSummary();
}
);
},
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")
);
}
},
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

@@ -3,11 +3,17 @@ package monica
import (
"encoding/json"
"github.com/BurntSushi/toml"
"io/ioutil"
"log"
)
var ConfigRaw []byte
func Run(configFile string) (err error) {
if _, err = toml.DecodeFile(configFile, cg); err == nil {
if ConfigRaw, err = ioutil.ReadFile(configFile); err != nil {
return
}
if _, err = toml.Decode(string(ConfigRaw), cg); err == nil {
for name, config := range plugins {
if cfg, ok := cg.Plugins[name]; ok {
b, _ := json.Marshal(cfg)

View File

@@ -84,24 +84,34 @@ func init() {
})
}
func run() {
http.HandleFunc("/api/stop", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
if streamPath := r.URL.Query().Get("stream"); streamPath != "" {
if b, ok := AllRoom.Load(streamPath); ok {
b.(*Room).Cancel()
w.Write([]byte("success"))
} else {
w.Write([]byte("no query stream"))
}
} else {
w.Write([]byte("no such stream"))
}
})
http.HandleFunc("/api/stop", stopPublish)
http.HandleFunc("/api/summary", summary)
http.HandleFunc("/api/logs", watchLogs)
http.HandleFunc("/api/config", getConfig)
http.HandleFunc("/", website)
log.Printf("server gateway start at %s", config.ListenAddr)
log.Fatal(http.ListenAndServe(config.ListenAddr, nil))
}
func getConfig(w http.ResponseWriter, r *http.Request) {
w.Write(ConfigRaw)
}
func watchLogs(w http.ResponseWriter, r *http.Request) {
AddWriter(NewSSE(w, r.Context()))
<-r.Context().Done()
}
func stopPublish(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
if streamPath := r.URL.Query().Get("stream"); streamPath != "" {
if b, ok := AllRoom.Load(streamPath); ok {
b.(*Room).Cancel()
w.Write([]byte("success"))
} else {
w.Write([]byte("no query stream"))
}
} else {
w.Write([]byte("no such stream"))
}
}
func website(w http.ResponseWriter, r *http.Request) {
filePath := r.URL.Path
if filePath == "/" {