reconstruct web interface with react.js

This commit is contained in:
OttoMao
2015-05-19 21:19:43 +08:00
parent 157e478abb
commit d765060fff
41 changed files with 1886 additions and 17045 deletions

View File

@@ -0,0 +1,93 @@
/*
web socket util for AnyProxy
https://github.com/alibaba/anyproxy
*/
/*
{
baseUrl : ""
}
config
config.baseUrl
config.port
config.onOpen
config.onClose
config.onError
config.onGetData
config.onGetUpdate
config.onGetBody
config.onError
*/
function anyproxy_wsUtil(config){
config = config || {};
if(!WebSocket){
throw (new Error("webSocket is not available on this browser"));
}
var self = this;
var baseUrl = config.baseUrl || "127.0.0.1",
socketPort = config.port || 8003;
var dataSocket = new WebSocket("ws://" + baseUrl + ":" + socketPort);
self.bodyCbMap = {};
dataSocket.onmessage = function(event){
config.onGetData && config.onGetData.call(self,event.data);
try{
var data = JSON.parse(event.data),
type = data.type,
content = data.content,
reqRef = data.reqRef;
}catch(e){
config.onError && config.onError.call(self, new Error("failed to parse socket data - " + e.toString()) );
}
if(type == "update"){
config.onGetUpdate && config.onGetUpdate.call(self, content);
}else if(type == "body"){
config.onGetBody && config.onGetBody.call(self, content, reqRef);
if(data.reqRef && self.bodyCbMap[reqRef]){
self.bodyCbMap[reqRef].call(self,content);
}
}
}
dataSocket.onopen = function(e){
config.onOpen && config.onOpen.call(self,e);
}
dataSocket.onclose = function(e){
config.onClose && config.onClose.call(self,e);
}
dataSocket.onerror = function(e){
config.onError && config.onError.call(self,e);
}
self.dataSocket = dataSocket;
};
anyproxy_wsUtil.prototype.send = function(data){
if(typeof data == "object"){
data = JSON.stringify(data);
}
this.dataSocket.send(data);
};
anyproxy_wsUtil.prototype.reqBody = function(id,callback){
if(!id) return;
var payload = {
type : "reqBody",
id : id
};
if(callback){
var reqRef = "r_" + Math.random()*100 + "_" + (new Date().getTime());
payload.reqRef = reqRef;
this.bodyCbMap[reqRef] = callback;
}
this.send(payload);
};
module.exports = anyproxy_wsUtil;

27
web/build/detail.js Normal file
View File

@@ -0,0 +1,27 @@
define(function(require,exports,module){
var $ = require("$");
// var cbMap = {};
// function render(data,cb){
// var resultEl = $(_.template(tpl, data)),
// id = data._id;
// try{
// //if finished
// var reqRef = "r" + Math.random() + "_" + new Date().getTime();
// if(data.statusCode){
// //fetch body
// ws.reqBody(id,function(content){
// $(".J_responseBody", resultEl).html(he.encode(content.body));
// cb(resultEl);
// });
// }
// }catch(e){
// cb(resultEl);
// };
// }
module.exports = DetailPanel;
});

174
web/build/detailPanel.js Normal file
View File

