Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file

version: 2
updates:
- package-ecosystem: "" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "weekly"
23 changes: 23 additions & 0 deletions src/api/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,29 @@ def get_product_config() -> JSONResponse:
)


@router.get("/config/payout")
def get_payout_config() -> JSONResponse:
"""Return payout configuration including scheduler timing.

This endpoint is intentionally public (no authentication required) as it only
exposes non-sensitive timing and reward configuration that is useful for
displaying UI elements like countdown timers.
"""
cfg = get_config()
payout = cfg.payout
return JSONResponse(
{
"payout_amount_ban_per_kill": payout.payout_amount_ban_per_kill,
"scheduler_minutes": payout.scheduler_minutes,
"daily_payout_cap": payout.daily_payout_cap,
"weekly_payout_cap": payout.weekly_payout_cap,
"reset_tz": payout.reset_tz,
"settlement_order": payout.settlement_order,
"batch_size": payout.batch_size,
}
)


@router.get("/debug/yunite")
def debug_yunite(
discord_user_id: str = Query(..., description="Discord user ID to lookup"),
Expand Down
56 changes: 55 additions & 1 deletion static/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
let isAdmin = false;
let productConfig = null;
let isDryRun = false;
let schedulerIntervalMinutes = 20; // Default from configs/payout.yaml
let countdownStartTime = Date.now(); // Track when countdown started

// ── DOM refs ─────────────────────────────────────────
const $ = (sel) => document.querySelector(sel);
Expand Down Expand Up @@ -60,6 +62,17 @@
}
}
} catch (_) { /* config not critical */ }

// Load payout config for scheduler timing
try {
const r = await fetch("/config/payout");
if (r.ok) {
const payoutConfig = await r.json();
if (payoutConfig.scheduler_minutes) {
schedulerIntervalMinutes = payoutConfig.scheduler_minutes;
}
}
} catch (_) { /* payout config not critical */ }
}

// ── Navigation ───────────────────────────────────────
Expand Down Expand Up @@ -87,6 +100,10 @@
if (navEl) navEl.style.display = user ? "flex" : "none";
const userInfo = $(".user-info");
if (userInfo) userInfo.style.display = user ? "flex" : "none";

// Show/hide footer based on user login
const footer = $("#app-footer");
if (footer) footer.style.display = user ? "block" : "none";

// Load data for page
if (name === "dashboard" && user) loadDashboard();
Expand Down Expand Up @@ -518,6 +535,43 @@
setTimeout(() => el.classList.remove("visible"), 3000);
}

// ── Countdown Timers ─────────────────────────────────
function updateCountdowns() {
const now = Date.now();
const intervalMs = schedulerIntervalMinutes * 60 * 1000;

// Calculate time elapsed since countdown started
const elapsed = now - countdownStartTime;
// Calculate time until next run (cycles through the interval)
const msUntilNext = intervalMs - (elapsed % intervalMs);

// Format time as HH:MM:SS
const formatTime = (ms) => {
const totalSec = Math.floor(ms / 1000);
const hours = Math.floor(totalSec / 3600);
const minutes = Math.floor((totalSec % 3600) / 60);
const seconds = totalSec % 60;
return `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
};

const accrualEl = $("#countdown-accrual");
const settlementEl = $("#countdown-settlement");

// Both accrual and settlement run together in the same scheduler cycle
if (accrualEl) accrualEl.textContent = formatTime(msUntilNext);
if (settlementEl) settlementEl.textContent = formatTime(msUntilNext);
}

function startCountdownTimers() {
// Update immediately
updateCountdowns();
// Then update every second
setInterval(updateCountdowns, 1000);
}

// ── Boot ─────────────────────────────────────────────
document.addEventListener("DOMContentLoaded", init);
document.addEventListener("DOMContentLoaded", () => {
init();
startCountdownTimers();
});
})();
14 changes: 14 additions & 0 deletions static/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,20 @@ <h2>🛠️ Admin Actions</h2>

</main>

<!-- Footer with countdowns -->
<footer id="app-footer" style="display:none;">
<div class="footer-content">
<div class="countdown-box">
<div class="countdown-label">Next Accrual Check</div>
<div class="countdown-value" id="countdown-accrual">--:--:--</div>
</div>
<div class="countdown-box">
<div class="countdown-label">Next Settlement</div>
<div class="countdown-value" id="countdown-settlement">--:--:--</div>
</div>
</div>
</footer>

<script src="/static/app.js"></script>
</body>
</html>
48 changes: 48 additions & 0 deletions static/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,50 @@ td.tx-hash {
color: var(--text-muted);
}

/* ── Footer with countdowns ──────────────────────────── */
footer {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: var(--surface);
border-top: 1px solid var(--border);
padding: 12px 24px;
z-index: 100;
}
.footer-content {
max-width: 960px;
margin: 0 auto;
display: flex;
justify-content: center;
gap: 32px;
}
.countdown-box {
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
}
.countdown-label {
font-size: 11px;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: .5px;
font-weight: 600;
}
.countdown-value {
font-size: 18px;
font-weight: 700;
color: var(--accent);
font-family: "SF Mono", "Fira Code", monospace;
letter-spacing: 1px;
}

/* Add padding to main to account for fixed footer */
body {
padding-bottom: 70px;
}

/* ── Responsive ──────────────────────────────────────── */
@media (max-width: 640px) {
main { padding: 16px; }
Expand All @@ -386,4 +430,8 @@ td.tx-hash {
.login-page h1 { font-size: 36px; }
.how-it-works { gap: 10px; }
.step-arrow { font-size: 16px; }
footer { padding: 10px 16px; }
.footer-content { gap: 20px; }
.countdown-label { font-size: 10px; }
.countdown-value { font-size: 16px; }
}
Loading