小功能增加

This commit is contained in:
langhuihui
2020-02-11 17:16:33 +08:00
parent 649a5b558a
commit fea6e98ca7
15 changed files with 1279 additions and 148 deletions

21
main.go
View File

@@ -6,9 +6,6 @@ import (
"errors" "errors"
"flag" "flag"
"fmt" "fmt"
"github.com/BurntSushi/toml"
. "github.com/langhuihui/monibuca/monica"
"github.com/langhuihui/monibuca/monica/util"
"io/ioutil" "io/ioutil"
"log" "log"
"mime" "mime"
@@ -19,6 +16,10 @@ import (
"path" "path"
"runtime" "runtime"
"strings" "strings"
"github.com/BurntSushi/toml"
. "github.com/langhuihui/monibuca/monica"
"github.com/langhuihui/monibuca/monica/util"
) )
type InstanceDesc struct { type InstanceDesc struct {
@@ -110,6 +111,7 @@ func initInstance(w http.ResponseWriter, r *http.Request) {
instanceDesc := new(InstanceDesc) instanceDesc := new(InstanceDesc)
sse := util.NewSSE(w, r.Context()) sse := util.NewSSE(w, r.Context())
err := json.Unmarshal([]byte(r.URL.Query().Get("info")), instanceDesc) err := json.Unmarshal([]byte(r.URL.Query().Get("info")), instanceDesc)
clearDir := r.URL.Query().Get("clear") != ""
defer func() { defer func() {
if err != nil { if err != nil {
sse.WriteEvent("exception", []byte(err.Error())) sse.WriteEvent("exception", []byte(err.Error()))
@@ -121,7 +123,7 @@ func initInstance(w http.ResponseWriter, r *http.Request) {
return return
} }
sse.WriteEvent("step", []byte("1:参数解析成功!")) sse.WriteEvent("step", []byte("1:参数解析成功!"))
err = instanceDesc.createDir(sse) err = instanceDesc.createDir(sse, clearDir)
if err != nil { if err != nil {
return return
} }
@@ -164,7 +166,10 @@ func (p *InstanceDesc) writeExecSSE(sse *util.SSE, cmd *exec.Cmd) error {
cmd.Dir = p.Path cmd.Dir = p.Path
return sse.WriteExec(cmd) 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) err = os.MkdirAll(p.Path, 0666)
if err != nil { if err != nil {
return return
@@ -206,8 +211,10 @@ func main(){
} }
sse.WriteEvent("step", []byte("5:go build 成功!")) sse.WriteEvent("step", []byte("5:go build 成功!"))
build.Reset() build.Reset()
build.WriteString("kill -9 `cat pid`\nnohup .") build.WriteString("kill -9 `cat pid`\nnohup ./")
build.WriteString(path.Dir(path.Join(p.Path, "main.go"))) binFile := strings.TrimSuffix(p.Path, "/")
_, binFile = path.Split(binFile)
build.WriteString(binFile)
build.WriteString(" > log.txt & echo $! > pid\n") build.WriteString(" > log.txt & echo $! > pid\n")
err = ioutil.WriteFile(path.Join(p.Path, "restart.sh"), build.Bytes(), 0777) err = ioutil.WriteFile(path.Join(p.Path, "restart.sh"), build.Bytes(), 0777)
if err != nil { if err != nil {

Binary file not shown.

View File

@@ -9,10 +9,12 @@ import (
) )
var ConfigRaw []byte var ConfigRaw []byte
var Version = "0.1.2" var Version = "0.2.1"
func Run(configFile string) (err error) { func Run(configFile string) (err error) {
log.Printf("start monibuca version:%s", Version)
if ConfigRaw, err = ioutil.ReadFile(configFile); err != nil { if ConfigRaw, err = ioutil.ReadFile(configFile); err != nil {
log.Printf("read config file error: %v", err)
return return
} }
go Summary.StartSummary() go Summary.StartSummary()
@@ -31,6 +33,8 @@ func Run(configFile string) (err error) {
go config.Run() go config.Run()
} }
} }
} else {
log.Printf("decode config file error: %v", err)
} }
return return
} }

View File

@@ -1,8 +1,7 @@
package gateway package gateway
import ( import (
. "github.com/langhuihui/monibuca/monica" "encoding/json"
. "github.com/langhuihui/monibuca/monica/util"
"io/ioutil" "io/ioutil"
"log" "log"
"mime" "mime"
@@ -10,6 +9,9 @@ import (
"path" "path"
"runtime" "runtime"
"time" "time"
. "github.com/langhuihui/monibuca/monica"
. "github.com/langhuihui/monibuca/monica/util"
) )
var ( var (
@@ -30,6 +32,7 @@ func init() {
}) })
} }
func run() { func run() {
http.HandleFunc("/api/sysInfo", sysInfo)
http.HandleFunc("/api/stop", stopPublish) http.HandleFunc("/api/stop", stopPublish)
http.HandleFunc("/api/summary", summary) http.HandleFunc("/api/summary", summary)
http.HandleFunc("/api/logs", watchLogs) 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.fd72a180.js rel=preload as=script><link href=/js/chunk-vendors.6b87e1b5.js rel=preload as=script><link href=/css/chunk-vendors.22ebf426.css rel=stylesheet><link href=/css/app.200d2f8f.css rel=stylesheet></head><body><noscript><strong>We're sorry but pm doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id=app></div><script src=/js/chunk-vendors.6b87e1b5.js></script><script src=/js/app.fd72a180.js></script></body></html>

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.fd72a180.js vendored Normal file

File diff suppressed because one or more lines are too long

1
pm/dist/js/app.fd72a180.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 http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0"> <meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico"> <link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title>pm</title> <title>Monibuca Instance Manager</title>
<script src="ajax.js"></script>
</head> </head>
<body> <body>
<noscript> <noscript>

View File

@@ -12,46 +12,54 @@
<div> <div>
<pre>{{log}}</pre> <pre>{{log}}</pre>
</div> </div>
<div slot="footer">
<Checkbox v-model="clearDir">安装前清空目录</Checkbox>
<Button type="primary" @click="start">开始</Button>
<Button type="success" @click="close" v-if="status=='finish'">完成</Button>
</div>
</Modal> </Modal>
</template> </template>
<script> <script>
let eventSource = null let eventSource = null;
export default { export default {
name: "CreateInstance", name: "CreateInstance",
props: { props: {
info: Object, info: Object
}, },
watch: { methods:{
info(v) { start(){
if (v) { eventSource = new EventSource(
eventSource = new EventSource("/create?info="+JSON.stringify(v)) "/create?info=" + JSON.stringify(this.info)+(this.clearDir?"&clear=true":"")
eventSource.onmessage = evt => { );
this.log += evt.data + "\n" eventSource.onopen = () => (this.log = "");
if (evt.data == "success") { eventSource.onmessage = evt => {
this.status = "finish" this.log += evt.data + "\n";
eventSource.close() if (evt.data == "success") {
} this.status = "finish";
eventSource.close();
} }
eventSource.addEventListener("exception", evt => { };
this.log += evt.data + "\n" eventSource.addEventListener("exception", evt => {
this.status = "error" this.log += evt.data + "\n";
eventSource.close() this.status = "error";
}) eventSource.close();
eventSource.addEventListener("step", evt => { });
let [step,msg] = evt.data.split(":") eventSource.addEventListener("step", evt => {
this.currentStep = step|0 let [step, msg] = evt.data.split(":");
this.log+=msg+"\n" this.currentStep = step | 0;
}) this.log += msg + "\n";
} });
} },close(){
}, this.$Modal.remove()
data() {
return {currentStep: 0, log: "", status: "process"}
} }
},
data() {
return { clearDir: true, currentStep: 0, log: "", status: "process" };
} }
};
</script> </script>
<style scoped> <style scoped>
</style> </style>

View File

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