@@ -0,0 +1,174 @@
function init(React){
function dragableBar(initX,cb){
var self = this,
dragging = true;
var ghostbar = $('<div class="ghostbar"></div>').css("left",initX).prependTo('body');
$(document).mousemove(function(e){
e.preventDefault();
ghostbar.css("left",e.pageX + "px");
});
$(document).mouseup(function(e){
if(!dragging) return;
dragging = false;
var deltaPageX = e.pageX - initX;
cb && cb.call(null,{
delta : deltaPageX,
finalX : e.pageX
});
ghostbar.remove();
$(document).unbind('mousemove');
});
}
var DetailPanel = React.createClass({displayName: "DetailPanel",
getInitialState : function(){
return {
show : false,
data : {},
body : {id : -1, content : null},
left : "35%"
};
},
componentDidMount:function(){
var self = this;
$(document).on("keyup",function(e){
if(e.keyCode == 27){ //ESC
self.setState({
show : false
});
}
});
},
setHide:function(){
this.setState({
show : false
});
},
setShow:function(ifShow){
this.setState({
show : true
});
},
loadBody:function(){
var self = this,
id = self.state.data.id;
if(!id) return;
ws.reqBody(id,function(content){
if(content.id == self.state.data.id){
self.setState({
body : content
});
}
});
},
dealDrag:function(){
var self = this,
leftVal = $(React.findDOMNode(this.refs.mainOverlay)).css("left");
dragableBar(leftVal, function(data){
if(data && data.finalX){
if(window.innerWidth - data.finalX < 200){
data.finalX = window.innerWidth - 200;
}
self.setState({
left : data.finalX + "px"
});
}
});
},
render : function(){
var reqHeaderSection = [],
resHeaderSection = [],
summarySection,
detailSection,
bodyContent;
if(this.state.data.reqHeader){
for(var key in this.state.data.reqHeader){
reqHeaderSection.push(React.createElement("li", {key: "reqHeader_" + key}, React.createElement("strong", null, key), " : ", this.state.data.reqHeader[key]))
}
}
summarySection = (
React.createElement("div", null,
React.createElement("section", {className: "req"},
React.createElement("h4", {className: "subTitle"}, "request"),
React.createElement("div", {className: "detail"},
React.createElement("ul", {className: "uk-list"},
React.createElement("li", null, this.state.data.method, " ", React.createElement("span", {title: "{this.state.data.path}"}, this.state.data.path), " HTTP/1.1"),
reqHeaderSection
)
)
),
React.createElement("section", {className: "reqBody"},
React.createElement("h4", {className: "subTitle"}, "request body"),
React.createElement("div", {className: "detail"},
React.createElement("p", null, this.state.data.reqBody)
)
)
)
);
if(this.state.data.statusCode){
if(this.state.body.id == this.state.data.id){
bodyContent = (React.createElement("pre", {className: "resBodyContent"}, this.state.body.body));
}else{
bodyContent = null;
this.loadBody();
}
if(this.state.data.resHeader){
for(var key in this.state.data.resHeader){
resHeaderSection.push(React.createElement("li", {key: "resHeader_" + key}, React.createElement("strong", null, key), " : ", this.state.data.resHeader[key]))
}
}
detailSection = (
React.createElement("div", null,
React.createElement("section", {className: "resHeader"},
React.createElement("h4", {className: "subTitle"}, "response header"),
React.createElement("div", {className: "detail"},
React.createElement("ul", {className: "uk-list"},
React.createElement("li", null, "HTTP/1.1 ", React.createElement("span", {className: "http_status http_status_" + this.state.data.statusCode}, this.state.data.statusCode)),
resHeaderSection
)
)
),
React.createElement("section", {className: "resBody"},
React.createElement("h4", {className: "subTitle"}, "response body"),
React.createElement("div", {className: "detail"}, bodyContent)
)
)
);
}
return (
React.createElement("div", {style: {display:this.state.show ? "block" :"none"}},
React.createElement("div", {className: "overlay_mask", onClick: this.setHide}),
React.createElement("div", {className: "recordDetailOverlay", ref: "mainOverlay", style: {left: this.state.left}},
React.createElement("div", {className: "dragbar", onMouseDown: this.dealDrag}),
React.createElement("span", {className: "escBtn", onClick: this.setHide}, "Close (ESC)"),
React.createElement("div", null,
summarySection,
detailSection
)
)
)
);
}
});
return DetailPanel;
}
module.exports.init = init;

43
web/build/event.js Normal file
View File

