From 5a607c8c8bfe4757ab945ff1f38348286bd23e1a Mon Sep 17 00:00:00 2001 From: "Claude (Chronicler #63)" Date: Mon, 6 Apr 2026 11:47:20 +0000 Subject: [PATCH] refactor(modpackchecker): Batch 3+4 fixes - frontend, admin, docs BATCH 3 - Frontend & UI: wrapper.tsx (Console Widget): - FIXED: API URL from .../ext/modpackchecker/check to .../check - Added 429 rate limit handling with user-friendly message UpdateBadge.tsx (Dashboard Badge): - Added 60-second TTL to global cache (was infinite) - Prevents stale data during client-side navigation admin/view.blade.php: - Disabled Discord webhook field (PRO TIER badge) - Disabled Check Interval field (PRO TIER badge) - Added support callout linking to Discord BATCH 4 - Documentation: README.md: - Fixed architecture diagram (server_uuid, status string) - Added app/Services/ModpackApiService.php to file structure - Fixed API endpoint URLs throughout - Updated installation for BuiltByBit (.blueprint package) - Updated 'Adding New Platform' instructions for Service pattern - Added Support section with Discord link - Changed license to explicit commercial terms NEW: CHANGELOG.md - Version history for future updates - Documents v1.0.0 features Reviewed by: Gemini AI (Architecture Consultant) Signed-off-by: Claude (Chronicler #63) --- .../blueprint-extension/CHANGELOG.md | 32 +++ .../blueprint-extension/README.md | 223 ++++++---------- .../blueprint-extension/admin/view.blade.php | 42 +-- .../views/dashboard/UpdateBadge.tsx | 244 ++---------------- .../views/server/wrapper.tsx | 19 +- 5 files changed, 167 insertions(+), 393 deletions(-) create mode 100644 services/modpack-version-checker/blueprint-extension/CHANGELOG.md diff --git a/services/modpack-version-checker/blueprint-extension/CHANGELOG.md b/services/modpack-version-checker/blueprint-extension/CHANGELOG.md new file mode 100644 index 0000000..ea75353 --- /dev/null +++ b/services/modpack-version-checker/blueprint-extension/CHANGELOG.md @@ -0,0 +1,32 @@ +# Changelog + +All notable changes to ModpackChecker will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [1.0.0] - 2026-04-06 + +### Added +- Initial release +- Dashboard badge showing update status (🟠 update available / 🟢 up to date) +- Console widget with "Check for Updates" button +- Support for 4 modpack platforms: + - CurseForge (requires API key) + - Modrinth (no key required) + - FTB via modpacks.ch (no key required) + - Technic (no key required, dynamic build detection) +- Admin panel for CurseForge API key configuration +- Cron command for automated background checks +- Rate limiting: 2 manual checks per minute per server +- 60-second TTL cache for dashboard badges +- Foreign key cascade delete for data integrity + +### Architecture +- Centralized `ModpackApiService` for all platform API calls +- Cached Technic launcher build number (12-hour TTL) +- Database table `modpackchecker_servers` for status caching + +--- + +*Fire + Frost + Foundation = Where Love Builds Legacy* 🔥❄️💙 diff --git a/services/modpack-version-checker/blueprint-extension/README.md b/services/modpack-version-checker/blueprint-extension/README.md index 49f1939..a97c179 100644 --- a/services/modpack-version-checker/blueprint-extension/README.md +++ b/services/modpack-version-checker/blueprint-extension/README.md @@ -1,8 +1,8 @@ # ModpackChecker — Pterodactyl Blueprint Extension **Version:** 1.0.0 -**Author:** Firefrost Gaming -**License:** Proprietary (Commercial product for BuiltByBit) +**Author:** Firefrost Gaming / Frostystyle +**License:** Commercial License - Unauthorized redistribution, resale, or sharing of this source code is strictly prohibited. A Pterodactyl Panel extension that checks modpack versions across CurseForge, Modrinth, FTB, and Technic platforms. Shows update status on the dashboard and provides manual version checks from the server console. @@ -19,7 +19,7 @@ A Pterodactyl Panel extension that checks modpack versions across CurseForge, Mo 7. [Development](#development) 8. [API Reference](#api-reference) 9. [Troubleshooting](#troubleshooting) -10. [Design Decisions](#design-decisions) +10. [Support](#support) --- @@ -30,17 +30,18 @@ A Pterodactyl Panel extension that checks modpack versions across CurseForge, Mo - **🟠 Orange (Fire #FF6B35):** Update available - **🟢 Teal (Frost #4ECDC4):** Up to date - Hover for version details tooltip -- Single API call per page load (cached globally) +- Single API call per page load (cached with 60s TTL) ### Console Widget - "Check for Updates" button on each server's console page - Real-time version check against platform API -- Shows modpack name, current version, and latest version +- Rate limited: 2 checks per minute per server +- Shows modpack name and latest version ### Admin Panel - Configure CurseForge API key -- View extension status -- (Future: Rate limit settings, notification preferences) +- View supported platforms +- PRO features: Discord notifications, custom check intervals ### Supported Platforms | Platform | ID Type | API Key Required | Status | @@ -50,8 +51,6 @@ A Pterodactyl Panel extension that checks modpack versions across CurseForge, Mo | FTB | Numeric modpack ID | ❌ No | ✅ Working | | Technic | URL slug | ❌ No | ✅ Working | -> **Note:** Technic requires a dynamic build number parameter. The extension automatically fetches the current launcher build from Technic's API to avoid 401 errors. - --- ## Architecture Overview @@ -59,17 +58,11 @@ A Pterodactyl Panel extension that checks modpack versions across CurseForge, Mo ``` ┌─────────────────────────────────────────────────────────────────────────────┐ │ MODPACK VERSION CHECKER │ -│ Architecture Diagram │ └─────────────────────────────────────────────────────────────────────────────┘ ┌─────────────────────────────────────┐ │ CRON JOB (runs every 4-6 hrs) │ │ php artisan modpackchecker:check │ - │ │ - │ • Finds servers with MODPACK_* │ - │ • Calls platform APIs one by one │ - │ • 2-second delay between calls │ - │ • Stores results in database │ └──────────────────┬──────────────────┘ │ ▼ @@ -77,10 +70,10 @@ A Pterodactyl Panel extension that checks modpack versions across CurseForge, Mo │ DATABASE CACHE │ │ modpackchecker_servers table │ │ │ - │ • server_id, server_uuid │ + │ • server_uuid │ │ • platform, modpack_id │ │ • current_version, latest_version │ - │ • update_available (boolean) │ + │ • status (string) │ │ • last_checked timestamp │ └──────────────────┬──────────────────┘ │ @@ -92,116 +85,76 @@ A Pterodactyl Panel extension that checks modpack versions across CurseForge, Mo │ (UpdateBadge.tsx) │ │ (wrapper.tsx) │ │ │ │ │ │ • Reads from cache ONLY │ │ • Manual "Check" button │ - │ • Never calls external │ │ • LIVE API call │ - │ • One API call per page │ │ • Single server only │ - │ • Shows 🟠 or 🟢 dot │ │ • Shows full details │ + │ • 60-second TTL │ │ • LIVE API call │ + │ • Shows 🟠 or 🟢 dot │ │ • Rate limited (2/min) │ └───────────────────────────┘ └───────────────────────────┘ ``` -### Why This Architecture? - -**The Problem:** A panel with 50 servers and 20 active users could generate thousands of API calls per day if each dashboard view triggered live checks. - -**The Solution:** -1. Cron job handles external API calls with rate limiting -2. Dashboard reads from local cache only -3. Console provides on-demand checks for specific servers - -This was validated by Gemini AI during architectural review. - --- ## File Structure ``` blueprint-extension/ -├── README.md # This file -├── conf.yml # Blueprint configuration -├── build.sh # Injection script (runs during blueprint -build) -├── icon.png # Extension icon (128x128, Gemini-designed) +├── README.md +├── CHANGELOG.md +├── conf.yml +├── build.sh +├── icon.png │ -├── app/ # Merges into Pterodactyl's app/ via requests.app -│ ├── Console/ -│ │ └── Commands/ -│ │ └── CheckModpackUpdates.php # Laravel cron command -│ └── Http/ -│ └── Controllers/ -│ └── ModpackAPIController.php # API endpoints (manualCheck, getStatus) +├── app/ +│ ├── Console/Commands/ +│ │ └── CheckModpackUpdates.php +│ ├── Http/Controllers/ +│ │ └── ModpackAPIController.php +│ └── Services/ +│ └── ModpackApiService.php │ ├── admin/ -│ ├── controller.php # Admin panel logic -│ └── view.blade.php # Admin panel UI +│ ├── controller.php +│ └── view.blade.php │ -├── database/ -│ └── migrations/ -│ └── 2026_04_06_000000_create_modpackchecker_servers_table.php +├── database/migrations/ +│ └── 2026_04_06_000000_create_modpackchecker_servers_table.php │ ├── routes/ -│ └── client.php # API route definitions +│ └── client.php │ └── views/ - ├── server/ - │ └── wrapper.tsx # Console "Check for Updates" widget - └── dashboard/ - └── UpdateBadge.tsx # Dashboard status dot component + ├── server/wrapper.tsx + └── dashboard/UpdateBadge.tsx ``` -### Why the `app/` folder structure? - -Blueprint's `requests.app` field merges the contents of your `app/` folder directly into Pterodactyl's `app/` directory. This means: - -1. **PSR-4 Autoloading:** Your classes are automatically found by Laravel's autoloader -2. **Correct Namespaces:** Use `Pterodactyl\Http\Controllers` (not custom Blueprint namespaces) -3. **Case Sensitivity:** Linux requires exact folder casing — `Controllers/` not `controllers/` - -This architecture was validated through painful debugging and Gemini AI consultation. - --- ## Installation -### Prerequisites -- Pterodactyl Panel v1.11+ -- Blueprint Framework (beta-2026-01 or newer) -- PHP 8.1+ -- Node.js 18+ +### Standard Installation (BuiltByBit) -### Steps +1. Upload the downloaded `modpackchecker.blueprint` file to your Pterodactyl panel's root directory (usually `/var/www/pterodactyl`). -1. **Copy extension to Blueprint directory:** +2. Run the Blueprint installation command: ```bash - cp -r blueprint-extension /var/www/pterodactyl/.blueprint/extensions/modpackchecker - chown -R www-data:www-data /var/www/pterodactyl/.blueprint/extensions/modpackchecker + blueprint -install modpackchecker ``` -2. **Build the extension:** - ```bash - cd /var/www/pterodactyl - blueprint -build - ``` +3. The framework will automatically inject the frontend components and rebuild the panel assets. -3. **Compile frontend assets:** +4. Set up the cron job for automated checks: ```bash - export NODE_OPTIONS=--openssl-legacy-provider - yarn build:production - ``` - -4. **Run database migration:** - ```bash - php artisan migrate - ``` - -5. **Restart PHP-FPM:** - ```bash - systemctl restart php8.3-fpm - ``` - -6. **Set up cron job:** - ```bash - # Add to /etc/crontab or crontab -e + # Add to crontab 0 */6 * * * www-data cd /var/www/pterodactyl && php artisan modpackchecker:check >> /dev/null 2>&1 ``` +### Developer/Manual Installation + +If installing from raw source: +```bash +cp -r blueprint-extension /var/www/pterodactyl/.blueprint/extensions/modpackchecker +chown -R www-data:www-data /var/www/pterodactyl/.blueprint/extensions/modpackchecker +blueprint -build +``` + --- ## Configuration @@ -218,9 +171,9 @@ For modpack detection, set these variables in your server's egg: ### CurseForge API Key -CurseForge requires an API key. To configure: +CurseForge requires an API key: -1. Apply for API access at https://docs.curseforge.com/ +1. Apply for API access at https://console.curseforge.com/ 2. Go to **Admin Panel → Extensions → ModpackChecker** 3. Enter your API key and save @@ -229,7 +182,7 @@ CurseForge requires an API key. To configure: ## Usage ### Dashboard Badge -No action needed — badges appear automatically for servers that: +Badges appear automatically for servers that: - Have `MODPACK_PLATFORM` egg variable set - Have been checked by the cron job at least once @@ -241,55 +194,41 @@ No action needed — badges appear automatically for servers that: ### Cron Command Run manually for testing: ```bash -cd /var/www/pterodactyl php artisan modpackchecker:check ``` -Output: -``` -Starting modpack update check... -Found 12 servers with modpack configuration -Checking: ATM9 Server (a1b2c3d4-...) - 🟠 UPDATE AVAILABLE: All The Mods 9 - 0.2.60 -Checking: Vanilla+ (e5f6g7h8-...) - 🟢 Up to date: Vanilla+ - 1.2.0 -Modpack update check complete! -``` - --- ## Development -### Local Development -1. Enable Blueprint developer mode in admin panel -2. Make changes in `.blueprint/dev/` or `.blueprint/extensions/modpackchecker/` -3. Run `blueprint -build` after changes -4. Run `yarn build:production` for frontend changes +### Adding a New Platform + +1. Open `app/Services/ModpackApiService.php` +2. Add your new platform check method (e.g., `private function checkNewPlatform(string $id): array`) +3. Add the platform key to the `match()` statement inside the `fetchLatestVersion()` method +4. The Controller and Cron Command will automatically inherit the new logic +5. Update this README to reflect the newly supported platform ### Testing API Endpoints ```bash # Manual check (requires auth token) -curl -X POST "https://panel.example.com/api/client/servers/{uuid}/ext/modpackchecker/check" \ +curl -X POST "https://panel.example.com/api/client/extensions/modpackchecker/servers/{uuid}/check" \ -H "Authorization: Bearer {token}" -# Get all statuses (requires auth token) +# Get all statuses curl "https://panel.example.com/api/client/extensions/modpackchecker/status" \ -H "Authorization: Bearer {token}" ``` -### Adding a New Platform -1. Add check method to `ModpackAPIController.php` (e.g., `checkNewPlatform()`) -2. Add to the `match()` statement in `checkVersion()` -3. Add same method to `CheckModpackUpdates.php` -4. Update this README - --- ## API Reference -### POST /api/client/servers/{server}/ext/modpackchecker/check +### POST `/api/client/extensions/modpackchecker/servers/{server}/check` -Manual version check for a specific server. Makes live API call. +Manual version check for a specific server. Triggers a live API call to the modpack platform. + +**Rate Limit:** 2 requests per minute per server **Response:** ```json @@ -303,7 +242,7 @@ Manual version check for a specific server. Makes live API call. } ``` -### GET /api/client/extensions/modpackchecker/status +### GET `/api/client/extensions/modpackchecker/status` Get cached status for all servers accessible to the authenticated user. @@ -315,12 +254,6 @@ Get cached status for all servers accessible to the authenticated user. "modpack_name": "All The Mods 9", "current_version": "0.2.51", "latest_version": "0.2.60" - }, - "e5f6g7h8-...": { - "update_available": false, - "modpack_name": "Adrenaserver", - "current_version": "1.7.0", - "latest_version": "1.7.0" } } ``` @@ -344,36 +277,24 @@ Get cached status for all servers accessible to the authenticated user. 2. Verify controller namespace: `Pterodactyl\Http\Controllers` 3. Restart PHP-FPM: `systemctl restart php8.3-fpm` -### Build.sh not running -1. Ensure file is executable: `chmod +x build.sh` -2. Check Blueprint version supports build scripts -3. Run manually from panel root: `bash .blueprint/extensions/modpackchecker/build.sh` +### "Rate limit reached" message +The manual check is limited to 2 requests per minute per server. Wait 60 seconds and try again. --- -## Design Decisions +## Support -### Why cache instead of live checks? -Rate limits. CurseForge allows ~1000 requests/day for personal keys. A busy panel could exhaust that in hours without caching. - -### Why 2-second sleep in cron? -Prevents burst traffic to APIs. 50 servers × 2 seconds = ~2 minute runtime, which is acceptable for a background job. - -### Why inline styles in React? -The component is injected into Pterodactyl's build. Adding CSS classes would require modifying their build pipeline. Inline styles are self-contained. - -### Why separate console widget and dashboard badge? -Different use cases: -- Dashboard: Quick overview, needs to be fast → cached -- Console: User wants current info → live API call is acceptable +**Need help?** Join our Discord for support: +- **Discord:** [discord.firefrostgaming.com](https://discord.firefrostgaming.com) +- **Email:** dev@firefrostgaming.com +- **Website:** [firefrostgaming.com](https://firefrostgaming.com) --- ## Credits **Developed by:** Firefrost Gaming / Frostystyle -**Contact:** dev@firefrostgaming.com -**Website:** https://firefrostgaming.com +**Architecture Review:** Gemini AI **Part of Firefrost Gaming** *Fire + Frost + Foundation = Where Love Builds Legacy* 🔥❄️💙 diff --git a/services/modpack-version-checker/blueprint-extension/admin/view.blade.php b/services/modpack-version-checker/blueprint-extension/admin/view.blade.php index c90c87b..e331495 100644 --- a/services/modpack-version-checker/blueprint-extension/admin/view.blade.php +++ b/services/modpack-version-checker/blueprint-extension/admin/view.blade.php @@ -92,32 +92,26 @@ - +

Check Interval + PRO TIER

- Professional
- + + +

- How often to automatically check for modpack updates. - More frequent checks use more API quota. + Standard tier is locked to daily cron checks. + Upgrade to Professional for more frequent automated checks.

@@ -126,14 +120,14 @@
- +

Discord Notifications + PRO TIER

- Professional
@@ -145,10 +139,10 @@ value="{{ $discord_webhook_url }}" placeholder="https://discord.com/api/webhooks/..." class="form-control" + disabled />

- Receive alerts when modpack updates are available. - Create a webhook in your Discord server settings. + Upgrade to Professional to receive automated update alerts in your Discord server.

@@ -205,4 +199,16 @@
+ + +
+
+
+

Need Help?

+

+ Join our Discord for support: discord.firefrostgaming.com +

+
+
+
diff --git a/services/modpack-version-checker/blueprint-extension/views/dashboard/UpdateBadge.tsx b/services/modpack-version-checker/blueprint-extension/views/dashboard/UpdateBadge.tsx index 7f49e32..d019765 100644 --- a/services/modpack-version-checker/blueprint-extension/views/dashboard/UpdateBadge.tsx +++ b/services/modpack-version-checker/blueprint-extension/views/dashboard/UpdateBadge.tsx @@ -11,307 +11,113 @@ * - 🟠 Fire (#FF6B35): Update available for this modpack * - No dot: Server has no modpack configured or not yet checked * - * Colors match Firefrost Gaming brand palette. - * - * CRITICAL ARCHITECTURE DECISION (Gemini-approved): - * This component is intentionally "dumb" - it ONLY reads from a local cache. - * It NEVER makes external API calls to modpack platforms. - * - * WHY? - * Imagine a dashboard with 20 servers. If each server row triggered a live - * API call to CurseForge/Modrinth, you'd make 20+ requests on every page load. - * Multiply by multiple users refreshing throughout the day, and you'd hit - * rate limits within hours. - * - * Instead, this component: - * 1. Makes ONE API call to our backend (/api/client/extensions/modpackchecker/status) - * 2. Backend returns cached data from modpackchecker_servers table - * 3. Results are cached globally in JS memory for the session - * 4. Each badge instance reads from this shared cache - * - * The actual modpack checking is done by a cron job (CheckModpackUpdates.php) - * that runs on a schedule with proper rate limiting. - * - * INJECTION: - * This component is injected into ServerRow.tsx by build.sh during - * `blueprint -build`. It receives the server UUID as a prop. - * - * DEPENDENCIES: - * - @/api/http: Pterodactyl's axios wrapper (handles auth automatically) - * - Backend endpoint: GET /api/client/extensions/modpackchecker/status + * CACHING: + * Uses a global cache with 60-second TTL to prevent excessive API calls + * while ensuring reasonably fresh data during navigation. * * @package ModpackChecker Blueprint Extension * @author Firefrost Gaming / Frostystyle * @version 1.0.0 - * @see CheckModpackUpdates.php (cron that populates the cache) - * @see ModpackAPIController::getStatus() (backend endpoint) - * ============================================================================= */ import React, { useEffect, useState } from 'react'; import http from '@/api/http'; -// ============================================================================= -// TYPE DEFINITIONS -// ============================================================================= - -/** - * Status data for a single server, as returned from the backend. - * - * This mirrors the structure returned by ModpackAPIController::getStatus(). - * All fields except update_available are optional because servers might - * have partial data (e.g., error during last check). - */ interface ServerStatus { - /** True if latest_version differs from current_version */ update_available: boolean; - /** Human-readable modpack name (e.g., "All The Mods 9") */ modpack_name?: string; - /** Version currently installed on the server */ current_version?: string; - /** Latest version available from the platform */ latest_version?: string; } -/** - * The full cache structure - keyed by server UUID. - * - * Example: - * { - * "a1b2c3d4-e5f6-7890-...": { update_available: true, modpack_name: "ATM9", ... }, - * "b2c3d4e5-f6g7-8901-...": { update_available: false, modpack_name: "Vanilla+", ... } - * } - */ interface StatusCache { [serverUuid: string]: ServerStatus; } -// ============================================================================= -// GLOBAL CACHE -// ============================================================================= -// -// These module-level variables are shared across ALL instances of UpdateBadge. -// This is intentional - we want exactly ONE API call for the entire dashboard, -// not one per server row. -// -// The pattern here is a simple "fetch-once" cache: -// - globalCache: Stores the data once fetched -// - fetchPromise: Prevents duplicate in-flight requests -// -// LIFECYCLE: -// 1. First UpdateBadge mounts → fetchAllStatuses() called → API request starts -// 2. Second UpdateBadge mounts → fetchAllStatuses() returns same promise -// 3. API response arrives → globalCache populated, all badges update -// 4. Any future calls → return globalCache immediately (no API call) -// -// CACHE INVALIDATION: -// Currently, cache persists until page refresh. For real-time updates, -// you could add a timeout or expose a refresh function. -// ============================================================================= - -/** Cached status data. Null until first fetch completes. */ +// Global cache with TTL support let globalCache: StatusCache | null = null; - -/** Promise for in-flight fetch. Prevents duplicate requests. */ +let cacheTimestamp: number = 0; let fetchPromise: Promise | null = null; +const CACHE_TTL_MS = 60000; // 60 seconds + /** - * Fetch all server statuses from the backend. - * - * This function implements a "fetch-once" pattern: - * - First call: Makes the API request, stores result in globalCache - * - Subsequent calls: Returns cached data immediately - * - Concurrent calls: Wait for the same promise (no duplicate requests) - * - * ENDPOINT: GET /api/client/extensions/modpackchecker/status - * - * The backend (ModpackAPIController::getStatus) returns only servers - * that the authenticated user has access to, so there's no data leakage. - * - * ERROR HANDLING: - * On failure, we cache an empty object rather than null. This prevents - * retry spam - if the API is down, we don't hammer it on every badge mount. - * Users can refresh the page to retry. - * - * @returns Promise resolving to the status cache (keyed by server UUID) + * Fetch all server statuses with 60-second TTL caching. */ const fetchAllStatuses = async (): Promise => { - // FAST PATH: Return cached data if available - if (globalCache !== null) { + const now = Date.now(); + + // Return cached data if it exists AND is less than 60 seconds old + if (globalCache !== null && (now - cacheTimestamp < CACHE_TTL_MS)) { return globalCache; } - - // DEDUP PATH: If a fetch is already in progress, wait for it - // instead of starting another request + + // If a fetch is already in progress, wait for it if (fetchPromise !== null) { return fetchPromise; } - // FETCH PATH: Start a new API request - // This is the only code path that actually makes an HTTP call + // Start new fetch fetchPromise = http.get('/api/client/extensions/modpackchecker/status') .then((response) => { - // Store the response data in the global cache globalCache = response.data || {}; + cacheTimestamp = Date.now(); return globalCache; }) .catch((error) => { - // Log the error for debugging console.error('ModpackChecker: Failed to fetch status', error); - // Cache empty object to prevent retry spam - // Users can refresh the page to try again globalCache = {}; return globalCache; }) .finally(() => { - // Clear the promise reference - // This allows future retries if cache is manually cleared fetchPromise = null; }); return fetchPromise; }; -// ============================================================================= -// COMPONENT -// ============================================================================= - -/** - * Props for the UpdateBadge component. - */ interface UpdateBadgeProps { - /** - * The UUID of the server to show status for. - * This is passed from ServerRow.tsx where the component is injected. - * Example: "a1b2c3d4-e5f6-7890-abcd-ef1234567890" - */ serverUuid: string; } -/** - * Dashboard badge showing modpack update status. - * - * Renders a small colored dot next to the server name: - * - Orange (#FF6B35) = Update available (Fire brand color) - * - Teal (#4ECDC4) = Up to date (Frost brand color) - * - Nothing = No modpack configured or not yet checked by cron - * - * Includes a native browser tooltip on hover showing version details. - * - * USAGE (injected by build.sh, not manually added): - * ```tsx - *

{server.name}

- * ``` - * - * ACCESSIBILITY: - * - Uses aria-label for screen readers - * - Native title attribute provides tooltip for sighted users - * - Color is not the only indicator (tooltip shows text status) - */ const UpdateBadge: React.FC = ({ serverUuid }) => { - // ========================================================================= - // STATE - // ========================================================================= - - /** This specific server's status (extracted from global cache) */ const [status, setStatus] = useState(null); - - /** Loading state - true until we've checked the cache */ const [loading, setLoading] = useState(true); - // ========================================================================= - // DATA FETCHING - // ========================================================================= - useEffect(() => { - // Fetch from global cache (makes API call only on first badge mount) fetchAllStatuses() .then((cache) => { - // Extract this server's status from the cache - // Will be null/undefined if server not in cache setStatus(cache[serverUuid] || null); setLoading(false); }); - }, [serverUuid]); // Re-run if serverUuid changes (unlikely in practice) + }, [serverUuid]); - // ========================================================================= - // RENDER CONDITIONS - // ========================================================================= - - // Don't render anything while waiting for cache - // This prevents flicker - badges appear all at once when data arrives - if (loading) { - return null; - } - - // Don't render if no status data exists for this server - // This happens for servers that: - // - Don't have MODPACK_PLATFORM configured - // - Haven't been checked by the cron job yet - // - Had an error during their last check - if (!status) { + // Don't render while loading or if no status data + if (loading || !status || !status.modpack_name) { return null; } - // Don't render if we have a status entry but no modpack name - // This can happen if the check errored but created a partial record - if (!status.modpack_name) { - return null; - } - - // ========================================================================= - // STYLING - // ========================================================================= - - /** - * Inline styles for the dot indicator. - * - * Using inline styles rather than CSS classes because: - * 1. This component is injected into Pterodactyl's build - * 2. We can't easily add to their CSS pipeline - * 3. Inline styles are self-contained and reliable - * - * BRAND COLORS (Firefrost Gaming): - * - Fire: #FF6B35 (used for "update available" - action needed) - * - Frost: #4ECDC4 (used for "up to date" - all good) - */ const dotStyle: React.CSSProperties = { - // Layout display: 'inline-block', width: '8px', height: '8px', - borderRadius: '50%', // Perfect circle - marginLeft: '8px', // Space from server name - - // Color based on update status + borderRadius: '50%', + marginLeft: '8px', backgroundColor: status.update_available ? '#FF6B35' : '#4ECDC4', - - // Subtle glow effect for visual polish - // Uses rgba version of the same color at 50% opacity boxShadow: status.update_available - ? '0 0 4px rgba(255, 107, 53, 0.5)' // Fire glow - : '0 0 4px rgba(78, 205, 196, 0.5)', // Frost glow + ? '0 0 4px rgba(255, 107, 53, 0.5)' + : '0 0 4px rgba(78, 205, 196, 0.5)', }; - /** - * Tooltip text shown on hover. - * - * Uses native browser tooltip (title attribute) for simplicity. - * A fancier tooltip library could be added later if needed. - */ const tooltipText = status.update_available ? `Update available: ${status.latest_version}` : `Up to date: ${status.latest_version}`; - // ========================================================================= - // RENDER - // ========================================================================= - return ( ); }; diff --git a/services/modpack-version-checker/blueprint-extension/views/server/wrapper.tsx b/services/modpack-version-checker/blueprint-extension/views/server/wrapper.tsx index b9cecd0..68a5cbc 100644 --- a/services/modpack-version-checker/blueprint-extension/views/server/wrapper.tsx +++ b/services/modpack-version-checker/blueprint-extension/views/server/wrapper.tsx @@ -24,14 +24,23 @@ const ModpackVersionCard: React.FC = () => { setStatus('loading'); try { - const response = await http.post(`/api/client/extensions/modpackchecker/servers/${uuid}/ext/modpackchecker/check`); + // Updated to match Batch 1 route optimization + const response = await http.post(`/api/client/extensions/modpackchecker/servers/${uuid}/check`); setData(response.data); setStatus(response.data.success ? 'success' : 'error'); } catch (error: any) { - setData({ - success: false, - error: error.response?.data?.message || 'Failed to check for updates', - }); + // Handle 429 Rate Limit responses with user-friendly message + if (error.response?.status === 429) { + setData({ + success: false, + error: 'Rate limit reached. Please wait 60 seconds.', + }); + } else { + setData({ + success: false, + error: error.response?.data?.message || 'Failed to check for updates', + }); + } setStatus('error'); } };