From 38bf34ebd9826c6f69d97906282632fbd5cff06b Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Thu, 27 Nov 2014 01:38:30 +0100 Subject: web++ --- web/src/js/app.js | 4 +- web/src/js/components/eventlog.jsx.js | 6 +- web/src/js/components/flowdetail.jsx.js | 148 ++++++++++++++----------- web/src/js/components/flowtable-columns.jsx.js | 3 - web/src/js/components/flowtable.jsx.js | 6 +- web/src/js/components/footer.jsx.js | 2 - web/src/js/components/header.jsx.js | 9 +- web/src/js/components/mainview.jsx.js | 120 +++++++++++--------- web/src/js/components/proxyapp.jsx.js | 31 +++--- web/src/js/components/utils.jsx.js | 2 - web/src/js/stores/flowstore.js | 28 +++-- 11 files changed, 193 insertions(+), 166 deletions(-) (limited to 'web/src/js') diff --git a/web/src/js/app.js b/web/src/js/app.js index 4ee35d60..b5d50d34 100644 --- a/web/src/js/app.js +++ b/web/src/js/app.js @@ -1,5 +1,7 @@ $(function () { - window.app = React.renderComponent(ProxyApp, document.body); + ReactRouter.run(routes, function (Handler) { + React.render(, document.body); + }); var UpdateConnection = new Connection("/updates"); UpdateConnection.onmessage = function (message) { var m = JSON.parse(message.data); diff --git a/web/src/js/components/eventlog.jsx.js b/web/src/js/components/eventlog.jsx.js index 08a6dfb4..34d84cdf 100644 --- a/web/src/js/components/eventlog.jsx.js +++ b/web/src/js/components/eventlog.jsx.js @@ -1,5 +1,3 @@ -/** @jsx React.DOM */ - var LogMessage = React.createClass({ render: function(){ var entry = this.props.entry; @@ -57,7 +55,8 @@ var EventLogContents = React.createClass({ }); var ToggleFilter = React.createClass({ - toggle: function(){ + toggle: function(e){ + e.preventDefault(); return this.props.toggleLevel(this.props.name); }, render: function(){ @@ -97,7 +96,6 @@ var EventLog = React.createClass({ var filter = this.state.filter; filter[level] = !filter[level]; this.setState({filter: filter}); - return false; }, render: function () { return ( diff --git a/web/src/js/components/flowdetail.jsx.js b/web/src/js/components/flowdetail.jsx.js index 3ba025a9..5c4168a9 100644 --- a/web/src/js/components/flowdetail.jsx.js +++ b/web/src/js/components/flowdetail.jsx.js @@ -1,34 +1,32 @@ -/** @jsx React.DOM */ - var FlowDetailNav = React.createClass({ - render: function(){ + render: function () { - var items = this.props.tabs.map(function(e){ + var items = this.props.tabs.map(function (e) { var str = e.charAt(0).toUpperCase() + e.slice(1); var className = this.props.active === e ? "active" : ""; - var onClick = function(){ + var onClick = function (e) { this.props.selectTab(e); - return false; + e.preventDefault(); }.bind(this); return {str}; + href="#" + className={className} + onClick={onClick}>{str}; }.bind(this)); return ( ); - } + } }); var Headers = React.createClass({ - render: function(){ - var rows = this.props.message.headers.map(function(header, i){ + render: function () { + var rows = this.props.message.headers.map(function (header, i) { return ( - {header[0]+":"} + {header[0] + ":"} {header[1]} ); @@ -44,16 +42,16 @@ var Headers = React.createClass({ }); var FlowDetailRequest = React.createClass({ - render: function(){ + render: function () { var flow = this.props.flow; var first_line = [ - flow.request.method, - RequestUtils.pretty_url(flow.request), - "HTTP/"+ flow.response.httpversion.join(".") - ].join(" "); + flow.request.method, + RequestUtils.pretty_url(flow.request), + "HTTP/" + flow.response.httpversion.join(".") + ].join(" "); var content = null; - if(flow.request.contentLength > 0){ - content = "Request Content Size: "+ formatSize(flow.request.contentLength); + if (flow.request.contentLength > 0) { + content = "Request Content Size: " + formatSize(flow.request.contentLength); } else { content =
No Content
; } @@ -72,16 +70,16 @@ var FlowDetailRequest = React.createClass({ }); var FlowDetailResponse = React.createClass({ - render: function(){ + render: function () { var flow = this.props.flow; var first_line = [ - "HTTP/"+ flow.response.httpversion.join("."), - flow.response.code, - flow.response.msg - ].join(" "); + "HTTP/" + flow.response.httpversion.join("."), + flow.response.code, + flow.response.msg + ].join(" "); var content = null; - if(flow.response.contentLength > 0){ - content = "Response Content Size: "+ formatSize(flow.response.contentLength); + if (flow.response.contentLength > 0) { + content = "Response Content Size: " + formatSize(flow.response.contentLength); } else { content =
No Content
; } @@ -100,42 +98,53 @@ var FlowDetailResponse = React.createClass({ }); var TimeStamp = React.createClass({ - render: function() { + render: function () { - if(!this.props.t){ + if (!this.props.t) { //should be return null, but that triggers a React bug. return ; } var ts = (new Date(this.props.t * 1000)).toISOString(); - ts = ts.replace("T", " ").replace("Z",""); + ts = ts.replace("T", " ").replace("Z", ""); var delta; - if(this.props.deltaTo){ - delta = formatTimeDelta(1000 * (this.props.t-this.props.deltaTo)); + if (this.props.deltaTo) { + delta = formatTimeDelta(1000 * (this.props.t - this.props.deltaTo)); delta = {"(" + delta + ")"}; } else { delta = null; } - return {this.props.title + ":"}{ts} {delta}; + return + {this.props.title + ":"} + {ts} {delta} + ; } }); var ConnectionInfo = React.createClass({ - render: function() { + render: function () { var conn = this.props.conn; var address = conn.address.address.join(":"); var sni = ; //should be null, but that triggers a React bug. - if(conn.sni){ - sni = TLS SNI:{conn.sni}; + if (conn.sni) { + sni = + + TLS SNI: + + {conn.sni} + ; } return ( - + + + + {sni}
Address:{address}
Address:{address}
@@ -144,7 +153,7 @@ var ConnectionInfo = React.createClass({ }); var CertificateInfo = React.createClass({ - render: function(){ + render: function () { //TODO: We should fetch human-readable certificate representation // from the server var flow = this.props.flow; @@ -165,7 +174,7 @@ var CertificateInfo = React.createClass({ }); var Timing = React.createClass({ - render: function(){ + render: function () { var flow = this.props.flow; var sc = flow.server_conn; var cc = flow.client_conn; @@ -218,46 +227,46 @@ var Timing = React.createClass({ } //Add unique key for each row. - timestamps.forEach(function(e){ + timestamps.forEach(function (e) { e.key = e.title; }); timestamps = _.sortBy(timestamps, 't'); - var rows = timestamps.map(function(e){ - return TimeStamp(e); + var rows = timestamps.map(function (e) { + return ; }); return (
-

Timing

- - +

Timing

+
+ {rows} - -
+ +
); } }); var FlowDetailConnectionInfo = React.createClass({ - render: function(){ + render: function () { var flow = this.props.flow; var client_conn = flow.client_conn; var server_conn = flow.server_conn; return (
-

Client Connection

- +

Client Connection

+ -

Server Connection

- +

Server Connection

+ - + - +
); @@ -271,29 +280,38 @@ var tabs = { }; var FlowDetail = React.createClass({ - getDefaultProps: function(){ + getDefaultProps: function () { return { - tabs: ["request","response", "details"] + tabs: ["request", "response", "details"] }; }, - mixins: [StickyHeadMixin], - nextTab: function(i) { + mixins: [StickyHeadMixin, ReactRouter.Navigation, ReactRouter.State], + nextTab: function (i) { var currentIndex = this.props.tabs.indexOf(this.props.active); // JS modulo operator doesn't correct negative numbers, make sure that we are positive. var nextIndex = (currentIndex + i + this.props.tabs.length) % this.props.tabs.length; - this.props.selectTab(this.props.tabs[nextIndex]); + this.selectTab(this.props.tabs[nextIndex]); + }, + selectTab: function (panel) { + this.replaceWith( + "flow", + { + flowId: this.getParams().flowId, + detailTab: panel + } + ); }, - render: function(){ + render: function () { var flow = JSON.stringify(this.props.flow, null, 2); var Tab = tabs[this.props.active]; return (
+ tabs={this.props.tabs} + active={this.props.active} + selectTab={this.selectTab}/>
- ); - } + ); + } }); \ No newline at end of file diff --git a/web/src/js/components/flowtable-columns.jsx.js b/web/src/js/components/flowtable-columns.jsx.js index b7db71b7..8a44c072 100644 --- a/web/src/js/components/flowtable-columns.jsx.js +++ b/web/src/js/components/flowtable-columns.jsx.js @@ -1,6 +1,3 @@ -/** @jsx React.DOM */ - - var TLSColumn = React.createClass({ statics: { renderTitle: function(){ diff --git a/web/src/js/components/flowtable.jsx.js b/web/src/js/components/flowtable.jsx.js index fc4d8fbc..2baf728f 100644 --- a/web/src/js/components/flowtable.jsx.js +++ b/web/src/js/components/flowtable.jsx.js @@ -1,10 +1,8 @@ -/** @jsx React.DOM */ - var FlowRow = React.createClass({ render: function(){ var flow = this.props.flow; - var columns = this.props.columns.map(function(column){ - return ; + var columns = this.props.columns.map(function(Column){ + return ; }.bind(this)); var className = ""; if(this.props.selected){ diff --git a/web/src/js/components/footer.jsx.js b/web/src/js/components/footer.jsx.js index 9bcbbc2a..6ba253bf 100644 --- a/web/src/js/components/footer.jsx.js +++ b/web/src/js/components/footer.jsx.js @@ -1,5 +1,3 @@ -/** @jsx React.DOM */ - var Footer = React.createClass({ render: function () { var mode = this.props.settings.mode; diff --git a/web/src/js/components/header.jsx.js b/web/src/js/components/header.jsx.js index 994bc759..5c905889 100644 --- a/web/src/js/components/header.jsx.js +++ b/web/src/js/components/header.jsx.js @@ -1,5 +1,3 @@ -/** @jsx React.DOM */ - var MainMenu = React.createClass({ statics: { title: "Traffic", @@ -48,15 +46,16 @@ var header_entries = [MainMenu, ToolsMenu, ReportsMenu]; var Header = React.createClass({ + mixins: [ReactRouter.Navigation], getInitialState: function () { return { active: header_entries[0] }; }, - handleClick: function (active) { - ReactRouter.transitionTo(active.route); + handleClick: function (active, e) { + e.preventDefault(); + this.transitionTo(active.route); this.setState({active: active}); - return false; }, handleFileClick: function () { console.log("File click"); diff --git a/web/src/js/components/mainview.jsx.js b/web/src/js/components/mainview.jsx.js index f0dfb59a..799bd0d2 100644 --- a/web/src/js/components/mainview.jsx.js +++ b/web/src/js/components/mainview.jsx.js @@ -1,71 +1,82 @@ -/** @jsx React.DOM */ - var MainView = React.createClass({ - getInitialState: function() { + mixins: [ReactRouter.Navigation, ReactRouter.State], + getInitialState: function () { return { - flows: [], + flows: [] }; }, + componentWillReceiveProps: function (nextProps) { + if (nextProps.flowStore !== this.props.flowStore) { + this.closeView(); + this.openView(nextProps.flowStore); + } + }, + openView: function (store) { + var view = new FlowView(store); + this.setState({ + view: view + }); + view.addListener("add", this.onFlowChange); + view.addListener("update", this.onFlowChange); + view.addListener("remove", this.onFlowChange); + view.addListener("recalculate", this.onFlowChange); + }, + closeView: function () { + this.state.view.close(); + }, componentDidMount: function () { - //FIXME: The store should be global, move out of here. - window.flowstore = new LiveFlowStore(); - - this.flowStore = window.flowstore.open_view(); - this.flowStore.addListener("add",this.onFlowChange); - this.flowStore.addListener("update",this.onFlowChange); - this.flowStore.addListener("remove",this.onFlowChange); - this.flowStore.addListener("recalculate",this.onFlowChange); + this.openView(this.props.flowStore); }, componentWillUnmount: function () { - this.flowStore.removeListener("change",this.onFlowChange); - this.flowStore.close(); + this.closeView(); }, onFlowChange: function () { + console.warn("onFlowChange is deprecated"); this.setState({ - flows: this.flowStore.flows + flows: this.state.view.flows }); }, - selectDetailTab: function(panel) { - ReactRouter.replaceWith( - "flow", - { - flowId: this.props.params.flowId, - detailTab: panel - } - ); - }, - selectFlow: function(flow) { - if(flow){ - ReactRouter.replaceWith( - "flow", + selectFlow: function (flow) { + if (flow) { + this.replaceWith( + "flow", { flowId: flow.id, - detailTab: this.props.params.detailTab || "request" + detailTab: this.getParams().detailTab || "request" } ); - this.refs.flowTable.scrollIntoView(flow); + console.log("TODO: Scroll into view"); + //this.refs.flowTable.scrollIntoView(flow); } else { - ReactRouter.replaceWith("flows"); + this.replaceWith("flows"); } }, - selectFlowRelative: function(i){ + selectFlowRelative: function (i) { + var flows = this.state.view.flows; var index; - if(!this.props.params.flowId){ - if(i > 0){ - index = this.state.flows.length-1; + if (!this.getParams().flowId) { + if (i > 0) { + index = flows.length - 1; } else { index = 0; } } else { - index = _.findIndex(this.state.flows, function(f){ - return f.id === this.props.params.flowId; - }.bind(this)); - index = Math.min(Math.max(0, index+i), this.state.flows.length-1); + i = flows.length; + var currFlowId = this.getParams().flowId; + while (i--) { + if (flows[i].id === currFlowId) { + index = i; + break; + } + } + index = Math.min( + Math.max(0, index + i), + flows.length - 1); } - this.selectFlow(this.state.flows[index]); + this.selectFlow(flows[index]); }, - onKeyDown: function(e){ - switch(e.keyCode){ + onKeyDown: function (e) { + switch (e.keyCode) { case Key.K: case Key.UP: this.selectFlowRelative(-1); @@ -86,14 +97,14 @@ var MainView = React.createClass({ break; case Key.H: case Key.LEFT: - if(this.refs.flowDetails){ + if (this.refs.flowDetails) { this.refs.flowDetails.nextTab(-1); } break; case Key.L: case Key.TAB: case Key.RIGHT: - if(this.refs.flowDetails){ + if (this.refs.flowDetails) { this.refs.flowDetails.nextTab(+1); } break; @@ -101,18 +112,17 @@ var MainView = React.createClass({ console.debug("keydown", e.keyCode); return; } - return false; + e.preventDefault(); }, - render: function() { - var selected = _.find(this.state.flows, { id: this.props.params.flowId }); + render: function () { + var selected = this.props.flowStore.get(this.getParams().flowId); var details; - if(selected){ + if (selected) { details = ( - + ); } else { details = null; @@ -121,9 +131,9 @@ var MainView = React.createClass({ return (
+ flows={this.state.view ? this.state.view.flows : []} + selectFlow={this.selectFlow} + selected={selected} /> { details ? : null } {details}
diff --git a/web/src/js/components/proxyapp.jsx.js b/web/src/js/components/proxyapp.jsx.js index ff6e8da1..3545cfe0 100644 --- a/web/src/js/components/proxyapp.jsx.js +++ b/web/src/js/components/proxyapp.jsx.js @@ -1,5 +1,3 @@ -/** @jsx React.DOM */ - //TODO: Move out of here, just a stub. var Reports = React.createClass({ render: function () { @@ -10,7 +8,10 @@ var Reports = React.createClass({ var ProxyAppMain = React.createClass({ getInitialState: function () { - return { settings: SettingsStore.getAll() }; + return { + settings: SettingsStore.getAll(), + flowStore: new LiveFlowStore() + }; }, componentDidMount: function () { SettingsStore.addListener("change", this.onSettingsChange); @@ -25,30 +26,28 @@ var ProxyAppMain = React.createClass({ return (
- + {this.state.settings.showEventLog ? : null} {this.state.settings.showEventLog ? : null}
- ); + ); } }); -var Routes = ReactRouter.Routes; var Route = ReactRouter.Route; +var RouteHandler = ReactRouter.RouteHandler; var Redirect = ReactRouter.Redirect; var DefaultRoute = ReactRouter.DefaultRoute; var NotFoundRoute = ReactRouter.NotFoundRoute; -var ProxyApp = ( - - - - - - - - - ); \ No newline at end of file +var routes = ( + + + + + + +); \ No newline at end of file diff --git a/web/src/js/components/utils.jsx.js b/web/src/js/components/utils.jsx.js index 91cb8458..22aca577 100644 --- a/web/src/js/components/utils.jsx.js +++ b/web/src/js/components/utils.jsx.js @@ -1,5 +1,3 @@ -/** @jsx React.DOM */ - //React utils. For other utilities, see ../utils.js var Splitter = React.createClass({ diff --git a/web/src/js/stores/flowstore.js b/web/src/js/stores/flowstore.js index 53048441..91a5d9ec 100644 --- a/web/src/js/stores/flowstore.js +++ b/web/src/js/stores/flowstore.js @@ -37,13 +37,8 @@ _.extend(FlowStore.prototype, { this._pos_map[flow.id] = i; } }, - open_view: function (filt, sort) { - var view = new FlowView(this._flow_list, filt, sort); - this._views.push(view); - return view; - }, - close_view: function (view) { - this._views = _.without(this._views, view); + get: function(flow_id){ + return this._flow_list[this._pos_map[flow_id]]; } }); @@ -60,6 +55,15 @@ function LiveFlowStore(endpoint) { }.bind(this); } _.extend(LiveFlowStore.prototype, FlowStore.prototype, { + close: function(){ + this.conn.close(); + }, + add: function(flow) { + // Make sure that deferred adds don't add an element twice. + if(!this._pos_map[flow.id]){ + FlowStore.prototype.add.call(this, flow); + } + }, handle_update: function (type, data) { console.log("LiveFlowStore.handle_update", type, data); if (this.updates_before_init) { @@ -100,16 +104,22 @@ SortByInsertionOrder.prototype.key = function (flow) { var default_sort = (new SortByInsertionOrder()).key; -function FlowView(flows, filt, sort) { +function FlowView(store, filt, sort) { EventEmitter.call(this); filt = filt || function (flow) { return true; }; sort = sort || default_sort; - this.recalculate(flows, filt, sort); + + this.store = store; + this.store._views.push(this); + this.recalculate(this.store._flow_list, filt, sort); } _.extend(FlowView.prototype, EventEmitter.prototype, { + close: function(){ + this.store._views = _.without(this.store._views, this); + }, recalculate: function (flows, filt, sort) { if (filt) { this.filt = filt; -- cgit v1.2.3