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

1
.gitignore vendored
View File

@ -2,6 +2,7 @@ cert/**/*.srl
cert/**/*.key
cert/**/*.crt
cert/**/*.csr
web/build/.module-cache/
tmp.txt
.*.swp
._*

View File

@ -3,7 +3,7 @@ var isRootCAFileExists = require("./certMgr.js").isRootCAFileExists(),
module.exports = {
summary:function(){
var tip = "the default rule for anyproxy, support : CORS. ";
var tip = "the default rule for anyproxy which supports CORS. ";
if(!isRootCAFileExists){
tip += "\nRoot CA does not exist, will not intercept any https requests.";
}

View File

@ -7,7 +7,9 @@ var express = require("express"),
qrCode = require('qrcode-npm'),
util = require("./util"),
certMgr = require("./certMgr"),
logUtil = require("./log");
logUtil = require("./log"),
compress = require('compression');
function webInterface(config){
var port = config.port,
@ -21,6 +23,7 @@ function webInterface(config){
staticDir = path.join(__dirname,'../web');
var app = express();
app.use(compress()); //invoke gzip
app.use(function(req, res, next) {
res.setHeader("note", "THIS IS A REQUEST FROM ANYPROXY WEB INTERFACE");
return next();

View File

@ -11,15 +11,16 @@
"async-task-mgr": ">=1.1.0",
"colorful": "^2.1.0",
"commander": "~2.3.0",
"compression": "^1.4.4",
"express": "^4.8.5",
"iconv-lite": "^0.4.6",
"ip": "^0.3.2",
"juicer": "^0.6.6-stable",
"nedb": "^0.11.0",
"npm": "^2.7.0",
"qrcode-npm": "0.0.3",
"stream-throttle": "^0.1.3",
"ws": "^0.4.32",
"npm": "^2.7.0"
"ws": "^0.4.32"
},
"devDependencies": {
"proxy-eval": ">=1.1.1"

View File

@ -156,7 +156,7 @@ function proxyServer(option){
var config = {
port : proxyWebPort,
wsPort : socketPort,
ruleSummaery : requestHandler.getRuleSummary(),
ruleSummary : requestHandler.getRuleSummary(),
ip : ip.address()
};

1
test/ab_local.sh Executable file
View File

@ -0,0 +1 @@
ab -X 127.0.0.1:8001 -n $1 -c $2 -H "HOST:127.0.0.1" http://127.0.0.1:8080/

1
test/ab_taobao.sh Executable file
View File

@ -0,0 +1 @@
ab -X 127.0.0.1:8001 -n $1 -c $2 -H "HOST:www.taobao.com" http://www.taobao.com/

File diff suppressed because one or more lines are too long

2
web/build.sh Executable file
View File

@ -0,0 +1,2 @@
jsx /src /build
webpack --progress --colors

View File

@ -30,7 +30,7 @@ function anyproxy_wsUtil(config){
var dataSocket = new WebSocket("ws://" + baseUrl + ":" + socketPort);
// self.bodyCbMap = {};
self.bodyCbMap = {};
dataSocket.onmessage = function(event){
config.onGetData && config.onGetData.call(self,event.data);
@ -43,7 +43,6 @@ function anyproxy_wsUtil(config){
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);
@ -90,3 +89,5 @@ anyproxy_wsUtil.prototype.reqBody = function(id,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;
}
});
})();

View File

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;

View File

@ -11,6 +11,10 @@ body{
min-width: 1090px;
}
body, html {
height: 100%;
}
.iconfont{
font-family:"iconfont" !important;
font-size:16px;font-style:normal;
@ -89,7 +93,7 @@ body{
border-radius: 2px;
}
.topHead .topBtn:hover{
.topHead .topBtn:hover:not(.btn_disable){
background: #07D;
transition:0.1s;
color: #FFF;
@ -131,7 +135,7 @@ body{
}
.mainTableWrapper thead{
background: #DDD;
background: #F4F5F9;
border-bottom: 1px solid #777;
}
@ -215,14 +219,13 @@ body{
width: 100%;
height: 100%;
background: #EEE;
opacity: 0.85;
opacity: 0.5;
}
.recordDetailOverlay{
z-index: 1;
height: 100%;
position: fixed;
left: 35%;
right: 0;
background: #FFF;
border-left: 1px solid #CCC;
@ -281,21 +284,21 @@ body{
color: #777;
}
#dragbar{
.dragbar{
position:absolute;
left:0px;
left:-5px;
top:0px;
height: 100%;
float: left;
background-color:#CCC;
width: 3px;
width: 10px;
cursor: col-resize;
}
#ghostbar{
.ghostbar{
position:fixed;
width:3px;
height: 100vh;
background-color:#000;
opacity:0.5;
position:absolute;
cursor: col-resize;
z-index:999
}

View File

@ -1,61 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Anyproxy</title>
<link rel="stylesheet" href="/css/uikit.gradient.min.css" />
<link rel="stylesheet" href="/css/page.css" />
<link rel="icon" type="image/png" href="/favico.png" />
<script charset="utf-8" id="seajsnode"src="http://static.alipayobjects.com/seajs/??seajs/2.2.0/sea.js,seajs-combo/1.0.1/seajs-combo.js,seajs-style/1.0.2/seajs-style.js"></script>
</head>
<body>
<div id="J_detailPanel" class="detailPanel"></div>
<script type="text/javascript">
seajs.config({
base: 'http://static.alipayobjects.com/',
alias: {
'$' : 'jquery/jquery/1.7.2/jquery',
'Backbone' : 'gallery/backbone/1.1.2/backbone.js',
'Underscore': 'gallery/underscore/1.6.0/underscore.js'
}
});
seajs.use(['$', 'Underscore', 'Backbone',"./detail"], function($, _, Backbone,Detail) {
window.render = function(data){
Detail.render(data,function(tpl){
$("#J_detailPanel").html(tpl);
});
}
//init render
if(window.renderData){
window.render(window.renderData);
}
});
</script>
<style type="text/css">
body{
word-wrap: break-word;
}
.detailPanel{
padding: 40px 20px;
font-size: 22px;
line-height: 30px;
}
.subTitle{
border-left: 6px solid #1FA2D6;
font-size: 24px;
line-height: 36px;
font-weight: bold;
}
</style>
</body>
</html>

View File

@ -1,64 +0,0 @@
define("./detail",['$', 'gallery/underscore/1.6.0/underscore.js'],function(require,exports,module){
var _ = require("gallery/underscore/1.6.0/underscore.js"),
$ = require("$");
var tpl = ""+
' <section class="req">'+
' <h4 class="subTitle">request</h4>'+
' <div class="detail">'+
' <ul class="uk-list">'+
' <li><%= method %> <span title="<%= path %>"><%= path %></span> HTTP/1.1</li>'+
// ' <li></li>'+
' <% _.each(reqHeader, function(v,k) { %> <li><strong><%= k %></strong> : <%= v %></li><% }); %>'+
' </ul>'+
' </div>'+
' </section>'+
''+
' <section class="reqBody">'+
' <h4 class="subTitle">request body</h4>'+
' <div class="detail">'+
' <p><%= reqBody %></p>'+
' </div>'+
' </section>'+
''+
' <% if(statusCode) { %>'+
' <section class="resHeader">'+
' <h4 class="subTitle">response header</h4>'+
' <div class="detail">'+
' <ul class="uk-list">'+
' <li>HTTP/1.1 <span class="http_status http_status_<%= statusCode %>"><%= statusCode %></span></li>'+
' <% _.each(resHeader, function(v,k) { %> <li><strong><%= k %></strong> : <%= v %></li><% }); %>'+
' </ul>'+
' </div>'+
' </section>'+
''+
' <section class="resBody">'+
' <h4 class="subTitle">response body</h4>'+
' <div class="detail">'+
' <pre class="J_responseBody resBodyContent"></pre>'+
' </div>'+
' </section>'+
' <% } %>';
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);
};
}
exports.render = render;
});

329
web/he.js

File diff suppressed because one or more lines are too long

View File

@ -5,28 +5,24 @@
<link rel="stylesheet" href="/css/uikit.gradient.min.css" />
<link rel="stylesheet" href="/css/page.css" />
<link rel="icon" type="image/png" href="/favico.png" />
<script charset="utf-8" id="seajsnode" src="http://static.alipayobjects.com/seajs/??seajs/2.2.0/sea.js,seajs-combo/1.0.1/seajs-combo.js,seajs-style/1.0.2/seajs-style.js"></script>
<script src="./react.js"></script>
</head>
<body>
<div class="topHead">
<div class="logoWrapper">
<h1>Anyproxy</h1>
<img class="logo_bottom anim_rotation" id="J_indicator" src="./logo_bottom.png" width="50" height="50" style="visibility:hidden" />
<div id="J_indicatorEl"></div>
</div>
<div class="ctrlWrapper">
<a href="#" class="J_statusBtn"><span class="topBtn"><i class="uk-icon-stop"></i>Stop</span></a>
<a href="#" class="J_statusBtn btn_disable"><span class="topBtn"><i class="uk-icon-play"></i>Resume</span></a>
<a href="#"><span class="J_statusBtn topBtn"><i class="uk-icon-stop"></i>Stop</span></a>
<a href="#"><span class="J_statusBtn btn_disable topBtn"><i class="uk-icon-play"></i>Resume</span></a>
<a href="#" class="J_clearBtn"><span class="topBtn"><i class="uk-icon-eraser"></i>Clear(Ctrl+X)</span></a>
<span class="sep">|</span>
<a href="/fetchCrtFile" target="_blank"><span class="topBtn"><i class="uk-icon-certificate"></i>Download rootCA.crt</span></a>
<a href="/qr_root" class="J_fetchRootQR" target="_blank"><span class="topBtn"><i class="uk-icon-certificate"></i>QRCode of rootCA.crt</span></a>
<!-- <a href="#"><span class="topBtn"><i class="uk-icon-cog"></i>Others</span></a> -->
<span class="sep">|</span>
<a href="https://github.com/alibaba/anyproxy" target="_blank"><span class="topBtn"><i class="uk-icon-external-link-square"></i>Anyproxy(Github)</span></a>
</div>
<div class="ruleDesc">
@ -38,33 +34,9 @@
<div class="mainTableWrapper" id="J_content"></div>
<div class="recordDetailOverlay J_recordDetailOverlay" style="display:none">
<div id="dragbar"></div>
<span class="escBtn J_escBtn">Close (ESC)</span>
<div class="J_recordDetailOverlayContentWrapper">
<div class="J_recordDetailOverlayContent"></div>
</div>
</div>
<input type="hidden" id="socketPort" value="{{wsPort}}" />
<input type="hidden" id="baseUrl" value="{{ipAddress}}" />
<input type="hidden" id="customMenu" value="{{menu}}" />
<script id="main_table_row" type="text/template">
<td class="data_id"><%= _id %></td>
<td><%= method %> <span class="protocol protocol_<%= protocol %>" title="https"><i class="iconfont">&#xf00c9;</i></span> </td>
<td class="http_status http_status_<%= statusCode %>"><%= statusCode %></td>
<td title="<%= host %>"><%= host %></td>
<td title="<%= path %>"><%= path %></td>
<td><%= mime %></td>
<td><%= startTimeStr %></td>
</script>
<script src="http://lib.sinaapp.com/js/jquery/1.9.1/jquery-1.9.1.min.js"></script>
<script src="/anyproxy_wsUtil.js"></script>
<script src="/he.js"></script>
<script src="/list.js"></script>
<script src="/build/index.js"></script>
<script src="./page.js"></script>
</body>
</html>

View File

@ -0,0 +1,95 @@
/*
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);
};
if(typeof module != "undefined"){
module.exports = anyproxy_wsUtil;
}

43
web/lib/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;

16
web/lib/react.js vendored Normal file

File diff suppressed because one or more lines are too long

2
web/lib/zepto.js Normal file

File diff suppressed because one or more lines are too long

View File

@ -1,226 +0,0 @@
seajs.config({
base: 'http://static.alipayobjects.com/',
alias: {
'$' : 'jquery/jquery/1.7.2/jquery',
'Backbone' : 'gallery/backbone/1.1.2/backbone.js',
'Underscore': 'gallery/underscore/1.6.0/underscore.js',
"Handlebars": 'gallery/handlebars/1.0.2/handlebars.js',
"Popup" : 'arale/popup/1.1.6/popup'
}
});
seajs.use(['$', 'Underscore', 'Backbone',"Handlebars","Popup","./detail"], function($, _, Backbone,Handlebars,Popup,Detail) {
Backbone.$ = $;
var isInApp = window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.list;
//record detail
var DetailView = function(){
var self = this,
$detailEl = $(".J_recordDetailOverlay"),
$mask;
//init mask
$mask = $("<div></div>").addClass("overlay_mask").hide();
$("body").append($mask);
//bind events
$(document).on("keyup",function(e){
if(e.keyCode == 27){ //ESC
self.hideDetail();
}
});
$mask.on("click",function(e){
self.hideDetail();
});
$(".J_escBtn",$detailEl).on("click",function(e){
self.hideDetail();
});
self.showDetail = function(data){
Detail.render(data,function(tpl){
$(".J_recordDetailOverlayContent",$detailEl).html(tpl);
$detailEl.show();
$mask.show();
});
};
self.hideDetail = function(){
$detailEl.hide();
$mask.hide();
};
};
var detailPanel = new DetailView();
//record list
var RecordModel = Backbone.Model.extend({});
var RecordList = Backbone.Collection.extend({
initialize:function(){
var self = this;
self.on("add",function(data){
new RecordRowView({
model:data,
detailPanel : detailPanel
});
});
self.on("reset",function(){
$(".J_tableBody").empty();
});
}
});
var RecordRowView = Backbone.View.extend({
tagName : "tr",
className:function(){
return this.model.toJSON().id % 2 ? "row_odd" : "row_even";
},
tpl : $("#main_table_row").html(),
initialize:function(data){
var self = this;
self.model.on("change",self.render,self);
self.addNewRecord();
self.detailPanel = data.detailPanel;
},
events: {
click: function(e){
e.stopPropagation();
var self = this;
var detailData = self.model.toJSON();
if(!isInApp){
self.detailPanel.showDetail(detailData);
}else{
window.webkit.messageHandlers.list.postMessage(JSON.stringify(detailData));
}
}
},
render: function(){
var self = this,
data = self.model.toJSON();
if(!data.statusCode){
data.statusCode = "-";
}else{
self.$el.addClass("record_status_done")
}
if(!data.mime){
data.mime = "-";
}
var html = _.template(self.tpl, data);
self.$el.attr("recordId",data.id).empty().html(html);
return self;
},
addNewRecord:function(){
$(".J_tableBody").prepend(this.render().$el);
}
});
var recList = new RecordList();
//other controllers
$(".J_clearBtn").on("click",function(e){
e.stopPropagation();
e.preventDefault();
clearLogs();
});
$(document).on("keyup",function(e){
if(e.keyCode == 88 && e.ctrlKey){ // ctrl + x
clearLogs();
}
});
function clearLogs(){
recList.reset();
}
(function(){
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;
indicatorEl.fadeOut();
// indicatorEl.css("visibility","hidden");
}else{
ifPause = false;
indicatorEl.fadeIn();
// indicatorEl.css("visibility","visible");
}
});
})();
//draggable panel
(function(){
var i = 0;
var dragging = false,
pageX = 0;
$('#dragbar').mousedown(function(e){
pageX = e.pageX;
e.preventDefault();
dragging = true;
var main = $('.J_recordDetailOverlay');
var ghostbar = $('<div>',{
id:'ghostbar',
css: {
height: main.outerHeight(),
top: main.offset().top,
left: main.offset().left
}
}).appendTo('body');
$(document).mousemove(function(e){
ghostbar.css("left",e.pageX);
});
});
$(document).mouseup(function(e){
if(dragging){
var deltaPageX = e.pageX - pageX;
$('.J_recordDetailOverlay').css("left",pageX + deltaPageX);
if($('.J_recordDetailOverlay').width()<=100){
$('.J_recordDetailOverlay').animate({
'right': $('.J_recordDetailOverlay').width()
},300,function(){
$('.J_escBtn').trigger('click');
});
}
pageX = e.pageX;
$('#ghostbar').remove();
$(document).unbind('mousemove');
dragging = false;
}
});
})();
});
Date.prototype.format = function (fmt) {
var o = {
"M+": this.getMonth() + 1, //月份
"d+": this.getDate(), //日
"h+": this.getHours(), //小时
"m+": this.getMinutes(), //分
"s+": this.getSeconds(), //秒
"q+": Math.floor((this.getMonth() + 3) / 3), //季度
"S": this.getMilliseconds() //毫秒
};
if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (this.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;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

704
web/page.js Normal file

File diff suppressed because one or more lines are too long

174
web/src/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({
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(<li key={"reqHeader_" + key}><strong>{key}</strong> : {this.state.data.reqHeader[key]}</li>)
}
}
summarySection = (
<div>
<section className="req">
<h4 className="subTitle">request</h4>
<div className="detail">
<ul className="uk-list">
<li>{this.state.data.method} <span title="{this.state.data.path}">{this.state.data.path}</span> HTTP/1.1</li>
{reqHeaderSection}
</ul>
</div>
</section>
<section className="reqBody">
<h4 className="subTitle">request body</h4>
<div className="detail">
<p>{this.state.data.reqBody}</p>
</div>
</section>
</div>
);
if(this.state.data.statusCode){
if(this.state.body.id == this.state.data.id){
bodyContent = (<pre className="resBodyContent">{this.state.body.body}</pre>);
}else{
bodyContent = null;
this.loadBody();
}
if(this.state.data.resHeader){
for(var key in this.state.data.resHeader){
resHeaderSection.push(<li key={"resHeader_" + key}><strong>{key}</strong> : {this.state.data.resHeader[key]}</li>)
}
}
detailSection = (
<div>
<section className="resHeader">
<h4 className="subTitle">response header</h4>
<div className="detail">
<ul className="uk-list">
<li>HTTP/1.1 <span className={"http_status http_status_" + this.state.data.statusCode}>{this.state.data.statusCode}</span></li>
{resHeaderSection}
</ul>
</div>
</section>
<section className="resBody">
<h4 className="subTitle">response body</h4>
<div className="detail">{bodyContent}</div>
</section>
</div>
);
}
return (
<div style={{display:this.state.show ? "block" :"none"}}>
<div className="overlay_mask" onClick={this.setHide}></div>
<div className="recordDetailOverlay" ref="mainOverlay" style={{left: this.state.left}}>
<div className="dragbar" onMouseDown={this.dealDrag}></div>
<span className="escBtn" onClick={this.setHide}>Close (ESC)</span>
<div>
{summarySection}
{detailSection}
</div>
</div>
</div>
);
}
});
return DetailPanel;
}
module.exports.init = init;

141
web/src/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(
<WsIndicator />,
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(
<RecordPanel />,
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;
}
});
})();

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

@ -0,0 +1,51 @@
function init(React){
var RecordRow = require("./recordRow").init(React);
var RecordPanel = React.createClass({
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(<RecordRow key={item.id} data={item}></RecordRow>);
}
}
return (
<table className="uk-table uk-table-condensed uk-table-hover">
<thead>
<tr>
<th className="col_id">id</th>
<th className="col_method">method</th>
<th className="col_code">code</th>
<th className="col_host">host</th>
<th className="col_path">path</th>
<th className="col_mime">mime type</th>
<th className="col_time">time</th>
</tr>
</thead>
<tbody>
{rowCollection}
</tbody>
</table>
);
}
});
return RecordPanel;
}
module.exports.init = init;

70
web/src/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(
<DetailPanel />,
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({
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(
<tr className={trClasses} onClick={this.handleClick}>
<td className="data_id">{this.props.data._id}</td>
<td>{this.props.data.method} <span className={"protocol protocol_" + this.props.data.protocol} title="https"><i className="iconfont">&#xf00c9;</i></span> </td>
<td className={"http_status http_status_" + this.props.data.statusCode}>{this.props.data.statusCode}</td>
<td title={this.props.data.host}>{this.props.data.host}</td>
<td title={this.props.data.path}>{this.props.data.path}</td>
<td>{this.props.data.mime}</td>
<td>{dateStr}</td>
</tr>
);
},
shouldComponentUpdate:function(nextPros){
return nextPros.data._needRender;
},
componentDidUpdate:function(){},
componentWillUnmount:function(){}
});
return RecordRow;
}
module.exports.init = init;

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

@ -0,0 +1,18 @@
function init(React){
var WsIndicator = React.createClass({
getInitialState:function(){
return {
isValid: false
}
},
render:function(){
return (
<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;

2
web/watch.sh Executable file
View File

@ -0,0 +1,2 @@
jsx --watch src/ build/ &
webpack --progress --colors --watch

8
web/webpack.config.js Normal file
View File

@ -0,0 +1,8 @@
module.exports = {
entry: "./build/index.js",
output: {
path: __dirname,
filename: "page.js"
},
module: {}
};

View File

@ -20,7 +20,7 @@
<button type="button" id="J_sendReq">Send</button>
<div class="logPanel" id="J_logPanel"></div>
<script src="./anyproxy_wsUtil.js"></script>
<script src="./lib/anyproxy_wsUtil.js"></script>
<script type="text/javascript">
//print log

View File

@ -1,182 +0,0 @@
.topHead{
background: #000;
height: 42px;
position: relative;
}
.topHead h1{
color: rgb(204,204,204);
display: inline-block;
}
.topHead .topBtn{
margin: 0 5px;
}
.topHead .btn_disable{
color: #777;
}
.mainTableWrapper{
margin-top: 0;
}
.mainTableWrapper table{
table-layout: fixed;
}
.mainTableWrapper td,
.mainTableWrapper th{
padding: 4px 12px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.mainTableWrapper tbody tr{
color: #AAA;
}
.mainTableWrapper tbody tr.record_status_done{
color: #000;
}
.mainTableWrapper .col_id{
width: 25px;
padding-right: 20px;
}
.mainTableWrapper .col_code{
width: 40px;
}
.mainTableWrapper .col_method{
width: 70px;
}
.mainTableWrapper .col_host{
width: 180px;
}
.mainTableWrapper .col_mime{
width: 150px;
}
.mainTableWrapper .col_time{
width: 160px;
}
.mainTableWrapper tr.row_odd{
background: #f5f5f5;
}
.mainTableWrapper tr.row_even{
background: #FFFFFF;
}
.uk-table-hover tbody tr:hover{
cursor: pointer;
background: #CCC;
}
.resHeader{
width: 400px;
}
.resBody textarea{
width: 400px;
height: 280px;
}
.subTitle{
padding-left: 6px;
border-left: 3px solid #1FA2D6;
font-size: 16px;
line-height: 16px;
}
.detail{
padding-left: 12px;
}
.overlay_mask{
position: fixed;
top:0;
left: 0;
width: 100%;
height: 100%;
background: #EEE;
opacity: 0.85;
}
.recordDetailOverlay{
z-index: 1;
height: 100%;
position: fixed;
left: 35%;
right: 0;
background: #FFF;
border-left: 1px solid #CCC;
top: 0;
padding: 10px 10px 20px 10px;
overflow-y:scroll;
box-sizing: border-box;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
}
.recordDetailOverlay .escBtn{
position: absolute;
right: 10px;
top: 8px;
color: #777;
cursor: pointer;
text-decoration: underline;
}
.recordDetailOverlay li{
white-space: nowrap;
word-wrap: break-word;
}
.data_id{
color: #777;
}
.http_status{
font-weight: 700;
}
.http_status_200{
color: #408E2F;
}
.http_status_404,
.http_status_500,
.http_status_501,
.http_status_502,
.http_status_503,
.http_status_504
{
color: #910A0A;
}
#dragbar{
position:absolute;
left:0px;
top:0px;
height: 100%;
float: left;
background-color:#CCC;
width: 3px;
cursor: col-resize;
}
#ghostbar{
width:3px;
background-color:#000;
opacity:0.5;
position:absolute;
cursor: col-resize;
z-index:999
}

File diff suppressed because one or more lines are too long

View File

@ -1,208 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Anyproxy</title>
<link rel="stylesheet" href="/css/uikit.gradient.min.css" />
<link rel="stylesheet" href="/css/page.css" />
<link rel="icon" type="image/png" href="/favico.png" />
<script charset="utf-8" id="seajsnode"src="http://static.alipayobjects.com/seajs/??seajs/2.2.0/sea.js,seajs-combo/1.0.1/seajs-combo.js,seajs-style/1.0.2/seajs-style.js"></script>
</head>
<body>
<div class="topHead">
<h1>Anyproxy - Settings</h1>
</div>
<div>
<h3>Current Rules</h3>
<hr>
<div class="list sectionWrapper">
<ul class="uk-list uk-list-line uk-list-space J_listWrapper">
</ul>
</div>
<h3>Add new rule</h3>
<hr>
<div class="content sectionWrapper">
<form class="uk-form uk-form-stacked J_infoForm">
<div class="uk-form-row">
<label class="uk-form-label" for="form-s-it">Name</label>
<div class="uk-form-controls">
<input type="text" name="name" required placeholder="rule name">
</div>
</div>
<div class="uk-form-row">
<label class="uk-form-label" for="form-s-it">URL keywords</label>
<div class="uk-form-controls">
<input type="text" class="uk-form-width-large" name="urlKey" placeholder="api.sample.com/apiA">
</div>
</div>
<div class="uk-form-row">
<label class="uk-form-label" for="form-s-ip">Body keywords</label>
<div class="uk-form-controls">
<input type="text" class="uk-form-width-large" name="reqBodyKey" placeholder="some keywords in request body">
</div>
</div>
<div class="uk-form-row">
<label class="uk-form-label" for="form-s-t">using Response Body</label>
<div class="uk-form-controls">
<textarea cols="70" rows="8" name="localResponse" placeholder="replace response with data"></textarea>
</div>
</div>
<div class="uk-form-row">
<button class="uk-button J_addBtn" type="button">add</button>
</div>
</form>
</div>
</div>
<style type="text/css">
.removeBtn{
display: inline-block;
margin-left: 10px;
}
.sectionWrapper{
padding: 0px 20px 20px;
}
</style>
<script type="text/template" id="listItemTpl">
<li>
<strong>{{name}}</strong>&nbsp;&nbsp;&nbsp;<a href="#" class="J_remove removeBtn" ruleId="{{id}}">(remove)</a><br>
{{urlKey}} {{reqBodyKey}}
<br>{{localResponse}}
</li>
</script>
<script type="text/javascript">
seajs.config({
base: 'http://static.alipayobjects.com/',
alias: {
'$' : 'jquery/jquery/1.7.2/jquery',
'Backbone' : 'gallery/backbone/1.1.2/backbone.js',
'Underscore': 'gallery/underscore/1.6.0/underscore.js'
}
});
seajs.use(['$','Underscore' ,'Backbone'], function($, _ ,Backbone) {
function dataMgmt(){
var self = this,
currentID = 0,
SAVING_KEY = "anyproxy_local",
currentLSString = localStorage.getItem(SAVING_KEY),
currentData = currentLSString ? JSON.parse(currentLSString) : [];
//init currentID
currentData.map(function(item){
currentID = (item.id >= currentID ? item.id + 1 : currentID);
});
_.extend(self, Backbone.Events);
self.data = currentData;
self.add = function(data){
data.id = currentID;
++currentID;
currentData.push(data);
updateLS();
}
self.remove = function(targetId){
currentData.map(function(item,index){
if(parseInt(item.id) == parseInt(targetId)){
currentData.splice(index,1);
}
});
updateLS();
}
self.syncToServer = function(cb){
$.post("/update",JSON.stringify(currentData),cb);
}
function updateLS(){
localStorage.setItem(SAVING_KEY,JSON.stringify(currentData));
self.syncToServer(function(){
self.trigger("update");
});
}
}
//config.model
function ruleViewController(config){
var self = this,
wrapper = config.wrapper,
liTpl = config.liTpl,
model = config.model;
self.render = function(data){
return substitute(liTpl,data);
}
model.on("update",function(data){
window.location.reload();
});
//init
model.data.map(function(item){
wrapper.append(self.render(item));
console.log(item);
});
}
var dataMgmtInstance = new dataMgmt();
dataMgmtInstance.syncToServer();
var ruleView = new ruleViewController({
model : dataMgmtInstance,
wrapper : $(".J_listWrapper"),
liTpl : $("#listItemTpl").html()
});
$(".J_addBtn").on("click",function(e){
e.preventDefault();
var info = $(".J_infoForm").serializeArray();
var finalData = {};
info.map(function(item){
finalData[item.name] = item.value;
});
dataMgmtInstance.add(finalData);
});
$(".J_listWrapper").on("click",function(e){
var srcNode = $(e.srcElement);
if(srcNode.hasClass("J_remove")){
var id = srcNode.attr("ruleId");
dataMgmtInstance.remove(parseInt(id));
}
});
function substitute(str, object, regexp){
return String(str).replace(regexp || (/\{\{([^{}]+)\}\}/g), function(match, name){
if (match.charAt(0) == '\\') return match.slice(1);
return (object[name] != null) ? object[name] : '';
});
};
});
</script>
</body>
</html>