Merge branch 'main' of https://github.com/gorogoroumaru/mitmproxy into hover-menu

This commit is contained in:
zokutyou2@gmail.com 2021-06-10 22:17:34 +09:00
commit 6824666424
26 changed files with 485 additions and 264 deletions

View File

@ -275,6 +275,8 @@ class ClearAll(RequestHandler):
class ResumeFlows(RequestHandler):
def post(self):
for f in self.view:
if not f.intercepted:
continue
f.resume()
self.view.update([f])

View File

@ -11,6 +11,13 @@ and activate your virtualenv environment before proceeding.**
- Run `yarn test` to run the test suite.
## Advanced Tools
- `yarn run gulp` supports live-reloading if you install a matching
[browser extension](http://livereload.com/extensions/).
- You can debug application state using the [Redux DevTools](https://github.com/reduxjs/redux-devtools).
## Architecture
There are two components:

View File

@ -2,7 +2,6 @@
.user-select (@val) {
-webkit-touch-callout: @val;
-webkit-user-select: @val;
-khtml-user-select: @val;
-moz-user-select: @val;
-ms-user-select: @val;
user-select: @val;
@ -33,6 +32,7 @@
&.sort-asc, &.sort-desc {
background-color: lighten(#F2F2F2, 3%);
}
&.sort-asc:after, &.sort-desc:after {
font: normal normal normal 14px/1 FontAwesome;
position: absolute;
@ -41,9 +41,11 @@
padding: 2px;
background-color: fadeout(lighten(#F2F2F2, 3%), 20%);
}
&.sort-asc:after {
content: "\f0de";
}
&.sort-desc:after {
content: "\f0dd";
}
@ -53,17 +55,22 @@
tr {
cursor: pointer;
background-color: white;
&:nth-child(even) {
background-color: rgba(0, 0, 0, 0.05);
background-color: #f2f2f2;
}
&.selected {
background-color: hsla(209, 52%, 84%, 0.5) !important;
background-color: #e0ebf5 !important;
}
&.highlighted {
background-color: hsla(48, 100%, 50%, 0.4);
background-color: #ffeb99;
}
&.highlighted:nth-child(even) {
background-color: hsla(48, 100%, 50%, 0.5);
background-color: #ffe57f;
}
}
@ -80,6 +87,7 @@
color: @interceptorange;
}
}
tr.intercepted.has-response {
.col-status, .col-size, .col-time {
color: @interceptorange;
@ -88,6 +96,7 @@
.fa {
line-height: inherit;
&.pull-right {
margin-left: 0;
}
@ -96,39 +105,89 @@
.col-tls {
width: 10px;
}
.col-tls-https {
background-color: rgba(0, 185, 0, 0.5);
}
.col-icon {
width: 32px;
}
.col-path {
.fa-repeat {
color: green;
}
.fa-pause {
color: @interceptorange;
}
.fa-exclamation, .fa-times {
color: darkred;
}
}
.col-method {
width: 60px;
}
.col-status {
width: 50px;
}
.col-size {
width: 70px;
}
.col-time {
width: 50px;
}
.col-timestamp {
width: auto;
}
td.col-time, td.col-size {
text-align: right;
}
.col-quickactions {
width: 0;
direction: rtl;
overflow: hidden;
background-color: inherit;
font-size: 20px;
}
tr:hover .col-quickactions {
overflow: visible;
}
.col-quickactions > div {
height: 32px;
background-color: inherit;
display: inline-flex;
align-items: center;
div {
margin-right: 2px;
height: 32px;
width: 32px;
border-radius: 16px;
&:hover {
background-color: rgba(0, 0, 0, 5%);
}
}
}
.col-quickactions .fa-ellipsis-h {
transform: translate(-3px, 3px);
}
.col-quickactions .fa-play {
transform: translate(-1px, 2px);
}
}

View File

@ -3,7 +3,7 @@
@import (reference) "../../node_modules/bootstrap/less/mixins/labels.less";
@import (reference) "../../node_modules/bootstrap/less/labels.less";
@menu-height: 85px;
@menu-height: 95px;
header {
padding-top: 6px;
@ -20,17 +20,16 @@ header {
}
@menu-legend-height: 16px;
@menu-group-hmargin: 3px;
@menu-group-hmargin: 5px;
.menu-group {
margin: 0 @menu-group-hmargin;
margin: 0 @menu-group-hmargin 0 (@menu-group-hmargin + 1px);
display: inline-block;
height: @menu-height;
vertical-align: top;
}
.menu-content {
height: @menu-height - @menu-legend-height;
text-align: center;
display: flow-root;
> .btn {
height: @menu-height - @menu-legend-height;
@ -45,6 +44,16 @@ header {
margin: 0 auto 5px;
}
}
> .btn.btn-sm {
height: (@menu-height - @menu-legend-height)/3;
padding: 0 5px;
i {
display: inline-block;
font-size: 14px;
margin: 0;
}
}
}
.menu-entry {
@ -65,6 +74,7 @@ header {
}
.menu-legend {
color: #777;
height: @menu-legend-height;
text-align: center;
font-size: 12px;
@ -73,7 +83,7 @@ header {
.menu-group + .menu-group:before {
@space: 10px;
margin-left: -@menu-group-hmargin;
margin-left: -(@menu-group-hmargin + 1px);
content: " ";
border-left: solid 1px lighten(grey, 40%);
margin-top: @space;
@ -82,19 +92,20 @@ header {
}
@menu-main-gutter-width: 5px;
.menu-main {
.make-row(@menu-main-gutter-width);
padding: 2px 5px;
.main-menu {
display: flex;
.menu-group {
width: 50%;
}
.btn-sm {
margin-top: 6px;
}
}
.filter-input {
.make-sm-column(5, @menu-main-gutter-width);
padding: 2.5px;
margin: 4px 0;
@media (max-width: @screen-xs-max) {
padding: 2px 2.5px;
> .form-control, > .input-group-addon, > .input-group-btn > .btn {
height: 23.5px;
padding: 1px 5px;

View File

@ -4,12 +4,12 @@ exports[`ShowFullContentButton Component should connect to state 1`] = `null`;
exports[`ShowFullContentButton Component should render correctly 1`] = `
<div>
<div
<button
className="view-all-content-btn btn-xs btn btn-default"
onClick={[Function]}
>
Show full content
</div>
</button>
<span
className="pull-right"
>

View File

@ -1,116 +1,135 @@
import React from 'react'
import renderer from 'react-test-renderer'
import * as Columns from '../../../components/FlowTable/FlowColumns'
import { TFlow } from '../../ducks/tutils'
import {
IconColumn,
MethodColumn,
PathColumn,
QuickActionsColumn,
SizeColumn,
StatusColumn,
TimeColumn,
TimeStampColumn,
TLSColumn
} from '../../../components/FlowTable/FlowColumns'
import {TFlow, TStore} from '../../ducks/tutils'
import {Provider} from 'react-redux'
describe('FlowColumns Components', () => {
describe('Flowcolumns Components', () => {
let tflow = TFlow()
it('should render TLSColumn', () => {
let tlsColumn = renderer.create(<Columns.TLSColumn flow={tflow}/>),
let tlsColumn = renderer.create(<TLSColumn flow={tflow}/>),
tree = tlsColumn.toJSON()
expect(tree).toMatchSnapshot()
})
it('should render IconColumn', () => {
let iconColumn = renderer.create(<Columns.IconColumn flow={tflow}/>),
let iconColumn = renderer.create(<IconColumn flow={tflow}/>),
tree = iconColumn.toJSON()
// plain
expect(tree).toMatchSnapshot()
// not modified
tflow.response.status_code = 304
iconColumn = renderer.create(<Columns.IconColumn flow={tflow}/>)
iconColumn = renderer.create(<IconColumn flow={tflow}/>)
tree = iconColumn.toJSON()
expect(tree).toMatchSnapshot()
// redirect
tflow.response.status_code = 302
iconColumn = renderer.create(<Columns.IconColumn flow={tflow}/>)
iconColumn = renderer.create(<IconColumn flow={tflow}/>)
tree = iconColumn.toJSON()
expect(tree).toMatchSnapshot()
// image
let imageFlow = TFlow()
imageFlow.response.headers = [['Content-Type', 'image/jpeg']]
iconColumn = renderer.create(<Columns.IconColumn flow={imageFlow}/>)
iconColumn = renderer.create(<IconColumn flow={imageFlow}/>)
tree = iconColumn.toJSON()
expect(tree).toMatchSnapshot()
// javascript
let jsFlow = TFlow()
jsFlow.response.headers = [['Content-Type', 'application/x-javascript']]
iconColumn = renderer.create(<Columns.IconColumn flow={jsFlow}/>)
iconColumn = renderer.create(<IconColumn flow={jsFlow}/>)
tree = iconColumn.toJSON()
expect(tree).toMatchSnapshot()
// css
let cssFlow = TFlow()
cssFlow.response.headers = [['Content-Type', 'text/css']]
iconColumn = renderer.create(<Columns.IconColumn flow={cssFlow}/>)
iconColumn = renderer.create(<IconColumn flow={cssFlow}/>)
tree = iconColumn.toJSON()
expect(tree).toMatchSnapshot()
// html
let htmlFlow = TFlow()
htmlFlow.response.headers = [['Content-Type', 'text/html']]
iconColumn = renderer.create(<Columns.IconColumn flow={htmlFlow}/>)
iconColumn = renderer.create(<IconColumn flow={htmlFlow}/>)
tree = iconColumn.toJSON()
expect(tree).toMatchSnapshot()
// default
let fooFlow = TFlow()
fooFlow.response.headers = [['Content-Type', 'foo']]
iconColumn = renderer.create(<Columns.IconColumn flow={fooFlow}/>)
iconColumn = renderer.create(<IconColumn flow={fooFlow}/>)
tree = iconColumn.toJSON()
expect(tree).toMatchSnapshot()
// no response
tflow.response = null
iconColumn = renderer.create(<Columns.IconColumn flow={tflow}/>)
iconColumn = renderer.create(<IconColumn flow={tflow}/>)
tree = iconColumn.toJSON()
expect(tree).toMatchSnapshot()
})
it('should render pathColumn', () => {
let pathColumn = renderer.create(<Columns.PathColumn flow={tflow}/>),
let pathColumn = renderer.create(<PathColumn flow={tflow}/>),
tree = pathColumn.toJSON()
expect(tree).toMatchSnapshot()
tflow.error.msg = 'Connection killed.'
tflow.intercepted = true
pathColumn = renderer.create(<Columns.PathColumn flow={tflow}/>)
pathColumn = renderer.create(<PathColumn flow={tflow}/>)
tree = pathColumn.toJSON()
expect(tree).toMatchSnapshot()
})
it('should render MethodColumn', () => {
let methodColumn =renderer.create(<Columns.MethodColumn flow={tflow}/>),
let methodColumn = renderer.create(<MethodColumn flow={tflow}/>),
tree = methodColumn.toJSON()
expect(tree).toMatchSnapshot()
})
it('should render StatusColumn', () => {
let statusColumn = renderer.create(<Columns.StatusColumn flow={tflow}/>),
let statusColumn = renderer.create(<StatusColumn flow={tflow}/>),
tree = statusColumn.toJSON()
expect(tree).toMatchSnapshot()
})
it('should render SizeColumn', () => {
tflow = TFlow()
let sizeColumn = renderer.create(<Columns.SizeColumn flow={tflow}/>),
let sizeColumn = renderer.create(<SizeColumn flow={tflow}/>),
tree = sizeColumn.toJSON()
expect(tree).toMatchSnapshot()
})
it('should render TimeColumn', () => {
let timeColumn = renderer.create(<Columns.TimeColumn flow={tflow}/>),
let timeColumn = renderer.create(<TimeColumn flow={tflow}/>),
tree = timeColumn.toJSON()
expect(tree).toMatchSnapshot()
tflow.response = null
timeColumn = renderer.create(<Columns.TimeColumn flow={tflow}/>),
timeColumn = renderer.create(<TimeColumn flow={tflow}/>)
tree = timeColumn.toJSON()
expect(tree).toMatchSnapshot()
})
it('should render TimeStampColumn', () => {
let timeStampColumn = renderer.create(<Columns.TimeStampColumn flow={tflow}/>),
let timeStampColumn = renderer.create(<TimeStampColumn flow={tflow}/>),
tree = timeStampColumn.toJSON()
tflow.request.timestamp_start =
expect(tree).toMatchSnapshot()
})
it('should render QuickActionsColumn', () => {
let store = TStore(),
provider = renderer.create(
<Provider store={store}>
<QuickActionsColumn flow={tflow}/>
</Provider>),
tree = provider.toJSON()
expect(tree).toMatchSnapshot()
})
})

View File

@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`FlowColumns Components should render IconColumn 1`] = `
exports[`Flowcolumns Components should render IconColumn 1`] = `
<td
className="col-icon"
>
@ -10,7 +10,7 @@ exports[`FlowColumns Components should render IconColumn 1`] = `
</td>
`;
exports[`FlowColumns Components should render IconColumn 2`] = `
exports[`Flowcolumns Components should render IconColumn 2`] = `
<td
className="col-icon"
>
@ -20,7 +20,7 @@ exports[`FlowColumns Components should render IconColumn 2`] = `
</td>
`;
exports[`FlowColumns Components should render IconColumn 3`] = `
exports[`Flowcolumns Components should render IconColumn 3`] = `
<td
className="col-icon"
>
@ -30,7 +30,7 @@ exports[`FlowColumns Components should render IconColumn 3`] = `
</td>
`;
exports[`FlowColumns Components should render IconColumn 4`] = `
exports[`Flowcolumns Components should render IconColumn 4`] = `
<td
className="col-icon"
>
@ -40,7 +40,7 @@ exports[`FlowColumns Components should render IconColumn 4`] = `
</td>
`;
exports[`FlowColumns Components should render IconColumn 5`] = `
exports[`Flowcolumns Components should render IconColumn 5`] = `
<td
className="col-icon"
>
@ -50,7 +50,7 @@ exports[`FlowColumns Components should render IconColumn 5`] = `
</td>
`;
exports[`FlowColumns Components should render IconColumn 6`] = `
exports[`Flowcolumns Components should render IconColumn 6`] = `
<td
className="col-icon"
>
@ -60,7 +60,7 @@ exports[`FlowColumns Components should render IconColumn 6`] = `
</td>
`;
exports[`FlowColumns Components should render IconColumn 7`] = `
exports[`Flowcolumns Components should render IconColumn 7`] = `
<td
className="col-icon"
>
@ -70,7 +70,7 @@ exports[`FlowColumns Components should render IconColumn 7`] = `
</td>
`;
exports[`FlowColumns Components should render IconColumn 8`] = `
exports[`Flowcolumns Components should render IconColumn 8`] = `
<td
className="col-icon"
>
@ -80,7 +80,7 @@ exports[`FlowColumns Components should render IconColumn 8`] = `
</td>
`;
exports[`FlowColumns Components should render IconColumn 9`] = `
exports[`Flowcolumns Components should render IconColumn 9`] = `
<td
className="col-icon"
>
@ -90,7 +90,7 @@ exports[`FlowColumns Components should render IconColumn 9`] = `
</td>
`;
exports[`FlowColumns Components should render MethodColumn 1`] = `
exports[`Flowcolumns Components should render MethodColumn 1`] = `
<td
className="col-method"
>
@ -98,7 +98,23 @@ exports[`FlowColumns Components should render MethodColumn 1`] = `
</td>
`;
exports[`FlowColumns Components should render SizeColumn 1`] = `
exports[`Flowcolumns Components should render QuickActionsColumn 1`] = `
<td
className="col-quickactions"
>
<div>
<div
className="quickaction"
>
<i
className="fa fa-fw fa-ellipsis-h"
/>
</div>
</div>
</td>
`;
exports[`Flowcolumns Components should render SizeColumn 1`] = `
<td
className="col-size"
>
@ -106,7 +122,7 @@ exports[`FlowColumns Components should render SizeColumn 1`] = `
</td>
`;
exports[`FlowColumns Components should render StatusColumn 1`] = `
exports[`Flowcolumns Components should render StatusColumn 1`] = `
<td
className="col-status"
style={
@ -117,13 +133,13 @@ exports[`FlowColumns Components should render StatusColumn 1`] = `
/>
`;
exports[`FlowColumns Components should render TLSColumn 1`] = `
exports[`Flowcolumns Components should render TLSColumn 1`] = `
<td
className="col-tls col-tls-http"
/>
`;
exports[`FlowColumns Components should render TimeColumn 1`] = `
exports[`Flowcolumns Components should render TimeColumn 1`] = `
<td
className="col-time"
>
@ -131,7 +147,7 @@ exports[`FlowColumns Components should render TimeColumn 1`] = `
</td>
`;
exports[`FlowColumns Components should render TimeColumn 2`] = `
exports[`Flowcolumns Components should render TimeColumn 2`] = `
<td
className="col-time"
>
@ -139,7 +155,7 @@ exports[`FlowColumns Components should render TimeColumn 2`] = `
</td>
`;
exports[`FlowColumns Components should render TimeStampColumn 1`] = `
exports[`Flowcolumns Components should render TimeStampColumn 1`] = `
<td
className="col-start"
>
@ -147,7 +163,7 @@ exports[`FlowColumns Components should render TimeStampColumn 1`] = `
</td>
`;
exports[`FlowColumns Components should render pathColumn 1`] = `
exports[`Flowcolumns Components should render pathColumn 1`] = `
<td
className="col-path"
>
@ -158,7 +174,7 @@ exports[`FlowColumns Components should render pathColumn 1`] = `
</td>
`;
exports[`FlowColumns Components should render pathColumn 2`] = `
exports[`Flowcolumns Components should render pathColumn 2`] = `
<td
className="col-path"
>

View File

@ -48,6 +48,19 @@ exports[`FlowRow Component should render correctly 1`] = `
>
3s
</td>
<td
className="col-quickactions"
>
<div>
<div
className="quickaction"
>
<i
className="fa fa-fw fa-ellipsis-h"
/>
</div>
</div>
</td>
<div
className="dropdown pull-left btn btn-default"
>
@ -103,20 +116,6 @@ exports[`FlowRow Component should render correctly 1`] = `
</a>
</li>
<li>
<a
href="#"
onClick={[Function]}
>
<i
className="fa fa-fw fa-plus"
/>
 Intercept all requests from
192.168.0.1
</a>
</li>
</ul>
</div>
</tr>

View File

@ -44,6 +44,12 @@ exports[`FlowTableHead Component should connect to state 1`] = `
>
Time
</th>
<th
className="col-quickactions"
onClick={[Function]}
>
</th>
</tr>
`;
@ -97,5 +103,11 @@ exports[`FlowTableHead Component should render correctly 1`] = `
>
Time
</th>
<th
className="col-quickactions"
onClick={[Function]}
>
</th>
</tr>
`;

View File

@ -16,7 +16,7 @@ describe('FilterInput Component', () => {
let filterInput = TestUtil.renderIntoDocument(
<FilterInput type='foo' color='red' placeholder='bar' value='' onChange={jest.fn()}/>)
it('should handle componentWillReceiveProps', () => {
filterInput.componentWillReceiveProps({value: 'foo'})
filterInput.UNSAFE_componentWillReceiveProps({value: 'foo'})
expect(filterInput.state.value).toEqual('foo')
})

View File

@ -1,14 +1,16 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`FlowMenu Component should connect to state 1`] = `
<div>
<div
className="flow-menu"
>
<div
className="menu-group"
>
<div
className="menu-content"
>
<div
<button
className="btn btn-default"
onClick={[Function]}
title="[r]eplay flow"
@ -17,8 +19,8 @@ exports[`FlowMenu Component should connect to state 1`] = `
className="fa fa-fw fa-repeat text-primary"
/>
Replay
</div>
<div
</button>
<button
className="btn btn-default"
onClick={[Function]}
title="[D]uplicate flow"
@ -27,8 +29,8 @@ exports[`FlowMenu Component should connect to state 1`] = `
className="fa fa-fw fa-copy text-info"
/>
Duplicate
</div>
<div
</button>
<button
className="btn btn-default"
disabled={true}
title="revert changes to flow [V]"
@ -37,8 +39,8 @@ exports[`FlowMenu Component should connect to state 1`] = `
className="fa fa-fw fa-history text-warning"
/>
Revert
</div>
<div
</button>
<button
className="btn btn-default"
onClick={[Function]}
title="[d]elete flow"
@ -47,7 +49,7 @@ exports[`FlowMenu Component should connect to state 1`] = `
className="fa fa-fw fa-trash text-danger"
/>
Delete
</div>
</button>
</div>
<div
className="menu-legend"
@ -61,7 +63,7 @@ exports[`FlowMenu Component should connect to state 1`] = `
<div
className="menu-content"
>
<div
<button
className="btn btn-default"
onClick={[Function]}
title="download"
@ -70,7 +72,7 @@ exports[`FlowMenu Component should connect to state 1`] = `
className="fa fa-fw fa-download"
/>
Download
</div>
</button>
</div>
<div
className="menu-legend"
@ -84,7 +86,7 @@ exports[`FlowMenu Component should connect to state 1`] = `
<div
className="menu-content"
>
<div
<button
className="btn btn-default"
disabled={true}
title="[a]ccept intercepted flow"
@ -93,8 +95,8 @@ exports[`FlowMenu Component should connect to state 1`] = `
className="fa fa-fw fa-play text-success"
/>
Resume
</div>
<div
</button>
<button
className="btn btn-default"
disabled={true}
title="kill intercepted flow [x]"
@ -103,7 +105,7 @@ exports[`FlowMenu Component should connect to state 1`] = `
className="fa fa-fw fa-times text-danger"
/>
Abort
</div>
</button>
</div>
<div
className="menu-legend"
@ -115,14 +117,16 @@ exports[`FlowMenu Component should connect to state 1`] = `
`;
exports[`FlowMenu Component should render correctly with flow 1`] = `
<div>
<div
className="flow-menu"
>
<div
className="menu-group"
>
<div
className="menu-content"
>
<div
<button
className="btn btn-default"
onClick={[Function]}
title="[r]eplay flow"
@ -131,8 +135,8 @@ exports[`FlowMenu Component should render correctly with flow 1`] = `
className="fa fa-fw fa-repeat text-primary"
/>
Replay
</div>
<div
</button>
<button
className="btn btn-default"
onClick={[Function]}
title="[D]uplicate flow"
@ -141,8 +145,8 @@ exports[`FlowMenu Component should render correctly with flow 1`] = `
className="fa fa-fw fa-copy text-info"
/>
Duplicate
</div>
<div
</button>
<button
className="btn btn-default"
disabled={false}
onClick={[Function]}
@ -152,8 +156,8 @@ exports[`FlowMenu Component should render correctly with flow 1`] = `
className="fa fa-fw fa-history text-warning"
/>
Revert
</div>
<div
</button>
<button
className="btn btn-default"
onClick={[Function]}
title="[d]elete flow"
@ -162,7 +166,7 @@ exports[`FlowMenu Component should render correctly with flow 1`] = `
className="fa fa-fw fa-trash text-danger"
/>
Delete
</div>
</button>
</div>
<div
className="menu-legend"
@ -176,7 +180,7 @@ exports[`FlowMenu Component should render correctly with flow 1`] = `
<div
className="menu-content"
>
<div
<button
className="btn btn-default"
onClick={[Function]}
title="download"
@ -185,7 +189,7 @@ exports[`FlowMenu Component should render correctly with flow 1`] = `
className="fa fa-fw fa-download"
/>
Download
</div>
</button>
</div>
<div
className="menu-legend"
@ -199,7 +203,7 @@ exports[`FlowMenu Component should render correctly with flow 1`] = `
<div
className="menu-content"
>
<div
<button
className="btn btn-default"
disabled={false}
onClick={[Function]}
@ -209,8 +213,8 @@ exports[`FlowMenu Component should render correctly with flow 1`] = `
className="fa fa-fw fa-play text-success"
/>
Resume
</div>
<div
</button>
<button
className="btn btn-default"
disabled={false}
onClick={[Function]}
@ -220,7 +224,7 @@ exports[`FlowMenu Component should render correctly with flow 1`] = `
className="fa fa-fw fa-times text-danger"
/>
Abort
</div>
</button>
</div>
<div
className="menu-legend"

View File

@ -2,85 +2,121 @@
exports[`MainMenu Component should render and connect to state 1`] = `
<div
className="menu-main"
className="main-menu"
>
<div
className="filter-input input-group"
className="menu-group"
>
<span
className="input-group-addon"
<div
className="menu-content"
>
<i
className="fa fa-fw fa-search"
style={
Object {
"color": "black",
}
}
/>
</span>
<input
className="form-control"
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
onKeyDown={[Function]}
placeholder="Search"
type="text"
value="~u foo"
/>
<div
className="filter-input input-group"
>
<span
className="input-group-addon"
>
<i
className="fa fa-fw fa-search"
style={
Object {
"color": "black",
}
}
/>
</span>
<input
className="form-control"
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
onKeyDown={[Function]}
placeholder="Search"
type="text"
value="~u foo"
/>
</div>
<div
className="filter-input input-group"
>
<span
className="input-group-addon"
>
<i
className="fa fa-fw fa-tag"
style={
Object {
"color": "hsl(48, 100%, 50%)",
}
}
/>
</span>
<input
className="form-control"
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
onKeyDown={[Function]}
placeholder="Highlight"
type="text"
value="~a bar"
/>
</div>
</div>
<div
className="menu-legend"
>
Find
</div>
</div>
<div
className="filter-input input-group"
className="menu-group"
>
<span
className="input-group-addon"
<div
className="menu-content"
>
<i
className="fa fa-fw fa-tag"
style={
Object {
"color": "hsl(48, 100%, 50%)",
}
}
/>
</span>
<input
className="form-control"
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
onKeyDown={[Function]}
placeholder="Highlight"
type="text"
value="~a bar"
/>
</div>
<div
className="filter-input input-group"
>
<span
className="input-group-addon"
<div
className="filter-input input-group"
>
<span
className="input-group-addon"
>
<i
className="fa fa-fw fa-pause"
style={
Object {
"color": "hsl(208, 56%, 53%)",
}
}
/>
</span>
<input
className="form-control"
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
onKeyDown={[Function]}
placeholder="Intercept"
type="text"
value=""
/>
</div>
<button
className="btn-sm btn btn-default"
onClick={[Function]}
title="[a]ccept all"
>
<i
className="fa fa-fw fa-forward text-success"
/>
Resume All
</button>
</div>
<div
className="menu-legend"
>
<i
className="fa fa-fw fa-pause"
style={
Object {
"color": "hsl(208, 56%, 53%)",
}
}
/>
</span>
<input
className="form-control"
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
onKeyDown={[Function]}
placeholder="Intercept"
type="text"
value=""
/>
Intercept
</div>
</div>
</div>
`;

View File

@ -8,7 +8,7 @@ exports[`OptionMenu Component should render correctly 1`] = `
<div
className="menu-content"
>
<div
<button
className="btn btn-default"
onClick={[Function]}
title="Open Options"
@ -20,7 +20,7 @@ exports[`OptionMenu Component should render correctly 1`] = `
<sup>
alpha
</sup>
</div>
</button>
</div>
<div
className="menu-legend"

View File

@ -56,6 +56,12 @@ exports[`FlowTable Component should render correctly 1`] = `
>
Time
</th>
<th
className="col-quickactions"
onClick={[Function]}
>
</th>
</tr>
</thead>
<tbody>

View File

@ -24,7 +24,7 @@ describe('Dropdown Component', () => {
it('should handle open/close action', () => {
document.body.addEventListener('click', ()=>{})
let tree = dropup.toJSON(),
e = { preventDefault: jest.fn() }
e = { preventDefault: jest.fn(), stopPropagation: jest.fn() }
tree.children[0].props.onClick(e)
expect(tree).toMatchSnapshot()

View File

@ -1,18 +1,18 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Button Component should be able to be disabled 1`] = `
<div
<button
className="classname btn btn-default"
disabled="true"
>
<a>
foo
</a>
</div>
</button>
`;
exports[`Button Component should render correctly 1`] = `
<div
<button
className="classname btn btn-default"
onClick={[Function]}
title="title"
@ -23,5 +23,5 @@ exports[`Button Component should render correctly 1`] = `
<a>
foo
</a>
</div>
</button>
`;

View File

@ -1,28 +1,30 @@
import React from 'react'
import { render } from 'react-dom'
import { applyMiddleware, createStore } from 'redux'
import { Provider } from 'react-redux'
import {render} from 'react-dom'
import {applyMiddleware, compose, createStore} from 'redux'
import {Provider} from 'react-redux'
import thunk from 'redux-thunk'
import ProxyApp from './components/ProxyApp'
import rootReducer from './ducks/index'
import { add as addLog } from './ducks/eventLog'
import {add as addLog} from './ducks/eventLog'
import useUrlState from './urlState'
import WebSocketBackend from './backends/websocket'
import StaticBackend from './backends/static'
import { logger } from 'redux-logger'
import {logger} from 'redux-logger'
const middlewares = [thunk];
// logger must be last
if (process.env.NODE_ENV !== 'production') {
middlewares.push(logger);
middlewares.push(logger);
}
// logger must be last
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
rootReducer,
applyMiddleware(...middlewares)
composeEnhancers(applyMiddleware(...middlewares))
)
useUrlState(store)
@ -39,7 +41,7 @@ window.addEventListener('error', msg => {
document.addEventListener('DOMContentLoaded', () => {
render(
<Provider store={store}>
<ProxyApp />
<ProxyApp/>
</Provider>,
document.getElementById("mitmproxy")
)

View File

@ -12,7 +12,7 @@ ViewSelector.propTypes = {
}
export function ViewSelector ({contentViews, activeView, setContentView}){
let inner = <span> <b>View:</b> {activeView.toLowerCase()} <span className="caret"></span> </span>
let inner = <span> <b>View:</b> {activeView.toLowerCase()} <span className="caret"/> </span>
return (
<Dropdown dropup className="pull-left" btnClass="btn btn-default btn-xs" text={inner}>
@ -28,7 +28,7 @@ export function ViewSelector ({contentViews, activeView, setContentView}){
export default connect (
state => ({
contentViews: state.settings.contentViews,
contentViews: state.settings.contentViews || [],
activeView: state.ui.flow.contentView,
}), {
setContentView,

View File

@ -1,23 +1,25 @@
import React, { Component } from 'react'
import React from 'react'
import {useDispatch} from 'react-redux'
import classnames from 'classnames'
import { RequestUtils, ResponseUtils } from '../../flow/utils.js'
import { formatSize, formatTimeDelta, formatTimeStamp } from '../../utils.js'
import {RequestUtils, ResponseUtils} from '../../flow/utils.js'
import {formatSize, formatTimeDelta, formatTimeStamp} from '../../utils.js'
import * as flowActions from "../../ducks/flows";
export const defaultColumnNames = ["tls", "icon", "path", "method", "status", "size", "time"]
export function TLSColumn({ flow }) {
export function TLSColumn({flow}) {
return (
<td className={classnames('col-tls', flow.request.scheme === 'https' ? 'col-tls-https' : 'col-tls-http')}></td>
<td className={classnames('col-tls', flow.request.scheme === 'https' ? 'col-tls-https' : 'col-tls-http')}/>
)
}
TLSColumn.headerClass = 'col-tls'
TLSColumn.headerName = ''
export function IconColumn({ flow }) {
export function IconColumn({flow}) {
return (
<td className="col-icon">
<div className={classnames('resource-icon', IconColumn.getIcon(flow))}></div>
<div className={classnames('resource-icon', IconColumn.getIcon(flow))}/>
</td>
)
}
@ -55,23 +57,23 @@ IconColumn.getIcon = flow => {
return 'resource-icon-plain'
}
export function PathColumn({ flow }) {
export function PathColumn({flow}) {
let err;
if(flow.error){
if (flow.error.msg === "Connection killed."){
err = <i className="fa fa-fw fa-times pull-right"></i>
if (flow.error) {
if (flow.error.msg === "Connection killed.") {
err = <i className="fa fa-fw fa-times pull-right"/>
} else {
err = <i className="fa fa-fw fa-exclamation pull-right"></i>
err = <i className="fa fa-fw fa-exclamation pull-right"/>
}
}
return (
<td className="col-path">
{flow.request.is_replay && (
<i className="fa fa-fw fa-repeat pull-right"></i>
<i className="fa fa-fw fa-repeat pull-right"/>
)}
{flow.intercepted && (
<i className="fa fa-fw fa-pause pull-right"></i>
<i className="fa fa-fw fa-pause pull-right"/>
)}
{err}
{RequestUtils.pretty_url(flow.request)}
@ -82,7 +84,7 @@ export function PathColumn({ flow }) {
PathColumn.headerClass = 'col-path'
PathColumn.headerName = 'Path'
export function MethodColumn({ flow }) {
export function MethodColumn({flow}) {
return (
<td className="col-method">{flow.request.method}</td>
)
@ -91,22 +93,18 @@ export function MethodColumn({ flow }) {
MethodColumn.headerClass = 'col-method'
MethodColumn.headerName = 'Method'
export function StatusColumn({ flow }) {
export function StatusColumn({flow}) {
let color = 'darkred';
if (flow.response && 100 <= flow.response.status_code && flow.response.status_code < 200) {
color = 'green'
}
else if (flow.response && 200 <= flow.response.status_code && flow.response.status_code < 300) {
} else if (flow.response && 200 <= flow.response.status_code && flow.response.status_code < 300) {
color = 'darkgreen'
}
else if (flow.response && 300 <= flow.response.status_code && flow.response.status_code < 400) {
} else if (flow.response && 300 <= flow.response.status_code && flow.response.status_code < 400) {
color = 'lightblue'
}
else if (flow.response && 400 <= flow.response.status_code && flow.response.status_code < 500) {
} else if (flow.response && 400 <= flow.response.status_code && flow.response.status_code < 500) {
color = 'lightred'
}
else if (flow.response && 500 <= flow.response.status_code && flow.response.status_code < 600) {
} else if (flow.response && 500 <= flow.response.status_code && flow.response.status_code < 600) {
color = 'lightred'
}
@ -118,7 +116,7 @@ export function StatusColumn({ flow }) {
StatusColumn.headerClass = 'col-status'
StatusColumn.headerName = 'Status'
export function SizeColumn({ flow }) {
export function SizeColumn({flow}) {
return (
<td className="col-size">{formatSize(SizeColumn.getTotalSize(flow))}</td>
)
@ -135,7 +133,7 @@ SizeColumn.getTotalSize = flow => {
SizeColumn.headerClass = 'col-size'
SizeColumn.headerName = 'Size'
export function TimeColumn({ flow }) {
export function TimeColumn({flow}) {
return (
<td className="col-time">
{flow.response ? (
@ -150,7 +148,7 @@ export function TimeColumn({ flow }) {
TimeColumn.headerClass = 'col-time'
TimeColumn.headerName = 'Time'
export function TimeStampColumn({ flow }) {
export function TimeStampColumn({flow}) {
return (
<td className="col-start">
{flow.request.timestamp_start ? (
@ -165,7 +163,33 @@ export function TimeStampColumn({ flow }) {
TimeStampColumn.headerClass = 'col-timestamp'
TimeStampColumn.headerName = 'TimeStamp'
export default [
export function QuickActionsColumn({flow}) {
const dispatch = useDispatch()
function resume(e) {
dispatch(flowActions.resume(flow))
e.preventDefault()
e.stopPropagation()
}
return (
<td className="col-quickactions">
<div>
<div className="quickaction"><i className="fa fa-fw fa-ellipsis-h"/></div>
{flow.intercepted
? <div className="quickaction" onClick={resume}><i className="fa fa-fw fa-play text-success"/></div>
: null}
</div>
</td>
)
}
QuickActionsColumn.headerClass = 'col-quickactions'
QuickActionsColumn.headerName = ''
export const columns = {};
for (let col of [
TLSColumn,
IconColumn,
PathColumn,
@ -174,4 +198,7 @@ export default [
TimeStampColumn,
SizeColumn,
TimeColumn,
]
QuickActionsColumn,
]) {
columns[col.name.replace(/Column$/, "").toLowerCase()] = col;
}

View File

@ -1,10 +1,10 @@
import React from 'react'
import React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import {connect} from 'react-redux'
import classnames from 'classnames'
import columns, {defaultColumnNames} from './FlowColumns'
import {columns, defaultColumnNames} from './FlowColumns'
import { setSort } from '../../ducks/flows'
import {setSort} from '../../ducks/flows'
FlowTableHead.propTypes = {
setSort: PropTypes.func.isRequired,
@ -14,19 +14,13 @@ FlowTableHead.propTypes = {
}
export function getDisplayColumns(displayColumnNames) {
let displayColumns = []
if (typeof displayColumnNames == "undefined") {
return columns
return Object.values(columns)
}
for (const column of columns) {
if (displayColumnNames.includes(column.name.slice(0,-6).toLowerCase())) {
displayColumns.push(column)
}
}
return displayColumns
return displayColumnNames.map(x => columns[x]).concat([columns.quickactions]);
}
export function FlowTableHead({ sortColumn, sortDesc, setSort, displayColumnNames}) {
export function FlowTableHead({sortColumn, sortDesc, setSort, displayColumnNames}) {
const sortType = sortDesc ? 'sort-desc' : 'sort-asc'
const displayColumns = getDisplayColumns(displayColumnNames)

View File

@ -13,7 +13,7 @@ function Footer({ settings }) {
stickyauth, stickycookie, stream_large_bodies, listen_host, listen_port, version, server} = settings;
return (
<footer>
{mode && mode != "regular" && (
{mode && mode !== "regular" && (
<span className="label label-success">{mode} mode</span>
)}
{intercept && (
@ -25,8 +25,8 @@ function Footer({ settings }) {
{no_upstream_cert && (
<span className="label label-success">no-upstream-cert</span>
)}
{rawtcp && (
<span className="label label-success">raw-tcp</span>
{!rawtcp && (
<span className="label label-success">no-raw-tcp</span>
)}
{!http2 && (
<span className="label label-success">no-http2</span>

View File

@ -22,7 +22,7 @@ export function FlowMenu({ flow, resumeFlow, killFlow, replayFlow, duplicateFlow
if (!flow)
return <div/>
return (
<div>
<div className="flow-menu">
<HideInStatic>
<div className="menu-group">
<div className="menu-content">
@ -72,8 +72,6 @@ export function FlowMenu({ flow, resumeFlow, killFlow, replayFlow, duplicateFlow
<div className="menu-legend">Interception</div>
</div>
</HideInStatic>
</div>
)
}

View File

@ -1,24 +1,37 @@
import React, { Component } from "react"
import PropTypes from 'prop-types'
import { connect } from "react-redux"
import React from "react"
import {connect} from "react-redux"
import FilterInput from "./FilterInput"
import { update as updateSettings } from "../../ducks/settings"
import { setFilter, setHighlight } from "../../ducks/flows"
import {update as updateSettings} from "../../ducks/settings"
import * as flowsActions from "../../ducks/flows"
import {setFilter, setHighlight} from "../../ducks/flows"
import Button from "../common/Button"
MainMenu.title = "Start"
export default function MainMenu() {
return (
<div className="menu-main">
<FlowFilterInput/>
<HighlightInput/>
<InterceptInput/>
<div className="main-menu">
<div className="menu-group">
<div className="menu-content">
<FlowFilterInput/>
<HighlightInput/>
</div>
<div className="menu-legend">Find</div>
</div>
<div className="menu-group">
<div className="menu-content">
<InterceptInput/>
<ResumeAll/>
</div>
<div className="menu-legend">Intercept</div>
</div>
</div>
)
}
export function setIntercept(intercept) {
updateSettings({ intercept })
return updateSettings({intercept})
}
const InterceptInput = connect(
@ -28,7 +41,7 @@ const InterceptInput = connect(
type: 'pause',
color: 'hsl(208, 56%, 53%)'
}),
{ onChange: setIntercept }
{onChange: setIntercept}
)(FilterInput);
const FlowFilterInput = connect(
@ -38,7 +51,7 @@ const FlowFilterInput = connect(
type: 'search',
color: 'black'
}),
{ onChange: setFilter }
{onChange: setFilter}
)(FilterInput);
const HighlightInput = connect(
@ -48,5 +61,19 @@ const HighlightInput = connect(
type: 'tag',
color: 'hsl(48, 100%, 50%)'
}),
{ onChange: setHighlight }
{onChange: setHighlight}
)(FilterInput);
export function ResumeAll({resumeAll}) {
return (
<Button className="btn-sm" title="[a]ccept all"
icon="fa-forward text-success" onClick={() => resumeAll()}>
Resume All
</Button>
)
}
ResumeAll = connect(
null,
{resumeAll: flowsActions.resumeAll}
)(ResumeAll)

View File

@ -4,19 +4,19 @@ import classnames from "classnames"
Button.propTypes = {
onClick: PropTypes.func.isRequired,
children: PropTypes.node.isRequired,
children: PropTypes.node,
icon: PropTypes.string,
title: PropTypes.string,
}
export default function Button({ onClick, children, icon, disabled, className, title }) {
return (
<div className={classnames(className, 'btn btn-default')}
<button className={classnames(className, 'btn btn-default')}
onClick={disabled ? undefined : onClick}
disabled={disabled}
title={title}>
{icon && (<i className={"fa fa-fw " + icon}/> )}
{children}
</div>
</button>
)
}

View File

@ -1,4 +1,4 @@
import React, { Component } from 'react'
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
@ -18,21 +18,22 @@ export default class Dropdown extends Component {
constructor(props, context) {
super(props, context)
this.state = { open: false }
this.state = {open: false}
this.close = this.close.bind(this)
this.open = this.open.bind(this)
}
close() {
this.setState({ open: false })
this.setState({open: false})
document.removeEventListener('click', this.close)
}
open(e){
open(e) {
e.preventDefault()
if (this.state.open) {
return
}
e.stopPropagation();
this.setState({open: !this.state.open})
document.addEventListener('click', this.close)
}
@ -40,13 +41,13 @@ export default class Dropdown extends Component {
render() {
const {dropup, className, btnClass, text, children} = this.props
return (
<div className={classnames( (dropup ? 'dropup' : 'dropdown'), className, { open: this.state.open })}>
<div className={classnames((dropup ? 'dropup' : 'dropdown'), className, {open: this.state.open})}>
<a href='#' className={btnClass}
onClick={this.open}>
{text}
</a>
<ul className="dropdown-menu" role="menu">
{children.map ( (item, i) => <li key={i}> {item} </li> )}
{children.map((item, i) => <li key={i}> {item} </li>)}
</ul>
</div>
)

View File

@ -2544,6 +2544,7 @@ dedent@^0.7.0:
deep-diff@^0.3.5:
version "0.3.8"
resolved "https://registry.yarnpkg.com/deep-diff/-/deep-diff-0.3.8.tgz#c01de63efb0eec9798801d40c7e0dae25b582c84"
integrity sha1-wB3mPvsO7JeYgB1Ax+Da4ltYLIQ=
deep-extend@^0.6.0:
version "0.6.0"