diff options
25 files changed, 396 insertions, 41 deletions
| diff --git a/libmproxy/controller.py b/libmproxy/controller.py index b754ac36..39e7695f 100644 --- a/libmproxy/controller.py +++ b/libmproxy/controller.py @@ -77,7 +77,7 @@ class Slave(threading.Thread):          self.server.serve_forever() -class Master: +class Master(object):      """          Masters get and respond to messages from slaves.      """ diff --git a/libmproxy/web/__init__.py b/libmproxy/web/__init__.py index c7e0d20d..69971436 100644 --- a/libmproxy/web/__init__.py +++ b/libmproxy/web/__init__.py @@ -1,8 +1,8 @@ +from __future__ import absolute_import, print_function  import tornado.ioloop  import tornado.httpserver -from .. import controller, utils, flow, script, proxy -import app -import pprint +from .. import controller, flow +from . import app  class Stop(Exception): @@ -59,7 +59,7 @@ class WebMaster(flow.FlowMaster):      def __init__(self, server, options):          self.options = options          self.app = app.Application(self.options.wdebug) -        flow.FlowMaster.__init__(self, server, WebState()) +        super(WebMaster, self).__init__(server, WebState())          self.last_log_id = 0 @@ -90,7 +90,6 @@ class WebMaster(flow.FlowMaster):          return f      def handle_response(self, f): -        s = f.get_state(True)          app.ClientConnection.broadcast("update_flow", f.get_state(True))          flow.FlowMaster.handle_response(self, f)          if f: diff --git a/libmproxy/web/static/css/app.css b/libmproxy/web/static/css/app.css index ecc7426c..27acd68a 100644 --- a/libmproxy/web/static/css/app.css +++ b/libmproxy/web/static/css/app.css @@ -6,6 +6,60 @@ html {  *:after {    box-sizing: inherit;  } +.resource-icon { +  width: 32px; +  height: 32px; +} +.resource-icon-css { +  background-image: url("../images/sprite.png"); +  background-position: 0px 0px; +  background-size: 32px 320px!important; +} +.resource-icon-document { +  background-image: url("../images/sprite.png"); +  background-position: 0px -32px; +  background-size: 32px 320px!important; +} +.resource-icon-js { +  background-image: url("../images/sprite.png"); +  background-position: 0px -64px; +  background-size: 32px 320px!important; +} +.resource-icon-plain { +  background-image: url("../images/sprite.png"); +  background-position: 0px -96px; +  background-size: 32px 320px!important; +} +.resource-icon-executable { +  background-image: url("../images/sprite.png"); +  background-position: 0px -128px; +  background-size: 32px 320px!important; +} +.resource-icon-flash { +  background-image: url("../images/sprite.png"); +  background-position: 0px -160px; +  background-size: 32px 320px!important; +} +.resource-icon-image { +  background-image: url("../images/sprite.png"); +  background-position: 0px -192px; +  background-size: 32px 320px!important; +} +.resource-icon-java { +  background-image: url("../images/sprite.png"); +  background-position: 0px -224px; +  background-size: 32px 320px!important; +} +.resource-icon-not-modified { +  background-image: url("../images/sprite.png"); +  background-position: 0px -256px; +  background-size: 32px 320px!important; +} +.resource-icon-redirect { +  background-image: url("../images/sprite.png"); +  background-position: 0px -288px; +  background-size: 32px 320px!important; +}  html,  body,  #container { @@ -69,6 +123,33 @@ header .menu {  }  .flow-table {    width: 100%; +  table-layout: fixed; +} +.flow-table thead { +  background-color: #dadada; +} +.flow-table td { +  overflow: hidden; +  white-space: nowrap; +  text-overflow: ellipsis; +} +.flow-table .col-tls { +  width: 10px; +} +.flow-table .col-tls-https { +  background-color: rgba(0, 185, 0, 0.5); +} +.flow-table .col-icon { +  width: 32px; +} +.flow-table .col-method { +  width: 60px; +} +.flow-table .col-status { +  width: 50px; +} +.flow-table .col-time { +  width: 120px;  }  .eventlog {    flex: 0 0 auto; diff --git a/libmproxy/web/flows.json b/libmproxy/web/static/flows.json index bdbfd5cc..bdbfd5cc 100644 --- a/libmproxy/web/flows.json +++ b/libmproxy/web/static/flows.json diff --git a/libmproxy/web/static/images/sprite.png b/libmproxy/web/static/images/sprite.pngBinary files differ new file mode 100644 index 00000000..ff57a37d --- /dev/null +++ b/libmproxy/web/static/images/sprite.png diff --git a/libmproxy/web/static/js/app.js b/libmproxy/web/static/js/app.js index e967af89..ea49db4d 100644 --- a/libmproxy/web/static/js/app.js +++ b/libmproxy/web/static/js/app.js @@ -269,13 +269,14 @@ _.extend(FlowView.prototype, EventEmitter.prototype, {          var updates = this.flows;          this.flows = flows;          updates.forEach(function(flow){ -            this.update(flow); +            this._update(flow);          }.bind(this)); +        this.emit("change");      }, -    update: function(flow){ +    _update: function(flow){          console.debug("FIXME: Use UUID");          var idx = _.findIndex(this.flows, function(f){ -            return flow.request.timestamp_start == f.request.timestamp_start +            return flow.request.timestamp_start == f.request.timestamp_start;          });          if(idx < 0){ @@ -283,6 +284,9 @@ _.extend(FlowView.prototype, EventEmitter.prototype, {          } else {              this.flows[idx] = flow;          } +    }, +    update: function(flow){ +        this._update(flow);          this.emit("change");      },  }); @@ -294,6 +298,11 @@ function _FlowStore() {  _.extend(_FlowStore.prototype, EventEmitter.prototype, {      getView: function (since) {          var view = new FlowView(this, !since); + +        $.getJSON("/static/flows.json", function(flows){ +           view.add_bulk(flows);  +        }); +          return view;      },      handle: function (action) { @@ -442,7 +451,10 @@ var FlowRow = React.createClass({displayName: 'FlowRow',      render: function(){          var flow = this.props.flow;          var columns = this.props.columns.map(function(column){ -            return column({flow: flow}); +            return column({ +                key: column.displayName, +                flow: flow +            });          }.bind(this));          return React.DOM.tr(null, columns);      } @@ -460,55 +472,89 @@ var FlowTableHead = React.createClass({displayName: 'FlowTableHead',  var FlowTableBody = React.createClass({displayName: 'FlowTableBody',      render: function(){          var rows = this.props.flows.map(function(flow){ -            return FlowRow({flow: flow, columns: this.props.columns}) +            //TODO: Add UUID +            return FlowRow({flow: flow, columns: this.props.columns});          }.bind(this));          return React.DOM.tbody(null, rows);      }  }); + +var TLSColumn = React.createClass({displayName: 'TLSColumn', +    statics: { +        renderTitle: function(){ +            return React.DOM.th({key: "tls", className: "col-tls"}); +        } +    }, +    render: function(){ +        var flow = this.props.flow; +        var ssl = (flow.request.scheme == "https"); +        return React.DOM.td({className: ssl ? "col-tls-https" : "col-tls-http"}); +    } +}); + + +var IconColumn = React.createClass({displayName: 'IconColumn', +    statics: { +        renderTitle: function(){ +            return React.DOM.th({key: "icon", className: "col-icon"}); +        } +    }, +    render: function(){ +        var flow = this.props.flow; +        return React.DOM.td({className: "resource-icon resource-icon-plain"}); +    } +}); +  var PathColumn = React.createClass({displayName: 'PathColumn',      statics: {          renderTitle: function(){ -            return React.DOM.th({key: "PathColumn"}, "Path"); +            return React.DOM.th({key: "path", className: "col-path"}, "Path");          }      },      render: function(){          var flow = this.props.flow; -        return React.DOM.td({key: "PathColumn"}, flow.request.scheme + "://" + flow.request.host + flow.request.path); +        return React.DOM.td(null, flow.request.scheme + "://" + flow.request.host + flow.request.path);      }  }); + +  var MethodColumn = React.createClass({displayName: 'MethodColumn',      statics: {          renderTitle: function(){ -            return React.DOM.th({key: "MethodColumn"}, "Method"); +            return React.DOM.th({key: "method", className: "col-method"}, "Method");          }      },      render: function(){          var flow = this.props.flow; -        return React.DOM.td({key: "MethodColumn"}, flow.request.method); +        return React.DOM.td(null, flow.request.method);      }  }); + +  var StatusColumn = React.createClass({displayName: 'StatusColumn',      statics: {          renderTitle: function(){ -            return React.DOM.th({key: "StatusColumn"}, "Status"); +            return React.DOM.th({key: "status", className: "col-status"}, "Status");          }      },      render: function(){          var flow = this.props.flow;          var status;          if(flow.response){ -            status = flow.response.code + " " + flow.response.msg; +            status = flow.response.code;          } else {              status = null;          } -        return React.DOM.td({key: "StatusColumn"}, status); +        return React.DOM.td(null, status);      }  }); + +  var TimeColumn = React.createClass({displayName: 'TimeColumn',      statics: {          renderTitle: function(){ -            return React.DOM.th({key: "TimeColumn"}, "Time"); +            return React.DOM.th({key: "time", className: "col-time"}, "Time");          }      },      render: function(){ @@ -519,11 +565,13 @@ var TimeColumn = React.createClass({displayName: 'TimeColumn',          } else {              time = "...";          } -        return React.DOM.td({key: "TimeColumn"}, time); +        return React.DOM.td(null, time);      }  }); -var all_columns = [PathColumn, MethodColumn, StatusColumn, TimeColumn]; + +var all_columns = [TLSColumn, IconColumn, PathColumn, MethodColumn, StatusColumn, TimeColumn]; +  var FlowTable = React.createClass({displayName: 'FlowTable',      getInitialState: function () { diff --git a/web/gulpfile.js b/web/gulpfile.js index f34bc4a8..c1ca8e67 100644 --- a/web/gulpfile.js +++ b/web/gulpfile.js @@ -1,7 +1,7 @@  var gulp = require("gulp"); +var merge = require('merge-stream');  var concat = require('gulp-concat'); -var gutil = require('gulp-util');  var jshint = require("gulp-jshint");  var less = require("gulp-less");  var livereload = require("gulp-livereload"); @@ -10,7 +10,9 @@ var notify = require("gulp-notify");  var plumber = require("gulp-plumber");  var qunit = require("gulp-qunit");  var react = require("gulp-react"); +var rename = require("gulp-rename");  var sourcemaps = require('gulp-sourcemaps'); +var sprite = require('gulp-sprite-generator');  var uglify = require('gulp-uglify'); @@ -50,10 +52,12 @@ var path = {      },      css: {          vendor: ["css/vendor.less"], -        app: ["css/app.less"] +        app: ["css/app.less"], +        spritefile: "css/sprites.less"      },      fonts: ["src/vendor/fontawesome/fontawesome-webfont.*"], -    html: ["src/*.html", "!src/benchmark.html", "!src/test.html"] +    html: ["src/*.html", "!src/benchmark.html", "!src/test.html"], +    images: "src/images",  }; @@ -124,9 +128,24 @@ gulp.task("jshint", function () {          .pipe(dont_break_on_errors())          .pipe(react())          .pipe(jshint()) -        .pipe(jshint.reporter("jshint-stylish")) +        .pipe(jshint.reporter("jshint-stylish"));  }); +gulp.task("sprites", function () { +    // Sprite generator is a gulp task, which accepts options object and +    // returns two streams for style and image piping. +    var spriteOutput = gulp.src([path.css.spritefile], {base: "src", cwd: "src"}) +        .pipe(sprite({ +            spriteSheetName: "sprite.png", +            spriteSheetPath: "../images", +        })); +    var css = spriteOutput.css +        .pipe(rename({extname:".compiled.less"})) +        .pipe(gulp.dest("src/css")); +    var img = spriteOutput.img.pipe(gulp.dest(path.dist + "static/images")); +    // https://github.com/gulpjs/gulp/blob/master/docs/recipes/using-multiple-sources-in-one-task.md +    return merge(css, img); +});  gulp.task("html", function () {      return gulp.src(path.html) @@ -141,7 +160,7 @@ gulp.task('test', function() {  }); -common = ["fonts", "html", "jshint"]; +common = ["fonts", "html", "jshint", "sprites"];  gulp.task("dev", common.concat(["styles-dev", "scripts-dev"]));  gulp.task("prod", common.concat(["styles-prod", "scripts-prod"])); @@ -150,5 +169,6 @@ gulp.task("default", ["dev"], function () {      gulp.watch(["src/vendor/**"], ["scripts-vendor-dev", "styles-vendor-dev"]);      gulp.watch(["src/js/**"], ["scripts-app-dev", "jshint"]);      gulp.watch(["src/css/**"], ["styles-app-dev"]); +    gulp.watch(["src/images/**", "src/css/sprites.less"], ["sprites"]);      gulp.watch(["src/*.html"], ["html"]);  }); diff --git a/web/package.json b/web/package.json index 6adb6687..de90587f 100644 --- a/web/package.json +++ b/web/package.json @@ -15,10 +15,13 @@      "gulp-plumber": "^0.6.5",      "gulp-qunit": "^0.3.3",      "gulp-react": "^1.0.1", +    "gulp-rename": "^1.2.0",      "gulp-sourcemaps": "^1.1.5", +    "gulp-sprite-generator": "^0.2.0",      "gulp-uglify": "^1.0.1",      "gulp-util": "^3.0.1",      "jshint-stylish": "^0.4.0", +    "merge-stream": "^0.1.5",      "react": "",      "react-tools": ""    } diff --git a/web/src/css/app.less b/web/src/css/app.less index 1eec0687..39ac14cd 100644 --- a/web/src/css/app.less +++ b/web/src/css/app.less @@ -7,6 +7,7 @@ html {      box-sizing: inherit;  } +@import (less) "sprites.compiled.less";  @import (less) "layout.less";  @import (less) "header.less";  @import (less) "flowtable.less"; diff --git a/web/src/css/flowtable.less b/web/src/css/flowtable.less index 95f235f4..deef9c81 100644 --- a/web/src/css/flowtable.less +++ b/web/src/css/flowtable.less @@ -1,5 +1,34 @@  .flow-table {  	width: 100%; +	table-layout: fixed; +	thead { +		background-color: #dadada;   +	} +	td { +		overflow: hidden; +		white-space: nowrap; +		text-overflow: ellipsis; +	} + + +	.col-tls { +		width: 10px; +	} +	.col-tls-https { +		background-color: rgba(0, 185, 0, 0.5); +	} +	.col-icon { +		width: 32px; +	} +	.col-method { +		width: 60px; +	} +	.col-status { +		width: 50px; +	} +	.col-time { +		width: 120px; +	}  }
\ No newline at end of file diff --git a/web/src/css/sprites.compiled.less b/web/src/css/sprites.compiled.less new file mode 100644 index 00000000..27951ee5 --- /dev/null +++ b/web/src/css/sprites.compiled.less @@ -0,0 +1,58 @@ +.resource-icon { +    width: 32px; +    height: 32px; +} + +// From Chrome Dev Tools +.resource-icon-css { +	background-image: url("../images/sprite.png"); +    background-position: -0px -0px; +    background-size: 32px 320px!important; +} +.resource-icon-document { +	background-image: url("../images/sprite.png"); +    background-position: -0px -32px; +    background-size: 32px 320px!important; +} +.resource-icon-js { +	background-image: url("../images/sprite.png"); +    background-position: -0px -64px; +    background-size: 32px 320px!important; +} +.resource-icon-plain { +	background-image: url("../images/sprite.png"); +    background-position: -0px -96px; +    background-size: 32px 320px!important; +} + +// Own +.resource-icon-executable { +	background-image: url("../images/sprite.png"); +    background-position: -0px -128px; +    background-size: 32px 320px!important; +} +.resource-icon-flash { +	background-image: url("../images/sprite.png"); +    background-position: -0px -160px; +    background-size: 32px 320px!important; +} +.resource-icon-image { +	background-image: url("../images/sprite.png"); +    background-position: -0px -192px; +    background-size: 32px 320px!important; +} +.resource-icon-java { +	background-image: url("../images/sprite.png"); +    background-position: -0px -224px; +    background-size: 32px 320px!important; +} +.resource-icon-not-modified { +	background-image: url("../images/sprite.png"); +    background-position: -0px -256px; +    background-size: 32px 320px!important; +} +.resource-icon-redirect { +	background-image: url("../images/sprite.png"); +    background-position: -0px -288px; +    background-size: 32px 320px!important; +}
\ No newline at end of file diff --git a/web/src/css/sprites.less b/web/src/css/sprites.less new file mode 100644 index 00000000..9a63fdd3 --- /dev/null +++ b/web/src/css/sprites.less @@ -0,0 +1,38 @@ +.resource-icon { +    width: 32px; +    height: 32px; +} + +// From Chrome Dev Tools +.resource-icon-css { +	background-image: url(../images/chrome-devtools/resourceCSSIcon.png); +} +.resource-icon-document { +	background-image: url(../images/chrome-devtools/resourceDocumentIcon.png); +} +.resource-icon-js { +	background-image: url(../images/chrome-devtools/resourceJSIcon.png); +} +.resource-icon-plain { +	background-image: url(../images/chrome-devtools/resourcePlainIcon.png); +} + +// Own +.resource-icon-executable { +	background-image: url(../images/resourceExecutableIcon.png); +} +.resource-icon-flash { +	background-image: url(../images/resourceFlashIcon.png); +} +.resource-icon-image { +	background-image: url(../images/resourceImageIcon.png); +} +.resource-icon-java { +	background-image: url(../images/resourceJavaIcon.png); +} +.resource-icon-not-modified { +	background-image: url(../images/resourceNotModifiedIcon.png); +} +.resource-icon-redirect { +	background-image: url(../images/resourceRedirectIcon.png); +}
\ No newline at end of file diff --git a/web/src/images/chrome-devtools/LICENSE b/web/src/images/chrome-devtools/LICENSE new file mode 100644 index 00000000..6e4f8b9f --- /dev/null +++ b/web/src/images/chrome-devtools/LICENSE @@ -0,0 +1,30 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// +// The Chromium Authors can be found at +// http://src.chromium.org/svn/trunk/src/AUTHORS +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +//    * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +//    * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +//    * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file diff --git a/web/src/images/chrome-devtools/resourceCSSIcon.png b/web/src/images/chrome-devtools/resourceCSSIcon.pngBinary files differ new file mode 100644 index 00000000..18828d06 --- /dev/null +++ b/web/src/images/chrome-devtools/resourceCSSIcon.png diff --git a/web/src/images/chrome-devtools/resourceDocumentIcon.png b/web/src/images/chrome-devtools/resourceDocumentIcon.pngBinary files differ new file mode 100644 index 00000000..fdc10e47 --- /dev/null +++ b/web/src/images/chrome-devtools/resourceDocumentIcon.png diff --git a/web/src/images/chrome-devtools/resourceJSIcon.png b/web/src/images/chrome-devtools/resourceJSIcon.pngBinary files differ new file mode 100644 index 00000000..c1b72189 --- /dev/null +++ b/web/src/images/chrome-devtools/resourceJSIcon.png diff --git a/web/src/images/chrome-devtools/resourcePlainIcon.png b/web/src/images/chrome-devtools/resourcePlainIcon.pngBinary files differ new file mode 100644 index 00000000..8c82a4c7 --- /dev/null +++ b/web/src/images/chrome-devtools/resourcePlainIcon.png diff --git a/web/src/images/resourceExecutableIcon.png b/web/src/images/resourceExecutableIcon.pngBinary files differ new file mode 100644 index 00000000..fa70c2fd --- /dev/null +++ b/web/src/images/resourceExecutableIcon.png diff --git a/web/src/images/resourceFlashIcon.png b/web/src/images/resourceFlashIcon.pngBinary files differ new file mode 100644 index 00000000..ead5a4d0 --- /dev/null +++ b/web/src/images/resourceFlashIcon.png diff --git a/web/src/images/resourceImageIcon.png b/web/src/images/resourceImageIcon.pngBinary files differ new file mode 100644 index 00000000..23163042 --- /dev/null +++ b/web/src/images/resourceImageIcon.png diff --git a/web/src/images/resourceJavaIcon.png b/web/src/images/resourceJavaIcon.pngBinary files differ new file mode 100644 index 00000000..553b3391 --- /dev/null +++ b/web/src/images/resourceJavaIcon.png diff --git a/web/src/images/resourceNotModifiedIcon.png b/web/src/images/resourceNotModifiedIcon.pngBinary files differ new file mode 100644 index 00000000..9c6a879d --- /dev/null +++ b/web/src/images/resourceNotModifiedIcon.png diff --git a/web/src/images/resourceRedirectIcon.png b/web/src/images/resourceRedirectIcon.pngBinary files differ new file mode 100644 index 00000000..58fe3ac1 --- /dev/null +++ b/web/src/images/resourceRedirectIcon.png diff --git a/web/src/js/components/flowtable.jsx b/web/src/js/components/flowtable.jsx index 5e9f6718..a94e559f 100644 --- a/web/src/js/components/flowtable.jsx +++ b/web/src/js/components/flowtable.jsx @@ -4,7 +4,10 @@ var FlowRow = React.createClass({      render: function(){          var flow = this.props.flow;          var columns = this.props.columns.map(function(column){ -            return column({flow: flow}); +            return column({ +                key: column.displayName, +                flow: flow +            });          }.bind(this));          return <tr>{columns}</tr>;      } @@ -22,55 +25,89 @@ var FlowTableHead = React.createClass({  var FlowTableBody = React.createClass({      render: function(){          var rows = this.props.flows.map(function(flow){ -            return <FlowRow flow={flow} columns={this.props.columns}/> +            //TODO: Add UUID +            return <FlowRow flow={flow} columns={this.props.columns}/>;          }.bind(this));          return <tbody>{rows}</tbody>;      }  }); + +var TLSColumn = React.createClass({ +    statics: { +        renderTitle: function(){ +            return <th key="tls" className="col-tls"></th>; +        } +    }, +    render: function(){ +        var flow = this.props.flow; +        var ssl = (flow.request.scheme == "https"); +        return <td className={ssl ? "col-tls-https" : "col-tls-http"}></td>; +    } +}); + + +var IconColumn = React.createClass({ +    statics: { +        renderTitle: function(){ +            return <th key="icon" className="col-icon"></th>; +        } +    }, +    render: function(){ +        var flow = this.props.flow; +        return <td className="resource-icon resource-icon-plain"></td>; +    } +}); +  var PathColumn = React.createClass({      statics: {          renderTitle: function(){ -            return <th key="PathColumn">Path</th>; +            return <th key="path" className="col-path">Path</th>;          }      },      render: function(){          var flow = this.props.flow; -        return <td key="PathColumn">{flow.request.scheme + "://" + flow.request.host + flow.request.path}</td>; +        return <td>{flow.request.scheme + "://" + flow.request.host + flow.request.path}</td>;      }  }); + +  var MethodColumn = React.createClass({      statics: {          renderTitle: function(){ -            return <th key="MethodColumn">Method</th>; +            return <th key="method" className="col-method">Method</th>;          }      },      render: function(){          var flow = this.props.flow; -        return <td key="MethodColumn">{flow.request.method}</td>; +        return <td>{flow.request.method}</td>;      }  }); + +  var StatusColumn = React.createClass({      statics: {          renderTitle: function(){ -            return <th key="StatusColumn">Status</th>; +            return <th key="status" className="col-status">Status</th>;          }      },      render: function(){          var flow = this.props.flow;          var status;          if(flow.response){ -            status = flow.response.code + " " + flow.response.msg; +            status = flow.response.code;          } else {              status = null;          } -        return <td key="StatusColumn">{status}</td>; +        return <td>{status}</td>;      }  }); + +  var TimeColumn = React.createClass({      statics: {          renderTitle: function(){ -            return <th key="TimeColumn">Time</th>; +            return <th key="time" className="col-time">Time</th>;          }      },      render: function(){ @@ -81,11 +118,13 @@ var TimeColumn = React.createClass({          } else {              time = "...";          } -        return <td key="TimeColumn">{time}</td>; +        return <td>{time}</td>;      }  }); -var all_columns = [PathColumn, MethodColumn, StatusColumn, TimeColumn]; + +var all_columns = [TLSColumn, IconColumn, PathColumn, MethodColumn, StatusColumn, TimeColumn]; +  var FlowTable = React.createClass({      getInitialState: function () { diff --git a/web/src/js/stores/flowstore.js b/web/src/js/stores/flowstore.js index 006eeb24..a5cb74ba 100644 --- a/web/src/js/stores/flowstore.js +++ b/web/src/js/stores/flowstore.js @@ -30,13 +30,14 @@ _.extend(FlowView.prototype, EventEmitter.prototype, {          var updates = this.flows;          this.flows = flows;          updates.forEach(function(flow){ -            this.update(flow); +            this._update(flow);          }.bind(this)); +        this.emit("change");      }, -    update: function(flow){ +    _update: function(flow){          console.debug("FIXME: Use UUID");          var idx = _.findIndex(this.flows, function(f){ -            return flow.request.timestamp_start == f.request.timestamp_start +            return flow.request.timestamp_start == f.request.timestamp_start;          });          if(idx < 0){ @@ -44,6 +45,9 @@ _.extend(FlowView.prototype, EventEmitter.prototype, {          } else {              this.flows[idx] = flow;          } +    }, +    update: function(flow){ +        this._update(flow);          this.emit("change");      },  }); @@ -55,6 +59,11 @@ function _FlowStore() {  _.extend(_FlowStore.prototype, EventEmitter.prototype, {      getView: function (since) {          var view = new FlowView(this, !since); + +        $.getJSON("/static/flows.json", function(flows){ +           view.add_bulk(flows);  +        }); +          return view;      },      handle: function (action) { | 
