aboutsummaryrefslogtreecommitdiffstats
path: root/web/src
diff options
context:
space:
mode:
Diffstat (limited to 'web/src')
-rw-r--r--web/src/js/actions.js16
-rw-r--r--web/src/js/components/eventlog.jsx.js35
-rw-r--r--web/src/js/components/flowtable.jsx.js4
-rw-r--r--web/src/js/components/mainview.jsx.js2
-rw-r--r--web/src/js/components/proxyapp.jsx.js2
-rw-r--r--web/src/js/components/virtualscroll.jsx.js14
-rw-r--r--web/src/js/dispatcher.js4
-rw-r--r--web/src/js/store/settingstore.js (renamed from web/src/js/stores/settingstore.js)0
-rw-r--r--web/src/js/store/store.js (renamed from web/src/js/stores/base.js)50
-rw-r--r--web/src/js/store/view.js87
-rw-r--r--web/src/js/stores/eventlogstore.js99
-rw-r--r--web/src/js/stores/flowstore.js105
-rw-r--r--web/src/js/utils.js36
13 files changed, 182 insertions, 272 deletions
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 <pre onScroll={this.onScroll}>
- { this.getPlaceholderTop() }
+ { this.getPlaceholderTop(this.state.log.length) }
{rows}
{ this.getPlaceholderBottom(this.state.log.length) }
</pre>;
@@ -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({
<FlowTableHead ref="head"
columns={this.state.columns}/>
<tbody ref="body">
- { this.getPlaceholderTop() }
+ { this.getPlaceholderTop(flows.length) }
{rows}
{ this.getPlaceholderBottom(flows.length) }
</tbody>
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 = <Tag key="placeholder-top" style={style}></Tag>;
@@ -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/stores/settingstore.js b/web/src/js/store/settingstore.js
index 7eef9b8f..7eef9b8f 100644
--- a/web/src/js/stores/settingstore.js
+++ b/web/src/js/store/settingstore.js
diff --git a/web/src/js/stores/base.js b/web/src/js/store/store.js
index f9534bd2..da288a5d 100644
--- a/web/src/js/stores/base.js
+++ b/web/src/js/store/store.js
@@ -1,34 +1,3 @@
-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();
@@ -82,6 +51,9 @@ _.extend(Store.prototype, {
},
get: function (elem_id) {
return this._list[this._pos_map[elem_id]];
+ },
+ index: function(elem_id) {
+ return this._pos_map[elem_id];
}
});
@@ -107,7 +79,7 @@ _.extend(LiveStore.prototype, Store.prototype, {
return this.fetch();
}
if (event.type === this.type) {
- if (event.cmd === "reset") {
+ if (event.cmd === StoreCmds.RESET) {
this.fetch();
} else if (this._updates_before_fetch) {
console.log("defer update", event);
@@ -131,11 +103,21 @@ _.extend(LiveStore.prototype, Store.prototype, {
handle_fetch: function (data) {
this._fetchxhr = false;
console.log(this.type + " fetched.", this._updates_before_fetch);
- this.reset(data.flows);
+ 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]);
}
},
-}); \ No newline at end of file
+});
+
+
+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/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/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