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') 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