Compare commits

...

2 Commits

Author SHA1 Message Date
langhuihui
eac623639d 界面增加重启和升级 2020-02-11 21:59:31 +08:00
langhuihui
fea6e98ca7 小功能增加 2020-02-11 17:27:05 +08:00
15 changed files with 1349 additions and 163 deletions

58
main.go
View File

@@ -6,9 +6,6 @@ import (
"errors"
"flag"
"fmt"
"github.com/BurntSushi/toml"
. "github.com/langhuihui/monibuca/monica"
"github.com/langhuihui/monibuca/monica/util"
"io/ioutil"
"log"
"mime"
@@ -19,6 +16,10 @@ import (
"path"
"runtime"
"strings"
"github.com/BurntSushi/toml"
. "github.com/langhuihui/monibuca/monica"
"github.com/langhuihui/monibuca/monica/util"
)
type InstanceDesc struct {
@@ -28,7 +29,7 @@ type InstanceDesc struct {
Config string
}
var instances map[string]*InstanceDesc
var instances = make(map[string]*InstanceDesc)
var instancesDir string
func main() {
@@ -110,6 +111,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 +123,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
}
@@ -140,31 +142,38 @@ func initInstance(w http.ResponseWriter, r *http.Request) {
}
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")
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.writeExecSSE(sse, exec.Command("go", "get", "-u", "github.com/langhuihui/monibuca/monica")); err != nil {
sse.WriteEvent("failed", []byte(err.Error()))
} else {
sse.Write([]byte("success"))
}
} else {
sse.WriteEvent("failed", []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()))
if instance, ok := instances[instanceName]; ok {
if err := instance.writeExecSSE(sse, exec.Command("sh", "restart.sh")); err != nil {
sse.WriteEvent("failed", []byte(err.Error()))
} else {
sse.Write([]byte("success"))
}
} else {
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) 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
@@ -206,19 +215,16 @@ func main(){
}
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("kill -9 `cat pid`\nnohup ./")
binFile := strings.TrimSuffix(p.Path, "/")
_, binFile = path.Split(binFile)
build.WriteString(binFile)
build.WriteString(" > log.txt & 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 p.writeExecSSE(sse, exec.Command("sh", "restart.sh"))
}
func Home() (string, error) {
user, err := user.Current()

Binary file not shown.

View File

@@ -9,10 +9,12 @@ import (
)
var ConfigRaw []byte
var Version = "0.1.2"
var Version = "0.2.2"
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 +33,8 @@ func Run(configFile string) (err error) {
go config.Run()
}
}
} else {
log.Printf("decode config file error: %v", err)
}
return
}

View File

@@ -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,6 +9,9 @@ import (
"path"
"runtime"
"time"
. "github.com/langhuihui/monibuca/monica"
. "github.com/langhuihui/monibuca/monica/util"
)
var (
@@ -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,9 @@ func summary(w http.ResponseWriter, r *http.Request) {
}
}
}
func sysInfo(w http.ResponseWriter, r *http.Request) {
bytes, err := json.Marshal(&struct{ Version string }{Version: Version})
if err == nil {
_, err = w.Write(bytes)
}
}

535
pm/dist/ajax.js vendored Normal file
View 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
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>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.c617bcf5.js rel=preload as=script><link href=/js/chunk-vendors.6b87e1b5.js rel=preload as=script><link href=/css/chunk-vendors.22ebf426.css rel=stylesheet><link href=/css/app.200d2f8f.css rel=stylesheet></head><body><noscript><strong>We're sorry but pm doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id=app></div><script src=/js/chunk-vendors.6b87e1b5.js></script><script src=/js/app.c617bcf5.js></script></body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

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

File diff suppressed because one or more lines are too long

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

File diff suppressed because one or more lines are too long

535
pm/public/ajax.js Normal file
View 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;
})();

View File

@@ -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>

View File

