diff options
Diffstat (limited to 'web/src/js/components')
| -rw-r--r-- | web/src/js/components/eventlog.js | 1 | ||||
| -rw-r--r-- | web/src/js/components/flowdetail.js | 399 | ||||
| -rw-r--r-- | web/src/js/components/flowview/contentview.js | 158 | ||||
| -rw-r--r-- | web/src/js/components/flowview/details.js | 181 | ||||
| -rw-r--r-- | web/src/js/components/flowview/index.js | 74 | ||||
| -rw-r--r-- | web/src/js/components/flowview/messages.js | 91 | ||||
| -rw-r--r-- | web/src/js/components/flowview/nav.js | 61 | ||||
| -rw-r--r-- | web/src/js/components/header.js | 2 | ||||
| -rw-r--r-- | web/src/js/components/mainview.js | 6 | 
9 files changed, 571 insertions, 402 deletions
diff --git a/web/src/js/components/eventlog.js b/web/src/js/components/eventlog.js index 23508275..de69462b 100644 --- a/web/src/js/components/eventlog.js +++ b/web/src/js/components/eventlog.js @@ -3,6 +3,7 @@ var common = require("./common.js");  var Query = require("../actions.js").Query;  var VirtualScrollMixin = require("./virtualscroll.js");  var views = require("../store/view.js"); +var _ = require("lodash");  var LogMessage = React.createClass({      render: function () { diff --git a/web/src/js/components/flowdetail.js b/web/src/js/components/flowdetail.js deleted file mode 100644 index 1d019ffb..00000000 --- a/web/src/js/components/flowdetail.js +++ /dev/null @@ -1,399 +0,0 @@ -var React = require("react"); -var _ = require("lodash"); - -var common = require("./common.js"); -var actions = require("../actions.js"); -var flowutils = require("../flow/utils.js"); -var toputils = require("../utils.js"); - -var NavAction = React.createClass({ -    onClick: function (e) { -        e.preventDefault(); -        this.props.onClick(); -    }, -    render: function () { -        return ( -            <a title={this.props.title} -                href="#" -                className="nav-action" -                onClick={this.onClick}> -                <i className={"fa fa-fw " + this.props.icon}></i> -            </a> -        ); -    } -}); - -var FlowDetailNav = React.createClass({ -    render: function () { -        var flow = this.props.flow; - -        var tabs = this.props.tabs.map(function (e) { -            var str = e.charAt(0).toUpperCase() + e.slice(1); -            var className = this.props.active === e ? "active" : ""; -            var onClick = function (event) { -                this.props.selectTab(e); -                event.preventDefault(); -            }.bind(this); -            return <a key={e} -                href="#" -                className={className} -                onClick={onClick}>{str}</a>; -        }.bind(this)); - -        var acceptButton = null; -        if(flow.intercepted){ -            acceptButton = <NavAction title="[a]ccept intercepted flow" icon="fa-play" onClick={actions.FlowActions.accept.bind(null, flow)} />; -        } -        var revertButton = null; -        if(flow.modified){ -            revertButton = <NavAction title="revert changes to flow [V]" icon="fa-history" onClick={actions.FlowActions.revert.bind(null, flow)} />; -        } - -        return ( -            <nav ref="head" className="nav-tabs nav-tabs-sm"> -                {tabs} -                <NavAction title="[d]elete flow" icon="fa-trash" onClick={actions.FlowActions.delete.bind(null, flow)} /> -                <NavAction title="[D]uplicate flow" icon="fa-copy" onClick={actions.FlowActions.duplicate.bind(null, flow)} /> -                <NavAction disabled title="[r]eplay flow" icon="fa-repeat" onClick={actions.FlowActions.replay.bind(null, flow)} /> -                {acceptButton} -                {revertButton} -            </nav> -        ); -    } -}); - -var Headers = React.createClass({ -    render: function () { -        var rows = this.props.message.headers.map(function (header, i) { -            return ( -                <tr key={i}> -                    <td className="header-name">{header[0] + ":"}</td> -                    <td className="header-value">{header[1]}</td> -                </tr> -            ); -        }); -        return ( -            <table className="header-table"> -                <tbody> -                    {rows} -                </tbody> -            </table> -        ); -    } -}); - -var FlowDetailRequest = React.createClass({ -    render: function () { -        var flow = this.props.flow; -        var first_line = [ -            flow.request.method, -            flowutils.RequestUtils.pretty_url(flow.request), -            "HTTP/" + flow.request.httpversion.join(".") -        ].join(" "); -        var content = null; -        if (flow.request.contentLength > 0) { -            content = "Request Content Size: " + toputils.formatSize(flow.request.contentLength); -        } else { -            content = <div className="alert alert-info">No Content</div>; -        } - -        //TODO: Styling - -        return ( -            <section> -                <div className="first-line">{ first_line }</div> -                <Headers message={flow.request}/> -                <hr/> -                {content} -            </section> -        ); -    } -}); - -var FlowDetailResponse = React.createClass({ -    render: function () { -        var flow = this.props.flow; -        var first_line = [ -            "HTTP/" + flow.response.httpversion.join("."), -            flow.response.code, -            flow.response.msg -        ].join(" "); -        var content = null; -        if (flow.response.contentLength > 0) { -            content = "Response Content Size: " + toputils.formatSize(flow.response.contentLength); -        } else { -            content = <div className="alert alert-info">No Content</div>; -        } - -        //TODO: Styling - -        return ( -            <section> -                <div className="first-line">{ first_line }</div> -                <Headers message={flow.response}/> -                <hr/> -                {content} -            </section> -        ); -    } -}); - -var FlowDetailError = React.createClass({ -    render: function () { -        var flow = this.props.flow; -        return ( -            <section> -                <div className="alert alert-warning"> -                {flow.error.msg} -                    <div> -                        <small>{ toputils.formatTimeStamp(flow.error.timestamp) }</small> -                    </div> -                </div> -            </section> -        ); -    } -}); - -var TimeStamp = React.createClass({ -    render: function () { - -        if (!this.props.t) { -            //should be return null, but that triggers a React bug. -            return <tr></tr>; -        } - -        var ts = toputils.formatTimeStamp(this.props.t); - -        var delta; -        if (this.props.deltaTo) { -            delta = toputils.formatTimeDelta(1000 * (this.props.t - this.props.deltaTo)); -            delta = <span className="text-muted">{"(" + delta + ")"}</span>; -        } else { -            delta = null; -        } - -        return <tr> -            <td>{this.props.title + ":"}</td> -            <td>{ts} {delta}</td> -        </tr>; -    } -}); - -var ConnectionInfo = React.createClass({ - -    render: function () { -        var conn = this.props.conn; -        var address = conn.address.address.join(":"); - -        var sni = <tr key="sni"></tr>; //should be null, but that triggers a React bug. -        if (conn.sni) { -            sni = <tr key="sni"> -                <td> -                    <abbr title="TLS Server Name Indication">TLS SNI:</abbr> -                </td> -                <td>{conn.sni}</td> -            </tr>; -        } -        return ( -            <table className="connection-table"> -                <tbody> -                    <tr key="address"> -                        <td>Address:</td> -                        <td>{address}</td> -                    </tr> -                    {sni} -                </tbody> -            </table> -        ); -    } -}); - -var CertificateInfo = React.createClass({ -    render: function () { -        //TODO: We should fetch human-readable certificate representation -        // from the server -        var flow = this.props.flow; -        var client_conn = flow.client_conn; -        var server_conn = flow.server_conn; - -        var preStyle = {maxHeight: 100}; -        return ( -            <div> -            {client_conn.cert ? <h4>Client Certificate</h4> : null} -            {client_conn.cert ? <pre style={preStyle}>{client_conn.cert}</pre> : null} - -            {server_conn.cert ? <h4>Server Certificate</h4> : null} -            {server_conn.cert ? <pre style={preStyle}>{server_conn.cert}</pre> : null} -            </div> -        ); -    } -}); - -var Timing = React.createClass({ -    render: function () { -        var flow = this.props.flow; -        var sc = flow.server_conn; -        var cc = flow.client_conn; -        var req = flow.request; -        var resp = flow.response; - -        var timestamps = [ -            { -                title: "Server conn. initiated", -                t: sc.timestamp_start, -                deltaTo: req.timestamp_start -            }, { -                title: "Server conn. TCP handshake", -                t: sc.timestamp_tcp_setup, -                deltaTo: req.timestamp_start -            }, { -                title: "Server conn. SSL handshake", -                t: sc.timestamp_ssl_setup, -                deltaTo: req.timestamp_start -            }, { -                title: "Client conn. established", -                t: cc.timestamp_start, -                deltaTo: req.timestamp_start -            }, { -                title: "Client conn. SSL handshake", -                t: cc.timestamp_ssl_setup, -                deltaTo: req.timestamp_start -            }, { -                title: "First request byte", -                t: req.timestamp_start, -            }, { -                title: "Request complete", -                t: req.timestamp_end, -                deltaTo: req.timestamp_start -            } -        ]; - -        if (flow.response) { -            timestamps.push( -                { -                    title: "First response byte", -                    t: resp.timestamp_start, -                    deltaTo: req.timestamp_start -                }, { -                    title: "Response complete", -                    t: resp.timestamp_end, -                    deltaTo: req.timestamp_start -                } -            ); -        } - -        //Add unique key for each row. -        timestamps.forEach(function (e) { -            e.key = e.title; -        }); - -        timestamps = _.sortBy(timestamps, 't'); - -        var rows = timestamps.map(function (e) { -            return <TimeStamp {...e}/>; -        }); - -        return ( -            <div> -                <h4>Timing</h4> -                <table className="timing-table"> -                    <tbody> -                    {rows} -                    </tbody> -                </table> -            </div> -        ); -    } -}); - -var FlowDetailConnectionInfo = React.createClass({ -    render: function () { -        var flow = this.props.flow; -        var client_conn = flow.client_conn; -        var server_conn = flow.server_conn; -        return ( -            <section> - -                <h4>Client Connection</h4> -                <ConnectionInfo conn={client_conn}/> - -                <h4>Server Connection</h4> -                <ConnectionInfo conn={server_conn}/> - -                <CertificateInfo flow={flow}/> - -                <Timing flow={flow}/> - -            </section> -        ); -    } -}); - -var allTabs = { -    request: FlowDetailRequest, -    response: FlowDetailResponse, -    error: FlowDetailError, -    details: FlowDetailConnectionInfo -}; - -var FlowDetail = React.createClass({ -    mixins: [common.StickyHeadMixin, common.Navigation, common.State], -    getTabs: function (flow) { -        var tabs = []; -        ["request", "response", "error"].forEach(function (e) { -            if (flow[e]) { -                tabs.push(e); -            } -        }); -        tabs.push("details"); -        return tabs; -    }, -    nextTab: function (i) { -        var tabs = this.getTabs(this.props.flow); -        var currentIndex = tabs.indexOf(this.getParams().detailTab); -        // JS modulo operator doesn't correct negative numbers, make sure that we are positive. -        var nextIndex = (currentIndex + i + tabs.length) % tabs.length; -        this.selectTab(tabs[nextIndex]); -    }, -    selectTab: function (panel) { -        this.replaceWith( -            "flow", -            { -                flowId: this.getParams().flowId, -                detailTab: panel -            } -        ); -    }, -    render: function () { -        var flow = this.props.flow; -        var tabs = this.getTabs(flow); -        var active = this.getParams().detailTab; - -        if (!_.contains(tabs, active)) { -            if (active === "response" && flow.error) { -                active = "error"; -            } else if (active === "error" && flow.response) { -                active = "response"; -            } else { -                active = tabs[0]; -            } -            this.selectTab(active); -        } - -        var Tab = allTabs[active]; -        return ( -            <div className="flow-detail" onScroll={this.adjustHead}> -                <FlowDetailNav ref="head" -                    flow={flow} -                    tabs={tabs} -                    active={active} -                    selectTab={this.selectTab}/> -                <Tab flow={flow}/> -            </div> -        ); -    } -}); - -module.exports = { -    FlowDetail: FlowDetail -};
\ No newline at end of file diff --git a/web/src/js/components/flowview/contentview.js b/web/src/js/components/flowview/contentview.js new file mode 100644 index 00000000..09a64bb2 --- /dev/null +++ b/web/src/js/components/flowview/contentview.js @@ -0,0 +1,158 @@ +var React = require("react"); +var _ = require("lodash"); + +var MessageUtils = require("../../flow/utils.js").MessageUtils; +var utils = require("../../utils.js"); + +var image_regex = /^image\/(png|jpe?g|gif|vnc.microsoft.icon|x-icon)$/i; +var Image = React.createClass({ +    statics: { +        matches: function (message) { +            return image_regex.test(MessageUtils.getContentType(message)); +        } +    }, +    render: function () { +        var message_name = this.props.flow.request === this.props.message ? "request" : "response"; +        var url = "/flows/" + this.props.flow.id + "/" + message_name + "/content"; +        return <div className="flowview-image"> +            <img src={url} alt="preview" className="img-thumbnail"/> +        </div>; +    } +}); + +var Raw = React.createClass({ +    statics: { +        matches: function (message) { +            return true; +        } +    }, +    render: function () { +        //FIXME +        return <div>raw</div>; +    } +}); + + +var Auto = React.createClass({ +    statics: { +        matches: function () { +            return false; // don't match itself +        }, +        findView: function (message) { +            for (var i = 0; i < all.length; i++) { +                if (all[i].matches(message)) { +                    return all[i]; +                } +            } +            return all[all.length - 1]; +        } +    }, +    render: function () { +        var View = Auto.findView(this.props.message); +        return <View {...this.props}/>; +    } +}); + +var all = [Auto, Image, Raw]; + + +var ContentEmpty = React.createClass({ +    render: function () { +        var message_name = this.props.flow.request === this.props.message ? "request" : "response"; +        return <div className="alert alert-info">No {message_name} content.</div>; +    } +}); + +var ContentMissing = React.createClass({ +    render: function () { +        var message_name = this.props.flow.request === this.props.message ? "Request" : "Response"; +        return <div className="alert alert-info">{message_name} content missing.</div>; +    } +}); + +var TooLarge = React.createClass({ +    render: function () { +        var size = utils.formatSize(this.props.message.contentLength); +        return <div className="alert alert-warning"> +            <button onClick={this.props.onClick} className="btn btn-xs btn-warning pull-right">Display anyway</button> +            {size} content size. +        </div>; +    } +}); + +var ViewSelector = React.createClass({ +    render: function () { +        var views = []; +        for (var i = 0; i < all.length; i++) { +            var view = all[i]; +            var className = "btn btn-default"; +            if (view === this.props.active) { +                className += " active"; +            } +            var text; +            if (view === Auto) { +                text = "auto: " + Auto.findView(this.props.message).displayName.toLowerCase(); +            } else { +                text = view.displayName.toLowerCase(); +            } +            views.push( +                <button +                    key={view.displayName} +                    onClick={this.props.selectView.bind(null, view)} +                    className={className}> +                    {text} +                </button> +            ); +        } + +        return <div className="view-selector btn-group btn-group-xs">{views}</div>; +    } +}); + +var ContentView = React.createClass({ +    getInitialState: function () { +        return { +            displayLarge: false, +            View: Auto +        }; +    }, +    propTypes: { +        // It may seem a bit weird at the first glance: +        // Every view takes the flow and the message as props, e.g. +        // <Auto flow={flow} message={flow.request}/> +        flow: React.PropTypes.object.isRequired, +        message: React.PropTypes.object.isRequired, +    }, +    selectView: function (view) { +        this.setState({ +            View: view +        }); +    }, +    displayLarge: function () { +        this.setState({displayLarge: true}); +    }, +    componentWillReceiveProps: function (nextProps) { +        if (nextProps.message !== this.props.message) { +            this.setState(this.getInitialState()); +        } +    }, +    render: function () { +        var message = this.props.message; +        if (message.contentLength === 0) { +            return <ContentEmpty {...this.props}/>; +        } else if (message.contentLength === null) { +            return <ContentMissing {...this.props}/>; +        } else if (message.contentLength > 1024 * 1024 * 3 && !this.state.displayLarge) { +            return <TooLarge {...this.props} onClick={this.displayLarge}/>; +        } + +        return <div> +            <this.state.View {...this.props} /> +            <div className="text-center"> +                <ViewSelector selectView={this.selectView} active={this.state.View} message={message}/> +            </div> +        </div>; +    } +}); + +module.exports = ContentView;
\ No newline at end of file diff --git a/web/src/js/components/flowview/details.js b/web/src/js/components/flowview/details.js new file mode 100644 index 00000000..00e0116c --- /dev/null +++ b/web/src/js/components/flowview/details.js @@ -0,0 +1,181 @@ +var React = require("react"); +var _ = require("lodash"); + +var utils = require("../../utils.js"); + +var TimeStamp = React.createClass({ +    render: function () { + +        if (!this.props.t) { +            //should be return null, but that triggers a React bug. +            return <tr></tr>; +        } + +        var ts = utils.formatTimeStamp(this.props.t); + +        var delta; +        if (this.props.deltaTo) { +            delta = utils.formatTimeDelta(1000 * (this.props.t - this.props.deltaTo)); +            delta = <span className="text-muted">{"(" + delta + ")"}</span>; +        } else { +            delta = null; +        } + +        return <tr> +            <td>{this.props.title + ":"}</td> +            <td>{ts} {delta}</td> +        </tr>; +    } +}); + +var ConnectionInfo = React.createClass({ + +    render: function () { +        var conn = this.props.conn; +        var address = conn.address.address.join(":"); + +        var sni = <tr key="sni"></tr>; //should be null, but that triggers a React bug. +        if (conn.sni) { +            sni = <tr key="sni"> +                <td> +                    <abbr title="TLS Server Name Indication">TLS SNI:</abbr> +                </td> +                <td>{conn.sni}</td> +            </tr>; +        } +        return ( +            <table className="connection-table"> +                <tbody> +                    <tr key="address"> +                        <td>Address:</td> +                        <td>{address}</td> +                    </tr> +                    {sni} +                </tbody> +            </table> +        ); +    } +}); + +var CertificateInfo = React.createClass({ +    render: function () { +        //TODO: We should fetch human-readable certificate representation +        // from the server +        var flow = this.props.flow; +        var client_conn = flow.client_conn; +        var server_conn = flow.server_conn; + +        var preStyle = {maxHeight: 100}; +        return ( +            <div> +            {client_conn.cert ? <h4>Client Certificate</h4> : null} +            {client_conn.cert ? <pre style={preStyle}>{client_conn.cert}</pre> : null} + +            {server_conn.cert ? <h4>Server Certificate</h4> : null} +            {server_conn.cert ? <pre style={preStyle}>{server_conn.cert}</pre> : null} +            </div> +        ); +    } +}); + +var Timing = React.createClass({ +    render: function () { +        var flow = this.props.flow; +        var sc = flow.server_conn; +        var cc = flow.client_conn; +        var req = flow.request; +        var resp = flow.response; + +        var timestamps = [ +            { +                title: "Server conn. initiated", +                t: sc.timestamp_start, +                deltaTo: req.timestamp_start +            }, { +                title: "Server conn. TCP handshake", +                t: sc.timestamp_tcp_setup, +                deltaTo: req.timestamp_start +            }, { +                title: "Server conn. SSL handshake", +                t: sc.timestamp_ssl_setup, +                deltaTo: req.timestamp_start +            }, { +                title: "Client conn. established", +                t: cc.timestamp_start, +                deltaTo: req.timestamp_start +            }, { +                title: "Client conn. SSL handshake", +                t: cc.timestamp_ssl_setup, +                deltaTo: req.timestamp_start +            }, { +                title: "First request byte", +                t: req.timestamp_start, +            }, { +                title: "Request complete", +                t: req.timestamp_end, +                deltaTo: req.timestamp_start +            } +        ]; + +        if (flow.response) { +            timestamps.push( +                { +                    title: "First response byte", +                    t: resp.timestamp_start, +                    deltaTo: req.timestamp_start +                }, { +                    title: "Response complete", +                    t: resp.timestamp_end, +                    deltaTo: req.timestamp_start +                } +            ); +        } + +        //Add unique key for each row. +        timestamps.forEach(function (e) { +            e.key = e.title; +        }); + +        timestamps = _.sortBy(timestamps, 't'); + +        var rows = timestamps.map(function (e) { +            return <TimeStamp {...e}/>; +        }); + +        return ( +            <div> +                <h4>Timing</h4> +                <table className="timing-table"> +                    <tbody> +                    {rows} +                    </tbody> +                </table> +            </div> +        ); +    } +}); + +var Details = React.createClass({ +    render: function () { +        var flow = this.props.flow; +        var client_conn = flow.client_conn; +        var server_conn = flow.server_conn; +        return ( +            <section> + +                <h4>Client Connection</h4> +                <ConnectionInfo conn={client_conn}/> + +                <h4>Server Connection</h4> +                <ConnectionInfo conn={server_conn}/> + +                <CertificateInfo flow={flow}/> + +                <Timing flow={flow}/> + +            </section> +        ); +    } +}); + +module.exports = Details;
\ No newline at end of file diff --git a/web/src/js/components/flowview/index.js b/web/src/js/components/flowview/index.js new file mode 100644 index 00000000..0c31aca5 --- /dev/null +++ b/web/src/js/components/flowview/index.js @@ -0,0 +1,74 @@ +var React = require("react"); +var _ = require("lodash"); + +var common = require("../common.js"); +var Nav = require("./nav.js"); +var Messages = require("./messages.js"); +var Details = require("./details.js"); + +var allTabs = { +    request: Messages.Request, +    response: Messages.Response, +    error: Messages.Error, +    details: Details +}; + +var FlowView = React.createClass({ +    mixins: [common.StickyHeadMixin, common.Navigation, common.State], +    getTabs: function (flow) { +        var tabs = []; +        ["request", "response", "error"].forEach(function (e) { +            if (flow[e]) { +                tabs.push(e); +            } +        }); +        tabs.push("details"); +        return tabs; +    }, +    nextTab: function (i) { +        var tabs = this.getTabs(this.props.flow); +        var currentIndex = tabs.indexOf(this.getParams().detailTab); +        // JS modulo operator doesn't correct negative numbers, make sure that we are positive. +        var nextIndex = (currentIndex + i + tabs.length) % tabs.length; +        this.selectTab(tabs[nextIndex]); +    }, +    selectTab: function (panel) { +        this.replaceWith( +            "flow", +            { +                flowId: this.getParams().flowId, +                detailTab: panel +            } +        ); +    }, +    render: function () { +        var flow = this.props.flow; +        var tabs = this.getTabs(flow); +        var active = this.getParams().detailTab; + +        if (!_.contains(tabs, active)) { +            if (active === "response" && flow.error) { +                active = "error"; +            } else if (active === "error" && flow.response) { +                active = "response"; +            } else { +                active = tabs[0]; +            } +            this.selectTab(active); +        } + +        var Tab = allTabs[active]; +        return ( +            <div className="flow-detail" onScroll={this.adjustHead}> +                <Nav ref="head" +                    flow={flow} +                    tabs={tabs} +                    active={active} +                    selectTab={this.selectTab}/> +                <Tab flow={flow}/> +            </div> +        ); +    } +}); + +module.exports = FlowView;
\ No newline at end of file diff --git a/web/src/js/components/flowview/messages.js b/web/src/js/components/flowview/messages.js new file mode 100644 index 00000000..fe8fa200 --- /dev/null +++ b/web/src/js/components/flowview/messages.js @@ -0,0 +1,91 @@ +var React = require("react"); + +var flowutils = require("../../flow/utils.js"); +var utils = require("../../utils.js"); +var ContentView = require("./contentview.js"); + +var Headers = React.createClass({ +    render: function () { +        var rows = this.props.message.headers.map(function (header, i) { +            return ( +                <tr key={i}> +                    <td className="header-name">{header[0] + ":"}</td> +                    <td className="header-value">{header[1]}</td> +                </tr> +            ); +        }); +        return ( +            <table className="header-table"> +                <tbody> +                    {rows} +                </tbody> +            </table> +        ); +    } +}); + +var Request = React.createClass({ +    render: function () { +        var flow = this.props.flow; +        var first_line = [ +            flow.request.method, +            flowutils.RequestUtils.pretty_url(flow.request), +            "HTTP/" + flow.request.httpversion.join(".") +        ].join(" "); + +        //TODO: Styling + +        return ( +            <section> +                <div className="first-line">{ first_line }</div> +                <Headers message={flow.request}/> +                <hr/> +                <ContentView flow={flow} message={flow.request}/> +            </section> +        ); +    } +}); + +var Response = React.createClass({ +    render: function () { +        var flow = this.props.flow; +        var first_line = [ +            "HTTP/" + flow.response.httpversion.join("."), +            flow.response.code, +            flow.response.msg +        ].join(" "); + +        //TODO: Styling + +        return ( +            <section> +                <div className="first-line">{ first_line }</div> +                <Headers message={flow.response}/> +                <hr/> +                <ContentView flow={flow} message={flow.response}/> +            </section> +        ); +    } +}); + +var Error = React.createClass({ +    render: function () { +        var flow = this.props.flow; +        return ( +            <section> +                <div className="alert alert-warning"> +                {flow.error.msg} +                    <div> +                        <small>{ utils.formatTimeStamp(flow.error.timestamp) }</small> +                    </div> +                </div> +            </section> +        ); +    } +}); + +module.exports = { +    Request: Request, +    Response: Response, +    Error: Error +};
\ No newline at end of file diff --git a/web/src/js/components/flowview/nav.js b/web/src/js/components/flowview/nav.js new file mode 100644 index 00000000..46eda707 --- /dev/null +++ b/web/src/js/components/flowview/nav.js @@ -0,0 +1,61 @@ +var React = require("react"); + +var actions = require("../../actions.js"); + +var NavAction = React.createClass({ +    onClick: function (e) { +        e.preventDefault(); +        this.props.onClick(); +    }, +    render: function () { +        return ( +            <a title={this.props.title} +                href="#" +                className="nav-action" +                onClick={this.onClick}> +                <i className={"fa fa-fw " + this.props.icon}></i> +            </a> +        ); +    } +}); + +var Nav = React.createClass({ +    render: function () { +        var flow = this.props.flow; + +        var tabs = this.props.tabs.map(function (e) { +            var str = e.charAt(0).toUpperCase() + e.slice(1); +            var className = this.props.active === e ? "active" : ""; +            var onClick = function (event) { +                this.props.selectTab(e); +                event.preventDefault(); +            }.bind(this); +            return <a key={e} +                href="#" +                className={className} +                onClick={onClick}>{str}</a>; +        }.bind(this)); + +        var acceptButton = null; +        if(flow.intercepted){ +            acceptButton = <NavAction title="[a]ccept intercepted flow" icon="fa-play" onClick={actions.FlowActions.accept.bind(null, flow)} />; +        } +        var revertButton = null; +        if(flow.modified){ +            revertButton = <NavAction title="revert changes to flow [V]" icon="fa-history" onClick={actions.FlowActions.revert.bind(null, flow)} />; +        } + +        return ( +            <nav ref="head" className="nav-tabs nav-tabs-sm"> +                {tabs} +                <NavAction title="[d]elete flow" icon="fa-trash" onClick={actions.FlowActions.delete.bind(null, flow)} /> +                <NavAction title="[D]uplicate flow" icon="fa-copy" onClick={actions.FlowActions.duplicate.bind(null, flow)} /> +                <NavAction disabled title="[r]eplay flow" icon="fa-repeat" onClick={actions.FlowActions.replay.bind(null, flow)} /> +                {acceptButton} +                {revertButton} +            </nav> +        ); +    } +}); + +module.exports = Nav;
\ No newline at end of file diff --git a/web/src/js/components/header.js b/web/src/js/components/header.js index d1684fb2..eca06e74 100644 --- a/web/src/js/components/header.js +++ b/web/src/js/components/header.js @@ -173,7 +173,7 @@ var MainMenu = React.createClass({          this.setQuery(d);      },      onInterceptChange: function (val) { -        SettingsActions.update({intercept: val}); +        actions.SettingsActions.update({intercept: val});      },      render: function () {          var filter = this.getQuery()[Query.FILTER] || ""; diff --git a/web/src/js/components/mainview.js b/web/src/js/components/mainview.js index 184ef49f..81bf3b03 100644 --- a/web/src/js/components/mainview.js +++ b/web/src/js/components/mainview.js @@ -7,7 +7,7 @@ var toputils = require("../utils.js");  var views = require("../store/view.js");  var Filt = require("../filt/filt.js");  FlowTable = require("./flowtable.js"); -var flowdetail = require("./flowdetail.js"); +var FlowView = require("./flowview/index.js");  var MainView = React.createClass({      mixins: [common.Navigation, common.State], @@ -203,6 +203,8 @@ var MainView = React.createClass({                      actions.FlowActions.revert(flow);                  }                  break; +            case toputils.Key.SHIFT: +                break;              default:                  console.debug("keydown", e.keyCode);                  return; @@ -219,7 +221,7 @@ var MainView = React.createClass({          if (selected) {              details = [                  <common.Splitter key="splitter"/>, -                <flowdetail.FlowDetail key="flowDetails" ref="flowDetails" flow={selected}/> +                <FlowView key="flowDetails" ref="flowDetails" flow={selected}/>              ];          } else {              details = null;  | 