@@ -0,0 +1,43 @@
//Ref : http://jsfiddle.net/JxYca/3/
var EventManager = function() {
this.initialize();
};
EventManager.prototype = {
initialize: function() {
//declare listeners as an object
this.listeners = {};
},
// public methods
addListener: function(event, fn) {
if (!this.listeners[event]) {
this.listeners[event] = [];
}
if (fn instanceof Function) {
this.listeners[event].push(fn);
}
return this;
},
dispatchEvent: function(event, params) {
// loop through listeners array
for (var index = 0, l = this.listeners[event].length; index < l; index++) {
// execute matching 'event' - loop through all indices and
// when ev is found, execute
this.listeners[event][index].call(window, params);
}
},
removeListener: function(event, fn) {
// split array 1 item after our listener
// shorten to remove it
// join the two arrays back together
if (this.listeners[event]) {
for (var i = 0, l = this.listners[event].length; i < l; i++) {
if (this.listners[event][i] === fn) {
this.listners[event].slice(i, 1);
break;
}
}
}
}
};
module.exports = EventManager;

141
web/build/index.js Normal file
View File

@@ -0,0 +1,141 @@
require("../lib/zepto");
var EventManager = require('../lib/event'),
Anyproxy_wsUtil = require("../lib/anyproxy_wsUtil"),
React = require("../lib/react");
var WsIndicator = require("./wsIndicator").init(React),
RecordPanel = require("./recordPanel").init(React);
var ifPause = false,
recordSet = [];
//Event : wsGetUpdate
//Event : recordSetUpdated
//Event : wsOpen
//Event : wsEnd
var eventCenter = new EventManager();
//invoke AnyProxy web socket util
(function(){
try{
var ws = window.ws = new Anyproxy_wsUtil({
baseUrl : document.getElementById("baseUrl").value,
port : document.getElementById("socketPort").value,
onOpen : function(){
eventCenter.dispatchEvent("wsOpen");
},
onGetUpdate : function(content){
eventCenter.dispatchEvent("wsGetUpdate",content);
},
onError : function(e){
eventCenter.dispatchEvent("wsEnd");
},
onClose : function(e){
eventCenter.dispatchEvent("wsEnd");
}
});
window.ws = ws;
}catch(e){
alert("failed to invoking web socket on this browser");
}
})();
//websocket status indicator
(function(){
var wsIndicator = React.render(
React.createElement(WsIndicator, null),
document.getElementById("J_indicatorEl")
);
eventCenter.addListener("wsOpen",function(){
wsIndicator.setState({
isValid : true
});
});
eventCenter.addListener("wsEnd",function(){
wsIndicator.setState({
isValid : false
});
});
})();
//record panel
(function(){
//merge : right --> left
function util_merge(left,right){
for(var key in right){
left[key] = right[key];
}
return left;
}
function updateRecordSet(newRecord){
if(ifPause) return;
if(newRecord && newRecord.id){
if(!recordSet[newRecord.id]){
recordSet[newRecord.id] = newRecord;
}else{
util_merge(recordSet[newRecord.id],newRecord);
}
recordSet[newRecord.id]._justUpdated = true;
// React.addons.Perf.start();
eventCenter.dispatchEvent("recordSetUpdated");
// React.addons.Perf.stop();
}
}
eventCenter.addListener("wsGetUpdate",updateRecordSet);
var Panel = React.render(
React.createElement(RecordPanel, null),
document.getElementById("J_content")
);
eventCenter.addListener('recordSetUpdated',function(){
Panel.setState({
list : recordSet
});
});
})();
//action bar
(function(){
function clearLogs(){
recordSet = [];
eventCenter.dispatchEvent("recordSetUpdated");
}
$(document).on("keyup",function(e){
if(e.keyCode == 88 && e.ctrlKey){ // ctrl + x
clearLogs();
}
});
var clearLogBtn = $(".J_clearBtn");
clearLogBtn.on("click",function(e){
e.stopPropagation();
e.preventDefault();
clearLogs();
});
var statusBtn = $(".J_statusBtn");
statusBtn.on("click",function(e){
e.stopPropagation();
e.preventDefault();
$(".J_statusBtn").removeClass("btn_disable");
$(this).addClass("btn_disable");
if(/stop/i.test($(this).html()) ){
ifPause = true;
}else{
ifPause = false;
}
});
})();

21598
web/build/react.js vendored Normal file

File diff suppressed because it is too large Load Diff

51
web/build/recordPanel.js Normal file
View File

