@gibme/mikrotik 1.1.2 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import SSH from '@gibme/ssh';
2
2
  import { BandwidthTest, Response } from './types';
3
3
  import Cache from '@gibme/cache/memory';
4
- export { ConnectConfig } from '@gibme/ssh';
4
+ export type { ConnectConfig } from '@gibme/ssh';
5
5
  export type Direction = BandwidthTest.Direction;
6
6
  export type Protocol = BandwidthTest.Protocol;
7
7
  export type Status = BandwidthTest.Status;
@@ -9,10 +9,14 @@ export type Update = BandwidthTest.Update;
9
9
  export type Options = BandwidthTest.Options;
10
10
  export type Address = Response.Address;
11
11
  export type Interface = Response.Interface;
12
+ export type Tunnel = Response.Tunnel;
12
13
  export type Route = Response.Route;
13
14
  export type RouteCount = Response.RouteCount;
14
15
  export type Ping = Response.Ping;
15
16
  export type Traceroute = Response.Traceroute;
17
+ export type RouterBoard = Response.Routerboard;
18
+ export type Resource = Response.Resource;
19
+ export type Health = Response.Health;
16
20
  export default class Mikrotik extends SSH {
17
21
  protected static cache: Cache;
18
22
  /**
@@ -20,16 +24,21 @@ export default class Mikrotik extends SSH {
20
24
  *
21
25
  * @param min_distance
22
26
  * @param vrf
27
+ * @param active_only
23
28
  */
24
- get_ip_routes(min_distance?: number, vrf?: string): Promise<Route[]>;
29
+ get_ip_routes(min_distance?: number, vrf?: string, active_only?: boolean): Promise<Route[]>;
25
30
  /**
26
31
  * Retrieves the IP addresses active on the system
32
+ *
33
+ * @param active_only
27
34
  */
28
- get_ip_addresses(): Promise<Address[]>;
35
+ get_ip_addresses(active_only?: boolean): Promise<Address[]>;
29
36
  /**
30
37
  * Retrieves the interfaces active on the system
38
+ *
39
+ * @param active_only
31
40
  */
32
- get_interfaces(): Promise<Interface[]>;
41
+ get_interfaces(active_only?: boolean): Promise<Interface[]>;
33
42
  /**
34
43
  * For all device IP addresses, counts the routes that exit (either directly or tunneled)
35
44
  * the device using each IP address
@@ -65,6 +74,34 @@ export default class Mikrotik extends SSH {
65
74
  * @param source
66
75
  */
67
76
  traceroute(target: string, source?: string): Promise<Traceroute[]>;
77
+ /**
78
+ * Fetches routerboard information from the device
79
+ */
80
+ routerboard(): Promise<Response.Routerboard>;
81
+ /**
82
+ * Fetches the identity of the device
83
+ */
84
+ identity(): Promise<string>;
85
+ /**
86
+ * Fetches the resource information of the device
87
+ */
88
+ resource(): Promise<Response.Resource>;
89
+ /**
90
+ * Fetches the current RouterOS version
91
+ */
92
+ version(): Promise<string>;
93
+ /**
94
+ * Fetches the semantic RouterOS version
95
+ */
96
+ semantic_version(): Promise<{
97
+ major: number;
98
+ minor: number;
99
+ patch: number;
100
+ }>;
101
+ /**
102
+ * Fetches the current health information
103
+ */
104
+ health(): Promise<Response.Health>;
68
105
  /**
69
106
  * Executes a command with expected terse response and parses it accordingly
70
107
  *
@@ -72,4 +109,11 @@ export default class Mikrotik extends SSH {
72
109
  * @protected
73
110
  */
74
111
  protected terse<Type extends object = any>(command: string): Promise<Type[]>;
112
+ /**
113
+ * Executes a command that expects the result as a 'table' of key-value pairs separated by a colon (:)
114
+ *
115
+ * @param command
116
+ * @protected
117
+ */
118
+ protected kvs<Type extends object = any>(command: string): Promise<Type>;
75
119
  }
package/dist/index.js CHANGED
@@ -33,73 +33,99 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
33
33
  Object.defineProperty(exports, "__esModule", { value: true });
34
34
  const ssh_1 = __importDefault(require("@gibme/ssh"));
35
35
  const types_1 = require("./types");
