Compare commits

...

2 Commits

Author SHA1 Message Date
langhuihui
5563ddc0d2 增强对实例的控制 2020-02-14 09:54:53 +08:00
langhuihui
95657bd6df 增强对实例的控制 2020-02-13 17:41:39 +08:00
15 changed files with 223 additions and 55 deletions

104
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"
@@ -44,7 +46,8 @@ func main() {
} }
addr := flag.String("port", "8000", "http server port") addr := flag.String("port", "8000", "http server port")
flag.Parse() flag.Parse()
http.HandleFunc("/instance/import", importInstance)
http.HandleFunc("/instance/updateConfig", updateConfig)
http.HandleFunc("/instance/list", listInstance) http.HandleFunc("/instance/list", listInstance)
http.HandleFunc("/instance/create", initInstance) http.HandleFunc("/instance/create", initInstance)
http.HandleFunc("/instance/restart", restartInstance) http.HandleFunc("/instance/restart", restartInstance)
@@ -56,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")
@@ -160,18 +227,18 @@ func restartInstance(w http.ResponseWriter, r *http.Request) {
needBuild := r.URL.Query().Get("build") != "" needBuild := r.URL.Query().Get("build") != ""
if instance, ok := instances[instanceName]; ok { if instance, ok := instances[instanceName]; ok {
if needUpdate { if needUpdate {
if err := instance.writeExecSSE(sse, exec.Command("go", "get", "-u")); err != nil { if err := sse.WriteExec(instance.command("go", "get", "-u")); err != nil {
sse.WriteEvent("failed", []byte(err.Error())) sse.WriteEvent("failed", []byte(err.Error()))
return return
} }
} }
if needBuild { if needBuild {
if err := instance.writeExecSSE(sse, exec.Command("go", "build")); err != nil { if err := sse.WriteExec(instance.command("go", "build")); err != nil {
sse.WriteEvent("failed", []byte(err.Error())) sse.WriteEvent("failed", []byte(err.Error()))
return return
} }
} }
if err := instance.writeExecSSE(sse, exec.Command("sh", "restart.sh")); err != nil { if err := sse.WriteExec(instance.command("sh", "restart.sh")); err != nil {
sse.WriteEvent("failed", []byte(err.Error())) sse.WriteEvent("failed", []byte(err.Error()))
return return
} }
@@ -180,10 +247,7 @@ func restartInstance(w http.ResponseWriter, r *http.Request) {
sse.WriteEvent("failed", []byte("no such instance")) sse.WriteEvent("failed", []byte("no such instance"))
} }
} }
func (p *InstanceDesc) writeExecSSE(sse *util.SSE, cmd *exec.Cmd) error {
cmd.Dir = p.Path
return sse.WriteExec(cmd)
}
func (p *InstanceDesc) command(name string, args ...string) (cmd *exec.Cmd) { func (p *InstanceDesc) command(name string, args ...string) (cmd *exec.Cmd) {
cmd = exec.Command(name, args...) cmd = exec.Command(name, args...)
cmd.Dir = p.Path cmd.Dir = p.Path
@@ -223,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
} }
@@ -243,7 +307,25 @@ func main(){
if err != nil { if err != nil {
return return
} }
return p.writeExecSSE(sse, exec.Command("sh", "restart.sh")) return sse.WriteExec(p.command("sh", "restart.sh"))
}
func updateConfig(w http.ResponseWriter, r *http.Request) {
instanceName := r.URL.Query().Get("instance")
if instance, ok := instances[instanceName]; ok {
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

@@ -10,7 +10,7 @@ import (
) )
var ConfigRaw []byte var ConfigRaw []byte
var Version = "0.2.3" var Version = "0.2.5"
var EngineInfo = &struct { var EngineInfo = &struct {
Version string Version string
StartTime time.Time StartTime time.Time

View File

@@ -99,6 +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) {
w.Header().Set("Access-Control-Allow-Origin", "*")
bytes, err := json.Marshal(EngineInfo) 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.9b5890f5.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.9b5890f5.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.5c3b9309.js rel=preload as=script><link href=/js/chunk-vendors.f693d643.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.f693d643.js></script><script src=/js/app.5c3b9309.js></script></body></html>

2
pm/dist/js/app.5c3b9309.js vendored Normal file

File diff suppressed because one or more lines are too long

1
pm/dist/js/app.5c3b9309.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

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>

View File

@@ -0,0 +1,39 @@
<template>
<div>
<i-input v-model="instanceName" :placeholder="defaultInstanceName"></i-input>
<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:"",
instanceName:""
}
},
computed:{
defaultInstanceName(){
return this.instancePath.replace(/\\/g,"/").split("/").pop()
}
},
methods:{
doImport(){
window.ajax.get("/instance/import?path="+this.instancePath+"&name="+this.instanceName).then(x=>{
if(x=="success"){
this.$Message.success("导入成功!")
}else{
this.$Message.error(x)
}
})
}
}
}
</script>
<style scoped>
</style>

