diff options
author | Maximilian Hils <git@maximilianhils.com> | 2016-02-15 14:58:46 +0100 |
---|---|---|
committer | Maximilian Hils <git@maximilianhils.com> | 2016-02-15 14:58:46 +0100 |
commit | 33fa49277a821b9d38e8c9bf0bcf2adcfa2f6f04 (patch) | |
tree | 31914a601302579ff817504019296fd7e9e46765 /web/src/js/components/flowview | |
parent | 36f34f701991b5d474c005ec45e3b66e20f326a8 (diff) | |
download | mitmproxy-33fa49277a821b9d38e8c9bf0bcf2adcfa2f6f04.tar.gz mitmproxy-33fa49277a821b9d38e8c9bf0bcf2adcfa2f6f04.tar.bz2 mitmproxy-33fa49277a821b9d38e8c9bf0bcf2adcfa2f6f04.zip |
move mitmproxy
Diffstat (limited to 'web/src/js/components/flowview')
-rw-r--r-- | web/src/js/components/flowview/contentview.js | 237 | ||||
-rw-r--r-- | web/src/js/components/flowview/details.js | 181 | ||||
-rw-r--r-- | web/src/js/components/flowview/index.js | 127 | ||||
-rw-r--r-- | web/src/js/components/flowview/messages.js | 326 | ||||
-rw-r--r-- | web/src/js/components/flowview/nav.js | 61 |
5 files changed, 0 insertions, 932 deletions
diff --git a/web/src/js/components/flowview/contentview.js b/web/src/js/components/flowview/contentview.js deleted file mode 100644 index 63d22c1c..00000000 --- a/web/src/js/components/flowview/contentview.js +++ /dev/null @@ -1,237 +0,0 @@ -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 ViewImage = React.createClass({ - statics: { - matches: function (message) { - return image_regex.test(MessageUtils.getContentType(message)); - } - }, - render: function () { - var url = MessageUtils.getContentURL(this.props.flow, this.props.message); - return <div className="flowview-image"> - <img src={url} alt="preview" className="img-thumbnail"/> - </div>; - } -}); - -var RawMixin = { - getInitialState: function () { - return { - content: undefined, - request: undefined - } - }, - requestContent: function (nextProps) { - if (this.state.request) { - this.state.request.abort(); - } - var request = MessageUtils.getContent(nextProps.flow, nextProps.message); - this.setState({ - content: undefined, - request: request - }); - request.done(function (data) { - this.setState({content: data}); - }.bind(this)).fail(function (jqXHR, textStatus, errorThrown) { - if (textStatus === "abort") { - return; - } - this.setState({content: "AJAX Error: " + textStatus + "\r\n" + errorThrown}); - }.bind(this)).always(function () { - this.setState({request: undefined}); - }.bind(this)); - - }, - componentWillMount: function () { - this.requestContent(this.props); - }, - componentWillReceiveProps: function (nextProps) { - if (nextProps.message !== this.props.message) { - this.requestContent(nextProps); - } - }, - componentWillUnmount: function () { - if (this.state.request) { - this.state.request.abort(); - } - }, - render: function () { - if (!this.state.content) { - return <div className="text-center"> - <i className="fa fa-spinner fa-spin"></i> - </div>; - } - return this.renderContent(); - } -}; - -var ViewRaw = React.createClass({ - mixins: [RawMixin], - statics: { - matches: function (message) { - return true; - } - }, - renderContent: function () { - return <pre>{this.state.content}</pre>; - } -}); - -var json_regex = /^application\/json$/i; -var ViewJSON = React.createClass({ - mixins: [RawMixin], - statics: { - matches: function (message) { - return json_regex.test(MessageUtils.getContentType(message)); - } - }, - renderContent: function () { - var json = this.state.content; - try { - json = JSON.stringify(JSON.parse(json), null, 2); - } catch (e) { - } - return <pre>{json}</pre>; - } -}); - -var ViewAuto = 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 = ViewAuto.findView(this.props.message); - return <View {...this.props}/>; - } -}); - -var all = [ViewAuto, ViewImage, ViewJSON, ViewRaw]; - - -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({ - statics: { - isTooLarge: function (message) { - var max_mb = ViewImage.matches(message) ? 10 : 0.2; - return message.contentLength > 1024 * 1024 * max_mb; - } - }, - 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 === ViewAuto) { - text = "auto: " + ViewAuto.findView(this.props.message).displayName.toLowerCase().replace("view", ""); - } else { - text = view.displayName.toLowerCase().replace("view", ""); - } - 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: ViewAuto - }; - }, - 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 (!this.state.displayLarge && TooLarge.isTooLarge(message)) { - return <TooLarge {...this.props} onClick={this.displayLarge}/>; - } - - var downloadUrl = MessageUtils.getContentURL(this.props.flow, message); - - return <div> - <this.state.View {...this.props} /> - <div className="view-options text-center"> - <ViewSelector selectView={this.selectView} active={this.state.View} message={message}/> - - <a className="btn btn-default btn-xs" href={downloadUrl}> - <i className="fa fa-download"/> - </a> - </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 deleted file mode 100644 index 00e0116c..00000000 --- a/web/src/js/components/flowview/details.js +++ /dev/null @@ -1,181 +0,0 @@ -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 deleted file mode 100644 index 739a46dc..00000000 --- a/web/src/js/components/flowview/index.js +++ /dev/null @@ -1,127 +0,0 @@ -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 Prompt = require("../prompt.js"); - - -var allTabs = { - request: Messages.Request, - response: Messages.Response, - error: Messages.Error, - details: Details -}; - -var FlowView = React.createClass({ - mixins: [common.StickyHeadMixin, common.Navigation, common.RouterState], - getInitialState: function () { - return { - prompt: false - }; - }, - 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.getActive()); - // 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 - } - ); - }, - getActive: function(){ - return this.getParams().detailTab; - }, - promptEdit: function () { - var options; - switch(this.getActive()){ - case "request": - options = [ - "method", - "url", - {text:"http version", key:"v"}, - "header" - /*, "content"*/]; - break; - case "response": - options = [ - {text:"http version", key:"v"}, - "code", - "message", - "header" - /*, "content"*/]; - break; - case "details": - return; - default: - throw "Unknown tab for edit: " + this.getActive(); - } - - this.setState({ - prompt: { - done: function (k) { - this.setState({prompt: false}); - if(k){ - this.refs.tab.edit(k); - } - }.bind(this), - options: options - } - }); - }, - render: function () { - var flow = this.props.flow; - var tabs = this.getTabs(flow); - var active = this.getActive(); - - 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 prompt = null; - if (this.state.prompt) { - prompt = <Prompt {...this.state.prompt}/>; - } - - 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 ref="tab" flow={flow}/> - {prompt} - </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 deleted file mode 100644 index 7ac95d85..00000000 --- a/web/src/js/components/flowview/messages.js +++ /dev/null @@ -1,326 +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 utils = require("../../utils.js"); -var ContentView = require("./contentview.js"); -var ValueEditor = require("../editor.js").ValueEditor; - -var Headers = React.createClass({ - propTypes: { - onChange: React.PropTypes.func.isRequired, - message: React.PropTypes.object.isRequired - }, - onChange: function (row, col, val) { - var nextHeaders = _.cloneDeep(this.props.message.headers); - nextHeaders[row][col] = val; - if (!nextHeaders[row][0] && !nextHeaders[row][1]) { - // do not delete last row - if (nextHeaders.length === 1) { - nextHeaders[0][0] = "Name"; - nextHeaders[0][1] = "Value"; - } else { - nextHeaders.splice(row, 1); - // manually move selection target if this has been the last row. - if (row === nextHeaders.length) { - this._nextSel = (row - 1) + "-value"; - } - } - } - this.props.onChange(nextHeaders); - }, - edit: function () { - this.refs["0-key"].focus(); - }, - onTab: function (row, col, e) { - var headers = this.props.message.headers; - if (row === headers.length - 1 && col === 1) { - e.preventDefault(); - - var nextHeaders = _.cloneDeep(this.props.message.headers); - nextHeaders.push(["Name", "Value"]); - this.props.onChange(nextHeaders); - this._nextSel = (row + 1) + "-key"; - } - }, - componentDidUpdate: function () { - if (this._nextSel && this.refs[this._nextSel]) { - this.refs[this._nextSel].focus(); - this._nextSel = undefined; - } - }, - onRemove: function (row, col, e) { - if (col === 1) { - e.preventDefault(); - this.refs[row + "-key"].focus(); - } else if (row > 0) { - e.preventDefault(); - this.refs[(row - 1) + "-value"].focus(); - } - }, - render: function () { - - var rows = this.props.message.headers.map(function (header, i) { - - var kEdit = <HeaderEditor - ref={i + "-key"} - content={header[0]} - onDone={this.onChange.bind(null, i, 0)} - onRemove={this.onRemove.bind(null, i, 0)} - onTab={this.onTab.bind(null, i, 0)}/>; - var vEdit = <HeaderEditor - ref={i + "-value"} - content={header[1]} - onDone={this.onChange.bind(null, i, 1)} - onRemove={this.onRemove.bind(null, i, 1)} - onTab={this.onTab.bind(null, i, 1)}/>; - return ( - <tr key={i}> - <td className="header-name">{kEdit}:</td> - <td className="header-value">{vEdit}</td> - </tr> - ); - }.bind(this)); - return ( - <table className="header-table"> - <tbody> - {rows} - </tbody> - </table> - ); - } -}); - -var HeaderEditor = React.createClass({ - render: function () { - return <ValueEditor ref="input" {...this.props} onKeyDown={this.onKeyDown} inline/>; - }, - focus: function () { - this.getDOMNode().focus(); - }, - onKeyDown: function (e) { - switch (e.keyCode) { - case utils.Key.BACKSPACE: - var s = window.getSelection().getRangeAt(0); - if (s.startOffset === 0 && s.endOffset === 0) { - this.props.onRemove(e); - } - break; - case utils.Key.TAB: - if (!e.shiftKey) { - this.props.onTab(e); - } - break; - } - } -}); - -var RequestLine = React.createClass({ - render: function () { - var flow = this.props.flow; - var url = flowutils.RequestUtils.pretty_url(flow.request); - var httpver = flow.request.http_version; - - return <div className="first-line request-line"> - <ValueEditor - ref="method" - content={flow.request.method} - onDone={this.onMethodChange} - inline/> - - <ValueEditor - ref="url" - content={url} - onDone={this.onUrlChange} - isValid={this.isValidUrl} - inline/> - - <ValueEditor - ref="httpVersion" - content={httpver} - onDone={this.onHttpVersionChange} - isValid={flowutils.isValidHttpVersion} - inline/> - </div> - }, - isValidUrl: function (url) { - var u = flowutils.parseUrl(url); - return !!u.host; - }, - onMethodChange: function (nextMethod) { - actions.FlowActions.update( - this.props.flow, - {request: {method: nextMethod}} - ); - }, - onUrlChange: function (nextUrl) { - var props = flowutils.parseUrl(nextUrl); - props.path = props.path || ""; - actions.FlowActions.update( - this.props.flow, - {request: props} - ); - }, - onHttpVersionChange: function (nextVer) { - var ver = flowutils.parseHttpVersion(nextVer); - actions.FlowActions.update( - this.props.flow, - {request: {http_version: ver}} - ); - } -}); - -var ResponseLine = React.createClass({ - render: function () { - var flow = this.props.flow; - var httpver = flow.response.http_version; - return <div className="first-line response-line"> - <ValueEditor - ref="httpVersion" - content={httpver} - onDone={this.onHttpVersionChange} - isValid={flowutils.isValidHttpVersion} - inline/> - - <ValueEditor - ref="code" - content={flow.response.status_code + ""} - onDone={this.onCodeChange} - isValid={this.isValidCode} - inline/> - - <ValueEditor - ref="msg" - content={flow.response.msg} - onDone={this.onMsgChange} - inline/> - </div>; - }, - isValidCode: function (code) { - return /^\d+$/.test(code); - }, - onHttpVersionChange: function (nextVer) { - var ver = flowutils.parseHttpVersion(nextVer); - actions.FlowActions.update( - this.props.flow, - {response: {http_version: ver}} - ); - }, - onMsgChange: function (nextMsg) { - actions.FlowActions.update( - this.props.flow, - {response: {msg: nextMsg}} - ); - }, - onCodeChange: function (nextCode) { - nextCode = parseInt(nextCode); - actions.FlowActions.update( - this.props.flow, - {response: {code: nextCode}} - ); - } -}); - -var Request = React.createClass({ - render: function () { - var flow = this.props.flow; - return ( - <section className="request"> - <RequestLine ref="requestLine" flow={flow}/> - {/*<ResponseLine flow={flow}/>*/} - <Headers ref="headers" message={flow.request} onChange={this.onHeaderChange}/> - <hr/> - <ContentView flow={flow} message={flow.request}/> - </section> - ); - }, - edit: function (k) { - switch (k) { - case "m": - this.refs.requestLine.refs.method.focus(); - break; - case "u": - this.refs.requestLine.refs.url.focus(); - break; - case "v": - this.refs.requestLine.refs.httpVersion.focus(); - break; - case "h": - this.refs.headers.edit(); - break; - default: - throw "Unimplemented: " + k; - } - }, - onHeaderChange: function (nextHeaders) { - actions.FlowActions.update(this.props.flow, { - request: { - headers: nextHeaders - } - }); - } -}); - -var Response = React.createClass({ - render: function () { - var flow = this.props.flow; - return ( - <section className="response"> - {/*<RequestLine flow={flow}/>*/} - <ResponseLine ref="responseLine" flow={flow}/> - <Headers ref="headers" message={flow.response} onChange={this.onHeaderChange}/> - <hr/> - <ContentView flow={flow} message={flow.response}/> - </section> - ); - }, - edit: function (k) { - switch (k) { - case "c": - this.refs.responseLine.refs.status_code.focus(); - break; - case "m": - this.refs.responseLine.refs.msg.focus(); - break; - case "v": - this.refs.responseLine.refs.httpVersion.focus(); - break; - case "h": - this.refs.headers.edit(); - break; - default: - throw "Unimplemented: " + k; - } - }, - onHeaderChange: function (nextHeaders) { - actions.FlowActions.update(this.props.flow, { - response: { - headers: nextHeaders - } - }); - } -}); - -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 -}; diff --git a/web/src/js/components/flowview/nav.js b/web/src/js/components/flowview/nav.js deleted file mode 100644 index 46eda707..00000000 --- a/web/src/js/components/flowview/nav.js +++ /dev/null @@ -1,61 +0,0 @@ -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 |