dcp-worker 3.2.30-1 → 3.2.30-11

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/bin/dcp-worker CHANGED
@@ -4,25 +4,26 @@
4
4
  * Standalone NodeJS DCP Worker
5
5
  *
6
6
  * @author Ryan Rossiter, ryan@kingsds.network
7
+ * Paul, paul@distributive.network
8
+ * Wes Garland, wes@distributive.network
9
+ *
7
10
  * @date April 2020
11
+ * April-May 2023
12
+ * May-June 2023
8
13
  */
9
14
  'use strict';
10
15
 
16
+ var worker;
17
+
11
18
  const process = require('process');
12
19
  const fs = require('fs');
20
+ const path = require('path');
13
21
  const crypto = require('crypto');
14
22
  const chalk = require('chalk');
23
+ const telnetd = require('../lib/remote-console');
24
+ const utils = require('../lib/utils');
15
25
 
16
26
  const configName = process.env.DCP_CONFIG || '../etc/dcp-worker-config';
17
- var worker, dcpConfig, debugging;
18
-
19
- const debug = (...args) => {
20
- if (!debugging)
21
- debugging = require('dcp/internal/debugging').scope('dcp-worker');
22
- if (debugging())
23
- console.debug('dcp-worker:', ...args);
24
- };
25
-
26
27
  const EXIT_UNHANDLED = 5;
27
28
 
28
29
  /* Setup the telnet REPL up early to ensure early-failure log messages are captured */
@@ -34,13 +35,15 @@ const replHelpers = {
34
35
  },
35
36
  commands: {
36
37
  report: printReport,
37
- kill: process.exit,
38
+ kill: processExit,
38
39
  die: () => worker && worker.stop()
39
40
  },
40
41
  };
41
- require('../lib/remote-console').init(replHelpers);
42
+ telnetd.init(replHelpers);
42
43
 
43
- /* Initialize dcp-client with local config defaults and run the main function. DCP_CONFIG_COOKIE becomes dcpConfig.cookie. */
44
+ /* Initialize dcp-client with local config defaults and run the main function. DCP_CONFIG_COOKIE becomes dcpConfig.cookie.
45
+ * And dcpConfig is defined as a side effect of initializing dcp-client.
46
+ */
44
47
  process.env.DCP_CONFIG_COOKIE = (Math.random().toString(16)).slice(2) + '-' + process.pid + '-' + Date.now();
45
48
  require('dcp-client').init({ configName }).then(main).catch(handleUnhandled);
46
49
 
@@ -48,7 +51,6 @@ function parseCliArgs()
48
51
  {
49
52
  var defaultPidFileName;
50
53
 
51
- dcpConfig = require('dcp/dcp-config');
52
54
  defaultPidFileName = require('../lib/pidfile').getDefaultPidFileName(dcpConfig.worker.pidfile);
53
55
 
54
56
  const cliArgs = require('dcp/cli')
@@ -67,7 +69,6 @@ function parseCliArgs()
67
69
  alias: 'd',
68
70
  describe: 'default proportion of CPU,GPU to use when cores not specified',
69
71
  type: 'string',
70
- default: JSON.stringify(dcpConfig.worker.defaultCoreDensity),
71
72
  },
72
73
  verbose: {
73
74
  alias: 'v',
@@ -98,18 +99,21 @@ function parseCliArgs()
98
99
  },
99
100
  priorityOnly: {
100
101
  alias: 'P',
102
+ hidden: true,
101
103
  describe: 'Set the priority mode [deprecated]',
102
104
  type: 'boolean',
103
105
  default: false
104
106
  },
105
107
  'job-id': {
106
108
  alias: 'j',
109
+ hidden: true,
107
110
  describe: 'Restrict worker to a specific job (use N times for N jobs)',
108
111
  type: 'array',
109
112
  },
110
113
 
