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"
"flag"
"fmt"
"io"
"io/ioutil"
"log"
"mime"
@@ -14,6 +15,7 @@ import (
"os/exec"
"os/user"
"path"
"regexp"
"runtime"
"strings"
@@ -44,7 +46,8 @@ func main() {
}
addr := flag.String("port", "8000", "http server port")
flag.Parse()
http.HandleFunc("/instance/import", importInstance)
http.HandleFunc("/instance/updateConfig", updateConfig)
http.HandleFunc("/instance/list", listInstance)
http.HandleFunc("/instance/create", initInstance)
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 {
if homeDir, err := Home(); err == nil {
instancesDir = path.Join(homeDir, ".monibuca")
@@ -160,18 +227,18 @@ func restartInstance(w http.ResponseWriter, r *http.Request) {
needBuild := r.URL.Query().Get("build") != ""
if instance, ok := instances[instanceName]; ok {
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()))
return
}
}
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()))
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()))
return
}
@@ -180,10 +247,7 @@ func restartInstance(w http.ResponseWriter, r *http.Request) {
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) {
cmd = exec.Command(name, args...)
cmd.Dir = p.Path
@@ -223,12 +287,12 @@ func main(){
return
}
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 {
return
}
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 {
return
}
@@ -243,7 +307,25 @@ func main(){
if err != nil {
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) {
user, err := user.Current()

View File

@@ -10,7 +10,7 @@ import (
)
var ConfigRaw []byte
var Version = "0.2.3"
var Version = "0.2.5"
var EngineInfo = &struct {
Version string
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) {
w.Header().Set("Access-Control-Allow-Origin", "*")
bytes, err := json.Marshal(EngineInfo)
if err == nil {
_, 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>
<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">
<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,16 +1,19 @@
<template>
<div>
<List border>
<ListItem v-for="item in instances" :key="item.Name">
<ListItemMeta :title="item.Name" :description="item.Path"></ListItemMeta>
<template v-if="hasGateway(item)">
{{item.Info}}
</template>
<template slot="action">
<li v-if="hasGateway(item)" @click="window.open(gateWayHref(item),'_blank')">
<li @click="changeConfig(item)">
<Icon type="ios-settings"/>
修改配置
</li>
<li v-if="hasGateway(item)" @click="openGateway(item)">
<Icon type="md-browsers"/>
管理界面
</li>
<li @click="restart(item)">
<li @click="currentItem=item,showRestart=true">
<Icon type="ios-refresh"/>
重启
</li>
@@ -20,11 +23,17 @@
</li>
</template>
</ListItem>
<Modal v-model="showRestart">
</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>
</List>
<Modal v-model="showConfig" title="修改实例配置" @on-ok="submitConfigChange">
<i-input type="textarea" v-model="currentConfig" :rows="20">
</i-input>
</Modal>
</div>
</template>
<script>
@@ -33,7 +42,15 @@
export default {
name: "InstanceList",
data() {
return {instances: {}, showRestart: false, update: false, build: false}
return {
instances: [],
showRestart: false,
update: false,
build: false,
showConfig: false,
currentItem: null,
currentConfig: ""
}
},
mounted() {
window.ajax.getJSON("/instance/list").then(x => {
@@ -49,17 +66,44 @@
} else {
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) {
return item.Config.Plugins.hasOwnProperty("GateWay")
},
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({
content: 'restart ' + item.Name + '...',
duration: 0

View File

@@ -79,7 +79,9 @@
</ButtonGroup>
</div>
</TabPane>
<TabPane label="导入" name="name3"></TabPane>
<TabPane label="导入" name="name3">
<ImportInstance></ImportInstance>
</TabPane>
</Tabs>
</Content>
<Modal v-model="showAddPlugin" title="添加Plugin" @on-ok="addPlugin">
@@ -124,11 +126,11 @@
<script>
import CreateInstance from "../components/CreateInstance";
import InstanceList from "../components/InstanceList";
import ImportInstance from "../components/ImportInstance";
export default {
components: {
CreateInstance,InstanceList
CreateInstance,InstanceList,ImportInstance
},
data() {
return {