@@ -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(
"/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>

View File

@@ -7,6 +7,14 @@
<List border>
<ListItem v-for="item in instances" :key="item.Name">
<ListItemMeta :title="item.Name" :description="item.Path"></ListItemMeta>
<template slot="action">
<li @click="restart(item)">
<Icon type="ios-refresh" />重启
</li>
<li @click="upgrade(item)">
<Icon type="ios-sync" />升级引擎
</li>
</template>
</ListItem>
</List>
</TabPane>
@@ -26,15 +34,17 @@
{{item.Config}}
<template slot="action">
<li @click="removePlugin(name)">
<Icon type="ios-trash"/>
移除
<Icon type="ios-trash" />移除
</li>
</template>
</ListItem>
</List>
<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,23 +59,37 @@
</div>
</div>
<ButtonGroup style="display:table;margin:50px auto;">
<Button size="large" type="primary" @click="createStep--" v-if="createStep!=0">
<Icon type="ios-arrow-back"></Icon>
上一步
<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>
</ButtonGroup>
</div>
</TabPane>
<TabPane label="导入" name="name3">
</TabPane>
<TabPane label="导入" name="name3"></TabPane>
</Tabs>
</Content>
<Modal v-model="showAddPlugin" title="添加Plugin" @on-ok="addPlugin">
@@ -78,7 +102,10 @@
<Button slot="append" @click="showBuiltinPlugin=true">内置插件</Button>
</i-input>
</FormItem>
<Alert type="show-icon" v-if="!Object.values(builtinPlugins).includes(formPlugin.Path)">
<Alert
type="show-icon"
v-if="!Object.values(builtinPlugins).includes(formPlugin.Path)"
>
如果该插件是私有仓库请到服务器上输入echo "machine {{privateHost}} login 用户名 password 密码" >> ~/.netrc
并且添加环境变量GOPRIVATE={{privateHost}}
</Alert>
@@ -93,8 +120,7 @@
<ListItemMeta :title="name" :description="item"></ListItemMeta>
<template slot="action">
<li @click="addBuiltin(name,item)">
<Icon type="ios-add"/>
添加
<Icon type="ios-add" />添加
</li>
</template>
</ListItem>
@@ -105,102 +131,164 @@
</template>
<script>
import CreateInstance from "../components/CreateInstance"
import CreateInstance from "../components/CreateInstance";
export default {
components: {
CreateInstance
export default {
components: {
CreateInstance
},
data() {
return {
instanceName: "",
createStep: 0,
showCreate: false,
createInfo: null,
createPath: "/opt/monibuca",
instances: {},
plugins: {},
showAddPlugin: false,
formPlugin: {},
showBuiltinPlugin: false,
builtinPlugins: {
Auth: "github.com/langhuihui/monibuca/plugins/auth",
Cluster: "github.com/langhuihui/monibuca/plugins/cluster",
GateWay: "github.com/langhuihui/monibuca/plugins/gateway",
HDL: "github.com/langhuihui/monibuca/plugins/HDL",
Jessica: "github.com/langhuihui/monibuca/plugins/jessica",
QoS: "github.com/langhuihui/monibuca/plugins/QoS",
RecordFlv: "github.com/langhuihui/monibuca/plugins/record",
RTMP: "github.com/langhuihui/monibuca/plugins/rtmp"
},
defaultConfig: {
Auth: 'Key = "www.monibuca.com"',
RecordFlv: 'Path="./resource"',
QoS: 'Suffix = ["high","medium","low"]',
Cluster: 'Master = "localhost:2019"\nListenAddr = ":2019"',
GateWay: 'ListenAddr = ":8081"',
RTMP: 'ListenAddr = ":1935"',
Jessica: 'ListenAddr = ":8080"',
HDL: 'ListenAddr = ":2020"'
}
};
},
computed: {
pluginStr() {
return Object.values(this.plugins)
.map(x => x.Path)
.join("\n");
},
data() {
return {
instanceName: "",
createStep: 0,
showCreate: false,
createInfo: null,
createPath: "/opt/monibuca",
instances: [],
plugins: {},
showAddPlugin: false,
formPlugin: {},
showBuiltinPlugin: false,
builtinPlugins: {
Auth: "github.com/langhuihui/monibuca/plugins/auth",
Cluster: "github.com/langhuihui/monibuca/plugins/cluster",
GateWay: "github.com/langhuihui/monibuca/plugins/gateway",
HDL: "github.com/langhuihui/monibuca/plugins/HDL",
Jessica: "github.com/langhuihui/monibuca/plugins/jessica",
QoS: "github.com/langhuihui/monibuca/plugins/QoS",
RecordFlv: "github.com/langhuihui/monibuca/plugins/record",
RTMP: "github.com/langhuihui/monibuca/plugins/rtmp"
},
defaultConfig: {
Auth: 'Key = "www.monibuca.com"',
RecordFlv: 'Path="./resource"',
QoS: 'Suffix = ["high","medium","low"]',
Cluster: 'Master = "localhost:2019"\nListenAddr = ":2019"',
GateWay: 'ListenAddr = ":8081"',
RTMP: 'ListenAddr = ":1935"',
Jessica: 'ListenAddr = ":8080"',
HDL: 'ListenAddr = ":2020"',
configStr() {
return Object.values(this.plugins)
.map(
x => `[Plugins.${x.Name}]
${x.Config || ""}`
)
.join("\n");
},
privateHost() {
return (
(this.formPlugin.Path && this.formPlugin.Path.split("/")[0]) ||
"仓库域名"
);
}
},
mounted() {
window.ajax.getJSON("/list").then(x => {
this.instances = x;
});
},
methods: {
goUp() {
let paths = this.createPath.split("/");
paths.pop();
this.createPath = paths.join("/");
},
createInstance() {
this.showCreate = true;
this.createInfo = {
Name: this.instanceName || this.createPath.split("/").pop(),
Path: this.createPath,
Plugins: Object.values(this.plugins).map(x => x.Path),
Config: this.configStr
};
},
addPlugin() {
this.plugins[this.formPlugin.Name] = this.formPlugin;
this.formPlugin = {};
},
removePlugin(name) {
delete this.plugins[name];
this.$forceUpdate();
},
addBuiltin(name, item) {
this.formPlugin.Name = name;
this.formPlugin.Path = item;
this.formPlugin.Config = this.defaultConfig[name];
this.showBuiltinPlugin = false;
},
restart(item){
const msg = this.$Message.loading({
content: 'restart '+item.Name+'...',
duration: 0
});
var es = new EventSource("/restart/instance?instance="+item.Name)
es.onmessage = evt => {
if(evt.data=="success"){
this.$Message.success("重启成功!")
msg()
}else{
this.$Message.info(evt.data)
}
}
},
computed: {
pluginStr() {
return Object.values(this.plugins).map(x => x.Path).join("\n")
},
configStr() {
return Object.values(this.plugins).map(x => `[Plugins.${x.Name}]
${x.Config || ""}`).join("\n")
},
privateHost(){
return (this.formPlugin.Path && this.formPlugin.Path.split("/")[0])||"仓库域名"
es.addEventListener("failed",evt=>{
this.$Message.error(evt.data)
msg()
})
es.onerror = e => {
this.$Message.error(e);
msg()
es.close()
}
},
methods: {
goUp() {
let paths = this.createPath.split("/")
paths.pop()
this.createPath = paths.join("/")
},
createInstance() {
this.showCreate = true
this.createInfo = {
Name: this.instanceName || this.createPath.split('/').pop(),
Path: this.createPath,
Plugins: Object.values(this.plugins).map(x => x.Path),
Config: this.configStr
upgrade(item){
const msg = this.$Message.loading({
content: 'upgrade '+item.Name+'...',
duration: 0
});
var es = new EventSource("/upgrade/engine?instance="+item.Name)
es.onmessage = evt => {
if(evt.data=="success"){
this.$Message.success("更新完毕")
msg()
}else{
this.$Message.info(evt.data)
}
},
addPlugin() {
this.plugins[this.formPlugin.Name] = this.formPlugin
this.formPlugin = {}
},
removePlugin(name) {
delete this.plugins[name]
this.$forceUpdate()
},
addBuiltin(name, item) {
this.formPlugin.Name = name
this.formPlugin.Path = item
this.formPlugin.Config = this.defaultConfig[name]
this.showBuiltinPlugin = false
}
es.addEventListener("failed",evt=>{
this.$Message.error(evt.data)
msg()
})
es.onerror = e => {
this.$Message.error(e);
msg()
es.close()
}
}
}
};
</script>
<style>
.content {
background: white
}
.content {
background: white;
}
pre {
white-space: pre-wrap;
word-wrap: break-word;
}
pre {
white-space: pre-wrap;
word-wrap: break-word;
}
.ivu-tabs .ivu-tabs-tabpane {
padding: 20px;
}
.ivu-tabs .ivu-tabs-tabpane {
padding: 20px;
}
</style>