Compare commits

...

3 Commits

Author SHA1 Message Date
langhuihui
95657bd6df 增强对实例的控制 2020-02-13 17:41:39 +08:00
langhuihui
b9e19e75c8 增强对实例的控制 2020-02-13 10:43:32 +08:00
langhuihui
eac623639d 界面增加重启和升级 2020-02-11 21:59:31 +08:00
20 changed files with 532 additions and 298 deletions

158
main.go
View File

@@ -6,6 +6,7 @@ import (
"errors" "errors"
"flag" "flag"
"fmt" "fmt"
"io"
"io/ioutil" "io/ioutil"
"log" "log"
"mime" "mime"
@@ -14,6 +15,7 @@ import (
"os/exec" "os/exec"
"os/user" "os/user"
"path" "path"
"regexp"
"runtime" "runtime"
"strings" "strings"
@@ -29,7 +31,7 @@ type InstanceDesc struct {
Config string Config string
} }
var instances map[string]*InstanceDesc var instances = make(map[string]*InstanceDesc)
var instancesDir string var instancesDir string
func main() { func main() {
@@ -44,10 +46,12 @@ func main() {
} }
addr := flag.String("port", "8000", "http server port") addr := flag.String("port", "8000", "http server port")
flag.Parse() flag.Parse()
http.HandleFunc("/list", listInstance) http.HandleFunc("/instance/import", importInstance)
http.HandleFunc("/create", initInstance) http.HandleFunc("/instance/updateConfig", updateConfig)
http.HandleFunc("/upgrade/engine", upgradeEngine) http.HandleFunc("/instance/list", listInstance)
http.HandleFunc("/restart/instance", restartInstance) http.HandleFunc("/instance/create", initInstance)
http.HandleFunc("/instance/restart", restartInstance)
http.HandleFunc("/instance/shutdown", shutdownInstance)
http.HandleFunc("/", website) http.HandleFunc("/", website)
fmt.Printf("start listen at %s", *addr) fmt.Printf("start listen at %s", *addr)
if err := http.ListenAndServe(":"+*addr, nil); err != nil { if err := http.ListenAndServe(":"+*addr, nil); err != nil {
@@ -55,6 +59,70 @@ func main() {
} }
} }
func importInstance(w http.ResponseWriter, r *http.Request) {
var e error
defer func() {
result := "success"
if e != nil {
result = e.Error()
}
w.Write([]byte(result))
}()
name := r.URL.Query().Get("name")
if importPath := r.URL.Query().Get("path"); importPath != "" {
f, err := os.Open(importPath)
if e = err; err != nil {
return
}
children, err := f.Readdir(0)
if e = err; err == nil {
var hasMain, hasConfig, hasMod, hasRestart bool
for _, child := range children {
switch child.Name() {
case "main.go":
hasMain = true
case "config.toml":
hasConfig = true
case "go.mod":
hasMod = true
case "restart.sh":
hasRestart = true
}
}
if hasMain && hasConfig && hasMod && hasRestart {
if name == "" {
_, name = path.Split(importPath)
}
config, err := ioutil.ReadFile(path.Join(importPath, "config.toml"))
if e = err; err != nil {
return
}
mainGo, err := ioutil.ReadFile(path.Join(importPath, "main.go"))
if e = err; err != nil {
return
}
reg, err := regexp.Compile("_ \"(.+)\"")
if e = err; err != nil {
return
}
instances[name] = &InstanceDesc{
Name: name,
Path: importPath,
Plugins: nil,
Config: string(config),
}
for _, m := range reg.FindAllStringSubmatch(string(mainGo), -1) {
instances[name].Plugins = append(instances[name].Plugins, m[1])
}
} else {
e = errors.New("路径中缺少文件")
}
}
} else {
w.Write([]byte("参数错误"))
}
}
func readInstances() error { func readInstances() error {
if homeDir, err := Home(); err == nil { if homeDir, err := Home(); err == nil {
instancesDir = path.Join(homeDir, ".monibuca") instancesDir = path.Join(homeDir, ".monibuca")
@@ -140,31 +208,50 @@ func initInstance(w http.ResponseWriter, r *http.Request) {
} }
instances[instanceDesc.Name] = instanceDesc instances[instanceDesc.Name] = instanceDesc
} }
func upgradeEngine(w http.ResponseWriter, r *http.Request) { func shutdownInstance(w http.ResponseWriter, r *http.Request) {
sse := util.NewSSE(w, r.Context())
cmd := exec.Command("go", "get", "-u", "github.com/langhuihui/monibuca/monica")
instanceName := r.URL.Query().Get("instance") instanceName := r.URL.Query().Get("instance")
cmd.Dir = instances[instanceName].Path if instance, ok := instances[instanceName]; ok {
err := sse.WriteExec(cmd) if err := instance.command("kill", "-9", "`cat pid`").Run(); err == nil {
if err != nil { w.Write([]byte("success"))
sse.Write([]byte(err.Error())) } else {
w.Write([]byte(err.Error()))
}
} else {
w.Write([]byte("no such instance"))
} }
} }
func restartInstance(w http.ResponseWriter, r *http.Request) { func restartInstance(w http.ResponseWriter, r *http.Request) {
sse := util.NewSSE(w, r.Context()) sse := util.NewSSE(w, r.Context())
instanceName := r.URL.Query().Get("instance") instanceName := r.URL.Query().Get("instance")
cmd := exec.Command("sh", "restart.sh") needUpdate := r.URL.Query().Get("update") != ""
cmd.Dir = path.Join(instancesDir, instanceName) needBuild := r.URL.Query().Get("build") != ""
cmd.Stderr = sse if instance, ok := instances[instanceName]; ok {
cmd.Stdout = sse if needUpdate {
err := cmd.Start() if err := sse.WriteExec(instance.command("go", "get", "-u")); err != nil {
if err != nil { sse.WriteEvent("failed", []byte(err.Error()))
sse.Write([]byte(err.Error())) return
}
}
if needBuild {
if err := sse.WriteExec(instance.command("go", "build")); err != nil {
sse.WriteEvent("failed", []byte(err.Error()))
return
}
}
if err := sse.WriteExec(instance.command("sh", "restart.sh")); err != nil {
sse.WriteEvent("failed", []byte(err.Error()))
return
}
sse.Write([]byte("success"))
} else {
sse.WriteEvent("failed", []byte("no such instance"))
} }
} }
func (p *InstanceDesc) writeExecSSE(sse *util.SSE, cmd *exec.Cmd) error {
func (p *InstanceDesc) command(name string, args ...string) (cmd *exec.Cmd) {
cmd = exec.Command(name, args...)
cmd.Dir = p.Path cmd.Dir = p.Path
return sse.WriteExec(cmd) return
} }
func (p *InstanceDesc) createDir(sse *util.SSE, clearDir bool) (err error) { func (p *InstanceDesc) createDir(sse *util.SSE, clearDir bool) (err error) {
if clearDir { if clearDir {
@@ -200,12 +287,12 @@ func main(){
return return
} }
sse.WriteEvent("step", []byte("3:文件创建成功!")) sse.WriteEvent("step", []byte("3:文件创建成功!"))
err = p.writeExecSSE(sse, exec.Command("go", "mod", "init", p.Name)) err = sse.WriteExec(p.command("go", "mod", "init", p.Name))
if err != nil { if err != nil {
return return
} }
sse.WriteEvent("step", []byte("4:go mod 初始化完成!")) sse.WriteEvent("step", []byte("4:go mod 初始化完成!"))
err = p.writeExecSSE(sse, exec.Command("go", "build")) err = sse.WriteExec(p.command("go", "build"))
if err != nil { if err != nil {
return return
} }
@@ -220,12 +307,25 @@ func main(){
if err != nil { if err != nil {
return return
} }
cmd := exec.Command("sh", "restart.sh") return sse.WriteExec(p.command("sh", "restart.sh"))
cmd.Dir = p.Path }
cmd.Stderr = sse func updateConfig(w http.ResponseWriter, r *http.Request) {
cmd.Stdout = sse instanceName := r.URL.Query().Get("instance")
err = cmd.Start() if instance, ok := instances[instanceName]; ok {
return f, err := os.OpenFile(path.Join(instance.Path, "config.toml"), os.O_WRONLY|os.O_TRUNC, 0666)
if err != nil {
w.Write([]byte(err.Error()))
return
}
_, err = io.Copy(f, r.Body)
if err != nil {
w.Write([]byte(err.Error()))
return
}
w.Write([]byte("success"))
} else {
w.Write([]byte("no such instance"))
}
} }
func Home() (string, error) { func Home() (string, error) {
user, err := user.Current() user, err := user.Current()

View File

@@ -4,12 +4,17 @@ import (
"encoding/json" "encoding/json"
"io/ioutil" "io/ioutil"
"log" "log"
"time"
"github.com/BurntSushi/toml" "github.com/BurntSushi/toml"
) )
var ConfigRaw []byte var ConfigRaw []byte
var Version = "0.2.1" var Version = "0.2.4"
var EngineInfo = &struct {
Version string
StartTime time.Time
}{Version, time.Now()}
func Run(configFile string) (err error) { func Run(configFile string) (err error) {
log.Printf("start monibuca version:%s", Version) log.Printf("start monibuca version:%s", Version)

View File

@@ -1,28 +1,27 @@
package QoS package QoS
import ( import (
"strings"
. "github.com/langhuihui/monibuca/monica" . "github.com/langhuihui/monibuca/monica"
) )
var ( // var (
selectMap = map[string][]string{ // selectMap = map[string][]string{
"low": {"low", "medium", "high"}, // "low": {"low", "medium", "high"},
"medium": {"medium", "low", "high"}, // "medium": {"medium", "low", "high"},
"high": {"high", "medium", "low"}, // "high": {"high", "medium", "low"},
} // }
) // )
func getQualityName(name string, qualityLevel string) string { // func getQualityName(name string, qualityLevel string) string {
if qualityLevel == "" { // for _, l := range selectMap[qualityLevel] {
return name // if _, ok := AllRoom.Load(name + "/" + l); ok {
} // return name + "/" + l
for _, l := range selectMap[qualityLevel] { // }
if _, ok := AllRoom.Load(name + "/" + l); ok { // }
return name + "/" + l // return name + "/" + qualityLevel
} // }
}
return name + "/" + qualityLevel
}
var config = struct { var config = struct {
Suffix []string Suffix []string
@@ -39,8 +38,23 @@ func init() {
func run() { func run() {
OnDropHooks.AddHook(func(s *OutputStream) { OnDropHooks.AddHook(func(s *OutputStream) {
if s.TotalDrop > s.TotalPacket>>2 { if s.TotalDrop > s.TotalPacket>>2 {
//TODO var newStreamPath = ""
//s.Control<-&ChangeRoomCmd{s,AllRoom.Get()} for i, suf := range config.Suffix {
if strings.HasSuffix(s.StreamPath, suf) {
if i < len(config.Suffix)-1 {
newStreamPath = s.StreamPath + "/" + config.Suffix[i+1]
break
}
} else {
newStreamPath = s.StreamPath + "/" + suf
break
}
}
if newStreamPath != "" {
if _, ok := AllRoom.Load(newStreamPath); ok {
s.Control <- &ChangeRoomCmd{s, AllRoom.Get(newStreamPath)}
}
}
} }
}) })
} }

View File

@@ -15,8 +15,8 @@ import (
) )
var ( var (
config = new(ListenerConfig) config = new(ListenerConfig)
startTime = time.Now()
dashboardPath string dashboardPath string
) )
@@ -99,7 +99,7 @@ func summary(w http.ResponseWriter, r *http.Request) {
} }
} }
func sysInfo(w http.ResponseWriter, r *http.Request) { func sysInfo(w http.ResponseWriter, r *http.Request) {
bytes, err := json.Marshal(&struct{ Version string }{Version: Version}) bytes, err := json.Marshal(EngineInfo)
if err == nil { if err == nil {
_, err = w.Write(bytes) _, err = w.Write(bytes)
} }

2
pm/dist/index.html vendored
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 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.fd72a180.js rel=preload as=script><link href=/js/chunk-vendors.6b87e1b5.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.6b87e1b5.js></script><script src=/js/app.fd72a180.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.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>

2
pm/dist/js/app.fab2a06f.js vendored Normal file

File diff suppressed because one or more lines are too long

1
pm/dist/js/app.fab2a06f.js.map vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

5
pm/package-lock.json generated
View File

@@ -979,6 +979,11 @@
"@hapi/hoek": "8.5.0" "@hapi/hoek": "8.5.0"
} }
}, },
"@iarna/toml": {
"version": "2.2.3",
"resolved": "https://registry.npm.taobao.org/@iarna/toml/download/@iarna/toml-2.2.3.tgz",
"integrity": "sha1-8GC/bqr65NVqfaxhiYCDiwaW4qs="
},
"@intervolga/optimize-cssnano-plugin": { "@intervolga/optimize-cssnano-plugin": {
"version": "1.0.6", "version": "1.0.6",
"resolved": "https://registry.npm.taobao.org/@intervolga/optimize-cssnano-plugin/download/@intervolga/optimize-cssnano-plugin-1.0.6.tgz", "resolved": "https://registry.npm.taobao.org/@intervolga/optimize-cssnano-plugin/download/@intervolga/optimize-cssnano-plugin-1.0.6.tgz",

View File

@@ -8,6 +8,7 @@
"lint": "vue-cli-service lint" "lint": "vue-cli-service lint"
}, },
"dependencies": { "dependencies": {
"@iarna/toml": "^2.2.3",
"core-js": "^3.4.4", "core-js": "^3.4.4",
"view-design": "^4.0.0", "view-design": "^4.0.0",
"vue": "^2.6.10", "vue": "^2.6.10",

View File

@@ -1,5 +1,5 @@
<template> <template>
<Modal v-bind="$attrs" v-on="$listeners" :title="info.Path"> <Modal v-bind="$attrs" v-on="$listeners" :title="info && info.Path">
<Steps :current="currentStep" size="small" :status="status"> <Steps :current="currentStep" size="small" :status="status">
<Step title="解析请求"></Step> <Step title="解析请求"></Step>
<Step title="创建目录"></Step> <Step title="创建目录"></Step>
@@ -14,8 +14,7 @@
</div> </div>
<div slot="footer"> <div slot="footer">
<Checkbox v-model="clearDir">安装前清空目录</Checkbox> <Checkbox v-model="clearDir">安装前清空目录</Checkbox>
<Button type="primary" @click="start">开始</Button> <Button type="primary" @click="start" :loading="status=='process'">开始</Button>
<Button type="success" @click="close" v-if="status=='finish'">完成</Button>
</div> </div>
</Modal> </Modal>
</template> </template>
@@ -27,36 +26,37 @@ export default {
props: { props: {
info: Object info: Object
}, },
methods:{ methods: {
start(){ start() {
eventSource = new EventSource( this.status = "process";
"/create?info=" + JSON.stringify(this.info)+(this.clearDir?"&clear=true":"") eventSource = new EventSource(
); "/instance/create?info=" +
eventSource.onopen = () => (this.log = ""); JSON.stringify(this.info) +
eventSource.onmessage = evt => { (this.clearDir ? "&clear=true" : "")
this.log += evt.data + "\n"; );
if (evt.data == "success") { eventSource.onopen = () => (this.log = "");
this.status = "finish"; eventSource.onmessage = evt => {
eventSource.close(); this.log += evt.data + "\n";
} if (evt.data == "success") {
}; this.status = "finish";
eventSource.addEventListener("exception", evt => {
this.log += evt.data + "\n";
this.status = "error";
eventSource.close(); eventSource.close();
}); }
eventSource.addEventListener("step", evt => { };
let [step, msg] = evt.data.split(":"); eventSource.addEventListener("exception", evt => {
this.currentStep = step | 0; this.log += evt.data + "\n";
this.log += msg + "\n"; this.status = "error";
}); eventSource.close();
},close(){ });
this.$Modal.remove() eventSource.addEventListener("step", evt => {
} let [step, msg] = evt.data.split(":");
this.currentStep = step | 0;
this.log += msg + "\n";
});
},
}, },
data() { data() {
return { clearDir: true, currentStep: 0, log: "", status: "process" }; return { clearDir: true, currentStep: 0, log: "", status: "wait" };
} }
}; };
</script> </script>

View File

@@ -1,60 +0,0 @@
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<p>
For a guide and recipes on how to configure / customize this project,<br>
check out the
<a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>.
</p>
<h3>Installed CLI Plugins</h3>
<ul>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel" target="_blank" rel="noopener">babel</a></li>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-router" target="_blank" rel="noopener">router</a></li>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-vuex" target="_blank" rel="noopener">vuex</a></li>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint" target="_blank" rel="noopener">eslint</a></li>
</ul>
<h3>Essential Links</h3>
<ul>
<li><a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a></li>
<li><a href="https://forum.vuejs.org" target="_blank" rel="noopener">Forum</a></li>
<li><a href="https://chat.vuejs.org" target="_blank" rel="noopener">Community Chat</a></li>
<li><a href="https://twitter.com/vuejs" target="_blank" rel="noopener">Twitter</a></li>
<li><a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a></li>
</ul>
<h3>Ecosystem</h3>
<ul>
<li><a href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a></li>
<li><a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a></li>
<li><a href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank" rel="noopener">vue-devtools</a></li>
<li><a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a></li>
<li><a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a></li>
</ul>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
props: {
msg: String
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="less">
h3 {
margin: 40px 0 0;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>

View File

@@ -0,0 +1,32 @@
<template>
<div>
<i-input prefix="ios-home" v-model="instancePath" placeholder="输入实例所在的路径" search enter-button="Import" @on-search="doImport">
</i-input>
</div>
</template>
<script>
export default {
name: "ImportInstance",
data(){
return {
instancePath:""
}
},
methods:{
doImport(){
window.ajax.get("/instance/import?path="+this.instancePath).then(x=>{
if(x=="success"){
this.$Message.success("导入成功!")
}else{
this.$Message.error(x)
}
})
}
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,149 @@
<template>
<div>
<List border>
<ListItem v-for="item in instances" :key="item.Name">
<ListItemMeta :title="item.Name" :description="item.Path"></ListItemMeta>
{{item.Info}}
<template slot="action">
<li @click="changeConfig(item)">
<Icon type="ios-settings"/>
修改配置
</li>
<li v-if="hasGateway(item)" @click="window.open(gateWayHref(item),'_blank')">
<Icon type="md-browsers"/>
管理界面
</li>
<li @click="currentItem=item,showRestart=true">
<Icon type="ios-refresh"/>
重启
</li>
<li @click="shutdown(item)">
<Icon type="ios-power"/>
关闭
</li>
</template>
</ListItem>
</List>
<Modal v-model="showRestart" title="重启选项" @on-ok="restart">
<Checkbox v-model="update">go get -u</Checkbox>
<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>
</Modal>
</div>
</template>
<script>
import toml from "@iarna/toml"
export default {
name: "InstanceList",
data() {
return {
instances: [],
showRestart: false,
update: false,
build: false,
showConfig: false,
currentItem: null,
currentConfig: ""
}
},
mounted() {
window.ajax.getJSON("/instance/list").then(x => {
for (let name in x) {
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
}).catch(() => {
instance.Info = "无法访问实例"
})
} else {
instance.Info = "实例未配置网关插件"
}
this.instances.push(instance)
}
// this.instances = x;
});
},
methods: {
changeConfig(item) {
this.showConfig = true
this.currentItem = item
this.currentConfig = toml.stringify(item.Config)
},
submitConfigChange() {
try {
this.currentItem.Config = toml.parse(this.currentConfig)
window.ajax.post("/instance/updateConfig?instance=" + this.currentItem.Name, this.currentConfig).then(x => {
if (x == "success") {
this.$Message.success("更新成功!")
} else {
this.$Message.error(x)
}
}).catch(e => {
this.$Message.error(e)
})
} catch (e) {
this.$Message.error(e)
}
},
hasGateway(item) {
return item.Config.Plugins.hasOwnProperty("GateWay")
},
gateWayHref(item) {
return location.hostname + ":" + item.Config.Plugins.GateWay.ListenAddr.split(":").pop()
},
restart() {
let item = this.currentItem
const msg = this.$Message.loading({
content: 'restart ' + item.Name + '...',
duration: 0
});
let arg = item.Name
if (this.update) {
arg += "&update=true"
}
if (this.build) {
arg += "&build=true"
}
const es = new EventSource("/instance/restart?instance=" + arg)
es.onmessage = evt => {
if (evt.data == "success") {
this.$Message.success("重启成功!")
msg()
} else {
this.$Message.info(evt.data)
}
}
es.addEventListener("failed", evt => {
this.$Message.error(evt.data)
msg()
})
es.onerror = e => {
if (e) this.$Message.error(e);
msg()
es.close()
}
},
shutdown(item) {
window.ajax.get("/instance/shutdown?instance=" + item.Name).then(x => {
if (x == "success") {
this.$Message.success("已关闭实例")
} else {
this.$Message.error(x)
}
})
},
}
}
</script>
<style scoped>
</style>

View File

@@ -1,18 +0,0 @@
<template>
<div class="home">
<img alt="Vue logo" src="../assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js App"/>
</div>
</template>
<script>
// @ is an alias to /src
import HelloWorld from '@/components/HelloWorld.vue'
export default {
name: 'home',
components: {
HelloWorld
}
}
</script>

View File

@@ -4,11 +4,7 @@
<Content class="content"> <Content class="content">
<Tabs value="name1"> <Tabs value="name1">
<TabPane label="实例" name="name1"> <TabPane label="实例" name="name1">
<List border> <InstanceList></InstanceList>
<ListItem v-for="item in instances" :key="item.Name">
<ListItemMeta :title="item.Name" :description="item.Path"></ListItemMeta>
</ListItem>
</List>
</TabPane> </TabPane>
<TabPane label="创建" name="name2"> <TabPane label="创建" name="name2">
<Steps :current="createStep"> <Steps :current="createStep">
@@ -26,7 +22,8 @@
{{item.Config}} {{item.Config}}
<template slot="action"> <template slot="action">
<li @click="removePlugin(name)"> <li @click="removePlugin(name)">
<Icon type="ios-trash" />移除 <Icon type="ios-trash"/>
移除
</li> </li>
</template> </template>
</ListItem> </ListItem>
@@ -34,8 +31,8 @@
<div v-else> <div v-else>
<h3>实例名称</h3> <h3>实例名称</h3>
<i-input <i-input
v-model="instanceName" v-model="instanceName"
:placeholder="createPath.split('/').pop()" :placeholder="createPath.split('/').pop()"
></i-input> ></i-input>
<h4>安装路径</h4> <h4>安装路径</h4>
<div> <div>
@@ -52,27 +49,28 @@
</div> </div>
<ButtonGroup style="display:table;margin:50px auto;"> <ButtonGroup style="display:table;margin:50px auto;">
<Button <Button
size="large" size="large"
type="primary" type="primary"
@click="createStep--" @click="createStep--"
v-if="createStep!=0" v-if="createStep!=0"
> >
<Icon type="ios-arrow-back"></Icon>上一步 <Icon type="ios-arrow-back"></Icon>
上一步
</Button> </Button>
<Button <Button
size="large" size="large"
type="success" type="success"
@click="showAddPlugin=true" @click="showAddPlugin=true"
v-if="createStep==1" v-if="createStep==1"
> >
+ +
添加插件 添加插件
</Button> </Button>
<Button <Button
size="large" size="large"
type="primary" type="primary"
@click="createStep++" @click="createStep++"
v-if="createStep!=2" v-if="createStep!=2"
> >
下一步 下一步
<Icon type="ios-arrow-forward"></Icon> <Icon type="ios-arrow-forward"></Icon>
@@ -81,7 +79,9 @@
</ButtonGroup> </ButtonGroup>
</div> </div>
</TabPane> </TabPane>
<TabPane label="导入" name="name3"></TabPane> <TabPane label="导入" name="name3">
<ImportInstance></ImportInstance>
</TabPane>
</Tabs> </Tabs>
</Content> </Content>
<Modal v-model="showAddPlugin" title="添加Plugin" @on-ok="addPlugin"> <Modal v-model="showAddPlugin" title="添加Plugin" @on-ok="addPlugin">
@@ -95,8 +95,8 @@
</i-input> </i-input>
</FormItem> </FormItem>
<Alert <Alert
type="show-icon" type="show-icon"
v-if="!Object.values(builtinPlugins).includes(formPlugin.Path)" v-if="!Object.values(builtinPlugins).includes(formPlugin.Path)"
> >
如果该插件是私有仓库请到服务器上输入echo "machine {{privateHost}} login 用户名 password 密码" >> ~/.netrc 如果该插件是私有仓库请到服务器上输入echo "machine {{privateHost}} login 用户名 password 密码" >> ~/.netrc
并且添加环境变量GOPRIVATE={{privateHost}} 并且添加环境变量GOPRIVATE={{privateHost}}
@@ -112,7 +112,8 @@
<ListItemMeta :title="name" :description="item"></ListItemMeta> <ListItemMeta :title="name" :description="item"></ListItemMeta>
<template slot="action"> <template slot="action">
<li @click="addBuiltin(name,item)"> <li @click="addBuiltin(name,item)">
<Icon type="ios-add" />添加 <Icon type="ios-add"/>
添加
</li> </li>
</template> </template>
</ListItem> </ListItem>
@@ -123,116 +124,114 @@
</template> </template>
<script> <script>
import CreateInstance from "../components/CreateInstance"; import CreateInstance from "../components/CreateInstance";
import InstanceList from "../components/InstanceList";
import ImportInstance from "../components/ImportInstance";
export default { export default {
components: { components: {
CreateInstance CreateInstance,InstanceList,ImportInstance
},
data() {
return {
instanceName: "",
createStep: 0,
showCreate: false,
createInfo: null,
createPath: "/opt/monibuca",
instances: {},
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)
.map(x => x.Path)
.join("\n");
}, },
configStr() { data() {
return Object.values(this.plugins) return {
.map( instanceName: "",
x => `[Plugins.${x.Name}] createStep: 0,
${x.Config || ""}` showCreate: false,
) createInfo: null,
.join("\n"); createPath: "/opt/monibuca",
}, plugins: {},
privateHost() { showAddPlugin: false,
return ( formPlugin: {},
(this.formPlugin.Path && this.formPlugin.Path.split("/")[0]) || showBuiltinPlugin: false,
"仓库域名" builtinPlugins: {
); Auth: "github.com/langhuihui/monibuca/plugins/auth",
} Cluster: "github.com/langhuihui/monibuca/plugins/cluster",
}, GateWay: "github.com/langhuihui/monibuca/plugins/gateway",
mounted() { HDL: "github.com/langhuihui/monibuca/plugins/HDL",
window.ajax.getJSON("/list").then(x => { Jessica: "github.com/langhuihui/monibuca/plugins/jessica",
this.instances = x; QoS: "github.com/langhuihui/monibuca/plugins/QoS",
}); RecordFlv: "github.com/langhuihui/monibuca/plugins/record",
}, RTMP: "github.com/langhuihui/monibuca/plugins/rtmp"
methods: { },
goUp() { defaultConfig: {
let paths = this.createPath.split("/"); Auth: 'Key = "www.monibuca.com"',
paths.pop(); RecordFlv: 'Path="./resource"',
this.createPath = paths.join("/"); QoS: 'Suffix = ["high","medium","low"]',
}, Cluster: 'Master = "localhost:2019"\nListenAddr = ":2019"',
createInstance() { GateWay: 'ListenAddr = ":8081"',
this.showCreate = true; RTMP: 'ListenAddr = ":1935"',
this.createInfo = { Jessica: 'ListenAddr = ":8080"',
Name: this.instanceName || this.createPath.split("/").pop(), HDL: 'ListenAddr = ":2020"'
Path: this.createPath, }
Plugins: Object.values(this.plugins).map(x => x.Path),
Config: this.configStr
}; };
}, },
addPlugin() { computed: {
this.plugins[this.formPlugin.Name] = this.formPlugin; pluginStr() {
this.formPlugin = {}; return Object.values(this.plugins)
.map(x => x.Path)
.join("\n");
},
configStr() {
return Object.values(this.plugins)
.map(
x => `[Plugins.${x.Name}]
${x.Config || ""}`
)
.join("\n");
},
privateHost() {
return (
(this.formPlugin.Path && this.formPlugin.Path.split("/")[0]) ||
"仓库域名"
);
}
}, },
removePlugin(name) {
delete this.plugins[name]; methods: {
this.$forceUpdate();
}, goUp() {
addBuiltin(name, item) { let paths = this.createPath.split("/");
this.formPlugin.Name = name; paths.pop();
this.formPlugin.Path = item; this.createPath = paths.join("/");
this.formPlugin.Config = this.defaultConfig[name]; },
this.showBuiltinPlugin = false; createInstance() {
this.showCreate = true;
this.createInfo = {
Name: this.instanceName || this.createPath.split("/").pop(),
Path: this.createPath,
Plugins: Object.values(this.plugins).map(x => x.Path),
Config: this.configStr
};
},
addPlugin() {
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> </script>
<style> <style>
.content { .content {
background: white; background: white;
} }
pre { pre {
white-space: pre-wrap; white-space: pre-wrap;
word-wrap: break-word; word-wrap: break-word;
} }
.ivu-tabs .ivu-tabs-tabpane { .ivu-tabs .ivu-tabs-tabpane {
padding: 20px; padding: 20px;
} }
</style> </style>