111
114
  join: {
112
115
  alias: 'g',
116
+ hidden: true,
113
117
  describe: 'Join compute group; the format is "joinKey,joinSecret" or "joinKey,eh1-joinHash"',
114
118
  type: 'array'
115
119
  },
@@ -120,22 +124,27 @@ function parseCliArgs()
120
124
 
121
125
  leavePublicGroup: {
122
126
  type: 'boolean',
127
+ hidden: true,
123
128
  describe: 'Do not fetch slices from public compute group.',
124
129
  default: undefined,
125
130
  },
126
131
 
127
132
  publicGroupFallback: {
133
+ hidden: true,
128
134
  describe: 'If set, worker will prefer private groups but fall back on the public group if no preferred work is available',
129
135
  type: 'boolean',
130
- default: undefined,
136
+ default: 'undefined',
137
+ defaultDescription: undefined,
131
138
  },
132
139
 
133
140
  identityKey: {
141
+ hidden: true,
134
142
  describe: 'Identity key, in hex format',
135
143
  type: 'string',
136
144
  group: 'Identity options',
137
145
  },
138
146
  identityKeystore: {
147
+ hidden: true,
139
148
  describe: 'Identity keystore, in json format',
140
149
  type: 'string',
141
150
  group: 'Identity options',
@@ -147,30 +156,40 @@ function parseCliArgs()
147
156
  group: 'Output options',
148
157
  },
149
158
  eventDebug: {
150
- hide: true,
159
+ hidden: true,
151
160
  describe: 'If set, dump all sandbox and worker events',
152
161
  },
153
162
 
154
163
  logfile: {
155
- describe: 'Path to log file (if --output=file)',
164
+ describe: 'Path to log file',
156
165
  type: 'string',
157
166
  group: 'Log File output options',
167
+ default: path.resolve('../log/dcp-worker.log'),
158
168
  },
159
169
  syslogAddress: {
160
- describe: 'Address of rsyslog server (if --output=syslog)',
170
+ describe: 'Address of syslog server',
171
+ type: 'string',
172
+ group: 'Syslog output options',
173
+ default: 'loghost',
174
+ },
175
+ syslogFacility: {
176
+ describe: 'Name of syslog facility',
161
177
  type: 'string',
162
178
  group: 'Syslog output options',
179
+ default: 'local7',
163
180
  },
164
181
  syslogTransport: {
165
- describe: 'Transport to connect to rsyslog daemon (if --output=syslog)',
182
+ describe: 'Transport to connect to use for syslog',
166
183
  type: 'string',
167
- choices: ['udp','tcp'],
184
+ choices: ['udp','tcp','unix','tls'],
168
185
  group: 'Syslog output options',
186
+ default: 'udp',
169
187
  },
170
188
  syslogPort: {
171
- describe: 'UDP/TCP port of rsyslog server',
189
+ describe: 'UDP/TCP port to use for syslog',
172
190
  type: 'number',
173
191
  group: 'Syslog output options',
192
+ default: 514,
174
193
  },
175
194
 
176
195
  allowedOrigins: {
@@ -202,8 +221,8 @@ function parseCliArgs()
202
221
 
203
222
  if (cliArgs.dumpConfig)
204
223
  {
205
- console.debug(JSON.stringify(require('dcp/dcp-config'), null, 2));
206
- process.exit(0);
224
+ console.log(JSON.stringify(require('dcp/dcp-config'), null, 2));
225
+ processExit(0);
207
226
  }
208
227
 
209
228
  return cliArgs;
@@ -228,6 +247,20 @@ function addConfig(target, ...objs)
228
247
  Object.assign(target, tmp);
229
248
  }
230
249
 
250
+ /**
251
+ * Replacement for process.exit() that tries to increase the probability
252
+ * that remote log messages will make it out over the network.
253
+ */
254
+ function processExit()
255
+ {
256
+ logClosing('debug', 'Exit Code:', process.exitCode || 0);
257
+ if (console.close)
258
+ console.close();
259
+ setImmediate(() => {
260
+ process.exit.apply(null, arguments);
261
+ });
262
+ }
263
+
231
264
  /**
232
265
  * Main program entry point. Assumes DCP client is already initialized and console logging is ready.
233
266
  */
