diff options
Diffstat (limited to 'web/src/js')
| -rw-r--r-- | web/src/js/components/ContentView/CodeEditor.jsx | 3 | ||||
| -rw-r--r-- | web/src/js/components/ContentView/ContentViews.jsx | 33 | ||||
| -rw-r--r-- | web/src/js/components/ContentView/UploadContentButton.jsx | 24 | ||||
| -rw-r--r-- | web/src/js/components/ContentView/ViewSelector.jsx | 80 | ||||
| -rw-r--r-- | web/src/js/components/Footer.jsx | 33 | ||||
| -rw-r--r-- | web/src/js/components/Header/FileMenu.jsx | 133 | ||||
| -rw-r--r-- | web/src/js/components/Header/FlowMenu.jsx | 6 | ||||
| -rw-r--r-- | web/src/js/components/Header/OptionMenu.jsx | 6 | ||||
| -rw-r--r-- | web/src/js/components/common/Dropdown.jsx | 53 | ||||
| -rw-r--r-- | web/src/js/components/common/FileChooser.jsx | 27 | ||||
| -rw-r--r-- | web/src/js/components/common/ToggleInputButton.jsx | 30 | ||||
| -rw-r--r-- | web/src/js/ducks/flows.js | 12 | ||||
| -rw-r--r-- | web/src/js/ducks/ui/flow.js | 3 | ||||
| -rw-r--r-- | web/src/js/utils.js | 7 | 
14 files changed, 227 insertions, 223 deletions
diff --git a/web/src/js/components/ContentView/CodeEditor.jsx b/web/src/js/components/ContentView/CodeEditor.jsx index d0430e6f..8afc128f 100644 --- a/web/src/js/components/ContentView/CodeEditor.jsx +++ b/web/src/js/components/ContentView/CodeEditor.jsx @@ -1,5 +1,4 @@ -import React, { Component, PropTypes } from 'react' -import { render } from 'react-dom'; +import React, {PropTypes} from 'react'  import Codemirror from 'react-codemirror'; diff --git a/web/src/js/components/ContentView/ContentViews.jsx b/web/src/js/components/ContentView/ContentViews.jsx index cd593023..32a07564 100644 --- a/web/src/js/components/ContentView/ContentViews.jsx +++ b/web/src/js/components/ContentView/ContentViews.jsx @@ -30,6 +30,12 @@ function Edit({ content, onChange }) {  Edit = ContentLoader(Edit)  class ViewServer extends Component { +    static propTypes  = { +        showFullContent: PropTypes.bool.isRequired, +        maxLines: PropTypes.number.isRequired, +        setContentViewDescription : PropTypes.func.isRequired, +        setContent: PropTypes.func.isRequired +    }      componentWillMount(){          this.setContentView(this.props) @@ -40,6 +46,7 @@ class ViewServer extends Component {              this.setContentView(nextProps)          }      } +      setContentView(props){          try {              this.data = JSON.parse(props.content) @@ -50,25 +57,31 @@ class ViewServer extends Component {          props.setContentViewDescription(props.contentView != this.data.description ? this.data.description : '')          props.setContent(this.data.lines)      } +      render() {          const {content, contentView, message, maxLines} = this.props          let lines = this.props.showFullContent ? this.data.lines : this.data.lines.slice(0, maxLines) -        return <div> +        return ( +            <div>                  <pre>                      {lines.map((line, i) =>                          <div key={`line${i}`}> -                            {line.map((tuple, j) => -                                <span key={`tuple${j}`} className={tuple[0]}> -                                    {tuple[1]} -                                </span> -                            )} +                            {line.map((element, j) => { +                                let [style, text] = element +                                return ( +                                    <span key={`tuple${j}`} className={style}> +                                        {text} +                                    </span> +                                ) +                            })}                          </div>                      )}                  </pre> -            {ViewImage.matches(message) && -            <ViewImage {...this.props} /> -            } -        </div> +                {ViewImage.matches(message) && +                <ViewImage {...this.props} /> +                } +            </div> +        )      }  } diff --git a/web/src/js/components/ContentView/UploadContentButton.jsx b/web/src/js/components/ContentView/UploadContentButton.jsx index 0652b584..de349af4 100644 --- a/web/src/js/components/ContentView/UploadContentButton.jsx +++ b/web/src/js/components/ContentView/UploadContentButton.jsx @@ -1,28 +1,18 @@  import { PropTypes } from 'react' +import FileChooser from '../common/FileChooser'  UploadContentButton.propTypes = {      uploadContent: PropTypes.func.isRequired,  }  export default function UploadContentButton({ uploadContent }) { - -    let fileInput; - +          return ( -        <a className="btn btn-default btn-xs" -           onClick={() => fileInput.click()} -           title="Upload a file to replace the content."> -            <i className="fa fa-upload"/> -            <input -                ref={ref => fileInput = ref} -                className="hidden" -                type="file" -                onChange={e => { -                    if (e.target.files.length > 0) uploadContent(e.target.files[0]) -                }} -            /> -        </a> - +        <FileChooser +            icon="fa-upload" +            title="Upload a file to replace the content." +            onOpenFile={uploadContent} +            className="btn btn-default btn-xs"/>      )  } diff --git a/web/src/js/components/ContentView/ViewSelector.jsx b/web/src/js/components/ContentView/ViewSelector.jsx index 59ec4276..ab433ea3 100644 --- a/web/src/js/components/ContentView/ViewSelector.jsx +++ b/web/src/js/components/ContentView/ViewSelector.jsx @@ -1,72 +1,36 @@  import React, { PropTypes, Component } from 'react' -import classnames from 'classnames'  import { connect } from 'react-redux'  import * as ContentViews from './ContentViews' -import { setContentView } from "../../ducks/ui/flow"; - -function ViewItem({ name, setContentView, children }) { -    return ( -        <li> -            <a href="#" onClick={() => setContentView(name)}> -                {children} -            </a> -        </li> -    ) -} +import { setContentView } from '../../ducks/ui/flow'; +import Dropdown from '../common/Dropdown' -/*ViewSelector.propTypes = { +ViewSelector.propTypes = {      contentViews: PropTypes.array.isRequired,      activeView: PropTypes.string.isRequired,      isEdit: PropTypes.bool.isRequired, -    isContentViewSelectorOpen: PropTypes.bool.isRequired, -    setContentViewSelectorOpen: PropTypes.func.isRequired -}*/ - - -class ViewSelector extends Component { -      constructor(props, context) { -        super(props, context) -        this.close = this.close.bind(this) -        this.state = {open: false} -    } -    close() { -        this.setState({open: false}) -        document.removeEventListener('click', this.close) -    } - -    onDropdown(e){ -        e.preventDefault() -        this.setState({open: !this.state.open}) -        document.addEventListener('click', this.close) -    } +    setContentView: PropTypes.func.isRequired +} -    render() { -        const {contentViews, activeView, isEdit, setContentView} = this.props -        let edit = ContentViews.Edit.displayName +function ViewSelector ({contentViews, activeView, isEdit, setContentView}){ +    let edit = ContentViews.Edit.displayName +    let inner = <span> <b>View:</b> {activeView}<span className="caret"></span> </span> -        return ( -            <div className={classnames('dropup pull-left', { open: this.state.open })}> -                <a className="btn btn-default btn-xs" -                   onClick={ e => this.onDropdown(e) } -                   href="#"> -                    <b>View:</b> {activeView}<span className="caret"></span> +    return ( +        <Dropdown dropup className="pull-left" btnClass="btn btn-default btn-xs" text={inner}> +            {contentViews.map(name => +                <a href="#" key={name} onClick={e => {e.preventDefault(); setContentView(name)}}> +                    {name.toLowerCase().replace('_', ' ')}                  </a> -                <ul className="dropdown-menu" role="menu"> -                    {contentViews.map(name => -                        <ViewItem key={name} setContentView={setContentView} name={name}> -                            {name.toLowerCase().replace('_', ' ')} -                        </ViewItem> -                    )} -                    {isEdit && -                    <ViewItem key={edit} setContentView={setContentView} name={edit}> -                        {edit.toLowerCase()} -                    </ViewItem> -                    } -                </ul> -            </div> -        ) -    } +                ) +            } +            {isEdit && +                <a href="#" onClick={e => {e.preventDefault(); setContentView(edit)}}> +                    {edit.toLowerCase()} +                </a> +            } +        </Dropdown> +    )  }  export default connect ( diff --git a/web/src/js/components/Footer.jsx b/web/src/js/components/Footer.jsx index 2bda70e1..96e7b7db 100644 --- a/web/src/js/components/Footer.jsx +++ b/web/src/js/components/Footer.jsx @@ -7,40 +7,41 @@ Footer.propTypes = {  }  function Footer({ settings }) { +    let {mode, intercept, showhost, no_upstream_cert, rawtcp, http2, anticache, anticomp, stickyauth, stickycookie, stream} = settings;      return (          <footer> -            {settings.mode && settings.mode != "regular" && ( -                <span className="label label-success">{settings.mode} mode</span> +            {mode && mode != "regular" && ( +                <span className="label label-success">{mode} mode</span>              )} -            {settings.intercept && ( -                <span className="label label-success">Intercept: {settings.intercept}</span> +            {intercept && ( +                <span className="label label-success">Intercept: {intercept}</span>              )} -            {settings.showhost && ( +            {showhost && (                  <span className="label label-success">showhost</span>              )} -            {settings.no_upstream_cert && ( +            {no_upstream_cert && (                  <span className="label label-success">no-upstream-cert</span>              )} -            {settings.rawtcp && ( +            {rawtcp && (                  <span className="label label-success">raw-tcp</span>              )} -            {!settings.http2 && ( +            {!http2 && (                  <span className="label label-success">no-http2</span>              )} -            {settings.anticache && ( +            {anticache && (                  <span className="label label-success">anticache</span>              )} -            {settings.anticomp && ( +            {anticomp && (                  <span className="label label-success">anticomp</span>              )} -            {settings.stickyauth && ( -                <span className="label label-success">stickyauth: {settings.stickyauth}</span> +            {stickyauth && ( +                <span className="label label-success">stickyauth: {stickyauth}</span>              )} -            {settings.stickycookie && ( -                <span className="label label-success">stickycookie: {settings.stickycookie}</span> +            {stickycookie && ( +                <span className="label label-success">stickycookie: {stickycookie}</span>              )} -            {settings.stream && ( -                <span className="label label-success">stream: {formatSize(settings.stream)}</span> +            {stream && ( +                <span className="label label-success">stream: {formatSize(stream)}</span>              )}          </footer>      ) diff --git a/web/src/js/components/Header/FileMenu.jsx b/web/src/js/components/Header/FileMenu.jsx index d3786475..53c63ea1 100644 --- a/web/src/js/components/Header/FileMenu.jsx +++ b/web/src/js/components/Header/FileMenu.jsx @@ -1,103 +1,46 @@ -import React, { Component } from 'react' +import React, { Component, PropTypes } from 'react'  import { connect } from 'react-redux' -import classnames from 'classnames' +import FileChooser from '../common/FileChooser' +import Dropdown, {Divider} from '../common/Dropdown'  import * as flowsActions from '../../ducks/flows' -class FileMenu extends Component { - -    constructor(props, context) { -        super(props, context) -        this.state = { show: false } - -        this.close = this.close.bind(this) -        this.onFileClick = this.onFileClick.bind(this) -        this.onNewClick = this.onNewClick.bind(this) -        this.onOpenClick = this.onOpenClick.bind(this) -        this.onOpenFile = this.onOpenFile.bind(this) -        this.onSaveClick = this.onSaveClick.bind(this) -    } - -    close() { -        this.setState({ show: false }) -        document.removeEventListener('click', this.close) -    } - -    onFileClick(e) { -        e.preventDefault() - -        if (this.state.show) { -            return -        } - -        document.addEventListener('click', this.close) -        this.setState({ show: true }) -    } - -    onNewClick(e) { -        e.preventDefault() -        if (confirm('Delete all flows?')) { -            this.props.clearFlows() -        } -    } - -    onOpenClick(e) { -        e.preventDefault() -        this.fileInput.click() -    } - -    onOpenFile(e) { -        e.preventDefault() -        if (e.target.files.length > 0) { -            this.props.loadFlows(e.target.files[0]) -            this.fileInput.value = '' -        } -    } +FileMenu.propTypes = { +    clearFlows: PropTypes.func.isRequired, +    loadFlows: PropTypes.func.isRequired, +    saveFlows: PropTypes.func.isRequired +} -    onSaveClick(e) { -        e.preventDefault() -        this.props.saveFlows() -    } +FileMenu.onNewClick = (e, clearFlows) => { +    e.preventDefault(); +    if (confirm('Delete all flows?')) +        clearFlows() +} -    render() { -        return ( -            <div className={classnames('dropdown pull-left', { open: this.state.show })}> -                <a href="#" className="special" onClick={this.onFileClick}>mitmproxy</a> -                <ul className="dropdown-menu" role="menu"> -                    <li> -                        <a href="#" onClick={this.onNewClick}> -                            <i className="fa fa-fw fa-file"></i> -                            New -                        </a> -                    </li> -                    <li> -                        <a href="#" onClick={this.onOpenClick}> -                            <i className="fa fa-fw fa-folder-open"></i> -                            Open... -                        </a> -                        <input -                            ref={ref => this.fileInput = ref} -                            className="hidden" -                            type="file" -                            onChange={this.onOpenFile} -                        /> -                    </li> -                    <li> -                        <a href="#" onClick={this.onSaveClick}> -                            <i className="fa fa-fw fa-floppy-o"></i> -                            Save... -                        </a> -                    </li> -                    <li role="presentation" className="divider"></li> -                    <li> -                        <a href="http://mitm.it/" target="_blank"> -                            <i className="fa fa-fw fa-external-link"></i> -                            Install Certificates... -                        </a> -                    </li> -                </ul> -            </div> -        ) -    } +function FileMenu ({clearFlows, loadFlows, saveFlows}) { +     return ( +        <Dropdown className="pull-left" btnClass="special" text="mitmproxy"> +            <a href="#" onClick={e => FileMenu.onNewClick(e, clearFlows)}> +                <i className="fa fa-fw fa-file"></i> +                New +            </a> +            <FileChooser +                icon="fa-folder-open" +                text="Open..." +                onOpenFile={file => loadFlows(file)} +            /> +            <a href="#" onClick={e =>{ e.preventDefault(); saveFlows();}}> +                <i className="fa fa-fw fa-floppy-o"></i> +                Save... +            </a> + +            <Divider/> + +            <a href="http://mitm.it/" target="_blank"> +                <i className="fa fa-fw fa-external-link"></i> +                Install Certificates... +            </a> +        </Dropdown> +    )  }  export default connect( diff --git a/web/src/js/components/Header/FlowMenu.jsx b/web/src/js/components/Header/FlowMenu.jsx index bdd30d5e..e78a49aa 100644 --- a/web/src/js/components/Header/FlowMenu.jsx +++ b/web/src/js/components/Header/FlowMenu.jsx @@ -8,10 +8,14 @@ FlowMenu.title = 'Flow'  FlowMenu.propTypes = {      flow: PropTypes.object.isRequired, +    acceptFlow: PropTypes.func.isRequired, +    replayFlow: PropTypes.func.isRequired, +    duplicateFlow: PropTypes.func.isRequired, +    removeFlow: PropTypes.func.isRequired, +    revertFlow: PropTypes.func.isRequired  }  function FlowMenu({ flow, acceptFlow, replayFlow, duplicateFlow, removeFlow, revertFlow }) { -      return (          <div>              <div className="menu-row"> diff --git a/web/src/js/components/Header/OptionMenu.jsx b/web/src/js/components/Header/OptionMenu.jsx index a338fed0..a11062f2 100644 --- a/web/src/js/components/Header/OptionMenu.jsx +++ b/web/src/js/components/Header/OptionMenu.jsx @@ -41,17 +41,17 @@ function OptionMenu({ settings, updateSettings }) {                  />                  <ToggleInputButton name="stickyauth" placeholder="Sticky auth filter"                      checked={!!settings.stickyauth} -                    txt={settings.stickyauth || ''} +                    txt={settings.stickyauth}                      onToggleChanged={txt => updateSettings({ stickyauth: !settings.stickyauth ? txt : null })}                  />                  <ToggleInputButton name="stickycookie" placeholder="Sticky cookie filter"                      checked={!!settings.stickycookie} -                    txt={settings.stickycookie || ''} +                    txt={settings.stickycookie}                      onToggleChanged={txt => updateSettings({ stickycookie: !settings.stickycookie ? txt : null })}                  />                  <ToggleInputButton name="stream" placeholder="stream..."                      checked={!!settings.stream} -                    txt={settings.stream || ''} +                    txt={settings.stream}                      inputType="number"                      onToggleChanged={txt => updateSettings({ stream: !settings.stream ? txt : null })}                  /> diff --git a/web/src/js/components/common/Dropdown.jsx b/web/src/js/components/common/Dropdown.jsx new file mode 100644 index 00000000..cc95a6dc --- /dev/null +++ b/web/src/js/components/common/Dropdown.jsx @@ -0,0 +1,53 @@ +import React, { Component, PropTypes } from 'react' +import classnames from 'classnames' + +export const Divider = () => <hr className="divider"/> + +export default class Dropdown extends Component { + +    static propTypes = { +        dropup: PropTypes.bool, +        className: PropTypes.string, +        btnClass: PropTypes.string.isRequired +    } + +    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 +        } +        this.setState({open: !this.state.open}) +        document.addEventListener('click', this.close) +    } + +    render() { +        const {dropup, className, btnClass, text, children} = this.props +        return ( +            <div className={classnames( (dropup ? 'dropup' : 'dropdown'), className, { open: this.state.open })}> +                <a href='#' className={btnClass} +                   onClick={this.open}> +                    {text} +                </a> +                <ul className="dropdown-menu" role="menu"> +                    {children.map ( (item, i) =>  <li key={i}> {item} </li> )} +                </ul> +            </div> +        ) +    } +} diff --git a/web/src/js/components/common/FileChooser.jsx b/web/src/js/components/common/FileChooser.jsx new file mode 100644 index 00000000..d59d2d6d --- /dev/null +++ b/web/src/js/components/common/FileChooser.jsx @@ -0,0 +1,27 @@ +import React, { PropTypes } from 'react' + +FileChooser.propTypes = { +    icon: PropTypes.string, +    text: PropTypes.string, +    className: PropTypes.string, +    title: PropTypes.string, +    onOpenFile: PropTypes.func.isRequired +} + +export default function FileChooser({ icon, text, className, title, onOpenFile }) { +    let fileInput; +    return ( +        <a href='#' onClick={() => fileInput.click()} +           className={className} +           title={title}> +            <i className={'fa fa-fw ' + icon}></i> +            {text} +             <input +                ref={ref => fileInput = ref} +                className="hidden" +                type="file" +                onChange={e => { e.preventDefault(); if(e.target.files.length > 0) onOpenFile(e.target.files[0]); fileInput = "";}} +            /> +        </a> +    ) +} diff --git a/web/src/js/components/common/ToggleInputButton.jsx b/web/src/js/components/common/ToggleInputButton.jsx index 25d620ae..5fa24c10 100644 --- a/web/src/js/components/common/ToggleInputButton.jsx +++ b/web/src/js/components/common/ToggleInputButton.jsx @@ -6,17 +6,16 @@ export default class ToggleInputButton extends Component {      static propTypes = {          name: PropTypes.string.isRequired, -        txt: PropTypes.string.isRequired, -        onToggleChanged: PropTypes.func.isRequired +        txt: PropTypes.string, +        onToggleChanged: PropTypes.func.isRequired, +        checked: PropTypes.bool.isRequired, +        placeholder: PropTypes.string.isRequired, +        inputType: PropTypes.string      }      constructor(props) {          super(props) -        this.state = { txt: props.txt } -    } - -    onChange(e) { -        this.setState({ txt: e.target.value }) +        this.state = { txt: props.txt || '' }      }      onKeyDown(e) { @@ -27,23 +26,24 @@ export default class ToggleInputButton extends Component {      }      render() { +        const {checked, onToggleChanged, name, inputType, placeholder} = this.props          return (              <div className="input-group toggle-input-btn">                  <span className="input-group-btn" -                      onClick={() => this.props.onToggleChanged(this.state.txt)}> -                    <div className={classnames('btn', this.props.checked ? 'btn-primary' : 'btn-default')}> -                        <span className={classnames('fa', this.props.checked ? 'fa-check-square-o' : 'fa-square-o')}/> +                      onClick={() => onToggleChanged(this.state.txt)}> +                    <div className={classnames('btn', checked ? 'btn-primary' : 'btn-default')}> +                        <span className={classnames('fa', checked ? 'fa-check-square-o' : 'fa-square-o')}/>                            -                        {this.props.name} +                        {name}                      </div>                  </span>                  <input                      className="form-control" -                    placeholder={this.props.placeholder} -                    disabled={this.props.checked} +                    placeholder={placeholder} +                    disabled={checked}                      value={this.state.txt} -                    type={this.props.inputType} -                    onChange={e => this.onChange(e)} +                    type={inputType || 'text'} +                    onChange={e => this.setState({ txt: e.target.value })}                      onKeyDown={e => this.onKeyDown(e)}                  />              </div> diff --git a/web/src/js/ducks/flows.js b/web/src/js/ducks/flows.js index f96653a9..404db0d1 100644 --- a/web/src/js/ducks/flows.js +++ b/web/src/js/ducks/flows.js @@ -1,5 +1,6 @@  import { fetchApi } from '../utils'  import reduceList, * as listActions from './utils/list' +import { selectRelative } from './flowView'  import * as msgQueueActions from './msgQueue'  import * as websocketActions from './websocket' @@ -210,5 +211,14 @@ export function updateFlow(item) {   * @private   */  export function removeFlow(id) { -    return { type: REMOVE, id } +    return (dispatch, getState) => { +        let currentIndex = getState().flowView.indexOf[getState().flows.selected[0]] +        let maxIndex = getState().flowView.data.length - 1 +        let deleteLastEntry = maxIndex == 0 +        if (deleteLastEntry) +            dispatch(select()) +        else +            dispatch(selectRelative(currentIndex == maxIndex ? -1 : 1) ) +        dispatch({ type: REMOVE, id }) +    }  } diff --git a/web/src/js/ducks/ui/flow.js b/web/src/js/ducks/ui/flow.js index 0360321c..4a6d64cd 100644 --- a/web/src/js/ducks/ui/flow.js +++ b/web/src/js/ducks/ui/flow.js @@ -149,6 +149,5 @@ export function setContent(content){  }  export function stopEdit(flow, modifiedFlow) { -    let diff = getDiff(flow, modifiedFlow) -    return flowsActions.update(flow, diff) +    return flowsActions.update(flow, getDiff(flow, modifiedFlow))  } diff --git a/web/src/js/utils.js b/web/src/js/utils.js index e44182d0..e8470cec 100644 --- a/web/src/js/utils.js +++ b/web/src/js/utils.js @@ -107,14 +107,15 @@ fetchApi.put = (url, json, options) => fetchApi(          ...options      }  ) - +// deep comparison of two json objects (dicts). arrays are handeled as a single value. +// return: json object including only the changed keys value pairs.  export function getDiff(obj1, obj2) {      let result = {...obj2};      for(let key in obj1) {          if(_.isEqual(obj2[key], obj1[key]))              result[key] = undefined -        else if(!(Array.isArray(obj2[key]) && Array.isArray(obj1[key])) && -                typeof obj2[key] == 'object' && typeof obj1[key] == 'object') +        else if(Object.prototype.toString.call(obj2[key]) === '[object Object]' && +                Object.prototype.toString.call(obj1[key]) === '[object Object]' )              result[key] = getDiff(obj1[key], obj2[key])      }      return result  | 
