diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..5990d9c --- /dev/null +++ b/.github/dependabot.yml @@ -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" diff --git a/src/api/config.py b/src/api/config.py index 4871dd6..b803408 100644 --- a/src/api/config.py +++ b/src/api/config.py @@ -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"), diff --git a/static/app.js b/static/app.js index 5a2b19b..7230323 100644 --- a/static/app.js +++ b/static/app.js @@ -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); @@ -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 ─────────────────────────────────────── @@ -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(); @@ -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(); + }); })(); diff --git a/static/index.html b/static/index.html index 473b5ad..1df98da 100644 --- a/static/index.html +++ b/static/index.html @@ -300,6 +300,20 @@