mirror of
https://github.com/Grasscutters/mitmproxy.git
synced 2024-11-22 07:08:10 +00:00
convert components in FlowView, Header, Modal, ValueEditor into typescript
This commit is contained in:
parent
e0b8a48392
commit
29997bca4b
@ -2,75 +2,29 @@ jest.mock('../../../flow/utils')
|
|||||||
|
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import renderer from 'react-test-renderer'
|
import renderer from 'react-test-renderer'
|
||||||
import ConnectedFlowMenu, { FlowMenu } from '../../../components/Header/FlowMenu'
|
import FlowMenu from '../../../components/Header/FlowMenu'
|
||||||
import { TFlow, TStore }from '../../ducks/tutils'
|
import { TFlow, TStore }from '../../ducks/tutils'
|
||||||
import { MessageUtils } from "../../../flow/utils"
|
import { MessageUtils } from "../../../flow/utils"
|
||||||
import { Provider } from 'react-redux'
|
import { Provider } from 'react-redux'
|
||||||
|
|
||||||
describe('FlowMenu Component', () => {
|
describe('FlowMenu Component', () => {
|
||||||
let actions = {
|
let tflow = new TFlow(),
|
||||||
resumeFlow: jest.fn(),
|
store = new TStore()
|
||||||
killFlow: jest.fn(),
|
|
||||||
replayFlow: jest.fn(),
|
|
||||||
duplicateFlow: jest.fn(),
|
|
||||||
removeFlow: jest.fn(),
|
|
||||||
revertFlow: jest.fn()
|
|
||||||
},
|
|
||||||
tflow = new TFlow()
|
|
||||||
tflow.modified = true
|
tflow.modified = true
|
||||||
tflow.intercepted = true
|
tflow.intercepted = true
|
||||||
|
global.fetch = jest.fn()
|
||||||
|
|
||||||
it('should render correctly without flow', () => {
|
let flowMenu = renderer.create(
|
||||||
let flowMenu = renderer.create(
|
<Provider store={store}>
|
||||||
<FlowMenu removeFlow={actions.removeFlow}
|
<FlowMenu />
|
||||||
killFlow={actions.killFlow}
|
</Provider>
|
||||||
replayFlow={actions.replayFlow}
|
),
|
||||||
duplicateFlow={actions.duplicateFlow}
|
|
||||||
resumeFlow={actions.resumeFlow}
|
|
||||||
revertFlow={actions.revertFlow}/>),
|
|
||||||
tree = flowMenu.toJSON()
|
|
||||||
expect(tree).toMatchSnapshot()
|
|
||||||
})
|
|
||||||
|
|
||||||
let flowMenu = renderer.create(<FlowMenu
|
|
||||||
flow={tflow}
|
|
||||||
removeFlow={actions.removeFlow}
|
|
||||||
killFlow={actions.killFlow}
|
|
||||||
replayFlow={actions.replayFlow}
|
|
||||||
duplicateFlow={actions.duplicateFlow}
|
|
||||||
resumeFlow={actions.resumeFlow}
|
|
||||||
revertFlow={actions.revertFlow}/>),
|
|
||||||
tree = flowMenu.toJSON()
|
tree = flowMenu.toJSON()
|
||||||
|
|
||||||
it('should render correctly with flow', () => {
|
it('should render correctly with flow', () => {
|
||||||
expect(tree).toMatchSnapshot()
|
expect(tree).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
let menu_content_1 = tree.children[0].children[0]
|
|
||||||
it('should handle replayFlow', () => {
|
|
||||||
let button = menu_content_1.children[0]
|
|
||||||
button.props.onClick()
|
|
||||||
expect(actions.replayFlow).toBeCalledWith(tflow)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should handle duplicateFlow', () => {
|
|
||||||
let button = menu_content_1.children[1]
|
|
||||||
button.props.onClick()
|
|
||||||
expect(actions.duplicateFlow).toBeCalledWith(tflow)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should handle revertFlow', () => {
|
|
||||||
let button = menu_content_1.children[2]
|
|
||||||
button.props.onClick()
|
|
||||||
expect(actions.revertFlow).toBeCalledWith(tflow)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should handle removeFlow', () => {
|
|
||||||
let button = menu_content_1.children[3]
|
|
||||||
button.props.onClick()
|
|
||||||
expect(actions.removeFlow).toBeCalledWith(tflow)
|
|
||||||
})
|
|
||||||
|
|
||||||
let menu_content_2 = tree.children[1].children[0]
|
let menu_content_2 = tree.children[1].children[0]
|
||||||
it('should handle download', () => {
|
it('should handle download', () => {
|
||||||
let button = menu_content_2.children[0]
|
let button = menu_content_2.children[0]
|
||||||
@ -78,24 +32,4 @@ describe('FlowMenu Component', () => {
|
|||||||
expect(MessageUtils.getContentURL).toBeCalledWith(tflow, tflow.response)
|
expect(MessageUtils.getContentURL).toBeCalledWith(tflow, tflow.response)
|
||||||
})
|
})
|
||||||
|
|
||||||
let menu_content_3 = tree.children[2].children[0]
|
|
||||||
it('should handle resumeFlow', () => {
|
|
||||||
let button = menu_content_3.children[0]
|
|
||||||
button.props.onClick()
|
|
||||||
expect(actions.resumeFlow).toBeCalledWith(tflow)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should handle killFlow', () => {
|
|
||||||
let button = menu_content_3.children[1]
|
|
||||||
button.props.onClick()
|
|
||||||
expect(actions.killFlow).toBeCalledWith(tflow)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should connect to state', () => {
|
|
||||||
let store = TStore(),
|
|
||||||
provider = renderer.create(<Provider store={store}><ConnectedFlowMenu/></Provider>),
|
|
||||||
tree = provider.toJSON()
|
|
||||||
expect(tree).toMatchSnapshot()
|
|
||||||
})
|
|
||||||
|
|
||||||
})
|
})
|
||||||
|
@ -1,121 +1,5 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`FlowMenu Component should connect to state 1`] = `
|
|
||||||
<div
|
|
||||||
className="flow-menu"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="menu-group"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="menu-content"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
className="btn btn-default"
|
|
||||||
onClick={[Function]}
|
|
||||||
title="[r]eplay flow"
|
|
||||||
>
|
|
||||||
<i
|
|
||||||
className="fa fa-fw fa-repeat text-primary"
|
|
||||||
/>
|
|
||||||
Replay
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className="btn btn-default"
|
|
||||||
onClick={[Function]}
|
|
||||||
title="[D]uplicate flow"
|
|
||||||
>
|
|
||||||
<i
|
|
||||||
className="fa fa-fw fa-copy text-info"
|
|
||||||
/>
|
|
||||||
Duplicate
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className="btn btn-default"
|
|
||||||
disabled={true}
|
|
||||||
title="revert changes to flow [V]"
|
|
||||||
>
|
|
||||||
<i
|
|
||||||
className="fa fa-fw fa-history text-warning"
|
|
||||||
/>
|
|
||||||
Revert
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className="btn btn-default"
|
|
||||||
onClick={[Function]}
|
|
||||||
title="[d]elete flow"
|
|
||||||
>
|
|
||||||
<i
|
|
||||||
className="fa fa-fw fa-trash text-danger"
|
|
||||||
/>
|
|
||||||
Delete
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="menu-legend"
|
|
||||||
>
|
|
||||||
Flow Modification
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="menu-group"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="menu-content"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
className="btn btn-default"
|
|
||||||
onClick={[Function]}
|
|
||||||
title="download"
|
|
||||||
>
|
|
||||||
<i
|
|
||||||
className="fa fa-fw fa-download"
|
|
||||||
/>
|
|
||||||
Download
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="menu-legend"
|
|
||||||
>
|
|
||||||
Export
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="menu-group"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="menu-content"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
className="btn btn-default"
|
|
||||||
disabled={true}
|
|
||||||
title="[a]ccept intercepted flow"
|
|
||||||
>
|
|
||||||
<i
|
|
||||||
className="fa fa-fw fa-play text-success"
|
|
||||||
/>
|
|
||||||
Resume
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className="btn btn-default"
|
|
||||||
disabled={true}
|
|
||||||
title="kill intercepted flow [x]"
|
|
||||||
>
|
|
||||||
<i
|
|
||||||
className="fa fa-fw fa-times text-danger"
|
|
||||||
/>
|
|
||||||
Abort
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="menu-legend"
|
|
||||||
>
|
|
||||||
Interception
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`FlowMenu Component should render correctly with flow 1`] = `
|
exports[`FlowMenu Component should render correctly with flow 1`] = `
|
||||||
<div
|
<div
|
||||||
className="flow-menu"
|
className="flow-menu"
|
||||||
@ -234,5 +118,3 @@ exports[`FlowMenu Component should render correctly with flow 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`FlowMenu Component should render correctly without flow 1`] = `<div />`;
|
|
||||||
|
@ -21,6 +21,8 @@ export {TFlow}
|
|||||||
|
|
||||||
const tflow1: HTTPFlow = TFlow();
|
const tflow1: HTTPFlow = TFlow();
|
||||||
const tflow2: HTTPFlow = TFlow();
|
const tflow2: HTTPFlow = TFlow();
|
||||||
|
tflow1.modified = true
|
||||||
|
tflow1.intercepted = true
|
||||||
tflow2.id = "flow2";
|
tflow2.id = "flow2";
|
||||||
tflow2.request.path = "/second";
|
tflow2.request.path = "/second";
|
||||||
|
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import CodeMirror from "../../contrib/CodeMirror"
|
import CodeMirror from "../../contrib/CodeMirror"
|
||||||
|
|
||||||
|
|
||||||
CodeEditor.propTypes = {
|
type CodeEditorProps = {
|
||||||
content: PropTypes.string.isRequired,
|
content: string,
|
||||||
onChange: PropTypes.func.isRequired,
|
onChange: Function,
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function CodeEditor ( { content, onChange} ){
|
export default function CodeEditor ( { content, onChange}: CodeEditorProps ){
|
||||||
|
|
||||||
let options = {
|
let options = {
|
||||||
lineNumbers: true
|
lineNumbers: true
|
||||||
|
@ -1,14 +1,12 @@
|
|||||||
import React, { Component } from 'react'
|
import React from 'react'
|
||||||
import { MessageUtils } from '../../flow/utils'
|
import { MessageUtils } from '../../flow/utils'
|
||||||
|
import { Flow, HTTPMessage } from '../../flow'
|
||||||
|
|
||||||
type ContentLoaderProps = {
|
type ContentLoaderProps = {
|
||||||
content: string,
|
content: string,
|
||||||
contentView: object,
|
contentView: object,
|
||||||
flow: object,
|
flow: Flow,
|
||||||
message: {
|
message: HTTPMessage,
|
||||||
content: string,
|
|
||||||
contentHash: string,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ContentLoaderStates = {
|
type ContentLoaderStates = {
|
||||||
|
@ -3,11 +3,12 @@ import ViewSelector from './ViewSelector'
|
|||||||
import UploadContentButton from './UploadContentButton'
|
import UploadContentButton from './UploadContentButton'
|
||||||
import DownloadContentButton from './DownloadContentButton'
|
import DownloadContentButton from './DownloadContentButton'
|
||||||
import { useAppSelector } from "../../ducks";
|
import { useAppSelector } from "../../ducks";
|
||||||
|
import { Flow, HTTPMessage } from '../../flow'
|
||||||
|
|
||||||
type ContentViewOptionsProps = {
|
type ContentViewOptionsProps = {
|
||||||
flow: object,
|
flow: Flow,
|
||||||
message: object,
|
message: HTTPMessage,
|
||||||
uploadContent: () => void,
|
uploadContent: (content: string) => Promise<Response>,
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ContentViewOptions({ flow, message, uploadContent }: ContentViewOptionsProps) {
|
export default function ContentViewOptions({ flow, message, uploadContent }: ContentViewOptionsProps) {
|
||||||
|
@ -24,7 +24,7 @@ function ViewImage({ flow, message }: ViewImageProps) {
|
|||||||
|
|
||||||
type EditProps = {
|
type EditProps = {
|
||||||
content: string,
|
content: string,
|
||||||
onChange: () => void,
|
onChange: (content: string) => any,
|
||||||
}
|
}
|
||||||
|
|
||||||
function PureEdit({ content, onChange }: EditProps) {
|
function PureEdit({ content, onChange }: EditProps) {
|
||||||
@ -39,7 +39,7 @@ type PureViewServerProps = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type PureViewServerStates = {
|
type PureViewServerStates = {
|
||||||
lines: string[][],
|
lines: [style: string, text: string][][],
|
||||||
description: string,
|
description: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { MessageUtils } from "../../flow/utils"
|
import { MessageUtils } from "../../flow/utils"
|
||||||
|
import { Flow, HTTPMessage } from '../../flow'
|
||||||
|
|
||||||
type DownloadContentButtonProps = {
|
type DownloadContentButtonProps = {
|
||||||
flow: object,
|
flow: Flow,
|
||||||
message: object,
|
message: HTTPMessage,
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function DownloadContentButton({ flow, message }: DownloadContentButtonProps) {
|
export default function DownloadContentButton({ flow, message }: DownloadContentButtonProps) {
|
||||||
|
@ -2,10 +2,11 @@ import React from 'react'
|
|||||||
import { formatSize } from '../../utils'
|
import { formatSize } from '../../utils'
|
||||||
import UploadContentButton from './UploadContentButton'
|
import UploadContentButton from './UploadContentButton'
|
||||||
import DownloadContentButton from './DownloadContentButton'
|
import DownloadContentButton from './DownloadContentButton'
|
||||||
|
import { HTTPFlow, HTTPMessage } from '../../flow'
|
||||||
|
|
||||||
interface ContentProps {
|
interface ContentProps {
|
||||||
flow: { request: object },
|
flow: HTTPFlow,
|
||||||
message: { contentLength: number },
|
message: HTTPMessage,
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ContentTooLargeProps extends ContentProps {
|
interface ContentTooLargeProps extends ContentProps {
|
||||||
|
@ -10,13 +10,14 @@ export default function ShowFullContentButton() {
|
|||||||
contentLines = useAppSelector(state => state.ui.flow.content.length)
|
contentLines = useAppSelector(state => state.ui.flow.content.length)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
!showFullContent &&
|
!showFullContent ? (
|
||||||
<div>
|
<div>
|
||||||
<Button className="view-all-content-btn btn-xs" onClick={() => dispatch(setShowFullContent())}>
|
<Button className="view-all-content-btn btn-xs" onClick={() => dispatch(setShowFullContent())}>
|
||||||
Show full content
|
Show full content
|
||||||
</Button>
|
</Button>
|
||||||
<span className="pull-right"> {visibleLines}/{contentLines} are visible </span>
|
<span className="pull-right"> {visibleLines}/{contentLines} are visible </span>
|
||||||
</div>
|
</div>
|
||||||
|
) : null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ import React from 'react'
|
|||||||
import FileChooser from '../common/FileChooser'
|
import FileChooser from '../common/FileChooser'
|
||||||
|
|
||||||
type UploadContentButtonProps = {
|
type UploadContentButtonProps = {
|
||||||
uploadContent: () => any,
|
uploadContent: (content: string) => Promise<Response>,
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function UploadContentButton({ uploadContent }: UploadContentButtonProps) {
|
export default function UploadContentButton({ uploadContent }: UploadContentButtonProps) {
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import React, { Component } from 'react'
|
import React from 'react'
|
||||||
import { connect } from 'react-redux'
|
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
|
|
||||||
import Nav from './FlowView/Nav'
|
import Nav from './FlowView/Nav'
|
||||||
@ -7,10 +6,15 @@ import { ErrorView as Error, Request, Response } from './FlowView/Messages'
|
|||||||
import Details from './FlowView/Details'
|
import Details from './FlowView/Details'
|
||||||
|
|
||||||
import { selectTab } from '../ducks/ui/flow'
|
import { selectTab } from '../ducks/ui/flow'
|
||||||
|
import {useAppDispatch, useAppSelector} from "../ducks";
|
||||||
|
|
||||||
export const allTabs = { Request, Response, Error, Details }
|
export const allTabs = { Request, Response, Error, Details }
|
||||||
|
|
||||||
function FlowView({ flow, tabName, selectTab }) {
|
export default function FlowView() {
|
||||||
|
const dispatch = useAppDispatch(),
|
||||||
|
flow = useAppSelector(state => state.flows.byId[state.flows.selected[0]])
|
||||||
|
|
||||||
|
let tabName = useAppSelector(state => state.ui.flow.tab)
|
||||||
|
|
||||||
// only display available tab names
|
// only display available tab names
|
||||||
const tabs = ['request', 'response', 'error'].filter(k => flow[k])
|
const tabs = ['request', 'response', 'error'].filter(k => flow[k])
|
||||||
@ -33,19 +37,9 @@ function FlowView({ flow, tabName, selectTab }) {
|
|||||||
<Nav
|
<Nav
|
||||||
tabs={tabs}
|
tabs={tabs}
|
||||||
active={tabName}
|
active={tabName}
|
||||||
onSelectTab={selectTab}
|
onSelectTab={(tab: string) => dispatch(selectTab(tab))}
|
||||||
/>
|
/>
|
||||||
<Tab flow={flow}/>
|
<Tab flow={flow}/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(
|
|
||||||
state => ({
|
|
||||||
flow: state.flows.byId[state.flows.selected[0]],
|
|
||||||
tabName: state.ui.flow.tab,
|
|
||||||
}),
|
|
||||||
{
|
|
||||||
selectTab,
|
|
||||||
}
|
|
||||||
)(FlowView)
|
|
@ -1,7 +1,14 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {formatTimeDelta, formatTimeStamp} from '../../utils'
|
import {formatTimeDelta, formatTimeStamp} from '../../utils'
|
||||||
|
import { Flow, HTTPMessage } from '../../flow'
|
||||||
|
|
||||||
export function TimeStamp({t, deltaTo, title}) {
|
type TimeStampProps = {
|
||||||
|
t: number,
|
||||||
|
deltaTo: number,
|
||||||
|
title: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export function TimeStamp({t, deltaTo, title}: TimeStampProps) {
|
||||||
return t ? (
|
return t ? (
|
||||||
<tr>
|
<tr>
|
||||||
<td>{title}:</td>
|
<td>{title}:</td>
|
||||||
@ -19,7 +26,19 @@ export function TimeStamp({t, deltaTo, title}) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ConnectionInfo({conn}) {
|
type ConnectionInfoProps = {
|
||||||
|
conn: {
|
||||||
|
address: string[],
|
||||||
|
sni: string,
|
||||||
|
tls_version: string,
|
||||||
|
cipher_name: string,
|
||||||
|
alpn_proto_negotiated: string,
|
||||||
|
ip_address: string[],
|
||||||
|
source_address: string[],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ConnectionInfo({conn}: ConnectionInfoProps) {
|
||||||
return (
|
return (
|
||||||
<table className="connection-table">
|
<table className="connection-table">
|
||||||
<tbody>
|
<tbody>
|
@ -1,203 +0,0 @@
|
|||||||
import React, { Component } from 'react'
|
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import { connect } from 'react-redux'
|
|
||||||
|
|
||||||
|
|
||||||
import { RequestUtils, isValidHttpVersion, parseUrl } from '../../flow/utils'
|
|
||||||
import { formatTimeStamp } from '../../utils'
|
|
||||||
import ContentView from '../ContentView'
|
|
||||||
import ContentViewOptions from '../ContentView/ContentViewOptions'
|
|
||||||
import ValidateEditor from '../ValueEditor/ValidateEditor'
|
|
||||||
import ValueEditor from '../ValueEditor/ValueEditor'
|
|
||||||
import HideInStatic from '../common/HideInStatic'
|
|
||||||
|
|
||||||
import Headers from './Headers'
|
|
||||||
import { startEdit, updateEdit } from '../../ducks/ui/flow'
|
|
||||||
import * as FlowActions from '../../ducks/flows'
|
|
||||||
import ToggleEdit from './ToggleEdit'
|
|
||||||
|
|
||||||
function RequestLine({ flow, readonly, updateFlow }) {
|
|
||||||
return (
|
|
||||||
<div className="first-line request-line">
|
|
||||||
<div>
|
|
||||||
<ValueEditor
|
|
||||||
content={flow.request.method}
|
|
||||||
readonly={readonly}
|
|
||||||
onDone={method => updateFlow({ request: { method } })}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<ValidateEditor
|
|
||||||
content={RequestUtils.pretty_url(flow.request)}
|
|
||||||
readonly={readonly}
|
|
||||||
onDone={url => updateFlow({ request: {path: '', ...parseUrl(url)}})}
|
|
||||||
isValid={url => !!parseUrl(url).host}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<ValidateEditor
|
|
||||||
content={flow.request.http_version}
|
|
||||||
readonly={readonly}
|
|
||||||
onDone={http_version => updateFlow({ request: { http_version } })}
|
|
||||||
isValid={isValidHttpVersion}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function ResponseLine({ flow, readonly, updateFlow }) {
|
|
||||||
return (
|
|
||||||
<div className="first-line response-line">
|
|
||||||
<ValidateEditor
|
|
||||||
content={flow.response.http_version}
|
|
||||||
readonly={readonly}
|
|
||||||
onDone={nextVer => updateFlow({ response: { http_version: nextVer } })}
|
|
||||||
isValid={isValidHttpVersion}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<ValidateEditor
|
|
||||||
content={flow.response.status_code + ''}
|
|
||||||
readonly={readonly}
|
|
||||||
onDone={code => updateFlow({ response: { code: parseInt(code) } })}
|
|
||||||
isValid={code => /^\d+$/.test(code)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<ValueEditor
|
|
||||||
content={flow.response.reason}
|
|
||||||
readonly={readonly}
|
|
||||||
onDone={msg => updateFlow({ response: { msg } })}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const Message = connect(
|
|
||||||
state => ({
|
|
||||||
flow: state.ui.flow.modifiedFlow || state.flows.byId[state.flows.selected[0]],
|
|
||||||
isEdit: !!state.ui.flow.modifiedFlow,
|
|
||||||
}),
|
|
||||||
{
|
|
||||||
updateFlow: updateEdit,
|
|
||||||
uploadContent: FlowActions.uploadContent
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
export class Request extends Component {
|
|
||||||
render() {
|
|
||||||
const { flow, isEdit, updateFlow, uploadContent } = this.props
|
|
||||||
let noContent = !isEdit && (flow.request.contentLength == 0 || flow.request.contentLength == null)
|
|
||||||
return (
|
|
||||||
<section className="request">
|
|
||||||
<article>
|
|
||||||
<ToggleEdit/>
|
|
||||||
<RequestLine
|
|
||||||
flow={flow}
|
|
||||||
readonly={!isEdit}
|
|
||||||
updateFlow={updateFlow}/>
|
|
||||||
<Headers
|
|
||||||
message={flow.request}
|
|
||||||
readonly={!isEdit}
|
|
||||||
onChange={headers => updateFlow({ request: { headers } })}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<hr/>
|
|
||||||
<ContentView
|
|
||||||
readonly={!isEdit}
|
|
||||||
flow={flow}
|
|
||||||
onContentChange={content => updateFlow({ request: {content}})}
|
|
||||||
message={flow.request}/>
|
|
||||||
|
|
||||||
<hr/>
|
|
||||||
<Headers
|
|
||||||
message={flow.request}
|
|
||||||
readonly={!isEdit}
|
|
||||||
onChange={trailers => updateFlow({ request: { trailers } })}
|
|
||||||
type='trailers'
|
|
||||||
/>
|
|
||||||
</article>
|
|
||||||
<HideInStatic>
|
|
||||||
{!noContent &&
|
|
||||||
<footer>
|
|
||||||
<ContentViewOptions
|
|
||||||
flow={flow}
|
|
||||||
readonly={!isEdit}
|
|
||||||
message={flow.request}
|
|
||||||
uploadContent={content => uploadContent(flow, content, "request")}/>
|
|
||||||
</footer>
|
|
||||||
}
|
|
||||||
</HideInStatic>
|
|
||||||
</section>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Request = Message(Request)
|
|
||||||
|
|
||||||
|
|
||||||
export class Response extends Component {
|
|
||||||
render() {
|
|
||||||
const { flow, isEdit, updateFlow, uploadContent } = this.props
|
|
||||||
let noContent = !isEdit && (flow.response.contentLength == 0 || flow.response.contentLength == null)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section className="response">
|
|
||||||
<article>
|
|
||||||
<ToggleEdit/>
|
|
||||||
<ResponseLine
|
|
||||||
flow={flow}
|
|
||||||
readonly={!isEdit}
|
|
||||||
updateFlow={updateFlow}/>
|
|
||||||
<Headers
|
|
||||||
message={flow.response}
|
|
||||||
readonly={!isEdit}
|
|
||||||
onChange={headers => updateFlow({ response: { headers } })}
|
|
||||||
/>
|
|
||||||
<hr/>
|
|
||||||
<ContentView
|
|
||||||
readonly={!isEdit}
|
|
||||||
flow={flow}
|
|
||||||
onContentChange={content => updateFlow({ response: {content}})}
|
|
||||||
message={flow.response}
|
|
||||||
/>
|
|
||||||
<hr/>
|
|
||||||
<Headers
|
|
||||||
message={flow.response}
|
|
||||||
readonly={!isEdit}
|
|
||||||
onChange={trailers => updateFlow({ response: { trailers } })}
|
|
||||||
type='trailers'
|
|
||||||
/>
|
|
||||||
</article>
|
|
||||||
<HideInStatic>
|
|
||||||
{!noContent &&
|
|
||||||
<footer >
|
|
||||||
<ContentViewOptions
|
|
||||||
flow={flow}
|
|
||||||
message={flow.response}
|
|
||||||
uploadContent={content => uploadContent(flow, content, "response")}
|
|
||||||
readonly={!isEdit}/>
|
|
||||||
</footer>
|
|
||||||
}
|
|
||||||
</HideInStatic>
|
|
||||||
</section>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Response = Message(Response)
|
|
||||||
|
|
||||||
|
|
||||||
ErrorView.propTypes = {
|
|
||||||
flow: PropTypes.object.isRequired,
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ErrorView({ flow }) {
|
|
||||||
return (
|
|
||||||
<section className="error">
|
|
||||||
<div className="alert alert-warning">
|
|
||||||
{flow.error.msg}
|
|
||||||
<div>
|
|
||||||
<small>{formatTimeStamp(flow.error.timestamp)}</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
)
|
|
||||||
}
|
|
198
web/src/js/components/FlowView/Messages.tsx
Normal file
198
web/src/js/components/FlowView/Messages.tsx
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
import { RequestUtils, isValidHttpVersion, parseUrl } from '../../flow/utils'
|
||||||
|
import { formatTimeStamp } from '../../utils'
|
||||||
|
import ContentView from '../ContentView'
|
||||||
|
import ContentViewOptions from '../ContentView/ContentViewOptions'
|
||||||
|
import ValidateEditor from '../ValueEditor/ValidateEditor'
|
||||||
|
import ValueEditor from '../ValueEditor/ValueEditor'
|
||||||
|
import HideInStatic from '../common/HideInStatic'
|
||||||
|
|
||||||
|
import Headers from './Headers'
|
||||||
|
import { updateEdit as updateFlow } from '../../ducks/ui/flow'
|
||||||
|
import { uploadContent } from '../../ducks/flows'
|
||||||
|
import ToggleEdit from './ToggleEdit'
|
||||||
|
import { useAppDispatch, useAppSelector } from "../../ducks";
|
||||||
|
import { HTTPFlow, HTTPMessage } from '../../flow'
|
||||||
|
|
||||||
|
|
||||||
|
type RequestLineProps = {
|
||||||
|
flow: HTTPFlow,
|
||||||
|
readonly: boolean,
|
||||||
|
}
|
||||||
|
|
||||||
|
function RequestLine({ flow, readonly }: RequestLineProps) {
|
||||||
|
const dispatch = useAppDispatch()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="first-line request-line">
|
||||||
|
<div>
|
||||||
|
<ValueEditor
|
||||||
|
content={flow.request.method}
|
||||||
|
readonly={readonly}
|
||||||
|
onDone={method => dispatch(updateFlow({ request: { method } }))}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ValidateEditor
|
||||||
|
content={RequestUtils.pretty_url(flow.request)}
|
||||||
|
readonly={readonly}
|
||||||
|
onDone={url => dispatch(updateFlow({ request: {path: '', ...parseUrl(url)}}))}
|
||||||
|
isValid={url => !!parseUrl(url).host}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ValidateEditor
|
||||||
|
content={flow.request.http_version}
|
||||||
|
readonly={readonly}
|
||||||
|
onDone={http_version => dispatch(updateFlow({ request: { http_version } }))}
|
||||||
|
isValid={isValidHttpVersion}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ResponseLineProps = {
|
||||||
|
flow: HTTPFlow,
|
||||||
|
readonly: boolean,
|
||||||
|
}
|
||||||
|
|
||||||
|
function ResponseLine({ flow, readonly }: ResponseLineProps) {
|
||||||
|
const dispatch = useAppDispatch()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="first-line response-line">
|
||||||
|
<ValidateEditor
|
||||||
|
content={flow.response?.http_version}
|
||||||
|
readonly={readonly}
|
||||||
|
onDone={nextVer => dispatch(updateFlow({ response: { http_version: nextVer } }))}
|
||||||
|
isValid={isValidHttpVersion}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ValidateEditor
|
||||||
|
content={flow.response?.status_code + ''}
|
||||||
|
readonly={readonly}
|
||||||
|
onDone={code => dispatch(updateFlow({ response: { code: parseInt(code) } }))}
|
||||||
|
isValid={code => /^\d+$/.test(code)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ValueEditor
|
||||||
|
content={flow.response?.reason}
|
||||||
|
readonly={readonly}
|
||||||
|
onDone={msg => dispatch(updateFlow({ response: { msg } }))}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Request() {
|
||||||
|
const dispatch = useAppDispatch(),
|
||||||
|
flow = useAppSelector(state => state.ui.flow.modifiedFlow || state.flows.byId[state.flows.selected[0]]),
|
||||||
|
isEdit = useAppSelector(state => !!state.ui.flow.modifiedFlow)
|
||||||
|
|
||||||
|
let noContent = !isEdit && (flow.request.contentLength == 0 || flow.request.contentLength == null)
|
||||||
|
return (
|
||||||
|
<section className="request">
|
||||||
|
<article>
|
||||||
|
<ToggleEdit/>
|
||||||
|
<RequestLine
|
||||||
|
flow={flow}
|
||||||
|
readonly={!isEdit} />
|
||||||
|
<Headers
|
||||||
|
message={flow.request}
|
||||||
|
readonly={!isEdit}
|
||||||
|
onChange={headers => dispatch(updateFlow({ request: { headers } }))}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
<ContentView
|
||||||
|
readonly={!isEdit}
|
||||||
|
flow={flow}
|
||||||
|
onContentChange={content => dispatch(updateFlow({ request: {content}}))}
|
||||||
|
message={flow.request}/>
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
<Headers
|
||||||
|
message={flow.request}
|
||||||
|
readonly={!isEdit}
|
||||||
|
onChange={trailers => dispatch(updateFlow({ request: { trailers } }))}
|
||||||
|
type='trailers'
|
||||||
|
/>
|
||||||
|
</article>
|
||||||
|
<HideInStatic>
|
||||||
|
{!noContent &&
|
||||||
|
<footer>
|
||||||
|
<ContentViewOptions
|
||||||
|
flow={flow}
|
||||||
|
message={flow.request}
|
||||||
|
uploadContent={content => dispatch(uploadContent(flow, content, "request"))}/>
|
||||||
|
</footer>
|
||||||
|
}
|
||||||
|
</HideInStatic>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Response() {
|
||||||
|
const dispatch = useAppDispatch(),
|
||||||
|
flow = useAppSelector(state => state.ui.flow.modifiedFlow || state.flows.byId[state.flows.selected[0]]),
|
||||||
|
isEdit = useAppSelector(state => !!state.ui.flow.modifiedFlow)
|
||||||
|
|
||||||
|
let noContent = !isEdit && (flow.response.contentLength == 0 || flow.response.contentLength == null)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="response">
|
||||||
|
<article>
|
||||||
|
<ToggleEdit/>
|
||||||
|
<ResponseLine
|
||||||
|
flow={flow}
|
||||||
|
readonly={!isEdit} />
|
||||||
|
<Headers
|
||||||
|
message={flow.response}
|
||||||
|
readonly={!isEdit}
|
||||||
|
onChange={headers => dispatch(updateFlow({ response: { headers } }))}
|
||||||
|
/>
|
||||||
|
<hr/>
|
||||||
|
<ContentView
|
||||||
|
readonly={!isEdit}
|
||||||
|
flow={flow}
|
||||||
|
onContentChange={content => dispatch(updateFlow({ response: {content}}))}
|
||||||
|
message={flow.response}
|
||||||
|
/>
|
||||||
|
<hr/>
|
||||||
|
<Headers
|
||||||
|
message={flow.response}
|
||||||
|
readonly={!isEdit}
|
||||||
|
onChange={trailers => dispatch(updateFlow({ response: { trailers } }))}
|
||||||
|
type='trailers'
|
||||||
|
/>
|
||||||
|
</article>
|
||||||
|
<HideInStatic>
|
||||||
|
{!noContent &&
|
||||||
|
<footer >
|
||||||
|
<ContentViewOptions
|
||||||
|
flow={flow}
|
||||||
|
message={flow.response}
|
||||||
|
uploadContent={content => dispatch(uploadContent(flow, content, "response"))} />
|
||||||
|
</footer>
|
||||||
|
}
|
||||||
|
</HideInStatic>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ErrorViewProps = {
|
||||||
|
flow: HTTPFlow
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ErrorView({ flow }: ErrorViewProps) {
|
||||||
|
return (
|
||||||
|
<section className="error">
|
||||||
|
<div className="alert alert-warning">
|
||||||
|
{flow.error?.msg}
|
||||||
|
<div>
|
||||||
|
<small>{formatTimeStamp(flow.error?.timestamp)}</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
@ -1,16 +1,14 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import { connect } from 'react-redux'
|
|
||||||
import classnames from 'classnames'
|
import classnames from 'classnames'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
|
|
||||||
NavAction.propTypes = {
|
type NavActionProps = {
|
||||||
icon: PropTypes.string.isRequired,
|
icon: string,
|
||||||
title: PropTypes.string.isRequired,
|
title: string,
|
||||||
onClick: PropTypes.func.isRequired,
|
onClick: (e: any) => void,
|
||||||
}
|
}
|
||||||
|
|
||||||
export function NavAction({ icon, title, onClick }) {
|
export function NavAction({ icon, title, onClick }: NavActionProps) {
|
||||||
return (
|
return (
|
||||||
<a title={title}
|
<a title={title}
|
||||||
href="#"
|
href="#"
|
||||||
@ -24,13 +22,13 @@ export function NavAction({ icon, title, onClick }) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Nav.propTypes = {
|
type NavProps = {
|
||||||
active: PropTypes.string.isRequired,
|
active: string,
|
||||||
tabs: PropTypes.array.isRequired,
|
tabs: string[],
|
||||||
onSelectTab: PropTypes.func.isRequired,
|
onSelectTab: (e: string) => void,
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Nav({ active, tabs, onSelectTab }) {
|
export default function Nav({ active, tabs, onSelectTab }: NavProps) {
|
||||||
return (
|
return (
|
||||||
<nav className="nav-tabs nav-tabs-sm">
|
<nav className="nav-tabs nav-tabs-sm">
|
||||||
{tabs.map(tab => (
|
{tabs.map(tab => (
|
@ -1,40 +1,25 @@
|
|||||||
import React, { Component } from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import {useAppDispatch, useAppSelector} from "../../ducks";
|
||||||
import { connect } from 'react-redux'
|
|
||||||
|
|
||||||
import { startEdit, stopEdit } from '../../ducks/ui/flow'
|
import { startEdit, stopEdit } from '../../ducks/ui/flow'
|
||||||
|
|
||||||
ToggleEdit.propTypes = {
|
|
||||||
isEdit: PropTypes.bool.isRequired,
|
|
||||||
flow: PropTypes.object.isRequired,
|
|
||||||
startEdit: PropTypes.func.isRequired,
|
|
||||||
stopEdit: PropTypes.func.isRequired,
|
|
||||||
}
|
|
||||||
|
|
||||||
function ToggleEdit({ isEdit, startEdit, stopEdit, flow, modifiedFlow }) {
|
export default function ToggleEdit() {
|
||||||
|
const dispatch = useAppDispatch(),
|
||||||
|
isEdit = useAppSelector(state => !!state.ui.flow.modifiedFlow),
|
||||||
|
modifiedFlow = useAppSelector(state => state.ui.flow.modifiedFlow|| state.flows.byId[state.flows.selected[0]]),
|
||||||
|
flow = useAppSelector(state => state.flows.byId[state.flows.selected[0]])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="edit-flow-container">
|
<div className="edit-flow-container">
|
||||||
{isEdit ?
|
{isEdit ?
|
||||||
<a className="edit-flow" title="Finish Edit" onClick={() => stopEdit(flow, modifiedFlow)}>
|
<a className="edit-flow" title="Finish Edit" onClick={() => dispatch(stopEdit(flow, modifiedFlow))}>
|
||||||
<i className="fa fa-check"/>
|
<i className="fa fa-check"/>
|
||||||
</a>
|
</a>
|
||||||
:
|
:
|
||||||
<a className="edit-flow" title="Edit Flow" onClick={() => startEdit(flow)}>
|
<a className="edit-flow" title="Edit Flow" onClick={() => dispatch(startEdit(flow))}>
|
||||||
<i className="fa fa-pencil"/>
|
<i className="fa fa-pencil"/>
|
||||||
</a>
|
</a>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(
|
|
||||||
state => ({
|
|
||||||
isEdit: !!state.ui.flow.modifiedFlow,
|
|
||||||
modifiedFlow: state.ui.flow.modifiedFlow || state.flows.byId[state.flows.selected[0]],
|
|
||||||
flow: state.flows.byId[state.flows.selected[0]]
|
|
||||||
}),
|
|
||||||
{
|
|
||||||
startEdit,
|
|
||||||
stopEdit,
|
|
||||||
}
|
|
||||||
)(ToggleEdit)
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import React, { Component } from 'react'
|
import React, { Component } from 'react'
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import { connect } from 'react-redux'
|
import { connect } from 'react-redux'
|
||||||
import classnames from 'classnames'
|
import classnames from 'classnames'
|
||||||
import MainMenu from './Header/MainMenu'
|
import MainMenu from './Header/MainMenu'
|
||||||
|
@ -1,13 +1,20 @@
|
|||||||
import React, { Component } from 'react'
|
import React, { Component } from 'react'
|
||||||
import { fetchApi } from "../../utils";
|
import { fetchApi } from "../../utils";
|
||||||
|
|
||||||
|
type FilterDocsProps = {
|
||||||
|
selectHandler: (cmd: string) => void,
|
||||||
|
}
|
||||||
|
|
||||||
export default class FilterDocs extends Component {
|
type FilterDocsStates = {
|
||||||
|
doc: {commands: string[][]}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class FilterDocs extends Component<FilterDocsProps, FilterDocsStates> {
|
||||||
|
|
||||||
// @todo move to redux
|
// @todo move to redux
|
||||||
|
|
||||||
static xhr = null
|
static xhr: Promise<any>
|
||||||
static doc = null
|
static doc: {commands: string[][]}
|
||||||
|
|
||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
super(props, context)
|
super(props, context)
|
||||||
@ -18,7 +25,7 @@ export default class FilterDocs extends Component {
|
|||||||
if (!FilterDocs.xhr) {
|
if (!FilterDocs.xhr) {
|
||||||
FilterDocs.xhr = fetchApi('/filter-help').then(response => response.json())
|
FilterDocs.xhr = fetchApi('/filter-help').then(response => response.json())
|
||||||
FilterDocs.xhr.catch(() => {
|
FilterDocs.xhr.catch(() => {
|
||||||
FilterDocs.xhr = null
|
FilterDocs.xhr = Promise.resolve()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (!this.state.doc) {
|
if (!this.state.doc) {
|
||||||
@ -43,7 +50,7 @@ export default class FilterDocs extends Component {
|
|||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
<tr key="docs-link">
|
<tr key="docs-link">
|
||||||
<td colSpan="2">
|
<td colSpan={2}>
|
||||||
<a href="https://mitmproxy.org/docs/latest/concepts-filters/"
|
<a href="https://mitmproxy.org/docs/latest/concepts-filters/"
|
||||||
target="_blank">
|
target="_blank">
|
||||||
<i className="fa fa-external-link"/>
|
<i className="fa fa-external-link"/>
|
@ -1,24 +1,23 @@
|
|||||||
import React from "react"
|
import React from "react"
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import { connect } from "react-redux"
|
|
||||||
import Button from "../common/Button"
|
import Button from "../common/Button"
|
||||||
import { MessageUtils } from "../../flow/utils.js"
|
import { MessageUtils } from "../../flow/utils.js"
|
||||||
import * as flowsActions from "../../ducks/flows"
|
|
||||||
import HideInStatic from "../common/HideInStatic";
|
import HideInStatic from "../common/HideInStatic";
|
||||||
|
import { useAppDispatch, useAppSelector } from "../../ducks";
|
||||||
|
import {
|
||||||
|
resume as resumeFlow,
|
||||||
|
replay as replayFlow,
|
||||||
|
duplicate as duplicateFlow,
|
||||||
|
revert as revertFlow,
|
||||||
|
remove as removeFlow,
|
||||||
|
kill as killFlow
|
||||||
|
} from "../../ducks/flows"
|
||||||
|
|
||||||
FlowMenu.title = 'Flow'
|
FlowMenu.title = 'Flow'
|
||||||
|
|
||||||
FlowMenu.propTypes = {
|
export default function FlowMenu() {
|
||||||
flow: PropTypes.object,
|
const dispatch = useAppDispatch(),
|
||||||
resumeFlow: PropTypes.func.isRequired,
|
flow = useAppSelector(state => state.flows.byId[state.flows.selected[0]])
|
||||||
killFlow: PropTypes.func.isRequired,
|
|
||||||
replayFlow: PropTypes.func.isRequired,
|
|
||||||
duplicateFlow: PropTypes.func.isRequired,
|
|
||||||
removeFlow: PropTypes.func.isRequired,
|
|
||||||
revertFlow: PropTypes.func.isRequired
|
|
||||||
}
|
|
||||||
|
|
||||||
export function FlowMenu({ flow, resumeFlow, killFlow, replayFlow, duplicateFlow, removeFlow, revertFlow }) {
|
|
||||||
if (!flow)
|
if (!flow)
|
||||||
return <div/>
|
return <div/>
|
||||||
return (
|
return (
|
||||||
@ -27,19 +26,19 @@ export function FlowMenu({ flow, resumeFlow, killFlow, replayFlow, duplicateFlow
|
|||||||
<div className="menu-group">
|
<div className="menu-group">
|
||||||
<div className="menu-content">
|
<div className="menu-content">
|
||||||
<Button title="[r]eplay flow" icon="fa-repeat text-primary"
|
<Button title="[r]eplay flow" icon="fa-repeat text-primary"
|
||||||
onClick={() => replayFlow(flow)}>
|
onClick={() => dispatch(replayFlow(flow))}>
|
||||||
Replay
|
Replay
|
||||||
</Button>
|
</Button>
|
||||||
<Button title="[D]uplicate flow" icon="fa-copy text-info"
|
<Button title="[D]uplicate flow" icon="fa-copy text-info"
|
||||||
onClick={() => duplicateFlow(flow)}>
|
onClick={() => dispatch(duplicateFlow(flow))}>
|
||||||
Duplicate
|
Duplicate
|
||||||
</Button>
|
</Button>
|
||||||
<Button disabled={!flow || !flow.modified} title="revert changes to flow [V]"
|
<Button disabled={!flow || !flow.modified} title="revert changes to flow [V]"
|
||||||
icon="fa-history text-warning" onClick={() => revertFlow(flow)}>
|
icon="fa-history text-warning" onClick={() => dispatch(revertFlow(flow))}>
|
||||||
Revert
|
Revert
|
||||||
</Button>
|
</Button>
|
||||||
<Button title="[d]elete flow" icon="fa-trash text-danger"
|
<Button title="[d]elete flow" icon="fa-trash text-danger"
|
||||||
onClick={() => removeFlow(flow)}>
|
onClick={() => dispatch(removeFlow(flow))}>
|
||||||
Delete
|
Delete
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@ -61,11 +60,11 @@ export function FlowMenu({ flow, resumeFlow, killFlow, replayFlow, duplicateFlow
|
|||||||
<div className="menu-group">
|
<div className="menu-group">
|
||||||
<div className="menu-content">
|
<div className="menu-content">
|
||||||
<Button disabled={!flow || !flow.intercepted} title="[a]ccept intercepted flow"
|
<Button disabled={!flow || !flow.intercepted} title="[a]ccept intercepted flow"
|
||||||
icon="fa-play text-success" onClick={() => resumeFlow(flow)}>
|
icon="fa-play text-success" onClick={() => dispatch(resumeFlow(flow))}>
|
||||||
Resume
|
Resume
|
||||||
</Button>
|
</Button>
|
||||||
<Button disabled={!flow || !flow.intercepted} title="kill intercepted flow [x]"
|
<Button disabled={!flow || !flow.intercepted} title="kill intercepted flow [x]"
|
||||||
icon="fa-times text-danger" onClick={() => killFlow(flow)}>
|
icon="fa-times text-danger" onClick={() => dispatch(killFlow(flow))}>
|
||||||
Abort
|
Abort
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@ -75,17 +74,3 @@ export function FlowMenu({ flow, resumeFlow, killFlow, replayFlow, duplicateFlow
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(
|
|
||||||
state => ({
|
|
||||||
flow: state.flows.byId[state.flows.selected[0]],
|
|
||||||
}),
|
|
||||||
{
|
|
||||||
resumeFlow: flowsActions.resume,
|
|
||||||
killFlow: flowsActions.kill,
|
|
||||||
replayFlow: flowsActions.replay,
|
|
||||||
duplicateFlow: flowsActions.duplicate,
|
|
||||||
removeFlow: flowsActions.remove,
|
|
||||||
revertFlow: flowsActions.revert,
|
|
||||||
}
|
|
||||||
)(FlowMenu)
|
|
@ -1,21 +1,24 @@
|
|||||||
import React from "react"
|
import React from "react"
|
||||||
import { connect } from "react-redux"
|
|
||||||
import { EventlogToggle, OptionsToggle } from "./MenuToggle"
|
import { EventlogToggle, OptionsToggle } from "./MenuToggle"
|
||||||
import Button from "../common/Button"
|
import Button from "../common/Button"
|
||||||
import DocsLink from "../common/DocsLink"
|
import DocsLink from "../common/DocsLink"
|
||||||
import HideInStatic from "../common/HideInStatic";
|
import HideInStatic from "../common/HideInStatic";
|
||||||
import * as modalActions from "../../ducks/ui/modal"
|
import * as modalActions from "../../ducks/ui/modal"
|
||||||
|
import { useAppDispatch } from "../../ducks";
|
||||||
|
|
||||||
OptionMenu.title = 'Options'
|
OptionMenu.title = 'Options'
|
||||||
|
|
||||||
function OptionMenu({ openOptions }) {
|
export default function OptionMenu() {
|
||||||
|
const dispatch = useAppDispatch()
|
||||||
|
const openOptions = () => modalActions.setActiveModal('OptionModal')
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<HideInStatic>
|
<HideInStatic>
|
||||||
<div className="menu-group">
|
<div className="menu-group">
|
||||||
<div className="menu-content">
|
<div className="menu-content">
|
||||||
<Button title="Open Options" icon="fa-cogs text-primary"
|
<Button title="Open Options" icon="fa-cogs text-primary"
|
||||||
onClick={openOptions}>
|
onClick={() => dispatch(openOptions())}>
|
||||||
Edit Options <sup>alpha</sup>
|
Edit Options <sup>alpha</sup>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@ -47,10 +50,3 @@ function OptionMenu({ openOptions }) {
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(
|
|
||||||
null,
|
|
||||||
{
|
|
||||||
openOptions: () => modalActions.setActiveModal('OptionModal')
|
|
||||||
}
|
|
||||||
)(OptionMenu)
|
|
@ -1,15 +1,11 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import { connect } from 'react-redux'
|
|
||||||
import Splitter from './common/Splitter'
|
import Splitter from './common/Splitter'
|
||||||
import FlowTable from './FlowTable'
|
import FlowTable from './FlowTable'
|
||||||
import FlowView from './FlowView'
|
import FlowView from './FlowView'
|
||||||
|
import {useAppSelector} from "../ducks";
|
||||||
|
|
||||||
MainView.propTypes = {
|
export default function MainView() {
|
||||||
hasSelection: PropTypes.bool.isRequired,
|
const hasSelection = useAppSelector(state => !!state.flows.byId[state.flows.selected[0]])
|
||||||
}
|
|
||||||
|
|
||||||
function MainView({ hasSelection }) {
|
|
||||||
return (
|
return (
|
||||||
<div className="main-view">
|
<div className="main-view">
|
||||||
<FlowTable/>
|
<FlowTable/>
|
||||||
@ -18,10 +14,3 @@ function MainView({ hasSelection }) {
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(
|
|
||||||
state => ({
|
|
||||||
hasSelection: !!state.flows.byId[state.flows.selected[0]]
|
|
||||||
}),
|
|
||||||
{}
|
|
||||||
)(MainView)
|
|
@ -1,24 +1,13 @@
|
|||||||
import React, { Component } from 'react'
|
import React from 'react'
|
||||||
import { connect } from 'react-redux'
|
|
||||||
import ModalList from './ModalList'
|
import ModalList from './ModalList'
|
||||||
|
import { useAppSelector } from "../../ducks";
|
||||||
|
|
||||||
class PureModal extends Component {
|
|
||||||
|
|
||||||
constructor(props, context) {
|
export default function PureModal() {
|
||||||
super(props, context)
|
const activeModal = useAppSelector(state => state.ui.modal.activeModal)
|
||||||
}
|
const ActiveModal = ModalList.find(m => m.name === activeModal )
|
||||||
|
|
||||||
render() {
|
return(
|
||||||
const { activeModal } = this.props
|
activeModal ? <ActiveModal/> : <div/>
|
||||||
const ActiveModal = ModalList.find(m => m.name === activeModal )
|
)
|
||||||
return(
|
|
||||||
activeModal ? <ActiveModal/> : <div/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(
|
|
||||||
state => ({
|
|
||||||
activeModal: state.ui.modal.activeModal
|
|
||||||
})
|
|
||||||
)(PureModal)
|
|
||||||
|
@ -1,47 +0,0 @@
|
|||||||
import React, { Component } from 'react'
|
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import { connect } from 'react-redux'
|
|
||||||
|
|
||||||
import { onKeyDown } from '../ducks/ui/keyboard'
|
|
||||||
import MainView from './MainView'
|
|
||||||
import Header from './Header'
|
|
||||||
import CommandBar from './CommandBar'
|
|
||||||
import EventLog from './EventLog'
|
|
||||||
import Footer from './Footer'
|
|
||||||
import Modal from './Modal/Modal'
|
|
||||||
|
|
||||||
class ProxyAppMain extends Component {
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
window.addEventListener('keydown', this.props.onKeyDown);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
window.removeEventListener('keydown', this.props.onKeyDown);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { showEventLog } = this.props
|
|
||||||
return (
|
|
||||||
<div id="container" tabIndex="0">
|
|
||||||
<Header/>
|
|
||||||
<MainView />
|
|
||||||
<CommandBar />
|
|
||||||
{showEventLog && (
|
|
||||||
<EventLog key="eventlog"/>
|
|
||||||
)}
|
|
||||||
<Footer />
|
|
||||||
<Modal/>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(
|
|
||||||
state => ({
|
|
||||||
showEventLog: state.eventLog.visible,
|
|
||||||
}),
|
|
||||||
{
|
|
||||||
onKeyDown,
|
|
||||||
}
|
|
||||||
)(ProxyAppMain)
|
|
35
web/src/js/components/ProxyApp.tsx
Normal file
35
web/src/js/components/ProxyApp.tsx
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import React, { useEffect } from 'react'
|
||||||
|
|
||||||
|
import { onKeyDown } from '../ducks/ui/keyboard'
|
||||||
|
import MainView from './MainView'
|
||||||
|
import Header from './Header'
|
||||||
|
import CommandBar from './CommandBar'
|
||||||
|
import EventLog from './EventLog'
|
||||||
|
import Footer from './Footer'
|
||||||
|
import Modal from './Modal/Modal'
|
||||||
|
import {useAppDispatch, useAppSelector} from "../ducks";
|
||||||
|
|
||||||
|
export default function ProxyAppMain() {
|
||||||
|
const dispatch = useAppDispatch(),
|
||||||
|
showEventLog = useAppSelector(state => state.eventLog.visible)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
window.addEventListener('keydown', (e) => dispatch(onKeyDown(e)));
|
||||||
|
return function cleanup() {
|
||||||
|
window.removeEventListener('keydown', (e) => dispatch(onKeyDown(e)));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div id="container" tabIndex={0}>
|
||||||
|
<Header/>
|
||||||
|
<MainView />
|
||||||
|
<CommandBar />
|
||||||
|
{showEventLog && (
|
||||||
|
<EventLog key="eventlog"/>
|
||||||
|
)}
|
||||||
|
<Footer />
|
||||||
|
<Modal/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
21
web/src/js/components/ValueEditor/ValidateEditor.jsx → web/src/js/components/ValueEditor/ValidateEditor.tsx
Executable file → Normal file
21
web/src/js/components/ValueEditor/ValidateEditor.jsx → web/src/js/components/ValueEditor/ValidateEditor.tsx
Executable file → Normal file
@ -1,19 +1,20 @@
|
|||||||
import React, { Component } from 'react'
|
import React, { Component } from 'react'
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import ValueEditor from './ValueEditor'
|
import ValueEditor from './ValueEditor'
|
||||||
import classnames from 'classnames'
|
import classnames from 'classnames'
|
||||||
|
|
||||||
|
type ValidateEditorProps = {
|
||||||
|
content: string | undefined,
|
||||||
|
readonly: boolean,
|
||||||
|
onDone: (content: string) => void,
|
||||||
|
className?: string,
|
||||||
|
isValid: (content: string) => boolean,
|
||||||
|
}
|
||||||
|
|
||||||
export default class ValidateEditor extends Component {
|
type ValidateEditorStates = {
|
||||||
|
valid: boolean,
|
||||||
static propTypes = {
|
}
|
||||||
content: PropTypes.string.isRequired,
|
|
||||||
readonly: PropTypes.bool,
|
|
||||||
onDone: PropTypes.func.isRequired,
|
|
||||||
className: PropTypes.string,
|
|
||||||
isValid: PropTypes.func.isRequired,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
export default class ValidateEditor extends Component<ValidateEditorProps, ValidateEditorStates> {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
this.state = { valid: props.isValid(props.content) }
|
this.state = { valid: props.isValid(props.content) }
|
@ -1,7 +1,7 @@
|
|||||||
import React from "react"
|
import React from "react"
|
||||||
|
|
||||||
type DocLinkProps = {
|
type DocLinkProps = {
|
||||||
children: React.ReactNode,
|
children?: React.ReactNode,
|
||||||
resource: string
|
resource: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,5 +5,5 @@ type HideInStaticProps = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function HideInStatic({ children }: HideInStaticProps) {
|
export default function HideInStatic({ children }: HideInStaticProps) {
|
||||||
return (window.MITMWEB_CONF && window.MITMWEB_CONF.static) ? null : [children]
|
return (window.MITMWEB_CONF && window.MITMWEB_CONF.static) ? null : <>{[children]}</>
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ import React from 'react'
|
|||||||
|
|
||||||
type ToggleButtonProps = {
|
type ToggleButtonProps = {
|
||||||
checked: boolean,
|
checked: boolean,
|
||||||
onToggle: () => void,
|
onToggle: () => any,
|
||||||
text: string
|
text: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ import { Key } from '../../utils'
|
|||||||
type ToggleInputButtonProps = {
|
type ToggleInputButtonProps = {
|
||||||
name: string,
|
name: string,
|
||||||
txt: string,
|
txt: string,
|
||||||
onToggleChanged: (string) => void,
|
onToggleChanged: Function,
|
||||||
checked: boolean,
|
checked: boolean,
|
||||||
placeholder: string,
|
placeholder: string,
|
||||||
inputType: string,
|
inputType: string,
|
||||||
|
@ -67,6 +67,7 @@ export interface HTTPMessage {
|
|||||||
trailers?: Headers
|
trailers?: Headers
|
||||||
contentLength: number
|
contentLength: number
|
||||||
contentHash: string
|
contentHash: string
|
||||||
|
content?: string
|
||||||
timestamp_start: number
|
timestamp_start: number
|
||||||
timestamp_end?: number
|
timestamp_end?: number
|
||||||
}
|
}
|
||||||
|
@ -59,7 +59,7 @@ export var formatTimeDelta = function (milliseconds) {
|
|||||||
|
|
||||||
export var formatTimeStamp = function (seconds, utc_to_local=true) {
|
export var formatTimeStamp = function (seconds, utc_to_local=true) {
|
||||||
var utc = new Date(seconds * 1000);
|
var utc = new Date(seconds * 1000);
|
||||||
if (utc_to_local && !process.env.JEST_WORKER_ID) {
|
if (utc_to_local) {
|
||||||
var local = utc.getTime() - utc.getTimezoneOffset() * 60 * 1000;
|
var local = utc.getTime() - utc.getTimezoneOffset() * 60 * 1000;
|
||||||
var ts = new Date(local).toISOString();
|
var ts = new Date(local).toISOString();
|
||||||
} else {
|
} else {
|
||||||
|
Loading…
Reference in New Issue
Block a user