@@ -235,21 +268,22 @@ async function main()
235
268
  {
236
269
  const wallet = require('dcp/wallet');
237
270
  const DCPWorker = require('dcp/worker').Worker;
238
- const { startWorkerLogger } = require('../lib/startWorkerLogger');
239
271
  const cliArgs = parseCliArgs();
240
272
  const sawOptions = {
241
273
  hostname: cliArgs.hostname,
242
274
  port: cliArgs.port
243
275
  };
244
276
 
245
- verifyDefaultConfigIntegrity(); /* Bail before TUI & as early as possible if config file is bad */
277
+ telnetd.setMainEval(function mainEval() { return eval(arguments[0]) }); // eslint-disable-line no-eval
278
+ require('../lib/startWorkerLogger').init(cliArgs); /* Start remote logger as early as possible */
279
+ verifyDefaultConfigIntegrity(); /* Bail before TUI & as early as possible if bad conf */
246
280
 
247
281
  process.on('SIGINT', handleSigDeath);
248
282
  process.on('SIGTERM', handleSigDeath);
249
283
  process.on('SIGQUIT', handleSigDeath);
250
284
  process.on('unhandledRejection', handleUnhandled);
251
285
  process.on('uncaughtException', handleUnhandled);
252
-
286
+
253
287
  let paymentAddress = false
254
288
  || cliArgs.paymentAddress
255
289
  || dcpConfig.worker.paymentAddress
@@ -285,6 +319,7 @@ async function main()
285
319
  const dcpWorkerOptions = dcpConfig.worker;
286
320
  const forceOptions = {
287
321
  paymentAddress,
322
+ maxWorkingSandboxes: cliArgs.cores,
288
323
  };
289
324
  const defaultOptions = {
290
325
  sandboxOptions: {
@@ -339,26 +374,26 @@ async function main()
339
374
  dcpWorkerOptions.watchdogInterval = cliArgs.watchdogInterval;
340
375
 
341
376
  worker = new DCPWorker(identityKeystore, dcpWorkerOptions);
342
- worker.on('error', (...payload) => console.error(...payload));
343
- worker.on('warning', (...payload) => console.warn(...payload));
377
+ worker.on('error', (...payload) => console.error(...payload));
378
+ worker.on('warning', (...payload) => console.warn (...payload));
379
+ worker.on('stop', () => { console.log('Worker is stopping') });
380
+ worker.on('end', () => { logClosing('log', 'Worker has stopped') });
381
+ require('../lib/default-ui-events').hook(worker, cliArgs);
344
382
 
383
+ if (cliArgs.outputMode === 'dashboard')
384
+ require('../lib/dashboard-tui').init(worker, cliArgs);
385
+
345
386
  /* Let incorrect event-loop references keep us alive when linked with a debug library, but
346
387
  * exit quickly/accurately for production code even when the library isn't perfect.
347
388
  */
348
389
  if (require('dcp/build').config.build !== 'debug')
349
- worker.on('end', process.exit);
390
+ worker.on('end', processExit);
350
391
  else
351
- worker.on('end', () => setTimeout(process.exit, getCleanupTimeoutMs()).unref());
392
+ worker.on('end', () => setTimeout(processExit, getCleanupTimeoutMs()).unref());
352
393
 
353
394
  if (cliArgs.eventDebug)
354
395
  worker.enableDebugEvents = true;
355
396
 
356
- worker.on('stop', () => { console.log('Worker is stopping') });
357
- worker.on('end', () => { logClosing('log', 'Worker has stopped') });
358
- startWorkerLogger(worker, cliArgs);
359
-
360
- require('../lib/remote-console').setMainEval(function mainEval() { return eval(arguments[0]) });
361
-
362
397
  if (dcpWorkerOptions.publicGroupFallback)
363
398
  {
364
399
  if (dcpWorkerOptions.leavePublicGroup)
@@ -372,23 +407,10 @@ async function main()
372
407
 
373
408
  function fetchEventHandler(ev)
374
409
  {
375
- var slicesFetched;
376
-
377
410
  if (ev instanceof Error)
378
- {
379
411
  console.error('Error fetching task:', ev);
380
- return;
381
- }
382
-
383
- if (typeof ev === 'string') /* <= June 2023 Worker events: remove ~ Sep 2023 /wg */
384
- slicesFetched = ev;
385
412
  else
386
- {
387
- const task = ev;
388
- slicesFetched = task.slices?.length;
389
- }
390
-
391
- dcpWorkerOptions.leavePublicGroup = Boolean(slicesFetched > 0);
413
+ dcpWorkerOptions.leavePublicGroup = Boolean(utils.slicesFetched(ev) > 0);
392
414
  }
393
415
  }
394
416
  }
@@ -415,21 +437,23 @@ async function main()
415
437
 
416
438
  if (dcpWorkerOptions.jobAddresses?.length > 0)
417
439
  introBanner += ` * Processing only ${qty(dcpWorkerOptions.jobAddresses, 'job')} ` + dcpWorkerOptions.jobAddresses.join(', ') + '\n';
418
- if (dcpWorkerOptions.computeGroups?.length > 0)
440
+ if (dcpWorkerOptions.computeGroups && Object.keys(dcpWorkerOptions.computeGroups).length > 0)
419
441
  introBanner += ` . Joining compute ${qty(dcpWorkerOptions.computeGroups, 'group')} ` + dcpWorkerOptions.computeGroups.map(el => el.joinKey).join(', ') + '\n';
420
442
  if (dcpWorkerOptions.publicGroupFallback)
421
443
  introBanner += ' . Falling back on public group when preferred groups have no work' + '\n';
422
444
  if (dcpWorkerOptions.leavePublicGroup)
423
445
  introBanner += ' . Leaving the public compute group' + '\n';
424
446
  if (dcpWorkerOptions.cores)
425
- introBanner += ` . Target cores: ${JSON.stringify(dcpWorkerOptions.cores)}\n`;
447
+ introBanner += ` . Configured Cores: ${dcpWorkerOptions.cores.cpu},${dcpWorkerOptions.cores.gpu}\n`
426
448
  else
427
449
  introBanner += ` . Target core density: ${JSON.stringify(dcpWorkerOptions.defaultCoreDensity)}\n`;
428
450
  if (cliArgs.verbose)
429
451
  introBanner += ` + Verbosity level: ${cliArgs.verbose}` + '\n';
430
452
  if (cliArgs.eventDebug)
431
453
  introBanner += ' + Event debug on' + '\n';
432
-
454
+ if (telnetd.hasOwnProperty('port'))
455
+ introBanner += ` ! telnetd listening on port ${telnetd.port}\n`;
456
+
433
457
  introBanner += ' . Supervisor version: ' + worker.supervisorVersion;
434
458
  introBanner += ' . Output mode: ' + cliArgs.outputMode + '\n';
435
459
  introBanner += ' * Ready' + '\n';
@@ -442,7 +466,7 @@ async function main()
442
466
  if (cliArgs.outputMode !== 'dashboard')
443
467
  setInterval(printReport, parseFloat(cliArgs.reportInterval) * 1000).unref();
444
468
  else
445
- console.log('Ignoring --reportInterval in dashboard output mode');
469
+ console.warn('Ignoring --reportInterval in dashboard output mode');
446
470
  }
447
471
 
448
472
  /* Start the worker. Normal process exit happens by virtue of the worker<end> event */
@@ -469,21 +493,22 @@ function processCoresAndDensity (dcpWorkerOptions, cliArgs)
469
493
 
470
494
  const parseArg = (which) => {
471
495
  if (!cliArgs[which])
472
- return false;
473
-
474
- const [cpu, gpu] = cliArgs[which].split(',');
475
- dcpWorkerOptions[which] = { cpu: Number(cpu || defaultTargets[which].cpu),
476
- gpu: Number(gpu || defaultTargets[which].gpu) };
477
- return true;
496
+ dcpWorkerOptions[which] = defaultTargets[which];
497
+ else
498
+ {
499
+ const [cpu, gpu] = cliArgs[which].split(',');
500
+ dcpWorkerOptions[which] = { cpu: Number(cpu || defaultTargets[which].cpu),
501
+ gpu: Number(gpu || defaultTargets[which].gpu) };
502
+ }
478
503
  };
479
504
 
480
505
  parseArg('density');
481
506
  parseArg('cores');
482
507
 
483
508
  if (dcpWorkerOptions.cores)
484
- debug('cores = ', dcpWorkerOptions.cores);
485
- else
486
- debug('core density = ', dcpWorkerOptions.defaultCoreDensity);
509
+ debugging() && console.debug('dcp-worker: cores =', dcpWorkerOptions.cores);
510
+ if (dcpWorkerOptions.density)
511
+ debugging() && console.debug('dcp-worker: core density =', dcpWorkerOptions.density);
487
512
  }