36
- const ip_1 = __importDefault(require("ip"));
36
+ const ip_address_1 = require("ip-address");
37
37
  const memory_1 = __importDefault(require("@gibme/cache/memory"));
38
38
  const dns_1 = require("dns");
39
+ const semver_1 = require("semver");
40
+ /** @ignore */
41
+ const toVersion = (version) => (0, semver_1.valid)((0, semver_1.coerce)(version)) || version;
42
+ /** @ignore */
43
+ const toIP4 = (address) => {
44
+ try {
45
+ return new ip_address_1.Address4(address);
46
+ }
47
+ catch (_a) {
48
+ return undefined;
49
+ }
50
+ };
39
51
  class Mikrotik extends ssh_1.default {
40
52
  /**
41
53
  * Retrieves the routes in the routing table
42
54
  *
43
55
  * @param min_distance
44
56
  * @param vrf
57
+ * @param active_only
45
58
  */
46
59
  get_ip_routes() {
47
- return __awaiter(this, arguments, void 0, function* (min_distance = 0, vrf) {
48
- const ifaces = yield this.get_interfaces();
49
- const ips = yield this.get_ip_addresses();
50
- const routes = yield this.terse('/ip route print terse without-paging where active');
60
+ return __awaiter(this, arguments, void 0, function* (min_distance = 0, vrf, active_only = true) {
61
+ const [ifaces, ips] = yield Promise.all([
62
+ this.get_interfaces(),
63
+ this.get_ip_addresses()
64
+ ]);
65
+ const command = `/ip route print terse without-paging${active_only ? ' where active' : ''}`;
66
+ const routes = yield this.terse(command);
51
67
  return routes.map(route => {
52
68
  var _a;
53
69
  const _network = route['dst-address'].split('/');
54
70
  const address = _network[0];
55
71
  const cidr = parseInt(_network[1]);
56
72
  const [gateway, iface] = (() => {
57
- if (route.gateway) {
58
- if (route.gateway.includes('%')) {
59
- return route.gateway.split('%', 2);
60
- }
61
- else if (ip_1.default.isV4Format(route.gateway)) {
62
- const ip = ips.filter(ip => ip.isLocal(route.gateway)).shift();
63
- return [route.gateway, ip === null || ip === void 0 ? void 0 : ip.iface];
64
- }
65
- else {
66
- const ip = ips.filter(ip => ip.iface === route.gateway).shift();
67
- return [ip === null || ip === void 0 ? void 0 : ip.ipaddress, route.gateway];
68
- }
73
+ if (!route.gateway)
74
+ return [undefined, undefined];
75
+ const gw = toIP4(route.gateway);
76
+ if (route.gateway.includes('%')) {
77
+ const [gateway, ...iface] = route.gateway.split('%');
78
+ return [gateway, iface.join('%')];
79
+ }
80
+ else if (gw) {
81
+ const ip = ips.filter(ip => ip.includes(route.gateway)).shift();
82
+ return [route.gateway, ip === null || ip === void 0 ? void 0 : ip.iface];
69
83
  }
70
84
  else {
71
- return [undefined, undefined];
85
+ const ip = ips.filter(ip => ip.iface === route.gateway).shift();
86
+ return [ip === null || ip === void 0 ? void 0 : ip.ipaddress, route.gateway];
72
87
  }
73
88
  })();
74
89
  const distance = parseInt(route.distance);
75
90
  const scope = parseInt(route.scope);
76
91
  const target_scope = parseInt(route.target_scope) || undefined;
77
92
  // this does not account for if a device has multiple addresses in the same network
78
- const preferred_source = (_a = ips.filter(ip => gateway ? ip.isLocal(gateway) : false).shift()) === null || _a === void 0 ? void 0 : _a.ipaddress;
79
- const _vrf = route['routing-mark'] || 'main';
93
+ const preferred_source = (_a = ips.filter(ip => gateway ? ip.includes(gateway) : false).shift()) === null || _a === void 0 ? void 0 : _a.ipaddress;
94
+ const _vrf = route['routing-table'] || route['routing-mark'] || 'main';
80
95
  const tunnel = (() => {
81
96
  const tunnel = ifaces.filter(_iface => _iface.name === iface).shift();
82
- if (!tunnel)
97
+ const parent = ips.filter(ip => ip.ipaddress === (tunnel === null || tunnel === void 0 ? void 0 : tunnel.local_address)).shift();
98
+ if (!tunnel || !parent)
83
99
  return undefined;
84
- const root = ips.filter(ip => ip.ipaddress === tunnel.local_address).shift();
85
- if (!root)
86
- return undefined;
87
- return Object.assign(Object.assign({}, tunnel), { root });
100
+ return Object.assign(Object.assign({}, tunnel), { parent });
88
101
  })();
89
- return {
90
- network: {
91
- address,
92
- cidr
93
- },
94
- preferred_source,
95
- gateway,
96
- iface,
97
- tunnel,
102
+ const result = {
103
+ network: address,
104
+ cidr,
98
105
  distance,
99
106
  scope,
100
- target_scope,
101
- vrf: _vrf
107
+ vrf: _vrf,
108
+ includes: (ipaddress) => {
109
+ const temp_address = toIP4(ipaddress);
110
+ const temp_network = toIP4(`${address}/${cidr}`);
111
+ if (!temp_address || !temp_network)
112
+ return false;
113
+ return temp_address.isInSubnet(temp_network);
114
+ }
102
115
  };
116
+ if (preferred_source)
117
+ result.preferred_source = preferred_source;
118
+ if (gateway)
119
+ result.gateway = gateway;
120
+ if (iface)
121
+ result.iface = iface;
122
+ if (tunnel)
123
+ result.tunnel = tunnel;
124
+ if (target_scope)
125
+ result.target_scope = target_scope;
126
+ if (route.comment)
127
+ result.comment = route.comment;
128
+ return result;
103
129
  })
104
130
  .filter(route => route.distance >= min_distance)
105
131
  .filter(route => vrf ? route.vrf === vrf : true);
@@ -107,76 +133,84 @@ class Mikrotik extends ssh_1.default {
107
133
  }
108
134
  /**
109
135
  * Retrieves the IP addresses active on the system
136
+ *
137
+ * @param active_only
110
138
  */
111
139
  get_ip_addresses() {
112
- return __awaiter(this, void 0, void 0, function* () {
113
- const addresses = yield this.terse('/ip address print terse without-paging where !disabled');
140
+ return __awaiter(this, arguments, void 0, function* (active_only = true) {
141
+ const command = `/ip address print terse without-paging${active_only ? ' where !disabled' : ''}`;
142
+ const addresses = yield this.terse(command);
114
143
  return addresses.map(line => {
115
144
  const _address = line.address.split('/');
116
145
  const ipaddress = _address[0];
117
146
  const cidr = parseInt(_address[1]);
118
- return {
147
+ const result = {
119
148
  ipaddress,
120
149
  network: line.network,
121
150
  cidr,
122
151
  iface: line.interface,
123
- isLocal: (ipaddress) => ip_1.default.cidrSubnet(`${line.network}/${cidr}`).contains(ipaddress)
152
+ includes: (ipaddress) => {
153
+ const temp_address = toIP4(ipaddress);
154
+ const temp_network = toIP4(`${line.network}/${cidr}`);
155
+ if (!temp_address || !temp_network)
156
+ return false;
157
+ return temp_address.isInSubnet(temp_network);
158
+ }
124
159
  };
160
+ if (line.comment)
161
+ result.comment = line.comment;
162
+ return result;
125
163
  });
126
164
  });
127
165
  }
128
166
  /**
129
167
  * Retrieves the interfaces active on the system
168
+ *
169
+ * @param active_only
130
170
  */
131
171
  get_interfaces() {
132
- return __awaiter(this, void 0, void 0, function* () {
133
- const ifaces = yield this.terse('/interface print terse without-paging where !disabled');
134
- const gre = yield this.terse('/interface gre print terse without-paging where !disabled');
135
- const ipip = yield this.terse('/interface ipip print terse without-paging where !disabled');
136
- const eoip = yield this.terse('/interface eoip print terse without-paging where !disabled');
172
+ return __awaiter(this, arguments, void 0, function* (active_only = true) {
173
+ const filter = active_only ? ' where !disabled' : '';
174
+ const [ifaces, gre, ipip, eoip] = yield Promise.all([
175
+ this.terse(`/interface print terse without-paging${filter}`),
176
+ this.terse(`/interface gre print terse without-paging${filter}`),
177
+ this.terse(`/interface ipip print terse without-paging${filter}`),
178
+ this.terse(`/interface eoip print terse without-paging${filter}`)
179
+ ]);
180
+ const tunnels = [...gre, ...ipip, ...eoip];
137
181
  return ifaces.map(line => {
138
182
  const _type = (() => {
139
183
  switch (line.type) {
184
+ case 'ether':
185
+ return 'ethernet';
186
+ case 'lo':
187
+ return 'loopback';
140
188
  case 'gre-tunnel':
141
189
  return 'gre';
142
190
  case 'ipip-tunnel':
143
191
  return 'ipip';
144
192
  case 'eoip-tunnel':
145
193
  return 'eoip';
194
+ case 'wg':
195
+ return 'wireguard';
146
196
  default:
147
197
  return line.type;
148
198
  }
149
199
  })();
150
- const local_address = (() => {
151
- switch (line.type) {
152
- case 'gre-tunnel':
153
- return gre.filter(tunnel => tunnel.name === line.name)[0]['local-address'];
154
- case 'ipip':
155
- return ipip.filter(tunnel => tunnel.name === line.name)[0]['local-address'];
156
- case 'eoip':
157
- return eoip.filter(tunnel => tunnel.name === line.name)[0]['local-address'];
158
- default:
159
- return undefined;
160
- }
161
- })();
162
- const remote_address = (() => {
163
- switch (line.type) {
164
- case 'gre-tunnel':
165
- return gre.filter(tunnel => tunnel.name === line.name)[0]['remote-address'];
166
- case 'ipip':
167
- return ipip.filter(tunnel => tunnel.name === line.name)[0]['remote-address'];
168
- case 'eoip':
169
- return eoip.filter(tunnel => tunnel.name === line.name)[0]['remote-address'];
170
- default:
171
- return undefined;
172
- }
173
- })();
174
- return {
200
+ const tunnel = tunnels.filter(tunnel => tunnel.name === line.name).shift();
201
+ const local_address = tunnel === null || tunnel === void 0 ? void 0 : tunnel['local-address'];
202
+ const remote_address = tunnel === null || tunnel === void 0 ? void 0 : tunnel['remote-address'];
203
+ const result = {
175
204
  name: line.name,
176
- type: _type,
177
- local_address,
178
- remote_address
205
+ type: _type
179
206
  };
207
+ if (local_address)
208
+ result.local_address = local_address;
209
+ if (remote_address)
210
+ result.remote_address = remote_address;
211
+ if (line.comment)
212
+ result.comment = line.comment;
213
+ return result;
180
214
  });
181
215
  });
182
216
  }
@@ -189,8 +223,10 @@ class Mikrotik extends ssh_1.default {
189
223
  */
190
224
  get_route_counts() {
191
225
  return __awaiter(this, arguments, void 0, function* (min_distance = 0, vrf) {
192
- const routes = yield this.get_ip_routes(min_distance, vrf);
193
- const ips = yield this.get_ip_addresses();
226
+ const [routes, ips] = yield Promise.all([
227
+ this.get_ip_routes(min_distance, vrf),
228
+ this.get_ip_addresses()
229
+ ]);
194
230
  const results = {};
195
231
  ips.forEach(ip => {
196
232
  results[ip.ipaddress] = {
@@ -203,12 +239,12 @@ class Mikrotik extends ssh_1.default {
203
239
  ip: '',
204
240
  count: 0
205
241
  };
206
- for (const key of Object.keys(results)) {
242
+ Object.keys(results).forEach(key => {
207
243
  if (results[key].count > best.count) {
208
244
  best.count = results[key].count;
209
245
  best.ip = key;
210
246
  }
211
- }
247
+ });
212
248
  if (results[best.ip]) {
213
249
  results[best.ip].active = true;
214
250
  }
@@ -228,13 +264,7 @@ class Mikrotik extends ssh_1.default {
228
264
  * @param options
229
265
  */
230
266
  bandwidth_test(target_1, username_1, password_1) {
231
- return __awaiter(this, arguments, void 0, function* (target, username, password, options = {
232
- duration: 15,
233
- direction: 'both',
234
- protocol: 'udp',
235
- random_data: false,
236
- callback: () => { }
237
- }) {
267
+ return __awaiter(this, arguments, void 0, function* (target, username, password, options = {}) {
238
268
  // eslint-disable-next-line @typescript-eslint/no-this-alias
239
269
  const $ = this;
240
270
  const sleep = (timeout) => __awaiter(this, void 0, void 0, function* () { return new Promise(resolve => setTimeout(resolve, timeout)); });
@@ -370,10 +400,7 @@ class Mikrotik extends ssh_1.default {
370
400
  };
371
401
  if (source)
372
402
  result.source = source;
373
- let command = `/ping address=${target} count=1`;
374
- if (source) {
375
- command += ` src-address=${source}`;
376
- }
403
+ const command = `/ping address=${target} count=1${source ? ` src-address=${source}` : ''}`;
377
404
  try {
378
405
  const response = (yield this.exec(command))
379
406
  .toString()
@@ -402,10 +429,7 @@ class Mikrotik extends ssh_1.default {
402
429
  */
403
430
  traceroute(target, source) {
404
431
  return __awaiter(this, void 0, void 0, function* () {
405
- let command = `/tool traceroute address=${target} count=1`;
406
- if (source) {
407
- command += ` src-address=${source}`;
408
- }
432
+ const command = `/tool traceroute address=${target} count=1${source ? ` src-address=${source}` : ''}`;
409
433
  const response = (yield this.exec(command))
410
434
  .toString()
411
435
  .split('\r\n\r\n')
@@ -420,7 +444,7 @@ class Mikrotik extends ssh_1.default {
420
444
  if (!response)
421
445
  throw new Error(`Could not perform traceroute to ${target}`);
422
446
  const results = [];
423
- const resolve = (ip) => __awaiter(this, void 0, void 0, function* () {
447
+ const resolveDNS = (ip) => __awaiter(this, void 0, void 0, function* () {
424
448
  return new Promise(resolve => {
425
449
  (0, dns_1.reverse)(ip, (error, addresses) => {
426
450
  if (error)
@@ -445,7 +469,7 @@ class Mikrotik extends ssh_1.default {
445
469
  results.push(result);
446
470
  }
447
471
  (yield Promise.all(results.filter(result => result.address)
448
- .map(result => resolve(result.address || ''))))
472
+ .map(result => resolveDNS(result.address || ''))))
449
473
  .forEach(result => {
450
474
  for (let i = 0; i < results.length; i++) {
451
475
  if (result[0] === results[i].address && result[1]) {
@@ -456,6 +480,161 @@ class Mikrotik extends ssh_1.default {
456
480
  return results;
457
481
  });
458
482
  }
483
+ /**
484
+ * Fetches routerboard information from the device
485
+ */
486
+ routerboard() {
487
+ return __awaiter(this, void 0, void 0, function* () {
488
+ const result = yield this.kvs('/system routerboard print');
489
+ return {
490
+ routerboard: result.routerboard === 'yes',
491
+ board_name: result['board-name'],
492
+ model: result.model,
493
+ serial_number: result['serial-number'],
494
+ firmware_type: result['firmware-type'],
495
+ factory_firmware: toVersion(result['factory-firmware']),
496
+ current_firmware: toVersion(result['current-firmware']),
497
+ upgrade_firmware: toVersion(result['upgrade-firmware'])
498
+ };
499
+ });
500
+ }
501
+ /**
502
+ * Fetches the identity of the device
503
+ */
504
+ identity() {
505
+ return __awaiter(this, void 0, void 0, function* () {
506
+ const response = yield this.kvs('/system identity print');
507
+ return response.name;
508
+ });
509
+ }
510
+ /**
511
+ * Fetches the resource information of the device
512
+ */
513
+ resource() {
514
+ return __awaiter(this, void 0, void 0, function* () {
515
+ const response = yield this.kvs('/system resource print');
516
+ const result = {
517
+ uptime: response.uptime,
518
+ version: toVersion(response.version),
519
+ build_time: new Date(`${response['build-time']}Z`),
520
+ factory_sofware: response['factory-software'],
521
+ free_memory: response['free-memory'],
522
+ total_memory: response['total-memory'],
523
+ cpu: response.cpu,
524
+ cpu_count: parseInt(response['cpu-count']) || 0,
525
+ cpu_frequency: parseInt(response['cpu-frequency']) || 0,
526
+ cpu_load: (parseInt(response['cpu-load']) || 0) / 100,
527
+ hdd_space: {
528
+ free: response['free-hdd-space'],
529
+ total: response['total-hdd-space']
530
+ },
531
+ architecture_name: response['architecture-name'],
532
+ board_name: response['board-name'],
533
+ platform: response.platform
534
+ };
535
+ if (response.version.includes('long-term')) {
536
+ result.lts = true;
537
+ }
538
+ else if (response.version.includes('stable')) {
539
+ result.stable = true;
540
+ }
541
+ else if (response.version.includes('testing')) {
542
+ result.testing = true;
543
+ }
544
+ else if (response.version.includes('development')) {
545
+ result.development = true;
546
+ }
547
+ if (response['write-sect-since-reboot'] && response['write-sect-total'] && response['bad-blocks']) {
548
+ result.nvram = {
549
+ write_since_reboot: parseInt(response['write-sect-since-reboot']) || 0,
550
+ write_total: parseInt(response['write-sect-total']) || 0,
551
+ bad_blocks: parseInt(response['bad-blocks']) || 0
552
+ };
553
+ }
554
+ return result;
555
+ });
556
+ }
557
+ /**
558
+ * Fetches the current RouterOS version
559
+ */
560
+ version() {
561
+ return __awaiter(this, void 0, void 0, function* () {
562
+ return (yield this.resource()).version;
563
+ });
564
+ }
565
+ /**
566
+ * Fetches the semantic RouterOS version
567
+ */
568
+ semantic_version() {
569
+ return __awaiter(this, void 0, void 0, function* () {
570
+ const version = yield this.version();
571
+ const [major, minor, patch] = version.split('.')
572
+ .map(elem => parseInt(elem) || 0);
573
+ return {
574
+ major,
575
+ minor,
576
+ patch
577
+ };
578
+ });
579
+ }
580
+ /**
581
+ * Fetches the current health information
582
+ */
583
+ health() {
584
+ return __awaiter(this, void 0, void 0, function* () {
585
+ const { major } = yield this.semantic_version();
586
+ const result = {};
587
+ if (major === 6) {
588
+ const response = yield this.kvs('/system health print');
589
+ result.voltage = parseFloat(response.voltage) || 0;
590
+ result.current = parseFloat(response.current) || 0;
591
+ result.temperature = parseFloat(response.temperature) || 0;
592
+ result.power_consumption = parseFloat(response['power-consumption']) || 0;
593
+ if (response['psu-voltage']) {
594
+ result.psu_voltage = parseFloat(response['psu-voltage']);
595
+ }
596
+ if (response['psu1-voltage']) {
597
+ result.psu1_voltage = parseFloat(response['psu1-voltage']);
598
+ }
599
+ if (response['psu2-voltage']) {
600
+ result.psu2_voltage = parseFloat(response['psu2-voltage']);
601
+ }
602
+ return result;
603
+ }
604
+ else if (major === 7) {
605
+ const response = yield this.terse('/system health print terse');
606
+ for (const { name, value, type } of response) {
607
+ const num = parseFloat(value) || 0;
608
+ switch (name) {
609
+ case 'voltage':
610
+ result.voltage = num;
611
+ break;
612
+ case 'temperature':
613
+ result.temperature = num;
614
+ break;
615
+ case 'power-consumption':
616
+ result.power_consumption = num;
617
+ break;
618
+ case 'current':
619
+ result.current = type === 'A' ? num * 1000 : num;
620
+ break;
621
+ case 'psu-voltage':
622
+ result.psu_voltage = num;
623
+ break;
624
+ case 'psu1-voltage':
625
+ result.psu1_voltage = num;
626
+ break;
627
+ case 'psu2-voltage':
628
+ result.psu2_voltage = num;
629
+ }
630
+ }
631
+ return result;
632
+ }
633
+ else {
634
+ throw new Error('RouterOS version is not supported for this command');
635
+ }
636
+ });
637
+ }
459
638
  /**
460
639
  * Executes a command with expected terse response and parses it accordingly
461
640
  *
@@ -469,15 +648,19 @@ class Mikrotik extends ssh_1.default {
469
648
  .toString()
470
649
  .split('\r\n')
471
650
  .map(line => line.trim())
472
- .filter(line => line.split(/\s+/)
473
- .length !== 0 && line.length !== 0);
651
+ .filter(line => line.split(/\s+/).length !== 0 && line.length !== 0);
474
652
  lines.forEach(line => {
475
653
  const result = {};
476
654
  line.split(/\s+/)
477
- .filter(col => col.includes('='))
655
+ .map(col => col.trim())
478
656
  .forEach(col => {
479
- const [key, value] = col.split('=', 2);
480
- result[key] = value;
657
+ if (col.includes('=')) {
658
+ const [key, ...value] = col.split('=');
659
+ result[key] = value.join('=');
660
+ }
661
+ else {
662
+ result[col] = col;
663
+ }
481
664
  });
482
665
  if (Object.keys(result).length !== 0)
483
666
  results.push(result);
@@ -485,6 +668,28 @@ class Mikrotik extends ssh_1.default {
485
668
  return results;
486
669
  });
487
670
  }
671
+ /**
672
+ * Executes a command that expects the result as a 'table' of key-value pairs separated by a colon (:)
673
+ *
674
+ * @param command
675
+ * @protected
676
+ */
677
+ kvs(command) {
678
+ return __awaiter(this, void 0, void 0, function* () {
679
+ const result = {};
680
+ const lines = (yield this.exec(command))
681
+ .toString()
682
+ .split('\r\n')
683
+ .map(line => line.trim())
684
+ .filter(line => line.length !== 0)
685
+ .map(line => line.split(':')
686
+ .map(col => col.trim()));
687
+ for (const [key, ...value] of lines) {
688
+ result[key] = value.join(':');
689
+ }
690
+ return result;
691
+ });
692
+ }
488
693
  }
489
694
  Mikrotik.cache = new memory_1.default({
490
695
  stdTTL: 15,
package/dist/types.d.ts CHANGED
@@ -38,51 +38,142 @@ export declare namespace BandwidthTest {
38
38
  }
39
39
  export {};
40
40
  }
41
+ export declare namespace CommandResponses {
42
+ type YesNo = 'yes' | 'no';
43
+ interface Comment {
44
+ comment?: string;
45
+ }
46
+ interface Route extends Comment {
47
+ 'dst-address': string;
48
+ gateway: string;
49
+ distance: string;
50
+ scope: string;
51
+ target_scope: string;
52
+ 'routing-table'?: string;
53
+ 'routing-mark'?: string;
54
+ 'bgp-as-path'?: string;
55
+ 'bgp-origin'?: string;
56
+ 'bgp-local-pref'?: string;
57
+ 'received-from'?: string;
58
+ 'check-gateway'?: string;
59
+ 'suppress-hw-offload'?: string;
60
+ 'blackhole'?: string;
61
+ 'immediate-gw'?: string;
62
+ reachable?: string;
63
+ 'gateway-status'?: string;
64
+ }
65
+ interface Address extends Comment {
66
+ address: string;
67
+ network: string;
68
+ interface: string;
69
+ 'actual-interface': string;
70
+ }
71
+ interface Interface extends Comment {
72
+ name: string;
73
+ type: string;
74
+ mtu: string;
75
+ 'default-name'?: string;
76
+ 'actual-mtu': string;
77
+ 'mac-address'?: string;
78
+ 'link-downs'?: string;
79
+ l2mtu?: string;
80
+ }
81
+ interface TunnelInterface extends Omit<Interface, 'type'>, Comment {
82
+ 'local-address': string;
83
+ 'remote-address': string;
84
+ 'clamp-tcp-mss': YesNo;
85
+ 'dont-fragment': YesNo;
86
+ 'allow-fast-path': YesNo;
87
+ }
88
+ interface Routeboard {
89
+ routerboard: YesNo;
90
+ 'board-name': string;
91
+ model: string;
92
+ 'serial-number': string;
93
+ 'firmware-type': string;
94
+ 'factory-firmware': string;
95
+ 'current-firmware': string;
96
+ 'upgrade-firmware': string;
97
+ }
98
+ interface Identity {
99
+ name: string;
100
+ }
101
+ interface Resource {
102
+ uptime: string;
103
+ version: string;
104
+ 'build-time': string;
105
+ 'factory-software': string;
106
+ 'free-memory': string;
107
+ 'total-memory': string;
108
+ cpu: string;
109
+ 'cpu-count': string;
110
+ 'cpu-frequency': string;
111
+ 'cpu-load': string;
112
+ 'free-hdd-space': string;
113
+ 'total-hdd-space': string;
114
+ 'architecture-name': string;
115
+ 'board-name': string;
116
+ platform: string;
117
+ 'write-sect-since-reboot'?: string;
118
+ 'write-sect-total'?: string;
119
+ 'bad-blocks'?: string;
120
+ }
121
+ interface Health {
122
+ voltage: string;
123
+ current: string;
124
+ temperature: string;
125
+ 'power-consumption': string;
126
+ 'psu-voltage'?: string;
127
+ 'psu1-voltage'?: string;
128
+ 'psu2-voltage'?: string;
129
+ }
130
+ }
41
131
  export declare namespace Response {
42
- interface Address {
132
+ interface includesAddress {
133
+ includes(ipaddress: string): boolean;
134
+ }
135
+ interface Comment {
136
+ comment?: string;
137
+ }
138
+ export interface Address extends includesAddress, Comment {
43
139
  ipaddress: string;
44
140
  network: string;
45
141
  cidr: number;
46
142
  iface: string;
47
- isLocal(ipaddress: string): boolean;
48
143
  }
49
- interface Interface {
144
+ export interface Interface extends Comment {
50
145
  name: string;
51
146
  type: string;
52
147
  local_address?: string;
53
148
  remote_address?: string;
54
149
  }
55
- interface Tunnel extends Interface {
56
- root: Address;
150
+ export interface Tunnel extends Interface {
151
+ parent: Address;
57
152
  }
58
- interface Route {
59
- network: {
60
- address: string;
61
- cidr: number;
62
- };
153
+ export interface Route extends includesAddress, Comment {
154
+ network: string;
155
+ cidr: number;
156
+ distance: number;
157
+ scope: number;
158
+ vrf: string;
63
159
  preferred_source?: string;
64
160
  gateway?: string;
65
161
  iface?: string;
66
162
  tunnel?: Tunnel;
67
- distance: number;
68
- scope: number;
69
163
  target_scope?: number;
70
- vrf: string;
71
164
  }
72
- interface RouteCount {
165
+ export interface RouteCount {
73
166
  interface: string;
74
167
  count: number;
75
168
  active: boolean;
76
169
  }
77
- interface Ping {
78
- source?: string;
170
+ export interface Ping {
79
171
  target: string;
80
172
  latency: number;
173
+ source?: string;
81
174
  }
82
- interface Traceroute {
175
+ export interface Traceroute {
83
176
  hop: number;
84
- address?: string;
85
- hostname?: string;
86
177
  loss: number;
87
178
  sent: number;
88
179
  last: number;
@@ -90,5 +181,55 @@ export declare namespace Response {
90
181
  best: number;
91
182
  worst: number;
92
183
  timeout: boolean;
184
+ address?: string;
185
+ hostname?: string;
186
+ }
187
+ export interface Routerboard {
188
+ routerboard: boolean;
189
+ board_name: string;
190
+ model: string;
191
+ serial_number: string;
192
+ firmware_type: string;
193
+ factory_firmware: string;
194
+ current_firmware: string;
195
+ upgrade_firmware: string;
93
196
  }
197
+ export interface Resource {
198
+ uptime: string;
199
+ version: string;
200
+ build_time: Date;
201
+ factory_sofware: string;
202
+ free_memory: string;
203
+ total_memory: string;
204
+ cpu: string;
205
+ cpu_count: number;
206
+ cpu_frequency: number;
207
+ cpu_load: number;
208
+ hdd_space: {
209
+ free: string;
210
+ total: string;
211
+ };
212
+ architecture_name: string;
213
+ board_name: string;
214
+ platform: string;
215
+ lts?: boolean;
216
+ stable?: boolean;
217
+ testing?: boolean;
218
+ development?: boolean;
219
+ nvram?: {
220
+ write_since_reboot: number;
221
+ write_total: number;
222
+ bad_blocks: number;
223
+ };
224
+ }
225
+ export interface Health {
226
+ voltage: number;
227
+ current: number;
228
+ temperature: number;
229
+ power_consumption: number;
230
+ psu_voltage?: number;
231
+ psu1_voltage?: number;
232
+ psu2_voltage?: number;
233
+ }
234
+ export {};
94
235
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gibme/mikrotik",
3
- "version": "1.1.2",
3
+ "version": "2.0.0",
4
4
  "description": "A simple mikrotik helper/wrapper",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -57,7 +57,8 @@
57
57
  "dependencies": {
58
58
  "@gibme/cache": "^1.1.5",
59
59
  "@gibme/ssh": "^1.0.2",
60
- "@types/ip": "^1.1.3",
61
- "ip": "^2.0.1"
60
+ "@types/jsbn": "^1.2.33",
61
+ "ip-address": "^9.0.5",
62
+ "semver": "^7.6.2"
62
63
  }
63
64
  }