mirror of
https://github.com/Grasscutters/mitmproxy.git
synced 2024-11-23 08:11:00 +00:00
web: duplicate, delete, replay flows
This commit is contained in:
parent
b1311faa68
commit
1c4f4c2494
@ -428,6 +428,11 @@ class FlowStore(FlowList):
|
|||||||
self.views = []
|
self.views = []
|
||||||
self._recalculate_views()
|
self._recalculate_views()
|
||||||
|
|
||||||
|
def get(self, flow_id):
|
||||||
|
for f in self._list:
|
||||||
|
if f.id == flow_id:
|
||||||
|
return f
|
||||||
|
|
||||||
def __contains__(self, f):
|
def __contains__(self, f):
|
||||||
return f in self._set
|
return f in self._set
|
||||||
|
|
||||||
|
@ -96,6 +96,8 @@ class Flow(stateobject.StateObject):
|
|||||||
def copy(self):
|
def copy(self):
|
||||||
f = copy.copy(self)
|
f = copy.copy(self)
|
||||||
|
|
||||||
|
f.id = str(uuid.uuid4())
|
||||||
|
f.live = False
|
||||||
f.client_conn = self.client_conn.copy()
|
f.client_conn = self.client_conn.copy()
|
||||||
f.server_conn = self.server_conn.copy()
|
f.server_conn = self.server_conn.copy()
|
||||||
|
|
||||||
@ -142,6 +144,8 @@ class Flow(stateobject.StateObject):
|
|||||||
Intercept this Flow. Processing will stop until accept_intercept is
|
Intercept this Flow. Processing will stop until accept_intercept is
|
||||||
called.
|
called.
|
||||||
"""
|
"""
|
||||||
|
if self.intercepted:
|
||||||
|
return
|
||||||
self.intercepted = True
|
self.intercepted = True
|
||||||
master.handle_intercept(self)
|
master.handle_intercept(self)
|
||||||
|
|
||||||
@ -149,6 +153,8 @@ class Flow(stateobject.StateObject):
|
|||||||
"""
|
"""
|
||||||
Continue with the flow - called after an intercept().
|
Continue with the flow - called after an intercept().
|
||||||
"""
|
"""
|
||||||
|
if not self.intercepted:
|
||||||
|
return
|
||||||
self.intercepted = False
|
self.intercepted = False
|
||||||
self.reply()
|
self.reply()
|
||||||
master.handle_accept_intercept(self)
|
master.handle_accept_intercept(self)
|
||||||
|
@ -35,7 +35,7 @@ class WebFlowView(flow.FlowView):
|
|||||||
app.ClientConnection.broadcast(
|
app.ClientConnection.broadcast(
|
||||||
type="flows",
|
type="flows",
|
||||||
cmd="remove",
|
cmd="remove",
|
||||||
data=f.get_state(short=True)
|
data=f.id
|
||||||
)
|
)
|
||||||
|
|
||||||
def _recalculate(self, flows):
|
def _recalculate(self, flows):
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
import os.path
|
import os.path
|
||||||
import sys
|
|
||||||
import tornado.web
|
import tornado.web
|
||||||
import tornado.websocket
|
import tornado.websocket
|
||||||
import logging
|
import logging
|
||||||
import json
|
import json
|
||||||
from .. import flow
|
|
||||||
|
|
||||||
|
|
||||||
|
class APIError(tornado.web.HTTPError):
|
||||||
|
pass
|
||||||
|
|
||||||
class IndexHandler(tornado.web.RequestHandler):
|
class IndexHandler(tornado.web.RequestHandler):
|
||||||
def get(self):
|
def get(self):
|
||||||
_ = self.xsrf_token # https://github.com/tornadoweb/tornado/issues/645
|
_ = self.xsrf_token # https://github.com/tornadoweb/tornado/issues/645
|
||||||
@ -36,38 +37,82 @@ class ClientConnection(WebSocketEventBroadcaster):
|
|||||||
connections = set()
|
connections = set()
|
||||||
|
|
||||||
|
|
||||||
class Flows(tornado.web.RequestHandler):
|
class RequestHandler(tornado.web.RequestHandler):
|
||||||
|
@property
|
||||||
|
def state(self):
|
||||||
|
return self.application.master.state
|
||||||
|
|
||||||
|
@property
|
||||||
|
def master(self):
|
||||||
|
return self.application.master
|
||||||
|
|
||||||
|
@property
|
||||||
|
def flow(self):
|
||||||
|
flow_id = str(self.path_kwargs["flow_id"])
|
||||||
|
flow = self.state.flows.get(flow_id)
|
||||||
|
if flow:
|
||||||
|
return flow
|
||||||
|
else:
|
||||||
|
raise APIError(400, "Flow not found.")
|
||||||
|
|
||||||
|
def write_error(self, status_code, **kwargs):
|
||||||
|
if "exc_info" in kwargs and isinstance(kwargs["exc_info"][1], APIError):
|
||||||
|
self.finish(kwargs["exc_info"][1].log_message)
|
||||||
|
else:
|
||||||
|
super(RequestHandler, self).write_error(status_code, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class Flows(RequestHandler):
|
||||||
def get(self):
|
def get(self):
|
||||||
self.write(dict(
|
self.write(dict(
|
||||||
data=[f.get_state(short=True) for f in self.application.state.flows]
|
data=[f.get_state(short=True) for f in self.state.flows]
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|
||||||
class AcceptFlows(tornado.web.RequestHandler):
|
class ClearAll(RequestHandler):
|
||||||
def post(self):
|
def post(self):
|
||||||
self.application.state.flows.accept_all(self.application.master)
|
self.state.clear()
|
||||||
|
|
||||||
|
|
||||||
class AcceptFlow(tornado.web.RequestHandler):
|
class AcceptFlows(RequestHandler):
|
||||||
|
def post(self):
|
||||||
|
self.state.flows.accept_all(self.master)
|
||||||
|
|
||||||
|
|
||||||
|
class AcceptFlow(RequestHandler):
|
||||||
def post(self, flow_id):
|
def post(self, flow_id):
|
||||||
flow_id = str(flow_id)
|
self.flow.accept_intercept(self.master)
|
||||||
for flow in self.application.state.flows:
|
|
||||||
if flow.id == flow_id:
|
|
||||||
flow.accept_intercept(self.application.master)
|
|
||||||
break
|
|
||||||
|
|
||||||
class Events(tornado.web.RequestHandler):
|
|
||||||
|
class FlowHandler(RequestHandler):
|
||||||
|
def delete(self, flow_id):
|
||||||
|
self.flow.kill(self.master)
|
||||||
|
self.state.delete_flow(self.flow)
|
||||||
|
|
||||||
|
|
||||||
|
class DuplicateFlow(RequestHandler):
|
||||||
|
def post(self, flow_id):
|
||||||
|
self.master.duplicate_flow(self.flow)
|
||||||
|
|
||||||
|
class ReplayFlow(RequestHandler):
|
||||||
|
def post(self, flow_id):
|
||||||
|
self.flow.backup()
|
||||||
|
r = self.master.replay_request(self.flow)
|
||||||
|
if r:
|
||||||
|
raise APIError(400, r)
|
||||||
|
|
||||||
|
class Events(RequestHandler):
|
||||||
def get(self):
|
def get(self):
|
||||||
self.write(dict(
|
self.write(dict(
|
||||||
data=list(self.application.state.events)
|
data=list(self.state.events)
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|
||||||
class Settings(tornado.web.RequestHandler):
|
class Settings(RequestHandler):
|
||||||
def get(self):
|
def get(self):
|
||||||
self.write(dict(
|
self.write(dict(
|
||||||
data=dict(
|
data=dict(
|
||||||
intercept=self.application.state.intercept_txt
|
intercept=self.state.intercept_txt
|
||||||
)
|
)
|
||||||
))
|
))
|
||||||
|
|
||||||
@ -81,7 +126,7 @@ class Settings(tornado.web.RequestHandler):
|
|||||||
if k == "_xsrf":
|
if k == "_xsrf":
|
||||||
continue
|
continue
|
||||||
elif k == "intercept":
|
elif k == "intercept":
|
||||||
self.application.state.set_intercept(v[0])
|
self.state.set_intercept(v[0])
|
||||||
update[k] = v[0]
|
update[k] = v[0]
|
||||||
else:
|
else:
|
||||||
print "Warning: Unknown setting {}: {}".format(k, v)
|
print "Warning: Unknown setting {}: {}".format(k, v)
|
||||||
@ -93,17 +138,7 @@ class Settings(tornado.web.RequestHandler):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class Clear(tornado.web.RequestHandler):
|
|
||||||
def post(self):
|
|
||||||
self.application.state.clear()
|
|
||||||
|
|
||||||
|
|
||||||
class Application(tornado.web.Application):
|
class Application(tornado.web.Application):
|
||||||
|
|
||||||
@property
|
|
||||||
def state(self):
|
|
||||||
return self.master.state
|
|
||||||
|
|
||||||
def __init__(self, master, debug):
|
def __init__(self, master, debug):
|
||||||
self.master = master
|
self.master = master
|
||||||
handlers = [
|
handlers = [
|
||||||
@ -112,9 +147,12 @@ class Application(tornado.web.Application):
|
|||||||
(r"/events", Events),
|
(r"/events", Events),
|
||||||
(r"/flows", Flows),
|
(r"/flows", Flows),
|
||||||
(r"/flows/accept", AcceptFlows),
|
(r"/flows/accept", AcceptFlows),
|
||||||
(r"/flows/([0-9a-f\-]+)/accept", AcceptFlow),
|
(r"/flows/(?P<flow_id>[0-9a-f\-]+)", FlowHandler),
|
||||||
|
(r"/flows/(?P<flow_id>[0-9a-f\-]+)/accept", AcceptFlow),
|
||||||
|
(r"/flows/(?P<flow_id>[0-9a-f\-]+)/duplicate", DuplicateFlow),
|
||||||
|
(r"/flows/(?P<flow_id>[0-9a-f\-]+)/replay", ReplayFlow),
|
||||||
(r"/settings", Settings),
|
(r"/settings", Settings),
|
||||||
(r"/clear", Clear),
|
(r"/clear", ClearAll),
|
||||||
]
|
]
|
||||||
settings = dict(
|
settings = dict(
|
||||||
template_path=os.path.join(os.path.dirname(__file__), "templates"),
|
template_path=os.path.join(os.path.dirname(__file__), "templates"),
|
||||||
|
@ -1,81 +1,3 @@
|
|||||||
// http://blog.vjeux.com/2013/javascript/scroll-position-with-react.html (also contains inverse example)
|
|
||||||
var AutoScrollMixin = {
|
|
||||||
componentWillUpdate: function () {
|
|
||||||
var node = this.getDOMNode();
|
|
||||||
this._shouldScrollBottom = (
|
|
||||||
node.scrollTop !== 0 &&
|
|
||||||
node.scrollTop + node.clientHeight === node.scrollHeight
|
|
||||||
);
|
|
||||||
},
|
|
||||||
componentDidUpdate: function () {
|
|
||||||
if (this._shouldScrollBottom) {
|
|
||||||
var node = this.getDOMNode();
|
|
||||||
node.scrollTop = node.scrollHeight;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
var StickyHeadMixin = {
|
|
||||||
adjustHead: function () {
|
|
||||||
// Abusing CSS transforms to set the element
|
|
||||||
// referenced as head into some kind of position:sticky.
|
|
||||||
var head = this.refs.head.getDOMNode();
|
|
||||||
head.style.transform = "translate(0," + this.getDOMNode().scrollTop + "px)";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
var Navigation = _.extend({}, ReactRouter.Navigation, {
|
|
||||||
setQuery: function (dict) {
|
|
||||||
var q = this.context.getCurrentQuery();
|
|
||||||
for(var i in dict){
|
|
||||||
if(dict.hasOwnProperty(i)){
|
|
||||||
q[i] = dict[i] || undefined; //falsey values shall be removed.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
q._ = "_"; // workaround for https://github.com/rackt/react-router/pull/599
|
|
||||||
this.replaceWith(this.context.getCurrentPath(), this.context.getCurrentParams(), q);
|
|
||||||
},
|
|
||||||
replaceWith: function(routeNameOrPath, params, query) {
|
|
||||||
if(routeNameOrPath === undefined){
|
|
||||||
routeNameOrPath = this.context.getCurrentPath();
|
|
||||||
}
|
|
||||||
if(params === undefined){
|
|
||||||
params = this.context.getCurrentParams();
|
|
||||||
}
|
|
||||||
if(query === undefined){
|
|
||||||
query = this.context.getCurrentQuery();
|
|
||||||
}
|
|
||||||
ReactRouter.Navigation.replaceWith.call(this, routeNameOrPath, params, query);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
_.extend(Navigation.contextTypes, ReactRouter.State.contextTypes);
|
|
||||||
|
|
||||||
var State = _.extend({}, ReactRouter.State, {
|
|
||||||
getInitialState: function () {
|
|
||||||
this._query = this.context.getCurrentQuery();
|
|
||||||
this._queryWatches = [];
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
onQueryChange: function (key, callback) {
|
|
||||||
this._queryWatches.push({
|
|
||||||
key: key,
|
|
||||||
callback: callback
|
|
||||||
});
|
|
||||||
},
|
|
||||||
componentWillReceiveProps: function (nextProps, nextState) {
|
|
||||||
var q = this.context.getCurrentQuery();
|
|
||||||
for (var i = 0; i < this._queryWatches.length; i++) {
|
|
||||||
var watch = this._queryWatches[i];
|
|
||||||
if (this._query[watch.key] !== q[watch.key]) {
|
|
||||||
watch.callback(this._query[watch.key], q[watch.key], watch.key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this._query = q;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var Key = {
|
var Key = {
|
||||||
UP: 38,
|
UP: 38,
|
||||||
DOWN: 40,
|
DOWN: 40,
|
||||||
@ -157,6 +79,31 @@ EventEmitter.prototype.removeListener = function (events, f) {
|
|||||||
}
|
}
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
function getCookie(name) {
|
||||||
|
var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
|
||||||
|
return r ? r[1] : undefined;
|
||||||
|
}
|
||||||
|
var xsrf = $.param({_xsrf: getCookie("_xsrf")});
|
||||||
|
|
||||||
|
//Tornado XSRF Protection.
|
||||||
|
jQuery.ajaxPrefilter(function (options) {
|
||||||
|
if (["post", "put", "delete"].indexOf(options.type.toLowerCase()) >= 0 && options.url[0] === "/") {
|
||||||
|
if (options.data) {
|
||||||
|
options.data += ("&" + xsrf);
|
||||||
|
} else {
|
||||||
|
options.data = xsrf;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Log AJAX Errors
|
||||||
|
$(document).ajaxError(function (event, jqXHR, ajaxSettings, thrownError) {
|
||||||
|
var message = jqXHR.responseText;
|
||||||
|
console.error(message, arguments);
|
||||||
|
EventLogActions.add_event(thrownError + ": " + message);
|
||||||
|
window.alert(message);
|
||||||
|
});
|
||||||
const PayloadSources = {
|
const PayloadSources = {
|
||||||
VIEW: "view",
|
VIEW: "view",
|
||||||
SERVER: "server"
|
SERVER: "server"
|
||||||
@ -272,7 +219,18 @@ var FlowActions = {
|
|||||||
accept_all: function(){
|
accept_all: function(){
|
||||||
jQuery.post("/flows/accept");
|
jQuery.post("/flows/accept");
|
||||||
},
|
},
|
||||||
|
"delete": function(flow){
|
||||||
|
jQuery.ajax({
|
||||||
|
type:"DELETE",
|
||||||
|
url: "/flows/" + flow.id
|
||||||
|
});
|
||||||
|
},
|
||||||
|
duplicate: function(flow){
|
||||||
|
jQuery.post("/flows/" + flow.id + "/duplicate");
|
||||||
|
},
|
||||||
|
replay: function(flow){
|
||||||
|
jQuery.post("/flows/" + flow.id + "/replay");
|
||||||
|
},
|
||||||
update: function (flow) {
|
update: function (flow) {
|
||||||
AppDispatcher.dispatchViewAction({
|
AppDispatcher.dispatchViewAction({
|
||||||
type: ActionTypes.FLOW_STORE,
|
type: ActionTypes.FLOW_STORE,
|
||||||
@ -2140,7 +2098,7 @@ _.extend(ListStore.prototype, EventEmitter.prototype, {
|
|||||||
this.emit("update", elem);
|
this.emit("update", elem);
|
||||||
},
|
},
|
||||||
remove: function (elem_id) {
|
remove: function (elem_id) {
|
||||||
if (!(elem.id in this._pos_map)) {
|
if (!(elem_id in this._pos_map)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.list.splice(this._pos_map[elem_id], 1);
|
this.list.splice(this._pos_map[elem_id], 1);
|
||||||
@ -2408,6 +2366,84 @@ function Connection(url) {
|
|||||||
}
|
}
|
||||||
//React utils. For other utilities, see ../utils.js
|
//React utils. For other utilities, see ../utils.js
|
||||||
|
|
||||||
|
// http://blog.vjeux.com/2013/javascript/scroll-position-with-react.html (also contains inverse example)
|
||||||
|
var AutoScrollMixin = {
|
||||||
|
componentWillUpdate: function () {
|
||||||
|
var node = this.getDOMNode();
|
||||||
|
this._shouldScrollBottom = (
|
||||||
|
node.scrollTop !== 0 &&
|
||||||
|
node.scrollTop + node.clientHeight === node.scrollHeight
|
||||||
|
);
|
||||||
|
},
|
||||||
|
componentDidUpdate: function () {
|
||||||
|
if (this._shouldScrollBottom) {
|
||||||
|
var node = this.getDOMNode();
|
||||||
|
node.scrollTop = node.scrollHeight;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
var StickyHeadMixin = {
|
||||||
|
adjustHead: function () {
|
||||||
|
// Abusing CSS transforms to set the element
|
||||||
|
// referenced as head into some kind of position:sticky.
|
||||||
|
var head = this.refs.head.getDOMNode();
|
||||||
|
head.style.transform = "translate(0," + this.getDOMNode().scrollTop + "px)";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
var Navigation = _.extend({}, ReactRouter.Navigation, {
|
||||||
|
setQuery: function (dict) {
|
||||||
|
var q = this.context.getCurrentQuery();
|
||||||
|
for(var i in dict){
|
||||||
|
if(dict.hasOwnProperty(i)){
|
||||||
|
q[i] = dict[i] || undefined; //falsey values shall be removed.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
q._ = "_"; // workaround for https://github.com/rackt/react-router/pull/599
|
||||||
|
this.replaceWith(this.context.getCurrentPath(), this.context.getCurrentParams(), q);
|
||||||
|
},
|
||||||
|
replaceWith: function(routeNameOrPath, params, query) {
|
||||||
|
if(routeNameOrPath === undefined){
|
||||||
|
routeNameOrPath = this.context.getCurrentPath();
|
||||||
|
}
|
||||||
|
if(params === undefined){
|
||||||
|
params = this.context.getCurrentParams();
|
||||||
|
}
|
||||||
|
if(query === undefined){
|
||||||
|
query = this.context.getCurrentQuery();
|
||||||
|
}
|
||||||
|
ReactRouter.Navigation.replaceWith.call(this, routeNameOrPath, params, query);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
_.extend(Navigation.contextTypes, ReactRouter.State.contextTypes);
|
||||||
|
|
||||||
|
var State = _.extend({}, ReactRouter.State, {
|
||||||
|
getInitialState: function () {
|
||||||
|
this._query = this.context.getCurrentQuery();
|
||||||
|
this._queryWatches = [];
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
onQueryChange: function (key, callback) {
|
||||||
|
this._queryWatches.push({
|
||||||
|
key: key,
|
||||||
|
callback: callback
|
||||||
|
});
|
||||||
|
},
|
||||||
|
componentWillReceiveProps: function (nextProps, nextState) {
|
||||||
|
var q = this.context.getCurrentQuery();
|
||||||
|
for (var i = 0; i < this._queryWatches.length; i++) {
|
||||||
|
var watch = this._queryWatches[i];
|
||||||
|
if (this._query[watch.key] !== q[watch.key]) {
|
||||||
|
watch.callback(this._query[watch.key], q[watch.key], watch.key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this._query = q;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
var Splitter = React.createClass({displayName: 'Splitter',
|
var Splitter = React.createClass({displayName: 'Splitter',
|
||||||
getDefaultProps: function () {
|
getDefaultProps: function () {
|
||||||
return {
|
return {
|
||||||
@ -2512,23 +2548,6 @@ var Splitter = React.createClass({displayName: 'Splitter',
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function getCookie(name) {
|
|
||||||
var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
|
|
||||||
return r ? r[1] : undefined;
|
|
||||||
}
|
|
||||||
var xsrf = $.param({_xsrf: getCookie("_xsrf")});
|
|
||||||
|
|
||||||
//Tornado XSRF Protection.
|
|
||||||
$.ajaxPrefilter(function (options) {
|
|
||||||
if (["post","put","delete"].indexOf(options.type.toLowerCase()) >= 0 && options.url[0] === "/") {
|
|
||||||
if (options.data) {
|
|
||||||
options.data += ("&" + xsrf);
|
|
||||||
} else {
|
|
||||||
options.data = xsrf;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
var VirtualScrollMixin = {
|
var VirtualScrollMixin = {
|
||||||
getInitialState: function () {
|
getInitialState: function () {
|
||||||
return {
|
return {
|
||||||
@ -3686,6 +3705,10 @@ var MainView = React.createClass({displayName: 'MainView',
|
|||||||
this.selectFlow(flows[index]);
|
this.selectFlow(flows[index]);
|
||||||
},
|
},
|
||||||
onKeyDown: function (e) {
|
onKeyDown: function (e) {
|
||||||
|
var flow = this.getSelected();
|
||||||
|
if(e.ctrlKey){
|
||||||
|
return;
|
||||||
|
}
|
||||||
switch (e.keyCode) {
|
switch (e.keyCode) {
|
||||||
case Key.K:
|
case Key.K:
|
||||||
case Key.UP:
|
case Key.UP:
|
||||||
@ -3724,11 +3747,32 @@ var MainView = React.createClass({displayName: 'MainView',
|
|||||||
this.refs.flowDetails.nextTab(+1);
|
this.refs.flowDetails.nextTab(+1);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case Key.C:
|
||||||
|
if (e.shiftKey) {
|
||||||
|
FlowActions.clear();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Key.D:
|
||||||
|
if (flow) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
case Key.A:
|
case Key.A:
|
||||||
if (e.shiftKey) {
|
if (e.shiftKey) {
|
||||||
FlowActions.accept_all();
|
FlowActions.accept_all();
|
||||||
} else if(this.getSelected()) {
|
} else if (flow) {
|
||||||
FlowActions.accept(this.getSelected());
|
FlowActions.accept(flow);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Key.R:
|
||||||
|
if(!e.shiftKey && flow){
|
||||||
|
FlowActions.replay(flow);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -77,7 +77,18 @@ var FlowActions = {
|
|||||||
accept_all: function(){
|
accept_all: function(){
|
||||||
jQuery.post("/flows/accept");
|
jQuery.post("/flows/accept");
|
||||||
},
|
},
|
||||||
|
"delete": function(flow){
|
||||||
|
jQuery.ajax({
|
||||||
|
type:"DELETE",
|
||||||
|
url: "/flows/" + flow.id
|
||||||
|
});
|
||||||
|
},
|
||||||
|
duplicate: function(flow){
|
||||||
|
jQuery.post("/flows/" + flow.id + "/duplicate");
|
||||||
|
},
|
||||||
|
replay: function(flow){
|
||||||
|
jQuery.post("/flows/" + flow.id + "/replay");
|
||||||
|
},
|
||||||
update: function (flow) {
|
update: function (flow) {
|
||||||
AppDispatcher.dispatchViewAction({
|
AppDispatcher.dispatchViewAction({
|
||||||
type: ActionTypes.FLOW_STORE,
|
type: ActionTypes.FLOW_STORE,
|
||||||
|
@ -105,6 +105,10 @@ var MainView = React.createClass({
|
|||||||
this.selectFlow(flows[index]);
|
this.selectFlow(flows[index]);
|
||||||
},
|
},
|
||||||
onKeyDown: function (e) {
|
onKeyDown: function (e) {
|
||||||
|
var flow = this.getSelected();
|
||||||
|
if(e.ctrlKey){
|
||||||
|
return;
|
||||||
|
}
|
||||||
switch (e.keyCode) {
|
switch (e.keyCode) {
|
||||||
case Key.K:
|
case Key.K:
|
||||||
case Key.UP:
|
case Key.UP:
|
||||||
@ -143,11 +147,32 @@ var MainView = React.createClass({
|
|||||||
this.refs.flowDetails.nextTab(+1);
|
this.refs.flowDetails.nextTab(+1);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case Key.C:
|
||||||
|
if (e.shiftKey) {
|
||||||
|
FlowActions.clear();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Key.D:
|
||||||
|
if (flow) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
case Key.A:
|
case Key.A:
|
||||||
if (e.shiftKey) {
|
if (e.shiftKey) {
|
||||||
FlowActions.accept_all();
|
FlowActions.accept_all();
|
||||||
} else if(this.getSelected()) {
|
} else if (flow) {
|
||||||
FlowActions.accept(this.getSelected());
|
FlowActions.accept(flow);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Key.R:
|
||||||
|
if(!e.shiftKey && flow){
|
||||||
|
FlowActions.replay(flow);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -1,5 +1,83 @@
|
|||||||
//React utils. For other utilities, see ../utils.js
|
//React utils. For other utilities, see ../utils.js
|
||||||
|
|
||||||
|
// http://blog.vjeux.com/2013/javascript/scroll-position-with-react.html (also contains inverse example)
|
||||||
|
var AutoScrollMixin = {
|
||||||
|
componentWillUpdate: function () {
|
||||||
|
var node = this.getDOMNode();
|
||||||
|
this._shouldScrollBottom = (
|
||||||
|
node.scrollTop !== 0 &&
|
||||||
|
node.scrollTop + node.clientHeight === node.scrollHeight
|
||||||
|
);
|
||||||
|
},
|
||||||
|
componentDidUpdate: function () {
|
||||||
|
if (this._shouldScrollBottom) {
|
||||||
|
var node = this.getDOMNode();
|
||||||
|
node.scrollTop = node.scrollHeight;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
var StickyHeadMixin = {
|
||||||
|
adjustHead: function () {
|
||||||
|
// Abusing CSS transforms to set the element
|
||||||
|
// referenced as head into some kind of position:sticky.
|
||||||
|
var head = this.refs.head.getDOMNode();
|
||||||
|
head.style.transform = "translate(0," + this.getDOMNode().scrollTop + "px)";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
var Navigation = _.extend({}, ReactRouter.Navigation, {
|
||||||
|
setQuery: function (dict) {
|
||||||
|
var q = this.context.getCurrentQuery();
|
||||||
|
for(var i in dict){
|
||||||
|
if(dict.hasOwnProperty(i)){
|
||||||
|
q[i] = dict[i] || undefined; //falsey values shall be removed.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
q._ = "_"; // workaround for https://github.com/rackt/react-router/pull/599
|
||||||
|
this.replaceWith(this.context.getCurrentPath(), this.context.getCurrentParams(), q);
|
||||||
|
},
|
||||||
|
replaceWith: function(routeNameOrPath, params, query) {
|
||||||
|
if(routeNameOrPath === undefined){
|
||||||
|
routeNameOrPath = this.context.getCurrentPath();
|
||||||
|
}
|
||||||
|
if(params === undefined){
|
||||||
|
params = this.context.getCurrentParams();
|
||||||
|
}
|
||||||
|
if(query === undefined){
|
||||||
|
query = this.context.getCurrentQuery();
|
||||||
|
}
|
||||||
|
ReactRouter.Navigation.replaceWith.call(this, routeNameOrPath, params, query);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
_.extend(Navigation.contextTypes, ReactRouter.State.contextTypes);
|
||||||
|
|
||||||
|
var State = _.extend({}, ReactRouter.State, {
|
||||||
|
getInitialState: function () {
|
||||||
|
this._query = this.context.getCurrentQuery();
|
||||||
|
this._queryWatches = [];
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
onQueryChange: function (key, callback) {
|
||||||
|
this._queryWatches.push({
|
||||||
|
key: key,
|
||||||
|
callback: callback
|
||||||
|
});
|
||||||
|
},
|
||||||
|
componentWillReceiveProps: function (nextProps, nextState) {
|
||||||
|
var q = this.context.getCurrentQuery();
|
||||||
|
for (var i = 0; i < this._queryWatches.length; i++) {
|
||||||
|
var watch = this._queryWatches[i];
|
||||||
|
if (this._query[watch.key] !== q[watch.key]) {
|
||||||
|
watch.callback(this._query[watch.key], q[watch.key], watch.key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this._query = q;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
var Splitter = React.createClass({
|
var Splitter = React.createClass({
|
||||||
getDefaultProps: function () {
|
getDefaultProps: function () {
|
||||||
return {
|
return {
|
||||||
@ -104,20 +182,3 @@ var Splitter = React.createClass({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function getCookie(name) {
|
|
||||||
var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
|
|
||||||
return r ? r[1] : undefined;
|
|
||||||
}
|
|
||||||
var xsrf = $.param({_xsrf: getCookie("_xsrf")});
|
|
||||||
|
|
||||||
//Tornado XSRF Protection.
|
|
||||||
$.ajaxPrefilter(function (options) {
|
|
||||||
if (["post","put","delete"].indexOf(options.type.toLowerCase()) >= 0 && options.url[0] === "/") {
|
|
||||||
if (options.data) {
|
|
||||||
options.data += ("&" + xsrf);
|
|
||||||
} else {
|
|
||||||
options.data = xsrf;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
@ -19,7 +19,7 @@ _.extend(ListStore.prototype, EventEmitter.prototype, {
|
|||||||
this.emit("update", elem);
|
this.emit("update", elem);
|
||||||
},
|
},
|
||||||
remove: function (elem_id) {
|
remove: function (elem_id) {
|
||||||
if (!(elem.id in this._pos_map)) {
|
if (!(elem_id in this._pos_map)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.list.splice(this._pos_map[elem_id], 1);
|
this.list.splice(this._pos_map[elem_id], 1);
|
||||||
|
@ -1,81 +1,3 @@
|
|||||||
// http://blog.vjeux.com/2013/javascript/scroll-position-with-react.html (also contains inverse example)
|
|
||||||
var AutoScrollMixin = {
|
|
||||||
componentWillUpdate: function () {
|
|
||||||
var node = this.getDOMNode();
|
|
||||||
this._shouldScrollBottom = (
|
|
||||||
node.scrollTop !== 0 &&
|
|
||||||
node.scrollTop + node.clientHeight === node.scrollHeight
|
|
||||||
);
|
|
||||||
},
|
|
||||||
componentDidUpdate: function () {
|
|
||||||
if (this._shouldScrollBottom) {
|
|
||||||
var node = this.getDOMNode();
|
|
||||||
node.scrollTop = node.scrollHeight;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
var StickyHeadMixin = {
|
|
||||||
adjustHead: function () {
|
|
||||||
// Abusing CSS transforms to set the element
|
|
||||||
// referenced as head into some kind of position:sticky.
|
|
||||||
var head = this.refs.head.getDOMNode();
|
|
||||||
head.style.transform = "translate(0," + this.getDOMNode().scrollTop + "px)";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
var Navigation = _.extend({}, ReactRouter.Navigation, {
|
|
||||||
setQuery: function (dict) {
|
|
||||||
var q = this.context.getCurrentQuery();
|
|
||||||
for(var i in dict){
|
|
||||||
if(dict.hasOwnProperty(i)){
|
|
||||||
q[i] = dict[i] || undefined; //falsey values shall be removed.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
q._ = "_"; // workaround for https://github.com/rackt/react-router/pull/599
|
|
||||||
this.replaceWith(this.context.getCurrentPath(), this.context.getCurrentParams(), q);
|
|
||||||
},
|
|
||||||
replaceWith: function(routeNameOrPath, params, query) {
|
|
||||||
if(routeNameOrPath === undefined){
|
|
||||||
routeNameOrPath = this.context.getCurrentPath();
|
|
||||||
}
|
|
||||||
if(params === undefined){
|
|
||||||
params = this.context.getCurrentParams();
|
|
||||||
}
|
|
||||||
if(query === undefined){
|
|
||||||
query = this.context.getCurrentQuery();
|
|
||||||
}
|
|
||||||
ReactRouter.Navigation.replaceWith.call(this, routeNameOrPath, params, query);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
_.extend(Navigation.contextTypes, ReactRouter.State.contextTypes);
|
|
||||||
|
|
||||||
var State = _.extend({}, ReactRouter.State, {
|
|
||||||
getInitialState: function () {
|
|
||||||
this._query = this.context.getCurrentQuery();
|
|
||||||
this._queryWatches = [];
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
onQueryChange: function (key, callback) {
|
|
||||||
this._queryWatches.push({
|
|
||||||
key: key,
|
|
||||||
callback: callback
|
|
||||||
});
|
|
||||||
},
|
|
||||||
componentWillReceiveProps: function (nextProps, nextState) {
|
|
||||||
var q = this.context.getCurrentQuery();
|
|
||||||
for (var i = 0; i < this._queryWatches.length; i++) {
|
|
||||||
var watch = this._queryWatches[i];
|
|
||||||
if (this._query[watch.key] !== q[watch.key]) {
|
|
||||||
watch.callback(this._query[watch.key], q[watch.key], watch.key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this._query = q;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var Key = {
|
var Key = {
|
||||||
UP: 38,
|
UP: 38,
|
||||||
DOWN: 40,
|
DOWN: 40,
|
||||||
@ -157,3 +79,28 @@ EventEmitter.prototype.removeListener = function (events, f) {
|
|||||||
}
|
}
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
function getCookie(name) {
|
||||||
|
var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
|
||||||
|
return r ? r[1] : undefined;
|
||||||
|
}
|
||||||
|
var xsrf = $.param({_xsrf: getCookie("_xsrf")});
|
||||||
|
|
||||||
|
//Tornado XSRF Protection.
|
||||||
|
jQuery.ajaxPrefilter(function (options) {
|
||||||
|
if (["post", "put", "delete"].indexOf(options.type.toLowerCase()) >= 0 && options.url[0] === "/") {
|
||||||
|
if (options.data) {
|
||||||
|
options.data += ("&" + xsrf);
|
||||||
|
} else {
|
||||||
|
options.data = xsrf;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Log AJAX Errors
|
||||||
|
$(document).ajaxError(function (event, jqXHR, ajaxSettings, thrownError) {
|
||||||
|
var message = jqXHR.responseText;
|
||||||
|
console.error(message, arguments);
|
||||||
|
EventLogActions.add_event(thrownError + ": " + message);
|
||||||
|
window.alert(message);
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user