mirror of
https://github.com/langhuihui/monibuca.git
synced 2025-09-27 16:32:06 +08:00
Compare commits
7 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
709c2c6ac7 | ||
![]() |
f96bc11ddb | ||
![]() |
5563ddc0d2 | ||
![]() |
95657bd6df | ||
![]() |
b9e19e75c8 | ||
![]() |
eac623639d | ||
![]() |
fea6e98ca7 |
201
main.go
201
main.go
@@ -6,9 +6,7 @@ import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/BurntSushi/toml"
|
||||
. "github.com/langhuihui/monibuca/monica"
|
||||
"github.com/langhuihui/monibuca/monica/util"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"mime"
|
||||
@@ -17,8 +15,14 @@ import (
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
. "github.com/langhuihui/monibuca/monica"
|
||||
"github.com/langhuihui/monibuca/monica/util"
|
||||
)
|
||||
|
||||
type InstanceDesc struct {
|
||||
@@ -28,7 +32,7 @@ type InstanceDesc struct {
|
||||
Config string
|
||||
}
|
||||
|
||||
var instances map[string]*InstanceDesc
|
||||
var instances = make(map[string]*InstanceDesc)
|
||||
var instancesDir string
|
||||
|
||||
func main() {
|
||||
@@ -43,10 +47,13 @@ func main() {
|
||||
}
|
||||
addr := flag.String("port", "8000", "http server port")
|
||||
flag.Parse()
|
||||
http.HandleFunc("/list", listInstance)
|
||||
http.HandleFunc("/create", initInstance)
|
||||
http.HandleFunc("/upgrade/engine", upgradeEngine)
|
||||
http.HandleFunc("/restart/instance", restartInstance)
|
||||
http.HandleFunc("/instance/listDir", listDir)
|
||||
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)
|
||||
http.HandleFunc("/instance/shutdown", shutdownInstance)
|
||||
http.HandleFunc("/", website)
|
||||
fmt.Printf("start listen at %s", *addr)
|
||||
if err := http.ListenAndServe(":"+*addr, nil); err != nil {
|
||||
@@ -54,6 +61,88 @@ 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() {
|
||||
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")
|
||||
@@ -110,6 +199,7 @@ func initInstance(w http.ResponseWriter, r *http.Request) {
|
||||
instanceDesc := new(InstanceDesc)
|
||||
sse := util.NewSSE(w, r.Context())
|
||||
err := json.Unmarshal([]byte(r.URL.Query().Get("info")), instanceDesc)
|
||||
clearDir := r.URL.Query().Get("clear") != ""
|
||||
defer func() {
|
||||
if err != nil {
|
||||
sse.WriteEvent("exception", []byte(err.Error()))
|
||||
@@ -121,7 +211,7 @@ func initInstance(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
sse.WriteEvent("step", []byte("1:参数解析成功!"))
|
||||
err = instanceDesc.createDir(sse)
|
||||
err = instanceDesc.createDir(sse, clearDir)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -138,33 +228,55 @@ func initInstance(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
instances[instanceDesc.Name] = instanceDesc
|
||||
}
|
||||
func upgradeEngine(w http.ResponseWriter, r *http.Request) {
|
||||
sse := util.NewSSE(w, r.Context())
|
||||
cmd := exec.Command("go", "get", "-u", "github.com/langhuihui/monibuca/monica")
|
||||
func shutdownInstance(w http.ResponseWriter, r *http.Request) {
|
||||
instanceName := r.URL.Query().Get("instance")
|
||||
cmd.Dir = instances[instanceName].Path
|
||||
err := sse.WriteExec(cmd)
|
||||
if err != nil {
|
||||
sse.Write([]byte(err.Error()))
|
||||
if instance, ok := instances[instanceName]; ok {
|
||||
if err := instance.command("kill", "-9", "`cat pid`").Run(); err == nil {
|
||||
w.Write([]byte("success"))
|
||||
} else {
|
||||
w.Write([]byte(err.Error()))
|
||||
}
|
||||
} else {
|
||||
w.Write([]byte("no such instance"))
|
||||
}
|
||||
}
|
||||
func restartInstance(w http.ResponseWriter, r *http.Request) {
|
||||
sse := util.NewSSE(w, r.Context())
|
||||
instanceName := r.URL.Query().Get("instance")
|
||||
cmd := exec.Command("sh", "restart.sh")
|
||||
cmd.Dir = path.Join(instancesDir, instanceName)
|
||||
cmd.Stderr = sse
|
||||
cmd.Stdout = sse
|
||||
err := cmd.Start()
|
||||
if err != nil {
|
||||
sse.Write([]byte(err.Error()))
|
||||
needUpdate := r.URL.Query().Get("update") != ""
|
||||
needBuild := r.URL.Query().Get("build") != ""
|
||||
if instance, ok := instances[instanceName]; ok {
|
||||
if needUpdate {
|
||||
if err := sse.WriteExec(instance.command("go", "get", "-u")); err != nil {
|
||||
sse.WriteEvent("failed", []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
|
||||
return sse.WriteExec(cmd)
|
||||
return
|
||||
}
|
||||
func (p *InstanceDesc) createDir(sse *util.SSE) (err error) {
|
||||
func (p *InstanceDesc) createDir(sse *util.SSE, clearDir bool) (err error) {
|
||||
if clearDir {
|
||||
os.RemoveAll(p.Path)
|
||||
}
|
||||
err = os.MkdirAll(p.Path, 0666)
|
||||
if err != nil {
|
||||
return
|
||||
@@ -195,30 +307,45 @@ 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
|
||||
}
|
||||
sse.WriteEvent("step", []byte("5:go build 成功!"))
|
||||
build.Reset()
|
||||
build.WriteString("kill -9 `cat pid`\nnohup .")
|
||||
build.WriteString(path.Dir(path.Join(p.Path, "main.go")))
|
||||
build.WriteString(" > log.txt & echo $! > pid\n")
|
||||
build.WriteString("kill -9 `cat pid`\nnohup ./")
|
||||
binFile := strings.TrimSuffix(p.Path, "/")
|
||||
_, binFile = path.Split(binFile)
|
||||
build.WriteString(binFile)
|
||||
build.WriteString(" & echo $! > pid\n")
|
||||
err = ioutil.WriteFile(path.Join(p.Path, "restart.sh"), build.Bytes(), 0777)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
cmd := exec.Command("sh", "restart.sh")
|
||||
cmd.Dir = p.Path
|
||||
cmd.Stderr = sse
|
||||
cmd.Stdout = sse
|
||||
err = cmd.Start()
|
||||
return
|
||||
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()
|
||||
|
BIN
monibuca.exe~
BIN
monibuca.exe~
Binary file not shown.
@@ -4,15 +4,22 @@ import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
)
|
||||
|
||||
var ConfigRaw []byte
|
||||
var Version = "0.1.2"
|
||||
var Version = "0.2.6"
|
||||
var EngineInfo = &struct {
|
||||
Version string
|
||||
StartTime time.Time
|
||||
}{Version, time.Now()}
|
||||
|
||||
func Run(configFile string) (err error) {
|
||||
log.Printf("start monibuca version:%s", Version)
|
||||
if ConfigRaw, err = ioutil.ReadFile(configFile); err != nil {
|
||||
log.Printf("read config file error: %v", err)
|
||||
return
|
||||
}
|
||||
go Summary.StartSummary()
|
||||
@@ -31,6 +38,8 @@ func Run(configFile string) (err error) {
|
||||
go config.Run()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.Printf("decode config file error: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@@ -1,28 +1,27 @@
|
||||
package QoS
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
. "github.com/langhuihui/monibuca/monica"
|
||||
)
|
||||
|
||||
var (
|
||||
selectMap = map[string][]string{
|
||||
"low": {"low", "medium", "high"},
|
||||
"medium": {"medium", "low", "high"},
|
||||
"high": {"high", "medium", "low"},
|
||||
}
|
||||
)
|
||||
// var (
|
||||
// selectMap = map[string][]string{
|
||||
// "low": {"low", "medium", "high"},
|
||||
// "medium": {"medium", "low", "high"},
|
||||
// "high": {"high", "medium", "low"},
|
||||
// }
|
||||
// )
|
||||
|
||||
func getQualityName(name string, qualityLevel string) string {
|
||||
if qualityLevel == "" {
|
||||
return name
|
||||
}
|
||||
for _, l := range selectMap[qualityLevel] {
|
||||
if _, ok := AllRoom.Load(name + "/" + l); ok {
|
||||
return name + "/" + l
|
||||
}
|
||||
}
|
||||
return name + "/" + qualityLevel
|
||||
}
|
||||
// func getQualityName(name string, qualityLevel string) string {
|
||||
// for _, l := range selectMap[qualityLevel] {
|
||||
// if _, ok := AllRoom.Load(name + "/" + l); ok {
|
||||
// return name + "/" + l
|
||||
// }
|
||||
// }
|
||||
// return name + "/" + qualityLevel
|
||||
// }
|
||||
|
||||
var config = struct {
|
||||
Suffix []string
|
||||
@@ -39,8 +38,23 @@ func init() {
|
||||
func run() {
|
||||
OnDropHooks.AddHook(func(s *OutputStream) {
|
||||
if s.TotalDrop > s.TotalPacket>>2 {
|
||||
//TODO
|
||||
//s.Control<-&ChangeRoomCmd{s,AllRoom.Get()}
|
||||
var newStreamPath = ""
|
||||
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)}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@@ -1,8 +1,7 @@
|
||||
package gateway
|
||||
|
||||
import (
|
||||
. "github.com/langhuihui/monibuca/monica"
|
||||
. "github.com/langhuihui/monibuca/monica/util"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"mime"
|
||||
@@ -10,11 +9,14 @@ import (
|
||||
"path"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
. "github.com/langhuihui/monibuca/monica"
|
||||
. "github.com/langhuihui/monibuca/monica/util"
|
||||
)
|
||||
|
||||
var (
|
||||
config = new(ListenerConfig)
|
||||
|
||||
config = new(ListenerConfig)
|
||||
startTime = time.Now()
|
||||
dashboardPath string
|
||||
)
|
||||
|
||||
@@ -30,6 +32,7 @@ func init() {
|
||||
})
|
||||
}
|
||||
func run() {
|
||||
http.HandleFunc("/api/sysInfo", sysInfo)
|
||||
http.HandleFunc("/api/stop", stopPublish)
|
||||
http.HandleFunc("/api/summary", summary)
|
||||
http.HandleFunc("/api/logs", watchLogs)
|
||||
@@ -95,3 +98,10 @@ 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()
|
||||
//}
|
535
pm/dist/ajax.js
vendored
Normal file
535
pm/dist/ajax.js
vendored
Normal file
@@ -0,0 +1,535 @@
|
||||
// a simple ajax
|
||||
!(function () {
|
||||
|
||||
var jsonType = 'application/json';
|
||||
var htmlType = 'text/html';
|
||||
var xmlTypeRE = /^(?:text|application)\/xml/i;
|
||||
var blankRE = /^\s*$/; // \s
|
||||
|
||||
/*
|
||||
* default setting
|
||||
* */
|
||||
var _settings = {
|
||||
|
||||
type: "GET",
|
||||
|
||||
beforeSend: noop,
|
||||
|
||||
success: noop,
|
||||
|
||||
error: noop,
|
||||
|
||||
complete: noop,
|
||||
|
||||
context: null,
|
||||
|
||||
xhr: function () {
|
||||
return new window.XMLHttpRequest();
|
||||
},
|
||||
|
||||
accepts: {
|
||||
json: jsonType,
|
||||
xml: 'application/xml, text/xml',
|
||||
html: htmlType,
|
||||
text: 'text/plain'
|
||||
},
|
||||
|
||||
crossDomain: false,
|
||||
|
||||
timeout: 0,
|
||||
|
||||
username: null,
|
||||
|
||||
password: null,
|
||||
|
||||
processData: true,
|
||||
|
||||
promise: noop
|
||||
};
|
||||
|
||||
function noop() {
|
||||
}
|
||||
|
||||
var ajax = function (options) {
|
||||
|
||||
//
|
||||
var settings = extend({}, options || {});
|
||||
|
||||
//
|
||||
for (var key in _settings) {
|
||||
if (settings[key] === undefined) {
|
||||
settings[key] = _settings[key];
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
try {
|
||||
var q = {};
|
||||
var promise = new Promise(function (resolve, reject) {
|
||||
q.resolve = resolve;
|
||||
q.reject = reject;
|
||||
});
|
||||
|
||||
promise.resolve = q.resolve;
|
||||
promise.reject = q.reject;
|
||||
|
||||
settings.promise = promise;
|
||||
}
|
||||
catch (e) {
|
||||
//
|
||||
settings.promise = {
|
||||
resolve: noop,
|
||||
reject: noop
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
if (!settings.crossDomain) {
|
||||
settings.crossDomain = /^([\w-]+:)?\/\/([^\/]+)/.test(settings.url) && RegExp.$2 !== window.location.href;
|
||||
}
|
||||
|
||||
var dataType = settings.dataType;
|
||||
// jsonp
|
||||
if (dataType === 'jsonp') {
|
||||
//
|
||||
var hasPlaceholder = /=\?/.test(settings.url);
|
||||
if (!hasPlaceholder) {
|
||||
var jsonpCallback = (settings.jsonp || 'callback') + '=?';
|
||||
|
||||
settings.url = appendQuery(settings.url, jsonpCallback)
|
||||
}
|
||||
return JSONP(settings);
|
||||
}
|
||||
|
||||
// url
|
||||
if (!settings.url) {
|
||||
settings.url = window.location.toString();
|
||||
}
|
||||
|
||||
//
|
||||
serializeData(settings);
|
||||
|
||||
var mime = settings.accepts[dataType]; // mime
|
||||
var baseHeader = {}; // header
|
||||
var protocol = /^([\w-]+:)\/\//.test(settings.url) ? RegExp.$1 : window.location.protocol; // protocol
|
||||
var xhr = _settings.xhr();
|
||||
var abortTimeout;
|
||||
|
||||
// X-Requested-With header
|
||||
// For cross-domain requests, seeing as conditions for a preflight are
|
||||
// akin to a jigsaw puzzle, we simply never set it to be sure.
|
||||
// (it can always be set on a per-request basis or even using ajaxSetup)
|
||||
// For same-domain requests, won't change header if already provided.
|
||||
if (!settings.crossDomain && !baseHeader['X-Requested-With']) {
|
||||
baseHeader['X-Requested-With'] = 'XMLHttpRequest';
|
||||
}
|
||||
|
||||
// mime
|
||||
if (mime) {
|
||||
//
|
||||
baseHeader['Accept'] = mime;
|
||||
|
||||
if (mime.indexOf(',') > -1) {
|
||||
mime = mime.split(',', 2)[0]
|
||||
}
|
||||
//
|
||||
xhr.overrideMimeType && xhr.overrideMimeType(mime);
|
||||
}
|
||||
|
||||
// contentType
|
||||
if (settings.contentType || (settings.data && settings.type.toUpperCase() !== 'GET')) {
|
||||
baseHeader['Content-Type'] = (settings.contentType || 'application/x-www-form-urlencoded; charset=UTF-8');
|
||||
}
|
||||
|
||||
// headers
|
||||
settings.headers = extend(baseHeader, settings.headers || {});
|
||||
|
||||
// on ready state change
|
||||
xhr.onreadystatechange = function () {
|
||||
// readystate
|
||||
if (xhr.readyState === 4) {
|
||||
clearTimeout(abortTimeout);
|
||||
var result;
|
||||
var error = false;
|
||||
//
|
||||
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
|
||||
dataType = dataType || mimeToDataType(xhr.getResponseHeader('content-type'));
|
||||
result = xhr.responseText;
|
||||
|
||||
try {
|
||||
// xml
|
||||
if (dataType === 'xml') {
|
||||
result = xhr.responseXML;
|
||||
}
|
||||
// json
|
||||
else if (dataType === 'json') {
|
||||
result = blankRE.test(result) ? null : JSON.parse(result);
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
error = e;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
ajaxError(error, 'parseerror', xhr, settings);
|
||||
}
|
||||
else {
|
||||
ajaxSuccess(result, xhr, settings);
|
||||
}
|
||||
}
|
||||
else {
|
||||
ajaxError(null, 'error', xhr, settings);
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
// async
|
||||
var async = 'async' in settings ? settings.async : true;
|
||||
|
||||
// open
|
||||
xhr.open(settings.type, settings.url, async, settings.username, settings.password);
|
||||
|
||||
// xhrFields
|
||||
if (settings.xhrFields) {
|
||||
for (var name in settings.xhrFields) {
|
||||
xhr[name] = settings.xhrFields[name];
|
||||
}
|
||||
}
|
||||
|
||||
// Override mime type if needed
|
||||
if (settings.mimeType && xhr.overrideMimeType) {
|
||||
xhr.overrideMimeType(settings.mimeType);
|
||||
}
|
||||
|
||||
|
||||
// set request header
|
||||
for (var name in settings.headers) {
|
||||
// Support: IE<9
|
||||
// IE's ActiveXObject throws a 'Type Mismatch' exception when setting
|
||||
// request header to a null-value.
|
||||
//
|
||||
// To keep consistent with other XHR implementations, cast the value
|
||||
// to string and ignore `undefined`.
|
||||
if (settings.headers[name] !== undefined) {
|
||||
xhr.setRequestHeader(name, settings.headers[name] + "");
|
||||
}
|
||||
}
|
||||
|
||||
// before send
|
||||
if (ajaxBeforeSend(xhr, settings) === false) {
|
||||
xhr.abort();
|
||||
return false;
|
||||
}
|
||||
|
||||
// timeout
|
||||
if (settings.timeout > 0) {
|
||||
abortTimeout = window.setTimeout(function () {
|
||||
xhr.onreadystatechange = noop;
|
||||
xhr.abort();
|
||||
ajaxError(null, 'timeout', xhr, settings);
|
||||
}, settings.timeout);
|
||||
}
|
||||
|
||||
// send
|
||||
xhr.send(settings.data ? settings.data : null);
|
||||
|
||||
return settings.promise;
|
||||
};
|
||||
|
||||
/*
|
||||
* method get
|
||||
* */
|
||||
ajax.get = function (url, data, success, dataType) {
|
||||
if (isFunction(data)) {
|
||||
dataType = dataType || success;
|
||||
success = data;
|
||||
data = undefined;
|
||||
}
|
||||
|
||||
return ajax({
|
||||
url: url,
|
||||
data: data,
|
||||
success: success,
|
||||
dataType: dataType
|
||||
});
|
||||
};
|
||||
|
||||
/*
|
||||
* method post
|
||||
*
|
||||
* dataType:
|
||||
* */
|
||||
ajax.post = function (url, data, success, dataType) {
|
||||
if (isFunction(data)) {
|
||||
dataType = dataType || success;
|
||||
success = data;
|
||||
data = undefined;
|
||||
}
|
||||
return ajax({
|
||||
type: 'POST',
|
||||
url: url,
|
||||
data: data,
|
||||
success: success,
|
||||
dataType: dataType
|
||||
})
|
||||
};
|
||||
|
||||
/*
|
||||
* method getJSON
|
||||
* */
|
||||
ajax.getJSON = function (url, data, success) {
|
||||
|
||||
if (isFunction(data)) {
|
||||
success = data;
|
||||
data = undefined;
|
||||
}
|
||||
|
||||
return ajax({
|
||||
url: url,
|
||||
data: data,
|
||||
success: success,
|
||||
dataType: 'json'
|
||||
})
|
||||
};
|
||||
|
||||
/*
|
||||
* method ajaxSetup
|
||||
* */
|
||||
ajax.ajaxSetup = function (target, settings) {
|
||||
return settings ? extend(extend(target, _settings), settings) : extend(_settings, target);
|
||||
};
|
||||
|
||||
/*
|
||||
* utils
|
||||
*
|
||||
* */
|
||||
|
||||
|
||||
// triggers and extra global event ajaxBeforeSend that's like ajaxSend but cancelable
|
||||
function ajaxBeforeSend(xhr, settings) {
|
||||
var context = settings.context;
|
||||
//
|
||||
if (settings.beforeSend.call(context, xhr, settings) === false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ajax success
|
||||
function ajaxSuccess(data, xhr, settings) {
|
||||
var context = settings.context;
|
||||
var status = 'success';
|
||||
settings.success.call(context, data, status, xhr);
|
||||
settings.promise.resolve(data, status, xhr);
|
||||
ajaxComplete(status, xhr, settings);
|
||||
}
|
||||
|
||||
// status: "success", "notmodified", "error", "timeout", "abort", "parsererror"
|
||||
function ajaxComplete(status, xhr, settings) {
|
||||
var context = settings.context;
|
||||
settings.complete.call(context, xhr, status);
|
||||
}
|
||||
|
||||
// type: "timeout", "error", "abort", "parsererror"
|
||||
function ajaxError(error, type, xhr, settings) {
|
||||
var context = settings.context;
|
||||
settings.error.call(context, xhr, type, error);
|
||||
settings.promise.reject(xhr, type, error);
|
||||
ajaxComplete(type, xhr, settings);
|
||||
}
|
||||
|
||||
|
||||
// jsonp
|
||||
/*
|
||||
* tks: https://www.cnblogs.com/rubylouvre/archive/2011/02/13/1953087.html
|
||||
* */
|
||||
function JSONP(options) {
|
||||
//
|
||||
var callbackName = options.jsonpCallback || 'jsonp' + (new Date().getTime());
|
||||
|
||||
var script = window.document.createElement('script');
|
||||
|
||||
var abort = function () {
|
||||
// 设置 window.xxx = noop
|
||||
if (callbackName in window) {
|
||||
window[callbackName] = noop;
|
||||
}
|
||||
};
|
||||
|
||||
var xhr = {abort: abort};
|
||||
var abortTimeout;
|
||||
|
||||
var head = window.document.getElementsByTagName('head')[0] || window.document.documentElement;
|
||||
|
||||
// ie8+
|
||||
script.onerror = function (error) {
|
||||
_error(error);
|
||||
};
|
||||
|
||||
function _error(error) {
|
||||
window.clearTimeout(abortTimeout);
|
||||
xhr.abort();
|
||||
ajaxError(error.type, xhr, error.type, options);
|
||||
_removeScript();
|
||||
}
|
||||
|
||||
window[callbackName] = function (data) {
|
||||
window.clearTimeout(abortTimeout);
|
||||
ajaxSuccess(data, xhr, options);
|
||||
_removeScript();
|
||||
};
|
||||
|
||||
//
|
||||
serializeData(options);
|
||||
|
||||
script.src = options.url.replace(/=\?/, '=' + callbackName);
|
||||
//
|
||||
script.src = appendQuery(script.src, '_=' + (new Date()).getTime());
|
||||
//
|
||||
script.async = true;
|
||||
|
||||
// script charset
|
||||
if (options.scriptCharset) {
|
||||
script.charset = options.scriptCharset;
|
||||
}
|
||||
|
||||
//
|
||||
head.insertBefore(script, head.firstChild);
|
||||
|
||||
//
|
||||
if (options.timeout > 0) {
|
||||
abortTimeout = window.setTimeout(function () {
|
||||
xhr.abort();
|
||||
ajaxError('timeout', xhr, 'timeout', options);
|
||||
_removeScript();
|
||||
}, options.timeout);
|
||||
}
|
||||
|
||||
// remove script
|
||||
function _removeScript() {
|
||||
if (script.clearAttributes) {
|
||||
script.clearAttributes();
|
||||
} else {
|
||||
script.onload = script.onreadystatechange = script.onerror = null;
|
||||
}
|
||||
|
||||
if (script.parentNode) {
|
||||
script.parentNode.removeChild(script);
|
||||
}
|
||||
//
|
||||
script = null;
|
||||
|
||||
delete window[callbackName];
|
||||
}
|
||||
|
||||
return options.promise;
|
||||
}
|
||||
|
||||
// mime to data type
|
||||
function mimeToDataType(mime) {
|
||||
return mime && (mime === htmlType ? 'html' : mime === jsonType ? 'json' : xmlTypeRE.test(mime) && 'xml') || 'text'
|
||||
}
|
||||
|
||||
// append query
|
||||
function appendQuery(url, query) {
|
||||
return (url + '&' + query).replace(/[&?]{1,2}/, '?');
|
||||
}
|
||||
|
||||
// serialize data
|
||||
function serializeData(options) {
|
||||
// formData
|
||||
if (isObject(options) && !isFormData(options.data) && options.processData) {
|
||||
options.data = param(options.data);
|
||||
}
|
||||
|
||||
if (options.data && (!options.type || options.type.toUpperCase() === 'GET')) {
|
||||
options.url = appendQuery(options.url, options.data);
|
||||
}
|
||||
}
|
||||
|
||||
// serialize
|
||||
function serialize(params, obj, traditional, scope) {
|
||||
var _isArray = isArray(obj);
|
||||
|
||||
for (var key in obj) {
|
||||
var value = obj[key];
|
||||
|
||||
if (scope) {
|
||||
key = traditional ? scope : scope + '[' + (_isArray ? '' : key) + ']';
|
||||
}
|
||||
|
||||
// handle data in serializeArray format
|
||||
if (!scope && _isArray) {
|
||||
params.add(value.name, value.value);
|
||||
|
||||
}
|
||||
else if (traditional ? _isArray(value) : isObject(value)) {
|
||||
serialize(params, value, traditional, key);
|
||||
}
|
||||
else {
|
||||
params.add(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// param
|
||||
function param(obj, traditional) {
|
||||
var params = [];
|
||||
//
|
||||
params.add = function (k, v) {
|
||||
this.push(encodeURIComponent(k) + '=' + encodeURIComponent(v));
|
||||
};
|
||||
serialize(params, obj, traditional);
|
||||
return params.join('&').replace('%20', '+');
|
||||
}
|
||||
|
||||
// extend
|
||||
function extend(target) {
|
||||
var slice = Array.prototype.slice;
|
||||
var args = slice.call(arguments, 1);
|
||||
//
|
||||
for (var i = 0, length = args.length; i < length; i++) {
|
||||
var source = args[i] || {};
|
||||
for (var key in source) {
|
||||
if (source.hasOwnProperty(key) && source[key] !== undefined) {
|
||||
target[key] = source[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
// is object
|
||||
function isObject(obj) {
|
||||
var type = typeof obj;
|
||||
return type === 'function' || type === 'object' && !!obj;
|
||||
}
|
||||
|
||||
// is formData
|
||||
function isFormData(obj) {
|
||||
return obj instanceof FormData;
|
||||
}
|
||||
|
||||
// is array
|
||||
function isArray(value) {
|
||||
return Object.prototype.toString.call(value) === "[object Array]";
|
||||
}
|
||||
|
||||
// is function
|
||||
function isFunction(value) {
|
||||
return typeof value === "function";
|
||||
}
|
||||
|
||||
// browser
|
||||
window.ajax = ajax;
|
||||
})();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
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>pm</title><link href=/css/app.74a1e2f4.css rel=preload as=style><link href=/css/chunk-vendors.22ebf426.css rel=preload as=style><link href=/js/app.4b08c1d1.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.74a1e2f4.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.4b08c1d1.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.4b08c1d1.js
vendored
2
pm/dist/js/app.4b08c1d1.js
vendored
File diff suppressed because one or more lines are too long
1
pm/dist/js/app.4b08c1d1.js.map
vendored
1
pm/dist/js/app.4b08c1d1.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.6b87e1b5.js.map
vendored
1
pm/dist/js/chunk-vendors.6b87e1b5.js.map
vendored
File diff suppressed because one or more lines are too long
5
pm/package-lock.json
generated
5
pm/package-lock.json
generated
@@ -979,6 +979,11 @@
|
||||
"@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": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npm.taobao.org/@intervolga/optimize-cssnano-plugin/download/@intervolga/optimize-cssnano-plugin-1.0.6.tgz",
|
||||
|
@@ -8,6 +8,7 @@
|
||||
"lint": "vue-cli-service lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@iarna/toml": "^2.2.3",
|
||||
"core-js": "^3.4.4",
|
||||
"view-design": "^4.0.0",
|
||||
"vue": "^2.6.10",
|
||||
@@ -37,7 +38,7 @@
|
||||
"plugin:vue/essential",
|
||||
"eslint:recommended"
|
||||
],
|
||||
"rules": {},
|
||||
"rules": {"no-console": "off"},
|
||||
"parserOptions": {
|
||||
"parser": "babel-eslint"
|
||||
}
|
||||
|
535
pm/public/ajax.js
Normal file
535
pm/public/ajax.js
Normal file
@@ -0,0 +1,535 @@
|
||||
// a simple ajax
|
||||
!(function () {
|
||||
|
||||
var jsonType = 'application/json';
|
||||
var htmlType = 'text/html';
|
||||
var xmlTypeRE = /^(?:text|application)\/xml/i;
|
||||
var blankRE = /^\s*$/; // \s
|
||||
|
||||
/*
|
||||
* default setting
|
||||
* */
|
||||
var _settings = {
|
||||
|
||||
type: "GET",
|
||||
|
||||
beforeSend: noop,
|
||||
|
||||
success: noop,
|
||||
|
||||
error: noop,
|
||||
|
||||
complete: noop,
|
||||
|
||||
context: null,
|
||||
|
||||
xhr: function () {
|
||||
return new window.XMLHttpRequest();
|
||||
},
|
||||
|
||||
accepts: {
|
||||
json: jsonType,
|
||||
xml: 'application/xml, text/xml',
|
||||
html: htmlType,
|
||||
text: 'text/plain'
|
||||
},
|
||||
|
||||
crossDomain: false,
|
||||
|
||||
timeout: 0,
|
||||
|
||||
username: null,
|
||||
|
||||
password: null,
|
||||
|
||||
processData: true,
|
||||
|
||||
promise: noop
|
||||
};
|
||||
|
||||
function noop() {
|
||||
}
|
||||
|
||||
var ajax = function (options) {
|
||||
|
||||
//
|
||||
var settings = extend({}, options || {});
|
||||
|
||||
//
|
||||
for (var key in _settings) {
|
||||
if (settings[key] === undefined) {
|
||||
settings[key] = _settings[key];
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
try {
|
||||
var q = {};
|
||||
var promise = new Promise(function (resolve, reject) {
|
||||
q.resolve = resolve;
|
||||
q.reject = reject;
|
||||
});
|
||||
|
||||
promise.resolve = q.resolve;
|
||||
promise.reject = q.reject;
|
||||
|
||||
settings.promise = promise;
|
||||
}
|
||||
catch (e) {
|
||||
//
|
||||
settings.promise = {
|
||||
resolve: noop,
|
||||
reject: noop
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
if (!settings.crossDomain) {
|
||||
settings.crossDomain = /^([\w-]+:)?\/\/([^\/]+)/.test(settings.url) && RegExp.$2 !== window.location.href;
|
||||
}
|
||||
|
||||
var dataType = settings.dataType;
|
||||
// jsonp
|
||||
if (dataType === 'jsonp') {
|
||||
//
|
||||
var hasPlaceholder = /=\?/.test(settings.url);
|
||||
if (!hasPlaceholder) {
|
||||
var jsonpCallback = (settings.jsonp || 'callback') + '=?';
|
||||
|
||||
settings.url = appendQuery(settings.url, jsonpCallback)
|
||||
}
|
||||
return JSONP(settings);
|
||||
}
|
||||
|
||||
// url
|
||||
if (!settings.url) {
|
||||
settings.url = window.location.toString();
|
||||
}
|
||||
|
||||
//
|
||||
serializeData(settings);
|
||||
|
||||
var mime = settings.accepts[dataType]; // mime
|
||||
var baseHeader = {}; // header
|
||||
var protocol = /^([\w-]+:)\/\//.test(settings.url) ? RegExp.$1 : window.location.protocol; // protocol
|
||||
var xhr = _settings.xhr();
|
||||
var abortTimeout;
|
||||
|
||||
// X-Requested-With header
|
||||
// For cross-domain requests, seeing as conditions for a preflight are
|
||||
// akin to a jigsaw puzzle, we simply never set it to be sure.
|
||||
// (it can always be set on a per-request basis or even using ajaxSetup)
|
||||
// For same-domain requests, won't change header if already provided.
|
||||
if (!settings.crossDomain && !baseHeader['X-Requested-With']) {
|
||||
baseHeader['X-Requested-With'] = 'XMLHttpRequest';
|
||||
}
|
||||
|
||||
// mime
|
||||
if (mime) {
|
||||
//
|
||||
baseHeader['Accept'] = mime;
|
||||
|
||||
if (mime.indexOf(',') > -1) {
|
||||
mime = mime.split(',', 2)[0]
|
||||
}
|
||||
//
|
||||
xhr.overrideMimeType && xhr.overrideMimeType(mime);
|
||||
}
|
||||
|
||||
// contentType
|
||||
if (settings.contentType || (settings.data && settings.type.toUpperCase() !== 'GET')) {
|
||||
baseHeader['Content-Type'] = (settings.contentType || 'application/x-www-form-urlencoded; charset=UTF-8');
|
||||
}
|
||||
|
||||
// headers
|
||||
settings.headers = extend(baseHeader, settings.headers || {});
|
||||
|
||||
// on ready state change
|
||||
xhr.onreadystatechange = function () {
|
||||
// readystate
|
||||
if (xhr.readyState === 4) {
|
||||
clearTimeout(abortTimeout);
|
||||
var result;
|
||||
var error = false;
|
||||
//
|
||||
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
|
||||
dataType = dataType || mimeToDataType(xhr.getResponseHeader('content-type'));
|
||||
result = xhr.responseText;
|
||||
|
||||
try {
|
||||
// xml
|
||||
if (dataType === 'xml') {
|
||||
result = xhr.responseXML;
|
||||
}
|
||||
// json
|
||||
else if (dataType === 'json') {
|
||||
result = blankRE.test(result) ? null : JSON.parse(result);
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
error = e;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
ajaxError(error, 'parseerror', xhr, settings);
|
||||
}
|
||||
else {
|
||||
ajaxSuccess(result, xhr, settings);
|
||||
}
|
||||
}
|
||||
else {
|
||||
ajaxError(null, 'error', xhr, settings);
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
// async
|
||||
var async = 'async' in settings ? settings.async : true;
|
||||
|
||||
// open
|
||||
xhr.open(settings.type, settings.url, async, settings.username, settings.password);
|
||||
|
||||
// xhrFields
|
||||
if (settings.xhrFields) {
|
||||
for (var name in settings.xhrFields) {
|
||||
xhr[name] = settings.xhrFields[name];
|
||||
}
|
||||
}
|
||||
|
||||
// Override mime type if needed
|
||||
if (settings.mimeType && xhr.overrideMimeType) {
|
||||
xhr.overrideMimeType(settings.mimeType);
|
||||
}
|
||||
|
||||
|
||||
// set request header
|
||||
for (var name in settings.headers) {
|
||||
// Support: IE<9
|
||||
// IE's ActiveXObject throws a 'Type Mismatch' exception when setting
|
||||
// request header to a null-value.
|
||||
//
|
||||
// To keep consistent with other XHR implementations, cast the value
|
||||
// to string and ignore `undefined`.
|
||||
if (settings.headers[name] !== undefined) {
|
||||
xhr.setRequestHeader(name, settings.headers[name] + "");
|
||||
}
|
||||
}
|
||||
|
||||
// before send
|
||||
if (ajaxBeforeSend(xhr, settings) === false) {
|
||||
xhr.abort();
|
||||
return false;
|
||||
}
|
||||
|
||||
// timeout
|
||||
if (settings.timeout > 0) {
|
||||
abortTimeout = window.setTimeout(function () {
|
||||
xhr.onreadystatechange = noop;
|
||||
xhr.abort();
|
||||
ajaxError(null, 'timeout', xhr, settings);
|
||||
}, settings.timeout);
|
||||
}
|
||||
|
||||
// send
|
||||
xhr.send(settings.data ? settings.data : null);
|
||||
|
||||
return settings.promise;
|
||||
};
|
||||
|
||||
/*
|
||||
* method get
|
||||
* */
|
||||
ajax.get = function (url, data, success, dataType) {
|
||||
if (isFunction(data)) {
|
||||
dataType = dataType || success;
|
||||
success = data;
|
||||
data = undefined;
|
||||
}
|
||||
|
||||
return ajax({
|
||||
url: url,
|
||||
data: data,
|
||||
success: success,
|
||||
dataType: dataType
|
||||
});
|
||||
};
|
||||
|
||||
/*
|
||||
* method post
|
||||
*
|
||||
* dataType:
|
||||
* */
|
||||
ajax.post = function (url, data, success, dataType) {
|
||||
if (isFunction(data)) {
|
||||
dataType = dataType || success;
|
||||
success = data;
|
||||
data = undefined;
|
||||
}
|
||||
return ajax({
|
||||
type: 'POST',
|
||||
url: url,
|
||||
data: data,
|
||||
success: success,
|
||||
dataType: dataType
|
||||
})
|
||||
};
|
||||
|
||||
/*
|
||||
* method getJSON
|
||||
* */
|
||||
ajax.getJSON = function (url, data, success) {
|
||||
|
||||
if (isFunction(data)) {
|
||||
success = data;
|
||||
data = undefined;
|
||||
}
|
||||
|
||||
return ajax({
|
||||
url: url,
|
||||
data: data,
|
||||
success: success,
|
||||
dataType: 'json'
|
||||
})
|
||||
};
|
||||
|
||||
/*
|
||||
* method ajaxSetup
|
||||
* */
|
||||
ajax.ajaxSetup = function (target, settings) {
|
||||
return settings ? extend(extend(target, _settings), settings) : extend(_settings, target);
|
||||
};
|
||||
|
||||
/*
|
||||
* utils
|
||||
*
|
||||
* */
|
||||
|
||||
|
||||
// triggers and extra global event ajaxBeforeSend that's like ajaxSend but cancelable
|
||||
function ajaxBeforeSend(xhr, settings) {
|
||||
var context = settings.context;
|
||||
//
|
||||
if (settings.beforeSend.call(context, xhr, settings) === false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ajax success
|
||||
function ajaxSuccess(data, xhr, settings) {
|
||||
var context = settings.context;
|
||||
var status = 'success';
|
||||
settings.success.call(context, data, status, xhr);
|
||||
settings.promise.resolve(data, status, xhr);
|
||||
ajaxComplete(status, xhr, settings);
|
||||
}
|
||||
|
||||
// status: "success", "notmodified", "error", "timeout", "abort", "parsererror"
|
||||
function ajaxComplete(status, xhr, settings) {
|
||||
var context = settings.context;
|
||||
settings.complete.call(context, xhr, status);
|
||||
}
|
||||
|
||||
// type: "timeout", "error", "abort", "parsererror"
|
||||
function ajaxError(error, type, xhr, settings) {
|
||||
var context = settings.context;
|
||||
settings.error.call(context, xhr, type, error);
|
||||
settings.promise.reject(xhr, type, error);
|
||||
ajaxComplete(type, xhr, settings);
|
||||
}
|
||||
|
||||
|
||||
// jsonp
|
||||
/*
|
||||
* tks: https://www.cnblogs.com/rubylouvre/archive/2011/02/13/1953087.html
|
||||
* */
|
||||
function JSONP(options) {
|
||||
//
|
||||
var callbackName = options.jsonpCallback || 'jsonp' + (new Date().getTime());
|
||||
|
||||
var script = window.document.createElement('script');
|
||||
|
||||
var abort = function () {
|
||||
// 设置 window.xxx = noop
|
||||
if (callbackName in window) {
|
||||
window[callbackName] = noop;
|
||||
}
|
||||
};
|
||||
|
||||
var xhr = {abort: abort};
|
||||
var abortTimeout;
|
||||
|
||||
var head = window.document.getElementsByTagName('head')[0] || window.document.documentElement;
|
||||
|
||||
// ie8+
|
||||
script.onerror = function (error) {
|
||||
_error(error);
|
||||
};
|
||||
|
||||
function _error(error) {
|
||||
window.clearTimeout(abortTimeout);
|
||||
xhr.abort();
|
||||
ajaxError(error.type, xhr, error.type, options);
|
||||
_removeScript();
|
||||
}
|
||||
|
||||
window[callbackName] = function (data) {
|
||||
window.clearTimeout(abortTimeout);
|
||||
ajaxSuccess(data, xhr, options);
|
||||
_removeScript();
|
||||
};
|
||||
|
||||
//
|
||||
serializeData(options);
|
||||
|
||||
script.src = options.url.replace(/=\?/, '=' + callbackName);
|
||||
//
|
||||
script.src = appendQuery(script.src, '_=' + (new Date()).getTime());
|
||||
//
|
||||
script.async = true;
|
||||
|
||||
// script charset
|
||||
if (options.scriptCharset) {
|
||||
script.charset = options.scriptCharset;
|
||||
}
|
||||
|
||||
//
|
||||
head.insertBefore(script, head.firstChild);
|
||||
|
||||
//
|
||||
if (options.timeout > 0) {
|
||||
abortTimeout = window.setTimeout(function () {
|
||||
xhr.abort();
|
||||
ajaxError('timeout', xhr, 'timeout', options);
|
||||
_removeScript();
|
||||
}, options.timeout);
|
||||
}
|
||||
|
||||
// remove script
|
||||
function _removeScript() {
|
||||
if (script.clearAttributes) {
|
||||
script.clearAttributes();
|
||||
} else {
|
||||
script.onload = script.onreadystatechange = script.onerror = null;
|
||||
}
|
||||
|
||||
if (script.parentNode) {
|
||||
script.parentNode.removeChild(script);
|
||||
}
|
||||
//
|
||||
script = null;
|
||||
|
||||
delete window[callbackName];
|
||||
}
|
||||
|
||||
return options.promise;
|
||||
}
|
||||
|
||||
// mime to data type
|
||||
function mimeToDataType(mime) {
|
||||
return mime && (mime === htmlType ? 'html' : mime === jsonType ? 'json' : xmlTypeRE.test(mime) && 'xml') || 'text'
|
||||
}
|
||||
|
||||
// append query
|
||||
function appendQuery(url, query) {
|
||||
return (url + '&' + query).replace(/[&?]{1,2}/, '?');
|
||||
}
|
||||
|
||||
// serialize data
|
||||
function serializeData(options) {
|
||||
// formData
|
||||
if (isObject(options) && !isFormData(options.data) && options.processData) {
|
||||
options.data = param(options.data);
|
||||
}
|
||||
|
||||
if (options.data && (!options.type || options.type.toUpperCase() === 'GET')) {
|
||||
options.url = appendQuery(options.url, options.data);
|
||||
}
|
||||
}
|
||||
|
||||
// serialize
|
||||
function serialize(params, obj, traditional, scope) {
|
||||
var _isArray = isArray(obj);
|
||||
|
||||
for (var key in obj) {
|
||||
var value = obj[key];
|
||||
|
||||
if (scope) {
|
||||
key = traditional ? scope : scope + '[' + (_isArray ? '' : key) + ']';
|
||||
}
|
||||
|
||||
// handle data in serializeArray format
|
||||
if (!scope && _isArray) {
|
||||
params.add(value.name, value.value);
|
||||
|
||||
}
|
||||
else if (traditional ? _isArray(value) : isObject(value)) {
|
||||
serialize(params, value, traditional, key);
|
||||
}
|
||||
else {
|
||||
params.add(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// param
|
||||
function param(obj, traditional) {
|
||||
var params = [];
|
||||
//
|
||||
params.add = function (k, v) {
|
||||
this.push(encodeURIComponent(k) + '=' + encodeURIComponent(v));
|
||||
};
|
||||
serialize(params, obj, traditional);
|
||||
return params.join('&').replace('%20', '+');
|
||||
}
|
||||
|
||||
// extend
|
||||
function extend(target) {
|
||||
var slice = Array.prototype.slice;
|
||||
var args = slice.call(arguments, 1);
|
||||
//
|
||||
for (var i = 0, length = args.length; i < length; i++) {
|
||||
var source = args[i] || {};
|
||||
for (var key in source) {
|
||||
if (source.hasOwnProperty(key) && source[key] !== undefined) {
|
||||
target[key] = source[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
// is object
|
||||
function isObject(obj) {
|
||||
var type = typeof obj;
|
||||
return type === 'function' || type === 'object' && !!obj;
|
||||
}
|
||||
|
||||
// is formData
|
||||
function isFormData(obj) {
|
||||
return obj instanceof FormData;
|
||||
}
|
||||
|
||||
// is array
|
||||
function isArray(value) {
|
||||
return Object.prototype.toString.call(value) === "[object Array]";
|
||||
}
|
||||
|
||||
// is function
|
||||
function isFunction(value) {
|
||||
return typeof value === "function";
|
||||
}
|
||||
|
||||
// browser
|
||||
window.ajax = ajax;
|
||||
})();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@@ -5,7 +5,8 @@
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||
<title>pm</title>
|
||||
<title>Monibuca Instance Manager</title>
|
||||
<script src="ajax.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
|
@@ -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>
|
||||
@@ -12,46 +12,54 @@
|
||||
<div>
|
||||
<pre>{{log}}</pre>
|
||||
</div>
|
||||
<div slot="footer">
|
||||
<Checkbox v-model="clearDir">安装前清空目录</Checkbox>
|
||||
<Button type="primary" @click="start" :loading="status=='process'">开始</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
let eventSource = null
|
||||
export default {
|
||||
name: "CreateInstance",
|
||||
props: {
|
||||
info: Object,
|
||||
},
|
||||
watch: {
|
||||
info(v) {
|
||||
if (v) {
|
||||
eventSource = new EventSource("/create?info="+JSON.stringify(v))
|
||||
eventSource.onmessage = evt => {
|
||||
this.log += evt.data + "\n"
|
||||
if (evt.data == "success") {
|
||||
this.status = "finish"
|
||||
eventSource.close()
|
||||
}
|
||||
}
|
||||
eventSource.addEventListener("exception", evt => {
|
||||
this.log += evt.data + "\n"
|
||||
this.status = "error"
|
||||
eventSource.close()
|
||||
})
|
||||
eventSource.addEventListener("step", evt => {
|
||||
let [step,msg] = evt.data.split(":")
|
||||
this.currentStep = step|0
|
||||
this.log+=msg+"\n"
|
||||
})
|
||||
let eventSource = null;
|
||||
export default {
|
||||
name: "CreateInstance",
|
||||
props: {
|
||||
info: Object
|
||||
},
|
||||
methods: {
|
||||
start() {
|
||||
this.status = "process";
|
||||
eventSource = new EventSource(
|
||||
"/instance/create?info=" +
|
||||
JSON.stringify(this.info) +
|
||||
(this.clearDir ? "&clear=true" : "")
|
||||
);
|
||||
eventSource.onopen = () => (this.log = "");
|
||||
eventSource.onmessage = evt => {
|
||||
this.log += evt.data + "\n";
|
||||
if (evt.data == "success") {
|
||||
this.status = "finish";
|
||||
eventSource.close();
|
||||
}
|
||||
}
|
||||
};
|
||||
eventSource.addEventListener("exception", evt => {
|
||||
this.log += evt.data + "\n";
|
||||
this.status = "error";
|
||||
eventSource.close();
|
||||
});
|
||||
eventSource.addEventListener("step", evt => {
|
||||
let [step, msg] = evt.data.split(":");
|
||||
this.currentStep = step | 0;
|
||||
this.log += msg + "\n";
|
||||
});
|
||||
},
|
||||
data() {
|
||||
return {currentStep: 0, log: "", status: "process"}
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return { clearDir: true, currentStep: 0, log: "", status: "wait" };
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@@ -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>
|
47
pm/src/components/ImportInstance.vue
Normal file
47
pm/src/components/ImportInstance.vue
Normal file
@@ -0,0 +1,47 @@
|
||||
<template>
|
||||
<div>
|
||||
<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:"",
|
||||
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+"&name="+this.instanceName).then(x=>{
|
||||
if(x=="success"){
|
||||
this.$Message.success("导入成功!")
|
||||
}else{
|
||||
this.$Message.error(x)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
156
pm/src/components/InstanceList.vue
Normal file
156
pm/src/components/InstanceList.vue
Normal file
@@ -0,0 +1,156 @@
|
||||
<template>
|
||||
<div>
|
||||
<List border>
|
||||
<ListItem v-for="item in instances" :key="item.Name">
|
||||
<ListItemMeta :title="item.Name" :description="item.Path"></ListItemMeta>
|
||||
<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="openGateway(item)">
|
||||
<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"
|
||||
import StartTime from "./StartTime"
|
||||
|
||||
export default {
|
||||
name: "InstanceList",
|
||||
components: {StartTime},
|
||||
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
|
||||
}).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)
|
||||
}
|
||||
},
|
||||
openGateway(item) {
|
||||
window.open(this.gateWayHref(item), '_blank')
|
||||
},
|
||||
hasGateway(item) {
|
||||
return item.Config.Plugins.hasOwnProperty("GateWay")
|
||||
},
|
||||
gateWayHref(item) {
|
||||
return "http://" + 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 && e.toString()) 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>
|
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: {
|
||||
},
|
||||
|
@@ -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>
|
@@ -4,11 +4,7 @@
|
||||
<Content class="content">
|
||||
<Tabs value="name1">
|
||||
<TabPane label="实例" name="name1">
|
||||
<List border>
|
||||
<ListItem v-for="item in instances" :key="item.Name">
|
||||
<ListItemMeta :title="item.Name" :description="item.Path"></ListItemMeta>
|
||||
</ListItem>
|
||||
</List>
|
||||
<InstanceList></InstanceList>
|
||||
</TabPane>
|
||||
<TabPane label="创建" name="name2">
|
||||
<Steps :current="createStep">
|
||||
@@ -17,24 +13,24 @@
|
||||
<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 v-model="instanceName" :placeholder="createPath.split('/').pop()"></i-input>
|
||||
<i-input
|
||||
v-model="instanceName"
|
||||
:placeholder="createPath.split('/').pop()"
|
||||
></i-input>
|
||||
<h4>安装路径:</h4>
|
||||
<div>
|
||||
<pre>{{createPath}}</pre>
|
||||
@@ -49,14 +45,31 @@
|
||||
</div>
|
||||
</div>
|
||||
<ButtonGroup style="display:table;margin:50px auto;">
|
||||
<Button size="large" type="primary" @click="createStep--" v-if="createStep!=0">
|
||||
<Button
|
||||
size="large"
|
||||
type="primary"
|
||||
@click="createStep--"
|
||||
v-if="createStep!=0"
|
||||
>
|
||||
<Icon type="ios-arrow-back"></Icon>
|
||||
上一步
|
||||
</Button>
|
||||
<Button size="large" type="success" @click="showAddPlugin=true" v-if="createStep==1">+
|
||||
<Button
|
||||
size="large"
|
||||
type="success"
|
||||
@click="showAddPlugin=true"
|
||||
v-if="createStep==1"
|
||||
>
|
||||
+
|
||||
添加插件
|
||||
</Button>
|
||||
<Button size="large" type="primary" @click="createStep++" v-if="createStep!=2">下一步
|
||||
<Button
|
||||
size="large"
|
||||
type="primary"
|
||||
@click="createStep++"
|
||||
v-if="createStep!=2"
|
||||
>
|
||||
下一步
|
||||
<Icon type="ios-arrow-forward"></Icon>
|
||||
</Button>
|
||||
<Button size="large" type="success" @click="createInstance" v-else>开始创建</Button>
|
||||
@@ -64,7 +77,7 @@
|
||||
</div>
|
||||
</TabPane>
|
||||
<TabPane label="导入" name="name3">
|
||||
|
||||
<ImportInstance></ImportInstance>
|
||||
</TabPane>
|
||||
</Tabs>
|
||||
</Content>
|
||||
@@ -74,11 +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>
|
||||
@@ -87,112 +98,90 @@
|
||||
</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>
|
||||
|
||||
<script>
|
||||
import CreateInstance from "../components/CreateInstance"
|
||||
import CreateInstance from "../components/CreateInstance";
|
||||
import InstanceList from "../components/InstanceList";
|
||||
import ImportInstance from "../components/ImportInstance";
|
||||
import PathSelector from "../components/PathSelector"
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CreateInstance
|
||||
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",
|
||||
instances: [],
|
||||
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).map(x => x.Path).join("\n")
|
||||
return Object.values(this.plugins).filter(x => x.enabled)
|
||||
.map(x => x.Path)
|
||||
.join("\n");
|
||||
},
|
||||
configStr() {
|
||||
return Object.values(this.plugins).map(x => `[Plugins.${x.Name}]
|
||||
${x.Config || ""}`).join("\n")
|
||||
return Object.values(this.plugins).filter(x => x.enabled)
|
||||
.map(
|
||||
x => `[Plugins.${x.Name}]
|
||||
${x.Config || ""}`
|
||||
)
|
||||
.join("\n");
|
||||
},
|
||||
privateHost(){
|
||||
return (this.formPlugin.Path && this.formPlugin.Path.split("/")[0])||"仓库域名"
|
||||
privateHost() {
|
||||
return (
|
||||
(this.formPlugin.Path && this.formPlugin.Path.split("/")[0]) ||
|
||||
"仓库域名"
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
goUp() {
|
||||
let paths = this.createPath.split("/")
|
||||
paths.pop()
|
||||
this.createPath = paths.join("/")
|
||||
let paths = this.createPath.split("/");
|
||||
paths.pop();
|
||||
this.createPath = paths.join("/");
|
||||
},
|
||||
createInstance() {
|
||||
this.showCreate = true
|
||||
this.showCreate = true;
|
||||
this.createInfo = {
|
||||
Name: this.instanceName || this.createPath.split('/').pop(),
|
||||
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
|
||||
}
|
||||
};
|
||||
},
|
||||
addPlugin() {
|
||||
this.plugins[this.formPlugin.Name] = this.formPlugin
|
||||
this.formPlugin = {}
|
||||
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>
|
||||
|
||||
<style>
|
||||
.content {
|
||||
background: white
|
||||
background: white;
|
||||
}
|
||||
|
||||
pre {
|
||||
|
Reference in New Issue
Block a user