Rate monitor: daily-budget ring around the Spotify avatar
The payload has carried daily_budget {used, limit, exhausted} forever and the
dashboard rendered none of it. The avatar disc now wears a conic progress rim
that fills as the day's real-API budget is spent — green to 70%, amber to 95%,
red after — and flips purple once the worker has bridged to Spotify Free for
the rest of the day (using_free now included in the emit payload). Tooltip
carries the exact used/limit numbers.
This commit is contained in:
parent
09af31154e
commit
7efb5da893
3 changed files with 49 additions and 0 deletions
|
|
@ -35033,6 +35033,9 @@ def _emit_rate_monitor_loop():
|
||||||
}
|
}
|
||||||
if svc_key == 'spotify' and enr.get('daily_budget'):
|
if svc_key == 'spotify' and enr.get('daily_budget'):
|
||||||
entry['worker']['daily_budget'] = enr['daily_budget']
|
entry['worker']['daily_budget'] = enr['daily_budget']
|
||||||
|
# Budget ring styling: purple when the worker has
|
||||||
|
# bridged to Spotify Free after spending the budget.
|
||||||
|
entry['worker']['using_free'] = bool(enr.get('using_free'))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.debug("enrichment worker status build failed: %s", e)
|
logger.debug("enrichment worker status build failed: %s", e)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -383,6 +383,28 @@ function _renderEqualizerBars(grid, data) {
|
||||||
}
|
}
|
||||||
bar.classList.toggle('cooldown', cooling);
|
bar.classList.toggle('cooldown', cooling);
|
||||||
|
|
||||||
|
// Daily-budget ring (Spotify is the only service with a real daily
|
||||||
|
// cap): a conic rim inside the avatar disc fills as the budget is
|
||||||
|
// spent — green → amber → red, purple once the worker has bridged
|
||||||
|
// to Spotify Free for the rest of the day.
|
||||||
|
const budget = worker.daily_budget;
|
||||||
|
if (budget && budget.limit > 0) {
|
||||||
|
const bPct = Math.min(1, (budget.used || 0) / budget.limit);
|
||||||
|
const bridged = !!worker.using_free && !!budget.exhausted;
|
||||||
|
bar.classList.add('has-budget');
|
||||||
|
bar.style.setProperty('--eq-budget', String(Math.max(bPct, 0.02)));
|
||||||
|
bar.style.setProperty('--eq-budget-color',
|
||||||
|
bridged ? '#a78bfa' : bPct < 0.7 ? '#4ade80' : bPct < 0.95 ? '#fbbf24' : '#ef4444');
|
||||||
|
const avatar = bar.querySelector('.rate-eq-avatar');
|
||||||
|
if (avatar) {
|
||||||
|
avatar.title = bridged
|
||||||
|
? `Daily budget spent — running on Spotify Free (${budget.used}/${budget.limit})`
|
||||||
|
: `Daily API budget: ${budget.used}/${budget.limit}`;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
bar.classList.remove('has-budget');
|
||||||
|
}
|
||||||
|
|
||||||
_eqDisplay[svc] = { value, pct, peak, peakAt, rlTotal, cooling };
|
_eqDisplay[svc] = { value, pct, peak, peakAt, rlTotal, cooling };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -63083,6 +63083,30 @@ body.reduce-effects .dash-card::after {
|
||||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);
|
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Daily-budget ring — a conic progress rim hugging the avatar disc's inner
|
||||||
|
* edge, filling as the day's API budget is spent. Color comes from JS
|
||||||
|
* (green → amber → red, purple when bridged to Spotify Free). Drawn inside
|
||||||
|
* the disc because the avatar clips overflow. */
|
||||||
|
.rate-eq-avatar::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: conic-gradient(
|
||||||
|
var(--eq-budget-color, #4ade80) calc(var(--eq-budget, 0) * 360deg),
|
||||||
|
transparent 0);
|
||||||
|
-webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 3.5px), #000 calc(100% - 2.5px));
|
||||||
|
mask: radial-gradient(farthest-side, transparent calc(100% - 3.5px), #000 calc(100% - 2.5px));
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.4s;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rate-eq.has-budget .rate-eq-avatar::after {
|
||||||
|
opacity: 0.95;
|
||||||
|
}
|
||||||
|
|
||||||
.rate-eq.active .rate-eq-avatar {
|
.rate-eq.active .rate-eq-avatar {
|
||||||
animation: rate-eq-avatar-halo 2.8s ease-in-out infinite;
|
animation: rate-eq-avatar-halo 2.8s ease-in-out infinite;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue