Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Master #44

Merged
merged 5 commits into from
Aug 21, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 80 additions & 57 deletions Readme.md
Original file line number Diff line number Diff line change
@@ -1,46 +1,48 @@
# Mikronode

Full-Featured asynchronous Mikrotik API interface for [NodeJS](http://nodejs.org).

```javscript
var MikroNode = require('mikronode');

var device = new MikroNode('192.168.0.1');

device.connect('admin','password').then(function(connection) {

var chan=conn.openChannel("addresses"); // open a named channel
var chan2=conn.openChannel("firewall_connections",true); // open a named channel, turn on "closeOnDone"
device.connect()
.then(([login])=>{
return login('username','password');
})
.then(function(connection) {

chan.write('/ip/address/print');
var chan=conn.openChannel("addresses"); // open a named channel
var chan2=conn.openChannel("firewall_connections",true); // open a named channel, turn on "closeOnDone"

chan.on('done',function(data) {
chan.write('/ip/address/print');

chan.on('done',function(data) {

// data is all of the sentences in an array.
data.forEach(function(item) {
console.log('Interface/IP: '+item.data.interface+"/"+item.data.address);
});

chan.close(); // close the channel.
chan.close(); // close the channel. It is not autoclosed by default.
conn.close(); // when closing connection, the socket is closed and program ends.

});
});
});

chan.write('/ip/firewall/print');
chan.write('/ip/firewall/print');

chan.done.subscribe(function(data){
chan.done.subscribe(function(data){

// data is all of the sentences in an array.
data.forEach(function(item) {
var data = MikroNode.resultsToObj(item.data); // convert array of field items to object.
console.log('Interface/IP: '+data.interface+"/"+data.address);
});

});
});
});

});

```
## Installation

Clone this repository into your node_modules directory.
Expand Down Expand Up @@ -78,13 +80,28 @@

### Connection

Calling new MikroNode(host) returns an API object.

* apiInstance.connect('user','pass',optionsObject)
Connect to the target device. The callback function is called after successful login with the current connection object as its parameter.
* conn.openChannel(id)
Open and return a new channel object. Each channel is a unique command line to the mikrotik, allowing simultaneous execution of commands. The ID parameter is optional.
* conn.connected()
Calling new MikroNode(host[,port,socketTimeout]) returns an object representing the device.
```javascript
var MikroNode = require('mikronode');
var Device =new MikroNode(host,port);
Device.connect().then(([login])=>login('admin','password')).then(function(conn) {
var chan=conn.openChannel();
});
```
With the above code, the following is API description. conn is Connection object, chan is Channel object.
* MikroNode.resultsToObj(dataObj) <Object|Array>
Convert the sentence format of the mikrotik into a more easily readable
* Device.connect([cb]) <Promise>
Connect to the target device. The optional callback function is called after successful connect with the function to call to login as the 2nd parameter, and any connection errors as the first.
the connect method returns a Promise that is resolved when connecting.
* Device.socketOpts (write-only property)
Optionally allows the setting of parameters that the socket connecting to the mikrotik will use.
* Device.TLS(tlsOpts)
Enable TLS and set it's options. Take note that you will need to be sure the port the API is trying to connect is an SSL/TLS port. For unauthenticated SSL connections (no signed certs) only ADH cipher is supported. This is a limitation of the RouterOS software
* Device.setDebug(level)
Set the default debug logging level for the device, and all subsequent created connections.
* conn.openChannel(id|name) <Channel>
Open and return a new channel object. Each channel is a unique command line to the mikrotik, allowing simultaneous execution of commands. The ID parameter is optional. If not specified, the current timestamp is used. If too many channels are opened at one time without specifying a name, there could be duplicate names. * conn.connected()
Returns true is currently connected to a mikrotik device.
* conn.closeChannel(id)
Closes an open channel. This will call the close method of the channel object.
Expand All @@ -98,20 +115,22 @@

### Channel

The following methods are available for channels:
The following property/methods are available for channels:

* channel.done
* channel.done <Observable>
"done" is the stream that contains events when the done sentence comes from the device.
When subscribing, the stream's data contans an object with each line received in an array.
* channel.read
* channel.data <Observable>
For each sentence received, this has an observable event. Only sentences designated for this channel will pass through this sentence.
This is handy when following trailing output from a listen command, where the data could be endless.
* channel.trap <Observable>
Any traps that occur on a channel can be captured in this observable stream.
* chanenl.sync(b)
If b == true, each command is run synchronously. Otherwise commands are executed as they are passed.
* channel.closeOnDone(b)
If b == true, when a done event occurs, close the channel after all commands queued have been executed.
* channel.getId()
* channel.write(lines[,optionsObject])
* channel.write(lines[,optionsObject]) <Promise>
Returns a promise that is resolved when the command sent is complete and is "done"
The promise is rejected if a trap or fatal error occurs.
Lines can be a string, or an array of strings. If it is a string, then it is split on the EOL character and each resulting line is sent as a separate word (in API speak)
Expand All @@ -124,11 +143,11 @@
## Examples

### Connect to a Mikrotik, and add an address to ether1

```javascript
var api = require('mikronode');

var device = new api('192.168.0.1');
device.connect('admin','password').then(function(conn) {
device.connect().then(([login])=>login('admin','password')).then(function(conn) {

var chan=conn.openChannel();

Expand All @@ -142,19 +161,18 @@
chan.close();
conn.close();
});

```
### Writing the program for the example API conversation on the [Mikrotik Wiki](http://wiki.mikrotik.com/wiki/API#.2Fcancel.2C_simultaneous_commands)

```javascript
var MikroNode = require('mikronode');

var device = new MikroNode('192.168.0.1');
device.connect('admin','password').then(function(conn) {

device.connect().then(([login])=>login('admin','password')).then(function(conn) {
conn.closeOnDone(true); // when all channels are "done" the connection should close.

var chan1=conn.openChannel("interface_listener");
chan1.write('/interface/listen');
chan1.read.subscribe(function(item) {
chan1.data.subscribe(function(item) {
var packet=MikroNode.resultsToObj(item.data);
console.log('Interface change: '+JSON.stringify(packet));
});
Expand Down Expand Up @@ -193,14 +211,14 @@
});
});
});

```
### Simplifying the above by reducing the number of channels.
Notice how the callback embedding is not needed using the syncronous capability.

```javascript
var MikroNode = require('mikronode');

var device = new MikroNode('192.168.0.1');
device.connect('admin','password').then(function(conn) {
device.connect().then(([login])=>login('admin','password')).then(function(conn) {
conn.closeOnDone(true); // All channels need to complete before the connection will close.
var listenChannel=conn.openChannel();
listenChannel.write('/interface/listen');
Expand All @@ -227,13 +245,13 @@
});
actionChannel.close(); // The above commands will complete before this is closed.
});

```
### Promises add simplicity:

```javascript
var MikroNode = require('mikronode');
var device = new MikroNode('192.168.0.1');
device.connect('admin','password').then(function(conn) {
console.log("Logged in.")
device.connect().then(([login])=>login('admin','password')).then(function(conn) {
console.log("Logged in.");
conn.closeOnDone(true); // All channels need to complete before the connection will close.
var listenChannel=conn.openChannel("listen");

Expand All @@ -255,25 +273,39 @@
.catch(error=>console.log("Listen channel rejection:",error));

// All our actions go through this.
var actionChannel=conn.openChannel("action");
var actionChannel=conn.openChannel("action",false); // don't close on done... because we are running these using promises, the commands complete before each then is complete.

// Do things async. This is to prove that promises work as expected along side streams.
actionChannel.sync(false);
actionChannel.closeOnDone(false); // Turn off closeOnDone because the timeouts set to allow the mikrotik to reflect the changes takes too long. The channel would close.

// These will run synchronsously (even though sync is not set to true)
console.log("Disabling interface");
actionChannel.write('/interface/set',{'disabled':'yes','.id':'ether1'}).then(results=>{
console.log("Disable complete.");
// Delay 1 second before running next command so that the Interface change listener can report the change.
return new Promise((r,x)=>setTimeout(r,1000)).then(()=>actionChannel.write('/interface/set',{'disabled':'no','.id':'ether1'}));
// when the first item comes in from the listen channel, it should send the next command.
const {promise,resolve,reject}=MikroNode.getUnwrappedPromise();
listenChannel.data
.take(1)
// This is just to prove that it grabbed the first one.
.do(d=>console.log("Data:",MikroNode.resultsToObj(d.data)))
.subscribe(d=>actionChannel.write('/interface/set',{'disabled':'no','.id':'ether1'}).then(resolve,reject));
return promise;
})
.then(results=>{
console.log("Enabled complete.");
// Delay 1 second before running next command so that the Interface change listener can report the change.
return new Promise((r,x)=>setTimeout(r,1000)).then(()=>actionChannel.write('/interface/getall'));
// return new Promise((r,x)=>setTimeout(r,1000)).then(()=>actionChannel.write('/interface/getall'));
const {promise,resolve,reject}=MikroNode.getUnwrappedPromise();
// when the second item comes in from the listen channel, it should send the next command.
listenChannel.data
.take(1)
// This is just to prove that it grabbed the second one.
.do(d=>console.log("Data:",MikroNode.resultsToObj(d.data)))
.subscribe(d=>actionChannel.write('/interface/getall').then(resolve,reject));
return promise;
})
.then(results=>{
var formatted=MikroNode.resultsToObj(results);
var formatted=MikroNode.resultsToObj(results.data);
var columns=[".id","name","mac-address","comment"];
var filtered=formatted.map(line=>columns.reduce((p,c)=>{p[c]=line[c];return p},{}));
console.log('Interface [ID,Name,MAC-Address]: ',JSON.stringify(filtered,true,4));
Expand All @@ -286,26 +318,17 @@
console.log("Closing everything.");
listenChannel.close(true); // This should call the /cancel command to stop the listen.
actionChannel.close();
});

// This just watches for responses from the writes in the promises above. There are no results in the set commands, but there is a large result for the getall
actionChannel.done.subscribe(function(results) {
console.log('Interface (done): ',results);
},error=>{
console.log("Error during done subscription",error)
},()=>{
console.log("Action channel done.");
});
});

```

### The methods *decodeLength* and *encodeString* were written based on code [here on the Mikrotik Wiki](http://wiki.mikrotik.com/wiki/API_PHP_class#Class).

## License

(The MIT License)

Copyright (c) 2011,2012,2013,2014,2015,2016 Brandon Myers <trakkasure@gmail.com>
Copyright (c) 2011,2012,2013,2014,2015,2016,2017 Brandon Myers <trakkasure@gmail.com>

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
Expand Down
4 changes: 2 additions & 2 deletions examples/getInterfacesAndRoutes.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
var api=require('../dist/mikronode.js');

var device=new api(/* Host */'10.10.10.1' /*, Port */ /*, Timeout */);
var device=new api(/* Host */'10.10.10.10' /*, Port */ /*, Timeout */);
// device.setDebug(api.DEBUG);

// connect: user, password.
device.connect('username','password').then(function(conn) {
device.connect().then(([login])=>login('username','password')).then(function(conn) {
var c1=conn.openChannel();
var c2=conn.openChannel();
c1.closeOnDone(true);
Expand Down
4 changes: 2 additions & 2 deletions examples/ipCountOnly.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@

var MikroNode = require('../dist/mikronode.js');
// Create API instance to a host.
var device = new MikroNode('10.10.10.1');
var device = new MikroNode('10.10.10.10');
// device.setDebug(MikroNode.DEBUG);

// Connect to MikroTik device
device.connect('username','password').then(
device.connect(/* socketOpts */).then(([login])=>login('username','password')).then(
function(conn) {
// When all channels are marked done, close the connection.
conn.closeOnDone(true);
Expand Down
35 changes: 25 additions & 10 deletions examples/promise.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
var MikroNode = require('../src/index.js');
require('babel-register');var MikroNode = require('../src');

var device = new MikroNode('10.10.10.10');
device.connect('admin','password').then(function(conn) {
console.log("Logged in.")
// device.setDebug(MikroNode.DEBUG);
device.connect().then(([login])=>login('admin','password')).then(function(conn) {
console.log("Logged in.");
conn.closeOnDone(true); // All channels need to complete before the connection will close.
var listenChannel=conn.openChannel("listen");

Expand All @@ -24,25 +25,39 @@ device.connect('admin','password').then(function(conn) {
.catch(error=>console.log("Listen channel rejection:",error));

// All our actions go through this.
var actionChannel=conn.openChannel("action");
var actionChannel=conn.openChannel("action",false); // don't close on done... because we are running these using promises, the commands complete before each then is complete.

// Do things async. This is to prove that promises work as expected along side streams.
actionChannel.sync(false);
actionChannel.closeOnDone(false); // Turn off closeOnDone because the timeouts set to allow the mikrotik to reflect the changes takes too long. The channel would close.

// These will run synchronsously (even though sync is not set to true)
console.log("Disabling interface");
actionChannel.write('/interface/set',{'disabled':'yes','.id':'ether1'}).then(results=>{
console.log("Disable complete.");
// Delay 1 second before running next command so that the Interface change listener can report the change.
return new Promise((r,x)=>setTimeout(r,1000)).then(()=>actionChannel.write('/interface/set',{'disabled':'no','.id':'ether1'}));
// when the first item comes in from the listen channel, it should send the next command.
const {promise,resolve,reject}=MikroNode.getUnwrappedPromise();
listenChannel.data
.take(1)
// This is just to prove that it grabbed the first one.
.do(d=>console.log("Data:",MikroNode.resultsToObj(d.data)))
.subscribe(d=>actionChannel.write('/interface/set',{'disabled':'no','.id':'ether1'}).then(resolve,reject));
return promise;
})
.then(results=>{
console.log("Enabled complete.");
// Delay 1 second before running next command so that the Interface change listener can report the change.
return new Promise((r,x)=>setTimeout(r,1000)).then(()=>actionChannel.write('/interface/getall'));
// return new Promise((r,x)=>setTimeout(r,1000)).then(()=>actionChannel.write('/interface/getall'));
const {promise,resolve,reject}=MikroNode.getUnwrappedPromise();
// when the second item comes in from the listen channel, it should send the next command.
listenChannel.data
.take(1)
// This is just to prove that it grabbed the second one.
.do(d=>console.log("Data:",MikroNode.resultsToObj(d.data)))
.subscribe(d=>actionChannel.write('/interface/getall').then(resolve,reject));
return promise;
})
.then(results=>{
var formatted=MikroNode.resultsToObj(results);
var formatted=MikroNode.resultsToObj(results.data);
var columns=[".id","name","mac-address","comment"];
var filtered=formatted.map(line=>columns.reduce((p,c)=>{p[c]=line[c];return p},{}));
console.log('Interface [ID,Name,MAC-Address]: ',JSON.stringify(filtered,true,4));
Expand All @@ -56,4 +71,4 @@ device.connect('admin','password').then(function(conn) {
listenChannel.close(true); // This should call the /cancel command to stop the listen.
actionChannel.close();
});
});
}).catch(e=>console.log("Error connecting: ",e));
4 changes: 2 additions & 2 deletions examples/simpleWrite.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@

var MikroNode = require('../src');
var MikroNode = require('../dist/mikronode.js');
// Create API instance to a host.
var device = new MikroNode('10.10.10.10');
device.setDebug(MikroNode.DEBUG);

// Connect to MikroTik device
device.connect('admin','password').then(conn=>{
device.connect().then(([login])=>login('admin','password')).then(conn=>{
// When all channels are marked done, close the connection.
console.log('connected');

Expand Down
4 changes: 2 additions & 2 deletions examples/watchIpList.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
var MikroNode = require('../dist/mikronode.js');

// Create API link to host. No connection yet..
var device = new MikroNode('10.10.10.1');
var device = new MikroNode('10.10.10.10');

// Debug level is "DEBUG"
// device.setDebug(MikroNode.DEBUG);

var removeId=[];
// Connect to the MikroTik device.
device.connect('username','password').then(function(conn) {
device.connect().then(([login])=>login('username','password')).then(function(conn) {

var channel=conn.openChannel('all_addresses');
channel.closeOnDone(true); // only use this channel for one command.
Expand Down
Loading