Rate monitor: rate-limit bans drain down as a visible countdown
A banned service used to just tint red. The payload carries the seconds
remaining (rl_remaining), so the bar now locks into a cooldown state: the live
VU dims, a red column drains away as the ban ticks down (largest remaining
seen is latched as the denominator — only remaining is sent), and an m:ss
timer counts to recovery. The moment the ban expires the track flashes green
('recovered') and the VU takes the stage back. 'Back in 4:00', not
'something's red'.
This commit is contained in:
parent
12ad373a83
commit
09af31154e
2 changed files with 105 additions and 1 deletions
|
|
@ -237,6 +237,10 @@ function _renderEqualizerBars(grid, data) {
|
||||||
<div class="rate-eq-tip"></div>
|
<div class="rate-eq-tip"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="rate-eq-peak"></div>
|
<div class="rate-eq-peak"></div>
|
||||||
|
<div class="rate-eq-cooldown">
|
||||||
|
<div class="rate-eq-cooldown-fill"></div>
|
||||||
|
<div class="rate-eq-cooldown-time"></div>
|
||||||
|
</div>
|
||||||
<div class="rate-eq-value">0</div>
|
<div class="rate-eq-value">0</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="rate-eq-meta">
|
<div class="rate-eq-meta">
|
||||||
|
|
@ -350,7 +354,36 @@ function _renderEqualizerBars(grid, data) {
|
||||||
bar.classList.toggle('active', value > 0 || wStatus === 'running');
|
bar.classList.toggle('active', value > 0 || wStatus === 'running');
|
||||||
bar.classList.toggle('rate-limited', isRateLimited);
|
bar.classList.toggle('rate-limited', isRateLimited);
|
||||||
|
|
||||||
_eqDisplay[svc] = { value, pct, peak, peakAt };
|
// Cooldown drain — a ban isn't just "red", it's a COUNTDOWN. The
|
||||||
|
// payload only carries seconds remaining, so latch the largest value
|
||||||
|
// seen this ban as the denominator; the red column then drains away
|
||||||
|
// as the ban ticks down and the timer shows m:ss until recovery.
|
||||||
|
const rlRemaining = isRateLimited ? Math.max(0, Math.round(d.rl_remaining || 0)) : 0;
|
||||||
|
let rlTotal = prev.rlTotal || 0;
|
||||||
|
const cooling = rlRemaining > 0;
|
||||||
|
if (cooling) {
|
||||||
|
rlTotal = Math.max(rlTotal, rlRemaining);
|
||||||
|
const cdFill = bar.querySelector('.rate-eq-cooldown-fill');
|
||||||
|
if (cdFill) cdFill.style.height = `${(rlRemaining / rlTotal) * 100}%`;
|
||||||
|
const cdTime = bar.querySelector('.rate-eq-cooldown-time');
|
||||||
|
if (cdTime) {
|
||||||
|
const m = Math.floor(rlRemaining / 60);
|
||||||
|
const s = String(rlRemaining % 60).padStart(2, '0');
|
||||||
|
cdTime.textContent = `${m}:${s}`;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
rlTotal = 0;
|
||||||
|
// Recovery moment: the ban just ended — flash the bar back alive.
|
||||||
|
if (prev.cooling) {
|
||||||
|
bar.classList.remove('recovered');
|
||||||
|
void bar.offsetWidth; // restart the animation
|
||||||
|
bar.classList.add('recovered');
|
||||||
|
window.setTimeout(() => bar.classList.remove('recovered'), 1300);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bar.classList.toggle('cooldown', cooling);
|
||||||
|
|
||||||
|
_eqDisplay[svc] = { value, pct, peak, peakAt, rlTotal, cooling };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -63265,6 +63265,77 @@ body.reduce-effects .dash-card::after {
|
||||||
opacity: 0.75;
|
opacity: 0.75;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Cooldown drain — while a service is rate-limit banned, the track shows a
|
||||||
|
* red column that DRAINS as the ban ticks down plus an m:ss countdown.
|
||||||
|
* "Back in 4:00", not just "something's red". JS drives the fill height
|
||||||
|
* (remaining/total) and timer text; .cooldown gates visibility. */
|
||||||
|
.rate-eq-cooldown {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
transition: opacity 0.4s;
|
||||||
|
z-index: 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rate-eq.cooldown .rate-eq-cooldown {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rate-eq.cooldown .rate-eq-fill,
|
||||||
|
.rate-eq.cooldown .rate-eq-peak {
|
||||||
|
opacity: 0.18; /* the live VU yields the stage to the countdown */
|
||||||
|
}
|
||||||
|
|
||||||
|
.rate-eq-cooldown-fill {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(180deg,
|
||||||
|
rgba(239, 68, 68, 0.55) 0%,
|
||||||
|
rgba(239, 68, 68, 0.28) 60%,
|
||||||
|
rgba(239, 68, 68, 0.12) 100%);
|
||||||
|
border-radius: 0 0 11px 11px;
|
||||||
|
box-shadow: inset 0 1px 0 rgba(255, 120, 120, 0.5);
|
||||||
|
transition: height 0.9s linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rate-eq-cooldown-time {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
text-align: center;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 700;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
color: rgba(255, 200, 200, 0.95);
|
||||||
|
text-shadow: 0 0 8px rgba(239, 68, 68, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Recovery flash — the moment a ban expires the track blinks back alive */
|
||||||
|
.rate-eq.recovered .rate-eq-track {
|
||||||
|
animation: rate-eq-recovered 1.2s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes rate-eq-recovered {
|
||||||
|
0% {
|
||||||
|
border-color: rgba(74, 222, 128, 0.9);
|
||||||
|
box-shadow:
|
||||||
|
inset 0 1px 0 rgba(255, 255, 255, 0.04),
|
||||||
|
0 0 26px rgba(74, 222, 128, 0.55);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
border-color: rgba(255, 255, 255, 0.06);
|
||||||
|
box-shadow:
|
||||||
|
inset 0 1px 0 rgba(255, 255, 255, 0.04),
|
||||||
|
inset 0 -10px 24px rgba(0, 0, 0, 0.35);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Vertical shimmer scan — runs continuously when active */
|
/* Vertical shimmer scan — runs continuously when active */
|
||||||
.rate-eq-shimmer {
|
.rate-eq-shimmer {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue