Merge branch 'dev' into pr_1789

This commit is contained in:
Nick O'Leary
2018-10-22 10:46:47 +01:00
713 changed files with 26906 additions and 8163 deletions

View File

@@ -0,0 +1,178 @@
Copyright JS Foundation and other contributors, http://js.foundation
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS

View File

@@ -0,0 +1,12 @@
@node-red/editor-api
====================
Node-RED editor api module.
This provides an Express application that can be used to serve the Node-RED
editor.
### Source
The main Node-RED modules are maintained as a monorepo on [GitHub](https://github.com/node-red/node-red).

View File

@@ -0,0 +1,41 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var apiUtils = require("../util");
var runtimeAPI;
module.exports = {
init: function(_runtimeAPI) {
runtimeAPI = _runtimeAPI;
},
get: function(req,res) {
var opts = {
user: req.user,
scope: req.params.scope,
id: req.params.id,
key: req.params[0],
store: req.query['store']
}
runtimeAPI.context.getValue(opts).then(function(result) {
res.json(result);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
}
}

View File

@@ -0,0 +1,69 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var runtimeAPI;
var apiUtils = require("../util");
module.exports = {
init: function(_runtimeAPI) {
runtimeAPI = _runtimeAPI;
},
get: function(req,res) {
var opts = {
user: req.user,
id: req.params.id
}
runtimeAPI.flows.getFlow(opts).then(function(result) {
return res.json(result);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
},
post: function(req,res) {
var opts = {
user: req.user,
flow: req.body
}
runtimeAPI.flows.addFlow(opts).then(function(id) {
return res.json({id:id});
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
},
put: function(req,res) {
var opts = {
user: req.user,
id: req.params.id,
flow: req.body
}
runtimeAPI.flows.updateFlow(opts).then(function(id) {
return res.json({id:id});
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
},
delete: function(req,res) {
var opts = {
user: req.user,
id: req.params.id
}
runtimeAPI.flows.deleteFlow(opts).then(function() {
res.status(204).end();
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
}
}

View File

@@ -0,0 +1,70 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var runtimeAPI;
var apiUtils = require("../util");
module.exports = {
init: function(_runtimeAPI) {
runtimeAPI = _runtimeAPI;
},
get: function(req,res) {
var version = req.get("Node-RED-API-Version")||"v1";
if (!/^v[12]$/.test(version)) {
return res.status(400).json({code:"invalid_api_version", message:"Invalid API Version requested"});
}
var opts = {
user: req.user
}
runtimeAPI.flows.getFlows(opts).then(function(result) {
if (version === "v1") {
res.json(result.flows);
} else if (version === "v2") {
res.json(result);
}
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
},
post: function(req,res) {
var version = req.get("Node-RED-API-Version")||"v1";
if (!/^v[12]$/.test(version)) {
return res.status(400).json({code:"invalid_api_version", message:"Invalid API Version requested"});
}
var opts = {
user: req.user,
deploymentType: req.get("Node-RED-Deployment-Type")||"full"
}
if (opts.deploymentType !== 'reload') {
if (version === "v1") {
opts.flows = {flows: req.body}
} else {
opts.flows = req.body;
}
}
runtimeAPI.flows.setFlows(opts).then(function(result) {
if (version === "v1") {
res.status(204).end();
} else {
res.json(result);
}
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
}
}

View File

@@ -0,0 +1,67 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var express = require("express");
var nodes = require("./nodes");
var flows = require("./flows");
var flow = require("./flow");
var context = require("./context");
var auth = require("../auth");
var apiUtil = require("../util");
module.exports = {
init: function(runtimeAPI) {
flows.init(runtimeAPI);
flow.init(runtimeAPI);
nodes.init(runtimeAPI);
context.init(runtimeAPI);
var needsPermission = auth.needsPermission;
var adminApp = express();
// Flows
adminApp.get("/flows",needsPermission("flows.read"),flows.get,apiUtil.errorHandler);
adminApp.post("/flows",needsPermission("flows.write"),flows.post,apiUtil.errorHandler);
// Flow
adminApp.get("/flow/:id",needsPermission("flows.read"),flow.get,apiUtil.errorHandler);
adminApp.post("/flow",needsPermission("flows.write"),flow.post,apiUtil.errorHandler);
adminApp.delete("/flow/:id",needsPermission("flows.write"),flow.delete,apiUtil.errorHandler);
adminApp.put("/flow/:id",needsPermission("flows.write"),flow.put,apiUtil.errorHandler);
// Nodes
adminApp.get("/nodes",needsPermission("nodes.read"),nodes.getAll,apiUtil.errorHandler);
adminApp.post("/nodes",needsPermission("nodes.write"),nodes.post,apiUtil.errorHandler);
adminApp.get(/\/nodes\/messages/,needsPermission("nodes.read"),nodes.getModuleCatalogs,apiUtil.errorHandler);
adminApp.get(/\/nodes\/((@[^\/]+\/)?[^\/]+\/[^\/]+)\/messages/,needsPermission("nodes.read"),nodes.getModuleCatalog,apiUtil.errorHandler);
adminApp.get(/\/nodes\/((@[^\/]+\/)?[^\/]+)$/,needsPermission("nodes.read"),nodes.getModule,apiUtil.errorHandler);
adminApp.put(/\/nodes\/((@[^\/]+\/)?[^\/]+)$/,needsPermission("nodes.write"),nodes.putModule,apiUtil.errorHandler);
adminApp.delete(/\/nodes\/((@[^\/]+\/)?[^\/]+)$/,needsPermission("nodes.write"),nodes.delete,apiUtil.errorHandler);
adminApp.get(/\/nodes\/((@[^\/]+\/)?[^\/]+)\/([^\/]+)$/,needsPermission("nodes.read"),nodes.getSet,apiUtil.errorHandler);
adminApp.put(/\/nodes\/((@[^\/]+\/)?[^\/]+)\/([^\/]+)$/,needsPermission("nodes.write"),nodes.putSet,apiUtil.errorHandler);
// Context
adminApp.get("/context/:scope(global)",needsPermission("context.read"),context.get,apiUtil.errorHandler);
adminApp.get("/context/:scope(global)/*",needsPermission("context.read"),context.get,apiUtil.errorHandler);
adminApp.get("/context/:scope(node|flow)/:id",needsPermission("context.read"),context.get,apiUtil.errorHandler);
adminApp.get("/context/:scope(node|flow)/:id/*",needsPermission("context.read"),context.get,apiUtil.errorHandler);
return adminApp;
}
}

View File

@@ -0,0 +1,173 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var apiUtils = require("../util");
var runtimeAPI;
module.exports = {
init: function(_runtimeAPI) {
runtimeAPI = _runtimeAPI;
},
getAll: function(req,res) {
var opts = {
user: req.user
}
if (req.get("accept") == "application/json") {
runtimeAPI.nodes.getNodeList(opts).then(function(list) {
res.json(list);
})
} else {
opts.lang = apiUtils.determineLangFromHeaders(req.acceptsLanguages());
runtimeAPI.nodes.getNodeConfigs(opts).then(function(configs) {
res.send(configs);
})
}
},
post: function(req,res) {
var opts = {
user: req.user,
module: req.body.module,
version: req.body.version
}
runtimeAPI.nodes.addModule(opts).then(function(info) {
res.json(info);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
},
delete: function(req,res) {
var opts = {
user: req.user,
module: req.params[0]
}
runtimeAPI.nodes.removeModule(opts).then(function() {
res.status(204).end();
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
},
getSet: function(req,res) {
var opts = {
user: req.user,
id: req.params[0] + "/" + req.params[2]
}
if (req.get("accept") === "application/json") {
runtimeAPI.nodes.getNodeInfo(opts).then(function(result) {
res.send(result);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
} else {
opts.lang = apiUtils.determineLangFromHeaders(req.acceptsLanguages());
runtimeAPI.nodes.getNodeConfig(opts).then(function(result) {
return res.send(result);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
}
},
getModule: function(req,res) {
var opts = {
user: req.user,
module: req.params[0]
}
runtimeAPI.nodes.getModuleInfo(opts).then(function(result) {
res.send(result);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
},
putSet: function(req,res) {
var body = req.body;
if (!body.hasOwnProperty("enabled")) {
// log.audit({event: "nodes.module.set",error:"invalid_request"},req);
res.status(400).json({code:"invalid_request", message:"Invalid request"});
return;
}
var opts = {
user: req.user,
id: req.params[0] + "/" + req.params[2],
enabled: body.enabled
}
runtimeAPI.nodes.setNodeSetState(opts).then(function(result) {
res.send(result);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
},
putModule: function(req,res) {
var body = req.body;
if (!body.hasOwnProperty("enabled")) {
// log.audit({event: "nodes.module.set",error:"invalid_request"},req);
res.status(400).json({code:"invalid_request", message:"Invalid request"});
return;
}
var opts = {
user: req.user,
module: req.params[0],
enabled: body.enabled
}
runtimeAPI.nodes.setModuleState(opts).then(function(result) {
res.send(result);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
},
getModuleCatalog: function(req,res) {
var opts = {
user: req.user,
module: req.params[0],
lang: req.query.lng
}
runtimeAPI.nodes.getModuleCatalog(opts).then(function(result) {
res.json(result);
}).catch(function(err) {
console.log(err.stack);
apiUtils.rejectHandler(req,res,err);
})
},
getModuleCatalogs: function(req,res) {
var opts = {
user: req.user,
lang: req.query.lng
}
runtimeAPI.nodes.getModuleCatalogs(opts).then(function(result) {
res.json(result);
}).catch(function(err) {
console.log(err.stack);
apiUtils.rejectHandler(req,res,err);
})
},
getIcons: function(req,res) {
var opts = {
user: req.user
}
runtimeAPI.nodes.getIconList(opts).then(function(list) {
res.json(list);
});
}
};

View File

@@ -0,0 +1,31 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var clients = [
{id:"node-red-editor",secret:"not_available"},
{id:"node-red-admin",secret:"not_available"}
];
module.exports = {
get: function(id) {
for (var i=0;i<clients.length;i++) {
if (clients[i].id == id) {
return Promise.resolve(clients[i]);
}
}
return Promise.resolve(null);
}
}

View File

@@ -0,0 +1,213 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var passport = require("passport");
var oauth2orize = require("oauth2orize");
var strategies = require("./strategies");
var Tokens = require("./tokens");
var Users = require("./users");
var permissions = require("./permissions");
var theme = require("../editor/theme");
var settings = null;
var log = require("@node-red/util").log; // TODO: separate module
passport.use(strategies.bearerStrategy.BearerStrategy);
passport.use(strategies.clientPasswordStrategy.ClientPasswordStrategy);
passport.use(strategies.anonymousStrategy);
var server = oauth2orize.createServer();
server.exchange(oauth2orize.exchange.password(strategies.passwordTokenExchange));
function init(_settings,storage) {
settings = _settings;
if (settings.adminAuth) {
var mergedAdminAuth = Object.assign({}, settings.adminAuth, settings.adminAuth.module);
Users.init(mergedAdminAuth);
Tokens.init(mergedAdminAuth,runtime.storage);
}
}
function needsPermission(permission) {
return function(req,res,next) {
if (settings && settings.adminAuth) {
return passport.authenticate(['bearer','anon'],{ session: false })(req,res,function() {
if (!req.user) {
return next();
}
if (permissions.hasPermission(req.authInfo.scope,permission)) {
return next();
}
log.audit({event: "permission.fail", permissions: permission},req);
return res.status(401).end();
});
} else {
next();
}
}
}
function ensureClientSecret(req,res,next) {
if (!req.body.client_secret) {
req.body.client_secret = 'not_available';
}
next();
}
function authenticateClient(req,res,next) {
return passport.authenticate(['oauth2-client-password'], {session: false})(req,res,next);
}
function getToken(req,res,next) {
return server.token()(req,res,next);
}
function login(req,res) {
var response = {};
if (settings.adminAuth) {
var mergedAdminAuth = Object.assign({}, settings.adminAuth, settings.adminAuth.module);
if (mergedAdminAuth.type === "credentials") {
response = {
"type":"credentials",
"prompts":[{id:"username",type:"text",label:"user.username"},{id:"password",type:"password",label:"user.password"}]
}
} else if (mergedAdminAuth.type === "strategy") {
var urlPrefix = (settings.httpAdminRoot==='/')?"":settings.httpAdminRoot;
response = {
"type":"strategy",
"prompts":[{type:"button",label:mergedAdminAuth.strategy.label, url: urlPrefix + "auth/strategy"}]
}
if (mergedAdminAuth.strategy.icon) {
response.prompts[0].icon = mergedAdminAuth.strategy.icon;
}
if (mergedAdminAuth.strategy.image) {
response.prompts[0].image = theme.serveFile('/login/',mergedAdminAuth.strategy.image);
}
}
if (theme.context().login && theme.context().login.image) {
response.image = theme.context().login.image;
}
}
res.json(response);
}
function revoke(req,res) {
var token = req.body.token;
// TODO: audit log
Tokens.revoke(token).then(function() {
log.audit({event: "auth.login.revoke"},req);
if (settings.editorTheme && settings.editorTheme.logout && settings.editorTheme.logout.redirect) {
res.json({redirect:settings.editorTheme.logout.redirect});
} else {
res.status(200).end();
}
});
}
function completeVerify(profile,done) {
Users.authenticate(profile).then(function(user) {
if (user) {
Tokens.create(user.username,"node-red-editor",user.permissions).then(function(tokens) {
log.audit({event: "auth.login",username:user.username,scope:user.permissions});
user.tokens = tokens;
done(null,user);
});
} else {
log.audit({event: "auth.login.fail.oauth",username:typeof profile === "string"?profile:profile.username});
done(null,false);
}
});
}
function genericStrategy(adminApp,strategy) {
var crypto = require("crypto")
var session = require('express-session')
var MemoryStore = require('memorystore')(session)
adminApp.use(session({
// As the session is only used across the life-span of an auth
// hand-shake, we can use a instance specific random string
secret: crypto.randomBytes(20).toString('hex'),
resave: false,
saveUninitialized: false,
store: new MemoryStore({
checkPeriod: 86400000 // prune expired entries every 24h
})
}));
//TODO: all passport references ought to be in ./auth
adminApp.use(passport.initialize());
adminApp.use(passport.session());
var options = strategy.options;
passport.use(new strategy.strategy(options,
function() {
var originalDone = arguments[arguments.length-1];
if (options.verify) {
var args = Array.from(arguments);
args[args.length-1] = function(err,profile) {
if (err) {
return originalDone(err);
} else {
return completeVerify(profile,originalDone);
}
};
options.verify.apply(null,args);
} else {
var profile = arguments[arguments.length - 2];
return completeVerify(profile,originalDone);
}
}
));
adminApp.get('/auth/strategy',
passport.authenticate(strategy.name, {session:false, failureRedirect: settings.httpAdminRoot }),
completeGenerateStrategyAuth
);
adminApp.get('/auth/strategy/callback',
passport.authenticate(strategy.name, {session:false, failureRedirect: settings.httpAdminRoot }),
completeGenerateStrategyAuth
);
}
function completeGenerateStrategyAuth(req,res) {
var tokens = req.user.tokens;
delete req.user.tokens;
// Successful authentication, redirect home.
res.redirect(settings.httpAdminRoot + '?access_token='+tokens.accessToken);
}
module.exports = {
init: init,
needsPermission: needsPermission,
ensureClientSecret: ensureClientSecret,
authenticateClient: authenticateClient,
getToken: getToken,
errorHandler: function(err,req,res,next) {
//TODO: audit log statment
//console.log(err.stack);
//log.log({level:"audit",type:"auth",msg:err.toString()});
return server.errorHandler()(err,req,res,next);
},
login: login,
revoke: revoke,
genericStrategy: genericStrategy
}

View File

@@ -0,0 +1,65 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var util = require('util');
var readRE = /^((.+)\.)?read$/
var writeRE = /^((.+)\.)?write$/
function hasPermission(userScope,permission) {
if (permission === "") {
return true;
}
var i;
if (util.isArray(permission)) {
// Multiple permissions requested - check each one
for (i=0;i<permission.length;i++) {
if (!hasPermission(userScope,permission[i])) {
return false;
}
}
// All permissions check out
return true;
}
if (util.isArray(userScope)) {
if (userScope.length === 0) {
return false;
}
for (i=0;i<userScope.length;i++) {
if (hasPermission(userScope[i],permission)) {
return true;
}
}
return false;
}
if (userScope === "*" || userScope === permission) {
return true;
}
if (userScope === "read" || userScope === "*.read") {
return readRE.test(permission);
} else if (userScope === "write" || userScope === "*.write") {
return writeRE.test(permission);
}
return false;
}
module.exports = {
hasPermission: hasPermission,
}

View File

@@ -0,0 +1,131 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var BearerStrategy = require('passport-http-bearer').Strategy;
var ClientPasswordStrategy = require('passport-oauth2-client-password').Strategy;
var passport = require("passport");
var crypto = require("crypto");
var util = require("util");
var Tokens = require("./tokens");
var Users = require("./users");
var Clients = require("./clients");
var permissions = require("./permissions");
var log = require("@node-red/util").log; // TODO: separate module
var bearerStrategy = function (accessToken, done) {
// is this a valid token?
Tokens.get(accessToken).then(function(token) {
if (token) {
Users.get(token.user).then(function(user) {
if (user) {
done(null,user,{scope:token.scope});
} else {
log.audit({event: "auth.invalid-token"});
done(null,false);
}
});
} else {
log.audit({event: "auth.invalid-token"});
done(null,false);
}
});
}
bearerStrategy.BearerStrategy = new BearerStrategy(bearerStrategy);
var clientPasswordStrategy = function(clientId, clientSecret, done) {
Clients.get(clientId).then(function(client) {
if (client && client.secret == clientSecret) {
done(null,client);
} else {
log.audit({event: "auth.invalid-client",client:clientId});
done(null,false);
}
});
}
clientPasswordStrategy.ClientPasswordStrategy = new ClientPasswordStrategy(clientPasswordStrategy);
var loginAttempts = [];
var loginSignInWindow = 600000; // 10 minutes
var passwordTokenExchange = function(client, username, password, scope, done) {
var now = Date.now();
loginAttempts = loginAttempts.filter(function(logEntry) {
return logEntry.time + loginSignInWindow > now;
});
loginAttempts.push({time:now, user:username});
var attemptCount = 0;
loginAttempts.forEach(function(logEntry) {
/* istanbul ignore else */
if (logEntry.user == username) {
attemptCount++;
}
});
if (attemptCount > 5) {
log.audit({event: "auth.login.fail.too-many-attempts",username:username,client:client.id});
done(new Error("Too many login attempts. Wait 10 minutes and try again"),false);
return;
}
Users.authenticate(username,password).then(function(user) {
if (user) {
if (scope === "") {
scope = user.permissions;
}
if (permissions.hasPermission(user.permissions,scope)) {
loginAttempts = loginAttempts.filter(function(logEntry) {
return logEntry.user !== username;
});
Tokens.create(username,client.id,scope).then(function(tokens) {
log.audit({event: "auth.login",username:username,client:client.id,scope:scope});
done(null,tokens.accessToken,null,{expires_in:tokens.expires_in});
});
} else {
log.audit({event: "auth.login.fail.permissions",username:username,client:client.id,scope:scope});
done(null,false);
}
} else {
log.audit({event: "auth.login.fail.credentials",username:username,client:client.id,scope:scope});
done(null,false);
}
});
}
function AnonymousStrategy() {
passport.Strategy.call(this);
this.name = 'anon';
}
util.inherits(AnonymousStrategy, passport.Strategy);
AnonymousStrategy.prototype.authenticate = function(req) {
var self = this;
Users.default().then(function(anon) {
if (anon) {
self.success(anon,{scope:anon.permissions});
} else {
self.fail(401);
}
});
}
module.exports = {
bearerStrategy: bearerStrategy,
clientPasswordStrategy: clientPasswordStrategy,
passwordTokenExchange: passwordTokenExchange,
anonymousStrategy: new AnonymousStrategy()
}

View File

@@ -0,0 +1,129 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
function generateToken(length) {
var c = "ABCDEFGHIJKLMNOPQRSTUZWXYZabcdefghijklmnopqrstuvwxyz1234567890";
var token = [];
for (var i=0;i<length;i++) {
token.push(c[Math.floor(Math.random()*c.length)]);
}
return token.join("");
}
var storage;
var sessionExpiryTime
var sessions = {};
var loadedSessions = null;
var apiAccessTokens;
function expireSessions() {
var now = Date.now();
var modified = false;
for (var t in sessions) {
if (sessions.hasOwnProperty(t)) {
var session = sessions[t];
if (!session.hasOwnProperty("expires") || session.expires < now) {
delete sessions[t];
modified = true;
}
}
}
if (modified) {
return storage.saveSessions(sessions);
} else {
return Promise.resolve();
}
}
function loadSessions() {
if (loadedSessions === null) {
loadedSessions = storage.getSessions().then(function(_sessions) {
sessions = _sessions||{};
return expireSessions();
});
}
return loadedSessions;
}
module.exports = {
init: function(adminAuthSettings, _storage) {
storage = _storage;
sessionExpiryTime = adminAuthSettings.sessionExpiryTime || 604800; // 1 week in seconds
// At this point, storage will not have been initialised, so defer loading
// the sessions until there's a request for them.
loadedSessions = null;
apiAccessTokens = {};
if ( Array.isArray(adminAuthSettings.tokens) ) {
apiAccessTokens = adminAuthSettings.tokens.reduce(function(prev, current) {
prev[current.token] = {
user: current.user,
scope: current.scope
};
return prev;
}, {});
}
return Promise.resolve();
},
get: function(token) {
return loadSessions().then(function() {
var info = apiAccessTokens[token] || null;
if (info) {
return Promise.resolve(info);
} else {
if (sessions[token]) {
if (sessions[token].expires < Date.now()) {
return expireSessions().then(function() { return null });
}
}
return Promise.resolve(sessions[token]);
}
});
},
create: function(user,client,scope) {
return loadSessions().then(function() {
var accessToken = generateToken(128);
var accessTokenExpiresAt = Date.now() + (sessionExpiryTime*1000);
var session = {
user:user,
client:client,
scope:scope,
accessToken: accessToken,
expires: accessTokenExpiresAt
};
sessions[accessToken] = session;
return storage.saveSessions(sessions).then(function() {
return {
accessToken: accessToken,
expires_in: sessionExpiryTime
}
});
});
},
revoke: function(token) {
return loadSessions().then(function() {
delete sessions[token];
return storage.saveSessions(sessions);
});
}
}

View File

@@ -0,0 +1,122 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var util = require("util");
var clone = require("clone");
var bcrypt;
try { bcrypt = require('bcrypt'); }
catch(e) { bcrypt = require('bcryptjs'); }
var users = {};
var defaultUser = null;
function authenticate() {
var username = arguments[0];
if (typeof username !== 'string') {
username = username.username;
}
const args = Array.from(arguments);
return api.get(username).then(function(user) {
if (user) {
if (args.length === 2) {
// Username/password authentication
var password = args[1];
return new Promise(function(resolve,reject) {
bcrypt.compare(password, user.password, function(err, res) {
resolve(res?cleanUser(user):null);
});
});
} else {
// Try to extract common profile information
if (args[0].hasOwnProperty('photos') && args[0].photos.length > 0) {
user.image = args[0].photos[0].value;
}
return cleanUser(user);
}
}
return null;
});
}
function get(username) {
return Promise.resolve(users[username]);
}
function getDefaultUser() {
return Promise.resolve(null);
}
var api = {
get: get,
authenticate: authenticate,
default: getDefaultUser
}
function init(config) {
users = {};
defaultUser = null;
if (config.type == "credentials" || config.type == "strategy") {
if (config.users) {
if (typeof config.users === "function") {
api.get = config.users;
} else {
var us = config.users;
/* istanbul ignore else */
if (!util.isArray(us)) {
us = [us];
}
for (var i=0;i<us.length;i++) {
var u = us[i];
users[u.username] = clone(u);
}
}
}
if (config.authenticate && typeof config.authenticate === "function") {
api.authenticate = config.authenticate;
} else {
api.authenticate = authenticate;
}
} else {
api.get = get;
api.authenticate = authenticate;
api.default = api.default;
}
if (config.default) {
if (typeof config.default === "function") {
api.default = config.default;
} else {
api.default = function() {
return Promise.resolve({
"anonymous": true,
"permissions":config.default.permissions
});
}
}
} else {
api.default = getDefaultUser;
}
}
function cleanUser(user) {
if (user && user.hasOwnProperty('password')) {
user = clone(user);
delete user.password;
}
return user;
}
module.exports = {
init: init,
get: function(username) { return api.get(username).then(cleanUser)},
authenticate: function() { return api.authenticate.apply(null, arguments) },
default: function() { return api.default(); }
};

View File

@@ -0,0 +1,243 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var ws = require("ws");
var log = require("@node-red/util").log; // TODO: separate module
var Tokens;
var Users;
var Permissions;
var server;
var settings;
var runtimeAPI;
var wsServer;
var activeConnections = [];
var anonymousUser;
var retained = {};
var heartbeatTimer;
var lastSentTime;
function init(_server,_settings,_runtimeAPI) {
server = _server;
settings = _settings;
runtimeAPI = _runtimeAPI;
Tokens = require("../auth/tokens");
Users = require("../auth/users");
Permissions = require("../auth/permissions");
}
function generateSession(length) {
var c = "ABCDEFGHIJKLMNOPQRSTUZWXYZabcdefghijklmnopqrstuvwxyz1234567890";
var token = [];
for (var i=0;i<length;i++) {
token.push(c[Math.floor(Math.random()*c.length)]);
}
return token.join("");
}
function CommsConnection(ws) {
this.session = generateSession(32);
this.ws = ws;
this.stack = [];
this.user = null;
this.lastSentTime = 0;
var self = this;
log.audit({event: "comms.open"});
log.trace("comms.open "+self.session);
var pendingAuth = (settings.adminAuth != null);
if (!pendingAuth) {
addActiveConnection(self);
}
ws.on('close',function() {
log.audit({event: "comms.close",user:self.user, session: self.session});
log.trace("comms.close "+self.session);
removeActiveConnection(self);
});
ws.on('message', function(data,flags) {
var msg = null;
try {
msg = JSON.parse(data);
} catch(err) {
log.trace("comms received malformed message : "+err.toString());
return;
}
if (!pendingAuth) {
if (msg.subscribe) {
self.subscribe(msg.subscribe);
// handleRemoteSubscription(ws,msg.subscribe);
}
} else {
var completeConnection = function(userScope,sendAck) {
try {
if (!userScope || !Permissions.hasPermission(userScope,"status.read")) {
ws.send(JSON.stringify({auth:"fail"}));
ws.close();
} else {
pendingAuth = false;
addActiveConnection(self);
if (sendAck) {
ws.send(JSON.stringify({auth:"ok"}));
}
}
} catch(err) {
console.log(err.stack);
// Just in case the socket closes before we attempt
// to send anything.
}
}
if (msg.auth) {
Tokens.get(msg.auth).then(function(client) {
if (client) {
Users.get(client.user).then(function(user) {
if (user) {
self.user = user;
log.audit({event: "comms.auth",user:self.user});
completeConnection(client.scope,true);
} else {
log.audit({event: "comms.auth.fail"});
completeConnection(null,false);
}
});
} else {
log.audit({event: "comms.auth.fail"});
completeConnection(null,false);
}
});
} else {
if (anonymousUser) {
log.audit({event: "comms.auth",user:anonymousUser});
self.user = anonymousUser;
completeConnection(anonymousUser.permissions,false);
//TODO: duplicated code - pull non-auth message handling out
if (msg.subscribe) {
self.subscribe(msg.subscribe);
}
} else {
log.audit({event: "comms.auth.fail"});
completeConnection(null,false);
}
}
}
});
ws.on('error', function(err) {
log.warn(log._("comms.error",{message:err.toString()}));
});
}
CommsConnection.prototype.send = function(topic,data) {
var self = this;
if (topic && data) {
this.stack.push({topic:topic,data:data});
}
if (!this._xmitTimer) {
this._xmitTimer = setTimeout(function() {
try {
self.ws.send(JSON.stringify(self.stack));
self.lastSentTime = Date.now();
} catch(err) {
removeActiveConnection(self);
log.warn(log._("comms.error-send",{message:err.toString()}));
}
delete self._xmitTimer;
self.stack = [];
},50);
}
}
CommsConnection.prototype.subscribe = function(topic) {
runtimeAPI.comms.subscribe({
user: this.user,
client: this,
topic: topic
})
}
function start() {
if (!settings.disableEditor) {
Users.default().then(function(_anonymousUser) {
anonymousUser = _anonymousUser;
var webSocketKeepAliveTime = settings.webSocketKeepAliveTime || 15000;
var path = settings.httpAdminRoot || "/";
path = (path.slice(0,1) != "/" ? "/":"") + path + (path.slice(-1) == "/" ? "":"/") + "comms";
wsServer = new ws.Server({
server:server,
path:path,
// Disable the deflate option due to this issue
// https://github.com/websockets/ws/pull/632
// that is fixed in the 1.x release of the ws module
// that we cannot currently pickup as it drops node 0.10 support
//perMessageDeflate: false
});
wsServer.on('connection',function(ws) {
var commsConnection = new CommsConnection(ws);
});
wsServer.on('error', function(err) {
log.warn(log._("comms.error-server",{message:err.toString()}));
});
lastSentTime = Date.now();
heartbeatTimer = setInterval(function() {
var now = Date.now();
if (now-lastSentTime > webSocketKeepAliveTime) {
activeConnections.forEach(connection => connection.send("hb",lastSentTime));
}
}, webSocketKeepAliveTime);
});
}
}
function stop() {
if (heartbeatTimer) {
clearInterval(heartbeatTimer);
heartbeatTimer = null;
}
if (wsServer) {
wsServer.close();
wsServer = null;
}
}
function addActiveConnection(connection) {
activeConnections.push(connection);
runtimeAPI.comms.addConnection({client: connection});
}
function removeActiveConnection(connection) {
for (var i=0;i<activeConnections.length;i++) {
if (activeConnections[i] === connection) {
activeConnections.splice(i,1);
runtimeAPI.comms.removeConnection({client:connection})
break;
}
}
}
module.exports = {
init:init,
start:start,
stop:stop
}

View File

@@ -0,0 +1,36 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var runtimeAPI;
var apiUtils = require("../util");
module.exports = {
init: function(_runtimeAPI) {
runtimeAPI = _runtimeAPI
},
get: function (req, res) {
var opts = {
user: req.user,
type: req.params.type,
id: req.params.id
}
runtimeAPI.flows.getNodeCredentials(opts).then(function(result) {
res.json(result);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
}
}

View File

@@ -0,0 +1,124 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var express = require("express");
var path = require('path');
var comms = require("./comms");
var library = require("./library");
var info = require("./settings");
var auth = require("../auth");
var nodes = require("../admin/nodes"); // TODO: move /icons into here
var needsPermission;
var runtimeAPI;
var log = require("@node-red/util").log; // TODO: separate module
var i18n = require("@node-red/util").i18n; // TODO: separate module
var apiUtil = require("../util");
var ensureRuntimeStarted = function(req,res,next) {
runtimeAPI.isStarted().then( started => {
if (!started) {
log.error("Node-RED runtime not started");
res.status(503).send("Not started");
} else {
next()
}
})
}
module.exports = {
init: function(server, settings, _runtimeAPI) {
runtimeAPI = _runtimeAPI;
needsPermission = auth.needsPermission;
if (!settings.disableEditor) {
info.init(runtimeAPI);
comms.init(server,settings,runtimeAPI);
var ui = require("./ui");
ui.init(runtimeAPI);
var editorApp = express();
if (settings.requireHttps === true) {
editorApp.enable('trust proxy');
editorApp.use(function (req, res, next) {
if (req.secure) {
next();
} else {
res.redirect('https://' + req.headers.host + req.originalUrl);
}
});
}
editorApp.get("/",ensureRuntimeStarted,ui.ensureSlash,ui.editor);
editorApp.get("/icons",needsPermission("nodes.read"),nodes.getIcons,apiUtil.errorHandler);
editorApp.get("/icons/:module/:icon",ui.icon);
editorApp.get("/icons/:scope/:module/:icon",ui.icon);
var theme = require("./theme");
theme.init(settings);
editorApp.use("/theme",theme.app());
editorApp.use("/",ui.editorResources);
//Projects
var projects = require("./projects");
projects.init(runtimeAPI);
editorApp.use("/projects",projects.app());
// Locales
var locales = require("./locales");
locales.init(runtimeAPI);
editorApp.get(/locales\/(.+)\/?$/,locales.get,apiUtil.errorHandler);
// Library
var library = require("./library");
library.init(runtimeAPI);
editorApp.get("/library/flows",needsPermission("library.read"),library.getAll,apiUtil.errorHandler);
editorApp.get(/library\/([^\/]+)(?:$|\/(.*))/,needsPermission("library.read"),library.getEntry);
editorApp.post(/library\/([^\/]+)\/(.*)/,needsPermission("library.write"),library.saveEntry);
// Credentials
var credentials = require("./credentials");
credentials.init(runtimeAPI);
editorApp.get('/credentials/:type/:id', needsPermission("credentials.read"),credentials.get,apiUtil.errorHandler);
// Settings
editorApp.get("/settings",needsPermission("settings.read"),info.runtimeSettings,apiUtil.errorHandler);
// User Settings
editorApp.get("/settings/user",needsPermission("settings.read"),info.userSettings,apiUtil.errorHandler);
// User Settings
editorApp.post("/settings/user",needsPermission("settings.write"),info.updateUserSettings,apiUtil.errorHandler);
// SSH keys
editorApp.use("/settings/user/keys",needsPermission("settings.write"),info.sshkeys());
return editorApp;
}
},
start: function() {
var catalogPath = path.resolve(path.join(path.dirname(require.resolve("@node-red/editor-client")),"locales"));
return i18n.registerMessageCatalogs([
{namespace: "editor", dir: catalogPath, file:"editor.json"},
{namespace: "jsonata", dir: catalogPath, file:"jsonata.json"},
{namespace: "infotips", dir: catalogPath, file:"infotips.json"}
]).then(function(){
comms.start();
});
},
stop: comms.stop
}

View File

@@ -0,0 +1,83 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var apiUtils = require("../util");
var fs = require('fs');
var fspath = require('path');
var when = require('when');
var runtimeAPI;
module.exports = {
init: function(_runtimeAPI) {
runtimeAPI = _runtimeAPI;
},
getAll: function(req,res) {
var opts = {
user: req.user,
type: 'flows'
}
runtimeAPI.library.getEntries(opts).then(function(result) {
res.json(result);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
});
},
getEntry: function(req,res) {
var opts = {
user: req.user,
type: req.params[0],
path: req.params[1]||""
}
runtimeAPI.library.getEntry(opts).then(function(result) {
if (typeof result === "string") {
if (opts.type === 'flows') {
res.writeHead(200, {'Content-Type': 'application/json'});
} else {
res.writeHead(200, {'Content-Type': 'text/plain'});
}
res.write(result);
res.end();
} else {
res.json(result);
}
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
});
},
saveEntry: function(req,res) {
var opts = {
user: req.user,
type: req.params[0],
path: req.params[1]||""
}
// TODO: horrible inconsistencies between flows and all other types
if (opts.type === "flows") {
opts.meta = {};
opts.body = JSON.stringify(req.body);
} else {
opts.meta = req.body;
opts.body = opts.meta.text;
delete opts.meta.text;
}
runtimeAPI.library.saveEntry(opts).then(function(result) {
res.status(204).end();
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
});
}
}

View File

@@ -0,0 +1,53 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var fs = require('fs');
var path = require('path');
//var apiUtil = require('../util');
var i18n = require("@node-red/util").i18n; // TODO: separate module
var runtimeAPI;
function loadResource(lang, namespace) {
var catalog = i18n.i.getResourceBundle(lang, namespace);
if (!catalog) {
var parts = lang.split("-");
if (parts.length == 2) {
var new_lang = parts[0];
return i18n.i.getResourceBundle(new_lang, namespace);
}
}
return catalog;
}
module.exports = {
init: function(_runtimeAPI) {
runtimeAPI = _runtimeAPI;
},
get: function(req,res) {
var namespace = req.params[0];
var lngs = req.query.lng;
namespace = namespace.replace(/\.json$/,"");
var lang = req.query.lng; //apiUtil.determineLangFromHeaders(req.acceptsLanguages() || []);
var prevLang = i18n.i.language;
// Trigger a load from disk of the language if it is not the default
i18n.i.changeLanguage(lang, function(){
var catalog = loadResource(lang, namespace);
res.json(catalog||{});
});
i18n.i.changeLanguage(prevLang);
}
}

View File

@@ -0,0 +1,511 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var express = require("express");
var apiUtils = require("../util");
var runtimeAPI;
var needsPermission = require("../auth").needsPermission;
function listProjects(req,res) {
var opts = {
user: req.user
}
runtimeAPI.projects.listProjects(opts).then(function(result) {
res.json(result);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
});
}
function getProject(req,res) {
var opts = {
user: req.user,
id: req.params.id
}
runtimeAPI.projects.getProject(opts).then(function(data) {
if (data) {
res.json(data);
} else {
res.status(404).end();
}
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
}
function getProjectStatus(req,res) {
var opts = {
user: req.user,
id: req.params.id,
remote: req.query.remote
}
runtimeAPI.projects.getStatus(opts).then(function(data){
if (data) {
res.json(data);
} else {
res.status(404).end();
}
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
}
function getProjectRemotes(req,res) {
var opts = {
user: req.user,
id: req.params.id
}
runtimeAPI.projects.getRemotes(opts).then(function(data) {
res.json(data);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
}
module.exports = {
init: function(_runtimeAPI) {
runtimeAPI = _runtimeAPI;
},
app: function() {
var app = express();
app.use(function(req,res,next) {
runtimeAPI.projects.available().then(function(available) {
if (!available) {
res.status(404).end();
} else {
next();
}
})
});
// Projects
// List all projects
app.get("/", needsPermission("projects.read"),listProjects);
// Create project
app.post("/", needsPermission("projects.write"), function(req,res) {
var opts = {
user: req.user,
project: req.body
}
runtimeAPI.projects.createProject(opts).then(function(result) {
res.json(result);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
});
});
// Update a project
app.put("/:id", needsPermission("projects.write"), function(req,res) {
var opts = {
user: req.user,
id: req.params.id,
project: req.body
}
if (req.body.active) {
runtimeAPI.projects.setActiveProject(opts).then(function() {
listProjects(req,res);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
} else if (req.body.initialise) {
runtimeAPI.projects.initialiseProject(opts).then(function() {
getProject(req,res);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
} else if (req.body.hasOwnProperty('credentialSecret') ||
req.body.hasOwnProperty('description') ||
req.body.hasOwnProperty('dependencies')||
req.body.hasOwnProperty('summary') ||
req.body.hasOwnProperty('files') ||
req.body.hasOwnProperty('git')) {
runtimeAPI.projects.updateProject(opts).then(function() {
getProject(req,res);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
} else {
res.status(400).json({error:"unexpected_error", message:"invalid_request"});
}
});
// Get project metadata
app.get("/:id", needsPermission("projects.read"), getProject);
// Delete project
app.delete("/:id", needsPermission("projects.write"), function(req,res) {
var opts = {
user: req.user,
id: req.params.id
}
runtimeAPI.projects.deleteProject(opts).then(function() {
res.status(204).end();
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
});
// Get project status - files, commit counts, branch info
app.get("/:id/status", needsPermission("projects.read"), getProjectStatus);
// Project file listing
app.get("/:id/files", needsPermission("projects.read"), function(req,res) {
var opts = {
user: req.user,
id: req.params.id
}
runtimeAPI.projects.getFiles(opts).then(function(data) {
res.json(data);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
});
// Get file content in a given tree (index/stage)
app.get("/:id/files/:treeish/*", needsPermission("projects.read"), function(req,res) {
var opts = {
user: req.user,
id: req.params.id,
path: req.params[0],
tree: req.params.treeish
}
runtimeAPI.projects.getFile(opts).then(function(data) {
res.json({content:data});
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
});
// Revert a file
app.delete("/:id/files/_/*", needsPermission("projects.write"), function(req,res) {
var opts = {
user: req.user,
id: req.params.id,
path: req.params[0]
}
runtimeAPI.projects.revertFile(opts).then(function() {
res.status(204).end();
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
});
// Stage a file
app.post("/:id/stage/*", needsPermission("projects.write"), function(req,res) {
var opts = {
user: req.user,
id: req.params.id,
path: req.params[0]
}
runtimeAPI.projects.stageFile(opts).then(function() {
getProjectStatus(req,res);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
});
// Stage multiple files
app.post("/:id/stage", needsPermission("projects.write"), function(req,res) {
var opts = {
user: req.user,
id: req.params.id,
path: req.body.files
}
runtimeAPI.projects.stageFile(opts).then(function() {
getProjectStatus(req,res);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
});
// Commit changes
app.post("/:id/commit", needsPermission("projects.write"), function(req,res) {
var opts = {
user: req.user,
id: req.params.id,
message: req.body.message
}
runtimeAPI.projects.commit(opts).then(function() {
getProjectStatus(req,res);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
});
// Unstage a file
app.delete("/:id/stage/*", needsPermission("projects.write"), function(req,res) {
var opts = {
user: req.user,
id: req.params.id,
path: req.params[0]
}
runtimeAPI.projects.unstageFile(opts).then(function() {
getProjectStatus(req,res);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
});
// Unstage multiple files
app.delete("/:id/stage", needsPermission("projects.write"), function(req, res) {
var opts = {
user: req.user,
id: req.params.id
}
runtimeAPI.projects.unstageFile(opts).then(function() {
getProjectStatus(req,res);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
});
// Get a file diff
app.get("/:id/diff/:type/*", needsPermission("projects.read"), function(req,res) {
var opts = {
user: req.user,
id: req.params.id,
path: req.params[0],
type: req.params.type
}
runtimeAPI.projects.getFileDiff(opts).then(function(data) {
res.json({
diff: data
})
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
});
// Get a list of commits
app.get("/:id/commits", needsPermission("projects.read"), function(req, res) {
var opts = {
user: req.user,
id: req.params.id,
limit: req.query.limit || 20,
before: req.query.before
}
runtimeAPI.projects.getCommits(opts).then(function(data) {
res.json(data);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
});
// Get an individual commit details
app.get("/:id/commits/:sha", needsPermission("projects.read"), function(req, res) {
var opts = {
user: req.user,
id: req.params.id,
sha: req.params.sha
}
runtimeAPI.projects.getCommit(opts).then(function(data) {
res.json({commit:data});
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
});
// Push local commits to remote
app.post("/:id/push/?*", needsPermission("projects.write"), function(req,res) {
var opts = {
user: req.user,
id: req.params.id,
remote: req.params[0],
track: req.query.u
}
runtimeAPI.projects.push(opts).then(function(data) {
res.status(204).end();
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
});
// Pull remote commits
app.post("/:id/pull/?*", needsPermission("projects.write"), function(req,res) {
var opts = {
user: req.user,
id: req.params.id,
remote: req.params[0],
track: req.query.setUpstream,
allowUnrelatedHistories: req.query.allowUnrelatedHistories
}
runtimeAPI.projects.pull(opts).then(function(data) {
res.status(204).end();
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
});
// Abort an ongoing merge
app.delete("/:id/merge", needsPermission("projects.write"), function(req, res) {
var opts = {
user: req.user,
id: req.params.id
}
runtimeAPI.projects.abortMerge(opts).then(function() {
res.status(204).end();
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
});
// Resolve a merge
app.post("/:id/resolve/*", needsPermission("projects.write"), function(req, res) {
var opts = {
user: req.user,
id: req.params.id,
path: req.params[0],
resolution: req.body.resolutions
}
runtimeAPI.projects.resolveMerge(opts).then(function() {
res.status(204).end();
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
});
// Get a list of local branches
app.get("/:id/branches", needsPermission("projects.read"), function(req, res) {
var opts = {
user: req.user,
id: req.params.id,
remote: false
}
runtimeAPI.projects.getBranches(opts).then(function(data) {
res.json(data);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
});
// Delete a local branch - ?force=true
app.delete("/:id/branches/:branchName", needsPermission("projects.write"), function(req, res) {
var opts = {
user: req.user,
id: req.params.id,
branch: req.params.branchName,
force: !!req.query.force
}
runtimeAPI.projects.deleteBranch(opts).then(function(data) {
res.status(204).end();
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
});
// Get a list of remote branches
app.get("/:id/branches/remote", needsPermission("projects.read"), function(req, res) {
var opts = {
user: req.user,
id: req.params.id,
remote: true
}
runtimeAPI.projects.getBranches(opts).then(function(data) {
res.json(data);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
});
// Get branch status - commit counts/ahead/behind
app.get("/:id/branches/remote/*/status", needsPermission("projects.read"), function(req, res) {
var opts = {
user: req.user,
id: req.params.id,
branch: req.params[0]
}
runtimeAPI.projects.getBranchStatus(opts).then(function(data) {
res.json(data);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
});
// Set the active local branch
app.post("/:id/branches", needsPermission("projects.write"), function(req, res) {
var opts = {
user: req.user,
id: req.params.id,
branch: req.body.name,
create: req.body.create
}
runtimeAPI.projects.setBranch(opts).then(function(data) {
res.json(data);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
});
// Get a list of remotes
app.get("/:id/remotes", needsPermission("projects.read"), getProjectRemotes);
// Add a remote
app.post("/:id/remotes", needsPermission("projects.write"), function(req,res) {
var opts = {
user: req.user,
id: req.params.id,
remote: req.body
}
if (/^https?:\/\/[^/]+@/i.test(req.body.url)) {
res.status(400).json({error:"unexpected_error", message:"Git http url must not include username/password"});
return;
}
runtimeAPI.projects.addRemote(opts).then(function(data) {
getProjectRemotes(req,res);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
});
// Delete a remote
app.delete("/:id/remotes/:remoteName", needsPermission("projects.write"), function(req, res) {
var opts = {
user: req.user,
id: req.params.id,
remote: req.params.remoteName
}
runtimeAPI.projects.removeRemote(opts).then(function(data) {
getProjectRemotes(req,res);
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
});
// Update a remote
app.put("/:id/remotes/:remoteName", needsPermission("projects.write"), function(req,res) {
var remote = req.body || {};
remote.name = req.params.remoteName;
var opts = {
user: req.user,
id: req.params.id,
remote: remote
}
runtimeAPI.projects.updateRemote(opts).then(function() {
res.status(204).end();
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
})
});
return app;
}
}

View File

@@ -0,0 +1,60 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var apiUtils = require("../util");
var runtimeAPI;
var sshkeys = require("./sshkeys");
var theme = require("./theme");
module.exports = {
init: function(_runtimeAPI) {
runtimeAPI = _runtimeAPI;
sshkeys.init(runtimeAPI);
},
runtimeSettings: function(req,res) {
var opts = {
user: req.user
}
runtimeAPI.settings.getRuntimeSettings(opts).then(function(result) {
var themeSettings = theme.settings();
if (themeSettings) {
result.editorTheme = themeSettings;
}
res.json(result);
});
},
userSettings: function(req, res) {
var opts = {
user: req.user
}
runtimeAPI.settings.getUserSettings(opts).then(function(result) {
res.json(result);
});
},
updateUserSettings: function(req,res) {
var opts = {
user: req.user,
settings: req.body
}
runtimeAPI.settings.updateUserSettings(opts).then(function(result) {
res.status(204).end();
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
});
},
sshkeys: function() {
return sshkeys.app()
}
}

View File

@@ -0,0 +1,101 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var apiUtils = require("../util");
var express = require("express");
var runtimeAPI;
function getUsername(userObj) {
var username = '__default';
if ( userObj && userObj.name ) {
username = userObj.name;
}
return username;
}
module.exports = {
init: function(_runtimeAPI) {
runtimeAPI = _runtimeAPI;
},
app: function() {
var app = express();
// List all SSH keys
app.get("/", function(req,res) {
var opts = {
user: req.user
}
runtimeAPI.settings.getUserKeys(opts).then(function(list) {
res.json({
keys: list
});
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
});
});
// Get SSH key detail
app.get("/:id", function(req,res) {
var opts = {
user: req.user,
id: req.params.id
}
runtimeAPI.settings.getUserKey(opts).then(function(data) {
res.json({
publickey: data
});
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
});
});
// Generate a SSH key
app.post("/", function(req,res) {
var opts = {
user: req.user,
id: req.params.id
}
// TODO: validate params
opts.name = req.body.name;
opts.password = req.body.password;
opts.comment = req.body.comment;
opts.size = req.body.size;
runtimeAPI.settings.generateUserKey(opts).then(function(name) {
res.json({
name: name
});
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
});
});
// Delete a SSH key
app.delete("/:id", function(req,res) {
var opts = {
user: req.user,
id: req.params.id
}
runtimeAPI.settings.removeUserKey(opts).then(function(name) {
res.status(204).end();
}).catch(function(err) {
apiUtils.rejectHandler(req,res,err);
});
});
return app;
}
}

View File

@@ -0,0 +1,197 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var express = require("express");
var util = require("util");
var path = require("path");
var fs = require("fs");
var clone = require("clone");
var defaultContext = {
page: {
title: "Node-RED",
favicon: "favicon.ico",
tabicon: "red/images/node-red-icon-black.svg"
},
header: {
title: "Node-RED",
image: "red/images/node-red.png"
},
asset: {
red: (process.env.NODE_ENV == "development")? "red/red.js":"red/red.min.js",
main: (process.env.NODE_ENV == "development")? "red/main.js":"red/main.min.js",
}
};
var theme = null;
var themeContext = clone(defaultContext);
var themeSettings = null;
var themeApp;
function serveFile(app,baseUrl,file) {
try {
var stats = fs.statSync(file);
var url = baseUrl+path.basename(file);
//console.log(url,"->",file);
app.get(url,function(req, res) {
res.sendFile(file);
});
return "theme"+url;
} catch(err) {
//TODO: log filenotfound
return null;
}
}
function serveFilesFromTheme(themeValue, themeApp, directory) {
var result = [];
if (themeValue) {
var array = themeValue;
if (!util.isArray(array)) {
array = [array];
}
for (var i=0;i<array.length;i++) {
var url = serveFile(themeApp,directory,array[i]);
if (url) {
result.push(url);
}
}
}
return result
}
module.exports = {
init: function(settings) {
themeContext = clone(defaultContext);
themeSettings = null;
theme = settings.editorTheme || {};
},
app: function() {
var i;
var url;
themeSettings = {};
themeApp = express();
if (theme.page) {
themeContext.page.css = serveFilesFromTheme(
theme.page.css,
themeApp,
"/css/")
themeContext.page.scripts = serveFilesFromTheme(
theme.page.scripts,
themeApp,
"/scripts/")
if (theme.page.favicon) {
url = serveFile(themeApp,"/favicon/",theme.page.favicon)
if (url) {
themeContext.page.favicon = url;
}
}
if (theme.page.tabicon) {
url = serveFile(themeApp,"/tabicon/",theme.page.tabicon)
if (url) {
themeContext.page.tabicon = url;
}
}
themeContext.page.title = theme.page.title || themeContext.page.title;
}
if (theme.header) {
themeContext.header.title = theme.header.title || themeContext.header.title;
if (theme.header.hasOwnProperty("url")) {
themeContext.header.url = theme.header.url;
}
if (theme.header.hasOwnProperty("image")) {
if (theme.header.image) {
url = serveFile(themeApp,"/header/",theme.header.image);
if (url) {
themeContext.header.image = url;
}
} else {
themeContext.header.image = null;
}
}
}
if (theme.deployButton) {
if (theme.deployButton.type == "simple") {
themeSettings.deployButton = {
type: "simple"
}
if (theme.deployButton.label) {
themeSettings.deployButton.label = theme.deployButton.label;
}
if (theme.deployButton.icon) {
url = serveFile(themeApp,"/deploy/",theme.deployButton.icon);
if (url) {
themeSettings.deployButton.icon = url;
}
}
}
}
if (theme.hasOwnProperty("userMenu")) {
themeSettings.userMenu = theme.userMenu;
}
if (theme.login) {
if (theme.login.image) {
url = serveFile(themeApp,"/login/",theme.login.image);
if (url) {
themeContext.login = {
image: url
}
}
}
}
if (theme.hasOwnProperty("menu")) {
themeSettings.menu = theme.menu;
}
if (theme.hasOwnProperty("palette")) {
themeSettings.palette = theme.palette;
}
if (theme.hasOwnProperty("projects")) {
themeSettings.projects = theme.projects;
}
return themeApp;
},
context: function() {
return themeContext;
},
settings: function() {
return themeSettings;
},
serveFile: function(baseUrl,file) {
return serveFile(themeApp,baseUrl,file);
}
}

View File

@@ -0,0 +1,75 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var express = require('express');
var fs = require("fs");
var path = require("path");
var Mustache = require("mustache");
var mime = require("mime");
var apiUtils = require("../util");
var theme = require("./theme");
var runtimeAPI;
var editorClientDir = path.dirname(require.resolve("@node-red/editor-client"));
var defaultNodeIcon = path.join(editorClientDir,"public","red","images","icons","arrow-in.png");
var editorTemplatePath = path.join(editorClientDir,"templates","index.mst");
var editorTemplate;
module.exports = {
init: function(_runtimeAPI) {
runtimeAPI = _runtimeAPI;
editorTemplate = fs.readFileSync(editorTemplatePath,"utf8");
Mustache.parse(editorTemplate);
},
ensureSlash: function(req,res,next) {
var parts = req.originalUrl.split("?");
if (parts[0].slice(-1) != "/") {
parts[0] += "/";
var redirect = parts.join("?");
res.redirect(301,redirect);
} else {
next();
}
},
icon: function(req,res) {
var icon = req.params.icon;
var scope = req.params.scope;
var module = scope ? scope + '/' + req.params.module : req.params.module;
var opts = {
user: req.user,
module: module,
icon: icon
}
runtimeAPI.nodes.getIcon(opts).then(function(data) {
if (data) {
var contentType = mime.lookup(icon);
res.set("Content-Type", contentType);
res.send(data);
} else {
res.sendFile(defaultNodeIcon);
}
}).catch(function(err) {
console.log(err.stack);
apiUtils.rejectHandler(req,res,err);
})
},
editor: function(req,res) {
res.send(Mustache.render(editorTemplate,theme.context()));
},
editorResources: express.static(path.join(editorClientDir,'public'))
};

View File

@@ -0,0 +1,105 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var express = require("express");
var bodyParser = require("body-parser");
var util = require('util');
var passport = require('passport');
var when = require('when');
var cors = require('cors');
var auth = require("./auth");
var apiUtil = require("./util");
var adminApp;
var server;
var editor;
function init(_server,settings,storage,runtimeAPI) {
server = _server;
if (settings.httpAdminRoot !== false) {
adminApp = express();
var cors = require('cors');
var corsHandler = cors({
origin: "*",
methods: "GET,PUT,POST,DELETE"
});
adminApp.use(corsHandler);
auth.init(settings,storage);
var maxApiRequestSize = settings.apiMaxLength || '5mb';
adminApp.use(bodyParser.json({limit:maxApiRequestSize}));
adminApp.use(bodyParser.urlencoded({limit:maxApiRequestSize,extended:true}));
adminApp.get("/auth/login",auth.login,apiUtil.errorHandler);
if (settings.adminAuth) {
if (settings.adminAuth.type === "strategy") {
auth.genericStrategy(adminApp,settings.adminAuth.strategy);
} else if (settings.adminAuth.type === "credentials") {
adminApp.use(passport.initialize());
adminApp.post("/auth/token",
auth.ensureClientSecret,
auth.authenticateClient,
auth.getToken,
auth.errorHandler
);
}
adminApp.post("/auth/revoke",auth.needsPermission(""),auth.revoke,apiUtil.errorHandler);
}
// Editor
if (!settings.disableEditor) {
editor = require("./editor");
var editorApp = editor.init(server, settings, runtimeAPI);
adminApp.use(editorApp);
}
if (settings.httpAdminCors) {
var corsHandler = cors(settings.httpAdminCors);
adminApp.use(corsHandler);
}
var adminApiApp = require("./admin").init(runtimeAPI);
adminApp.use(adminApiApp);
} else {
adminApp = null;
}
}
function start() {
if (editor) {
return editor.start();
} else {
return when.resolve();
}
}
function stop() {
if (editor) {
editor.stop();
}
return when.resolve();
}
module.exports = {
init: init,
start: start,
stop: stop,
auth: {
needsPermission: auth.needsPermission
},
get adminApp() { return adminApp; },
get server() { return server; }
};

View File

@@ -0,0 +1,47 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var log = require("@node-red/util").log; // TODO: separate module
var i18n = require("@node-red/util").i18n; // TODO: separate module
module.exports = {
errorHandler: function(err,req,res,next) {
if (err.message === "request entity too large") {
log.error(err);
} else {
log.error(err.stack);
}
log.audit({event: "api.error",error:err.code||"unexpected_error",message:err.toString()},req);
res.status(400).json({error:"unexpected_error", message:err.toString()});
},
determineLangFromHeaders: function(acceptedLanguages){
var lang = i18n.defaultLang;
acceptedLanguages = acceptedLanguages || [];
if (acceptedLanguages.length >= 1) {
lang = acceptedLanguages[0];
}
return lang;
},
rejectHandler: function(req,res,err) {
res.status(err.status||500).json({
code: err.code||"unexpected_error",
message: err.message||err.toString()
});
}
}

View File

@@ -0,0 +1,33 @@
{
"name": "@node-red/editor",
"version": "0.20.0-alpha.0",
"license": "Apache-2.0",
"main": "./lib/index.js",
"repository": {
"type": "git",
"url": "https://github.com/node-red/node-red.git"
},
"contributors": [
{ "name": "Nick O'Leary" },
{ "name": "Dave Conway-Jones"}
],
"dependencies": {
"@node-red/util": "*",
"@node-red/editor-client": "*",
"bcryptjs": "2.4.3",
"body-parser": "1.18.3",
"clone": "2.1.2",
"cors": "2.8.4",
"express-session": "1.15.6",
"express": "4.16.3",
"memorystore": "1.6.0",
"mime": "1.4.1",
"mustache": "2.3.2",
"oauth2orize": "1.11.0",
"passport-http-bearer": "1.0.1",
"passport-oauth2-client-password": "0.1.2",
"passport": "0.4.0",
"when": "3.7.8",
"ws": "1.1.5"
}
}

View File

@@ -0,0 +1 @@
src

View File

@@ -0,0 +1,178 @@
Copyright JS Foundation and other contributors, http://js.foundation
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS

View File

@@ -0,0 +1,10 @@
@node-red/editor-client
====================
Node-RED editor resources module.
This provides all of the client-side resources of the Node-RED editor application.
### Source
The main Node-RED modules are maintained as a monorepo on [GitHub](https://github.com/node-red/node-red).

View File

@@ -0,0 +1,17 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
module.exports = false

View File

@@ -0,0 +1,859 @@
{
"common": {
"label": {
"name": "Name",
"ok": "Ok",
"done":"Done",
"cancel": "Cancel",
"delete": "Delete",
"close": "Close",
"load": "Load",
"save": "Save",
"import": "Import",
"export": "Export",
"back": "Back",
"next": "Next",
"clone": "Clone project",
"cont": "Continue"
}
},
"workspace": {
"defaultName": "Flow __number__",
"editFlow": "Edit flow: __name__",
"confirmDelete": "Confirm delete",
"delete": "Are you sure you want to delete '__label__'?",
"dropFlowHere": "Drop the flow here",
"addFlow": "Add Flow",
"status": "Status",
"enabled": "Enabled",
"disabled":"Disabled",
"info": "Description",
"tip": "Description accepts Markdown and will appear in the Info tab."
},
"menu": {
"label": {
"view": {
"view": "View",
"grid": "Grid",
"showGrid": "Show grid",
"snapGrid": "Snap to grid",
"gridSize": "Grid size",
"textDir": "Text Direction",
"defaultDir": "Default",
"ltr": "Left-to-right",
"rtl": "Right-to-left",
"auto": "Contextual"
},
"sidebar": {
"show": "Show sidebar"
},
"palette": {
"show": "Show palette"
},
"settings": "Settings",
"userSettings": "User Settings",
"nodes": "Nodes",
"displayStatus": "Show node status",
"displayConfig": "Configuration nodes",
"import": "Import",
"export": "Export",
"search": "Search flows",
"searchInput": "search your flows",
"clipboard": "Clipboard",
"library": "Library",
"examples": "Examples",
"subflows": "Subflows",
"createSubflow": "Create Subflow",
"selectionToSubflow": "Selection to Subflow",
"flows": "Flows",
"add": "Add",
"rename": "Rename",
"delete": "Delete",
"keyboardShortcuts": "Keyboard shortcuts",
"login": "Login",
"logout": "Logout",
"editPalette":"Manage palette",
"other": "Other",
"showTips": "Show tips",
"help": "Node-RED website",
"projects": "Projects",
"projects-new": "New",
"projects-open": "Open",
"projects-settings": "Project Settings"
}
},
"actions": {
"toggle-navigator": "Toggle navigator",
"zoom-out": "Zoom out",
"zoom-reset": "Reset zoom",
"zoom-in": "Zoom in"
},
"user": {
"loggedInAs": "Logged in as __name__",
"username": "Username",
"password": "Password",
"login": "Login",
"loginFailed": "Login failed",
"notAuthorized": "Not authorized",
"errors": {
"settings": "You must be logged in to access settings",
"deploy": "You must be logged in to deploy changes",
"notAuthorized": "You must be logged in to perform this action"
}
},
"notification": {
"warning": "<strong>Warning</strong>: __message__",
"warnings": {
"undeployedChanges": "node has undeployed changes",
"nodeActionDisabled": "node actions disabled within subflow",
"missing-types": "<p>Flows stopped due to missing node types.</p>",
"safe-mode":"<p>Flows stopped in safe mode.</p><p>You can modify your flows and deploy the changes to restart.</p>",
"restartRequired": "Node-RED must be restarted to enable upgraded modules",
"credentials_load_failed": "<p>Flows stopped as the credentials could not be decrypted.</p><p>The flow credential file is encrypted, but the project's encryption key is missing or invalid.</p>",
"credentials_load_failed_reset":"<p>Credentials could not be decrypted</p><p>The flow credential file is encrypted, but the project's encryption key is missing or invalid.</p><p>The flow credential file will be reset on the next deployment. Any existing flow credentials will be cleared.</p>",
"missing_flow_file": "<p>Project flow file not found.</p><p>The project is not configured with a flow file.</p>",
"missing_package_file": "<p>Project package file not found.</p><p>The project is missing a package.json file.</p>",
"project_empty": "<p>The project is empty.</p><p>Do you want to create a default set of project files?<br/>Otherwise, you will have to manually add files to the project outside of the editor.</p>",
"project_not_found": "<p>Project '__project__' not found.</p>",
"git_merge_conflict": "<p>Automatic merging of changes failed.</p><p>Fix the unmerged conflicts then commit the results.</p>"
},
"error": "<strong>Error</strong>: __message__",
"errors": {
"lostConnection": "Lost connection to server, reconnecting...",
"lostConnectionReconnect": "Lost connection to server, reconnecting in __time__s.",
"lostConnectionTry": "Try now",
"cannotAddSubflowToItself": "Cannot add subflow to itself",
"cannotAddCircularReference": "Cannot add subflow - circular reference detected",
"unsupportedVersion": "<p>Using an unsupported version of Node.js</p><p>You should upgrade to the latest Node.js LTS release</p>",
"failedToAppendNode": "<p>Failed to load '__module__'</p><p>__error__</p>"
},
"project": {
"change-branch": "Change to local branch '__project__'",
"merge-abort": "Git merge aborted",
"loaded": "Project '__project__' loaded",
"updated": "Project '__project__' updated",
"pull": "Project '__project__' reloaded",
"revert": "Project '__project__' reloaded",
"merge-complete": "Git merge completed"
},
"label": {
"manage-project-dep": "Manage project dependencies",
"setup-cred": "Setup credentials",
"setup-project": "Setup project files",
"create-default-package": "Create default package file",
"no-thanks": "No thanks",
"create-default-project": "Create default project files",
"show-merge-conflicts": "Show merge conflicts"
}
},
"clipboard": {
"nodes": "Nodes",
"selectNodes": "Select the text above and copy to the clipboard.",
"pasteNodes": "Paste nodes here",
"importNodes": "Import nodes",
"exportNodes": "Export nodes to clipboard",
"importUnrecognised": "Imported unrecognised type:",
"importUnrecognised_plural": "Imported unrecognised types:",
"nodesExported": "Nodes exported to clipboard",
"nodeCopied": "__count__ node copied",
"nodeCopied_plural": "__count__ nodes copied",
"invalidFlow": "Invalid flow: __message__",
"export": {
"selected":"selected nodes",
"current":"current flow",
"all":"all flows",
"compact":"compact",
"formatted":"formatted",
"copy": "Export to clipboard"
},
"import": {
"import": "Import to",
"newFlow": "new flow"
},
"copyMessagePath": "Path copied",
"copyMessageValue": "Value copied",
"copyMessageValue_truncated": "Truncated value copied"
},
"deploy": {
"deploy": "Deploy",
"full": "Full",
"fullDesc": "Deploys everything in the workspace",
"modifiedFlows": "Modified Flows",
"modifiedFlowsDesc": "Only deploys flows that contain changed nodes",
"modifiedNodes": "Modified Nodes",
"modifiedNodesDesc": "Only deploys nodes that have changed",
"restartFlows": "Restart Flows",
"restartFlowsDesc": "Restarts the current deployed flows",
"successfulDeploy": "Successfully deployed",
"successfulRestart": "Successfully restarted flows",
"deployFailed": "Deploy failed: __message__",
"unusedConfigNodes":"You have some unused configuration nodes.",
"unusedConfigNodesLink":"Click here to see them",
"errors": {
"noResponse": "no response from server"
},
"confirm": {
"button": {
"ignore": "Ignore",
"confirm": "Confirm deploy",
"review": "Review changes",
"cancel": "Cancel",
"merge": "Merge",
"overwrite": "Ignore & deploy"
},
"undeployedChanges": "You have undeployed changes.\n\nLeaving this page will lose these changes.",
"improperlyConfigured": "The workspace contains some nodes that are not properly configured:",
"unknown": "The workspace contains some unknown node types:",
"confirm": "Are you sure you want to deploy?",
"doNotWarn": "do not warn about this again",
"conflict": "The server is running a more recent set of flows.",
"backgroundUpdate": "The flows on the server have been updated.",
"conflictChecking": "Checking to see if the changes can be merged automatically",
"conflictAutoMerge": "The changes include no conflicts and can be merged automatically.",
"conflictManualMerge": "The changes include conflicts that must be resolved before they can be deployed.",
"plusNMore": "+ __count__ more"
}
},
"eventLog": {
"title": "Event log",
"view": "View log"
},
"diff": {
"unresolvedCount": "__count__ unresolved conflict",
"unresolvedCount_plural": "__count__ unresolved conflicts",
"globalNodes": "Global nodes",
"flowProperties": "Flow Properties",
"type": {
"added": "added",
"changed": "changed",
"unchanged": "unchanged",
"deleted": "deleted",
"flowDeleted": "flow deleted",
"flowAdded": "flow added",
"movedTo": "moved to __id__",
"movedFrom": "moved from __id__"
},
"nodeCount": "__count__ node",
"nodeCount_plural": "__count__ nodes",
"local":"Local changes",
"remote":"Remote changes",
"reviewChanges": "Review Changes",
"noBinaryFileShowed": "Cannot show binary file contents",
"viewCommitDiff": "View Commit Changes",
"compareChanges": "Compare Changes",
"saveConflict": "Save conflict resolution",
"conflictHeader": "<span>__resolved__</span> of <span>__unresolved__</span> conflicts resolved",
"commonVersionError": "Common Version doesn't contain valid JSON:",
"oldVersionError": "Old Version doesn't contain valid JSON:",
"newVersionError": "New Version doesn't contain valid JSON:"
},
"subflow": {
"editSubflow": "Edit flow template: __name__",
"edit": "Edit flow template",
"subflowInstances": "There is __count__ instance of this subflow template",
"subflowInstances_plural": "There are __count__ instances of this subflow template",
"editSubflowProperties": "edit properties",
"input": "inputs:",
"output": "outputs:",
"deleteSubflow": "delete subflow",
"info": "Description",
"category": "Category",
"format":"markdown format",
"errors": {
"noNodesSelected": "<strong>Cannot create subflow</strong>: no nodes selected",
"multipleInputsToSelection": "<strong>Cannot create subflow</strong>: multiple inputs to selection"
}
},
"editor": {
"configEdit": "Edit",
"configAdd": "Add",
"configUpdate": "Update",
"configDelete": "Delete",
"nodesUse": "__count__ node uses this config",
"nodesUse_plural": "__count__ nodes use this config",
"addNewConfig": "Add new __type__ config node",
"editNode": "Edit __type__ node",
"editConfig": "Edit __type__ config node",
"addNewType": "Add new __type__...",
"nodeProperties": "node properties",
"portLabels": "node settings",
"labelInputs": "Inputs",
"labelOutputs": "Outputs",
"settingIcon": "Icon",
"noDefaultLabel": "none",
"defaultLabel": "use default label",
"searchIcons": "Search icons",
"useDefault": "use default",
"description": "Description",
"errors": {
"scopeChange": "Changing the scope will make it unavailable to nodes in other flows that use it"
}
},
"keyboard": {
"title": "Keyboard Shortcuts",
"keyboard": "Keyboard",
"filterActions": "filter actions",
"shortcut": "shortcut",
"scope": "scope",
"unassigned": "Unassigned",
"global": "global",
"workspace": "workspace",
"selectAll": "Select all nodes",
"selectAllConnected": "Select all connected nodes",
"addRemoveNode": "Add/remove node from selection",
"editSelected": "Edit selected node",
"deleteSelected": "Delete selected nodes or link",
"importNode": "Import nodes",
"exportNode": "Export nodes",
"nudgeNode": "Move selected nodes (1px)",
"moveNode": "Move selected nodes (20px)",
"toggleSidebar": "Toggle sidebar",
"copyNode": "Copy selected nodes",
"cutNode": "Cut selected nodes",
"pasteNode": "Paste nodes",
"undoChange": "Undo the last change performed",
"searchBox": "Open search box",
"managePalette": "Manage palette"
},
"library": {
"openLibrary": "Open Library...",
"saveToLibrary": "Save to Library...",
"typeLibrary": "__type__ library",
"unnamedType": "Unnamed __type__",
"exportToLibrary": "Export nodes to library",
"dialogSaveOverwrite": "A __libraryType__ called __libraryName__ already exists. Overwrite?",
"invalidFilename": "Invalid filename",
"savedNodes": "Saved nodes",
"savedType": "Saved __type__",
"saveFailed": "Save failed: __message__",
"filename": "Filename",
"folder": "Folder",
"filenamePlaceholder": "file",
"fullFilenamePlaceholder": "a/b/file",
"folderPlaceholder": "a/b",
"breadcrumb": "Library"
},
"palette": {
"noInfo": "no information available",
"filter": "filter nodes",
"search": "search modules",
"addCategory": "Add new...",
"label": {
"subflows": "subflows",
"input": "input",
"output": "output",
"function": "function",
"social": "social",
"storage": "storage",
"analysis": "analysis",
"advanced": "advanced"
},
"actions": {
"collapse-all": "Collapse all categories",
"expand-all": "Expand all categories"
},
"event": {
"nodeAdded": "Node added to palette:",
"nodeAdded_plural": "Nodes added to palette",
"nodeRemoved": "Node removed from palette:",
"nodeRemoved_plural": "Nodes removed from palette:",
"nodeEnabled": "Node enabled:",
"nodeEnabled_plural": "Nodes enabled:",
"nodeDisabled": "Node disabled:",
"nodeDisabled_plural": "Nodes disabled:",
"nodeUpgraded": "Node module __module__ upgraded to version __version__"
},
"editor": {
"title": "Manage palette",
"palette": "Palette",
"times": {
"seconds": "seconds ago",
"minutes": "minutes ago",
"minutesV": "__count__ minutes ago",
"hoursV": "__count__ hour ago",
"hoursV_plural": "__count__ hours ago",
"daysV": "__count__ day ago",
"daysV_plural": "__count__ days ago",
"weeksV": "__count__ week ago",
"weeksV_plural": "__count__ weeks ago",
"monthsV": "__count__ month ago",
"monthsV_plural": "__count__ months ago",
"yearsV": "__count__ year ago",
"yearsV_plural": "__count__ years ago",
"yearMonthsV": "__y__ year, __count__ month ago",
"yearMonthsV_plural": "__y__ year, __count__ months ago",
"yearsMonthsV": "__y__ years, __count__ month ago",
"yearsMonthsV_plural": "__y__ years, __count__ months ago"
},
"nodeCount": "__label__ node",
"nodeCount_plural": "__label__ nodes",
"moduleCount": "__count__ module available",
"moduleCount_plural": "__count__ modules available",
"inuse": "in use",
"enableall": "enable all",
"disableall": "disable all",
"enable": "enable",
"disable": "disable",
"remove": "remove",
"update": "update to __version__",
"updated": "updated",
"install": "install",
"installed": "installed",
"loading": "Loading catalogues...",
"tab-nodes": "Nodes",
"tab-install": "Install",
"sort": "sort:",
"sortAZ": "a-z",
"sortRecent": "recent",
"more": "+ __count__ more",
"errors": {
"catalogLoadFailed": "<p>Failed to load node catalogue.</p><p>Check the browser console for more information</p>",
"installFailed": "<p>Failed to install: __module__</p><p>__message__</p><p>Check the log for more information</p>",
"removeFailed": "<p>Failed to remove: __module__</p><p>__message__</p><p>Check the log for more information</p>",
"updateFailed": "<p>Failed to update: __module__</p><p>__message__</p><p>Check the log for more information</p>",
"enableFailed": "<p>Failed to enable: __module__</p><p>__message__</p><p>Check the log for more information</p>",
"disableFailed": "<p>Failed to disable: __module__</p><p>__message__</p><p>Check the log for more information</p>"
},
"confirm": {
"install": {
"body":"<p>Installing '__module__'</p><p>Before installing, please read the node's documentation. Some nodes have dependencies that cannot be automatically resolved and can require a restart of Node-RED.</p>",
"title": "Install nodes"
},
"remove": {
"body":"<p>Removing '__module__'</p><p>Removing the node will uninstall it from Node-RED. The node may continue to use resources until Node-RED is restarted.</p>",
"title": "Remove nodes"
},
"update": {
"body":"<p>Updating '__module__'</p><p>Updating the node will require a restart of Node-RED to complete the update. This must be done manually.</p>",
"title": "Update nodes"
},
"cannotUpdate": {
"body":"An update for this node is available, but it is not installed in a location that the palette manager can update.<br/><br/>Please refer to the documentation for how to update this node."
},
"button": {
"review": "Open node information",
"install": "Install",
"remove": "Remove",
"update": "Update"
}
}
}
},
"sidebar": {
"info": {
"name": "Node information",
"tabName": "Name",
"label": "info",
"node": "Node",
"type": "Type",
"module": "Module",
"id": "ID",
"status": "Status",
"enabled": "Enabled",
"disabled": "Disabled",
"subflow": "Subflow",
"instances": "Instances",
"properties": "Properties",
"info": "Information",
"desc": "Description",
"blank": "blank",
"null": "null",
"showMore": "show more",
"showLess": "show less",
"flow": "Flow",
"selection":"Selection",
"nodes":"__count__ nodes",
"flowDesc": "Flow Description",
"subflowDesc": "Subflow Description",
"nodeHelp": "Node Help",
"none":"None",
"arrayItems": "__count__ items",
"showTips":"You can open the tips from the settings panel"
},
"config": {
"name": "Configuration nodes",
"label": "config",
"global": "On all flows",
"none": "none",
"subflows": "subflows",
"flows": "flows",
"filterUnused":"unused",
"filterAll":"all",
"filtered": "__count__ hidden"
},
"context": {
"name":"Context Data",
"label":"context",
"none": "none selected",
"refresh": "refresh to load",
"empty": "empty",
"node": "Node",
"flow": "Flow",
"global": "Global"
},
"palette": {
"name": "Palette management",
"label": "palette"
},
"project": {
"label": "project",
"name": "Project",
"description": "Description",
"dependencies": "Dependencies",
"settings": "Settings",
"noSummaryAvailable": "No summary available",
"editDescription": "Edit project description",
"editDependencies": "Edit project dependencies",
"editReadme": "Edit README.md",
"showProjectSettings": "Show project settings",
"projectSettings": {
"title": "Project Settings",
"edit": "edit",
"none": "None",
"install": "install",
"removeFromProject": "remove from project",
"addToProject": "add to project",
"files": "Files",
"flow": "Flow",
"credentials": "Credentials",
"invalidEncryptionKey": "Invalid encryption key",
"encryptionEnabled": "Encryption enabled",
"encryptionDisabled": "Encryption disabled",
"setTheEncryptionKey": "Set the encryption key:",
"resetTheEncryptionKey": "Reset the encryption key:",
"changeTheEncryptionKey": "Change the encryption key:",
"currentKey": "Current key",
"newKey": "New key",
"credentialsAlert": "This will delete all existing credentials",
"versionControl": "Version Control",
"branches": "Branches",
"noBranches": "No branches",
"deleteConfirm": "Are you sure you want to delete the local branch '__name__'? This cannot be undone.",
"unmergedConfirm": "The local branch '__name__' has unmerged changes that will be lost. Are you sure you want to delete it?",
"deleteUnmergedBranch": "Delete unmerged branch",
"gitRemotes": "Git remotes",
"addRemote": "add remote",
"addRemote2": "Add remote",
"remoteName": "Remote name",
"nameRule": "Must contain only A-Z 0-9 _ -",
"url": "URL",
"urlRule": "https://, ssh:// or file://",
"urlRule2": "Do not include the username/password in the URL",
"noRemotes": "No remotes",
"deleteRemoteConfrim": "Are you sure you want to delete the remote '__name__'?",
"deleteRemote": "Delete remote"
},
"userSettings": {
"committerDetail": "Committer Details",
"committerTip": "Leave blank to use system default",
"userName": "Username",
"email": "Email",
"sshKeys": "SSH Keys",
"sshKeysTip": "Allows you to create secure connections to remote git repositories.",
"add": "add key",
"addSshKey": "Add SSH Key",
"addSshKeyTip": "Generate a new public/private key pair",
"name": "Name",
"nameRule": "Must contain only A-Z 0-9 _ -",
"passphrase": "Passphrase",
"passphraseShort": "Passphrase too short",
"optional": "Optional",
"cancel": "Cancel",
"generate": "Generate key",
"noSshKeys": "No SSH keys",
"copyPublicKey": "Copy public key to clipboard",
"delete": "Delete key",
"gitConfig": "Git config",
"deleteConfirm": "Are you sure you want to delete the SSH key __name__? This cannot be undone."
},
"versionControl": {
"unstagedChanges": "Unstaged changes",
"stagedChanges": "Staged changes",
"resolveConflicts": "Resolve conflicts",
"head": "HEAD",
"staged": "Staged",
"unstaged": "Unstaged",
"local": "Local",
"remote": "Remote",
"revert": "Are you sure you want to revert the changes to '__file__'? This cannot be undone.",
"revertChanges": "Revert changes",
"localChanges": "Local Changes",
"none": "None",
"conflictResolve": "All conflicts resolved. Commit the changes to complete the merge.",
"localFiles": "Local files",
"all": "all",
"unmergedChanges": "Unmerged changes",
"abortMerge": "abort merge",
"commit": "commit",
"changeToCommit": "Changes to commit",
"commitPlaceholder": "Enter your commit message",
"cancelCapital": "Cancel",
"commitCapital": "Commit",
"commitHistory": "Commit History",
"branch": "Branch:",
"moreCommits": " more commit(s)",
"changeLocalBranch": "Change local branch",
"createBranchPlaceholder": "Find or create a branch",
"upstream": "upstream",
"localOverwrite": "You have local changes that would be overwritten by changing the branch. You must either commit or undo those changes first.",
"manageRemoteBranch": "Manage remote branch",
"unableToAccess": "Unable to access remote repository",
"retry": "Retry",
"setUpstreamBranch": "Set as upstream branch",
"createRemoteBranchPlaceholder": "Find or create a remote branch",
"trackedUpstreamBranch": "The created branch will be set as the tracked upstream branch.",
"selectUpstreamBranch": "The branch will be created. Select below to set it as the tracked upstream branch.",
"pushFailed": "Push failed as the remote has more recent commits. Pull and merge first, then push again.",
"push": "push",
"pull": "pull",
"unablePull": "<p>Unable to pull remote changes; your unstaged local changes would be overwritten.</p><p>Commit your changes and try again.</p>",
"showUnstagedChanges": "Show unstaged changes",
"connectionFailed": "Could not connect to remote repository: ",
"pullUnrelatedHistory": "<p>The remote has an unrelated history of commits.</p><p>Are you sure you want to pull the changes into your local repository?</p>",
"pullChanges": "Pull changes",
"history": "history",
"projectHistory": "Project History",
"daysAgo": "__count__ day ago",
"daysAgo_plural": "__count__ days ago",
"hoursAgo": "__count__ hour ago",
"hoursAgo_plural": "__count__ hours ago",
"minsAgo": "__count__ min ago",
"minsAgo_plural": "__count__ mins ago",
"secondsAgo": "Seconds ago",
"notTracking": "Your local branch is not currently tracking a remote branch.",
"statusUnmergedChanged": "Your repository has unmerged changes. You need to fix the conflicts and commit the result.",
"repositoryUpToDate": "Your repository is up to date.",
"commitsAhead": "Your repository is __count__ commit ahead of the remote. You can push this commit now.",
"commitsAhead_plural": "Your repository is __count__ commits ahead of the remote. You can push these commits now.",
"commitsBehind": "Your repository is __count__ commit behind of the remote. You can pull this commit now.",
"commitsBehind_plural": "Your repository is __count__ commits behind of the remote. You can pull these commits now.",
"commitsAheadAndBehind1": "Your repository is __count__ commit behind and ",
"commitsAheadAndBehind1_plural": "Your repository is __count__ commits behind and ",
"commitsAheadAndBehind2": "__count__ commit ahead of the remote. ",
"commitsAheadAndBehind2_plural": "__count__ commits ahead of the remote. ",
"commitsAheadAndBehind3": "You must pull the remote commit down before pushing.",
"commitsAheadAndBehind3_plural": "You must pull the remote commits down before pushing."
}
}
},
"typedInput": {
"type": {
"str": "string",
"num": "number",
"re": "regular expression",
"bool": "boolean",
"json": "JSON",
"bin": "buffer",
"date": "timestamp",
"jsonata": "expression",
"env": "env variable"
}
},
"editableList": {
"add": "add"
},
"search": {
"empty": "No matches found",
"addNode": "add a node..."
},
"expressionEditor": {
"functions": "Functions",
"functionReference": "Function reference",
"insert": "Insert",
"title": "JSONata Expression editor",
"test": "Test",
"data": "Example message",
"result": "Result",
"format": "format expression",
"compatMode": "Compatibility mode enabled",
"compatModeDesc": "<h3>JSONata compatibility mode</h3><p> The current expression appears to still reference <code>msg</code> so will be evaluated in compatibility mode. Please update the expression to not use <code>msg</code> as this mode will be removed in the future.</p><p> When JSONata support was first added to Node-RED, it required the expression to reference the <code>msg</code> object. For example <code>msg.payload</code> would be used to access the payload.</p><p> That is no longer necessary as the expression will be evaluated against the message directly. To access the payload, the expression should be just <code>payload</code>.</p>",
"noMatch": "No matching result",
"errors": {
"invalid-expr": "Invalid JSONata expression:\n __message__",
"invalid-msg": "Invalid example JSON message:\n __message__",
"context-unsupported": "Cannot test context functions\n $flowContext or $globalContext",
"eval": "Error evaluating expression:\n __message__"
}
},
"jsEditor": {
"title": "JavaScript editor"
},
"jsonEditor": {
"title": "JSON editor",
"format": "format JSON"
},
"markdownEditor": {
"title": "Markdown editor"
},
"bufferEditor": {
"title": "Buffer editor",
"modeString": "Handle as UTF-8 String",
"modeArray": "Handle as JSON array",
"modeDesc":"<h3>Buffer editor</h3><p>The Buffer type is stored as a JSON array of byte values. The editor will attempt to parse the entered value as a JSON array. If it is not valid JSON, it will be treated as a UTF-8 String and converted to an array of the individual character code points.</p><p>For example, a value of <code>Hello World</code> will be converted to the JSON array:<pre>[72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100]</pre></p>"
},
"projects": {
"config-git": "Configure Git client",
"welcome": {
"hello": "Hello! We have introduced 'projects' to Node-RED.",
"desc0": "This is a new way for you to manage your flow files and includes version control of your flows.",
"desc1": "To get started you can create your first project or clone an existing project from a git repository.",
"desc2": "If you are not sure, you can skip this for now. You will still be able to create your first project from the 'Projects' menu at any time.",
"create": "Create Project",
"clone": "Clone Repository",
"not-right-now": "Not right now"
},
"git-config": {
"setup": "Setup your version control client",
"desc0": "Node-RED uses the open source tool Git for version control. It tracks changes to your project files and lets you push them to remote repositories.",
"desc1": "When you commit a set of changes, Git records who made the changes with a username and email address. The Username can be anything you want - it does not need to be your real name.",
"desc2": "Your Git client is already configured with the details below.",
"desc3": "You can change these settings later under the 'Git config' tab of the settings dialog.",
"username": "Username",
"email": "Email"
},
"project-details": {
"create": "Create your project",
"desc0": "A project is maintained as a Git repository. It makes it much easier to share your flows with others and to collaborate on them.",
"desc1": "You can create multiple projects and quickly switch between them from the editor.",
"desc2": "To begin, your project needs a name and an optional description.",
"already-exists": "Project already exists",
"must-contain": "Must contain only A-Z 0-9 _ -",
"project-name": "Project name",
"desc": "Description",
"opt": "Optional"
},
"clone-project": {
"clone": "Clone a project",
"desc0": "If you already have a git repository containing a project, you can clone it to get started.",
"already-exists": "Project already exists",
"must-contain": "Must contain only A-Z 0-9 _ -",
"project-name": "Project name",
"no-info-in-url": "Do not include the username/password in the url",
"git-url": "Git repository URL",
"protocols": "https://, ssh:// or file://",
"auth-failed": "Authentication failed",
"username": "Username",
"passwd": "Password",
"ssh-key": "SSH Key",
"passphrase": "Passphrase",
"ssh-key-desc": "Before you can clone a repository over ssh you must add an SSH key to access it.",
"ssh-key-add": "Add an ssh key",
"credential-key": "Credentials encryption key",
"cant-get-ssh-key": "Error! Can't get selected SSH key path.",
"already-exists2": "already exists",
"git-error": "git error",
"connection-failed": "Connection failed",
"not-git-repo": "Not a git repository",
"repo-not-found": "Repository not found"
},
"default-files": {
"create": "Create your project files",
"desc0": "A project contains your flow files, a README file and a package.json file.",
"desc1": "It can contain any other files you want to maintain in the Git repository.",
"desc2": "Your existing flow and credential files will be copied into the project.",
"flow-file": "Flow file",
"credentials-file": "Credentials file"
},
"encryption-config": {
"setup": "Setup encryption of your credentials file",
"desc0": "Your flow credentials file can be encrypted to keep its contents secure.",
"desc1": "If you want to store these credentials in a public Git repository, you must encrypt them by providing a secret key phrase.",
"desc2": "Your flow credentials file is not currently encrypted.",
"desc3": "That means its contents, such as passwords and access tokens, can be read by anyone with access to the file.",
"desc4": "If you want to store these credentials in a public Git repository, you must encrypt them by providing a secret key phrase.",
"desc5": "Your flow credentials file is currently encrypted using the credentialSecret property from your settings file as the key.",
"desc6": "Your flow credentials file is currently encrypted using a system-generated key. You should provide a new secret key for this project.",
"desc7": "The key will be stored separately from your project files. You will need to provide the key to use this project in another instance of Node-RED.",
"credentials": "Credentials",
"enable": "Enable encryption",
"disable": "Disable encryption",
"disabled": "disabled",
"copy": "Copy over existing key",
"use-custom": "Use custom key",
"desc8": "The credentials file will not be encrypted and its contents easily read",
"create-project-files": "Create project files",
"create-project": "Create project",
"already-exists": "already exists",
"git-error": "git error",
"git-auth-error": "git auth error"
},
"create-success": {
"success": "You have successfully created your first project!",
"desc0": "You can now continue to use Node-RED just as you always have.",
"desc1": "The 'info' tab in the sidebar shows you what your current active project is. The button next to the name can be used to access the project settings view.",
"desc2": "The 'history' tab in the sidebar can be used to view files that have changed in your project and to commit them. It shows you a complete history of your commits and allows you to push your changes to a remote repository."
},
"create": {
"projects": "Projects",
"already-exists": "Project already exists",
"must-contain": "Must contain only A-Z 0-9 _ -",
"no-info-in-url": "Do not include the username/password in the url",
"open": "Open Project",
"create": "Create Project",
"clone": "Clone Repository",
"project-name": "Project name",
"desc": "Description",
"opt": "Optional",
"flow-file": "Flow file",
"credentials": "Credentials",
"enable-encryption": "Enable encryption",
"disable-encryption": "Disable encryption",
"encryption-key": "Encryption key",
"desc0": "A phrase to secure your credentials with",
"desc1": "The credentials file will not be encrypted and its contents easily read",
"git-url": "Git repository URL",
"protocols": "https://, ssh:// or file://",
"auth-failed": "Authentication failed",
"username": "Username",
"password": "Password",
"ssh-key": "SSH Key",
"passphrase": "Passphrase",
"desc2": "Before you can clone a repository over ssh you must add an SSH key to access it.",
"add-ssh-key": "Add an ssh key",
"credentials-encryption-key": "Credentials encryption key",
"already-exists-2": "already exists",
"git-error": "git error",
"con-failed": "Connection failed",
"not-git": "Not a git repository",
"no-resource": "Repository not found",
"cant-get-ssh-key-path": "Error! Can't get selected SSH key path.",
"unexpected_error": "unexpected_error"
},
"delete": {
"confirm": "Are you sure you want to delete this project?"
},
"create-project-list": {
"search": "search your projects",
"current": "current"
},
"require-clean": {
"confirm": "<p>You have undeployed changes that will be lost.</p><p>Do you want to continue?</p>"
},
"send-req": {
"auth-req": "Authentication required for repository",
"username": "Username",
"password": "Password",
"passphrase": "Passphrase",
"retry": "Retry",
"update-failed": "Failed to update auth",
"unhandled": "Unhandled error response"
},
"create-branch-list": {
"invalid": "Invalid branch",
"create": "Create branch",
"current": "current"
},
"create-default-file-set": {
"no-active": "Cannot create default file set without an active project",
"no-empty": "Cannot create default file set on a non-empty project",
"git-error": "git error"
},
"errors" : {
"no-username-email": "Your Git client is not configured with a username/email.",
"unexpected": "An unexpected error occurred",
"code": "code"
}
}
}

View File

@@ -0,0 +1,23 @@
{
"info": {
"tip0" : "You can remove the selected nodes or links with {{core:delete-selection}}",
"tip1" : "Search for nodes using {{core:search}}",
"tip2" : "{{core:toggle-sidebar}} will toggle the view of this sidebar",
"tip3" : "You can manage your palette of nodes with {{core:manage-palette}}",
"tip4" : "Your flow configuration nodes are listed in the sidebar panel. It can been accessed from the menu or with {{core:show-config-tab}}",
"tip5" : "Enable or disable these tips from the option in the settings",
"tip6" : "Move the selected nodes using the [left] [up] [down] and [right] keys. Hold [shift] to nudge them further",
"tip7" : "Dragging a node onto a wire will splice it into the link",
"tip8" : "Export the selected nodes, or the current tab with {{core:show-export-dialog}}",
"tip9" : "Import a flow by dragging its JSON into the editor, or with {{core:show-import-dialog}}",
"tip10" : "[shift] [click] and drag on a node port to move all of the attached wires or just the selected one",
"tip11" : "Show the Info tab with {{core:show-info-tab}} or the Debug tab with {{core:show-debug-tab}}",
"tip12" : "[ctrl] [click] in the workspace to open the quick-add dialog",
"tip13" : "Hold down [ctrl] when you [click] on a node port to enable quick-wiring",
"tip14" : "Hold down [shift] when you [click] on a node to also select all of its connected nodes",
"tip15" : "Hold down [ctrl] when you [click] on a node to add or remove it from the current selection",
"tip16" : "Switch flow tabs with {{core:show-previous-tab}} and {{core:show-next-tab}}",
"tip17" : "You can confirm your changes in the node edit tray with {{core:confirm-edit-tray}} or cancel them with {{core:cancel-edit-tray}}",
"tip18" : "Pressing {{core:edit-selected-node}} will edit the first node in the current selection"
}
}

View File

@@ -0,0 +1,223 @@
{
"$string": {
"args": "arg",
"desc": "Casts the *arg* parameter to a string using the following casting rules:\n\n - Strings are unchanged\n - Functions are converted to an empty string\n - Numeric infinity and NaN throw an error because they cannot be represented as a JSON number\n - All other values are converted to a JSON string using the `JSON.stringify` function"
},
"$length": {
"args": "str",
"desc": "Returns the number of characters in the string `str`. An error is thrown if `str` is not a string."
},
"$substring": {
"args": "str, start[, length]",
"desc": "Returns a string containing the characters in the first parameter `str` starting at position `start` (zero-offset). If `length` is specified, then the substring will contain maximum `length` characters. If `start` is negative then it indicates the number of characters from the end of `str`."
},
"$substringBefore": {
"args": "str, chars",
"desc": "Returns the substring before the first occurrence of the character sequence `chars` in `str`. If `str` does not contain `chars`, then it returns `str`."
},
"$substringAfter": {
"args": "str, chars",
"desc": "Returns the substring after the first occurrence of the character sequence `chars` in `str`. If `str` does not contain `chars`, then it returns `str`."
},
"$uppercase": {
"args": "str",
"desc": "Returns a string with all the characters of `str` converted to uppercase."
},
"$lowercase": {
"args": "str",
"desc": "Returns a string with all the characters of `str` converted to lowercase."
},
"$trim": {
"args": "str",
"desc": "Normalizes and trims all whitespace characters in `str` by applying the following steps:\n\n - All tabs, carriage returns, and line feeds are replaced with spaces.\n- Contiguous sequences of spaces are reduced to a single space.\n- Trailing and leading spaces are removed.\n\n If `str` is not specified (i.e. this function is invoked with no arguments), then the context value is used as the value of `str`. An error is thrown if `str` is not a string."
},
"$contains": {
"args": "str, pattern",
"desc": "Returns `true` if `str` is matched by `pattern`, otherwise it returns `false`. If `str` is not specified (i.e. this function is invoked with one argument), then the context value is used as the value of `str`. The `pattern` parameter can either be a string or a regular expression."
},
"$split": {
"args": "str[, separator][, limit]",
"desc": "Splits the `str` parameter into an array of substrings. It is an error if `str` is not a string. The optional `separator` parameter specifies the characters within the `str` about which it should be split as either a string or regular expression. If `separator` is not specified, then the empty string is assumed, and `str` will be split into an array of single characters. It is an error if `separator` is not a string. The optional `limit` parameter is a number that specifies the maximum number of substrings to include in the resultant array. Any additional substrings are discarded. If `limit` is not specified, then `str` is fully split with no limit to the size of the resultant array. It is an error if `limit` is not a non-negative number."
},
"$join": {
"args": "array[, separator]",
"desc": "Joins an array of component strings into a single concatenated string with each component string separated by the optional `separator` parameter. It is an error if the input `array` contains an item which isn't a string. If `separator` is not specified, then it is assumed to be the empty string, i.e. no `separator` between the component strings. It is an error if `separator` is not a string."
},
"$match": {
"args": "str, pattern [, limit]",
"desc": "Applies the `str` string to the `pattern` regular expression and returns an array of objects, with each object containing information about each occurrence of a match within `str`."
},
"$replace": {
"args": "str, pattern, replacement [, limit]",
"desc": "Finds occurrences of `pattern` within `str` and replaces them with `replacement`.\n\nThe optional `limit` parameter is the maximum number of replacements."
},
"$now": {
"args":"",
"desc":"Generates a timestamp in ISO 8601 compatible format and returns it as a string."
},
"$base64encode": {
"args":"string",
"desc":"Converts an ASCII string to a base 64 representation. Each character in the string is treated as a byte of binary data. This requires that all characters in the string are in the 0x00 to 0xFF range, which includes all characters in URI encoded strings. Unicode characters outside of that range are not supported."
},
"$base64decode": {
"args":"string",
"desc":"Converts base 64 encoded bytes to a string, using a UTF-8 Unicode codepage."
},
"$number": {
"args": "arg",
"desc": "Casts the `arg` parameter to a number using the following casting rules:\n\n - Numbers are unchanged\n - Strings that contain a sequence of characters that represent a legal JSON number are converted to that number\n - All other values cause an error to be thrown."
},
"$abs": {
"args":"number",
"desc":"Returns the absolute value of the `number` parameter."
},
"$floor": {
"args":"number",
"desc":"Returns the value of `number` rounded down to the nearest integer that is smaller or equal to `number`."
},
"$ceil": {
"args":"number",
"desc":"Returns the value of `number` rounded up to the nearest integer that is greater than or equal to `number`."
},
"$round": {
"args":"number [, precision]",
"desc":"Returns the value of the `number` parameter rounded to the number of decimal places specified by the optional `precision` parameter."
},
"$power": {
"args":"base, exponent",
"desc":"Returns the value of `base` raised to the power of `exponent`."
},
"$sqrt": {
"args":"number",
"desc":"Returns the square root of the value of the `number` parameter."
},
"$random": {
"args":"",
"desc":"Returns a pseudo random number greater than or equal to zero and less than one."
},
"$millis": {
"args":"",
"desc":"Returns the number of milliseconds since the Unix Epoch (1 January, 1970 UTC) as a number. All invocations of `$millis()` within an evaluation of an expression will all return the same value."
},
"$sum": {
"args": "array",
"desc": "Returns the arithmetic sum of an `array` of numbers. It is an error if the input `array` contains an item which isn't a number."
},
"$max": {
"args": "array",
"desc": "Returns the maximum number in an `array` of numbers. It is an error if the input `array` contains an item which isn't a number."
},
"$min": {
"args": "array",
"desc": "Returns the minimum number in an `array` of numbers. It is an error if the input `array` contains an item which isn't a number."
},
"$average": {
"args": "array",
"desc": "Returns the mean value of an `array` of numbers. It is an error if the input `array` contains an item which isn't a number."
},
"$boolean": {
"args": "arg",
"desc": "Casts the argument to a Boolean using the following rules:\n\n - `Boolean` : unchanged\n - `string`: empty : `false`\n - `string`: non-empty : `true`\n - `number`: `0` : `false`\n - `number`: non-zero : `true`\n - `null` : `false`\n - `array`: empty : `false`\n - `array`: contains a member that casts to `true` : `true`\n - `array`: all members cast to `false` : `false`\n - `object`: empty : `false`\n - `object`: non-empty : `true`\n - `function` : `false`"
},
"$not": {
"args": "arg",
"desc": "Returns Boolean NOT on the argument. `arg` is first cast to a boolean"
},
"$exists": {
"args": "arg",
"desc": "Returns Boolean `true` if the `arg` expression evaluates to a value, or `false` if the expression does not match anything (e.g. a path to a non-existent field reference)."
},
"$count": {
"args": "array",
"desc": "Returns the number of items in the array"
},
"$append": {
"args": "array, array",
"desc": "Appends two arrays"
},
"$sort": {
"args":"array [, function]",
"desc":"Returns an array containing all the values in the `array` parameter, but sorted into order.\n\nIf a comparator `function` is supplied, then it must be a function that takes two parameters:\n\n`function(left, right)`\n\nThis function gets invoked by the sorting algorithm to compare two values left and right. If the value of left should be placed after the value of right in the desired sort order, then the function must return Boolean `true` to indicate a swap. Otherwise it must return `false`."
},
"$reverse": {
"args":"array",
"desc":"Returns an array containing all the values from the `array` parameter, but in reverse order."
},
"$shuffle": {
"args":"array",
"desc":"Returns an array containing all the values from the `array` parameter, but shuffled into random order."
},
"$zip": {
"args":"array, ...",
"desc":"Returns a convolved (zipped) array containing grouped arrays of values from the `array1` … `arrayN` arguments from index 0, 1, 2...."
},
"$keys": {
"args": "object",
"desc": "Returns an array containing the keys in the object. If the argument is an array of objects, then the array returned contains a de-duplicated list of all the keys in all of the objects."
},
"$lookup": {
"args": "object, key",
"desc": "Returns the value associated with key in object. If the first argument is an array of objects, then all of the objects in the array are searched, and the values associated with all occurrences of key are returned."
},
"$spread": {
"args": "object",
"desc": "Splits an object containing key/value pairs into an array of objects, each of which has a single key/value pair from the input object. If the parameter is an array of objects, then the resultant array contains an object for every key/value pair in every object in the supplied array."
},
"$merge": {
"args": "array&lt;object&gt;",
"desc": "Merges an array of `objects` into a single `object` containing all the key/value pairs from each of the objects in the input array. If any of the input objects contain the same key, then the returned `object` will contain the value of the last one in the array. It is an error if the input array contains an item that is not an object."
},
"$sift": {
"args":"object, function",
"desc":"Returns an object that contains only the key/value pairs from the `object` parameter that satisfy the predicate `function` passed in as the second parameter.\n\nThe `function` that is supplied as the second parameter must have the following signature:\n\n`function(value [, key [, object]])`"
},
"$each": {
"args":"object, function",
"desc":"Returns an array containing the values return by the `function` when applied to each key/value pair in the `object`."
},
"$map": {
"args":"array, function",
"desc":"Returns an array containing the results of applying the `function` parameter to each value in the `array` parameter.\n\nThe `function` that is supplied as the second parameter must have the following signature:\n\n`function(value [, index [, array]])`"
},
"$filter": {
"args":"array, function",
"desc":"Returns an array containing only the values in the `array` parameter that satisfy the `function` predicate.\n\nThe `function` that is supplied as the second parameter must have the following signature:\n\n`function(value [, index [, array]])`"
},
"$reduce": {
"args":"array, function [, init]",
"desc":"Returns an aggregated value derived from applying the `function` parameter successively to each value in `array` in combination with the result of the previous application of the function.\n\nThe function must accept two arguments, and behaves like an infix operator between each value within the `array`.\n\nThe optional `init` parameter is used as the initial value in the aggregation."
},
"$flowContext": {
"args": "string[, string]",
"desc": "Retrieves a flow context property.\n\nThis is a Node-RED defined function."
},
"$globalContext": {
"args": "string[, string]",
"desc": "Retrieves a global context property.\n\nThis is a Node-RED defined function."
},
"$pad": {
"args": "string, width [, char]",
"desc": "Returns a copy of the `string` with extra padding, if necessary, so that its total number of characters is at least the absolute value of the `width` parameter.\n\nIf `width` is a positive number, then the string is padded to the right; if negative, it is padded to the left.\n\nThe optional `char` argument specifies the padding character(s) to use. If not specified, it defaults to the space character."
},
"$fromMillis": {
"args": "number",
"desc": "Convert a number representing milliseconds since the Unix Epoch (1 January, 1970 UTC) to a timestamp string in the ISO 8601 format."
},
"$formatNumber": {
"args": "number, picture [, options]",
"desc": "Casts the `number` to a string and formats it to a decimal representation as specified by the `picture` string.\n\n The behaviour of this function is consistent with the XPath/XQuery function fn:format-number as defined in the XPath F&O 3.1 specification. The picture string parameter defines how the number is formatted and has the same syntax as fn:format-number.\n\nThe optional third argument `options` is used to override the default locale specific formatting characters such as the decimal separator. If supplied, this argument must be an object containing name/value pairs specified in the decimal format section of the XPath F&O 3.1 specification."
},
"$formatBase": {
"args": "number [, radix]",
"desc": "Casts the `number` to a string and formats it to an integer represented in the number base specified by the `radix` argument. If `radix` is not specified, then it defaults to base 10. `radix` can be between 2 and 36, otherwise an error is thrown."
},
"$toMillis": {
"args": "timestamp",
"desc": "Convert a `timestamp` string in the ISO 8601 format to the number of milliseconds since the Unix Epoch (1 January, 1970 UTC) as a number. An error is thrown if the string is not in the correct format."
},
"$env": {
"args": "arg",
"desc": "Returns the value of an environment variable.\n\nThis is a Node-RED defined function."
}
}

View File

@@ -0,0 +1,848 @@
{
"common": {
"label": {
"name": "名前",
"ok": "OK",
"done": "完了",
"cancel": "中止",
"delete": "削除",
"close": "閉じる",
"load": "読み込み",
"save": "保存",
"import": "読み込み",
"export": "書き出し",
"back": "戻る",
"next": "進む",
"clone": "プロジェクトをクローン",
"cont": "続ける"
}
},
"workspace": {
"defaultName": "フロー __number__",
"editFlow": "フローを編集: __name__",
"confirmDelete": "削除の確認",
"delete": "本当に '__label__' を削除しますか?",
"dropFlowHere": "ここにフローをドロップしてください",
"status": "状態",
"enabled": "有効",
"disabled": "無効",
"info": "詳細",
"tip": "マークダウン形式で記述した「詳細」は「情報タブ」に表示されます。"
},
"menu": {
"label": {
"view": {
"view": "表示",
"grid": "グリッド",
"showGrid": "グリッドを表示",
"snapGrid": "ノードの配置を補助",
"gridSize": "グリッドの大きさ",
"textDir": "テキストの方向",
"defaultDir": "標準",
"ltr": "左から右",
"rtl": "右から左",
"auto": "文脈"
},
"sidebar": {
"show": "サイドバーを表示"
},
"settings": "設定",
"userSettings": "ユーザ設定",
"nodes": "ノード",
"displayStatus": "ノードの状態を表示",
"displayConfig": "ノードの設定",
"import": "読み込み",
"export": "書き出し",
"search": "ノードを検索",
"searchInput": "ノードを検索",
"clipboard": "クリップボード",
"library": "ライブラリ",
"examples": "サンプル",
"subflows": "サブフロー",
"createSubflow": "サブフローを作成",
"selectionToSubflow": "選択部分をサブフロー化",
"flows": "フロー",
"add": "フローを新規追加",
"rename": "フロー名を変更",
"delete": "フローを削除",
"keyboardShortcuts": "ショートカットキーの説明",
"login": "ログイン",
"logout": "ログアウト",
"editPalette": "パレットの管理",
"other": "その他",
"showTips": "ヒントを表示",
"help": "Node-REDウェブサイト",
"projects": "プロジェクト",
"projects-new": "新規",
"projects-open": "開く",
"projects-settings": "設定"
}
},
"actions": {
"toggle-navigator": "ナビゲータの表示/非表示を切替",
"zoom-out": "縮小",
"zoom-reset": "拡大/縮小を初期化",
"zoom-in": "拡大"
},
"user": {
"loggedInAs": "__name__ としてログインしました",
"username": "ユーザ名",
"password": "パスワード",
"login": "ログイン",
"loginFailed": "ログインに失敗しました",
"notAuthorized": "権限がありません",
"errors": {
"settings": "設定を参照するには、ログインする必要があります",
"deploy": "変更をデプロイするには、ログインする必要があります",
"notAuthorized": "本アクションを行うには、ログインする必要があります"
}
},
"notification": {
"warning": "<strong>警告</strong>: __message__",
"warnings": {
"undeployedChanges": "ノードの変更をデプロイしていません",
"nodeActionDisabled": "ノードのアクションは、サブフロー内で無効になっています",
"missing-types": "不明なノードが存在するため、フローを停止しました。詳細はログを確認してください。",
"safe-mode": "<p>セーフモードでフローを停止しました</p><p>フローを変更し、再起動するために変更をデプロイできます</p>",
"restartRequired": "更新されたモジュールを有効化するため、Node-REDを再起動する必要があります",
"credentials_load_failed": "<p>認証情報を復号できないため、フローを停止しました</p><p>フローの認証情報ファイルは暗号化されています。しかし、プロジェクトの暗号鍵が存在しない、または不正です</p>",
"credentials_load_failed_reset": "<p>認証情報を復号できません</p><p>フローの認証情報ファイルは暗号化されています。しかし、プロジェクトの暗号鍵が存在しない、または不正です。</p><p>次回のデプロイでフローの認証情報ファイルがリセットされます。既存フローの認証情報は削除されます。</p>",
"missing_flow_file": "<p>プロジェクトのフローファイルが存在しません</p><p>本プロジェクトにフローファイルが登録されていません</p>",
"missing_package_file": "<p>プロジェクトのパッケージファイルが存在しません</p><p>本プロジェクトにはpackage.jsonファイルがありません</p>",
"project_empty": "<p>空のプロジェクトです</p><p>デフォルトのプロジェクトファイルを作成しますか?<br/>作成しない場合、エディタの外でファイルをプロジェクトへ手動で追加する必要があります</p>",
"project_not_found": "<p>プロジェクト '__project__' が存在しません</p>",
"git_merge_conflict": "<p>変更の自動マージが失敗しました</p><p>マージされていない競合を解決し、コミットしてください</p>"
},
"error": "<strong>エラー</strong>: __message__",
"errors": {
"lostConnection": "サーバとの接続が切断されました: 再接続しています",
"lostConnectionReconnect": "サーバとの接続が切断されました: __time__ 秒後に再接続します",
"lostConnectionTry": "すぐに接続",
"cannotAddSubflowToItself": "サブフロー自身を追加できません",
"cannotAddCircularReference": "循環参照を検出したため、サブフローを追加できません",
"unsupportedVersion": "サポートされていないバージョンのNode.jsを使用しています。<br/>最新のNode.js LTSに更新してください。",
"failedToAppendNode": "<p>'__module__'がロードできませんでした。</p><p>__error__</p>"
},
"project": {
"change-branch": "ローカルブランチ'__project__'に変更しました",
"merge-abort": "Gitマージを中止しました",
"loaded": "プロジェクト'__project__'をロードしました",
"updated": "プロジェクト'__project__'を更新しました",
"pull": "プロジェクト'__project__'を再ロードしました",
"revert": "プロジェクト'__project__'を再ロードしました",
"merge-complete": "Gitマージが完了しました"
},
"label": {
"manage-project-dep": "プロジェクトの依存関係の管理",
"setup-cred": "認証情報の設定",
"setup-project": "プロジェクトファイルの設定",
"create-default-package": "デフォルトパッケージファイルの作成",
"no-thanks": "不要",
"create-default-project": "デフォルトプロジェクトファイルの作成",
"show-merge-conflicts": "マージ競合を表示"
}
},
"clipboard": {
"nodes": "ノード",
"selectNodes": "上のテキストを選択し、クリップボードへコピーしてください",
"pasteNodes": "JSON形式のフローデータを貼り付けてください",
"importNodes": "フローをクリップボートから読み込み",
"exportNodes": "フローをクリップボードへ書き出し",
"importUnrecognised": "認識できない型が読み込まれました:",
"importUnrecognised_plural": "認識できない型が読み込まれました:",
"nodesExported": "クリップボードへフローを書き出しました",
"nodeCopied": "__count__ 個のノードをコピーしました",
"nodeCopied_plural": "__count__ 個のノードをコピーしました",
"invalidFlow": "不正なフロー: __message__",
"export": {
"selected": "選択したフロー",
"current": "現在のタブ",
"all": "全てのタブ",
"compact": "インデントのないJSONフォーマット",
"formatted": "インデント付きのJSONフォーマット",
"copy": "書き出し"
},
"import": {
"import": "読み込み先",
"newFlow": "新規のタブ"
},
"copyMessagePath": "パスをコピーしました",
"copyMessageValue": "値をコピーしました",
"copyMessageValue_truncated": "切り捨てられた値をコピーしました"
},
"deploy": {
"deploy": "デプロイ",
"full": "全て",
"fullDesc": "ワークスペースを全てデプロイ",
"modifiedFlows": "変更したフロー",
"modifiedFlowsDesc": "変更したノードを含むフローのみデプロイ",
"modifiedNodes": "変更したノード",
"modifiedNodesDesc": "変更したノードのみデプロイ",
"restartFlows": "フローを再起動",
"restartFlowsDesc": "デプロイされた現在のフローを再起動",
"successfulDeploy": "デプロイが成功しました",
"successfulRestart": "フローの再起動が成功しました",
"deployFailed": "デプロイが失敗しました: __message__",
"unusedConfigNodes": "使われていない「ノードの設定」があります。",
"unusedConfigNodesLink": "設定を参照する",
"errors": {
"noResponse": "サーバの応答がありません"
},
"confirm": {
"button": {
"ignore": "無視",
"confirm": "デプロイの確認",
"review": "差分を確認",
"cancel": "中止",
"merge": "変更をマージ",
"overwrite": "無視してデプロイ"
},
"undeployedChanges": "デプロイしていない変更があります。このページを抜けると変更が削除されます。",
"improperlyConfigured": "以下のノードは、正しくプロパティが設定されていません:",
"unknown": "ワークスペースに未知の型のノードがあります。",
"confirm": "このままデプロイしても良いですか?",
"doNotWarn": "この警告を再度表示しない",
"conflict": "フローを編集している間に、他のブラウザがフローをデプロイしました。デプロイを継続すると、他のブラウザがデプロイしたフローが削除されます。",
"backgroundUpdate": "サーバ上のフローが更新されました",
"conflictChecking": "変更を自動的にマージしてよいか確認してください。",
"conflictAutoMerge": "変更の衝突がないため、自動的にマージできます。",
"conflictManualMerge": "変更に衝突があるため、デプロイ前に解決する必要があります。",
"plusNMore": "さらに __count__ 個"
}
},
"diff": {
"unresolvedCount": "未解決の衝突 __count__",
"unresolvedCount_plural": "未解決の衝突 __count__",
"globalNodes": "グローバルノード",
"flowProperties": "フロープロパティ",
"type": {
"added": "追加",
"changed": "変更",
"unchanged": "変更なし",
"deleted": "削除",
"flowDeleted": "削除されたフロー",
"flowAdded": "追加されたフロー",
"movedTo": "__id__ へ移動",
"movedFrom": "__id__ から移動"
},
"nodeCount": "__count__ 個のノード",
"nodeCount_plural": "__count__ 個のノード",
"local": "ローカルの変更",
"remote": "リモートの変更",
"reviewChanges": "変更を表示",
"noBinaryFileShowed": "バイナリファイルの中身は表示することができません",
"viewCommitDiff": "コミットの内容を表示",
"compareChanges": "変更を比較",
"saveConflict": "解決して保存",
"conflictHeader": "<span>__unresolved__</span> 個中 <span>__resolved__</span> 個のコンフリクトを解決",
"commonVersionError": "共通バージョンは正しいJSON形式ではありません:",
"oldVersionError": "古いバージョンは正しいJSON形式ではありません:",
"newVersionError": "新しいバージョンは正しいJSON形式ではありません:"
},
"subflow": {
"editSubflow": "フローのテンプレートを編集: __name__",
"edit": "フローのテンプレートを編集",
"subflowInstances": "このサブフローのテンプレートのインスタンスが __count__ 個存在します",
"subflowInstances_plural": "このサブフローのテンプレートのインスタンスが __count__ 個存在します",
"editSubflowProperties": "プロパティを編集",
"input": "入力:",
"output": "出力:",
"deleteSubflow": "サブフローを削除",
"info": "詳細",
"category": "カテゴリ",
"format": "マークダウン形式",
"errors": {
"noNodesSelected": "<strong>サブフローを作成できません</strong>: ノードが選択されていません",
"multipleInputsToSelection": "<strong>サブフローを作成できません</strong>: 複数の入力が選択されています"
}
},
"editor": {
"configEdit": "編集",
"configAdd": "追加",
"configUpdate": "更新",
"configDelete": "削除",
"nodesUse": "__count__ 個のノードが、この設定を使用しています",
"nodesUse_plural": "__count__ 個のノードが、この設定を使用しています",
"addNewConfig": "新規に __type__ ノードの設定を追加",
"editNode": "__type__ ノードを編集",
"editConfig": "__type__ ノードの設定を編集",
"addNewType": "新規に __type__ を追加...",
"nodeProperties": "プロパティ",
"portLabels": "設定",
"labelInputs": "入力",
"labelOutputs": "出力",
"settingIcon": "アイコン",
"noDefaultLabel": "なし",
"defaultLabel": "既定の名前を使用",
"searchIcons": "アイコンを検索",
"useDefault": "デフォルトを使用",
"description": "詳細",
"errors": {
"scopeChange": "スコープの変更は、他のフローで使われているノードを無効にします"
}
},
"keyboard": {
"title": "キーボードショートカット",
"keyboard": "キーボード",
"filterActions": "動作を検索",
"shortcut": "ショートカット",
"scope": "範囲",
"unassigned": "未割当",
"global": "グローバル",
"workspace": "ワークスペース",
"selectAll": "全てのノードを選択",
"selectAllConnected": "接続された全てのノードを選択",
"addRemoveNode": "ノードの選択、選択解除",
"editSelected": "選択したノードを編集",
"deleteSelected": "選択したノードや接続を削除",
"importNode": "フローの読み込み",
"exportNode": "フローの書き出し",
"nudgeNode": "選択したノードを移動(移動量小)",
"moveNode": "選択したノードを移動(移動量大)",
"toggleSidebar": "サイドバーの表示非表示",
"copyNode": "選択したノードをコピー",
"cutNode": "選択したノードを切り取り",
"pasteNode": "ノードを貼り付け",
"undoChange": "変更操作を戻す",
"searchBox": "ノードを検索",
"managePalette": "パレットの管理"
},
"library": {
"openLibrary": "ライブラリを開く",
"saveToLibrary": "ライブラリへ保存",
"typeLibrary": "__type__ ライブラリ",
"unnamedType": "名前なし __type__",
"exportToLibrary": "ライブラリへフローを書き出す",
"dialogSaveOverwrite": "__libraryName__ という __libraryType__ は既に存在しています 上書きしますか?",
"invalidFilename": "不正なファイル名",
"savedNodes": "フローを保存しました",
"savedType": "__type__ を保存しました",
"saveFailed": "保存に失敗しました: __message__",
"filename": "ファイル名",
"folder": "フォルダ",
"filenamePlaceholder": "ファイル",
"fullFilenamePlaceholder": "a/b/file",
"folderPlaceholder": "a/b",
"breadcrumb": "ライブラリ"
},
"palette": {
"noInfo": "情報がありません",
"filter": "ノードを検索",
"search": "ノードを検索",
"addCategory": "新規追加...",
"label": {
"subflows": "サブフロー",
"input": "入力",
"output": "出力",
"function": "機能",
"social": "ソーシャル",
"storage": "ストレージ",
"analysis": "分析",
"advanced": "その他"
},
"actions": {
"collapse-all": "全カテゴリを折畳む",
"expand-all": "全カテゴリを展開"
},
"event": {
"nodeAdded": "ノードをパレットへ追加しました:",
"nodeAdded_plural": "ノードをパレットへ追加しました",
"nodeRemoved": "ノードをパレットから削除しました:",
"nodeRemoved_plural": "ノードをパレットから削除しました:",
"nodeEnabled": "ノードを有効化しました:",
"nodeEnabled_plural": "ノードを有効化しました:",
"nodeDisabled": "ノードを無効化しました:",
"nodeDisabled_plural": "ノードを無効化しました:",
"nodeUpgraded": "ノードモジュール __module__ をバージョン __version__ へ更新しました"
},
"editor": {
"title": "パレットの管理",
"palette": "パレット",
"times": {
"seconds": "秒前",
"minutes": "分前",
"minutesV": "__count__ 分前",
"hoursV": "__count__ 時間前",
"hoursV_plural": "__count__ 時間前",
"daysV": "__count__ 日前",
"daysV_plural": "__count__ 日前",
"weeksV": "__count__ 週間前",
"weeksV_plural": "__count__ 週間前",
"monthsV": "__count__ ヵ月前",
"monthsV_plural": "__count__ ヵ月前",
"yearsV": "__count__ 年前",
"yearsV_plural": "__count__ 年前",
"yearMonthsV": "__y__ 年 __count__ ヵ月前",
"yearMonthsV_plural": "__y__ 年 __count__ ヵ月前",
"yearsMonthsV": "__y__ 年 __count__ ヵ月前",
"yearsMonthsV_plural": "__y__ 年 __count__ ヵ月前"
},
"nodeCount": "__label__ 個のノード",
"nodeCount_plural": "__label__ 個のノード",
"moduleCount": "__count__ 個のモジュール",
"moduleCount_plural": "__count__ 個のモジュール",
"inuse": "使用中",
"enableall": "全て有効化",
"disableall": "全て無効化",
"enable": "有効化",
"disable": "無効化",
"remove": "削除",
"update": "__version__ へ更新",
"updated": "更新済",
"install": "ノードを追加",
"installed": "追加しました",
"loading": "カタログを読み込み中",
"tab-nodes": "現在のノード",
"tab-install": "ノードを追加",
"sort": "並べ替え:",
"sortAZ": "辞書順",
"sortRecent": "日付順",
"more": "+ さらに __count__ 個",
"errors": {
"catalogLoadFailed": "ノードのカタログの読み込みに失敗しました。<br>詳細はブラウザのコンソールを確認してください。",
"installFailed": "追加処理が失敗しました: __module__<br>__message__<br>詳細はログを確認してください。",
"removeFailed": "削除処理が失敗しました: __module__<br>__message__<br>詳細はログを確認してください。",
"updateFailed": "更新処理が失敗しました: __module__<br>__message__<br>詳細はログを確認してください。",
"enableFailed": "有効化処理が失敗しました: __module__<br>__message__<br>詳細はログを確認してください。",
"disableFailed": "無効化処理が失敗しました: __module__<br>__message__<br>詳細はログを確認してください。"
},
"confirm": {
"install": {
"body": "ードを追加する前に、ドキュメントを確認してください。ードによっては、モジュールの依存関係を自動的に解決できない場合や、Node-REDの再起動が必要となる場合があります。",
"title": "ノードを追加"
},
"remove": {
"body": "Node-REDからードを削除します。ードはNode-REDが再起動されるまで、リソースを使い続けます。",
"title": "ノードを削除"
},
"update": {
"body": "更新を完了するには手動でNode-REDを再起動する必要があります。",
"title": "ノードの更新"
},
"cannotUpdate": {
"body": "ノードの更新があります。「パレットの管理」の画面では更新されません。ドキュメントを参照し、ノードの更新手順を確認してください。"
},
"button": {
"review": "ノードの情報を参照",
"install": "追加",
"remove": "削除",
"update": "更新"
}
}
}
},
"sidebar": {
"info": {
"name": "ノードの情報を表示",
"tabName": "名前",
"label": "情報",
"node": "ノード",
"type": "型",
"id": "ID",
"status": "状態",
"enabled": "有効",
"disabled": "無効",
"subflow": "サブフロー",
"instances": "インスタンス",
"properties": "プロパティ",
"info": "情報",
"desc": "詳細",
"blank": "ブランク",
"null": "ヌル",
"showMore": "さらに表示",
"showLess": "表示を省略",
"flow": "フロー",
"selection": "選択",
"nodes": "__count__ 個のノード",
"flowDesc": "フローの詳細",
"subflowDesc": "サブフローの詳細",
"nodeHelp": "ノードのヘルプ",
"none": "なし",
"arrayItems": "__count__ 要素",
"showTips": "設定からヒントを表示できます"
},
"config": {
"name": "ノードの設定を表示",
"label": "ノードの設定",
"global": "全てのフロー上",
"none": "なし",
"subflows": "サブフロー",
"flows": "フロー",
"filterUnused": "未使用",
"filterAll": "全て",
"filtered": "__count__ 個が無効"
},
"context": {
"name": "コンテキストデータ",
"label": "コンテキストデータ",
"none": "選択されていません",
"refresh": "読み込みのため更新してください",
"empty": "データが存在しません",
"node": "Node",
"flow": "Flow",
"global": "Global"
},
"palette": {
"name": "パレットの管理",
"label": "パレット"
},
"project": {
"label": "プロジェクト",
"name": "プロジェクト",
"description": "詳細",
"dependencies": "依存関係",
"settings": "設定",
"noSummaryAvailable": "サマリが存在しません",
"editDescription": "プロジェクトの詳細を編集",
"editDependencies": "プロジェクトの依存関係を編集",
"editReadme": "README.mdを編集",
"showProjectSettings": "プロジェクト設定を表示",
"projectSettings": {
"title": "プロジェクト設定",
"edit": "編集",
"none": "なし",
"install": "インストール",
"removeFromProject": "プロジェクトから削除",
"addToProject": "プロジェクトへ追加",
"files": "ファイル",
"flow": "フロー",
"credentials": "認証情報",
"invalidEncryptionKey": "不正な暗号化キー",
"encryptionEnabled": "暗号化が有効になっています",
"encryptionDisabled": "暗号化が無効になっています",
"setTheEncryptionKey": "暗号化キーを設定:",
"resetTheEncryptionKey": "暗号化キーを初期化:",
"changeTheEncryptionKey": "暗号化キーを変更:",
"currentKey": "現在のキー",
"newKey": "新規のキー",
"credentialsAlert": "既存の認証情報は全て削除されます",
"versionControl": "バージョン管理",
"branches": "ブランチ",
"noBranches": "ブランチなし",
"deleteConfirm": "本当にローカルブランチ'__name__'を削除しますか?削除すると元に戻すことはできません。",
"unmergedConfirm": "ローカルブランチ'__name__'にはマージされていない変更があります。この変更は削除されます。本当に削除しますか?",
"deleteUnmergedBranch": "マージされていないブランチを削除",
"gitRemotes": "Gitリモート",
"addRemote": "リモートを追加",
"addRemote2": "リモートを追加",
"remoteName": "リモート名",
"nameRule": "A-Z 0-9 _ - のみを含む",
"url": "URL",
"urlRule": "https://、ssh:// または file://",
"urlRule2": "URLにユーザ名、パスワードを含んではいけません",
"noRemotes": "リモートなし",
"deleteRemoteConfrim": "本当にリモート'__name__'を削除しますか?",
"deleteRemote": "リモートを削除"
},
"userSettings": {
"committerDetail": "コミッター詳細",
"committerTip": "システムのデフォルトを使用する場合、空白のままにしてください",
"userName": "ユーザ名",
"email": "メールアドレス",
"sshKeys": "SSH キー",
"sshKeysTip": "gitリポジトリへのセキュアな接続を作成できます。",
"add": "キーを追加",
"addSshKey": "SSHキーを追加",
"addSshKeyTip": "新しい公開鍵/秘密鍵ペアを生成します",
"name": "名前",
"nameRule": "A-Z 0-9 _ - のみを含む",
"passphrase": "パスフレーズ",
"passphraseShort": "パスフレーズが短すぎます",
"optional": "任意",
"cancel": "中止",
"generate": "キーを生成",
"noSshKeys": "SSHキーがありません",
"copyPublicKey": "公開鍵をクリップボードにコピー",
"delete": "キーを削除",
"gitConfig": "Git設定",
"deleteConfirm": "SSHキー __name__ を削除してもよいですか? 削除したSSHキーを元に戻すことはできません。"
},
"versionControl": {
"unstagedChanges": "ステージングされていない変更",
"stagedChanges": "ステージングされた変更",
"resolveConflicts": "コンフリクトの解決",
"head": "最新",
"staged": "ステージング",
"unstaged": "未ステージング",
"local": "ローカル",
"remote": "リモート",
"revert": "'__file__'への変更を本当に戻しますか?この操作は元に戻せません。",
"revertChanges": "変更を戻す",
"localChanges": "ローカルの変更",
"none": "なし",
"conflictResolve": "全てのコンフリクトが解消されました。マージを完了するため、変更をコミットしてください。",
"localFiles": "ローカルファイル",
"all": "全て",
"unmergedChanges": "マージされていない変更",
"abortMerge": "マージ中止",
"commit": "コミット",
"changeToCommit": "コミット対象とする変更",
"commitPlaceholder": "コミットメッセージを入力してください。",
"cancelCapital": "キャンセル",
"commitCapital": "コミット",
"commitHistory": "コミット履歴",
"branch": "ブランチ:",
"moreCommits": "個のコミット",
"changeLocalBranch": "ローカルブランチの変更",
"createBranchPlaceholder": "ブランチの検索または作成",
"upstream": "アップストリーム",
"localOverwrite": "ブランチの変更によって上書きされたローカルの変更があります。これらの変更を先にコミットするか、あるいは元に戻さなければなりません。",
"manageRemoteBranch": "リモートブランチの管理",
"unableToAccess": "リモートのリポジトリにアクセスできません。",
"retry": "リトライ",
"setUpstreamBranch": "アップストリームとして設定する",
"createRemoteBranchPlaceholder": "リモートブランチの検索または作成",
"trackedUpstreamBranch": "作成されたブランチは、トラッキングされたアップストリームブランチとなります。",
"selectUpstreamBranch": "ブランチが作成されました。トラッキングするアップストリームブランチを選択してください。",
"pushFailed": "リモートに新しいコミットがあるため、プッシュに失敗しました。プルしてマージしてから、再度プッシュしてください。",
"push": "プッシュ",
"pull": "プル",
"unablePull": "<p>リモートの変更のプル失敗:ステージングされていないローカルの変更を上書きされてしまいます。</p><p>変更をコミットしてから再度実行してください。</p>",
"showUnstagedChanges": "ステージングされていない変更を表示",
"connectionFailed": "リモートリポジトリに接続できません: ",
"pullUnrelatedHistory": "<p>リモートに関連のないコミット履歴があります。</p><p>本当に変更をプルしてローカルリポジトリに反映しますか?</p>",
"pullChanges": "プル変更",
"history": "履歴",
"projectHistory": "プロジェクト履歴",
"daysAgo": "__count__ 日前",
"daysAgo_plural": "__count__ 日前",
"hoursAgo": "__count__ 時間前",
"hoursAgo_plural": "__count__ 時間前",
"minsAgo": "__count__ 分前",
"minsAgo_plural": "__count__ 分前",
"secondsAgo": "数秒前",
"notTracking": "ローカルブランチは現在リモートブランチをトラッキングしていません。",
"statusUnmergedChanged": "リポジトリ内にマージされていない変更があります。コンフリクトを解決してコミットしてください。",
"repositoryUpToDate": "リポジトリは最新です。",
"commitsAhead": "あなたのリポジトリはリモートより__count__コミット進んでいます。現在のコミットをプッシュできます。",
"commitsAhead_plural": "あなたのリポジトリはリモートより__count__コミット進んでいます。現在のコミットをプッシュできます。",
"commitsBehind": "あなたのリポジトリはリモートより__count__コミット遅れています。現在のコミットをプルできます。",
"commitsBehind_plural": "あなたのリポジトリはリモートより__count__コミット遅れています。現在のコミットをプルできます。",
"commitsAheadAndBehind1": "あなたのリポジトリはリモートより__count__コミット遅れており、かつ",
"commitsAheadAndBehind1_plural": "あなたのリポジトリはリモートより__count__コミット遅れており、かつ",
"commitsAheadAndBehind2": "__count__コミット進んでいます。 ",
"commitsAheadAndBehind2_plural": "__count__コミット進んでいます。 ",
"commitsAheadAndBehind3": "プッシュする前にリモートのコミットをプルしてください。",
"commitsAheadAndBehind3_plural": "プッシュする前にリモートのコミットをプルしてください。"
}
}
},
"typedInput": {
"type": {
"str": "文字列",
"num": "数値",
"re": "正規表現",
"bool": "真偽",
"json": "JSON",
"bin": "バッファ",
"date": "日時",
"jsonata": "JSONata式",
"env": "環境変数"
}
},
"editableList": {
"add": "追加"
},
"search": {
"empty": "一致したものが見つかりませんでした",
"addNode": "ノードを追加..."
},
"expressionEditor": {
"functions": "関数",
"functionReference": "関数リファレンス",
"insert": "挿入",
"title": "JSONata式エディタ",
"test": "テスト",
"data": "メッセージ例",
"result": "結果",
"format": "整形",
"compatMode": "互換モードが有効になっています",
"compatModeDesc": "<h3>JSONata互換モード</h3><p> 入力された式では <code>msg</code> を参照しているため、互換モードで評価します。このモードは将来廃止予定のため、式で <code>msg</code> を使わないよう修正してください。</p><p> JSONataをNode-REDで最初にサポートした際には、 <code>msg</code> オブジェクトの参照が必要でした。例えば <code>msg.payload</code> がペイロードを参照するために使われていました。</p><p> 直接メッセージに対して式を評価するようになったため、この形式は使えなくなります。ペイロードを参照するには、単に <code>payload</code> にしてください。</p>",
"noMatch": "一致した結果なし",
"errors": {
"invalid-expr": "不正なJSONata式:\n __message__",
"invalid-msg": "不正なJSONメッセージ例:\n __message__",
"context-unsupported": "$flowContext や $globalContextの\nコンテキスト機能をテストできません",
"eval": "表現評価エラー:\n __message__"
}
},
"jsEditor": {
"title": "JavaScriptエディタ"
},
"jsonEditor": {
"title": "JSONエディタ",
"format": "JSONフォーマット"
},
"markdownEditor": {
"title": "マークダウンエディタ"
},
"bufferEditor": {
"title": "バッファエディタ",
"modeString": "UTF-8文字列として処理",
"modeArray": "JSON配列として処理",
"modeDesc": "<h3>バッファエディタ</h3><p>バッファ型は、バイト値から成るJSON配列として格納されます。このエディタは、入力値をJSON配列として構文解析します。もし不正なJSON配列の場合、UTF-8文字列として扱い、各文字コード番号から成る配列へ変換します。</p><p>例えば、 <code>Hello World</code> という値を、以下のJSON配列へ変換します。<pre>[72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100]</pre></p>"
},
"projects": {
"config-git": "Gitクライアントの設定",
"welcome": {
"hello": "こんにちは! Node-REDで「プロジェクト」機能が利用できるようになりました。",
"desc0": "フローファイルの管理方法が刷新され、バージョン管理も可能です。",
"desc1": "まず最初にプロジェクトを作成するか、既存のGitリポジトリからプロジェクトをクローンしてください。",
"desc2": "とりあえずこの処理をスキップしてもかまいません。「プロジェクト」メニューから、いつでもプロジェクトの作成を開始できます。",
"create": "プロジェクトの作成",
"clone": "プロジェクトのクローン",
"not-right-now": "後にする"
},
"git-config": {
"setup": "バージョン管理クライアントの設定",
"desc0": "Node-REDはオープンソースツールのGitを使ってバージョン管理を行います。Gitによりプロジェクトファイルに対する変化を記録し、外部リポジトリに保存することができます。",
"desc1": "変更をコミットする際、変更を行った人物の情報としてユーザ名とEmailアドレスをGitが記憶します。ユーザ名は本名でなくても構いません。好きな名前を使ってください。",
"desc2": "Gitクライアントの現在の設定は以下の通りです。",
"desc3": "設定ダイアログの「Git設定」タブから別途変更することもできます。",
"username": "ユーザ名",
"email": "Email"
},
"project-details": {
"create": "プロジェクトの作成",
"desc0": "プロジェクトはGitリポジトリとして管理します。Gitリポジトリを使ってフローの共有やコラボレーションが簡単にできます。",
"desc1": "複数のプロジェクトを作成し、エディタから即座に変更できます。",
"desc2": "まず、プロジェクト名と説明(任意)を指定してください。",
"already-exists": "既に存在するプロジェクトです",
"must-contain": "A-Z 0-9 _ - のみ指定可能",
"project-name": "プロジェクト名",
"desc": "説明",
"opt": "任意"
},
"clone-project": {
"clone": "プロジェクトをクローン",
"desc0": "プロジェクトを含んだGitリポジトリを作成済みの場合、クローンを作成することができます。",
"already-exists": "既に存在するプロジェクトです",
"must-contain": "A-Z 0-9 _ - のみ指定可能",
"project-name": "プロジェクト名",
"no-info-in-url": "URLにユーザ名/パスワードを含めないようにしてください",
"git-url": "GitリポジトリのURL",
"protocols": "https://, ssh:// もしくは file://",
"auth-failed": "認証に失敗しました",
"username": "ユーザ名",
"passwd": "パスワード",
"ssh-key": "SSHキー",
"passphrase": "パスフレーズ",
"ssh-key-desc": "SSHでリポジトリをクローンする前にSSHキーを追加してください。",
"ssh-key-add": "SSHキーの追加",
"credential-key": "認証情報の暗号化キー",
"cant-get-ssh-key": "エラー! 選択したSSHキーのパスを取得できません。",
"already-exists2": "既に存在します",
"git-error": "Gitエラー",
"connection-failed": "接続に失敗しました",
"not-git-repo": "Gitリポジトリではありません",
"repo-not-found": "リポジトリが見つかりません"
},
"default-files": {
"create": "プロジェクト関連ファイルの作成",
"desc0": "プロジェクトはフローファイル、README、package.jsonを含みます。",
"desc1": "その他、Gitリポジトリで管理したいファイルを含めても構いません。",
"desc2": "既存のフローと認証情報ファイルをプロジェクトにコピーします。",
"flow-file": "フローファイル",
"credentials-file": "認証情報ファイル"
},
"encryption-config": {
"setup": "認証情報ファイルの暗号化設定",
"desc0": "フロー認証情報ファイルを暗号化して内容の安全性を担保できます。",
"desc1": "認証情報を公開Gitリポジトリに保存する際には、秘密キーフレーズによって暗号化します。",
"desc2": "認証情報ファイルは暗号化されていません。",
"desc3": "パスワードやアクセストークンといった認証情報を他人が参照できます。",
"desc4": "認証情報を公開Gitリポジトリに保存する際には、秘密キーフレーズによって暗号化します。",
"desc5": "フロー認証情報ファイルはsettingsファイルのcredentialSecretプロパティで暗号化されています。",
"desc6": "フロー認証情報ファイルはシステムが生成したキーによって暗号化されています。このプロジェクト用に新しい秘密キーを指定してください。",
"desc7": "キーはプロジェクトファイルとは別に保存されます。他のNode-REDでこのプロジェクトを利用するには、このプロジェクトのキーが必要です。",
"credentials": "認証情報",
"enable": "暗号化を有効にする",
"disable": "暗号化を無効にする",
"disabled": "無効",
"copy": "既存のキーをコピー",
"use-custom": "カスタムキーを使用",
"desc8": "認証情報ファイルが暗号化されないため、簡単に読み出すことができます。",
"create-project-files": "プロジェクト関連ファイル作成",
"create-project": "プロジェクト作成",
"already-exists": "既に存在",
"git-error": "Gitエラー",
"git-auth-error": "Git認証エラー"
},
"create-success": {
"success": "最初のプロジェクトの作成が成功しました!",
"desc0": "以降は、これまでと同様にNode-REDを利用できます。",
"desc1": "サイドバーの「情報」タブに現在選択されたプロジェクトを表示します。プロジェクト名の隣のボタンでプロジェクト設定画面を呼び出すことができます。",
"desc2": "サイドバーの「履歴」タブで変更が加えられたプロジェクト内のファイルを確認しコミットできます。このサイドバーでは、全てのコミット履歴を確認し、変更を外部リポジトリにプッシュすることが可能です。"
},
"create": {
"projects": "プロジェクト",
"already-exists": "プロジェクトは既に存在します",
"must-contain": "A-Z 0-9 _ - のみ指定可能",
"no-info-in-url": "URLにユーザ名/パスワードを含めないようにしてください",
"open": "プロジェクトを開く",
"create": "プロジェクトを作成",
"clone": "プロジェクトをクローン",
"project-name": "プロジェクト名",
"desc": "説明",
"opt": "任意",
"flow-file": "フローファイル",
"credentials": "認証情報",
"enable-encryption": "暗号化を有効にする",
"disable-encryption": "暗号化を無効にする",
"encryption-key": "暗号化キー",
"desc0": "認証情報をセキュアにするためのフレーズ",
"desc1": "認証情報ファイルが暗号化されないため、簡単に読み出すことができます",
"git-url": "GitリポジトリのURL",
"protocols": "https://, ssh:// もしくは file://",
"auth-failed": "認証に失敗しました",
"username": "ユーザ名",
"password": "パスワード",
"ssh-key": "SSHキー",
"passphrase": "パスフレーズ",
"desc2": "SSHでリポジトリをクローンする前にSSHキーを追加してください。",
"add-ssh-key": "SSHキーの追加",
"credentials-encryption-key": "認証情報の暗号化キー",
"already-exists-2": "既に存在します",
"git-error": "Gitエラー",
"con-failed": "接続に失敗しました",
"not-git": "Gitリポジトリではありません",
"no-resource": "リポジトリが見つかりません",
"cant-get-ssh-key-path": "エラー! 選択したSSHキーのパスを取得できません。",
"unexpected_error": "予期しないエラー"
},
"delete": {
"confirm": "プロジェクトを削除しても良いですか?"
},
"create-project-list": {
"search": "プロジェクトを検索",
"current": "有効"
},
"require-clean": {
"confirm": "<p>デプロイされていない変更は失われます。</p><p>続けますか?</p>"
},
"send-req": {
"auth-req": "リポジトリ対する認証が必要です",
"username": "ユーザ名",
"password": "パスワード",
"passphrase": "パスフレーズ",
"retry": "リトライ",
"update-failed": "認証の更新に失敗しました",
"unhandled": "エラー応答が処理されませんでした"
},
"create-branch-list": {
"invalid": "不正なブランチ",
"create": "ブランチの作成",
"current": "有効"
},
"create-default-file-set": {
"no-active": "有効なプロジェクトが無い場合、デフォルトのファイル群を作成できません。",
"no-empty": "デフォルトのファイル群を空でないプロジェクトに作成することはできません。",
"git-error": "Gitエラー"
},
"errors": {
"no-username-email": "Gitクライアントのユーザ名/emailが設定されていません。",
"unexpected": "予期しないエラーが発生しました",
"code": "コード"
}
}
}

View File

@@ -0,0 +1,23 @@
{
"info": {
"tip0": "選択したノードや接続を {{core:delete-selection}} により、削除できます。",
"tip1": "{{core:search}} で、フロー内のノードを検索できます。",
"tip2": "{{core:toggle-sidebar}} で、サイドバーの表示/非表示の切り替えができます。",
"tip3": "{{core:manage-palette}} で「パレットの管理」が表示されます。",
"tip4": "フロー内の「ノードの設定」は、サイドバーに一覧表示できます。メニューから呼び出すか {{core:show-config-tab}} を入力してください。",
"tip5": "設定により、ヒントの表示/非表示を変更できます。",
"tip6": "[left] [up] [down] [right] で選択したノードを移動できます。[shift] を押すと移動量が大きくなります。",
"tip7": "ノードを接続の上へドラッグすると、接続内にノードを挿入できます。",
"tip8": "選択したードやタブ内のフローをJSONデータとして書き出すには {{core:show-export-dialog}} を押してください。",
"tip9": "フローデータが入ったJSONファイルをエディタへドラッグ、または {{core:show-import-dialog}} により、フローを読み込むことができます。",
"tip10": "ノードの端子に複数の接続がある時、[shift] を押しながら [click] しドラッグすることで、複数の接続をまとめて他のノードの端子へ移動できます。",
"tip11": "{{core:show-info-tab}} で「情報」タブを表示します。 {{core:show-debug-tab}} で「デバッグ」タブを表示します。",
"tip12": "[ctrl] を押しながらワークスペースを [click] すると、ノードのダイアログが表示され、素早くノードを追加できます。",
"tip13": "[ctrl] を押しながらノードの端子や後続のノードを [click] すると、複数のノードを素早く接続できます。",
"tip14": "[shift] を押しながらノードを [click] すると、接続された全てのノードを選択できます。",
"tip15": "[ctrl] を押しながらノードを [click] すると、選択/非選択を切り替えできます。",
"tip16": "{{core:show-previous-tab}} や {{core:show-next-tab}} で、タブの切り替えができます。",
"tip17": "ノードのプロバティ設定画面にて {{core:confirm-edit-tray}} を押すと、変更を確定できます。また、 {{core:cancel-edit-tray}} を押すと、変更を取り消せます。",
"tip18": "ノードを選択し、 {{core:edit-selected-node}} を押すとプロパティ設定画面が表示されます。"
}
}

View File

@@ -0,0 +1,222 @@
{
"$string": {
"args": "arg",
"desc": "以下の型変換ルールを用いて、引数 *arg* を文字列へ型変換します。:\n\n - 文字列は変換しません。\n - 関数は空の文字列に変換します。\n - JSONの数値として表現できないため、無限大やNaNはエラーになります。\n - 他の値は `JSON.stringify` 関数を用いて、JSONの文字列へ変換します。"
},
"$length": {
"args": "str",
"desc": "文字列 `str` の文字数を返します。 `str` が文字列でない場合、エラーを返します。"
},
"$substring": {
"args": "str, start[, length]",
"desc": "位置 `start` (ゼロオフセット)から開始する引数 `str` の文字列を返します。 `length` を指定した場合、部分文字列は最大 `length` 文字を持ちます。 `start` が負の値の場合、その値は `str` の末尾からの文字数を指します。"
},
"$substringBefore": {
"args": "str, chars",
"desc": "`str` 内で先頭に存在する文字列 `chars` より前の部分文字列を返します。 `str` が `chars` を持たない場合、 `str` を返します。"
},
"$substringAfter": {
"args": "str, chars",
"desc": "`str` 内で先頭に存在する文字列 `chars` より後ろの部分文字列を返します。 `str` が `chars` を持たない場合、 `str` を返します。"
},
"$uppercase": {
"args": "str",
"desc": "`str` の全ての文字を大文字にした文字列を返します。"
},
"$lowercase": {
"args": "str",
"desc": "`str` の全ての文字を小文字にした文字列を返します。"
},
"$trim": {
"args": "str",
"desc": "以下のステップを適用して `str` 内の全ての空白文字を取り除き、正規化します。\n\n - 全てのタブ、キャリッジリターン、ラインフィードを空白に置き換える。\n- 連続する空白を1つの空白に減らす。\n- 末尾と先頭の空白を削除する。\n\n `str` を指定しない場合(例: 本関数を引数なしで呼び出す)、コンテキスト値を `str` の値として使用します。 `str` が文字列でない場合、エラーになります。"
},
"$contains": {
"args": "str, pattern",
"desc": "`str` が `pattern` とマッチした場合は `true` 、マッチしない場合は `false` を返します。 `str` を指定しない場合(例: 本関数を1つの引数で呼び出す)、コンテキスト値を `str` の値として使用します。引数 `pattern` は文字列や正規表現とすることができます。"
},
"$split": {
"args": "str[, separator][, limit]",
"desc": "引数 `str` を分割し、部分文字列の配列にします。 `str` が文字列でない場合、エラーになります。省略可能な引数 `separator` には `str` を分割する文字を文字列または正規表現で指定します。 `separator` を指定しない場合、空の文字列と見なし、 `str` は1文字ずつから成る配列に分割します。 `separator` が文字列でない場合、エラーになります。省略可能な引数 `limit` には、結果の配列が持つ部分文字列の最大数を指定します。この数を超える部分文字列は破棄されます。 `limit` を指定しない場合、 `str` は結果の配列のサイズに上限なく完全に分割されます。 `limit` が負の値の場合、エラーになります。"
},
"$join": {
"args": "array[, separator]",
"desc": "文字列の配列を、省略可能な引数 `separator` で区切った1つの文字列へ連結します。配列 `array` が文字列でない要素を含む場合、エラーになります。 `separator` を指定しない場合、空の文字列と見なします(例: 文字列間の `separator` なし)。 `separator` が文字列でない場合、エラーになります。"
},
"$match": {
"args": "str, pattern [, limit]",
"desc": "文字列 `str` に対して正規表現 `pattern` を適用し、オブジェクトの配列を返します。配列要素のオブジェクトは `str` のうちマッチした部分の情報を保持します。"
},
"$replace": {
"args": "str, pattern, replacement [, limit]",
"desc": "文字列 `str` からパターン `pattern` を検索し、置換文字列 `replacement` に置き換えます。\n\n任意の引数 `limit` には、置換回数の上限値を指定します。"
},
"$now": {
"args": "",
"desc": "ISO 8601互換形式の時刻を生成し、文字列として返します。"
},
"$base64encode": {
"args": "string",
"desc": "ASCII形式の文字列をBase 64形式へ変換します。文字列内の各文字は、バイナリデータのバイト値として扱われます。文字列内の文字は、URIエンコードした文字列も含め、0x00から0xFFの範囲である必要があります。範囲外のユニコードの文字はサポートされません。"
},
"$base64decode": {
"args": "string",
"desc": "UTF-8のコードページを用いて、Base 64形式のバイト値を文字列に変換します。"
},
"$number": {
"args": "arg",
"desc": "以下の型変換ルールを用いて、引数 `arg` を数値へ変換します。:\n\n - 数値は、変換しません。\n - 正しいJSONの数値を表す文字列は、数値に変換します。\n - 他の形式の値は、エラーになります。"
},
"$abs": {
"args": "number",
"desc": "引数 `number` の絶対値を返します。"
},
"$floor": {
"args": "number",
"desc": "`number` の値を `number` 以下の最も近い整数値へ切り捨てた値を返します。"
},
"$ceil": {
"args": "number",
"desc": "`number` の値を `number` 以上の最も近い整数値へ切り上げた値を返します。"
},
"$round": {
"args": "number [, precision]",
"desc": "引数 `number` の値を四捨五入した値を返します。任意の引数 `precision` には、四捨五入で用いる小数点以下の桁数を指定します。"
},
"$power": {
"args": "base, exponent",
"desc": "基数 `base` を指数 `exponent` 分、累乗した値を返します。"
},
"$sqrt": {
"args": "number",
"desc": "引数 `number` の平方根を返します。"
},
"$random": {
"args": "",
"desc": "0以上、1未満の疑似乱数を返します。"
},
"$millis": {
"args": "",
"desc": "Unixエポック(1 January, 1970 UTC)からの経過ミリ秒を数値として返します。評価対象式に含まれる `$millis()` の呼び出しは、全て同じ値を返します。"
},
"$sum": {
"args": "array",
"desc": "数値の配列 `array` の合計値を返します。 `array` が数値でない要素を含む場合、エラーになります。"
},
"$max": {
"args": "array",
"desc": "数値の配列 `array` 内の最大値を返します。 `array` が数値でない要素を含む場合、エラーになります。"
},
"$min": {
"args": "array",
"desc": "数値の配列 `array` 内の最小値を返します。 `array` が数値でない要素を含む場合、エラーになります。"
},
"$average": {
"args": "array",
"desc": "数値の配列 `array` の平均値を返します。 `array` が数値でない要素を含む場合、エラーになります。"
},
"$boolean": {
"args": "arg",
"desc": "以下のルールを用いて、ブーリアン型へ型変換します。:\n\n - `Boolean` : 変換しない\n - `string`: 空 : `false`\n - `string`: 空でない : `true`\n - `number`: `0` : `false`\n - `number`: 0でない : `true`\n - `null` : `false`\n - `array`: 空 : `false`\n - `array`: `true` に型変換された要素を持つ: `true`\n - `array`: 全ての要素が `false` に型変換: `false`\n - `object`: 空 : `false`\n - `object`: 空でない : `true`\n - `function` : `false`"
},
"$not": {
"args": "arg",
"desc": "引数の否定をブーリアン型で返します。 `arg` は最初にブーリアン型に型変換されます。"
},
"$exists": {
"args": "arg",
"desc": "`arg` の式の評価値が存在する場合は `true` 、式の評価結果が未定義の場合(例: 存在しない参照フィールドへのパス)は `false` を返します。"
},
"$count": {
"args": "array",
"desc": "配列の要素数を返します。"
},
"$append": {
"args": "array, array",
"desc": "2つの配列を連結します。"
},
"$sort": {
"args": "array [, function]",
"desc": "配列 `array` 内の値を並び変えた配列を返します。\n\n比較関数 `function` を用いる場合、比較関数は以下のとおり2つの引数を持つ必要があります。\n\n`function(left, right)`\n\n比較関数は、leftとrightの2つの値を比較するために、値を並び替える処理で呼び出されます。もし、求められる並び順にてleftの値をrightの値より後ろに置きたい場合は、比較関数は置き換えを表すブーリアン型の `true` を返す必要があります。一方、置き換えが不要の場合は `false` を返す必要があります。"
},
"$reverse": {
"args": "array",
"desc": "配列 `array` の値を、逆順にした配列を返します。"
},
"$shuffle": {
"args": "array",
"desc": "配列 `array` の値を、ランダムな順番にした配列を返します。"
},
"$zip": {
"args": "array, ...",
"desc": "配列 `array1` … `arrayN` の位置 0, 1, 2.... の値を要素とする配列から成る配列を返します。"
},
"$keys": {
"args": "object",
"desc": "オブジェクト内のキーを含む配列を返します。引数がオブジェクトの配列の場合、返す配列は全オブジェクトの全キーの重複の無いリストとなります。"
},
"$lookup": {
"args": "object, key",
"desc": "オブジェクト内のキーが持つ値を返します。最初の引数がオブジェクトの配列の場合、配列内の全てのオブジェクトを検索し、存在する全てのキーが持つ値を返します。"
},
"$spread": {
"args": "object",
"desc": "key/valueのペアを持つオブジェクトを、各要素が1つのkey/valueのペアを持つオブジェクトの配列に分割します。引数がオブジェクトの配列の場合、結果の配列は各オブジェクトから得た各key/valueのペアのオブジェクトを持ちます。"
},
"$merge": {
"args": "array&lt;object&gt;",
"desc": "`object` の配列を1つの `object` へマージします。 マージ結果のオブジェクトは入力配列内の各オブジェクトのkey/valueペアを含みます。入力のオブジェクトが同じキーを持つ場合、戻り値の `object` には配列の最後のオブジェクトのkey/value値が格納されます。入力の配列がオブジェクトでない要素を含む場合、エラーとなります。"
},
"$sift": {
"args": "object, function",
"desc": "引数 `object` が持つkey/valueのペアのうち、関数 `function` によってふるい分けたオブジェクトのみを返します。\n\n関数 `function` は、以下の引数を持つ必要があります。\n\n`function(value [, key [, object]])`"
},
"$each": {
"args": "object, function",
"desc": "`object` の各key/valueのペアに対して、関数 `function` を適用し、その返却値から成る配列を返します。"
},
"$map": {
"args": "array, function",
"desc": "配列 `array` 内の各値に対して、関数 `function` を適用した結果から成る配列を返します。\n\n関数 `function` は、以下の引数を持つ必要があります。\n\n`function(value [, index [, array]])`"
},
"$filter": {
"args": "array, function",
"desc": "配列 `array` 内の値のうち、関数 `function` の条件を満たす値のみを持つ配列を返します。\n\n関数 `function` は、以下の引数を持つ必要があります。\n\n`function(value [, index [, array]])`"
},
"$reduce": {
"args": "array, function [, init]",
"desc": "配列の各要素値に関数 `function` を連続的に適用して得られる集約値を返します。 `function` の適用の際には、直前の `function` の適用結果と要素値が引数として与えられます。\n\n関数 `function` は引数を2つ取り、配列の各要素の間に配置する中置演算子のように作用しなくてはなりません。\n\n任意の引数 `init` には、集約時の初期値を設定します。"
},
"$flowContext": {
"args": "string",
"desc": "フローコンテキストのプロパティを取得します。"
},
"$globalContext": {
"args": "string",
"desc": "グローバルコンテキストのプロパティを取得します。"
},
"$pad": {
"args": "string, width [, char]",
"desc": "文字数が引数 `width` の絶対値以上となるよう、必要に応じて追加文字を付け足した `string` のコピーを返します。\n\n`width` が正の値の場合、文字列の右側に追加文字を付け足します。もし負の値の場合、文字列の左側に追加文字を付け足します。\n\n任意の引数 `char` には、本関数で用いる追加文字を指定します。もし追加文字を指定しない場合は、既定値として空白文字を使用します。"
},
"$fromMillis": {
"args": "number",
"desc": "Unixエポック(1 January, 1970 UTC)からの経過ミリ秒を表す数値を、ISO 8601形式のタイムスタンプの文字列に変換します。"
},
"$formatNumber": {
"args": "number, picture [, options]",
"desc": "`number` を文字列へ変換し、文字列 `picture` に指定した数値表現になるよう書式を変更します。\n\nこの関数の動作は、XPath F&O 3.1の仕様に定義されているXPath/XQuery関数のfn:format-numberの動作と同じです。引数の文字列 picture は、fn:format-numberと同じ構文で数値の書式を定義します。\n\n任意の第三引数 `options` は、小数点記号の様な既定のロケール固有の書式設定文字を上書きするために使用します。この引数を指定する場合、XPath F&O 3.1の仕様の数値形式の項に記述されているname/valueペアを含むオブジェクトでなければなりません。"
},
"$formatBase": {
"args": "number [, radix]",
"desc": "`number` を引数 `radix` に指定した値を基数とした形式の文字列に変換します。 `radix` が指定されていない場合、基数10を既定値として設定します。 `radix` には236の値を設定でき、それ以外の値の場合はエラーになります。"
},
"$toMillis": {
"args": "timestamp",
"desc": "ISO 8601形式の文字列 `timestamp` を、Unixエポック(1 January, 1970 UTC)からの経過ミリ秒を表す数値へ変換します。 文字列が正しい形式でない場合、エラーとなります。"
},
"$env": {
"args": "arg",
"desc": "環境変数の値を返します。\n\n本関数はNode-REDの定義関数です。"
}
}

View File

@@ -0,0 +1,458 @@
{
"common": {
"label": {
"name": "姓名",
"ok": "确认",
"done": "完成",
"cancel": "取消",
"delete": "删除",
"close": "关闭",
"load": "读取",
"save": "保存",
"import": "导入",
"export": "导出"
}
},
"workspace": {
"defaultName": "流程__number__",
"editFlow": "编辑流程: __name__",
"confirmDelete": "确认删除",
"delete": "你确定想删除 '__label__'?",
"dropFlowHere": "把流程放到这里",
"status": "状态",
"enabled": "有效",
"disabled": "无效",
"info": "详细描述",
"tip": "详细描述支持Markdown轻量级标记语言并将出现在信息标签中。"
},
"menu": {
"label": {
"view": {
"view": "显示",
"grid": "网格",
"showGrid": "显示网格",
"snapGrid": "对齐网格",
"gridSize": "网格尺寸",
"textDir": "文本方向",
"defaultDir": "默认方向",
"ltr": "从左到右",
"rtl": "从右到左",
"auto": "上下文"
},
"sidebar": {
"show": "显示侧边栏"
},
"settings": "设置",
"userSettings": "用户设置",
"nodes": "节点",
"displayStatus": "显示节点状态",
"displayConfig": "修改节点配置",
"import": "导入",
"export": "导出",
"search": "查找流程",
"searchInput": "查找流程",
"clipboard": "剪贴板",
"library": "库",
"examples": "例子",
"subflows": "子流程",
"createSubflow": "新建子流程",
"selectionToSubflow": "将选择部分更改为子流程",
"flows": "流程",
"add": "增加",
"rename": "重命名",
"delete": "删除",
"keyboardShortcuts": "键盘快捷方式",
"login": "登陆",
"logout": "退出",
"editPalette":"节点管理",
"other": "其他",
"showTips": "显示小提示"
}
},
"user": {
"loggedInAs": "作为__name__登陆",
"username": "账号",
"password": "密码",
"login": "登陆",
"loginFailed": "登陆失败",
"notAuthorized": "未授权",
"errors": {
"settings": "设置信息需要登陆后才能访问",
"deploy": "改动需要登陆后才能部署",
"notAuthorized": "此操作需要登陆后才能执行"
}
},
"notification": {
"warning": "<strong>警告</strong>: __message__",
"warnings": {
"undeployedChanges": "节点中存在未部署的更改",
"nodeActionDisabled": "节点动作在子流程中被禁用",
"missing-types": "流程由于缺少节点类型而停止。请检查日志的详细信息",
"restartRequired": "Node-RED必须重新启动以启用升级的模块"
},
"error": "<strong>Error</strong>: __message__",
"errors": {
"lostConnection": "丢失与服务器的连接,重新连接...",
"lostConnectionReconnect": "丢失与服务器的连接__time__秒后重新连接",
"lostConnectionTry": "现在尝试",
"cannotAddSubflowToItself": "无法向其自身添加子流程",
"cannotAddCircularReference": "无法添加子流程 - 循环引用",
"unsupportedVersion": "您正在使用不受支持的Node.js版本<br/>请升级到最新版本的Node.js LTS"
}
},
"clipboard": {
"nodes": "节点",
"selectNodes": "选择上面的文本并复制到剪贴板",
"pasteNodes": "在这里粘贴节点",
"importNodes": "导入节点",
"exportNodes": "导出节点至剪贴板",
"importUnrecognised": "导入了无法识别的类型:",
"importUnrecognised_plural": "导入了无法识别的类型:",
"nodesExported": "节点导出到了剪贴板",
"nodeCopied": "已复制__count__个节点",
"nodeCopied_plural": "已复制__count__个节点",
"invalidFlow": "无效的流程: __message__",
"export": {
"selected": "已选择的节点",
"current": "现在的节点",
"all": "所有流程",
"compact": "紧凑",
"formatted": "已格式化",
"copy": "导出到剪贴板"
},
"import": {
"import": "导入到",
"newFlow": "新流程"
},
"copyMessagePath": "已复制路径",
"copyMessageValue": "已复制数值",
"copyMessageValue_truncated": "已复制舍弃的数值"
},
"deploy": {
"deploy": "部署",
"full": "全面",
"fullDesc": "在工作区中部署所有内容",
"modifiedFlows": "已修改的流程",
"modifiedFlowsDesc": "只部署包含已更改节点的流",
"modifiedNodes": "已更改的节点",
"modifiedNodesDesc": "只部署已经更改的节点",
"successfulDeploy": "部署成功",
"deployFailed": "部署失败: __message__",
"unusedConfigNodes": "您有一些未使用的配置节点",
"unusedConfigNodesLink": "点击此处查看它们",
"errors": {
"noResponse": "服务器没有响应"
},
"confirm": {
"button": {
"ignore": "忽略",
"confirm": "确认部署",
"review": "查看更改",
"cancel": "取消",
"merge": "合并",
"overwrite": "忽略 & 部署"
},
"undeployedChanges": "您有未部署的更改。\n\n离开此页面将丢失这些更改。",
"improperlyConfigured": "工作区包含一些未正确配置的节点:",
"unknown": "工作区包含一些未知的节点类型:",
"confirm": "你确定要部署吗?",
"conflict": "服务器正在运行较新的一组流程。",
"backgroundUpdate": "服务器上的流程已更新。",
"conflictChecking": "检查是否可以自动合并更改",
"conflictAutoMerge": "此更改不包括冲突,可以自动合并",
"conflictManualMerge": "这些更改包括了在部署之前必须解决的冲突。"
}
},
"diff": {
"unresolvedCount": "__count__个未解决的冲突",
"unresolvedCount_plural": "__count__个未解决的冲突",
"type": {
"added": "已添加",
"changed": "已更改",
"unchanged": "未更改",
"deleted": "已删除",
"flowDeleted": "已删除流程",
"flowAdded": "已添加流程",
"movedTo": "移动至__id__",
"movedFrom": "从__id__移动"
},
"nodeCount": "__count__个节点",
"nodeCount_plural": "__count__个节点",
"local": "本地",
"remote": "远程"
},
"subflow": {
"editSubflow": "编辑流程模板: __name__",
"edit": "编辑流程模板",
"subflowInstances": "这个子流程模板有__count__个实例",
"subflowInstances_plural": "这个子流程模板有__count__个实例",
"editSubflowProperties": "编辑属性",
"input": "输入:",
"output": "输出:",
"deleteSubflow": "删除子流程",
"info": "详细描述",
"format": "标记格式",
"errors": {
"noNodesSelected": "<strong>无法创建子流程</strong>: 未选择节点",
"multipleInputsToSelection": "<strong>无法创建子流程</strong>: 多个输入到了选择"
}
},
"editor": {
"configEdit": "编辑",
"configAdd": "添加",
"configUpdate": "更新",
"configDelete": "删除",
"nodesUse": "__count__个节点使用此配置",
"nodesUse_plural": "__count__个节点使用此配置",
"addNewConfig": "添加新的__type__配置",
"editNode": "编辑__type__节点",
"editConfig": "编辑__type__配置",
"addNewType": "添加新的__type__节点",
"nodeProperties": "节点属性",
"portLabels": "端口标签",
"labelInputs": "输入",
"labelOutputs": "输出",
"noDefaultLabel": "无",
"defaultLabel": "使用默认标签",
"errors": {
"scopeChange": "更改范围将使其他流中的节点无法使用"
}
},
"keyboard": {
"title": "键盘快捷键",
"unassigned": "未分配",
"selectAll": "选择所有节点",
"selectAllConnected": "选择所有连接的节点",
"addRemoveNode": "从选择中添加/删除节点",
"editSelected": "编辑选定节点",
"deleteSelected": "删除选定节点或链接",
"importNode": "导入节点",
"exportNode": "导出节点",
"nudgeNode": "移动所选节点(1px)",
"moveNode": "移动所选节点(20px)",
"toggleSidebar": "切换侧边栏",
"copyNode": "复制所选节点",
"cutNode": "剪切所选节点",
"pasteNode": "粘贴节点",
"undoChange": "撤消上次执行的更改",
"searchBox": "打开搜索框",
"managePalette": "管理面板"
},
"library": {
"openLibrary": "打开库...",
"saveToLibrary": "保存到库...",
"typeLibrary": "__type__类型库",
"unnamedType": "无名__type__",
"exportToLibrary": "将节点导出到库",
"dialogSaveOverwrite": "一个叫做__libraryName__的__libraryType__已经存在您需要覆盖么",
"invalidFilename": "无效的文件名",
"savedNodes": "保存的节点",
"savedType": "已保存__type__",
"saveFailed": "保存失败: __message__",
"filename": "文件名",
"folder": "文件夹",
"filenamePlaceholder": "文件",
"fullFilenamePlaceholder": "a/b/文件",
"folderPlaceholder": "a/b",
"breadcrumb": "库"
},
"palette": {
"noInfo": "无可用信息",
"filter": "过滤节点",
"search": "搜索模块",
"label": {
"subflows": "子流程",
"input": "输入",
"output": "输出",
"function": "功能",
"social": "社交",
"storage": "存储",
"analysis": "分析",
"advanced": "高级"
},
"event": {
"nodeAdded": "添加到面板中的节点:",
"nodeAdded_plural": "添加到面板中的多个节点",
"nodeRemoved": "从面板中删除的节点:",
"nodeRemoved_plural": "从面板中删除的多个节点:",
"nodeEnabled": "启用节点:",
"nodeEnabled_plural": "启用多个节点:",
"nodeDisabled": "禁用节点:",
"nodeDisabled_plural": "禁用多个节点:",
"nodeUpgraded": "节点模块__module__升级到__version__版本"
},
"editor": {
"title": "面板管理",
"times": {
"seconds": "秒前",
"minutes": "分前",
"minutesV": "__count__分前",
"hoursV": "__count__小时前",
"hoursV_plural": "__count__小时前",
"daysV": "__count__天前",
"daysV_plural": "__count__天前",
"weeksV": "__count__周前",
"weeksV_plural": "__count__周前",
"monthsV": "__count__月前",
"monthsV_plural": "__count__月前",
"yearsV": "__count__年前",
"yearsV_plural": "__count__年前",
"yearMonthsV": "__y__年, __count__月前",
"yearMonthsV_plural": "__y__年, __count__月前",
"yearsMonthsV": "__y__年, __count__月前",
"yearsMonthsV_plural": "__y__年, __count__月前"
},
"nodeCount": "__label__个节点",
"nodeCount_plural": "__label__个节点",
"moduleCount": "__count__个可用模块",
"moduleCount_plural": "__count__个可用模块",
"inuse": "使用中",
"enableall": "全部启用",
"disableall": "全部禁用",
"enable": "启用",
"disable": "禁用",
"remove": "移除",
"update": "更新至__version__版本",
"updated": "已更新",
"install": "安装",
"installed": "已安装",
"loading": "加载目录...",
"tab-nodes": "节点",
"tab-install": "安装",
"sort": "排序:",
"sortAZ": "a-z顺序",
"sortRecent": "日期顺序",
"more": "增加__count__个",
"errors": {
"catalogLoadFailed": "无法加载节点目录。<br>查看浏览器控制台了解更多信息",
"installFailed": "无法安装: __module__<br>__message__<br>查看日志了解更多信息",
"removeFailed": "无法删除: __module__<br>__message__<br>查看日志了解更多信息",
"updateFailed": "无法更新: __module__<br>__message__<br>查看日志了解更多信息",
"enableFailed": "无法启用: __module__<br>__message__<br>查看日志了解更多信息",
"disableFailed": "无法禁用: __module__<br>__message__<br>查看日志了解更多信息"
},
"confirm": {
"install": {
"body": "在安装之前请阅读节点的文档某些节点的依赖关系不能自动解决可能需要重新启动Node-RED。",
"title": "安装节点"
},
"remove": {
"body": "删除节点将从Node-RED卸载它。节点可能会继续使用资源直到重新启动Node-RED。",
"title": "删除节点"
},
"update": {
"body": "更新节点将需要重新启动Node-RED来完成更新该过程必须由手动完成。",
"title": "更新节点"
},
"cannotUpdate": {
"body": "此节点的更新可用,但不会安装在面板管理器可以更新的位置。<br/><br/>请参阅有关如何更新此节点的文档。"
},
"button": {
"review": "打开节点信息",
"install": "安装",
"remove": "删除",
"update": "更新"
}
}
}
},
"sidebar": {
"info": {
"name": "节点信息",
"tabName": "名称",
"label": "信息",
"node": "节点",
"type": "类型",
"id": "ID",
"status": "状态",
"enabled": "启用",
"disabled": "禁用",
"subflow": "子流程",
"instances": "实例",
"properties": "属性",
"info": "信息",
"blank": "空白",
"null": "空",
"showMore": "展开",
"showLess": "收起",
"flow": "流程",
"selection":"选择",
"nodes":"__count__ 个节点",
"flowDesc": "流程描述",
"subflowDesc": "子流程描述",
"nodeHelp": "节点帮助",
"none":"无",
"arrayItems": "__count__个项目",
"showTips": "您可以从设置面板启用提示信息"
},
"config": {
"name": "配置节点",
"label": "配置",
"global": "所有流程",
"none": "无",
"subflows": "子流程",
"flows": "流程",
"filterUnused": "未使用",
"filterAll": "所有",
"filtered": "__count__ 个隐藏"
},
"palette": {
"name": "节点管理",
"label": "节点"
},
"project": {
"label": "项目",
"name": "项目",
"description": "描述",
"dependencies": "依赖",
"settings": "设置",
"editDescription": "编辑项目描述",
"editDependencies": "编辑项目依赖"
}
},
"typedInput": {
"type": {
"str": "文字列",
"num": "数字",
"re": "正则表达式",
"bool": "布尔",
"json": "JSON",
"bin": "二进制流",
"date": "时间戳"
}
},
"editableList": {
"add": "添加"
},
"search": {
"empty": "找不到匹配",
"addNode": "添加一个节点..."
},
"expressionEditor": {
"functions": "功能",
"insert": "插入",
"title": "JSONata表达式编辑器",
"data": "示例消息",
"result": "结果",
"format": "格式表达方法",
"compatMode": "兼容模式启用",
"compatModeDesc": "<h3>JSONata的兼容模式</h3><p> 目前的表达式仍然参考<code>msg</code>,所以将以兼容性模式进行评估。请更新表达式,使其不使用<code>msg</code>,因为此模式将在将来删除。</p><p> 当JSONata支持首次添加到Node-RED时它需要表达式引用<code>msg</code>对象。例如<code>msg.payload</code>将用于访问有效负载。</p><p> 这样便不再需要表达式直接针对消息进行评估。要访问有效负载,表达式应该只是<code>payload</code>.</p>",
"noMatch": "无匹配结果",
"errors": {
"invalid-expr": "无效的JSONata表达式:\n __message__",
"invalid-msg": "无效的示例JSON消息:\n __message__",
"context-unsupported": "无法测试上下文函数\n $flowContext 或 $globalContext",
"eval": "评估表达式错误:\n __message__"
}
},
"jsonEditor": {
"title": "JSON编辑器",
"format": "格式化JSON"
},
"bufferEditor": {
"title": "缓冲区编辑器",
"modeString": "作为UTF-8字符串处理",
"modeArray": "作为JSON数组处理",
"modeDesc": "<h3>缓冲区编辑器</h3><p>缓冲区类型被存储为字节值的JSON数组。编辑器将尝试将输入的数值解析为JSON数组。如果它不是有效的JSON它将被视为UTF-8字符串并被转换为单个字符代码点的数组。</p><p>例如,<code>Hello World</code>的值会被转换为JSON数组<pre>[72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100]</pre></p>"
}
}

View File

@@ -0,0 +1,23 @@
{
"info": {
"tip0" : "您可以用 {{core:delete-selection}} 删除选择的节点或链接。",
"tip1" : "{{core:search}} 可以在流程内搜索节点。",
"tip2": "{{core:toggle-sidebar}} 可以显示或隐藏边栏。",
"tip3": "您可以在 {{core:manage-palette}} 中管理节点的控制面板。",
"tip4": "边栏中会列出流程中所有的配置节点。您可以通过菜单或者 {{core:show-config-tab}} 来访问这些节点。",
"tip5": "您可以在设定中选择显示或隐藏这些提示。",
"tip6": "您可以用[left] [up] [down] [right]键来移动被选中的节点。按住[shift]可以更快地移动节点。",
"tip7": "把节点拖到连接上可以向连接中插入节点。",
"tip8": "您可以用 {{core:show-export-dialog}} 来导出被选中的节点或标签页中的流程。",
"tip9": "您可以将流程的json文件拖入编辑框或 {{core:show-import-dialog}} 来导入流程。",
"tip10": "按住[shift]后单击并拖动节点可以将该节点的多个连接一并移动到其他节点的端口。",
"tip11": "{{core:show-info-tab}} 可以显示「信息」标签页。 {{core:show-debug-tab}} 可以显示「调试」标签页。",
"tip12": "按住[ctrl]的同时点击工作界面可以在节点的对话栏中快速添加节点。",
"tip13": "按住[ctrl]的同时点击节点的端口或后续节点可以快速连接多个节点。",
"tip14": "按住[shift]的同时点击节点会选中所有被连接的节点。",
"tip15": "按住[ctrl]的同时点击节点可以在选中或取消选中节点。",
"tip16": "{{core:show-previous-tab}} 和 {{core:show-next-tab}} 可以切换标签页。",
"tip17": "您可以在节点的属性配置画面中通过 {{core:confirm-edit-tray}} 来更改设置,或者用 {{core:cancel-edit-tray}} 来取消更改。",
"tip18": "您可以通过点击 {{core:edit-selected-node}} 来显示被选中节点的属性设置画面。"
}
}

View File

@@ -0,0 +1,218 @@
{
"$string": {
"args": "arg",
"desc": "通过以下的类型转换规则将参数*arg*转换成字符串:\n\n - 字符串不转换。\n -函数转换成空的字符串。\n - JSON的值无法用数字表示所以用无限大或者NaN非数表示。\n - 用JSON.stringify函数将其他值转换成JSON字符串。"
},
"$length": {
"args": "str",
"desc": "输出字符串str的字数。如果str不是字符串抛出错误。"
},
"$substring": {
"args": "str, start[, length]",
"desc": "输出`start`位置后的的首次出现的包括`str`的子字符串。 如果`length`被指定,那么的字符串中将只包括前`length`个文字。如果`start`是负数则输出从`str`末尾开始的`length`个文字"
},
"$substringBefore": {
"args": "str, chars",
"desc": "输出str中首次出现的chars之前的子字符串如果str中不包括chars则输出str。"
},
"$substringAfter": {
"args": "str, chars",
"desc": "输出str中首次出现的chars之后的子字符串如果str中不包括chars则输出str。"
},
"$uppercase": {
"args": "str",
"desc": "`将str中的所有字母变为大写后输出。"
},
"$lowercase": {
"args": "str",
"desc": "将str中的所有字母变为小写后输出。"
},
"$trim": {
"args": "str",
"desc": "将以下步骤应用于`str`来去除所有空白文字并实现标准化。\n\n 将全部tab制表符、回车键、换行字符用空白代替。\n- 将连续的空白文字变成一个空白文字。\n- 消除开头和末尾的空白文字。\n\n如果`str`没有被指定(即在无输入参数的情况下调用本函数),将上下文的值作为`str`来使用。 如果`str` 不是字符串则抛出错误。"
},
"$contains": {
"args": "str, pattern",
"desc": "字符串`str` 和 `pattern`匹配的话输出`true`,不匹配的情况下输出 `false`。 不指定`str`的情况下(比如用一个参数调用本函数时)、将上下文的值作为`str`来使用。参数 `pattern`可以为字符串或正则表达。"
},
"$split": {
"args": "str[, separator][, limit]",
"desc": "将参数`str`分解成由子字符串组成的数组。 如果`str`不是字符串抛出错误。可以省略的参数 `separator`中指定字符串`str`的分隔符。分隔符可以是文字或正则表达式。在不指定`separator`的情况下、将分隔符看作空的字符串并把`str`拆分成由单个字母组成的数组。如果`separator`不是字符串则抛出错误。在可省略的参数`limit`中指定分割后的子字符串的最大个数。超出个数的子字符串将被舍弃。如果`limit`没有被指定,`str` 将不考虑子字符串的个数而将字符串完全分隔。如果`limit`是负数则抛出错误。"
},
"$join": {
"args": "array[, separator]",
"desc": "用可以省略的参数 `separator`来把多个字符串连接。如果`array`不是字符串则抛出错误。 如果没有指定`separator`,则用空字符串来连接字符(即字符串之间没有`separator`)。 如果`separator`不是字符则抛出错误。"
},
"$match": {
"args": "str, pattern [, limit]",
"desc": "对字符串`str`使用正则表达式`pattern`并输出与`str`相匹配的部分信息。"
},
"$replace": {
"args": "str, pattern, replacement [, limit]",
"desc": "在字符串`str`中搜索`pattern`并用`replacement`来替换。\n\n可选参数`limit`用来指定替换次数的上限。"
},
"$now": {
"args": "",
"desc": "生成ISO 8601互換格式的时刻并作为字符串输出。"
},
"$base64encode": {
"args": "string",
"desc": "将ASCII格式的字符串转换为Base 64格式。将字符串中的文字视作二进制形式的数据处理。包含URI编码在内的字符串文字必须在0x00到0xFF的范围内否则不会被支持。"
},
"$base64decode": {
"args": "string",
"desc": "用UTF-8代码页将Base 64形式二进制值转换为字符串。"
},
"$number": {
"args": "arg",
"desc": "用下述的规则将参数 `arg`转换为数值。:\n\n 数值不做转换。\n 将字符串中合法的JSON数値表示转换成数値。\n 其他形式的值则抛出错误。"
},
"$abs": {
"args": "number",
"desc": "输出参数`number`的绝对值。"
},
"$floor": {
"args": "number",
"desc": "输出比`number`的值小的最大整数。"
},
"$ceil": {
"args": "number",
"desc": "输出比`number`的值大的最小整数。"
},
"$round": {
"args": "number [, precision]",
"desc": "输出四舍五入后的参数`number`。可省略的参数 `precision`指定四舍五入后小数点下的位数。"
},
"$power": {
"args": "base, exponent",
"desc": "输出底数`base`的`exponent`次幂。"
},
"$sqrt": {
"args": "number",
"desc": "输出参数 `number`的平方根。"
},
"$random": {
"args": "",
"desc": "输出比0大比1小的伪随机数。"
},
"$millis": {
"args": "",
"desc": "返回从UNIX时间 (1970年1月1日 UTC/GMT的午夜开始到现在的毫秒数。在同一个表达式的测试中所有对`$millis()`的调用将会返回相同的值。"
},
"$sum": {
"args": "array",
"desc": "输出数组`array`的总和。如果`array`不是数值则抛出错误。"
},
"$max": {
"args": "array",
"desc": "输出数组`array`的最大值。如果`array`不是数值则抛出错误。"
},
"$min": {
"args": "array",
"desc": "输出数组`array`的最小值。如果`array`不是数值则抛出错误。。"
},
"$average": {
"args": "array",
"desc": "输出数组`array`的平均数。如果`array`不是数值则抛出错误。。"
},
"$boolean": {
"args": "arg",
"desc": "用下述规则将数据转换成布尔值。:\n\n - 不转换布尔值`Boolean`。\n 将空的字符串`string`转换为`false`\n 将不为空的字符串`string`转换为`true`\n 将为0的数字`number`转换成`false`\n 将不为0的数字`number`转换成`true`\n –将`null`转换成`false`\n –将空的数组`array`转换成`false`\n –如果数组`array`中含有可以转换成`true`的要素则转换成`true`\n –如果`array`中没有可转换成`true`的要素则转换成`false`\n 空的对象`object`转换成`false`\n 非空的对象`object`转换成`true`\n –将函数`function`转换成`false`"
},
"$not": {
"args": "arg",
"desc": "输出做取反运算后的布尔值。首先将`arg`转换为布尔值。"
},
"$exists": {
"args": "arg",
"desc": "如果算式`arg`的值存在则输出`true`。如果算式的值不存在(比如指向不存在区域的引用)则输出`false`。"
},
"$count": {
"args": "array",
"desc": "输出数组中的元素数。"
},
"$append": {
"args": "array, array",
"desc": "将两个数组连接。"
},
"$sort": {
"args": "array [, function]",
"desc": "输出排序后的数组`array`。\n\n如果使用了比较函数`function`,则下述两个参数需要被指定。\n\n`function(left, right)`\n\n该比较函数是为了比较left和right两个值而被排序算法调用的。如果用户希望left的值被置于right的值之后那么该函数必须输出布尔值`true`来表示位置交换。而在不需要位置交换时函数必须输出`false`。"
},
"$reverse": {
"args": "array",
"desc": "输出倒序后的数组`array`。"
},
"$shuffle": {
"args": "array",
"desc": "输出随机排序后的数组 `array`。"
},
"$zip": {
"args": "array, ...",
"desc": "将数组中的值按索引顺序打包后输出。"
},
"$keys": {
"args": "object",
"desc": "输出由对象内的键组成的数组。如果参数是对象的数组则输出由所有对象中的键去重后组成的队列。"
},
"$lookup": {
"args": "object, key",
"desc": "输出对象中与参数`key`对应的值。如果第一个参数`object`是数组,那么数组中所有的对象都将被搜索并输出这些对象中与参数`key`对应的值。"
},
"$spread": {
"args": "object",
"desc": "将对象中的键值对分隔成每个要素中只含有一个键值对的数组。如果参数`object`是数组,那么返回值的数组中包含所有对象中的键值对。"
},
"$merge": {
"args": "array&lt;object&gt;",
"desc": "将输入数组`objects`中所有的键值对合并到一个`object`中并返回。如果输入数组的要素中含有重复的键,则返回的`object`中将只包含数组中最后出现要素的值。如果输入数组中包括对象以外的元素,则抛出错误。"
},
"$sift": {
"args": "object, function",
"desc": "输出参数`object`中符合`function`的键值对。\n\n`function`必须含有下述参数。\n\n`function(value [, key [, object]])`"
},
"$each": {
"args": "object, function",
"desc": "将函数`function`应用于`object`中的所有键值对并输出由所有返回值组成的数组。"
},
"$map": {
"args": "array, function",
"desc": "将函数`function`应用于数组`array`中所有的值并输出由返回值组成的数组。\n\n`function`中必须含有下述参数。\n\n`function(value [, index [, array]])`"
},
"$filter": {
"args": "array, function",
"desc": "输出数组`array`中符合函数`function`条件的值组成的数组。\n\n`function`必须包括下述参数。\n\n`function(value [, index [, array]])`"
},
"$reduce": {
"args": "array, function [, init]",
"desc": "将`function`依次应用于数组中的各要素值。 其中,前一个要素值的计算结果将参与到下一次的函数运算中。。\n\n函数`function`接受两个参数并作为中缀表示法中的操作符。\n\n可省略的参数`init`将作为运算的初始值。"
},
"$flowContext": {
"args": "string",
"desc": "获取流上下文(流等级的上下文,可以让所有节点共享)的属性。"
},
"$globalContext": {
"args": "string",
"desc": "获取全局上下文的属性。"
},
"$pad": {
"args": "string, width [, char]",
"desc": "根据需要,向字符串`string`的副本中填充文字使该字符串的字数达到`width`的绝对值并返回填充文字后的字符串。\n\n如果`width`的值为正,则向字符串`string`的右侧填充文字,如果`width`为负,则向字符串`string`的左侧填充文字。\n\n可选参数`char`用来指定填充的文字。如果未指定该参数,则填充空白文字。"
},
"$fromMillis": {
"args": "number",
"desc": "将表示从UNIX时间 (1970年1月1日 UTC/GMT的午夜开始到现在的毫秒数的数值转换成ISO 8601形式时间戳的字符串。"
},
"$formatNumber": {
"args": "number, picture [, options]",
"desc": "将`number`转换成具有`picture`所指定的数值格式的字符串。\n\n此函数的功能与XPath F&O 3.1规格中定义的XPath/XQuery函数的fn:format-number功能相一致。参数`picture`用于指定数值的转换格式其语法与fn:format-number中的定义一致。\n\n可选的第三参数`options`用来覆盖默认的局部环境格式如小数点分隔符。如果指定该参数那么该参数必须是包含name/value对的对象并且name/value对必须符合XPath F&O 3.1规格中记述的数值格式。"
},
"$formatBase": {
"args": "number [, radix]",
"desc": "将`number`变换为以参数`radix`的值为基数形式的字符串。如果不指定`radix`的值则默认基数为10。指定的`radix`值必须在236之间否则抛出错误。"
},
"$toMillis": {
"args": "timestamp",
"desc": "将ISO 8601格式的字符串`timestamp`转换为从UNIX时间 (1970年1月1日 UTC/GMT的午夜开始到现在的毫秒数。如果该字符串的格式不正确则抛出错误。"
}
}

View File

@@ -0,0 +1,14 @@
{
"name": "@node-red/editor-client",
"version": "0.20.0-alpha.0",
"license": "Apache-2.0",
"repository": {
"type": "git",
"url": "https://github.com/node-red/node-red.git"
},
"contributors": [
{ "name": "Nick O'Leary" },
{ "name": "Dave Conway-Jones"}
],
"main": "./lib/index.js"
}

View File

@@ -0,0 +1,50 @@
How to build the custom ACE modes for Node-RED
----------------------------------------------
Node-RED includes custom JSONata and JavaScript modes.
## JSONata
The `ace/mode/jsonata` mode is maintained under `editor-client/src/vendor/jsonata`.
Those files are edited in place and copied into the build by Grunt.
## JavaScript
The `ace/mode/nrjavascript` mode is used exclusively by the Function node. It
inherits almost entirely from the normal JavaScript mode. The one key difference
is that it wraps the code with a Function before parsing. This is required to
avoid some false-flagged errors.
The source of the mode is under `editor-client/src/ace/mode`. If those files are
modified in anyway, they *must* be manually built to generate the files under
`editor-client/src/ace/bin` and checked in. Those files are the ones the Grunt
built copies out in the Node-RED build.
### Building the mode files
#### Setup build environment
cd /tmp/
git clone https://github.com/ajaxorg/ace.git
cd ace
npm install
#### Copy mode src files into build environment
cd <node-red-source-directory
cp packages/node_modules/@node-red/editor-client/src/ace/mode/* \
/tmp/ace/lib/ace/mode/
#### Run the build
cd /tmp/ace
node ./Makefile.dryice.js -m -nc
#### Copy the built versions back
cp build/src-min-noconflict/*-nrjavascript.js \
<node-red-source-directory>/packages/node_modules/@node-red/editor-client/src/ace/bin/
cp build/src-min-noconflict/snippets/nrjavascript.js \
<node-red-source-directory>/packages/node_modules/@node-red/editor-client/src/ace/bin/snippets/

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,9 @@
ace.define("ace/snippets/nrjavascript",[],function(e,t,n){"use strict";t.snippetText=undefined,t.scope="nrjavascript"});
(function() {
ace.require(["ace/snippets/nrjavascript"], function(m) {
if (typeof module == "object" && typeof exports == "object" && module) {
module.exports = m;
}
});
})();

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,49 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
define(function(require, exports, module) {
"use strict";
var oop = require("../lib/oop");
var JavaScriptMode = require("./javascript").Mode;
var WorkerClient = require("../worker/worker_client").WorkerClient;
var Mode = function() {
// Inherit everything from the standard JavaScript mode
JavaScriptMode.call(this);
};
oop.inherits(Mode, JavaScriptMode);
(function() {
// Insert our custom worker
this.createWorker = function(session) {
var worker = new WorkerClient(["ace"], "ace/mode/nrjavascript_worker", "NRJavaScriptWorker");
worker.attachToDocument(session.getDocument());
worker.on("annotate", function(results) {
session.setAnnotations(results.data);
});
worker.on("terminate", function() {
session.clearAnnotations();
});
return worker;
};
this.$id = "ace/mode/nrjavascript";
}).call(Mode.prototype);
exports.Mode = Mode;
});

View File

@@ -0,0 +1,189 @@
/* ***** BEGIN LICENSE BLOCK *****
* Distributed under the BSD license:
*
* Copyright (c) 2010, Ajax.org B.V.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Ajax.org B.V. nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* ***** END LICENSE BLOCK ***** */
define(function(require, exports, module) {
"use strict";
var oop = require("../lib/oop");
var Mirror = require("../worker/mirror").Mirror;
var lint = require("./javascript/jshint").JSHINT;
function startRegex(arr) {
return RegExp("^(" + arr.join("|") + ")");
}
var disabledWarningsRe = startRegex([
"Bad for in variable '(.+)'.",
'Missing "use strict"'
]);
var errorsRe = startRegex([
"Unexpected",
"Expected ",
"Confusing (plus|minus)",
"\\{a\\} unterminated regular expression",
"Unclosed ",
"Unmatched ",
"Unbegun comment",
"Bad invocation",
"Missing space after",
"Missing operator at"
]);
var infoRe = startRegex([
"Expected an assignment",
"Bad escapement of EOL",
"Unexpected comma",
"Unexpected space",
"Missing radix parameter.",
"A leading decimal point can",
"\\['{a}'\\] is better written in dot notation.",
"'{a}' used out of scope"
]);
var NRJavaScriptWorker = exports.NRJavaScriptWorker = function(sender) {
Mirror.call(this, sender);
this.setTimeout(500);
this.setOptions();
};
oop.inherits(NRJavaScriptWorker, Mirror);
(function() {
this.setOptions = function(options) {
this.options = options || {
// undef: true,
// unused: true,
esnext: true,
moz: true,
devel: true,
browser: true,
node: true,
laxcomma: true,
laxbreak: true,
lastsemic: true,
onevar: false,
passfail: false,
maxerr: 100,
expr: true,
multistr: true,
globalstrict: true
};
this.doc.getValue() && this.deferredUpdate.schedule(100);
};
this.changeOptions = function(newOptions) {
oop.mixin(this.options, newOptions);
this.doc.getValue() && this.deferredUpdate.schedule(100);
};
this.isValidJS = function(str) {
try {
// evaluated code can only create variables in this function
eval("throw 0;" + str);
} catch(e) {
if (e === 0)
return true;
}
return false;
};
this.onUpdate = function() {
var value = this.doc.getValue();
value = value.replace(/^#!.*\n/, "\n");
if (!value)
return this.sender.emit("annotate", []);
// [Node-RED] wrap the code in a function
value = "async function __nodered__(msg) {\n"+value+"\n}";
var errors = [];
// jshint reports many false errors
// report them as error only if code is actually invalid
var maxErrorLevel = this.isValidJS(value) ? "warning" : "error";
// var start = new Date();
lint(value, this.options, this.options.globals);
var results = lint.errors;
var errorAdded = false;
for (var i = 0; i < results.length; i++) {
var error = results[i];
if (!error)
continue;
var raw = error.raw;
var type = "warning";
if (raw == "Missing semicolon.") {
var str = error.evidence.substr(error.character);
str = str.charAt(str.search(/\S/));
if (maxErrorLevel == "error" && str && /[\w\d{(['"]/.test(str)) {
error.reason = 'Missing ";" before statement';
type = "error";
} else {
type = "info";
}
}
else if (disabledWarningsRe.test(raw)) {
continue;
}
else if (infoRe.test(raw)) {
type = "info";
}
else if (errorsRe.test(raw)) {
errorAdded = true;
type = maxErrorLevel;
}
else if (raw == "'{a}' is not defined.") {
type = "warning";
}
else if (raw == "'{a}' is defined but never used.") {
type = "info";
}
errors.push({
// [Node-RED] offset the row for the added line
row: error.line-2,
column: error.character-1,
text: error.reason,
type: type,
raw: raw
});
if (errorAdded) {
// break;
}
}
// console.log("lint time: " + (new Date() - start));
this.sender.emit("annotate", errors);
};
}).call(NRJavaScriptWorker.prototype);
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 291 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 386 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 289 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 368 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 392 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1015 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 393 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 386 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 386 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0" y="0" width="480" height="480" viewBox="0, 0, 480, 480">
<g id="Calque_1">
<path d="M408,16 C438.928,16 464,41.072 464,72 L464,408 C464,438.928 438.928,464 408,464 L72,464 C41.072,464 16,438.928 16,408 L16,296.002 L69.339,296.002 C89.715,296.002 105.993,279.497 105.993,259.12 L105.992,242.19 C190.296,243.397 214.83,265.317 241.661,288.777 C267.502,311.375 296.48,335.662 371.989,336.261 L371.993,344.57 C372.002,364.947 388.693,382 409.069,382 L463.991,382 L463.991,356 L409.069,356 C402.189,356 396.991,351.449 396.991,344.569 L396.991,308.32 C396.991,301.44 402.189,296 409.069,296 L463.991,296 L463.991,272 L409.069,272 C388.693,272 372.002,287.943 371.993,308.32 L371.99,316.908 C300.63,316.672 280.362,296.883 254.41,274.189 C232.267,254.825 206.244,233.534 148.914,225.789 C149.412,225.361 149.872,224.945 150.353,224.505 C161.391,214.382 167.343,202.153 173.167,191.593 C178.99,181.034 184.469,172.221 193.444,166.061 C200.725,161.064 211.08,157.338 226.992,156.647 L226.993,165.123 C226.997,185.5 243.431,202.999 263.808,202.999 L411.141,202.999 C431.517,202.999 447.993,185.5 447.993,165.123 L447.993,128.874 C447.993,108.497 431.517,91.999 411.141,91.999 L263.808,91.999 C243.431,91.999 226.983,108.496 226.993,128.874 L226.998,137.281 C207.794,138.053 193.238,142.713 182.496,150.086 C169.469,159.028 162.277,171.247 156.21,182.247 C150.144,193.247 145.009,203.104 137.25,210.218 C130.497,216.411 121.157,221.193 105.993,222.976 L105.993,222.579 C106.111,202.203 89.715,186.002 69.339,186.002 L16,186.002 L16,72 C16,41.072 41.072,16 72,16 L408,16 z" fill="#000000"/>
<path d="M16,211.002 L69.339,211.002 C76.219,211.002 81.992,215.991 81.992,222.871 L81.992,259.12 C81.992,266 76.219,272.002 69.339,272.002 L16,272.002 L16,211.002 z" fill="#000000"/>
<path d="M411.135,116.997 C418.015,116.997 422.987,121.992 422.987,128.872 L422.987,165.122 C422.987,172.002 418.015,176.998 411.135,176.998 L263.802,176.998 C256.923,176.998 250.99,172.002 250.99,165.122 L250.99,128.872 C250.99,121.993 256.923,116.997 263.802,116.997 L411.135,116.997 z" fill="#000000"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="480" width="480" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" viewBox="0 0 480.00002 479.99999">
<title>Node-RED Icon</title>
<metadata>
<rdf:RDF>
<cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
<dc:title>Node-RED Icon</dc:title>
<cc:license rdf:resource="http://creativecommons.org/licenses/by/3.0/"/>
<dc:creator>
<cc:Agent>
<dc:title>Nick O&apos;Leary</dc:title>
</cc:Agent>
</dc:creator>
</cc:Work>
<cc:License rdf:about="http://creativecommons.org/licenses/by/3.0/">
<cc:permits rdf:resource="http://creativecommons.org/ns#Reproduction"/>
<cc:permits rdf:resource="http://creativecommons.org/ns#Distribution"/>
<cc:requires rdf:resource="http://creativecommons.org/ns#Notice"/>
<cc:requires rdf:resource="http://creativecommons.org/ns#Attribution"/>
<cc:permits rdf:resource="http://creativecommons.org/ns#DerivativeWorks"/>
</cc:License>
</rdf:RDF>
</metadata>
<g transform="translate(0 -572.36)">
<rect style="color-rendering:auto;color:#000000;isolation:auto;mix-blend-mode:normal;shape-rendering:auto;solid-color:#000000;image-rendering:auto" ry="56" height="448" width="448" y="588.36" x="16" fill="#8f0000"/>
<g transform="matrix(8.545 0 0 8.545 -786.19 -1949.8)">
<path style="color-rendering:auto;text-decoration-color:#000000;color:#000000;isolation:auto;mix-blend-mode:normal;shape-rendering:auto;solid-color:#000000;block-progression:tb;text-decoration-line:none;text-decoration-style:solid;image-rendering:auto;white-space:normal;text-indent:0;text-transform:none" d="m104.41 321.21c0.0138-2.3846-1.905-4.2806-4.2896-4.2806h-6.243v2.9257h6.243c0.80513 0 1.4808 0.58383 1.4808 1.389v4.2422c0 0.80513-0.67566 1.5075-1.4808 1.5075h-6.243v2.8086h6.243c2.3846 0 4.2895-1.9315 4.2895-4.3162l-0.00005-1.9812c9.8659 0.14125 12.737 2.7065 15.877 5.4519 3.0241 2.6446 6.4153 5.4869 15.252 5.557l0.00046 0.97238c0.001 2.3846 1.9543 4.3803 4.3389 4.3803h6.4273v-3.0427h-6.4273c-0.80514 0-1.4135-0.53255-1.4135-1.3377v-4.2422c0-0.80513 0.60835-1.4418 1.4135-1.4418h6.4273v-2.8086h-6.4273c-2.3846 0-4.3379 1.8658-4.3389 4.2504l-0.00045 1.005c-8.351-0.0276-10.723-2.3434-13.76-4.9992-2.5914-2.2662-5.6368-4.7578-12.346-5.6642 0.0583-0.0501 0.11211-0.0987 0.16838-0.15027 1.2918-1.1846 1.9884-2.6158 2.6699-3.8516 0.68148-1.2357 1.3227-2.267 2.373-2.9879 0.85207-0.58483 2.0639-1.0208 3.926-1.1017l0.00018 0.99192c0.00043 2.3846 1.9236 4.4325 4.3083 4.4325h17.242c2.3846 0 4.3127-2.0479 4.3127-4.4325v-4.2422c0-2.3846-1.9281-4.3153-4.3127-4.3153h-17.242c-2.3846 0-4.3095 1.9306-4.3083 4.3153l0.00051 0.98395c-2.2474 0.0903-3.9508 0.6357-5.2079 1.4985-1.5245 1.0464-2.3662 2.4764-3.0762 3.7637-0.70992 1.2873-1.3108 2.4408-2.2188 3.2734-0.79034 0.72475-1.8834 1.2844-3.658 1.493zm18.468-12.356h17.242c0.80514 0 1.387 0.58455 1.387 1.3897v4.2422c0 0.80514-0.5819 1.3898-1.387 1.3898h-17.242c-0.80514 0-1.4994-0.58462-1.4994-1.3898v-4.2422c0-0.80513 0.69431-1.3897 1.4994-1.3897z" fill="#fff"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1019 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 600 B

View File

@@ -0,0 +1,41 @@
<!--
The MIT License (MIT)
Copyright (c) 2014 Brent Jackson
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
https://github.com/jxnblk/loading
-->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="32" height="32" fill="#999">
<path transform="translate(2)" d="M0 12 V20 H4 V12z">
<animate attributeName="d" values="M0 12 V20 H4 V12z; M0 4 V28 H4 V4z; M0 12 V20 H4 V12z; M0 12 V20 H4 V12z" dur="1.2s" repeatCount="indefinite" begin="0" keytimes="0;.2;.5;1" keySplines="0.2 0.2 0.4 0.8;0.2 0.6 0.4 0.8;0.2 0.8 0.4 0.8" calcMode="spline" />
</path>
<path transform="translate(8)" d="M0 12 V20 H4 V12z">
<animate attributeName="d" values="M0 12 V20 H4 V12z; M0 4 V28 H4 V4z; M0 12 V20 H4 V12z; M0 12 V20 H4 V12z" dur="1.2s" repeatCount="indefinite" begin="0.2" keytimes="0;.2;.5;1" keySplines="0.2 0.2 0.4 0.8;0.2 0.6 0.4 0.8;0.2 0.8 0.4 0.8" calcMode="spline" />
</path>
<path transform="translate(14)" d="M0 12 V20 H4 V12z">
<animate attributeName="d" values="M0 12 V20 H4 V12z; M0 4 V28 H4 V4z; M0 12 V20 H4 V12z; M0 12 V20 H4 V12z" dur="1.2s" repeatCount="indefinite" begin="0.4" keytimes="0;.2;.5;1" keySplines="0.2 0.2 0.4 0.8;0.2 0.6 0.4 0.8;0.2 0.8 0.4 0.8" calcMode="spline" />
</path>
<path transform="translate(20)" d="M0 12 V20 H4 V12z">
<animate attributeName="d" values="M0 12 V20 H4 V12z; M0 4 V28 H4 V4z; M0 12 V20 H4 V12z; M0 12 V20 H4 V12z" dur="1.2s" repeatCount="indefinite" begin="0.6" keytimes="0;.2;.5;1" keySplines="0.2 0.2 0.4 0.8;0.2 0.6 0.4 0.8;0.2 0.8 0.4 0.8" calcMode="spline" />
</path>
<path transform="translate(26)" d="M0 12 V20 H4 V12z">
<animate attributeName="d" values="M0 12 V20 H4 V12z; M0 4 V28 H4 V4z; M0 12 V20 H4 V12z; M0 12 V20 H4 V12z" dur="1.2s" repeatCount="indefinite" begin="0.8" keytimes="0;.2;.5;1" keySplines="0.2 0.2 0.4 0.8;0.2 0.6 0.4 0.8;0.2 0.8 0.4 0.8" calcMode="spline" />
</path>
</svg>

Binary file not shown.

After

Width:  |  Height:  |  Size: 410 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 638 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 546 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 638 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 646 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 809 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 563 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 588 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 502 B

View File

@@ -0,0 +1,178 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
RED.comms = (function() {
var errornotification = null;
var clearErrorTimer = null;
var connectCountdownTimer = null;
var connectCountdown = 10;
var subscriptions = {};
var ws;
var pendingAuth = false;
var reconnectAttempts = 0;
var active = false;
function connectWS() {
active = true;
var wspath;
if (RED.settings.apiRootUrl) {
var m = /^(https?):\/\/(.*)$/.exec(RED.settings.apiRootUrl);
if (m) {
console.log(m);
wspath = "ws"+(m[1]==="https"?"s":"")+"://"+m[2]+"comms";
}
} else {
var path = location.hostname;
var port = location.port;
if (port.length !== 0) {
path = path+":"+port;
}
path = path+document.location.pathname;
path = path+(path.slice(-1) == "/"?"":"/")+"comms";
wspath = "ws"+(document.location.protocol=="https:"?"s":"")+"://"+path;
}
var auth_tokens = RED.settings.get("auth-tokens");
pendingAuth = (auth_tokens!=null);
function completeConnection() {
for (var t in subscriptions) {
if (subscriptions.hasOwnProperty(t)) {
ws.send(JSON.stringify({subscribe:t}));
}
}
}
ws = new WebSocket(wspath);
ws.onopen = function() {
reconnectAttempts = 0;
if (errornotification) {
clearErrorTimer = setTimeout(function() {
errornotification.close();
errornotification = null;
},1000);
}
if (pendingAuth) {
ws.send(JSON.stringify({auth:auth_tokens.access_token}));
} else {
completeConnection();
}
}
ws.onmessage = function(event) {
var message = JSON.parse(event.data);
for (var m = 0; m < message.length; m++) {
var msg = message[m];
if (pendingAuth && msg.auth) {
if (msg.auth === "ok") {
pendingAuth = false;
completeConnection();
} else if (msg.auth === "fail") {
// anything else is an error...
active = false;
RED.user.login({updateMenu:true},function() {
connectWS();
})
}
}
else if (msg.topic) {
for (var t in subscriptions) {
if (subscriptions.hasOwnProperty(t)) {
var re = new RegExp("^"+t.replace(/([\[\]\?\(\)\\\\$\^\*\.|])/g,"\\$1").replace(/\+/g,"[^/]+").replace(/\/#$/,"(\/.*)?")+"$");
if (re.test(msg.topic)) {
var subscribers = subscriptions[t];
if (subscribers) {
for (var i=0;i<subscribers.length;i++) {
subscribers[i](msg.topic,msg.data);
}
}
}
}
}
}
}
};
ws.onclose = function() {
if (!active) {
return;
}
if (clearErrorTimer) {
clearTimeout(clearErrorTimer);
clearErrorTimer = null;
}
reconnectAttempts++;
if (reconnectAttempts < 10) {
setTimeout(connectWS,1000);
if (reconnectAttempts > 5 && errornotification == null) {
errornotification = RED.notify(RED._("notification.errors.lostConnection"),"error",true);
}
} else if (reconnectAttempts < 20) {
setTimeout(connectWS,2000);
} else {
connectCountdown = 60;
connectCountdownTimer = setInterval(function() {
connectCountdown--;
if (connectCountdown === 0) {
errornotification.update(RED._("notification.errors.lostConnection"));
clearInterval(connectCountdownTimer);
connectWS();
} else {
var msg = RED._("notification.errors.lostConnectionReconnect",{time: connectCountdown})+' <a href="#">'+ RED._("notification.errors.lostConnectionTry")+'</a>';
errornotification.update(msg);
$(errornotification).find("a").click(function(e) {
e.preventDefault();
errornotification.update(RED._("notification.errors.lostConnection"));
clearInterval(connectCountdownTimer);
connectWS();
})
}
},1000);
}
}
}
function subscribe(topic,callback) {
if (subscriptions[topic] == null) {
subscriptions[topic] = [];
}
subscriptions[topic].push(callback);
if (ws && ws.readyState == 1) {
ws.send(JSON.stringify({subscribe:topic}));
}
}
function unsubscribe(topic,callback) {
if (subscriptions[topic]) {
for (var i=0;i<subscriptions[topic].length;i++) {
if (subscriptions[topic][i] === callback) {
subscriptions[topic].splice(i,1);
break;
}
}
if (subscriptions[topic].length === 0) {
delete subscriptions[topic];
}
}
}
return {
connect: connectWS,
subscribe: subscribe,
unsubscribe:unsubscribe
}
})();

View File

@@ -0,0 +1,53 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
RED.events = (function() {
var handlers = {};
function on(evt,func) {
handlers[evt] = handlers[evt]||[];
handlers[evt].push(func);
}
function off(evt,func) {
var handler = handlers[evt];
if (handler) {
for (var i=0;i<handler.length;i++) {
if (handler[i] === func) {
handler.splice(i,1);
return;
}
}
}
}
function emit(evt,arg) {
if (handlers[evt]) {
for (var i=0;i<handlers[evt].length;i++) {
try {
handlers[evt][i](arg);
} catch(err) {
console.log("RED.events.emit error: ["+evt+"] "+(err.toString()));
console.log(err);
}
}
}
}
return {
on: on,
off: off,
emit: emit
}
})();

View File

@@ -0,0 +1,831 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
RED.nodes.fontAwesome = (function() {
var iconMap = {
"fa-glass": "\uf000",
"fa-music": "\uf001",
"fa-search": "\uf002",
"fa-envelope-o": "\uf003",
"fa-heart": "\uf004",
"fa-star": "\uf005",
"fa-star-o": "\uf006",
"fa-user": "\uf007",
"fa-film": "\uf008",
"fa-th-large": "\uf009",
"fa-th": "\uf00a",
"fa-th-list": "\uf00b",
"fa-check": "\uf00c",
"fa-remove": "\uf00d",
"fa-close": "\uf00d",
"fa-times": "\uf00d",
"fa-search-plus": "\uf00e",
"fa-search-minus": "\uf010",
"fa-power-off": "\uf011",
"fa-signal": "\uf012",
"fa-gear": "\uf013",
"fa-cog": "\uf013",
"fa-trash-o": "\uf014",
"fa-home": "\uf015",
"fa-file-o": "\uf016",
"fa-clock-o": "\uf017",
"fa-road": "\uf018",
"fa-download": "\uf019",
"fa-arrow-circle-o-down": "\uf01a",
"fa-arrow-circle-o-up": "\uf01b",
"fa-inbox": "\uf01c",
"fa-play-circle-o": "\uf01d",
"fa-rotate-right": "\uf01e",
"fa-repeat": "\uf01e",
"fa-refresh": "\uf021",
"fa-list-alt": "\uf022",
"fa-lock": "\uf023",
"fa-flag": "\uf024",
"fa-headphones": "\uf025",
"fa-volume-off": "\uf026",
"fa-volume-down": "\uf027",
"fa-volume-up": "\uf028",
"fa-qrcode": "\uf029",
"fa-barcode": "\uf02a",
"fa-tag": "\uf02b",
"fa-tags": "\uf02c",
"fa-book": "\uf02d",
"fa-bookmark": "\uf02e",
"fa-print": "\uf02f",
"fa-camera": "\uf030",
"fa-font": "\uf031",
"fa-bold": "\uf032",
"fa-italic": "\uf033",
"fa-text-height": "\uf034",
"fa-text-width": "\uf035",
"fa-align-left": "\uf036",
"fa-align-center": "\uf037",
"fa-align-right": "\uf038",
"fa-align-justify": "\uf039",
"fa-list": "\uf03a",
"fa-dedent": "\uf03b",
"fa-outdent": "\uf03b",
"fa-indent": "\uf03c",
"fa-video-camera": "\uf03d",
"fa-photo": "\uf03e",
"fa-image": "\uf03e",
"fa-picture-o": "\uf03e",
"fa-pencil": "\uf040",
"fa-map-marker": "\uf041",
"fa-adjust": "\uf042",
"fa-tint": "\uf043",
"fa-edit": "\uf044",
"fa-pencil-square-o": "\uf044",
"fa-share-square-o": "\uf045",
"fa-check-square-o": "\uf046",
"fa-arrows": "\uf047",
"fa-step-backward": "\uf048",
"fa-fast-backward": "\uf049",
"fa-backward": "\uf04a",
"fa-play": "\uf04b",
"fa-pause": "\uf04c",
"fa-stop": "\uf04d",
"fa-forward": "\uf04e",
"fa-fast-forward": "\uf050",
"fa-step-forward": "\uf051",
"fa-eject": "\uf052",
"fa-chevron-left": "\uf053",
"fa-chevron-right": "\uf054",
"fa-plus-circle": "\uf055",
"fa-minus-circle": "\uf056",
"fa-times-circle": "\uf057",
"fa-check-circle": "\uf058",
"fa-question-circle": "\uf059",
"fa-info-circle": "\uf05a",
"fa-crosshairs": "\uf05b",
"fa-times-circle-o": "\uf05c",
"fa-check-circle-o": "\uf05d",
"fa-ban": "\uf05e",
"fa-arrow-left": "\uf060",
"fa-arrow-right": "\uf061",
"fa-arrow-up": "\uf062",
"fa-arrow-down": "\uf063",
"fa-mail-forward": "\uf064",
"fa-share": "\uf064",
"fa-expand": "\uf065",
"fa-compress": "\uf066",
"fa-plus": "\uf067",
"fa-minus": "\uf068",
"fa-asterisk": "\uf069",
"fa-exclamation-circle": "\uf06a",
"fa-gift": "\uf06b",
"fa-leaf": "\uf06c",
"fa-fire": "\uf06d",
"fa-eye": "\uf06e",
"fa-eye-slash": "\uf070",
"fa-warning": "\uf071",
"fa-exclamation-triangle": "\uf071",
"fa-plane": "\uf072",
"fa-calendar": "\uf073",
"fa-random": "\uf074",
"fa-comment": "\uf075",
"fa-magnet": "\uf076",
"fa-chevron-up": "\uf077",
"fa-chevron-down": "\uf078",
"fa-retweet": "\uf079",
"fa-shopping-cart": "\uf07a",
"fa-folder": "\uf07b",
"fa-folder-open": "\uf07c",
"fa-arrows-v": "\uf07d",
"fa-arrows-h": "\uf07e",
"fa-bar-chart-o": "\uf080",
"fa-bar-chart": "\uf080",
"fa-camera-retro": "\uf083",
"fa-key": "\uf084",
"fa-gears": "\uf085",
"fa-cogs": "\uf085",
"fa-comments": "\uf086",
"fa-thumbs-o-up": "\uf087",
"fa-thumbs-o-down": "\uf088",
"fa-star-half": "\uf089",
"fa-heart-o": "\uf08a",
"fa-sign-out": "\uf08b",
"fa-thumb-tack": "\uf08d",
"fa-external-link": "\uf08e",
"fa-sign-in": "\uf090",
"fa-trophy": "\uf091",
"fa-upload": "\uf093",
"fa-lemon-o": "\uf094",
"fa-phone": "\uf095",
"fa-square-o": "\uf096",
"fa-bookmark-o": "\uf097",
"fa-phone-square": "\uf098",
"fa-unlock": "\uf09c",
"fa-credit-card": "\uf09d",
"fa-feed": "\uf09e",
"fa-rss": "\uf09e",
"fa-hdd-o": "\uf0a0",
"fa-bullhorn": "\uf0a1",
"fa-bell-o": "\uf0a2",
"fa-certificate": "\uf0a3",
"fa-hand-o-right": "\uf0a4",
"fa-hand-o-left": "\uf0a5",
"fa-hand-o-up": "\uf0a6",
"fa-hand-o-down": "\uf0a7",
"fa-arrow-circle-left": "\uf0a8",
"fa-arrow-circle-right": "\uf0a9",
"fa-arrow-circle-up": "\uf0aa",
"fa-arrow-circle-down": "\uf0ab",
"fa-globe": "\uf0ac",
"fa-wrench": "\uf0ad",
"fa-tasks": "\uf0ae",
"fa-filter": "\uf0b0",
"fa-briefcase": "\uf0b1",
"fa-arrows-alt": "\uf0b2",
"fa-group": "\uf0c0",
"fa-users": "\uf0c0",
"fa-chain": "\uf0c1",
"fa-link": "\uf0c1",
"fa-cloud": "\uf0c2",
"fa-flask": "\uf0c3",
"fa-cut": "\uf0c4",
"fa-scissors": "\uf0c4",
"fa-copy": "\uf0c5",
"fa-files-o": "\uf0c5",
"fa-paperclip": "\uf0c6",
"fa-save": "\uf0c7",
"fa-floppy-o": "\uf0c7",
"fa-square": "\uf0c8",
"fa-navicon": "\uf0c9",
"fa-reorder": "\uf0c9",
"fa-bars": "\uf0c9",
"fa-list-ul": "\uf0ca",
"fa-list-ol": "\uf0cb",
"fa-strikethrough": "\uf0cc",
"fa-underline": "\uf0cd",
"fa-table": "\uf0ce",
"fa-magic": "\uf0d0",
"fa-truck": "\uf0d1",
"fa-money": "\uf0d6",
"fa-caret-down": "\uf0d7",
"fa-caret-up": "\uf0d8",
"fa-caret-left": "\uf0d9",
"fa-caret-right": "\uf0da",
"fa-columns": "\uf0db",
"fa-unsorted": "\uf0dc",
"fa-sort": "\uf0dc",
"fa-sort-down": "\uf0dd",
"fa-sort-desc": "\uf0dd",
"fa-sort-up": "\uf0de",
"fa-sort-asc": "\uf0de",
"fa-envelope": "\uf0e0",
"fa-rotate-left": "\uf0e2",
"fa-undo": "\uf0e2",
"fa-legal": "\uf0e3",
"fa-gavel": "\uf0e3",
"fa-dashboard": "\uf0e4",
"fa-tachometer": "\uf0e4",
"fa-comment-o": "\uf0e5",
"fa-comments-o": "\uf0e6",
"fa-flash": "\uf0e7",
"fa-bolt": "\uf0e7",
"fa-sitemap": "\uf0e8",
"fa-umbrella": "\uf0e9",
"fa-paste": "\uf0ea",
"fa-clipboard": "\uf0ea",
"fa-lightbulb-o": "\uf0eb",
"fa-exchange": "\uf0ec",
"fa-cloud-download": "\uf0ed",
"fa-cloud-upload": "\uf0ee",
"fa-user-md": "\uf0f0",
"fa-stethoscope": "\uf0f1",
"fa-suitcase": "\uf0f2",
"fa-bell": "\uf0f3",
"fa-coffee": "\uf0f4",
"fa-cutlery": "\uf0f5",
"fa-file-text-o": "\uf0f6",
"fa-building-o": "\uf0f7",
"fa-hospital-o": "\uf0f8",
"fa-ambulance": "\uf0f9",
"fa-medkit": "\uf0fa",
"fa-fighter-jet": "\uf0fb",
"fa-beer": "\uf0fc",
"fa-h-square": "\uf0fd",
"fa-plus-square": "\uf0fe",
"fa-angle-double-left": "\uf100",
"fa-angle-double-right": "\uf101",
"fa-angle-double-up": "\uf102",
"fa-angle-double-down": "\uf103",
"fa-angle-left": "\uf104",
"fa-angle-right": "\uf105",
"fa-angle-up": "\uf106",
"fa-angle-down": "\uf107",
"fa-desktop": "\uf108",
"fa-laptop": "\uf109",
"fa-tablet": "\uf10a",
"fa-mobile-phone": "\uf10b",
"fa-mobile": "\uf10b",
"fa-circle-o": "\uf10c",
"fa-quote-left": "\uf10d",
"fa-quote-right": "\uf10e",
"fa-spinner": "\uf110",
"fa-circle": "\uf111",
"fa-mail-reply": "\uf112",
"fa-reply": "\uf112",
"fa-folder-o": "\uf114",
"fa-folder-open-o": "\uf115",
"fa-smile-o": "\uf118",
"fa-frown-o": "\uf119",
"fa-meh-o": "\uf11a",
"fa-gamepad": "\uf11b",
"fa-keyboard-o": "\uf11c",
"fa-flag-o": "\uf11d",
"fa-flag-checkered": "\uf11e",
"fa-terminal": "\uf120",
"fa-code": "\uf121",
"fa-mail-reply-all": "\uf122",
"fa-reply-all": "\uf122",
"fa-star-half-empty": "\uf123",
"fa-star-half-full": "\uf123",
"fa-star-half-o": "\uf123",
"fa-location-arrow": "\uf124",
"fa-crop": "\uf125",
"fa-code-fork": "\uf126",
"fa-unlink": "\uf127",
"fa-chain-broken": "\uf127",
"fa-question": "\uf128",
"fa-info": "\uf129",
"fa-exclamation": "\uf12a",
"fa-superscript": "\uf12b",
"fa-subscript": "\uf12c",
"fa-eraser": "\uf12d",
"fa-puzzle-piece": "\uf12e",
"fa-microphone": "\uf130",
"fa-microphone-slash": "\uf131",
"fa-shield": "\uf132",
"fa-calendar-o": "\uf133",
"fa-fire-extinguisher": "\uf134",
"fa-rocket": "\uf135",
"fa-chevron-circle-left": "\uf137",
"fa-chevron-circle-right": "\uf138",
"fa-chevron-circle-up": "\uf139",
"fa-chevron-circle-down": "\uf13a",
"fa-anchor": "\uf13d",
"fa-unlock-alt": "\uf13e",
"fa-bullseye": "\uf140",
"fa-ellipsis-h": "\uf141",
"fa-ellipsis-v": "\uf142",
"fa-rss-square": "\uf143",
"fa-play-circle": "\uf144",
"fa-ticket": "\uf145",
"fa-minus-square": "\uf146",
"fa-minus-square-o": "\uf147",
"fa-level-up": "\uf148",
"fa-level-down": "\uf149",
"fa-check-square": "\uf14a",
"fa-pencil-square": "\uf14b",
"fa-external-link-square": "\uf14c",
"fa-share-square": "\uf14d",
"fa-compass": "\uf14e",
"fa-toggle-down": "\uf150",
"fa-caret-square-o-down": "\uf150",
"fa-toggle-up": "\uf151",
"fa-caret-square-o-up": "\uf151",
"fa-toggle-right": "\uf152",
"fa-caret-square-o-right": "\uf152",
"fa-euro": "\uf153",
"fa-eur": "\uf153",
"fa-gbp": "\uf154",
"fa-dollar": "\uf155",
"fa-usd": "\uf155",
"fa-rupee": "\uf156",
"fa-inr": "\uf156",
"fa-cny": "\uf157",
"fa-rmb": "\uf157",
"fa-yen": "\uf157",
"fa-jpy": "\uf157",
"fa-ruble": "\uf158",
"fa-rouble": "\uf158",
"fa-rub": "\uf158",
"fa-won": "\uf159",
"fa-krw": "\uf159",
"fa-file": "\uf15b",
"fa-file-text": "\uf15c",
"fa-sort-alpha-asc": "\uf15d",
"fa-sort-alpha-desc": "\uf15e",
"fa-sort-amount-asc": "\uf160",
"fa-sort-amount-desc": "\uf161",
"fa-sort-numeric-asc": "\uf162",
"fa-sort-numeric-desc": "\uf163",
"fa-thumbs-up": "\uf164",
"fa-thumbs-down": "\uf165",
"fa-long-arrow-down": "\uf175",
"fa-long-arrow-up": "\uf176",
"fa-long-arrow-left": "\uf177",
"fa-long-arrow-right": "\uf178",
"fa-female": "\uf182",
"fa-male": "\uf183",
"fa-sun-o": "\uf185",
"fa-moon-o": "\uf186",
"fa-archive": "\uf187",
"fa-bug": "\uf188",
"fa-arrow-circle-o-right": "\uf18e",
"fa-arrow-circle-o-left": "\uf190",
"fa-toggle-left": "\uf191",
"fa-caret-square-o-left": "\uf191",
"fa-dot-circle-o": "\uf192",
"fa-wheelchair": "\uf193",
"fa-turkish-lira": "\uf195",
"fa-try": "\uf195",
"fa-plus-square-o": "\uf196",
"fa-space-shuttle": "\uf197",
"fa-envelope-square": "\uf199",
"fa-institution": "\uf19c",
"fa-bank": "\uf19c",
"fa-university": "\uf19c",
"fa-mortar-board": "\uf19d",
"fa-graduation-cap": "\uf19d",
"fa-language": "\uf1ab",
"fa-fax": "\uf1ac",
"fa-building": "\uf1ad",
"fa-child": "\uf1ae",
"fa-paw": "\uf1b0",
"fa-spoon": "\uf1b1",
"fa-cube": "\uf1b2",
"fa-cubes": "\uf1b3",
"fa-recycle": "\uf1b8",
"fa-automobile": "\uf1b9",
"fa-car": "\uf1b9",
"fa-cab": "\uf1ba",
"fa-taxi": "\uf1ba",
"fa-tree": "\uf1bb",
"fa-database": "\uf1c0",
"fa-file-pdf-o": "\uf1c1",
"fa-file-word-o": "\uf1c2",
"fa-file-excel-o": "\uf1c3",
"fa-file-powerpoint-o": "\uf1c4",
"fa-file-photo-o": "\uf1c5",
"fa-file-picture-o": "\uf1c5",
"fa-file-image-o": "\uf1c5",
"fa-file-zip-o": "\uf1c6",
"fa-file-archive-o": "\uf1c6",
"fa-file-sound-o": "\uf1c7",
"fa-file-audio-o": "\uf1c7",
"fa-file-movie-o": "\uf1c8",
"fa-file-video-o": "\uf1c8",
"fa-file-code-o": "\uf1c9",
"fa-life-bouy": "\uf1cd",
"fa-life-buoy": "\uf1cd",
"fa-life-saver": "\uf1cd",
"fa-support": "\uf1cd",
"fa-life-ring": "\uf1cd",
"fa-circle-o-notch": "\uf1ce",
"fa-send": "\uf1d8",
"fa-paper-plane": "\uf1d8",
"fa-send-o": "\uf1d9",
"fa-paper-plane-o": "\uf1d9",
"fa-history": "\uf1da",
"fa-circle-thin": "\uf1db",
"fa-header": "\uf1dc",
"fa-paragraph": "\uf1dd",
"fa-sliders": "\uf1de",
"fa-bomb": "\uf1e2",
"fa-soccer-ball-o": "\uf1e3",
"fa-futbol-o": "\uf1e3",
"fa-tty": "\uf1e4",
"fa-binoculars": "\uf1e5",
"fa-plug": "\uf1e6",
"fa-newspaper-o": "\uf1ea",
"fa-wifi": "\uf1eb",
"fa-calculator": "\uf1ec",
"fa-bell-slash": "\uf1f6",
"fa-bell-slash-o": "\uf1f7",
"fa-trash": "\uf1f8",
"fa-copyright": "\uf1f9",
"fa-at": "\uf1fa",
"fa-eyedropper": "\uf1fb",
"fa-paint-brush": "\uf1fc",
"fa-birthday-cake": "\uf1fd",
"fa-area-chart": "\uf1fe",
"fa-pie-chart": "\uf200",
"fa-line-chart": "\uf201",
"fa-toggle-off": "\uf204",
"fa-toggle-on": "\uf205",
"fa-bicycle": "\uf206",
"fa-bus": "\uf207",
"fa-cc": "\uf20a",
"fa-shekel": "\uf20b",
"fa-sheqel": "\uf20b",
"fa-ils": "\uf20b",
"fa-cart-plus": "\uf217",
"fa-cart-arrow-down": "\uf218",
"fa-diamond": "\uf219",
"fa-ship": "\uf21a",
"fa-user-secret": "\uf21b",
"fa-motorcycle": "\uf21c",
"fa-street-view": "\uf21d",
"fa-heartbeat": "\uf21e",
"fa-venus": "\uf221",
"fa-mars": "\uf222",
"fa-mercury": "\uf223",
"fa-intersex": "\uf224",
"fa-transgender": "\uf224",
"fa-transgender-alt": "\uf225",
"fa-venus-double": "\uf226",
"fa-mars-double": "\uf227",
"fa-venus-mars": "\uf228",
"fa-mars-stroke": "\uf229",
"fa-mars-stroke-v": "\uf22a",
"fa-mars-stroke-h": "\uf22b",
"fa-neuter": "\uf22c",
"fa-genderless": "\uf22d",
"fa-server": "\uf233",
"fa-user-plus": "\uf234",
"fa-user-times": "\uf235",
"fa-hotel": "\uf236",
"fa-bed": "\uf236",
"fa-train": "\uf238",
"fa-subway": "\uf239",
"fa-battery-4": "\uf240",
"fa-battery": "\uf240",
"fa-battery-full": "\uf240",
"fa-battery-3": "\uf241",
"fa-battery-three-quarters": "\uf241",
"fa-battery-2": "\uf242",
"fa-battery-half": "\uf242",
"fa-battery-1": "\uf243",
"fa-battery-quarter": "\uf243",
"fa-battery-0": "\uf244",
"fa-battery-empty": "\uf244",
"fa-mouse-pointer": "\uf245",
"fa-i-cursor": "\uf246",
"fa-object-group": "\uf247",
"fa-object-ungroup": "\uf248",
"fa-sticky-note": "\uf249",
"fa-sticky-note-o": "\uf24a",
"fa-clone": "\uf24d",
"fa-balance-scale": "\uf24e",
"fa-hourglass-o": "\uf250",
"fa-hourglass-1": "\uf251",
"fa-hourglass-start": "\uf251",
"fa-hourglass-2": "\uf252",
"fa-hourglass-half": "\uf252",
"fa-hourglass-3": "\uf253",
"fa-hourglass-end": "\uf253",
"fa-hourglass": "\uf254",
"fa-hand-grab-o": "\uf255",
"fa-hand-rock-o": "\uf255",
"fa-hand-stop-o": "\uf256",
"fa-hand-paper-o": "\uf256",
"fa-hand-scissors-o": "\uf257",
"fa-hand-lizard-o": "\uf258",
"fa-hand-spock-o": "\uf259",
"fa-hand-pointer-o": "\uf25a",
"fa-hand-peace-o": "\uf25b",
"fa-trademark": "\uf25c",
"fa-registered": "\uf25d",
"fa-creative-commons": "\uf25e",
"fa-tv": "\uf26c",
"fa-television": "\uf26c",
"fa-calendar-plus-o": "\uf271",
"fa-calendar-minus-o": "\uf272",
"fa-calendar-times-o": "\uf273",
"fa-calendar-check-o": "\uf274",
"fa-industry": "\uf275",
"fa-map-pin": "\uf276",
"fa-map-signs": "\uf277",
"fa-map-o": "\uf278",
"fa-map": "\uf279",
"fa-commenting": "\uf27a",
"fa-commenting-o": "\uf27b",
"fa-credit-card-alt": "\uf283",
"fa-pause-circle": "\uf28b",
"fa-pause-circle-o": "\uf28c",
"fa-stop-circle": "\uf28d",
"fa-stop-circle-o": "\uf28e",
"fa-shopping-bag": "\uf290",
"fa-shopping-basket": "\uf291",
"fa-hashtag": "\uf292",
"fa-percent": "\uf295",
"fa-universal-access": "\uf29a",
"fa-wheelchair-alt": "\uf29b",
"fa-question-circle-o": "\uf29c",
"fa-blind": "\uf29d",
"fa-audio-description": "\uf29e",
"fa-volume-control-phone": "\uf2a0",
"fa-braille": "\uf2a1",
"fa-assistive-listening-systems": "\uf2a2",
"fa-asl-interpreting": "\uf2a3",
"fa-american-sign-language-interpreting": "\uf2a3",
"fa-deafness": "\uf2a4",
"fa-hard-of-hearing": "\uf2a4",
"fa-deaf": "\uf2a4",
"fa-signing": "\uf2a7",
"fa-sign-language": "\uf2a7",
"fa-low-vision": "\uf2a8",
"fa-handshake-o": "\uf2b5",
"fa-envelope-open": "\uf2b6",
"fa-envelope-open-o": "\uf2b7",
"fa-address-book": "\uf2b9",
"fa-address-book-o": "\uf2ba",
"fa-vcard": "\uf2bb",
"fa-address-card": "\uf2bb",
"fa-vcard-o": "\uf2bc",
"fa-address-card-o": "\uf2bc",
"fa-user-circle": "\uf2bd",
"fa-user-circle-o": "\uf2be",
"fa-user-o": "\uf2c0",
"fa-id-badge": "\uf2c1",
"fa-drivers-license": "\uf2c2",
"fa-id-card": "\uf2c2",
"fa-drivers-license-o": "\uf2c3",
"fa-id-card-o": "\uf2c3",
"fa-thermometer-4": "\uf2c7",
"fa-thermometer": "\uf2c7",
"fa-thermometer-full": "\uf2c7",
"fa-thermometer-3": "\uf2c8",
"fa-thermometer-three-quarters": "\uf2c8",
"fa-thermometer-2": "\uf2c9",
"fa-thermometer-half": "\uf2c9",
"fa-thermometer-1": "\uf2ca",
"fa-thermometer-quarter": "\uf2ca",
"fa-thermometer-0": "\uf2cb",
"fa-thermometer-empty": "\uf2cb",
"fa-shower": "\uf2cc",
"fa-bathtub": "\uf2cd",
"fa-s15": "\uf2cd",
"fa-bath": "\uf2cd",
"fa-podcast": "\uf2ce",
"fa-window-maximize": "\uf2d0",
"fa-window-minimize": "\uf2d1",
"fa-window-restore": "\uf2d2",
"fa-times-rectangle": "\uf2d3",
"fa-window-close": "\uf2d3",
"fa-times-rectangle-o": "\uf2d4",
"fa-window-close-o": "\uf2d4",
"fa-microchip": "\uf2db",
"fa-snowflake-o": "\uf2dc"
};
var brandIconMap = {
"fa-twitter-square": "\uf081",
"fa-facebook-square": "\uf082",
"fa-linkedin-square": "\uf08c",
"fa-github-square": "\uf092",
"fa-twitter": "\uf099",
"fa-facebook-f": "\uf09a",
"fa-facebook": "\uf09a",
"fa-github": "\uf09b",
"fa-pinterest": "\uf0d2",
"fa-pinterest-square": "\uf0d3",
"fa-google-plus-square": "\uf0d4",
"fa-google-plus": "\uf0d5",
"fa-linkedin": "\uf0e1",
"fa-github-alt": "\uf113",
"fa-maxcdn": "\uf136",
"fa-html5": "\uf13b",
"fa-css3": "\uf13c",
"fa-btc": "\uf15a",
"fa-bitcoin": "\uf15a",
"fa-youtube-square": "\uf166",
"fa-youtube": "\uf167",
"fa-xing": "\uf168",
"fa-xing-square": "\uf169",
"fa-youtube-play": "\uf16a",
"fa-dropbox": "\uf16b",
"fa-stack-overflow": "\uf16c",
"fa-instagram": "\uf16d",
"fa-flickr": "\uf16e",
"fa-adn": "\uf170",
"fa-bitbucket": "\uf171",
"fa-bitbucket-square": "\uf172",
"fa-tumblr": "\uf173",
"fa-tumblr-square": "\uf174",
"fa-apple": "\uf179",
"fa-windows": "\uf17a",
"fa-android": "\uf17b",
"fa-linux": "\uf17c",
"fa-dribbble": "\uf17d",
"fa-skype": "\uf17e",
"fa-foursquare": "\uf180",
"fa-trello": "\uf181",
"fa-gittip": "\uf184",
"fa-gratipay": "\uf184",
"fa-vk": "\uf189",
"fa-weibo": "\uf18a",
"fa-renren": "\uf18b",
"fa-pagelines": "\uf18c",
"fa-stack-exchange": "\uf18d",
"fa-vimeo-square": "\uf194",
"fa-slack": "\uf198",
"fa-wordpress": "\uf19a",
"fa-openid": "\uf19b",
"fa-yahoo": "\uf19e",
"fa-google": "\uf1a0",
"fa-reddit": "\uf1a1",
"fa-reddit-square": "\uf1a2",
"fa-stumbleupon-circle": "\uf1a3",
"fa-stumbleupon": "\uf1a4",
"fa-delicious": "\uf1a5",
"fa-digg": "\uf1a6",
"fa-pied-piper-pp": "\uf1a7",
"fa-pied-piper-alt": "\uf1a8",
"fa-drupal": "\uf1a9",
"fa-joomla": "\uf1aa",
"fa-behance": "\uf1b4",
"fa-behance-square": "\uf1b5",
"fa-steam": "\uf1b6",
"fa-steam-square": "\uf1b7",
"fa-spotify": "\uf1bc",
"fa-deviantart": "\uf1bd",
"fa-soundcloud": "\uf1be",
"fa-vine": "\uf1ca",
"fa-codepen": "\uf1cb",
"fa-jsfiddle": "\uf1cc",
"fa-ra": "\uf1d0",
"fa-rebel": "\uf1d0",
"fa-resistance": "\uf1d0",
"fa-ge": "\uf1d1",
"fa-empire": "\uf1d1",
"fa-git-square": "\uf1d2",
"fa-git": "\uf1d3",
"fa-yc-square": "\uf1d4",
"fa-hacker-news": "\uf1d4",
"fa-y-combinator-square": "\uf1d4",
"fa-tencent-weibo": "\uf1d5",
"fa-qq": "\uf1d6",
"fa-wechat": "\uf1d7",
"fa-weixin": "\uf1d7",
"fa-share-alt": "\uf1e0",
"fa-share-alt-square": "\uf1e1",
"fa-slideshare": "\uf1e7",
"fa-twitch": "\uf1e8",
"fa-yelp": "\uf1e9",
"fa-paypal": "\uf1ed",
"fa-google-wallet": "\uf1ee",
"fa-cc-visa": "\uf1f0",
"fa-cc-mastercard": "\uf1f1",
"fa-cc-discover": "\uf1f2",
"fa-cc-amex": "\uf1f3",
"fa-cc-paypal": "\uf1f4",
"fa-cc-stripe": "\uf1f5",
"fa-lastfm": "\uf202",
"fa-lastfm-square": "\uf203",
"fa-ioxhost": "\uf208",
"fa-angellist": "\uf209",
"fa-meanpath": "\uf20c",
"fa-buysellads": "\uf20d",
"fa-connectdevelop": "\uf20e",
"fa-dashcube": "\uf210",
"fa-forumbee": "\uf211",
"fa-leanpub": "\uf212",
"fa-sellsy": "\uf213",
"fa-shirtsinbulk": "\uf214",
"fa-simplybuilt": "\uf215",
"fa-skyatlas": "\uf216",
"fa-facebook-official": "\uf230",
"fa-pinterest-p": "\uf231",
"fa-whatsapp": "\uf232",
"fa-viacoin": "\uf237",
"fa-medium": "\uf23a",
"fa-yc": "\uf23b",
"fa-y-combinator": "\uf23b",
"fa-optin-monster": "\uf23c",
"fa-opencart": "\uf23d",
"fa-expeditedssl": "\uf23e",
"fa-cc-jcb": "\uf24b",
"fa-cc-diners-club": "\uf24c",
"fa-gg": "\uf260",
"fa-gg-circle": "\uf261",
"fa-tripadvisor": "\uf262",
"fa-odnoklassniki": "\uf263",
"fa-odnoklassniki-square": "\uf264",
"fa-get-pocket": "\uf265",
"fa-wikipedia-w": "\uf266",
"fa-safari": "\uf267",
"fa-chrome": "\uf268",
"fa-firefox": "\uf269",
"fa-opera": "\uf26a",
"fa-internet-explorer": "\uf26b",
"fa-contao": "\uf26d",
"fa-500px": "\uf26e",
"fa-amazon": "\uf270",
"fa-houzz": "\uf27c",
"fa-vimeo": "\uf27d",
"fa-black-tie": "\uf27e",
"fa-fonticons": "\uf280",
"fa-reddit-alien": "\uf281",
"fa-edge": "\uf282",
"fa-codiepie": "\uf284",
"fa-modx": "\uf285",
"fa-fort-awesome": "\uf286",
"fa-usb": "\uf287",
"fa-product-hunt": "\uf288",
"fa-mixcloud": "\uf289",
"fa-scribd": "\uf28a",
"fa-bluetooth": "\uf293",
"fa-bluetooth-b": "\uf294",
"fa-gitlab": "\uf296",
"fa-wpbeginner": "\uf297",
"fa-wpforms": "\uf298",
"fa-envira": "\uf299",
"fa-glide": "\uf2a5",
"fa-glide-g": "\uf2a6",
"fa-viadeo": "\uf2a9",
"fa-viadeo-square": "\uf2aa",
"fa-snapchat": "\uf2ab",
"fa-snapchat-ghost": "\uf2ac",
"fa-snapchat-square": "\uf2ad",
"fa-pied-piper": "\uf2ae",
"fa-first-order": "\uf2b0",
"fa-yoast": "\uf2b1",
"fa-themeisle": "\uf2b2",
"fa-google-plus-circle": "\uf2b3",
"fa-google-plus-official": "\uf2b3",
"fa-fa": "\uf2b4",
"fa-font-awesome": "\uf2b4",
"fa-linode": "\uf2b8",
"fa-quora": "\uf2c4",
"fa-free-code-camp": "\uf2c5",
"fa-telegram": "\uf2c6",
"fa-bandcamp": "\uf2d5",
"fa-grav": "\uf2d6",
"fa-etsy": "\uf2d7",
"fa-imdb": "\uf2d8",
"fa-ravelry": "\uf2d9",
"fa-eercast": "\uf2da",
"fa-superpowers": "\uf2dd",
"fa-wpexplorer": "\uf2de",
"fa-meetup": "\uf2e0"
};
var iconList = [];
var isUsed = {};
Object.keys(iconMap).forEach(function(icon) {
var unicode = iconMap[icon];
// skip icons with a same unicode
if (isUsed[unicode] !== true) {
iconList.push(icon);
isUsed[unicode] = true;
}
});
isUsed = undefined;
return {
getIconUnicode: function(name) {
return iconMap[name] || brandIconMap[name];
},
getIconList: function() {
return iconList;
},
}
})();

View File

@@ -0,0 +1,332 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
RED.history = (function() {
var undo_history = [];
function undoEvent(ev) {
var i;
var len;
var node;
var subflow;
var modifiedTabs = {};
if (ev) {
if (ev.t == 'multi') {
len = ev.events.length;
for (i=len-1;i>=0;i--) {
undoEvent(ev.events[i]);
}
} else if (ev.t == 'replace') {
RED.nodes.clear();
var imported = RED.nodes.import(ev.config);
imported[0].forEach(function(n) {
if (ev.changed[n.id]) {
n.changed = true;
}
})
RED.nodes.version(ev.rev);
} else if (ev.t == 'add') {
if (ev.nodes) {
for (i=0;i<ev.nodes.length;i++) {
node = RED.nodes.node(ev.nodes[i]);
if (node.z) {
modifiedTabs[node.z] = true;
}
RED.nodes.remove(ev.nodes[i]);
}
}
if (ev.links) {
for (i=0;i<ev.links.length;i++) {
RED.nodes.removeLink(ev.links[i]);
}
}
if (ev.workspaces) {
for (i=0;i<ev.workspaces.length;i++) {
RED.nodes.removeWorkspace(ev.workspaces[i].id);
RED.workspaces.remove(ev.workspaces[i]);
}
}
if (ev.subflows) {
for (i=0;i<ev.subflows.length;i++) {
RED.nodes.removeSubflow(ev.subflows[i]);
RED.workspaces.remove(ev.subflows[i]);
}
}
if (ev.subflow) {
if (ev.subflow.instances) {
ev.subflow.instances.forEach(function(n) {
var node = RED.nodes.node(n.id);
if (node) {
node.changed = n.changed;
node.dirty = true;
}
});
}
if (ev.subflow.hasOwnProperty('changed')) {
subflow = RED.nodes.subflow(ev.subflow.id);
if (subflow) {
subflow.changed = ev.subflow.changed;
}
}
}
if (ev.removedLinks) {
for (i=0;i<ev.removedLinks.length;i++) {
RED.nodes.addLink(ev.removedLinks[i]);
}
}
} else if (ev.t == "delete") {
if (ev.workspaces) {
for (i=0;i<ev.workspaces.length;i++) {
RED.nodes.addWorkspace(ev.workspaces[i]);
RED.workspaces.add(ev.workspaces[i]);
}
}
if (ev.subflow && ev.subflow.subflow) {
RED.nodes.addSubflow(ev.subflow.subflow);
}
if (ev.subflowInputs && ev.subflowInputs.length > 0) {
subflow = RED.nodes.subflow(ev.subflowInputs[0].z);
subflow.in.push(ev.subflowInputs[0]);
subflow.in[0].dirty = true;
}
if (ev.subflowOutputs && ev.subflowOutputs.length > 0) {
subflow = RED.nodes.subflow(ev.subflowOutputs[0].z);
ev.subflowOutputs.sort(function(a,b) { return a.i-b.i});
for (i=0;i<ev.subflowOutputs.length;i++) {
var output = ev.subflowOutputs[i];
subflow.out.splice(output.i,0,output);
for (var j=output.i+1;j<subflow.out.length;j++) {
subflow.out[j].i++;
subflow.out[j].dirty = true;
}
RED.nodes.eachLink(function(l) {
if (l.source.type == "subflow:"+subflow.id) {
if (l.sourcePort >= output.i) {
l.sourcePort++;
}
}
});
}
}
if (ev.subflow && ev.subflow.hasOwnProperty('instances')) {
ev.subflow.instances.forEach(function(n) {
var node = RED.nodes.node(n.id);
if (node) {
node.changed = n.changed;
node.dirty = true;
}
});
}
if (subflow) {
RED.nodes.filterNodes({type:"subflow:"+subflow.id}).forEach(function(n) {
n.inputs = subflow.in.length;
n.outputs = subflow.out.length;
while (n.outputs > n.ports.length) {
n.ports.push(n.ports.length);
}
n.resize = true;
n.dirty = true;
});
}
if (ev.nodes) {
for (i=0;i<ev.nodes.length;i++) {
RED.nodes.add(ev.nodes[i]);
modifiedTabs[ev.nodes[i].z] = true;
}
}
if (ev.links) {
for (i=0;i<ev.links.length;i++) {
RED.nodes.addLink(ev.links[i]);
}
}
if (ev.changes) {
for (i in ev.changes) {
if (ev.changes.hasOwnProperty(i)) {
node = RED.nodes.node(i);
if (node) {
for (var d in ev.changes[i]) {
if (ev.changes[i].hasOwnProperty(d)) {
node[d] = ev.changes[i][d];
}
}
node.dirty = true;
}
}
}
}
} else if (ev.t == "move") {
for (i=0;i<ev.nodes.length;i++) {
var n = ev.nodes[i];
n.n.x = n.ox;
n.n.y = n.oy;
n.n.dirty = true;
n.n.moved = n.moved;
}
// A move could have caused a link splice
if (ev.links) {
for (i=0;i<ev.links.length;i++) {
RED.nodes.removeLink(ev.links[i]);
}
}
if (ev.removedLinks) {
for (i=0;i<ev.removedLinks.length;i++) {
RED.nodes.addLink(ev.removedLinks[i]);
}
}
} else if (ev.t == "edit") {
for (i in ev.changes) {
if (ev.changes.hasOwnProperty(i)) {
if (ev.node._def.defaults && ev.node._def.defaults[i] && ev.node._def.defaults[i].type) {
// This is a config node property
var currentConfigNode = RED.nodes.node(ev.node[i]);
if (currentConfigNode) {
currentConfigNode.users.splice(currentConfigNode.users.indexOf(ev.node),1);
}
var newConfigNode = RED.nodes.node(ev.changes[i]);
if (newConfigNode) {
newConfigNode.users.push(ev.node);
}
}
ev.node[i] = ev.changes[i];
}
}
if (ev.subflow) {
if (ev.subflow.hasOwnProperty('inputCount')) {
if (ev.node.in.length > ev.subflow.inputCount) {
ev.node.in.splice(ev.subflow.inputCount);
} else if (ev.subflow.inputs.length > 0) {
ev.node.in = ev.node.in.concat(ev.subflow.inputs);
}
}
if (ev.subflow.hasOwnProperty('outputCount')) {
if (ev.node.out.length > ev.subflow.outputCount) {
ev.node.out.splice(ev.subflow.outputCount);
} else if (ev.subflow.outputs.length > 0) {
ev.node.out = ev.node.out.concat(ev.subflow.outputs);
}
}
if (ev.subflow.hasOwnProperty('instances')) {
ev.subflow.instances.forEach(function(n) {
var node = RED.nodes.node(n.id);
if (node) {
node.changed = n.changed;
node.dirty = true;
}
});
}
RED.editor.validateNode(ev.node);
RED.nodes.filterNodes({type:"subflow:"+ev.node.id}).forEach(function(n) {
n.inputs = ev.node.in.length;
n.outputs = ev.node.out.length;
RED.editor.updateNodeProperties(n);
RED.editor.validateNode(n);
});
} else {
var outputMap;
if (ev.outputMap) {
outputMap = {};
for (var port in ev.outputMap) {
if (ev.outputMap.hasOwnProperty(port) && ev.outputMap[port] !== "-1") {
outputMap[ev.outputMap[port]] = port;
}
}
}
RED.editor.updateNodeProperties(ev.node,outputMap);
RED.editor.validateNode(ev.node);
}
if (ev.links) {
for (i=0;i<ev.links.length;i++) {
RED.nodes.addLink(ev.links[i]);
}
}
ev.node.dirty = true;
ev.node.changed = ev.changed;
} else if (ev.t == "createSubflow") {
if (ev.nodes) {
RED.nodes.filterNodes({z:ev.subflow.subflow.id}).forEach(function(n) {
n.z = ev.activeWorkspace;
n.dirty = true;
});
for (i=0;i<ev.nodes.length;i++) {
RED.nodes.remove(ev.nodes[i]);
}
}
if (ev.links) {
for (i=0;i<ev.links.length;i++) {
RED.nodes.removeLink(ev.links[i]);
}
}
RED.nodes.removeSubflow(ev.subflow.subflow);
RED.workspaces.remove(ev.subflow.subflow);
if (ev.removedLinks) {
for (i=0;i<ev.removedLinks.length;i++) {
RED.nodes.addLink(ev.removedLinks[i]);
}
}
} else if (ev.t == "reorder") {
if (ev.order) {
RED.workspaces.order(ev.order);
}
}
Object.keys(modifiedTabs).forEach(function(id) {
var subflow = RED.nodes.subflow(id);
if (subflow) {
RED.editor.validateNode(subflow);
}
});
RED.nodes.dirty(ev.dirty);
RED.view.redraw(true);
RED.palette.refresh();
RED.workspaces.refresh();
RED.sidebar.config.refresh();
}
}
return {
//TODO: this function is a placeholder until there is a 'save' event that can be listened to
markAllDirty: function() {
for (var i=0;i<undo_history.length;i++) {
undo_history[i].dirty = true;
}
},
list: function() {
return undo_history
},
depth: function() {
return undo_history.length;
},
push: function(ev) {
undo_history.push(ev);
},
pop: function() {
var ev = undo_history.pop();
undoEvent(ev);
},
peek: function() {
return undo_history[undo_history.length-1];
},
clear: function() {
undo_history = [];
}
}
})();

View File

@@ -0,0 +1,89 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
RED.i18n = (function() {
var apiRootUrl;
return {
init: function(options, done) {
apiRootUrl = options.apiRootUrl||"";
i18n.init({
resGetPath: apiRootUrl+'locales/__ns__?lng=__lng__',
dynamicLoad: false,
load:'current',
ns: {
namespaces: ["editor","node-red","jsonata","infotips"],
defaultNs: "editor"
},
fallbackLng: ['en-US'],
useCookie: false
},function() {
done();
});
RED["_"] = function() {
return i18n.t.apply(null,arguments);
}
},
loadNodeCatalog: function(namespace,done) {
var languageList = i18n.functions.toLanguages(i18n.detectLanguage());
var toLoad = languageList.length;
languageList.forEach(function(lang) {
$.ajax({
headers: {
"Accept":"application/json"
},
cache: false,
url: apiRootUrl+'nodes/'+namespace+'/messages?lng='+lang,
success: function(data) {
i18n.addResourceBundle(lang,namespace,data);
toLoad--;
if (toLoad === 0) {
done();
}
}
});
})
},
loadNodeCatalogs: function(done) {
var languageList = i18n.functions.toLanguages(i18n.detectLanguage());
var toLoad = languageList.length;
languageList.forEach(function(lang) {
$.ajax({
headers: {
"Accept":"application/json"
},
cache: false,
url: apiRootUrl+'nodes/messages?lng='+lang,
success: function(data) {
var namespaces = Object.keys(data);
namespaces.forEach(function(ns) {
i18n.addResourceBundle(lang,ns,data[ns]);
});
toLoad--;
if (toLoad === 0) {
done();
}
}
});
})
}
}
})();

View File

@@ -0,0 +1,46 @@
{
"*": {
"ctrl-shift-p":"core:manage-palette",
"ctrl-f": "core:search",
"ctrl-=": "core:zoom-in",
"ctrl--": "core:zoom-out",
"ctrl-0": "core:zoom-reset",
"ctrl-enter": "core:confirm-edit-tray",
"ctrl-escape": "core:cancel-edit-tray",
"ctrl-g i": "core:show-info-tab",
"ctrl-g d": "core:show-debug-tab",
"ctrl-g c": "core:show-config-tab",
"ctrl-g x": "core:show-context-tab",
"ctrl-e": "core:show-export-dialog",
"ctrl-i": "core:show-import-dialog",
"ctrl-space": "core:toggle-sidebar",
"ctrl-p": "core:toggle-palette",
"ctrl-,": "core:show-user-settings",
"ctrl-alt-r": "core:show-remote-diff",
"ctrl-alt-n": "core:new-project",
"ctrl-alt-o": "core:open-project",
"ctrl-g v": "core:show-version-control-tab",
"ctrl-shift-l": "core:show-event-log"
},
"workspace": {
"backspace": "core:delete-selection",
"delete": "core:delete-selection",
"enter": "core:edit-selected-node",
"ctrl-c": "core:copy-selection-to-internal-clipboard",
"ctrl-x": "core:cut-selection-to-internal-clipboard",
"ctrl-v": "core:paste-from-internal-clipboard",
"ctrl-z": "core:undo",
"ctrl-a": "core:select-all-nodes",
"shift-?": "core:show-help",
"up": "core:move-selection-up",
"right": "core:move-selection-right",
"down": "core:move-selection-down",
"left": "core:move-selection-left",
"shift-up": "core:step-selection-up",
"shift-right": "core:step-selection-right",
"shift-down": "core:step-selection-down",
"shift-left": "core:step-selection-left",
"ctrl-shift-j": "core:show-previous-tab",
"ctrl-shift-k": "core:show-next-tab"
}
}

View File

@@ -0,0 +1,24 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
$(function() {
if ((window.location.hostname !== "localhost") && (window.location.hostname !== "127.0.0.1")) {
document.title = document.title+" : "+window.location.hostname;
}
RED.init({
apiRootUrl: ""
});
});

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,549 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var RED = (function() {
function appendNodeConfig(nodeConfig,done) {
done = done || function(){};
var m = /<!-- --- \[red-module:(\S+)\] --- -->/.exec(nodeConfig.trim());
var moduleId;
if (m) {
moduleId = m[1];
} else {
moduleId = "unknown";
}
try {
var hasDeferred = false;
var nodeConfigEls = $("<div>"+nodeConfig+"</div>");
nodeConfigEls.find("script").each(function(i,el) {
var srcUrl = $(el).attr('src');
if (srcUrl && !/^\s*(https?:|\/|\.)/.test(srcUrl)) {
$(el).remove();
var newScript = document.createElement("script");
newScript.onload = function() { $("body").append(nodeConfigEls); done() }
$('body').append(newScript);
newScript.src = RED.settings.apiRootUrl+srcUrl;
hasDeferred = true;
}
})
if (!hasDeferred) {
$("body").append(nodeConfigEls);
done();
}
} catch(err) {
RED.notify(RED._("notification.errors.failedToAppendNode",{module:moduleId, error:err.toString()}),{
type: "error",
timeout: 10000
});
console.log("["+moduleId+"] "+err.toString());
done();
}
}
function loadNodeList() {
$.ajax({
headers: {
"Accept":"application/json"
},
cache: false,
url: 'nodes',
success: function(data) {
RED.nodes.setNodeList(data);
RED.i18n.loadNodeCatalogs(function() {
loadIconList(loadNodes);
});
}
});
}
function loadIconList(done) {
$.ajax({
headers: {
"Accept":"application/json"
},
cache: false,
url: 'icons',
success: function(data) {
RED.nodes.setIconSets(data);
if (done) {
done();
}
}
});
}
function loadNodes() {
$.ajax({
headers: {
"Accept":"text/html"
},
cache: false,
url: 'nodes',
success: function(data) {
var configs = data.trim().split(/(?=<!-- --- \[red-module:\S+\] --- -->)/);
var stepConfig = function() {
if (configs.length === 0) {
$("body").i18n();
$("#palette > .palette-spinner").hide();
$(".palette-scroll").removeClass("hide");
$("#palette-search").removeClass("hide");
loadFlows(function() {
if (RED.settings.theme("projects.enabled",false)) {
RED.projects.refresh(function(activeProject) {
RED.sidebar.info.refresh()
if (!activeProject) {
// Projects enabled but no active project
RED.menu.setDisabled('menu-item-projects-open',true);
RED.menu.setDisabled('menu-item-projects-settings',true);
if (activeProject === false) {
// User previously decline the migration to projects.
} else { // null/undefined
RED.projects.showStartup();
}
}
completeLoad();
});
} else {
// Projects disabled by the user
RED.sidebar.info.refresh()
completeLoad();
}
});
} else {
var config = configs.shift();
appendNodeConfig(config,stepConfig);
}
}
stepConfig();
}
});
}
function loadFlows(done) {
$.ajax({
headers: {
"Accept":"application/json",
},
cache: false,
url: 'flows',
success: function(nodes) {
if (nodes) {
var currentHash = window.location.hash;
RED.nodes.version(nodes.rev);
RED.nodes.import(nodes.flows);
RED.nodes.dirty(false);
RED.view.redraw(true);
if (/^#flow\/.+$/.test(currentHash)) {
RED.workspaces.show(currentHash.substring(6));
}
}
done();
}
});
}
function completeLoad() {
var persistentNotifications = {};
RED.comms.subscribe("notification/#",function(topic,msg) {
var parts = topic.split("/");
var notificationId = parts[1];
if (notificationId === "runtime-deploy") {
// handled in ui/deploy.js
return;
}
if (notificationId === "node") {
// handled below
return;
}
if (notificationId === "project-update") {
RED.nodes.clear();
RED.history.clear();
RED.view.redraw(true);
RED.projects.refresh(function() {
loadFlows(function() {
var project = RED.projects.getActiveProject();
var message = {
"change-branch": RED._("notification.project.change-branch", {project: project.git.branches.local}),
"merge-abort": RED._("notification.project.merge-abort"),
"loaded": RED._("notification.project.loaded", {project: msg.project}),
"updated": RED._("notification.project.updated", {project: msg.project}),
"pull": RED._("notification.project.pull", {project: msg.project}),
"revert": RED._("notification.project.revert", {project: msg.project}),
"merge-complete": RED._("notification.project.merge-complete")
}[msg.action];
RED.notify("<p>"+message+"</p>");
RED.sidebar.info.refresh()
});
});
return;
}
if (msg.text) {
msg.default = msg.text;
var text = RED._(msg.text,msg);
var options = {
type: msg.type,
fixed: msg.timeout === undefined,
timeout: msg.timeout,
id: notificationId
}
if (notificationId === "runtime-state") {
if (msg.error === "safe-mode") {
options.buttons = [
{
text: RED._("common.label.close"),
click: function() {
persistentNotifications[notificationId].hideNotification();
}
}
]
} else if (msg.error === "missing-types") {
text+="<ul><li>"+msg.types.join("</li><li>")+"</li></ul>";
if (!!RED.projects.getActiveProject()) {
options.buttons = [
{
text: RED._("notification.label.manage-project-dep"),
click: function() {
persistentNotifications[notificationId].hideNotification();
RED.projects.settings.show('deps');
}
}
]
// } else if (RED.settings.theme('palette.editable') !== false) {
} else {
options.buttons = [
{
text: RED._("common.label.close"),
click: function() {
persistentNotifications[notificationId].hideNotification();
}
}
]
}
} else if (msg.error === "credentials_load_failed") {
if (RED.settings.theme("projects.enabled",false)) {
// projects enabled
if (RED.user.hasPermission("projects.write")) {
options.buttons = [
{
text: "Setup credentials",
click: function() {
persistentNotifications[notificationId].hideNotification();
RED.projects.showCredentialsPrompt();
}
}
]
}
} else {
options.buttons = [
{
text: "Close",
click: function() {
persistentNotifications[notificationId].hideNotification();
}
}
]
}
} else if (msg.error === "missing_flow_file") {
if (RED.user.hasPermission("projects.write")) {
options.buttons = [
{
text: "Setup project files",
click: function() {
persistentNotifications[notificationId].hideNotification();
RED.projects.showFilesPrompt();
}
}
]
}
} else if (msg.error === "missing_package_file") {
if (RED.user.hasPermission("projects.write")) {
options.buttons = [
{
text: "Create default package file",
click: function() {
persistentNotifications[notificationId].hideNotification();
RED.projects.createDefaultPackageFile();
}
}
]
}
} else if (msg.error === "project_empty") {
if (RED.user.hasPermission("projects.write")) {
options.buttons = [
{
text: "No thanks",
click: function() {
persistentNotifications[notificationId].hideNotification();
}
},
{
text: "Create default project files",
click: function() {
persistentNotifications[notificationId].hideNotification();
RED.projects.createDefaultFileSet();
}
}
]
}
} else if (msg.error === "git_merge_conflict") {
RED.nodes.clear();
RED.sidebar.versionControl.refresh(true);
if (RED.user.hasPermission("projects.write")) {
options.buttons = [
{
text: "Show merge conflicts",
click: function() {
persistentNotifications[notificationId].hideNotification();
RED.sidebar.versionControl.showLocalChanges();
}
}
]
}
}
}
if (!persistentNotifications.hasOwnProperty(notificationId)) {
persistentNotifications[notificationId] = RED.notify(text,options);
} else {
persistentNotifications[notificationId].update(text,options);
}
} else if (persistentNotifications.hasOwnProperty(notificationId)) {
persistentNotifications[notificationId].close();
delete persistentNotifications[notificationId];
}
});
RED.comms.subscribe("status/#",function(topic,msg) {
var parts = topic.split("/");
var node = RED.nodes.node(parts[1]);
if (node) {
if (msg.hasOwnProperty("text")) {
if (msg.text[0] !== ".") {
msg.text = node._(msg.text.toString(),{defaultValue:msg.text.toString()});
}
}
node.status = msg;
node.dirty = true;
RED.view.redraw();
}
});
RED.comms.subscribe("notification/node/#",function(topic,msg) {
var i,m;
var typeList;
var info;
if (topic == "notification/node/added") {
var addedTypes = [];
msg.forEach(function(m) {
var id = m.id;
RED.nodes.addNodeSet(m);
addedTypes = addedTypes.concat(m.types);
RED.i18n.loadNodeCatalog(id, function() {
$.get('nodes/'+id, function(data) {
appendNodeConfig(data);
});
});
});
if (addedTypes.length) {
typeList = "<ul><li>"+addedTypes.join("</li><li>")+"</li></ul>";
RED.notify(RED._("palette.event.nodeAdded", {count:addedTypes.length})+typeList,"success");
}
loadIconList();
} else if (topic == "notification/node/removed") {
for (i=0;i<msg.length;i++) {
m = msg[i];
info = RED.nodes.removeNodeSet(m.id);
if (info.added) {
typeList = "<ul><li>"+m.types.join("</li><li>")+"</li></ul>";
RED.notify(RED._("palette.event.nodeRemoved", {count:m.types.length})+typeList,"success");
}
}
loadIconList();
} else if (topic == "notification/node/enabled") {
if (msg.types) {
info = RED.nodes.getNodeSet(msg.id);
if (info.added) {
RED.nodes.enableNodeSet(msg.id);
typeList = "<ul><li>"+msg.types.join("</li><li>")+"</li></ul>";
RED.notify(RED._("palette.event.nodeEnabled", {count:msg.types.length})+typeList,"success");
} else {
$.get('nodes/'+msg.id, function(data) {
appendNodeConfig(data);
typeList = "<ul><li>"+msg.types.join("</li><li>")+"</li></ul>";
RED.notify(RED._("palette.event.nodeAdded", {count:msg.types.length})+typeList,"success");
});
}
}
} else if (topic == "notification/node/disabled") {
if (msg.types) {
RED.nodes.disableNodeSet(msg.id);
typeList = "<ul><li>"+msg.types.join("</li><li>")+"</li></ul>";
RED.notify(RED._("palette.event.nodeDisabled", {count:msg.types.length})+typeList,"success");
}
} else if (topic == "node/upgraded") {
RED.notify(RED._("palette.event.nodeUpgraded", {module:msg.module,version:msg.version}),"success");
RED.nodes.registry.setModulePendingUpdated(msg.module,msg.version);
}
// Refresh flow library to ensure any examples are updated
RED.library.loadFlowLibrary();
});
RED.comms.subscribe("event-log/#", function(topic,payload) {
var id = topic.substring(9);
RED.eventLog.log(id,payload);
});
}
function showAbout() {
$.get('red/about', function(data) {
var aboutHeader = '<div style="text-align:center;">'+
'<img width="50px" src="red/images/node-red-icon.svg" />'+
'</div>';
RED.sidebar.info.set(aboutHeader+marked(data));
RED.sidebar.info.show();
});
}
function loadEditor() {
var menuOptions = [];
if (RED.settings.theme("projects.enabled",false)) {
menuOptions.push({id:"menu-item-projects-menu",label:"Projects",options:[
{id:"menu-item-projects-new",label:"New",disabled:false,onselect:"core:new-project"},
{id:"menu-item-projects-open",label:"Open",disabled:false,onselect:"core:open-project"},
{id:"menu-item-projects-settings",label:"Project Settings",disabled:false,onselect:"core:show-project-settings"}
]});
}
menuOptions.push({id:"menu-item-view-menu",label:RED._("menu.label.view.view"),options:[
// {id:"menu-item-view-show-grid",setting:"view-show-grid",label:RED._("menu.label.view.showGrid"),toggle:true,onselect:"core:toggle-show-grid"},
// {id:"menu-item-view-snap-grid",setting:"view-snap-grid",label:RED._("menu.label.view.snapGrid"),toggle:true,onselect:"core:toggle-snap-grid"},
// {id:"menu-item-status",setting:"node-show-status",label:RED._("menu.label.displayStatus"),toggle:true,onselect:"core:toggle-status", selected: true},
//null,
// {id:"menu-item-bidi",label:RED._("menu.label.view.textDir"),options:[
// {id:"menu-item-bidi-default",toggle:"text-direction",label:RED._("menu.label.view.defaultDir"),selected: true, onselect:function(s) { if(s){RED.text.bidi.setTextDirection("")}}},
// {id:"menu-item-bidi-ltr",toggle:"text-direction",label:RED._("menu.label.view.ltr"), onselect:function(s) { if(s){RED.text.bidi.setTextDirection("ltr")}}},
// {id:"menu-item-bidi-rtl",toggle:"text-direction",label:RED._("menu.label.view.rtl"), onselect:function(s) { if(s){RED.text.bidi.setTextDirection("rtl")}}},
// {id:"menu-item-bidi-auto",toggle:"text-direction",label:RED._("menu.label.view.auto"), onselect:function(s) { if(s){RED.text.bidi.setTextDirection("auto")}}}
// ]},
// null,
{id:"menu-item-palette",label:RED._("menu.label.palette.show"),toggle:true,onselect:"core:toggle-palette", selected: true},
{id:"menu-item-sidebar",label:RED._("menu.label.sidebar.show"),toggle:true,onselect:"core:toggle-sidebar", selected: true},
{id:"menu-item-event-log",label:RED._("eventLog.title"),onselect:"core:show-event-log"},
null
]});
menuOptions.push(null);
menuOptions.push({id:"menu-item-import",label:RED._("menu.label.import"),options:[
{id:"menu-item-import-clipboard",label:RED._("menu.label.clipboard"),onselect:"core:show-import-dialog"},
{id:"menu-item-import-library",label:RED._("menu.label.library"),options:[]}
]});
menuOptions.push({id:"menu-item-export",label:RED._("menu.label.export"),options:[
{id:"menu-item-export-clipboard",label:RED._("menu.label.clipboard"),onselect:"core:show-export-dialog"},
{id:"menu-item-export-library",label:RED._("menu.label.library"),disabled:true,onselect:"core:library-export"}
]});
menuOptions.push(null);
menuOptions.push({id:"menu-item-search",label:RED._("menu.label.search"),onselect:"core:search"});
menuOptions.push(null);
menuOptions.push({id:"menu-item-config-nodes",label:RED._("menu.label.displayConfig"),onselect:"core:show-config-tab"});
menuOptions.push({id:"menu-item-workspace",label:RED._("menu.label.flows"),options:[
{id:"menu-item-workspace-add",label:RED._("menu.label.add"),onselect:"core:add-flow"},
{id:"menu-item-workspace-edit",label:RED._("menu.label.rename"),onselect:"core:edit-flow"},
{id:"menu-item-workspace-delete",label:RED._("menu.label.delete"),onselect:"core:remove-flow"}
]});
menuOptions.push({id:"menu-item-subflow",label:RED._("menu.label.subflows"), options: [
{id:"menu-item-subflow-create",label:RED._("menu.label.createSubflow"),onselect:"core:create-subflow"},
{id:"menu-item-subflow-convert",label:RED._("menu.label.selectionToSubflow"),disabled:true,onselect:"core:convert-to-subflow"},
]});
menuOptions.push(null);
if (RED.settings.theme('palette.editable') !== false) {
menuOptions.push({id:"menu-item-edit-palette",label:RED._("menu.label.editPalette"),onselect:"core:manage-palette"});
menuOptions.push(null);
}
menuOptions.push({id:"menu-item-user-settings",label:RED._("menu.label.settings"),onselect:"core:show-user-settings"});
menuOptions.push(null);
menuOptions.push({id:"menu-item-keyboard-shortcuts",label:RED._("menu.label.keyboardShortcuts"),onselect:"core:show-help"});
menuOptions.push({id:"menu-item-help",
label: RED.settings.theme("menu.menu-item-help.label",RED._("menu.label.help")),
href: RED.settings.theme("menu.menu-item-help.url","http://nodered.org/docs")
});
menuOptions.push({id:"menu-item-node-red-version", label:"v"+RED.settings.version, onselect: "core:show-about" });
RED.view.init();
RED.userSettings.init();
RED.user.init();
RED.library.init();
RED.keyboard.init();
RED.palette.init();
RED.eventLog.init();
if (RED.settings.theme('palette.editable') !== false) {
RED.palette.editor.init();
} else {
console.log("Palette editor disabled");
}
RED.sidebar.init();
if (RED.settings.theme("projects.enabled",false)) {
RED.projects.init();
} else {
console.log("Projects disabled");
}
RED.subflow.init();
RED.workspaces.init();
RED.clipboard.init();
RED.search.init();
RED.editor.init();
RED.diff.init();
RED.menu.init({id:"btn-sidemenu",options: menuOptions});
RED.deploy.init(RED.settings.theme("deployButton",null));
RED.notifications.init();
RED.actions.add("core:show-about", showAbout);
RED.nodes.init();
RED.comms.connect();
$("#main-container").show();
$(".header-toolbar").show();
loadNodeList();
}
var initialised = false;
function init(options) {
if (initialised) {
throw new Error("RED already initialised");
}
initialised = true;
ace.require("ace/ext/language_tools");
options = options || {};
options.apiRootUrl = options.apiRootUrl || "";
if (options.apiRootUrl && !/\/$/.test(options.apiRootUrl)) {
options.apiRootUrl = options.apiRootUrl+"/";
}
RED.i18n.init(options, function() {
RED.settings.init(options, loadEditor);
})
}
return {
init: init
}
})();

View File

@@ -0,0 +1,217 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
RED.settings = (function () {
var loadedSettings = {};
var userSettings = {};
var settingsDirty = false;
var pendingSave;
var hasLocalStorage = function () {
try {
return 'localStorage' in window && window['localStorage'] !== null;
} catch (e) {
return false;
}
};
var set = function (key, value) {
if (!hasLocalStorage()) {
return;
}
if (key === "auth-tokens") {
localStorage.setItem(key, JSON.stringify(value));
} else {
userSettings[key] = value;
saveUserSettings();
}
};
/**
* If the key is not set in the localStorage it returns <i>undefined</i>
* Else return the JSON parsed value
* @param key
* @returns {*}
*/
var get = function (key) {
if (!hasLocalStorage()) {
return undefined;
}
if (key === "auth-tokens") {
return JSON.parse(localStorage.getItem(key));
} else {
return userSettings[key];
}
};
var remove = function (key) {
if (!hasLocalStorage()) {
return;
}
if (key === "auth-tokens") {
localStorage.removeItem(key);
} else {
delete userSettings[key];
saveUserSettings();
}
};
var setProperties = function(data) {
for (var prop in loadedSettings) {
if (loadedSettings.hasOwnProperty(prop) && RED.settings.hasOwnProperty(prop)) {
delete RED.settings[prop];
}
}
for (prop in data) {
if (data.hasOwnProperty(prop)) {
RED.settings[prop] = data[prop];
}
}
loadedSettings = data;
};
var setUserSettings = function(data) {
userSettings = data;
}
var init = function (options, done) {
var accessTokenMatch = /[?&]access_token=(.*?)(?:$|&)/.exec(window.location.search);
if (accessTokenMatch) {
var accessToken = accessTokenMatch[1];
RED.settings.set("auth-tokens",{access_token: accessToken});
window.location.search = "";
}
RED.settings.apiRootUrl = options.apiRootUrl;
$.ajaxSetup({
beforeSend: function(jqXHR,settings) {
// Only attach auth header for requests to relative paths
if (!/^\s*(https?:|\/|\.)/.test(settings.url)) {
if (options.apiRootUrl) {
settings.url = options.apiRootUrl+settings.url;
}
var auth_tokens = RED.settings.get("auth-tokens");
if (auth_tokens) {
jqXHR.setRequestHeader("Authorization","Bearer "+auth_tokens.access_token);
}
jqXHR.setRequestHeader("Node-RED-API-Version","v2");
}
}
});
load(done);
}
var load = function(done) {
$.ajax({
headers: {
"Accept": "application/json"
},
dataType: "json",
cache: false,
url: 'settings',
success: function (data) {
setProperties(data);
if (!RED.settings.user || RED.settings.user.anonymous) {
RED.settings.remove("auth-tokens");
}
console.log("Node-RED: " + data.version);
loadUserSettings(done);
},
error: function(jqXHR,textStatus,errorThrown) {
if (jqXHR.status === 401) {
if (/[?&]access_token=(.*?)(?:$|&)/.test(window.location.search)) {
window.location.search = "";
}
RED.user.login(function() { load(done); });
} else {
console.log("Unexpected error loading settings:",jqXHR.status,textStatus);
}
}
});
};
function loadUserSettings(done) {
$.ajax({
headers: {
"Accept": "application/json"
},
dataType: "json",
cache: false,
url: 'settings/user',
success: function (data) {
setUserSettings(data);
done();
},
error: function(jqXHR,textStatus,errorThrown) {
console.log("Unexpected error loading user settings:",jqXHR.status,textStatus);
}
});
}
function saveUserSettings() {
if (RED.user.hasPermission("settings.write")) {
if (pendingSave) {
clearTimeout(pendingSave);
}
pendingSave = setTimeout(function() {
pendingSave = null;
$.ajax({
method: 'POST',
contentType: 'application/json',
url: 'settings/user',
data: JSON.stringify(userSettings),
success: function (data) {
},
error: function(jqXHR,textStatus,errorThrown) {
console.log("Unexpected error saving user settings:",jqXHR.status,textStatus);
}
});
},300);
}
}
function theme(property,defaultValue) {
if (!RED.settings.editorTheme) {
return defaultValue;
}
var parts = property.split(".");
var v = RED.settings.editorTheme;
try {
for (var i=0;i<parts.length;i++) {
v = v[parts[i]];
}
if (v === undefined) {
return defaultValue;
}
return v;
} catch(err) {
return defaultValue;
}
}
return {
init: init,
load: load,
loadUserSettings: loadUserSettings,
set: set,
get: get,
remove: remove,
theme: theme
}
})();

View File

@@ -0,0 +1,130 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
RED.text = {};
RED.text.bidi = (function() {
var textDir = "";
var LRE = "\u202A",
RLE = "\u202B",
PDF = "\u202C";
function isRTLValue(stringValue) {
var length = stringValue.length;
for (var i=0;i<length;i++) {
if (isBidiChar(stringValue.charCodeAt(i))) {
return true;
}
else if(isLatinChar(stringValue.charCodeAt(i))) {
return false;
}
}
return false;
}
function isBidiChar(c) {
return (c >= 0x05d0 && c <= 0x05ff)||
(c >= 0x0600 && c <= 0x065f)||
(c >= 0x066a && c <= 0x06ef)||
(c >= 0x06fa && c <= 0x07ff)||
(c >= 0xfb1d && c <= 0xfdff)||
(c >= 0xfe70 && c <= 0xfefc);
}
function isLatinChar(c){
return (c > 64 && c < 91)||(c > 96 && c < 123)
}
/**
* Determines the text direction of a given string.
* @param value - the string
*/
function resolveBaseTextDir(value) {
if (textDir == "auto") {
if (isRTLValue(value)) {
return "rtl";
} else {
return "ltr";
}
}
else {
return textDir;
}
}
function onInputChange() {
$(this).attr("dir", resolveBaseTextDir($(this).val()));
}
/**
* Adds event listeners to the Input to ensure its text-direction attribute
* is properly set based on its content.
* @param input - the input field
*/
function prepareInput(input) {
input.on("keyup",onInputChange).on("paste",onInputChange).on("cut",onInputChange);
// Set the initial text direction
onInputChange.call(input);
}
/**
* Enforces the text direction of a given string by adding
* UCC (Unicode Control Characters)
* @param value - the string
*/
function enforceTextDirectionWithUCC(value) {
if (value) {
var dir = resolveBaseTextDir(value);
if (dir == "ltr") {
return LRE + value + PDF;
}
else if (dir == "rtl") {
return RLE + value + PDF;
}
}
return value;
}
/**
* Enforces the text direction for all the spans with style bidiAware under
* workspace or sidebar div
*/
function enforceTextDirectionOnPage() {
$("#workspace").find('span.bidiAware').each(function() {
$(this).attr("dir", resolveBaseTextDir($(this).html()));
});
$("#sidebar").find('span.bidiAware').each(function() {
$(this).attr("dir", resolveBaseTextDir($(this).text()));
});
}
/**
* Sets the text direction preference
* @param dir - the text direction preference
*/
function setTextDirection(dir) {
textDir = dir;
RED.nodes.eachNode(function(n) { n.dirty = true;});
RED.view.redraw();
RED.palette.refresh();
enforceTextDirectionOnPage();
}
return {
setTextDirection: setTextDirection,
enforceTextDirectionWithUCC: enforceTextDirectionWithUCC,
resolveBaseTextDir: resolveBaseTextDir,
prepareInput: prepareInput
}
})();

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,35 @@
RED.actions = (function() {
var actions = {
}
function addAction(name,handler) {
actions[name] = handler;
}
function removeAction(name) {
delete actions[name];
}
function getAction(name) {
return actions[name];
}
function invokeAction(name) {
if (actions.hasOwnProperty(name)) {
actions[name]();
}
}
function listActions() {
var result = [];
Object.keys(actions).forEach(function(action) {
var shortcut = RED.keyboard.getShortcut(action);
result.push({id:action,scope:shortcut?shortcut.scope:undefined,key:shortcut?shortcut.key:undefined,user:shortcut?shortcut.user:undefined})
})
return result;
}
return {
add: addAction,
remove: removeAction,
get: getAction,
invoke: invokeAction,
list: listActions
}
})();

View File

@@ -0,0 +1,379 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
RED.clipboard = (function() {
var dialog;
var dialogContainer;
var exportNodesDialog;
var importNodesDialog;
var disabled = false;
function setupDialogs() {
dialog = $('<div id="clipboard-dialog" class="hide node-red-dialog"><form class="dialog-form form-horizontal"></form></div>')
.appendTo("body")
.dialog({
modal: true,
autoOpen: false,
width: 500,
resizable: false,
buttons: [
{
id: "clipboard-dialog-cancel",
text: RED._("common.label.cancel"),
click: function() {
$( this ).dialog( "close" );
}
},
{
id: "clipboard-dialog-close",
class: "primary",
text: RED._("common.label.close"),
click: function() {
$( this ).dialog( "close" );
}
},
{
id: "clipboard-dialog-copy",
class: "primary",
text: RED._("clipboard.export.copy"),
click: function() {
$("#clipboard-export").select();
document.execCommand("copy");
document.getSelection().removeAllRanges();
RED.notify(RED._("clipboard.nodesExported"));
$( this ).dialog( "close" );
}
},
{
id: "clipboard-dialog-ok",
class: "primary",
text: RED._("common.label.import"),
click: function() {
RED.view.importNodes($("#clipboard-import").val(),$("#import-tab > a.selected").attr('id') === 'import-tab-new');
$( this ).dialog( "close" );
}
}
],
open: function(e) {
$(this).parent().find(".ui-dialog-titlebar-close").hide();
},
close: function(e) {
}
});
dialogContainer = dialog.children(".dialog-form");
exportNodesDialog =
'<div class="form-row">'+
'<label style="width:auto;margin-right: 10px;" data-i18n="clipboard.export.copy"></label>'+
'<span id="export-range-group" class="button-group">'+
'<a id="export-range-selected" class="editor-button toggle" href="#" data-i18n="clipboard.export.selected"></a>'+
'<a id="export-range-flow" class="editor-button toggle" href="#" data-i18n="clipboard.export.current"></a>'+
'<a id="export-range-full" class="editor-button toggle" href="#" data-i18n="clipboard.export.all"></a>'+
'</span>'+
'</div>'+
'<div class="form-row">'+
'<textarea readonly style="resize: none; width: 100%; border-radius: 4px;font-family: monospace; font-size: 12px; background:#f3f3f3; padding-left: 0.5em; box-sizing:border-box;" id="clipboard-export" rows="5"></textarea>'+
'</div>'+
'<div class="form-row" style="text-align: right;">'+
'<span id="export-format-group" class="button-group">'+
'<a id="export-format-mini" class="editor-button editor-button-small toggle" href="#" data-i18n="clipboard.export.compact"></a>'+
'<a id="export-format-full" class="editor-button editor-button-small toggle" href="#" data-i18n="clipboard.export.formatted"></a>'+
'</span>'+
'</div>';
importNodesDialog = '<div class="form-row">'+
'<textarea style="resize: none; width: 100%; border-radius: 0px;font-family: monospace; font-size: 12px; background:#eee; padding-left: 0.5em; box-sizing:border-box;" id="clipboard-import" rows="5" placeholder="'+
RED._("clipboard.pasteNodes")+
'"></textarea>'+
'</div>'+
'<div class="form-row">'+
'<label style="width:auto;margin-right: 10px;" data-i18n="clipboard.import.import"></label>'+
'<span id="import-tab" class="button-group">'+
'<a id="import-tab-current" class="editor-button toggle selected" href="#" data-i18n="clipboard.export.current"></a>'+
'<a id="import-tab-new" class="editor-button toggle" href="#" data-i18n="clipboard.import.newFlow"></a>'+
'</span>'+
'</div>';
}
function validateImport() {
var importInput = $("#clipboard-import");
var v = importInput.val();
v = v.substring(v.indexOf('['),v.lastIndexOf(']')+1);
try {
JSON.parse(v);
importInput.removeClass("input-error");
importInput.val(v);
$("#clipboard-dialog-ok").button("enable");
} catch(err) {
if (v !== "") {
importInput.addClass("input-error");
}
$("#clipboard-dialog-ok").button("disable");
}
}
function importNodes() {
if (disabled) {
return;
}
dialogContainer.empty();
dialogContainer.append($(importNodesDialog));
dialogContainer.i18n();
$("#clipboard-dialog-ok").show();
$("#clipboard-dialog-cancel").show();
$("#clipboard-dialog-close").hide();
$("#clipboard-dialog-copy").hide();
$("#clipboard-dialog-ok").button("disable");
$("#clipboard-import").keyup(validateImport);
$("#clipboard-import").on('paste',function() { setTimeout(validateImport,10)});
$("#import-tab > a").click(function(evt) {
evt.preventDefault();
if ($(this).hasClass('disabled') || $(this).hasClass('selected')) {
return;
}
$(this).parent().children().removeClass('selected');
$(this).addClass('selected');
});
dialog.dialog("option","title",RED._("clipboard.importNodes")).dialog("open");
}
function exportNodes() {
if (disabled) {
return;
}
dialogContainer.empty();
dialogContainer.append($(exportNodesDialog));
dialogContainer.i18n();
var format = RED.settings.flowFilePretty ? "export-format-full" : "export-format-mini";
$("#export-format-group > a").click(function(evt) {
evt.preventDefault();
if ($(this).hasClass('disabled') || $(this).hasClass('selected')) {
$("#clipboard-export").focus();
return;
}
$(this).parent().children().removeClass('selected');
$(this).addClass('selected');
var flow = $("#clipboard-export").val();
if (flow.length > 0) {
var nodes = JSON.parse(flow);
format = $(this).attr('id');
if (format === 'export-format-full') {
flow = JSON.stringify(nodes,null,4);
} else {
flow = JSON.stringify(nodes);
}
$("#clipboard-export").val(flow);
$("#clipboard-export").focus();
}
});
$("#export-range-group > a").click(function(evt) {
evt.preventDefault();
if ($(this).hasClass('disabled') || $(this).hasClass('selected')) {
$("#clipboard-export").focus();
return;
}
$(this).parent().children().removeClass('selected');
$(this).addClass('selected');
var type = $(this).attr('id');
var flow = "";
var nodes = null;
if (type === 'export-range-selected') {
var selection = RED.view.selection();
// Don't include the subflow meta-port nodes in the exported selection
nodes = RED.nodes.createExportableNodeSet(selection.nodes.filter(function(n) { return n.type !== 'subflow'}));
} else if (type === 'export-range-flow') {
var activeWorkspace = RED.workspaces.active();
nodes = RED.nodes.filterNodes({z:activeWorkspace});
var parentNode = RED.nodes.workspace(activeWorkspace)||RED.nodes.subflow(activeWorkspace);
nodes.unshift(parentNode);
nodes = RED.nodes.createExportableNodeSet(nodes);
} else if (type === 'export-range-full') {
nodes = RED.nodes.createCompleteNodeSet(false);
}
if (nodes !== null) {
if (format === "export-format-full") {
flow = JSON.stringify(nodes,null,4);
} else {
flow = JSON.stringify(nodes);
}
}
if (flow.length > 0) {
$("#export-copy").removeClass('disabled');
} else {
$("#export-copy").addClass('disabled');
}
$("#clipboard-export").val(flow);
$("#clipboard-export").focus();
})
$("#clipboard-dialog-ok").hide();
$("#clipboard-dialog-cancel").hide();
$("#clipboard-dialog-copy").hide();
$("#clipboard-dialog-close").hide();
var selection = RED.view.selection();
if (selection.nodes) {
$("#export-range-selected").click();
} else {
$("#export-range-selected").addClass('disabled').removeClass('selected');
$("#export-range-flow").click();
}
if (format === "export-format-full") {
$("#export-format-full").click();
} else {
$("#export-format-mini").click();
}
$("#clipboard-export")
.focus(function() {
var textarea = $(this);
textarea.select();
textarea.mouseup(function() {
textarea.unbind("mouseup");
return false;
})
});
dialog.dialog("option","title",RED._("clipboard.exportNodes")).dialog( "open" );
$("#clipboard-export").focus();
if (!document.queryCommandSupported("copy")) {
$("#clipboard-dialog-cancel").hide();
$("#clipboard-dialog-close").show();
} else {
$("#clipboard-dialog-cancel").show();
$("#clipboard-dialog-copy").show();
}
}
function hideDropTarget() {
$("#dropTarget").hide();
RED.keyboard.remove("escape");
}
function copyText(value,element,msg) {
var truncated = false;
if (typeof value !== "string" ) {
value = JSON.stringify(value, function(key,value) {
if (value !== null && typeof value === 'object') {
if (value.__enc__) {
if (value.hasOwnProperty('data') && value.hasOwnProperty('length')) {
truncated = value.data.length !== value.length;
return value.data;
}
if (value.type === 'function' || value.type === 'internal') {
return undefined
}
if (value.type === 'number') {
// Handle NaN and Infinity - they are not permitted
// in JSON. We can either substitute with a String
// representation or null
return null;
}
}
}
return value;
});
}
if (truncated) {
msg += "_truncated";
}
$("#clipboard-hidden").val(value).select();
var result = document.execCommand("copy");
if (result && element) {
var popover = RED.popover.create({
target: element,
direction: 'left',
size: 'small',
content: RED._(msg)
});
setTimeout(function() {
popover.close();
},1000);
popover.open();
}
return result;
}
return {
init: function() {
setupDialogs();
$('<input type="text" id="clipboard-hidden">').appendTo("body");
RED.actions.add("core:show-export-dialog",exportNodes);
RED.actions.add("core:show-import-dialog",importNodes);
RED.events.on("editor:open",function() { disabled = true; });
RED.events.on("editor:close",function() { disabled = false; });
RED.events.on("search:open",function() { disabled = true; });
RED.events.on("search:close",function() { disabled = false; });
RED.events.on("type-search:open",function() { disabled = true; });
RED.events.on("type-search:close",function() { disabled = false; });
$('#chart').on("dragenter",function(event) {
if ($.inArray("text/plain",event.originalEvent.dataTransfer.types) != -1 ||
$.inArray("Files",event.originalEvent.dataTransfer.types) != -1) {
$("#dropTarget").css({display:'table'});
RED.keyboard.add("*", "escape" ,hideDropTarget);
}
});
$('#dropTarget').on("dragover",function(event) {
if ($.inArray("text/plain",event.originalEvent.dataTransfer.types) != -1 ||
$.inArray("Files",event.originalEvent.dataTransfer.types) != -1) {
event.preventDefault();
}
})
.on("dragleave",function(event) {
hideDropTarget();
})
.on("drop",function(event) {
if ($.inArray("text/plain",event.originalEvent.dataTransfer.types) != -1) {
var data = event.originalEvent.dataTransfer.getData("text/plain");
data = data.substring(data.indexOf('['),data.lastIndexOf(']')+1);
RED.view.importNodes(data);
} else if ($.inArray("Files",event.originalEvent.dataTransfer.types) != -1) {
var files = event.originalEvent.dataTransfer.files;
if (files.length === 1) {
var file = files[0];
var reader = new FileReader();
reader.onload = (function(theFile) {
return function(e) {
RED.view.importNodes(e.target.result);
};
})(file);
reader.readAsText(file);
}
}
hideDropTarget();
event.preventDefault();
});
},
import: importNodes,
export: exportNodes,
copyText: copyText
}
})();

View File

@@ -0,0 +1,131 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
(function($) {
$.widget( "nodered.checkboxSet", {
_create: function() {
var that = this;
this.uiElement = this.element.wrap( "<span>" ).parent();
this.uiElement.addClass("red-ui-checkboxSet");
if (this.options.parent) {
this.parent = this.options.parent;
this.parent.checkboxSet('addChild',this.element);
}
this.children = [];
this.partialFlag = false;
this.stateValue = 0;
var initialState = this.element.prop('checked');
this.options = [
$('<span class="red-ui-checkboxSet-option hide"><i class="fa fa-square-o"></i></span>').appendTo(this.uiElement),
$('<span class="red-ui-checkboxSet-option hide"><i class="fa fa-check-square-o"></i></span>').appendTo(this.uiElement),
$('<span class="red-ui-checkboxSet-option hide"><i class="fa fa-minus-square-o"></i></span>').appendTo(this.uiElement)
];
if (initialState) {
this.options[1].show();
} else {
this.options[0].show();
}
this.element.change(function() {
if (this.checked) {
that.options[0].hide();
that.options[1].show();
that.options[2].hide();
} else {
that.options[1].hide();
that.options[0].show();
that.options[2].hide();
}
var isChecked = this.checked;
that.children.forEach(function(child) {
child.checkboxSet('state',isChecked,false,true);
})
})
this.uiElement.click(function(e) {
e.stopPropagation();
// state returns null for a partial state. Clicking on that should
// result in false.
that.state((that.state()===false)?true:false);
})
if (this.parent) {
this.parent.checkboxSet('updateChild',this);
}
},
_destroy: function() {
if (this.parent) {
this.parent.checkboxSet('removeChild',this.element);
}
},
addChild: function(child) {
var that = this;
this.children.push(child);
},
removeChild: function(child) {
var index = this.children.indexOf(child);
if (index > -1) {
this.children.splice(index,1);
}
},
updateChild: function(child) {
var checkedCount = 0;
this.children.forEach(function(c,i) {
if (c.checkboxSet('state') === true) {
checkedCount++;
}
});
if (checkedCount === 0) {
this.state(false,true);
} else if (checkedCount === this.children.length) {
this.state(true,true);
} else {
this.state(null,true);
}
},
disable: function() {
this.uiElement.addClass('disabled');
},
state: function(state,suppressEvent,suppressParentUpdate) {
if (arguments.length === 0) {
return this.partialFlag?null:this.element.is(":checked");
} else {
this.partialFlag = (state === null);
var trueState = this.partialFlag||state;
this.element.prop('checked',trueState);
if (state === true) {
this.options[0].hide();
this.options[1].show();
this.options[2].hide();
} else if (state === false) {
this.options[2].hide();
this.options[1].hide();
this.options[0].show();
} else if (state === null) {
this.options[0].hide();
this.options[1].hide();
this.options[2].show();
}
if (!suppressEvent) {
this.element.trigger('change',null);
}
if (!suppressParentUpdate && this.parent) {
this.parent.checkboxSet('updateChild',this);
}
}
}
})
})(jQuery);

View File

@@ -0,0 +1,332 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
(function($) {
/**
* options:
* - addButton : boolean|string - text for add label, default 'add'
* - height : number|'auto'
* - resize : function - called when list as a whole is resized
* - resizeItem : function(item) - called to resize individual item
* - sortable : boolean|string - string is the css selector for handle
* - sortItems : function(items) - when order of items changes
* - connectWith : css selector of other sortables
* - removable : boolean - whether to display delete button on items
* - addItem : function(row,index,itemData) - when an item is added
* - removeItem : function(itemData) - called when an item is removed
* - filter : function(itemData) - called for each item to determine if it should be shown
* - sort : function(itemDataA,itemDataB) - called to sort items
* - scrollOnAdd : boolean - whether to scroll to newly added items
* methods:
* - addItem(itemData)
* - removeItem(itemData)
* - width(width)
* - height(height)
* - items()
* - empty()
* - filter(filter)
* - sort(sort)
* - length()
*/
$.widget( "nodered.editableList", {
_create: function() {
var that = this;
this.element.addClass('red-ui-editableList-list');
this.uiWidth = this.element.width();
this.uiContainer = this.element
.wrap( "<div>" )
.parent();
if (this.options.header) {
this.options.header.addClass("red-ui-editableList-header");
this.borderContainer = this.uiContainer.wrap("<div>").parent();
this.borderContainer.prepend(this.options.header);
this.topContainer = this.borderContainer.wrap("<div>").parent();
} else {
this.topContainer = this.uiContainer.wrap("<div>").parent();
}
this.topContainer.addClass('red-ui-editableList');
if (this.options.class) {
this.topContainer.addClass(this.options.class);
}
if (this.options.addButton !== false) {
var addLabel;
if (typeof this.options.addButton === 'string') {
addLabel = this.options.addButton
} else {
if (RED && RED._) {
addLabel = RED._("editableList.add");
} else {
addLabel = 'add';
}
}
$('<a href="#" class="editor-button editor-button-small red-ui-editableList-addButton" style="margin-top: 4px;"><i class="fa fa-plus"></i> '+addLabel+'</a>')
.appendTo(this.topContainer)
.click(function(evt) {
evt.preventDefault();
that.addItem({});
});
}
if (this.element.css("position") === "absolute") {
["top","left","bottom","right"].forEach(function(s) {
var v = that.element.css(s);
if (v!=="auto" && v!=="") {
that.topContainer.css(s,v);
that.uiContainer.css(s,"0");
that.element.css(s,'auto');
}
})
this.element.css("position","static");
this.topContainer.css("position","absolute");
this.uiContainer.css("position","absolute");
}
if (this.options.header) {
this.borderContainer.addClass("red-ui-editableList-border");
} else {
this.uiContainer.addClass("red-ui-editableList-border");
}
this.uiContainer.addClass("red-ui-editableList-container");
this.uiHeight = this.element.height();
this.activeFilter = this.options.filter||null;
this.activeSort = this.options.sort||null;
this.scrollOnAdd = this.options.scrollOnAdd;
if (this.scrollOnAdd === undefined) {
this.scrollOnAdd = true;
}
var minHeight = this.element.css("minHeight");
if (minHeight !== '0px') {
this.uiContainer.css("minHeight",minHeight);
this.element.css("minHeight",0);
}
var maxHeight = this.element.css("maxHeight");
if (maxHeight !== '0px') {
this.uiContainer.css("maxHeight",maxHeight);
this.element.css("maxHeight",null);
}
if (this.options.height !== 'auto') {
this.uiContainer.css("overflow-y","scroll");
if (!isNaN(this.options.height)) {
this.uiHeight = this.options.height;
}
}
this.element.height('auto');
var attrStyle = this.element.attr('style');
var m;
if ((m = /width\s*:\s*(\d+%)/i.exec(attrStyle)) !== null) {
this.element.width('100%');
this.uiContainer.width(m[1]);
}
if (this.options.sortable) {
var handle = (typeof this.options.sortable === 'string')?
this.options.sortable :
".red-ui-editableList-item-handle";
var sortOptions = {
axis: "y",
update: function( event, ui ) {
if (that.options.sortItems) {
that.options.sortItems(that.items());
}
},
handle:handle,
cursor: "move",
tolerance: "pointer",
forcePlaceholderSize:true,
placeholder: "red-ui-editabelList-item-placeholder",
start: function(e, ui){
ui.placeholder.height(ui.item.height()-4);
}
};
if (this.options.connectWith) {
sortOptions.connectWith = this.options.connectWith;
}
this.element.sortable(sortOptions);
}
this._resize();
// this.menu = this._createMenu(this.types, function(v) { that.type(v) });
// this.type(this.options.default||this.types[0].value);
},
_resize: function() {
var currentFullHeight = this.topContainer.height();
var innerHeight = this.uiContainer.height();
var delta = currentFullHeight - innerHeight;
if (this.uiHeight !== 0) {
this.uiContainer.height(this.uiHeight-delta);
}
if (this.options.resize) {
this.options.resize();
}
if (this.options.resizeItem) {
var that = this;
this.element.children().each(function(i) {
that.options.resizeItem($(this).find(".red-ui-editableList-item-content"),i);
});
}
},
_destroy: function() {
},
_refreshFilter: function() {
var that = this;
var count = 0;
if (!this.activeFilter) {
return this.element.children().show();
}
var items = this.items();
items.each(function (i,el) {
var data = el.data('data');
try {
if (that.activeFilter(data)) {
el.parent().show();
count++;
} else {
el.parent().hide();
}
} catch(err) {
console.log(err);
el.parent().show();
count++;
}
});
return count;
},
_refreshSort: function() {
if (this.activeSort) {
var items = this.element.children();
var that = this;
items.sort(function(A,B) {
return that.activeSort($(A).find(".red-ui-editableList-item-content").data('data'),$(B).find(".red-ui-editableList-item-content").data('data'));
});
$.each(items,function(idx,li) {
that.element.append(li);
})
}
},
width: function(desiredWidth) {
this.uiWidth = desiredWidth;
this._resize();
},
height: function(desiredHeight) {
this.uiHeight = desiredHeight;
this._resize();
},
addItem: function(data) {
var that = this;
data = data || {};
var li = $('<li>');
var added = false;
if (this.activeSort) {
var items = this.items();
var skip = false;
items.each(function(i,el) {
if (added) { return }
var itemData = el.data('data');
if (that.activeSort(data,itemData) < 0) {
li.insertBefore(el.closest("li"));
added = true;
}
});
}
if (!added) {
li.appendTo(this.element);
}
var row = $('<div/>').addClass("red-ui-editableList-item-content").appendTo(li);
row.data('data',data);
if (this.options.sortable === true) {
$('<i class="red-ui-editableList-item-handle fa fa-bars"></i>').appendTo(li);
li.addClass("red-ui-editableList-item-sortable");
}
if (this.options.removable) {
var deleteButton = $('<a/>',{href:"#",class:"red-ui-editableList-item-remove editor-button editor-button-small"}).appendTo(li);
$('<i/>',{class:"fa fa-remove"}).appendTo(deleteButton);
li.addClass("red-ui-editableList-item-removable");
deleteButton.click(function(evt) {
evt.preventDefault();
var data = row.data('data');
li.addClass("red-ui-editableList-item-deleting")
li.fadeOut(300, function() {
$(this).remove();
if (that.options.removeItem) {
that.options.removeItem(data);
}
});
});
}
if (this.options.addItem) {
var index = that.element.children().length-1;
setTimeout(function() {
that.options.addItem(row,index,data);
if (that.activeFilter) {
try {
if (!that.activeFilter(data)) {
li.hide();
}
} catch(err) {
}
}
if (!that.activeSort && that.scrollOnAdd) {
setTimeout(function() {
that.uiContainer.scrollTop(that.element.height());
},0);
}
},0);
}
},
addItems: function(items) {
for (var i=0; i<items.length;i++) {
this.addItem(items[i]);
}
},
removeItem: function(data) {
var items = this.element.children().filter(function(f) {
return data === $(this).find(".red-ui-editableList-item-content").data('data');
});
items.remove();
if (this.options.removeItem) {
this.options.removeItem(data);
}
},
items: function() {
return this.element.children().map(function(i) { return $(this).find(".red-ui-editableList-item-content"); });
},
empty: function() {
this.element.empty();
},
filter: function(filter) {
if (filter !== undefined) {
this.activeFilter = filter;
}
return this._refreshFilter();
},
sort: function(sort) {
if (sort !== undefined) {
this.activeSort = sort;
}
return this._refreshSort();
},
length: function() {
return this.element.children().length;
}
});
})(jQuery);

View File

@@ -0,0 +1,267 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
RED.menu = (function() {
var menuItems = {};
function createMenuItem(opt) {
var item;
if (opt !== null && opt.id) {
var themeSetting = RED.settings.theme("menu."+opt.id);
if (themeSetting === false) {
return null;
}
}
function setInitialState() {
var savedStateActive = RED.settings.get("menu-" + opt.id);
if (opt.setting) {
// May need to migrate pre-0.17 setting
if (savedStateActive !== null) {
RED.settings.set(opt.setting,savedStateActive);
RED.settings.remove("menu-" + opt.id);
} else {
savedStateActive = RED.settings.get(opt.setting);
}
}
if (savedStateActive) {
link.addClass("active");
triggerAction(opt.id,true);
} else if (savedStateActive === false) {
link.removeClass("active");
triggerAction(opt.id,false);
} else if (opt.hasOwnProperty("selected")) {
if (opt.selected) {
link.addClass("active");
} else {
link.removeClass("active");
}
triggerAction(opt.id,opt.selected);
}
}
if (opt === null) {
item = $('<li class="divider"></li>');
} else {
item = $('<li></li>');
if (opt.group) {
item.addClass("menu-group-"+opt.group);
}
var linkContent = '<a '+(opt.id?'id="'+opt.id+'" ':'')+'tabindex="-1" href="#">';
if (opt.toggle) {
linkContent += '<i class="fa fa-square pull-left"></i>';
linkContent += '<i class="fa fa-check-square pull-left"></i>';
}
if (opt.icon !== undefined) {
if (/\.png/.test(opt.icon)) {
linkContent += '<img src="'+opt.icon+'"/> ';
} else {
linkContent += '<i class="'+(opt.icon?opt.icon:'" style="display: inline-block;"')+'"></i> ';
}
}
if (opt.sublabel) {
linkContent += '<span class="menu-label-container"><span class="menu-label">'+opt.label+'</span>'+
'<span class="menu-sublabel">'+opt.sublabel+'</span></span>'
} else {
linkContent += '<span class="menu-label">'+opt.label+'</span>'
}
linkContent += '</a>';
var link = $(linkContent).appendTo(item);
menuItems[opt.id] = opt;
if (opt.onselect) {
link.click(function(e) {
e.preventDefault();
if ($(this).parent().hasClass("disabled")) {
return;
}
if (opt.toggle) {
var selected = isSelected(opt.id);
if (typeof opt.toggle === "string") {
if (!selected) {
for (var m in menuItems) {
if (menuItems.hasOwnProperty(m)) {
var mi = menuItems[m];
if (mi.id != opt.id && opt.toggle == mi.toggle) {
setSelected(mi.id,false);
}
}
}
setSelected(opt.id,true);
}
} else {
setSelected(opt.id, !selected);
}
} else {
triggerAction(opt.id);
}
});
if (opt.toggle) {
setInitialState();
}
} else if (opt.href) {
link.attr("target","_blank").attr("href",opt.href);
} else if (!opt.options) {
item.addClass("disabled");
link.click(function(event) {
event.preventDefault();
});
}
if (opt.options) {
item.addClass("dropdown-submenu pull-left");
var submenu = $('<ul id="'+opt.id+'-submenu" class="dropdown-menu"></ul>').appendTo(item);
for (var i=0;i<opt.options.length;i++) {
var li = createMenuItem(opt.options[i]);
if (li) {
li.appendTo(submenu);
}
}
}
if (opt.disabled) {
item.addClass("disabled");
}
}
return item;
}
function createMenu(options) {
var menuParent = $("#"+options.id);
var topMenu = $("<ul/>",{id:options.id+"-submenu", class:"dropdown-menu pull-right"});
if (menuParent.length === 1) {
topMenu.insertAfter(menuParent);
}
var lastAddedSeparator = false;
for (var i=0;i<options.options.length;i++) {
var opt = options.options[i];
if (opt !== null || !lastAddedSeparator) {
var li = createMenuItem(opt);
if (li) {
li.appendTo(topMenu);
lastAddedSeparator = (opt === null);
}
}
}
return topMenu;
}
function triggerAction(id, args) {
var opt = menuItems[id];
var callback = opt.onselect;
if (typeof opt.onselect === 'string') {
callback = RED.actions.get(opt.onselect);
}
if (callback) {
callback.call(opt,args);
} else {
console.log("No callback for",id,opt.onselect);
}
}
function isSelected(id) {
return $("#" + id).hasClass("active");
}
function setSelected(id,state) {
if (isSelected(id) == state) {
return;
}
var opt = menuItems[id];
if (state) {
$("#"+id).addClass("active");
} else {
$("#"+id).removeClass("active");
}
if (opt && opt.onselect) {
triggerAction(opt.id,state);
}
RED.settings.set(opt.setting||("menu-"+opt.id), state);
}
function toggleSelected(id) {
setSelected(id,!isSelected(id));
}
function setDisabled(id,state) {
if (state) {
$("#"+id).parent().addClass("disabled");
} else {
$("#"+id).parent().removeClass("disabled");
}
}
function addItem(id,opt) {
var item = createMenuItem(opt);
if (opt.group) {
var groupItems = $("#"+id+"-submenu").children(".menu-group-"+opt.group);
if (groupItems.length === 0) {
item.appendTo("#"+id+"-submenu");
} else {
for (var i=0;i<groupItems.length;i++) {
var groupItem = groupItems[i];
var label = $(groupItem).find(".menu-label").html();
if (opt.label < label) {
$(groupItem).before(item);
break;
}
}
if (i === groupItems.length) {
item.appendTo("#"+id+"-submenu");
}
}
} else {
item.appendTo("#"+id+"-submenu");
}
}
function removeItem(id) {
$("#"+id).parent().remove();
}
function setAction(id,action) {
var opt = menuItems[id];
if (opt) {
opt.onselect = action;
}
}
return {
init: createMenu,
setSelected: setSelected,
isSelected: isSelected,
toggleSelected: toggleSelected,
setDisabled: setDisabled,
addItem: addItem,
removeItem: removeItem,
setAction: setAction
//TODO: add an api for replacing a submenu - see library.js:loadFlowLibrary
}
})();

View File

@@ -0,0 +1,118 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
RED.panels = (function() {
function createPanel(options) {
var container = options.container || $("#"+options.id);
var children = container.children();
if (children.length !== 2) {
throw new Error("Container must have exactly two children");
}
var vertical = (!options.dir || options.dir === "vertical");
container.addClass("red-ui-panels");
if (!vertical) {
container.addClass("red-ui-panels-horizontal");
}
var separator = $('<div class="red-ui-panels-separator"></div>').insertAfter(children[0]);
var startPosition;
var panelSizes = [];
var modifiedSizes = false;
var panelRatio = 0.5;
separator.draggable({
axis: vertical?"y":"x",
containment: container,
scroll: false,
start:function(event,ui) {
startPosition = vertical?ui.position.top:ui.position.left;
panelSizes = [
vertical?$(children[0]).height():$(children[0]).width(),
vertical?$(children[1]).height():$(children[1]).width()
];
},
drag: function(event,ui) {
var size = vertical?container.height():container.width();
var delta = (vertical?ui.position.top:ui.position.left)-startPosition;
var newSizes = [panelSizes[0]+delta,panelSizes[1]-delta];
if (vertical) {
$(children[0]).height(newSizes[0]);
$(children[1]).height(newSizes[1]);
ui.position.top -= delta;
} else {
$(children[0]).width(newSizes[0]);
$(children[1]).width(newSizes[1]);
ui.position.left -= delta;
}
if (options.resize) {
options.resize(newSizes[0],newSizes[1]);
}
panelRatio = newSizes[0]/(size-8);
},
stop:function(event,ui) {
modifiedSizes = true;
}
});
var panel = {
ratio: function(ratio) {
panelRatio = ratio;
modifiedSizes = true;
if (ratio === 0 || ratio === 1) {
separator.hide();
} else {
separator.show();
}
if (vertical) {
panel.resize(container.height());
} else {
panel.resize(container.width());
}
},
resize: function(size) {
var panelSizes;
if (vertical) {
panelSizes = [$(children[0]).height(),$(children[1]).height()];
container.height(size);
} else {
panelSizes = [$(children[0]).width(),$(children[1]).width()];
container.width(size);
}
if (modifiedSizes) {
var topPanelSize = panelRatio*(size-8);
var bottomPanelSize = size - topPanelSize - 8;
panelSizes = [topPanelSize,bottomPanelSize];
if (vertical) {
$(children[0]).outerHeight(panelSizes[0]);
$(children[1]).outerHeight(panelSizes[1]);
} else {
$(children[0]).outerWidth(panelSizes[0]);
$(children[1]).outerWidth(panelSizes[1]);
}
}
if (options.resize) {
options.resize(panelSizes[0],panelSizes[1]);
}
}
}
return panel;
}
return {
create: createPanel
}
})();

View File

@@ -0,0 +1,219 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
RED.popover = (function() {
var deltaSizes = {
"default": {
top: 10,
topTop: 30,
leftRight: 17,
leftLeft: 25,
leftBottom: 8,
leftTop: 11
},
"small": {
top: 6,
topTop: 20,
leftRight: 8,
leftLeft: 26,
leftBottom: 8,
leftTop: 9
}
}
function createPopover(options) {
var target = options.target;
var direction = options.direction || "right";
var trigger = options.trigger;
var content = options.content;
var delay = options.delay;
var autoClose = options.autoClose;
var width = options.width||"auto";
var size = options.size||"default";
if (!deltaSizes[size]) {
throw new Error("Invalid RED.popover size value:",size);
}
var timer = null;
var active;
var div;
var openPopup = function(instant) {
if (active) {
div = $('<div class="red-ui-popover"></div>');
if (size !== "default") {
div.addClass("red-ui-popover-size-"+size);
}
if (typeof content === 'function') {
var result = content.call(res);
if (result === null) {
return;
}
if (typeof result === 'string') {
div.text(result);
} else {
div.append(result);
}
} else {
div.html(content);
}
if (width !== "auto") {
div.width(width);
}
div.appendTo("body");
var targetPos = target.offset();
var targetWidth = target.outerWidth();
var targetHeight = target.outerHeight();
var divHeight = div.height();
var divWidth = div.width();
var viewportTop = $(window).scrollTop();
var viewportLeft = $(window).scrollLeft();
var viewportBottom = viewportTop + $(window).height();
var viewportRight = viewportLeft + $(window).width();
var top = 0;
var left = 0;
var d = direction;
if (d === 'right') {
top = targetPos.top+targetHeight/2-divHeight/2-deltaSizes[size].top;
left = targetPos.left+targetWidth+deltaSizes[size].leftRight;
} else if (d === 'left') {
top = targetPos.top+targetHeight/2-divHeight/2-deltaSizes[size].top;
left = targetPos.left-deltaSizes[size].leftLeft-divWidth;
} else if (d === 'bottom') {
top = targetPos.top+targetHeight+deltaSizes[size].top;
left = targetPos.left+targetWidth/2-divWidth/2 - deltaSizes[size].leftBottom;
if (left+divWidth > viewportRight) {
d = "left";
top = targetPos.top+targetHeight/2-divHeight/2-deltaSizes[size].top;
left = targetPos.left-deltaSizes[size].leftLeft-divWidth;
if (top+divHeight+targetHeight/2 + 5 > viewportBottom) {
top -= (top+divHeight+targetHeight/2 - viewportBottom + 5)
}
} else if (top+divHeight > viewportBottom) {
d = 'top';
top = targetPos.top-deltaSizes[size].topTop-divHeight;
left = targetPos.left+targetWidth/2-divWidth/2 - deltaSizes[size].leftTop;
}
} else if (d === 'top') {
top = targetPos.top-deltaSizes[size].topTop-divHeight;
left = targetPos.left+targetWidth/2-divWidth/2 - deltaSizes[size].leftTop;
if (top < 0) {
d = 'bottom';
top = targetPos.top+targetHeight+deltaSizes[size].top;
left = targetPos.left+targetWidth/2-divWidth/2 - deltaSizes[size].leftBottom;
}
}
div.addClass('red-ui-popover-'+d).css({top: top, left: left});
if (instant) {
div.show();
} else {
div.fadeIn("fast");
}
}
}
var closePopup = function(instant) {
if (!active) {
if (div) {
if (instant) {
$(this).remove();
} else {
div.fadeOut("fast",function() {
$(this).remove();
});
}
div = null;
}
}
}
if (trigger === 'hover') {
target.on('mouseenter',function(e) {
clearTimeout(timer);
active = true;
timer = setTimeout(openPopup,delay.show);
});
target.on('mouseleave', function(e) {
if (timer) {
clearTimeout(timer);
}
active = false;
setTimeout(closePopup,delay.hide);
});
} else if (trigger === 'click') {
target.click(function(e) {
e.preventDefault();
e.stopPropagation();
active = !active;
if (!active) {
closePopup();
} else {
openPopup();
}
});
} else if (autoClose) {
setTimeout(function() {
active = false;
closePopup();
},autoClose);
}
var res = {
setContent: function(_content) {
content = _content;
return res;
},
open: function (instant) {
active = true;
openPopup(instant);
return res;
},
close: function (instant) {
active = false;
closePopup(instant);
return res;
}
}
return res;
}
return {
create: createPopover,
tooltip: function(target,content, action) {
var label = content;
if (action) {
label = function() {
var label = content;
var shortcut = RED.keyboard.getShortcut(action);
if (shortcut && shortcut.key) {
label = $('<span>'+content+' <span class="red-ui-popover-key">'+RED.keyboard.formatKey(shortcut.key, true)+'</span></span>');
}
return label;
}
}
RED.popover.create({
target:target,
trigger: "hover",
size: "small",
direction: "bottom",
content: label,
delay: { show: 350, hide: 10 }
});
}
}
})();

View File

@@ -0,0 +1,99 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
(function($) {
$.widget( "nodered.searchBox", {
_create: function() {
var that = this;
this.currentTimeout = null;
this.lastSent = "";
this.element.val("");
this.uiContainer = this.element.wrap("<div>").parent();
this.uiContainer.addClass("red-ui-searchBox-container");
$('<i class="fa fa-search"></i>').prependTo(this.uiContainer);
this.clearButton = $('<a href="#"><i class="fa fa-times"></i></a>').appendTo(this.uiContainer);
this.clearButton.on("click",function(e) {
e.preventDefault();
that.element.val("");
that._change("",true);
that.element.focus();
});
this.resultCount = $('<span>',{class:"red-ui-searchBox-resultCount hide"}).appendTo(this.uiContainer);
this.element.val("");
this.element.on("keydown",function(evt) {
if (evt.keyCode === 27) {
that.element.val("");
}
})
this.element.on("keyup",function(evt) {
that._change($(this).val());
});
this.element.on("focus",function() {
$("body").one("mousedown",function() {
that.element.blur();
});
});
},
_change: function(val,instant) {
var fireEvent = false;
if (val === "") {
this.clearButton.hide();
fireEvent = true;
} else {
this.clearButton.show();
fireEvent = (val.length >= (this.options.minimumLength||0));
}
var current = this.element.val();
fireEvent = fireEvent && current !== this.lastSent;
if (fireEvent) {
if (!instant && this.options.delay > 0) {
clearTimeout(this.currentTimeout);
var that = this;
this.currentTimeout = setTimeout(function() {
that.lastSent = that.element.val();
that._trigger("change");
},this.options.delay);
} else {
this._trigger("change");
}
}
},
value: function(val) {
if (val === undefined) {
return this.element.val();
} else {
this.element.val(val);
this._change(val);
}
},
count: function(val) {
if (val === undefined || val === null || val === "") {
this.resultCount.text("").hide();
} else {
this.resultCount.text(val).show();
}
},
change: function() {
this._trigger("change");
}
});
})(jQuery);

View File

@@ -0,0 +1,161 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
RED.stack = (function() {
function createStack(options) {
var container = options.container;
container.addClass("red-ui-stack");
var contentHeight = 0;
var entries = [];
var visible = true;
// TODO: make this a singleton function - and watch out for stacks no longer
// in the DOM
var resizeStack = function() {
if (entries.length > 0) {
var headerHeight = 0;
entries.forEach(function(entry) {
headerHeight += entry.header.outerHeight();
});
var height = container.innerHeight();
contentHeight = height - headerHeight - (entries.length-1);
entries.forEach(function(e) {
e.contentWrap.height(contentHeight);
});
}
}
if (options.fill && options.singleExpanded) {
$(window).resize(resizeStack);
$(window).focus(resizeStack);
}
return {
add: function(entry) {
entries.push(entry);
entry.container = $('<div class="palette-category">').appendTo(container);
if (!visible) {
entry.container.hide();
}
var header = $('<div class="palette-header"></div>').appendTo(entry.container);
entry.header = header;
entry.contentWrap = $('<div></div>',{style:"position:relative"}).appendTo(entry.container);
if (options.fill) {
entry.contentWrap.css("height",contentHeight);
}
entry.content = $('<div></div>').appendTo(entry.contentWrap);
if (entry.collapsible !== false) {
header.click(function() {
if (options.singleExpanded) {
if (!entry.isExpanded()) {
for (var i=0;i<entries.length;i++) {
if (entries[i].isExpanded()) {
entries[i].collapse();
}
}
entry.expand();
}
} else {
entry.toggle();
}
});
var icon = $('<i class="fa fa-angle-down"></i>').appendTo(header);
if (entry.expanded) {
entry.container.addClass("palette-category-expanded");
icon.addClass("expanded");
} else {
entry.contentWrap.hide();
}
} else {
$('<i style="opacity: 0.5;" class="fa fa-angle-down expanded"></i>').appendTo(header);
header.css("cursor","default");
}
entry.title = $('<span></span>').html(entry.title).appendTo(header);
entry.toggle = function() {
if (entry.isExpanded()) {
entry.collapse();
return false;
} else {
entry.expand();
return true;
}
};
entry.expand = function() {
if (!entry.isExpanded()) {
if (entry.onexpand) {
entry.onexpand.call(entry);
}
if (options.singleExpanded) {
entries.forEach(function(e) {
if (e !== entry) {
e.collapse();
}
})
}
icon.addClass("expanded");
entry.container.addClass("palette-category-expanded");
entry.contentWrap.slideDown(200);
return true;
}
};
entry.collapse = function() {
if (entry.isExpanded()) {
icon.removeClass("expanded");
entry.container.removeClass("palette-category-expanded");
entry.contentWrap.slideUp(200);
return true;
}
};
entry.isExpanded = function() {
return entry.container.hasClass("palette-category-expanded");
};
if (options.fill && options.singleExpanded) {
resizeStack();
}
return entry;
},
hide: function() {
visible = false;
entries.forEach(function(entry) {
entry.container.hide();
});
return this;
},
show: function() {
visible = true;
entries.forEach(function(entry) {
entry.container.show();
});
return this;
},
resize: function() {
if (resizeStack) {
resizeStack();
}
}
}
}
return {
create: createStack
}
})();

View File

@@ -0,0 +1,524 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
RED.tabs = (function() {
var defaultTabIcon = "fa fa-lemon-o";
function createTabs(options) {
var tabs = {};
var pinnedTabsCount = 0;
var currentTabWidth;
var currentActiveTabWidth = 0;
var collapsibleMenu;
var ul = options.element || $("#"+options.id);
var wrapper = ul.wrap( "<div>" ).parent();
var scrollContainer = ul.wrap( "<div>" ).parent();
wrapper.addClass("red-ui-tabs");
if (options.vertical) {
wrapper.addClass("red-ui-tabs-vertical");
}
if (options.addButton) {
wrapper.addClass("red-ui-tabs-add");
var addButton = $('<div class="red-ui-tab-button"><a href="#"><i class="fa fa-plus"></i></a></div>').appendTo(wrapper);
addButton.find('a').click(function(evt) {
evt.preventDefault();
if (typeof options.addButton === 'function') {
options.addButton();
} else if (typeof options.addButton === 'string') {
RED.actions.invoke(options.addButton);
}
})
if (typeof options.addButton === 'string') {
var l = options.addButton;
if (options.addButtonCaption) {
l = options.addButtonCaption
}
RED.popover.tooltip(addButton,l,options.addButton);
}
}
var scrollLeft;
var scrollRight;
if (options.scrollable) {
wrapper.addClass("red-ui-tabs-scrollable");
scrollContainer.addClass("red-ui-tabs-scroll-container");
scrollContainer.scroll(updateScroll);
scrollLeft = $('<div class="red-ui-tab-button red-ui-tab-scroll red-ui-tab-scroll-left"><a href="#" style="display:none;"><i class="fa fa-caret-left"></i></a></div>').appendTo(wrapper).find("a");
scrollLeft.on('mousedown',function(evt) { scrollEventHandler(evt,'-=150') }).on('click',function(evt){ evt.preventDefault();});
scrollRight = $('<div class="red-ui-tab-button red-ui-tab-scroll red-ui-tab-scroll-right"><a href="#" style="display:none;"><i class="fa fa-caret-right"></i></a></div>').appendTo(wrapper).find("a");
scrollRight.on('mousedown',function(evt) { scrollEventHandler(evt,'+=150') }).on('click',function(evt){ evt.preventDefault();});
}
if (options.collapsible) {
// var dropDown = $('<div>',{class:"red-ui-tabs-select"}).appendTo(wrapper);
// ul.hide();
wrapper.addClass("red-ui-tabs-collapsible");
var collapsedButtonsRow = $('<div class="red-ui-tab-link-buttons"></div>').appendTo(wrapper);
if (options.menu !== false) {
var selectButton = $('<a href="#"><i class="fa fa-caret-down"></i></a>').appendTo(collapsedButtonsRow);
selectButton.addClass("red-ui-tab-link-button-menu")
selectButton.click(function(evt) {
evt.preventDefault();
if (!collapsibleMenu) {
var pinnedOptions = [];
var options = [];
ul.children().each(function(i,el) {
var id = $(el).data('tabId');
var opt = {
id:"red-ui-tabs-menu-option-"+id,
icon: tabs[id].iconClass || defaultTabIcon,
label: tabs[id].name,
onselect: function() {
activateTab(id);
}
};
if (tabs[id].pinned) {
pinnedOptions.push(opt);
} else {
options.push(opt);
}
});
options = pinnedOptions.concat(options);
collapsibleMenu = RED.menu.init({id:"debug-message-option-menu",options: options});
collapsibleMenu.css({
position: "absolute"
})
collapsibleMenu.on('mouseleave', function(){ $(this).hide() });
collapsibleMenu.on('mouseup', function() { $(this).hide() });
collapsibleMenu.appendTo("body");
}
var elementPos = selectButton.offset();
collapsibleMenu.css({
top: (elementPos.top+selectButton.height()-20)+"px",
left: (elementPos.left - collapsibleMenu.width() + selectButton.width())+"px"
})
collapsibleMenu.toggle();
})
}
}
function scrollEventHandler(evt,dir) {
evt.preventDefault();
if ($(this).hasClass('disabled')) {
return;
}
var currentScrollLeft = scrollContainer.scrollLeft();
scrollContainer.animate( { scrollLeft: dir }, 100);
var interval = setInterval(function() {
var newScrollLeft = scrollContainer.scrollLeft()
if (newScrollLeft === currentScrollLeft) {
clearInterval(interval);
return;
}
currentScrollLeft = newScrollLeft;
scrollContainer.animate( { scrollLeft: dir }, 100);
},100);
$(this).one('mouseup',function() {
clearInterval(interval);
})
}
ul.children().first().addClass("active");
ul.children().addClass("red-ui-tab");
function onTabClick() {
if (options.onclick) {
options.onclick(tabs[$(this).attr('href').slice(1)]);
}
activateTab($(this));
return false;
}
function updateScroll() {
if (ul.children().length !== 0) {
var sl = scrollContainer.scrollLeft();
var scWidth = scrollContainer.width();
var ulWidth = ul.width();
if (sl === 0) {
scrollLeft.hide();
} else {
scrollLeft.show();
}
if (sl === ulWidth-scWidth) {
scrollRight.hide();
} else {
scrollRight.show();
}
}
}
function onTabDblClick() {
if (options.ondblclick) {
options.ondblclick(tabs[$(this).attr('href').slice(1)]);
}
return false;
}
function activateTab(link) {
if (typeof link === "string") {
link = ul.find("a[href='#"+link+"']");
}
if (link.length === 0) {
return;
}
if (!link.parent().hasClass("active")) {
ul.children().removeClass("active");
ul.children().css({"transition": "width 100ms"});
link.parent().addClass("active");
var parentId = link.parent().attr('id');
wrapper.find(".red-ui-tab-link-button").removeClass("active selected");
$("#"+parentId+"-link-button").addClass("active selected");
if (options.scrollable) {
var pos = link.parent().position().left;
if (pos-21 < 0) {
scrollContainer.animate( { scrollLeft: '+='+(pos-50) }, 300);
} else if (pos + 120 > scrollContainer.width()) {
scrollContainer.animate( { scrollLeft: '+='+(pos + 140-scrollContainer.width()) }, 300);
}
}
if (options.onchange) {
options.onchange(tabs[link.attr('href').slice(1)]);
}
updateTabWidths();
setTimeout(function() {
ul.children().css({"transition": ""});
},100);
}
}
function activatePreviousTab() {
var previous = ul.find("li.active").prev();
if (previous.length > 0) {
activateTab(previous.find("a"));
}
}
function activateNextTab() {
var next = ul.find("li.active").next();
if (next.length > 0) {
activateTab(next.find("a"));
}
}
function updateTabWidths() {
if (options.vertical) {
return;
}
var tabs = ul.find("li.red-ui-tab");
var width = wrapper.width();
var tabCount = tabs.size();
var tabWidth;
if (options.collapsible) {
tabWidth = width - collapsedButtonsRow.width()-10;
if (tabWidth < 198) {
var delta = 198 - tabWidth;
var b = collapsedButtonsRow.find("a:last").prev();
while (b.is(":not(:visible)")) {
b = b.prev();
}
if (!b.hasClass("red-ui-tab-link-button-pinned")) {
b.hide();
}
tabWidth = width - collapsedButtonsRow.width()-10;
} else {
var space = width - 198 - collapsedButtonsRow.width();
if (space > 40) {
collapsedButtonsRow.find("a:not(:visible):first").show();
tabWidth = width - collapsedButtonsRow.width()-10;
}
}
tabs.css({width:tabWidth});
} else {
var tabWidth = (width-12-(tabCount*6))/tabCount;
currentTabWidth = (100*tabWidth/width)+"%";
currentActiveTabWidth = currentTabWidth+"%";
if (options.scrollable) {
tabWidth = Math.max(tabWidth,140);
currentTabWidth = tabWidth+"px";
currentActiveTabWidth = 0;
var listWidth = Math.max(wrapper.width(),12+(tabWidth+6)*tabCount);
ul.width(listWidth);
updateScroll();
} else if (options.hasOwnProperty("minimumActiveTabWidth")) {
if (tabWidth < options.minimumActiveTabWidth) {
tabCount -= 1;
tabWidth = (width-12-options.minimumActiveTabWidth-(tabCount*6))/tabCount;
currentTabWidth = (100*tabWidth/width)+"%";
currentActiveTabWidth = options.minimumActiveTabWidth+"px";
} else {
currentActiveTabWidth = 0;
}
}
if (options.collapsible) {
console.log(currentTabWidth);
}
tabs.css({width:currentTabWidth});
if (tabWidth < 50) {
ul.find(".red-ui-tab-close").hide();
ul.find(".red-ui-tab-icon").hide();
ul.find(".red-ui-tab-label").css({paddingLeft:Math.min(12,Math.max(0,tabWidth-38))+"px"})
} else {
ul.find(".red-ui-tab-close").show();
ul.find(".red-ui-tab-icon").show();
ul.find(".red-ui-tab-label").css({paddingLeft:""})
}
if (currentActiveTabWidth !== 0) {
ul.find("li.red-ui-tab.active").css({"width":options.minimumActiveTabWidth});
ul.find("li.red-ui-tab.active .red-ui-tab-close").show();
ul.find("li.red-ui-tab.active .red-ui-tab-icon").show();
ul.find("li.red-ui-tab.active .red-ui-tab-label").css({paddingLeft:""})
}
}
}
ul.find("li.red-ui-tab a").on("click",onTabClick).on("dblclick",onTabDblClick);
setTimeout(function() {
updateTabWidths();
},0);
function removeTab(id) {
var li = ul.find("a[href='#"+id+"']").parent();
if (li.hasClass("active")) {
var tab = li.prev();
if (tab.size() === 0) {
tab = li.next();
}
activateTab(tab.find("a"));
}
li.remove();
if (tabs[id].pinned) {
pinnedTabsCount--;
}
if (options.onremove) {
options.onremove(tabs[id]);
}
delete tabs[id];
updateTabWidths();
collapsibleMenu = null;
}
return {
addTab: function(tab) {
tabs[tab.id] = tab;
var li = $("<li/>",{class:"red-ui-tab"}).appendTo(ul);
li.attr('id',"red-ui-tab-"+(tab.id.replace(".","-")));
li.data("tabId",tab.id);
if (options.maximumTabWidth) {
li.css("maxWidth",options.maximumTabWidth+"px");
}
var link = $("<a/>",{href:"#"+tab.id, class:"red-ui-tab-label"}).appendTo(li);
if (tab.icon) {
$('<img src="'+tab.icon+'" class="red-ui-tab-icon"/>').appendTo(link);
} else if (tab.iconClass) {
$('<i>',{class:"red-ui-tab-icon "+tab.iconClass}).appendTo(link);
}
var span = $('<span/>',{class:"bidiAware"}).text(tab.label).appendTo(link);
span.attr('dir', RED.text.bidi.resolveBaseTextDir(tab.label));
if (options.collapsible) {
li.addClass("red-ui-tab-pinned");
var pinnedLink = $('<a href="#'+tab.id+'" class="red-ui-tab-link-button"></a>');
if (tab.pinned) {
if (pinnedTabsCount === 0) {
pinnedLink.prependTo(collapsedButtonsRow)
} else {
pinnedLink.insertAfter(collapsedButtonsRow.find("a.red-ui-tab-link-button-pinned:last"));
}
} else {
if (options.menu !== false) {
pinnedLink.insertBefore(collapsedButtonsRow.find("a:last"));
} else {
pinnedLink.appendTo(collapsedButtonsRow);
}
}
pinnedLink.attr('id',li.attr('id')+"-link-button");
if (tab.iconClass) {
$('<i>',{class:tab.iconClass}).appendTo(pinnedLink);
} else {
$('<i>',{class:defaultTabIcon}).appendTo(pinnedLink);
}
pinnedLink.click(function(evt) {
evt.preventDefault();
activateTab(tab.id);
});
if (tab.pinned) {
pinnedLink.addClass("red-ui-tab-link-button-pinned");
pinnedTabsCount++;
}
RED.popover.tooltip($(pinnedLink), tab.name, tab.action);
}
link.on("click",onTabClick);
link.on("dblclick",onTabDblClick);
if (tab.closeable) {
var closeLink = $("<a/>",{href:"#",class:"red-ui-tab-close"}).appendTo(li);
closeLink.append('<i class="fa fa-times" />');
closeLink.on("click",function(event) {
event.preventDefault();
removeTab(tab.id);
});
}
if (options.onadd) {
options.onadd(tab);
}
link.attr("title",tab.label);
if (ul.find("li.red-ui-tab").size() == 1) {
activateTab(link);
}
if (options.onreorder) {
var originalTabOrder;
var tabDragIndex;
var tabElements = [];
var startDragIndex;
li.draggable({
axis:"x",
distance: 20,
start: function(event,ui) {
originalTabOrder = [];
tabElements = [];
ul.children().each(function(i) {
tabElements[i] = {
el:$(this),
text: $(this).text(),
left: $(this).position().left,
width: $(this).width()
};
if ($(this).is(li)) {
tabDragIndex = i;
startDragIndex = i;
}
originalTabOrder.push($(this).data("tabId"));
});
ul.children().each(function(i) {
if (i!==tabDragIndex) {
$(this).css({
position: 'absolute',
left: tabElements[i].left+"px",
width: tabElements[i].width+2,
transition: "left 0.3s"
});
}
})
if (!li.hasClass('active')) {
li.css({'zIndex':1});
}
},
drag: function(event,ui) {
ui.position.left += tabElements[tabDragIndex].left+scrollContainer.scrollLeft();
var tabCenter = ui.position.left + tabElements[tabDragIndex].width/2 - scrollContainer.scrollLeft();
for (var i=0;i<tabElements.length;i++) {
if (i === tabDragIndex) {
continue;
}
if (tabCenter > tabElements[i].left && tabCenter < tabElements[i].left+tabElements[i].width) {
if (i < tabDragIndex) {
tabElements[i].left += tabElements[tabDragIndex].width+8;
tabElements[tabDragIndex].el.detach().insertBefore(tabElements[i].el);
} else {
tabElements[i].left -= tabElements[tabDragIndex].width+8;
tabElements[tabDragIndex].el.detach().insertAfter(tabElements[i].el);
}
tabElements[i].el.css({left:tabElements[i].left+"px"});
tabElements.splice(i, 0, tabElements.splice(tabDragIndex, 1)[0]);
tabDragIndex = i;
break;
}
}
},
stop: function(event,ui) {
ul.children().css({position:"relative",left:"",transition:""});
if (!li.hasClass('active')) {
li.css({zIndex:""});
}
updateTabWidths();
if (startDragIndex !== tabDragIndex) {
options.onreorder(originalTabOrder, $.makeArray(ul.children().map(function() { return $(this).data('tabId');})));
}
activateTab(tabElements[tabDragIndex].el.data('tabId'));
}
})
}
setTimeout(function() {
updateTabWidths();
},10);
collapsibleMenu = null;
},
removeTab: removeTab,
activateTab: activateTab,
nextTab: activateNextTab,
previousTab: activatePreviousTab,
resize: updateTabWidths,
count: function() {
return ul.find("li.red-ui-tab").size();
},
contains: function(id) {
return ul.find("a[href='#"+id+"']").length > 0;
},
renameTab: function(id,label) {
tabs[id].label = label;
var tab = ul.find("a[href='#"+id+"']");
tab.attr("title",label);
tab.find("span.bidiAware").text(label).attr('dir', RED.text.bidi.resolveBaseTextDir(label));
updateTabWidths();
},
order: function(order) {
var existingTabOrder = $.makeArray(ul.children().map(function() { return $(this).data('tabId');}));
if (existingTabOrder.length !== order.length) {
return
}
var i;
var match = true;
for (i=0;i<order.length;i++) {
if (order[i] !== existingTabOrder[i]) {
match = false;
break;
}
}
if (match) {
return;
}
var existingTabMap = {};
var existingTabs = ul.children().detach().each(function() {
existingTabMap[$(this).data("tabId")] = $(this);
});
for (i=0;i<order.length;i++) {
existingTabMap[order[i]].appendTo(ul);
}
}
}
}
return {
create: createTabs
}
})();

View File

@@ -0,0 +1,700 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
(function($) {
var contextParse = function(v) {
var parts = RED.utils.parseContextKey(v);
return {
option: parts.store,
value: parts.key
}
}
var contextExport = function(v,opt) {
if (!opt) {
return v;
}
var store = ((typeof opt === "string")?opt:opt.value)
if (store !== RED.settings.context.default) {
return "#:("+store+")::"+v;
} else {
return v;
}
}
var allOptions = {
msg: {value:"msg",label:"msg.",validate:RED.utils.validatePropertyExpression},
flow: {value:"flow",label:"flow.",hasValue:true,
options:[],
validate:RED.utils.validatePropertyExpression,
parse: contextParse,
export: contextExport
},
global: {value:"global",label:"global.",hasValue:true,
options:[],
validate:RED.utils.validatePropertyExpression,
parse: contextParse,
export: contextExport
},
str: {value:"str",label:"string",icon:"red/images/typedInput/az.png"},
num: {value:"num",label:"number",icon:"red/images/typedInput/09.png",validate:/^[+-]?[0-9]*\.?[0-9]*([eE][-+]?[0-9]+)?$/},
bool: {value:"bool",label:"boolean",icon:"red/images/typedInput/bool.png",options:["true","false"]},
json: {
value:"json",
label:"JSON",
icon:"red/images/typedInput/json.png",
validate: function(v) { try{JSON.parse(v);return true;}catch(e){return false;}},
expand: function() {
var that = this;
var value = this.value();
try {
value = JSON.stringify(JSON.parse(value),null,4);
} catch(err) {
}
RED.editor.editJSON({
value: value,
complete: function(v) {
var value = v;
try {
value = JSON.stringify(JSON.parse(v));
} catch(err) {
}
that.value(value);
}
})
}
},
re: {value:"re",label:"regular expression",icon:"red/images/typedInput/re.png"},
date: {value:"date",label:"timestamp",hasValue:false},
jsonata: {
value: "jsonata",
label: "expression",
icon: "red/images/typedInput/expr.png",
validate: function(v) { try{jsonata(v);return true;}catch(e){return false;}},
expand:function() {
var that = this;
RED.editor.editExpression({
value: this.value().replace(/\t/g,"\n"),
complete: function(v) {
that.value(v.replace(/\n/g,"\t"));
}
})
}
},
bin: {
value: "bin",
label: "buffer",
icon: "red/images/typedInput/bin.png",
expand: function() {
var that = this;
RED.editor.editBuffer({
value: this.value(),
complete: function(v) {
that.value(v);
}
})
}
},
env: {
value: "env",
label: "env variable",
icon: "red/images/typedInput/env.png"
}
};
var nlsd = false;
$.widget( "nodered.typedInput", {
_create: function() {
try {
if (!nlsd && RED && RED._) {
for (var i in allOptions) {
if (allOptions.hasOwnProperty(i)) {
allOptions[i].label = RED._("typedInput.type."+i,{defaultValue:allOptions[i].label});
}
}
var contextStores = RED.settings.context.stores;
var contextOptions = contextStores.map(function(store) {
return {value:store,label: store, icon:'<i class="red-ui-typedInput-icon fa fa-database" style="color: #'+(store==='memory'?'ddd':'777')+'"></i>'}
})
if (contextOptions.length < 2) {
allOptions.flow.options = [];
allOptions.global.options = [];
} else {
allOptions.flow.options = contextOptions;
allOptions.global.options = contextOptions;
}
}
nlsd = true;
var that = this;
this.disarmClick = false;
this.input = $('<input type="text"></input>');
this.input.insertAfter(this.element);
this.input.val(this.element.val());
this.element.addClass('red-ui-typedInput');
this.uiWidth = this.element.outerWidth();
this.elementDiv = this.input.wrap("<div>").parent().addClass('red-ui-typedInput-input');
this.uiSelect = this.elementDiv.wrap( "<div>" ).parent();
var attrStyle = this.element.attr('style');
var m;
if ((m = /width\s*:\s*(calc\s*\(.*\)|\d+(%|px))/i.exec(attrStyle)) !== null) {
this.input.css('width','100%');
this.uiSelect.width(m[1]);
this.uiWidth = null;
} else {
this.uiSelect.width(this.uiWidth);
}
["Right","Left"].forEach(function(d) {
var m = that.element.css("margin"+d);
that.uiSelect.css("margin"+d,m);
that.input.css("margin"+d,0);
});
this.uiSelect.addClass("red-ui-typedInput-container");
this.element.attr('type','hidden');
this.options.types = this.options.types||Object.keys(allOptions);
this.selectTrigger = $('<button tabindex="0"></button>').prependTo(this.uiSelect);
$('<i class="red-ui-typedInput-icon fa fa-sort-desc"></i>').toggle(this.options.types.length > 1).appendTo(this.selectTrigger);
this.selectLabel = $('<span class="red-ui-typedInput-type-label"></span>').appendTo(this.selectTrigger);
this.types(this.options.types);
if (this.options.typeField) {
this.typeField = $(this.options.typeField).hide();
var t = this.typeField.val();
if (t && this.typeMap[t]) {
this.options.default = t;
}
} else {
this.typeField = $("<input>",{type:'hidden'}).appendTo(this.uiSelect);
}
this.input.on('focus', function() {
that.uiSelect.addClass('red-ui-typedInput-focus');
});
this.input.on('blur', function() {
that.uiSelect.removeClass('red-ui-typedInput-focus');
});
this.input.on('change', function() {
that.validate();
that.element.val(that.value());
that.element.trigger('change',that.propertyType,that.value());
})
this.selectTrigger.click(function(event) {
event.preventDefault();
that._showTypeMenu();
});
this.selectTrigger.on('keydown',function(evt) {
if (evt.keyCode === 40) {
// Down
that._showTypeMenu();
}
}).on('focus', function() {
that.uiSelect.addClass('red-ui-typedInput-focus');
})
// explicitly set optionSelectTrigger display to inline-block otherwise jQ sets it to 'inline'
this.optionSelectTrigger = $('<button tabindex="0" class="red-ui-typedInput-option-trigger" style="display:inline-block"><span class="red-ui-typedInput-option-caret"><i class="red-ui-typedInput-icon fa fa-sort-desc"></i></span></button>').appendTo(this.uiSelect);
this.optionSelectLabel = $('<span class="red-ui-typedInput-option-label"></span>').prependTo(this.optionSelectTrigger);
RED.popover.tooltip(this.optionSelectLabel,function() {
return that.optionValue;
});
this.optionSelectTrigger.click(function(event) {
event.preventDefault();
that._showOptionSelectMenu();
}).on('keydown', function(evt) {
if (evt.keyCode === 40) {
// Down
that._showOptionSelectMenu();
}
}).on('blur', function() {
that.uiSelect.removeClass('red-ui-typedInput-focus');
}).on('focus', function() {
that.uiSelect.addClass('red-ui-typedInput-focus');
});
this.optionExpandButton = $('<button tabindex="0" class="red-ui-typedInput-option-expand" style="display:inline-block"><i class="red-ui-typedInput-icon fa fa-ellipsis-h"></i></button>').appendTo(this.uiSelect);
this.type(this.options.default||this.typeList[0].value);
}catch(err) {
console.log(err.stack);
}
},
_showTypeMenu: function() {
if (this.typeList.length > 1) {
this._showMenu(this.menu,this.selectTrigger);
this.menu.find("[value='"+this.propertyType+"']").focus();
} else {
this.input.focus();
}
},
_showOptionSelectMenu: function() {
if (this.optionMenu) {
this.optionMenu.css({
minWidth:this.optionSelectLabel.width()
});
this._showMenu(this.optionMenu,this.optionSelectTrigger);
var selectedOption = this.optionMenu.find("[value='"+this.optionValue+"']");
if (selectedOption.length === 0) {
selectedOption = this.optionMenu.children(":first");
}
selectedOption.focus();
}
},
_hideMenu: function(menu) {
$(document).off("mousedown.close-property-select");
menu.hide();
if (this.elementDiv.is(":visible")) {
this.input.focus();
} else if (this.optionSelectTrigger.is(":visible")){
this.optionSelectTrigger.focus();
} else {
this.selectTrigger.focus();
}
},
_createMenu: function(opts,callback) {
var that = this;
var menu = $("<div>").addClass("red-ui-typedInput-options");
opts.forEach(function(opt) {
if (typeof opt === 'string') {
opt = {value:opt,label:opt};
}
var op = $('<a href="#"></a>').attr("value",opt.value).appendTo(menu);
if (opt.label) {
op.text(opt.label);
}
if (opt.icon) {
if (opt.icon.indexOf("<") === 0) {
$(opt.icon).prependTo(op);
} else if (opt.icon.indexOf("/") !== -1) {
$('<img>',{src:opt.icon,style:"margin-right: 4px; height: 18px;"}).prependTo(op);
} else {
$('<i>',{class:"red-ui-typedInput-icon "+opt.icon}).prependTo(op);
}
} else {
op.css({paddingLeft: "18px"});
}
if (!opt.icon && !opt.label) {
op.text(opt.value);
}
op.click(function(event) {
event.preventDefault();
callback(opt.value);
that._hideMenu(menu);
});
});
menu.css({
display: "none",
});
menu.appendTo(document.body);
menu.on('keydown', function(evt) {
if (evt.keyCode === 40) {
// DOWN
$(this).children(":focus").next().focus();
} else if (evt.keyCode === 38) {
// UP
$(this).children(":focus").prev().focus();
} else if (evt.keyCode === 27) {
that._hideMenu(menu);
}
})
return menu;
},
_showMenu: function(menu,relativeTo) {
if (this.disarmClick) {
this.disarmClick = false;
return
}
var that = this;
var pos = relativeTo.offset();
var height = relativeTo.height();
var menuHeight = menu.height();
var top = (height+pos.top-3);
if (top+menuHeight > $(window).height()) {
top -= (top+menuHeight)-$(window).height()+5;
}
menu.css({
top: top+"px",
left: (2+pos.left)+"px",
});
menu.slideDown(100);
this._delay(function() {
that.uiSelect.addClass('red-ui-typedInput-focus');
$(document).on("mousedown.close-property-select", function(event) {
if(!$(event.target).closest(menu).length) {
that._hideMenu(menu);
}
if ($(event.target).closest(relativeTo).length) {
that.disarmClick = true;
event.preventDefault();
}
})
});
},
_getLabelWidth: function(label) {
var labelWidth = label.outerWidth();
if (labelWidth === 0) {
var container = $('<div class="red-ui-typedInput-container"></div>').css({
position:"absolute",
top:0,
left:-1000
}).appendTo(document.body);
var newTrigger = label.clone().appendTo(container);
labelWidth = newTrigger.outerWidth();
container.remove();
}
return labelWidth;
},
_resize: function() {
if (this.uiWidth !== null) {
this.uiSelect.width(this.uiWidth);
}
var type = this.typeMap[this.propertyType];
if (type && type.hasValue === false) {
this.selectTrigger.addClass("red-ui-typedInput-full-width");
} else {
this.selectTrigger.removeClass("red-ui-typedInput-full-width");
var labelWidth = this._getLabelWidth(this.selectTrigger);
this.elementDiv.css('left',labelWidth+"px");
if (this.optionExpandButton.is(":visible")) {
this.elementDiv.css('right',"22px");
} else {
this.elementDiv.css('right','0');
this.input.css({
'border-top-right-radius': '4px',
'border-bottom-right-radius': '4px'
});
}
// if (this.optionSelectTrigger) {
// this.optionSelectTrigger.css({'left':(labelWidth)+"px",'width':'calc( 100% - '+labelWidth+'px )'});
// }
if (this.optionSelectTrigger) {
if (type && type.options && type.hasValue === true) {
this.optionSelectLabel.css({'left':'auto'})
var lw = this._getLabelWidth(this.optionSelectLabel);
this.optionSelectTrigger.css({'width':(23+lw)+"px"});
this.elementDiv.css('right',(23+lw)+"px");
this.input.css({
'border-top-right-radius': 0,
'border-bottom-right-radius': 0
});
} else {
this.optionSelectLabel.css({'left':'0'})
this.optionSelectTrigger.css({'width':'calc( 100% - '+labelWidth+'px )'});
if (!this.optionExpandButton.is(":visible")) {
this.elementDiv.css({'right':0});
this.input.css({
'border-top-right-radius': '4px',
'border-bottom-right-radius': '4px'
});
}
}
}
}
},
_updateOptionSelectLabel: function(o) {
var opt = this.typeMap[this.propertyType];
this.optionSelectLabel.empty();
if (o.icon) {
if (o.icon.indexOf("<") === 0) {
$(o.icon).prependTo(this.optionSelectLabel);
} else if (o.icon.indexOf("/") !== -1) {
// url
$('<img>',{src:o.icon,style:"height: 18px;"}).prependTo(this.optionSelectLabel);
} else {
// icon class
$('<i>',{class:"red-ui-typedInput-icon "+o.icon}).prependTo(this.optionSelectLabel);
}
} else if (o.label) {
this.optionSelectLabel.text(o.label);
} else {
this.optionSelectLabel.text(o.value);
}
if (opt.hasValue) {
this.optionValue = o.value;
this._resize();
this.input.trigger('change',this.propertyType,this.value());
}
},
_destroy: function() {
if (this.optionMenu) {
this.optionMenu.remove();
}
this.menu.remove();
},
types: function(types) {
var that = this;
var currentType = this.type();
this.typeMap = {};
this.typeList = types.map(function(opt) {
var result;
if (typeof opt === 'string') {
result = allOptions[opt];
} else {
result = opt;
}
that.typeMap[result.value] = result;
return result;
});
this.selectTrigger.toggleClass("disabled", this.typeList.length === 1);
this.selectTrigger.find(".fa-sort-desc").toggle(this.typeList.length > 1)
if (this.menu) {
this.menu.remove();
}
this.menu = this._createMenu(this.typeList, function(v) { that.type(v) });
if (currentType && !this.typeMap.hasOwnProperty(currentType)) {
this.type(this.typeList[0].value);
} else {
this.propertyType = null;
this.type(currentType);
}
setTimeout(function() {that._resize();},0);
},
width: function(desiredWidth) {
this.uiWidth = desiredWidth;
this._resize();
},
value: function(value) {
if (!arguments.length) {
var v = this.input.val();
if (this.typeMap[this.propertyType].export) {
v = this.typeMap[this.propertyType].export(v,this.optionValue)
}
return v;
} else {
var selectedOption;
if (this.typeMap[this.propertyType].options) {
for (var i=0;i<this.typeMap[this.propertyType].options.length;i++) {
var op = this.typeMap[this.propertyType].options[i];
if (typeof op === "string") {
if (op === value) {
selectedOption = this.activeOptions[op];
break;
}
} else if (op.value === value) {
selectedOption = op;
break;
}
}
if (!selectedOption) {
selectedOption = {value:""}
}
this._updateOptionSelectLabel(selectedOption)
}
this.input.val(value);
this.input.trigger('change',this.type(),value);
}
},
type: function(type) {
if (!arguments.length) {
return this.propertyType;
} else {
var that = this;
var opt = this.typeMap[type];
if (opt && this.propertyType !== type) {
this.propertyType = type;
if (this.typeField) {
this.typeField.val(type);
}
this.selectLabel.empty();
var image;
if (opt.icon) {
if (opt.icon.indexOf("<") === 0) {
$(opt.icon).prependTo(this.selectLabel);
}
else if (opt.icon.indexOf("/") !== -1) {
image = new Image();
image.name = opt.icon;
image.src = opt.icon;
$('<img>',{src:opt.icon,style:"margin-right: 4px;height: 18px;"}).prependTo(this.selectLabel);
}
else {
$('<i>',{class:"red-ui-typedInput-icon "+opt.icon}).prependTo(this.selectLabel);
}
} else {
this.selectLabel.text(opt.label);
}
if (opt.options) {
if (this.optionExpandButton) {
this.optionExpandButton.hide();
}
if (this.optionSelectTrigger) {
this.optionSelectTrigger.show();
if (!opt.hasValue) {
this.elementDiv.hide();
} else {
this.elementDiv.show();
}
this.activeOptions = {};
opt.options.forEach(function(o) {
if (typeof o === 'string') {
that.activeOptions[o] = {label:o,value:o};
} else {
that.activeOptions[o.value] = o;
}
});
if (!that.activeOptions.hasOwnProperty(that.optionValue)) {
that.optionValue = null;
}
this.optionMenu = this._createMenu(opt.options,function(v){
that._updateOptionSelectLabel(that.activeOptions[v]);
if (!opt.hasValue) {
that.value(that.activeOptions[v].value)
}
});
var op;
if (!opt.hasValue) {
var currentVal = this.input.val();
var validValue = false;
for (var i=0;i<opt.options.length;i++) {
op = opt.options[i];
if (typeof op === "string" && op === currentVal) {
that._updateOptionSelectLabel({value:currentVal});
validValue = true;
break;
} else if (op.value === currentVal) {
that._updateOptionSelectLabel(op);
validValue = true;
break;
}
}
if (!validValue) {
op = opt.options[0];
if (typeof op === "string") {
this.value(op);
that._updateOptionSelectLabel({value:op});
} else {
this.value(op.value);
that._updateOptionSelectLabel(op);
}
}
} else {
var selectedOption = this.optionValue||opt.options[0];
if (opt.parse) {
var parts = opt.parse(this.input.val());
if (parts.option) {
selectedOption = parts.option;
if (!this.activeOptions.hasOwnProperty(selectedOption)) {
parts.option = Object.keys(this.activeOptions)[0];
selectedOption = parts.option
}
}
this.input.val(parts.value);
if (opt.export) {
this.element.val(opt.export(parts.value,parts.option||selectedOption));
}
}
if (typeof selectedOption === "string") {
this.optionValue = selectedOption;
if (!this.activeOptions.hasOwnProperty(selectedOption)) {
selectedOption = Object.keys(this.activeOptions)[0];
}
if (!selectedOption) {
this.optionSelectTrigger.hide();
} else {
this._updateOptionSelectLabel(this.activeOptions[selectedOption]);
}
} else if (selectedOption) {
this.optionValue = selectedOption.value;
this._updateOptionSelectLabel(selectedOption);
} else {
this.optionSelectTrigger.hide();
}
}
}
} else {
if (this.optionMenu) {
this.optionMenu.remove();
this.optionMenu = null;
}
if (this.optionSelectTrigger) {
this.optionSelectTrigger.hide();
}
if (opt.hasValue === false) {
this.oldValue = this.input.val();
this.input.val("");
this.elementDiv.hide();
} else {
if (this.oldValue !== undefined) {
this.input.val(this.oldValue);
delete this.oldValue;
}
this.elementDiv.show();
}
if (this.optionExpandButton) {
if (opt.expand && typeof opt.expand === 'function') {
this.optionExpandButton.show();
this.optionExpandButton.off('click');
this.optionExpandButton.on('click',function(evt) {
evt.preventDefault();
opt.expand.call(that);
})
} else {
this.optionExpandButton.hide();
}
}
this.input.trigger('change',this.propertyType,this.value());
}
if (image) {
image.onload = function() { that._resize(); }
image.onerror = function() { that._resize(); }
} else {
this._resize();
}
}
}
},
validate: function() {
var result;
var value = this.value();
var type = this.type();
if (this.typeMap[type] && this.typeMap[type].validate) {
var val = this.typeMap[type].validate;
if (typeof val === 'function') {
result = val(value);
} else {
result = val.test(value);
}
} else {
result = true;
}
if (result) {
this.uiSelect.removeClass('input-error');
} else {
this.uiSelect.addClass('input-error');
}
return result;
},
show: function() {
this.uiSelect.show();
this._resize();
},
hide: function() {
this.uiSelect.hide();
}
});
})(jQuery);

View File

@@ -0,0 +1,508 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
RED.deploy = (function() {
var deploymentTypes = {
"full":{img:"red/images/deploy-full-o.png"},
"nodes":{img:"red/images/deploy-nodes-o.png"},
"flows":{img:"red/images/deploy-flows-o.png"}
}
var ignoreDeployWarnings = {
unknown: false,
unusedConfig: false,
invalid: false
}
var deploymentType = "full";
var deployInflight = false;
var currentDiff = null;
function changeDeploymentType(type) {
deploymentType = type;
$("#btn-deploy-icon").attr("src",deploymentTypes[type].img);
}
/**
* options:
* type: "default" - Button with drop-down options - no further customisation available
* type: "simple" - Button without dropdown. Customisations:
* label: the text to display - default: "Deploy"
* icon : the icon to use. Null removes the icon. default: "red/images/deploy-full-o.png"
*/
function init(options) {
options = options || {};
var type = options.type || "default";
if (type == "default") {
$('<li><span class="deploy-button-group button-group">'+
'<a id="btn-deploy" class="deploy-button disabled" href="#">'+
'<span class="deploy-button-content">'+
'<img id="btn-deploy-icon" src="red/images/deploy-full-o.png"> '+
'<span>'+RED._("deploy.deploy")+'</span>'+
'</span>'+
'<span class="deploy-button-spinner hide">'+
'<img src="red/images/spin.svg"/>'+
'</span>'+
'</a>'+
'<a id="btn-deploy-options" data-toggle="dropdown" class="deploy-button" href="#"><i class="fa fa-caret-down"></i></a>'+
'</span></li>').prependTo(".header-toolbar");
RED.menu.init({id:"btn-deploy-options",
options: [
{id:"deploymenu-item-full",toggle:"deploy-type",icon:"red/images/deploy-full.png",label:RED._("deploy.full"),sublabel:RED._("deploy.fullDesc"),selected: true, onselect:function(s) { if(s){changeDeploymentType("full")}}},
{id:"deploymenu-item-flow",toggle:"deploy-type",icon:"red/images/deploy-flows.png",label:RED._("deploy.modifiedFlows"),sublabel:RED._("deploy.modifiedFlowsDesc"), onselect:function(s) {if(s){changeDeploymentType("flows")}}},
{id:"deploymenu-item-node",toggle:"deploy-type",icon:"red/images/deploy-nodes.png",label:RED._("deploy.modifiedNodes"),sublabel:RED._("deploy.modifiedNodesDesc"),onselect:function(s) { if(s){changeDeploymentType("nodes")}}},
null,
{id:"deploymenu-item-reload", icon:"red/images/deploy-reload.png",label:RED._("deploy.restartFlows"),sublabel:RED._("deploy.restartFlowsDesc"),onselect:"core:restart-flows"},
]
});
} else if (type == "simple") {
var label = options.label || RED._("deploy.deploy");
var icon = 'red/images/deploy-full-o.png';
if (options.hasOwnProperty('icon')) {
icon = options.icon;
}
$('<li><span class="deploy-button-group button-group">'+
'<a id="btn-deploy" class="deploy-button disabled" href="#">'+
'<span class="deploy-button-content">'+
(icon?'<img id="btn-deploy-icon" src="'+icon+'"> ':'')+
'<span>'+label+'</span>'+
'</span>'+
'<span class="deploy-button-spinner hide">'+
'<img src="red/images/spin.svg"/>'+
'</span>'+
'</a>'+
'</span></li>').prependTo(".header-toolbar");
}
$('#btn-deploy').click(function(event) {
event.preventDefault();
save();
});
RED.actions.add("core:deploy-flows",save);
RED.actions.add("core:restart-flows",restart);
RED.events.on('nodes:change',function(state) {
if (state.dirty) {
window.onbeforeunload = function() {
return RED._("deploy.confirm.undeployedChanges");
}
$("#btn-deploy").removeClass("disabled");
} else {
window.onbeforeunload = null;
$("#btn-deploy").addClass("disabled");
}
});
var activeNotifyMessage;
RED.comms.subscribe("notification/runtime-deploy",function(topic,msg) {
if (!activeNotifyMessage) {
var currentRev = RED.nodes.version();
if (currentRev === null || deployInflight || currentRev === msg.revision) {
return;
}
var message = $('<p>').text(RED._('deploy.confirm.backgroundUpdate'));
activeNotifyMessage = RED.notify(message,{
modal: true,
fixed: true,
buttons: [
{
text: RED._('deploy.confirm.button.ignore'),
click: function() {
activeNotifyMessage.close();
activeNotifyMessage = null;
}
},
{
text: RED._('deploy.confirm.button.review'),
class: "primary",
click: function() {
activeNotifyMessage.close();
var nns = RED.nodes.createCompleteNodeSet();
resolveConflict(nns,false);
activeNotifyMessage = null;
}
}
]
});
}
});
}
function getNodeInfo(node) {
var tabLabel = "";
if (node.z) {
var tab = RED.nodes.workspace(node.z);
if (!tab) {
tab = RED.nodes.subflow(node.z);
tabLabel = tab.name;
} else {
tabLabel = tab.label;
}
}
var label = RED.utils.getNodeLabel(node,node.id);
return {tab:tabLabel,type:node.type,label:label};
}
function sortNodeInfo(A,B) {
if (A.tab < B.tab) { return -1;}
if (A.tab > B.tab) { return 1;}
if (A.type < B.type) { return -1;}
if (A.type > B.type) { return 1;}
if (A.name < B.name) { return -1;}
if (A.name > B.name) { return 1;}
return 0;
}
function resolveConflict(currentNodes, activeDeploy) {
var message = $('<div>');
$('<p data-i18n="deploy.confirm.conflict"></p>').appendTo(message);
var conflictCheck = $('<div id="node-dialog-confirm-deploy-conflict-checking" class="node-dialog-confirm-conflict-row">'+
'<img src="red/images/spin.svg"/><div data-i18n="deploy.confirm.conflictChecking"></div>'+
'</div>').appendTo(message);
var conflictAutoMerge = $('<div class="node-dialog-confirm-conflict-row">'+
'<i style="color: #3a3;" class="fa fa-check"></i><div data-i18n="deploy.confirm.conflictAutoMerge"></div>'+
'</div>').hide().appendTo(message);
var conflictManualMerge = $('<div id="node-dialog-confirm-deploy-conflict-manual-merge" class="node-dialog-confirm-conflict-row">'+
'<i style="color: #999;" class="fa fa-exclamation"></i><div data-i18n="deploy.confirm.conflictManualMerge"></div>'+
'</div>').hide().appendTo(message);
message.i18n();
currentDiff = null;
var buttons = [
{
text: RED._("common.label.cancel"),
click: function() {
conflictNotification.close();
}
},
{
id: "node-dialog-confirm-deploy-review",
text: RED._("deploy.confirm.button.review"),
class: "primary disabled",
click: function() {
if (!$("#node-dialog-confirm-deploy-review").hasClass('disabled')) {
RED.diff.showRemoteDiff();
conflictNotification.close();
}
}
},
{
id: "node-dialog-confirm-deploy-merge",
text: RED._("deploy.confirm.button.merge"),
class: "primary disabled",
click: function() {
if (!$("#node-dialog-confirm-deploy-merge").hasClass('disabled')) {
RED.diff.mergeDiff(currentDiff);
conflictNotification.close();
}
}
}
];
if (activeDeploy) {
buttons.push({
id: "node-dialog-confirm-deploy-overwrite",
text: RED._("deploy.confirm.button.overwrite"),
class: "primary",
click: function() {
save(true,activeDeploy);
conflictNotification.close();
}
})
}
var conflictNotification = RED.notify(message,{
modal: true,
fixed: true,
width: 600,
buttons: buttons
});
var now = Date.now();
RED.diff.getRemoteDiff(function(diff) {
var ellapsed = Math.max(1000 - (Date.now()-now), 0);
currentDiff = diff;
setTimeout(function() {
conflictCheck.hide();
var d = Object.keys(diff.conflicts);
if (d.length === 0) {
conflictAutoMerge.show();
$("#node-dialog-confirm-deploy-merge").removeClass('disabled')
} else {
conflictManualMerge.show();
}
$("#node-dialog-confirm-deploy-review").removeClass('disabled')
},ellapsed);
})
}
function cropList(list) {
if (list.length > 5) {
var remainder = list.length - 5;
list = list.slice(0,5);
list.push(RED._("deploy.confirm.plusNMore",{count:remainder}));
}
return list;
}
function restart() {
var startTime = Date.now();
$(".deploy-button-content").css('opacity',0);
$(".deploy-button-spinner").show();
var deployWasEnabled = !$("#btn-deploy").hasClass("disabled");
$("#btn-deploy").addClass("disabled");
deployInflight = true;
$("#header-shade").show();
$("#editor-shade").show();
$("#palette-shade").show();
$("#sidebar-shade").show();
$.ajax({
url:"flows",
type: "POST",
headers: {
"Node-RED-Deployment-Type":"reload"
}
}).done(function(data,textStatus,xhr) {
if (deployWasEnabled) {
$("#btn-deploy").removeClass("disabled");
}
RED.notify('<p>'+RED._("deploy.successfulRestart")+'</p>',"success");
}).fail(function(xhr,textStatus,err) {
if (deployWasEnabled) {
$("#btn-deploy").removeClass("disabled");
}
if (xhr.status === 401) {
RED.notify(RED._("deploy.deployFailed",{message:RED._("user.notAuthorized")}),"error");
} else if (xhr.status === 409) {
resolveConflict(nns, true);
} else if (xhr.responseText) {
RED.notify(RED._("deploy.deployFailed",{message:xhr.responseText}),"error");
} else {
RED.notify(RED._("deploy.deployFailed",{message:RED._("deploy.errors.noResponse")}),"error");
}
}).always(function() {
deployInflight = false;
var delta = Math.max(0,300-(Date.now()-startTime));
setTimeout(function() {
$(".deploy-button-content").css('opacity',1);
$(".deploy-button-spinner").hide();
$("#header-shade").hide();
$("#editor-shade").hide();
$("#palette-shade").hide();
$("#sidebar-shade").hide();
},delta);
});
}
function save(skipValidation,force) {
if (!$("#btn-deploy").hasClass("disabled")) {
if (!RED.user.hasPermission("flows.write")) {
RED.notify(RED._("user.errors.deploy"),"error");
return;
}
if (!skipValidation) {
var hasUnknown = false;
var hasInvalid = false;
var hasUnusedConfig = false;
var unknownNodes = [];
var invalidNodes = [];
RED.nodes.eachNode(function(node) {
hasInvalid = hasInvalid || !node.valid;
if (!node.valid) {
invalidNodes.push(getNodeInfo(node));
}
if (node.type === "unknown") {
if (unknownNodes.indexOf(node.name) == -1) {
unknownNodes.push(node.name);
}
}
});
hasUnknown = unknownNodes.length > 0;
var unusedConfigNodes = [];
RED.nodes.eachConfig(function(node) {
if (node.users.length === 0 && (node._def.hasUsers !== false)) {
unusedConfigNodes.push(getNodeInfo(node));
hasUnusedConfig = true;
}
});
var showWarning = false;
var notificationMessage;
var notificationButtons = [];
var notification;
if (hasUnknown && !ignoreDeployWarnings.unknown) {
showWarning = true;
notificationMessage = "<p>"+RED._('deploy.confirm.unknown')+"</p>"+
'<ul class="node-dialog-configm-deploy-list"><li>'+cropList(unknownNodes).join("</li><li>")+"</li></ul><p>"+
RED._('deploy.confirm.confirm')+
"</p>";
notificationButtons= [
{
id: "node-dialog-confirm-deploy-deploy",
text: RED._("deploy.confirm.button.confirm"),
class: "primary",
click: function() {
save(true);
notification.close();
}
}
];
} else if (hasInvalid && !ignoreDeployWarnings.invalid) {
showWarning = true;
invalidNodes.sort(sortNodeInfo);
notificationMessage = "<p>"+RED._('deploy.confirm.improperlyConfigured')+"</p>"+
'<ul class="node-dialog-configm-deploy-list"><li>'+cropList(invalidNodes.map(function(A) { return (A.tab?"["+A.tab+"] ":"")+A.label+" ("+A.type+")"})).join("</li><li>")+"</li></ul><p>"+
RED._('deploy.confirm.confirm')+
"</p>";
notificationButtons= [
{
id: "node-dialog-confirm-deploy-deploy",
text: RED._("deploy.confirm.button.confirm"),
class: "primary",
click: function() {
save(true);
notification.close();
}
}
];
}
if (showWarning) {
notificationButtons.unshift(
{
text: RED._("common.label.cancel"),
click: function() {
notification.close();
}
}
);
notification = RED.notify(notificationMessage,{
modal: true,
fixed: true,
buttons:notificationButtons
});
return;
}
}
var nns = RED.nodes.createCompleteNodeSet();
var startTime = Date.now();
$(".deploy-button-content").css('opacity',0);
$(".deploy-button-spinner").show();
$("#btn-deploy").addClass("disabled");
var data = {flows:nns};
if (!force) {
data.rev = RED.nodes.version();
}
deployInflight = true;
$("#header-shade").show();
$("#editor-shade").show();
$("#palette-shade").show();
$("#sidebar-shade").show();
$.ajax({
url:"flows",
type: "POST",
data: JSON.stringify(data),
contentType: "application/json; charset=utf-8",
headers: {
"Node-RED-Deployment-Type":deploymentType
}
}).done(function(data,textStatus,xhr) {
RED.nodes.dirty(false);
RED.nodes.version(data.rev);
RED.nodes.originalFlow(nns);
if (hasUnusedConfig) {
RED.notify(
'<p>'+RED._("deploy.successfulDeploy")+'</p>'+
'<p>'+RED._("deploy.unusedConfigNodes")+' <a href="#" onclick="RED.sidebar.config.show(true); return false;">'+RED._("deploy.unusedConfigNodesLink")+'</a></p>',"success",false,6000);
} else {
RED.notify('<p>'+RED._("deploy.successfulDeploy")+'</p>',"success");
}
RED.nodes.eachNode(function(node) {
if (node.changed) {
node.dirty = true;
node.changed = false;
}
if (node.moved) {
node.dirty = true;
node.moved = false;
}
if(node.credentials) {
delete node.credentials;
}
});
RED.nodes.eachConfig(function (confNode) {
confNode.changed = false;
if (confNode.credentials) {
delete confNode.credentials;
}
});
RED.nodes.eachSubflow(function(subflow) {
subflow.changed = false;
});
RED.nodes.eachWorkspace(function(ws) {
ws.changed = false;
});
// Once deployed, cannot undo back to a clean state
RED.history.markAllDirty();
RED.view.redraw();
RED.events.emit("deploy");
}).fail(function(xhr,textStatus,err) {
RED.nodes.dirty(true);
$("#btn-deploy").removeClass("disabled");
if (xhr.status === 401) {
RED.notify(RED._("deploy.deployFailed",{message:RED._("user.notAuthorized")}),"error");
} else if (xhr.status === 409) {
resolveConflict(nns, true);
} else if (xhr.responseText) {
RED.notify(RED._("deploy.deployFailed",{message:xhr.responseText}),"error");
} else {
RED.notify(RED._("deploy.deployFailed",{message:RED._("deploy.errors.noResponse")}),"error");
}
}).always(function() {
deployInflight = false;
var delta = Math.max(0,300-(Date.now()-startTime));
setTimeout(function() {
$(".deploy-button-content").css('opacity',1);
$(".deploy-button-spinner").hide();
$("#header-shade").hide();
$("#editor-shade").hide();
$("#palette-shade").hide();
$("#sidebar-shade").hide();
},delta);
});
}
}
return {
init: init,
setDeployInflight: function(state) {
deployInflight = state;
}
}
})();

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,209 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
RED.editor.types._buffer = (function() {
var template = '<script type="text/x-red" data-template-name="_buffer"><div id="node-input-buffer-panels"><div id="node-input-buffer-panel-str" class="red-ui-panel"><div class="form-row" style="margin-bottom: 3px; text-align: right;"><span class="node-input-buffer-type"><i class="fa fa-exclamation-circle"></i> <span id="node-input-buffer-type-string" data-i18n="bufferEditor.modeString"></span><span id="node-input-buffer-type-array" data-i18n="bufferEditor.modeArray"></span></span></div><div class="form-row node-text-editor-row"><div class="node-text-editor" id="node-input-buffer-str"></div></div></div><div id="node-input-buffer-panel-bin" class="red-ui-panel"><div class="form-row node-text-editor-row" style="margin-top: 10px"><div class="node-text-editor" id="node-input-buffer-bin"></div></div></div></div></script>';
function stringToUTF8Array(str) {
var data = [];
var i=0, l = str.length;
for (i=0; i<l; i++) {
var char = str.charCodeAt(i);
if (char < 0x80) {
data.push(char);
} else if (char < 0x800) {
data.push(0xc0 | (char >> 6));
data.push(0x80 | (char & 0x3f));
} else if (char < 0xd800 || char >= 0xe000) {
data.push(0xe0 | (char >> 12));
data.push(0x80 | ((char>>6) & 0x3f));
data.push(0x80 | (char & 0x3f));
} else {
i++;
char = 0x10000 + (((char & 0x3ff)<<10) | (str.charAt(i) & 0x3ff));
data.push(0xf0 | (char >>18));
data.push(0x80 | ((char>>12) & 0x3f));
data.push(0x80 | ((char>>6) & 0x3f));
data.push(0x80 | (char & 0x3f));
}
}
return data;
}
return {
init: function() {
$(template).appendTo(document.body);
},
show: function(options) {
var value = options.value;
var onComplete = options.complete;
var type = "_buffer"
RED.view.state(RED.state.EDITING);
var bufferStringEditor = [];
var bufferBinValue;
var panels;
var trayOptions = {
title: options.title,
width: "inherit",
buttons: [
{
id: "node-dialog-cancel",
text: RED._("common.label.cancel"),
click: function() {
RED.tray.close();
}
},
{
id: "node-dialog-ok",
text: RED._("common.label.done"),
class: "primary",
click: function() {
onComplete(JSON.stringify(bufferBinValue));
RED.tray.close();
}
}
],
resize: function(dimensions) {
var height = $("#dialog-form").height();
if (panels) {
panels.resize(height);
}
},
open: function(tray) {
var trayBody = tray.find('.editor-tray-body');
var dialogForm = RED.editor.buildEditForm(tray.find('.editor-tray-body'),'dialog-form',type,'editor');
bufferStringEditor = RED.editor.createEditor({
id: 'node-input-buffer-str',
value: "",
mode:"ace/mode/text"
});
bufferStringEditor.getSession().setValue(value||"",-1);
bufferBinEditor = RED.editor.createEditor({
id: 'node-input-buffer-bin',
value: "",
mode:"ace/mode/text",
readOnly: true
});
var changeTimer;
var buildBuffer = function(data) {
var valid = true;
var isString = typeof data === 'string';
var binBuffer = [];
if (isString) {
bufferBinValue = stringToUTF8Array(data);
} else {
bufferBinValue = data;
}
var i=0,l=bufferBinValue.length;
var c = 0;
for(i=0;i<l;i++) {
var d = parseInt(bufferBinValue[i]);
if (!isString && (isNaN(d) || d < 0 || d > 255)) {
valid = false;
break;
}
if (i>0) {
if (i%8 === 0) {
if (i%16 === 0) {
binBuffer.push("\n");
} else {
binBuffer.push(" ");
}
} else {
binBuffer.push(" ");
}
}
binBuffer.push((d<16?"0":"")+d.toString(16).toUpperCase());
}
if (valid) {
$("#node-input-buffer-type-string").toggle(isString);
$("#node-input-buffer-type-array").toggle(!isString);
bufferBinEditor.setValue(binBuffer.join(""),1);
}
return valid;
}
var bufferStringUpdate = function() {
var value = bufferStringEditor.getValue();
var isValidArray = false;
if (/^[\s]*\[[\s\S]*\][\s]*$/.test(value)) {
isValidArray = true;
try {
var data = JSON.parse(value);
isValidArray = buildBuffer(data);
} catch(err) {
isValidArray = false;
}
}
if (!isValidArray) {
buildBuffer(value);
}
}
bufferStringEditor.getSession().on('change', function() {
clearTimeout(changeTimer);
changeTimer = setTimeout(bufferStringUpdate,200);
});
bufferStringUpdate();
dialogForm.i18n();
panels = RED.panels.create({
id:"node-input-buffer-panels",
resize: function(p1Height,p2Height) {
var p1 = $("#node-input-buffer-panel-str");
p1Height -= $(p1.children()[0]).outerHeight(true);
var editorRow = $(p1.children()[1]);
p1Height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
$("#node-input-buffer-str").css("height",(p1Height-5)+"px");
bufferStringEditor.resize();
var p2 = $("#node-input-buffer-panel-bin");
editorRow = $(p2.children()[0]);
p2Height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
$("#node-input-buffer-bin").css("height",(p2Height-5)+"px");
bufferBinEditor.resize();
}
});
$(".node-input-buffer-type").click(function(e) {
e.preventDefault();
RED.sidebar.info.set(RED._("bufferEditor.modeDesc"));
RED.sidebar.info.show();
})
},
close: function() {
if (options.onclose) {
options.onclose();
}
bufferStringEditor.destroy();
bufferBinEditor.destroy();
},
show: function() {}
}
RED.tray.show(trayOptions);
}
}
})();

Some files were not shown because too many files have changed in this diff Show More