mirror of
https://github.com/vee1e/mitmproxy.git
synced 2026-09-01 18:27:18 +00:00
add dropdown submenu
This commit is contained in:
parent
6824666424
commit
fffd0017d8
9 changed files with 185 additions and 105 deletions
|
|
@ -7,14 +7,17 @@
|
|||
"start": "gulp"
|
||||
},
|
||||
"dependencies": {
|
||||
"@popperjs/core": "^2.9.2",
|
||||
"bootstrap": "^3.3.7",
|
||||
"classnames": "^2.3.1",
|
||||
"lodash": "^4.17.21",
|
||||
"mock-xmlhttprequest": "^1.1.0",
|
||||
"popper.js": "^1.16.1",
|
||||
"prop-types": "^15.7.2",
|
||||
"react": "^17.0.2",
|
||||
"react-codemirror": "^1.0.0",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-popper": "^2.2.5",
|
||||
"react-redux": "^7.2.4",
|
||||
"react-test-renderer": "^17.0.2",
|
||||
"redux": "^4.1.0",
|
||||
|
|
|
|||
|
|
@ -190,4 +190,52 @@
|
|||
transform: translate(-1px, 2px);
|
||||
}
|
||||
|
||||
.dropdown-submenu {
|
||||
position:relative;
|
||||
}
|
||||
|
||||
.dropdown-submenu>.dropdown-menu {
|
||||
top:0;
|
||||
left:100%;
|
||||
margin-top:-6px;
|
||||
margin-left:-1px;
|
||||
-webkit-border-radius:0 6px 6px 6px;
|
||||
-moz-border-radius:0 6px 6px 6px;
|
||||
border-radius:0 6px 6px 6px;
|
||||
}
|
||||
|
||||
.dropdown-submenu:hover>.dropdown-menu {
|
||||
display:block;
|
||||
}
|
||||
|
||||
.dropdown-submenu>a:after {
|
||||
display:block;
|
||||
content:" ";
|
||||
float:right;
|
||||
width:0;
|
||||
height:0;
|
||||
border-color:transparent;
|
||||
border-style:solid;
|
||||
border-width:5px 0 5px 5px;
|
||||
border-left-color:#cccccc;
|
||||
margin-top:5px;
|
||||
margin-right:-10px;
|
||||
}
|
||||
|
||||
.dropdown-submenu:hover>a:after {
|
||||
border-left-color:#ffffff;
|
||||
}
|
||||
|
||||
.dropdown-submenu.pull-left {
|
||||
float:none;
|
||||
}
|
||||
|
||||
.dropdown-submenu.pull-left>.dropdown-menu {
|
||||
left:-100%;
|
||||
margin-left:10px;
|
||||
-webkit-border-radius:6px 0 6px 6px;
|
||||
-moz-border-radius:6px 0 6px 6px;
|
||||
border-radius:6px 0 6px 6px;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -102,15 +102,7 @@ exports[`Flowcolumns Components should render QuickActionsColumn 1`] = `
|
|||
<td
|
||||
className="col-quickactions"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
className="quickaction"
|
||||
>
|
||||
<i
|
||||
className="fa fa-fw fa-ellipsis-h"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div />
|
||||
</td>
|
||||
`;
|
||||
|
||||
|
|
|
|||
|
|
@ -51,72 +51,7 @@ exports[`FlowRow Component should render correctly 1`] = `
|
|||
<td
|
||||
className="col-quickactions"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
className="quickaction"
|
||||
>
|
||||
<i
|
||||
className="fa fa-fw fa-ellipsis-h"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div />
|
||||
</td>
|
||||
<div
|
||||
className="dropdown pull-left btn btn-default"
|
||||
>
|
||||
<a
|
||||
className="special"
|
||||
href="#"
|
||||
onClick={[Function]}
|
||||
>
|
||||
Actions
|
||||
</a>
|
||||
<ul
|
||||
className="dropdown-menu"
|
||||
role="menu"
|
||||
>
|
||||
<li>
|
||||
|
||||
<a
|
||||
href="#"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<i
|
||||
className="fa fa-fw fa-plus"
|
||||
/>
|
||||
Intercept
|
||||
address
|
||||
</a>
|
||||
|
||||
</li>
|
||||
<li>
|
||||
|
||||
<a
|
||||
href="#"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<i
|
||||
className="fa fa-fw fa-plus"
|
||||
/>
|
||||
Intercept
|
||||
address/path
|
||||
</a>
|
||||
|
||||
</li>
|
||||
<li>
|
||||
|
||||
<a
|
||||
href="#"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<i
|
||||
className="fa fa-fw fa-plus"
|
||||
/>
|
||||
Intercept all POST requests from this host
|
||||
</a>
|
||||
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</tr>
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import classnames from 'classnames'
|
|||
import {RequestUtils, ResponseUtils} from '../../flow/utils.js'
|
||||
import {formatSize, formatTimeDelta, formatTimeStamp} from '../../utils.js'
|
||||
import * as flowActions from "../../ducks/flows";
|
||||
import HoverMenu from "./HoverMenu";
|
||||
|
||||
export const defaultColumnNames = ["tls", "icon", "path", "method", "status", "size", "time"]
|
||||
|
||||
|
|
@ -163,7 +164,7 @@ export function TimeStampColumn({flow}) {
|
|||
TimeStampColumn.headerClass = 'col-timestamp'
|
||||
TimeStampColumn.headerName = 'TimeStamp'
|
||||
|
||||
export function QuickActionsColumn({flow}) {
|
||||
export function QuickActionsColumn({flow, selected}) {
|
||||
const dispatch = useDispatch()
|
||||
|
||||
function resume(e) {
|
||||
|
|
@ -175,7 +176,9 @@ export function QuickActionsColumn({flow}) {
|
|||
return (
|
||||
<td className="col-quickactions">
|
||||
<div>
|
||||
<div className="quickaction"><i className="fa fa-fw fa-ellipsis-h"/></div>
|
||||
{selected ?
|
||||
<div className="quickaction"><HoverMenu /></div>
|
||||
: null}
|
||||
{flow.intercepted
|
||||
? <div className="quickaction" onClick={resume}><i className="fa fa-fw fa-play text-success"/></div>
|
||||
: null}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import classnames from 'classnames'
|
|||
import {defaultColumnNames} from './FlowColumns'
|
||||
import { pure } from '../../utils'
|
||||
import {getDisplayColumns} from './FlowTableHead'
|
||||
import HoverMenu from './HoverMenu'
|
||||
import { connect } from 'react-redux'
|
||||
|
||||
FlowRow.propTypes = {
|
||||
|
|
@ -28,9 +27,8 @@ function FlowRow({ flow, selected, highlighted, onSelect, displayColumnNames })
|
|||
return (
|
||||
<tr className={className} onClick={() => onSelect(flow.id)}>
|
||||
{displayColumns.map(Column => (
|
||||
<Column key={Column.name} flow={flow}/>
|
||||
<Column key={Column.name} flow={flow} selected={selected}/>
|
||||
))}
|
||||
<HoverMenu/>
|
||||
</tr>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,12 @@
|
|||
import React, { Component } from 'react'
|
||||
import React, { Component, useCallback } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { update as updateSettings } from "../../ducks/settings"
|
||||
import Dropdown from '../common/Dropdown'
|
||||
import DropdownSubMenu from '../common/DropdownSubMenu'
|
||||
|
||||
class HoverMenu extends Component {
|
||||
constructor(props, context) {
|
||||
super(props, context)
|
||||
}
|
||||
|
||||
onClick(e, example) {
|
||||
let SubMenu = ({flow, intercept, updateSettings}) => {
|
||||
const onClick = useCallback((e, example) => {
|
||||
e.preventDefault();
|
||||
let intercept = this.props.intercept
|
||||
if (intercept && intercept.includes(example)) {
|
||||
return
|
||||
}
|
||||
|
|
@ -19,7 +15,46 @@ class HoverMenu extends Component {
|
|||
} else {
|
||||
intercept = `${intercept} | ${example}`
|
||||
}
|
||||
this.props.updateSettings({ intercept })
|
||||
updateSettings({ intercept })
|
||||
}, [intercept])
|
||||
|
||||
return (
|
||||
<DropdownSubMenu text="Intercept requests like this">
|
||||
<a href="#" onClick={(e) =>{
|
||||
onClick(e, flow.request.host)
|
||||
}}>
|
||||
<i className="fa fa-fw fa-plus"></i>
|
||||
Intercept {flow.request.host}
|
||||
</a>
|
||||
{ flow.request.path != "/" ? <a href="#" onClick={(e) =>{
|
||||
onClick(e, flow.request.host + flow.request.path)
|
||||
}}>
|
||||
<i className="fa fa-fw fa-plus"></i>
|
||||
Intercept {flow.request.host + flow.request.path}
|
||||
</a> : null }
|
||||
<a href="#" onClick={(e) =>{
|
||||
onClick(e, `~m POST & ${flow.request.host}`)
|
||||
}}>
|
||||
<i className="fa fa-fw fa-plus"></i>
|
||||
Intercept all POST requests from this host
|
||||
</a>
|
||||
</DropdownSubMenu>
|
||||
)
|
||||
}
|
||||
|
||||
SubMenu = connect(
|
||||
state => ({
|
||||
flow: state.flows.byId[state.flows.selected[0]],
|
||||
intercept: state.settings.intercept,
|
||||
}),
|
||||
{
|
||||
updateSettings,
|
||||
}
|
||||
)(SubMenu)
|
||||
|
||||
class HoverMenu extends Component {
|
||||
constructor(props, context) {
|
||||
super(props, context)
|
||||
}
|
||||
|
||||
render() {
|
||||
|
|
@ -28,24 +63,18 @@ class HoverMenu extends Component {
|
|||
return null
|
||||
}
|
||||
return (
|
||||
<Dropdown className="pull-left btn btn-default" btnClass="special" icon="fa-ellipsis-v" text="Actions">
|
||||
<Dropdown className="pull-right" btnClass="special" icon="fa fa-fw fa-ellipsis-h" submenu={<SubMenu />}>
|
||||
<a href="#" onClick={(e) =>{
|
||||
this.onClick(e, flow.request.host)
|
||||
e.preventDefault()
|
||||
}}>
|
||||
<i className="fa fa-fw fa-plus"></i>
|
||||
Intercept {flow.request.host}
|
||||
Copy as Curl
|
||||
</a>
|
||||
{ flow.request.path != "/" ? <a href="#" onClick={(e) =>{
|
||||
this.onClick(e, flow.request.host + flow.request.path)
|
||||
}}>
|
||||
<i className="fa fa-fw fa-plus"></i>
|
||||
Intercept {flow.request.host + flow.request.path}
|
||||
</a> : null }
|
||||
<a href="#" onClick={(e) =>{
|
||||
this.onClick(e, `~m POST & ${flow.request.host}`)
|
||||
e.preventDefault()
|
||||
}}>
|
||||
<i className="fa fa-fw fa-plus"></i>
|
||||
Intercept all POST requests from this host
|
||||
Download HAR
|
||||
</a>
|
||||
</Dropdown>
|
||||
)
|
||||
|
|
@ -55,9 +84,6 @@ class HoverMenu extends Component {
|
|||
export default connect(
|
||||
state => ({
|
||||
flow: state.flows.byId[state.flows.selected[0]],
|
||||
intercept: state.settings.intercept,
|
||||
}),
|
||||
{
|
||||
updateSettings,
|
||||
}
|
||||
null
|
||||
)(HoverMenu)
|
||||
|
|
@ -39,15 +39,17 @@ export default class Dropdown extends Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
const {dropup, className, btnClass, text, children} = this.props
|
||||
const {dropup, className, btnClass, text, icon, children, submenu} = this.props
|
||||
return (
|
||||
<div className={classnames((dropup ? 'dropup' : 'dropdown'), className, {open: this.state.open})}>
|
||||
<a href='#' className={btnClass}
|
||||
onClick={this.open}>
|
||||
{icon ? <i className={icon}/> : null}
|
||||
{text}
|
||||
</a>
|
||||
<ul className="dropdown-menu" role="menu">
|
||||
{children.map((item, i) => <li key={i}> {item} </li>)}
|
||||
{submenu}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
73
web/src/js/components/common/DropdownSubMenu.jsx
Normal file
73
web/src/js/components/common/DropdownSubMenu.jsx
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
import React, {Component} from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Manager, Reference, Popper } from 'react-popper';
|
||||
import classnames from 'classnames'
|
||||
|
||||
export const Divider = () => <hr className="divider"/>
|
||||
|
||||
export default class DropdownSubMenu extends Component {
|
||||
|
||||
static propTypes = {
|
||||
dropup: PropTypes.bool,
|
||||
className: PropTypes.string,
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
dropup: false
|
||||
}
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context)
|
||||
this.state = {open: false}
|
||||
this.close = this.close.bind(this)
|
||||
this.open = this.open.bind(this)
|
||||
}
|
||||
|
||||
close() {
|
||||
this.setState({open: false})
|
||||
document.removeEventListener('click', this.close)
|
||||
}
|
||||
|
||||
open(e) {
|
||||
e.preventDefault()
|
||||
if (this.state.open) {
|
||||
return
|
||||
}
|
||||
e.stopPropagation();
|
||||
this.setState({open: !this.state.open})
|
||||
document.addEventListener('click', this.close)
|
||||
}
|
||||
|
||||
render() {
|
||||
const {text, children} = this.props
|
||||
|
||||
return (
|
||||
<li className="dropdown-submenu">
|
||||
<Manager>
|
||||
<Reference>
|
||||
{({ ref }) => (
|
||||
<a tabIndex="-1" ref={ref} href="#">{text}</a>
|
||||
)}
|
||||
</Reference>
|
||||
<Popper
|
||||
placement="bottom-end"
|
||||
modifiers={[
|
||||
{
|
||||
name: 'offset',
|
||||
options: {
|
||||
offset: [0, 30],
|
||||
}
|
||||
},
|
||||
]}
|
||||
>
|
||||
{({ ref, style, placement, arrowProps }) => (
|
||||
<ul ref={ref} style={style} data-placement={placement} className="dropdown-menu pull-left">
|
||||
{children.map((item, i) => <li key={i}> {item} </li>)}
|
||||
</ul>
|
||||
)}
|
||||
</Popper>
|
||||
</Manager>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue