diff options
Diffstat (limited to 'web/src/js/components/header.js')
| -rw-r--r-- | web/src/js/components/header.js | 399 | 
1 files changed, 399 insertions, 0 deletions
diff --git a/web/src/js/components/header.js b/web/src/js/components/header.js new file mode 100644 index 00000000..998a41df --- /dev/null +++ b/web/src/js/components/header.js @@ -0,0 +1,399 @@ +var React = require("react"); +var $ = require("jquery"); + +var Filt = require("../filt/filt.js"); +var utils = require("../utils.js"); +var common = require("./common.js"); +var actions = require("../actions.js"); +var Query = require("../actions.js").Query; + +var FilterDocs = React.createClass({ +    statics: { +        xhr: false, +        doc: false +    }, +    componentWillMount: function () { +        if (!FilterDocs.doc) { +            FilterDocs.xhr = $.getJSON("/filter-help").done(function (doc) { +                FilterDocs.doc = doc; +                FilterDocs.xhr = false; +            }); +        } +        if (FilterDocs.xhr) { +            FilterDocs.xhr.done(function () { +                this.forceUpdate(); +            }.bind(this)); +        } +    }, +    render: function () { +        if (!FilterDocs.doc) { +            return <i className="fa fa-spinner fa-spin"></i>; +        } else { +            var commands = FilterDocs.doc.commands.map(function (c) { +                return <tr key={c[1]}> +                    <td>{c[0].replace(" ", '\u00a0')}</td> +                    <td>{c[1]}</td> +                </tr>; +            }); +            commands.push(<tr key="docs-link"> +                <td colSpan="2"> +                    <a href="https://mitmproxy.org/doc/features/filters.html" +                        target="_blank"> +                        <i className="fa fa-external-link"></i> +                      mitmproxy docs</a> +                </td> +            </tr>); +            return <table className="table table-condensed"> +                <tbody>{commands}</tbody> +            </table>; +        } +    } +}); +var FilterInput = React.createClass({ +    mixins: [common.ChildFocus], +    getInitialState: function () { +        // Consider both focus and mouseover for showing/hiding the tooltip, +        // because onBlur of the input is triggered before the click on the tooltip +        // finalized, hiding the tooltip just as the user clicks on it. +        return { +            value: this.props.value, +            focus: false, +            mousefocus: false +        }; +    }, +    componentWillReceiveProps: function (nextProps) { +        this.setState({value: nextProps.value}); +    }, +    onChange: function (e) { +        var nextValue = e.target.value; +        this.setState({ +            value: nextValue +        }); +        // Only propagate valid filters upwards. +        if (this.isValid(nextValue)) { +            this.props.onChange(nextValue); +        } +    }, +    isValid: function (filt) { +        try { +            Filt.parse(filt || this.state.value); +            return true; +        } catch (e) { +            return false; +        } +    }, +    getDesc: function () { +        var desc; +        try { +            desc = Filt.parse(this.state.value).desc; +        } catch (e) { +            desc = "" + e; +        } +        if (desc !== "true") { +            return desc; +        } else { +            return ( +                <FilterDocs/> +            ); +        } +    }, +    onFocus: function () { +        this.setState({focus: true}); +    }, +    onBlur: function () { +        this.setState({focus: false}); +    }, +    onMouseEnter: function () { +        this.setState({mousefocus: true}); +    }, +    onMouseLeave: function () { +        this.setState({mousefocus: false}); +    }, +    onKeyDown: function (e) { +        if (e.keyCode === utils.Key.ESC || e.keyCode === utils.Key.ENTER) { +            this.blur(); +            // If closed using ESC/ENTER, hide the tooltip. +            this.setState({mousefocus: false}); +        } +        e.stopPropagation(); +    }, +    blur: function () { +        this.refs.input.getDOMNode().blur(); +        this.returnFocus(); +    }, +    select: function () { +        this.refs.input.getDOMNode().select(); +    }, +    render: function () { +        var isValid = this.isValid(); +        var icon = "fa fa-fw fa-" + this.props.type; +        var groupClassName = "filter-input input-group" + (isValid ? "" : " has-error"); + +        var popover; +        if (this.state.focus || this.state.mousefocus) { +            popover = ( +                <div className="popover bottom" onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}> +                    <div className="arrow"></div> +                    <div className="popover-content"> +                    {this.getDesc()} +                    </div> +                </div> +            ); +        } + +        return ( +            <div className={groupClassName}> +                <span className="input-group-addon"> +                    <i className={icon} style={{color: this.props.color}}></i> +                </span> +                <input type="text" placeholder={this.props.placeholder} className="form-control" +                    ref="input" +                    onChange={this.onChange} +                    onFocus={this.onFocus} +                    onBlur={this.onBlur} +                    onKeyDown={this.onKeyDown} +                    value={this.state.value}/> +                {popover} +            </div> +        ); +    } +}); + +var MainMenu = React.createClass({ +    mixins: [common.Navigation, common.RouterState, common.SettingsState], +    statics: { +        title: "Start", +        route: "flows" +    }, +    onSearchChange: function (val) { +        var d = {}; +        d[Query.SEARCH] = val; +        this.setQuery(d); +    }, +    onHighlightChange: function (val) { +        var d = {}; +        d[Query.HIGHLIGHT] = val; +        this.setQuery(d); +    }, +    onInterceptChange: function (val) { +        actions.SettingsActions.update({intercept: val}); +    }, +    render: function () { +        var search = this.getQuery()[Query.SEARCH] || ""; +        var highlight = this.getQuery()[Query.HIGHLIGHT] || ""; +        var intercept = this.state.settings.intercept || ""; + +        return ( +            <div> +                <div className="menu-row"> +                    <FilterInput +                        ref="search" +                        placeholder="Search" +                        type="search" +                        color="black" +                        value={search} +                        onChange={this.onSearchChange} /> +                    <FilterInput +                        ref="highlight" +                        placeholder="Highlight" +                        type="tag" +                        color="hsl(48, 100%, 50%)" +                        value={highlight} +                        onChange={this.onHighlightChange}/> +                    <FilterInput +                        ref="intercept" +                        placeholder="Intercept" +                        type="pause" +                        color="hsl(208, 56%, 53%)" +                        value={intercept} +                        onChange={this.onInterceptChange}/> +                </div> +                <div className="clearfix"></div> +            </div> +        ); +    } +}); + + +var ViewMenu = React.createClass({ +    statics: { +        title: "View", +        route: "flows" +    }, +    mixins: [common.Navigation, common.RouterState], +    toggleEventLog: function () { +        var d = {}; + +        if (this.getQuery()[Query.SHOW_EVENTLOG]) { +            d[Query.SHOW_EVENTLOG] = undefined; +        } else { +            d[Query.SHOW_EVENTLOG] = "t"; // any non-false value will do it, keep it short +        } + +        this.setQuery(d); +    }, +    render: function () { +        var showEventLog = this.getQuery()[Query.SHOW_EVENTLOG]; +        return ( +            <div> +                <button +                    className={"btn " + (showEventLog ? "btn-primary" : "btn-default")} +                    onClick={this.toggleEventLog}> +                    <i className="fa fa-database"></i> +                 Show Eventlog +                </button> +                <span> </span> +            </div> +        ); +    } +}); + + +var ReportsMenu = React.createClass({ +    statics: { +        title: "Visualization", +        route: "reports" +    }, +    render: function () { +        return <div>Reports Menu</div>; +    } +}); + +var FileMenu = React.createClass({ +    getInitialState: function () { +        return { +            showFileMenu: false +        }; +    }, +    handleFileClick: function (e) { +        e.preventDefault(); +        if (!this.state.showFileMenu) { +            var close = function () { +                this.setState({showFileMenu: false}); +                document.removeEventListener("click", close); +            }.bind(this); +            document.addEventListener("click", close); + +            this.setState({ +                showFileMenu: true +            }); +        } +    }, +    handleNewClick: function (e) { +        e.preventDefault(); +        if (confirm("Delete all flows?")) { +            actions.FlowActions.clear(); +        } +    }, +    handleOpenClick: function (e) { +        e.preventDefault(); +        console.error("unimplemented: handleOpenClick"); +    }, +    handleSaveClick: function (e) { +        e.preventDefault(); +        console.error("unimplemented: handleSaveClick"); +    }, +    handleShutdownClick: function (e) { +        e.preventDefault(); +        console.error("unimplemented: handleShutdownClick"); +    }, +    render: function () { +        var fileMenuClass = "dropdown pull-left" + (this.state.showFileMenu ? " open" : ""); + +        return ( +            <div className={fileMenuClass}> +                <a href="#" className="special" onClick={this.handleFileClick}> mitmproxy </a> +                <ul className="dropdown-menu" role="menu"> +                    <li> +                        <a href="#" onClick={this.handleNewClick}> +                            <i className="fa fa-fw fa-file"></i> +                            New +                        </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> +                {/* +                 <li> +                 <a href="#" onClick={this.handleOpenClick}> +                 <i className="fa fa-fw fa-folder-open"></i> +                 Open +                 </a> +                 </li> +                 <li> +                 <a href="#" onClick={this.handleSaveClick}> +                 <i className="fa fa-fw fa-save"></i> +                 Save +                 </a> +                 </li> +                 <li role="presentation" className="divider"></li> +                 <li> +                 <a href="#" onClick={this.handleShutdownClick}> +                 <i className="fa fa-fw fa-plug"></i> +                 Shutdown +                 </a> +                 </li> +                 */} +                </ul> +            </div> +        ); +    } +}); + + +var header_entries = [MainMenu, ViewMenu /*, ReportsMenu */]; + + +var Header = React.createClass({ +    mixins: [common.Navigation], +    getInitialState: function () { +        return { +            active: header_entries[0] +        }; +    }, +    handleClick: function (active, e) { +        e.preventDefault(); +        this.replaceWith(active.route); +        this.setState({active: active}); +    }, +    render: function () { +        var header = header_entries.map(function (entry, i) { +            var className; +            if (entry === this.state.active) { +                className = "active"; +            } else { +                className = ""; +            } +            return ( +                <a key={i} +                    href="#" +                    className={className} +                    onClick={this.handleClick.bind(this, entry)}> +                    { entry.title} +                </a> +            ); +        }.bind(this)); + +        return ( +            <header> +                <nav className="nav-tabs nav-tabs-lg"> +                    <FileMenu/> +                    {header} +                </nav> +                <div className="menu"> +                    <this.state.active ref="active"/> +                </div> +            </header> +        ); +    } +}); + + +module.exports = { +    Header: Header, +    MainMenu: MainMenu +};
\ No newline at end of file  | 
