make columns configurable and customizable

This commit is contained in:
gorogoroumaru 2020-08-30 13:10:11 +00:00 committed by Maximilian Hils
parent c996f1ee74
commit 1efe44745b
11 changed files with 84 additions and 16 deletions

View File

@ -137,6 +137,7 @@ def mitmweb(opts):
opts.make_parser(group, "web_open_browser")
opts.make_parser(group, "web_port", metavar="PORT")
opts.make_parser(group, "web_host", metavar="HOST")
opts.make_parser(group, "web_columns")
common_options(parser, opts)
group = parser.add_argument_group(

File diff suppressed because one or more lines are too long

View File

@ -47,13 +47,13 @@
"use strict";function _interopRequireWildcard(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var o in e)Object.prototype.hasOwnProperty.call(e,o)&&(t[o]=e[o]);return t.default=e,t}function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e}}function _classCallCheck(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function _possibleConstructorReturn(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function _inherits(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(exports,"__esModule",{value:!0}),exports.PureFlowTable=void 0;var _createClass=function(){function e(e,t){for(var o=0;o<t.length;o++){var l=t[o];l.enumerable=l.enumerable||!1,l.configurable=!0,"value"in l&&(l.writable=!0),Object.defineProperty(e,l.key,l)}}return function(t,o,l){return o&&e(t.prototype,o),l&&e(t,l),t}}(),_react=require("react"),_react2=_interopRequireDefault(_react),_propTypes=require("prop-types"),_propTypes2=_interopRequireDefault(_propTypes),_reactDom=require("react-dom"),_reactDom2=_interopRequireDefault(_reactDom),_reactRedux=require("react-redux"),_shallowequal=require("shallowequal"),_shallowequal2=_interopRequireDefault(_shallowequal),_AutoScroll=require("./helpers/AutoScroll"),_AutoScroll2=_interopRequireDefault(_AutoScroll),_VirtualScroll=require("./helpers/VirtualScroll"),_FlowTableHead=require("./FlowTable/FlowTableHead"),_FlowTableHead2=_interopRequireDefault(_FlowTableHead),_FlowRow=require("./FlowTable/FlowRow"),_FlowRow2=_interopRequireDefault(_FlowRow),_filt=require("../filt/filt"),_filt2=_interopRequireDefault(_filt),_flows=require("../ducks/flows"),flowsActions=_interopRequireWildcard(_flows),FlowTable=function(e){function t(e,o){_classCallCheck(this,t);var l=_possibleConstructorReturn(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,e,o));return l.state={vScroll:(0,_VirtualScroll.calcVScroll)()},l.onViewportUpdate=l.onViewportUpdate.bind(l),l}return _inherits(t,_react2.default.Component),_createClass(t,[{key:"componentWillMount",value:function(){window.addEventListener("resize",this.onViewportUpdate)}},{key:"componentWillUnmount",value:function(){window.removeEventListener("resize",this.onViewportUpdate)}},{key:"componentDidUpdate",value:function(){if(this.onViewportUpdate(),this.shouldScrollIntoView){this.shouldScrollIntoView=!1;var e=this.props,t=e.rowHeight,o=e.flows,l=e.selected,r=_reactDom2.default.findDOMNode(this),i=_reactDom2.default.findDOMNode(this.refs.head),a=i?i.offsetHeight:0,n=o.indexOf(l)*t+a,u=n+t,s=r.scrollTop,c=r.offsetHeight;n-a<s?r.scrollTop=n-a:u>s+c&&(r.scrollTop=u-c)}}},{key:"componentWillReceiveProps",value:function(e){e.selected&&e.selected!==this.props.selected&&(this.shouldScrollIntoView=!0)}},{key:"onViewportUpdate",value:function(){var e=_reactDom2.default.findDOMNode(this),t=e.scrollTop,o=(0,_VirtualScroll.calcVScroll)({viewportTop:t,viewportHeight:e.offsetHeight,itemCount:this.props.flows.length,rowHeight:this.props.rowHeight});this.state.viewportTop===t&&(0,_shallowequal2.default)(this.state.vScroll,o)||this.setState({vScroll:o,viewportTop:t})}},{key:"render",value:function(){var e=this,t=this.state,o=t.vScroll,l=t.viewportTop,r=this.props,i=r.flows,a=r.selected,n=r.highlight,u=n?_filt2.default.parse(n):function(){return!1};return _react2.default.createElement("div",{className:"flow-table",onScroll:this.onViewportUpdate},_react2.default.createElement("table",null,_react2.default.createElement("thead",{ref:"head",style:{transform:"translateY("+l+"px)"}},_react2.default.createElement(_FlowTableHead2.default,null)),_react2.default.createElement("tbody",null,_react2.default.createElement("tr",{style:{height:o.paddingTop}}),i.slice(o.start,o.end).map(function(t){return _react2.default.createElement(_FlowRow2.default,{key:t.id,flow:t,selected:t===a,highlighted:u(t),onSelect:e.props.selectFlow})}),_react2.default.createElement("tr",{style:{height:o.paddingBottom}}))))}}]),t}();FlowTable.propTypes={selectFlow:_propTypes2.default.func.isRequired,flows:_propTypes2.default.array.isRequired,rowHeight:_propTypes2.default.number,highlight:_propTypes2.default.string,selected:_propTypes2.default.object},FlowTable.defaultProps={rowHeight:32};var PureFlowTable=exports.PureFlowTable=(0,_AutoScroll2.default)(FlowTable);exports.default=(0,_reactRedux.connect)(function(e){return{flows:e.flows.view,highlight:e.flows.highlight,selected:e.flows.byId[e.flows.selected[0]]}},{selectFlow:flowsActions.select})(PureFlowTable);
},{"../ducks/flows":56,"../filt/filt":67,"./FlowTable/FlowRow":18,"./FlowTable/FlowTableHead":19,"./helpers/AutoScroll":52,"./helpers/VirtualScroll":53,"prop-types":"prop-types","react":"react","react-dom":"react-dom","react-redux":"react-redux","shallowequal":"shallowequal"}],17:[function(require,module,exports){
"use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e}}function TLSColumn(e){var t=e.flow;return _react2.default.createElement("td",{className:(0,_classnames2.default)("col-tls","https"===t.request.scheme?"col-tls-https":"col-tls-http")})}function IconColumn(e){var t=e.flow;return _react2.default.createElement("td",{className:"col-icon"},_react2.default.createElement("div",{className:(0,_classnames2.default)("resource-icon",IconColumn.getIcon(t))}))}function PathColumn(e){var t=e.flow,s=void 0;return t.error&&(s="Connection killed."===t.error.msg?_react2.default.createElement("i",{className:"fa fa-fw fa-times pull-right"}):_react2.default.createElement("i",{className:"fa fa-fw fa-exclamation pull-right"})),_react2.default.createElement("td",{className:"col-path"},t.request.is_replay&&_react2.default.createElement("i",{className:"fa fa-fw fa-repeat pull-right"}),t.intercepted&&_react2.default.createElement("i",{className:"fa fa-fw fa-pause pull-right"}),s,_utils.RequestUtils.pretty_url(t.request))}function MethodColumn(e){var t=e.flow;return _react2.default.createElement("td",{className:"col-method"},t.request.method)}function StatusColumn(e){var t=e.flow,s="darkred";return t.response&&100<=t.response.status_code&&t.response.status_code<200?s="green":t.response&&200<=t.response.status_code&&t.response.status_code<300?s="darkgreen":t.response&&300<=t.response.status_code&&t.response.status_code<400?s="lightblue":t.response&&400<=t.response.status_code&&t.response.status_code<500?s="lightred":t.response&&500<=t.response.status_code&&t.response.status_code<600&&(s="lightred"),_react2.default.createElement("td",{className:"col-status",style:{color:s}},t.response&&t.response.status_code)}function SizeColumn(e){var t=e.flow;return _react2.default.createElement("td",{className:"col-size"},(0,_utils2.formatSize)(SizeColumn.getTotalSize(t)))}function TimeColumn(e){var t=e.flow;return _react2.default.createElement("td",{className:"col-time"},t.response?(0,_utils2.formatTimeDelta)(1e3*(t.response.timestamp_end-t.request.timestamp_start)):"...")}Object.defineProperty(exports,"__esModule",{value:!0}),exports.TLSColumn=TLSColumn,exports.IconColumn=IconColumn,exports.PathColumn=PathColumn,exports.MethodColumn=MethodColumn,exports.StatusColumn=StatusColumn,exports.SizeColumn=SizeColumn,exports.TimeColumn=TimeColumn;var _react=require("react"),_react2=_interopRequireDefault(_react),_classnames=require("classnames"),_classnames2=_interopRequireDefault(_classnames),_utils=require("../../flow/utils.js"),_utils2=require("../../utils.js");TLSColumn.headerClass="col-tls",TLSColumn.headerName="",IconColumn.headerClass="col-icon",IconColumn.headerName="",IconColumn.getIcon=function(e){if(!e.response)return"resource-icon-plain";var t=_utils.ResponseUtils.getContentType(e.response)||"";return 304===e.response.status_code?"resource-icon-not-modified":300<=e.response.status_code&&e.response.status_code<400?"resource-icon-redirect":t.indexOf("image")>=0?"resource-icon-image":t.indexOf("javascript")>=0?"resource-icon-js":t.indexOf("css")>=0?"resource-icon-css":t.indexOf("html")>=0?"resource-icon-document":"resource-icon-plain"},PathColumn.headerClass="col-path",PathColumn.headerName="Path",MethodColumn.headerClass="col-method",MethodColumn.headerName="Method",StatusColumn.headerClass="col-status",StatusColumn.headerName="Status",SizeColumn.getTotalSize=function(e){var t=e.request.contentLength;return e.response&&(t+=e.response.contentLength||0),t},SizeColumn.headerClass="col-size",SizeColumn.headerName="Size",TimeColumn.headerClass="col-time",TimeColumn.headerName="Time",exports.default=[TLSColumn,IconColumn,PathColumn,MethodColumn,StatusColumn,SizeColumn,TimeColumn];
"use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e}}function TLSColumn(e){var t=e.flow;return _react2.default.createElement("td",{className:(0,_classnames2.default)("col-tls","https"===t.request.scheme?"col-tls-https":"col-tls-http")})}function IconColumn(e){var t=e.flow;return _react2.default.createElement("td",{className:"col-icon"},_react2.default.createElement("div",{className:(0,_classnames2.default)("resource-icon",IconColumn.getIcon(t))}))}function PathColumn(e){var t=e.flow,s=void 0;return t.error&&(s="Connection killed."===t.error.msg?_react2.default.createElement("i",{className:"fa fa-fw fa-times pull-right"}):_react2.default.createElement("i",{className:"fa fa-fw fa-exclamation pull-right"})),_react2.default.createElement("td",{className:"col-path"},t.request.is_replay&&_react2.default.createElement("i",{className:"fa fa-fw fa-repeat pull-right"}),t.intercepted&&_react2.default.createElement("i",{className:"fa fa-fw fa-pause pull-right"}),s,_utils.RequestUtils.pretty_url(t.request))}function MethodColumn(e){var t=e.flow;return _react2.default.createElement("td",{className:"col-method"},t.request.method)}function StatusColumn(e){var t=e.flow,s="darkred";return t.response&&100<=t.response.status_code&&t.response.status_code<200?s="green":t.response&&200<=t.response.status_code&&t.response.status_code<300?s="darkgreen":t.response&&300<=t.response.status_code&&t.response.status_code<400?s="lightblue":t.response&&400<=t.response.status_code&&t.response.status_code<500?s="lightred":t.response&&500<=t.response.status_code&&t.response.status_code<600&&(s="lightred"),_react2.default.createElement("td",{className:"col-status",style:{color:s}},t.response&&t.response.status_code)}function SizeColumn(e){var t=e.flow;return _react2.default.createElement("td",{className:"col-size"},(0,_utils2.formatSize)(SizeColumn.getTotalSize(t)))}function TimeColumn(e){var t=e.flow;return _react2.default.createElement("td",{className:"col-time"},t.response?(0,_utils2.formatTimeDelta)(1e3*(t.response.timestamp_end-t.request.timestamp_start)):"...")}function TimeStampColumn(e){var t=e.flow;return _react2.default.createElement("td",{className:"col-start"},t.request.timestamp_start?(0,_utils2.formatTimeStamp)(t.request.timestamp_start):"...")}Object.defineProperty(exports,"__esModule",{value:!0}),exports.defaultColumnNames=void 0,exports.TLSColumn=TLSColumn,exports.IconColumn=IconColumn,exports.PathColumn=PathColumn,exports.MethodColumn=MethodColumn,exports.StatusColumn=StatusColumn,exports.SizeColumn=SizeColumn,exports.TimeColumn=TimeColumn,exports.TimeStampColumn=TimeStampColumn;var _react=require("react"),_react2=_interopRequireDefault(_react),_classnames=require("classnames"),_classnames2=_interopRequireDefault(_classnames),_utils=require("../../flow/utils.js"),_utils2=require("../../utils.js"),defaultColumnNames=exports.defaultColumnNames=["tls","icon","path","method","status","size","time"];TLSColumn.headerClass="col-tls",TLSColumn.headerName="",IconColumn.headerClass="col-icon",IconColumn.headerName="",IconColumn.getIcon=function(e){if(!e.response)return"resource-icon-plain";var t=_utils.ResponseUtils.getContentType(e.response)||"";return 304===e.response.status_code?"resource-icon-not-modified":300<=e.response.status_code&&e.response.status_code<400?"resource-icon-redirect":t.indexOf("image")>=0?"resource-icon-image":t.indexOf("javascript")>=0?"resource-icon-js":t.indexOf("css")>=0?"resource-icon-css":t.indexOf("html")>=0?"resource-icon-document":"resource-icon-plain"},PathColumn.headerClass="col-path",PathColumn.headerName="Path",MethodColumn.headerClass="col-method",MethodColumn.headerName="Method",StatusColumn.headerClass="col-status",StatusColumn.headerName="Status",SizeColumn.getTotalSize=function(e){var t=e.request.contentLength;return e.response&&(t+=e.response.contentLength||0),t},SizeColumn.headerClass="col-size",SizeColumn.headerName="Size",TimeColumn.headerClass="col-time",TimeColumn.headerName="Time",TimeStampColumn.headerClass="col-timestamp",TimeStampColumn.headerName="TimeStamp",exports.default=[TLSColumn,IconColumn,PathColumn,MethodColumn,StatusColumn,TimeStampColumn,SizeColumn,TimeColumn];
},{"../../flow/utils.js":68,"../../utils.js":70,"classnames":"classnames","react":"react"}],18:[function(require,module,exports){
"use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e}}function FlowRow(e){var t=e.flow,r=e.selected,l=e.highlighted,s=e.onSelect,o=(0,_classnames2.default)({selected:r,highlighted:l,intercepted:t.intercepted,"has-request":t.request,"has-response":t.response});return _react2.default.createElement("tr",{className:o,onClick:function(){return s(t.id)}},_FlowColumns2.default.map(function(e){return _react2.default.createElement(e,{key:e.name,flow:t})}))}Object.defineProperty(exports,"__esModule",{value:!0});var _react=require("react"),_react2=_interopRequireDefault(_react),_propTypes=require("prop-types"),_propTypes2=_interopRequireDefault(_propTypes),_classnames=require("classnames"),_classnames2=_interopRequireDefault(_classnames),_FlowColumns=require("./FlowColumns"),_FlowColumns2=_interopRequireDefault(_FlowColumns),_utils=require("../../utils");FlowRow.propTypes={onSelect:_propTypes2.default.func.isRequired,flow:_propTypes2.default.object.isRequired,highlighted:_propTypes2.default.bool,selected:_propTypes2.default.bool},exports.default=(0,_utils.pure)(FlowRow);
"use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e}}function FlowRow(e){var t=e.flow,r=e.selected,l=e.highlighted,o=e.onSelect,s=e.displayColumnNames,u=(0,_classnames2.default)({selected:r,highlighted:l,intercepted:t.intercepted,"has-request":t.request,"has-response":t.response}),a=(0,_FlowTableHead.getDisplayColumns)(s);return _react2.default.createElement("tr",{className:u,onClick:function(){return o(t.id)}},a.map(function(e){return _react2.default.createElement(e,{key:e.name,flow:t})}))}Object.defineProperty(exports,"__esModule",{value:!0});var _react=require("react"),_react2=_interopRequireDefault(_react),_propTypes=require("prop-types"),_propTypes2=_interopRequireDefault(_propTypes),_classnames=require("classnames"),_classnames2=_interopRequireDefault(_classnames),_FlowColumns=require("./FlowColumns"),_utils=require("../../utils"),_FlowTableHead=require("./FlowTableHead"),_reactRedux=require("react-redux");FlowRow.propTypes={onSelect:_propTypes2.default.func.isRequired,flow:_propTypes2.default.object.isRequired,highlighted:_propTypes2.default.bool,selected:_propTypes2.default.bool},exports.default=(0,_reactRedux.connect)(function(e){return{displayColumnNames:e.options.web_columns?e.options.web_columns.value:_FlowColumns.defaultColumnNames}})((0,_utils.pure)(FlowRow));
},{"../../utils":70,"./FlowColumns":17,"classnames":"classnames","prop-types":"prop-types","react":"react"}],19:[function(require,module,exports){
"use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e}}function FlowTableHead(e){var r=e.sortColumn,t=e.sortDesc,s=e.setSort,o=t?"sort-desc":"sort-asc";return _react2.default.createElement("tr",null,_FlowColumns2.default.map(function(e){return _react2.default.createElement("th",{className:(0,_classnames2.default)(e.headerClass,r===e.name&&o),key:e.name,onClick:function(){return s(e.name,e.name===r&&!t)}},e.headerName)}))}Object.defineProperty(exports,"__esModule",{value:!0}),exports.FlowTableHead=FlowTableHead;var _react=require("react"),_react2=_interopRequireDefault(_react),_propTypes=require("prop-types"),_propTypes2=_interopRequireDefault(_propTypes),_reactRedux=require("react-redux"),_classnames=require("classnames"),_classnames2=_interopRequireDefault(_classnames),_FlowColumns=require("./FlowColumns"),_FlowColumns2=_interopRequireDefault(_FlowColumns),_flows=require("../../ducks/flows");FlowTableHead.propTypes={setSort:_propTypes2.default.func.isRequired,sortDesc:_propTypes2.default.bool.isRequired,sortColumn:_propTypes2.default.string},exports.default=(0,_reactRedux.connect)(function(e){return{sortDesc:e.flows.sort.desc,sortColumn:e.flows.sort.column}},{setSort:_flows.setSort})(FlowTableHead);
},{"../../utils":70,"./FlowColumns":17,"./FlowTableHead":19,"classnames":"classnames","prop-types":"prop-types","react":"react","react-redux":"react-redux"}],19:[function(require,module,exports){
"use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e}}function getDisplayColumns(e){var r=[];if(void 0===e)return _FlowColumns2.default;var o=!0,s=!1,t=void 0;try{for(var l,a=_FlowColumns2.default[Symbol.iterator]();!(o=(l=a.next()).done);o=!0){var u=l.value;e.includes(u.name.slice(0,-6).toLowerCase())&&r.push(u)}}catch(e){s=!0,t=e}finally{try{!o&&a.return&&a.return()}finally{if(s)throw t}}return r}function FlowTableHead(e){var r=e.sortColumn,o=e.sortDesc,s=e.setSort,t=e.displayColumnNames,l=o?"sort-desc":"sort-asc",a=getDisplayColumns(t);return _react2.default.createElement("tr",null,a.map(function(e){return _react2.default.createElement("th",{className:(0,_classnames2.default)(e.headerClass,r===e.name&&l),key:e.name,onClick:function(){return s(e.name,e.name===r&&!o)}},e.headerName)}))}Object.defineProperty(exports,"__esModule",{value:!0}),exports.getDisplayColumns=getDisplayColumns,exports.FlowTableHead=FlowTableHead;var _react=require("react"),_react2=_interopRequireDefault(_react),_propTypes=require("prop-types"),_propTypes2=_interopRequireDefault(_propTypes),_reactRedux=require("react-redux"),_classnames=require("classnames"),_classnames2=_interopRequireDefault(_classnames),_FlowColumns=require("./FlowColumns"),_FlowColumns2=_interopRequireDefault(_FlowColumns),_flows=require("../../ducks/flows");FlowTableHead.propTypes={setSort:_propTypes2.default.func.isRequired,sortDesc:_propTypes2.default.bool.isRequired,sortColumn:_propTypes2.default.string,displayColumnNames:_propTypes2.default.array},exports.default=(0,_reactRedux.connect)(function(e){return{sortDesc:e.flows.sort.desc,sortColumn:e.flows.sort.column,displayColumnNames:e.options.web_columns?e.options.web_columns.value:_FlowColumns.defaultColumnNames}},{setSort:_flows.setSort})(FlowTableHead);
},{"../../ducks/flows":56,"./FlowColumns":17,"classnames":"classnames","prop-types":"prop-types","react":"react","react-redux":"react-redux"}],20:[function(require,module,exports){
"use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e}}function FlowView(e){var r=e.flow,a=e.tabName,t=e.selectTab,s=["request","response","error"].filter(function(e){return r[e]});s.push("details"),s.indexOf(a)<0&&(a="response"===a&&r.error?"error":"error"===a&&r.response?"response":s[0]);var l=allTabs[_lodash2.default.capitalize(a)];return _react2.default.createElement("div",{className:"flow-detail"},_react2.default.createElement(_Nav2.default,{tabs:s,active:a,onSelectTab:t}),_react2.default.createElement(l,{flow:r}))}Object.defineProperty(exports,"__esModule",{value:!0}),exports.allTabs=void 0;var _react=require("react"),_react2=_interopRequireDefault(_react),_reactRedux=require("react-redux"),_lodash=require("lodash"),_lodash2=_interopRequireDefault(_lodash),_Nav=require("./FlowView/Nav"),_Nav2=_interopRequireDefault(_Nav),_Messages=require("./FlowView/Messages"),_Details=require("./FlowView/Details"),_Details2=_interopRequireDefault(_Details),_flow=require("../ducks/ui/flow"),allTabs=exports.allTabs={Request:_Messages.Request,Response:_Messages.Response,Error:_Messages.ErrorView,Details:_Details2.default};exports.default=(0,_reactRedux.connect)(function(e){return{flow:e.flows.byId[e.flows.selected[0]],tabName:e.ui.flow.tab}},{selectTab:_flow.selectTab})(FlowView);

View File

@ -1,6 +1,8 @@
import webbrowser
from mitmproxy import ctx
from typing import Sequence
class WebAddon:
@ -21,6 +23,10 @@ class WebAddon:
"web_host", str, "127.0.0.1",
"Web UI host."
)
loader.add_option(
"web_columns", Sequence[str], ["tls", "icon", "path", "method", "status", "size", "time"],
"Columns to show in the flow list"
)
def running(self):
if hasattr(ctx.options, "web_open_browser") and ctx.options.web_open_browser:

View File

@ -125,6 +125,9 @@
.col-time {
width: 50px;
}
.col-timestamp {
width: auto;
}
td.col-time, td.col-size {
text-align: right;
}

View File

@ -1,12 +1,17 @@
import React from 'react'
import renderer from 'react-test-renderer'
import FlowRow from '../../../components/FlowTable/FlowRow'
import { TFlow } from '../../ducks/tutils'
import { TFlow, TStore } from '../../ducks/tutils'
import { Provider } from 'react-redux'
describe('FlowRow Component', () => {
let tFlow = new TFlow(),
selectFn = jest.fn(),
flowRow = renderer.create(<FlowRow flow={tFlow} onSelect={selectFn}/>),
store = TStore(),
flowRow = renderer.create(
<Provider store={store} >
<FlowRow flow={tFlow} onSelect={selectFn}/>
</Provider>),
tree = flowRow.toJSON()
it('should render correctly', () => {

View File

@ -7,7 +7,11 @@ import { TStore } from '../../ducks/tutils'
describe('FlowTableHead Component', () => {
let sortFn = jest.fn(),
flowTableHead = renderer.create(<FlowTableHead setSort={sortFn} sortDesc={true}/>),
store = TStore(),
flowTableHead = renderer.create(
<Provider store={store}>
<FlowTableHead setSort={sortFn} sortDesc={true}/>
</Provider>),
tree =flowTableHead.toJSON()
it('should render correctly', () => {

View File

@ -79,6 +79,12 @@ exports[`FlowTableHead Component should render correctly 1`] = `
>
Status
</th>
<th
className="col-timestamp"
onClick={[Function]}
>
TimeStamp
</th>
<th
className="col-size"
onClick={[Function]}

View File

@ -1,7 +1,9 @@
import React, { Component } from 'react'
import classnames from 'classnames'
import { RequestUtils, ResponseUtils } from '../../flow/utils.js'
import { formatSize, formatTimeDelta } from '../../utils.js'
import { formatSize, formatTimeDelta, formatTimeStamp } from '../../utils.js'
export const defaultColumnNames = ["tls", "icon", "path", "method", "status", "size", "time"]
export function TLSColumn({ flow }) {
return (
@ -148,12 +150,28 @@ export function TimeColumn({ flow }) {
TimeColumn.headerClass = 'col-time'
TimeColumn.headerName = 'Time'
export function TimeStampColumn({ flow }) {
return (
<td className="col-start">
{flow.request.timestamp_start ? (
formatTimeStamp(flow.request.timestamp_start)
) : (
'...'
)}
</td>
)
}
TimeStampColumn.headerClass = 'col-timestamp'
TimeStampColumn.headerName = 'TimeStamp'
export default [
TLSColumn,
IconColumn,
PathColumn,
MethodColumn,
StatusColumn,
TimeStampColumn,
SizeColumn,
TimeColumn,
]

View File

@ -1,8 +1,10 @@
import React from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
import columns from './FlowColumns'
import {defaultColumnNames} from './FlowColumns'
import { pure } from '../../utils'
import {getDisplayColumns} from './FlowTableHead'
import { connect } from 'react-redux'
FlowRow.propTypes = {
onSelect: PropTypes.func.isRequired,
@ -11,7 +13,7 @@ FlowRow.propTypes = {
selected: PropTypes.bool,
}
function FlowRow({ flow, selected, highlighted, onSelect }) {
function FlowRow({ flow, selected, highlighted, onSelect, displayColumnNames }) {
const className = classnames({
'selected': selected,
'highlighted': highlighted,
@ -20,13 +22,19 @@ function FlowRow({ flow, selected, highlighted, onSelect }) {
'has-response': flow.response,
})
const displayColumns = getDisplayColumns(displayColumnNames)
return (
<tr className={className} onClick={() => onSelect(flow.id)}>
{columns.map(Column => (
{displayColumns.map(Column => (
<Column key={Column.name} flow={flow}/>
))}
</tr>
)
}
export default pure(FlowRow)
export default connect(
state => ({
displayColumnNames: state.options["web_columns"] ? state.options["web_columns"].value : defaultColumnNames,
})
)(pure(FlowRow))

View File

@ -2,7 +2,7 @@ import React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import classnames from 'classnames'
import columns from './FlowColumns'
import columns, {defaultColumnNames} from './FlowColumns'
import { setSort } from '../../ducks/flows'
@ -10,14 +10,30 @@ FlowTableHead.propTypes = {
setSort: PropTypes.func.isRequired,
sortDesc: PropTypes.bool.isRequired,
sortColumn: PropTypes.string,
displayColumnNames: PropTypes.array,
}
export function FlowTableHead({ sortColumn, sortDesc, setSort }) {
export function getDisplayColumns(displayColumnNames) {
let displayColumns = []
if (typeof displayColumnNames == "undefined") {
return columns
}
for (const column of columns) {
if (displayColumnNames.includes(column.name.slice(0,-6).toLowerCase())) {
displayColumns.push(column)
}
}
return displayColumns
}
export function FlowTableHead({ sortColumn, sortDesc, setSort, displayColumnNames}) {
const sortType = sortDesc ? 'sort-desc' : 'sort-asc'
const displayColumns = getDisplayColumns(displayColumnNames)
return (
<tr>
{columns.map(Column => (
{displayColumns.map(Column => (
<th className={classnames(Column.headerClass, sortColumn === Column.name && sortType)}
key={Column.name}
onClick={() => setSort(Column.name, Column.name !== sortColumn ? false : !sortDesc)}>
@ -32,6 +48,7 @@ export default connect(
state => ({
sortDesc: state.flows.sort.desc,
sortColumn: state.flows.sort.column,
displayColumnNames: state.options["web_columns"] ? state.options["web_columns"].value : defaultColumnNames,
}),
{
setSort