diff options
author | Maximilian Hils <git@maximilianhils.com> | 2014-09-18 21:13:50 +0200 |
---|---|---|
committer | Maximilian Hils <git@maximilianhils.com> | 2014-09-18 21:13:50 +0200 |
commit | d1ba150ea79689a55898efa760f7d77ca5ed601c (patch) | |
tree | 3b92ea9bae396fe1ab0b60310f4aa473c1194d0f /web | |
parent | 01da54f1c306a5d595046bd39bf2be8bbc86c132 (diff) | |
download | mitmproxy-d1ba150ea79689a55898efa760f7d77ca5ed601c.tar.gz mitmproxy-d1ba150ea79689a55898efa760f7d77ca5ed601c.tar.bz2 mitmproxy-d1ba150ea79689a55898efa760f7d77ca5ed601c.zip |
web: detailpane impl
Diffstat (limited to 'web')
-rw-r--r-- | web/gulpfile.js | 2 | ||||
-rw-r--r-- | web/src/css/app.less | 2 | ||||
-rw-r--r-- | web/src/css/flowdetail.less | 7 | ||||
-rw-r--r-- | web/src/css/flowtable.less | 26 | ||||
-rw-r--r-- | web/src/css/header.less | 16 | ||||
-rw-r--r-- | web/src/css/layout.less | 16 | ||||
-rw-r--r-- | web/src/css/tabs.less | 45 | ||||
-rw-r--r-- | web/src/js/components/flowdetail.jsx.js | 62 | ||||
-rw-r--r-- | web/src/js/components/flowtable-columns.jsx.js | 17 | ||||
-rw-r--r-- | web/src/js/components/flowtable.jsx.js | 88 | ||||
-rw-r--r-- | web/src/js/components/header.jsx.js | 68 | ||||
-rw-r--r-- | web/src/js/components/mainview.jsx.js | 69 | ||||
-rw-r--r-- | web/src/js/components/proxyapp.jsx.js | 25 | ||||
-rw-r--r-- | web/src/js/stores/flowstore.js | 4 | ||||
-rw-r--r-- | web/src/js/utils.js | 12 |
15 files changed, 335 insertions, 124 deletions
diff --git a/web/gulpfile.js b/web/gulpfile.js index a0f7825b..fc78bd1f 100644 --- a/web/gulpfile.js +++ b/web/gulpfile.js @@ -44,6 +44,8 @@ var path = { 'js/components/header.jsx.js', 'js/components/flowtable-columns.jsx.js', 'js/components/flowtable.jsx.js', + 'js/components/flowdetail.jsx.js', + 'js/components/mainview.jsx.js', 'js/components/eventlog.jsx.js', 'js/components/footer.jsx.js', 'js/components/proxyapp.jsx.js', diff --git a/web/src/css/app.less b/web/src/css/app.less index cc65cfdd..26f22572 100644 --- a/web/src/css/app.less +++ b/web/src/css/app.less @@ -9,7 +9,9 @@ html { @import (less) "sprites.less"; @import (less) "layout.less"; +@import (less) "tabs.less"; @import (less) "header.less"; @import (less) "flowtable.less"; +@import (less) "flowdetail.less"; @import (less) "eventlog.less"; @import (less) "footer.less";
\ No newline at end of file diff --git a/web/src/css/flowdetail.less b/web/src/css/flowdetail.less new file mode 100644 index 00000000..19eeecc0 --- /dev/null +++ b/web/src/css/flowdetail.less @@ -0,0 +1,7 @@ +.flow-detail { + overflow: auto; + + nav { + background-color: #F2F2F2; + } +}
\ No newline at end of file diff --git a/web/src/css/flowtable.less b/web/src/css/flowtable.less index d78fe31c..2a9c318b 100644 --- a/web/src/css/flowtable.less +++ b/web/src/css/flowtable.less @@ -1,9 +1,24 @@ .flow-table { width: 100%; - table-layout: fixed; + overflow: auto; + + table { + width: 100%; + table-layout: fixed; + } thead { - background-color: #dadada; + background-color: #F2F2F2; + line-height: 23px; + } + + th { + font-weight: normal; + box-shadow: 0 1px 0 #a6a6a6; + } + + tbody { + outline: 0; } tr { @@ -19,9 +34,7 @@ text-overflow: ellipsis; } - //tr:nth-child(odd) { background-color : white; } tr:nth-child(even) { background-color : rgba(0,0,0,0.05); } - //tr:hover { background-color : hsla(209, 52%, 84%, 0.5); } .col-tls { width: 10px; @@ -39,6 +52,9 @@ width: 50px; } .col-time { - width: 120px; + width: 50px; + } + td.col-time { + text-align: right; } }
\ No newline at end of file diff --git a/web/src/css/header.less b/web/src/css/header.less index 2a3c9765..66e8ac8a 100644 --- a/web/src/css/header.less +++ b/web/src/css/header.less @@ -7,7 +7,7 @@ header { } @separator-color: lighten(grey, 15%); - +/* nav { border-bottom: solid @separator-color 1px; @@ -20,16 +20,10 @@ header { //font-family: Lato; &.active { - border-color: @separator-color; - border-bottom-color: white; + border-color: @separator-color; + border-bottom-color: white; } - &:hover { - /* - @preview: lightgrey; - border-top-color: @preview; - border-left-color: @preview; - border-right-color: @preview; - */ + &.active, &:hover { text-decoration: none; } &.special { @@ -43,7 +37,7 @@ header { } } } - +*/ .menu { padding: 10px; border-bottom: solid @separator-color 1px; diff --git a/web/src/css/layout.less b/web/src/css/layout.less index 320baee8..6e4abd24 100644 --- a/web/src/css/layout.less +++ b/web/src/css/layout.less @@ -13,7 +13,19 @@ html, body, #container { } } -main { +.main-view { flex: 1 1 auto; - overflow: auto; + + display: flex; + flex-direction: row; + + &.vertical { + flex-direction: column; + } + + .flow-detail, .flow-table { + flex: 1 1 auto; + flex-basis: 50%; + } + }
\ No newline at end of file diff --git a/web/src/css/tabs.less b/web/src/css/tabs.less new file mode 100644 index 00000000..36bc5b68 --- /dev/null +++ b/web/src/css/tabs.less @@ -0,0 +1,45 @@ +.nav-tabs { + + @separator-color: lighten(grey, 15%); + + border-bottom: solid @separator-color 1px; + + a { + display: inline-block; + border: solid transparent 1px; + text-decoration: none; + //text-transform: uppercase; + //font-family: Lato; + + &.active { + background-color: white; + border-color: @separator-color; + border-bottom-color: white; + } + &.special { + @special-color: #396cad; + color: white; + background-color: @special-color; + border-bottom-color: @special-color; + &:hover { + background-color: lighten(@special-color, 10%); + } + } + } + +} + +.nav-tabs-lg { + a { + padding: 3px 14px; + margin: 0 2px -1px; + } +} + +.nav-tabs-sm { + a { + padding: 0px 7px; + margin: 2px 2px -1px; + + } +}
\ No newline at end of file diff --git a/web/src/js/components/flowdetail.jsx.js b/web/src/js/components/flowdetail.jsx.js new file mode 100644 index 00000000..253084c2 --- /dev/null +++ b/web/src/js/components/flowdetail.jsx.js @@ -0,0 +1,62 @@ +/** @jsx React.DOM */ + +var FlowDetailNav = React.createClass({ + render: function(){ + + var items = ["request", "response", "details"].map(function(e){ + var str = e.charAt(0).toUpperCase() + e.slice(1); + var className = this.props.active === e ? "active" : ""; + var onClick = function(){ + this.props.selectTab(e); + return false; + }.bind(this); + return <a key={e} + href="#" + className={className} + onClick={onClick}>{str}</a>; + }.bind(this)); + return ( + <nav ref="head" className="nav-tabs nav-tabs-sm"> + {items} + </nav> + ); + } +}); + +var FlowDetailRequest = React.createClass({ + render: function(){ + return <div>request</div>; + } +}); + +var FlowDetailResponse = React.createClass({ + render: function(){ + return <div>response</div>; + } +}); + +var FlowDetailConnectionInfo = React.createClass({ + render: function(){ + return <div>details</div>; + } +}) + +var tabs = { + request: FlowDetailRequest, + response: FlowDetailResponse, + details: FlowDetailConnectionInfo +} + +var FlowDetail = React.createClass({ + mixins: [StickyHeadMixin], + render: function(){ + var flow = JSON.stringify(this.props.flow, null, 2); + var Tab = tabs[this.props.active]; + return ( + <div className="flow-detail" onScroll={this.adjustHead}> + <FlowDetailNav active={this.props.active} selectTab={this.props.selectTab}/> + <Tab/> + </div> + ); + } +});
\ 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 e0cee365..0eb9966c 100644 --- a/web/src/js/components/flowtable-columns.jsx.js +++ b/web/src/js/components/flowtable-columns.jsx.js @@ -10,7 +10,12 @@ var TLSColumn = React.createClass({ render: function(){ var flow = this.props.flow; var ssl = (flow.request.scheme == "https"); - return <td className={ssl ? "col-tls-https" : "col-tls-http"}></td>; + var classes = React.addons.classSet({ + "col-tls": true, + "col-tls-https": ssl, + "col-tls-http": !ssl + }); + return <td className={classes}></td>; } }); @@ -23,7 +28,7 @@ var IconColumn = React.createClass({ }, render: function(){ var flow = this.props.flow; - return <td className="resource-icon resource-icon-plain"></td>; + return <td className="col-icon"><div className="resource-icon resource-icon-plain"></div></td>; } }); @@ -35,7 +40,7 @@ var PathColumn = React.createClass({ }, render: function(){ var flow = this.props.flow; - return <td>{flow.request.scheme + "://" + flow.request.host + flow.request.path}</td>; + return <td className="col-path">{flow.request.scheme + "://" + flow.request.host + flow.request.path}</td>; } }); @@ -48,7 +53,7 @@ var MethodColumn = React.createClass({ }, render: function(){ var flow = this.props.flow; - return <td>{flow.request.method}</td>; + return <td className="col-method">{flow.request.method}</td>; } }); @@ -67,7 +72,7 @@ var StatusColumn = React.createClass({ } else { status = null; } - return <td>{status}</td>; + return <td className="col-status">{status}</td>; } }); @@ -86,7 +91,7 @@ var TimeColumn = React.createClass({ } else { time = "..."; } - return <td>{time}</td>; + return <td className="col-time">{time}</td>; } }); diff --git a/web/src/js/components/flowtable.jsx.js b/web/src/js/components/flowtable.jsx.js index e0c285da..47576d70 100644 --- a/web/src/js/components/flowtable.jsx.js +++ b/web/src/js/components/flowtable.jsx.js @@ -4,10 +4,7 @@ var FlowRow = React.createClass({ render: function(){ var flow = this.props.flow; var columns = this.props.columns.map(function(column){ - return column({ - key: column.displayName, - flow: flow - }); + return <column key={column.displayName} flow={flow}/>; }.bind(this)); var className = ""; if(this.props.selected){ @@ -47,30 +44,13 @@ var FlowTableBody = React.createClass({ var FlowTable = React.createClass({ + mixins: [StickyHeadMixin, AutoScrollMixin], getInitialState: function () { return { - flows: [], columns: all_columns }; }, - componentDidMount: function () { - this.flowStore = FlowStore.getView(); - this.flowStore.addListener("change",this.onFlowChange); - }, - componentWillUnmount: function () { - this.flowStore.removeListener("change",this.onFlowChange); - this.flowStore.close(); - }, - onFlowChange: function () { - this.setState({ - flows: this.flowStore.getAll() - }); - }, - selectFlow: function(flow){ - this.setState({ - selected: 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(); @@ -89,65 +69,59 @@ var FlowTable = React.createClass({ viewport.scrollTop = flowNode_bottom - viewport.offsetHeight; } }, - selectRowRelative: function(i){ + selectFlowRelative: function(i){ var index; - if(!this.state.selected){ + if(!this.props.selected){ if(i > 0){ - index = this.flows.length-1; + index = this.props.flows.length-1; } else { index = 0; } } else { - index = _.findIndex(this.state.flows, function(f){ - return f === this.state.selected; + index = _.findIndex(this.props.flows, function(f){ + return f === this.props.selected; }.bind(this)); - index = Math.min(Math.max(0, index+i), this.state.flows.length-1); + index = Math.min(Math.max(0, index+i), this.props.flows.length-1); } - this.selectFlow(this.state.flows[index]); + this.props.selectFlow(this.props.flows[index]); }, onKeyDown: function(e){ switch(e.keyCode){ case Key.DOWN: - this.selectRowRelative(+1); - return false; + this.selectFlowRelative(+1); break; case Key.UP: - this.selectRowRelative(-1); - return false; + this.selectFlowRelative(-1); break; - case Key.ENTER: - console.log("Open details pane...", this.state.selected); + case Key.PAGE_DOWN: + this.selectFlowRelative(+10); + break; + case Key.PAGE_UP: + this.selectFlowRelative(-10); break; case Key.ESC: - console.log("") + this.props.selectFlow(null); + break; default: console.debug("keydown", e.keyCode); return; } return false; }, - onScroll: function(e){ - //Abusing CSS transforms to set thead into position:fixed. - var head = this.refs.head.getDOMNode(); - head.style.transform = "translate(0,"+this.getDOMNode().scrollTop+"px)"; - }, render: function () { - var flows = this.state.flows.map(function(flow){ - return <div>{flow.request.method} {flow.request.scheme}://{flow.request.host}{flow.request.path}</div>; - }); return ( - <main onScroll={this.onScroll}> - <table className="flow-table"> - <FlowTableHead ref="head" - columns={this.state.columns}/> - <FlowTableBody ref="body" - selectFlow={this.selectFlow} - onKeyDown={this.onKeyDown} - selected={this.state.selected} - columns={this.state.columns} - flows={this.state.flows}/> - </table> - </main> + <div className="flow-table" onScroll={this.adjustHead}> + <table> + <FlowTableHead ref="head" + columns={this.state.columns}/> + <FlowTableBody ref="body" + flows={this.props.flows} + selected={this.props.selected} + selectFlow={this.props.selectFlow} + columns={this.state.columns} + onKeyDown={this.onKeyDown}/> + </table> + </div> ); } }); diff --git a/web/src/js/components/header.jsx.js b/web/src/js/components/header.jsx.js index 8f613ff1..92a58282 100644 --- a/web/src/js/components/header.jsx.js +++ b/web/src/js/components/header.jsx.js @@ -1,6 +1,10 @@ /** @jsx React.DOM */ var MainMenu = React.createClass({ + statics: { + title: "Traffic", + route: "flows" + }, toggleEventLog: function () { SettingsActions.update({ showEventLog: !this.props.settings.showEventLog @@ -16,72 +20,74 @@ var MainMenu = React.createClass({ ); } }); + + var ToolsMenu = React.createClass({ + statics: { + title: "Tools", + route: "flows" + }, render: function () { return <div>Tools Menu</div>; } }); + + var ReportsMenu = React.createClass({ + statics: { + title: "Visualization", + route: "reports" + }, render: function () { return <div>Reports Menu</div>; } }); -var _Header_Entries = { - main: { - title: "Traffic", - route: "main", - menu: MainMenu - }, - tools: { - title: "Tools", - route: "main", - menu: ToolsMenu - }, - reports: { - title: "Visualization", - route: "reports", - menu: ReportsMenu - } -}; +var header_entries = [MainMenu, ToolsMenu, ReportsMenu]; + var Header = React.createClass({ getInitialState: function () { return { - active: "main" + active: header_entries[0] }; }, handleClick: function (active) { + ReactRouter.transitionTo(active.route); this.setState({active: active}); - ReactRouter.transitionTo(_Header_Entries[active].route); return false; }, handleFileClick: function () { console.log("File click"); }, render: function () { - var header = []; - for (var item in _Header_Entries) { - var classes = this.state.active == item ? "active" : ""; - header.push(<a key={item} href="#" className={classes} - onClick={this.handleClick.bind(this, item)}>{ _Header_Entries[item].title }</a>); - } - - var menu = _Header_Entries[this.state.active].menu({ - settings: this.props.settings - }); + var header = header_entries.map(function(entry){ + var classes = React.addons.classSet({ + active: entry == this.state.active + }); + return ( + <a key={entry.title} + href="#" + className={classes} + onClick={this.handleClick.bind(this, entry)} + > + { entry.title} + </a> + ); + }.bind(this)); + return ( <header> <div className="title-bar"> mitmproxy { this.props.settings.version } </div> - <nav> + <nav className="nav-tabs nav-tabs-lg"> <a href="#" className="special" onClick={this.handleFileClick}> File </a> {header} </nav> <div className="menu"> - { menu } + <this.state.active settings={this.props.settings}/> </div> </header> ); diff --git a/web/src/js/components/mainview.jsx.js b/web/src/js/components/mainview.jsx.js new file mode 100644 index 00000000..10ecfed0 --- /dev/null +++ b/web/src/js/components/mainview.jsx.js @@ -0,0 +1,69 @@ +/** @jsx React.DOM */ + +var MainView = React.createClass({ + getInitialState: function() { + return { + flows: [], + }; + }, + componentDidMount: function () { + console.log("get view"); + this.flowStore = FlowStore.getView(); + this.flowStore.addListener("change",this.onFlowChange); + }, + componentWillUnmount: function () { + this.flowStore.removeListener("change",this.onFlowChange); + this.flowStore.close(); + }, + onFlowChange: function () { + this.setState({ + flows: this.flowStore.getAll() + }); + }, + selectFlow: function(flow) { + if(flow){ + ReactRouter.replaceWith( + "flow", + { + flowId: flow.id, + detailTab: this.props.params.detailTab || "request" + } + ); + this.refs.flowTable.scrollIntoView(flow); + } else { + ReactRouter.replaceWith("flows"); + } + }, + selectDetailTab: function(panel) { + ReactRouter.replaceWith( + "flow", + { + flowId: this.props.params.flowId, + detailTab: panel + } + ); + }, + render: function() { + var selected = _.find(this.state.flows, { id: this.props.params.flowId }); + + var details = null; + if(selected){ + details = ( + <FlowDetail ref="flowDetails" + flow={selected} + selectTab={this.selectDetailTab} + active={this.props.params.detailTab}/> + ); + } + + return ( + <div className="main-view"> + <FlowTable ref="flowTable" + flows={this.state.flows} + selectFlow={this.selectFlow} + selected={selected} /> + {details} + </div> + ); + } +});
\ No newline at end of file diff --git a/web/src/js/components/proxyapp.jsx.js b/web/src/js/components/proxyapp.jsx.js index 486e723f..6895b852 100644 --- a/web/src/js/components/proxyapp.jsx.js +++ b/web/src/js/components/proxyapp.jsx.js @@ -26,7 +26,7 @@ var ProxyAppMain = React.createClass({ return ( <div id="container"> <Header settings={this.state.settings}/> - <this.props.activeRouteHandler/> + <this.props.activeRouteHandler settings={this.state.settings}/> {this.state.settings.showEventLog ? <EventLog/> : null} <Footer settings={this.state.settings}/> </div> @@ -35,12 +35,19 @@ var ProxyAppMain = React.createClass({ }); +var Routes = ReactRouter.Routes; +var Route = ReactRouter.Route; +var Redirect = ReactRouter.Redirect; +var DefaultRoute = ReactRouter.DefaultRoute; +var NotFoundRoute = ReactRouter.NotFoundRoute; + + var ProxyApp = ( - <ReactRouter.Routes location="hash"> - <ReactRouter.Route name="app" path="/" handler={ProxyAppMain}> - <ReactRouter.Route name="main" handler={FlowTable}/> - <ReactRouter.Route name="reports" handler={Reports}/> - <ReactRouter.Redirect to="main"/> - </ReactRouter.Route> - </ReactRouter.Routes> - ); + <Routes location="hash"> + <Route path="/" handler={ProxyAppMain}> + <Route name="flows" path="flows" handler={MainView}/> + <Route name="flow" path="flows/:flowId/:detailTab" handler={MainView}/> + <Route name="reports" handler={Reports}/> + </Route> + </Routes> + );
\ No newline at end of file diff --git a/web/src/js/stores/flowstore.js b/web/src/js/stores/flowstore.js index ba5b0788..c7623367 100644 --- a/web/src/js/stores/flowstore.js +++ b/web/src/js/stores/flowstore.js @@ -63,8 +63,8 @@ _.extend(_FlowStore.prototype, EventEmitter.prototype, { flows = flows.concat(_.cloneDeep(flows)).concat(_.cloneDeep(flows)); var id = 1; flows.forEach(function(flow){ - flow.id = "uuid-"+id++; - }) + flow.id = "uuid-" + id++; + }); view.add_bulk(flows); }); diff --git a/web/src/js/utils.js b/web/src/js/utils.js index 6e545d8a..947ad5c1 100644 --- a/web/src/js/utils.js +++ b/web/src/js/utils.js @@ -12,12 +12,22 @@ var AutoScrollMixin = { }, }; +var StickyHeadMixin = { + adjustHead: function(){ + // Abusing CSS transforms to set the element + // referenced as head into some kind of position:sticky. + var head = this.refs.head.getDOMNode(); + head.style.transform = "translate(0,"+this.getDOMNode().scrollTop+"px)"; + } +}; var Key = { UP: 38, DOWN: 40, + PAGE_UP: 33, + PAGE_DOWN: 34, LEFT: 37, RIGHT: 39, ENTER: 13, ESC: 27 -}
\ No newline at end of file +};
\ No newline at end of file |