aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMaximilian Hils <git@maximilianhils.com>2014-12-25 02:03:55 +0100
committerMaximilian Hils <git@maximilianhils.com>2014-12-25 02:03:55 +0100
commit7ed6f10e357ef7b08fc94b46a0901218e62f418e (patch)
tree02f1d8b9401643f82c715c6cc55c9ebb1b36eaf2
parentbd1c04ac56c57e13edb2e20aeea7226ab554f516 (diff)
downloadmitmproxy-7ed6f10e357ef7b08fc94b46a0901218e62f418e.tar.gz
mitmproxy-7ed6f10e357ef7b08fc94b46a0901218e62f418e.tar.bz2
mitmproxy-7ed6f10e357ef7b08fc94b46a0901218e62f418e.zip
web: much ui work, such christmas
-rw-r--r--libmproxy/protocol/http.py3
-rw-r--r--libmproxy/web/app.py1
-rw-r--r--libmproxy/web/static/css/app.css17
-rw-r--r--libmproxy/web/static/js/app.js201
-rw-r--r--web/src/css/flowtable.less153
-rw-r--r--web/src/css/tabs.less6
-rw-r--r--web/src/js/components/flowdetail.jsx.js79
-rw-r--r--web/src/js/components/flowtable-columns.jsx.js6
-rw-r--r--web/src/js/components/header.jsx.js103
-rw-r--r--web/src/js/components/mainview.jsx.js13
10 files changed, 400 insertions, 182 deletions
diff --git a/libmproxy/protocol/http.py b/libmproxy/protocol/http.py
index c6e67498..da7c4240 100644
--- a/libmproxy/protocol/http.py
+++ b/libmproxy/protocol/http.py
@@ -309,7 +309,8 @@ class HTTPRequest(HTTPMessage):
host=str,
port=int,
path=str,
- form_out=str
+ form_out=str,
+ is_replay=bool
)
@classmethod
diff --git a/libmproxy/web/app.py b/libmproxy/web/app.py
index c90922cb..d3c44bf7 100644
--- a/libmproxy/web/app.py
+++ b/libmproxy/web/app.py
@@ -112,6 +112,7 @@ class Settings(RequestHandler):
def get(self):
self.write(dict(
data=dict(
+ mode=str(self.master.server.config.mode),
intercept=self.state.intercept_txt
)
))
diff --git a/libmproxy/web/static/css/app.css b/libmproxy/web/static/css/app.css
index 93ace709..2ec275a3 100644
--- a/libmproxy/web/static/css/app.css
+++ b/libmproxy/web/static/css/app.css
@@ -122,6 +122,11 @@ body,
padding: 0px 7px;
margin: 2px 2px -1px;
}
+.nav-tabs-sm a.nav-action {
+ float: right;
+ padding: 0;
+ margin: 1px 0 0px;
+}
header {
background-color: white;
}
@@ -183,6 +188,12 @@ header .menu {
.flow-table tr.intercepted.has-response .col-time {
color: #ff8000;
}
+.flow-table .fa {
+ line-height: inherit;
+}
+.flow-table .fa.pull-right {
+ margin-left: 0;
+}
.flow-table .col-tls {
width: 10px;
}
@@ -192,6 +203,12 @@ header .menu {
.flow-table .col-icon {
width: 32px;
}
+.flow-table .col-path .fa-repeat {
+ color: green;
+}
+.flow-table .col-path .fa-pause {
+ color: #ff8000;
+}
.flow-table .col-method {
width: 60px;
}
diff --git a/libmproxy/web/static/js/app.js b/libmproxy/web/static/js/app.js
index ff961294..92f48d14 100644
--- a/libmproxy/web/static/js/app.js
+++ b/libmproxy/web/static/js/app.js
@@ -2742,23 +2742,9 @@ var FilterInput = React.createClass({displayName: 'FilterInput',
var MainMenu = React.createClass({displayName: 'MainMenu',
mixins: [Navigation, State],
statics: {
- title: "Traffic",
+ title: "Start",
route: "flows"
},
- 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);
- },
- clearFlows: function () {
- FlowActions.clear();
- },
onFilterChange: function (val) {
var d = {};
d[Query.FILTER] = val;
@@ -2776,22 +2762,9 @@ var MainMenu = React.createClass({displayName: 'MainMenu',
var filter = this.getQuery()[Query.FILTER] || "";
var highlight = this.getQuery()[Query.HIGHLIGHT] || "";
var intercept = this.props.settings.intercept || "";
- var showEventLog = this.getQuery()[Query.SHOW_EVENTLOG];
return (
React.createElement("div", null,
- React.createElement("button", {
- className: "btn " + (showEventLog ? "btn-primary" : "btn-default"),
- onClick: this.toggleEventLog},
- React.createElement("i", {className: "fa fa-database"}),
- " Display Event Log"
- ),
- React.createElement("span", null, " "),
- React.createElement("button", {className: "btn btn-default", onClick: this.clearFlows},
- React.createElement("i", {className: "fa fa-eraser"}),
- " Clear Flows"
- ),
- React.createElement("span", null, " "),
React.createElement("form", {className: "form-inline", style: {display: "inline"}},
React.createElement(FilterInput, {
placeholder: "Filter",
@@ -2820,13 +2793,36 @@ var MainMenu = React.createClass({displayName: 'MainMenu',
});
-var ToolsMenu = React.createClass({displayName: 'ToolsMenu',
+var ViewMenu = React.createClass({displayName: 'ViewMenu',
statics: {
- title: "Tools",
+ title: "View",
route: "flows"
},
+ mixins: [Navigation, State],
+ 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 () {
- return React.createElement("div", null, "Tools Menu");
+ var showEventLog = this.getQuery()[Query.SHOW_EVENTLOG];
+ return (
+ React.createElement("div", null,
+ React.createElement("button", {
+ className: "btn " + (showEventLog ? "btn-primary" : "btn-default"),
+ onClick: this.toggleEventLog},
+ React.createElement("i", {className: "fa fa-database"}),
+ " Show Eventlog"
+ ),
+ React.createElement("span", null, " ")
+ )
+ );
}
});
@@ -2863,7 +2859,9 @@ var FileMenu = React.createClass({displayName: 'FileMenu',
},
handleNewClick: function (e) {
e.preventDefault();
- console.error("unimplemented: handleNewClick");
+ if (confirm("Delete all flows?")) {
+ FlowActions.clear();
+ }
},
handleOpenClick: function (e) {
e.preventDefault();
@@ -2890,25 +2888,34 @@ var FileMenu = React.createClass({displayName: 'FileMenu',
"New"
)
),
- React.createElement("li", null,
- React.createElement("a", {href: "#", onClick: this.handleOpenClick},
- React.createElement("i", {className: "fa fa-fw fa-folder-open"}),
- "Open"
- )
- ),
- React.createElement("li", null,
- React.createElement("a", {href: "#", onClick: this.handleSaveClick},
- React.createElement("i", {className: "fa fa-fw fa-save"}),
- "Save"
- )
- ),
React.createElement("li", {role: "presentation", className: "divider"}),
React.createElement("li", null,
- React.createElement("a", {href: "#", onClick: this.handleShutdownClick},
- React.createElement("i", {className: "fa fa-fw fa-plug"}),
- "Shutdown"
+ React.createElement("a", {href: "http://mitm.it/", target: "_blank"},
+ React.createElement("i", {className: "fa fa-fw fa-lock"}),
+ "Install Certificates..."
)
)
+ /*
+ <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>
+ */
)
)
);
@@ -2916,7 +2923,7 @@ var FileMenu = React.createClass({displayName: 'FileMenu',
});
-var header_entries = [MainMenu, ToolsMenu, ReportsMenu];
+var header_entries = [MainMenu, ViewMenu /*, ReportsMenu */];
var Header = React.createClass({displayName: 'Header',
@@ -3032,7 +3039,11 @@ var PathColumn = React.createClass({displayName: 'PathColumn',
},
render: function () {
var flow = this.props.flow;
- return React.createElement("td", {className: "col-path"}, flow.request.scheme + "://" + flow.request.host + flow.request.path);
+ return React.createElement("td", {className: "col-path"},
+ flow.request.is_replay ? React.createElement("i", {className: "fa fa-fw fa-repeat pull-right"}) : null,
+ flow.intercepted ? React.createElement("i", {className: "fa fa-fw fa-pause pull-right"}) : null,
+ flow.request.scheme + "://" + flow.request.host + flow.request.path
+ );
}
});
@@ -3247,10 +3258,75 @@ var FlowTable = React.createClass({displayName: 'FlowTable',
}
});
+var DeleteButton = React.createClass({displayName: 'DeleteButton',
+ onClick: function (e) {
+ e.preventDefault();
+ FlowActions.delete(this.props.flow);
+ },
+ render: function () {
+ return (
+ React.createElement("a", {title: "[d]elete Flow",
+ href: "#",
+ className: "nav-action",
+ onClick: this.onClick},
+ React.createElement("i", {className: "fa fa-fw fa-trash"})
+ )
+ );
+ }
+});
+var DuplicateButton = React.createClass({displayName: 'DuplicateButton',
+ onClick: function (e) {
+ e.preventDefault();
+ FlowActions.duplicate(this.props.flow);
+ },
+ render: function () {
+ return (
+ React.createElement("a", {title: "[D]uplicate Flow",
+ href: "#",
+ className: "nav-action",
+ onClick: this.onClick},
+ React.createElement("i", {className: "fa fa-fw fa-edit"})
+ )
+ );
+ }
+});
+var ReplayButton = React.createClass({displayName: 'ReplayButton',
+ onClick: function (e) {
+ e.preventDefault();
+ FlowActions.replay(this.props.flow);
+ },
+ render: function () {
+ return (
+ React.createElement("a", {title: "[r]eplay Flow",
+ href: "#",
+ className: "nav-action",
+ onClick: this.onClick},
+ React.createElement("i", {className: "fa fa-fw fa-close"})
+ )
+ );
+ }
+});
+var AcceptButton = React.createClass({displayName: 'AcceptButton',
+ onClick: function (e) {
+ e.preventDefault();
+ FlowActions.accept(this.props.flow);
+ },
+ render: function () {
+ return (
+ React.createElement("a", {title: "[a]ccept (resume) Flow",
+ href: "#",
+ className: "nav-action",
+ onClick: this.onClick},
+ React.createElement("i", {className: "fa fa-fw fa-play"})
+ )
+ );
+ }
+});
var FlowDetailNav = React.createClass({displayName: 'FlowDetailNav',
render: function () {
+ var flow = this.props.flow;
- var items = this.props.tabs.map(function (e) {
+ 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) {
@@ -3262,9 +3338,14 @@ var FlowDetailNav = React.createClass({displayName: 'FlowDetailNav',
className: className,
onClick: onClick}, str);
}.bind(this));
+
return (
React.createElement("nav", {ref: "head", className: "nav-tabs nav-tabs-sm"},
- items
+ tabs,
+ React.createElement(DeleteButton, {flow: flow}),
+ React.createElement(DuplicateButton, {flow: flow}),
+ React.createElement(ReplayButton, {flow: flow}),
+ flow.intercepted ? React.createElement(AcceptButton, {flow: this.props.flow}) : null
)
);
}
@@ -3353,7 +3434,9 @@ var FlowDetailError = React.createClass({displayName: 'FlowDetailError',
React.createElement("section", null,
React.createElement("div", {className: "alert alert-warning"},
flow.error.msg,
- React.createElement("div", null, React.createElement("small", null, formatTimeStamp(flow.error.timestamp) ))
+ React.createElement("div", null,
+ React.createElement("small", null, formatTimeStamp(flow.error.timestamp) )
+ )
)
)
);
@@ -3590,6 +3673,7 @@ var FlowDetail = React.createClass({displayName: 'FlowDetail',
return (
React.createElement("div", {className: "flow-detail", onScroll: this.adjustHead},
React.createElement(FlowDetailNav, {ref: "head",
+ flow: flow,
tabs: tabs,
active: active,
selectTab: this.selectTab}),
@@ -3644,6 +3728,7 @@ var MainView = React.createClass({displayName: 'MainView',
view.addListener("recalculate", this.onRecalculate);
view.addListener("add update remove", this.onUpdate);
+ view.addListener("remove", this.onRemove);
},
onRecalculate: function () {
this.forceUpdate();
@@ -3657,6 +3742,12 @@ var MainView = React.createClass({displayName: 'MainView',
this.forceUpdate();
}
},
+ onRemove: function (flow_id, index) {
+ if (flow_id === this.getParams().flowId) {
+ var flow_to_select = this.state.view.list[Math.min(index, this.state.view.list.length -1)];
+ this.selectFlow(flow_to_select);
+ }
+ },
closeView: function () {
this.state.view.close();
},
@@ -3706,7 +3797,7 @@ var MainView = React.createClass({displayName: 'MainView',
},
onKeyDown: function (e) {
var flow = this.getSelected();
- if(e.ctrlKey){
+ if (e.ctrlKey) {
return;
}
switch (e.keyCode) {
@@ -3757,8 +3848,6 @@ var MainView = React.createClass({displayName: 'MainView',
if (e.shiftKey) {
FlowActions.duplicate(flow);
} else {
- var last_flow = this.state.view.index(flow) === this.state.view.list.length - 1;
- this.selectFlowRelative(last_flow ? -1 : +1);
FlowActions.delete(flow);
}
}
@@ -3771,7 +3860,7 @@ var MainView = React.createClass({displayName: 'MainView',
}
break;
case Key.R:
- if(!e.shiftKey && flow){
+ if (!e.shiftKey && flow) {
FlowActions.replay(flow);
}
break;
diff --git a/web/src/css/flowtable.less b/web/src/css/flowtable.less
index 38f5e0e8..9988f1a8 100644
--- a/web/src/css/flowtable.less
+++ b/web/src/css/flowtable.less
@@ -1,78 +1,95 @@
.flow-table {
- width: 100%;
- overflow: auto;
+ width: 100%;
+ overflow: auto;
- table {
- width: 100%;
- table-layout: fixed;
- }
+ table {
+ width: 100%;
+ table-layout: fixed;
+ }
- thead {
- background-color: #F2F2F2;
- line-height: 23px;
- }
+ thead {
+ background-color: #F2F2F2;
+ line-height: 23px;
+ }
- th {
- font-weight: normal;
- box-shadow: 0 1px 0 #a6a6a6;
- }
+ th {
+ font-weight: normal;
+ box-shadow: 0 1px 0 #a6a6a6;
+ }
- tr {
- cursor: pointer;
+ tr {
+ cursor: pointer;
- &:nth-child(even) {
- background-color : rgba(0,0,0,0.05);
- }
- &.selected {
- background-color: hsla(209, 52%, 84%, 0.5) !important;
- }
- &.highlighted {
- background-color: hsla(48, 100%, 50%, 0.4);
- }
- &.highlighted:nth-child(even) {
- background-color: hsla(48, 100%, 50%, 0.5);
- }
- }
+ &:nth-child(even) {
+ background-color: rgba(0, 0, 0, 0.05);
+ }
+ &.selected {
+ background-color: hsla(209, 52%, 84%, 0.5) !important;
+ }
+ &.highlighted {
+ background-color: hsla(48, 100%, 50%, 0.4);
+ }
+ &.highlighted:nth-child(even) {
+ background-color: hsla(48, 100%, 50%, 0.5);
+ }
+ }
- td {
- overflow: hidden;
- white-space: nowrap;
- text-overflow: ellipsis;
- }
+ td {
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ }
- tr.intercepted:not(.has-response) {
- .col-path, .col-method {
- color: hsl(30, 100%, 50%);
- }
- }
- tr.intercepted.has-response {
- .col-status, .col-size, .col-time {
- color: hsl(30, 100%, 50%);
- }
- }
+ @interceptorange: hsl(30, 100%, 50%);
- .col-tls {
- width: 10px;
- }
- .col-tls-https {
- background-color: rgba(0, 185, 0, 0.5);
- }
- .col-icon {
- width: 32px;
- }
- .col-method {
- width: 60px;
- }
- .col-status {
- width: 50px;
- }
- .col-size {
- width: 70px;
- }
- .col-time {
- width: 50px;
- }
- td.col-time, td.col-size {
- text-align: right;
- }
+ tr.intercepted:not(.has-response) {
+ .col-path, .col-method {
+ color: @interceptorange;
+ }
+ }
+ tr.intercepted.has-response {
+ .col-status, .col-size, .col-time {
+ color: @interceptorange;
+ }
+ }
+
+ .fa {
+ line-height: inherit;
+ &.pull-right {
+ margin-left: 0;
+ }
+ }
+
+ .col-tls {
+ width: 10px;
+ }
+ .col-tls-https {
+ background-color: rgba(0, 185, 0, 0.5);
+ }
+ .col-icon {
+ width: 32px;
+ }
+ .col-path {
+ .fa-repeat {
+ color: green;
+ }
+ .fa-pause {
+ color: @interceptorange;
+ }
+ }
+ .col-method {
+ width: 60px;
+ }
+ .col-status {
+ width: 50px;
+ }
+ .col-size {
+ width: 70px;
+ }
+ .col-time {
+ width: 50px;
+ }
+ td.col-time, td.col-size {
+ text-align: right;
+ }
} \ No newline at end of file
diff --git a/web/src/css/tabs.less b/web/src/css/tabs.less
index 36bc5b68..43f7264e 100644
--- a/web/src/css/tabs.less
+++ b/web/src/css/tabs.less
@@ -40,6 +40,10 @@
a {
padding: 0px 7px;
margin: 2px 2px -1px;
-
+ }
+ a.nav-action {
+ float: right;
+ padding: 0;
+ margin: 1px 0 0px;
}
} \ No newline at end of file
diff --git a/web/src/js/components/flowdetail.jsx.js b/web/src/js/components/flowdetail.jsx.js
index 5727ec75..dfc0099e 100644
--- a/web/src/js/components/flowdetail.jsx.js
+++ b/web/src/js/components/flowdetail.jsx.js
@@ -1,7 +1,72 @@
+var DeleteButton = React.createClass({
+ onClick: function (e) {
+ e.preventDefault();
+ FlowActions.delete(this.props.flow);
+ },
+ render: function () {
+ return (
+ <a title="[d]elete Flow"
+ href="#"
+ className="nav-action"
+ onClick={this.onClick}>
+ <i className="fa fa-fw fa-trash"></i>
+ </a>
+ );
+ }
+});
+var DuplicateButton = React.createClass({
+ onClick: function (e) {
+ e.preventDefault();
+ FlowActions.duplicate(this.props.flow);
+ },
+ render: function () {
+ return (
+ <a title="[D]uplicate Flow"
+ href="#"
+ className="nav-action"
+ onClick={this.onClick}>
+ <i className="fa fa-fw fa-edit"></i>
+ </a>
+ );
+ }
+});
+var ReplayButton = React.createClass({
+ onClick: function (e) {
+ e.preventDefault();
+ FlowActions.replay(this.props.flow);
+ },
+ render: function () {
+ return (
+ <a title="[r]eplay Flow"
+ href="#"
+ className="nav-action"
+ onClick={this.onClick}>
+ <i className="fa fa-fw fa-close"></i>
+ </a>
+ );
+ }
+});
+var AcceptButton = React.createClass({
+ onClick: function (e) {
+ e.preventDefault();
+ FlowActions.accept(this.props.flow);
+ },
+ render: function () {
+ return (
+ <a title="[a]ccept (resume) Flow"
+ href="#"
+ className="nav-action"
+ onClick={this.onClick}>
+ <i className="fa fa-fw fa-play"></i>
+ </a>
+ );
+ }
+});
var FlowDetailNav = React.createClass({
render: function () {
+ var flow = this.props.flow;
- var items = this.props.tabs.map(function (e) {
+ 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) {
@@ -13,9 +78,14 @@ var FlowDetailNav = React.createClass({
className={className}
onClick={onClick}>{str}</a>;
}.bind(this));
+
return (
<nav ref="head" className="nav-tabs nav-tabs-sm">
- {items}
+ {tabs}
+ <DeleteButton flow={flow}/>
+ <DuplicateButton flow={flow}/>
+ <ReplayButton flow={flow}/>
+ { flow.intercepted ? <AcceptButton flow={this.props.flow}/> : null }
</nav>
);
}
@@ -104,7 +174,9 @@ var FlowDetailError = React.createClass({
<section>
<div className="alert alert-warning">
{flow.error.msg}
- <div><small>{ formatTimeStamp(flow.error.timestamp) }</small></div>
+ <div>
+ <small>{ formatTimeStamp(flow.error.timestamp) }</small>
+ </div>
</div>
</section>
);
@@ -341,6 +413,7 @@ var FlowDetail = React.createClass({
return (
<div className="flow-detail" onScroll={this.adjustHead}>
<FlowDetailNav ref="head"
+ flow={flow}
tabs={tabs}
active={active}
selectTab={this.selectTab}/>
diff --git a/web/src/js/components/flowtable-columns.jsx.js b/web/src/js/components/flowtable-columns.jsx.js
index 1aa256c4..9162e077 100644
--- a/web/src/js/components/flowtable-columns.jsx.js
+++ b/web/src/js/components/flowtable-columns.jsx.js
@@ -66,7 +66,11 @@ var PathColumn = React.createClass({
},
render: function () {
var flow = this.props.flow;
- return <td className="col-path">{flow.request.scheme + "://" + flow.request.host + flow.request.path}</td>;
+ return <td className="col-path">
+ {flow.request.is_replay ? <i className="fa fa-fw fa-repeat pull-right"></i> : null}
+ {flow.intercepted ? <i className="fa fa-fw fa-pause pull-right"></i> : null}
+ {flow.request.scheme + "://" + flow.request.host + flow.request.path}
+ </td>;
}
});
diff --git a/web/src/js/components/header.jsx.js b/web/src/js/components/header.jsx.js
index 9e6b8f2f..e1016950 100644
--- a/web/src/js/components/header.jsx.js
+++ b/web/src/js/components/header.jsx.js
@@ -111,23 +111,9 @@ var FilterInput = React.createClass({
var MainMenu = React.createClass({
mixins: [Navigation, State],
statics: {
- title: "Traffic",
+ title: "Start",
route: "flows"
},
- 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);
- },
- clearFlows: function () {
- FlowActions.clear();
- },
onFilterChange: function (val) {
var d = {};
d[Query.FILTER] = val;
@@ -145,22 +131,9 @@ var MainMenu = React.createClass({
var filter = this.getQuery()[Query.FILTER] || "";
var highlight = this.getQuery()[Query.HIGHLIGHT] || "";
var intercept = this.props.settings.intercept || "";
- 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;Display Event Log
- </button>
- <span> </span>
- <button className="btn btn-default" onClick={this.clearFlows}>
- <i className="fa fa-eraser"></i>
- &nbsp;Clear Flows
- </button>
- <span> </span>
<form className="form-inline" style={{display: "inline"}}>
<FilterInput
placeholder="Filter"
@@ -189,13 +162,36 @@ var MainMenu = React.createClass({
});
-var ToolsMenu = React.createClass({
+var ViewMenu = React.createClass({
statics: {
- title: "Tools",
+ title: "View",
route: "flows"
},
+ mixins: [Navigation, State],
+ 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 () {
- return <div>Tools Menu</div>;
+ 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>
+ );
}
});
@@ -232,7 +228,9 @@ var FileMenu = React.createClass({
},
handleNewClick: function (e) {
e.preventDefault();
- console.error("unimplemented: handleNewClick");
+ if (confirm("Delete all flows?")) {
+ FlowActions.clear();
+ }
},
handleOpenClick: function (e) {
e.preventDefault();
@@ -259,25 +257,34 @@ var FileMenu = React.createClass({
New
</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 href="http://mitm.it/" target="_blank">
+ <i className="fa fa-fw fa-lock"></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>
);
@@ -285,7 +292,7 @@ var FileMenu = React.createClass({
});
-var header_entries = [MainMenu, ToolsMenu, ReportsMenu];
+var header_entries = [MainMenu, ViewMenu /*, ReportsMenu */];
var Header = React.createClass({
diff --git a/web/src/js/components/mainview.jsx.js b/web/src/js/components/mainview.jsx.js
index 78415ad0..41f22a95 100644
--- a/web/src/js/components/mainview.jsx.js
+++ b/web/src/js/components/mainview.jsx.js
@@ -44,6 +44,7 @@ var MainView = React.createClass({
view.addListener("recalculate", this.onRecalculate);
view.addListener("add update remove", this.onUpdate);
+ view.addListener("remove", this.onRemove);
},
onRecalculate: function () {
this.forceUpdate();
@@ -57,6 +58,12 @@ var MainView = React.createClass({
this.forceUpdate();
}
},
+ onRemove: function (flow_id, index) {
+ if (flow_id === this.getParams().flowId) {
+ var flow_to_select = this.state.view.list[Math.min(index, this.state.view.list.length -1)];
+ this.selectFlow(flow_to_select);
+ }
+ },
closeView: function () {
this.state.view.close();
},
@@ -106,7 +113,7 @@ var MainView = React.createClass({
},
onKeyDown: function (e) {
var flow = this.getSelected();
- if(e.ctrlKey){
+ if (e.ctrlKey) {
return;
}
switch (e.keyCode) {
@@ -157,8 +164,6 @@ var MainView = React.createClass({
if (e.shiftKey) {
FlowActions.duplicate(flow);
} else {
- var last_flow = this.state.view.index(flow) === this.state.view.list.length - 1;
- this.selectFlowRelative(last_flow ? -1 : +1);
FlowActions.delete(flow);
}
}
@@ -171,7 +176,7 @@ var MainView = React.createClass({
}
break;
case Key.R:
- if(!e.shiftKey && flow){
+ if (!e.shiftKey && flow) {
FlowActions.replay(flow);
}
break;