mirror of
https://github.com/langhuihui/monibuca.git
synced 2025-09-27 14:22:08 +08:00
Compare commits
5 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
40fba0d348 | ||
![]() |
73941d1e0b | ||
![]() |
709c2c6ac7 | ||
![]() |
f96bc11ddb | ||
![]() |
5563ddc0d2 |
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.ce470878.css rel=preload as=style><link href=/css/chunk-vendors.22ebf426.css rel=preload as=style><link href=/js/app.017fb959.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.ce470878.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.017fb959.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.ce470878.css rel=preload as=style><link href=/css/chunk-vendors.22ebf426.css rel=preload as=style><link href=/js/app.16c0d7c9.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.ce470878.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.16c0d7c9.js></script></body></html>
|
9
dashboard/dist/jessibuca/renderer.js
vendored
9
dashboard/dist/jessibuca/renderer.js
vendored
@@ -1,4 +1,6 @@
|
||||
window.AudioContext = window.AudioContext || window.webkitAudioContext;
|
||||
function Jessibuca(opt) {
|
||||
this.audioContext = new window.AudioContext()
|
||||
this.canvasElement = opt.canvas;
|
||||
this.contextOptions = opt.contextOptions;
|
||||
this.videoBuffer = opt.videoBuffer || 1
|
||||
@@ -65,9 +67,7 @@ function Jessibuca(opt) {
|
||||
}
|
||||
}
|
||||
};
|
||||
window.AudioContext = window.AudioContext || window.webkitAudioContext;
|
||||
function _unlock() {
|
||||
var context = Jessibuca.prototype.audioContext = Jessibuca.prototype.audioContext || new window.AudioContext();
|
||||
function _unlock(context) {
|
||||
context.resume();
|
||||
var source = context.createBufferSource();
|
||||
source.buffer = context.createBuffer(1, 1, 22050);
|
||||
@@ -81,7 +81,7 @@ function _unlock() {
|
||||
// document.addEventListener("touchend", _unlock, true);
|
||||
Jessibuca.prototype.audioEnabled = function (flag) {
|
||||
if (flag) {
|
||||
_unlock()
|
||||
_unlock(this.audioContext)
|
||||
this.audioEnabled = function (flag) {
|
||||
if (flag) {
|
||||
this.audioContext.resume();
|
||||
@@ -452,6 +452,7 @@ Jessibuca.prototype.close = function () {
|
||||
if (this.audioInterval) {
|
||||
clearInterval(this.audioInterval)
|
||||
}
|
||||
delete this.playAudio
|
||||
this.decoderWorker.postMessage({ cmd: "close" })
|
||||
this.contextGL.clear(this.contextGL.COLOR_BUFFER_BIT);
|
||||
}
|
||||
|
2
dashboard/dist/js/app.017fb959.js
vendored
2
dashboard/dist/js/app.017fb959.js
vendored
File diff suppressed because one or more lines are too long
1
dashboard/dist/js/app.017fb959.js.map
vendored
1
dashboard/dist/js/app.017fb959.js.map
vendored
File diff suppressed because one or more lines are too long
2
dashboard/dist/js/app.16c0d7c9.js
vendored
Normal file
2
dashboard/dist/js/app.16c0d7c9.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
dashboard/dist/js/app.16c0d7c9.js.map
vendored
Normal file
1
dashboard/dist/js/app.16c0d7c9.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -1,4 +1,6 @@
|
||||
window.AudioContext = window.AudioContext || window.webkitAudioContext;
|
||||
function Jessibuca(opt) {
|
||||
this.audioContext = new window.AudioContext()
|
||||
this.canvasElement = opt.canvas;
|
||||
this.contextOptions = opt.contextOptions;
|
||||
this.videoBuffer = opt.videoBuffer || 1
|
||||
@@ -65,9 +67,7 @@ function Jessibuca(opt) {
|
||||
}
|
||||
}
|
||||
};
|
||||
window.AudioContext = window.AudioContext || window.webkitAudioContext;
|
||||
function _unlock() {
|
||||
var context = Jessibuca.prototype.audioContext = Jessibuca.prototype.audioContext || new window.AudioContext();
|
||||
function _unlock(context) {
|
||||
context.resume();
|
||||
var source = context.createBufferSource();
|
||||
source.buffer = context.createBuffer(1, 1, 22050);
|
||||
@@ -81,7 +81,7 @@ function _unlock() {
|
||||
// document.addEventListener("touchend", _unlock, true);
|
||||
Jessibuca.prototype.audioEnabled = function (flag) {
|
||||
if (flag) {
|
||||
_unlock()
|
||||
_unlock(this.audioContext)
|
||||
this.audioEnabled = function (flag) {
|
||||
if (flag) {
|
||||
this.audioContext.resume();
|
||||
@@ -452,6 +452,7 @@ Jessibuca.prototype.close = function () {
|
||||
if (this.audioInterval) {
|
||||
clearInterval(this.audioInterval)
|
||||
}
|
||||
delete this.playAudio
|
||||
this.decoderWorker.postMessage({ cmd: "close" })
|
||||
this.contextGL.clear(this.contextGL.COLOR_BUFFER_BIT);
|
||||
}
|
||||
|
@@ -9,8 +9,8 @@
|
||||
>
|
||||
<canvas id="canvas" width="488" height="275" style="background: black" />
|
||||
<div slot="footer">
|
||||
音频缓冲:
|
||||
<InputNumber v-model="audioBuffer" size="small"></InputNumber>
|
||||
<!-- 音频缓冲:-->
|
||||
<!-- <InputNumber v-model="audioBuffer" size="small"></InputNumber>-->
|
||||
<Button v-if="audioEnabled" @click="turnOff" icon="md-volume-off" />
|
||||
<Button v-else @click="turnOn" icon="md-volume-up"></Button>
|
||||
</div>
|
||||
@@ -24,7 +24,7 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
audioEnabled: false,
|
||||
audioBuffer: 12,
|
||||
// audioBuffer: 12,
|
||||
url: ""
|
||||
};
|
||||
},
|
||||
@@ -32,9 +32,9 @@ export default {
|
||||
audioEnabled(value) {
|
||||
h5lc.audioEnabled(value);
|
||||
},
|
||||
audioBuffer(v) {
|
||||
h5lc.audioBuffer = v;
|
||||
}
|
||||
// audioBuffer(v) {
|
||||
// h5lc.audioBuffer = v;
|
||||
// }
|
||||
},
|
||||
mounted() {
|
||||
h5lc = new window.Jessibuca({
|
||||
|
34
main.go
34
main.go
@@ -15,6 +15,7 @@ import (
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
@@ -46,6 +47,7 @@ func main() {
|
||||
}
|
||||
addr := flag.String("port", "8000", "http server port")
|
||||
flag.Parse()
|
||||
http.HandleFunc("/instance/listDir", listDir)
|
||||
http.HandleFunc("/instance/import", importInstance)
|
||||
http.HandleFunc("/instance/updateConfig", updateConfig)
|
||||
http.HandleFunc("/instance/list", listInstance)
|
||||
@@ -59,6 +61,24 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
func listDir(w http.ResponseWriter, r *http.Request) {
|
||||
if input := r.URL.Query().Get("input"); input != "" {
|
||||
if dir, err := os.Open(filepath.Dir(input)); err == nil {
|
||||
var dirs []string
|
||||
if infos, err := dir.Readdir(0); err == nil {
|
||||
for _, info := range infos {
|
||||
if info.IsDir() {
|
||||
dirs = append(dirs, info.Name())
|
||||
}
|
||||
}
|
||||
if bytes, err := json.Marshal(dirs); err == nil {
|
||||
w.Write(bytes)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func importInstance(w http.ResponseWriter, r *http.Request) {
|
||||
var e error
|
||||
defer func() {
|
||||
@@ -70,6 +90,9 @@ func importInstance(w http.ResponseWriter, r *http.Request) {
|
||||
}()
|
||||
name := r.URL.Query().Get("name")
|
||||
if importPath := r.URL.Query().Get("path"); importPath != "" {
|
||||
if strings.HasSuffix(importPath, "/") {
|
||||
importPath = importPath[:len(importPath)-1]
|
||||
}
|
||||
f, err := os.Open(importPath)
|
||||
if e = err; err != nil {
|
||||
return
|
||||
@@ -114,6 +137,13 @@ func importInstance(w http.ResponseWriter, r *http.Request) {
|
||||
for _, m := range reg.FindAllStringSubmatch(string(mainGo), -1) {
|
||||
instances[name].Plugins = append(instances[name].Plugins, m[1])
|
||||
}
|
||||
var file *os.File
|
||||
file, e = os.OpenFile(path.Join(instancesDir, name+".toml"), os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
tomlEncoder := toml.NewEncoder(file)
|
||||
e = tomlEncoder.Encode(instances[name])
|
||||
} else {
|
||||
e = errors.New("路径中缺少文件")
|
||||
}
|
||||
@@ -298,11 +328,11 @@ func main(){
|
||||
}
|
||||
sse.WriteEvent("step", []byte("5:go build 成功!"))
|
||||
build.Reset()
|
||||
build.WriteString("kill -9 `cat pid`\nnohup ./")
|
||||
build.WriteString("kill -9 `cat pid`\n ./")
|
||||
binFile := strings.TrimSuffix(p.Path, "/")
|
||||
_, binFile = path.Split(binFile)
|
||||
build.WriteString(binFile)
|
||||
build.WriteString(" > log.txt & echo $! > pid\n")
|
||||
build.WriteString(" & echo $! > pid\n")
|
||||
err = ioutil.WriteFile(path.Join(p.Path, "restart.sh"), build.Bytes(), 0777)
|
||||
if err != nil {
|
||||
return
|
||||
|
@@ -38,7 +38,7 @@ func (av *AVPacket) ADTS2ASC() (tagPacket *AVPacket) {
|
||||
tagPacket = NewAVPacket(FLV_TAG_TYPE_AUDIO)
|
||||
tagPacket.Payload = ADTSToAudioSpecificConfig(av.Payload)
|
||||
tagPacket.IsAACSequence = true
|
||||
ADTSLength := 7 + (int(av.Payload[1]&1) << 1)
|
||||
ADTSLength := 7 + ((1 - int(av.Payload[1]&1)) << 1)
|
||||
if len(av.Payload) > ADTSLength {
|
||||
av.Payload[0] = 0xAF
|
||||
av.Payload[1] = 0x01 //raw AAC
|
||||
|
@@ -10,7 +10,7 @@ import (
|
||||
)
|
||||
|
||||
var ConfigRaw []byte
|
||||
var Version = "0.2.4"
|
||||
var Version = "0.2.8"
|
||||
var EngineInfo = &struct {
|
||||
Version string
|
||||
StartTime time.Time
|
||||
|
@@ -35,6 +35,7 @@ func (ts *TS) run() {
|
||||
spsHead := []byte{0xE1, 0, 0}
|
||||
ppsHead := []byte{0x01, 0, 0}
|
||||
nalLength := []byte{0, 0, 0, 0}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ts.Done():
|
||||
@@ -45,9 +46,18 @@ func (ts *TS) run() {
|
||||
ts.TotalPesCount++
|
||||
switch tsPesPkt.PesPkt.Header.StreamID & 0xF0 {
|
||||
case mpegts.STREAM_ID_AUDIO:
|
||||
av := avformat.NewAVPacket(avformat.FLV_TAG_TYPE_AUDIO)
|
||||
av.Payload = tsPesPkt.PesPkt.Payload
|
||||
ts.PushAudio(av)
|
||||
data := tsPesPkt.PesPkt.Payload
|
||||
for remainLen := len(data); remainLen > 0; {
|
||||
// AACFrameLength(13)
|
||||
// xx xxxxxxxx xxx
|
||||
frameLen := (int(data[3]&3) << 11) | (int(data[4]) << 3) | (int(data[5]) >> 5)
|
||||
av := avformat.NewAVPacket(avformat.FLV_TAG_TYPE_AUDIO)
|
||||
av.Payload = data[:frameLen]
|
||||
ts.PushAudio(av)
|
||||
data = data[frameLen:remainLen]
|
||||
remainLen = remainLen - frameLen
|
||||
}
|
||||
|
||||
case mpegts.STREAM_ID_VIDEO:
|
||||
var err error
|
||||
av := avformat.NewAVPacket(avformat.FLV_TAG_TYPE_VIDEO)
|
||||
|
@@ -99,6 +99,7 @@ func summary(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
func sysInfo(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
bytes, err := json.Marshal(EngineInfo)
|
||||
if err == nil {
|
||||
_, err = w.Write(bytes)
|
||||
|
78
plugins/logrotate/index.go
Normal file
78
plugins/logrotate/index.go
Normal file
@@ -0,0 +1,78 @@
|
||||
package logrotate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
. "github.com/langhuihui/monibuca/monica"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"time"
|
||||
)
|
||||
|
||||
var config = new(LogRotate)
|
||||
|
||||
type LogRotate struct {
|
||||
Path string
|
||||
Size int64
|
||||
Days int
|
||||
file *os.File
|
||||
currentSize int64
|
||||
createTime time.Time
|
||||
hours float64
|
||||
splitFunc func() bool
|
||||
}
|
||||
|
||||
func init() {
|
||||
InstallPlugin(&PluginConfig{
|
||||
Name: "LogRotate",
|
||||
Type: PLUGIN_HOOK,
|
||||
Config: config,
|
||||
Run: run,
|
||||
})
|
||||
}
|
||||
func run() {
|
||||
if config.Size > 0 {
|
||||
config.splitFunc = config.splitBySize
|
||||
} else {
|
||||
if config.Days == 0 {
|
||||
config.Days = 1
|
||||
}
|
||||
config.hours = float64(config.Days) * 24
|
||||
config.splitFunc = config.splitByTime
|
||||
}
|
||||
config.createTime = time.Now()
|
||||
err := os.MkdirAll(config.Path, 0666)
|
||||
config.file, err = os.OpenFile(path.Join(config.Path, fmt.Sprintf("%s.log", config.createTime.Format("2006-01-02T15:04:05"))), os.O_TRUNC|os.O_WRONLY|os.O_CREATE, 0666)
|
||||
if err == nil {
|
||||
stat, _ := config.file.Stat()
|
||||
config.currentSize = stat.Size()
|
||||
AddWriter(config)
|
||||
} else {
|
||||
log.Println(err)
|
||||
}
|
||||
}
|
||||
func (l *LogRotate) splitBySize() bool {
|
||||
return l.currentSize >= l.Size
|
||||
}
|
||||
func (l *LogRotate) splitByTime() bool {
|
||||
return time.Since(l.createTime).Hours() > l.hours
|
||||
}
|
||||
func (l *LogRotate) Write(data []byte) (n int, err error) {
|
||||
n, err = l.file.Write(data)
|
||||
l.currentSize += int64(n)
|
||||
if err == nil {
|
||||
if l.splitFunc() {
|
||||
l.createTime = time.Now()
|
||||
if file, err := os.OpenFile(path.Join(l.Path, fmt.Sprintf("%s.log", l.createTime.Format("2006-01-02T15:04:05"))), os.O_TRUNC|os.O_WRONLY|os.O_CREATE, 0666); err == nil {
|
||||
l.file = file
|
||||
l.currentSize = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
//func (l *LogRotate) FindLog(grep string) string{
|
||||
// cmd:=exec.Command("grep",fmt.Sprintf("\"%s\"",grep),l.Path)
|
||||
// err:=cmd.Run()
|
||||
//}
|
2
pm/dist/index.html
vendored
2
pm/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 Instance Manager</title><script src=ajax.js></script><link href=/css/app.200d2f8f.css rel=preload as=style><link href=/css/chunk-vendors.22ebf426.css rel=preload as=style><link href=/js/app.fab2a06f.js rel=preload as=script><link href=/js/chunk-vendors.f701a5a3.js rel=preload as=script><link href=/css/chunk-vendors.22ebf426.css rel=stylesheet><link href=/css/app.200d2f8f.css rel=stylesheet></head><body><noscript><strong>We're sorry but pm doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id=app></div><script src=/js/chunk-vendors.f701a5a3.js></script><script src=/js/app.fab2a06f.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 Instance Manager</title><script src=ajax.js></script><link href=/css/app.200d2f8f.css rel=preload as=style><link href=/css/chunk-vendors.22ebf426.css rel=preload as=style><link href=/js/app.13e2de5f.js rel=preload as=script><link href=/js/chunk-vendors.2e3b192a.js rel=preload as=script><link href=/css/chunk-vendors.22ebf426.css rel=stylesheet><link href=/css/app.200d2f8f.css rel=stylesheet></head><body><noscript><strong>We're sorry but pm doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id=app></div><script src=/js/chunk-vendors.2e3b192a.js></script><script src=/js/app.13e2de5f.js></script></body></html>
|
2
pm/dist/js/app.13e2de5f.js
vendored
Normal file
2
pm/dist/js/app.13e2de5f.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
pm/dist/js/app.13e2de5f.js.map
vendored
Normal file
1
pm/dist/js/app.13e2de5f.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
2
pm/dist/js/app.fab2a06f.js
vendored
2
pm/dist/js/app.fab2a06f.js
vendored
File diff suppressed because one or more lines are too long
1
pm/dist/js/app.fab2a06f.js.map
vendored
1
pm/dist/js/app.fab2a06f.js.map
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
pm/dist/js/chunk-vendors.2e3b192a.js.map
vendored
Normal file
1
pm/dist/js/chunk-vendors.2e3b192a.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
1
pm/dist/js/chunk-vendors.f701a5a3.js.map
vendored
1
pm/dist/js/chunk-vendors.f701a5a3.js.map
vendored
File diff suppressed because one or more lines are too long
@@ -38,7 +38,7 @@
|
||||
"plugin:vue/essential",
|
||||
"eslint:recommended"
|
||||
],
|
||||
"rules": {},
|
||||
"rules": {"no-console": "off"},
|
||||
"parserOptions": {
|
||||
"parser": "babel-eslint"
|
||||
}
|
||||
|
@@ -1,21 +1,36 @@
|
||||
<template>
|
||||
<div>
|
||||
<i-input prefix="ios-home" v-model="instancePath" placeholder="输入实例所在的路径" search enter-button="Import" @on-search="doImport">
|
||||
<PathSelector v-model="instancePath" placeholder="输入实例所在的路径"></PathSelector>
|
||||
<i-input style="width: 300px;margin:40px auto" v-model="instanceName" :placeholder="defaultInstanceName" search enter-button="Import" @on-search="doImport">
|
||||
<span slot="prepend">实例名称</span>
|
||||
</i-input>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import PathSelector from "./PathSelector"
|
||||
export default {
|
||||
name: "ImportInstance",
|
||||
components:{
|
||||
PathSelector
|
||||
},
|
||||
data(){
|
||||
return {
|
||||
instancePath:""
|
||||
instancePath:"",
|
||||
instanceName:""
|
||||
}
|
||||
},
|
||||
computed:{
|
||||
defaultInstanceName(){
|
||||
let path = this.instancePath.replace(/\\/g,"/")
|
||||
let s = path.split("/")
|
||||
if(path.endsWith("/")) s.pop()
|
||||
return s.pop()
|
||||
}
|
||||
},
|
||||
methods:{
|
||||
doImport(){
|
||||
window.ajax.get("/instance/import?path="+this.instancePath).then(x=>{
|
||||
window.ajax.get("/instance/import?path="+this.instancePath+"&name="+this.instanceName).then(x=>{
|
||||
if(x=="success"){
|
||||
this.$Message.success("导入成功!")
|
||||
}else{
|
||||
|
@@ -3,13 +3,17 @@
|
||||
<List border>
|
||||
<ListItem v-for="item in instances" :key="item.Name">
|
||||
<ListItemMeta :title="item.Name" :description="item.Path"></ListItemMeta>
|
||||
{{item.Info}}
|
||||
<template v-if="item.Info.StartTime">
|
||||
引擎版本:{{item.Info.Version}} <br>启动时间:
|
||||
<StartTime :value="item.Info.StartTime"></StartTime>
|
||||
</template>
|
||||
<template v-else>{{item.Info}}</template>
|
||||
<template slot="action">
|
||||
<li @click="changeConfig(item)">
|
||||
<Icon type="ios-settings"/>
|
||||
修改配置
|
||||
</li>
|
||||
<li v-if="hasGateway(item)" @click="window.open(gateWayHref(item),'_blank')">
|
||||
<li v-if="hasGateway(item)" @click="openGateway(item)">
|
||||
<Icon type="md-browsers"/>
|
||||
管理界面
|
||||
</li>
|
||||
@@ -29,18 +33,18 @@
|
||||
<Checkbox v-model="build">go build</Checkbox>
|
||||
</Modal>
|
||||
<Modal v-model="showConfig" title="修改实例配置" @on-ok="submitConfigChange">
|
||||
<i-input type="textarea" v-model="currentConfig" :rows="20">
|
||||
|
||||
</i-input>
|
||||
<i-input type="textarea" v-model="currentConfig" :rows="20"></i-input>
|
||||
</Modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import toml from "@iarna/toml"
|
||||
import StartTime from "./StartTime"
|
||||
|
||||
export default {
|
||||
name: "InstanceList",
|
||||
components: {StartTime},
|
||||
data() {
|
||||
return {
|
||||
instances: [],
|
||||
@@ -58,8 +62,8 @@
|
||||
let instance = x[name]
|
||||
instance.Config = toml.parse(instance.Config)
|
||||
if (this.hasGateway(instance)) {
|
||||
window.ajax.getJSON("//" + this.gateWayHref(instance) + "/api/sysInfo").then(x => {
|
||||
instance.Info = "引擎版本:" + x.Version + "启动时间:" + x.StartTime
|
||||
window.ajax.getJSON(this.gateWayHref(instance) + "/api/sysInfo").then(x => {
|
||||
instance.Info = x
|
||||
}).catch(() => {
|
||||
instance.Info = "无法访问实例"
|
||||
})
|
||||
@@ -93,11 +97,14 @@
|
||||
this.$Message.error(e)
|
||||
}
|
||||
},
|
||||
openGateway(item) {
|
||||
window.open(this.gateWayHref(item), '_blank')
|
||||
},
|
||||
hasGateway(item) {
|
||||
return item.Config.Plugins.hasOwnProperty("GateWay")
|
||||
},
|
||||
gateWayHref(item) {
|
||||
return location.hostname + ":" + item.Config.Plugins.GateWay.ListenAddr.split(":").pop()
|
||||
return "http://" + location.hostname + ":" + item.Config.Plugins.GateWay.ListenAddr.split(":").pop()
|
||||
},
|
||||
restart() {
|
||||
let item = this.currentItem
|
||||
@@ -126,7 +133,7 @@
|
||||
msg()
|
||||
})
|
||||
es.onerror = e => {
|
||||
if (e) this.$Message.error(e);
|
||||
if (e && e.toString()) this.$Message.error(e);
|
||||
msg()
|
||||
es.close()
|
||||
}
|
||||
|
66
pm/src/components/PathSelector.vue
Normal file
66
pm/src/components/PathSelector.vue
Normal file
@@ -0,0 +1,66 @@
|
||||
<template>
|
||||
<div>
|
||||
<i-input ref="input" v-bind="$attrs" v-on="$listeners" clearable @on-change="onInput">
|
||||
<Button slot="prepend" icon="md-arrow-round-up" @click="goUp"></Button>
|
||||
</i-input>
|
||||
<CellGroup @on-click="onSelectCand">
|
||||
<Cell v-for="item in candidate" :key="item" :title="item" :name="item"></Cell>
|
||||
</CellGroup>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "PathSelector",
|
||||
data() {
|
||||
return {
|
||||
candidate: [],
|
||||
lastInput: "",
|
||||
searching: false,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
dir(){
|
||||
let paths = this.$refs.input.value.split("/");
|
||||
paths.pop();
|
||||
return paths.join("/");
|
||||
},
|
||||
goUp() {
|
||||
this.lastInput = this.$attrs.value = this.dir()
|
||||
this.$refs.input.$emit('input', this.$attrs.value)
|
||||
this.search(this.lastInput)
|
||||
},
|
||||
onSelectCand(name) {
|
||||
this.lastInput = this.$attrs.value = this.dir()+"/"+name+"/"
|
||||
this.$refs.input.$emit('input', this.$attrs.value)
|
||||
this.search(this.lastInput)
|
||||
},
|
||||
onInput(evt) {
|
||||
this.lastInput = evt.target.value
|
||||
this.search(this.lastInput)
|
||||
},
|
||||
search(v) {
|
||||
if(this.searching)return
|
||||
window.ajax.getJSON("/instance/listDir?input=" + v).then(x => {
|
||||
this.candidate = x
|
||||
if (this.lastInput != v) {
|
||||
this.search(this.lastInput)
|
||||
}else{
|
||||
this.searching = false
|
||||
}
|
||||
}).catch(e => {
|
||||
this.$Message.error(e)
|
||||
if (this.lastInput != v) {
|
||||
this.search(this.lastInput)
|
||||
}else{
|
||||
this.searching = false
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
18
pm/src/components/StartTime.vue
Normal file
18
pm/src/components/StartTime.vue
Normal file
@@ -0,0 +1,18 @@
|
||||
<template>
|
||||
<Poptip trigger="hover" :content="'⌚️'+ new Date(value).toLocaleString()">
|
||||
<Time :time="new Date(value)"></Time>
|
||||
</Poptip>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "StartTime",
|
||||
props:{
|
||||
value:String
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@@ -1,10 +1,40 @@
|
||||
import Vue from 'vue'
|
||||
import Vuex from 'vuex'
|
||||
|
||||
Vue.use(Vuex)
|
||||
|
||||
export default new Vuex.Store({
|
||||
state: {
|
||||
defaultPlugins:{
|
||||
GateWay:[
|
||||
"gateway",'ListenAddr = ":8081"',"网关插件,提供各种API服务,包括信息采集和控制等,控制台页面展示(静态资源服务器)"
|
||||
],
|
||||
LogRotate:[
|
||||
"logrotate",`Path = "log"
|
||||
Size = 0
|
||||
Days = 1`,"日志分割插件,Size 代表按照字节数分割,0代表采用时间分割"
|
||||
],
|
||||
Jessica:[
|
||||
"jessica",'ListenAddr = ":8080"',"WebSocket协议订阅,采用私有协议,搭配Jessibuca播放器实现低延时播放"
|
||||
],
|
||||
Cluster:[
|
||||
"cluster",'Master = "localhost:2019"\nListenAddr = ":2019"',"集群插件,可以实现级联转发功能,Master代表上游服务器,ListenAdder代表源服务器监听端口,可只配置一项"
|
||||
],
|
||||
RTMP:[
|
||||
"rtmp",'ListenAddr = ":1935"',"rtmp协议实现,基本发布和订阅功能"
|
||||
],
|
||||
RecordFlv:[
|
||||
"record",'Path="./resource"',"录制视频流到flv文件"
|
||||
],
|
||||
HDL:[
|
||||
"HDL",'ListenAddr = ":2020"',"Http-flv格式实现,可以对接CDN厂商进行回源拉流"
|
||||
],
|
||||
Auth:[
|
||||
"auth",'Key = "www.monibuca.com"',"一个鉴权验证模块"
|
||||
],
|
||||
Qos:[
|
||||
"QoS",'Suffix = ["high","medium","low"]',"质量控制插件,可以动态改变订阅的不同的质量的流"
|
||||
]
|
||||
}
|
||||
},
|
||||
mutations: {
|
||||
},
|
||||
|
@@ -13,21 +13,18 @@
|
||||
<Step title="完成" content="完成实例创建"></Step>
|
||||
</Steps>
|
||||
<div style="margin:50px;width:auto">
|
||||
<i-input v-model="createPath" v-if="createStep==0">
|
||||
<Button slot="prepend" icon="md-arrow-round-up" @click="goUp"></Button>
|
||||
</i-input>
|
||||
<List v-else-if="createStep==1" border>
|
||||
<ListItem v-for="(item,name) in plugins" :key="name">
|
||||
<ListItemMeta :title="name" :description="item.Path"></ListItemMeta>
|
||||
{{item.Config}}
|
||||
<template slot="action">
|
||||
<li @click="removePlugin(name)">
|
||||
<Icon type="ios-trash"/>
|
||||
移除
|
||||
</li>
|
||||
</template>
|
||||
</ListItem>
|
||||
</List>
|
||||
<PathSelector v-model="createPath" v-if="createStep==0"></PathSelector>
|
||||
<div style="display: flex;flex-wrap: wrap" v-else-if="createStep==1">
|
||||
<Card v-for="(item,name) in plugins" :key="name" style="width:200px;margin:5px">
|
||||
<Poptip :content="item.Description" slot="extra" width="200" word-wrap>
|
||||
<Icon size="18" type="ios-help-circle-outline" style="cursor:pointer"/>
|
||||
</Poptip>
|
||||
<Poptip :content="item.Path" trigger="hover" word-wrap slot="title">
|
||||
<Checkbox v-model="item.enabled" style="color: #eb5e46">{{name}}</Checkbox>
|
||||
</Poptip>
|
||||
<i-input type="textarea" v-model="item.Config" placeholder="请输入toml格式"></i-input>
|
||||
</Card>
|
||||
</div>
|
||||
<div v-else>
|
||||
<h3>实例名称:</h3>
|
||||
<i-input
|
||||
@@ -90,14 +87,9 @@
|
||||
<i-input v-model="formPlugin.Name" placeholder="插件名称必须和插件注册时的名称一致"></i-input>
|
||||
</FormItem>
|
||||
<FormItem label="插件包地址">
|
||||
<i-input v-model="formPlugin.Path">
|
||||
<Button slot="append" @click="showBuiltinPlugin=true">内置插件</Button>
|
||||
</i-input>
|
||||
<i-input v-model="formPlugin.Path"></i-input>
|
||||
</FormItem>
|
||||
<Alert
|
||||
type="show-icon"
|
||||
v-if="!Object.values(builtinPlugins).includes(formPlugin.Path)"
|
||||
>
|
||||
<Alert show-icon type="warning">
|
||||
如果该插件是私有仓库,请到服务器上输入:echo "machine {{privateHost}} login 用户名 password 密码" >> ~/.netrc
|
||||
并且添加环境变量GOPRIVATE={{privateHost}}
|
||||
</Alert>
|
||||
@@ -106,19 +98,6 @@
|
||||
</FormItem>
|
||||
</Form>
|
||||
</Modal>
|
||||
<Modal v-model="showBuiltinPlugin">
|
||||
<List>
|
||||
<ListItem v-for="(item,name) in builtinPlugins" :key="name">
|
||||
<ListItemMeta :title="name" :description="item"></ListItemMeta>
|
||||
<template slot="action">
|
||||
<li @click="addBuiltin(name,item)">
|
||||
<Icon type="ios-add"/>
|
||||
添加
|
||||
</li>
|
||||
</template>
|
||||
</ListItem>
|
||||
</List>
|
||||
</Modal>
|
||||
<CreateInstance v-model="showCreate" :info="createInfo"></CreateInstance>
|
||||
</Layout>
|
||||
</template>
|
||||
@@ -127,52 +106,42 @@
|
||||
import CreateInstance from "../components/CreateInstance";
|
||||
import InstanceList from "../components/InstanceList";
|
||||
import ImportInstance from "../components/ImportInstance";
|
||||
import PathSelector from "../components/PathSelector"
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CreateInstance,InstanceList,ImportInstance
|
||||
CreateInstance, InstanceList, ImportInstance, PathSelector
|
||||
},
|
||||
data() {
|
||||
let plugins = {}
|
||||
for (let name in this.$store.state.defaultPlugins) {
|
||||
plugins[name] = {
|
||||
Name: name,
|
||||
enabled: ["GateWay", "LogRotate", "Jessica"].includes(name),
|
||||
Path: "github.com/langhuihui/monibuca/plugins/" + this.$store.state.defaultPlugins[name][0],
|
||||
Config: this.$store.state.defaultPlugins[name][1],
|
||||
Description: this.$store.state.defaultPlugins[name][2],
|
||||
}
|
||||
}
|
||||
return {
|
||||
instanceName: "",
|
||||
createStep: 0,
|
||||
showCreate: false,
|
||||
createInfo: null,
|
||||
createPath: "/opt/monibuca",
|
||||
plugins: {},
|
||||
plugins,
|
||||
showAddPlugin: false,
|
||||
formPlugin: {},
|
||||
showBuiltinPlugin: false,
|
||||
builtinPlugins: {
|
||||
Auth: "github.com/langhuihui/monibuca/plugins/auth",
|
||||
Cluster: "github.com/langhuihui/monibuca/plugins/cluster",
|
||||
GateWay: "github.com/langhuihui/monibuca/plugins/gateway",
|
||||
HDL: "github.com/langhuihui/monibuca/plugins/HDL",
|
||||
Jessica: "github.com/langhuihui/monibuca/plugins/jessica",
|
||||
QoS: "github.com/langhuihui/monibuca/plugins/QoS",
|
||||
RecordFlv: "github.com/langhuihui/monibuca/plugins/record",
|
||||
RTMP: "github.com/langhuihui/monibuca/plugins/rtmp"
|
||||
},
|
||||
defaultConfig: {
|
||||
Auth: 'Key = "www.monibuca.com"',
|
||||
RecordFlv: 'Path="./resource"',
|
||||
QoS: 'Suffix = ["high","medium","low"]',
|
||||
Cluster: 'Master = "localhost:2019"\nListenAddr = ":2019"',
|
||||
GateWay: 'ListenAddr = ":8081"',
|
||||
RTMP: 'ListenAddr = ":1935"',
|
||||
Jessica: 'ListenAddr = ":8080"',
|
||||
HDL: 'ListenAddr = ":2020"'
|
||||
}
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
pluginStr() {
|
||||
return Object.values(this.plugins)
|
||||
return Object.values(this.plugins).filter(x => x.enabled)
|
||||
.map(x => x.Path)
|
||||
.join("\n");
|
||||
},
|
||||
configStr() {
|
||||
return Object.values(this.plugins)
|
||||
return Object.values(this.plugins).filter(x => x.enabled)
|
||||
.map(
|
||||
x => `[Plugins.${x.Name}]
|
||||
${x.Config || ""}`
|
||||
@@ -188,7 +157,6 @@ ${x.Config || ""}`
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
goUp() {
|
||||
let paths = this.createPath.split("/");
|
||||
paths.pop();
|
||||
@@ -199,7 +167,7 @@ ${x.Config || ""}`
|
||||
this.createInfo = {
|
||||
Name: this.instanceName || this.createPath.split("/").pop(),
|
||||
Path: this.createPath,
|
||||
Plugins: Object.values(this.plugins).map(x => x.Path),
|
||||
Plugins: Object.values(this.plugins).filter(x => x.enabled).map(x => x.Path),
|
||||
Config: this.configStr
|
||||
};
|
||||
},
|
||||
@@ -207,16 +175,6 @@ ${x.Config || ""}`
|
||||
this.plugins[this.formPlugin.Name] = this.formPlugin;
|
||||
this.formPlugin = {};
|
||||
},
|
||||
removePlugin(name) {
|
||||
delete this.plugins[name];
|
||||
this.$forceUpdate();
|
||||
},
|
||||
addBuiltin(name, item) {
|
||||
this.formPlugin.Name = name;
|
||||
this.formPlugin.Path = item;
|
||||
this.formPlugin.Config = this.defaultConfig[name];
|
||||
this.showBuiltinPlugin = false;
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
Reference in New Issue
Block a user