Add partial implementation of adding library sources via editor

This adds lots of commented out code that provides a settings panel
to add new library sources. It is incomplete as it doesn't actually
add/update the library sources on the runtime.

For 1.3, I'm focussing on allowing additional sources get added
via the settings file only. I've done enough work on the editor
side to convince myself more work is needed than I can justify
at this time on what is otherwise not going to be a widely
used feature.
This commit is contained in:
Nick O'Leary 2021-01-22 11:20:28 +00:00 committed by Nick O'Leary
parent 8a076c01ab
commit 3f9a29730f
No known key found for this signature in database
GPG Key ID: 4F2157149161A6C9
13 changed files with 393 additions and 39 deletions

View File

@ -93,6 +93,7 @@ module.exports = {
// Library
var library = require("./library");
library.init(runtimeAPI);
// editorApp.get("/library/:id",needsPermission("library.read"),library.getLibraryConfig);
editorApp.get(/^\/library\/([^\/]+)\/([^\/]+)(?:$|\/(.*))/,needsPermission("library.read"),library.getEntry);
editorApp.post(/^\/library\/([^\/]+)\/([^\/]+)\/(.*)/,needsPermission("library.write"),library.saveEntry);

View File

@ -24,6 +24,17 @@ module.exports = {
init: function(_runtimeAPI) {
runtimeAPI = _runtimeAPI;
},
// getLibraryConfig: function(req,res) {
// var opts = {
// user: req.user,
// library: req.params.id
// }
// runtimeAPI.library.getConfig(opts).then(function(result) {
// res.json(result);
// }).catch(function(err) {
// apiUtils.rejectHandler(req,res,err);
// });
// },
getEntry: function(req,res) {
var opts = {
user: req.user,

View File

@ -854,10 +854,15 @@ RED.clipboard = (function() {
}
function loadFlowLibrary(browser,library) {
var icon = 'fa fa-hdd-o';
if (library.icon) {
var fullIcon = RED.utils.separateIconPath(library.icon);
icon = (fullIcon.module==="font-awesome"?"fa ":"")+fullIcon.file;
}
browser.data([{
library: library.id,
type: "flows",
icon: library.icon || 'fa fa-hdd-o',
icon: icon,
label: RED._(library.label||library.id),
path: "",
expanded: true,

View File

@ -483,9 +483,235 @@ RED.library = (function() {
}
}
// var libraryPlugins = {};
//
// function showLibraryDetailsDialog(container, lib, done) {
// var dialog = $('<div>').addClass("red-ui-projects-dialog-list-dialog").hide().appendTo(container);
// $('<div>').addClass("red-ui-projects-dialog-list-dialog-header").text(lib?"Edit library source":"Add library source").appendTo(dialog);
// var formRow = $('<div class="red-ui-settings-row"></div>').appendTo(dialog);
// $('<label>').text("Type").appendTo(formRow);
// var typeSelect = $('<select>').appendTo(formRow);
// for (var type in libraryPlugins) {
// if (libraryPlugins.hasOwnProperty(type)) {
// $('<option>').attr('value',type).attr('selected',(lib && lib.type === type)?true:null).text(libraryPlugins[type].name).appendTo(typeSelect);
// }
// }
// var dialogBody = $("<div>").addClass("red-ui-settings-section").appendTo(dialog);
// var libraryFields = {};
// var fieldsModified = {};
// function validateFields() {
// var validForm = true;
// for (var p in libraryFields) {
// if (libraryFields.hasOwnProperty(p)) {
// var v = libraryFields[p].input.val().trim();
// if (v === "") {
// validForm = false;
// if (libraryFields[p].modified) {
// libraryFields[p].input.addClass("input-error");
// }
// } else {
// libraryFields[p].input.removeClass("input-error");
// }
// }
// }
// okayButton.attr("disabled",validForm?null:"disabled");
// }
// typeSelect.on("change", function(evt) {
// dialogBody.empty();
// libraryFields = {};
// fieldsModified = {};
// var libDef = libraryPlugins[$(this).val()];
// var defaultIcon = lib?lib.icon:(libDef.icon || "font-awesome/fa-image");
// formRow = $('<div class="red-ui-settings-row"></div>').appendTo(dialogBody);
// $('<label>').text(RED._("editor.settingIcon")).appendTo(formRow);
// libraryFields['icon'] = {input: $('<input type="hidden">').val(defaultIcon) };
// var iconButton = $('<button type="button" class="red-ui-button"></button>').appendTo(formRow);
// iconButton.on("click", function(evt) {
// evt.preventDefault();
// var icon = libraryFields['icon'].input.val() || "";
// var iconPath = (icon ? RED.utils.separateIconPath(icon) : {});
// RED.editor.showIconPicker(iconButton, null, iconPath, true, function (newIcon) {
// iconButton.empty();
// var path = newIcon || "";
// var newPath = RED.utils.separateIconPath(path);
// if (newPath) {
// $('<i class="fa"></i>').addClass(newPath.file).appendTo(iconButton);
// }
// libraryFields['icon'].input.val(path);
// });
// })
// var newPath = RED.utils.separateIconPath(defaultIcon);
// $('<i class="fa '+newPath.file+'"></i>').appendTo(iconButton);
//
// var libProps = libDef.defaults;
// var libPropKeys = Object.keys(libProps).map(function(p) { return {id: p, def: libProps[p]}});
// libPropKeys.unshift({id: "label", def: {value:""}})
//
// libPropKeys.forEach(function(prop) {
// var p = prop.id;
// var def = prop.def;
// formRow = $('<div class="red-ui-settings-row"></div>').appendTo(dialogBody);
// var label = libDef._(def.label || "label."+p,{defaultValue: p});
// if (label === p) {
// label = libDef._("editor:common.label."+p,{defaultValue:p});
// }
// $('<label>').text(label).appendTo(formRow);
// libraryFields[p] = {
// input: $('<input type="text">').val(lib?(lib[p]||lib.config[p]):def.value).appendTo(formRow),
// modified: false
// }
// if (def.type === "password") {
// libraryFields[p].input.attr("type","password").typedInput({type:"cred"})
// }
//
// libraryFields[p].input.on("change paste keyup", function(evt) {
// if (!evt.key || evt.key.length === 1) {
// libraryFields[p].modified = true;
// }
// validateFields();
// })
// var desc = libDef._("desc."+p, {defaultValue: ""});
// if (desc) {
// $('<label class="red-ui-projects-edit-form-sublabel"></label>').append($('<small>').text(desc)).appendTo(formRow);
// }
// });
// validateFields();
// })
//
// var dialogButtons = $('<span class="button-row" style="position: relative; float: right; margin: 10px;"></span>').appendTo(dialog);
// var cancelButton = $('<button class="red-ui-button"></button>').text(RED._("common.label.cancel")).appendTo(dialogButtons).on("click", function(evt) {
// evt.preventDefault();
// done(false);
// })
// var okayButton = $('<button class="red-ui-button"></button>').text(lib?"Update library":"Add library").appendTo(dialogButtons).on("click", function(evt) {
// evt.preventDefault();
// var item;
// if (!lib) {
// item = {
// id: libraryFields['label'].input.val().trim().toLowerCase().replace(/( |[^a-z0-9])/g,"-"),
// user: true,
// type: typeSelect.val(),
// config: {}
// }
// } else {
// item = lib;
// }
//
// item.label = libraryFields['label'].input.val().trim();
// item.icon = libraryFields['icon'].input.val();
//
// for (var p in libraryFields) {
// if (libraryFields.hasOwnProperty(p) && p !== 'label') {
// item.config[p] = libraryFields[p].input.val().trim();
// }
// }
// done(item);
// });
//
// typeSelect.trigger("change");
// if (lib) {
// typeSelect.attr('disabled',true);
// }
//
// dialog.slideDown(200);
// }
//
// function createSettingsPane() {
// var pane = $('<div id="red-ui-settings-tab-library-manager"></div>');
// var toolbar = $('<div>').css("text-align","right").appendTo(pane);
// var addButton = $('<button class="red-ui-button"><i class="fa fa-plus"></i> Add library</button>').appendTo(toolbar);
//
// var addingLibrary = false;
//
// var libraryList = $("<ol>").css({
// position: "absolute",
// left: "10px",
// right: "10px",
// top: "50px",
// bottom: "10px"
// }).appendTo(pane).editableList({
// addButton: false,
// addItem: function(row,index,itemData) {
// if (itemData.id) {
// row.addClass("red-ui-settings-tab-library-entry");
// var iconCell = $("<span>").appendTo(row);
// if (itemData.icon) {
// var iconPath = RED.utils.separateIconPath(itemData.icon);
// if (iconPath) {
// $("<i>").addClass("fa "+iconPath.file).appendTo(iconCell);
// }
// }
// $("<span>").text(RED._(itemData.label)).appendTo(row);
// $("<span>").text(RED._(itemData.type)).appendTo(row);
// $('<button class="red-ui-button red-ui-button-small"></button>').text(RED._("sidebar.project.projectSettings.edit")).appendTo(
// $('<span>').appendTo(row)
// ).on("click", function(evt) {
// if (addingLibrary) {
// return;
// }
// evt.preventDefault();
// addingLibrary = true;
// row.empty();
// row.removeClass("red-ui-settings-tab-library-entry");
// showLibraryDetailsDialog(row,itemData,function(newItem) {
// var itemIndex = libraryList.editableList("indexOf", itemData);
// libraryList.editableList("removeItem", itemData);
// if (newItem) {
// libraryList.editableList("insertItemAt", newItem, itemIndex);
// } else {
// libraryList.editableList("insertItemAt", itemData,itemIndex);
// }
// addingLibrary = false;
//
// })
// })
//
// } else {
// showLibraryDetailsDialog(row,null,function(newItem) {
// libraryList.editableList("removeItem", itemData);
// if (newItem) {
// libraryList.editableList("addItem", newItem);
// }
// addingLibrary = false;
// })
//
// }
// }
// });
//
// addButton.on('click', function(evt) {
// evt.preventDefault();
// if (!addingLibrary) {
// addingLibrary = true;
// libraryList.editableList("addItem",{user:true});
// }
// })
// var libraries = RED.settings.libraries || [];
// libraries.forEach(function(library) {
// if (library.user) {
// libraryList.editableList("addItem",library)
// }
// })
//
// return pane;
// }
//
//
return {
init: function() {
// RED.events.on("registry:plugin-added", function(id) {
// var plugin = RED.plugins.getPlugin(id);
// if (plugin.type === "node-red-library-source") {
// libraryPlugins[id] = plugin;
// }
// });
//
// RED.userSettings.add({
// id:'library-manager',
// title: "NLS: Libraries",
// get: createSettingsPane,
// close: function() {}
// });
$(_librarySave).appendTo("#red-ui-editor").i18n();
$(_libraryLookup).appendTo("#red-ui-editor").i18n();

View File

@ -231,4 +231,42 @@
padding: 0;
margin: 0;
}
}
#red-ui-settings-tab-library-manager {
box-sizing: border-box;
padding: 10px;
position: relative;
height: 100%;
li {
padding: 0;
}
}
.red-ui-settings-tab-library-entry {
display: flex;
flex-direction: row;
span:not(:nth-child(2)) {
@include disable-selection;
}
span {
padding: 8px 0;
}
span:first-child {
display: inline-block;
width: 20px;
padding-right: 8px;
text-align: center;
flex-grow: 0;
}
span:nth-child(2) {
flex-grow: 1;
}
span:nth-child(3), span:nth-child(4) {
flex-grow: 0;
padding-right: 5px;
color: $secondary-text-color;
font-size: 0.9em;
}
}

View File

@ -807,6 +807,7 @@ div.red-ui-projects-dialog-ssh-public-key {
border: 1px solid $secondary-border-color;
.red-ui-projects-edit-form-sublabel {
margin-top: -8px !important;
margin-right: 50px;
display: block !important;
width: auto !important;
}

View File

@ -25,6 +25,27 @@ var api = module.exports = {
runtime = _runtime;
},
// /* *
// * Gets the configuration of a library source.
// * @param {Object} opts
// * @param {User} opts.user - the user calling the api
// * @param {String} opts.library - the library
// * @param {Object} opts.req - the request to log (optional)
// * @return {Promise<String|Object>} - resolves when complete
// * @memberof @node-red/runtime_library
// */
// getConfig: async function(opts) {
// runtime.log.audit({event: "library.getConfig",library:opts.library}, opts.req);
// try {
// return runtime.library.getConfig(opts.library)
// } catch(err) {
// var error = new Error();
// error.code = "not_found";
// error.status = 404;
// throw error;
// }
// },
/**
* Gets an entry from the library.
* @param {Object} opts

View File

@ -95,7 +95,11 @@ function getEntry(type,path) {
}
module.exports = {
name: '_examples_',
id: "examples",
label: "editor:library.types.examples",
icon: "font-awesome/fa-life-ring",
types: ["flows"],
readOnly: true,
init: init,
getEntry: getEntry
}

View File

@ -18,6 +18,7 @@
const {events} = require("@node-red/util")
const knownTypes = {};
const libraries = {};
const libraryConfigs = {};
const libraryPlugins = {};
// Libraries defined in the settings file. Their configurations
@ -37,7 +38,9 @@ function init(runtime) {
runtimeLibraries.forEach(library => {
if (library.type === id) {
library.local = false;
libraryConfigs[library.id] = library;
libraries[library.id] = new plugin.class(library)
libraryConfigs[library.id].type = id;
if (libraries[library.id].init) {
libraries[library.id].init();
}
@ -49,11 +52,13 @@ function init(runtime) {
})
knownTypes.flows = 'node-red';
libraries["local"] = require("./local");
libraries["local"].init(runtime);
libraryConfigs["local"] = libraries["local"]
libraries["examples"] = require("./examples");
libraries["examples"].init(runtime);
libraries["local"] = require("./local");
libraries["local"].init(runtime);
libraryConfigs["examples"] = libraries["examples"]
try {
runtimeLibraries = runtime.settings.editorTheme.library.sources;
@ -100,42 +105,58 @@ function saveEntry(library,type,path,meta,body) {
}
function getLibraries() {
const libraryList = [
{
id: "local",
label: "editor:library.types.local",
user: false,
icon: "fa fa-bath"
},
{
id: "examples",
label: "editor:library.types.examples",
user: false,
readOnly: true,
types: ['flows']
}
];
const libraryList = []
for (let id in libraries) {
if (libraries.hasOwnProperty(id)) {
if (id !== 'local' && id !== 'examples') {
libraryList.push({
id: id,
label: libraries[id].name || id,
user: false,
icon: libraries[id].icon
})
var config = getConfig(id);
// Don't include the full configuration of each library when providing
// the list of all libraries
delete config.config;
libraryList.push(config);
}
}
return libraryList;
}
function getConfig(id) {
var lib = {
id: id,
label: libraryConfigs[id].label || id,
user: false,
icon: libraryConfigs[id].icon
}
if (libraryConfigs[id].types) {
lib.types = libraryConfigs[id].types
}
if (libraryConfigs[id].readOnly) {
lib.readOnly = libraryConfigs[id].readOnly
}
if (libraryConfigs[id].type) {
lib.type = libraryConfigs[id].type;
var def = libraryPlugins[lib.type];
if (def && def.defaults) {
lib.config = {};
for (var d in def.defaults) {
if (def.defaults.hasOwnProperty(d)) {
if (def.defaults[d].type !== 'password') {
lib.config[d] = libraryConfigs[id][d];
} else if (!!libraryConfigs[id][d]) {
lib.config["has_"+d] = true;
}
}
}
}
}
return libraryList;
return lib;
}
module.exports = {
init: init,
getLibraries: getLibraries,
// getConfig: getConfig,
register: registerType,
getEntry: getEntry,
saveEntry: saveEntry

View File

@ -30,7 +30,9 @@ function saveEntry(type,path,meta,body) {
}
module.exports = {
name: 'local',
id: "local",
label: "editor:library.types.local",
icon: "font-awesome/fa-hdd-o",
init: init,
getEntry: getEntry,
saveEntry: saveEntry

View File

@ -4,8 +4,19 @@
name: "Local File-System Library",
icon: "font-awesome/fa-hdd-o",
defaults: {
name: { value: "" },
path: { value: ""}
path: { value: ""},
// secret: { type: "password" }
}
})
RED.plugins.registerPlugin("node-red-library-dbstore", {
type: "node-red-library-source",
name: "Database Library",
icon: "font-awesome/fa-database",
defaults: {
connection: { value: ""}
}
})
</script>

View File

@ -1,13 +1,15 @@
module.exports = function(RED) {
const PLUGIN_TYPE_ID = "node-red-library-filestore";
class FileStorePlugin {
constructor(config) {
this.type = PLUGIN_TYPE_ID;
this.id = config.id;
this.name = config.name;
this.config = config;
this.label = config.label;
this.config = config.config;
this.icon = config.icon;
console.log("FileStorePlugin",config)
}
async init() {
@ -20,13 +22,16 @@ module.exports = function(RED) {
}
async saveEntry(type,path,meta,body) {
console.log("FileStorePlugin.saveLibraryEntry",type,path)
}
}
RED.plugins.registerPlugin("node-red-library-filestore", {
RED.plugins.registerPlugin(PLUGIN_TYPE_ID, {
type: "node-red-library-source",
class: FileStorePlugin
class: FileStorePlugin,
defaults: {
"path": { value: "" },
// "secret": { type: "password" }
}
})
}

View File

@ -0,0 +1,8 @@
{
"label": {
"path": "Path"
},
"desc": {
"path":"The local file-system path to the library"
}
}