-
Notifications
You must be signed in to change notification settings - Fork 36
Client Commands
- Menu navigation
- Printing, selecting data
- Adding entries
- Editing entries
- Removing entries
- Other commands
With the client returned after connecting, we are able to do a handfull of commands. Let's check them out.
Once we have connected to the routerboard and a RosApiMenu
client was returned, we then use it to navigate through the menus so we can do our commands.
To do that, we use the menu()
function and giving it the menu we want to navigate to, it will return a RosApiCommands
object so we are able to do some commands in that said menu.
Let's see an example:
menu(path: string): RosApiCommands
routeros.connect().then((client) => {
// Connected successfully
const addressMenu = client.menu("/ip address");
// We can either use the "/ip address" syntax or
// the usual API syntax like "/ip/address"
const accessMenu = client.menu("/ip/proxy/access");
console.log(addressMenu); // is at /ip address
console.log(accessMenu); // is at /ip proxy access
}).catch((err) => {
// Error when trying to connect
console.log(err);
});
Note: the menu()
function will always return a new RosApiCommands
object, so you don't need to worry about having to change back to a previous menu, you can reuse it anywhere you want once created.
Once we navigate to the menu we want, and having the RosApiCommands
object, we can then start doing commands with it. Let's start by printing some data using the get()
command:
routeros.connect().then((client) => {
// Connected successfully
const addressMenu = client.menu("/ip address");
// This would translate to something like
// '/ip address print'
addressMenu.get().then((addresses) => {
// Addresses is an array of objects of
// all addresses from the '/ip address' menu
console.log(addresses);
}).catch((err) => {
// Error getting data
console.log(err);
});
}).catch((err) => {
// Error when trying to connect
console.log(err);
});
There are other aliases you can use:
get(data?: object)
getAll(data?: object)
print(data?: object)
The array returned when getting the entries from a menu will be formated to be used in a way that we won't need to get the object properties by name, we can do it by index. Example:
{
address: "192.168.88.1/24",
network: "192.168.88.0",
interface: "ether5",
actualInterface: "ether2-master"
}
Note that the actualInterface
was formatted from routeros's actual-interface
. All dashed properties are converted to camelCase, so you don't need to call it like address['actual-interface']
, you can call it by index like address.actualInterface
. Other considerations:
- All properties that would start with a dot
.
gets the dot removed, which means that.id
,.nextid
and.dead
are converted toid
,nextid
anddead
respectively. - Values that are all numbers are parsed to float, allowing you to do calculations directly, otherwise it would be considered a string and concatenated in case you would make a sum.
-
true
orfalse
strings are parsed to boolean.
Note: if your workflow uses snake_case instead of camelCase, use this tip.
We can query when printing data using the where()
function. Example:
const addressMenu = client.menu("/ip address");
addressMenu.where("interface", "ether5").get().then((addresses) => {
// Addresses will be all addresses from interface ether5
console.log(addresses);
}).catch((err) => {
// Error getting data
console.log(err);
});
You can also pass an object when querying:
addressMenu.where("interface", "ether5").get();
// Or
addressMenu.where({interface: "ether5"}).get();
Where function is chainable, so you can do either:
addressMenu.where("interface", "ether5").where("network", "192.168.88.0").get();
// Or
addressMenu.where({interface: "ether5", network: "192.168.88.0"}).get();
You are able to get a bit more literal:
addressMenu
.where("interface", "ether5")
.andWhere("network", "192.168.88.0")
.get();
It is possible to use operations like AND
, OR
, NOT
and etc. Examples:
// OR
addressMenu
.where("interface", "ether5")
.orWhere("interface", "ether2")
.get();
// NOT
addressMenu
.whereNot("interface", "ether5")
.get();
// AND + NOT
addressMenu
.where("interface", "ether5")
.andWhereNot("network", "192.168.88.0")
.get();
// OR + NOT
addressMenu
.where("interface", "ether5")
.orWhereNot("interface" "ether2")
.get();
// HIGHER THAN
const interfaceMenu = client.menu("/interface");
interfaceMenu
.whereHigher("mtu", 1500)
.get();
// LOWER THAN
interfaceMenu
.whereLower("l2mtu", 1600)
.get();
// Property exists and has a value
interfaceMenu
.whereExists("comment") // or whereNotEmpty("comment")
.get();
// Property doesn't exists or has no value
interfaceMenu
.whereEmpty("comment") // or whereNotExists("comment")
.get();
Note: when querying using objects, all parameters are compared with the AND
operation.
The RouterOS has menus that are not a list of entries, like "/system routerboard". In thoses cases, if you use the get()
method, it would always return an array with length equal to 1. You can save time by using the getOne()
function:
const routerboardMenu = client.menu("/system routerboard");
routerboard.getOne().then((rbProps) => {
console.log(rbProps);
// rbProps {
// routerboard: "yes",
// model: "1100AHx2",
// serialNumber: "341102190E12",
// firmwareType: "p2020",
// factoryFirmware: 2.39,
// currentFirmware: 3.02,
// upgradeFirmware: 3.24
// }
}).catch((err) => {
// Error getting data
})
You can also use one of the aliases:
find(data?: object)
getOne(data?: object)
getOnly(data?: object)
You can pass your own filter using the API syntax using the whereRaw()
method. Example:
addressMenu.whereRaw([
"?interface=ether1",
"?type=ether",
"?#|"
]).get();
For more on the raw syntax, you can find it here.
We can filter the parameters that we want to retrieve by using the select()
function. Example:
addressMenu.select("id", "address", "interface").get().then((addresses) => {
console.log(addresses);
// addresses[0] {
// id: "*A1",
// address: "192.168.88.1",
// interface: "ether5"
// }
}).catch((err) => {
// Error getting data
});
This will omit all other properties, retrieving only the ones you asked for. Depending on the usage, opting to use select()
can increase the performance of the query. You can also pass an array of properties:
const props = ["id", "address", "interface"];
addressMenu.select(props).get();
You can also use the aliases:
select(fields: string | string[])
only(fields: string | string[])
proplist(fields: string | string[])
There are some menus on the routerOS that accepts some options when printing data. Imagine you want to print all the interfaces and you also want to pass the detail
to get all properties of the menu, to do that we use the options()
method. It follows the same principle of the select()
function, example:
interfaceMenu.options("detail").get();
// You can use multiple parameters
interfaceMenu.options("detail", "stats", "oid").get();
// Or pass them all as an array
interfaceMenu.options(["detail", "stats", "oid"]).get();
Note: careful when using the interval or follow and etc. option this way, when you are expecting continuous flow of data, use the stream instead.
Keep in mind that the options is used only for parameters that don't accept values. If you do need to pass an options that needs a value, use the query()
method instead. Example:
When using query()
or filter()
, the api will not add the question mark when writing over the socket, even when printing.
interfaceMenu.query("file", "afiletosavetheprint.txt").get();
This is not very beautiful, but in this specific case you can use the exec()
method like so:
interfaceMenu.exec("print", {
file: "afiletosavetheprint.txt"
}).then((data) => {
// Command executed successfully
console.log(data); // any data that your command should return, if any
}).catch((err) => {
// Error executing the command
});
You can see more of the exec()
method here.
Other aliases for query()
:
query(key: object | string, value?: string)
filter(key: object | string, value?: string)
Once inside a menu, we can add new items by using the add()
function, giving an object of parameters to add:
var vlanMenu = client.menu("/interface vlan");
vlanMenu.add({
name: "vlan40",
mtu: 1500,
interface: "ether1",
vlanId: 40
}).then((response) => {
console.log(response);
/* {
'$$path': "/interface/vlan",
id: "*EA",
name: "vlan40",
mtu: 1500,
interface: "ether1",
vlanId: 40,
...
} */
}).catch((err) => {
// Error adding
console.log(err);
});
Other aliases:
add(data: object)
create(data: object)
Note: remember that all dashed properties of the routerOS, like the vlanId
used here, can be represented in 3 ways: vlanId
which is the one we used, vlan_id
if you prefer snake_case or vlan-id
which is routerOS's default.
Note 2: when adding an entry, it will always return the ID (or
Changed in v0.10.0. When adding an entry, the item added will be returned with it's internal id.ret
) of the just added item, if no error was given.
When editing, we have to consider two types of menus:
- List menus
- Non list menus
In list menus, we need to inform the id
of the item we want to update. In non-list menus we just give the parameters that needs to be updated.
Some menus or items have parameters that can't be updated to an empty value, to do that we need to unset
the property, which is covered below.
To update properties, you guessed it, we use the update()
method:
addressMenu.update({
address: "192.168.50.1/24",
comment: "WIFI network on VLAN 5"
}, item.id).then((response) => {
// Updated!
// The response will be the updated item
}).catch((err) => {
// Error updating
console.log(err);
});
Note that we passed the item id in the second parameter, we can omit it and use the where()
method to define the id if you want:
addressMenu.where("id", item.id).update({
address: "192.168.50.1/24",
comment: "WIFI network on VLAN 5"
}).then((response) => {
// Updated!
// The response will be the updated item
}).catch((err) => {
// Error updating
console.log(err);
});
We can also query for an item to update:
addressMenu.where("interface", "ether4").update({
address: "192.168.50.1/24",
comment: "WIFI network on VLAN 5"
}).then((response) => {
// Updated!
// The response will be the updated item
}).catch((err) => {
// Error updating
console.log(err);
});
If it is a non-list menu:
var snmpMenu = client.menu("/ip snmp");
snmpMenu.update({
enabled: true
}).then((response) => {
// Updated!
// The response will be the updated info
}).catch((err) => {
// Error updating
console.log(err);
});
Other aliases:
update(data: object, ids?: string | string[])
set(data: object, ids?: string | string[])
edit(data: object, ids?: string | string[])
Like stated before, some properties can't be set to an empty value, that's a routerOS behavior. To deal with that, we need to unset the property by using the unset()
method:
var filterMenu = client.menu("/ip firewall filter");
filterMenu.unset([
"srcAddress",
"inInterface",
"outInterface"
], item.id).then((response) => {
// All properties was unset!
// The item(s) changed will be returned with the updated info
}).catch((err) => {
// Error unsetting
console.log(err);
});
You can also query unset:
var filterMenu = client.menu("/ip firewall filter");
filterMenu.where("dstPort", 8080).orWhere("chain", "output").unset([
"srcAddress",
"inInterface",
"outInterface"
]).then((response) => {
// All properties was unset!
// The item(s) changed will be returned with the updated info
}).catch((err) => {
// Error unsetting
console.log(err);
});
Note: you can give either an array of properties to unset or just a string with one property. When giving an array of properties, the API will unset them randomly in parallel. If trying to unset the second property, for example, and that property doesn't exist, the API will throw an error but won't stop unsetting the other properties. Keep that in mind if you want to unset the property only if another property needs to be unset first.
Note 2: unsetting properties that are already unset shouldn't throw any errors. But unsetting properties that can't be unset (ones that needs to be updated as empty string instead of unsetting) will throw an error.
Enabling and disabling only works with list menus, which is, that contains a list of entries (or items). To enable or disable an entry, you can do it in more than one way:
var filterMenu = client.menu("/ip firewall filter");
filterMenu.get().then((list) => {
var firstItem = list[0];
// DISABLING
filterMenu.disable(firstItem.id);
filterMenu.where("id", firstItem.id).disable();
filterMenu.where("id", firstItem.id).update({
disabled: true
});
// ENABLING
filterMenu.enable(firstItem.id);
filterMenu.where("id", firstItem.id).enable();
filterMenu.where("id", firstItem.id).update({
disabled: false
});
}).catch((err) => {
// Error getting the list of firewall filter entries
console.log(err);
})
Keep in mind that, whichever method you choose, it will always return a Promise
:
filterMenu.disable(firstItem.id).then(() => {
// Disabled!
}).catch((err) => {
// Error disabling
console.log(err);
});
// ----------------------- //
filterMenu.enable(firstItem.id).then(() => {
// Enabled!
}).catch((err) => {
// Error enabling
console.log(err);
});
Like enabling and disabling, moving is just for list menus. To move an item we have to keep in mind that:
- The item moved will always be above the destination.
- You can move by either the item
id
or the number that you see when using winbox. - To move to the bottom as the last item, just omit the destination.
- You can move one or multiples items above a single destination. Let's see an example:
filterMenu.move(thirdItem.id, firstItem.id).then((response) => {
// Third item moved above first item!
}).catch((err) => {
// Error moving
console.log(err);
});
// Moving multiple
filterMenu.move([
thirdItem.id, // *A1
fourthItem.id, // *A2
fifthItem.id, // *A3
sixthItem.id // *A4
], firstItem.id /* *A0 */).then((response) => {
// All items moved above first item! At once!
}).catch((err) => {
// Error moving
console.log(err);
});
Note: the move()
method doesn't work without informing the ids you want to move. If you want to query-move, you need to use the moveAbove()
method:
filterMenu.where("chain", "input").moveAbove(firstFowardRule.id).then((response) => {
// response will be an array of all items moved
}).catch((err) => {
// error moving
});
Once again, removing only works in list menus. You can remove an entry using the remove()
method:
filterMenu.remove(firstItem.id);
// OR
filterMenu.where("id", firstItem.id).remove();
You can remove multiple items at once:
var filterMenu = client.menu("/ip firewall filter");
filterMenu.get().then((filterList) => {
var ids = filterList.map((entry) => {
return entry.id;
});
filterMenu.remove(ids).then((response) => {
// Items removed!
}).catch((err) => {
// Error removing
console.log(err);
});
}).catch((err) => {
// Error getting the list of firewall filter entries
console.log(err);
})
You can also do it by querying:
var filterMenu = client.menu("/ip firewall filter");
filterMenu.where("chain", "output").remove().then((response) => {
// response is an array of all items removed
}).catch((err) => {
// Error getting the list of firewall filter entries
console.log(err);
})
Other aliases:
remove(ids?: string | string[])
delete(ids?: string | string[])
CAREFUL. You can use the purge()
method:
filterMenu.purge().then((response) => {
// Menu cleaned!
}).catch((err) => {
// Error purging entries
console.log(err);
})
In routerOS we have commands other than add
, set
, remove
, enable
and etc. To use those commands we can invoke the exec()
function. Let's say we want to generate and export of our routerboard and generate a backup:
var rootMenu = client.menu("/");
var now = new Date();
var year = now.getYear();
var month = now.getMonth() + 1;
rootMenu.exec("export", {
file: `export-${ year }-${ month }.rsc`
}).then((response) => {
// Export done, let's backup
return client.menu("/system backup").exec("save", {
name: `backup-${ year }-${ month }`,
dontEncrypt: true
});
}).then((response) => {
// Backup done!
console.log(response);
}).catch((err) => {
// Error exporting or backing up
console.log(err);
});
Next: Streaming Content