mirror of
https://github.com/Grasscutters/mitmproxy.git
synced 2024-11-21 22:58:24 +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 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 { MessageUtils } from "../../../flow/utils"
|
||||
import { Provider } from 'react-redux'
|
||||
|
||||
describe('FlowMenu Component', () => {
|
||||
let actions = {
|
||||
resumeFlow: jest.fn(),
|
||||
killFlow: jest.fn(),
|
||||
replayFlow: jest.fn(),
|
||||
duplicateFlow: jest.fn(),
|
||||
removeFlow: jest.fn(),
|
||||
revertFlow: jest.fn()
|
||||
},
|
||||
tflow = new TFlow()
|
||||
let tflow = new TFlow(),
|
||||
store = new TStore()
|
||||
tflow.modified = true
|
||||
tflow.intercepted = true
|
||||
global.fetch = jest.fn()
|
||||
|
||||
it('should render correctly without flow', () => {
|
||||
let flowMenu = renderer.create(
|
||||
<FlowMenu removeFlow={actions.removeFlow}
|
||||
killFlow={actions.killFlow}
|
||||
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}/>),
|
||||
let flowMenu = renderer.create(
|
||||
<Provider store={store}>
|
||||
<FlowMenu />
|
||||
</Provider>
|
||||
),
|
||||
tree = flowMenu.toJSON()
|
||||
|
||||
it('should render correctly with flow', () => {
|
||||
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]
|
||||
it('should handle download', () => {
|
||||
let button = menu_content_2.children[0]
|
||||
@ -78,24 +32,4 @@ describe('FlowMenu Component', () => {
|
||||
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
|
||||
|
||||
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`] = `
|
||||
<div
|
||||
className="flow-menu"
|
||||
@ -234,5 +118,3 @@ exports[`FlowMenu Component should render correctly with flow 1`] = `
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`FlowMenu Component should render correctly without flow 1`] = `<div />`;
|
||||
|
@ -21,6 +21,8 @@ export {TFlow}
|
||||
|
||||
const tflow1: HTTPFlow = TFlow();
|
||||
const tflow2: HTTPFlow = TFlow();
|
||||
tflow1.modified = true
|
||||
tflow1.intercepted = true
|
||||
tflow2.id = "flow2";
|
||||
tflow2.request.path = "/second";
|
||||
|
||||
|
@ -1,14 +1,13 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import CodeMirror from "../../contrib/CodeMirror"
|
||||
|
||||
|
||||
CodeEditor.propTypes = {
|
||||
content: PropTypes.string.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
type CodeEditorProps = {
|
||||
content: string,
|
||||
onChange: Function,
|
||||
}
|
||||
|
||||
export default function CodeEditor ( { content, onChange} ){
|
||||
export default function CodeEditor ( { content, onChange}: CodeEditorProps ){
|
||||
|
||||
let options = {
|
||||
lineNumbers: true
|
||||
|
@ -1,14 +1,12 @@
|
||||
import React, { Component } from 'react'
|
||||
import React from 'react'
|
||||
import { MessageUtils } from '../../flow/utils'
|
||||
import { Flow, HTTPMessage } from '../../flow'
|
||||
|
||||
type ContentLoaderProps = {
|
||||
content: string,
|
||||
contentView: object,
|
||||
flow: object,
|
||||
message: {
|
||||
content: string,
|
||||
contentHash: string,
|
||||
},
|
||||
flow: Flow,
|
||||
message: HTTPMessage,
|
||||
}
|
||||
|
||||
type ContentLoaderStates = {
|
||||
|
@ -3,11 +3,12 @@ import ViewSelector from './ViewSelector'
|
||||
import UploadContentButton from './UploadContentButton'
|
||||
import DownloadContentButton from './DownloadContentButton'
|
||||
import { useAppSelector } from "../../ducks";
|
||||
import { Flow, HTTPMessage } from '../../flow'
|
||||
|
||||
type ContentViewOptionsProps = {
|
||||
flow: object,
|
||||
message: object,
|
||||
uploadContent: () => void,
|
||||
flow: Flow,
|
||||
message: HTTPMessage,
|
||||
uploadContent: (content: string) => Promise<Response>,
|
||||
}
|
||||
|
||||
export default function ContentViewOptions({ flow, message, uploadContent }: ContentViewOptionsProps) {
|
||||
|
@ -24,7 +24,7 @@ function ViewImage({ flow, message }: ViewImageProps) {
|
||||
|
||||
type EditProps = {
|
||||
content: string,
|
||||
onChange: () => void,
|
||||
onChange: (content: string) => any,
|
||||
}
|
||||
|
||||
function PureEdit({ content, onChange }: EditProps) {
|
||||
@ -39,7 +39,7 @@ type PureViewServerProps = {
|
||||
}
|
||||
|
||||
type PureViewServerStates = {
|
||||
lines: string[][],
|
||||
lines: [style: string, text: string][][],
|
||||
description: string,
|
||||
}
|
||||
|
||||
|
@ -1,9 +1,10 @@
|
||||
import React from 'react'
|
||||
import { MessageUtils } from "../../flow/utils"
|
||||
import { Flow, HTTPMessage } from '../../flow'
|
||||
|
||||
type DownloadContentButtonProps = {
|
||||
flow: object,
|
||||
message: object,
|
||||
flow: Flow,
|
||||
message: HTTPMessage,
|
||||
}
|
||||
|
||||
export default function DownloadContentButton({ flow, message }: DownloadContentButtonProps) {
|
||||
|
@ -2,10 +2,11 @@ import React from 'react'
|
||||
import { formatSize } from '../../utils'
|
||||
import UploadContentButton from './UploadContentButton'
|
||||
import DownloadContentButton from './DownloadContentButton'
|
||||
import { HTTPFlow, HTTPMessage } from '../../flow'
|
||||
|
||||
interface ContentProps {
|
||||
flow: { request: object },
|
||||
message: { contentLength: number },
|
||||
flow: HTTPFlow,
|
||||
message: HTTPMessage,
|
||||
}
|
||||
|
||||
interface ContentTooLargeProps extends ContentProps {
|
||||
|
@ -10,13 +10,14 @@ export default function ShowFullContentButton() {
|
||||
contentLines = useAppSelector(state => state.ui.flow.content.length)
|
||||
|
||||
return (
|
||||
!showFullContent &&
|
||||
!showFullContent ? (
|
||||
<div>
|
||||
<Button className="view-all-content-btn btn-xs" onClick={() => dispatch(setShowFullContent())}>
|
||||
Show full content
|
||||
</Button>
|
||||
<span className="pull-right"> {visibleLines}/{contentLines} are visible </span>
|
||||
</div>
|
||||
) : null
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,7 @@ import React from 'react'
|
||||
import FileChooser from '../common/FileChooser'
|
||||
|
||||
type UploadContentButtonProps = {
|
||||
uploadContent: () => any,
|
||||
uploadContent: (content: string) => Promise<Response>,
|
||||
}
|
||||
|
||||
export default function UploadContentButton({ uploadContent }: UploadContentButtonProps) {
|
||||
|
@ -1,5 +1,4 @@
|
||||
import React, { Component } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import React from 'react'
|
||||
import _ from 'lodash'
|
||||
|
||||
import Nav from './FlowView/Nav'
|
||||
@ -7,10 +6,15 @@ import { ErrorView as Error, Request, Response } from './FlowView/Messages'
|
||||
import Details from './FlowView/Details'
|
||||
|
||||
import { selectTab } from '../ducks/ui/flow'
|
||||
import {useAppDispatch, useAppSelector} from "../ducks";
|
||||
|
||||
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
|
||||
const tabs = ['request', 'response', 'error'].filter(k => flow[k])
|
||||
@ -33,19 +37,9 @@ function FlowView({ flow, tabName, selectTab }) {
|
||||
<Nav
|
||||
tabs={tabs}
|
||||
active={tabName}
|
||||
onSelectTab={selectTab}
|
||||
onSelectTab={(tab: string) => dispatch(selectTab(tab))}
|
||||
/>
|
||||
<Tab flow={flow}/>
|
||||
</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 {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 ? (
|
||||
<tr>
|
||||
<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 (
|
||||
<table className="connection-table">
|
||||
<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 PropTypes from 'prop-types'
|
||||
import { connect } from 'react-redux'
|
||||
import classnames from 'classnames'
|
||||
import _ from 'lodash'
|
||||
|
||||
NavAction.propTypes = {
|
||||
icon: PropTypes.string.isRequired,
|
||||
title: PropTypes.string.isRequired,
|
||||
onClick: PropTypes.func.isRequired,
|
||||
type NavActionProps = {
|
||||
icon: string,
|
||||
title: string,
|
||||
onClick: (e: any) => void,
|
||||
}
|
||||
|
||||
export function NavAction({ icon, title, onClick }) {
|
||||
export function NavAction({ icon, title, onClick }: NavActionProps) {
|
||||
return (
|
||||
<a title={title}
|
||||
href="#"
|
||||
@ -24,13 +22,13 @@ export function NavAction({ icon, title, onClick }) {
|
||||
)
|
||||
}
|
||||
|
||||
Nav.propTypes = {
|
||||
active: PropTypes.string.isRequired,
|
||||
tabs: PropTypes.array.isRequired,
|
||||
onSelectTab: PropTypes.func.isRequired,
|
||||
type NavProps = {
|
||||
active: string,
|
||||
tabs: string[],
|
||||
onSelectTab: (e: string) => void,
|
||||
}
|
||||
|
||||
export default function Nav({ active, tabs, onSelectTab }) {
|
||||
export default function Nav({ active, tabs, onSelectTab }: NavProps) {
|
||||
return (
|
||||
<nav className="nav-tabs nav-tabs-sm">
|
||||
{tabs.map(tab => (
|
@ -1,40 +1,25 @@
|
||||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { connect } from 'react-redux'
|
||||
|
||||
import React from 'react'
|
||||
import {useAppDispatch, useAppSelector} from "../../ducks";
|
||||
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 (
|
||||
<div className="edit-flow-container">
|
||||
{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"/>
|
||||
</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"/>
|
||||
</a>
|
||||
}
|
||||
</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 PropTypes from 'prop-types'
|
||||
import { connect } from 'react-redux'
|
||||
import classnames from 'classnames'
|
||||
import MainMenu from './Header/MainMenu'
|
||||
|
@ -1,13 +1,20 @@
|
||||
import React, { Component } from 'react'
|
||||
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
|
||||
|
||||
static xhr = null
|
||||
static doc = null
|
||||
static xhr: Promise<any>
|
||||
static doc: {commands: string[][]}
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context)
|
||||
@ -18,7 +25,7 @@ export default class FilterDocs extends Component {
|
||||
if (!FilterDocs.xhr) {
|
||||
FilterDocs.xhr = fetchApi('/filter-help').then(response => response.json())
|
||||
FilterDocs.xhr.catch(() => {
|
||||
FilterDocs.xhr = null
|
||||
FilterDocs.xhr = Promise.resolve()
|
||||
})
|
||||
}
|
||||
if (!this.state.doc) {
|
||||
@ -43,7 +50,7 @@ export default class FilterDocs extends Component {
|
||||
</tr>
|
||||
))}
|
||||
<tr key="docs-link">
|
||||
<td colSpan="2">
|
||||
<td colSpan={2}>
|
||||
<a href="https://mitmproxy.org/docs/latest/concepts-filters/"
|
||||
target="_blank">
|
||||
<i className="fa fa-external-link"/>
|
@ -1,24 +1,23 @@
|
||||
import React from "react"
|
||||
import PropTypes from 'prop-types'
|
||||
import { connect } from "react-redux"
|
||||
import Button from "../common/Button"
|
||||
import { MessageUtils } from "../../flow/utils.js"
|
||||
import * as flowsActions from "../../ducks/flows"
|
||||
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.propTypes = {
|
||||
flow: PropTypes.object,
|
||||
resumeFlow: PropTypes.func.isRequired,
|
||||
killFlow: PropTypes.func.isRequired,
|
||||
replayFlow: PropTypes.func.isRequired,
|
||||
duplicateFlow: PropTypes.func.isRequired,
|
||||
removeFlow: PropTypes.func.isRequired,
|
||||
revertFlow: PropTypes.func.isRequired
|
||||
}
|
||||
export default function FlowMenu() {
|
||||
const dispatch = useAppDispatch(),
|
||||
flow = useAppSelector(state => state.flows.byId[state.flows.selected[0]])
|
||||
|
||||
export function FlowMenu({ flow, resumeFlow, killFlow, replayFlow, duplicateFlow, removeFlow, revertFlow }) {
|
||||
if (!flow)
|
||||
return <div/>
|
||||
return (
|
||||
@ -27,19 +26,19 @@ export function FlowMenu({ flow, resumeFlow, killFlow, replayFlow, duplicateFlow
|
||||
<div className="menu-group">
|
||||
<div className="menu-content">
|
||||
<Button title="[r]eplay flow" icon="fa-repeat text-primary"
|
||||
onClick={() => replayFlow(flow)}>
|
||||
onClick={() => dispatch(replayFlow(flow))}>
|
||||
Replay
|
||||
</Button>
|
||||
<Button title="[D]uplicate flow" icon="fa-copy text-info"
|
||||
onClick={() => duplicateFlow(flow)}>
|
||||
onClick={() => dispatch(duplicateFlow(flow))}>
|
||||
Duplicate
|
||||
</Button>
|
||||
<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
|
||||
</Button>
|
||||
<Button title="[d]elete flow" icon="fa-trash text-danger"
|
||||
onClick={() => removeFlow(flow)}>
|
||||
onClick={() => dispatch(removeFlow(flow))}>
|
||||
Delete
|
||||
</Button>
|
||||
</div>
|
||||
@ -61,11 +60,11 @@ export function FlowMenu({ flow, resumeFlow, killFlow, replayFlow, duplicateFlow
|
||||
<div className="menu-group">
|
||||
<div className="menu-content">
|
||||
<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
|
||||
</Button>
|
||||
<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
|
||||
</Button>
|
||||
</div>
|
||||
@ -75,17 +74,3 @@ export function FlowMenu({ flow, resumeFlow, killFlow, replayFlow, duplicateFlow
|
||||
</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 { connect } from "react-redux"
|
||||
import { EventlogToggle, OptionsToggle } from "./MenuToggle"
|
||||
import Button from "../common/Button"
|
||||
import DocsLink from "../common/DocsLink"
|
||||
import HideInStatic from "../common/HideInStatic";
|
||||
import * as modalActions from "../../ducks/ui/modal"
|
||||
import { useAppDispatch } from "../../ducks";
|
||||
|
||||
OptionMenu.title = 'Options'
|
||||
|
||||
function OptionMenu({ openOptions }) {
|
||||
export default function OptionMenu() {
|
||||
const dispatch = useAppDispatch()
|
||||
const openOptions = () => modalActions.setActiveModal('OptionModal')
|
||||
|
||||
return (
|
||||
<div>
|
||||
<HideInStatic>
|
||||
<div className="menu-group">
|
||||
<div className="menu-content">
|
||||
<Button title="Open Options" icon="fa-cogs text-primary"
|
||||
onClick={openOptions}>
|
||||
onClick={() => dispatch(openOptions())}>
|
||||
Edit Options <sup>alpha</sup>
|
||||
</Button>
|
||||
</div>
|
||||
@ -47,10 +50,3 @@ function OptionMenu({ openOptions }) {
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default connect(
|
||||
null,
|
||||
{
|
||||
openOptions: () => modalActions.setActiveModal('OptionModal')
|
||||
}
|
||||
)(OptionMenu)
|
@ -1,15 +1,11 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { connect } from 'react-redux'
|
||||
import Splitter from './common/Splitter'
|
||||
import FlowTable from './FlowTable'
|
||||
import FlowView from './FlowView'
|
||||
import {useAppSelector} from "../ducks";
|
||||
|
||||
MainView.propTypes = {
|
||||
hasSelection: PropTypes.bool.isRequired,
|
||||
}
|
||||
|
||||
function MainView({ hasSelection }) {
|
||||
export default function MainView() {
|
||||
const hasSelection = useAppSelector(state => !!state.flows.byId[state.flows.selected[0]])
|
||||
return (
|
||||
<div className="main-view">
|
||||
<FlowTable/>
|
||||
@ -18,10 +14,3 @@ function MainView({ hasSelection }) {
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default connect(
|
||||
state => ({
|
||||
hasSelection: !!state.flows.byId[state.flows.selected[0]]
|
||||
}),
|
||||
{}
|
||||
)(MainView)
|
@ -1,24 +1,13 @@
|
||||
import React, { Component } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import React from 'react'
|
||||
import ModalList from './ModalList'
|
||||
import { useAppSelector } from "../../ducks";
|
||||
|
||||
class PureModal extends Component {
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context)
|
||||
}
|
||||
export default function PureModal() {
|
||||
const activeModal = useAppSelector(state => state.ui.modal.activeModal)
|
||||
const ActiveModal = ModalList.find(m => m.name === activeModal )
|
||||
|
||||
render() {
|
||||
const { activeModal } = this.props
|
||||
const ActiveModal = ModalList.find(m => m.name === activeModal )
|
||||
return(
|
||||
activeModal ? <ActiveModal/> : <div/>
|
||||
)
|
||||
}
|
||||
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 PropTypes from 'prop-types'
|
||||
import ValueEditor from './ValueEditor'
|
||||
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 {
|
||||
|
||||
static propTypes = {
|
||||
content: PropTypes.string.isRequired,
|
||||
readonly: PropTypes.bool,
|
||||
onDone: PropTypes.func.isRequired,
|
||||
className: PropTypes.string,
|
||||
isValid: PropTypes.func.isRequired,
|
||||
}
|
||||
type ValidateEditorStates = {
|
||||
valid: boolean,
|
||||
}
|
||||
|
||||
export default class ValidateEditor extends Component<ValidateEditorProps, ValidateEditorStates> {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = { valid: props.isValid(props.content) }
|
@ -1,7 +1,7 @@
|
||||
import React from "react"
|
||||
|
||||
type DocLinkProps = {
|
||||
children: React.ReactNode,
|
||||
children?: React.ReactNode,
|
||||
resource: string
|
||||
}
|
||||
|
||||
|
@ -5,5 +5,5 @@ type 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 = {
|
||||
checked: boolean,
|
||||
onToggle: () => void,
|
||||
onToggle: () => any,
|
||||
text: string
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,7 @@ import { Key } from '../../utils'
|
||||
type ToggleInputButtonProps = {
|
||||
name: string,
|
||||
txt: string,
|
||||
onToggleChanged: (string) => void,
|
||||
onToggleChanged: Function,
|
||||
checked: boolean,
|
||||
placeholder: string,
|
||||
inputType: string,
|
||||
|
@ -67,6 +67,7 @@ export interface HTTPMessage {
|
||||
trailers?: Headers
|
||||
contentLength: number
|
||||
contentHash: string
|
||||
content?: string
|
||||
timestamp_start: number
|
||||
timestamp_end?: number
|
||||
}
|
||||
|
@ -59,7 +59,7 @@ export var formatTimeDelta = function (milliseconds) {
|
||||
|
||||
export var formatTimeStamp = function (seconds, utc_to_local=true) {
|
||||
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 ts = new Date(local).toISOString();
|
||||
} else {
|
||||
|
Loading…
Reference in New Issue
Block a user