I wanted to access the sub-folders of an FTP server (created automatically by my surveillance cameras), but the node https://flows.nodered.org/node/node-red-contrib-ftp only displayed the folders root.
@afelix found a proposal to modify the original node on the Github: https://github.com/joeartsea/node-red-contrib-ftp/issues/19
You must therefore open the two files found in (RPI example): /home/pi/.node-red/node_modules/node-red-contrib-ftp
- Copy / paste in
ftp.html
:
<script type="text/x-red" data-template-name="ftp">
<div class="form-row">
<label for="node-config-input-host"><i class="fa fa-bookmark"></i> Host</label>
<input type="text" id="node-config-input-host" placeholder="localhost" style="width: 40%;" />
<label for="node-config-input-port" style="margin-left: 10px; width: 35px; "> Port</label>
<input type="text" id="node-config-input-port" placeholder="21" style="width:45px">
</div>
<div class="form-row">
<label for="node-config-input-user"><i class="fa fa-user"></i> User</label>
<input type="text" id="node-config-input-user" placeholder="anonymous" />
</div>
<div class="form-row">
<label for="node-config-input-password"><i class="fa fa-lock"></i> Password</label>
<input type="password" id="node-config-input-password">
</div>
<div class="form-row">
<label for="node-config-input-connTimeout"><i class="fa fa-clock-o"></i> Connection Timeout</label>
<input type="text" id="node-config-input-connTimeout" placeholder="10000">
</div>
<div class="form-row">
<label for="node-config-input-pasvTimeout"><i class="fa fa-clock-o"></i> PASV Timeout</label>
<input type="text" id="node-config-input-pasvTimeout" placeholder="10000">
</div>
<div class="form-row">
<label for="node-config-input-keepalive"><i class="fa fa-clock-o"></i> Keepalive</label>
<input type="text" id="node-config-input-keepalive" placeholder="10000">
</div>
<div class="form-row">
<label for="node-input-secure"><i class="fa fa-key"></i> Data connection encryption</label>
<input type="checkbox" id="node-input-secure" placeholder="once" style="width: 10%; vertical-align: top;">
<label for="node-config-input-secureOptions" style="width: 20%; vertical-align: top;"> Secure Options</label>
<input type="text" id="node-config-input-secureOptions" style="width: 35%; vertical-align: top;">
</div>
</script>
<script type="text/x-red" data-help-name="ftp in">
<p>Allows LIST, GET, PUT and DELETE of files on a remote FTP server.</p>
<p><code>msg.filename</code> is the path/name of the file on the remote FTP server</p>
<p><code>msg.localFilename</code> is the path/name of the file on the local machine</p>
<p>NOTE: the local machine is where NR is running - this might not be the machine where your browser connects to Node Red.</p>
<p></p>
<p>GET uses <code>msg.filename</code> and <code>msg.localFilename</code></p>
<p>PUT uses <code>msg.filename</code> and <code>msg.localFilename</code></p>
<p>DELETE uses only <code>msg.filename</code></p>
<p>LIST uses only <code>msg.filename</code> and returns an array of the file/folders, containing type, name, sticky, rights(an object), acl, owner, group, size and date.</p>
<p></p>
<p>You can pass <code>msg.filename</code> and <code>msg.localFilename</code> to this node, however values entered in the node will take precedence.</p>
<p></p>
<p>If an error occurs, the error message will come out as <code>msg</code>.</p>
</script>
<script type="text/javascript">
RED.nodes.registerType('ftp', {
category: 'config',
color:"BurlyWood",
defaults: {
host: { value: '' },
port: { value: '' },
secure: { value: false },
secureOptions: { value: '' },
user: { value: '' },
connTimeout: { value: '' },
pasvTimeout: { value: '' },
keepalive: { value: '' }
},
credentials: {
password: { type: 'password', required: true },
},
label: function() {
return this.host
}
});
</script>
<script type="text/x-red" data-template-name="ftp in">
<div class="form-row">
<label for="node-input-ftp"><i class="fa fa-user"></i> Add new FTP Server</label>
<input type="text" id="node-input-ftp">
</div>
<div class="form-row">
<label for="node-input-operation"><i class="fa fa-wrench"></i> Operation</label>
<select type="text" id="node-input-operation">
<option value="list">list</option>
<option value="get">get</option>
<option value="put">put</option>
<option value="delete">delete</option>
</select>
</div>
<div class="form-row input-filename-row hidden">
<label for="node-input-filename"><i class="fa fa-file"></i> Filename</label>
<input type="text" id="node-input-filename" placeholder="Filename">
</div>
<div class="form-row input-localFilename-row hidden">
<label for="node-input-localFilename"><i class="fa fa-file"></i> Local Filename</label>
<input type="text" id="node-input-localFilename" placeholder="Local Filename">
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
</script>
<script type="text/javascript">
RED.nodes.registerType('ftp in', {
category: 'storage-input',
color:"BurlyWood",
defaults: {
ftp: { type: 'ftp', required: true },
operation: { value: 'list', required: true },
filename: { value: '' },
localFilename: { value: '' },
name: { value: '' }
},
inputs: 1,
outputs: 1,
icon: "file.png",
label: function () {
var ftpNode = RED.nodes.node(this.ftp);
return this.name || 'ftp';
},
labelStyle: function () {
return this.name ? 'node_label_italic' : '';
},
oneditprepare: function () {
var filename = $(".input-filename-row");
var localFilename = $(".input-localFilename-row");
$("#node-input-operation").change(function () {
var id = $("#node-input-operation option:selected").val();
console.log(id);
if (id == 'list') {
filename.show();
localFilename.hide();
} else if (id == 'put' || id == 'get') {
filename.show();
localFilename.show();
} else {
filename.show();
localFilename.hide();
}
});
}
});
</script>
- and in
ftp.js
:
module.exports = function (RED) {
'use strict';
var ftp = require('ftp');
var fs = require('fs');
function FtpNode(n) {
RED.nodes.createNode(this, n);
var node = this;
var credentials = RED.nodes.getCredentials(n.id);
this.options = {
'host': n.host || 'localhost',
'port': n.port || 21,
'secure': n.secure || false,
'secureOptions': n.secureOptions,
'user': n.user || 'anonymous',
'password': credentials.password || 'anonymous@',
'connTimeout': n.connTimeout || 10000,
'pasvTimeout': n.pasvTimeout || 10000,
'keepalive': n.keepalive || 10000
};
}
RED.nodes.registerType('ftp', FtpNode, {
credentials: {
password: { type: 'password' }
}
});
function FtpInNode(n) {
RED.nodes.createNode(this, n);
this.ftp = n.ftp;
this.operation = n.operation;
this.filename = n.filename;
this.localFilename = n.localFilename;
this.ftpConfig = RED.nodes.getNode(this.ftp);
if (this.ftpConfig) {
var node = this;
node.on('input', function (msg) {
var conn = new ftp();
var filename = node.filename || msg.filename || '';
var localFilename = node.localFilename || msg.localFilename || '';
this.sendMsg = function (err, result) {
if (err) {
node.error(err, msg);
node.status({ fill: 'red', shape: 'ring', text: 'failed' });
node.send(err);
return;
}
node.status({});
if (node.operation == 'get') {
result.once('close', function() { conn.end(); });
result.pipe(fs.createWriteStream(localFilename));
msg.payload = 'Get operation successful. ' + localFilename;
} else if (node.operation == 'put') {
conn.end();
msg.payload = 'Put operation successful.';
} else {
conn.end();
msg.message = 'Operation successful.';
msg.payload = result;
}
msg.filename = filename;
msg.localFilename = localFilename;
node.send(msg);
};
conn.on('ready', function () {
switch (node.operation) {
case 'list':
conn.list(filename, node.sendMsg);
break;
case 'get':
conn.get(filename, node.sendMsg);
break;
case 'put':
conn.put(localFilename, filename, node.sendMsg);
break;
case 'delete':
conn.delete(filename, node.sendMsg);
break;
}
});
conn.on('error', function(err) {
node.error(err, msg);
node.status({ fill: 'red', shape: 'ring', text: err.message });
node.send(err);
return;
});
conn.connect(node.ftpConfig.options);
});
} else {
this.error('missing ftp configuration');
}
}
RED.nodes.registerType('ftp in', FtpInNode);
}
- Restart Node-Red
This flow display files in the folder Test\newFolder
[{"id":"c00bd000.a5f0c","type":"ftp in","z":"a8b1b208.7036f","ftp":"42562df8.ff98b4","operation":"list","filename":"","localFilename":"","name":"list","x":470,"y":1600,"wires":[["a3de95d2.f1f7a8"]]},{"id":"a3de95d2.f1f7a8","type":"debug","z":"a8b1b208.7036f","name":"complet","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":640,"y":1600,"wires":[]},{"id":"b5da47bd.987978","type":"change","z":"a8b1b208.7036f","name":"pay to filename","rules":[{"t":"move","p":"payload","pt":"msg","to":"filename","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":320,"y":1600,"wires":[["c00bd000.a5f0c"]]},{"id":"19f7caab.4c1865","type":"inject","z":"a8b1b208.7036f","name":"Test\\newFolder","topic":"","payload":"Test\\newFolder","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":140,"y":1600,"wires":[["b5da47bd.987978"]]},{"id":"42562df8.ff98b4","type":"ftp","z":"","host":"192.168.1.143","port":"21","secureOptions":"","user":"superninja","connTimeout":"","pasvTimeout":"","keepalive":""}]