488
513
 
489
514
  /**
@@ -494,9 +519,7 @@ function logClosing(facility, ...message)
494
519
  {
495
520
  var screen = require('../lib/worker-loggers/dashboard').screen;
496
521
 
497
- if (!screen)
498
- console[facility](message);
499
- else
522
+ if (screen)
500
523
  {
501
524
  /* Turn off fullscreen TUI and resume "normal" console logging.
502
525
  * FUTURE: dashboard API should know how to unregister its hook so that we don't have to clobber
@@ -505,10 +528,11 @@ function logClosing(facility, ...message)
505
528
  screen.log(...message);
506
529
  screen.destroy();
507
530
  screen = false;
508
- console = new (require('console').Console)(process);
509
- require('../lib/remote-console').reintercept();
510
- console[facility].call(null, ...message);
531
+ console = new (require('console').Console)(process); // eslint-disable-line no-global-assign
532
+ telnetd.reintercept();
511
533
  }
534
+
535
+ console[facility](...message);
512
536
  }
513
537
 
514
538
  /**
@@ -517,7 +541,7 @@ function logClosing(facility, ...message)
517
541
  * the worker must be restarted. This handler does its best to report the rejection and give the worker a few
518
542
  * seconds in which to attempt to return slices to the scheduler before it gives up completely.
519
543
  */
