diff --git a/libmproxy/web/static/css/app.css b/libmproxy/web/static/css/app.css index f0ed95dd5..8cdfbac67 100644 --- a/libmproxy/web/static/css/app.css +++ b/libmproxy/web/static/css/app.css @@ -60,6 +60,7 @@ body, flex: 1 1 auto; display: flex; flex-direction: row; + outline: 0; } .main-view.vertical { flex-direction: column; @@ -212,27 +213,61 @@ header .menu { table-layout: fixed; word-break: break-all; } -.flow-detail table tr { +.flow-detail table tr:not(:first-child) { border-top: 1px solid #f7f7f7; } .flow-detail table td { vertical-align: top; } .connection-table td:first-child { - width: 33%; + width: 50%; padding-right: 1em; } .header-table .header-name { width: 33%; padding-right: 1em; } +.connection-table td, +.timing-table td { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} .eventlog { + height: 200px; flex: 0 0 auto; + display: flex; + flex-direction: column; +} +.eventlog > div { + background-color: #F2F2F2; + padding: 0 5px; + flex: 0 0 auto; +} +.eventlog > pre { + flex: 1 1 auto; margin: 0; border-radius: 0; - height: 200px; - overflow: auto; + overflow-x: auto; overflow-y: scroll; + background-color: #fcfcfc; +} +.eventlog .fa-close { + cursor: pointer; + float: right; + color: grey; + padding: 3px 0; + padding-left: 10px; +} +.eventlog .fa-close:hover { + color: black; +} +.eventlog .label { + cursor: pointer; + vertical-align: middle; + display: inline-block; + margin-top: -2px; + margin-left: 3px; } footer { box-shadow: 0 -1px 3px #d3d3d3; diff --git a/libmproxy/web/static/js/app.js b/libmproxy/web/static/js/app.js index 4fb9f92d8..fe317d7f0 100644 --- a/libmproxy/web/static/js/app.js +++ b/libmproxy/web/static/js/app.js @@ -121,14 +121,15 @@ var SettingsActions = { } }; +var event_id = 0; var EventLogActions = { - add_event: function(message, level){ + add_event: function(message){ AppDispatcher.dispatchViewAction({ type: ActionTypes.ADD_EVENT, data: { message: message, - level: level || "info", - source: "ui" + level: "web", + id: "viewAction-"+event_id++ } }); } @@ -263,7 +264,7 @@ _.extend(EventLogView.prototype, EventEmitter.prototype, { }, add: function (entry) { this.log.push(entry); - if(this.log.length > 50){ + if(this.log.length > 200){ this.log.shift(); } this.emit("change"); @@ -334,7 +335,6 @@ _.extend(_EventLogStore.prototype, EventEmitter.prototype, { var EventLogStore = new _EventLogStore(); AppDispatcher.register(EventLogStore.handle.bind(EventLogStore)); - function FlowView(store, live) { EventEmitter.call(this); this._store = store; @@ -443,7 +443,7 @@ _Connection.prototype.openWebSocketConnection = function () { ws.onclose = this.onclose.bind(this); }; _Connection.prototype.onopen = function (open) { - console.log("onopen", this, arguments); + console.debug("onopen", this, arguments); }; _Connection.prototype.onmessage = function (message) { //AppDispatcher.dispatchServerAction(...); @@ -452,11 +452,11 @@ _Connection.prototype.onmessage = function (message) { }; _Connection.prototype.onerror = function (error) { EventLogActions.add_event("WebSocket Connection Error."); - console.log("onerror", this, arguments); + console.debug("onerror", this, arguments); }; _Connection.prototype.onclose = function (close) { EventLogActions.add_event("WebSocket Connection closed."); - console.log("onclose", this, arguments); + console.debug("onclose", this, arguments); }; var Connection = new _Connection(location.origin + "/updates"); @@ -1139,7 +1139,7 @@ var Timing = React.createClass({displayName: 'Timing', return ( React.DOM.div(null, React.DOM.h4(null, "Timing"), - React.DOM.table(null, + React.DOM.table({className: "timing-table"}, React.DOM.tbody(null, rows ) @@ -1214,7 +1214,6 @@ var MainView = React.createClass({displayName: 'MainView', }; }, componentDidMount: function () { - console.log("get view"); this.flowStore = FlowStore.getView(); this.flowStore.addListener("change",this.onFlowChange); }, @@ -1334,7 +1333,32 @@ var MainView = React.createClass({displayName: 'MainView', }); /** @jsx React.DOM */ -var EventLog = React.createClass({displayName: 'EventLog', +var LogMessage = React.createClass({displayName: 'LogMessage', + render: function(){ + var entry = this.props.entry; + var indicator; + switch(entry.level){ + case "web": + indicator = React.DOM.i({className: "fa fa-fw fa-html5"}); + break; + case "debug": + indicator = React.DOM.i({className: "fa fa-fw fa-bug"}); + break; + default: + indicator = React.DOM.i({className: "fa fa-fw fa-info"}); + } + return ( + React.DOM.div(null, + indicator, " ", entry.message + ) + ); + }, + shouldComponentUpdate: function(){ + return false; // log entries are immutable. + } +}); + +var EventLogContents = React.createClass({displayName: 'EventLogContents', mixins:[AutoScrollMixin], getInitialState: function () { return { @@ -1354,23 +1378,76 @@ var EventLog = React.createClass({displayName: 'EventLog', log: this.log.getAll() }); }, + render: function () { + var messages = this.state.log.map(function(row) { + if(!this.props.filter[row.level]){ + return null; + } + return LogMessage({key: row.id, entry: row}); + }.bind(this)); + return React.DOM.pre(null, messages); + } +}); + +var ToggleFilter = React.createClass({displayName: 'ToggleFilter', + toggle: function(){ + return this.props.toggleLevel(this.props.name); + }, + render: function(){ + var className = "label "; + if (this.props.active) { + className += "label-primary"; + } else { + className += "label-default"; + } + return ( + React.DOM.a({ + href: "#", + className: className, + onClick: this.toggle}, + this.props.name + ) + ); + } +}); + +var EventLog = React.createClass({displayName: 'EventLog', + getInitialState: function(){ + return { + filter: { + "debug": false, + "info": true, + "web": true + } + }; + }, close: function () { SettingsActions.update({ showEventLog: false }); }, + toggleLevel: function(level){ + var filter = this.state.filter; + filter[level] = !filter[level]; + this.setState({filter: filter}); + return false; + }, render: function () { - var messages = this.state.log.map(function(row) { - var indicator = null; - if(row.source === "ui"){ - indicator = React.DOM.i({className: "fa fa-html5"}); - } - return ( - React.DOM.div({key: row.id}, - indicator, " ", row.message - )); - }); - return React.DOM.pre({className: "eventlog"}, messages); + return ( + React.DOM.div({className: "eventlog"}, + React.DOM.div(null, + "Eventlog", + React.DOM.div({className: "pull-right"}, + ToggleFilter({name: "debug", active: this.state.filter.debug, toggleLevel: this.toggleLevel}), + ToggleFilter({name: "info", active: this.state.filter.info, toggleLevel: this.toggleLevel}), + ToggleFilter({name: "web", active: this.state.filter.web, toggleLevel: this.toggleLevel}), + React.DOM.i({onClick: this.close, className: "fa fa-close"}) + ) + + ), + EventLogContents({filter: this.state.filter}) + ) + ); } }); /** @jsx React.DOM */ diff --git a/web/src/css/eventlog.less b/web/src/css/eventlog.less index 993304cfb..8b0a76479 100644 --- a/web/src/css/eventlog.less +++ b/web/src/css/eventlog.less @@ -1,10 +1,45 @@ .eventlog { + height: 200px; flex: 0 0 auto; - margin: 0; - border-radius: 0; - height: 200px; - overflow: auto; - overflow-y: scroll; + display: flex; + flex-direction: column; + + + > div { + background-color: #F2F2F2; + padding: 0 5px; + flex: 0 0 auto; + } + + > pre { + flex: 1 1 auto; + + margin: 0; + border-radius: 0; + overflow-x: auto; + overflow-y: scroll; + background-color: #fcfcfc; + } + + + .fa-close { + cursor: pointer; + float: right; + color: grey; + padding: 3px 0; + padding-left: 10px; + &:hover { + color: black; + } + } + + .label { + cursor: pointer; + vertical-align: middle; + display: inline-block; + margin-top: -2px; + margin-left: 3px; + } } \ No newline at end of file diff --git a/web/src/css/flowdetail.less b/web/src/css/flowdetail.less index 94920caa8..3a6191d4a 100644 --- a/web/src/css/flowdetail.less +++ b/web/src/css/flowdetail.less @@ -35,9 +35,9 @@ word-break: break-all; tr { - //&:not(:first-child){ + &:not(:first-child){ border-top: 1px solid #f7f7f7; - //} + } } td { @@ -51,7 +51,7 @@ .connection-table { td:first-child { - width: 33%; + width: 50%; padding-right: 1em; } } @@ -64,4 +64,12 @@ .header-value { } +} + +.connection-table, .timing-table { + td { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } } \ No newline at end of file diff --git a/web/src/css/layout.less b/web/src/css/layout.less index 510fde6a0..f6807f24b 100644 --- a/web/src/css/layout.less +++ b/web/src/css/layout.less @@ -19,6 +19,8 @@ html, body, #container { display: flex; flex-direction: row; + outline: 0; + &.vertical { flex-direction: column; } diff --git a/web/src/js/actions.js b/web/src/js/actions.js index 43fbfb141..9211403f6 100644 --- a/web/src/js/actions.js +++ b/web/src/js/actions.js @@ -23,14 +23,15 @@ var SettingsActions = { } }; +var event_id = 0; var EventLogActions = { - add_event: function(message, level){ + add_event: function(message){ AppDispatcher.dispatchViewAction({ type: ActionTypes.ADD_EVENT, data: { message: message, - level: level || "info", - source: "ui" + level: "web", + id: "viewAction-"+event_id++ } }); } diff --git a/web/src/js/components/eventlog.jsx.js b/web/src/js/components/eventlog.jsx.js index df212177f..08a6dfb44 100644 --- a/web/src/js/components/eventlog.jsx.js +++ b/web/src/js/components/eventlog.jsx.js @@ -1,6 +1,31 @@ /** @jsx React.DOM */ -var EventLog = React.createClass({ +var LogMessage = React.createClass({ + render: function(){ + var entry = this.props.entry; + var indicator; + switch(entry.level){ + case "web": + indicator = ; + break; + case "debug": + indicator = ; + break; + default: + indicator = ; + } + return ( +
{messages}; + } +}); + +var ToggleFilter = React.createClass({ + toggle: function(){ + return this.props.toggleLevel(this.props.name); + }, + render: function(){ + var className = "label "; + if (this.props.active) { + className += "label-primary"; + } else { + className += "label-default"; + } + return ( + + {this.props.name} + + ); + } +}); + +var EventLog = React.createClass({ + getInitialState: function(){ + return { + filter: { + "debug": false, + "info": true, + "web": true + } + }; + }, close: function () { SettingsActions.update({ showEventLog: false }); }, + toggleLevel: function(level){ + var filter = this.state.filter; + filter[level] = !filter[level]; + this.setState({filter: filter}); + return false; + }, render: function () { - var messages = this.state.log.map(function(row) { - var indicator = null; - if(row.source === "ui"){ - indicator = ; - } - return ( -
{messages}; + return ( +