@@ -0,0 +1,51 @@
function init(React){
var RecordRow = require("./recordRow").init(React);
var RecordPanel = React.createClass({displayName: "RecordPanel",
getInitialState : function(){
return {
list : []
};
},
render : function(){
var rowCollection = [];
for(var i = this.state.list.length-1 ; i >=0 ; i--){
var item = this.state.list[i];
if(item){
if(item._justUpdated){
item._justUpdated = false;
item._needRender = true;
}else{
item._needRender = false;
}
rowCollection.push(React.createElement(RecordRow, {key: item.id, data: item}));
}
}
return (
React.createElement("table", {className: "uk-table uk-table-condensed uk-table-hover"},
React.createElement("thead", null,
React.createElement("tr", null,
React.createElement("th", {className: "col_id"}, "id"),
React.createElement("th", {className: "col_method"}, "method"),
React.createElement("th", {className: "col_code"}, "code"),
React.createElement("th", {className: "col_host"}, "host"),
React.createElement("th", {className: "col_path"}, "path"),
React.createElement("th", {className: "col_mime"}, "mime type"),
React.createElement("th", {className: "col_time"}, "time")
)
),
React.createElement("tbody", null,
rowCollection
)
)
);
}
});
return RecordPanel;
}
module.exports.init = init;

70
web/build/recordRow.js Normal file
View File

@@ -0,0 +1,70 @@
function init(React){
var DetailPanel = require("./detailPanel").init(React);
$("body").append('<div id="J_detailPanel"></div>');
var detail = React.render(
React.createElement(DetailPanel, null),
document.getElementById("J_detailPanel")
);
function dateFormat(date,fmt) {
var o = {
"M+": date.getMonth() + 1, //月份
"d+": date.getDate(), //日
"h+": date.getHours(), //小时
"m+": date.getMinutes(), //分
"s+": date.getSeconds(), //秒
"q+": Math.floor((date.getMonth() + 3) / 3), //季度
"S" : date.getMilliseconds() //毫秒
};
if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (date.getFullYear() + "").substr(4 - RegExp.$1.length));
for (var k in o) if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
return fmt;
}
var RecordRow = React.createClass({displayName: "RecordRow",
getInitialState : function(){
return null;
},
handleClick:function(e){
detail.setState({
data : this.props.data,
show : true
});
},
render : function(){
var trClassesArr = [],
trClasses;
if(this.props.data.statusCode){
trClassesArr.push("record_status_done");
}
trClassesArr.push( ((Math.floor(this.props.data._id /2) - this.props.data._id /2) == 0)? "row_even" : "row_odd" );
trClasses = trClassesArr.join(" ");
var dateStr = dateFormat(new Date(this.props.data.startTime),"hh:mm:ss");
return(
React.createElement("tr", {className: trClasses, onClick: this.handleClick},
React.createElement("td", {className: "data_id"}, this.props.data._id),
React.createElement("td", null, this.props.data.method, " ", React.createElement("span", {className: "protocol protocol_" + this.props.data.protocol, title: "https"}, React.createElement("i", {className: "iconfont"}, "É")), " "),
React.createElement("td", {className: "http_status http_status_" + this.props.data.statusCode}, this.props.data.statusCode),
React.createElement("td", {title: this.props.data.host}, this.props.data.host),
React.createElement("td", {title: this.props.data.path}, this.props.data.path),
React.createElement("td", null, this.props.data.mime),
React.createElement("td", null, dateStr)
)
);
},
shouldComponentUpdate:function(nextPros){
return nextPros.data._needRender;
},
componentDidUpdate:function(){},
componentWillUnmount:function(){}
});
return RecordRow;
}
module.exports.init = init;

18
web/build/wsIndicator.js Normal file
View File

@@ -0,0 +1,18 @@
function init(React){
var WsIndicator = React.createClass({displayName: "WsIndicator",
getInitialState:function(){
return {
isValid: false
}
},
render:function(){
return (
React.createElement("img", {className: "logo_bottom anim_rotation", src: "https://t.alipayobjects.com/images/rmsweb/T1P_dfXa8oXXXXXXXX.png", width: "50", height: "50", style: {display: this.state.isValid ?"block" : "none"}})
);
}
});
return WsIndicator;
}
module.exports.init = init;