aboutsummaryrefslogtreecommitdiffstats
path: root/mitmproxy/web/src/js/components/header.js
diff options
context:
space:
mode:
Diffstat (limited to 'mitmproxy/web/src/js/components/header.js')
-rw-r--r--mitmproxy/web/src/js/components/header.js399
1 files changed, 399 insertions, 0 deletions
diff --git a/mitmproxy/web/src/js/components/header.js b/mitmproxy/web/src/js/components/header.js
new file mode 100644
index 00000000..998a41df
--- /dev/null
+++ b/mitmproxy/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>
+ &nbsp; 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>
+ &nbsp;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