@gibme/mikrotik 1.1.2 → 2.0.1

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)
83
- return undefined;
84
- const root = ips.filter(ip => ip.ipaddress === tunnel.local_address).shift();
85
- if (!root)
97
+ const parent = ips.filter(ip => ip.ipaddress === (tunnel === null || tunnel === void 0 ? void 0 : tunnel.local_address)).shift();
98
+ if (!tunnel || !parent)
86
99
  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)); });
@@ -345,14 +375,27 @@ class Mikrotik extends ssh_1.default {
345
375
  });
346
376
  }
347
377
  this.on('stream', handleStream);
378
+ const cleanup = () => __awaiter(this, void 0, void 0, function* () {
379
+ this.off('stream', handleStream);
380
+ yield Mikrotik.cache.del(target);
381
+ });
348
382
  yield Mikrotik.cache.set(target, target, options.duration); // set our mutex
349
- yield this.stream(`/tool bandwidth-test protocol=${options.protocol} ` +
383
+ const cancel = yield this.stream(`/tool bandwidth-test protocol=${options.protocol} ` +
350
384
  `user=${username} password=${password} ` +
351
385
  `duration=${options.duration}s direction=${options.direction} ` +
352
386
  `address=${target} random-data=${options.random_data ? 'yes' : 'no'} interval=1s`, {
353
387
  separator: '\r\n\r\n'
354
388
  });
355
- yield Mikrotik.cache.del(target); // release our mutex
389
+ this.once('stream_complete', () => __awaiter(this, void 0, void 0, function* () {
390
+ yield cleanup();
391
+ }));
392
+ this.once('stream_cancelled', () => __awaiter(this, void 0, void 0, function* () {
393
+ yield cleanup();
394
+ return reject(new Error('Bandwidth Test Cancelled'));
395
+ }));
396
+ if (options.timeout) {
397
+ setTimeout(cancel, options.timeout);
398
+ }
356
399
  }));
357
400
  });
358
401
  }
@@ -370,10 +413,7 @@ class Mikrotik extends ssh_1.default {
370
413
  };
371
414
  if (source)
372
415
  result.source = source;
