web: start gui
This commit is contained in:
parent
9bacb6d426
commit
d2475e6a14
|
@ -10,13 +10,15 @@
|
|||
"qunit": "",
|
||||
"benchmark": "",
|
||||
"benchmarkjs-runner": "",
|
||||
"bootstrap": ""
|
||||
"bootstrap": "",
|
||||
"react-bootstrap": ""
|
||||
},
|
||||
"install": {
|
||||
"path": "src/vendor",
|
||||
"sources": {
|
||||
"lodash": "bower_components/lodash/dist/lodash.js",
|
||||
"react": "bower_components/react/react-with-addons.js"
|
||||
"react": "bower_components/react/react-with-addons.js",
|
||||
"react-bootstrap": "bower_components/react-bootstrap/react-bootstrap.js"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,6 +6,7 @@ var jshint = require("gulp-jshint");
|
|||
var less = require("gulp-less");
|
||||
var livereload = require("gulp-livereload");
|
||||
var minifyCSS = require('gulp-minify-css');
|
||||
var plumber = require("gulp-plumber");
|
||||
var react = require("gulp-react");
|
||||
var sourcemaps = require('gulp-sourcemaps');
|
||||
var uglify = require('gulp-uglify');
|
||||
|
@ -19,11 +20,13 @@ var path = {
|
|||
'vendor/lodash/lodash.js',
|
||||
'vendor/react/react-with-addons.js',
|
||||
'vendor/react-router/react-router.js',
|
||||
'vendor/react-bootstrap/react-bootstrap.js'
|
||||
],
|
||||
app: [
|
||||
'js/router.jsx',
|
||||
'js/certinstall.jsx',
|
||||
'js/mitmproxy.js',
|
||||
'js/datastructures.js',
|
||||
'js/footer.react.js',
|
||||
'js/header.react.js',
|
||||
'js/mitmproxy.react.js',
|
||||
],
|
||||
},
|
||||
css: {
|
||||
|
@ -40,6 +43,7 @@ gulp.task("fonts", function () {
|
|||
|
||||
function styles(files, dev) {
|
||||
return (gulp.src(files, {base: "src", cwd: "src"})
|
||||
.pipe(plumber())
|
||||
.pipe(dev ? sourcemaps.init() : gutil.noop())
|
||||
.pipe(less())
|
||||
.pipe(dev ? sourcemaps.write(".", {sourceRoot: "/static"}) : gutil.noop())
|
||||
|
@ -58,8 +62,9 @@ gulp.task("styles-prod", ["styles-app-prod", "styles-vendor-prod"]);
|
|||
|
||||
function scripts(files, filename, dev) {
|
||||
return gulp.src(files, {base: "src", cwd: "src"})
|
||||
.pipe(plumber())
|
||||
.pipe(dev ? sourcemaps.init() : gutil.noop())
|
||||
.pipe(react())
|
||||
.pipe(react({harmony: true}))
|
||||
.pipe(concat(filename))
|
||||
.pipe(!dev ? uglify() : gutil.noop())
|
||||
.pipe(dev ? sourcemaps.write(".", {sourceRoot: "/static"}) : gutil.noop())
|
||||
|
@ -75,7 +80,8 @@ gulp.task("scripts-prod", ["scripts-app-prod", "scripts-vendor-prod"]);
|
|||
|
||||
gulp.task("jshint", function () {
|
||||
return gulp.src(["src/js/**"])
|
||||
.pipe(react())
|
||||
.pipe(plumber())
|
||||
.pipe(react({harmony: true}))
|
||||
.pipe(jshint())
|
||||
.pipe(jshint.reporter("jshint-stylish"))
|
||||
});
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
"gulp-less": "^1.3.5",
|
||||
"gulp-livereload": "^2.1.1",
|
||||
"gulp-minify-css": "^0.3.8",
|
||||
"gulp-plumber": "^0.6.5",
|
||||
"gulp-react": "^1.0.1",
|
||||
"gulp-sourcemaps": "^1.1.5",
|
||||
"gulp-uglify": "^1.0.1",
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
.certinstall {
|
||||
|
||||
// www.paulirish.com/2012/box-sizing-border-box-ftw/
|
||||
html {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@color: white;
|
||||
body {
|
||||
background-color: @color;
|
||||
}
|
||||
*, *:before, *:after {
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
@import (less) "layout.less";
|
||||
@import (less) "header.less";
|
||||
@import (less) "footer.less";
|
|
@ -0,0 +1,3 @@
|
|||
footer {
|
||||
padding: 0 10px;
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
header {
|
||||
|
||||
background-color: white;
|
||||
|
||||
.title-bar {
|
||||
line-height: 25px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@separator-color: lighten(grey, 15%);
|
||||
|
||||
nav {
|
||||
|
||||
border-bottom: solid @separator-color 1px;
|
||||
|
||||
a {
|
||||
display: inline-block;
|
||||
padding: 3px 14px;
|
||||
margin: 0 2px -1px;
|
||||
border: solid transparent 1px;
|
||||
//text-transform: uppercase;
|
||||
//font-family: Lato;
|
||||
|
||||
&.active {
|
||||
border-color: @separator-color;
|
||||
border-bottom-color: white;
|
||||
}
|
||||
&:hover {
|
||||
/*
|
||||
@preview: lightgrey;
|
||||
border-top-color: @preview;
|
||||
border-left-color: @preview;
|
||||
border-right-color: @preview;
|
||||
*/
|
||||
text-decoration: none;
|
||||
}
|
||||
&.special {
|
||||
@special-color: #396cad;
|
||||
color: white;
|
||||
background-color: @special-color;
|
||||
border-bottom-color: @special-color;
|
||||
&:hover {
|
||||
background-color: lighten(@special-color, 10%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:before {
|
||||
content: " ";
|
||||
}
|
||||
|
||||
&:after {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.menu {
|
||||
height: 100px;
|
||||
border-bottom: solid @separator-color 1px;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
html, body, #container {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
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;
|
||||
}
|
||||
|
||||
header {
|
||||
height: @headerheight;
|
||||
//Substract #container padding
|
||||
margin-top: -@headerheight;
|
||||
}
|
||||
|
||||
#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;
|
||||
}
|
|
@ -8,14 +8,11 @@
|
|||
<link rel="stylesheet" href="css/app.css"/>
|
||||
<script src="js/vendor.js"></script>
|
||||
<script src="js/app.js"></script>
|
||||
<script>
|
||||
$(function () {
|
||||
mitmproxy.init();
|
||||
});
|
||||
</script>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<div id="mitmproxy"></div>
|
||||
</body>
|
||||
<script>
|
||||
app = React.renderComponent(routes, document.body);
|
||||
</script>
|
||||
</html>
|
|
@ -1,27 +0,0 @@
|
|||
/** @jsx React.DOM */
|
||||
|
||||
var CertInstallView = React.createClass({
|
||||
render: function () {
|
||||
return <div className="certinstall">
|
||||
<h2> Click to install the mitmproxy certificate: </h2>
|
||||
<div id="certbank" className="row">
|
||||
<div className="col-md-3">
|
||||
<a href="/cert/pem"><i className="fa fa-apple fa-5x"></i></a>
|
||||
<p>Apple</p>
|
||||
</div>
|
||||
<div className="col-md-3">
|
||||
<a href="/cert/p12"><i className="fa fa-windows fa-5x"></i></a>
|
||||
<p>Windows</p>
|
||||
</div>
|
||||
<div className="col-md-3">
|
||||
<a href="/cert/pem"><i className="fa fa-android fa-5x"></i></a>
|
||||
<p>Android</p>
|
||||
</div>
|
||||
<div className="col-md-3">
|
||||
<a href="/cert/pem"><i className="fa fa-asterisk fa-5x"></i></a>
|
||||
<p>Other</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
});
|
|
@ -0,0 +1,101 @@
|
|||
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 = [];
|
||||
this._listeners = [];
|
||||
}
|
||||
|
||||
getAll() {
|
||||
return this.flows;
|
||||
}
|
||||
|
||||
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(f) {
|
||||
this.flows.push(f);
|
||||
this.emitChange();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
SETTINGS_CHANGED = "settings.change";
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
/** @jsx React.DOM */
|
||||
|
||||
var Footer = React.createClass({
|
||||
render : function(){
|
||||
var style = {
|
||||
textAlign: "center"
|
||||
};
|
||||
return (<footer>
|
||||
<span className="label label-success">transparent mode</span>
|
||||
</footer>);
|
||||
}
|
||||
});
|
|
@ -0,0 +1,73 @@
|
|||
/** @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>);
|
||||
}
|
||||
});
|
|
@ -1,10 +0,0 @@
|
|||
|
||||
mitmproxy = function () {
|
||||
function init() {
|
||||
React.renderComponent(Router(), $("#mitmproxy")[0]);
|
||||
}
|
||||
var exports = {
|
||||
init: init,
|
||||
};
|
||||
return exports;
|
||||
}();
|
|
@ -0,0 +1,60 @@
|
|||
/** @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)
|
||||
//TODO: Is there a sensible place where we can store this?
|
||||
var settings = new DummySettings({
|
||||
version: "0.12"
|
||||
});
|
||||
settings.addChangeListener(this._onSettingsChange);
|
||||
|
||||
//This would be async in some way or another.
|
||||
this._onSettingsChange(null, settings);
|
||||
},
|
||||
_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 Traffic = React.createClass({
|
||||
render: function(){
|
||||
var json = JSON.stringify(this.props, null, 4);
|
||||
var i = 5;
|
||||
while(i--) json += json;
|
||||
return (<pre>{json}</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={Traffic}/>
|
||||
<ReactRouter.Route name="reports" handler={Reports}/>
|
||||
<ReactRouter.Redirect to="main"/>
|
||||
</ReactRouter.Route>
|
||||
</ReactRouter.Routes>
|
||||
);
|
|
@ -1,10 +0,0 @@
|
|||
/** @jsx React.DOM */
|
||||
|
||||
var Router = React.createClass({
|
||||
render: function(){
|
||||
return <ReactRouter.Routes location="hash">
|
||||
<ReactRouter.Route name="certs" path="/" handler={CertInstallView}/>
|
||||
<ReactRouter.Route name="other" path="/other" handler={CertInstallView}/>
|
||||
</ReactRouter.Routes>;
|
||||
}
|
||||
});
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue