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): class ResumeFlows(RequestHandler):
def post(self): def post(self):
for f in self.view: for f in self.view:
if not f.intercepted:
continue
f.resume() f.resume()
self.view.update([f]) 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. - 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 ## Architecture
There are two components: There are two components:

View File

@ -2,7 +2,6 @@
.user-select (@val) { .user-select (@val) {
-webkit-touch-callout: @val; -webkit-touch-callout: @val;
-webkit-user-select: @val; -webkit-user-select: @val;
-khtml-user-select: @val;
-moz-user-select: @val; -moz-user-select: @val;
-ms-user-select: @val; -ms-user-select: @val;
user-select: @val; user-select: @val;
@ -33,6 +32,7 @@
&.sort-asc, &.sort-desc { &.sort-asc, &.sort-desc {
background-color: lighten(#F2F2F2, 3%); background-color: lighten(#F2F2F2, 3%);
} }
&.sort-asc:after, &.sort-desc:after { &.sort-asc:after, &.sort-desc:after {
font: normal normal normal 14px/1 FontAwesome; font: normal normal normal 14px/1 FontAwesome;
position: absolute; position: absolute;
@ -41,9 +41,11 @@
padding: 2px; padding: 2px;
background-color: fadeout(lighten(#F2F2F2, 3%), 20%); background-color: fadeout(lighten(#F2F2F2, 3%), 20%);
} }
&.sort-asc:after { &.sort-asc:after {
content: "\f0de"; content: "\f0de";
} }
&.sort-desc:after { &.sort-desc:after {
content: "\f0dd"; content: "\f0dd";
} }
@ -53,17 +55,22 @@
tr { tr {
cursor: pointer; cursor: pointer;
background-color: white;
&:nth-child(even) { &:nth-child(even) {
background-color: rgba(0, 0, 0, 0.05); background-color: #f2f2f2;
} }
&.selected { &.selected {
background-color: hsla(209, 52%, 84%, 0.5) !important; background-color: #e0ebf5 !important;
} }
&.highlighted { &.highlighted {
background-color: hsla(48, 100%, 50%, 0.4); background-color: #ffeb99;
} }
&.highlighted:nth-child(even) { &.highlighted:nth-child(even) {
background-color: hsla(48, 100%, 50%, 0.5); background-color: #ffe57f;
} }
} }
@ -80,6 +87,7 @@
color: @interceptorange; color: @interceptorange;
} }
} }
tr.intercepted.has-response { tr.intercepted.has-response {
.col-status, .col-size, .col-time { .col-status, .col-size, .col-time {
color: @interceptorange; color: @interceptorange;
@ -88,6 +96,7 @@
.fa { .fa {
line-height: inherit; line-height: inherit;
&.pull-right { &.pull-right {
margin-left: 0; margin-left: 0;
} }
@ -96,39 +105,89 @@
.col-tls { .col-tls {
width: 10px; width: 10px;
} }
.col-tls-https { .col-tls-https {
background-color: rgba(0, 185, 0, 0.5); background-color: rgba(0, 185, 0, 0.5);
} }
.col-icon { .col-icon {
width: 32px; width: 32px;
} }
.col-path { .col-path {
.fa-repeat { .fa-repeat {
color: green; color: green;
} }
.fa-pause { .fa-pause {
color: @interceptorange; color: @interceptorange;
} }
.fa-exclamation, .fa-times { .fa-exclamation, .fa-times {
color: darkred; color: darkred;
} }
} }
.col-method { .col-method {
width: 60px; width: 60px;
} }
.col-status { .col-status {
width: 50px; width: 50px;
} }
.col-size { .col-size {
width: 70px; width: 70px;
} }
.col-time { .col-time {
width: 50px; width: 50px;
} }
.col-timestamp { .col-timestamp {
width: auto; width: auto;
} }
td.col-time, td.col-size { td.col-time, td.col-size {
text-align: right; 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/mixins/labels.less";
@import (reference) "../../node_modules/bootstrap/less/labels.less"; @import (reference) "../../node_modules/bootstrap/less/labels.less";
@menu-height: 85px; @menu-height: 95px;
header { header {
padding-top: 6px; padding-top: 6px;
@ -20,17 +20,16 @@ header {
} }
@menu-legend-height: 16px; @menu-legend-height: 16px;
@menu-group-hmargin: 3px; @menu-group-hmargin: 5px;
.menu-group { .menu-group {
margin: 0 @menu-group-hmargin; margin: 0 @menu-group-hmargin 0 (@menu-group-hmargin + 1px);
display: inline-block; display: inline-block;
height: @menu-height; height: @menu-height;
vertical-align: top;
} }
.menu-content { .menu-content {
height: @menu-height - @menu-legend-height; height: @menu-height - @menu-legend-height;
text-align: center; display: flow-root;
> .btn { > .btn {
height: @menu-height - @menu-legend-height; height: @menu-height - @menu-legend-height;
@ -45,6 +44,16 @@ header {
margin: 0 auto 5px; 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 { .menu-entry {
@ -65,6 +74,7 @@ header {
} }
.menu-legend { .menu-legend {
color: #777;
height: @menu-legend-height; height: @menu-legend-height;
text-align: center; text-align: center;
font-size: 12px; font-size: 12px;
@ -73,7 +83,7 @@ header {
.menu-group + .menu-group:before { .menu-group + .menu-group:before {
@space: 10px; @space: 10px;
margin-left: -@menu-group-hmargin; margin-left: -(@menu-group-hmargin + 1px);
content: " "; content: " ";
border-left: solid 1px lighten(grey, 40%); border-left: solid 1px lighten(grey, 40%);
margin-top: @space; margin-top: @space;
@ -82,19 +92,20 @@ header {
} }
@menu-main-gutter-width: 5px; @menu-main-gutter-width: 5px;
.menu-main { .main-menu {
.make-row(@menu-main-gutter-width); display: flex;
padding: 2px 5px; .menu-group {
width: 50%;
}
.btn-sm {
margin-top: 6px;
}
} }
.filter-input { .filter-input {
.make-sm-column(5, @menu-main-gutter-width); margin: 4px 0;
padding: 2.5px;
@media (max-width: @screen-xs-max) { @media (max-width: @screen-xs-max) {
padding: 2px 2.5px;
> .form-control, > .input-group-addon, > .input-group-btn > .btn { > .form-control, > .input-group-addon, > .input-group-btn > .btn {
height: 23.5px; height: 23.5px;
padding: 1px 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`] = ` exports[`ShowFullContentButton Component should render correctly 1`] = `
<div> <div>
<div <button
className="view-all-content-btn btn-xs btn btn-default" className="view-all-content-btn btn-xs btn btn-default"
onClick={[Function]} onClick={[Function]}
> >
Show full content Show full content
</div> </button>
<span <span
className="pull-right" className="pull-right"
> >

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -12,7 +12,7 @@ ViewSelector.propTypes = {
} }
export function ViewSelector ({contentViews, activeView, setContentView}){ 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 ( return (
<Dropdown dropup className="pull-left" btnClass="btn btn-default btn-xs" text={inner}> <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 ( export default connect (
state => ({ state => ({
contentViews: state.settings.contentViews, contentViews: state.settings.contentViews || [],
activeView: state.ui.flow.contentView, activeView: state.ui.flow.contentView,
}), { }), {
setContentView, 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 classnames from 'classnames'
import { RequestUtils, ResponseUtils } from '../../flow/utils.js' import {RequestUtils, ResponseUtils} from '../../flow/utils.js'
import { formatSize, formatTimeDelta, formatTimeStamp } from '../../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 const defaultColumnNames = ["tls", "icon", "path", "method", "status", "size", "time"]
export function TLSColumn({ flow }) { export function TLSColumn({flow}) {
return ( 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.headerClass = 'col-tls'
TLSColumn.headerName = '' TLSColumn.headerName = ''
export function IconColumn({ flow }) { export function IconColumn({flow}) {
return ( return (
<td className="col-icon"> <td className="col-icon">
<div className={classnames('resource-icon', IconColumn.getIcon(flow))}></div> <div className={classnames('resource-icon', IconColumn.getIcon(flow))}/>
</td> </td>
) )
} }
@ -55,23 +57,23 @@ IconColumn.getIcon = flow => {
return 'resource-icon-plain' return 'resource-icon-plain'
} }
export function PathColumn({ flow }) { export function PathColumn({flow}) {
let err; let err;
if(flow.error){ if (flow.error) {
if (flow.error.msg === "Connection killed."){ if (flow.error.msg === "Connection killed.") {
err = <i className="fa fa-fw fa-times pull-right"></i> err = <i className="fa fa-fw fa-times pull-right"/>
} else { } else {
err = <i className="fa fa-fw fa-exclamation pull-right"></i> err = <i className="fa fa-fw fa-exclamation pull-right"/>
} }
} }
return ( return (
<td className="col-path"> <td className="col-path">
{flow.request.is_replay && ( {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 && ( {flow.intercepted && (
<i className="fa fa-fw fa-pause pull-right"></i> <i className="fa fa-fw fa-pause pull-right"/>
)} )}
{err} {err}
{RequestUtils.pretty_url(flow.request)} {RequestUtils.pretty_url(flow.request)}
@ -82,7 +84,7 @@ export function PathColumn({ flow }) {
PathColumn.headerClass = 'col-path' PathColumn.headerClass = 'col-path'
PathColumn.headerName = 'Path' PathColumn.headerName = 'Path'
export function MethodColumn({ flow }) { export function MethodColumn({flow}) {
return ( return (
<td className="col-method">{flow.request.method}</td> <td className="col-method">{flow.request.method}</td>
) )
@ -91,22 +93,18 @@ export function MethodColumn({ flow }) {
MethodColumn.headerClass = 'col-method' MethodColumn.headerClass = 'col-method'
MethodColumn.headerName = 'Method' MethodColumn.headerName = 'Method'
export function StatusColumn({ flow }) { export function StatusColumn({flow}) {
let color = 'darkred'; let color = 'darkred';
if (flow.response && 100 <= flow.response.status_code && flow.response.status_code < 200) { if (flow.response && 100 <= flow.response.status_code && flow.response.status_code < 200) {
color = 'green' 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' 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' 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' 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' color = 'lightred'
} }
@ -118,7 +116,7 @@ export function StatusColumn({ flow }) {
StatusColumn.headerClass = 'col-status' StatusColumn.headerClass = 'col-status'
StatusColumn.headerName = 'Status' StatusColumn.headerName = 'Status'
export function SizeColumn({ flow }) { export function SizeColumn({flow}) {
return ( return (
<td className="col-size">{formatSize(SizeColumn.getTotalSize(flow))}</td> <td className="col-size">{formatSize(SizeColumn.getTotalSize(flow))}</td>
) )
@ -135,7 +133,7 @@ SizeColumn.getTotalSize = flow => {
SizeColumn.headerClass = 'col-size' SizeColumn.headerClass = 'col-size'
SizeColumn.headerName = 'Size' SizeColumn.headerName = 'Size'
export function TimeColumn({ flow }) { export function TimeColumn({flow}) {
return ( return (
<td className="col-time"> <td className="col-time">
{flow.response ? ( {flow.response ? (
@ -150,7 +148,7 @@ export function TimeColumn({ flow }) {
TimeColumn.headerClass = 'col-time' TimeColumn.headerClass = 'col-time'
TimeColumn.headerName = 'Time' TimeColumn.headerName = 'Time'
export function TimeStampColumn({ flow }) { export function TimeStampColumn({flow}) {
return ( return (
<td className="col-start"> <td className="col-start">
{flow.request.timestamp_start ? ( {flow.request.timestamp_start ? (
@ -165,7 +163,33 @@ export function TimeStampColumn({ flow }) {
TimeStampColumn.headerClass = 'col-timestamp' TimeStampColumn.headerClass = 'col-timestamp'
TimeStampColumn.headerName = '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, TLSColumn,
IconColumn, IconColumn,
PathColumn, PathColumn,
@ -174,4 +198,7 @@ export default [
TimeStampColumn, TimeStampColumn,
SizeColumn, SizeColumn,
TimeColumn, 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 PropTypes from 'prop-types'
import { connect } from 'react-redux' import {connect} from 'react-redux'
import classnames from 'classnames' 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 = { FlowTableHead.propTypes = {
setSort: PropTypes.func.isRequired, setSort: PropTypes.func.isRequired,
@ -14,19 +14,13 @@ FlowTableHead.propTypes = {
} }
export function getDisplayColumns(displayColumnNames) { export function getDisplayColumns(displayColumnNames) {
let displayColumns = []
if (typeof displayColumnNames == "undefined") { if (typeof displayColumnNames == "undefined") {
return columns return Object.values(columns)
} }
for (const column of columns) { return displayColumnNames.map(x => columns[x]).concat([columns.quickactions]);
if (displayColumnNames.includes(column.name.slice(0,-6).toLowerCase())) {
displayColumns.push(column)
}
}
return displayColumns
} }
export function FlowTableHead({ sortColumn, sortDesc, setSort, displayColumnNames}) { export function FlowTableHead({sortColumn, sortDesc, setSort, displayColumnNames}) {
const sortType = sortDesc ? 'sort-desc' : 'sort-asc' const sortType = sortDesc ? 'sort-desc' : 'sort-asc'
const displayColumns = getDisplayColumns(displayColumnNames) 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; stickyauth, stickycookie, stream_large_bodies, listen_host, listen_port, version, server} = settings;
return ( return (
<footer> <footer>
{mode && mode != "regular" && ( {mode && mode !== "regular" && (
<span className="label label-success">{mode} mode</span> <span className="label label-success">{mode} mode</span>
)} )}
{intercept && ( {intercept && (
@ -25,8 +25,8 @@ function Footer({ settings }) {
{no_upstream_cert && ( {no_upstream_cert && (
<span className="label label-success">no-upstream-cert</span> <span className="label label-success">no-upstream-cert</span>
)} )}
{rawtcp && ( {!rawtcp && (
<span className="label label-success">raw-tcp</span> <span className="label label-success">no-raw-tcp</span>
)} )}
{!http2 && ( {!http2 && (
<span className="label label-success">no-http2</span> <span className="label label-success">no-http2</span>

View File

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

View File

@ -1,24 +1,37 @@
import React, { Component } from "react" import React from "react"
import PropTypes from 'prop-types' import {connect} from "react-redux"
import { connect } from "react-redux"
import FilterInput from "./FilterInput" import FilterInput from "./FilterInput"
import { update as updateSettings } from "../../ducks/settings" import {update as updateSettings} from "../../ducks/settings"
import { setFilter, setHighlight } from "../../ducks/flows" import * as flowsActions from "../../ducks/flows"
import {setFilter, setHighlight} from "../../ducks/flows"
import Button from "../common/Button"
MainMenu.title = "Start" MainMenu.title = "Start"
export default function MainMenu() { export default function MainMenu() {
return ( return (
<div className="menu-main"> <div className="main-menu">
<FlowFilterInput/> <div className="menu-group">
<HighlightInput/> <div className="menu-content">
<InterceptInput/> <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> </div>
) )
} }
export function setIntercept(intercept) { export function setIntercept(intercept) {
updateSettings({ intercept }) return updateSettings({intercept})
} }
const InterceptInput = connect( const InterceptInput = connect(
@ -28,7 +41,7 @@ const InterceptInput = connect(
type: 'pause', type: 'pause',
color: 'hsl(208, 56%, 53%)' color: 'hsl(208, 56%, 53%)'
}), }),
{ onChange: setIntercept } {onChange: setIntercept}
)(FilterInput); )(FilterInput);
const FlowFilterInput = connect( const FlowFilterInput = connect(
@ -38,7 +51,7 @@ const FlowFilterInput = connect(
type: 'search', type: 'search',
color: 'black' color: 'black'
}), }),
{ onChange: setFilter } {onChange: setFilter}
)(FilterInput); )(FilterInput);
const HighlightInput = connect( const HighlightInput = connect(
@ -48,5 +61,19 @@ const HighlightInput = connect(
type: 'tag', type: 'tag',
color: 'hsl(48, 100%, 50%)' color: 'hsl(48, 100%, 50%)'
}), }),
{ onChange: setHighlight } {onChange: setHighlight}
)(FilterInput); )(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 = { Button.propTypes = {
onClick: PropTypes.func.isRequired, onClick: PropTypes.func.isRequired,
children: PropTypes.node.isRequired, children: PropTypes.node,
icon: PropTypes.string, icon: PropTypes.string,
title: PropTypes.string, title: PropTypes.string,
} }
export default function Button({ onClick, children, icon, disabled, className, title }) { export default function Button({ onClick, children, icon, disabled, className, title }) {
return ( return (
<div className={classnames(className, 'btn btn-default')} <button className={classnames(className, 'btn btn-default')}
onClick={disabled ? undefined : onClick} onClick={disabled ? undefined : onClick}
disabled={disabled} disabled={disabled}
title={title}> title={title}>
{icon && (<i className={"fa fa-fw " + icon}/> )} {icon && (<i className={"fa fa-fw " + icon}/> )}
{children} {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 PropTypes from 'prop-types'
import classnames from 'classnames' import classnames from 'classnames'
@ -18,21 +18,22 @@ export default class Dropdown extends Component {
constructor(props, context) { constructor(props, context) {
super(props, context) super(props, context)
this.state = { open: false } this.state = {open: false}
this.close = this.close.bind(this) this.close = this.close.bind(this)
this.open = this.open.bind(this) this.open = this.open.bind(this)
} }
close() { close() {
this.setState({ open: false }) this.setState({open: false})
document.removeEventListener('click', this.close) document.removeEventListener('click', this.close)
} }
open(e){ open(e) {
e.preventDefault() e.preventDefault()
if (this.state.open) { if (this.state.open) {
return return
} }
e.stopPropagation();
this.setState({open: !this.state.open}) this.setState({open: !this.state.open})
document.addEventListener('click', this.close) document.addEventListener('click', this.close)
} }
@ -40,13 +41,13 @@ export default class Dropdown extends Component {
render() { render() {
const {dropup, className, btnClass, text, children} = this.props const {dropup, className, btnClass, text, children} = this.props
return ( 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} <a href='#' className={btnClass}
onClick={this.open}> onClick={this.open}>
{text} {text}
</a> </a>
<ul className="dropdown-menu" role="menu"> <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> </ul>
</div> </div>
) )

View File

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