mirror of
https://github.com/node-red/node-red.git
synced 2023-10-10 13:36:53 +02:00
Merge branch 'dev' into update-deps
This commit is contained in:
commit
69d643942c
@ -14,8 +14,6 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
**/
|
**/
|
||||||
|
|
||||||
var express = require("express");
|
|
||||||
|
|
||||||
var nodes = require("./nodes");
|
var nodes = require("./nodes");
|
||||||
var flows = require("./flows");
|
var flows = require("./flows");
|
||||||
var flow = require("./flow");
|
var flow = require("./flow");
|
||||||
@ -37,18 +35,9 @@ module.exports = {
|
|||||||
plugins.init(runtimeAPI);
|
plugins.init(runtimeAPI);
|
||||||
diagnostics.init(settings, runtimeAPI);
|
diagnostics.init(settings, runtimeAPI);
|
||||||
|
|
||||||
var needsPermission = auth.needsPermission;
|
const needsPermission = auth.needsPermission;
|
||||||
|
|
||||||
var adminApp = express();
|
|
||||||
|
|
||||||
var defaultServerSettings = {
|
|
||||||
"x-powered-by": false
|
|
||||||
}
|
|
||||||
var serverSettings = Object.assign({},defaultServerSettings,settings.httpServerOptions||{});
|
|
||||||
for (var eOption in serverSettings) {
|
|
||||||
adminApp.set(eOption, serverSettings[eOption]);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
const adminApp = apiUtil.createExpressApp(settings)
|
||||||
|
|
||||||
// Flows
|
// Flows
|
||||||
adminApp.get("/flows",needsPermission("flows.read"),flows.get,apiUtil.errorHandler);
|
adminApp.get("/flows",needsPermission("flows.read"),flows.get,apiUtil.errorHandler);
|
||||||
|
@ -46,14 +46,15 @@ module.exports = {
|
|||||||
runtimeAPI = _runtimeAPI;
|
runtimeAPI = _runtimeAPI;
|
||||||
needsPermission = auth.needsPermission;
|
needsPermission = auth.needsPermission;
|
||||||
if (!settings.disableEditor) {
|
if (!settings.disableEditor) {
|
||||||
info.init(runtimeAPI);
|
info.init(settings, runtimeAPI);
|
||||||
comms.init(server,settings,runtimeAPI);
|
comms.init(server,settings,runtimeAPI);
|
||||||
|
|
||||||
var ui = require("./ui");
|
var ui = require("./ui");
|
||||||
|
|
||||||
ui.init(runtimeAPI);
|
ui.init(runtimeAPI);
|
||||||
|
|
||||||
var editorApp = express();
|
const editorApp = apiUtil.createExpressApp(settings)
|
||||||
|
|
||||||
if (settings.requireHttps === true) {
|
if (settings.requireHttps === true) {
|
||||||
editorApp.enable('trust proxy');
|
editorApp.enable('trust proxy');
|
||||||
editorApp.use(function (req, res, next) {
|
editorApp.use(function (req, res, next) {
|
||||||
@ -86,7 +87,7 @@ module.exports = {
|
|||||||
|
|
||||||
//Projects
|
//Projects
|
||||||
var projects = require("./projects");
|
var projects = require("./projects");
|
||||||
projects.init(runtimeAPI);
|
projects.init(settings, runtimeAPI);
|
||||||
editorApp.use("/projects",projects.app());
|
editorApp.use("/projects",projects.app());
|
||||||
|
|
||||||
// Locales
|
// Locales
|
||||||
|
@ -14,9 +14,9 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
**/
|
**/
|
||||||
|
|
||||||
var express = require("express");
|
|
||||||
var apiUtils = require("../util");
|
var apiUtils = require("../util");
|
||||||
|
|
||||||
|
var settings;
|
||||||
var runtimeAPI;
|
var runtimeAPI;
|
||||||
var needsPermission = require("../auth").needsPermission;
|
var needsPermission = require("../auth").needsPermission;
|
||||||
|
|
||||||
@ -77,11 +77,12 @@ function getProjectRemotes(req,res) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
module.exports = {
|
module.exports = {
|
||||||
init: function(_runtimeAPI) {
|
init: function(_settings, _runtimeAPI) {
|
||||||
|
settings = _settings;
|
||||||
runtimeAPI = _runtimeAPI;
|
runtimeAPI = _runtimeAPI;
|
||||||
},
|
},
|
||||||
app: function() {
|
app: function() {
|
||||||
var app = express();
|
var app = apiUtils.createExpressApp(settings)
|
||||||
|
|
||||||
app.use(function(req,res,next) {
|
app.use(function(req,res,next) {
|
||||||
runtimeAPI.projects.available().then(function(available) {
|
runtimeAPI.projects.available().then(function(available) {
|
||||||
|
@ -18,9 +18,9 @@ var runtimeAPI;
|
|||||||
var sshkeys = require("./sshkeys");
|
var sshkeys = require("./sshkeys");
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
init: function(_runtimeAPI) {
|
init: function(settings, _runtimeAPI) {
|
||||||
runtimeAPI = _runtimeAPI;
|
runtimeAPI = _runtimeAPI;
|
||||||
sshkeys.init(runtimeAPI);
|
sshkeys.init(settings, runtimeAPI);
|
||||||
},
|
},
|
||||||
userSettings: function(req, res) {
|
userSettings: function(req, res) {
|
||||||
var opts = {
|
var opts = {
|
||||||
|
@ -17,13 +17,15 @@
|
|||||||
var apiUtils = require("../util");
|
var apiUtils = require("../util");
|
||||||
var express = require("express");
|
var express = require("express");
|
||||||
var runtimeAPI;
|
var runtimeAPI;
|
||||||
|
var settings;
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
init: function(_runtimeAPI) {
|
init: function(_settings, _runtimeAPI) {
|
||||||
runtimeAPI = _runtimeAPI;
|
runtimeAPI = _runtimeAPI;
|
||||||
|
settings = _settings;
|
||||||
},
|
},
|
||||||
app: function() {
|
app: function() {
|
||||||
var app = express();
|
const app = apiUtils.createExpressApp(settings);
|
||||||
|
|
||||||
// List all SSH keys
|
// List all SSH keys
|
||||||
app.get("/", function(req,res) {
|
app.get("/", function(req,res) {
|
||||||
|
@ -19,6 +19,7 @@ var util = require("util");
|
|||||||
var path = require("path");
|
var path = require("path");
|
||||||
var fs = require("fs");
|
var fs = require("fs");
|
||||||
var clone = require("clone");
|
var clone = require("clone");
|
||||||
|
const apiUtil = require("../util")
|
||||||
|
|
||||||
var defaultContext = {
|
var defaultContext = {
|
||||||
page: {
|
page: {
|
||||||
@ -27,8 +28,7 @@ var defaultContext = {
|
|||||||
tabicon: {
|
tabicon: {
|
||||||
icon: "red/images/node-red-icon-black.svg",
|
icon: "red/images/node-red-icon-black.svg",
|
||||||
colour: "#8f0000"
|
colour: "#8f0000"
|
||||||
},
|
}
|
||||||
version: require(path.join(__dirname,"../../package.json")).version
|
|
||||||
},
|
},
|
||||||
header: {
|
header: {
|
||||||
title: "Node-RED",
|
title: "Node-RED",
|
||||||
@ -40,6 +40,7 @@ var defaultContext = {
|
|||||||
vendorMonaco: ""
|
vendorMonaco: ""
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
var settings;
|
||||||
|
|
||||||
var theme = null;
|
var theme = null;
|
||||||
var themeContext = clone(defaultContext);
|
var themeContext = clone(defaultContext);
|
||||||
@ -92,7 +93,8 @@ function serveFilesFromTheme(themeValue, themeApp, directory, baseDirectory) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
init: function(settings, _runtimeAPI) {
|
init: function(_settings, _runtimeAPI) {
|
||||||
|
settings = _settings;
|
||||||
runtimeAPI = _runtimeAPI;
|
runtimeAPI = _runtimeAPI;
|
||||||
themeContext = clone(defaultContext);
|
themeContext = clone(defaultContext);
|
||||||
if (process.env.NODE_ENV == "development") {
|
if (process.env.NODE_ENV == "development") {
|
||||||
@ -113,7 +115,15 @@ module.exports = {
|
|||||||
var url;
|
var url;
|
||||||
themeSettings = {};
|
themeSettings = {};
|
||||||
|
|
||||||
themeApp = express();
|
themeApp = apiUtil.createExpressApp(settings);
|
||||||
|
|
||||||
|
const defaultServerSettings = {
|
||||||
|
"x-powered-by": false
|
||||||
|
}
|
||||||
|
const serverSettings = Object.assign({},defaultServerSettings,settings.httpServerOptions||{});
|
||||||
|
for (const eOption in serverSettings) {
|
||||||
|
themeApp.set(eOption, serverSettings[eOption]);
|
||||||
|
}
|
||||||
|
|
||||||
if (theme.page) {
|
if (theme.page) {
|
||||||
|
|
||||||
|
@ -37,7 +37,6 @@ var adminApp;
|
|||||||
var server;
|
var server;
|
||||||
var editor;
|
var editor;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialise the module.
|
* Initialise the module.
|
||||||
* @param {Object} settings The runtime settings
|
* @param {Object} settings The runtime settings
|
||||||
@ -49,7 +48,7 @@ var editor;
|
|||||||
function init(settings,_server,storage,runtimeAPI) {
|
function init(settings,_server,storage,runtimeAPI) {
|
||||||
server = _server;
|
server = _server;
|
||||||
if (settings.httpAdminRoot !== false) {
|
if (settings.httpAdminRoot !== false) {
|
||||||
adminApp = express();
|
adminApp = apiUtil.createExpressApp(settings);
|
||||||
|
|
||||||
var cors = require('cors');
|
var cors = require('cors');
|
||||||
var corsHandler = cors({
|
var corsHandler = cors({
|
||||||
@ -64,14 +63,6 @@ function init(settings,_server,storage,runtimeAPI) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var defaultServerSettings = {
|
|
||||||
"x-powered-by": false
|
|
||||||
}
|
|
||||||
var serverSettings = Object.assign({},defaultServerSettings,settings.httpServerOptions||{});
|
|
||||||
for (var eOption in serverSettings) {
|
|
||||||
adminApp.set(eOption, serverSettings[eOption]);
|
|
||||||
}
|
|
||||||
|
|
||||||
auth.init(settings,storage);
|
auth.init(settings,storage);
|
||||||
|
|
||||||
var maxApiRequestSize = settings.apiMaxLength || '5mb';
|
var maxApiRequestSize = settings.apiMaxLength || '5mb';
|
||||||
@ -136,10 +127,11 @@ async function stop() {
|
|||||||
editor.stop();
|
editor.stop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
init: init,
|
init,
|
||||||
start: start,
|
start,
|
||||||
stop: stop,
|
stop,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @memberof @node-red/editor-api
|
* @memberof @node-red/editor-api
|
||||||
|
@ -14,10 +14,9 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
**/
|
**/
|
||||||
|
|
||||||
|
const express = require("express");
|
||||||
|
|
||||||
var log = require("@node-red/util").log; // TODO: separate module
|
const { log, i18n } = require("@node-red/util");
|
||||||
var i18n = require("@node-red/util").i18n; // TODO: separate module
|
|
||||||
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
errorHandler: function(err,req,res,next) {
|
errorHandler: function(err,req,res,next) {
|
||||||
@ -64,5 +63,17 @@ module.exports = {
|
|||||||
path: req.path,
|
path: req.path,
|
||||||
ip: (req.headers && req.headers['x-forwarded-for']) || (req.connection && req.connection.remoteAddress) || undefined
|
ip: (req.headers && req.headers['x-forwarded-for']) || (req.connection && req.connection.remoteAddress) || undefined
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
createExpressApp: function(settings) {
|
||||||
|
const app = express();
|
||||||
|
|
||||||
|
const defaultServerSettings = {
|
||||||
|
"x-powered-by": false
|
||||||
|
}
|
||||||
|
const serverSettings = Object.assign({},defaultServerSettings,settings.httpServerOptions||{});
|
||||||
|
for (let eOption in serverSettings) {
|
||||||
|
app.set(eOption, serverSettings[eOption]);
|
||||||
|
}
|
||||||
|
return app
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -504,6 +504,7 @@
|
|||||||
"unassigned": "未割当",
|
"unassigned": "未割当",
|
||||||
"global": "グローバル",
|
"global": "グローバル",
|
||||||
"workspace": "ワークスペース",
|
"workspace": "ワークスペース",
|
||||||
|
"editor": "編集ダイアログ",
|
||||||
"selectAll": "全てのノードを選択",
|
"selectAll": "全てのノードを選択",
|
||||||
"selectNone": "選択を外す",
|
"selectNone": "選択を外す",
|
||||||
"selectAllConnected": "接続されたノードを選択",
|
"selectAllConnected": "接続されたノードを選択",
|
||||||
@ -1203,7 +1204,7 @@
|
|||||||
"fr": "フランス語",
|
"fr": "フランス語",
|
||||||
"ja": "日本語",
|
"ja": "日本語",
|
||||||
"ko": "韓国語",
|
"ko": "韓国語",
|
||||||
"pt-BR":"ポルトガル語",
|
"pt-BR": "ポルトガル語",
|
||||||
"ru": "ロシア語",
|
"ru": "ロシア語",
|
||||||
"zh-CN": "中国語(簡体)",
|
"zh-CN": "中国語(簡体)",
|
||||||
"zh-TW": "中国語(繁体)"
|
"zh-TW": "中国語(繁体)"
|
||||||
|
@ -37,13 +37,13 @@ RED.clipboard = (function() {
|
|||||||
// IE11 workaround
|
// IE11 workaround
|
||||||
// IE does not support data uri scheme for downloading data
|
// IE does not support data uri scheme for downloading data
|
||||||
var blob = new Blob([data], {
|
var blob = new Blob([data], {
|
||||||
type: "data:text/plain;charset=utf-8"
|
type: "data:application/json;charset=utf-8"
|
||||||
});
|
});
|
||||||
navigator.msSaveBlob(blob, file);
|
navigator.msSaveBlob(blob, file);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
var element = document.createElement('a');
|
var element = document.createElement('a');
|
||||||
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(data));
|
element.setAttribute('href', 'data:application/json;charset=utf-8,' + encodeURIComponent(data));
|
||||||
element.setAttribute('download', file);
|
element.setAttribute('download', file);
|
||||||
element.style.display = 'none';
|
element.style.display = 'none';
|
||||||
document.body.appendChild(element);
|
document.body.appendChild(element);
|
||||||
|
@ -50,7 +50,11 @@ RED.envVar = (function() {
|
|||||||
var new_env = [];
|
var new_env = [];
|
||||||
var items = list.editableList('items');
|
var items = list.editableList('items');
|
||||||
var credentials = gconf ? gconf.credentials : null;
|
var credentials = gconf ? gconf.credentials : null;
|
||||||
|
if (!gconf && list.editableList('length') === 0) {
|
||||||
|
// No existing global-config node and nothing in the list,
|
||||||
|
// so no need to do anything more
|
||||||
|
return
|
||||||
|
}
|
||||||
if (!credentials) {
|
if (!credentials) {
|
||||||
credentials = {
|
credentials = {
|
||||||
_ : {},
|
_ : {},
|
||||||
@ -78,6 +82,12 @@ RED.envVar = (function() {
|
|||||||
if (gconf === null) {
|
if (gconf === null) {
|
||||||
gconf = getGlobalConf(true);
|
gconf = getGlobalConf(true);
|
||||||
}
|
}
|
||||||
|
if (!gconf.credentials) {
|
||||||
|
gconf.credentials = {
|
||||||
|
_ : {},
|
||||||
|
map: {}
|
||||||
|
};
|
||||||
|
}
|
||||||
if ((JSON.stringify(new_env) !== JSON.stringify(gconf.env)) ||
|
if ((JSON.stringify(new_env) !== JSON.stringify(gconf.env)) ||
|
||||||
(JSON.stringify(credentials) !== JSON.stringify(gconf.credentials))) {
|
(JSON.stringify(credentials) !== JSON.stringify(gconf.credentials))) {
|
||||||
gconf.env = new_env;
|
gconf.env = new_env;
|
||||||
|
@ -2606,6 +2606,16 @@ RED.view = (function() {
|
|||||||
var result = RED.nodes.removeJunction(node)
|
var result = RED.nodes.removeJunction(node)
|
||||||
removedJunctions.push(node);
|
removedJunctions.push(node);
|
||||||
removedLinks = removedLinks.concat(result.links);
|
removedLinks = removedLinks.concat(result.links);
|
||||||
|
if (node.g) {
|
||||||
|
var group = RED.nodes.group(node.g);
|
||||||
|
if (selectedGroups.indexOf(group) === -1) {
|
||||||
|
// Don't use RED.group.removeFromGroup as that emits
|
||||||
|
// a change event on the node - but we're deleting it
|
||||||
|
var index = group.nodes.indexOf(node);
|
||||||
|
group.nodes.splice(index,1);
|
||||||
|
RED.group.markDirty(group);
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (node.direction === "out") {
|
if (node.direction === "out") {
|
||||||
removedSubflowOutputs.push(node);
|
removedSubflowOutputs.push(node);
|
||||||
|
@ -35,7 +35,11 @@ module.exports = function(RED) {
|
|||||||
}
|
}
|
||||||
else { node.previous = {}; }
|
else { node.previous = {}; }
|
||||||
}
|
}
|
||||||
var value = RED.util.getMessageProperty(msg,node.property);
|
var value;
|
||||||
|
try {
|
||||||
|
value = RED.util.getMessageProperty(msg,node.property);
|
||||||
|
}
|
||||||
|
catch(e) { }
|
||||||
if (value !== undefined) {
|
if (value !== undefined) {
|
||||||
var t = "_no_topic";
|
var t = "_no_topic";
|
||||||
if (node.septopics) { t = topic || t; }
|
if (node.septopics) { t = topic || t; }
|
||||||
|
@ -414,6 +414,7 @@
|
|||||||
"port": "ポート",
|
"port": "ポート",
|
||||||
"keepalive": "キープアライブ時間",
|
"keepalive": "キープアライブ時間",
|
||||||
"cleansession": "セッションの初期化",
|
"cleansession": "セッションの初期化",
|
||||||
|
"autoUnsubscribe": "切断時に購読を自動解除",
|
||||||
"cleanstart": "クリーンスタート",
|
"cleanstart": "クリーンスタート",
|
||||||
"use-tls": "TLSを使用",
|
"use-tls": "TLSを使用",
|
||||||
"tls-config": "TLS設定",
|
"tls-config": "TLS設定",
|
||||||
|
@ -89,6 +89,15 @@ function init(userSettings,httpServer,_adminApi) {
|
|||||||
|
|
||||||
nodeApp = express();
|
nodeApp = express();
|
||||||
adminApp = express();
|
adminApp = express();
|
||||||
|
const defaultServerSettings = {
|
||||||
|
"x-powered-by": false
|
||||||
|
}
|
||||||
|
const serverSettings = Object.assign({},defaultServerSettings,userSettings.httpServerOptions||{});
|
||||||
|
for (let eOption in serverSettings) {
|
||||||
|
nodeApp.set(eOption, serverSettings[eOption]);
|
||||||
|
adminApp.set(eOption, serverSettings[eOption]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if (_adminApi) {
|
if (_adminApi) {
|
||||||
adminApi = _adminApi;
|
adminApi = _adminApi;
|
||||||
|
@ -854,7 +854,7 @@ describe('inject node', function() {
|
|||||||
});
|
});
|
||||||
n1.on("call:error", function(err) {
|
n1.on("call:error", function(err) {
|
||||||
count++;
|
count++;
|
||||||
if (count == 2) {
|
if (count == 1) {
|
||||||
done();
|
done();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -61,12 +61,14 @@ describe("api/editor/index", function() {
|
|||||||
sinon.stub(NR_TEST_UTILS.require("@node-red/editor-api/lib/editor/"+m),"init").callsFake(function(){});
|
sinon.stub(NR_TEST_UTILS.require("@node-red/editor-api/lib/editor/"+m),"init").callsFake(function(){});
|
||||||
});
|
});
|
||||||
sinon.stub(NR_TEST_UTILS.require("@node-red/editor-api/lib/editor/theme"),"app").callsFake(function(){ return express()});
|
sinon.stub(NR_TEST_UTILS.require("@node-red/editor-api/lib/editor/theme"),"app").callsFake(function(){ return express()});
|
||||||
|
sinon.stub(NR_TEST_UTILS.require("@node-red/editor-api/lib/editor/settings"),"sshkeys").callsFake(function(){ return express()});
|
||||||
});
|
});
|
||||||
after(function() {
|
after(function() {
|
||||||
mockList.forEach(function(m) {
|
mockList.forEach(function(m) {
|
||||||
NR_TEST_UTILS.require("@node-red/editor-api/lib/editor/"+m).init.restore();
|
NR_TEST_UTILS.require("@node-red/editor-api/lib/editor/"+m).init.restore();
|
||||||
})
|
})
|
||||||
NR_TEST_UTILS.require("@node-red/editor-api/lib/editor/theme").app.restore();
|
NR_TEST_UTILS.require("@node-red/editor-api/lib/editor/theme").app.restore();
|
||||||
|
NR_TEST_UTILS.require("@node-red/editor-api/lib/editor/settings").sshkeys.restore();
|
||||||
auth.needsPermission.restore();
|
auth.needsPermission.restore();
|
||||||
log.error.restore();
|
log.error.restore();
|
||||||
});
|
});
|
||||||
|
@ -41,7 +41,7 @@ describe("api/editor/settings", function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('returns the user settings', function(done) {
|
it('returns the user settings', function(done) {
|
||||||
info.init({
|
info.init({}, {
|
||||||
settings: {
|
settings: {
|
||||||
getUserSettings: function(opts) {
|
getUserSettings: function(opts) {
|
||||||
if (opts.user !== "fred") {
|
if (opts.user !== "fred") {
|
||||||
@ -67,7 +67,7 @@ describe("api/editor/settings", function() {
|
|||||||
});
|
});
|
||||||
it('updates the user settings', function(done) {
|
it('updates the user settings', function(done) {
|
||||||
var update;
|
var update;
|
||||||
info.init({
|
info.init({}, {
|
||||||
settings: {
|
settings: {
|
||||||
updateUserSettings: function(opts) {
|
updateUserSettings: function(opts) {
|
||||||
if (opts.user !== "fred") {
|
if (opts.user !== "fred") {
|
||||||
|
@ -34,7 +34,7 @@ describe("api/editor/sshkeys", function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
before(function() {
|
before(function() {
|
||||||
sshkeys.init(mockRuntime);
|
sshkeys.init({}, mockRuntime);
|
||||||
app = express();
|
app = express();
|
||||||
app.use(bodyParser.json());
|
app.use(bodyParser.json());
|
||||||
app.use("/settings/user/keys", sshkeys.app());
|
app.use("/settings/user/keys", sshkeys.app());
|
||||||
|
Loading…
Reference in New Issue
Block a user