mirror of
https://github.com/Grasscutters/mitmproxy.git
synced 2024-11-26 18:18:25 +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._recalculate_views()
|
||||
|
||||
def get(self, flow_id):
|
||||
for f in self._list:
|
||||
if f.id == flow_id:
|
||||
return f
|
||||
|
||||
def __contains__(self, f):
|
||||
return f in self._set
|
||||
|
||||
|
@ -96,6 +96,8 @@ class Flow(stateobject.StateObject):
|
||||
def copy(self):
|
||||
f = copy.copy(self)
|
||||
|
||||
f.id = str(uuid.uuid4())
|
||||
f.live = False
|
||||
f.client_conn = self.client_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
|
||||
called.
|
||||
"""
|
||||
if self.intercepted:
|
||||
return
|
||||
self.intercepted = True
|
||||
master.handle_intercept(self)
|
||||
|
||||
@ -149,6 +153,8 @@ class Flow(stateobject.StateObject):
|
||||
"""
|
||||
Continue with the flow - called after an intercept().
|
||||
"""
|
||||
if not self.intercepted:
|
||||
return
|
||||
self.intercepted = False
|
||||
self.reply()
|
||||
master.handle_accept_intercept(self)
|
||||
|
@ -35,7 +35,7 @@ class WebFlowView(flow.FlowView):
|
||||
app.ClientConnection.broadcast(
|
||||
type="flows",
|
||||
cmd="remove",
|
||||
data=f.get_state(short=True)
|
||||
data=f.id
|
||||
)
|
||||
|
||||
def _recalculate(self, flows):
|
||||
|
@ -1,12 +1,13 @@
|
||||
import os.path
|
||||
import sys
|
||||
import tornado.web
|
||||
import tornado.websocket
|
||||
import logging
|
||||
import json
|
||||
from .. import flow
|
||||
|
||||
|
||||
class APIError(tornado.web.HTTPError):
|
||||
pass
|
||||
|
||||
class IndexHandler(tornado.web.RequestHandler):
|
||||
def get(self):
|
||||
_ = self.xsrf_token # https://github.com/tornadoweb/tornado/issues/645
|
||||
@ -36,38 +37,82 @@ class ClientConnection(WebSocketEventBroadcaster):
|
||||
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):
|
||||
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):
|
||||
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):
|
||||
flow_id = str(flow_id)
|
||||
for flow in self.application.state.flows:
|
||||
if flow.id == flow_id:
|
||||
flow.accept_intercept(self.application.master)
|
||||
break
|
||||
self.flow.accept_intercept(self.master)
|
||||
|
||||
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):
|
||||
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):
|
||||
self.write(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":
|
||||
continue
|
||||
elif k == "intercept":
|
||||
self.application.state.set_intercept(v[0])
|
||||
self.state.set_intercept(v[0])
|
||||
update[k] = v[0]
|
||||
else:
|
||||
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):
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
return self.master.state
|
||||
|
||||
def __init__(self, master, debug):
|
||||
self.master = master
|
||||
handlers = [
|
||||
@ -112,9 +147,12 @@ class Application(tornado.web.Application):
|
||||
(r"/events", Events),
|
||||
(r"/flows", Flows),
|
||||
(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"/clear", Clear),
|
||||
(r"/clear", ClearAll),
|
||||
]
|
||||
settings = dict(
|
||||
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 = {
|
||||
UP: 38,
|
||||
DOWN: 40,
|
||||
@ -92,7 +14,7 @@ var Key = {
|
||||
BACKSPACE: 8,
|
||||
};
|
||||
// Add A-Z
|
||||
for(var i=65; i <= 90; i++){
|
||||
for (var i = 65; i <= 90; i++) {
|
||||
Key[String.fromCharCode(i)] = i;
|
||||
}
|
||||
|
||||
@ -157,6 +79,31 @@ EventEmitter.prototype.removeListener = function (events, f) {
|
||||
}
|
||||
}.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 = {
|
||||
VIEW: "view",
|
||||
SERVER: "server"
|
||||
@ -272,7 +219,18 @@ var FlowActions = {
|
||||
accept_all: function(){
|
||||
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) {
|
||||
AppDispatcher.dispatchViewAction({
|
||||
type: ActionTypes.FLOW_STORE,
|
||||
@ -2140,7 +2098,7 @@ _.extend(ListStore.prototype, EventEmitter.prototype, {
|
||||
this.emit("update", elem);
|
||||
},
|
||||
remove: function (elem_id) {
|
||||
if (!(elem.id in this._pos_map)) {
|
||||
if (!(elem_id in this._pos_map)) {
|
||||
return;
|
||||
}
|
||||
this.list.splice(this._pos_map[elem_id], 1);
|
||||
@ -2408,6 +2366,84 @@ function Connection(url) {
|
||||
}
|
||||
//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',
|
||||
getDefaultProps: function () {
|
||||
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 = {
|
||||
getInitialState: function () {
|
||||
return {
|
||||
@ -3686,6 +3705,10 @@ var MainView = React.createClass({displayName: 'MainView',
|
||||
this.selectFlow(flows[index]);
|
||||
},
|
||||
onKeyDown: function (e) {
|
||||
var flow = this.getSelected();
|
||||
if(e.ctrlKey){
|
||||
return;
|
||||
}
|
||||
switch (e.keyCode) {
|
||||
case Key.K:
|
||||
case Key.UP:
|
||||
@ -3724,11 +3747,32 @@ var MainView = React.createClass({displayName: 'MainView',
|
||||
this.refs.flowDetails.nextTab(+1);
|
||||
}
|
||||
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:
|
||||
if (e.shiftKey) {
|
||||
FlowActions.accept_all();
|
||||
} else if(this.getSelected()) {
|
||||
FlowActions.accept(this.getSelected());
|
||||
} else if (flow) {
|
||||
FlowActions.accept(flow);
|
||||
}
|
||||
break;
|
||||
case Key.R:
|
||||
if(!e.shiftKey && flow){
|
||||
FlowActions.replay(flow);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
|
@ -77,7 +77,18 @@ var FlowActions = {
|
||||
accept_all: function(){
|
||||
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) {
|
||||
AppDispatcher.dispatchViewAction({
|
||||
type: ActionTypes.FLOW_STORE,
|
||||
|
@ -105,6 +105,10 @@ var MainView = React.createClass({
|
||||
this.selectFlow(flows[index]);
|
||||
},
|
||||
onKeyDown: function (e) {
|
||||
var flow = this.getSelected();
|
||||
if(e.ctrlKey){
|
||||
return;
|
||||
}
|
||||
switch (e.keyCode) {
|
||||
case Key.K:
|
||||
case Key.UP:
|
||||
@ -143,11 +147,32 @@ var MainView = React.createClass({
|
||||
this.refs.flowDetails.nextTab(+1);
|
||||
}
|
||||
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:
|
||||
if (e.shiftKey) {
|
||||
FlowActions.accept_all();
|
||||
} else if(this.getSelected()) {
|
||||
FlowActions.accept(this.getSelected());
|
||||
} else if (flow) {
|
||||
FlowActions.accept(flow);
|
||||
}
|
||||
break;
|
||||
case Key.R:
|
||||
if(!e.shiftKey && flow){
|
||||
FlowActions.replay(flow);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
|
@ -1,5 +1,83 @@
|
||||
//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({
|
||||
getDefaultProps: function () {
|
||||
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);
|
||||
},
|
||||
remove: function (elem_id) {
|
||||
if (!(elem.id in this._pos_map)) {
|
||||
if (!(elem_id in this._pos_map)) {
|
||||
return;
|
||||
}
|
||||
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 = {
|
||||
UP: 38,
|
||||
DOWN: 40,
|
||||
@ -92,7 +14,7 @@ var Key = {
|
||||
BACKSPACE: 8,
|
||||
};
|
||||
// Add A-Z
|
||||
for(var i=65; i <= 90; i++){
|
||||
for (var i = 65; i <= 90; i++) {
|
||||
Key[String.fromCharCode(i)] = i;
|
||||
}
|
||||
|
||||
@ -157,3 +79,28 @@ EventEmitter.prototype.removeListener = function (events, f) {
|
||||
}
|
||||
}.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