@gibme/mikrotik 1.1.1 → 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,32 +33,52 @@ 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] = (() => {
73
+ if (!route.gateway)
74
+ return [undefined, undefined];
75
+ const gw = toIP4(route.gateway);
57
76
  if (route.gateway.includes('%')) {
58
- return route.gateway.split('%', 2);
77
+ const [gateway, ...iface] = route.gateway.split('%');
78
+ return [gateway, iface.join('%')];
59
79
  }
60
- else if (ip_1.default.isV4Format(route.gateway)) {
61
- const ip = ips.filter(ip => ip.isLocal(route.gateway)).shift();
80
+ else if (gw) {
81
+ const ip = ips.filter(ip => ip.includes(route.gateway)).shift();
62
82
  return [route.gateway, ip === null || ip === void 0 ? void 0 : ip.iface];
63
83
  }
64
84
  else {
@@ -70,31 +90,42 @@ class Mikrotik extends ssh_1.default {
70
90
  const scope = parseInt(route.scope);
71
91
  const target_scope = parseInt(route.target_scope) || undefined;
72
92
  // this does not account for if a device has multiple addresses in the same network
73
- const preferred_source = (_a = ips.filter(ip => gateway ? ip.isLocal(gateway) : false).shift()) === null || _a === void 0 ? void 0 : _a.ipaddress;
74
- 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';
75
95
  const tunnel = (() => {
76
96
  const tunnel = ifaces.filter(_iface => _iface.name === iface).shift();
77
- 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)
78
99
  return undefined;
79
- const root = ips.filter(ip => ip.ipaddress === tunnel.local_address).shift();
80
- if (!root)
81
- return undefined;
82
- return Object.assign(Object.assign({}, tunnel), { root });
100
+ return Object.assign(Object.assign({}, tunnel), { parent });
83
101
  })();
84
- return {
85
- network: {
86
- address,
87
- cidr
88
- },
89
- preferred_source,
90
- gateway,
91
- iface,
92
- tunnel,
102
+ const result = {
103
+ network: address,
104
+ cidr,
93
105
  distance,
94
106
  scope,
95
- target_scope,
96
- 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
+ }
97
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;
98
129
  })
99
130
  .filter(route => route.distance >= min_distance)
100
131
  .filter(route => vrf ? route.vrf === vrf : true);
@@ -102,76 +133,84 @@ class Mikrotik extends ssh_1.default {
102
133
  }
103
134
  /**
104
135
  * Retrieves the IP addresses active on the system
136
+ *
137
+ * @param active_only
105
138
  */
106
139
  get_ip_addresses() {
107
- return __awaiter(this, void 0, void 0, function* () {
108
- 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);
109
143
  return addresses.map(line => {
110
144
  const _address = line.address.split('/');
111
145
  const ipaddress = _address[0];
112
146
  const cidr = parseInt(_address[1]);
113
- return {
147
+ const result = {
114
148
  ipaddress,
115
149
  network: line.network,
116
150
  cidr,
117
151
  iface: line.interface,
118
- 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
+ }
119
159
  };
160
+ if (line.comment)
161
+ result.comment = line.comment;
162
+ return result;
120
163
  });
121
164
  });
122
165
  }
123
166
  /**
124
167
  * Retrieves the interfaces active on the system
168
+ *
169
+ * @param active_only
125
170
  */
126
171
  get_interfaces() {
127
- return __awaiter(this, void 0, void 0, function* () {
128
- const ifaces = yield this.terse('/interface print terse without-paging where !disabled');
129
- const gre = yield this.terse('/interface gre print terse without-paging where !disabled');
130
- const ipip = yield this.terse('/interface ipip print terse without-paging where !disabled');
131
- 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];
132
181
  return ifaces.map(line => {
133
182
  const _type = (() => {
134
183
  switch (line.type) {
184
+ case 'ether':
185
+ return 'ethernet';
186
+ case 'lo':
187
+ return 'loopback';
135
188
  case 'gre-tunnel':
136
189
  return 'gre';
137
190
  case 'ipip-tunnel':
138
191
  return 'ipip';
139
192
  case 'eoip-tunnel':
140
193
  return 'eoip';
194
+ case 'wg':
195
+ return 'wireguard';
141
196
  default:
142
197
  return line.type;
143
198
  }
144
199
  })();
145
- const local_address = (() => {
146
- switch (line.type) {
147
- case 'gre-tunnel':
148
- return gre.filter(tunnel => tunnel.name === line.name)[0]['local-address'];
149
- case 'ipip':
150
- return ipip.filter(tunnel => tunnel.name === line.name)[0]['local-address'];
151
- case 'eoip':
152
- return eoip.filter(tunnel => tunnel.name === line.name)[0]['local-address'];
153
- default:
154
- return undefined;
155
- }
156
- })();
157
- const remote_address = (() => {
158
- switch (line.type) {
159
- case 'gre-tunnel':
160
- return gre.filter(tunnel => tunnel.name === line.name)[0]['remote-address'];
161
- case 'ipip':
162
- return ipip.filter(tunnel => tunnel.name === line.name)[0]['remote-address'];
163
- case 'eoip':
164
- return eoip.filter(tunnel => tunnel.name === line.name)[0]['remote-address'];
165
- default:
166
- return undefined;
167
- }
168
- })();
169
- 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 = {
170
204
  name: line.name,
171
- type: _type,
172
- local_address,
173
- remote_address
205
+ type: _type
174
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;
175
214
  });
176
215
  });
177
216
  }
@@ -184,8 +223,10 @@ class Mikrotik extends ssh_1.default {
184
223
  */
185
224
  get_route_counts() {
186
225
  return __awaiter(this, arguments, void 0, function* (min_distance = 0, vrf) {
187
- const routes = yield this.get_ip_routes(min_distance, vrf);
188
- 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
+ ]);
189
230
  const results = {};
190
231
  ips.forEach(ip => {
191
232
  results[ip.ipaddress] = {
@@ -198,12 +239,12 @@ class Mikrotik extends ssh_1.default {
198
239
  ip: '',
199
240
  count: 0
200
241
  };
201
- for (const key of Object.keys(results)) {
242
+ Object.keys(results).forEach(key => {
202
243
  if (results[key].count > best.count) {
203
244
  best.count = results[key].count;
204
245
  best.ip = key;
205
246
  }
206
- }
247
+ });
207
248
  if (results[best.ip]) {
208
249
  results[best.ip].active = true;
209
250
  }
@@ -223,13 +264,7 @@ class Mikrotik extends ssh_1.default {
223
264
  * @param options
224
265
  */
225
266
  bandwidth_test(target_1, username_1, password_1) {
226
- return __awaiter(this, arguments, void 0, function* (target, username, password, options = {
227
- duration: 15,
228
- direction: 'both',
229
- protocol: 'udp',
230
- random_data: false,
231
- callback: () => { }
232
- }) {
267
+ return __awaiter(this, arguments, void 0, function* (target, username, password, options = {}) {
233
268
  // eslint-disable-next-line @typescript-eslint/no-this-alias
234
269
  const $ = this;
235
270
  const sleep = (timeout) => __awaiter(this, void 0, void 0, function* () { return new Promise(resolve => setTimeout(resolve, timeout)); });
@@ -365,10 +400,7 @@ class Mikrotik extends ssh_1.default {
365
400
  };
366
401
  if (source)
367
402
  result.source = source;
368
- let command = `/ping address=${target} count=1`;
369
- if (source) {
370
- command += ` src-address=${source}`;
371
- }
403
+ const command = `/ping address=${target} count=1${source ? ` src-address=${source}` : ''}`;
372
404
  try {
373
405
  const response = (yield this.exec(command))
374
406
  .toString()
@@ -397,10 +429,7 @@ class Mikrotik extends ssh_1.default {
397
429
  */
398
430
  traceroute(target, source) {
399
431
  return __awaiter(this, void 0, void 0, function* () {
400
- let command = `/tool traceroute address=${target} count=1`;
401
- if (source) {
402
- command += ` src-address=${source}`;
403
- }
432
+ const command = `/tool traceroute address=${target} count=1${source ? ` src-address=${source}` : ''}`;
404
433
  const response = (yield this.exec(command))
405
434
  .toString()
406
435
  .split('\r\n\r\n')
@@ -415,7 +444,7 @@ class Mikrotik extends ssh_1.default {
415
444
  if (!response)
416
445
  throw new Error(`Could not perform traceroute to ${target}`);
417
446
  const results = [];
418
- const resolve = (ip) => __awaiter(this, void 0, void 0, function* () {
447
+ const resolveDNS = (ip) => __awaiter(this, void 0, void 0, function* () {
419
448
  return new Promise(resolve => {
420
449
  (0, dns_1.reverse)(ip, (error, addresses) => {
421
450
  if (error)
@@ -440,7 +469,7 @@ class Mikrotik extends ssh_1.default {
440
469
  results.push(result);
441
470
  }
442
471
  (yield Promise.all(results.filter(result => result.address)
443
- .map(result => resolve(result.address || ''))))
472
+ .map(result => resolveDNS(result.address || ''))))
444
473
  .forEach(result => {
445
474
  for (let i = 0; i < results.length; i++) {
446
475
  if (result[0] === results[i].address && result[1]) {
@@ -451,6 +480,161 @@ class Mikrotik extends ssh_1.default {
451
480
  return results;
452
481
  });
453
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
+ }
454
638
  /**
455
639
  * Executes a command with expected terse response and parses it accordingly
456
640
  *
@@ -464,15 +648,19 @@ class Mikrotik extends ssh_1.default {
464
648
  .toString()
465
649
  .split('\r\n')
466
650
  .map(line => line.trim())
467
- .filter(line => line.split(/\s+/)
468
- .length !== 0 && line.length !== 0);
651
+ .filter(line => line.split(/\s+/).length !== 0 && line.length !== 0);
469
652
  lines.forEach(line => {
470
653
  const result = {};
471
654
  line.split(/\s+/)
472
- .filter(col => col.includes('='))
655
+ .map(col => col.trim())
473
656
  .forEach(col => {
474
- const [key, value] = col.split('=', 2);
475
- 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
+ }
476
664
  });
477
665
  if (Object.keys(result).length !== 0)
478
666
  results.push(result);
@@ -480,6 +668,28 @@ class Mikrotik extends ssh_1.default {
480
668
  return results;
481
669
  });
482
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
+ }
483
693
  }
484
694
  Mikrotik.cache = new memory_1.default({
485
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.1",
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",
@@ -56,8 +56,9 @@
56
56
  },
57
57
  "dependencies": {
58
58
  "@gibme/cache": "^1.1.5",
59
- "@gibme/ssh": "^1.0.1",
60
- "@types/ip": "^1.1.3",
61
- "ip": "^2.0.1"
59
+ "@gibme/ssh": "^1.0.2",
60
+ "@types/jsbn": "^1.2.33",
61
+ "ip-address": "^9.0.5",
62
+ "semver": "^7.6.2"
62
63
  }
63
64
  }