dankgrinder 7.66.0 → 7.67.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/lib/dashboard.js +129 -172
- package/lib/grinder.js +1 -3
- package/package.json +1 -1
package/lib/dashboard.js
CHANGED
|
@@ -1,43 +1,37 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* DankGrinder CLI Dashboard
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Simple, stable terminal dashboard using \r overwrite + clear-line.
|
|
5
|
+
* No full screen clear — just overwrites rows from top to bottom.
|
|
5
6
|
*
|
|
6
7
|
* Usage:
|
|
7
8
|
* const { renderDashboard } = require('./dashboard');
|
|
8
|
-
* renderDashboard(context); // called from grinder.js
|
|
9
|
+
* renderDashboard(context); // called every ~1s from grinder.js
|
|
9
10
|
*
|
|
10
|
-
* Context
|
|
11
|
-
* {
|
|
12
|
-
* workers, dashboardStarted, dashboardRendering, dashboardLines,
|
|
13
|
-
* totalBalance, totalCoins, totalCommands, startTime,
|
|
14
|
-
* sessionPeakCoins, isNewHigh, recentLogs, globalCmdRate,
|
|
15
|
-
* earningsHistory, lastEarningsSample,
|
|
16
|
-
* CLOUD_MODE, CLUSTER_ENABLED, PKG_VERSION,
|
|
17
|
-
* AccountWorker, PULSE_CHARS, getSpinner, gradientText,
|
|
18
|
-
* rgb, c, BOX,
|
|
19
|
-
* }
|
|
11
|
+
* Context: same as before, see docstring in file header.
|
|
20
12
|
*/
|
|
21
13
|
|
|
22
14
|
'use strict';
|
|
23
15
|
|
|
24
|
-
// ──
|
|
25
|
-
let _c, _rgb,
|
|
16
|
+
// ── Local refs (set once per render via initColors) ────────────
|
|
17
|
+
let _c, _rgb, _getSpinner, _gradientText;
|
|
26
18
|
|
|
27
19
|
function initColors(ctx) {
|
|
28
20
|
_c = ctx.c;
|
|
29
21
|
_rgb = ctx.rgb;
|
|
30
|
-
_BOX = ctx.BOX;
|
|
31
22
|
_getSpinner = ctx.getSpinner;
|
|
32
23
|
_gradientText = ctx.gradientText;
|
|
33
24
|
}
|
|
34
25
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
26
|
+
// ── Helpers ─────────────────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
function vis(s) {
|
|
29
|
+
return String(s).replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
38
30
|
}
|
|
39
31
|
|
|
40
|
-
|
|
32
|
+
function pad(str, width) {
|
|
33
|
+
return str + ' '.repeat(Math.max(0, width - vis(str)));
|
|
34
|
+
}
|
|
41
35
|
|
|
42
36
|
function fmtCoins(n) {
|
|
43
37
|
if (n >= 1e9) return `${(n / 1e9).toFixed(2)}B`;
|
|
@@ -59,29 +53,26 @@ function fmtUptime(startTime) {
|
|
|
59
53
|
// ── Render ──────────────────────────────────────────────────────
|
|
60
54
|
|
|
61
55
|
function renderDashboard(ctx) {
|
|
62
|
-
if (!ctx.dashboardStarted || ctx.workers.length === 0 || ctx.dashboardRendering
|
|
56
|
+
if (!ctx.dashboardStarted || ctx.workers.length === 0 || ctx.dashboardRendering) return;
|
|
63
57
|
|
|
64
58
|
initColors(ctx);
|
|
65
59
|
|
|
60
|
+
const _ = _c.reset;
|
|
66
61
|
const tw = Math.max(process.stdout.columns || 80, 60);
|
|
67
62
|
|
|
68
|
-
// Color palette
|
|
69
|
-
const A = _rgb(139, 92, 246);
|
|
70
|
-
const G = _rgb(52, 211, 153);
|
|
71
|
-
const
|
|
72
|
-
const
|
|
73
|
-
const
|
|
74
|
-
const
|
|
75
|
-
const
|
|
76
|
-
const Y = _rgb(251, 191, 36); // yellow
|
|
63
|
+
// Color palette
|
|
64
|
+
const A = _rgb(139, 92, 246);
|
|
65
|
+
const G = _rgb(52, 211, 153);
|
|
66
|
+
const Au = _rgb(255, 215, 0);
|
|
67
|
+
const O = _rgb(251, 146, 60);
|
|
68
|
+
const Cy = _rgb(34, 211, 238);
|
|
69
|
+
const R = _rgb(239, 68, 68);
|
|
70
|
+
const Y = _rgb(251, 191, 36);
|
|
77
71
|
const W = _c.white;
|
|
78
72
|
const D = _c.dim;
|
|
79
|
-
const
|
|
80
|
-
const BOX = _BOX;
|
|
81
|
-
|
|
82
|
-
const rows = [];
|
|
73
|
+
const CL = _c.clearLine;
|
|
83
74
|
|
|
84
|
-
// ── Aggregate
|
|
75
|
+
// ── Aggregate ────────────────────────────────────────────────
|
|
85
76
|
let aggBalance = 0, aggCoins = 0, aggCommands = 0, aggErrors = 0;
|
|
86
77
|
for (const w of ctx.workers) {
|
|
87
78
|
aggBalance += (w.stats.balance || 0) + (w.stats.bankBalance || 0);
|
|
@@ -90,121 +81,107 @@ function renderDashboard(ctx) {
|
|
|
90
81
|
aggErrors += w.stats.errors || 0;
|
|
91
82
|
}
|
|
92
83
|
const successRate = aggCommands > 0 ? Math.round(((aggCommands - aggErrors) / aggCommands) * 100) : 100;
|
|
84
|
+
const elapsedHrs = (Date.now() - ctx.startTime) / 3_600_000;
|
|
85
|
+
const coinsPerHr = elapsedHrs > 0.01 ? Math.round(aggCoins / elapsedHrs) : 0;
|
|
86
|
+
const cpmVal = ctx.globalCmdRate.getRate().toFixed(1);
|
|
87
|
+
|
|
88
|
+
const activeCount = ctx.workers.filter(w => w.running && !w.paused && !w.dashboardPaused).length;
|
|
89
|
+
const invalidCount = ctx.workers.filter(w => w._tokenInvalid).length;
|
|
90
|
+
const pausedCount = ctx.workers.filter(w => w.paused || w.dashboardPaused).length;
|
|
91
|
+
const recovCount = ctx.workers.filter(w => w._recoveryAttempts > 0 && w._errorCooldownUntil > Date.now()).length;
|
|
92
|
+
const memMB = Math.round((process.memoryUsage?.rss?.() ?? process.memoryUsage().rss) / 1048576);
|
|
93
|
+
const spin = _getSpinner('braille');
|
|
94
|
+
|
|
95
|
+
// ── Line builder helper ───────────────────────────────────────
|
|
96
|
+
// prefix + content padded to tw + suffix
|
|
97
|
+
function line(prefix, content, suffix) {
|
|
98
|
+
const raw = content.replace(/\x1b\[[0-9;]*m/g, '');
|
|
99
|
+
const padLen = Math.max(0, tw - vis(prefix) - raw.length - vis(suffix));
|
|
100
|
+
return `${prefix}${content}${' '.repeat(padLen)}${suffix}`;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const dash = D + '─'.repeat(tw) + _;
|
|
104
|
+
|
|
105
|
+
// ── HEADER ────────────────────────────────────────────────────
|
|
106
|
+
const title = _c.bold + _gradientText(' DANK GRINDER ', [139, 92, 246], [52, 211, 153]) + _;
|
|
107
|
+
const modeTag = ctx.CLOUD_MODE ? `${Cy}CLOUD${_}` : (ctx.CLUSTER_ENABLED ? `${Cy}CLUSTER${_}` : `${D}local${_}`);
|
|
108
|
+
const activeTag = `${G}${activeCount}/${W}${ctx.workers.length}${_} active`;
|
|
109
|
+
const invTag = invalidCount > 0 ? ` ${R}${invalidCount} inv${_}` : '';
|
|
110
|
+
const pauseTag = pausedCount > 0 ? ` ${Y}${pausedCount} pause${_}` : '';
|
|
111
|
+
const recovTag = recovCount > 0 ? ` ${O}${recovCount} recov${_}` : '';
|
|
112
|
+
|
|
113
|
+
const h1 = `${title} ${D}v${ctx.PKG_VERSION}${_} ${G}${spin}${_} ${D}up${_} ${W}${fmtUptime(ctx.startTime)}${_} ${D}bal${_} ${Au}⏣${_}${W}${fmtCoins(aggBalance)}${_} ${D}/h${_} ${G}+⏣${fmtCoins(coinsPerHr)}${_} ${activeTag}${invTag}${pauseTag}${recovTag} ${D}${aggCommands}${_}cmds ${D}${cpmVal}${_}/min ${D}${memMB}MB ${modeTag}`;
|
|
114
|
+
|
|
115
|
+
// ── ACCOUNTS TABLE ───────────────────────────────────────────
|
|
116
|
+
const colNum = 4;
|
|
117
|
+
const colSts = 3;
|
|
118
|
+
const colName = Math.max(16, Math.min(22, Math.floor(tw * 0.22)));
|
|
119
|
+
const colBal = 10;
|
|
120
|
+
const colLvl = 5;
|
|
121
|
+
const colLs = 4;
|
|
122
|
+
const colEarn = 9;
|
|
123
|
+
const colAct = Math.max(8, tw - colNum - colSts - colName - colBal - colLvl - colLs - colEarn - 10);
|
|
124
|
+
const gap = ' ';
|
|
125
|
+
|
|
126
|
+
const headers = `${D}${pad('#', colNum)}${_}${gap}${D}${pad('S', colSts)}${_}${gap}${_gradientText(pad('Account', colName), [139, 92, 246], [96, 165, 250])}${_}${gap}${D}${pad('Balance', colBal)}${_}${gap}${D}${pad('Lvl', colLvl)}${_}${gap}${D}${pad('LS', colLs)}${_}${gap}${D}${pad('Earned', colEarn)}${_}${gap}${D}${pad('Activity', colAct)}${_}`;
|
|
127
|
+
|
|
128
|
+
const sorted = [...ctx.workers].sort((a, b) => a.idx - b.idx);
|
|
129
|
+
const maxRows = Math.max(5, Math.min(sorted.length, Math.floor((process.stdout.rows || 24) - 12)));
|
|
130
|
+
const visible = sorted.slice(0, maxRows);
|
|
131
|
+
const extraRows = sorted.length - maxRows;
|
|
132
|
+
|
|
133
|
+
// ── LIVE FEED ─────────────────────────────────────────────────
|
|
134
|
+
const logs = ctx.recentLogs.toArray();
|
|
93
135
|
|
|
94
|
-
|
|
95
|
-
const
|
|
96
|
-
const cpmVal = ctx.globalCmdRate.getRate().toFixed(1);
|
|
136
|
+
// ── FOOTER ───────────────────────────────────────────────────
|
|
137
|
+
const footer = `${modeTag} ${D}P=pause R=resume S=status Q=quit${_}`;
|
|
97
138
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
const recovCount = ctx.workers.filter(w => w._recoveryAttempts > 0 && w._errorCooldownUntil > Date.now()).length;
|
|
139
|
+
// ── WRITE ALL ROWS (overwrite from top) ─────────────────────
|
|
140
|
+
// We write \r + clear line to overwrite each line in place.
|
|
141
|
+
// No full screen clear — just overwrite. Simple and reliable.
|
|
102
142
|
|
|
103
|
-
|
|
104
|
-
? ctx.workers.reduce((s, w) => s + (w._lastPing < 200 ? 1 : w._lastPing < 500 ? 0.5 : 0), 0) / ctx.workers.length : 1;
|
|
105
|
-
const netDot = netQ > 0.8 ? `${G}●${_}` : netQ > 0.5 ? `${Y}●${_}` : `${R}●${_}`;
|
|
143
|
+
let rowNum = 0;
|
|
106
144
|
|
|
107
|
-
|
|
108
|
-
|
|
145
|
+
function writeRow(content) {
|
|
146
|
+
process.stdout.write(`${CL}\r${content}\n`);
|
|
147
|
+
rowNum++;
|
|
148
|
+
}
|
|
109
149
|
|
|
110
|
-
//
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
150
|
+
// 1. Header
|
|
151
|
+
writeRow(line(A + '╔', D + '═'.repeat(tw - 2), A + '╗'));
|
|
152
|
+
writeRow(line(`${A}║ `, h1, `${A} ║`));
|
|
153
|
+
writeRow(line(A + '╠', D + '═'.repeat(tw - 2), A + '╣'));
|
|
154
|
+
writeRow(line(`${A}║ `, `${D}session${_} ${G}${fmtCoins(aggCoins)}${_} ${D}coins${_} ${G}${successRate}%${_} ${D}success${_} ${W}${fmtCoins(ctx.sessionPeakCoins)}${_} ${D}peak${_}${ctx.isNewHigh ? ` ${Au}★ NEW HIGH${_}` : ''}`, `${A} ║`));
|
|
155
|
+
writeRow(line(A + '╚', D + '═'.repeat(tw - 2), A + '╝'));
|
|
114
156
|
|
|
115
|
-
//
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
const h1 = [
|
|
120
|
-
titleBar, versionTag, spin,
|
|
121
|
-
`${D}up${_} ${W}${fmtUptime(ctx.startTime)}${_}`,
|
|
122
|
-
netDot, `${D}net${_}`,
|
|
123
|
-
`${Au}⏣${_}${W}${fmtCoins(aggBalance)}${_} ${D}bal${_}`,
|
|
124
|
-
`${G}${coinsPerHr >= 0 ? '+' : ''}⏣${fmtCoins(coinsPerHr)}${_}/h`,
|
|
125
|
-
`${G}${activeCount}${_}/${W}${ctx.workers.length}${_} ${D}active${_}`,
|
|
126
|
-
invalidCount > 0 ? ` ${R}${invalidCount} inv${_}` : '',
|
|
127
|
-
pausedCount > 0 ? ` ${Y}${pausedCount} pause${_}` : '',
|
|
128
|
-
recovCount > 0 ? ` ${O}${recovCount} recov${_}` : '',
|
|
129
|
-
` ${D}${aggCommands}${_} cmds`,
|
|
130
|
-
` ${D}${cpmVal}${_}/min`,
|
|
131
|
-
` ${D}mem${_} ${memMB}MB`,
|
|
132
|
-
modeTag,
|
|
133
|
-
].filter(Boolean).join(' ');
|
|
134
|
-
|
|
135
|
-
rows.push(`${A}${BOX.dv}${_} ${pad(h1, tw - 4)} ${A}${BOX.dv}${_}`);
|
|
136
|
-
|
|
137
|
-
// Divider
|
|
138
|
-
rows.push(`${A}${BOX.teeD}${BOX.dh.repeat(tw - 2)}${BOX.teeU}${_}`);
|
|
139
|
-
|
|
140
|
-
// Stats row
|
|
141
|
-
const statsRow = [
|
|
142
|
-
`${G}${fmtCoins(aggCoins)}${_} ${D}coins${_}`,
|
|
143
|
-
`${G}${successRate}%${_} ${D}success${_}`,
|
|
144
|
-
`${W}${fmtCoins(ctx.sessionPeakCoins)}${_} ${D}peak${_}`,
|
|
145
|
-
ctx.isNewHigh ? `${Au}★ NEW HIGH${_}` : '',
|
|
146
|
-
].filter(Boolean).join(` ${D}│${_} `);
|
|
147
|
-
|
|
148
|
-
const statLine = ` ${D}session${_} ${statsRow}`;
|
|
149
|
-
rows.push(`${A}${BOX.dv}${_}${pad(statLine, tw - 4)}${A}${BOX.dv}${_}`);
|
|
150
|
-
|
|
151
|
-
// Bottom header border
|
|
152
|
-
rows.push(`${A}${BOX.dbl}${BOX.dh.repeat(tw - 2)}${BOX.dbr}${_}`);
|
|
153
|
-
|
|
154
|
-
// ── ACCOUNTS TABLE ──────────────────────────────────────────
|
|
155
|
-
const colNum = 4;
|
|
156
|
-
const colSts = 3;
|
|
157
|
-
const colName = Math.max(14, Math.min(22, Math.floor(tw * 0.20)));
|
|
158
|
-
const colBal = 10;
|
|
159
|
-
const colLvl = 5;
|
|
160
|
-
const colLs = 4;
|
|
161
|
-
const colEarn = 9;
|
|
162
|
-
const colAct = Math.max(6, tw - colNum - colSts - colName - colBal - colLvl - colLs - colEarn - 18);
|
|
163
|
-
const gap = 2;
|
|
164
|
-
const colGap = ' '.repeat(gap);
|
|
165
|
-
|
|
166
|
-
// Column header
|
|
167
|
-
const headers = [
|
|
168
|
-
`${D}${pad('#', colNum)}${_}`,
|
|
169
|
-
`${D}${pad('S', colSts)}${_}`,
|
|
170
|
-
`${_gradientText(pad('Account', colName), [139, 92, 246], [96, 165, 250])}${_}`,
|
|
171
|
-
`${D}${pad('Balance', colBal)}${_}`,
|
|
172
|
-
`${D}${pad('Lvl', colLvl)}${_}`,
|
|
173
|
-
`${D}${pad('LS', colLs)}${_}`,
|
|
174
|
-
`${D}${pad('Earned', colEarn)}${_}`,
|
|
175
|
-
`${D}${pad('Activity', colAct)}${_}`,
|
|
176
|
-
].join(colGap);
|
|
177
|
-
|
|
178
|
-
rows.push(`${A}${BOX.dtl}${BOX.dh.repeat(tw - 2)}${BOX.dtr}${_}`);
|
|
179
|
-
rows.push(`${A}${BOX.dv}${_} ${headers} ${A}${BOX.dv}${_}`);
|
|
180
|
-
rows.push(`${A}${BOX.teeD}${BOX.h.repeat(tw - 2)}${BOX.teeU}${_}`);
|
|
181
|
-
|
|
182
|
-
const sorted = [...ctx.workers].sort((a, b) => a.idx - b.idx);
|
|
183
|
-
const maxRows = Math.max(4, Math.min(sorted.length, Math.floor((process.stdout.rows || 24) - 14)));
|
|
184
|
-
const visible = sorted.slice(0, maxRows);
|
|
157
|
+
// 2. Table header
|
|
158
|
+
writeRow(line(A + '╔', D + '─'.repeat(tw - 2), A + '╗'));
|
|
159
|
+
writeRow(line(`${A}║ `, headers, `${A} ║`));
|
|
160
|
+
writeRow(line(A + '╟', D + '─'.repeat(tw - 2), A + '╢'));
|
|
185
161
|
|
|
162
|
+
// 3. Account rows
|
|
186
163
|
for (const wk of visible) {
|
|
187
164
|
const isRecov = wk._recoveryAttempts > 0 && wk._errorCooldownUntil > Date.now();
|
|
188
165
|
|
|
189
166
|
let stsIcon;
|
|
190
|
-
if (wk._tokenInvalid)
|
|
191
|
-
else if (!wk.running)
|
|
192
|
-
else if (isRecov)
|
|
193
|
-
else if (wk.paused)
|
|
194
|
-
else if (wk.dashboardPaused)
|
|
195
|
-
else if (wk.busy)
|
|
196
|
-
else
|
|
197
|
-
|
|
198
|
-
const name
|
|
167
|
+
if (wk._tokenInvalid) stsIcon = `${R}✗${_}`;
|
|
168
|
+
else if (!wk.running) stsIcon = `${D}○${_}`;
|
|
169
|
+
else if (isRecov) stsIcon = `${O}${_getSpinner('braille').substring(0, 1)}${_}`;
|
|
170
|
+
else if (wk.paused) stsIcon = `${R}⏸${_}`;
|
|
171
|
+
else if (wk.dashboardPaused) stsIcon = `${Y}⏸${_}`;
|
|
172
|
+
else if (wk.busy) stsIcon = `${G}${_getSpinner('pulse').substring(0, 1)}${_}`;
|
|
173
|
+
else stsIcon = `${G}●${_}`;
|
|
174
|
+
|
|
175
|
+
const name = (wk.username || wk.account.label || '?').substring(0, colName);
|
|
199
176
|
const nameStr = `${wk.color}${name}${_}`;
|
|
200
177
|
const balStr = wk.stats.balance > 0 ? `${Au}⏣${_}${W}${fmtCoins(wk.stats.balance)}${_}` : `${D}⏣-${_}`;
|
|
201
178
|
const lvl = wk._level || 0;
|
|
202
179
|
const lvlStr = lvl > 0 ? `${Cy}L${lvl}${_}` : `${D}L???${_}`;
|
|
203
180
|
const ls = wk._lifesavers;
|
|
204
181
|
let lsStr;
|
|
205
|
-
if (ls === 0)
|
|
182
|
+
if (ls === 0) lsStr = `${R}♥${ls}${_}`;
|
|
206
183
|
else if (ls != null && ls <= 2) lsStr = `${Y}♥${ls}${_}`;
|
|
207
|
-
else if (ls != null)
|
|
184
|
+
else if (ls != null) lsStr = `${G}♥${ls}${_}`;
|
|
208
185
|
else {
|
|
209
186
|
const p = ctx.PULSE_CHARS[Math.floor(Date.now() / 400) % ctx.PULSE_CHARS.length];
|
|
210
187
|
lsStr = `${D}${p}♥?${_}`;
|
|
@@ -212,41 +189,38 @@ function renderDashboard(ctx) {
|
|
|
212
189
|
|
|
213
190
|
const earn = wk.stats.coins || 0;
|
|
214
191
|
const earnStr = earn > 0 ? `${G}+${fmtCoins(earn)}${_}` : `${D}────${_}`;
|
|
215
|
-
const actRaw
|
|
216
|
-
const actStr
|
|
217
|
-
const numStr
|
|
192
|
+
const actRaw = (wk.lastStatus || 'ready').replace(/\x1b\[[0-9;]*m/g, '').substring(0, colAct);
|
|
193
|
+
const actStr = `${D}${pad(actRaw, colAct)}${_}`;
|
|
194
|
+
const numStr = `${D}${pad(String(wk.idx + 1), colNum)}${_}`;
|
|
218
195
|
|
|
219
|
-
const
|
|
196
|
+
const rowStr = [
|
|
220
197
|
numStr, pad(stsIcon, colSts), pad(nameStr, colName),
|
|
221
198
|
pad(balStr, colBal), pad(lvlStr, colLvl), pad(lsStr, colLs),
|
|
222
199
|
pad(earnStr, colEarn), actStr,
|
|
223
|
-
].join(
|
|
200
|
+
].join(gap);
|
|
224
201
|
|
|
225
|
-
|
|
202
|
+
writeRow(line(`${A}║ `, rowStr, `${A} ║`));
|
|
226
203
|
}
|
|
227
204
|
|
|
228
|
-
if (
|
|
229
|
-
|
|
230
|
-
rows.push(`${A}${BOX.dv}${_} ${D}+${rest} more${_}${' '.repeat(Math.max(0, tw - 18))}${A}${BOX.dv}${_}`);
|
|
205
|
+
if (extraRows > 0) {
|
|
206
|
+
writeRow(line(`${A}║ `, `${D}+${extraRows} more${_}${' '.repeat(Math.max(0, tw - 16))}`, `${A} ║`));
|
|
231
207
|
}
|
|
232
208
|
|
|
233
|
-
|
|
209
|
+
writeRow(line(A + '╚', D + '─'.repeat(tw - 2), A + '╝'));
|
|
234
210
|
|
|
235
|
-
//
|
|
236
|
-
const logs = ctx.recentLogs.toArray();
|
|
211
|
+
// 4. Live feed
|
|
237
212
|
if (logs.length > 0) {
|
|
238
213
|
const feedTitle = `${_gradientText(' LIVE FEED ', [139, 92, 246], [52, 211, 153])}${_} ${G}${_getSpinner('pulse')}${_}`;
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
rows.push(`${A}${BOX.teeD}${BOX.h.repeat(tw - 2)}${BOX.teeU}${_}`);
|
|
214
|
+
writeRow(line(A + '╔', D + '─'.repeat(tw - 2), A + '╗'));
|
|
215
|
+
writeRow(line(`${A}║ `, feedTitle, `${A} ║`));
|
|
216
|
+
writeRow(line(A + '╟', D + '─'.repeat(tw - 2), A + '╢'));
|
|
243
217
|
|
|
244
218
|
for (const entry of logs) {
|
|
245
219
|
let lineText;
|
|
246
220
|
if (typeof entry === 'string') {
|
|
247
221
|
lineText = entry;
|
|
248
222
|
} else if (entry && typeof entry === 'object') {
|
|
249
|
-
const ts = entry.ts ? new Date(entry.ts).toLocaleTimeString('en-US', { hour12: false
|
|
223
|
+
const ts = entry.ts ? new Date(entry.ts).toLocaleTimeString('en-US', { hour12: false }) : '';
|
|
250
224
|
const user = entry.username ? String(entry.username) : '';
|
|
251
225
|
const cmd = entry.command ? `[${entry.command}]` : '';
|
|
252
226
|
const resp = entry.response || '';
|
|
@@ -254,34 +228,17 @@ function renderDashboard(ctx) {
|
|
|
254
228
|
} else {
|
|
255
229
|
lineText = String(entry);
|
|
256
230
|
}
|
|
257
|
-
|
|
231
|
+
writeRow(line(`${A}║ `, `${D}${lineText}${_}`, `${A} ║`));
|
|
258
232
|
}
|
|
259
|
-
|
|
233
|
+
writeRow(line(A + '╚', D + '─'.repeat(tw - 2), A + '╝'));
|
|
260
234
|
}
|
|
261
235
|
|
|
262
|
-
//
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
rows.push(`${A}${BOX.dtl}${BOX.dh.repeat(tw - 2)}${BOX.dtr}${_}`);
|
|
268
|
-
rows.push(`${A}${BOX.dv}${_} ${pad(footerFull, tw - 4)} ${A}${BOX.dv}${_}`);
|
|
269
|
-
rows.push(`${A}${BOX.dbl}${BOX.dh.repeat(tw - 2)}${BOX.dbr}${_}`);
|
|
270
|
-
|
|
271
|
-
// ── Flush ────────────────────────────────────────────────────
|
|
272
|
-
// Clear entire screen buffer first so old startup logs don't bleed through
|
|
273
|
-
process.stdout.write('\x1b[2J\x1b[H');
|
|
274
|
-
process.stdout.write(_c.hide);
|
|
275
|
-
for (const row of rows) {
|
|
276
|
-
process.stdout.write(`${_c.clearLine}\r${row}\n`);
|
|
277
|
-
}
|
|
278
|
-
process.stdout.write(_c.show);
|
|
279
|
-
const leftover = ctx.dashboardLines - rows.length;
|
|
280
|
-
if (leftover > 0) {
|
|
281
|
-
process.stdout.write(`\x1b[${leftover}B\x1b[2K`);
|
|
282
|
-
}
|
|
236
|
+
// 5. Footer
|
|
237
|
+
writeRow(line(A + '╔', D + '─'.repeat(tw - 2), A + '╗'));
|
|
238
|
+
writeRow(line(`${A}║ `, footer, `${A} ║`));
|
|
239
|
+
writeRow(line(A + '╚', D + '─'.repeat(tw - 2), A + '╝'));
|
|
283
240
|
|
|
284
|
-
return
|
|
241
|
+
return rowNum;
|
|
285
242
|
}
|
|
286
243
|
|
|
287
244
|
module.exports = { renderDashboard };
|
package/lib/grinder.js
CHANGED
|
@@ -3358,10 +3358,8 @@ async function start(apiKey, apiUrl, opts = {}) {
|
|
|
3358
3358
|
setDashboardActive(true);
|
|
3359
3359
|
// Setup keyboard shortcuts
|
|
3360
3360
|
setupKeyboardShortcuts();
|
|
3361
|
-
//
|
|
3362
|
-
process.stdout.write('\x1b[2J\x1b[H');
|
|
3361
|
+
// Cursor hide to reduce visual noise during renders
|
|
3363
3362
|
process.stdout.write(c.hide);
|
|
3364
|
-
dashboardLines = 0;
|
|
3365
3363
|
|
|
3366
3364
|
// Re-render on terminal resize so layout adapts to window size
|
|
3367
3365
|
process.stdout.on('resize', () => {
|