From ed8249023fb7c0d429b9278c63b51ac071700987 Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Wed, 26 Nov 2014 04:18:21 +0100 Subject: introduce revised views, port over changes from multiple_views branch --- web/src/js/app.js | 8 +- web/src/js/components/mainview.jsx.js | 12 +- web/src/js/connection.js | 51 ++++---- web/src/js/stores/flowstore.js | 221 +++++++++++++++++++++++----------- 4 files changed, 194 insertions(+), 98 deletions(-) (limited to 'web/src/js') diff --git a/web/src/js/app.js b/web/src/js/app.js index 736072dc..4ee35d60 100644 --- a/web/src/js/app.js +++ b/web/src/js/app.js @@ -1,4 +1,8 @@ $(function () { - Connection.init(); - app = React.renderComponent(ProxyApp, document.body); + window.app = React.renderComponent(ProxyApp, document.body); + var UpdateConnection = new Connection("/updates"); + UpdateConnection.onmessage = function (message) { + var m = JSON.parse(message.data); + AppDispatcher.dispatchServerAction(m); + }; }); \ No newline at end of file diff --git a/web/src/js/components/mainview.jsx.js b/web/src/js/components/mainview.jsx.js index 795b8136..f0dfb59a 100644 --- a/web/src/js/components/mainview.jsx.js +++ b/web/src/js/components/mainview.jsx.js @@ -7,8 +7,14 @@ var MainView = React.createClass({ }; }, componentDidMount: function () { - this.flowStore = FlowStore.getView(); - this.flowStore.addListener("change",this.onFlowChange); + //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); }, componentWillUnmount: function () { this.flowStore.removeListener("change",this.onFlowChange); @@ -16,7 +22,7 @@ var MainView = React.createClass({ }, onFlowChange: function () { this.setState({ - flows: this.flowStore.getAll() + flows: this.flowStore.flows }); }, selectDetailTab: function(panel) { diff --git a/web/src/js/connection.js b/web/src/js/connection.js index 3edbfc20..64d550bf 100644 --- a/web/src/js/connection.js +++ b/web/src/js/connection.js @@ -1,33 +1,38 @@ -function _Connection(url) { - this.url = url; +function Connection(url) { + if(url[0] != "/"){ + this.url = url; + } else { + this.url = location.origin.replace("http", "ws") + url; + } + var ws = new WebSocket(this.url); + ws.onopen = function(){ + this.onopen.apply(this, arguments); + }.bind(this); + ws.onmessage = function(){ + this.onmessage.apply(this, arguments); + }.bind(this); + ws.onerror = function(){ + this.onerror.apply(this, arguments); + }.bind(this); + ws.onclose = function(){ + this.onclose.apply(this, arguments); + }.bind(this); + this.ws = ws; } -_Connection.prototype.init = function () { - this.openWebSocketConnection(); -}; -_Connection.prototype.openWebSocketConnection = function () { - this.ws = new WebSocket(this.url.replace("http", "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) { +Connection.prototype.onopen = function (open) { console.debug("onopen", this, arguments); }; -_Connection.prototype.onmessage = function (message) { - //AppDispatcher.dispatchServerAction(...); - var m = JSON.parse(message.data); - AppDispatcher.dispatchServerAction(m); +Connection.prototype.onmessage = function (message) { + console.warn("onmessage (not implemented)", this, message.data); }; -_Connection.prototype.onerror = function (error) { +Connection.prototype.onerror = function (error) { EventLogActions.add_event("WebSocket Connection Error."); console.debug("onerror", this, arguments); }; -_Connection.prototype.onclose = function (close) { +Connection.prototype.onclose = function (close) { EventLogActions.add_event("WebSocket Connection closed."); console.debug("onclose", this, arguments); }; - -var Connection = new _Connection(location.origin + "/updates"); +Connection.prototype.close = function(){ + this.ws.close(); +}; \ No newline at end of file diff --git a/web/src/js/stores/flowstore.js b/web/src/js/stores/flowstore.js index 7c0bddbd..53048441 100644 --- a/web/src/js/stores/flowstore.js +++ b/web/src/js/stores/flowstore.js @@ -1,91 +1,172 @@ -function FlowView(store, live) { - EventEmitter.call(this); - this._store = store; - this.live = live; - this.flows = []; - - this.add = this.add.bind(this); - this.update = this.update.bind(this); - - if (live) { - this._store.addListener(ActionTypes.ADD_FLOW, this.add); - this._store.addListener(ActionTypes.UPDATE_FLOW, this.update); - } +function FlowStore(endpoint) { + this._views = []; + this.reset(); } - -_.extend(FlowView.prototype, EventEmitter.prototype, { - close: function () { - this._store.removeListener(ActionTypes.ADD_FLOW, this.add); - this._store.removeListener(ActionTypes.UPDATE_FLOW, this.update); +_.extend(FlowStore.prototype, { + add: function (flow) { + this._pos_map[flow.id] = this._flow_list.length; + this._flow_list.push(flow); + for (var i = 0; i < this._views.length; i++) { + this._views[i].add(flow); + } }, - getAll: function () { - return this.flows; + update: function (flow) { + this._flow_list[this._pos_map[flow.id]] = flow; + for (var i = 0; i < this._views.length; i++) { + this._views[i].update(flow); + } }, - add: function (flow) { - return this.update(flow); + remove: function (flow_id) { + this._flow_list.splice(this._pos_map[flow_id], 1); + this._build_map(); + for (var i = 0; i < this._views.length; i++) { + this._views[i].remove(flow_id); + } }, - add_bulk: function (flows) { - //Treat all previously received updates as newer than the bulk update. - //If they weren't newer, we're about to receive an update for them very soon. - var updates = this.flows; - this.flows = flows; - updates.forEach(function(flow){ - this._update(flow); - }.bind(this)); - this.emit("change"); + reset: function (flows) { + this._flow_list = flows || []; + this._build_map(); + for (var i = 0; i < this._views.length; i++) { + this._views[i].recalculate(this._flow_list); + } + }, + _build_map: function () { + this._pos_map = {}; + for (var i = 0; i < this._flow_list.length; i++) { + var flow = this._flow_list[i]; + 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; }, - _update: function(flow){ - var idx = _.findIndex(this.flows, function(f){ - return flow.id === f.id; - }); + close_view: function (view) { + this._views = _.without(this._views, view); + } +}); + - if(idx < 0){ - this.flows.push(flow); - //if(this.flows.length > 100){ - // this.flows.shift(); - //} +function LiveFlowStore(endpoint) { + FlowStore.call(this); + this.updates_before_init = []; // (empty array is true in js) + this.endpoint = endpoint || "/flows"; + this.conn = new Connection(this.endpoint + "/updates"); + this.conn.onopen = this._onopen.bind(this); + this.conn.onmessage = function (e) { + var message = JSON.parse(e.data); + this.handle_update(message.type, message.data); + }.bind(this); +} +_.extend(LiveFlowStore.prototype, FlowStore.prototype, { + handle_update: function (type, data) { + console.log("LiveFlowStore.handle_update", type, data); + if (this.updates_before_init) { + console.log("defer update", type, data); + this.updates_before_init.push(arguments); } else { - this.flows[idx] = flow; + this[type](data); } }, - update: function(flow){ - this._update(flow); - this.emit("change"); + handle_fetch: function (data) { + console.log("Flows fetched."); + this.reset(data.flows); + var updates = this.updates_before_init; + this.updates_before_init = false; + for (var i = 0; i < updates.length; i++) { + this.handle_update.apply(this, updates[i]); + } + }, + _onopen: function () { + //Update stream openend, fetch list of flows. + console.log("Update Connection opened, fetching flows..."); + $.getJSON(this.endpoint, this.handle_fetch.bind(this)); }, }); +function SortByInsertionOrder() { + this.i = 0; + this.map = {}; + this.key = this.key.bind(this); +} +SortByInsertionOrder.prototype.key = function (flow) { + if (!(flow.id in this.map)) { + this.i++; + this.map[flow.id] = this.i; + } + return this.map[flow.id]; +}; + +var default_sort = (new SortByInsertionOrder()).key; -function _FlowStore() { +function FlowView(flows, filt, sort) { EventEmitter.call(this); + filt = filt || function (flow) { + return true; + }; + sort = sort || default_sort; + this.recalculate(flows, filt, sort); } -_.extend(_FlowStore.prototype, EventEmitter.prototype, { - getView: function (since) { - var view = new FlowView(this, !since); - - $.getJSON("/static/flows.json", function(flows){ - flows = flows.concat(_.cloneDeep(flows)).concat(_.cloneDeep(flows)); - var id = 1; - flows.forEach(function(flow){ - flow.id = "uuid-" + id++; - }); - view.add_bulk(flows); - }); +_.extend(FlowView.prototype, EventEmitter.prototype, { + recalculate: function (flows, filt, sort) { + if (filt) { + this.filt = filt; + } + if (sort) { + this.sort = sort; + } + this.flows = flows.filter(this.filt); + this.flows.sort(function (a, b) { + return this.sort(a) - this.sort(b); + }.bind(this)); + this.emit("recalculate"); + }, + add: function (flow) { + if (this.filt(flow)) { + var idx = _.sortedIndex(this.flows, flow, this.sort); + if (idx === this.flows.length) { //happens often, .push is way faster. + this.flows.push(flow); + } else { + this.flows.splice(idx, 0, flow); + } + this.emit("add", flow, idx); + } + }, + update: function (flow) { + var idx; + var i = this.flows.length; + // Search from the back, we usually update the latest flows. + while (i--) { + if (this.flows[i].id === flow.id) { + idx = i; + break; + } + } - return view; + if (idx === -1) { //not contained in list + this.add(flow); + } else if (!this.filt(flow)) { + this.remove(flow.id); + } else { + if (this.sort(this.flows[idx]) !== this.sort(flow)) { //sortpos has changed + this.remove(this.flows[idx]); + this.add(flow); + } else { + this.flows[idx] = flow; + this.emit("update", flow, idx); + } + } }, - handle: function (action) { - switch (action.type) { - case ActionTypes.ADD_FLOW: - case ActionTypes.UPDATE_FLOW: - this.emit(action.type, action.data); + remove: function (flow_id) { + var i = this.flows.length; + while (i--) { + if (this.flows[i].id === flow_id) { + this.flows.splice(i, 1); + this.emit("remove", flow_id, i); break; - default: - return; + } } } -}); - - -var FlowStore = new _FlowStore(); -AppDispatcher.register(FlowStore.handle.bind(FlowStore)); +}); \ No newline at end of file -- cgit v1.2.3 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 From e41c0be293502619b6a072fb9bcd8bbd8694b86a Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Thu, 27 Nov 2014 01:40:26 +0100 Subject: format code --- web/src/js/actions.js | 4 +- web/src/js/components/eventlog.jsx.js | 24 +++++----- web/src/js/components/flowtable-columns.jsx.js | 50 +++++++++++---------- web/src/js/components/flowtable.jsx.js | 56 ++++++++++++----------- web/src/js/components/footer.jsx.js | 2 +- web/src/js/components/header.jsx.js | 23 +++++----- web/src/js/components/utils.jsx.js | 52 ++++++++++----------- web/src/js/connection.js | 12 ++--- web/src/js/dispatcher.js | 2 +- web/src/js/stores/eventlogstore.js | 62 +++++++++++++------------- web/src/js/stores/flowstore.js | 10 ++--- web/src/js/utils.js | 4 +- 12 files changed, 153 insertions(+), 148 deletions(-) (limited to 'web/src/js') diff --git a/web/src/js/actions.js b/web/src/js/actions.js index 9211403f..3e7510ad 100644 --- a/web/src/js/actions.js +++ b/web/src/js/actions.js @@ -25,13 +25,13 @@ var SettingsActions = { var event_id = 0; var EventLogActions = { - add_event: function(message){ + add_event: function (message) { AppDispatcher.dispatchViewAction({ type: ActionTypes.ADD_EVENT, data: { message: message, level: "web", - id: "viewAction-"+event_id++ + id: "viewAction-" + event_id++ } }); } diff --git a/web/src/js/components/eventlog.jsx.js b/web/src/js/components/eventlog.jsx.js index 34d84cdf..4d61a39c 100644 --- a/web/src/js/components/eventlog.jsx.js +++ b/web/src/js/components/eventlog.jsx.js @@ -1,8 +1,8 @@ var LogMessage = React.createClass({ - render: function(){ + render: function () { var entry = this.props.entry; var indicator; - switch(entry.level){ + switch (entry.level) { case "web": indicator = ; break; @@ -18,13 +18,13 @@ var LogMessage = React.createClass({ ); }, - shouldComponentUpdate: function(){ + shouldComponentUpdate: function () { return false; // log entries are immutable. } }); var EventLogContents = React.createClass({ - mixins:[AutoScrollMixin], + mixins: [AutoScrollMixin], getInitialState: function () { return { log: [] @@ -44,8 +44,8 @@ var EventLogContents = React.createClass({ }); }, render: function () { - var messages = this.state.log.map(function(row) { - if(!this.props.filter[row.level]){ + var messages = this.state.log.map(function (row) { + if (!this.props.filter[row.level]) { return null; } return ; @@ -55,11 +55,11 @@ var EventLogContents = React.createClass({ }); var ToggleFilter = React.createClass({ - toggle: function(e){ + toggle: function (e) { e.preventDefault(); return this.props.toggleLevel(this.props.name); }, - render: function(){ + render: function () { var className = "label "; if (this.props.active) { className += "label-primary"; @@ -74,11 +74,11 @@ var ToggleFilter = React.createClass({ {this.props.name} ); - } + } }); var EventLog = React.createClass({ - getInitialState: function(){ + getInitialState: function () { return { filter: { "debug": false, @@ -92,7 +92,7 @@ var EventLog = React.createClass({ showEventLog: false }); }, - toggleLevel: function(level){ + toggleLevel: function (level) { var filter = this.state.filter; filter[level] = !filter[level]; this.setState({filter: filter}); @@ -101,7 +101,7 @@ var EventLog = React.createClass({ return (
- Eventlog + Eventlog
diff --git a/web/src/js/components/flowtable-columns.jsx.js b/web/src/js/components/flowtable-columns.jsx.js index 8a44c072..1aa256c4 100644 --- a/web/src/js/components/flowtable-columns.jsx.js +++ b/web/src/js/components/flowtable-columns.jsx.js @@ -1,14 +1,14 @@ var TLSColumn = React.createClass({ statics: { - renderTitle: function(){ + renderTitle: function () { return ; } }, - render: function(){ + render: function () { var flow = this.props.flow; var ssl = (flow.request.scheme == "https"); var classes; - if(ssl){ + if (ssl) { classes = "col-tls col-tls-https"; } else { classes = "col-tls col-tls-http"; @@ -20,23 +20,23 @@ var TLSColumn = React.createClass({ var IconColumn = React.createClass({ statics: { - renderTitle: function(){ + renderTitle: function () { return ; } }, - render: function(){ + render: function () { var flow = this.props.flow; var icon; - if(flow.response){ + if (flow.response) { var contentType = ResponseUtils.getContentType(flow.response); //TODO: We should assign a type to the flow somewhere else. - if(flow.response.code == 304) { + if (flow.response.code == 304) { icon = "resource-icon-not-modified"; - } else if(300 <= flow.response.code && flow.response.code < 400) { + } else if (300 <= flow.response.code && flow.response.code < 400) { icon = "resource-icon-redirect"; - } else if(contentType && contentType.indexOf("image") >= 0) { + } else if (contentType && contentType.indexOf("image") >= 0) { icon = "resource-icon-image"; } else if (contentType && contentType.indexOf("javascript") >= 0) { icon = "resource-icon-js"; @@ -46,23 +46,25 @@ var IconColumn = React.createClass({ icon = "resource-icon-document"; } } - if(!icon){ + if (!icon) { icon = "resource-icon-plain"; } icon += " resource-icon"; - return
; + return +
+ ; } }); var PathColumn = React.createClass({ statics: { - renderTitle: function(){ + renderTitle: function () { return Path; } }, - render: function(){ + render: function () { var flow = this.props.flow; return {flow.request.scheme + "://" + flow.request.host + flow.request.path}; } @@ -71,11 +73,11 @@ var PathColumn = React.createClass({ var MethodColumn = React.createClass({ statics: { - renderTitle: function(){ + renderTitle: function () { return Method; } }, - render: function(){ + render: function () { var flow = this.props.flow; return {flow.request.method}; } @@ -84,14 +86,14 @@ var MethodColumn = React.createClass({ var StatusColumn = React.createClass({ statics: { - renderTitle: function(){ + renderTitle: function () { return Status; } }, - render: function(){ + render: function () { var flow = this.props.flow; var status; - if(flow.response){ + if (flow.response) { status = flow.response.code; } else { status = null; @@ -103,15 +105,15 @@ var StatusColumn = React.createClass({ var SizeColumn = React.createClass({ statics: { - renderTitle: function(){ + renderTitle: function () { return Size; } }, - render: function(){ + render: function () { var flow = this.props.flow; var total = flow.request.contentLength; - if(flow.response){ + if (flow.response) { total += flow.response.contentLength || 0; } var size = formatSize(total); @@ -122,14 +124,14 @@ var SizeColumn = React.createClass({ var TimeColumn = React.createClass({ statics: { - renderTitle: function(){ + renderTitle: function () { return Time; } }, - render: function(){ + render: function () { var flow = this.props.flow; var time; - if(flow.response){ + if (flow.response) { time = formatTimeDelta(1000 * (flow.response.timestamp_end - flow.request.timestamp_start)); } else { time = "..."; diff --git a/web/src/js/components/flowtable.jsx.js b/web/src/js/components/flowtable.jsx.js index 2baf728f..6b56e512 100644 --- a/web/src/js/components/flowtable.jsx.js +++ b/web/src/js/components/flowtable.jsx.js @@ -1,11 +1,11 @@ var FlowRow = React.createClass({ - render: function(){ + render: function () { var flow = this.props.flow; - var columns = this.props.columns.map(function(Column){ + var columns = this.props.columns.map(function (Column) { return ; }.bind(this)); var className = ""; - if(this.props.selected){ + if (this.props.selected) { className += "selected"; } return ( @@ -13,35 +13,37 @@ var FlowRow = React.createClass({ {columns} ); }, - shouldComponentUpdate: function(nextProps){ + shouldComponentUpdate: function (nextProps) { var isEqual = ( - this.props.columns.length === nextProps.columns.length && - this.props.selected === nextProps.selected && - this.props.flow.response === nextProps.flow.response); + this.props.columns.length === nextProps.columns.length && + this.props.selected === nextProps.selected && + this.props.flow.response === nextProps.flow.response); return !isEqual; } }); var FlowTableHead = React.createClass({ - render: function(){ - var columns = this.props.columns.map(function(column){ + render: function () { + var columns = this.props.columns.map(function (column) { return column.renderTitle(); }.bind(this)); - return {columns}; + return + {columns} + ; } }); var FlowTableBody = React.createClass({ - render: function(){ - var rows = this.props.flows.map(function(flow){ + render: function () { + var rows = this.props.flows.map(function (flow) { var selected = (flow == this.props.selected); return ; + ref={flow.id} + flow={flow} + columns={this.props.columns} + selected={selected} + selectFlow={this.props.selectFlow} + />; }.bind(this)); return {rows}; } @@ -55,7 +57,7 @@ var FlowTable = React.createClass({ columns: all_columns }; }, - scrollIntoView: function(flow){ + scrollIntoView: function (flow) { // Now comes the fun part: Scroll the flow into the view. var viewport = this.getDOMNode(); var flowNode = this.refs.body.refs[flow.id].getDOMNode(); @@ -68,9 +70,9 @@ var FlowTable = React.createClass({ // -thead_height pixel earlier. flowNode_top -= this.refs.body.getDOMNode().offsetTop; - if(flowNode_top < viewport_top){ + if (flowNode_top < viewport_top) { viewport.scrollTop = flowNode_top; - } else if(flowNode_bottom > viewport_bottom) { + } else if (flowNode_bottom > viewport_bottom) { viewport.scrollTop = flowNode_bottom - viewport.offsetHeight; } }, @@ -79,14 +81,14 @@ var FlowTable = React.createClass({
+ columns={this.state.columns}/> + flows={this.props.flows} + selected={this.props.selected} + selectFlow={this.props.selectFlow} + columns={this.state.columns}/>
- ); + ); } }); diff --git a/web/src/js/components/footer.jsx.js b/web/src/js/components/footer.jsx.js index 6ba253bf..73fadef2 100644 --- a/web/src/js/components/footer.jsx.js +++ b/web/src/js/components/footer.jsx.js @@ -5,6 +5,6 @@ var Footer = React.createClass({
{mode != "regular" ? {mode} mode : null}
- ); + ); } }); diff --git a/web/src/js/components/header.jsx.js b/web/src/js/components/header.jsx.js index 5c905889..e50c4274 100644 --- a/web/src/js/components/header.jsx.js +++ b/web/src/js/components/header.jsx.js @@ -12,10 +12,11 @@ var MainMenu = React.createClass({ return (
- ); + ); } }); @@ -61,25 +62,25 @@ var Header = React.createClass({ console.log("File click"); }, render: function () { - var header = header_entries.map(function(entry, i){ + var header = header_entries.map(function (entry, i) { var classes = React.addons.classSet({ active: entry == this.state.active }); return ( - { entry.title} - ); + ); }.bind(this)); - + return (
- mitmproxy { this.props.settings.version } + mitmproxy { this.props.settings.version }
- ); + ); } }); diff --git a/web/src/js/components/utils.jsx.js b/web/src/js/components/utils.jsx.js index 22aca577..b1d9a006 100644 --- a/web/src/js/components/utils.jsx.js +++ b/web/src/js/components/utils.jsx.js @@ -6,85 +6,85 @@ var Splitter = React.createClass({ axis: "x" }; }, - getInitialState: function(){ + getInitialState: function () { return { applied: false, startX: false, startY: false }; }, - onMouseDown: function(e){ + onMouseDown: function (e) { this.setState({ startX: e.pageX, startY: e.pageY }); - window.addEventListener("mousemove",this.onMouseMove); - window.addEventListener("mouseup",this.onMouseUp); + window.addEventListener("mousemove", this.onMouseMove); + window.addEventListener("mouseup", this.onMouseUp); // Occasionally, only a dragEnd event is triggered, but no mouseUp. - window.addEventListener("dragend",this.onDragEnd); + window.addEventListener("dragend", this.onDragEnd); }, - onDragEnd: function(){ - this.getDOMNode().style.transform=""; - window.removeEventListener("dragend",this.onDragEnd); - window.removeEventListener("mouseup",this.onMouseUp); - window.removeEventListener("mousemove",this.onMouseMove); + onDragEnd: function () { + this.getDOMNode().style.transform = ""; + window.removeEventListener("dragend", this.onDragEnd); + window.removeEventListener("mouseup", this.onMouseUp); + window.removeEventListener("mousemove", this.onMouseMove); }, - onMouseUp: function(e){ + onMouseUp: function (e) { this.onDragEnd(); var node = this.getDOMNode(); var prev = node.previousElementSibling; var next = node.nextElementSibling; - var dX = e.pageX-this.state.startX; - var dY = e.pageY-this.state.startY; + var dX = e.pageX - this.state.startX; + var dY = e.pageY - this.state.startY; var flexBasis; - if(this.props.axis === "x"){ + if (this.props.axis === "x") { flexBasis = prev.offsetWidth + dX; } else { flexBasis = prev.offsetHeight + dY; } - prev.style.flex = "0 0 "+Math.max(0, flexBasis)+"px"; + prev.style.flex = "0 0 " + Math.max(0, flexBasis) + "px"; next.style.flex = "1 1 auto"; this.setState({ applied: true }); }, - onMouseMove: function(e){ + onMouseMove: function (e) { var dX = 0, dY = 0; - if(this.props.axis === "x"){ - dX = e.pageX-this.state.startX; + if (this.props.axis === "x") { + dX = e.pageX - this.state.startX; } else { - dY = e.pageY-this.state.startY; + dY = e.pageY - this.state.startY; } - this.getDOMNode().style.transform = "translate("+dX+"px,"+dY+"px)"; + this.getDOMNode().style.transform = "translate(" + dX + "px," + dY + "px)"; }, - reset: function(willUnmount) { + reset: function (willUnmount) { if (!this.state.applied) { return; } var node = this.getDOMNode(); var prev = node.previousElementSibling; var next = node.nextElementSibling; - + prev.style.flex = ""; next.style.flex = ""; - if(!willUnmount){ + if (!willUnmount) { this.setState({ applied: false }); } }, - componentWillUnmount: function(){ + componentWillUnmount: function () { this.reset(true); }, - render: function(){ + render: function () { var className = "splitter"; - if(this.props.axis === "x"){ + if (this.props.axis === "x") { className += " splitter-x"; } else { className += " splitter-y"; diff --git a/web/src/js/connection.js b/web/src/js/connection.js index 64d550bf..d511270d 100644 --- a/web/src/js/connection.js +++ b/web/src/js/connection.js @@ -1,20 +1,20 @@ function Connection(url) { - if(url[0] != "/"){ + if (url[0] != "/") { this.url = url; } else { this.url = location.origin.replace("http", "ws") + url; } var ws = new WebSocket(this.url); - ws.onopen = function(){ + ws.onopen = function () { this.onopen.apply(this, arguments); }.bind(this); - ws.onmessage = function(){ + ws.onmessage = function () { this.onmessage.apply(this, arguments); }.bind(this); - ws.onerror = function(){ + ws.onerror = function () { this.onerror.apply(this, arguments); }.bind(this); - ws.onclose = function(){ + ws.onclose = function () { this.onclose.apply(this, arguments); }.bind(this); this.ws = ws; @@ -33,6 +33,6 @@ Connection.prototype.onclose = function (close) { EventLogActions.add_event("WebSocket Connection closed."); console.debug("onclose", this, arguments); }; -Connection.prototype.close = function(){ +Connection.prototype.close = function () { this.ws.close(); }; \ No newline at end of file diff --git a/web/src/js/dispatcher.js b/web/src/js/dispatcher.js index 4fe23447..7fa97481 100644 --- a/web/src/js/dispatcher.js +++ b/web/src/js/dispatcher.js @@ -18,7 +18,7 @@ Dispatcher.prototype.unregister = function (callback) { }; Dispatcher.prototype.dispatch = function (payload) { console.debug("dispatch", payload); - for(var i = 0; i < this.callbacks.length; i++){ + for (var i = 0; i < this.callbacks.length; i++) { this.callbacks[i](payload); } }; diff --git a/web/src/js/stores/eventlogstore.js b/web/src/js/stores/eventlogstore.js index e356959a..439c2360 100644 --- a/web/src/js/stores/eventlogstore.js +++ b/web/src/js/stores/eventlogstore.js @@ -26,7 +26,7 @@ _.extend(EventLogView.prototype, EventEmitter.prototype, { }, add: function (entry) { this.log.push(entry); - if(this.log.length > 200){ + if (this.log.length > 200) { this.log.shift(); } this.emit("change"); @@ -51,37 +51,37 @@ _.extend(_EventLogStore.prototype, EventEmitter.prototype, { var view = new EventLogView(this, !since); return view; /* - //TODO: Really do bulk retrieval of last messages. - window.setTimeout(function () { - view.add_bulk([ - { - id: 1, - message: "Hello World" - }, - { - id: 2, - message: "I was already transmitted as an event." - } - ]); - }, 100); + //TODO: Really do bulk retrieval of last messages. + window.setTimeout(function () { + view.add_bulk([ + { + id: 1, + message: "Hello World" + }, + { + id: 2, + message: "I was already transmitted as an event." + } + ]); + }, 100); - var id = 2; - view.add({ - id: id++, - message: "I was already transmitted as an event." - }); - view.add({ - id: id++, - message: "I was only transmitted as an event before the bulk was added.." - }); - window.setInterval(function () { - view.add({ - id: id++, - message: "." - }); - }, 1000); - return view; - */ + var id = 2; + view.add({ + id: id++, + message: "I was already transmitted as an event." + }); + view.add({ + id: id++, + message: "I was only transmitted as an event before the bulk was added.." + }); + window.setInterval(function () { + view.add({ + id: id++, + message: "." + }); + }, 1000); + return view; + */ }, handle: function (action) { switch (action.type) { diff --git a/web/src/js/stores/flowstore.js b/web/src/js/stores/flowstore.js index 91a5d9ec..60cd2605 100644 --- a/web/src/js/stores/flowstore.js +++ b/web/src/js/stores/flowstore.js @@ -37,7 +37,7 @@ _.extend(FlowStore.prototype, { this._pos_map[flow.id] = i; } }, - get: function(flow_id){ + get: function (flow_id) { return this._flow_list[this._pos_map[flow_id]]; } }); @@ -55,12 +55,12 @@ function LiveFlowStore(endpoint) { }.bind(this); } _.extend(LiveFlowStore.prototype, FlowStore.prototype, { - close: function(){ + close: function () { this.conn.close(); }, - add: function(flow) { + add: function (flow) { // Make sure that deferred adds don't add an element twice. - if(!this._pos_map[flow.id]){ + if (!this._pos_map[flow.id]) { FlowStore.prototype.add.call(this, flow); } }, @@ -117,7 +117,7 @@ function FlowView(store, filt, sort) { } _.extend(FlowView.prototype, EventEmitter.prototype, { - close: function(){ + close: function () { this.store._views = _.without(this.store._views, this); }, recalculate: function (flows, filt, sort) { diff --git a/web/src/js/utils.js b/web/src/js/utils.js index fa15db8c..5ac522ac 100644 --- a/web/src/js/utils.js +++ b/web/src/js/utils.js @@ -41,8 +41,8 @@ var Key = { var formatSize = function (bytes) { var size = bytes; var prefix = ["B", "KB", "MB", "GB", "TB"]; - var i=0; - while (Math.abs(size) >= 1024 && i < prefix.length-1) { + var i = 0; + while (Math.abs(size) >= 1024 && i < prefix.length - 1) { i++; size = size / 1024; } -- cgit v1.2.3 From 9eecc8d6e237022b328f6eaee48b64287336b258 Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Thu, 27 Nov 2014 02:34:03 +0100 Subject: web: fixes --- web/src/js/components/mainview.jsx.js | 8 ++++---- web/src/js/stores/flowstore.js | 26 +++++++++++++++++--------- 2 files changed, 21 insertions(+), 13 deletions(-) (limited to 'web/src/js') diff --git a/web/src/js/components/mainview.jsx.js b/web/src/js/components/mainview.jsx.js index 799bd0d2..a1c9772e 100644 --- a/web/src/js/components/mainview.jsx.js +++ b/web/src/js/components/mainview.jsx.js @@ -51,18 +51,18 @@ var MainView = React.createClass({ this.replaceWith("flows"); } }, - selectFlowRelative: function (i) { + selectFlowRelative: function (shift) { var flows = this.state.view.flows; var index; if (!this.getParams().flowId) { - if (i > 0) { + if (shift > 0) { index = flows.length - 1; } else { index = 0; } } else { - i = flows.length; var currFlowId = this.getParams().flowId; + var i = flows.length; while (i--) { if (flows[i].id === currFlowId) { index = i; @@ -70,7 +70,7 @@ var MainView = React.createClass({ } } index = Math.min( - Math.max(0, index + i), + Math.max(0, index + shift), flows.length - 1); } this.selectFlow(flows[index]); diff --git a/web/src/js/stores/flowstore.js b/web/src/js/stores/flowstore.js index 60cd2605..37eb40eb 100644 --- a/web/src/js/stores/flowstore.js +++ b/web/src/js/stores/flowstore.js @@ -104,38 +104,46 @@ SortByInsertionOrder.prototype.key = function (flow) { var default_sort = (new SortByInsertionOrder()).key; -function FlowView(store, filt, sort) { +function FlowView(store, filt, sortfun) { EventEmitter.call(this); filt = filt || function (flow) { return true; }; - sort = sort || default_sort; + sortfun = sortfun || default_sort; this.store = store; this.store._views.push(this); - this.recalculate(this.store._flow_list, filt, sort); + this.recalculate(this.store._flow_list, filt, sortfun); } _.extend(FlowView.prototype, EventEmitter.prototype, { close: function () { this.store._views = _.without(this.store._views, this); }, - recalculate: function (flows, filt, sort) { + recalculate: function (flows, filt, sortfun) { if (filt) { this.filt = filt; } - if (sort) { - this.sort = sort; + if (sortfun) { + this.sortfun = sortfun; } + + //Ugly workaround: Call .sortfun() for each flow once in order, + //so that SortByInsertionOrder make sense. + var i = flows.length; + while(i--){ + this.sortfun(flows[i]); + } + this.flows = flows.filter(this.filt); this.flows.sort(function (a, b) { - return this.sort(a) - this.sort(b); + return this.sortfun(b) - this.sortfun(a); }.bind(this)); this.emit("recalculate"); }, add: function (flow) { if (this.filt(flow)) { - var idx = _.sortedIndex(this.flows, flow, this.sort); + var idx = _.sortedIndex(this.flows, flow, this.sortfun); if (idx === this.flows.length) { //happens often, .push is way faster. this.flows.push(flow); } else { @@ -160,7 +168,7 @@ _.extend(FlowView.prototype, EventEmitter.prototype, { } else if (!this.filt(flow)) { this.remove(flow.id); } else { - if (this.sort(this.flows[idx]) !== this.sort(flow)) { //sortpos has changed + if (this.sortfun(this.flows[idx]) !== this.sortfun(flow)) { //sortpos has changed this.remove(this.flows[idx]); this.add(flow); } else { -- cgit v1.2.3 From 7ca1ac0f3b7856c0ae44bfbf3b27ae4a424a1cc2 Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Fri, 28 Nov 2014 16:03:56 +0100 Subject: web: virtual scrolling --- web/src/js/components/flowtable.jsx.js | 107 ++++++++++++++++++++++++--------- web/src/js/components/mainview.jsx.js | 14 +---- web/src/js/stores/base.js | 22 ++++--- 3 files changed, 95 insertions(+), 48 deletions(-) (limited to 'web/src/js') diff --git a/web/src/js/components/flowtable.jsx.js b/web/src/js/components/flowtable.jsx.js index 6b56e512..76ceea41 100644 --- a/web/src/js/components/flowtable.jsx.js +++ b/web/src/js/components/flowtable.jsx.js @@ -14,11 +14,13 @@ var FlowRow = React.createClass({ ); }, shouldComponentUpdate: function (nextProps) { - var isEqual = ( - this.props.columns.length === nextProps.columns.length && - this.props.selected === nextProps.selected && - this.props.flow.response === nextProps.flow.response); - return !isEqual; + return true; + // Further optimization could be done here + // by calling forceUpdate on flow updates, selection changes and column changes. + //return ( + //(this.props.columns.length !== nextProps.columns.length) || + //(this.props.selected !== nextProps.selected) + //); } }); @@ -33,30 +35,51 @@ var FlowTableHead = React.createClass({ } }); -var FlowTableBody = React.createClass({ - render: function () { - var rows = this.props.flows.map(function (flow) { - var selected = (flow == this.props.selected); - return ; - }.bind(this)); - return {rows}; - } -}); +var ROW_HEIGHT = 32; var FlowTable = React.createClass({ mixins: [StickyHeadMixin, AutoScrollMixin], getInitialState: function () { return { - columns: all_columns + columns: all_columns, + start: 0, + stop: 0 }; }, + componentWillMount: function () { + if (this.props.view) { + this.props.view.addListener("add update remove recalculate", this.onChange); + } + }, + componentWillReceiveProps: function (nextProps) { + if (nextProps.view !== this.props.view) { + if (this.props.view) { + this.props.view.removeListener("add update remove recalculate"); + } + nextProps.view.addListener("add update remove recalculate", this.onChange); + } + }, + componentDidMount: function () { + this.onScroll(); + }, + onScroll: function () { + this.adjustHead(); + + var viewport = this.getDOMNode(); + var top = viewport.scrollTop; + var height = viewport.offsetHeight; + var start = Math.floor(top / ROW_HEIGHT); + var stop = start + Math.ceil(height / ROW_HEIGHT); + this.setState({ + start: start, + stop: stop + }); + }, + onChange: function () { + console.log("onChange"); + this.forceUpdate(); + }, scrollIntoView: function (flow) { // Now comes the fun part: Scroll the flow into the view. var viewport = this.getDOMNode(); @@ -77,16 +100,46 @@ var FlowTable = React.createClass({ } }, render: function () { + var space_top = 0, space_bottom = 0, fix_nth_row = null; + var rows = []; + if (this.props.view) { + var flows = this.props.view.flows; + var max = Math.min(flows.length, this.state.stop); + console.log("render", this.props.view.flows.length, this.state.start, max - this.state.start, flows.length - this.state.stop); + + for (var i = this.state.start; i < max; i++) { + var flow = flows[i]; + var selected = (flow === this.props.selected); + rows.push( + + ); + } + + space_top = this.state.start * ROW_HEIGHT; + space_bottom = Math.max(0, flows.length - this.state.stop) * ROW_HEIGHT; + if(this.state.start % 2 === 1){ + fix_nth_row = ; + } + } + + return ( -
+
- + + + { fix_nth_row } + {rows} + +
); diff --git a/web/src/js/components/mainview.jsx.js b/web/src/js/components/mainview.jsx.js index a1c9772e..fd9fdb8d 100644 --- a/web/src/js/components/mainview.jsx.js +++ b/web/src/js/components/mainview.jsx.js @@ -16,26 +16,16 @@ var MainView = React.createClass({ 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 () { + componentWillMount: function () { this.openView(this.props.flowStore); }, componentWillUnmount: function () { this.closeView(); }, - onFlowChange: function () { - console.warn("onFlowChange is deprecated"); - this.setState({ - flows: this.state.view.flows - }); - }, selectFlow: function (flow) { if (flow) { this.replaceWith( @@ -131,7 +121,7 @@ var MainView = React.createClass({ return (
{ details ? : null } diff --git a/web/src/js/stores/base.js b/web/src/js/stores/base.js index 952fa847..cf9f015e 100644 --- a/web/src/js/stores/base.js +++ b/web/src/js/stores/base.js @@ -10,16 +10,20 @@ EventEmitter.prototype.emit = function (event) { listener.apply(this, args); }.bind(this)); }; -EventEmitter.prototype.addListener = function (event, f) { - this.listeners[event] = this.listeners[event] || []; - this.listeners[event].push(f); +EventEmitter.prototype.addListener = function (events, f) { + events.split(" ").forEach(function (event) { + this.listeners[event] = this.listeners[event] || []; + this.listeners[event].push(f); + }.bind(this)); }; -EventEmitter.prototype.removeListener = function (event, f) { - if (!(event in this.listeners)) { +EventEmitter.prototype.removeListener = function (events, f) { + if (!(events in this.listeners)) { return false; } - var index = this.listeners[event].indexOf(f); - if (index >= 0) { - this.listeners[event].splice(index, 1); - } + events.split(" ").forEach(function (event) { + var index = this.listeners[event].indexOf(f); + if (index >= 0) { + this.listeners[event].splice(index, 1); + } + }.bind(this)); }; -- cgit v1.2.3 From c39b6e4277357c9da1dfd5e3e8c41b5b3427e0ce Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Fri, 28 Nov 2014 19:16:47 +0100 Subject: web: various fixes, add clear button --- web/src/js/components/flowdetail.jsx.js | 5 ++-- web/src/js/components/flowtable.jsx.js | 25 ++++++++++-------- web/src/js/components/header.jsx.js | 9 +++++-- web/src/js/components/mainview.jsx.js | 3 +-- web/src/js/components/utils.jsx.js | 17 ++++++++++++ web/src/js/stores/flowstore.js | 46 ++++++++++++++++++++++----------- 6 files changed, 72 insertions(+), 33 deletions(-) (limited to 'web/src/js') diff --git a/web/src/js/components/flowdetail.jsx.js b/web/src/js/components/flowdetail.jsx.js index 5c4168a9..74522f57 100644 --- a/web/src/js/components/flowdetail.jsx.js +++ b/web/src/js/components/flowdetail.jsx.js @@ -4,9 +4,9 @@ var FlowDetailNav = React.createClass({ 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 (e) { + var onClick = function (event) { this.props.selectTab(e); - e.preventDefault(); + event.preventDefault(); }.bind(this); return diff --git a/web/src/js/components/flowtable.jsx.js b/web/src/js/components/flowtable.jsx.js index 76ceea41..6d0f5ee7 100644 --- a/web/src/js/components/flowtable.jsx.js +++ b/web/src/js/components/flowtable.jsx.js @@ -83,20 +83,23 @@ var FlowTable = React.createClass({ scrollIntoView: function (flow) { // Now comes the fun part: Scroll the flow into the view. var viewport = this.getDOMNode(); - var flowNode = this.refs.body.refs[flow.id].getDOMNode(); + var thead_height = this.refs.body.getDOMNode().offsetTop; + + var flow_top = (this.props.view.index(flow) * ROW_HEIGHT) + thead_height; + var viewport_top = viewport.scrollTop; var viewport_bottom = viewport_top + viewport.offsetHeight; - var flowNode_top = flowNode.offsetTop; - var flowNode_bottom = flowNode_top + flowNode.offsetHeight; + var flow_bottom = flow_top + ROW_HEIGHT; + + // Account for pinned thead + - // Account for pinned thead by pretending that the flowNode starts - // -thead_height pixel earlier. - flowNode_top -= this.refs.body.getDOMNode().offsetTop; + console.log("scrollInto", flow_top, flow_bottom, viewport_top, viewport_bottom, thead_height); - if (flowNode_top < viewport_top) { - viewport.scrollTop = flowNode_top; - } else if (flowNode_bottom > viewport_bottom) { - viewport.scrollTop = flowNode_bottom - viewport.offsetHeight; + if (flow_top - thead_height < viewport_top) { + viewport.scrollTop = flow_top - thead_height; + } else if (flow_bottom > viewport_bottom) { + viewport.scrollTop = flow_bottom - viewport.offsetHeight; } }, render: function () { @@ -134,7 +137,7 @@ var FlowTable = React.createClass({ - + { fix_nth_row } {rows} diff --git a/web/src/js/components/header.jsx.js b/web/src/js/components/header.jsx.js index e50c4274..a3fe4d51 100644 --- a/web/src/js/components/header.jsx.js +++ b/web/src/js/components/header.jsx.js @@ -8,12 +8,17 @@ var MainMenu = React.createClass({ showEventLog: !this.props.settings.showEventLog }); }, + clearFlows: function(){ + $.post("/flows/clear"); + }, render: function () { return (
  +
); diff --git a/web/src/js/components/mainview.jsx.js b/web/src/js/components/mainview.jsx.js index fd9fdb8d..fe5d1c7c 100644 --- a/web/src/js/components/mainview.jsx.js +++ b/web/src/js/components/mainview.jsx.js @@ -35,8 +35,7 @@ var MainView = React.createClass({ detailTab: this.getParams().detailTab || "request" } ); - console.log("TODO: Scroll into view"); - //this.refs.flowTable.scrollIntoView(flow); + this.refs.flowTable.scrollIntoView(flow); } else { this.replaceWith("flows"); } diff --git a/web/src/js/components/utils.jsx.js b/web/src/js/components/utils.jsx.js index b1d9a006..12775adc 100644 --- a/web/src/js/components/utils.jsx.js +++ b/web/src/js/components/utils.jsx.js @@ -95,4 +95,21 @@ var Splitter = React.createClass({ ); } +}); + +function getCookie(name) { + var r = document.cookie.match("\\b" + name + "=([^;]*)\\b"); + return r ? r[1] : undefined; +} +var xsrf = $.param({_xsrf: getCookie("_xsrf")}); + +//Tornado XSRF Protection. +$.ajaxPrefilter(function(options){ + if(options.type === "post" && options.url[0] === "/"){ + if(options.data){ + options.data += ("&" + xsrf); + } else { + options.data = xsrf; + } + } }); \ No newline at end of file diff --git a/web/src/js/stores/flowstore.js b/web/src/js/stores/flowstore.js index 37eb40eb..cc7318a2 100644 --- a/web/src/js/stores/flowstore.js +++ b/web/src/js/stores/flowstore.js @@ -45,7 +45,8 @@ _.extend(FlowStore.prototype, { function LiveFlowStore(endpoint) { FlowStore.call(this); - this.updates_before_init = []; // (empty array is true in js) + this.updates_before_fetch = undefined; + this._fetchxhr = false; this.endpoint = endpoint || "/flows"; this.conn = new Connection(this.endpoint + "/updates"); this.conn.onopen = this._onopen.bind(this); @@ -60,33 +61,46 @@ _.extend(LiveFlowStore.prototype, FlowStore.prototype, { }, add: function (flow) { // Make sure that deferred adds don't add an element twice. - if (!this._pos_map[flow.id]) { + if (!(flow.id in this._pos_map)) { FlowStore.prototype.add.call(this, flow); } }, + _onopen: function () { + //Update stream openend, fetch list of flows. + console.log("Update Connection opened, fetching flows..."); + this.fetch(); + }, + fetch: function () { + if (this._fetchxhr) { + this._fetchxhr.abort(); + } + this._fetchxhr = $.getJSON(this.endpoint, this.handle_fetch.bind(this)); + this.updates_before_fetch = []; // (JS: empty array is true) + }, handle_update: function (type, data) { console.log("LiveFlowStore.handle_update", type, data); - if (this.updates_before_init) { + + if (type === "reset") { + return this.fetch(); + } + + if (this.updates_before_fetch) { console.log("defer update", type, data); - this.updates_before_init.push(arguments); + this.updates_before_fetch.push(arguments); } else { this[type](data); } }, handle_fetch: function (data) { + this._fetchxhr = false; console.log("Flows fetched."); this.reset(data.flows); - var updates = this.updates_before_init; - this.updates_before_init = false; + var updates = this.updates_before_fetch; + this.updates_before_fetch = false; for (var i = 0; i < updates.length; i++) { this.handle_update.apply(this, updates[i]); } }, - _onopen: function () { - //Update stream openend, fetch list of flows. - console.log("Update Connection opened, fetching flows..."); - $.getJSON(this.endpoint, this.handle_fetch.bind(this)); - }, }); function SortByInsertionOrder() { @@ -130,20 +144,22 @@ _.extend(FlowView.prototype, EventEmitter.prototype, { //Ugly workaround: Call .sortfun() for each flow once in order, //so that SortByInsertionOrder make sense. - var i = flows.length; - while(i--){ + for(var i = 0; i < flows.length; i++) { this.sortfun(flows[i]); } this.flows = flows.filter(this.filt); this.flows.sort(function (a, b) { - return this.sortfun(b) - this.sortfun(a); + return this.sortfun(a) - this.sortfun(b); }.bind(this)); this.emit("recalculate"); }, + index: function (flow) { + return _.sortedIndex(this.flows, flow, this.sortfun); + }, add: function (flow) { if (this.filt(flow)) { - var idx = _.sortedIndex(this.flows, flow, this.sortfun); + var idx = this.index(flow); if (idx === this.flows.length) { //happens often, .push is way faster. this.flows.push(flow); } else { -- cgit v1.2.3 From dd1a45140c8a6cb3f6c5d7247120c05fa37cdf24 Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Fri, 28 Nov 2014 20:03:04 +0100 Subject: web: add virtualscroll mixin --- web/src/js/components/flowtable.jsx.js | 65 ++++++++---------------------- web/src/js/components/virtualscroll.jsx.js | 54 +++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 48 deletions(-) create mode 100644 web/src/js/components/virtualscroll.jsx.js (limited to 'web/src/js') diff --git a/web/src/js/components/flowtable.jsx.js b/web/src/js/components/flowtable.jsx.js index 6d0f5ee7..f608f41d 100644 --- a/web/src/js/components/flowtable.jsx.js +++ b/web/src/js/components/flowtable.jsx.js @@ -39,12 +39,10 @@ var FlowTableHead = React.createClass({ var ROW_HEIGHT = 32; var FlowTable = React.createClass({ - mixins: [StickyHeadMixin, AutoScrollMixin], + mixins: [StickyHeadMixin, AutoScrollMixin, VirtualScrollMixin], getInitialState: function () { return { - columns: all_columns, - start: 0, - stop: 0 + columns: all_columns }; }, componentWillMount: function () { @@ -61,46 +59,26 @@ var FlowTable = React.createClass({ } }, componentDidMount: function () { - this.onScroll(); + this.onScroll2(); }, - onScroll: function () { + getDefaultProps: function () { + return { + rowHeight: ROW_HEIGHT + }; + }, + onScroll2: function () { this.adjustHead(); - - var viewport = this.getDOMNode(); - var top = viewport.scrollTop; - var height = viewport.offsetHeight; - var start = Math.floor(top / ROW_HEIGHT); - var stop = start + Math.ceil(height / ROW_HEIGHT); - this.setState({ - start: start, - stop: stop - }); + this.onScroll(); }, onChange: function () { console.log("onChange"); this.forceUpdate(); }, scrollIntoView: function (flow) { - // Now comes the fun part: Scroll the flow into the view. - var viewport = this.getDOMNode(); - var thead_height = this.refs.body.getDOMNode().offsetTop; - - var flow_top = (this.props.view.index(flow) * ROW_HEIGHT) + thead_height; - - var viewport_top = viewport.scrollTop; - var viewport_bottom = viewport_top + viewport.offsetHeight; - var flow_bottom = flow_top + ROW_HEIGHT; - - // Account for pinned thead - - - console.log("scrollInto", flow_top, flow_bottom, viewport_top, viewport_bottom, thead_height); - - if (flow_top - thead_height < viewport_top) { - viewport.scrollTop = flow_top - thead_height; - } else if (flow_bottom > viewport_bottom) { - viewport.scrollTop = flow_bottom - viewport.offsetHeight; - } + this.scrollRowIntoView( + this.props.view.index(flow), + this.refs.body.getDOMNode().offsetTop + ); }, render: function () { var space_top = 0, space_bottom = 0, fix_nth_row = null; @@ -108,7 +86,6 @@ var FlowTable = React.createClass({ if (this.props.view) { var flows = this.props.view.flows; var max = Math.min(flows.length, this.state.stop); - console.log("render", this.props.view.flows.length, this.state.start, max - this.state.start, flows.length - this.state.stop); for (var i = this.state.start; i < max; i++) { var flow = flows[i]; @@ -123,25 +100,17 @@ var FlowTable = React.createClass({ /> ); } - - space_top = this.state.start * ROW_HEIGHT; - space_bottom = Math.max(0, flows.length - this.state.stop) * ROW_HEIGHT; - if(this.state.start % 2 === 1){ - fix_nth_row = ; - } } - return ( -
+
- - { fix_nth_row } + { this.getPlaceholderTop() } {rows} - + { this.getPlaceholderBottom(flows.length) }
diff --git a/web/src/js/components/virtualscroll.jsx.js b/web/src/js/components/virtualscroll.jsx.js new file mode 100644 index 00000000..ec3daa41 --- /dev/null +++ b/web/src/js/components/virtualscroll.jsx.js @@ -0,0 +1,54 @@ +var VirtualScrollMixin = { + getInitialState: function () { + return { + start: 0, + stop: 0 + } + }, + getPlaceholderTop: function () { + var style = { + height: this.state.start * this.props.rowHeight + }; + var spacer = ; + + if (this.state.start % 2 === 1) { + // fix even/odd rows + return [spacer, ]; + } else { + return spacer; + } + }, + getPlaceholderBottom: function (total) { + var style = { + height: Math.max(0, total - this.state.stop) * this.props.rowHeight + }; + return ; + }, + onScroll: function () { + var viewport = this.getDOMNode(); + var top = viewport.scrollTop; + var height = viewport.offsetHeight; + var start = Math.floor(top / this.props.rowHeight); + var stop = start + Math.ceil(height / this.props.rowHeight); + this.setState({ + start: start, + stop: stop + }); + }, + scrollRowIntoView: function(index, head_height){ + + var row_top = (index * this.props.rowHeight) + head_height; + var row_bottom = row_top + this.props.rowHeight; + + var viewport = this.getDOMNode(); + var viewport_top = viewport.scrollTop; + var viewport_bottom = viewport_top + viewport.offsetHeight; + + // Account for pinned thead + if (row_top - head_height < viewport_top) { + viewport.scrollTop = row_top - head_height; + } else if (row_bottom > viewport_bottom) { + viewport.scrollTop = row_bottom - viewport.offsetHeight; + } + }, +}; \ No newline at end of file -- cgit v1.2.3 From f6c0e000da504a68ecd41a8f7ce59e2f71e0a218 Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Fri, 28 Nov 2014 20:54:52 +0100 Subject: event log: virtual scrolling --- web/src/js/components/eventlog.jsx.js | 39 ++++++++++++++++++++++-------- web/src/js/components/flowtable.jsx.js | 34 +++++++++----------------- web/src/js/components/virtualscroll.jsx.js | 30 ++++++++++++++++++++--- web/src/js/stores/flowstore.js | 2 +- 4 files changed, 68 insertions(+), 37 deletions(-) (limited to 'web/src/js') diff --git a/web/src/js/components/eventlog.jsx.js b/web/src/js/components/eventlog.jsx.js index 4d61a39c..708432d0 100644 --- a/web/src/js/components/eventlog.jsx.js +++ b/web/src/js/components/eventlog.jsx.js @@ -24,7 +24,7 @@ var LogMessage = React.createClass({ }); var EventLogContents = React.createClass({ - mixins: [AutoScrollMixin], + mixins: [AutoScrollMixin, VirtualScrollMixin], getInitialState: function () { return { log: [] @@ -39,18 +39,37 @@ var EventLogContents = React.createClass({ this.log.close(); }, onEventLogChange: function () { + var log = this.log.getAll().filter(function (entry) { + return this.props.filter[entry.level]; + }.bind(this)); this.setState({ - log: this.log.getAll() + log: log }); }, + componentWillReceiveProps: function () { + if (this.log) { + this.onEventLogChange(); + } + + }, + getDefaultProps: function () { + return { + rowHeight: 45, + rowHeightMin: 15, + placeholderTagName: "div" + }; + }, + renderRow: function(elem){ + return ; + }, render: function () { - var messages = this.state.log.map(function (row) { - if (!this.props.filter[row.level]) { - return null; - } - return ; - }.bind(this)); - return
{messages}
; + var rows = this.renderRows(this.state.log); + + return
+            { this.getPlaceholderTop() }
+            {rows}
+            { this.getPlaceholderBottom(this.state.log.length) }
+        
; } }); @@ -101,7 +120,7 @@ var EventLog = React.createClass({ return (
- Eventlog + Eventlog
diff --git a/web/src/js/components/flowtable.jsx.js b/web/src/js/components/flowtable.jsx.js index f608f41d..9eeddbaa 100644 --- a/web/src/js/components/flowtable.jsx.js +++ b/web/src/js/components/flowtable.jsx.js @@ -58,9 +58,6 @@ var FlowTable = React.createClass({ nextProps.view.addListener("add update remove recalculate", this.onChange); } }, - componentDidMount: function () { - this.onScroll2(); - }, getDefaultProps: function () { return { rowHeight: ROW_HEIGHT @@ -80,27 +77,20 @@ var FlowTable = React.createClass({ this.refs.body.getDOMNode().offsetTop ); }, + renderRow: function (flow) { + var selected = (flow === this.props.selected); + return ; + }, render: function () { - var space_top = 0, space_bottom = 0, fix_nth_row = null; - var rows = []; - if (this.props.view) { - var flows = this.props.view.flows; - var max = Math.min(flows.length, this.state.stop); + var flows = this.props.view ? this.props.view.flows : []; - for (var i = this.state.start; i < max; i++) { - var flow = flows[i]; - var selected = (flow === this.props.selected); - rows.push( - - ); - } - } + var rows = this.renderRows(flows); return (
diff --git a/web/src/js/components/virtualscroll.jsx.js b/web/src/js/components/virtualscroll.jsx.js index ec3daa41..ebbf13f3 100644 --- a/web/src/js/components/virtualscroll.jsx.js +++ b/web/src/js/components/virtualscroll.jsx.js @@ -5,35 +5,57 @@ var VirtualScrollMixin = { stop: 0 } }, + componentWillMount: function(){ + if(!this.props.rowHeight){ + console.warn("VirtualScrollMixin: No rowHeight specified", this); + } + }, getPlaceholderTop: function () { + var Tag = this.props.placeholderTagName || "tr"; var style = { height: this.state.start * this.props.rowHeight }; - var spacer = ; + var spacer = ; if (this.state.start % 2 === 1) { // fix even/odd rows - return [spacer, ]; + return [spacer, ]; } else { return spacer; } }, getPlaceholderBottom: function (total) { + var Tag = this.props.placeholderTagName || "tr"; var style = { height: Math.max(0, total - this.state.stop) * this.props.rowHeight }; - return ; + return ; + }, + componentDidMount: function () { + this.onScroll(); }, onScroll: function () { var viewport = this.getDOMNode(); var top = viewport.scrollTop; var height = viewport.offsetHeight; var start = Math.floor(top / this.props.rowHeight); - var stop = start + Math.ceil(height / this.props.rowHeight); + var stop = start + Math.ceil(height / (this.props.rowHeightMin || this.props.rowHeight)); + this.setState({ start: start, stop: stop }); + console.log(start, stop); + }, + renderRows: function(elems){ + var rows = []; + var max = Math.min(elems.length, this.state.stop); + + for (var i = this.state.start; i < max; i++) { + var elem = elems[i]; + rows.push(this.renderRow(elem)); + } + return rows; }, scrollRowIntoView: function(index, head_height){ diff --git a/web/src/js/stores/flowstore.js b/web/src/js/stores/flowstore.js index cc7318a2..c3231c24 100644 --- a/web/src/js/stores/flowstore.js +++ b/web/src/js/stores/flowstore.js @@ -144,7 +144,7 @@ _.extend(FlowView.prototype, EventEmitter.prototype, { //Ugly workaround: Call .sortfun() for each flow once in order, //so that SortByInsertionOrder make sense. - for(var i = 0; i < flows.length; i++) { + for (var i = 0; i < flows.length; i++) { this.sortfun(flows[i]); } -- cgit v1.2.3 From 096a3af273ccb309820351b466e62382f62a2c36 Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Sat, 29 Nov 2014 03:25:07 +0100 Subject: web: various improvements --- web/src/js/components/flowdetail.jsx.js | 67 +++++++++++++++++++++++------- web/src/js/components/flowtable.jsx.js | 6 +-- web/src/js/components/mainview.jsx.js | 30 +++++++++---- web/src/js/components/virtualscroll.jsx.js | 3 +- web/src/js/stores/flowstore.js | 2 +- web/src/js/utils.js | 5 +++ 6 files changed, 84 insertions(+), 29 deletions(-) (limited to 'web/src/js') diff --git a/web/src/js/components/flowdetail.jsx.js b/web/src/js/components/flowdetail.jsx.js index 74522f57..2bda5b80 100644 --- a/web/src/js/components/flowdetail.jsx.js +++ b/web/src/js/components/flowdetail.jsx.js @@ -47,7 +47,7 @@ var FlowDetailRequest = React.createClass({ var first_line = [ flow.request.method, RequestUtils.pretty_url(flow.request), - "HTTP/" + flow.response.httpversion.join(".") + "HTTP/" + flow.request.httpversion.join(".") ].join(" "); var content = null; if (flow.request.contentLength > 0) { @@ -97,6 +97,20 @@ var FlowDetailResponse = React.createClass({ } }); +var FlowDetailError = React.createClass({ + render: function () { + var flow = this.props.flow; + return ( +
+
+ {flow.error.msg} +
{ formatTimeStamp(flow.error.timestamp) }
+
+
+ ); + } +}); + var TimeStamp = React.createClass({ render: function () { @@ -105,8 +119,7 @@ var TimeStamp = React.createClass({ return ; } - var ts = (new Date(this.props.t * 1000)).toISOString(); - ts = ts.replace("T", " ").replace("Z", ""); + var ts = formatTimeStamp(this.props.t); var delta; if (this.props.deltaTo) { @@ -273,24 +286,31 @@ var FlowDetailConnectionInfo = React.createClass({ } }); -var tabs = { +var allTabs = { request: FlowDetailRequest, response: FlowDetailResponse, + error: FlowDetailError, details: FlowDetailConnectionInfo }; var FlowDetail = React.createClass({ - getDefaultProps: function () { - return { - tabs: ["request", "response", "details"] - }; - }, mixins: [StickyHeadMixin, ReactRouter.Navigation, ReactRouter.State], + getTabs: function (flow) { + var tabs = []; + ["request", "response", "error"].forEach(function (e) { + if (flow[e]) { + tabs.push(e); + } + }); + tabs.push("details"); + return tabs; + }, nextTab: function (i) { - var currentIndex = this.props.tabs.indexOf(this.props.active); + var tabs = this.getTabs(); + var currentIndex = tabs.indexOf(this.getParams().detailTab); // 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.selectTab(this.props.tabs[nextIndex]); + var nextIndex = (currentIndex + i + tabs.length) % tabs.length; + this.selectTab(tabs[nextIndex]); }, selectTab: function (panel) { this.replaceWith( @@ -302,14 +322,29 @@ var FlowDetail = React.createClass({ ); }, render: function () { - var Tab = tabs[this.props.active]; + var flow = this.props.flow; + var tabs = this.getTabs(flow); + var active = this.getParams().detailTab; + + if (!_.contains(tabs, active)) { + if (active === "response" && flow.error) { + active = "error"; + } else if (active === "error" && flow.response) { + active = "response"; + } else { + active = tabs[0]; + } + this.selectTab(active); + } + + var Tab = allTabs[active]; return (
- +
); } diff --git a/web/src/js/components/flowtable.jsx.js b/web/src/js/components/flowtable.jsx.js index 9eeddbaa..1a4efe89 100644 --- a/web/src/js/components/flowtable.jsx.js +++ b/web/src/js/components/flowtable.jsx.js @@ -63,12 +63,11 @@ var FlowTable = React.createClass({ rowHeight: ROW_HEIGHT }; }, - onScroll2: function () { + onScrollFlowTable: function () { this.adjustHead(); this.onScroll(); }, onChange: function () { - console.log("onChange"); this.forceUpdate(); }, scrollIntoView: function (flow) { @@ -88,12 +87,13 @@ var FlowTable = React.createClass({ />; }, render: function () { + //console.log("render flowtable", this.state.start, this.state.stop, this.props.selected); var flows = this.props.view ? this.props.view.flows : []; var rows = this.renderRows(flows); return ( -
+
diff --git a/web/src/js/components/mainview.jsx.js b/web/src/js/components/mainview.jsx.js index fe5d1c7c..d5066b1a 100644 --- a/web/src/js/components/mainview.jsx.js +++ b/web/src/js/components/mainview.jsx.js @@ -16,6 +16,21 @@ var MainView = React.createClass({ this.setState({ view: view }); + + view.addListener("recalculate", this.onRecalculate); + view.addListener("add update remove", this.onUpdate); + }, + onRecalculate: function(){ + this.forceUpdate(); + var selected = this.getSelected(); + if(selected){ + this.refs.flowTable.scrollIntoView(); + } + }, + onUpdate: function (flow) { + if (flow.id === this.getParams().flowId) { + this.forceUpdate(); + } }, closeView: function () { this.state.view.close(); @@ -103,16 +118,18 @@ var MainView = React.createClass({ } e.preventDefault(); }, + getSelected: function(){ + return this.props.flowStore.get(this.getParams().flowId); + }, render: function () { - var selected = this.props.flowStore.get(this.getParams().flowId); + var selected = this.getSelected(); var details; if (selected) { - details = ( - - ); + details = [ + , + + ]; } else { details = null; } @@ -123,7 +140,6 @@ var MainView = React.createClass({ view={this.state.view} selectFlow={this.selectFlow} selected={selected} /> - { details ? : null } {details} ); diff --git a/web/src/js/components/virtualscroll.jsx.js b/web/src/js/components/virtualscroll.jsx.js index ebbf13f3..5a67bbf5 100644 --- a/web/src/js/components/virtualscroll.jsx.js +++ b/web/src/js/components/virtualscroll.jsx.js @@ -3,7 +3,7 @@ var VirtualScrollMixin = { return { start: 0, stop: 0 - } + }; }, componentWillMount: function(){ if(!this.props.rowHeight){ @@ -45,7 +45,6 @@ var VirtualScrollMixin = { start: start, stop: stop }); - console.log(start, stop); }, renderRows: function(elems){ var rows = []; diff --git a/web/src/js/stores/flowstore.js b/web/src/js/stores/flowstore.js index c3231c24..1034bd53 100644 --- a/web/src/js/stores/flowstore.js +++ b/web/src/js/stores/flowstore.js @@ -93,7 +93,7 @@ _.extend(LiveFlowStore.prototype, FlowStore.prototype, { }, handle_fetch: function (data) { this._fetchxhr = false; - console.log("Flows fetched."); + console.log("Flows fetched.", this.updates_before_fetch); this.reset(data.flows); var updates = this.updates_before_fetch; this.updates_before_fetch = false; diff --git a/web/src/js/utils.js b/web/src/js/utils.js index 5ac522ac..329de956 100644 --- a/web/src/js/utils.js +++ b/web/src/js/utils.js @@ -59,4 +59,9 @@ var formatTimeDelta = function (milliseconds) { i++; } return Math.round(time) + prefix[i]; +}; + +var formatTimeStamp = function (seconds) { + var ts = (new Date(seconds * 1000)).toISOString(); + return ts.replace("T", " ").replace("Z", ""); }; \ No newline at end of file -- cgit v1.2.3 From 14a8d2f5b83a1ea28abbb490f6c94c43b4e1f960 Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Tue, 9 Dec 2014 18:18:14 +0100 Subject: always use the app dispatcher --- web/src/js/actions.js | 35 ++++++++++++++--- web/src/js/app.js | 7 +--- web/src/js/components/flowdetail.jsx.js | 2 +- web/src/js/components/mainview.jsx.js | 2 +- web/src/js/connection.js | 52 +++++++++---------------- web/src/js/stores/flowstore.js | 69 ++++++++++++++++++--------------- 6 files changed, 91 insertions(+), 76 deletions(-) (limited to 'web/src/js') diff --git a/web/src/js/actions.js b/web/src/js/actions.js index 3e7510ad..28bb58b8 100644 --- a/web/src/js/actions.js +++ b/web/src/js/actions.js @@ -1,13 +1,38 @@ var ActionTypes = { - //Settings + // Connection + CONNECTION_OPEN: "connection_open", + CONNECTION_CLOSE: "connection_close", + CONNECTION_ERROR: "connection_error", + + // Settings UPDATE_SETTINGS: "update_settings", - //EventLog + // EventLog ADD_EVENT: "add_event", - //Flow + // Flow ADD_FLOW: "add_flow", UPDATE_FLOW: "update_flow", + REMOVE_FLOW: "remove_flow", + RESET_FLOWS: "reset_flows", +}; + +var ConnectionActions = { + open: function () { + AppDispatcher.dispatchViewAction({ + type: ActionTypes.CONNECTION_OPEN + }); + }, + close: function () { + AppDispatcher.dispatchViewAction({ + type: ActionTypes.CONNECTION_CLOSE + }); + }, + error: function () { + AppDispatcher.dispatchViewAction({ + type: ActionTypes.CONNECTION_ERROR + }); + } }; var SettingsActions = { @@ -23,7 +48,7 @@ var SettingsActions = { } }; -var event_id = 0; +var EventLogActions_event_id = 0; var EventLogActions = { add_event: function (message) { AppDispatcher.dispatchViewAction({ @@ -31,7 +56,7 @@ var EventLogActions = { data: { message: message, level: "web", - id: "viewAction-" + event_id++ + id: "viewAction-" + EventLogActions_event_id++ } }); } diff --git a/web/src/js/app.js b/web/src/js/app.js index b5d50d34..5146cb46 100644 --- a/web/src/js/app.js +++ b/web/src/js/app.js @@ -1,10 +1,7 @@ $(function () { + window.ws = new Connection("/updates"); + ReactRouter.run(routes, function (Handler) { React.render(, document.body); }); - var UpdateConnection = new Connection("/updates"); - UpdateConnection.onmessage = function (message) { - var m = JSON.parse(message.data); - AppDispatcher.dispatchServerAction(m); - }; }); \ No newline at end of file diff --git a/web/src/js/components/flowdetail.jsx.js b/web/src/js/components/flowdetail.jsx.js index 2bda5b80..6d46cd2e 100644 --- a/web/src/js/components/flowdetail.jsx.js +++ b/web/src/js/components/flowdetail.jsx.js @@ -306,7 +306,7 @@ var FlowDetail = React.createClass({ return tabs; }, nextTab: function (i) { - var tabs = this.getTabs(); + var tabs = this.getTabs(this.props.flow); var currentIndex = tabs.indexOf(this.getParams().detailTab); // JS modulo operator doesn't correct negative numbers, make sure that we are positive. var nextIndex = (currentIndex + i + tabs.length) % tabs.length; diff --git a/web/src/js/components/mainview.jsx.js b/web/src/js/components/mainview.jsx.js index d5066b1a..c7c9ee9b 100644 --- a/web/src/js/components/mainview.jsx.js +++ b/web/src/js/components/mainview.jsx.js @@ -24,7 +24,7 @@ var MainView = React.createClass({ this.forceUpdate(); var selected = this.getSelected(); if(selected){ - this.refs.flowTable.scrollIntoView(); + this.refs.flowTable.scrollIntoView(selected); } }, onUpdate: function (flow) { diff --git a/web/src/js/connection.js b/web/src/js/connection.js index d511270d..6ca353b3 100644 --- a/web/src/js/connection.js +++ b/web/src/js/connection.js @@ -1,38 +1,24 @@ function Connection(url) { - if (url[0] != "/") { - this.url = url; - } else { - this.url = location.origin.replace("http", "ws") + url; + + if (url[0] === "/") { + url = location.origin.replace("http", "ws") + url; } - var ws = new WebSocket(this.url); + + var ws = new WebSocket(url); ws.onopen = function () { - this.onopen.apply(this, arguments); - }.bind(this); - ws.onmessage = function () { - this.onmessage.apply(this, arguments); - }.bind(this); + ConnectionActions.open(); + }; + ws.onmessage = function (message) { + var m = JSON.parse(message.data); + AppDispatcher.dispatchServerAction(m); + }; ws.onerror = function () { - this.onerror.apply(this, arguments); - }.bind(this); + ConnectionActions.error(); + EventLogActions.add_event("WebSocket connection error."); + }; ws.onclose = function () { - this.onclose.apply(this, arguments); - }.bind(this); - this.ws = ws; -} -Connection.prototype.onopen = function (open) { - console.debug("onopen", this, arguments); -}; -Connection.prototype.onmessage = function (message) { - console.warn("onmessage (not implemented)", this, message.data); -}; -Connection.prototype.onerror = function (error) { - EventLogActions.add_event("WebSocket Connection Error."); - console.debug("onerror", this, arguments); -}; -Connection.prototype.onclose = function (close) { - EventLogActions.add_event("WebSocket Connection closed."); - console.debug("onclose", this, arguments); -}; -Connection.prototype.close = function () { - this.ws.close(); -}; \ No newline at end of file + ConnectionActions.close(); + EventLogActions.add_event("WebSocket connection closed."); + }; + return ws; +} \ No newline at end of file diff --git a/web/src/js/stores/flowstore.js b/web/src/js/stores/flowstore.js index 1034bd53..4110ea7f 100644 --- a/web/src/js/stores/flowstore.js +++ b/web/src/js/stores/flowstore.js @@ -1,4 +1,4 @@ -function FlowStore(endpoint) { +function FlowStore() { this._views = []; this.reset(); } @@ -43,21 +43,46 @@ _.extend(FlowStore.prototype, { }); -function LiveFlowStore(endpoint) { +function LiveFlowStore() { FlowStore.call(this); this.updates_before_fetch = undefined; this._fetchxhr = false; - this.endpoint = endpoint || "/flows"; - this.conn = new Connection(this.endpoint + "/updates"); - this.conn.onopen = this._onopen.bind(this); - this.conn.onmessage = function (e) { - var message = JSON.parse(e.data); - this.handle_update(message.type, message.data); - }.bind(this); + + this.handle = this.handle.bind(this); + AppDispatcher.register(this.handle); + + // Avoid double-fetch on startup. + if(!(window.ws && window.ws.readyState === WebSocket.CONNECTING)) { + this.fetch(); + } } _.extend(LiveFlowStore.prototype, FlowStore.prototype, { + handle: function (event) { + switch (event.type) { + case ActionTypes.CONNECTION_OPEN: + case ActionTypes.RESET_FLOWS: + this.fetch(); + break; + case ActionTypes.ADD_FLOW: + case ActionTypes.UPDATE_FLOW: + case ActionTypes.REMOVE_FLOW: + if (this.updates_before_fetch) { + console.log("defer update", type, data); + this.updates_before_fetch.push(event); + } else { + if(event.type === ActionTypes.ADD_FLOW){ + this.add(event.data); + } else if (event.type === ActionTypes.UPDATE_FLOW){ + this.update(event.data); + } else { + this.remove(event.data); + } + } + break; + } + }, close: function () { - this.conn.close(); + AppDispatcher.unregister(this.handle); }, add: function (flow) { // Make sure that deferred adds don't add an element twice. @@ -65,32 +90,14 @@ _.extend(LiveFlowStore.prototype, FlowStore.prototype, { FlowStore.prototype.add.call(this, flow); } }, - _onopen: function () { - //Update stream openend, fetch list of flows. - console.log("Update Connection opened, fetching flows..."); - this.fetch(); - }, fetch: function () { + console.log("fetch"); if (this._fetchxhr) { this._fetchxhr.abort(); } - this._fetchxhr = $.getJSON(this.endpoint, this.handle_fetch.bind(this)); + this._fetchxhr = $.getJSON("/flows", this.handle_fetch.bind(this)); this.updates_before_fetch = []; // (JS: empty array is true) }, - handle_update: function (type, data) { - console.log("LiveFlowStore.handle_update", type, data); - - if (type === "reset") { - return this.fetch(); - } - - if (this.updates_before_fetch) { - console.log("defer update", type, data); - this.updates_before_fetch.push(arguments); - } else { - this[type](data); - } - }, handle_fetch: function (data) { this._fetchxhr = false; console.log("Flows fetched.", this.updates_before_fetch); @@ -98,7 +105,7 @@ _.extend(LiveFlowStore.prototype, FlowStore.prototype, { var updates = this.updates_before_fetch; this.updates_before_fetch = false; for (var i = 0; i < updates.length; i++) { - this.handle_update.apply(this, updates[i]); + this.handle(updates[i]); } }, }); -- cgit v1.2.3 From 05bc7e8cd8382aabdd44f7bc569d2fd421c26f21 Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Tue, 9 Dec 2014 18:55:16 +0100 Subject: generalize store --- web/src/js/stores/base.js | 112 +++++++++++++++++++++++++++++++++++++++++ web/src/js/stores/flowstore.js | 112 +---------------------------------------- 2 files changed, 114 insertions(+), 110 deletions(-) (limited to 'web/src/js') diff --git a/web/src/js/stores/base.js b/web/src/js/stores/base.js index cf9f015e..f9534bd2 100644 --- a/web/src/js/stores/base.js +++ b/web/src/js/stores/base.js @@ -27,3 +27,115 @@ EventEmitter.prototype.removeListener = function (events, f) { } }.bind(this)); }; + + +function Store() { + this._views = []; + this.reset(); +} +_.extend(Store.prototype, { + add: function (elem) { + if (elem.id in this._pos_map) { + return; + } + + this._pos_map[elem.id] = this._list.length; + this._list.push(elem); + for (var i = 0; i < this._views.length; i++) { + this._views[i].add(elem); + } + }, + update: function (elem) { + if (!(elem.id in this._pos_map)) { + return; + } + + this._list[this._pos_map[elem.id]] = elem; + for (var i = 0; i < this._views.length; i++) { + this._views[i].update(elem); + } + }, + remove: function (elem_id) { + if (!(elem.id in this._pos_map)) { + return; + } + + this._list.splice(this._pos_map[elem_id], 1); + this._build_map(); + for (var i = 0; i < this._views.length; i++) { + this._views[i].remove(elem_id); + } + }, + reset: function (elems) { + this._list = elems || []; + this._build_map(); + for (var i = 0; i < this._views.length; i++) { + this._views[i].recalculate(this._list); + } + }, + _build_map: function () { + this._pos_map = {}; + for (var i = 0; i < this._list.length; i++) { + var elem = this._list[i]; + this._pos_map[elem.id] = i; + } + }, + get: function (elem_id) { + return this._list[this._pos_map[elem_id]]; + } +}); + + +function LiveStore(type) { + Store.call(this); + this.type = type; + + this._updates_before_fetch = undefined; + this._fetchxhr = false; + + this.handle = this.handle.bind(this); + AppDispatcher.register(this.handle); + + // Avoid double-fetch on startup. + if (!(window.ws && window.ws.readyState === WebSocket.CONNECTING)) { + this.fetch(); + } +} +_.extend(LiveStore.prototype, Store.prototype, { + handle: function (event) { + if (event.type === ActionTypes.CONNECTION_OPEN) { + return this.fetch(); + } + if (event.type === this.type) { + if (event.cmd === "reset") { + this.fetch(); + } else if (this._updates_before_fetch) { + console.log("defer update", event); + this._updates_before_fetch.push(event); + } else { + this[event.cmd](event.data); + } + } + }, + close: function () { + AppDispatcher.unregister(this.handle); + }, + fetch: function () { + console.log("fetch " + this.type); + if (this._fetchxhr) { + this._fetchxhr.abort(); + } + this._fetchxhr = $.getJSON("/" + this.type, this.handle_fetch.bind(this)); + this._updates_before_fetch = []; // (JS: empty array is true) + }, + handle_fetch: function (data) { + this._fetchxhr = false; + console.log(this.type + " fetched.", this._updates_before_fetch); + this.reset(data.flows); + var updates = this._updates_before_fetch; + this._updates_before_fetch = false; + for (var i = 0; i < updates.length; i++) { + this.handle(updates[i]); + } + }, +}); \ No newline at end of file diff --git a/web/src/js/stores/flowstore.js b/web/src/js/stores/flowstore.js index 4110ea7f..8bffe5b2 100644 --- a/web/src/js/stores/flowstore.js +++ b/web/src/js/stores/flowstore.js @@ -1,114 +1,6 @@ -function FlowStore() { - this._views = []; - this.reset(); -} -_.extend(FlowStore.prototype, { - add: function (flow) { - this._pos_map[flow.id] = this._flow_list.length; - this._flow_list.push(flow); - for (var i = 0; i < this._views.length; i++) { - this._views[i].add(flow); - } - }, - update: function (flow) { - this._flow_list[this._pos_map[flow.id]] = flow; - for (var i = 0; i < this._views.length; i++) { - this._views[i].update(flow); - } - }, - remove: function (flow_id) { - this._flow_list.splice(this._pos_map[flow_id], 1); - this._build_map(); - for (var i = 0; i < this._views.length; i++) { - this._views[i].remove(flow_id); - } - }, - reset: function (flows) { - this._flow_list = flows || []; - this._build_map(); - for (var i = 0; i < this._views.length; i++) { - this._views[i].recalculate(this._flow_list); - } - }, - _build_map: function () { - this._pos_map = {}; - for (var i = 0; i < this._flow_list.length; i++) { - var flow = this._flow_list[i]; - this._pos_map[flow.id] = i; - } - }, - get: function (flow_id) { - return this._flow_list[this._pos_map[flow_id]]; - } -}); - - function LiveFlowStore() { - FlowStore.call(this); - this.updates_before_fetch = undefined; - this._fetchxhr = false; - - this.handle = this.handle.bind(this); - AppDispatcher.register(this.handle); - - // Avoid double-fetch on startup. - if(!(window.ws && window.ws.readyState === WebSocket.CONNECTING)) { - this.fetch(); - } + return new LiveStore("flows"); } -_.extend(LiveFlowStore.prototype, FlowStore.prototype, { - handle: function (event) { - switch (event.type) { - case ActionTypes.CONNECTION_OPEN: - case ActionTypes.RESET_FLOWS: - this.fetch(); - break; - case ActionTypes.ADD_FLOW: - case ActionTypes.UPDATE_FLOW: - case ActionTypes.REMOVE_FLOW: - if (this.updates_before_fetch) { - console.log("defer update", type, data); - this.updates_before_fetch.push(event); - } else { - if(event.type === ActionTypes.ADD_FLOW){ - this.add(event.data); - } else if (event.type === ActionTypes.UPDATE_FLOW){ - this.update(event.data); - } else { - this.remove(event.data); - } - } - break; - } - }, - close: function () { - AppDispatcher.unregister(this.handle); - }, - add: function (flow) { - // Make sure that deferred adds don't add an element twice. - if (!(flow.id in this._pos_map)) { - FlowStore.prototype.add.call(this, flow); - } - }, - fetch: function () { - console.log("fetch"); - if (this._fetchxhr) { - this._fetchxhr.abort(); - } - this._fetchxhr = $.getJSON("/flows", this.handle_fetch.bind(this)); - this.updates_before_fetch = []; // (JS: empty array is true) - }, - handle_fetch: function (data) { - this._fetchxhr = false; - console.log("Flows fetched.", this.updates_before_fetch); - this.reset(data.flows); - var updates = this.updates_before_fetch; - this.updates_before_fetch = false; - for (var i = 0; i < updates.length; i++) { - this.handle(updates[i]); - } - }, -}); function SortByInsertionOrder() { this.i = 0; @@ -134,7 +26,7 @@ function FlowView(store, filt, sortfun) { this.store = store; this.store._views.push(this); - this.recalculate(this.store._flow_list, filt, sortfun); + this.recalculate(this.store._list, filt, sortfun); } _.extend(FlowView.prototype, EventEmitter.prototype, { -- cgit v1.2.3 From e12bf19e35867f3ea69f45054decb024a75fc2b4 Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Wed, 10 Dec 2014 00:47:05 +0100 Subject: web: add event store, fix all those bugs --- web/src/js/actions.js | 16 +++- web/src/js/components/eventlog.jsx.js | 35 +++---- web/src/js/components/flowtable.jsx.js | 4 +- web/src/js/components/mainview.jsx.js | 2 +- web/src/js/components/proxyapp.jsx.js | 2 +- web/src/js/components/virtualscroll.jsx.js | 14 +-- web/src/js/dispatcher.js | 4 +- web/src/js/store/settingstore.js | 28 ++++++ web/src/js/store/store.js | 123 +++++++++++++++++++++++++ web/src/js/store/view.js | 87 ++++++++++++++++++ web/src/js/stores/base.js | 141 ----------------------------- web/src/js/stores/eventlogstore.js | 99 -------------------- web/src/js/stores/flowstore.js | 105 --------------------- web/src/js/stores/settingstore.js | 28 ------ web/src/js/utils.js | 36 ++++++++ 15 files changed, 317 insertions(+), 407 deletions(-) create mode 100644 web/src/js/store/settingstore.js create mode 100644 web/src/js/store/store.js create mode 100644 web/src/js/store/view.js delete mode 100644 web/src/js/stores/base.js delete mode 100644 web/src/js/stores/eventlogstore.js delete mode 100644 web/src/js/stores/flowstore.js delete mode 100644 web/src/js/stores/settingstore.js (limited to 'web/src/js') diff --git a/web/src/js/actions.js b/web/src/js/actions.js index 28bb58b8..82fbcdca 100644 --- a/web/src/js/actions.js +++ b/web/src/js/actions.js @@ -8,13 +8,18 @@ var ActionTypes = { UPDATE_SETTINGS: "update_settings", // EventLog + EVENT_STORE: "events", ADD_EVENT: "add_event", // Flow - ADD_FLOW: "add_flow", - UPDATE_FLOW: "update_flow", - REMOVE_FLOW: "remove_flow", - RESET_FLOWS: "reset_flows", + FLOW_STORE: "flows", +}; + +var StoreCmds = { + ADD: "add", + UPDATE: "update", + REMOVE: "remove", + RESET: "reset" }; var ConnectionActions = { @@ -52,7 +57,8 @@ var EventLogActions_event_id = 0; var EventLogActions = { add_event: function (message) { AppDispatcher.dispatchViewAction({ - type: ActionTypes.ADD_EVENT, + type: ActionTypes.EVENT_STORE, + cmd: StoreCmds.ADD, data: { message: message, level: "web", diff --git a/web/src/js/components/eventlog.jsx.js b/web/src/js/components/eventlog.jsx.js index 708432d0..3bd188ea 100644 --- a/web/src/js/components/eventlog.jsx.js +++ b/web/src/js/components/eventlog.jsx.js @@ -25,32 +25,33 @@ var LogMessage = React.createClass({ var EventLogContents = React.createClass({ mixins: [AutoScrollMixin, VirtualScrollMixin], - getInitialState: function () { + getInitialState: function(){ + var store = new EventLogStore(); + var view = new StoreView(store, function(entry){ + return this.props.filter[entry.level]; + }.bind(this)); + view.addListener("add recalculate", this.onEventLogChange); return { + store: store, + view: view, log: [] }; }, - componentDidMount: function () { - this.log = EventLogStore.getView(); - this.log.addListener("change", this.onEventLogChange); - }, componentWillUnmount: function () { - this.log.removeListener("change", this.onEventLogChange); - this.log.close(); + this.state.view.removeListener("add recalculate", this.onEventLogChange); + this.state.view.close(); + this.state.store.close(); }, onEventLogChange: function () { - var log = this.log.getAll().filter(function (entry) { - return this.props.filter[entry.level]; - }.bind(this)); this.setState({ - log: log + log: this.state.view.list }); }, - componentWillReceiveProps: function () { - if (this.log) { - this.onEventLogChange(); + componentWillReceiveProps: function (nextProps) { + if(nextProps.filter !== this.props.filter){ + this.props.filter = nextProps.filter; // Dirty: Make sure that view filter sees the update. + this.state.view.recalculate(this.state.store._list); } - }, getDefaultProps: function () { return { @@ -66,7 +67,7 @@ var EventLogContents = React.createClass({ var rows = this.renderRows(this.state.log); return
-            { this.getPlaceholderTop() }
+            { this.getPlaceholderTop(this.state.log.length) }
             {rows}
             { this.getPlaceholderBottom(this.state.log.length) }
         
; @@ -112,7 +113,7 @@ var EventLog = React.createClass({ }); }, toggleLevel: function (level) { - var filter = this.state.filter; + var filter = _.extend({}, this.state.filter); filter[level] = !filter[level]; this.setState({filter: filter}); }, diff --git a/web/src/js/components/flowtable.jsx.js b/web/src/js/components/flowtable.jsx.js index 1a4efe89..4b72dd29 100644 --- a/web/src/js/components/flowtable.jsx.js +++ b/web/src/js/components/flowtable.jsx.js @@ -88,7 +88,7 @@ var FlowTable = React.createClass({ }, render: function () { //console.log("render flowtable", this.state.start, this.state.stop, this.props.selected); - var flows = this.props.view ? this.props.view.flows : []; + var flows = this.props.view ? this.props.view.list : []; var rows = this.renderRows(flows); @@ -98,7 +98,7 @@ var FlowTable = React.createClass({
- { this.getPlaceholderTop() } + { this.getPlaceholderTop(flows.length) } {rows} { this.getPlaceholderBottom(flows.length) } diff --git a/web/src/js/components/mainview.jsx.js b/web/src/js/components/mainview.jsx.js index c7c9ee9b..570962e0 100644 --- a/web/src/js/components/mainview.jsx.js +++ b/web/src/js/components/mainview.jsx.js @@ -12,7 +12,7 @@ var MainView = React.createClass({ } }, openView: function (store) { - var view = new FlowView(store); + var view = new StoreView(store); this.setState({ view: view }); diff --git a/web/src/js/components/proxyapp.jsx.js b/web/src/js/components/proxyapp.jsx.js index 3545cfe0..e03b1a57 100644 --- a/web/src/js/components/proxyapp.jsx.js +++ b/web/src/js/components/proxyapp.jsx.js @@ -10,7 +10,7 @@ var ProxyAppMain = React.createClass({ getInitialState: function () { return { settings: SettingsStore.getAll(), - flowStore: new LiveFlowStore() + flowStore: new FlowStore() }; }, componentDidMount: function () { diff --git a/web/src/js/components/virtualscroll.jsx.js b/web/src/js/components/virtualscroll.jsx.js index 5a67bbf5..b1924949 100644 --- a/web/src/js/components/virtualscroll.jsx.js +++ b/web/src/js/components/virtualscroll.jsx.js @@ -5,15 +5,17 @@ var VirtualScrollMixin = { stop: 0 }; }, - componentWillMount: function(){ - if(!this.props.rowHeight){ + componentWillMount: function () { + if (!this.props.rowHeight) { console.warn("VirtualScrollMixin: No rowHeight specified", this); } }, - getPlaceholderTop: function () { + getPlaceholderTop: function (total) { var Tag = this.props.placeholderTagName || "tr"; + // When a large trunk of elements is removed from the button, start may be far off the viewport. + // To make this issue less severe, limit the top placeholder to the total number of rows. var style = { - height: this.state.start * this.props.rowHeight + height: Math.min(this.state.start, total) * this.props.rowHeight }; var spacer = ; @@ -46,7 +48,7 @@ var VirtualScrollMixin = { stop: stop }); }, - renderRows: function(elems){ + renderRows: function (elems) { var rows = []; var max = Math.min(elems.length, this.state.stop); @@ -56,7 +58,7 @@ var VirtualScrollMixin = { } return rows; }, - scrollRowIntoView: function(index, head_height){ + scrollRowIntoView: function (index, head_height) { var row_top = (index * this.props.rowHeight) + head_height; var row_bottom = row_top + this.props.rowHeight; diff --git a/web/src/js/dispatcher.js b/web/src/js/dispatcher.js index 7fa97481..860ade9f 100644 --- a/web/src/js/dispatcher.js +++ b/web/src/js/dispatcher.js @@ -11,9 +11,9 @@ Dispatcher.prototype.register = function (callback) { this.callbacks.push(callback); }; Dispatcher.prototype.unregister = function (callback) { - var index = this.callbacks.indexOf(f); + var index = this.callbacks.indexOf(callback); if (index >= 0) { - this.callbacks.splice(this.callbacks.indexOf(f), 1); + this.callbacks.splice(index, 1); } }; Dispatcher.prototype.dispatch = function (payload) { diff --git a/web/src/js/store/settingstore.js b/web/src/js/store/settingstore.js new file mode 100644 index 00000000..7eef9b8f --- /dev/null +++ b/web/src/js/store/settingstore.js @@ -0,0 +1,28 @@ +function _SettingsStore() { + EventEmitter.call(this); + + //FIXME: What do we do if we haven't requested anything from the server yet? + this.settings = { + version: "0.12", + showEventLog: true, + mode: "transparent", + }; +} +_.extend(_SettingsStore.prototype, EventEmitter.prototype, { + getAll: function () { + return this.settings; + }, + handle: function (action) { + switch (action.type) { + case ActionTypes.UPDATE_SETTINGS: + this.settings = action.settings; + this.emit("change"); + break; + default: + return; + } + } +}); + +var SettingsStore = new _SettingsStore(); +AppDispatcher.register(SettingsStore.handle.bind(SettingsStore)); diff --git a/web/src/js/store/store.js b/web/src/js/store/store.js new file mode 100644 index 00000000..da288a5d --- /dev/null +++ b/web/src/js/store/store.js @@ -0,0 +1,123 @@ +function Store() { + this._views = []; + this.reset(); +} +_.extend(Store.prototype, { + add: function (elem) { + if (elem.id in this._pos_map) { + return; + } + + this._pos_map[elem.id] = this._list.length; + this._list.push(elem); + for (var i = 0; i < this._views.length; i++) { + this._views[i].add(elem); + } + }, + update: function (elem) { + if (!(elem.id in this._pos_map)) { + return; + } + + this._list[this._pos_map[elem.id]] = elem; + for (var i = 0; i < this._views.length; i++) { + this._views[i].update(elem); + } + }, + remove: function (elem_id) { + if (!(elem.id in this._pos_map)) { + return; + } + + this._list.splice(this._pos_map[elem_id], 1); + this._build_map(); + for (var i = 0; i < this._views.length; i++) { + this._views[i].remove(elem_id); + } + }, + reset: function (elems) { + this._list = elems || []; + this._build_map(); + for (var i = 0; i < this._views.length; i++) { + this._views[i].recalculate(this._list); + } + }, + _build_map: function () { + this._pos_map = {}; + for (var i = 0; i < this._list.length; i++) { + var elem = this._list[i]; + this._pos_map[elem.id] = i; + } + }, + get: function (elem_id) { + return this._list[this._pos_map[elem_id]]; + }, + index: function(elem_id) { + return this._pos_map[elem_id]; + } +}); + + +function LiveStore(type) { + Store.call(this); + this.type = type; + + this._updates_before_fetch = undefined; + this._fetchxhr = false; + + this.handle = this.handle.bind(this); + AppDispatcher.register(this.handle); + + // Avoid double-fetch on startup. + if (!(window.ws && window.ws.readyState === WebSocket.CONNECTING)) { + this.fetch(); + } +} +_.extend(LiveStore.prototype, Store.prototype, { + handle: function (event) { + if (event.type === ActionTypes.CONNECTION_OPEN) { + return this.fetch(); + } + if (event.type === this.type) { + if (event.cmd === StoreCmds.RESET) { + this.fetch(); + } else if (this._updates_before_fetch) { + console.log("defer update", event); + this._updates_before_fetch.push(event); + } else { + this[event.cmd](event.data); + } + } + }, + close: function () { + AppDispatcher.unregister(this.handle); + }, + fetch: function () { + console.log("fetch " + this.type); + if (this._fetchxhr) { + this._fetchxhr.abort(); + } + this._fetchxhr = $.getJSON("/" + this.type, this.handle_fetch.bind(this)); + this._updates_before_fetch = []; // (JS: empty array is true) + }, + handle_fetch: function (data) { + this._fetchxhr = false; + console.log(this.type + " fetched.", this._updates_before_fetch); + this.reset(data.list); + var updates = this._updates_before_fetch; + this._updates_before_fetch = false; + for (var i = 0; i < updates.length; i++) { + this.handle(updates[i]); + } + }, +}); + + +function FlowStore() { + return new LiveStore(ActionTypes.FLOW_STORE); +} + + +function EventLogStore() { + return new LiveStore(ActionTypes.EVENT_STORE); +} \ No newline at end of file diff --git a/web/src/js/store/view.js b/web/src/js/store/view.js new file mode 100644 index 00000000..261429b2 --- /dev/null +++ b/web/src/js/store/view.js @@ -0,0 +1,87 @@ +function SortByStoreOrder(elem) { + return this.store.index(elem.id); +} + +var default_sort = SortByStoreOrder; +var default_filt = function(elem){ + return true; +}; + +function StoreView(store, filt, sortfun) { + EventEmitter.call(this); + filt = filt || default_filt; + sortfun = sortfun || default_sort; + + this.store = store; + this.store._views.push(this); + this.recalculate(this.store._list, filt, sortfun); +} + +_.extend(StoreView.prototype, EventEmitter.prototype, { + close: function () { + this.store._views = _.without(this.store._views, this); + }, + recalculate: function (elems, filt, sortfun) { + if (filt) { + this.filt = filt; + } + if (sortfun) { + this.sortfun = sortfun.bind(this); + } + + this.list = elems.filter(this.filt); + this.list.sort(function (a, b) { + return this.sortfun(a) - this.sortfun(b); + }.bind(this)); + this.emit("recalculate"); + }, + index: function (elem) { + return _.sortedIndex(this.list, elem, this.sortfun); + }, + add: function (elem) { + if (this.filt(elem)) { + var idx = this.index(elem); + if (idx === this.list.length) { //happens often, .push is way faster. + this.list.push(elem); + } else { + this.list.splice(idx, 0, elem); + } + this.emit("add", elem, idx); + } + }, + update: function (elem) { + var idx; + var i = this.list.length; + // Search from the back, we usually update the latest entries. + while (i--) { + if (this.list[i].id === elem.id) { + idx = i; + break; + } + } + + if (idx === -1) { //not contained in list + this.add(elem); + } else if (!this.filt(elem)) { + this.remove(elem.id); + } else { + if (this.sortfun(this.list[idx]) !== this.sortfun(elem)) { //sortpos has changed + this.remove(this.list[idx]); + this.add(elem); + } else { + this.list[idx] = elem; + this.emit("update", elem, idx); + } + } + }, + remove: function (elem_id) { + var idx = this.list.length; + while (idx--) { + if (this.list[idx].id === elem_id) { + this.list.splice(idx, 1); + this.emit("remove", elem_id, idx); + break; + } + } + } +}); \ No newline at end of file diff --git a/web/src/js/stores/base.js b/web/src/js/stores/base.js deleted file mode 100644 index f9534bd2..00000000 --- a/web/src/js/stores/base.js +++ /dev/null @@ -1,141 +0,0 @@ -function EventEmitter() { - this.listeners = {}; -} -EventEmitter.prototype.emit = function (event) { - if (!(event in this.listeners)) { - return; - } - var args = Array.prototype.slice.call(arguments, 1); - this.listeners[event].forEach(function (listener) { - listener.apply(this, args); - }.bind(this)); -}; -EventEmitter.prototype.addListener = function (events, f) { - events.split(" ").forEach(function (event) { - this.listeners[event] = this.listeners[event] || []; - this.listeners[event].push(f); - }.bind(this)); -}; -EventEmitter.prototype.removeListener = function (events, f) { - if (!(events in this.listeners)) { - return false; - } - events.split(" ").forEach(function (event) { - var index = this.listeners[event].indexOf(f); - if (index >= 0) { - this.listeners[event].splice(index, 1); - } - }.bind(this)); -}; - - -function Store() { - this._views = []; - this.reset(); -} -_.extend(Store.prototype, { - add: function (elem) { - if (elem.id in this._pos_map) { - return; - } - - this._pos_map[elem.id] = this._list.length; - this._list.push(elem); - for (var i = 0; i < this._views.length; i++) { - this._views[i].add(elem); - } - }, - update: function (elem) { - if (!(elem.id in this._pos_map)) { - return; - } - - this._list[this._pos_map[elem.id]] = elem; - for (var i = 0; i < this._views.length; i++) { - this._views[i].update(elem); - } - }, - remove: function (elem_id) { - if (!(elem.id in this._pos_map)) { - return; - } - - this._list.splice(this._pos_map[elem_id], 1); - this._build_map(); - for (var i = 0; i < this._views.length; i++) { - this._views[i].remove(elem_id); - } - }, - reset: function (elems) { - this._list = elems || []; - this._build_map(); - for (var i = 0; i < this._views.length; i++) { - this._views[i].recalculate(this._list); - } - }, - _build_map: function () { - this._pos_map = {}; - for (var i = 0; i < this._list.length; i++) { - var elem = this._list[i]; - this._pos_map[elem.id] = i; - } - }, - get: function (elem_id) { - return this._list[this._pos_map[elem_id]]; - } -}); - - -function LiveStore(type) { - Store.call(this); - this.type = type; - - this._updates_before_fetch = undefined; - this._fetchxhr = false; - - this.handle = this.handle.bind(this); - AppDispatcher.register(this.handle); - - // Avoid double-fetch on startup. - if (!(window.ws && window.ws.readyState === WebSocket.CONNECTING)) { - this.fetch(); - } -} -_.extend(LiveStore.prototype, Store.prototype, { - handle: function (event) { - if (event.type === ActionTypes.CONNECTION_OPEN) { - return this.fetch(); - } - if (event.type === this.type) { - if (event.cmd === "reset") { - this.fetch(); - } else if (this._updates_before_fetch) { - console.log("defer update", event); - this._updates_before_fetch.push(event); - } else { - this[event.cmd](event.data); - } - } - }, - close: function () { - AppDispatcher.unregister(this.handle); - }, - fetch: function () { - console.log("fetch " + this.type); - if (this._fetchxhr) { - this._fetchxhr.abort(); - } - this._fetchxhr = $.getJSON("/" + this.type, this.handle_fetch.bind(this)); - this._updates_before_fetch = []; // (JS: empty array is true) - }, - handle_fetch: function (data) { - this._fetchxhr = false; - console.log(this.type + " fetched.", this._updates_before_fetch); - this.reset(data.flows); - var updates = this._updates_before_fetch; - this._updates_before_fetch = false; - for (var i = 0; i < updates.length; i++) { - this.handle(updates[i]); - } - }, -}); \ No newline at end of file diff --git a/web/src/js/stores/eventlogstore.js b/web/src/js/stores/eventlogstore.js deleted file mode 100644 index 439c2360..00000000 --- a/web/src/js/stores/eventlogstore.js +++ /dev/null @@ -1,99 +0,0 @@ -// -// We have an EventLogView and an EventLogStore: -// The basic architecture is that one can request views on the event log -// from the store, which returns a view object and then deals with getting the data required for the view. -// The view object is accessed by React components and distributes updates etc. -// -// See also: components/EventLog.react.js -function EventLogView(store, live) { - EventEmitter.call(this); - this._store = store; - this.live = live; - this.log = []; - - this.add = this.add.bind(this); - - if (live) { - this._store.addListener(ActionTypes.ADD_EVENT, this.add); - } -} -_.extend(EventLogView.prototype, EventEmitter.prototype, { - close: function () { - this._store.removeListener(ActionTypes.ADD_EVENT, this.add); - }, - getAll: function () { - return this.log; - }, - add: function (entry) { - this.log.push(entry); - if (this.log.length > 200) { - this.log.shift(); - } - this.emit("change"); - }, - add_bulk: function (messages) { - var log = messages; - var last_id = log[log.length - 1].id; - var to_add = _.filter(this.log, function (entry) { - return entry.id > last_id; - }); - this.log = log.concat(to_add); - this.emit("change"); - } -}); - - -function _EventLogStore() { - EventEmitter.call(this); -} -_.extend(_EventLogStore.prototype, EventEmitter.prototype, { - getView: function (since) { - var view = new EventLogView(this, !since); - return view; - /* - //TODO: Really do bulk retrieval of last messages. - window.setTimeout(function () { - view.add_bulk([ - { - id: 1, - message: "Hello World" - }, - { - id: 2, - message: "I was already transmitted as an event." - } - ]); - }, 100); - - var id = 2; - view.add({ - id: id++, - message: "I was already transmitted as an event." - }); - view.add({ - id: id++, - message: "I was only transmitted as an event before the bulk was added.." - }); - window.setInterval(function () { - view.add({ - id: id++, - message: "." - }); - }, 1000); - return view; - */ - }, - handle: function (action) { - switch (action.type) { - case ActionTypes.ADD_EVENT: - this.emit(ActionTypes.ADD_EVENT, action.data); - break; - default: - return; - } - } -}); - - -var EventLogStore = new _EventLogStore(); -AppDispatcher.register(EventLogStore.handle.bind(EventLogStore)); \ No newline at end of file diff --git a/web/src/js/stores/flowstore.js b/web/src/js/stores/flowstore.js deleted file mode 100644 index 8bffe5b2..00000000 --- a/web/src/js/stores/flowstore.js +++ /dev/null @@ -1,105 +0,0 @@ -function LiveFlowStore() { - return new LiveStore("flows"); -} - -function SortByInsertionOrder() { - this.i = 0; - this.map = {}; - this.key = this.key.bind(this); -} -SortByInsertionOrder.prototype.key = function (flow) { - if (!(flow.id in this.map)) { - this.i++; - this.map[flow.id] = this.i; - } - return this.map[flow.id]; -}; - -var default_sort = (new SortByInsertionOrder()).key; - -function FlowView(store, filt, sortfun) { - EventEmitter.call(this); - filt = filt || function (flow) { - return true; - }; - sortfun = sortfun || default_sort; - - this.store = store; - this.store._views.push(this); - this.recalculate(this.store._list, filt, sortfun); -} - -_.extend(FlowView.prototype, EventEmitter.prototype, { - close: function () { - this.store._views = _.without(this.store._views, this); - }, - recalculate: function (flows, filt, sortfun) { - if (filt) { - this.filt = filt; - } - if (sortfun) { - this.sortfun = sortfun; - } - - //Ugly workaround: Call .sortfun() for each flow once in order, - //so that SortByInsertionOrder make sense. - for (var i = 0; i < flows.length; i++) { - this.sortfun(flows[i]); - } - - this.flows = flows.filter(this.filt); - this.flows.sort(function (a, b) { - return this.sortfun(a) - this.sortfun(b); - }.bind(this)); - this.emit("recalculate"); - }, - index: function (flow) { - return _.sortedIndex(this.flows, flow, this.sortfun); - }, - add: function (flow) { - if (this.filt(flow)) { - var idx = this.index(flow); - if (idx === this.flows.length) { //happens often, .push is way faster. - this.flows.push(flow); - } else { - this.flows.splice(idx, 0, flow); - } - this.emit("add", flow, idx); - } - }, - update: function (flow) { - var idx; - var i = this.flows.length; - // Search from the back, we usually update the latest flows. - while (i--) { - if (this.flows[i].id === flow.id) { - idx = i; - break; - } - } - - if (idx === -1) { //not contained in list - this.add(flow); - } else if (!this.filt(flow)) { - this.remove(flow.id); - } else { - if (this.sortfun(this.flows[idx]) !== this.sortfun(flow)) { //sortpos has changed - this.remove(this.flows[idx]); - this.add(flow); - } else { - this.flows[idx] = flow; - this.emit("update", flow, idx); - } - } - }, - remove: function (flow_id) { - var i = this.flows.length; - while (i--) { - if (this.flows[i].id === flow_id) { - this.flows.splice(i, 1); - this.emit("remove", flow_id, i); - break; - } - } - } -}); \ No newline at end of file diff --git a/web/src/js/stores/settingstore.js b/web/src/js/stores/settingstore.js deleted file mode 100644 index 7eef9b8f..00000000 --- a/web/src/js/stores/settingstore.js +++ /dev/null @@ -1,28 +0,0 @@ -function _SettingsStore() { - EventEmitter.call(this); - - //FIXME: What do we do if we haven't requested anything from the server yet? - this.settings = { - version: "0.12", - showEventLog: true, - mode: "transparent", - }; -} -_.extend(_SettingsStore.prototype, EventEmitter.prototype, { - getAll: function () { - return this.settings; - }, - handle: function (action) { - switch (action.type) { - case ActionTypes.UPDATE_SETTINGS: - this.settings = action.settings; - this.emit("change"); - break; - default: - return; - } - } -}); - -var SettingsStore = new _SettingsStore(); -AppDispatcher.register(SettingsStore.handle.bind(SettingsStore)); diff --git a/web/src/js/utils.js b/web/src/js/utils.js index 329de956..8ae7aa54 100644 --- a/web/src/js/utils.js +++ b/web/src/js/utils.js @@ -12,6 +12,7 @@ var AutoScrollMixin = { }, }; + var StickyHeadMixin = { adjustHead: function () { // Abusing CSS transforms to set the element @@ -21,6 +22,7 @@ var StickyHeadMixin = { } }; + var Key = { UP: 38, DOWN: 40, @@ -38,6 +40,7 @@ var Key = { L: 76 }; + var formatSize = function (bytes) { var size = bytes; var prefix = ["B", "KB", "MB", "GB", "TB"]; @@ -49,6 +52,7 @@ var formatSize = function (bytes) { return (Math.floor(size * 100) / 100.0).toFixed(2) + prefix[i]; }; + var formatTimeDelta = function (milliseconds) { var time = milliseconds; var prefix = ["ms", "s", "min", "h"]; @@ -61,7 +65,39 @@ var formatTimeDelta = function (milliseconds) { return Math.round(time) + prefix[i]; }; + var formatTimeStamp = function (seconds) { var ts = (new Date(seconds * 1000)).toISOString(); return ts.replace("T", " ").replace("Z", ""); +}; + + +function EventEmitter() { + this.listeners = {}; +} +EventEmitter.prototype.emit = function (event) { + if (!(event in this.listeners)) { + return; + } + var args = Array.prototype.slice.call(arguments, 1); + this.listeners[event].forEach(function (listener) { + listener.apply(this, args); + }.bind(this)); +}; +EventEmitter.prototype.addListener = function (events, f) { + events.split(" ").forEach(function (event) { + this.listeners[event] = this.listeners[event] || []; + this.listeners[event].push(f); + }.bind(this)); +}; +EventEmitter.prototype.removeListener = function (events, f) { + if (!(events in this.listeners)) { + return false; + } + events.split(" ").forEach(function (event) { + var index = this.listeners[event].indexOf(f); + if (index >= 0) { + this.listeners[event].splice(index, 1); + } + }.bind(this)); }; \ No newline at end of file -- cgit v1.2.3 From d2feaf5d84820e75e3931522d889748563972c75 Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Wed, 10 Dec 2014 02:48:04 +0100 Subject: web: take viewport resize into account --- web/src/js/components/proxyapp.jsx.js | 14 ++++++++++++-- web/src/js/components/utils.jsx.js | 16 ++++++++++++---- web/src/js/components/virtualscroll.jsx.js | 4 ++++ 3 files changed, 28 insertions(+), 6 deletions(-) (limited to 'web/src/js') diff --git a/web/src/js/components/proxyapp.jsx.js b/web/src/js/components/proxyapp.jsx.js index e03b1a57..e2b32e55 100644 --- a/web/src/js/components/proxyapp.jsx.js +++ b/web/src/js/components/proxyapp.jsx.js @@ -23,12 +23,22 @@ var ProxyAppMain = React.createClass({ this.setState({settings: SettingsStore.getAll()}); }, render: function () { + + var eventlog; + if (this.state.settings.showEventLog) { + eventlog = [ + , + + ]; + } else { + eventlog = null; + } + return (
- {this.state.settings.showEventLog ? : null} - {this.state.settings.showEventLog ? : null} + {eventlog}
); diff --git a/web/src/js/components/utils.jsx.js b/web/src/js/components/utils.jsx.js index 12775adc..81ba6b4d 100644 --- a/web/src/js/components/utils.jsx.js +++ b/web/src/js/components/utils.jsx.js @@ -51,6 +51,7 @@ var Splitter = React.createClass({ this.setState({ applied: true }); + this.onResize(); }, onMouseMove: function (e) { var dX = 0, dY = 0; @@ -61,6 +62,13 @@ var Splitter = React.createClass({ } this.getDOMNode().style.transform = "translate(" + dX + "px," + dY + "px)"; }, + onResize: function () { + // Trigger a global resize event. This notifies components that employ virtual scrolling + // that their viewport may have changed. + window.setTimeout(function () { + window.dispatchEvent(new CustomEvent("resize")); + }, 1); + }, reset: function (willUnmount) { if (!this.state.applied) { return; @@ -77,7 +85,7 @@ var Splitter = React.createClass({ applied: false }); } - + this.onResize(); }, componentWillUnmount: function () { this.reset(true); @@ -104,9 +112,9 @@ function getCookie(name) { var xsrf = $.param({_xsrf: getCookie("_xsrf")}); //Tornado XSRF Protection. -$.ajaxPrefilter(function(options){ - if(options.type === "post" && options.url[0] === "/"){ - if(options.data){ +$.ajaxPrefilter(function (options) { + if (options.type === "post" && options.url[0] === "/") { + if (options.data) { options.data += ("&" + xsrf); } else { options.data = xsrf; diff --git a/web/src/js/components/virtualscroll.jsx.js b/web/src/js/components/virtualscroll.jsx.js index b1924949..4f946cb4 100644 --- a/web/src/js/components/virtualscroll.jsx.js +++ b/web/src/js/components/virtualscroll.jsx.js @@ -35,6 +35,10 @@ var VirtualScrollMixin = { }, componentDidMount: function () { this.onScroll(); + window.addEventListener('resize', this.onScroll); + }, + componentWillUnmount: function(){ + window.removeEventListener('resize', this.onScroll); }, onScroll: function () { var viewport = this.getDOMNode(); -- cgit v1.2.3 From 7e40b8ab09d6d605307342fbfa21129ca15ff055 Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Wed, 10 Dec 2014 15:25:40 +0100 Subject: web: implement settings store, modularize store --- web/src/js/actions.js | 16 ++--- web/src/js/components/eventlog.jsx.js | 39 +++++++----- web/src/js/components/mainview.jsx.js | 2 +- web/src/js/components/proxyapp.jsx.js | 33 ++++++---- web/src/js/store/settingstore.js | 28 --------- web/src/js/store/store.js | 115 +++++++++++++++++++++++----------- web/src/js/store/view.js | 18 +++++- 7 files changed, 147 insertions(+), 104 deletions(-) (limited to 'web/src/js') diff --git a/web/src/js/actions.js b/web/src/js/actions.js index 82fbcdca..2c4183e4 100644 --- a/web/src/js/actions.js +++ b/web/src/js/actions.js @@ -4,14 +4,9 @@ var ActionTypes = { CONNECTION_CLOSE: "connection_close", CONNECTION_ERROR: "connection_error", - // Settings - UPDATE_SETTINGS: "update_settings", - - // EventLog + // Stores + SETTINGS_STORE: "settings", EVENT_STORE: "events", - ADD_EVENT: "add_event", - - // Flow FLOW_STORE: "flows", }; @@ -42,13 +37,14 @@ var ConnectionActions = { var SettingsActions = { update: function (settings) { - settings = _.merge({}, SettingsStore.getAll(), settings); + //TODO: Update server. //Facebook Flux: We do an optimistic update on the client already. AppDispatcher.dispatchViewAction({ - type: ActionTypes.UPDATE_SETTINGS, - settings: settings + type: ActionTypes.SETTINGS_STORE, + cmd: StoreCmds.UPDATE, + data: settings }); } }; diff --git a/web/src/js/components/eventlog.jsx.js b/web/src/js/components/eventlog.jsx.js index 3bd188ea..7ef369f8 100644 --- a/web/src/js/components/eventlog.jsx.js +++ b/web/src/js/components/eventlog.jsx.js @@ -25,22 +25,29 @@ var LogMessage = React.createClass({ var EventLogContents = React.createClass({ mixins: [AutoScrollMixin, VirtualScrollMixin], - getInitialState: function(){ - var store = new EventLogStore(); - var view = new StoreView(store, function(entry){ - return this.props.filter[entry.level]; - }.bind(this)); - view.addListener("add recalculate", this.onEventLogChange); + getInitialState: function () { return { - store: store, - view: view, log: [] }; }, + componentWillMount: function () { + this.openView(this.props.eventStore); + }, componentWillUnmount: function () { - this.state.view.removeListener("add recalculate", this.onEventLogChange); + this.closeView(); + }, + openView: function (store) { + var view = new StoreView(store, function (entry) { + return this.props.filter[entry.level]; + }.bind(this)); + this.setState({ + view: view + }); + + view.addListener("add recalculate", this.onEventLogChange); + }, + closeView: function () { this.state.view.close(); - this.state.store.close(); }, onEventLogChange: function () { this.setState({ @@ -48,9 +55,13 @@ var EventLogContents = React.createClass({ }); }, componentWillReceiveProps: function (nextProps) { - if(nextProps.filter !== this.props.filter){ + if (nextProps.filter !== this.props.filter) { this.props.filter = nextProps.filter; // Dirty: Make sure that view filter sees the update. - this.state.view.recalculate(this.state.store._list); + this.state.view.recalculate(this.props.eventStore.list); + } + if (nextProps.eventStore !== this.props.eventStore) { + this.closeView(); + this.openView(nextProps.eventStore); } }, getDefaultProps: function () { @@ -60,7 +71,7 @@ var EventLogContents = React.createClass({ placeholderTagName: "div" }; }, - renderRow: function(elem){ + renderRow: function (elem) { return ; }, render: function () { @@ -130,7 +141,7 @@ var EventLog = React.createClass({ - + ); } diff --git a/web/src/js/components/mainview.jsx.js b/web/src/js/components/mainview.jsx.js index 570962e0..17a024ee 100644 --- a/web/src/js/components/mainview.jsx.js +++ b/web/src/js/components/mainview.jsx.js @@ -56,7 +56,7 @@ var MainView = React.createClass({ } }, selectFlowRelative: function (shift) { - var flows = this.state.view.flows; + var flows = this.state.view.list; var index; if (!this.getParams().flowId) { if (shift > 0) { diff --git a/web/src/js/components/proxyapp.jsx.js b/web/src/js/components/proxyapp.jsx.js index e2b32e55..20162ad1 100644 --- a/web/src/js/components/proxyapp.jsx.js +++ b/web/src/js/components/proxyapp.jsx.js @@ -8,27 +8,38 @@ var Reports = React.createClass({ var ProxyAppMain = React.createClass({ getInitialState: function () { + var eventStore = new EventLogStore(); + var flowStore = new FlowStore(); + var settings = new SettingsStore(); + + // Default Settings before fetch + _.extend(settings.dict,{ + showEventLog: true + }); return { - settings: SettingsStore.getAll(), - flowStore: new FlowStore() + settings: settings, + flowStore: flowStore, + eventStore: eventStore }; }, componentDidMount: function () { - SettingsStore.addListener("change", this.onSettingsChange); + this.state.settings.addListener("recalculate", this.onSettingsChange); }, componentWillUnmount: function () { - SettingsStore.removeListener("change", this.onSettingsChange); + this.state.settings.removeListener("recalculate", this.onSettingsChange); }, - onSettingsChange: function () { - this.setState({settings: SettingsStore.getAll()}); + onSettingsChange: function(){ + this.setState({ + settings: this.state.settings + }); }, render: function () { var eventlog; - if (this.state.settings.showEventLog) { + if (this.state.settings.dict.showEventLog) { eventlog = [ , - + ]; } else { eventlog = null; @@ -36,10 +47,10 @@ var ProxyAppMain = React.createClass({ return (
-
- +
+ {eventlog} -
+
); } diff --git a/web/src/js/store/settingstore.js b/web/src/js/store/settingstore.js index 7eef9b8f..e69de29b 100644 --- a/web/src/js/store/settingstore.js +++ b/web/src/js/store/settingstore.js @@ -1,28 +0,0 @@ -function _SettingsStore() { - EventEmitter.call(this); - - //FIXME: What do we do if we haven't requested anything from the server yet? - this.settings = { - version: "0.12", - showEventLog: true, - mode: "transparent", - }; -} -_.extend(_SettingsStore.prototype, EventEmitter.prototype, { - getAll: function () { - return this.settings; - }, - handle: function (action) { - switch (action.type) { - case ActionTypes.UPDATE_SETTINGS: - this.settings = action.settings; - this.emit("change"); - break; - default: - return; - } - } -}); - -var SettingsStore = new _SettingsStore(); -AppDispatcher.register(SettingsStore.handle.bind(SettingsStore)); diff --git a/web/src/js/store/store.js b/web/src/js/store/store.js index da288a5d..0f94e496 100644 --- a/web/src/js/store/store.js +++ b/web/src/js/store/store.js @@ -1,65 +1,68 @@ -function Store() { - this._views = []; +function ListStore() { + EventEmitter.call(this); this.reset(); } -_.extend(Store.prototype, { +_.extend(ListStore.prototype, EventEmitter.prototype, { add: function (elem) { if (elem.id in this._pos_map) { return; } - - this._pos_map[elem.id] = this._list.length; - this._list.push(elem); - for (var i = 0; i < this._views.length; i++) { - this._views[i].add(elem); - } + this._pos_map[elem.id] = this.list.length; + this.list.push(elem); + this.emit("add", elem); }, update: function (elem) { if (!(elem.id in this._pos_map)) { return; } - - this._list[this._pos_map[elem.id]] = elem; - for (var i = 0; i < this._views.length; i++) { - this._views[i].update(elem); - } + this.list[this._pos_map[elem.id]] = elem; + this.emit("update", elem); }, remove: function (elem_id) { if (!(elem.id in this._pos_map)) { return; } - - this._list.splice(this._pos_map[elem_id], 1); + this.list.splice(this._pos_map[elem_id], 1); this._build_map(); - for (var i = 0; i < this._views.length; i++) { - this._views[i].remove(elem_id); - } + this.emit("remove", elem_id); }, reset: function (elems) { - this._list = elems || []; + this.list = elems || []; this._build_map(); - for (var i = 0; i < this._views.length; i++) { - this._views[i].recalculate(this._list); - } + this.emit("recalculate", this.list); }, _build_map: function () { this._pos_map = {}; - for (var i = 0; i < this._list.length; i++) { - var elem = this._list[i]; + for (var i = 0; i < this.list.length; i++) { + var elem = this.list[i]; this._pos_map[elem.id] = i; } }, get: function (elem_id) { - return this._list[this._pos_map[elem_id]]; + return this.list[this._pos_map[elem_id]]; }, - index: function(elem_id) { + index: function (elem_id) { return this._pos_map[elem_id]; } }); -function LiveStore(type) { - Store.call(this); +function DictStore() { + EventEmitter.call(this); + this.reset(); +} +_.extend(DictStore.prototype, EventEmitter.prototype, { + update: function (dict) { + _.merge(this.dict, dict); + this.emit("recalculate", this.dict); + }, + reset: function (dict) { + this.dict = dict || {}; + this.emit("recalculate", this.dict); + } +}); + +function LiveStoreMixin(type) { this.type = type; this._updates_before_fetch = undefined; @@ -73,7 +76,7 @@ function LiveStore(type) { this.fetch(); } } -_.extend(LiveStore.prototype, Store.prototype, { +_.extend(LiveStoreMixin.prototype, { handle: function (event) { if (event.type === ActionTypes.CONNECTION_OPEN) { return this.fetch(); @@ -92,18 +95,28 @@ _.extend(LiveStore.prototype, Store.prototype, { close: function () { AppDispatcher.unregister(this.handle); }, - fetch: function () { + fetch: function (data) { console.log("fetch " + this.type); if (this._fetchxhr) { this._fetchxhr.abort(); } - this._fetchxhr = $.getJSON("/" + this.type, this.handle_fetch.bind(this)); - this._updates_before_fetch = []; // (JS: empty array is true) + this._updates_before_fetch = []; // (JS: empty array is true) + if (data) { + this.handle_fetch(data); + } else { + this._fetchxhr = $.getJSON("/" + this.type) + .done(function (message) { + this.handle_fetch(message.data); + }.bind(this)) + .fail(function () { + EventLogActions.add_event("Could not fetch " + this.type); + }.bind(this)); + } }, handle_fetch: function (data) { this._fetchxhr = false; console.log(this.type + " fetched.", this._updates_before_fetch); - this.reset(data.list); + this.reset(data); var updates = this._updates_before_fetch; this._updates_before_fetch = false; for (var i = 0; i < updates.length; i++) { @@ -112,12 +125,40 @@ _.extend(LiveStore.prototype, Store.prototype, { }, }); +function LiveListStore(type) { + ListStore.call(this); + LiveStoreMixin.call(this, type); +} +_.extend(LiveListStore.prototype, ListStore.prototype, LiveStoreMixin.prototype); + +function LiveDictStore(type) { + DictStore.call(this); + LiveStoreMixin.call(this, type); +} +_.extend(LiveDictStore.prototype, DictStore.prototype, LiveStoreMixin.prototype); + function FlowStore() { - return new LiveStore(ActionTypes.FLOW_STORE); + return new LiveListStore(ActionTypes.FLOW_STORE); } +function SettingsStore() { + return new LiveDictStore(ActionTypes.SETTINGS_STORE); +} function EventLogStore() { - return new LiveStore(ActionTypes.EVENT_STORE); -} \ No newline at end of file + LiveListStore.call(this, ActionTypes.EVENT_STORE); +} +_.extend(EventLogStore.prototype, LiveListStore.prototype, { + fetch: function(){ + LiveListStore.prototype.fetch.apply(this, arguments); + + // Make sure to display updates even if fetching all events failed. + // This way, we can send "fetch failed" log messages to the log. + if(this._fetchxhr){ + this._fetchxhr.fail(function(){ + this.handle_fetch(null); + }.bind(this)); + } + } +}); \ No newline at end of file diff --git a/web/src/js/store/view.js b/web/src/js/store/view.js index 261429b2..56bc4dbd 100644 --- a/web/src/js/store/view.js +++ b/web/src/js/store/view.js @@ -13,13 +13,25 @@ function StoreView(store, filt, sortfun) { sortfun = sortfun || default_sort; this.store = store; - this.store._views.push(this); - this.recalculate(this.store._list, filt, sortfun); + + this.add = this.add.bind(this); + this.update = this.update.bind(this); + this.remove = this.remove.bind(this); + this.recalculate = this.recalculate.bind(this); + this.store.addListener("add", this.add); + this.store.addListener("update", this.update); + this.store.addListener("remove", this.remove); + this.store.addListener("recalculate", this.recalculate); + + this.recalculate(this.store.list, filt, sortfun); } _.extend(StoreView.prototype, EventEmitter.prototype, { close: function () { - this.store._views = _.without(this.store._views, this); + this.store.removeListener("add", this.add); + this.store.removeListener("update", this.update); + this.store.removeListener("remove", this.remove); + this.store.removeListener("recalculate", this.recalculate); }, recalculate: function (elems, filt, sortfun) { if (filt) { -- cgit v1.2.3 From 93e928dec4d1004d4f983ff343569f6966db9675 Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Wed, 10 Dec 2014 17:44:45 +0100 Subject: web: add file menu stub --- web/src/js/components/header.jsx.js | 92 +++++++++++++++++++++++++++++++++---- 1 file changed, 83 insertions(+), 9 deletions(-) (limited to 'web/src/js') diff --git a/web/src/js/components/header.jsx.js b/web/src/js/components/header.jsx.js index a3fe4d51..a23afa9b 100644 --- a/web/src/js/components/header.jsx.js +++ b/web/src/js/components/header.jsx.js @@ -8,17 +8,20 @@ var MainMenu = React.createClass({ showEventLog: !this.props.settings.showEventLog }); }, - clearFlows: function(){ + clearFlows: function () { $.post("/flows/clear"); }, render: function () { return (
  + +  Display Event Log + +  
); @@ -47,6 +50,80 @@ var ReportsMenu = React.createClass({ } }); +var FileMenu = React.createClass({ + getInitialState: function () { + return { + showFileMenu: false + }; + }, + handleFileClick: function (e) { + e.preventDefault(); + if (!this.state.showFileMenu) { + var close = function () { + this.setState({showFileMenu: false}); + document.removeEventListener("click", close); + }.bind(this); + document.addEventListener("click", close); + + this.setState({ + showFileMenu: true + }); + } + }, + handleNewClick: function(e){ + e.preventDefault(); + console.error("unimplemented: handleNewClick"); + }, + handleOpenClick: function(e){ + e.preventDefault(); + console.error("unimplemented: handleOpenClick"); + }, + handleSaveClick: function(e){ + e.preventDefault(); + console.error("unimplemented: handleSaveClick"); + }, + handleShutdownClick: function(e){ + e.preventDefault(); + console.error("unimplemented: handleShutdownClick"); + }, + render: function () { + var fileMenuClass = "dropdown pull-left" + (this.state.showFileMenu ? " open" : ""); + + return ( + + ); + } +}); + var header_entries = [MainMenu, ToolsMenu, ReportsMenu]; @@ -63,9 +140,6 @@ var Header = React.createClass({ this.transitionTo(active.route); this.setState({active: active}); }, - handleFileClick: function () { - console.log("File click"); - }, render: function () { var header = header_entries.map(function (entry, i) { var classes = React.addons.classSet({ @@ -85,10 +159,10 @@ var Header = React.createClass({ return (
- mitmproxy { this.props.settings.version } + mitmproxy { this.props.settings.version }
-- cgit v1.2.3 From dbb51640d967f7857ceb70b5b697e089085b7c6b Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Thu, 11 Dec 2014 16:35:50 +0100 Subject: web: add filter grammar --- web/src/js/filt/filt.pegjs | 93 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 web/src/js/filt/filt.pegjs (limited to 'web/src/js') diff --git a/web/src/js/filt/filt.pegjs b/web/src/js/filt/filt.pegjs new file mode 100644 index 00000000..06e2ecb3 --- /dev/null +++ b/web/src/js/filt/filt.pegjs @@ -0,0 +1,93 @@ +// PEG.js filter rules - see http://pegjs.majda.cz/online + +/* Explain Filter */ +{ + var or = function(first, second) { + return first + " or " + second; + }; + var and = function(first, second) { + return first + " and " + second; + }; + var not = function(expr) { + return "not " + expr; + }; + var binding = function(expr) { + return "(" + expr + ")"; + } + var assetFilter = "is asset"; + var trueFilter = true; + var falseFilter = false; + var bodyFilter = function(s) { + return "body ~= '" + s + "'"; + } + var urlFilter = function(s) { + return "url ~= '" + s + "'"; + } +} + +start "filter expression" + = __ orExpr:OrExpr __ { return orExpr; } + +ws "whitespace" = [ \t\n\r] +cc "control character" = [|&!()~"] +__ "optional whitespace" = ws* + +OrExpr + = first:AndExpr __ "|" __ second:OrExpr + { return or(first, second); } + / AndExpr + +AndExpr + = first:NotExpr __ "&" __ second:AndExpr + { return and(first, second); } + / first:NotExpr ws+ second:AndExpr + { return and(first, second); } + / NotExpr + +NotExpr + = "!" __ expr:NotExpr + { return not(expr); } + / BindingExpr + +BindingExpr + = "(" __ expr:OrExpr __ ")" + { return binding(orExpr); } + / Expr + +Expr + = NullaryExpr + / UnaryExpr + +NullaryExpr + = BooleanLiteral + / "~a" { return assetFilter; }; + +BooleanLiteral + = "true" { return trueFilter; } + / "false" { return falseFilter; } + +UnaryExpr + = "~b" ws+ s:StringLiteral { return bodyFilter(s); } + / s:StringLiteral { return urlFilter(s); } + +StringLiteral "string" + = '"' chars:DoubleStringChar* '"' { return chars.join(""); } + / "'" chars:SingleStringChar* "'" { return chars.join(""); } + / !cc chars:UnquotedStringChar+ { return chars.join(""); } + +DoubleStringChar + = !["\\] char:. { return char; } + / "\\" char:EscapeSequence { return char; } + +SingleStringChar + = !['\\] char:. { return char; } + / "\\" char:EscapeSequence { return char; } + +UnquotedStringChar + = !ws char:. { return char; } + +EscapeSequence + = ['"\\] + / "n" { return "\n"; } + / "r" { return "\r"; } + / "t" { return "\t"; } \ No newline at end of file -- cgit v1.2.3