dankgrinder 6.6.0 → 6.8.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/lib/grinder.js +214 -146
- package/package.json +1 -1
package/lib/grinder.js
CHANGED
|
@@ -393,222 +393,290 @@ function renderDashboard() {
|
|
|
393
393
|
setTimeout(() => { isNewHigh = false; }, 3000);
|
|
394
394
|
}
|
|
395
395
|
|
|
396
|
+
// ── Layout: use FULL terminal width ──
|
|
396
397
|
const lines = [];
|
|
397
|
-
const tw = Math.max(
|
|
398
|
-
const
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
const
|
|
402
|
-
const
|
|
403
|
-
const
|
|
404
|
-
const
|
|
405
|
-
const
|
|
406
|
-
const
|
|
407
|
-
const
|
|
408
|
-
const
|
|
409
|
-
const
|
|
410
|
-
const
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
398
|
+
const tw = Math.max(process.stdout.columns || 80, 60);
|
|
399
|
+
const iw = tw - 4; // inner width (inside box borders)
|
|
400
|
+
|
|
401
|
+
// Color shortcuts (local to this function)
|
|
402
|
+
const A = rgb(139, 92, 246); // accent purple
|
|
403
|
+
const G = rgb(52, 211, 153); // green
|
|
404
|
+
const B = rgb(96, 165, 250); // blue
|
|
405
|
+
const P = rgb(236, 72, 153); // pink
|
|
406
|
+
const Au = rgb(255, 215, 0); // gold
|
|
407
|
+
const O = rgb(251, 146, 60); // orange
|
|
408
|
+
const Cy = rgb(34, 211, 238); // cyan
|
|
409
|
+
const R = rgb(239, 68, 68); // red
|
|
410
|
+
const Y = rgb(251, 191, 36); // yellow
|
|
411
|
+
const W = c.white;
|
|
412
|
+
const D = c.dim;
|
|
413
|
+
const RE = /\x1b\[[0-9;]*m/g;
|
|
414
|
+
|
|
415
|
+
// ── Box drawing (scales to tw) ──
|
|
416
|
+
const bTop = A + '╔' + '═'.repeat(tw - 2) + '╗' + c.reset;
|
|
417
|
+
const bMid = A + '╟' + '─'.repeat(tw - 2) + '╢' + c.reset;
|
|
418
|
+
const bSep = A + '╠' + '═'.repeat(tw - 2) + '╣' + c.reset;
|
|
419
|
+
const bBot = A + '╚' + '═'.repeat(tw - 2) + '╝' + c.reset;
|
|
420
|
+
const bRow = (content) => {
|
|
421
|
+
const vis = content.replace(RE, '').length;
|
|
422
|
+
const pad = Math.max(0, iw - vis);
|
|
423
|
+
return A + '║' + c.reset + ' ' + content + ' '.repeat(pad) + ' ' + A + '║' + c.reset;
|
|
424
|
+
};
|
|
425
|
+
const bEmpty = bRow('');
|
|
426
|
+
|
|
427
|
+
// ═══════════════════════════════════════════════════════════════
|
|
428
|
+
// HEADER
|
|
429
|
+
// ═══════════════════════════════════════════════════════════════
|
|
430
|
+
lines.push(bTop);
|
|
431
|
+
lines.push(bEmpty);
|
|
432
|
+
|
|
433
|
+
// Title with animated spinner
|
|
416
434
|
const spin = getSpinner('braille');
|
|
417
|
-
const titleGrad = gradientText('
|
|
418
|
-
|
|
419
|
-
lines.push(boxLine(`${c.bold}${titleGrad}${c.reset} ${verStr} ${green}${spin}${c.reset}`, tw, accent));
|
|
435
|
+
const titleGrad = gradientText(' D A N K G R I N D E R ', [192, 132, 252], [52, 211, 153]);
|
|
436
|
+
lines.push(bRow(` ${c.bold}${titleGrad}${c.reset} ${D}v${PKG_VERSION}${c.reset} ${G}${spin}${c.reset}`));
|
|
420
437
|
|
|
421
|
-
//
|
|
438
|
+
// Subtitle info
|
|
422
439
|
const activeCount = workers.filter(w => w.running && !w.paused && !w.dashboardPaused).length;
|
|
440
|
+
const invalidCount = workers.filter(w => w._tokenInvalid).length;
|
|
423
441
|
const pausedCount = workers.filter(w => w.paused || w.dashboardPaused).length;
|
|
424
442
|
const recovCount = workers.filter(w => w._recoveryAttempts > 0 && w._errorCooldownUntil > Date.now()).length;
|
|
425
|
-
const mode = CLUSTER_ENABLED ? `${
|
|
443
|
+
const mode = CLUSTER_ENABLED ? `${Cy}CLUSTER${c.reset}` : `${D}Standalone${c.reset}`;
|
|
426
444
|
const cmdCount = AccountWorker.COMMAND_MAP.length;
|
|
427
445
|
|
|
428
|
-
// Net quality
|
|
429
446
|
const netQ = workers.length > 0
|
|
430
447
|
? workers.reduce((s, w) => s + (w._lastPing < 200 ? 1 : w._lastPing < 500 ? 0.5 : 0), 0) / workers.length : 1;
|
|
431
|
-
const netDot = netQ > 0.8 ? `${
|
|
432
|
-
const netLbl = netQ > 0.8 ? `${
|
|
448
|
+
const netDot = netQ > 0.8 ? `${G}●${c.reset}` : netQ > 0.5 ? `${Y}●${c.reset}` : `${R}●${c.reset}`;
|
|
449
|
+
const netLbl = netQ > 0.8 ? `${G}Good${c.reset}` : netQ > 0.5 ? `${Y}Fair${c.reset}` : `${R}Poor${c.reset}`;
|
|
450
|
+
|
|
451
|
+
const infoParts = [
|
|
452
|
+
mode,
|
|
453
|
+
`${netDot} ${netLbl}`,
|
|
454
|
+
`${B}${cmdCount}${c.reset} ${D}commands${c.reset}`,
|
|
455
|
+
`${G}${activeCount}${c.reset}${D}/${c.reset}${W}${workers.length}${c.reset} ${D}active${c.reset}`,
|
|
456
|
+
];
|
|
457
|
+
if (invalidCount > 0) infoParts.push(`${R}${invalidCount} invalid${c.reset}`);
|
|
458
|
+
if (pausedCount > 0) infoParts.push(`${Y}${pausedCount} paused${c.reset}`);
|
|
459
|
+
if (recovCount > 0) infoParts.push(`${O}${recovCount} recovering${c.reset}`);
|
|
460
|
+
lines.push(bRow(` ${infoParts.join(` ${D}·${c.reset} `)}`));
|
|
433
461
|
|
|
434
|
-
lines.push(
|
|
462
|
+
lines.push(bEmpty);
|
|
435
463
|
|
|
436
|
-
//
|
|
437
|
-
|
|
438
|
-
|
|
464
|
+
// ═══════════════════════════════════════════════════════════════
|
|
465
|
+
// STATS PANEL
|
|
466
|
+
// ═══════════════════════════════════════════════════════════════
|
|
467
|
+
lines.push(bSep);
|
|
468
|
+
lines.push(bEmpty);
|
|
439
469
|
|
|
440
|
-
//
|
|
441
|
-
const
|
|
442
|
-
|
|
443
|
-
const
|
|
444
|
-
|
|
470
|
+
// Earnings sparkline data
|
|
471
|
+
const now = Date.now();
|
|
472
|
+
if (now - lastEarningsSample > 8000) { earningsHistory.push(totalCoins); lastEarningsSample = now; }
|
|
473
|
+
const sparkW = Math.min(30, Math.floor(iw * 0.3));
|
|
474
|
+
const spark = drawSparkline(earningsHistory.toArray(), sparkW);
|
|
445
475
|
|
|
446
|
-
//
|
|
476
|
+
// Row 1: Balance + Earned
|
|
447
477
|
const elapsedHrs = (Date.now() - startTime) / 3_600_000;
|
|
448
478
|
const perHr = elapsedHrs > 0.01 ? Math.round(totalCoins / elapsedHrs) : 0;
|
|
449
|
-
const
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
// Sample sparkline
|
|
453
|
-
const now = Date.now();
|
|
454
|
-
if (now - lastEarningsSample > 8000) { earningsHistory.push(totalCoins); lastEarningsSample = now; }
|
|
455
|
-
const spark = drawSparkline(earningsHistory.toArray(), 20);
|
|
456
|
-
lines.push(boxLine(`${earnIcon} ${dim}EARNED${c.reset} ${earnVal} ${hrRate} ${spark}`, tw, accent));
|
|
479
|
+
const peakFlag = isNewHigh ? ` ${R}${c.bold}* NEW HIGH *${c.reset}` : '';
|
|
480
|
+
|
|
481
|
+
lines.push(bRow(` ${Au}⟐${c.reset} ${D}BALANCE${c.reset} ${c.bold}${Au}⏣ ${formatCoins(totalBalance)}${c.reset} ${G}▲${c.reset} ${D}EARNED${c.reset} ${c.bold}${G}+⏣ ${formatCoins(totalCoins)}${c.reset} ${D}(${c.reset}${G}${formatCoins(perHr)}/h${c.reset}${D})${c.reset}${peakFlag}`));
|
|
457
482
|
|
|
458
|
-
// Peak
|
|
459
|
-
|
|
460
|
-
lines.push(boxLine(`${peakIcon} ${dim}PEAK${c.reset} ${c.bold}${orange}⏣ ${formatCoins(sessionPeakCoins)}${c.reset} ${dim}this session${c.reset}`, tw, accent));
|
|
483
|
+
// Row 2: Peak + Trend sparkline
|
|
484
|
+
lines.push(bRow(` ${O}★${c.reset} ${D}PEAK${c.reset} ${c.bold}${O}⏣ ${formatCoins(sessionPeakCoins)}${c.reset} ${A}~${c.reset} ${D}TREND${c.reset} ${spark}`));
|
|
461
485
|
|
|
462
|
-
//
|
|
486
|
+
// Row 3: Commands + Success + Rate + Uptime
|
|
463
487
|
const cpmVal = globalCmdRate.getRate().toFixed(1);
|
|
464
|
-
const srColor = successRate >= 95 ?
|
|
465
|
-
const
|
|
466
|
-
const
|
|
467
|
-
lines.push(
|
|
488
|
+
const srColor = successRate >= 95 ? G : successRate >= 80 ? Y : R;
|
|
489
|
+
const srBarW = Math.min(15, Math.floor(iw * 0.12));
|
|
490
|
+
const srBar = progressBar(successRate, 100, srBarW, successRate >= 95 ? [52, 211, 153] : successRate >= 80 ? [251, 191, 36] : [239, 68, 68]);
|
|
491
|
+
lines.push(bRow(` ${B}◆${c.reset} ${D}CMDS${c.reset} ${c.bold}${B}${totalCommands}${c.reset} ${srColor}${successRate}%${c.reset} ${srBar} ${Cy}${cpmVal}${c.reset}${D}/min${c.reset} ${Y}◷${c.reset} ${D}UP${c.reset} ${c.bold}${Y}${formatUptime()}${c.reset}`));
|
|
468
492
|
|
|
469
|
-
// Memory
|
|
493
|
+
// Row 4: Memory
|
|
470
494
|
const memMB = Math.round((process.memoryUsage?.rss?.() ?? process.memoryUsage().rss) / 1048576);
|
|
471
|
-
const
|
|
472
|
-
const
|
|
473
|
-
const
|
|
474
|
-
lines.push(
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
495
|
+
const memCol = memMB > 900 ? [239, 68, 68] : memMB > 600 ? [251, 191, 36] : [52, 211, 153];
|
|
496
|
+
const memBarW = Math.min(20, Math.floor(iw * 0.15));
|
|
497
|
+
const memBar = progressBar(memMB, 1024, memBarW, memCol, [40, 40, 55]);
|
|
498
|
+
lines.push(bRow(` ${D}≡${c.reset} ${D}MEM${c.reset} ${rgb(memCol[0], memCol[1], memCol[2])}${c.bold}${memMB}MB${c.reset} ${memBar}`));
|
|
499
|
+
|
|
500
|
+
lines.push(bEmpty);
|
|
501
|
+
|
|
502
|
+
// ═══════════════════════════════════════════════════════════════
|
|
503
|
+
// ACCOUNTS TABLE (sorted by original index, proper alignment)
|
|
504
|
+
// ═══════════════════════════════════════════════════════════════
|
|
505
|
+
lines.push(bSep);
|
|
506
|
+
|
|
507
|
+
// Column widths scale with terminal
|
|
508
|
+
const colNum = 4; // " 1 "
|
|
509
|
+
const colSts = 3; // "● "
|
|
510
|
+
const colMedal = 4; // " 1st" or " "
|
|
511
|
+
const colBal = 12; // "⏣ 999.9M "
|
|
512
|
+
const colEarn = 10; // "+999.9K "
|
|
513
|
+
const colBar = 8; // "████░░░░"
|
|
514
|
+
const fixedW = colNum + colSts + colMedal + colBal + colEarn + colBar + 16; // 16 for spacing
|
|
515
|
+
const colName = Math.max(10, Math.min(22, Math.floor((iw - fixedW) * 0.45)));
|
|
516
|
+
const colActivity = Math.max(10, iw - fixedW - colName);
|
|
517
|
+
|
|
518
|
+
// Header
|
|
519
|
+
const hNum = `${D}${'#'.padStart(colNum)}${c.reset}`;
|
|
520
|
+
const hSts = `${D}${'ST'.padEnd(colSts)}${c.reset}`;
|
|
521
|
+
const hMedal = `${D}${'RNK'.padEnd(colMedal)}${c.reset}`;
|
|
522
|
+
const hName = `${gradientText('Account', [139, 92, 246], [96, 165, 250])}${''.padEnd(Math.max(0, colName - 7))}`;
|
|
523
|
+
const hBal = `${D}${'Balance'.padEnd(colBal)}${c.reset}`;
|
|
524
|
+
const hEarn = `${D}${'Earned'.padEnd(colEarn)}${c.reset}`;
|
|
525
|
+
const hBar = `${D}${''.padEnd(colBar)}${c.reset}`;
|
|
526
|
+
const hAct = `${D}Activity${c.reset}`;
|
|
527
|
+
|
|
528
|
+
lines.push(bRow(` ${hNum} ${hSts} ${hMedal} ${hName} ${hBal} ${hEarn} ${hBar} ${hAct}`));
|
|
529
|
+
lines.push(bRow(` ${D}${'─'.repeat(iw - 2)}${c.reset}`));
|
|
530
|
+
|
|
531
|
+
// Sort workers by original index for consistent display
|
|
532
|
+
const sortedWorkers = [...workers].sort((a, b) => a.idx - b.idx);
|
|
486
533
|
|
|
487
534
|
// Top 3 earners
|
|
488
535
|
const topEarners = [...workers]
|
|
489
|
-
.filter(w =>
|
|
536
|
+
.filter(w => (w.stats.coins || 0) > 0)
|
|
490
537
|
.sort((a, b) => (b.stats.coins || 0) - (a.stats.coins || 0))
|
|
491
538
|
.slice(0, 3);
|
|
492
|
-
const topIds = new
|
|
539
|
+
const topIds = new Map();
|
|
540
|
+
topEarners.forEach((w, i) => topIds.set(w.account.id, i));
|
|
493
541
|
|
|
494
542
|
const MAX_VIS = 30;
|
|
543
|
+
const visibleWorkers = sortedWorkers.slice(0, MAX_VIS);
|
|
495
544
|
|
|
496
|
-
const
|
|
497
|
-
|
|
498
|
-
const
|
|
499
|
-
const
|
|
500
|
-
const rawStat = (wk.lastStatus || 'idle').replace(RE_ANSI, '');
|
|
501
|
-
const statTxt = rawStat.substring(0, statusW);
|
|
545
|
+
for (const wk of visibleWorkers) {
|
|
546
|
+
const origNum = (wk.idx + 1).toString().padStart(colNum);
|
|
547
|
+
const rawStat = (wk.lastStatus || 'idle').replace(RE, '');
|
|
548
|
+
const activityText = rawStat.substring(0, colActivity);
|
|
502
549
|
|
|
503
|
-
// Status
|
|
504
|
-
let
|
|
550
|
+
// ── Status icon ──
|
|
551
|
+
let stsIcon, actLabel;
|
|
505
552
|
const isRecov = wk._recoveryAttempts > 0 && wk._errorCooldownUntil > Date.now();
|
|
506
553
|
if (wk._tokenInvalid) {
|
|
507
|
-
|
|
508
|
-
|
|
554
|
+
stsIcon = `${R}✗${c.reset}`;
|
|
555
|
+
actLabel = `${R}TOKEN INVALID${c.reset}`;
|
|
509
556
|
} else if (!wk.running) {
|
|
510
|
-
|
|
511
|
-
|
|
557
|
+
stsIcon = `${D}○${c.reset}`;
|
|
558
|
+
actLabel = `${D}offline${c.reset}`;
|
|
512
559
|
} else if (isRecov) {
|
|
513
560
|
const sL = Math.ceil((wk._errorCooldownUntil - Date.now()) / 1000);
|
|
514
|
-
|
|
515
|
-
|
|
561
|
+
stsIcon = `${O}${getSpinner('braille')}${c.reset}`;
|
|
562
|
+
actLabel = `${O}recovering (${sL}s)${c.reset}`;
|
|
516
563
|
} else if (wk.paused) {
|
|
517
|
-
|
|
518
|
-
|
|
564
|
+
stsIcon = `${R}⏸${c.reset}`;
|
|
565
|
+
actLabel = `${R}PAUSED${c.reset}`;
|
|
519
566
|
} else if (wk.dashboardPaused) {
|
|
520
|
-
|
|
521
|
-
|
|
567
|
+
stsIcon = `${Y}⏸${c.reset}`;
|
|
568
|
+
actLabel = `${Y}paused${c.reset}`;
|
|
522
569
|
} else if (wk.busy) {
|
|
523
|
-
|
|
524
|
-
|
|
570
|
+
stsIcon = `${G}${getSpinner('pulse')}${c.reset}`;
|
|
571
|
+
actLabel = `${D}${activityText}${c.reset}`;
|
|
525
572
|
} else {
|
|
526
|
-
|
|
527
|
-
|
|
573
|
+
stsIcon = `${G}●${c.reset}`;
|
|
574
|
+
actLabel = `${D}${activityText}${c.reset}`;
|
|
528
575
|
}
|
|
529
576
|
|
|
530
|
-
// Medal
|
|
531
|
-
let
|
|
577
|
+
// ── Medal (fixed 3-char visible width + 1 space) ──
|
|
578
|
+
let medalStr;
|
|
532
579
|
if (topIds.has(wk.account.id)) {
|
|
533
|
-
const rank =
|
|
534
|
-
|
|
535
|
-
|
|
580
|
+
const rank = topIds.get(wk.account.id);
|
|
581
|
+
if (rank === 0) medalStr = `${Au}1st${c.reset} `;
|
|
582
|
+
else if (rank === 1) medalStr = `${rgb(192, 192, 192)}2nd${c.reset} `;
|
|
583
|
+
else medalStr = `${rgb(205, 127, 50)}3rd${c.reset} `;
|
|
584
|
+
} else {
|
|
585
|
+
medalStr = ' '; // 4 chars: 3 empty + 1 space
|
|
536
586
|
}
|
|
537
587
|
|
|
538
|
-
|
|
588
|
+
// ── Name (fixed visible width, padded) ──
|
|
589
|
+
const rawName = (wk.username || wk.account.label || '?').substring(0, colName);
|
|
590
|
+
const nameStr = `${wk.color}${c.bold}${rawName.padEnd(colName)}${c.reset}`;
|
|
539
591
|
|
|
540
|
-
// Balance
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
592
|
+
// ── Balance (fixed visible width) ──
|
|
593
|
+
let balStr;
|
|
594
|
+
if (wk.stats.balance > 0) {
|
|
595
|
+
balStr = `${Au}⏣${c.reset}${W}${formatCoins(wk.stats.balance).padStart(colBal - 2)}${c.reset}`;
|
|
596
|
+
} else {
|
|
597
|
+
balStr = `${D}⏣${' '.repeat(colBal - 3)}-${c.reset}`;
|
|
598
|
+
}
|
|
544
599
|
|
|
545
|
-
//
|
|
600
|
+
// ── Earned (fixed visible width) ──
|
|
546
601
|
const earnNum = wk.stats.coins || 0;
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
602
|
+
let earnStr;
|
|
603
|
+
if (earnNum > 0) {
|
|
604
|
+
earnStr = `${G}+${formatCoins(earnNum).padEnd(colEarn - 1)}${c.reset}`;
|
|
605
|
+
} else if (earnNum < 0) {
|
|
606
|
+
earnStr = `${R}${formatCoins(earnNum).padEnd(colEarn)}${c.reset}`;
|
|
607
|
+
} else {
|
|
608
|
+
earnStr = `${D}${'-'.padEnd(colEarn)}${c.reset}`;
|
|
609
|
+
}
|
|
552
610
|
|
|
553
|
-
|
|
554
|
-
|
|
611
|
+
// ── Progress bar (fixed width) ──
|
|
612
|
+
const earnBarFill = earnNum > 0 ? Math.min(colBar, Math.max(1, Math.floor(Math.log10(earnNum + 1)))) : 0;
|
|
613
|
+
const earnBar = progressBar(earnBarFill, colBar, colBar, [52, 211, 153], [40, 40, 55]);
|
|
555
614
|
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
615
|
+
lines.push(bRow(` ${D}${origNum}${c.reset} ${stsIcon} ${medalStr}${nameStr} ${balStr} ${earnStr} ${earnBar} ${actLabel}`));
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
// Overflow summary
|
|
619
|
+
if (sortedWorkers.length > MAX_VIS) {
|
|
620
|
+
const rest = sortedWorkers.length - MAX_VIS;
|
|
621
|
+
let ha = 0, hp = 0, hr = 0, ho = 0, hi = 0;
|
|
622
|
+
for (let i = MAX_VIS; i < sortedWorkers.length; i++) {
|
|
623
|
+
const w = sortedWorkers[i];
|
|
624
|
+
if (w._tokenInvalid) hi++;
|
|
625
|
+
else if (!w.running) ho++;
|
|
565
626
|
else if (w.paused || w.dashboardPaused) hp++;
|
|
566
627
|
else if (w._recoveryAttempts > 0 && w._errorCooldownUntil > Date.now()) hr++;
|
|
567
628
|
else ha++;
|
|
568
629
|
}
|
|
569
|
-
const parts = [`${
|
|
570
|
-
if (ha > 0) parts.push(`${
|
|
571
|
-
if (hp > 0) parts.push(`${
|
|
572
|
-
if (hr > 0) parts.push(`${
|
|
573
|
-
if (
|
|
574
|
-
|
|
630
|
+
const parts = [`${D}+${rest} more${c.reset}`];
|
|
631
|
+
if (ha > 0) parts.push(`${G}${ha} active${c.reset}`);
|
|
632
|
+
if (hp > 0) parts.push(`${Y}${hp} paused${c.reset}`);
|
|
633
|
+
if (hr > 0) parts.push(`${O}${hr} recovering${c.reset}`);
|
|
634
|
+
if (hi > 0) parts.push(`${R}${hi} invalid${c.reset}`);
|
|
635
|
+
if (ho > 0) parts.push(`${D}${ho} offline${c.reset}`);
|
|
636
|
+
lines.push(bRow(` ${parts.join(` ${D}·${c.reset} `)}`));
|
|
575
637
|
}
|
|
576
638
|
|
|
577
|
-
//
|
|
639
|
+
// ═══════════════════════════════════════════════════════════════
|
|
640
|
+
// HEALTH BAR
|
|
641
|
+
// ═══════════════════════════════════════════════════════════════
|
|
578
642
|
const totalRecoveries = workers.reduce((s, w) => s + (w._totalRecoveries || 0), 0);
|
|
579
643
|
const totalDisconnects = workers.reduce((s, w) => s + (w._disconnectCount || 0), 0);
|
|
580
|
-
if (recovCount > 0 || pausedCount > 0 || totalRecoveries > 0 || totalDisconnects > 0) {
|
|
581
|
-
lines.push(
|
|
644
|
+
if (recovCount > 0 || pausedCount > 0 || invalidCount > 0 || totalRecoveries > 0 || totalDisconnects > 0) {
|
|
645
|
+
lines.push(bMid);
|
|
582
646
|
const hParts = [];
|
|
583
|
-
if (
|
|
584
|
-
if (
|
|
585
|
-
if (
|
|
586
|
-
if (
|
|
587
|
-
|
|
647
|
+
if (invalidCount > 0) hParts.push(`${R}✗ ${invalidCount} invalid${c.reset}`);
|
|
648
|
+
if (recovCount > 0) hParts.push(`${O}${getSpinner('braille')} ${recovCount} recovering${c.reset}`);
|
|
649
|
+
if (pausedCount > 0) hParts.push(`${Y}⏸ ${pausedCount} paused${c.reset}`);
|
|
650
|
+
if (totalRecoveries > 0) hParts.push(`${D}${totalRecoveries} auto-healed${c.reset}`);
|
|
651
|
+
if (totalDisconnects > 0) hParts.push(`${D}${totalDisconnects} reconnects${c.reset}`);
|
|
652
|
+
lines.push(bRow(` ${D}HEALTH${c.reset} ${hParts.join(` ${D}·${c.reset} `)}`));
|
|
588
653
|
}
|
|
589
654
|
|
|
590
|
-
// Cluster
|
|
655
|
+
// Cluster
|
|
591
656
|
if (CLUSTER_ENABLED) {
|
|
592
657
|
const nodeShort = NODE_ID.substring(0, 12);
|
|
593
658
|
const claimedCount = workers.filter(w => w.running).length;
|
|
594
|
-
lines.push(
|
|
659
|
+
lines.push(bRow(` ${Cy}CLUSTER${c.reset} ${D}node:${c.reset} ${Cy}${nodeShort}${c.reset} ${D}claimed:${c.reset} ${W}${claimedCount}${c.reset}`));
|
|
595
660
|
}
|
|
596
661
|
|
|
597
|
-
//
|
|
662
|
+
// ═══════════════════════════════════════════════════════════════
|
|
663
|
+
// LIVE LOG FEED
|
|
664
|
+
// ═══════════════════════════════════════════════════════════════
|
|
598
665
|
const logEntries = recentLogs.toArray();
|
|
599
666
|
if (logEntries.length > 0) {
|
|
600
|
-
lines.push(
|
|
601
|
-
|
|
602
|
-
lines.push(
|
|
667
|
+
lines.push(bSep);
|
|
668
|
+
lines.push(bRow(` ${gradientText('LIVE FEED', [139, 92, 246], [52, 211, 153])} ${G}${getSpinner('pulse')}${c.reset}`));
|
|
669
|
+
lines.push(bMid);
|
|
603
670
|
for (const entry of logEntries) {
|
|
604
|
-
lines.push(
|
|
671
|
+
lines.push(bRow(` ${D}${entry}${c.reset}`));
|
|
605
672
|
}
|
|
606
673
|
}
|
|
607
674
|
|
|
608
|
-
//
|
|
609
|
-
lines.push(
|
|
675
|
+
// ═══════════════════════════════════════════════════════════════
|
|
676
|
+
lines.push(bEmpty);
|
|
677
|
+
lines.push(bBot);
|
|
610
678
|
|
|
611
|
-
//
|
|
679
|
+
// ── Flush to terminal ──
|
|
612
680
|
process.stdout.write('\x1b[H');
|
|
613
681
|
for (const line of lines) {
|
|
614
682
|
process.stdout.write(c.clearLine + '\r' + line + '\n');
|
|
@@ -626,9 +694,9 @@ function log(type, msg, label) {
|
|
|
626
694
|
};
|
|
627
695
|
const tagRaw = label ? label.replace(/\x1b\[[0-9;]*m/g, '').substring(0, 12) : '';
|
|
628
696
|
const stripped = msg.replace(/\x1b\[[0-9;]*m/g, '');
|
|
629
|
-
const tw = Math.
|
|
697
|
+
const tw = Math.max(process.stdout.columns || 80, 60);
|
|
630
698
|
if (dashboardStarted) {
|
|
631
|
-
const maxLen = tw -
|
|
699
|
+
const maxLen = tw - 8;
|
|
632
700
|
const entry = `${time} ${icons[type] || '·'} ${tagRaw ? tagRaw + ' ' : ''}${stripped}`;
|
|
633
701
|
recentLogs.push(entry.substring(0, maxLen));
|
|
634
702
|
scheduleRender();
|