520
- async function handleUnhandled(error)
544
+ function handleUnhandled(error)
521
545
  {
522
546
  var _worker = worker;
523
547
  worker = false;
@@ -526,21 +550,21 @@ async function handleUnhandled(error)
526
550
 
527
551
  try
528
552
  {
529
- logClosing(error);
530
- } catch(e) {};
553
+ logClosing('error', error);
554
+ } catch(e) {} // eslint-disable-line no-empty
531
555
 
532
556
  if (!_worker)
533
557
  console.error('trapped unhandled error:', error)
534
558
  else
535
559
  {
536
560
  console.error('trapped unhandled error -- stopping worker:', error);
537
- _worker.on('end', process.exit);
561
+ _worker.on('end', processExit);
538
562
  _worker.stop();
539
563
  }
540
564
 
541
565
  setTimeout(() => {
542
566
  logClosing('error', 'handleFatalError timeout - exiting now');
543
- process.exit();
567
+ processExit();
544
568
  }, getCleanupTimeoutMs()).unref();
545
569
 
546
570
  try {
@@ -550,7 +574,7 @@ async function handleUnhandled(error)
550
574
  fs.appendFileSync(process.env.DCP_WORKER_UNHANDLED_REJECTION_LOG,
551
575
  `${Date.now()}: ${error.message}\n${error.stack}\n\n`);
552
576
  }
553
- } catch(e) {};
577
+ } catch(e) {} // eslint-disable-line no-empty
554
578
  }
555
579
 
556
580
  /** print the slice report via console.log */
@@ -608,14 +632,14 @@ function sliceReport()
608
632
 
609
633
  report += ('Progress:') + '\n';
610
634
  worker.workingSandboxes.forEach(sb => {
611
- const jobName = sb.job && sb.job.public && sb.job.public.name || `idek (${sb.jobAddress})`;
635
+ const jobName = sb.job?.public?.name || `idek (${sb.jobAddress})`;
612
636
  let el = Date.now() - sb.sliceStartTime;
613
637
  const t = el < 1000000
614
638
  ? toInterval(el)
615
639
  : 'new';
616
640
 
617
641
  el = sb.progressReports && sb.progressReports.last
618
- ? Date.now() - (sb.sliceStartTime + sb.progressReports.last.timestamp)
642
+ ? Date.now() - (sb.sliceStartTime + (sb.progressReports.last?.timestamp ?? 0))
619
643
  : 0;
620
644
  const pct = (typeof sb.progress) === 'number'
621
645
  ? `${Number(sb.progress).toFixed(0).padStart(2)}%`
@@ -647,14 +671,14 @@ function handleSigDeath(signalName, signal)
647
671
  process.off(signalName, handleSigDeath);
648
672
 
649
673
  if (!worker)
650
- console.error(`trapped ${signalName}, signal ${signal}`);
674
+ console.warn(`trapped ${signalName}, signal ${signal}`);
651
675
  else
652
676
  {
653
- console.error(`trapped ${signalName}, signal ${signal} -- stopping worker`);
677
+ console.warn(`trapped ${signalName}, signal ${signal} -- stopping worker`);
654
678
  worker.stop(signalName === 'SIGQUIT');
655
679
  }
656
680
 
657
- setTimeout(() => process.exit(signal - 128), getCleanupTimeoutMs()).unref();
681
+ setTimeout(() => processExit(signal - 128), getCleanupTimeoutMs()).unref();
658
682
  }
659
683
 
660
684
  /**
@@ -674,7 +698,7 @@ function getCleanupTimeoutMs()
674
698
  cleanupTimeout = defaultCT;
675
699
  if (!getCleanupTimeoutMs.warned)
676
700
  {
677
- console.warn(`warning: dcpConfig.worker.cleanupTimeout is not a number (${dcpConfig.worker.cleanupTimeout})`);
701
+ console.error(`warning: dcpConfig.worker.cleanupTimeout is not a number (${dcpConfig.worker.cleanupTimeout})`);
678
702
  getCleanupTimeoutMs.warned = true;
679
703
  }
680
704
  }
@@ -696,7 +720,7 @@ function verifyDefaultConfigIntegrity()
696
720
 
697
721
  if (!fs.existsSync(md5sumPath))
698
722
  {
699
- console.log(chalk.bold.red(` ! warning: ${md5sumPath} not found; cannot verify configuration integrity`));
723
+ console.error(chalk.bold.red(` ! warning: ${md5sumPath} not found; cannot verify configuration integrity`));
700
724
  require('dcp/utils').sleep(2);
701
725
  }
702
726
  else
@@ -718,7 +742,7 @@ function verifyDefaultConfigIntegrity()
718
742
  console.warn(' - the Windows Registry');
719
743
 
720
744
  if (require('dcp/build').config.build !== 'debug')
721
- process.exit(1);
745
+ processExit(1);
722
746
 
723
747
  console.log(chalk.bold.red.inverse("If this wasn't a debug build, the worker would exit now."));
724
748
  require('dcp/utils').sleep(2);
@@ -728,10 +752,18 @@ function verifyDefaultConfigIntegrity()
728
752
  if (dcpConfig.cookie !== process.env.DCP_CONFIG_COOKIE || !dcpConfig.cookie)
729
753
  {
730
754
  console.error(' ! DCP Worker default configuration was not loaded; exiting.');
731
- process.exit(1);
755
+ processExit(1);
732
756
  }
733
757
  }
734
758
 
759
+ /* thunk - ensures global debugging() symbol always available even if called before dcp-client init */
760
+ function debugging()
761
+ {
762
+ require('dcp-client');
763
+ debugging = require('dcp/internal/debugging').scope('dcp-worker');
764
+ return debugging.apply(this, arguments);
765
+ }
766
+
735
767
  /**
736
768
  * Cast b to boolean such that 'false' becomes false, falsey things become false, and everything else
737
769
  * becomes true.
package/docs/CODEOWNERS CHANGED
@@ -18,9 +18,11 @@
18
18
  /package-lock.json @eroosenmaallen
19
19
 
20
20
  [Wes]
21
+ /npm-hooks/ @wesgarland
21
22
  /README.md @wesgarland
22
23
  /docs/ @wesgarland
23
24
  /.eslintrc.json @wesgarland
25
+ /.npmrc @wesgarland
24
26
  /.tidelift @wesgarland
25
27
  /lib/ @wesgarland
26
28
  /LICENSE.md @wesgarland
@@ -49,7 +49,7 @@
49
49
  // keystore('~/.dcp/scott'),
50
50
  ],
51
51
 
52
- jobAddresses: [], /* If specified, restrict the worker to only these jobs */
52
+ jobAddresses: false, /* If specified, restrict the worker to only these jobs */
53
53
  paymentAddress: undefined, /* Bank account where earned funds are transfered if not specified on command-line */
54
54
  },
55
55
 
@@ -1,2 +1,2 @@
1
- 8edf1c57bee521ed7f151a21b8f3f34d
1
+ 4d49057dd014344e4be5a266e9754824
2
2
  ### DO NOT MODIFY THIS FILE!!! ###
@@ -1,3 +1,4 @@
1
+ 'use strict';
1
2
 
2
3
  Object.assign(exports, require('./log'));
3
4
  Object.assign(exports, require('./sandboxes'));
@@ -7,6 +7,7 @@
7
7
  * log component, but features text wrapping and behaves
8
8
  * closer to the built-in console log methods.
9
9
  */
10
+ 'use strict';
10
11
 
11
12
  const { Box } = require('blessed');
12
13
 
@@ -67,11 +67,7 @@ class Sandboxes extends Box {
67
67
  update(data=this.data) {
68
68
  this.data = data;
69
69
  for (let i = 0; i < this.data.length; i++) {
70
- if (i < this.progressBars.length) {
71
- this.updateProgressBar(i, this.data[i]);
72
- } else {
73
- this.createProgressBar();
74
- }
70
+ this.updateProgressBar(i, this.data[i]);
75
71
  }
76
72
 
77
73
  if (this.data.length < this.progressBars.length) {
@@ -80,7 +76,15 @@ class Sandboxes extends Box {
80
76
  }
81
77
  }
82
78
 
83
- this.setLabel(`${this.options.label} (${this.data.length})`);
79
+ this.setLabel(`${this.options.label} (${this.progressBars.length})`);
80
+ }
81
+
82
+ // Deletes last progress bar
83
+ deleteProgressBar() {
84
+ let i = this.progressBars.length - 1;
85
+ this.progressBars[i].label.destroy()
86
+ this.progressBars[i].progressBar.destroy()
87
+ this.progressBars.pop(i)
84
88
  }
85
89
  }
86
90
 
@@ -6,6 +6,7 @@
6
6
  * @author Sam Cantor
7
7
  * @date Nov 2020
8
8
  */
9
+ 'use strict';
9
10
 
10
11
  exports.check = function checkSchedulerVersion$$check(quiet)
11
12
  {