diff options
Diffstat (limited to 'web')
-rw-r--r-- | web/.babelrc | 2 | ||||
-rw-r--r-- | web/README | 2 | ||||
-rw-r--r-- | web/package.json | 1 | ||||
-rw-r--r-- | web/src/css/header.less | 18 | ||||
-rw-r--r-- | web/src/js/components/common.js | 84 | ||||
-rw-r--r-- | web/src/js/components/eventlog.js | 4 | ||||
-rw-r--r-- | web/src/js/components/flowview/contentview.js | 64 | ||||
-rw-r--r-- | web/src/js/components/flowview/index.js | 6 | ||||
-rw-r--r-- | web/src/js/components/footer.js | 32 | ||||
-rw-r--r-- | web/src/js/components/header.js | 133 | ||||
-rw-r--r-- | web/src/js/components/mainview.js | 13 | ||||
-rw-r--r-- | web/src/js/components/proxyapp.js | 57 |
12 files changed, 272 insertions, 144 deletions
diff --git a/web/.babelrc b/web/.babelrc index 5dd7708c..e2d67e33 100644 --- a/web/.babelrc +++ b/web/.babelrc @@ -1,4 +1,4 @@ { "presets": ["es2015", "react"], - "plugins": ["transform-class-properties"] + "plugins": ["transform-class-properties", "transform-object-rest-spread"] }
\ No newline at end of file @@ -3,4 +3,4 @@ Starting up - npm install - gulp -- run mitmweb and open http://localhost:8081/
\ No newline at end of file +- run mitmweb and open http://localhost:8081/ diff --git a/web/package.json b/web/package.json index 2eaac445..88e976e1 100644 --- a/web/package.json +++ b/web/package.json @@ -30,6 +30,7 @@ "babel-core": "^6.7.7", "babel-jest": "^12.0.2", "babel-plugin-transform-class-properties": "^6.6.0", + "babel-plugin-transform-object-rest-spread": "^6.8.0", "babel-preset-es2015": "^6.6.0", "babel-preset-react": "^6.5.0", "babelify": "^7.3.0", diff --git a/web/src/css/header.less b/web/src/css/header.less index 065471d1..b1bd9c04 100644 --- a/web/src/css/header.less +++ b/web/src/css/header.less @@ -18,6 +18,7 @@ header { .filter-input { .make-sm-column(3, @menu-row-gutter-width); + margin-bottom:5px; } .filter-input .popover { @@ -32,6 +33,19 @@ header { } } -.menu .btn { - margin: 2px 2px 2px 2px; +.menu .toggle-btn { + .make-xs-column(4, @menu-row-gutter-width); + .make-sm-column(3, @menu-row-gutter-width); + .make-lg-column(2, @menu-row-gutter-width); + margin-bottom:5px; +} + +.menu .toggle-btn .btn { + width: 100%; +} + +.menu .toggle-input-btn { + .make-sm-column(6, @menu-row-gutter-width); + .make-lg-column(4, @menu-row-gutter-width); + margin-bottom:5px; }
\ No newline at end of file diff --git a/web/src/js/components/common.js b/web/src/js/components/common.js index 21ca454f..87c34ffc 100644 --- a/web/src/js/components/common.js +++ b/web/src/js/components/common.js @@ -1,33 +1,8 @@ import React from "react" import ReactDOM from "react-dom" +import {Key} from "../utils.js"; import _ from "lodash" -export var Router = { - contextTypes: { - location: React.PropTypes.object, - router: React.PropTypes.object.isRequired - }, - updateLocation: function (pathname, queryUpdate) { - if (pathname === undefined) { - pathname = this.context.location.pathname; - } - var query = this.context.location.query; - if (queryUpdate !== undefined) { - for (var i in queryUpdate) { - if (queryUpdate.hasOwnProperty(i)) { - query[i] = queryUpdate[i] || undefined; //falsey values shall be removed. - } - } - } - this.context.router.replace({pathname, query}); - }, - getQuery: function () { - // For whatever reason, react-router always returns the same object, which makes comparing - // the current props with nextProps impossible. As a workaround, we just clone the query object. - return _.clone(this.context.location.query); - } -}; - export var Splitter = React.createClass({ getDefaultProps: function () { return { @@ -133,14 +108,55 @@ export var Splitter = React.createClass({ } }); -export const ToggleComponent = (props) => - <div - className={"btn " + (props.checked ? "btn-primary" : "btn-default")} - onClick={props.onToggleChanged}> - <span><i className={"fa " + (props.checked ? "fa-check-square-o" : "fa-square-o")}></i> {props.name}</span> - </div> +export const ToggleButton = (props) => + <div className="input-group toggle-btn"> + <div + className={"btn " + (props.checked ? "btn-primary" : "btn-default")} + onClick={props.onToggleChanged}> + <span className={"fa " + (props.checked ? "fa-check-square-o" : "fa-square-o")}> {props.name}</span> + </div> + </div>; + +ToggleButton.propTypes = { + name: React.PropTypes.string.isRequired, + onToggleChanged: React.PropTypes.func.isRequired +}; + +export class ToggleInputButton extends React.Component { + constructor(props) { + super(props); + this.state = {txt: props.txt}; + } -ToggleComponent.propTypes = { + render() { + return ( + <div className="input-group toggle-input-btn"> + <span + className="input-group-btn" + onClick={() => this.props.onToggleChanged(this.state.txt)}> + <div className={"btn " + (this.props.checked ? "btn-primary" : "btn-default")}> + <span className={"fa " + (this.props.checked ? "fa-check-square-o" : "fa-square-o")}/> + {this.props.name} + </div> + </span> + <input + className="form-control" + placeholder={this.props.placeholder} + disabled={this.props.checked} + value={this.state.txt} + type={this.props.inputType} + onChange={e => this.setState({txt: e.target.value})} + onKeyDown={e => {if (e.keyCode === Key.ENTER) this.props.onToggleChanged(this.state.txt); e.stopPropagation()}}/> + </div> + ); + } +} + +ToggleInputButton.propTypes = { name: React.PropTypes.string.isRequired, + txt: React.PropTypes.string.isRequired, onToggleChanged: React.PropTypes.func.isRequired -}
\ No newline at end of file +}; + + + diff --git a/web/src/js/components/eventlog.js b/web/src/js/components/eventlog.js index d1b23ace..6e4f9096 100644 --- a/web/src/js/components/eventlog.js +++ b/web/src/js/components/eventlog.js @@ -1,7 +1,6 @@ import React from "react" import ReactDOM from "react-dom" import shallowEqual from "shallowequal" -import {Router} from "./common.js" import {Query} from "../actions.js" import AutoScroll from "./helpers/AutoScroll"; import {calcVScroll} from "./helpers/VirtualScroll" @@ -144,7 +143,6 @@ function ToggleFilter ({ name, active, toggleLevel }) { const AutoScrollEventLog = AutoScroll(EventLogContents); var EventLog = React.createClass({ - mixins: [Router], getInitialState() { return { filter: { @@ -157,7 +155,7 @@ var EventLog = React.createClass({ close() { var d = {}; d[Query.SHOW_EVENTLOG] = undefined; - this.updateLocation(undefined, d); + this.props.updateLocation(undefined, d); }, toggleLevel(level) { var filter = _.extend({}, this.state.filter); diff --git a/web/src/js/components/flowview/contentview.js b/web/src/js/components/flowview/contentview.js index 2743eec3..cbac9a75 100644 --- a/web/src/js/components/flowview/contentview.js +++ b/web/src/js/components/flowview/contentview.js @@ -4,11 +4,15 @@ import _ from "lodash"; import {MessageUtils} from "../../flow/utils.js"; import {formatSize} from "../../utils.js"; -var image_regex = /^image\/(png|jpe?g|gif|vnc.microsoft.icon|x-icon)$/i; var ViewImage = React.createClass({ + propTypes: { + flow: React.PropTypes.object.isRequired, + message: React.PropTypes.object.isRequired, + }, statics: { + regex: /^image\/(png|jpe?g|gif|vnc.microsoft.icon|x-icon)$/i, matches: function (message) { - return image_regex.test(MessageUtils.getContentType(message)); + return ViewImage.regex.test(MessageUtils.getContentType(message)); } }, render: function () { @@ -19,7 +23,11 @@ var ViewImage = React.createClass({ } }); -var RawMixin = { +var ContentLoader = React.createClass({ + propTypes: { + flow: React.PropTypes.object.isRequired, + message: React.PropTypes.object.isRequired, + }, getInitialState: function () { return { content: undefined, @@ -66,41 +74,54 @@ var RawMixin = { <i className="fa fa-spinner fa-spin"></i> </div>; } - return this.renderContent(); + return React.cloneElement(this.props.children, { + content: this.state.content + }) } -}; +}); var ViewRaw = React.createClass({ - mixins: [RawMixin], + propTypes: { + content: React.PropTypes.string.isRequired, + }, statics: { + textView: true, matches: function (message) { return true; } }, - renderContent: function () { - return <pre>{this.state.content}</pre>; + render: function () { + return <pre>{this.props.content}</pre>; } }); -var json_regex = /^application\/json$/i; var ViewJSON = React.createClass({ - mixins: [RawMixin], + propTypes: { + content: React.PropTypes.string.isRequired, + }, statics: { + textView: true, + regex: /^application\/json$/i, matches: function (message) { - return json_regex.test(MessageUtils.getContentType(message)); + return ViewJSON.regex.test(MessageUtils.getContentType(message)); } }, - renderContent: function () { - var json = this.state.content; + render: function () { + var json = this.props.content; try { json = JSON.stringify(JSON.parse(json), null, 2); } catch (e) { + // @noop } return <pre>{json}</pre>; } }); var ViewAuto = React.createClass({ + propTypes: { + message: React.PropTypes.object.isRequired, + flow: React.PropTypes.object.isRequired, + }, statics: { matches: function () { return false; // don't match itself @@ -115,14 +136,18 @@ var ViewAuto = React.createClass({ } }, render: function () { + var { message, flow } = this.props var View = ViewAuto.findView(this.props.message); - return <View {...this.props}/>; + if (View.textView) { + return <ContentLoader message={message} flow={flow}><View content="" /></ContentLoader> + } else { + return <View message={message} flow={flow} /> + } } }); var all = [ViewAuto, ViewImage, ViewJSON, ViewRaw]; - var ContentEmpty = React.createClass({ render: function () { var message_name = this.props.flow.request === this.props.message ? "request" : "response"; @@ -210,6 +235,7 @@ var ContentView = React.createClass({ } }, render: function () { + var { flow, message } = this.props var message = this.props.message; if (message.contentLength === 0) { return <ContentEmpty {...this.props}/>; @@ -222,7 +248,11 @@ var ContentView = React.createClass({ var downloadUrl = MessageUtils.getContentURL(this.props.flow, message); return <div> - <this.state.View {...this.props} /> + {this.state.View.textView ? ( + <ContentLoader flow={flow} message={message}><this.state.View content="" /></ContentLoader> + ) : ( + <this.state.View flow={flow} message={message} /> + )} <div className="view-options text-center"> <ViewSelector selectView={this.selectView} active={this.state.View} message={message}/> @@ -234,4 +264,4 @@ var ContentView = React.createClass({ } }); -export default ContentView;
\ No newline at end of file +export default ContentView; diff --git a/web/src/js/components/flowview/index.js b/web/src/js/components/flowview/index.js index 47531f58..6f4f7395 100644 --- a/web/src/js/components/flowview/index.js +++ b/web/src/js/components/flowview/index.js @@ -1,6 +1,5 @@ import React from "react"; -import {Router, StickyHeadMixin} from "../common.js" import Nav from "./nav.js"; import {Request, Response, Error} from "./messages.js"; import Details from "./details.js"; @@ -15,7 +14,6 @@ var allTabs = { }; var FlowView = React.createClass({ - mixins: [StickyHeadMixin, Router], getInitialState: function () { return { prompt: false @@ -39,7 +37,7 @@ var FlowView = React.createClass({ this.selectTab(tabs[nextIndex]); }, selectTab: function (panel) { - this.updateLocation(`/flows/${this.props.flow.id}/${panel}`); + this.props.updateLocation(`/flows/${this.props.flow.id}/${panel}`); }, promptEdit: function () { var options; @@ -114,4 +112,4 @@ var FlowView = React.createClass({ } }); -export default FlowView;
\ No newline at end of file +export default FlowView; diff --git a/web/src/js/components/footer.js b/web/src/js/components/footer.js index e2d96288..8fe1081b 100644 --- a/web/src/js/components/footer.js +++ b/web/src/js/components/footer.js @@ -1,4 +1,5 @@ import React from "react"; +import {formatSize} from "../utils.js" import {SettingsState} from "./common.js"; Footer.propTypes = { @@ -6,7 +7,7 @@ Footer.propTypes = { }; export default function Footer({ settings }) { - const {mode, intercept} = settings; + const {mode, intercept, showhost, no_upstream_cert, rawtcp, http2, anticache, anticomp, stickyauth, stickycookie, stream} = settings; return ( <footer> {mode && mode != "regular" && ( @@ -15,6 +16,35 @@ export default function Footer({ settings }) { {intercept && ( <span className="label label-success">Intercept: {intercept}</span> )} + {showhost && ( + <span className="label label-success">showhost</span> + )} + {no_upstream_cert && ( + <span className="label label-success">no-upstream-cert</span> + )} + {rawtcp && ( + <span className="label label-success">raw-tcp</span> + )} + {!http2 && ( + <span className="label label-success">no-http2</span> + )} + {anticache && ( + <span className="label label-success">anticache</span> + )} + {anticomp && ( + <span className="label label-success">anticomp</span> + )} + {stickyauth && ( + <span className="label label-success">stickyauth: {stickyauth}</span> + )} + {stickycookie && ( + <span className="label label-success">stickycookie: {stickycookie}</span> + )} + {stream && ( + <span className="label label-success">stream: {formatSize(stream)}</span> + )} + + </footer> ); } diff --git a/web/src/js/components/header.js b/web/src/js/components/header.js index 226cb61f..643659c3 100644 --- a/web/src/js/components/header.js +++ b/web/src/js/components/header.js @@ -4,9 +4,10 @@ import $ from "jquery"; import Filt from "../filt/filt.js"; import {Key} from "../utils.js"; -import {Router, ToggleComponent} from "./common.js"; +import {ToggleInputButton, ToggleButton} from "./common.js"; import {SettingsActions, FlowActions} from "../actions.js"; import {Query} from "../actions.js"; +import {SettingsState} from "./common.js"; var FilterDocs = React.createClass({ statics: { @@ -161,7 +162,6 @@ var FilterInput = React.createClass({ }); export var MainMenu = React.createClass({ - mixins: [Router], propTypes: { settings: React.PropTypes.object.isRequired, }, @@ -172,19 +172,19 @@ export var MainMenu = React.createClass({ onSearchChange: function (val) { var d = {}; d[Query.SEARCH] = val; - this.updateLocation(undefined, d); + this.props.updateLocation(undefined, d); }, onHighlightChange: function (val) { var d = {}; d[Query.HIGHLIGHT] = val; - this.updateLocation(undefined, d); + this.props.updateLocation(undefined, d); }, onInterceptChange: function (val) { SettingsActions.update({intercept: val}); }, render: function () { - var search = this.getQuery()[Query.SEARCH] || ""; - var highlight = this.getQuery()[Query.HIGHLIGHT] || ""; + var search = this.props.query[Query.SEARCH] || ""; + var highlight = this.props.query[Query.HIGHLIGHT] || ""; var intercept = this.props.settings.intercept || ""; return ( @@ -224,72 +224,88 @@ var ViewMenu = React.createClass({ title: "View", route: "flows" }, - mixins: [Router], toggleEventLog: function () { var d = {}; - if (this.getQuery()[Query.SHOW_EVENTLOG]) { + if (this.props.query[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.updateLocation(undefined, d); + this.props.updateLocation(undefined, d); console.log('toggleevent'); }, render: function () { - var showEventLog = this.getQuery()[Query.SHOW_EVENTLOG]; + var showEventLog = this.props.query[Query.SHOW_EVENTLOG]; return ( - <div> - <ToggleComponent - checked={showEventLog} - name = "Show Eventlog" - onToggleChanged={this.toggleEventLog}/> - </div> + <div> + <div className="menu-row"> + <ToggleButton + checked={showEventLog} + name = "Show Eventlog" + onToggleChanged={this.toggleEventLog}/> + </div> + <div className="clearfix"></div> + </div> ); } }); - -class OptionMenu extends React.Component{ - static title = "Options"; - constructor(props){ - super(props); - this.state = { - options : - [ - {name: "--host", checked: true}, - {name: "--no-upstream-cert", checked: false}, - {name: "--http2", checked: false}, - {name: "--anticache", checked: false}, - {name: "--anticomp", checked: false}, - {name: "--stickycookie", checked: true}, - {name: "--stickyauth", checked: false}, - {name: "--stream", checked: false} - ] - } - } - setOption(entry){ - console.log(entry.name);//TODO: get options from outside and remove state - entry.checked = !entry.checked; - this.setState({options: this.state.options}); - } - render() { - return ( +export const OptionMenu = (props) => { + const {mode, intercept, showhost, no_upstream_cert, rawtcp, http2, anticache, anticomp, stickycookie, stickyauth, stream} = props.settings; + return ( <div> - {this.state.options.map((entry, i) => { - return ( - <ToggleComponent - key={i} - checked={entry.checked} - name = {entry.name} - onToggleChanged={() => this.setOption(entry)}/> - ); - })} + <div className="menu-row"> + <ToggleButton name="showhost" + checked={showhost} + onToggleChanged={() => SettingsActions.update({showhost: !showhost})} + /> + <ToggleButton name="no_upstream_cert" + checked={no_upstream_cert} + onToggleChanged={() => SettingsActions.update({no_upstream_cert: !no_upstream_cert})} + /> + <ToggleButton name="rawtcp" + checked={rawtcp} + onToggleChanged={() => SettingsActions.update({rawtcp: !rawtcp})} + /> + <ToggleButton name="http2" + checked={http2} + onToggleChanged={() => SettingsActions.update({http2: !http2})} + /> + <ToggleButton name="anticache" + checked={anticache} + onToggleChanged={() => SettingsActions.update({anticache: !anticache})} + /> + <ToggleButton name="anticomp" + checked={anticomp} + onToggleChanged={() => SettingsActions.update({anticomp: !anticomp})} + /> + <ToggleInputButton name="stickyauth" placeholder="Sticky auth filter" + checked={Boolean(stickyauth)} + txt={stickyauth || ""} + onToggleChanged={txt => SettingsActions.update({stickyauth: (!stickyauth ? txt : null)})} + /> + <ToggleInputButton name="stickycookie" placeholder="Sticky cookie filter" + checked={Boolean(stickycookie)} + txt={stickycookie || ""} + onToggleChanged={txt => SettingsActions.update({stickycookie: (!stickycookie ? txt : null)})} + /> + <ToggleInputButton name="stream" placeholder="stream..." + checked={Boolean(stream)} + txt={stream || ""} + inputType = "number" + onToggleChanged={txt => SettingsActions.update({stream: (!stream ? txt : null)})} + /> + </div> + <div className="clearfix"/> </div> - ); - } -} + ); +}; +OptionMenu.title = "Options"; +OptionMenu.propTypes = { + settings: React.PropTypes.object.isRequired +}; var ReportsMenu = React.createClass({ statics: { @@ -391,7 +407,6 @@ var header_entries = [MainMenu, ViewMenu, OptionMenu /*, ReportsMenu */]; export var Header = React.createClass({ - mixins: [Router], propTypes: { settings: React.PropTypes.object.isRequired, }, @@ -402,7 +417,7 @@ export var Header = React.createClass({ }, handleClick: function (active, e) { e.preventDefault(); - this.updateLocation(active.route); + this.props.updateLocation(active.route); this.setState({active: active}); }, render: function () { @@ -430,7 +445,11 @@ export var Header = React.createClass({ {header} </nav> <div className="menu"> - <this.state.active ref="active" settings={this.props.settings}/> + <this.state.active + settings={this.props.settings} + updateLocation={this.props.updateLocation} + query={this.props.query} + /> </div> </header> ); diff --git a/web/src/js/components/mainview.js b/web/src/js/components/mainview.js index 87c0c4bd..964e82db 100644 --- a/web/src/js/components/mainview.js +++ b/web/src/js/components/mainview.js @@ -5,12 +5,11 @@ import {Query} from "../actions.js"; import {Key} from "../utils.js"; import {StoreView} from "../store/view.js"; import Filt from "../filt/filt.js"; -import { Router, Splitter} from "./common.js" +import {Splitter} from "./common.js" import FlowTable from "./flowtable.js"; import FlowView from "./flowview/index.js"; var MainView = React.createClass({ - mixins: [Router], contextTypes: { flowStore: React.PropTypes.object.isRequired, }, @@ -41,9 +40,9 @@ var MainView = React.createClass({ }, getViewFilt: function () { try { - var filtStr = this.getQuery()[Query.SEARCH]; + var filtStr = this.props.query[Query.SEARCH]; var filt = filtStr ? Filt.parse(filtStr) : () => true; - var highlightStr = this.getQuery()[Query.HIGHLIGHT]; + var highlightStr = this.props.query[Query.HIGHLIGHT]; var highlight = highlightStr ? Filt.parse(highlightStr) : () => false; } catch (e) { console.error("Error when processing filter: " + e); @@ -94,10 +93,10 @@ var MainView = React.createClass({ selectFlow: function (flow) { if (flow) { var tab = this.props.routeParams.detailTab || "request"; - this.updateLocation(`/flows/${flow.id}/${tab}`); + this.props.updateLocation(`/flows/${flow.id}/${tab}`); this.refs.flowTable.scrollIntoView(flow); } else { - this.updateLocation("/flows"); + this.props.updateLocation("/flows"); } }, selectFlowRelative: function (shift) { @@ -225,6 +224,8 @@ var MainView = React.createClass({ key="flowDetails" ref="flowDetails" tab={this.props.routeParams.detailTab} + query={this.props.query} + updateLocation={this.props.updateLocation} flow={selected}/> ]; } else { diff --git a/web/src/js/components/proxyapp.js b/web/src/js/components/proxyapp.js index d17a1522..f47c5bb4 100644 --- a/web/src/js/components/proxyapp.js +++ b/web/src/js/components/proxyapp.js @@ -2,7 +2,7 @@ import React from "react"; import ReactDOM from "react-dom"; import _ from "lodash"; -import {Router, Splitter} from "./common.js" +import {Splitter} from "./common.js" import MainView from "./mainview.js"; import Footer from "./footer.js"; import {Header, MainMenu} from "./header.js"; @@ -21,13 +21,34 @@ var Reports = React.createClass({ var ProxyAppMain = React.createClass({ - mixins: [Router], childContextTypes: { flowStore: React.PropTypes.object.isRequired, eventStore: React.PropTypes.object.isRequired, returnFocus: React.PropTypes.func.isRequired, location: React.PropTypes.object.isRequired, }, + contextTypes: { + router: React.PropTypes.object.isRequired + }, + updateLocation: function (pathname, queryUpdate) { + if (pathname === undefined) { + pathname = this.props.location.pathname; + } + var query = this.props.location.query; + if (queryUpdate !== undefined) { + for (var i in queryUpdate) { + if (queryUpdate.hasOwnProperty(i)) { + query[i] = queryUpdate[i] || undefined; //falsey values shall be removed. + } + } + } + this.context.router.replace({pathname, query}); + }, + getQuery: function () { + // For whatever reason, react-router always returns the same object, which makes comparing + // the current props with nextProps impossible. As a workaround, we just clone the query object. + return _.clone(this.props.location.query); + }, componentDidMount: function () { this.focus(); this.settingsStore.addListener("recalculate", this.onSettingsChange); @@ -97,23 +118,23 @@ var ProxyAppMain = React.createClass({ e.preventDefault(); }, render: function () { + var query = this.getQuery(); var eventlog; if (this.props.location.query[Query.SHOW_EVENTLOG]) { eventlog = [ <Splitter key="splitter" axis="y"/>, - <EventLog key="eventlog"/> + <EventLog key="eventlog" updateLocation={this.updateLocation}/> ]; } else { eventlog = null; } - var children = React.cloneElement( - this.props.children, - { ref: "view", location: this.props.location } - ); return ( <div id="container" tabIndex="0" onKeyDown={this.onKeydown}> - <Header ref="header" settings={this.state.settings}/> - {children} + <Header ref="header" settings={this.state.settings} updateLocation={this.updateLocation} query={query} /> + {React.cloneElement( + this.props.children, + { ref: "view", location: this.props.location , updateLocation: this.updateLocation, query } + )} {eventlog} <Footer settings={this.state.settings}/> </div> @@ -125,12 +146,12 @@ var ProxyAppMain = React.createClass({ import { Route, Router as ReactRouter, hashHistory, Redirect} from "react-router"; export var app = ( -<ReactRouter history={hashHistory}> - <Redirect from="/" to="/flows" /> - <Route path="/" component={ProxyAppMain}> - <Route path="flows" component={MainView}/> - <Route path="flows/:flowId/:detailTab" component={MainView}/> - <Route path="reports" component={Reports}/> - </Route> -</ReactRouter> -);
\ No newline at end of file + <ReactRouter history={hashHistory}> + <Redirect from="/" to="/flows" /> + <Route path="/" component={ProxyAppMain}> + <Route path="flows" component={MainView}/> + <Route path="flows/:flowId/:detailTab" component={MainView}/> + <Route path="reports" component={Reports}/> + </Route> + </ReactRouter> +); |