aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--libmproxy/web/static/css/app.css51
-rw-r--r--libmproxy/web/static/fonts/FontAwesome.otfbin85908 -> 0 bytes
-rw-r--r--libmproxy/web/static/index.html18
-rw-r--r--libmproxy/web/static/js/app.js452
-rw-r--r--libmproxy/web/templates/index.html11
-rw-r--r--web/gulpfile.js19
-rw-r--r--web/src/css/app.less1
-rw-r--r--web/src/css/eventlog.less11
-rw-r--r--web/src/css/footer.less4
-rw-r--r--web/src/css/header.less9
-rw-r--r--web/src/css/layout.less33
-rw-r--r--web/src/index.html3
-rw-r--r--web/src/js/Connection.es6.js33
-rw-r--r--web/src/js/Dispatcher.es6.js36
-rw-r--r--web/src/js/actions.es6.js14
-rw-r--r--web/src/js/app.js5
-rw-r--r--web/src/js/components/EventLog.react.js13
-rw-r--r--web/src/js/components/Footer.react.js (renamed from web/src/js/footer.react.js)2
-rw-r--r--web/src/js/components/Header.react.js88
-rw-r--r--web/src/js/components/ProxyApp.react.js35
-rw-r--r--web/src/js/components/TrafficTable.react.js38
-rw-r--r--web/src/js/datastructures.es6.js105
-rw-r--r--web/src/js/header.react.js72
-rw-r--r--web/src/js/mitmproxy.react.js82
-rw-r--r--web/src/js/stores/EventLogStore.es6.js42
-rw-r--r--web/src/js/stores/SettingsStore.es6.js42
-rw-r--r--web/src/js/stores/base.es6.js26
27 files changed, 738 insertions, 507 deletions
diff --git a/libmproxy/web/static/css/app.css b/libmproxy/web/static/css/app.css
index 8ef7842a..fe8fd79a 100644
--- a/libmproxy/web/static/css/app.css
+++ b/libmproxy/web/static/css/app.css
@@ -13,25 +13,18 @@ body,
margin: 0;
overflow: hidden;
}
-header,
-footer {
- display: block;
-}
#container {
- padding: 153px 0 25px;
+ display: flex;
+ flex-direction: column;
}
-header {
- height: 153px;
- margin-top: -153px;
+#container > header,
+#container > footer,
+#container > .eventlog {
+ flex: 0 0 auto;
}
#main {
- height: 100%;
- display: block;
- overflow-y: auto;
-}
-footer {
- height: 25px;
- line-height: 25px;
+ flex: 1 1 auto;
+ overflow: auto;
}
header {
background-color: white;
@@ -55,11 +48,11 @@ header nav a.active {
}
header nav a:hover {
/*
- @preview: lightgrey;
- border-top-color: @preview;
- border-left-color: @preview;
- border-right-color: @preview;
- */
+ @preview: lightgrey;
+ border-top-color: @preview;
+ border-left-color: @preview;
+ border-right-color: @preview;
+ */
text-decoration: none;
}
header nav a.special {
@@ -70,18 +63,22 @@ header nav a.special {
header nav a.special:hover {
background-color: #5386c6;
}
-header nav:before {
- content: " ";
-}
-header nav:after {
- clear: both;
-}
header .menu {
height: 100px;
border-bottom: solid #a6a6a6 1px;
}
+.eventlog {
+ flex: 0 0 auto;
+}
+.eventlog pre {
+ margin: 0;
+ border-radius: 0;
+ height: 200px;
+ overflow: auto;
+}
footer {
- padding: 0 10px;
+ box-shadow: 0 -1px 3px #d3d3d3;
+ padding: 0px 10px 3px;
}
/*# sourceMappingURL=../css/app.css.map */ \ No newline at end of file
diff --git a/libmproxy/web/static/fonts/FontAwesome.otf b/libmproxy/web/static/fonts/FontAwesome.otf
deleted file mode 100644
index 81c9ad94..00000000
--- a/libmproxy/web/static/fonts/FontAwesome.otf
+++ /dev/null
Binary files differ
diff --git a/libmproxy/web/static/index.html b/libmproxy/web/static/index.html
deleted file mode 100644
index 509ef1eb..00000000
--- a/libmproxy/web/static/index.html
+++ /dev/null
@@ -1,18 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head lang="en">
- <meta charset="UTF-8">
- <title>mitmproxy</title>
- <meta name="viewport" content="width=device-width, initial-scale=1">
- <link rel="stylesheet" href="css/vendor.css"/>
- <link rel="stylesheet" href="css/app.css"/>
- <script src="js/vendor.js"></script>
- <script src="js/app.js"></script>
-</head>
-<body>
-<div id="mitmproxy"></div>
-</body>
-<script>
- app = React.renderComponent(routes, document.body);
-</script>
-</html> \ No newline at end of file
diff --git a/libmproxy/web/static/js/app.js b/libmproxy/web/static/js/app.js
index eeb6e5dd..7873046a 100644
--- a/libmproxy/web/static/js/app.js
+++ b/libmproxy/web/static/js/app.js
@@ -1,135 +1,261 @@
+const PayloadSources = {
+ VIEW_ACTION: "VIEW_ACTION",
+ SERVER_ACTION: "SERVER_ACTION"
+};
+
- function EventEmitter(){"use strict";
- this.listeners = {};
- }
- EventEmitter.prototype.emit=function(event){"use strict";
- if(!(event in this.listeners)){
- return;
- }
- this.listeners[event].forEach(function (listener) {
- listener(event, this);
- }.bind(this));
- };
- EventEmitter.prototype.addListener=function(event, f){"use strict";
- this.listeners[event] = this.listeners[event] || [];
- this.listeners[event].push(f);
- };
- EventEmitter.prototype.removeListener=function(event, f){"use strict";
- if(!(event in this.listeners)){
- return false;
- }
- var index = this.listeners.indexOf(f);
- if (index >= 0) {
- this.listeners.splice(this.listeners.indexOf(f), 1);
- }
- };
+ function Dispatcher() {"use strict";
+ this.callbacks = [];
+ }
-var FLOW_CHANGED = "flow.changed";
+ Dispatcher.prototype.register=function(callback){"use strict";
+ this.callbacks.push(callback);
+ };
-for(var EventEmitter____Key in EventEmitter){if(EventEmitter.hasOwnProperty(EventEmitter____Key)){FlowStore[EventEmitter____Key]=EventEmitter[EventEmitter____Key];}}var ____SuperProtoOfEventEmitter=EventEmitter===null?null:EventEmitter.prototype;FlowStore.prototype=Object.create(____SuperProtoOfEventEmitter);FlowStore.prototype.constructor=FlowStore;FlowStore.__superConstructor__=EventEmitter;
- function FlowStore() {"use strict";
- EventEmitter.call(this);
- this.flows = [];
+ Dispatcher.prototype.unregister=function(callback){"use strict";
+ var index = this.callbacks.indexOf(f);
+ if (index >= 0) {
+ this.callbacks.splice(this.callbacks.indexOf(f), 1);
}
+ };
- FlowStore.prototype.getAll=function() {"use strict";
- return this.flows;
- };
+ Dispatcher.prototype.dispatch=function(payload){"use strict";
+ console.debug("dispatch", payload);
+ this.callbacks.forEach(function(callback) {
+ callback(payload);
+ });
+ };
- FlowStore.prototype.close=function(){"use strict";
- console.log("FlowStore.close()");
- this.listeners = [];
- };
- FlowStore.prototype.emitChange=function() {"use strict";
- return this.emit(FLOW_CHANGED);
- };
- FlowStore.prototype.addChangeListener=function(f) {"use strict";
- this.addListener(FLOW_CHANGED, f);
- };
+AppDispatcher = new Dispatcher();
+AppDispatcher.dispatchViewAction = function(action){
+ action.actionSource = PayloadSources.VIEW_ACTION;
+ this.dispatch(action);
+};
+var ActionTypes = {
+ SETTINGS_UPDATE: "SETTINGS_UPDATE",
+ LOG_ADD: "LOG_ADD"
+};
- FlowStore.prototype.removeChangeListener=function(f) {"use strict";
- this.removeListener(FLOW_CHANGED, f);
- };
+var SettingsActions = {
+ update:function(settings) {
+ settings = _.merge({}, SettingsStore.getSettings(), settings);
+ AppDispatcher.dispatchViewAction({
+ actionType: ActionTypes.SETTINGS_UPDATE,
+ settings: settings
+ });
+ }
+};
+ function EventEmitter() {"use strict";
+ this.listeners = {};
+ }
+ EventEmitter.prototype.emit=function(event) {"use strict";
+ if (!(event in this.listeners)) {
+ return;
+ }
+ this.listeners[event].forEach(function(listener) {
+ listener(event, this);
+ }.bind(this));
+ };
+ EventEmitter.prototype.addListener=function(event, f) {"use strict";
+ this.listeners[event] = this.listeners[event] || [];
+ this.listeners[event].push(f);
+ };
+ EventEmitter.prototype.removeListener=function(event, f) {"use strict";
+ if (!(event in this.listeners)) {
+ return false;
+ }
+ var index = this.listeners[event].indexOf(f);
+ if (index >= 0) {
+ this.listeners[event].splice(index, 1);
+ }
+ };
+
+for(var EventEmitter____Key in EventEmitter){if(EventEmitter.hasOwnProperty(EventEmitter____Key)){_SettingsStore[EventEmitter____Key]=EventEmitter[EventEmitter____Key];}}var ____SuperProtoOfEventEmitter=EventEmitter===null?null:EventEmitter.prototype;_SettingsStore.prototype=Object.create(____SuperProtoOfEventEmitter);_SettingsStore.prototype.constructor=_SettingsStore;_SettingsStore.__superConstructor__=EventEmitter;
+ function _SettingsStore() {"use strict";
+ /*jshint validthis: true */
+ EventEmitter.call(this);
+ this.settings = { version: "0.12", showEventLog: true }; //FIXME: Need to get that from somewhere.
+ }
+ _SettingsStore.prototype.getSettings=function() {"use strict";
+ return this.settings;
+ };
+ _SettingsStore.prototype.handle=function(action) {"use strict";
+ switch (action.actionType) {
+ case ActionTypes.SETTINGS_UPDATE:
+ this.settings = action.settings;
+ this.emit("change");
+ break;
+ default:
+ return;
+ }
+ };
+
+var SettingsStore = new _SettingsStore();
+AppDispatcher.register(SettingsStore.handle.bind(SettingsStore));
+
+
+var SettingsMixin = {
+ getInitialState:function(){
+ return {
+ settings: SettingsStore.getSettings()
+ };
+ },
+ componentDidMount:function(){
+ SettingsStore.addListener("change", this._onSettingsChange);
+ },
+ componentWillUnmount:function(){
+ SettingsStore.removeListener("change", this._onSettingsChange);
+ },
+ _onSettingsChange:function(){
+ this.setState({
+ settings: SettingsStore.getSettings()
+ });
+ }
+};
+for(var EventEmitter____Key in EventEmitter){if(EventEmitter.hasOwnProperty(EventEmitter____Key)){_EventLogStore[EventEmitter____Key]=EventEmitter[EventEmitter____Key];}}var ____SuperProtoOfEventEmitter=EventEmitter===null?null:EventEmitter.prototype;_EventLogStore.prototype=Object.create(____SuperProtoOfEventEmitter);_EventLogStore.prototype.constructor=_EventLogStore;_EventLogStore.__superConstructor__=EventEmitter;
+ function _EventLogStore() {"use strict";
+ /*jshint validthis: true */
+ EventEmitter.call(this);
+ this.log = [];
+ }
+ _EventLogStore.prototype.getAll=function() {"use strict";
+ return this.log;
+ };
+ _EventLogStore.prototype.handle=function(action) {"use strict";
+ switch (action.actionType) {
+ case ActionTypes.LOG_ADD:
+ this.log.push(action.message);
+ this.emit("change");
+ break;
+ default:
+ return;
+ }
+ };
+
+var EventLogStore = new _EventLogStore();
+AppDispatcher.register(EventLogStore.handle.bind(EventLogStore));
+
+
+var EventLogMixin = {
+ getInitialState:function(){
+ return {
+ log: EventLog.getAll()
+ };
+ },
+ componentDidMount:function(){
+ SettingsStore.addListener("change", this._onEventLogChange);
+ },
+ componentWillUnmount:function(){
+ SettingsStore.removeListener("change", this._onEventLogChange);
+ },
+ _onEventLogChange:function(){
+ this.setState({
+ log: EventLog.getAll()
+ });
+ }
+};
-for(var FlowStore____Key in FlowStore){if(FlowStore.hasOwnProperty(FlowStore____Key)){DummyFlowStore[FlowStore____Key]=FlowStore[FlowStore____Key];}}var ____SuperProtoOfFlowStore=FlowStore===null?null:FlowStore.prototype;DummyFlowStore.prototype=Object.create(____SuperProtoOfFlowStore);DummyFlowStore.prototype.constructor=DummyFlowStore;DummyFlowStore.__superConstructor__=FlowStore;
- function DummyFlowStore(flows) {"use strict";
- FlowStore.call(this);
- this.flows = flows;
+ function Connection(root){"use strict";
+ if(!root){
+ root = location.origin + "/api/v1";
+ }
+ this.root = root;
+ this.openWebSocketConnection();
}
- DummyFlowStore.prototype.addFlow=function(flow) {"use strict";
- this.flows.push(flow);
- this.emitChange();
+ Connection.prototype.openWebSocketConnection=function(){"use strict";
+ this.ws = new WebSocket(this.root.replace("http","ws") + "/ws");
+ var ws = this.ws;
+
+ ws.onopen = this.onopen.bind(this);
+ ws.onmessage = this.onmessage.bind(this);
+ ws.onerror = this.onerror.bind(this);
+ ws.onclose = this.onclose.bind(this);
+ };
+
+ Connection.prototype.onopen=function(open){"use strict";
+ console.log("onopen", this, arguments);
+ };
+ Connection.prototype.onmessage=function(message){"use strict";
+ console.log("onmessage", this, arguments);
+ };
+ Connection.prototype.onerror=function(error){"use strict";
+ console.log("onerror", this, arguments);
+ };
+ Connection.prototype.onclose=function(close){"use strict";
+ console.log("onclose", this, arguments);
};
-var SETTINGS_CHANGED = "settings.changed";
-for(EventEmitter____Key in EventEmitter){if(EventEmitter.hasOwnProperty(EventEmitter____Key)){Settings[EventEmitter____Key]=EventEmitter[EventEmitter____Key];}}Settings.prototype=Object.create(____SuperProtoOfEventEmitter);Settings.prototype.constructor=Settings;Settings.__superConstructor__=EventEmitter;
- function Settings(){"use strict";
- EventEmitter.call(this);
- this.settings = false;
+ function Connection(root){"use strict";
+ if(!root){
+ root = location.origin + "/api/v1";
+ }
+ this.root = root;
+ this.openWebSocketConnection();
}
- Settings.prototype.getAll=function(){"use strict";
- return this.settings;
- };
+ Connection.prototype.openWebSocketConnection=function(){"use strict";
+ this.ws = new WebSocket(this.root.replace("http","ws") + "/ws");
+ var ws = this.ws;
- Settings.prototype.emitChange=function() {"use strict";
- return this.emit(SETTINGS_CHANGED);
+ ws.onopen = this.onopen.bind(this);
+ ws.onmessage = this.onmessage.bind(this);
+ ws.onerror = this.onerror.bind(this);
+ ws.onclose = this.onclose.bind(this);
};
- Settings.prototype.addChangeListener=function(f) {"use strict";
- this.addListener(SETTINGS_CHANGED, f);
+ Connection.prototype.onopen=function(open){"use strict";
+ console.log("onopen", this, arguments);
};
-
- Settings.prototype.removeChangeListener=function(f) {"use strict";
- this.removeListener(SETTINGS_CHANGED, f);
+ Connection.prototype.onmessage=function(message){"use strict";
+ console.log("onmessage", this, arguments);
};
-
-
-for(var Settings____Key in Settings){if(Settings.hasOwnProperty(Settings____Key)){DummySettings[Settings____Key]=Settings[Settings____Key];}}var ____SuperProtoOfSettings=Settings===null?null:Settings.prototype;DummySettings.prototype=Object.create(____SuperProtoOfSettings);DummySettings.prototype.constructor=DummySettings;DummySettings.__superConstructor__=Settings;
- function DummySettings(settings){"use strict";
- Settings.call(this);
- this.settings = settings;
- }
- DummySettings.prototype.update=function(obj){"use strict";
- _.merge(this.settings, obj);
- this.emitChange();
+ Connection.prototype.onerror=function(error){"use strict";
+ console.log("onerror", this, arguments);
+ };
+ Connection.prototype.onclose=function(close){"use strict";
+ console.log("onclose", this, arguments);
};
-/** @jsx React.DOM */
-var Footer = React.createClass({displayName: 'Footer',
- render : function(){
- return (React.DOM.footer(null,
- React.DOM.span({className: "label label-success"}, "transparent mode")
- ));
- }
-});
+
/** @jsx React.DOM */
var MainMenu = React.createClass({displayName: 'MainMenu',
- render : function(){
- return (React.DOM.div(null, "Main Menu"));
+ mixins: [SettingsMixin],
+ handleSettingsChange:function() {
+ SettingsActions.update({
+ showEventLog: this.refs.showEventLogInput.getDOMNode().checked
+ });
+ },
+ render:function(){
+ return React.DOM.div(null,
+ React.DOM.label(null,
+ React.DOM.input({type: "checkbox", ref: "showEventLogInput", checked: this.state.settings.showEventLog, onChange: this.handleSettingsChange}),
+ "Show Event Log"
+ )
+ );
}
});
var ToolsMenu = React.createClass({displayName: 'ToolsMenu',
- render : function(){
+ render:function(){
return (React.DOM.div(null, "Tools Menu"));
}
});
var ReportsMenu = React.createClass({displayName: 'ReportsMenu',
- render : function(){
+ render:function(){
return (React.DOM.div(null, "Reports Menu"));
}
});
+
var _Header_Entries = {
main: {
title: "Traffic",
@@ -149,83 +275,55 @@ var _Header_Entries = {
};
var Header = React.createClass({displayName: 'Header',
- getInitialState: function(){
- return {active: "main"};
+ mixins: [SettingsMixin],
+ getInitialState:function(){
+ return {
+ active: "main"
+ };
},
- handleClick: function(active){
+ handleClick:function(active){
this.setState({active: active});
ReactRouter.transitionTo(_Header_Entries[active].route);
return false;
},
- handleFileClick: function(){
+ handleFileClick:function(){
console.log("File click");
},
- render: function(){
+ render:function(){
var header = [];
for(var item in _Header_Entries){
var classes = this.state.active == item ? "active" : "";
header.push(React.DOM.a({key: item, href: "#", className: classes,
- onClick: this.handleClick.bind(this, item)}, _Header_Entries[item].title));
+ onClick: this.handleClick.bind(this, item)}, _Header_Entries[item].title));
}
var menu = _Header_Entries[this.state.active].menu();
return (
- React.DOM.header(null,
- React.DOM.div({className: "title-bar"},
- "mitmproxy ", this.props.settings.version
- ),
- React.DOM.nav(null,
- React.DOM.a({href: "#", className: "special", onClick: this.handleFileClick}, " File "),
- header
- ),
- React.DOM.div({className: "menu"},
- menu
- )
- ));
+ React.DOM.header(null,
+ React.DOM.div({className: "title-bar"},
+ "mitmproxy ", this.state.settings.version
+ ),
+ React.DOM.nav(null,
+ React.DOM.a({href: "#", className: "special", onClick: this.handleFileClick}, " File "),
+ header
+ ),
+ React.DOM.div({className: "menu"},
+ menu
+ )
+ ));
}
});
/** @jsx React.DOM */
-var App = React.createClass({displayName: 'App',
- getInitialState: function () {
- return {
- settings: {} //TODO: How explicit should we get here?
- //List all subattributes?
- };
- },
- componentDidMount: function () {
- //TODO: Replace DummyStore with real settings over WS (https://facebook.github.io/react/tips/initial-ajax.html)
- var settingsStore = new DummySettings({
- version: "0.12"
- });
- this.setState({settingsStore: settingsStore});
- settingsStore.addChangeListener(this.onSettingsChange);
- },
- onSettingsChange: function(event, settings){
- this.setState({settings: settings.getAll()});
- },
- render: function () {
- return (
- React.DOM.div({id: "container"},
- Header({settings: this.state.settings}),
- React.DOM.div({id: "main"},
- this.props.activeRouteHandler({settings: this.state.settings})
- ),
- Footer(null)
- )
- );
- }
-});
-
var TrafficTable = React.createClass({displayName: 'TrafficTable',
- getInitialState: function(){
+ /*getInitialState: function(){
return {
flows: []
};
- },
+ },*/
componentDidMount: function () {
- var flowStore = new DummyFlowStore([]);
+ /*var flowStore = new DummyFlowStore([]);
this.setState({flowStore: flowStore});
flowStore.addChangeListener(this.onFlowsChange);
@@ -236,35 +334,87 @@ var TrafficTable = React.createClass({displayName: 'TrafficTable',
flowStore.addFlow(flow);
}, _.random(i*400,i*400+1000));
});
- }.bind(this));
+ }.bind(this));*/
},
componentWillUnmount: function(){
- this.state.flowStore.close();
+ //this.state.flowStore.close();
},
onFlowsChange: function(event, flows){
- this.setState({flows: flows.getAll()});
+ //this.setState({flows: flows.getAll()});
},
render: function () {
- var flows = this.state.flows.map(function(flow){
- return React.DOM.div(null, flow.request.method, " ", flow.request.scheme, "://", flow.request.host, flow.request.path);
- });
- return React.DOM.pre(null, flows);
+ /*var flows = this.state.flows.map(function(flow){
+ return <div>{flow.request.method} {flow.request.scheme}://{flow.request.host}{flow.request.path}</div>;
+ }); *//**/
+ x = "WTF";
+ i = 12;
+ while(i--) x += x;
+ return React.DOM.div(null, React.DOM.pre(null, x));
}
});
+/** @jsx React.DOM */
+var EventLog = React.createClass({displayName: 'EventLog',
+ render:function(){
+ return (
+ React.DOM.div({className: "eventlog"},
+ React.DOM.pre(null,
+ "much log."
+ )
+ )
+ );
+ }
+});
+/** @jsx React.DOM */
+
+var Footer = React.createClass({displayName: 'Footer',
+ render:function(){
+ return (
+ React.DOM.footer(null,
+ React.DOM.span({className: "label label-success"}, "transparent mode")
+ )
+ );
+ }
+});
+/** @jsx React.DOM */
+
+//TODO: Move out of here, just a stub.
var Reports = React.createClass({displayName: 'Reports',
- render: function(){
+ render:function(){
return (React.DOM.div(null, "Report Editor"));
}
});
-var routes = (
+
+
+var ProxyAppMain = React.createClass({displayName: 'ProxyAppMain',
+ mixins: [SettingsMixin],
+ render:function() {
+ return (
+ React.DOM.div({id: "container"},
+ Header(null),
+ React.DOM.div({id: "main"}, this.props.activeRouteHandler(null)),
+ this.state.settings.showEventLog ? EventLog(null) : null,
+ Footer(null)
+ )
+ );
+ }
+});
+
+
+var ProxyApp = (
ReactRouter.Routes({location: "hash"},
- ReactRouter.Route({name: "app", path: "/", handler: App},
+ ReactRouter.Route({name: "app", path: "/", handler: ProxyAppMain},
ReactRouter.Route({name: "main", handler: TrafficTable}),
ReactRouter.Route({name: "reports", handler: Reports}),
ReactRouter.Redirect({to: "main"})
)
)
);
+
+$(function(){
+
+ app = React.renderComponent(ProxyApp, document.body);
+
+});
//# sourceMappingURL=app.js.map \ No newline at end of file
diff --git a/libmproxy/web/templates/index.html b/libmproxy/web/templates/index.html
index 5907dacd..6cef0d25 100644
--- a/libmproxy/web/templates/index.html
+++ b/libmproxy/web/templates/index.html
@@ -4,15 +4,12 @@
<meta charset="UTF-8">
<title>mitmproxy</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
- <link rel="stylesheet" href="static/css/vendor.css"/>
- <link rel="stylesheet" href="static/css/app.css"/>
- <script src="static/js/vendor.js"></script>
- <script src="static/js/app.js"></script>
+ <link rel="stylesheet" href="css/vendor.css"/>
+ <link rel="stylesheet" href="css/app.css"/>
+ <script src="js/vendor.js"></script>
+ <script src="js/app.js"></script>
</head>
<body>
<div id="mitmproxy"></div>
</body>
-<script>
- app = React.renderComponent(routes, document.body);
-</script>
</html> \ No newline at end of file
diff --git a/web/gulpfile.js b/web/gulpfile.js
index bae4955b..68a5e479 100644
--- a/web/gulpfile.js
+++ b/web/gulpfile.js
@@ -32,10 +32,19 @@ var path = {
'vendor/react-bootstrap/react-bootstrap.js'
],
app: [
- 'js/datastructures.es6.js',
- 'js/footer.react.js',
- 'js/header.react.js',
- 'js/mitmproxy.react.js',
+ 'js/Dispatcher.es6.js',
+ 'js/actions.es6.js',
+ 'js/stores/base.es6.js',
+ 'js/stores/SettingsStore.es6.js',
+ 'js/stores/EventLogStore.es6.js',
+ 'js/Connection.es6.js',
+ 'js/connection.es6.js',
+ 'js/components/Header.react.js',
+ 'js/components/TrafficTable.react.js',
+ 'js/components/EventLog.react.js',
+ 'js/components/Footer.react.js',
+ 'js/components/ProxyApp.react.js',
+ 'js/app.js',
],
},
css: {
@@ -116,4 +125,4 @@ gulp.task("default", ["dev"], function () {
gulp.watch(["src/js/**"], ["scripts-app-dev", "jshint"]);
gulp.watch(["src/css/**"], ["styles-app-dev"]);
gulp.watch(["src/*.html"], ["html"]);
-}); \ No newline at end of file
+});
diff --git a/web/src/css/app.less b/web/src/css/app.less
index ff3c614c..ce9d9149 100644
--- a/web/src/css/app.less
+++ b/web/src/css/app.less
@@ -9,5 +9,6 @@ html {
@import (less) "layout.less";
@import (less) "header.less";
+@import (less) "eventlog.less";
@import (less) "footer.less";
diff --git a/web/src/css/eventlog.less b/web/src/css/eventlog.less
new file mode 100644
index 00000000..0e97832b
--- /dev/null
+++ b/web/src/css/eventlog.less
@@ -0,0 +1,11 @@
+.eventlog {
+
+ flex: 0 0 auto;
+
+ pre {
+ margin: 0;
+ border-radius: 0;
+ height: 200px;
+ overflow: auto;
+ }
+} \ No newline at end of file
diff --git a/web/src/css/footer.less b/web/src/css/footer.less
index 69ab62ce..be7a1d76 100644
--- a/web/src/css/footer.less
+++ b/web/src/css/footer.less
@@ -1,4 +1,4 @@
footer {
- padding: 0 10px;
- //text-align: center;
+ box-shadow: 0 -1px 3px lightgray;
+ padding: 0px 10px 3px;
} \ No newline at end of file
diff --git a/web/src/css/header.less b/web/src/css/header.less
index 4f4af121..69a947c5 100644
--- a/web/src/css/header.less
+++ b/web/src/css/header.less
@@ -42,15 +42,6 @@ header {
}
}
}
-
- &:before {
- content: " ";
- }
-
- &:after {
- clear: both;
- }
-
}
.menu {
diff --git a/web/src/css/layout.less b/web/src/css/layout.less
index 7c5f79b9..c8fad204 100644
--- a/web/src/css/layout.less
+++ b/web/src/css/layout.less
@@ -4,33 +4,16 @@ html, body, #container {
overflow: hidden;
}
-header, footer {
- display: block;
-}
-
-@headerheight: 153px;
-@footerheight: 25px;
-
#container {
- //Set padding on container so that #main can take 100% height
- //If we don't do it, the scrollbars will be too large.
- padding: @headerheight 0 @footerheight;
-}
+ display: flex;
+ flex-direction: column;
-header {
- height: @headerheight;
- //Substract #container padding
- margin-top: -@headerheight;
+ > header, > footer, > .eventlog {
+ flex: 0 0 auto;
+ }
}
#main {
- height: 100%;
- display: block;
- overflow-y: auto;
-}
-
-footer {
- //This starts at the beginning of the #container padding, all fine.
- height: @footerheight;
- line-height: @footerheight;
-}
+ flex: 1 1 auto;
+ overflow: auto;
+} \ No newline at end of file
diff --git a/web/src/index.html b/web/src/index.html
index 509ef1eb..6cef0d25 100644
--- a/web/src/index.html
+++ b/web/src/index.html
@@ -12,7 +12,4 @@
<body>
<div id="mitmproxy"></div>
</body>
-<script>
- app = React.renderComponent(routes, document.body);
-</script>
</html> \ No newline at end of file
diff --git a/web/src/js/Connection.es6.js b/web/src/js/Connection.es6.js
new file mode 100644
index 00000000..9daa82e2
--- /dev/null
+++ b/web/src/js/Connection.es6.js
@@ -0,0 +1,33 @@
+class Connection {
+ constructor(root){
+ if(!root){
+ root = location.origin + "/api/v1";
+ }
+ this.root = root;
+ this.openWebSocketConnection();
+ }
+
+ openWebSocketConnection(){
+ this.ws = new WebSocket(this.root.replace("http","ws") + "/ws");
+ var ws = this.ws;
+
+ ws.onopen = this.onopen.bind(this);
+ ws.onmessage = this.onmessage.bind(this);
+ ws.onerror = this.onerror.bind(this);
+ ws.onclose = this.onclose.bind(this);
+ }
+
+ onopen(open){
+ console.log("onopen", this, arguments);
+ }
+ onmessage(message){
+ console.log("onmessage", this, arguments);
+ }
+ onerror(error){
+ console.log("onerror", this, arguments);
+ }
+ onclose(close){
+ console.log("onclose", this, arguments);
+ }
+
+}
diff --git a/web/src/js/Dispatcher.es6.js b/web/src/js/Dispatcher.es6.js
new file mode 100644
index 00000000..9bf70878
--- /dev/null
+++ b/web/src/js/Dispatcher.es6.js
@@ -0,0 +1,36 @@
+const PayloadSources = {
+ VIEW_ACTION: "VIEW_ACTION",
+ SERVER_ACTION: "SERVER_ACTION"
+};
+
+class Dispatcher {
+
+ constructor() {
+ this.callbacks = [];
+ }
+
+ register(callback){
+ this.callbacks.push(callback);
+ }
+
+ unregister(callback){
+ var index = this.callbacks.indexOf(f);
+ if (index >= 0) {
+ this.callbacks.splice(this.callbacks.indexOf(f), 1);
+ }
+ }
+
+ dispatch(payload){
+ console.debug("dispatch", payload);
+ this.callbacks.forEach((callback) => {
+ callback(payload);
+ });
+ }
+
+}
+
+AppDispatcher = new Dispatcher();
+AppDispatcher.dispatchViewAction = function(action){
+ action.actionSource = PayloadSources.VIEW_ACTION;
+ this.dispatch(action);
+}; \ No newline at end of file
diff --git a/web/src/js/actions.es6.js b/web/src/js/actions.es6.js
new file mode 100644
index 00000000..b6770074
--- /dev/null
+++ b/web/src/js/actions.es6.js
@@ -0,0 +1,14 @@
+var ActionTypes = {
+ SETTINGS_UPDATE: "SETTINGS_UPDATE",
+ LOG_ADD: "LOG_ADD"
+};
+
+var SettingsActions = {
+ update(settings) {
+ settings = _.merge({}, SettingsStore.getSettings(), settings);
+ AppDispatcher.dispatchViewAction({
+ actionType: ActionTypes.SETTINGS_UPDATE,
+ settings: settings
+ });
+ }
+}; \ No newline at end of file
diff --git a/web/src/js/app.js b/web/src/js/app.js
new file mode 100644
index 00000000..2e4557af
--- /dev/null
+++ b/web/src/js/app.js
@@ -0,0 +1,5 @@
+$(function(){
+
+ app = React.renderComponent(ProxyApp, document.body);
+
+}); \ No newline at end of file
diff --git a/web/src/js/components/EventLog.react.js b/web/src/js/components/EventLog.react.js
new file mode 100644
index 00000000..e710d30c
--- /dev/null
+++ b/web/src/js/components/EventLog.react.js
@@ -0,0 +1,13 @@
+/** @jsx React.DOM */
+
+var EventLog = React.createClass({
+ render(){
+ return (
+ <div className="eventlog">
+ <pre>
+ much log.
+ </pre>
+ </div>
+ );
+ }
+}); \ No newline at end of file
diff --git a/web/src/js/footer.react.js b/web/src/js/components/Footer.react.js
index 1b65e19d..ae0ccbe5 100644
--- a/web/src/js/footer.react.js
+++ b/web/src/js/components/Footer.react.js
@@ -1,7 +1,7 @@
/** @jsx React.DOM */
var Footer = React.createClass({
- render : function(){
+ render(){
return (
<footer>
<span className="label label-success">transparent mode</span>
diff --git a/web/src/js/components/Header.react.js b/web/src/js/components/Header.react.js
new file mode 100644
index 00000000..dc304d81
--- /dev/null
+++ b/web/src/js/components/Header.react.js
@@ -0,0 +1,88 @@
+/** @jsx React.DOM */
+
+var MainMenu = React.createClass({
+ mixins: [SettingsMixin],
+ handleSettingsChange() {
+ SettingsActions.update({
+ showEventLog: this.refs.showEventLogInput.getDOMNode().checked
+ });
+ },
+ render(){
+ return <div>
+ <label>
+ <input type="checkbox" ref="showEventLogInput" checked={this.state.settings.showEventLog} onChange={this.handleSettingsChange}/>
+ Show Event Log
+ </label>
+ </div>;
+ }
+});
+var ToolsMenu = React.createClass({
+ render(){
+ return (<div>Tools Menu</div>);
+ }
+});
+var ReportsMenu = React.createClass({
+ render(){
+ return (<div>Reports Menu</div>);
+ }
+});
+
+
+var _Header_Entries = {
+ main: {
+ title: "Traffic",
+ route: "main",
+ menu: MainMenu
+ },
+ tools: {
+ title: "Tools",
+ route: "main",
+ menu: ToolsMenu
+ },
+ reports: {
+ title: "Visualization",
+ route: "reports",
+ menu: ReportsMenu
+ }
+};
+
+var Header = React.createClass({
+ mixins: [SettingsMixin],
+ getInitialState(){
+ return {
+ active: "main"
+ };
+ },
+ handleClick(active){
+ this.setState({active: active});
+ ReactRouter.transitionTo(_Header_Entries[active].route);
+ return false;
+ },
+ handleFileClick(){
+ console.log("File click");
+ },
+
+ render(){
+ var header = [];
+ for(var item in _Header_Entries){
+ var classes = this.state.active == item ? "active" : "";
+ header.push(<a key={item} href="#" className={classes}
+ onClick={this.handleClick.bind(this, item)}>{ _Header_Entries[item].title }</a>);
+ }
+
+ var menu = _Header_Entries[this.state.active].menu();
+ return (
+ <header>
+ <div className="title-bar">
+ mitmproxy { this.state.settings.version }
+ </div>
+ <nav>
+ <a href="#" className="special" onClick={this.handleFileClick}> File </a>
+ {header}
+ </nav>
+ <div className="menu">
+ { menu }
+ </div>
+ </header>);
+ }
+}); \ No newline at end of file
diff --git a/web/src/js/components/ProxyApp.react.js b/web/src/js/components/ProxyApp.react.js
new file mode 100644
index 00000000..7953d938
--- /dev/null
+++ b/web/src/js/components/ProxyApp.react.js
@@ -0,0 +1,35 @@
+/** @jsx React.DOM */
+
+//TODO: Move out of here, just a stub.
+var Reports = React.createClass({
+ render(){
+ return (<div>Report Editor</div>);
+ }
+});
+
+
+
+var ProxyAppMain = React.createClass({
+ mixins: [SettingsMixin],
+ render() {
+ return (
+ <div id="container">
+ <Header/>
+ <div id="main"><this.props.activeRouteHandler/></div>
+ {this.state.settings.showEventLog ? <EventLog/> : null}
+ <Footer/>
+ </div>
+ );
+ }
+});
+
+
+var ProxyApp = (
+ <ReactRouter.Routes location="hash">
+ <ReactRouter.Route name="app" path="/" handler={ProxyAppMain}>
+ <ReactRouter.Route name="main" handler={TrafficTable}/>
+ <ReactRouter.Route name="reports" handler={Reports}/>
+ <ReactRouter.Redirect to="main"/>
+ </ReactRouter.Route>
+ </ReactRouter.Routes>
+);
diff --git a/web/src/js/components/TrafficTable.react.js b/web/src/js/components/TrafficTable.react.js
new file mode 100644
index 00000000..442f8da2
--- /dev/null
+++ b/web/src/js/components/TrafficTable.react.js
@@ -0,0 +1,38 @@
+/** @jsx React.DOM */
+
+var TrafficTable = React.createClass({
+ /*getInitialState: function(){
+ return {
+ flows: []
+ };
+ },*/
+ componentDidMount: function () {
+ /*var flowStore = new DummyFlowStore([]);
+ this.setState({flowStore: flowStore});
+
+ flowStore.addChangeListener(this.onFlowsChange);
+
+ $.getJSON("/flows.json").success(function (flows) {
+ flows.forEach(function (flow, i) {
+ window.setTimeout(function () {
+ flowStore.addFlow(flow);
+ }, _.random(i*400,i*400+1000));
+ });
+ }.bind(this));*/
+ },
+ componentWillUnmount: function(){
+ //this.state.flowStore.close();
+ },
+ onFlowsChange: function(event, flows){
+ //this.setState({flows: flows.getAll()});
+ },
+ render: function () {
+ /*var flows = this.state.flows.map(function(flow){
+ return <div>{flow.request.method} {flow.request.scheme}://{flow.request.host}{flow.request.path}</div>;
+ }); *//**/
+ x = "WTF";
+ i = 12;
+ while(i--) x += x;
+ return <div><pre>{x}</pre></div>;
+ }
+}); \ No newline at end of file
diff --git a/web/src/js/datastructures.es6.js b/web/src/js/datastructures.es6.js
deleted file mode 100644
index e9e2ee77..00000000
--- a/web/src/js/datastructures.es6.js
+++ /dev/null
@@ -1,105 +0,0 @@
-class EventEmitter {
- constructor(){
- this.listeners = {};
- }
- emit(event){
- if(!(event in this.listeners)){
- return;
- }
- this.listeners[event].forEach(function (listener) {
- listener(event, this);
- }.bind(this));
- }
- addListener(event, f){
- this.listeners[event] = this.listeners[event] || [];
- this.listeners[event].push(f);
- }
- removeListener(event, f){
- if(!(event in this.listeners)){
- return false;
- }
- var index = this.listeners.indexOf(f);
- if (index >= 0) {
- this.listeners.splice(this.listeners.indexOf(f), 1);
- }
- }
-}
-
-var FLOW_CHANGED = "flow.changed";
-
-class FlowStore extends EventEmitter{
- constructor() {
- super();
- this.flows = [];
- }
-
- getAll() {
- return this.flows;
- }
-
- close(){
- console.log("FlowStore.close()");
- this.listeners = [];
- }
-
- emitChange() {
- return this.emit(FLOW_CHANGED);
- }
-
- addChangeListener(f) {
- this.addListener(FLOW_CHANGED, f);
- }
-
- removeChangeListener(f) {
- this.removeListener(FLOW_CHANGED, f);
- }
-}
-
-class DummyFlowStore extends FlowStore {
- constructor(flows) {
- super();
- this.flows = flows;
- }
-
- addFlow(flow) {
- this.flows.push(flow);
- this.emitChange();
- }
-}
-
-
-var SETTINGS_CHANGED = "settings.changed";
-
-class Settings extends EventEmitter {
- constructor(){
- super();
- this.settings = false;
- }
-
- getAll(){
- return this.settings;
- }
-
- emitChange() {
- return this.emit(SETTINGS_CHANGED);
- }
-
- addChangeListener(f) {
- this.addListener(SETTINGS_CHANGED, f);
- }
-
- removeChangeListener(f) {
- this.removeListener(SETTINGS_CHANGED, f);
- }
-}
-
-class DummySettings extends Settings {
- constructor(settings){
- super();
- this.settings = settings;
- }
- update(obj){
- _.merge(this.settings, obj);
- this.emitChange();
- }
-} \ No newline at end of file
diff --git a/web/src/js/header.react.js b/web/src/js/header.react.js
deleted file mode 100644
index 85dc3106..00000000
--- a/web/src/js/header.react.js
+++ /dev/null
@@ -1,72 +0,0 @@
-/** @jsx React.DOM */
-
-var MainMenu = React.createClass({
- render: function(){
- return (<div>Main Menu</div>);
- }
-});
-var ToolsMenu = React.createClass({
- render: function(){
- return (<div>Tools Menu</div>);
- }
-});
-var ReportsMenu = React.createClass({
- render: function(){
- return (<div>Reports Menu</div>);
- }
-});
-
-var _Header_Entries = {
- main: {
- title: "Traffic",
- route: "main",
- menu: MainMenu
- },
- tools: {
- title: "Tools",
- route: "main",
- menu: ToolsMenu
- },
- reports: {
- title: "Visualization",
- route: "reports",
- menu: ReportsMenu
- }
-};
-
-var Header = React.createClass({
- getInitialState: function(){
- return {active: "main"};
- },
- handleClick: function(active){
- this.setState({active: active});
- ReactRouter.transitionTo(_Header_Entries[active].route);
- return false;
- },
- handleFileClick: function(){
- console.log("File click");
- },
- render: function(){
- var header = [];
- for(var item in _Header_Entries){
- var classes = this.state.active == item ? "active" : "";
- header.push(<a key={item} href="#" className={classes}
- onClick={this.handleClick.bind(this, item)}>{ _Header_Entries[item].title }</a>);
- }
-
- var menu = _Header_Entries[this.state.active].menu();
- return (
- <header>
- <div className="title-bar">
- mitmproxy { this.props.settings.version }
- </div>
- <nav>
- <a href="#" className="special" onClick={this.handleFileClick}> File </a>
- {header}
- </nav>
- <div className="menu">
- { menu }
- </div>
- </header>);
- }
-}); \ No newline at end of file
diff --git a/web/src/js/mitmproxy.react.js b/web/src/js/mitmproxy.react.js
deleted file mode 100644
index 609d2014..00000000
--- a/web/src/js/mitmproxy.react.js
+++ /dev/null
@@ -1,82 +0,0 @@
-/** @jsx React.DOM */
-
-var App = React.createClass({
- getInitialState: function () {
- return {
- settings: {} //TODO: How explicit should we get here?
- //List all subattributes?
- };
- },
- componentDidMount: function () {
- //TODO: Replace DummyStore with real settings over WS (https://facebook.github.io/react/tips/initial-ajax.html)
- var settingsStore = new DummySettings({
- version: "0.12"
- });
- this.setState({settingsStore: settingsStore});
- settingsStore.addChangeListener(this.onSettingsChange);
- },
- onSettingsChange: function(event, settings){
- this.setState({settings: settings.getAll()});
- },
- render: function () {
- return (
- <div id="container">
- <Header settings={this.state.settings}/>
- <div id="main">
- <this.props.activeRouteHandler settings={this.state.settings}/>
- </div>
- <Footer/>
- </div>
- );
- }
-});
-
-var TrafficTable = React.createClass({
- getInitialState: function(){
- return {
- flows: []
- };
- },
- componentDidMount: function () {
- var flowStore = new DummyFlowStore([]);
- this.setState({flowStore: flowStore});
-
- flowStore.addChangeListener(this.onFlowsChange);
-
- $.getJSON("/flows.json").success(function (flows) {
- flows.forEach(function (flow, i) {
- window.setTimeout(function () {
- flowStore.addFlow(flow);
- }, _.random(i*400,i*400+1000));
- });
- }.bind(this));
- },
- componentWillUnmount: function(){
- this.state.flowStore.close();
- },
- onFlowsChange: function(event, flows){
- this.setState({flows: flows.getAll()});
- },
- render: function () {
- var flows = this.state.flows.map(function(flow){
- return <div>{flow.request.method} {flow.request.scheme}://{flow.request.host}{flow.request.path}</div>;
- });
- return <pre>{flows}</pre>;
- }
-});
-
-var Reports = React.createClass({
- render: function(){
- return (<div>Report Editor</div>);
- }
-});
-
-var routes = (
- <ReactRouter.Routes location="hash">
- <ReactRouter.Route name="app" path="/" handler={App}>
- <ReactRouter.Route name="main" handler={TrafficTable}/>
- <ReactRouter.Route name="reports" handler={Reports}/>
- <ReactRouter.Redirect to="main"/>
- </ReactRouter.Route>
- </ReactRouter.Routes>
-); \ No newline at end of file
diff --git a/web/src/js/stores/EventLogStore.es6.js b/web/src/js/stores/EventLogStore.es6.js
new file mode 100644
index 00000000..caa9d77d
--- /dev/null
+++ b/web/src/js/stores/EventLogStore.es6.js
@@ -0,0 +1,42 @@
+class _EventLogStore extends EventEmitter {
+ constructor() {
+ /*jshint validthis: true */
+ super();
+ this.log = [];
+ }
+ getAll() {
+ return this.log;
+ }
+ handle(action) {
+ switch (action.actionType) {
+ case ActionTypes.LOG_ADD:
+ this.log.push(action.message);
+ this.emit("change");
+ break;
+ default:
+ return;
+ }
+ }
+}
+var EventLogStore = new _EventLogStore();
+AppDispatcher.register(EventLogStore.handle.bind(EventLogStore));
+
+
+var EventLogMixin = {
+ getInitialState(){
+ return {
+ log: EventLog.getAll()
+ };
+ },
+ componentDidMount(){
+ SettingsStore.addListener("change", this._onEventLogChange);
+ },
+ componentWillUnmount(){
+ SettingsStore.removeListener("change", this._onEventLogChange);
+ },
+ _onEventLogChange(){
+ this.setState({
+ log: EventLog.getAll()
+ });
+ }
+}; \ No newline at end of file
diff --git a/web/src/js/stores/SettingsStore.es6.js b/web/src/js/stores/SettingsStore.es6.js
new file mode 100644
index 00000000..7f3a6837
--- /dev/null
+++ b/web/src/js/stores/SettingsStore.es6.js
@@ -0,0 +1,42 @@
+class _SettingsStore extends EventEmitter {
+ constructor() {
+ /*jshint validthis: true */
+ super();
+ this.settings = { version: "0.12", showEventLog: true }; //FIXME: Need to get that from somewhere.
+ }
+ getSettings() {
+ return this.settings;
+ }
+ handle(action) {
+ switch (action.actionType) {
+ case ActionTypes.SETTINGS_UPDATE:
+ this.settings = action.settings;
+ this.emit("change");
+ break;
+ default:
+ return;
+ }
+ }
+}
+var SettingsStore = new _SettingsStore();
+AppDispatcher.register(SettingsStore.handle.bind(SettingsStore));
+
+
+var SettingsMixin = {
+ getInitialState(){
+ return {
+ settings: SettingsStore.getSettings()
+ };
+ },
+ componentDidMount(){
+ SettingsStore.addListener("change", this._onSettingsChange);
+ },
+ componentWillUnmount(){
+ SettingsStore.removeListener("change", this._onSettingsChange);
+ },
+ _onSettingsChange(){
+ this.setState({
+ settings: SettingsStore.getSettings()
+ });
+ }
+}; \ No newline at end of file
diff --git a/web/src/js/stores/base.es6.js b/web/src/js/stores/base.es6.js
new file mode 100644
index 00000000..9e9c69aa
--- /dev/null
+++ b/web/src/js/stores/base.es6.js
@@ -0,0 +1,26 @@
+class EventEmitter {
+ constructor() {
+ this.listeners = {};
+ }
+ emit(event) {
+ if (!(event in this.listeners)) {
+ return;
+ }
+ this.listeners[event].forEach(function(listener) {
+ listener(event, this);
+ }.bind(this));
+ }
+ addListener(event, f) {
+ this.listeners[event] = this.listeners[event] || [];
+ this.listeners[event].push(f);
+ }
+ removeListener(event, f) {
+ if (!(event in this.listeners)) {
+ return false;
+ }
+ var index = this.listeners[event].indexOf(f);
+ if (index >= 0) {
+ this.listeners[event].splice(index, 1);
+ }
+ }
+} \ No newline at end of file