View File

@@ -1,30 +1,39 @@
<template> <template>
<List border> <div>
<ListItem v-for="item in instances" :key="item.Name"> <List border>
<ListItemMeta :title="item.Name" :description="item.Path"></ListItemMeta> <ListItem v-for="item in instances" :key="item.Name">
<template v-if="hasGateway(item)"> <ListItemMeta :title="item.Name" :description="item.Path"></ListItemMeta>
{{item.Info}} {{item.Info}}
</template> <template slot="action">
<template slot="action"> <li @click="changeConfig(item)">
<li v-if="hasGateway(item)" @click="window.open(gateWayHref(item),'_blank')"> <Icon type="ios-settings"/>
<Icon type="md-browsers"/> 修改配置
管理界面 </li>
</li> <li v-if="hasGateway(item)" @click="openGateway(item)">
<li @click="restart(item)"> <Icon type="md-browsers"/>
<Icon type="ios-refresh"/> 管理界面
重启 </li>
</li> <li @click="currentItem=item,showRestart=true">
<li @click="shutdown(item)"> <Icon type="ios-refresh"/>
<Icon type="ios-power"/> 重启
关闭 </li>
</li> <li @click="shutdown(item)">
</template> <Icon type="ios-power"/>
</ListItem> 关闭
<Modal v-model="showRestart"> </li>
</template>
</ListItem>
</List>
<Modal v-model="showRestart" title="重启选项" @on-ok="restart">
<Checkbox v-model="update">go get -u</Checkbox> <Checkbox v-model="update">go get -u</Checkbox>
<Checkbox v-model="build">go build</Checkbox> <Checkbox v-model="build">go build</Checkbox>
</Modal> </Modal>
</List> <Modal v-model="showConfig" title="修改实例配置" @on-ok="submitConfigChange">
<i-input type="textarea" v-model="currentConfig" :rows="20">
</i-input>
</Modal>
</div>
</template> </template>
<script> <script>
@@ -33,7 +42,15 @@
export default { export default {
name: "InstanceList", name: "InstanceList",
data() { data() {
return {instances: {}, showRestart: false, update: false, build: false} return {
instances: [],
showRestart: false,
update: false,
build: false,
showConfig: false,
currentItem: null,
currentConfig: ""
}
}, },
mounted() { mounted() {
window.ajax.getJSON("/instance/list").then(x => { window.ajax.getJSON("/instance/list").then(x => {
@@ -49,17 +66,44 @@
} else { } else {
instance.Info = "实例未配置网关插件" instance.Info = "实例未配置网关插件"
} }
this.instances.push(instance)
} }
this.instances = x; // this.instances = x;
}); });
}, methods: { },
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)
}
},
openGateway(item){
window.open(this.gateWayHref(item),'_blank')
},
hasGateway(item) { hasGateway(item) {
return item.Config.Plugins.hasOwnProperty("GateWay") return item.Config.Plugins.hasOwnProperty("GateWay")
}, },
gateWayHref(item) { gateWayHref(item) {
return location.hostname + ":" + item.Config.Plugins.GateWay.split(":").pop() return location.hostname + ":" + item.Config.Plugins.GateWay.ListenAddr.split(":").pop()
}, },
restart(item) { restart() {
let item = this.currentItem
const msg = this.$Message.loading({ const msg = this.$Message.loading({
content: 'restart ' + item.Name + '...', content: 'restart ' + item.Name + '...',
duration: 0 duration: 0

View File

@@ -79,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">
@@ -124,11 +126,11 @@
<script> <script>
import CreateInstance from "../components/CreateInstance"; import CreateInstance from "../components/CreateInstance";
import InstanceList from "../components/InstanceList"; import InstanceList from "../components/InstanceList";
import ImportInstance from "../components/ImportInstance";
export default { export default {
components: { components: {
CreateInstance,InstanceList CreateInstance,InstanceList,ImportInstance
}, },
data() { data() {
return { return {