mirror of
https://github.com/node-red/node-red.git
synced 2023-10-10 13:36:53 +02:00
Merge branch 'pr_2990' into dev
This commit is contained in:
commit
4edea59ab1
@ -18,7 +18,7 @@
|
|||||||
/**
|
/**
|
||||||
* options:
|
* options:
|
||||||
* - addButton : boolean|string - text for add label, default 'add'
|
* - addButton : boolean|string - text for add label, default 'add'
|
||||||
* - buttons : array - list of custom buttons (objects with fields 'label', 'icon', 'title', 'click')
|
* - buttons : array - list of custom buttons (objects with fields 'id', 'label', 'icon', 'title', 'click')
|
||||||
* - height : number|'auto'
|
* - height : number|'auto'
|
||||||
* - resize : function - called when list as a whole is resized
|
* - resize : function - called when list as a whole is resized
|
||||||
* - resizeItem : function(item) - called to resize individual item
|
* - resizeItem : function(item) - called to resize individual item
|
||||||
@ -94,7 +94,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
buttons.forEach(function(button) {
|
buttons.forEach(function(button) {
|
||||||
var element = $('<a href="#" class="red-ui-button red-ui-button-small red-ui-editableList-addButton" style="margin-top: 4px; margin-right: 5px;"></a>')
|
var element = $('<button type="button" class="red-ui-button red-ui-button-small red-ui-editableList-addButton" style="margin-top: 4px; margin-right: 5px;"></button>')
|
||||||
.appendTo(that.topContainer)
|
.appendTo(that.topContainer)
|
||||||
.on("click", function(evt) {
|
.on("click", function(evt) {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
@ -103,6 +103,9 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (button.id) {
|
||||||
|
element.attr("id", button.id);
|
||||||
|
}
|
||||||
if (button.title) {
|
if (button.title) {
|
||||||
element.attr("title", button.title);
|
element.attr("title", button.title);
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
**/
|
**/
|
||||||
RED.tray = (function() {
|
RED.tray = (function() {
|
||||||
|
|
||||||
var stack = [];
|
var stack = [];
|
||||||
var editorStack;
|
var editorStack;
|
||||||
@ -288,9 +288,9 @@ RED.tray = (function() {
|
|||||||
right: -(tray.tray.width()+10)+"px"
|
right: -(tray.tray.width()+10)+"px"
|
||||||
});
|
});
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
if (tray.options.close) {
|
try {
|
||||||
tray.options.close();
|
if (tray.options.close) { tray.options.close(); }
|
||||||
}
|
} catch (ex) { }
|
||||||
tray.tray.remove();
|
tray.tray.remove();
|
||||||
if (stack.length > 0) {
|
if (stack.length > 0) {
|
||||||
var oldTray = stack[stack.length-1];
|
var oldTray = stack[stack.length-1];
|
||||||
|
@ -162,7 +162,62 @@
|
|||||||
height += 16;
|
height += 16;
|
||||||
$("#node-input-property-container").editableList('height',height);
|
$("#node-input-property-container").editableList('height',height);
|
||||||
}
|
}
|
||||||
|
/** Retrieve editableList items (refactored for re-use in the form inject button)*/
|
||||||
|
function getProps(el, legacy) {
|
||||||
|
var result = {
|
||||||
|
props: []
|
||||||
|
}
|
||||||
|
el.each(function(i) {
|
||||||
|
var prop = $(this);
|
||||||
|
var p = {
|
||||||
|
p:prop.find(".node-input-prop-property-name").typedInput('value')
|
||||||
|
};
|
||||||
|
if (p.p) {
|
||||||
|
p.v = prop.find(".node-input-prop-property-value").typedInput('value');
|
||||||
|
p.vt = prop.find(".node-input-prop-property-value").typedInput('type');
|
||||||
|
if(legacy) {
|
||||||
|
if (p.p === "payload") { // save payload to old "legacy" property
|
||||||
|
result.payloadType = p.vt;
|
||||||
|
result.payload = p.v;
|
||||||
|
delete p.v;
|
||||||
|
delete p.vt;
|
||||||
|
} else if (p.p === "topic" && p.vt === "str") {
|
||||||
|
result.topic = p.v;
|
||||||
|
delete p.v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result.props.push(p);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
/** Perform inject, optionally sending a custom msg (refactored for re-use in the form inject button)*/
|
||||||
|
function doInject(node, customMsg) {
|
||||||
|
var label = node._def.label.call(node);
|
||||||
|
if (label.length > 30) {
|
||||||
|
label = label.substring(0, 50) + "...";
|
||||||
|
}
|
||||||
|
label = label.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
||||||
|
$.ajax({
|
||||||
|
url: "inject/" + node.id,
|
||||||
|
type: "POST",
|
||||||
|
data: customMsg,
|
||||||
|
success: function (resp) {
|
||||||
|
RED.notify(node._("inject.success", { label: label }), { type: "success", id: "inject", timeout: 2000 });
|
||||||
|
},
|
||||||
|
error: function (jqXHR, textStatus, errorThrown) {
|
||||||
|
if (jqXHR.status == 404) {
|
||||||
|
RED.notify(node._("common.notification.error", { message: node._("common.notification.errors.not-deployed") }), "error");
|
||||||
|
} else if (jqXHR.status == 500) {
|
||||||
|
RED.notify(node._("common.notification.error", { message: node._("inject.errors.failed") }), "error");
|
||||||
|
} else if (jqXHR.status == 0) {
|
||||||
|
RED.notify(node._("common.notification.error", { message: node._("common.notification.errors.no-response") }), "error");
|
||||||
|
} else {
|
||||||
|
RED.notify(node._("common.notification.error", { message: node._("common.notification.errors.unexpected", { status: jqXHR.status, message: textStatus }) }), "error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
RED.nodes.registerType('inject',{
|
RED.nodes.registerType('inject',{
|
||||||
category: 'common',
|
category: 'common',
|
||||||
color:"#a6bbcf",
|
color:"#a6bbcf",
|
||||||
@ -283,14 +338,15 @@
|
|||||||
return this.name?"node_label_italic":"";
|
return this.name?"node_label_italic":"";
|
||||||
},
|
},
|
||||||
oneditprepare: function() {
|
oneditprepare: function() {
|
||||||
if (this.payloadType == null) {
|
var node = this;
|
||||||
if (this.payload == "") {
|
if (node.payloadType == null) {
|
||||||
this.payloadType = "date";
|
if (node.payload == "") {
|
||||||
|
node.payloadType = "date";
|
||||||
} else {
|
} else {
|
||||||
this.payloadType = "str";
|
node.payloadType = "str";
|
||||||
}
|
}
|
||||||
} else if (this.payloadType === 'string' || this.payloadType === 'none') {
|
} else if (node.payloadType === 'string' || node.payloadType === 'none') {
|
||||||
this.payloadType = "str";
|
node.payloadType = "str";
|
||||||
}
|
}
|
||||||
|
|
||||||
$("#inject-time-type-select").on("change", function() {
|
$("#inject-time-type-select").on("change", function() {
|
||||||
@ -345,17 +401,17 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
var repeattype = "none";
|
var repeattype = "none";
|
||||||
if (this.repeat != "" && this.repeat != 0) {
|
if (node.repeat != "" && node.repeat != 0) {
|
||||||
repeattype = "interval";
|
repeattype = "interval";
|
||||||
var r = "s";
|
var r = "s";
|
||||||
var c = this.repeat;
|
var c = node.repeat;
|
||||||
if (this.repeat % 60 === 0) { r = "m"; c = c/60; }
|
if (node.repeat % 60 === 0) { r = "m"; c = c/60; }
|
||||||
if (this.repeat % 1440 === 0) { r = "h"; c = c/60; }
|
if (node.repeat % 1440 === 0) { r = "h"; c = c/60; }
|
||||||
$("#inject-time-interval-count").val(c);
|
$("#inject-time-interval-count").val(c);
|
||||||
$("#inject-time-interval-units").val(r);
|
$("#inject-time-interval-units").val(r);
|
||||||
$("#inject-time-interval-days").prop("disabled","disabled");
|
$("#inject-time-interval-days").prop("disabled","disabled");
|
||||||
} else if (this.crontab) {
|
} else if (node.crontab) {
|
||||||
var cronparts = this.crontab.split(" ");
|
var cronparts = node.crontab.split(" ");
|
||||||
var days = cronparts[4];
|
var days = cronparts[4];
|
||||||
if (!isNaN(cronparts[0]) && !isNaN(cronparts[1])) {
|
if (!isNaN(cronparts[0]) && !isNaN(cronparts[1])) {
|
||||||
repeattype = "time";
|
repeattype = "time";
|
||||||
@ -431,7 +487,24 @@
|
|||||||
|
|
||||||
/* */
|
/* */
|
||||||
|
|
||||||
$('#node-input-property-container').css('min-height','120px').css('min-width','450px').editableList({
|
var eList = $('#node-input-property-container').css('min-height','120px').css('min-width','450px');
|
||||||
|
|
||||||
|
eList.editableList({
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
id: "node-inject-test-inject-button",
|
||||||
|
label: node._("inject.injectNow"),
|
||||||
|
click: function(e) {
|
||||||
|
var items = eList.editableList('items');
|
||||||
|
var result = getProps(items);
|
||||||
|
var m = {__user_inject_props__: []};
|
||||||
|
if (result && result.props && result.props.length) {
|
||||||
|
m.__user_inject_props__ = result.props;
|
||||||
|
}
|
||||||
|
doInject(node, m);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
addItem: function(container,i,opt) {
|
addItem: function(container,i,opt) {
|
||||||
var prop = opt;
|
var prop = opt;
|
||||||
if (!prop.hasOwnProperty('p')) {
|
if (!prop.hasOwnProperty('p')) {
|
||||||
@ -465,33 +538,38 @@
|
|||||||
removable: true,
|
removable: true,
|
||||||
sortable: true
|
sortable: true
|
||||||
});
|
});
|
||||||
|
$('#node-inject-test-inject-button').css("float", "right").css("margin-right", "unset");
|
||||||
|
|
||||||
if (!this.props) {
|
if (RED.nodes.subflow(node.z)) {
|
||||||
|
$('#node-inject-test-inject-button').attr("disabled",true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!node.props) {
|
||||||
var payload = {
|
var payload = {
|
||||||
p:'payload',
|
p:'payload',
|
||||||
v: this.payload ? this.payload : '',
|
v: node.payload ? node.payload : '',
|
||||||
vt:this.payloadType ? this.payloadType : 'date'
|
vt:node.payloadType ? node.payloadType : 'date'
|
||||||
};
|
};
|
||||||
var topic = {
|
var topic = {
|
||||||
p:'topic',
|
p:'topic',
|
||||||
v: this.topic ? this.topic : '',
|
v: node.topic ? node.topic : '',
|
||||||
vt:'string'
|
vt:'string'
|
||||||
}
|
}
|
||||||
this.props = [payload,topic];
|
node.props = [payload,topic];
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var i=0; i<this.props.length; i++) {
|
for (var i=0; i<node.props.length; i++) {
|
||||||
var prop = this.props[i];
|
var prop = node.props[i];
|
||||||
var newProp = { p: prop.p, v: prop.v, vt: prop.vt };
|
var newProp = { p: prop.p, v: prop.v, vt: prop.vt };
|
||||||
if (newProp.v === undefined) {
|
if (newProp.v === undefined) {
|
||||||
if (prop.p === 'payload') {
|
if (prop.p === 'payload') {
|
||||||
newProp.v = this.payload ? this.payload : '';
|
newProp.v = node.payload ? node.payload : '';
|
||||||
newProp.vt = this.payloadType ? this.payloadType : 'date';
|
newProp.vt = node.payloadType ? node.payloadType : 'date';
|
||||||
} else if (prop.p === 'topic' && prop.vt === "str") {
|
} else if (prop.p === 'topic' && prop.vt === "str") {
|
||||||
newProp.v = this.topic ? this.topic : '';
|
newProp.v = node.topic ? node.topic : '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$("#node-input-property-container").editableList('addItem',newProp);
|
eList.editableList('addItem',newProp);
|
||||||
}
|
}
|
||||||
|
|
||||||
$("#inject-time-type-select").trigger("change");
|
$("#inject-time-type-select").trigger("change");
|
||||||
@ -589,67 +667,26 @@
|
|||||||
$("#node-input-repeat").val(repeat);
|
$("#node-input-repeat").val(repeat);
|
||||||
$("#node-input-crontab").val(crontab);
|
$("#node-input-crontab").val(crontab);
|
||||||
|
|
||||||
/* Gather the injected properties of the msg object */
|
/* Gather the properties */
|
||||||
var props = $("#node-input-property-container").editableList('items');
|
var items = $("#node-input-property-container").editableList('items');
|
||||||
var node = this;
|
delete this.payloadType;
|
||||||
node.props= [];
|
delete this.payload;
|
||||||
delete node.payloadType;
|
this.topic = "";
|
||||||
delete node.payload;
|
var result = getProps(items, true);
|
||||||
node.topic = "";
|
this.props = result.props;
|
||||||
props.each(function(i) {
|
if(result.payloadType) { this.payloadType = result.payloadType; };
|
||||||
var prop = $(this);
|
if(result.payload) { this.payload = result.payload; };
|
||||||
var p = {
|
if(result.topic) { this.topic = result.topic; };
|
||||||
p:prop.find(".node-input-prop-property-name").typedInput('value')
|
|
||||||
};
|
|
||||||
if (p.p) {
|
|
||||||
p.v = prop.find(".node-input-prop-property-value").typedInput('value');
|
|
||||||
p.vt = prop.find(".node-input-prop-property-value").typedInput('type');
|
|
||||||
if (p.p === "payload") { // save payload to old "legacy" property
|
|
||||||
node.payloadType = p.vt;
|
|
||||||
node.payload = p.v;
|
|
||||||
delete p.v;
|
|
||||||
delete p.vt;
|
|
||||||
} else if (p.p === "topic" && p.vt === "str") {
|
|
||||||
node.topic = p.v;
|
|
||||||
delete p.v;
|
|
||||||
}
|
|
||||||
node.props.push(p);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
button: {
|
button: {
|
||||||
enabled: function() {
|
enabled: function() {
|
||||||
return !this.changed
|
return !this.changed
|
||||||
},
|
},
|
||||||
onclick: function() {
|
onclick: function () {
|
||||||
if (this.changed) {
|
if (this.changed) {
|
||||||
return RED.notify(RED._("notification.warning", {message:RED._("notification.warnings.undeployedChanges")}),"warning");
|
return RED.notify(RED._("notification.warning", { message: RED._("notification.warnings.undeployedChanges") }), "warning");
|
||||||
}
|
}
|
||||||
|
doInject(this);
|
||||||
var label = this._def.label.call(this);
|
|
||||||
if (label.length > 30) {
|
|
||||||
label = label.substring(0,50)+"...";
|
|
||||||
}
|
|
||||||
label = label.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">");
|
|
||||||
var node = this;
|
|
||||||
$.ajax({
|
|
||||||
url: "inject/"+this.id,
|
|
||||||
type:"POST",
|
|
||||||
success: function(resp) {
|
|
||||||
RED.notify(node._("inject.success",{label:label}),{type:"success",id:"inject", timeout: 2000});
|
|
||||||
},
|
|
||||||
error: function(jqXHR,textStatus,errorThrown) {
|
|
||||||
if (jqXHR.status == 404) {
|
|
||||||
RED.notify(node._("common.notification.error",{message:node._("common.notification.errors.not-deployed")}),"error");
|
|
||||||
} else if (jqXHR.status == 500) {
|
|
||||||
RED.notify(node._("common.notification.error",{message:node._("inject.errors.failed")}),"error");
|
|
||||||
} else if (jqXHR.status == 0) {
|
|
||||||
RED.notify(node._("common.notification.error",{message:node._("common.notification.errors.no-response")}),"error");
|
|
||||||
} else {
|
|
||||||
RED.notify(node._("common.notification.error",{message:node._("common.notification.errors.unexpected",{status:jqXHR.status,message:textStatus})}),"error");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
oneditresize: resizeDialog
|
oneditresize: resizeDialog
|
||||||
|
@ -100,8 +100,12 @@ module.exports = function(RED) {
|
|||||||
|
|
||||||
this.on("input", function(msg, send, done) {
|
this.on("input", function(msg, send, done) {
|
||||||
var errors = [];
|
var errors = [];
|
||||||
|
var props = this.props;
|
||||||
this.props.forEach(p => {
|
if(msg.__user_inject_props__ && Array.isArray(msg.__user_inject_props__)) {
|
||||||
|
props = msg.__user_inject_props__;
|
||||||
|
}
|
||||||
|
delete msg.__user_inject_props__;
|
||||||
|
props.forEach(p => {
|
||||||
var property = p.p;
|
var property = p.p;
|
||||||
var value = p.v ? p.v : '';
|
var value = p.v ? p.v : '';
|
||||||
var valueType = p.vt ? p.vt : 'str';
|
var valueType = p.vt ? p.vt : 'str';
|
||||||
@ -156,7 +160,11 @@ module.exports = function(RED) {
|
|||||||
var node = RED.nodes.getNode(req.params.id);
|
var node = RED.nodes.getNode(req.params.id);
|
||||||
if (node != null) {
|
if (node != null) {
|
||||||
try {
|
try {
|
||||||
node.receive();
|
if (req.body && req.body.__user_inject_props__) {
|
||||||
|
node.receive(req.body);
|
||||||
|
} else {
|
||||||
|
node.receive();
|
||||||
|
}
|
||||||
res.sendStatus(200);
|
res.sendStatus(200);
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
res.sendStatus(500);
|
res.sendStatus(500);
|
||||||
|
@ -32,6 +32,7 @@
|
|||||||
},
|
},
|
||||||
"inject": {
|
"inject": {
|
||||||
"inject": "inject",
|
"inject": "inject",
|
||||||
|
"injectNow": "inject now",
|
||||||
"repeat": "repeat = __repeat__",
|
"repeat": "repeat = __repeat__",
|
||||||
"crontab": "crontab = __crontab__",
|
"crontab": "crontab = __crontab__",
|
||||||
"stopped": "stopped",
|
"stopped": "stopped",
|
||||||
|
@ -510,6 +510,36 @@ describe('inject node', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should inject custom properties in message', function (done) {
|
||||||
|
//n1: inject node with { topic:"static", payload:"static", bool1:true, str1:"1" }
|
||||||
|
var flow = [{id: "n1", type: "inject", props: [{p:"payload", v:"static", vt:"str"}, {p:"topic", v:"static", vt:"str"}, {p:"bool1", v:"true", vt:"bool"}, {p:"str1", v:"1", vt:"str"}], wires: [["n2"]], z: "flow"},
|
||||||
|
{id: "n2", type: "helper"}];
|
||||||
|
helper.load(injectNode, flow, function () {
|
||||||
|
var n1 = helper.getNode("n1");
|
||||||
|
var n2 = helper.getNode("n2");
|
||||||
|
n2.on("input", function (msg) {
|
||||||
|
try {
|
||||||
|
msg.should.not.have.property("payload"); //payload removed
|
||||||
|
msg.should.have.property("topic", "t_override"); //changed value to t_override
|
||||||
|
msg.should.have.property("str1", 1);//changed type from str to num
|
||||||
|
msg.should.have.property("num1", 1);//new prop
|
||||||
|
msg.should.have.property("bool1", false);//changed value to false
|
||||||
|
done();
|
||||||
|
} catch (err) {
|
||||||
|
done(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
n1.receive({ __user_inject_props__: [
|
||||||
|
{p:"topic", v:"t_override", vt:"str"}, //change value to t_override
|
||||||
|
{p:"str1", v:"1", vt:"num"}, //change type
|
||||||
|
{p:"num1", v:"1", vt:"num"}, //new prop
|
||||||
|
{p:"bool1", v:"false", vt:"bool"}, //change value to false
|
||||||
|
]});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should inject multiple properties using legacy props if needed', function (done) {
|
it('should inject multiple properties using legacy props if needed', function (done) {
|
||||||
var flow = [{id: "n1", type: "inject", payload:"123", payloadType:"num", topic:"foo", props: [{p:"topic", vt:"str"}, {p:"payload"}], wires: [["n2"]], z: "flow"},
|
var flow = [{id: "n1", type: "inject", payload:"123", payloadType:"num", topic:"foo", props: [{p:"topic", vt:"str"}, {p:"payload"}], wires: [["n2"]], z: "flow"},
|
||||||
{id: "n2", type: "helper"}];
|
{id: "n2", type: "helper"}];
|
||||||
@ -592,6 +622,46 @@ describe('inject node', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should inject custom properties in posted message', function(done) {
|
||||||
|
var flow = [{id:"n1", type:"inject", payloadType:"str", topic: "t4",payload:"hello", wires:[["n4"]] },
|
||||||
|
{ id:"n4", type:"helper"}];
|
||||||
|
helper.load(injectNode, flow, function() {
|
||||||
|
var n4 = helper.getNode("n4");
|
||||||
|
n4.on("input", function(msg) {
|
||||||
|
msg.should.not.have.property("payload"); //payload removed
|
||||||
|
msg.should.have.property("topic", "t_override"); //changed value to t_override
|
||||||
|
msg.should.have.property("str1", "1"); //injected prop
|
||||||
|
msg.should.have.property("num1", 1); //injected prop
|
||||||
|
msg.should.have.property("bool1", true); //injected prop
|
||||||
|
|
||||||
|
helper.clearFlows().then(function() {
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
helper.request()
|
||||||
|
.post('/inject/n1')
|
||||||
|
.send({ __user_inject_props__: [
|
||||||
|
{p:"topic", v:"t_override", vt:"str"}, //change value to t_override
|
||||||
|
{p:"str1", v:"1", vt:"str"}, //new prop
|
||||||
|
{p:"num1", v:"1", vt:"num"}, //new prop
|
||||||
|
{p:"bool1", v:"true", vt:"bool"}, //new prop
|
||||||
|
]})
|
||||||
|
.expect(200).end(function(err) {
|
||||||
|
if (err) {
|
||||||
|
console.log(err);
|
||||||
|
return helper.clearFlows()
|
||||||
|
.then(function () {
|
||||||
|
done(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch(err) {
|
||||||
|
done(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should fail for invalid node', function(done) {
|
it('should fail for invalid node', function(done) {
|
||||||
helper.request().post('/inject/invalid').expect(404).end(done);
|
helper.request().post('/inject/invalid').expect(404).end(done);
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user