mirror of
https://github.com/node-red/node-red.git
synced 2023-10-10 13:36:53 +02:00
update UI, Runtime API, metadata handling, and others
This commit is contained in:
parent
d51aefa156
commit
4a1d66f210
@ -67,6 +67,11 @@ module.exports = {
|
|||||||
adminApp.get(/^\/nodes\/((@[^\/]+\/)?[^\/]+)\/([^\/]+)$/,needsPermission("nodes.read"),nodes.getSet,apiUtil.errorHandler);
|
adminApp.get(/^\/nodes\/((@[^\/]+\/)?[^\/]+)\/([^\/]+)$/,needsPermission("nodes.read"),nodes.getSet,apiUtil.errorHandler);
|
||||||
adminApp.put(/^\/nodes\/((@[^\/]+\/)?[^\/]+)\/([^\/]+)$/,needsPermission("nodes.write"),nodes.putSet,apiUtil.errorHandler);
|
adminApp.put(/^\/nodes\/((@[^\/]+\/)?[^\/]+)\/([^\/]+)$/,needsPermission("nodes.write"),nodes.putSet,apiUtil.errorHandler);
|
||||||
|
|
||||||
|
// NPM Modules
|
||||||
|
adminApp.get("/modules", needsPermission("nodes.read"), nodes.listNPMModules, apiUtil.errorHandler);
|
||||||
|
adminApp.delete("/modules/:spec", needsPermission("nodes.write"), nodes.uninstallNPMModule, apiUtil.errorHandler);
|
||||||
|
adminApp.post("/modules", needsPermission("nodes.write"), nodes.updateNPMModule, apiUtil.errorHandler);
|
||||||
|
|
||||||
// Context
|
// 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(global)/*",needsPermission("context.read"),context.get,apiUtil.errorHandler);
|
adminApp.get("/context/:scope(global)/*",needsPermission("context.read"),context.get,apiUtil.errorHandler);
|
||||||
|
@ -191,5 +191,45 @@ module.exports = {
|
|||||||
runtimeAPI.nodes.getIconList(opts).then(function(list) {
|
runtimeAPI.nodes.getIconList(opts).then(function(list) {
|
||||||
res.json(list);
|
res.json(list);
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
listNPMModules: function(req, res) {
|
||||||
|
var opts = {
|
||||||
|
user: req.user,
|
||||||
|
req: apiUtils.getRequestLogObject(req)
|
||||||
|
}
|
||||||
|
runtimeAPI.nodes.listNPMModules(opts).then(function(list) {
|
||||||
|
res.json(list);
|
||||||
|
}).catch(err => {
|
||||||
|
apiUtils.rejectHandler(req,res,err);
|
||||||
|
});;
|
||||||
|
},
|
||||||
|
|
||||||
|
uninstallNPMModule: function(req, res) {
|
||||||
|
var opts = {
|
||||||
|
user: req.user,
|
||||||
|
spec: req.params.spec,
|
||||||
|
req: apiUtils.getRequestLogObject(req)
|
||||||
|
}
|
||||||
|
runtimeAPI.nodes.uninstallNPMModule(opts).then(function(result) {
|
||||||
|
res.json(result);
|
||||||
|
}).catch(err => {
|
||||||
|
apiUtils.rejectHandler(req,res,err);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
updateNPMModule: function(req, res) {
|
||||||
|
var body = req.body;
|
||||||
|
var opts = {
|
||||||
|
user: req.user,
|
||||||
|
spec: body.spec,
|
||||||
|
update: body.update,
|
||||||
|
req: apiUtils.getRequestLogObject(req)
|
||||||
|
}
|
||||||
|
runtimeAPI.nodes.updateNPMModule(opts).then(function(result) {
|
||||||
|
res.json(result);
|
||||||
|
}).catch(err => {
|
||||||
|
apiUtils.rejectHandler(req,res,err);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1,280 +1,3 @@
|
|||||||
<script type="text/html" data-template-name="library-config">
|
|
||||||
<div class="form-row">
|
|
||||||
<label for="node-config-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
|
|
||||||
<div style="display: inline-block; width: calc(100% - 105px)">
|
|
||||||
<input type="text" id="node-config-input-name" data-i18n="[placeholder]common.label.name">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-row" style="margin-bottom: 2px; margin-top: 15px;">
|
|
||||||
<label style="width: 65%;"><i class="fa fa-cog"></i> <span>Import Libraries</span></label>
|
|
||||||
<input type="checkbox" id="node-config-input-showAll" style="display: inline-block; width: auto; vertical-align: top;">
|
|
||||||
<label for="node-config-input-showAll" style="width: auto;">Show All Libraries</label>
|
|
||||||
</div>
|
|
||||||
<div class="form-row "">
|
|
||||||
<ol id="node-input-libs-container"></ol>
|
|
||||||
</div>
|
|
||||||
</script>
|
|
||||||
|
|
||||||
|
|
||||||
<script type="text/javascript">
|
|
||||||
// object that maps from library name to its descriptor
|
|
||||||
var allLibs = {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add library descriptor
|
|
||||||
* @param {string} name - library name
|
|
||||||
*/
|
|
||||||
function addLib(name) {
|
|
||||||
var item = allLibs[name]
|
|
||||||
if (item) {
|
|
||||||
item.count++;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
allLibs[name] = {
|
|
||||||
name: name,
|
|
||||||
count: 1
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove library descriptor if reference count=0
|
|
||||||
* @param {string} name - library name
|
|
||||||
*/
|
|
||||||
function removeLib(name) {
|
|
||||||
var item = allLibs[name];
|
|
||||||
if (item) {
|
|
||||||
item.count--;
|
|
||||||
if (item.count === 0) {
|
|
||||||
delete allLibs[name];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function currentLibs() {
|
|
||||||
var result = [];
|
|
||||||
var libs = $("#node-input-libs-container").editableList("items");
|
|
||||||
libs.each(function(i) {
|
|
||||||
var item = $(this);
|
|
||||||
var n = item.find(".node-input-libs-val").val();
|
|
||||||
if (n && (n !== "")) {
|
|
||||||
result.push(n);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate library spec including conflicts with other specs
|
|
||||||
* @param {string} name - library name
|
|
||||||
*/
|
|
||||||
function checkLib(name) {
|
|
||||||
var m0 = name.match(/^([^@]+)(@.*)?$/);
|
|
||||||
if (m0) {
|
|
||||||
var lname0 = m0[1];
|
|
||||||
var ver0 = m0[2];
|
|
||||||
var ok = true;
|
|
||||||
var current = currentLibs(libs);
|
|
||||||
var libs = Object.keys(allLibs).concat(current);
|
|
||||||
libs.forEach(function(lib) {
|
|
||||||
if (name !== lib) {
|
|
||||||
var m1 = lib.match(/^([^@]+)(@.*)?$/);
|
|
||||||
if (m1) {
|
|
||||||
var lname1 = m1[1];
|
|
||||||
var ver1 = m1[2];
|
|
||||||
if ((lname1 === lname0) && (ver0 !== ver1)) {
|
|
||||||
ok = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return ok;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update library list
|
|
||||||
*/
|
|
||||||
function updateLib(done) {
|
|
||||||
allLibs = {};
|
|
||||||
RED.nodes.eachConfig(function(n) {
|
|
||||||
if (n.type === "library-config") {
|
|
||||||
var libs = n.libs || [];
|
|
||||||
libs.forEach(function(lib) { addLib(lib.name); });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
var root = RED.settings.apiRootUrl || "";
|
|
||||||
$.ajax({
|
|
||||||
headers: {
|
|
||||||
"Accept":"application/json"
|
|
||||||
},
|
|
||||||
cache: false,
|
|
||||||
url: root + "/function/modules",
|
|
||||||
success: function(data) {
|
|
||||||
data.forEach(function(lib) {
|
|
||||||
var name = lib.name;
|
|
||||||
if (lib.version && (lib.version !== "")) {
|
|
||||||
name += "@" +lib.version;
|
|
||||||
}
|
|
||||||
addLib(name);
|
|
||||||
});
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register NPM library configuration node
|
|
||||||
*/
|
|
||||||
RED.nodes.registerType("library-config", {
|
|
||||||
category: "config",
|
|
||||||
defaults: {
|
|
||||||
name: {value: ""},
|
|
||||||
libs: {value: []},
|
|
||||||
showAll: {value: false}
|
|
||||||
},
|
|
||||||
label: function() {
|
|
||||||
if (this.name && (this.name !== "")) {
|
|
||||||
return this.name;
|
|
||||||
}
|
|
||||||
return "Library Set:"+this.id;
|
|
||||||
},
|
|
||||||
oneditprepare: function() {
|
|
||||||
var node = this;
|
|
||||||
var popovers = {};
|
|
||||||
$("#node-input-libs-container").css('min-height','250px').css('min-width','450px').editableList({
|
|
||||||
addItem: function(container,i,opt) {
|
|
||||||
var disabled = (opt && !!opt.unused);
|
|
||||||
var row = $("<div/>").appendTo(container);
|
|
||||||
var fvar = $("<input/>", {
|
|
||||||
class: "node-input-libs-var",
|
|
||||||
placeholder: RED._("node-red:function.require.var"),
|
|
||||||
type: "text",
|
|
||||||
disabled: disabled
|
|
||||||
}).css({
|
|
||||||
width: "90px",
|
|
||||||
"margin-left": "5px"
|
|
||||||
}).appendTo(row);
|
|
||||||
var fmodule = $("<input/>", {
|
|
||||||
class: "node-input-libs-val",
|
|
||||||
placeholder: RED._("node-red:function.require.module"),
|
|
||||||
type: "text",
|
|
||||||
disabled: disabled
|
|
||||||
}).css({
|
|
||||||
width: "280px",
|
|
||||||
"margin-left": "5px"
|
|
||||||
}).appendTo(row);
|
|
||||||
|
|
||||||
fvar.on("change", function (e) {
|
|
||||||
opt.vname = $(this).val();
|
|
||||||
});
|
|
||||||
|
|
||||||
fmodule.on("change", function (e) {
|
|
||||||
var val = $(this).val();
|
|
||||||
if (!checkLib(val)) {
|
|
||||||
$(this).addClass("input-error");
|
|
||||||
var popover = RED.popover.tooltip($(this), "conflict in module spec");
|
|
||||||
popovers[i] = popover;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$(this).removeClass("input-error");
|
|
||||||
var popover = popovers[i];
|
|
||||||
if (popover) {
|
|
||||||
popover.setContent(function () {
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
opt.name = val;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (opt) {
|
|
||||||
if (opt.vname) {
|
|
||||||
fvar.val(opt.vname);
|
|
||||||
}
|
|
||||||
if (opt.name) {
|
|
||||||
fmodule.val(opt.name);
|
|
||||||
addLib(opt.name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
removeItem: function(item) {
|
|
||||||
removeLib(item.name);
|
|
||||||
},
|
|
||||||
removable: true,
|
|
||||||
sortable: true
|
|
||||||
});
|
|
||||||
var libs = node.libs ? node.libs : [];
|
|
||||||
libs.forEach(function(lib) {
|
|
||||||
var conf = {
|
|
||||||
vname: lib.vname,
|
|
||||||
name: lib.name
|
|
||||||
};
|
|
||||||
$("#node-input-libs-container").editableList("addItem", conf);
|
|
||||||
});
|
|
||||||
$("#node-config-input-showAll").on("change", function () {
|
|
||||||
var checked = $(this).prop("checked");
|
|
||||||
var fun = null;
|
|
||||||
if (checked) {
|
|
||||||
fun = null;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
fun = function(item) {
|
|
||||||
return (!item.unused) ||
|
|
||||||
((item.vname && (item.vname !== "")) &&
|
|
||||||
(item.name && (item.name !== "")));
|
|
||||||
};
|
|
||||||
}
|
|
||||||
$("#node-input-libs-container").editableList("filter", fun);
|
|
||||||
});
|
|
||||||
updateLib(function () {
|
|
||||||
Object.values(allLibs).forEach(function(lib) {
|
|
||||||
function pred(x) {
|
|
||||||
return (x.name === lib.name);
|
|
||||||
}
|
|
||||||
if (libs.find(pred)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var conf = {
|
|
||||||
vname: "",
|
|
||||||
name: lib.name,
|
|
||||||
unused: true
|
|
||||||
};
|
|
||||||
$("#node-input-libs-container").editableList("addItem", conf);
|
|
||||||
});
|
|
||||||
$("#node-config-input-showAll").change();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
oneditsave: function() {
|
|
||||||
var node = this;
|
|
||||||
var libs = $("#node-input-libs-container").editableList("items");
|
|
||||||
var oldLibs = node.libs || [];
|
|
||||||
var newLibs = [];
|
|
||||||
node.libs = newLibs;
|
|
||||||
libs.each(function(i) {
|
|
||||||
var item = $(this);
|
|
||||||
var v = item.find(".node-input-libs-var").val();
|
|
||||||
var n = item.find(".node-input-libs-val").val();
|
|
||||||
if (!v || (v === "") ||
|
|
||||||
!n || (n === "")) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
newLibs.push({
|
|
||||||
vname: v,
|
|
||||||
name: n
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
oneditresize: function(size) {
|
|
||||||
var height = size.height;
|
|
||||||
$("#node-input-libs-container").editableList("height", height -60);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
|
|
||||||
<script type="text/html" data-template-name="function">
|
<script type="text/html" data-template-name="function">
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
|
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
|
||||||
@ -327,12 +50,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="func-tab-config" style="display:none">
|
<div id="func-tab-config" style="display:none">
|
||||||
<div class="form-row">
|
<div class="form-row" style="margin-bottom: 0px;">
|
||||||
<label><i class="fa fa-wrench"></i> <span>Use Library</span></label>
|
<label><i class="fa fa-wrench"></i> <span>Use Library</span></label>
|
||||||
<input type="text" id="node-input-libs">
|
|
||||||
</div>
|
</div>
|
||||||
<div class="form-row" style="height: 250px; min-height: 150px">
|
<div class="form-row" style="height: 250px; min-height: 150px">
|
||||||
<ol id="node-input-require-container"></ol>
|
<ol id="node-input-libs-container"></ol>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -340,6 +62,536 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
|
// object that maps from library name to its descriptor
|
||||||
|
var allLibs = [];
|
||||||
|
|
||||||
|
function moduleName(module) {
|
||||||
|
var match = /^([^@]+)@(.+)/.exec(module);
|
||||||
|
if (match) {
|
||||||
|
return [match[1], match[2]];
|
||||||
|
}
|
||||||
|
return [module, undefined];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add library descriptor
|
||||||
|
* @param {string} name - library name
|
||||||
|
*/
|
||||||
|
function addLib(info) {
|
||||||
|
allLibs.push(info);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate library spec including conflicts with other specs
|
||||||
|
* @param {string} name - library name
|
||||||
|
*/
|
||||||
|
function checkLib(spec) {
|
||||||
|
var [name, ver] = moduleName(spec);
|
||||||
|
var libs = $("#node-input-libs-container").editableList("items");
|
||||||
|
var count = 0;
|
||||||
|
libs.each(function(i) {
|
||||||
|
if (count > 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var item = $(this);
|
||||||
|
var n = item.find(".node-input-libs-val").val();
|
||||||
|
if (n && (n !== "")) {
|
||||||
|
var [name1, ver1] = moduleName(n);
|
||||||
|
if (name1 === name) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return (count <= 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get library list
|
||||||
|
*/
|
||||||
|
function getModules() {
|
||||||
|
var root = RED.settings.apiRootUrl || "";
|
||||||
|
var promise = $.ajax({
|
||||||
|
url: root + "/modules",
|
||||||
|
type: "GET",
|
||||||
|
cache: false
|
||||||
|
});
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check and update in progress state of modules
|
||||||
|
*/
|
||||||
|
function checkInProgress(allLibs) {
|
||||||
|
var waitTime = 1000;
|
||||||
|
|
||||||
|
function update() {
|
||||||
|
map = {};
|
||||||
|
allLibs.forEach(function (lib) {
|
||||||
|
if (lib.inProgress) {
|
||||||
|
map[lib.name] = lib;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
getModules().then(function (data,textStatus,xhr) {
|
||||||
|
var count = 0;
|
||||||
|
data.forEach(lib => {
|
||||||
|
var name = lib.name;
|
||||||
|
if (lib.inProgress) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
else if (name in map) {
|
||||||
|
var lib2 = map[name];
|
||||||
|
if (lib2) {
|
||||||
|
delete map[name];
|
||||||
|
var cb = lib2.onReady;
|
||||||
|
if (cb) {
|
||||||
|
lib2.version = lib.version;
|
||||||
|
lib2.status["installed"] = true;
|
||||||
|
lib2.inProgress = false;
|
||||||
|
cb();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (count > 0) {
|
||||||
|
setTimeout(update, waitTime);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setTimeout(update, waitTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update library list
|
||||||
|
*/
|
||||||
|
function updateLib(node, done) {
|
||||||
|
var names = {};
|
||||||
|
var specs = {};
|
||||||
|
var libs = node.libs || [];
|
||||||
|
allLibs = [];
|
||||||
|
libs.forEach(lib => {
|
||||||
|
var vname = lib.vname;
|
||||||
|
var spec = lib.spec;
|
||||||
|
var [name, ver] = moduleName(spec);
|
||||||
|
var item = {
|
||||||
|
vname: vname,
|
||||||
|
name: name,
|
||||||
|
spec: spec,
|
||||||
|
status: { "edit":true }
|
||||||
|
};
|
||||||
|
addLib(item);
|
||||||
|
specs[spec] = true;
|
||||||
|
names[name] = true;
|
||||||
|
});
|
||||||
|
RED.nodes.eachNode(function(n) {
|
||||||
|
if ((n.type === "function") && (n !== node)) {
|
||||||
|
var libs = n.libs || [];
|
||||||
|
libs.forEach(lib => {
|
||||||
|
var spec = lib.spec;
|
||||||
|
var [name, ver] = moduleName(spec);
|
||||||
|
if (name in names) {
|
||||||
|
var items = allLibs.filter(item => (item.name === name));
|
||||||
|
items.forEach(item => {
|
||||||
|
item.status["used"] = true;
|
||||||
|
});
|
||||||
|
if (spec in specs) {
|
||||||
|
// olready found
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// spec conflict
|
||||||
|
}
|
||||||
|
var item = {
|
||||||
|
name: name,
|
||||||
|
spec: spec,
|
||||||
|
status: { "used": true }
|
||||||
|
};
|
||||||
|
addLib(item);
|
||||||
|
names[name] = true;
|
||||||
|
specs[spec] = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
var inProgressCount = 0;
|
||||||
|
getModules().then(function(data,textStatus,xhr) {
|
||||||
|
data.forEach(lib => {
|
||||||
|
var name = lib.name;
|
||||||
|
var version = lib.version;
|
||||||
|
var preinstalled = (lib.status == "preinstalled");
|
||||||
|
var spec = lib.spec;
|
||||||
|
var items = allLibs.filter(item => (item.name === name));
|
||||||
|
if (items.length > 0) {
|
||||||
|
items.forEach(item => {
|
||||||
|
item.version = version;
|
||||||
|
item.status["installed"] = true;
|
||||||
|
item.status["preinstalled"] = preinstalled;
|
||||||
|
item.inProgress = lib.inProgress;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
var item = {
|
||||||
|
name: name,
|
||||||
|
spec: spec,
|
||||||
|
version: version,
|
||||||
|
status: {
|
||||||
|
"installed": true,
|
||||||
|
"preinstalled": preinstalled
|
||||||
|
},
|
||||||
|
inProgress: lib.inProgress
|
||||||
|
};
|
||||||
|
addLib(item);
|
||||||
|
names[name] = true;
|
||||||
|
specs[spec] = true;
|
||||||
|
}
|
||||||
|
if (lib.inProgress) {
|
||||||
|
inProgressCount++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$("#node-input-libs-container").editableList("empty");
|
||||||
|
allLibs.forEach(function(lib) {
|
||||||
|
$("#node-input-libs-container").editableList("addItem", lib);
|
||||||
|
});
|
||||||
|
if (inProgressCount > 0) {
|
||||||
|
checkInProgress(allLibs);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (done) {
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uninstall NPM module
|
||||||
|
*/
|
||||||
|
function uninstallModule(node, spec, name, version,
|
||||||
|
moduleInfo, installButton,
|
||||||
|
installInfoRow, installedInfo,
|
||||||
|
removeButton,
|
||||||
|
progressRow,
|
||||||
|
done) {
|
||||||
|
var root = RED.settings.apiRootUrl || "";
|
||||||
|
progressRow.show();
|
||||||
|
$.ajax({
|
||||||
|
url: root + "/modules/" +spec,
|
||||||
|
type: "DELETE",
|
||||||
|
}).then(function(data,textStatus,xhr) {
|
||||||
|
moduleInfo.attr("disabled", false);
|
||||||
|
installButton.attr("disabled", false);
|
||||||
|
installInfoRow.hide();
|
||||||
|
installedInfo.val("");
|
||||||
|
removeButton.show();
|
||||||
|
progressRow.hide();
|
||||||
|
|
||||||
|
RED.notify("Successfully uninstalled:" +name);
|
||||||
|
if(done) {
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
}).fail(function(xhr,textStatus,err) {
|
||||||
|
progressRow.hide();
|
||||||
|
RED.notify("Failed to uninstall: " +name);
|
||||||
|
if (done) {
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updaate or Install NPM module
|
||||||
|
*/
|
||||||
|
function updateModule(node, spec, update,
|
||||||
|
moduleInfo, installButton,
|
||||||
|
installInfoRow, installedInfo, removeButton,
|
||||||
|
progressRow,
|
||||||
|
done) {
|
||||||
|
var [name, version] = moduleName(spec);
|
||||||
|
var root = RED.settings.apiRootUrl || "";
|
||||||
|
var errorMessage = "failed to "+(update ? "update": "install") +": " +name;
|
||||||
|
progressRow.show();
|
||||||
|
$.ajax({
|
||||||
|
url: root + "/modules",
|
||||||
|
type: "POST",
|
||||||
|
contentType: "application/json; charset=utf-8",
|
||||||
|
cache: false,
|
||||||
|
data: JSON.stringify({
|
||||||
|
spec: spec,
|
||||||
|
update: update
|
||||||
|
})
|
||||||
|
}).then(function(data) {
|
||||||
|
getModules().then(function(data,textStatus,xhr) {
|
||||||
|
var item = data.find(lib => (lib.name === name));
|
||||||
|
progressRow.hide();
|
||||||
|
if (item) {
|
||||||
|
moduleInfo.attr("disabled", true);
|
||||||
|
installButton.attr("disabled", true);
|
||||||
|
installInfoRow.show();
|
||||||
|
installedInfo.val(item.name+"@"+item.version);
|
||||||
|
removeButton.hide();
|
||||||
|
|
||||||
|
var msg = "Successfully "
|
||||||
|
+(update ? "updated": "installed")
|
||||||
|
+": " +name;
|
||||||
|
|
||||||
|
RED.notify(msg);
|
||||||
|
}
|
||||||
|
if (done) {
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
}).fail(function(xhr,textStatus,err) {
|
||||||
|
progressRow.hide();
|
||||||
|
RED.notify(errorMessage);
|
||||||
|
if (done) {
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}).fail(function(xhr,textStatus,err) {
|
||||||
|
progressRow.hide();
|
||||||
|
RED.notify(errorMessage);
|
||||||
|
if (done) {
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
function withNotification(msg, text, cb) {
|
||||||
|
var notification = RED.notify(msg, {
|
||||||
|
modal: true,
|
||||||
|
fixed: true,
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
text: RED._("common.label.cancel"),
|
||||||
|
click: function() {
|
||||||
|
notification.close();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: text,
|
||||||
|
class: "primary red-ui-palette-module-install-confirm-button-update",
|
||||||
|
click: function() {
|
||||||
|
notification.close();
|
||||||
|
cb();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function prepareLibraryConfig(node) {
|
||||||
|
$("#node-input-libs-container").css('min-height','250px').css('min-width','450px').editableList({
|
||||||
|
addItem: function(container,i,opt) {
|
||||||
|
var parent = container.parent();
|
||||||
|
var removeButton = parent.find(".red-ui-editableList-item-remove");
|
||||||
|
var disabled = (opt && !!opt.unused);
|
||||||
|
var row0 = $("<div/>").appendTo(container);
|
||||||
|
var fvar = $("<input/>", {
|
||||||
|
class: "node-input-libs-var",
|
||||||
|
placeholder: RED._("node-red:function.require.var"),
|
||||||
|
type: "text",
|
||||||
|
disabled: disabled
|
||||||
|
}).css({
|
||||||
|
width: "90px",
|
||||||
|
"margin-left": "5px"
|
||||||
|
}).appendTo(row0);
|
||||||
|
var fieldWidth = "260px";
|
||||||
|
var fmodule = $("<input/>", {
|
||||||
|
class: "node-input-libs-val",
|
||||||
|
placeholder: RED._("node-red:function.require.module"),
|
||||||
|
type: "text",
|
||||||
|
disabled: disabled
|
||||||
|
}).css({
|
||||||
|
width: fieldWidth,
|
||||||
|
"margin-left": "5px"
|
||||||
|
}).appendTo(row0);
|
||||||
|
|
||||||
|
var buttonWidth = "70px";
|
||||||
|
var install = $("<button/>", {
|
||||||
|
class: "red-ui-button red-ui-button-small"
|
||||||
|
}).css({
|
||||||
|
"margin-left": "10px",
|
||||||
|
width: buttonWidth
|
||||||
|
}).appendTo(row0);;
|
||||||
|
$("<i/>", {
|
||||||
|
class: "fa fa-cube"
|
||||||
|
}).appendTo(install);
|
||||||
|
$("<span/>").css({
|
||||||
|
"margin-left": "5px"
|
||||||
|
}).text("Install").appendTo(install);
|
||||||
|
|
||||||
|
var warn = $("<span/>").css({
|
||||||
|
"margin-left": "10px"
|
||||||
|
}).appendTo(row0);
|
||||||
|
$("<i/>", {
|
||||||
|
class: "fa fa-warning"
|
||||||
|
}).appendTo(warn);
|
||||||
|
RED.popover.tooltip(warn, "module spec conflict");
|
||||||
|
warn.hide();
|
||||||
|
|
||||||
|
var row1 = $("<div/>").css({
|
||||||
|
"margin-top": "5px"
|
||||||
|
}).appendTo(container);
|
||||||
|
|
||||||
|
var linstalled = $("<label/>").css({
|
||||||
|
width: "90px",
|
||||||
|
"text-align": "right",
|
||||||
|
"margin-right": "10px"
|
||||||
|
}).appendTo(row1);
|
||||||
|
$("<i/>", {
|
||||||
|
class: "fa fa-cube"
|
||||||
|
}).appendTo(linstalled);
|
||||||
|
var status = "Installed";
|
||||||
|
$("<span/>").css({
|
||||||
|
"margin-left": "5px"
|
||||||
|
}).text(status).appendTo(linstalled);
|
||||||
|
|
||||||
|
var finstalled = $("<input/>", {
|
||||||
|
type: "text",
|
||||||
|
disabled: true
|
||||||
|
}).css({
|
||||||
|
width: fieldWidth
|
||||||
|
}).appendTo(row1);
|
||||||
|
|
||||||
|
var uninstall = $("<button/>", {
|
||||||
|
class: "red-ui-button red-ui-button-small"
|
||||||
|
}).css({
|
||||||
|
"margin-left": "10px",
|
||||||
|
width: buttonWidth
|
||||||
|
}).appendTo(row1);
|
||||||
|
$("<i/>", {
|
||||||
|
class: "fa fa-trash"
|
||||||
|
}).appendTo(uninstall);
|
||||||
|
$("<span/>").css({
|
||||||
|
"margin-left": "5px"
|
||||||
|
}).text("Uninstall").appendTo(uninstall);
|
||||||
|
|
||||||
|
var update = $("<button/>", {
|
||||||
|
class: "red-ui-button red-ui-button-small"
|
||||||
|
}).css({
|
||||||
|
"margin-left": "10px",
|
||||||
|
width: buttonWidth
|
||||||
|
}).appendTo(row1);
|
||||||
|
$("<i/>", {
|
||||||
|
class: "fa fa-refresh"
|
||||||
|
}).appendTo(update);
|
||||||
|
$("<span/>").css({
|
||||||
|
"margin-left": "5px"
|
||||||
|
}).text("Update").appendTo(update);
|
||||||
|
|
||||||
|
var row2 = $("<div/>", {
|
||||||
|
class: "red-ui-palette-module-shade"
|
||||||
|
}).appendTo(container);
|
||||||
|
$("<img/>", {
|
||||||
|
src: "red/images/spin.svg",
|
||||||
|
class: "red-ui-palette-spinner"
|
||||||
|
}).appendTo(row2);
|
||||||
|
row2.hide();
|
||||||
|
|
||||||
|
fvar.on("change", function (e) {
|
||||||
|
opt.vname = $(this).val();
|
||||||
|
});
|
||||||
|
|
||||||
|
fmodule.on("change", function (e) {
|
||||||
|
var val = $(this).val();
|
||||||
|
if (!checkLib(val, opt.id)) {
|
||||||
|
$(this).addClass("input-error");
|
||||||
|
warn.show();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$(this).removeClass("input-error");
|
||||||
|
warn.hide();
|
||||||
|
if (val && (val !== "") &&
|
||||||
|
(!opt.version || (opt.version === ""))) {
|
||||||
|
install.attr("disabled", false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
opt.spec = val;
|
||||||
|
});
|
||||||
|
|
||||||
|
install.on("click", function (e) {
|
||||||
|
withNotification(
|
||||||
|
"Install NPM Module " +opt.spec,
|
||||||
|
"Install",
|
||||||
|
function (cb) {
|
||||||
|
updateModule(
|
||||||
|
node, opt.spec, false,
|
||||||
|
fmodule, install,
|
||||||
|
row1, finstalled, removeButton,
|
||||||
|
row2,
|
||||||
|
cb);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
uninstall.on("click", function (e) {
|
||||||
|
withNotification(
|
||||||
|
"Uninstall NPM Module "+opt.spec,
|
||||||
|
"Uninstall",
|
||||||
|
function (cb) {
|
||||||
|
uninstallModule(
|
||||||
|
node, opt.spec, opt.name, opt.version,
|
||||||
|
fmodule, install,
|
||||||
|
row1, finstalled,
|
||||||
|
removeButton,
|
||||||
|
row2,
|
||||||
|
cb);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
update.on("click", function (e) {
|
||||||
|
withNotification(
|
||||||
|
"Update NPM Module " +opt.spec,
|
||||||
|
"Update",
|
||||||
|
function (cb) {
|
||||||
|
updateModule(
|
||||||
|
node, opt.spec, true,
|
||||||
|
fmodule, install,
|
||||||
|
row1, finstalled, removeButton,
|
||||||
|
row2,
|
||||||
|
cb);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
install.attr("disabled", true);
|
||||||
|
if (opt) {
|
||||||
|
function updateData(data) {
|
||||||
|
if (data.vname && (data.vname !== "")) {
|
||||||
|
fvar.val(data.vname);
|
||||||
|
}
|
||||||
|
if (data.spec && (data.spec !== "")) {
|
||||||
|
fmodule.val(data.spec);
|
||||||
|
}
|
||||||
|
if (data.version && (data.version !== "")) {
|
||||||
|
fmodule.prop("disabled", true);
|
||||||
|
row1.show();
|
||||||
|
removeButton.hide();
|
||||||
|
finstalled.val(data.name+"@"+data.version)
|
||||||
|
if (data.status["preinstalled"]) {
|
||||||
|
uninstall.attr("disabled", true);
|
||||||
|
update.attr("disabled", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
install.attr("disabled", false);
|
||||||
|
row1.hide();
|
||||||
|
removeButton.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateData(opt);
|
||||||
|
if (opt.inProgress) {
|
||||||
|
row2.show();
|
||||||
|
opt.onReady = function () {
|
||||||
|
row2.hide();
|
||||||
|
updateData(opt);
|
||||||
|
opt.onReady = null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
removable: true,
|
||||||
|
sortable: true
|
||||||
|
});
|
||||||
|
updateLib(node, function () {
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
RED.nodes.registerType('function',{
|
RED.nodes.registerType('function',{
|
||||||
color:"#fdd0a2",
|
color:"#fdd0a2",
|
||||||
category: 'function',
|
category: 'function',
|
||||||
@ -350,7 +602,7 @@
|
|||||||
noerr: {value:0,required:true,validate:function(v) { return !v; }},
|
noerr: {value:0,required:true,validate:function(v) { return !v; }},
|
||||||
initialize: {value:""},
|
initialize: {value:""},
|
||||||
finalize: {value:""},
|
finalize: {value:""},
|
||||||
libs: {value:"", type:"library-config", required:false}
|
libs: {value: []}
|
||||||
},
|
},
|
||||||
inputs:1,
|
inputs:1,
|
||||||
outputs:1,
|
outputs:1,
|
||||||
@ -496,56 +748,7 @@
|
|||||||
RED.popover.tooltip($("#node-function-expand-js"), RED._("node-red:common.label.expand"));
|
RED.popover.tooltip($("#node-function-expand-js"), RED._("node-red:common.label.expand"));
|
||||||
RED.popover.tooltip($("#node-finalize-expand-js"), RED._("node-red:common.label.expand"));
|
RED.popover.tooltip($("#node-finalize-expand-js"), RED._("node-red:common.label.expand"));
|
||||||
|
|
||||||
$("#node-input-require-container").css('min-height','250px').css('min-width','450px').editableList({
|
prepareLibraryConfig(that);
|
||||||
addItem: function(container,i,opt) {
|
|
||||||
var row = $("<div/>").appendTo(container);
|
|
||||||
var fvar = $("<input/>", {
|
|
||||||
class: "node-input-require-var",
|
|
||||||
placeholder: RED._("node-red:function.require.var"),
|
|
||||||
type: "text",
|
|
||||||
disabled: true
|
|
||||||
}).css({
|
|
||||||
width: "130px",
|
|
||||||
"margin-left": "5px"
|
|
||||||
}).appendTo(row);
|
|
||||||
var fmodule = $("<input/>", {
|
|
||||||
class: "node-input-require-val",
|
|
||||||
placeholder: RED._("node-red:function.require.module"),
|
|
||||||
type: "text",
|
|
||||||
disabled: true
|
|
||||||
}).css({
|
|
||||||
width: "390px",
|
|
||||||
"margin-left": "5px"
|
|
||||||
}).appendTo(row);
|
|
||||||
if (opt) {
|
|
||||||
if (opt.vname) {
|
|
||||||
fvar.val(opt.vname);
|
|
||||||
}
|
|
||||||
if (opt.name) {
|
|
||||||
fmodule.val(opt.name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
addButton: false,
|
|
||||||
removable: false
|
|
||||||
});
|
|
||||||
|
|
||||||
function updateLibs() {
|
|
||||||
var id = $("#node-input-libs").val();
|
|
||||||
var node = RED.nodes.node(id);
|
|
||||||
if (node && node.libs) {
|
|
||||||
var libs = node.libs;
|
|
||||||
$("#node-input-require-container").editableList("empty");
|
|
||||||
libs.forEach(function (r) {
|
|
||||||
$("#node-input-require-container").editableList("addItem", r);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updateLibs();
|
|
||||||
$("#node-input-libs").on("change", function () {
|
|
||||||
updateLibs();
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
oneditsave: function() {
|
oneditsave: function() {
|
||||||
var node = this;
|
var node = this;
|
||||||
@ -578,6 +781,23 @@
|
|||||||
$("#node-input-noerr").val(noerr);
|
$("#node-input-noerr").val(noerr);
|
||||||
this.noerr = noerr;
|
this.noerr = noerr;
|
||||||
|
|
||||||
|
var libs = $("#node-input-libs-container").editableList("items");
|
||||||
|
var oldLibs = node.libs || [];
|
||||||
|
var newLibs = [];
|
||||||
|
node.libs = newLibs;
|
||||||
|
libs.each(function(i) {
|
||||||
|
var item = $(this);
|
||||||
|
var v = item.find(".node-input-libs-var").val();
|
||||||
|
var n = item.find(".node-input-libs-val").val();
|
||||||
|
if ((!v || (v === "")) ||
|
||||||
|
(!n || (n === ""))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
newLibs.push({
|
||||||
|
vname: v,
|
||||||
|
spec: n
|
||||||
|
});
|
||||||
|
});
|
||||||
},
|
},
|
||||||
oneditcancel: function() {
|
oneditcancel: function() {
|
||||||
var node = this;
|
var node = this;
|
||||||
@ -611,7 +831,7 @@
|
|||||||
this.editor.resize();
|
this.editor.resize();
|
||||||
this.finalizeEditor.resize();
|
this.finalizeEditor.resize();
|
||||||
|
|
||||||
$("#node-input-require-container").css("height", (height -155)+"px");
|
$("#node-input-libs-container").css("height", (height -155)+"px");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
@ -17,18 +17,6 @@
|
|||||||
module.exports = function(RED) {
|
module.exports = function(RED) {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
function LibsConfigNode(n) {
|
|
||||||
RED.nodes.createNode(this, n);
|
|
||||||
this.name = n.name;
|
|
||||||
this.libs = n.libs;
|
|
||||||
}
|
|
||||||
RED.nodes.registerType("library-config", LibsConfigNode);
|
|
||||||
|
|
||||||
RED.httpNode.get("/function/modules", function (req, res) {
|
|
||||||
var list = RED.nodes.listNPMModules();
|
|
||||||
res.send(list);
|
|
||||||
});
|
|
||||||
|
|
||||||
var util = require("util");
|
var util = require("util");
|
||||||
var vm = require("vm");
|
var vm = require("vm");
|
||||||
|
|
||||||
@ -101,9 +89,8 @@ module.exports = function(RED) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function FunctionNode(n) {
|
function FunctionNode(n) {
|
||||||
var libConf = RED.nodes.getNode(n.libs);
|
var libs = n.libs || [];
|
||||||
var libs = libConf ? libConf.libs : [];
|
n.modules = libs.map(x => x.spec).filter(x => (x && (x !== "")));
|
||||||
n.modules = libs.map(x => x.name).filter(x => (x && (x !== "")));
|
|
||||||
var loadPromise = RED.nodes.createNode(this,n);
|
var loadPromise = RED.nodes.createNode(this,n);
|
||||||
var node = this;
|
var node = this;
|
||||||
node.name = n.name;
|
node.name = n.name;
|
||||||
@ -301,7 +288,8 @@ module.exports = function(RED) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// wait for module installation
|
// wait for module installation
|
||||||
loadPromise.then(function () {
|
loadPromise.catch(()=>{
|
||||||
|
}).finally(function () {
|
||||||
if (node.hasOwnProperty("libs")) {
|
if (node.hasOwnProperty("libs")) {
|
||||||
var modules = node.libs;
|
var modules = node.libs;
|
||||||
modules.forEach(module => {
|
modules.forEach(module => {
|
||||||
@ -309,11 +297,14 @@ module.exports = function(RED) {
|
|||||||
if (vname && (vname !== "")) {
|
if (vname && (vname !== "")) {
|
||||||
sandbox[vname] = null;
|
sandbox[vname] = null;
|
||||||
try {
|
try {
|
||||||
var lib = RED.require(module.name);
|
var spec = module.spec;
|
||||||
|
if (spec && (spec !== "")) {
|
||||||
|
var lib = RED.require(module.spec);
|
||||||
sandbox[vname] = lib;
|
sandbox[vname] = lib;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
node.warn("failed to load library: "+ module.name);
|
node.warn("failed to load library: "+ module.spec);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -83,7 +83,7 @@ function createNodeApi(node) {
|
|||||||
httpAdmin: runtime.adminApp,
|
httpAdmin: runtime.adminApp,
|
||||||
server: runtime.server
|
server: runtime.server
|
||||||
}
|
}
|
||||||
copyObjectProperties(runtime.nodes,red.nodes,["createNode","getNode","eachNode","addCredentials","getCredentials","deleteCredentials", "listNPMModules"]);
|
copyObjectProperties(runtime.nodes,red.nodes,["createNode","getNode","eachNode","addCredentials","getCredentials","deleteCredentials"]);
|
||||||
red.nodes.registerType = function(type,constructor,opts) {
|
red.nodes.registerType = function(type,constructor,opts) {
|
||||||
runtime.nodes.registerType(node.id,type,constructor,opts);
|
runtime.nodes.registerType(node.id,type,constructor,opts);
|
||||||
}
|
}
|
||||||
|
@ -447,5 +447,47 @@ var api = module.exports = {
|
|||||||
} else {
|
} else {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets list of NPM modules
|
||||||
|
* @param {Object} opts
|
||||||
|
* @param {User} opts.user - the user calling the api
|
||||||
|
* @param {Object} opts.req - the request to log (optional)
|
||||||
|
* @return {Promise<Buffer>} - list of installed NPM modules
|
||||||
|
* @memberof @node-red/runtime_nodes
|
||||||
|
*/
|
||||||
|
listNPMModules: async function(opts) {
|
||||||
|
var promise = runtime.nodes.listNPMModules();
|
||||||
|
return promise;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uninstall NPM modules
|
||||||
|
* @param {Object} opts
|
||||||
|
* @param {User} opts.user - the user calling the api
|
||||||
|
* @param {Object} opts.req - the request to log (optional)
|
||||||
|
* @return {Promise<Object>} - object for request result
|
||||||
|
* @memberof @node-red/runtime_nodes
|
||||||
|
*/
|
||||||
|
uninstallNPMModule: async function(opts) {
|
||||||
|
var spec = opts.spec;
|
||||||
|
var promise = runtime.nodes.uninstallNPMModule(spec);
|
||||||
|
return promise;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update NPM modules
|
||||||
|
* @param {Object} opts
|
||||||
|
* @param {User} opts.user - the user calling the api
|
||||||
|
* @param {Object} opts.req - the request to log (optional)
|
||||||
|
* @return {Promise<Object>} - object for request result
|
||||||
|
* @memberof @node-red/runtime_nodes
|
||||||
|
*/
|
||||||
|
updateNPMModule: async function(opts) {
|
||||||
|
var spec = opts.spec;
|
||||||
|
var isUpdate = opts.update;
|
||||||
|
var promise = runtime.nodes.updateNPMModule(spec, isUpdate);
|
||||||
|
return promise;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -270,6 +270,7 @@ function stop() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// This is the internal api
|
// This is the internal api
|
||||||
var runtime = {
|
var runtime = {
|
||||||
version: getVersion,
|
version: getVersion,
|
||||||
|
@ -273,5 +273,7 @@ module.exports = {
|
|||||||
|
|
||||||
// NPM modules
|
// NPM modules
|
||||||
listNPMModules: npmModule.list,
|
listNPMModules: npmModule.list,
|
||||||
|
uninstallNPMModule: npmModule.uninstall,
|
||||||
|
updateNPMModule: npmModule.update,
|
||||||
loadNPMModule: npmModule.load
|
loadNPMModule: npmModule.load
|
||||||
};
|
};
|
||||||
|
@ -28,6 +28,8 @@ var log;
|
|||||||
|
|
||||||
var npmCommand = (process.platform === "win32") ? "npm.cmd" : "npm";
|
var npmCommand = (process.platform === "win32") ? "npm.cmd" : "npm";
|
||||||
|
|
||||||
|
var metadataFileName = "npm-modules.json";
|
||||||
|
|
||||||
var moduleProp = {};
|
var moduleProp = {};
|
||||||
var moduleBase = null;
|
var moduleBase = null;
|
||||||
|
|
||||||
@ -35,6 +37,8 @@ var allowInstall = true;
|
|||||||
var allowList = ["*"];
|
var allowList = ["*"];
|
||||||
var denyList = [];
|
var denyList = [];
|
||||||
|
|
||||||
|
var inProgress = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialise npm install module.
|
* Initialise npm install module.
|
||||||
* @param {Object} _runtime - runtime object
|
* @param {Object} _runtime - runtime object
|
||||||
@ -46,6 +50,8 @@ function init(_runtime) {
|
|||||||
log = _runtime.log;
|
log = _runtime.log;
|
||||||
|
|
||||||
moduleProp = {};
|
moduleProp = {};
|
||||||
|
inProgress = {};
|
||||||
|
|
||||||
moduleBase = settings.userDir || process.env.NODE_RED_HOME || ".";
|
moduleBase = settings.userDir || process.env.NODE_RED_HOME || ".";
|
||||||
|
|
||||||
if (settings.hasOwnProperty("externalModules")) {
|
if (settings.hasOwnProperty("externalModules")) {
|
||||||
@ -96,7 +102,6 @@ function register(type, prop) {
|
|||||||
*/
|
*/
|
||||||
function modulePath() { // takes variable length arguments in `arguments`
|
function modulePath() { // takes variable length arguments in `arguments`
|
||||||
var result = moduleBase;
|
var result = moduleBase;
|
||||||
result = path.join(result, "lib", "node_modules");
|
|
||||||
for(var i = 0; i < arguments.length; i++) {
|
for(var i = 0; i < arguments.length; i++) {
|
||||||
result = path.join(result, arguments[i]);
|
result = path.join(result, arguments[i]);
|
||||||
}
|
}
|
||||||
@ -106,9 +111,10 @@ function modulePath() { // takes variable length arguments in `arguments`
|
|||||||
/**
|
/**
|
||||||
* Decompose NPM module specification string
|
* Decompose NPM module specification string
|
||||||
* @param {string} module - module specification
|
* @param {string} module - module specification
|
||||||
|
* @return {Object} array [name, version], where name is name part and version is version part of the module
|
||||||
*/
|
*/
|
||||||
function moduleName(module) {
|
function moduleName(module) {
|
||||||
var match = /^([^@]+@.+)/.exec(module);
|
var match = /^([^@]+)@(.+)/.exec(module);
|
||||||
if (match) {
|
if (match) {
|
||||||
return [match[1], match[2]];
|
return [match[1], match[2]];
|
||||||
}
|
}
|
||||||
@ -116,15 +122,15 @@ function moduleName(module) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get NPM module package info
|
* Get NPM module info
|
||||||
* @param {string} name - module name
|
* @param {string} name - module name
|
||||||
* @param {string} name - module version
|
* @return {Object} package.json for specified NPM module
|
||||||
*/
|
*/
|
||||||
function infoNPM(name, ver) {
|
function infoNPM(name) {
|
||||||
var path = modulePath(name, "package.json");
|
var path = modulePath("node_modules", name, "package.json");
|
||||||
try {
|
try {
|
||||||
var pkg = require(path);
|
var pkg = fs.readFileSync(path);
|
||||||
return pkg;
|
return JSON.parse(pkg);
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
}
|
}
|
||||||
@ -132,17 +138,50 @@ function infoNPM(name, ver) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ensure existance of module installation directory
|
* Load NPM module metadata
|
||||||
|
* @return {object} module metadata object
|
||||||
*/
|
*/
|
||||||
function ensureLibDirectory() {
|
function loadMetadata() {
|
||||||
var path = modulePath();
|
var path = modulePath(metadataFileName);
|
||||||
if (!fs.existsSync(path)) {
|
try {
|
||||||
fs.mkdirSync(path, {
|
var pkg = fs.readFileSync(path);
|
||||||
recursive: true
|
return JSON.parse(pkg);
|
||||||
});
|
|
||||||
return fs.existsSync(path);
|
|
||||||
}
|
}
|
||||||
return true;
|
catch (e) {
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
modules: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save NPM module metadata
|
||||||
|
* @param {string} data - module metadata object
|
||||||
|
*/
|
||||||
|
function saveMetadata(data) {
|
||||||
|
var path = modulePath(metadataFileName);
|
||||||
|
var str = JSON.stringify(data, null, 4);
|
||||||
|
fs.writeFileSync(path, str);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find item in metadata
|
||||||
|
* @param {Object} meta - metadata
|
||||||
|
* @param {string} name - module name
|
||||||
|
* @return {object} metadata item
|
||||||
|
*/
|
||||||
|
function findModule(meta, name) {
|
||||||
|
var modules = meta.modules;
|
||||||
|
var item = modules.find(item => (item.name === name));
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setInProgress(name) {
|
||||||
|
inProgress[name] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearInProgress(name) {
|
||||||
|
inProgress[name] = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -151,11 +190,9 @@ function ensureLibDirectory() {
|
|||||||
*/
|
*/
|
||||||
function installNPM(module) {
|
function installNPM(module) {
|
||||||
var [name, ver] = moduleName(module);
|
var [name, ver] = moduleName(module);
|
||||||
if (!ensureLibDirectory()) {
|
setInProgress(name);
|
||||||
log.warn("failed to install: "+name);
|
return new Promise((resolve, reject) => {
|
||||||
return;
|
var pkg = infoNPM(name);
|
||||||
}
|
|
||||||
var pkg = infoNPM(name, ver);
|
|
||||||
if (!pkg) {
|
if (!pkg) {
|
||||||
var args = ["install", module];
|
var args = ["install", module];
|
||||||
var dir = modulePath();
|
var dir = modulePath();
|
||||||
@ -163,20 +200,50 @@ function installNPM(module) {
|
|||||||
cwd: dir
|
cwd: dir
|
||||||
}, true).then(result => {
|
}, true).then(result => {
|
||||||
if (result && (result.code === 0)) {
|
if (result && (result.code === 0)) {
|
||||||
log.info("successfully installed: "+name);
|
pkg = infoNPM(name);
|
||||||
|
var spec = name +(pkg ? "@"+pkg.version : "");
|
||||||
|
log.info("successfully installed: "+spec);
|
||||||
|
var meta = loadMetadata();
|
||||||
|
var item = {
|
||||||
|
name: name,
|
||||||
|
spec: module,
|
||||||
|
status: "installed",
|
||||||
|
};
|
||||||
|
meta.modules.push(item);
|
||||||
|
saveMetadata(meta);
|
||||||
|
clearInProgress(name);
|
||||||
|
resolve(true);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
log.warn("failed to install: "+name);
|
clearInProgress(name);
|
||||||
|
var msg = "failed to install: "+name;
|
||||||
|
log.warn(msg);
|
||||||
|
reject(msg);
|
||||||
}
|
}
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
var msg = e.hasOwnProperty("stderr") ? e.stderr : e;
|
clearInProgress(name);
|
||||||
log.warn("failed to install: "+name);
|
var msg = "failed to install: "+name
|
||||||
|
log.warn(msg);
|
||||||
|
reject(msg);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
log.info("already installed: "+name);
|
var meta = loadMetadata();
|
||||||
|
if (!findModule(meta, name)) {
|
||||||
|
var item = {
|
||||||
|
name: name,
|
||||||
|
spec: module,
|
||||||
|
status: "preinstalled",
|
||||||
|
};
|
||||||
|
meta.modules.push(item);
|
||||||
|
saveMetadata(meta);
|
||||||
}
|
}
|
||||||
return Promise.resolve();
|
clearInProgress(name);
|
||||||
|
var spec = name +(pkg ? ("@"+pkg.version) : "");
|
||||||
|
log.info("already installed: "+spec);
|
||||||
|
}
|
||||||
|
resolve(true);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -213,6 +280,8 @@ function checkInstall(node) {
|
|||||||
name = module.name;
|
name = module.name;
|
||||||
}
|
}
|
||||||
if (isAllowed(name)) {
|
if (isAllowed(name)) {
|
||||||
|
var [n, v] = moduleName(name);
|
||||||
|
setInProgress(name);
|
||||||
promises.push(installNPM(name));
|
promises.push(installNPM(name));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@ -230,7 +299,7 @@ function checkInstall(node) {
|
|||||||
function load(module) {
|
function load(module) {
|
||||||
try {
|
try {
|
||||||
var [name, ver] = moduleName(module);
|
var [name, ver] = moduleName(module);
|
||||||
var path = modulePath(name);
|
var path = modulePath("node_modules", name);
|
||||||
var npm = require(path);
|
var npm = require(path);
|
||||||
return npm;
|
return npm;
|
||||||
}
|
}
|
||||||
@ -243,32 +312,140 @@ function load(module) {
|
|||||||
* Get list of installed modules
|
* Get list of installed modules
|
||||||
*/
|
*/
|
||||||
function listModules() {
|
function listModules() {
|
||||||
var modPath = modulePath();
|
return new Promise((resolve, reject) => {
|
||||||
if (!fs.existsSync(modPath)) {
|
var meta = loadMetadata();
|
||||||
return [];
|
var modules = meta.modules;
|
||||||
|
modules.forEach(item => {
|
||||||
|
var name = item.name;
|
||||||
|
var info = infoNPM(name);
|
||||||
|
if (info) {
|
||||||
|
item.version = info.version;
|
||||||
}
|
}
|
||||||
var dir = fs.opendirSync(modPath);
|
item.inProgress = ((name in inProgress) && inProgress[name]);
|
||||||
var modules = [];
|
});
|
||||||
if (dir) {
|
Object.keys(inProgress).forEach(name => {
|
||||||
var ent = null;
|
if (inProgress[name] &&
|
||||||
while (ent = dir.readSync()) {
|
!modules.find(item => (item.name === name))) {
|
||||||
var name = ent.name;
|
modules.push({
|
||||||
if (ent.isDirectory() &&
|
name: name,
|
||||||
(name[0] !== ".")) {
|
spec: name,
|
||||||
var pkgPath = path.join(modPath, name, "package.json");
|
state: "inprogress",
|
||||||
if (fs.existsSync(pkgPath)) {
|
inProgress: true
|
||||||
var pkg = fs.readJSONSync(pkgPath);
|
});
|
||||||
var info = {
|
}
|
||||||
name: pkg.name,
|
});
|
||||||
version: pkg.version
|
resolve(meta.modules);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uninstall NPM modules
|
||||||
|
*/
|
||||||
|
function uninstall(module) {
|
||||||
|
var [name, ver] = moduleName(module);
|
||||||
|
setInProgress(name);
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
var pkg = infoNPM(name);
|
||||||
|
var meta = loadMetadata();
|
||||||
|
var item = findModule(meta, name);
|
||||||
|
if (pkg && item) {
|
||||||
|
if (item.status === "preinstalled") {
|
||||||
|
clearInProgress(name);
|
||||||
|
var msg = "can't uninstall preinstalled: "+name;
|
||||||
|
log.warn(msg);
|
||||||
|
reject(msg);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
var args = ["uninstall", module];
|
||||||
|
var dir = modulePath();
|
||||||
|
return exec.run(npmCommand, args, {
|
||||||
|
cwd: dir
|
||||||
|
}, true).then(result => {
|
||||||
|
if (result && (result.code === 0)) {
|
||||||
|
log.info("successfully uninstalled: "+name);
|
||||||
|
var meta = loadMetadata();
|
||||||
|
var items = meta.modules.filter(item => (item.name !== name));
|
||||||
|
meta.modules = items;
|
||||||
|
saveMetadata(meta);
|
||||||
|
clearInProgress(name);
|
||||||
|
resolve(true);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
clearInProgress(name);
|
||||||
|
var msg = "failed to uninstall: "+name;
|
||||||
|
log.warn(msg);
|
||||||
|
reject(msg);
|
||||||
|
}
|
||||||
|
}).catch(e => {
|
||||||
|
clearInProgress(name);
|
||||||
|
var msg = "failed to uninstall: "+name;
|
||||||
|
log.warn(msg);
|
||||||
|
reject(msg);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
clearInProgress(name);
|
||||||
|
var msg = "module not installed: "+name;
|
||||||
|
log.info(msg);
|
||||||
|
reject(msg);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update NPM modules
|
||||||
|
*/
|
||||||
|
function update(module, isUpdate) {
|
||||||
|
var act = (isUpdate ? "updated": "install")
|
||||||
|
var acted = (isUpdate ? "updated": "installed")
|
||||||
|
var [name, ver] = moduleName(module);
|
||||||
|
setInProgress(name);
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
var pkg = infoNPM(name);
|
||||||
|
if (!pkg || isUpdate) {
|
||||||
|
var args = ["install", module];
|
||||||
|
var dir = modulePath();
|
||||||
|
return exec.run(npmCommand, args, {
|
||||||
|
cwd: dir
|
||||||
|
}, true).then(result => {
|
||||||
|
if (result && (result.code === 0)) {
|
||||||
|
pkg = infoNPM(name);
|
||||||
|
var spec = name +(pkg ? "@"+pkg.version : "");
|
||||||
|
log.info("successfully "+acted+": "+spec);
|
||||||
|
var meta = loadMetadata();
|
||||||
|
var items = meta.modules.filter(item => (item.name !== name));
|
||||||
|
var item = {
|
||||||
|
name: name,
|
||||||
|
spec: module,
|
||||||
|
status: "installed",
|
||||||
};
|
};
|
||||||
modules.push(info);
|
items.push(item);
|
||||||
|
meta.modules = items;
|
||||||
|
saveMetadata(meta);
|
||||||
|
clearInProgress(name);
|
||||||
|
resolve(true);
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
clearInProgress(name);
|
||||||
|
var msg = "failed to "+act+": "+name;
|
||||||
|
log.warn(msg);
|
||||||
|
reject(msg);
|
||||||
}
|
}
|
||||||
|
}).catch(e => {
|
||||||
|
clearInProgress(name);
|
||||||
|
var msg = "failed to "+act+": "+name;
|
||||||
|
log.warn(msg);
|
||||||
|
reject(msg);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
dir.closeSync();
|
else {
|
||||||
|
clearInProgress(name);
|
||||||
|
var msg = "not "+acted+": "+name;
|
||||||
|
log.info(msg);
|
||||||
|
reject(msg);
|
||||||
}
|
}
|
||||||
return modules;
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
api = {
|
api = {
|
||||||
@ -276,6 +453,8 @@ api = {
|
|||||||
register: register,
|
register: register,
|
||||||
checkInstall: checkInstall,
|
checkInstall: checkInstall,
|
||||||
load: load,
|
load: load,
|
||||||
list: listModules
|
list: listModules,
|
||||||
|
uninstall: uninstall,
|
||||||
|
update: update
|
||||||
};
|
};
|
||||||
module.exports = api;
|
module.exports = api;
|
||||||
|
Loading…
Reference in New Issue
Block a user