373
- let command = `/ping address=${target} count=1`;
374
- if (source) {
375
- command += ` src-address=${source}`;
376
- }
416
+ const command = `/ping address=${target} count=1${source ? ` src-address=${source}` : ''}`;
377
417
  try {
378
418
  const response = (yield this.exec(command))
379
419
  .toString()
@@ -402,10 +442,7 @@ class Mikrotik extends ssh_1.default {
402
442
  */
403
443
  traceroute(target, source) {
404
444
  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
- }
445
+ const command = `/tool traceroute address=${target} count=1${source ? ` src-address=${source}` : ''}`;
409
446
  const response = (yield this.exec(command))
410
447
  .toString()
411
448
  .split('\r\n\r\n')
@@ -420,7 +457,7 @@ class Mikrotik extends ssh_1.default {
420
457
  if (!response)
421
458
  throw new Error(`Could not perform traceroute to ${target}`);
422
459
  const results = [];
423
- const resolve = (ip) => __awaiter(this, void 0, void 0, function* () {
460
+ const resolveDNS = (ip) => __awaiter(this, void 0, void 0, function* () {
424
461
  return new Promise(resolve => {
425
462
  (0, dns_1.reverse)(ip, (error, addresses) => {
426
463
  if (error)
@@ -445,7 +482,7 @@ class Mikrotik extends ssh_1.default {
445
482
  results.push(result);
446
483
  }
447
484
  (yield Promise.all(results.filter(result => result.address)
448
- .map(result => resolve(result.address || ''))))
485
+ .map(result => resolveDNS(result.address || ''))))
449
486
  .forEach(result => {
450
487
  for (let i = 0; i < results.length; i++) {
451
488
  if (result[0] === results[i].address && result[1]) {
@@ -456,6 +493,161 @@ class Mikrotik extends ssh_1.default {
456
493
  return results;
457
494
  });
458
495
  }
496
+ /**
497
+ * Fetches routerboard information from the device
498
+ */
499
+ routerboard() {
500
+ return __awaiter(this, void 0, void 0, function* () {
501
+ const result = yield this.kvs('/system routerboard print');
502
+ return {
503
+ routerboard: result.routerboard === 'yes',
504
+ board_name: result['board-name'],
505
+ model: result.model,
506
+ serial_number: result['serial-number'],
507
+ firmware_type: result['firmware-type'],
508
+ factory_firmware: toVersion(result['factory-firmware']),
509
+ current_firmware: toVersion(result['current-firmware']),
510
+ upgrade_firmware: toVersion(result['upgrade-firmware'])
511
+ };
512
+ });
513
+ }
514
+ /**
515
+ * Fetches the identity of the device
516
+ */
517
+ identity() {
518
+ return __awaiter(this, void 0, void 0, function* () {
519
+ const response = yield this.kvs('/system identity print');
520
+ return response.name;
521
+ });
522
+ }
523
+ /**
524
+ * Fetches the resource information of the device
525
+ */
526
+ resource() {
527
+ return __awaiter(this, void 0, void 0, function* () {
528
+ const response = yield this.kvs('/system resource print');
529
+ const result = {
530
+ uptime: response.uptime,
531
+ version: toVersion(response.version),
532
+ build_time: new Date(`${response['build-time']}Z`),
533
+ factory_sofware: response['factory-software'],
534
+ free_memory: response['free-memory'],
535
+ total_memory: response['total-memory'],
536
+ cpu: response.cpu,
537
+ cpu_count: parseInt(response['cpu-count']) || 0,
538
+ cpu_frequency: parseInt(response['cpu-frequency']) || 0,
539
+ cpu_load: (parseInt(response['cpu-load']) || 0) / 100,
540
+ hdd_space: {
541
+ free: response['free-hdd-space'],
542
+ total: response['total-hdd-space']
543
+ },
544
+ architecture_name: response['architecture-name'],
545
+ board_name: response['board-name'],
546
+ platform: response.platform
547
+ };
548
+ if (response.version.includes('long-term')) {
549
+ result.lts = true;
550
+ }
551
+ else if (response.version.includes('stable')) {
552
+ result.stable = true;
553
+ }
554
+ else if (response.version.includes('testing')) {
555
+ result.testing = true;
556
+ }
557
+ else if (response.version.includes('development')) {
558
+ result.development = true;
559
+ }
560
+ if (response['write-sect-since-reboot'] && response['write-sect-total'] && response['bad-blocks']) {
561
+ result.nvram = {
562
+ write_since_reboot: parseInt(response['write-sect-since-reboot']) || 0,
563
+ write_total: parseInt(response['write-sect-total']) || 0,
564
+ bad_blocks: parseInt(response['bad-blocks']) || 0
565
+ };
566
+ }
567
+ return result;
568
+ });
569
+ }
570
+ /**
571
+ * Fetches the current RouterOS version
572
+ */
573
+ version() {
574
+ return __awaiter(this, void 0, void 0, function* () {
575
+ return (yield this.resource()).version;
576
+ });
577
+ }
578
+ /**
579
+ * Fetches the semantic RouterOS version
580
+ */
581
+ semantic_version() {
582
+ return __awaiter(this, void 0, void 0, function* () {
583
+ const version = yield this.version();
584
+ const [major, minor, patch] = version.split('.')
585
+ .map(elem => parseInt(elem) || 0);
586
+ return {
587
+ major,
588
+ minor,
589
+ patch
590
+ };
591
+ });
592
+ }
593
+ /**
594
+ * Fetches the current health information
595
+ */
596
+ health() {
597
+ return __awaiter(this, void 0, void 0, function* () {
598
+ const { major } = yield this.semantic_version();
599
+ const result = {};
600
+ if (major === 6) {
601
+ const response = yield this.kvs('/system health print');
602
+ result.voltage = parseFloat(response.voltage) || 0;
603
+ result.current = parseFloat(response.current) || 0;
604
+ result.temperature = parseFloat(response.temperature) || 0;
605
+ result.power_consumption = parseFloat(response['power-consumption']) || 0;
606
+ if (response['psu-voltage']) {
607
+ result.psu_voltage = parseFloat(response['psu-voltage']);
608
+ }
609
+ if (response['psu1-voltage']) {
610
+ result.psu1_voltage = parseFloat(response['psu1-voltage']);
611
+ }
612
+ if (response['psu2-voltage']) {
613
+ result.psu2_voltage = parseFloat(response['psu2-voltage']);
614
+ }
615
+ return result;
616
+ }
617
+ else if (major === 7) {
618
+ const response = yield this.terse('/system health print terse');
619
+ for (const { name, value, type } of response) {
620
+ const num = parseFloat(value) || 0;
621
+ switch (name) {
622
+ case 'voltage':
623
+ result.voltage = num;
624
+ break;
625
+ case 'temperature':
626
+ result.temperature = num;
627
+ break;
628
+ case 'power-consumption':
629
+ result.power_consumption = num;
630
+ break;
631
+ case 'current':
632
+ result.current = type === 'A' ? num * 1000 : num;
633
+ break;
634
+ case 'psu-voltage':
635
+ result.psu_voltage = num;
636
+ break;
637
+ case 'psu1-voltage':
638
+ result.psu1_voltage = num;
639
+ break;
640
+ case 'psu2-voltage':
641
+ result.psu2_voltage = num;
642
+ }
643
+ }
644
+ return result;
645
+ }
646
+ else {
647
+ throw new Error('RouterOS version is not supported for this command');
648
+ }
649
+ });
650
+ }
459
651
  /**
460
652
  * Executes a command with expected terse response and parses it accordingly
461
653
  *
@@ -469,15 +661,19 @@ class Mikrotik extends ssh_1.default {
469
661
  .toString()
470
662
  .split('\r\n')
471
663
  .map(line => line.trim())
472
- .filter(line => line.split(/\s+/)
473
- .length !== 0 && line.length !== 0);
664
+ .filter(line => line.split(/\s+/).length !== 0 && line.length !== 0);
474
665
  lines.forEach(line => {
475
666
  const result = {};
476
667
  line.split(/\s+/)
477
- .filter(col => col.includes('='))
668
+ .map(col => col.trim())
478
669
  .forEach(col => {
479
- const [key, value] = col.split('=', 2);
480
- result[key] = value;
670
+ if (col.includes('=')) {
671
+ const [key, ...value] = col.split('=');
672
+ result[key] = value.join('=');
673
+ }
674
+ else {
675
+ result[col] = col;
676
+ }
481
677
  });
482
678
  if (Object.keys(result).length !== 0)
483
679
  results.push(result);
@@ -485,6 +681,28 @@ class Mikrotik extends ssh_1.default {
485
681
  return results;
486
682
  });
487
683
  }
684
+ /**
685
+ * Executes a command that expects the result as a 'table' of key-value pairs separated by a colon (:)
686
+ *
687
+ * @param command
688
+ * @protected
689
+ */
690
+ kvs(command) {
691
+ return __awaiter(this, void 0, void 0, function* () {
692
+ const result = {};
693
+ const lines = (yield this.exec(command))
694
+ .toString()
695
+ .split('\r\n')
696
+ .map(line => line.trim())
697
+ .filter(line => line.length !== 0)
698
+ .map(line => line.split(':')
699
+ .map(col => col.trim()));
700
+ for (const [key, ...value] of lines) {
701
+ result[key] = value.join(':');
702
+ }
703
+ return result;
704
+ });
705
+ }
488
706
  }
489
707
  Mikrotik.cache = new memory_1.default({
490
708
  stdTTL: 15,
package/dist/types.d.ts CHANGED
@@ -35,54 +35,146 @@ export declare namespace BandwidthTest {
35
35
  protocol: Protocol;
36
36
  random_data: boolean;
37
37
  callback: (frame: Update) => void;
38
+ timeout: number;
38
39
  }
39
40
  export {};
40
41
  }
42
+ export declare namespace CommandResponses {
43
+ type YesNo = 'yes' | 'no';
44
+ interface Comment {
45
+ comment?: string;
46
+ }
47
+ interface Route extends Comment {
48
+ 'dst-address': string;
49
+ gateway: string;
50
+ distance: string;
51
+ scope: string;
52
+ target_scope: string;
53
+ 'routing-table'?: string;
54
+ 'routing-mark'?: string;
55
+ 'bgp-as-path'?: string;
56
+ 'bgp-origin'?: string;
57
+ 'bgp-local-pref'?: string;
58
+ 'received-from'?: string;
59
+ 'check-gateway'?: string;
60
+ 'suppress-hw-offload'?: string;
61
+ 'blackhole'?: string;
62
+ 'immediate-gw'?: string;
63
+ reachable?: string;
64
+ 'gateway-status'?: string;
65
+ }
66
+ interface Address extends Comment {
67
+ address: string;
68
+ network: string;
69
+ interface: string;
70
+ 'actual-interface': string;
71
+ }
72
+ interface Interface extends Comment {
73
+ name: string;
74
+ type: string;
75
+ mtu: string;
76
+ 'default-name'?: string;
77
+ 'actual-mtu': string;
78
+ 'mac-address'?: string;
79
+ 'link-downs'?: string;
80
+ l2mtu?: string;
81
+ }
82
+ interface TunnelInterface extends Omit<Interface, 'type'>, Comment {
83
+ 'local-address': string;
84
+ 'remote-address': string;
85
+ 'clamp-tcp-mss': YesNo;
86
+ 'dont-fragment': YesNo;
87
+ 'allow-fast-path': YesNo;
88
+ }
89
+ interface Routeboard {
90
+ routerboard: YesNo;
91
+ 'board-name': string;
92
+ model: string;
93
+ 'serial-number': string;
94
+ 'firmware-type': string;
95
+ 'factory-firmware': string;
96
+ 'current-firmware': string;
97
+ 'upgrade-firmware': string;
98
+ }
99
+ interface Identity {
100
+ name: string;
101
+ }
102
+ interface Resource {
103
+ uptime: string;
104
+ version: string;
105
+ 'build-time': string;
106
+ 'factory-software': string;
107
+ 'free-memory': string;
108
+ 'total-memory': string;
109
+ cpu: string;
110
+ 'cpu-count': string;
111
+ 'cpu-frequency': string;
112
+ 'cpu-load': string;
113
+ 'free-hdd-space': string;
114
+ 'total-hdd-space': string;
115
+ 'architecture-name': string;
116
+ 'board-name': string;
117
+ platform: string;
118
+ 'write-sect-since-reboot'?: string;
119
+ 'write-sect-total'?: string;
120
+ 'bad-blocks'?: string;
121
+ }
122
+ interface Health {
123
+ voltage: string;
124
+ current: string;
125
+ temperature: string;
126
+ 'power-consumption': string;
127
+ 'psu-voltage'?: string;
128
+ 'psu1-voltage'?: string;
129
+ 'psu2-voltage'?: string;
130
+ }
131
+ }
41
132
  export declare namespace Response {
42
- interface Address {
133
+ interface includesAddress {
134
+ includes(ipaddress: string): boolean;
135
+ }
136
+ interface Comment {
137
+ comment?: string;
138
+ }
139
+ export interface Address extends includesAddress, Comment {
43
140
  ipaddress: string;
44
141
  network: string;
45
142
  cidr: number;
46
143
  iface: string;
47
- isLocal(ipaddress: string): boolean;
48
144
  }
49
- interface Interface {
145
+ export interface Interface extends Comment {
50
146
  name: string;
51
147
  type: string;
52
148
  local_address?: string;
53
149
  remote_address?: string;
54
150
  }
55
- interface Tunnel extends Interface {
56
- root: Address;
151
+ export interface Tunnel extends Interface {
152
+ parent: Address;
57
153
  }
58
- interface Route {
59
- network: {
60
- address: string;
61
- cidr: number;
62
- };
154
+ export interface Route extends includesAddress, Comment {
155
+ network: string;
156
+ cidr: number;
157
+ distance: number;
158
+ scope: number;
159
+ vrf: string;
63
160
  preferred_source?: string;
64
161
  gateway?: string;
65
162
  iface?: string;
66
163
  tunnel?: Tunnel;
67
- distance: number;
68
- scope: number;
69
164
  target_scope?: number;
70
- vrf: string;
71
165
  }
72
- interface RouteCount {
166
+ export interface RouteCount {
73
167
  interface: string;
74
168
  count: number;
75
169
  active: boolean;
76
170
  }
77
- interface Ping {
78
- source?: string;
171
+ export interface Ping {
79
172
  target: string;
80
173
  latency: number;
174
+ source?: string;
81
175
  }
82
- interface Traceroute {
176
+ export interface Traceroute {
83
177
  hop: number;
84
- address?: string;
85
- hostname?: string;
86
178
  loss: number;
87
179
  sent: number;
88
180
  last: number;
@@ -90,5 +182,55 @@ export declare namespace Response {
90
182
  best: number;
91
183
  worst: number;
92
184
  timeout: boolean;
185
+ address?: string;
186
+ hostname?: string;
187
+ }
188
+ export interface Routerboard {
189
+ routerboard: boolean;
190
+ board_name: string;
191
+ model: string;
192
+ serial_number: string;
193
+ firmware_type: string;
194
+ factory_firmware: string;
195
+ current_firmware: string;
196
+ upgrade_firmware: string;
93
197
  }
198
+ export interface Resource {
199
+ uptime: string;
200
+ version: string;
201
+ build_time: Date;
202
+ factory_sofware: string;
203
+ free_memory: string;
204
+ total_memory: string;
205
+ cpu: string;
206
+ cpu_count: number;
207
+ cpu_frequency: number;
208
+ cpu_load: number;
209
+ hdd_space: {
210
+ free: string;
211
+ total: string;
212
+ };
213
+ architecture_name: string;
214
+ board_name: string;
215
+ platform: string;
216
+ lts?: boolean;
217
+ stable?: boolean;
218
+ testing?: boolean;
219
+ development?: boolean;
220
+ nvram?: {
221
+ write_since_reboot: number;
222
+ write_total: number;
223
+ bad_blocks: number;
224
+ };
225
+ }
226
+ export interface Health {
227
+ voltage: number;
228
+ current: number;
229
+ temperature: number;
230
+ power_consumption: number;
231
+ psu_voltage?: number;
232
+ psu1_voltage?: number;
233
+ psu2_voltage?: number;
234
+ }
235
+ export {};
94
236
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gibme/mikrotik",
3
- "version": "1.1.2",
3
+ "version": "2.0.1",
4
4
  "description": "A simple mikrotik helper/wrapper",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -56,8 +56,9 @@
56
56
  },
57
57
  "dependencies": {
58
58
  "@gibme/cache": "^1.1.5",
59
- "@gibme/ssh": "^1.0.2",
60
- "@types/ip": "^1.1.3",
61
- "ip": "^2.0.1"
59
+ "@gibme/ssh": "^1.0.3",
60
+ "@types/jsbn": "^1.2.33",
61
+ "ip-address": "^9.0.5",
62
+ "semver": "^7.6.2"
62
63
  }
63
64
  }