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) <claude@firefrostgaming.com>
This commit is contained in:
@@ -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* 🔥❄️💙
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
# ModpackChecker — Pterodactyl Blueprint Extension
|
# ModpackChecker — Pterodactyl Blueprint Extension
|
||||||
|
|
||||||
**Version:** 1.0.0
|
**Version:** 1.0.0
|
||||||
**Author:** Firefrost Gaming
|
**Author:** Firefrost Gaming / Frostystyle
|
||||||
**License:** Proprietary (Commercial product for BuiltByBit)
|
**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.
|
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)
|
7. [Development](#development)
|
||||||
8. [API Reference](#api-reference)
|
8. [API Reference](#api-reference)
|
||||||
9. [Troubleshooting](#troubleshooting)
|
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
|
- **🟠 Orange (Fire #FF6B35):** Update available
|
||||||
- **🟢 Teal (Frost #4ECDC4):** Up to date
|
- **🟢 Teal (Frost #4ECDC4):** Up to date
|
||||||
- Hover for version details tooltip
|
- 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
|
### Console Widget
|
||||||
- "Check for Updates" button on each server's console page
|
- "Check for Updates" button on each server's console page
|
||||||
- Real-time version check against platform API
|
- 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
|
### Admin Panel
|
||||||
- Configure CurseForge API key
|
- Configure CurseForge API key
|
||||||
- View extension status
|
- View supported platforms
|
||||||
- (Future: Rate limit settings, notification preferences)
|
- PRO features: Discord notifications, custom check intervals
|
||||||
|
|
||||||
### Supported Platforms
|
### Supported Platforms
|
||||||
| Platform | ID Type | API Key Required | Status |
|
| 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 |
|
| FTB | Numeric modpack ID | ❌ No | ✅ Working |
|
||||||
| Technic | URL slug | ❌ 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
|
## Architecture Overview
|
||||||
@@ -59,17 +58,11 @@ A Pterodactyl Panel extension that checks modpack versions across CurseForge, Mo
|
|||||||
```
|
```
|
||||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||||
│ MODPACK VERSION CHECKER │
|
│ MODPACK VERSION CHECKER │
|
||||||
│ Architecture Diagram │
|
|
||||||
└─────────────────────────────────────────────────────────────────────────────┘
|
└─────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
|
||||||
┌─────────────────────────────────────┐
|
┌─────────────────────────────────────┐
|
||||||
│ CRON JOB (runs every 4-6 hrs) │
|
│ CRON JOB (runs every 4-6 hrs) │
|
||||||
│ php artisan modpackchecker:check │
|
│ 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 │
|
│ DATABASE CACHE │
|
||||||
│ modpackchecker_servers table │
|
│ modpackchecker_servers table │
|
||||||
│ │
|
│ │
|
||||||
│ • server_id, server_uuid │
|
│ • server_uuid │
|
||||||
│ • platform, modpack_id │
|
│ • platform, modpack_id │
|
||||||
│ • current_version, latest_version │
|
│ • current_version, latest_version │
|
||||||
│ • update_available (boolean) │
|
│ • status (string) │
|
||||||
│ • last_checked timestamp │
|
│ • last_checked timestamp │
|
||||||
└──────────────────┬──────────────────┘
|
└──────────────────┬──────────────────┘
|
||||||
│
|
│
|
||||||
@@ -92,116 +85,76 @@ A Pterodactyl Panel extension that checks modpack versions across CurseForge, Mo
|
|||||||
│ (UpdateBadge.tsx) │ │ (wrapper.tsx) │
|
│ (UpdateBadge.tsx) │ │ (wrapper.tsx) │
|
||||||
│ │ │ │
|
│ │ │ │
|
||||||
│ • Reads from cache ONLY │ │ • Manual "Check" button │
|
│ • Reads from cache ONLY │ │ • Manual "Check" button │
|
||||||
│ • Never calls external │ │ • LIVE API call │
|
│ • 60-second TTL │ │ • LIVE API call │
|
||||||
│ • One API call per page │ │ • Single server only │
|
│ • Shows 🟠 or 🟢 dot │ │ • Rate limited (2/min) │
|
||||||
│ • Shows 🟠 or 🟢 dot │ │ • Shows full details │
|
|
||||||
└───────────────────────────┘ └───────────────────────────┘
|
└───────────────────────────┘ └───────────────────────────┘
|
||||||
```
|
```
|
||||||
|
|
||||||
### 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
|
## File Structure
|
||||||
|
|
||||||
```
|
```
|
||||||
blueprint-extension/
|
blueprint-extension/
|
||||||
├── README.md # This file
|
├── README.md
|
||||||
├── conf.yml # Blueprint configuration
|
├── CHANGELOG.md
|
||||||
├── build.sh # Injection script (runs during blueprint -build)
|
├── conf.yml
|
||||||
├── icon.png # Extension icon (128x128, Gemini-designed)
|
├── build.sh
|
||||||
|
├── icon.png
|
||||||
│
|
│
|
||||||
├── app/ # Merges into Pterodactyl's app/ via requests.app
|
├── app/
|
||||||
│ ├── Console/
|
│ ├── Console/Commands/
|
||||||
│ │ └── Commands/
|
│ │ └── CheckModpackUpdates.php
|
||||||
│ │ └── CheckModpackUpdates.php # Laravel cron command
|
│ ├── Http/Controllers/
|
||||||
│ └── Http/
|
│ │ └── ModpackAPIController.php
|
||||||
│ └── Controllers/
|
│ └── Services/
|
||||||
│ └── ModpackAPIController.php # API endpoints (manualCheck, getStatus)
|
│ └── ModpackApiService.php
|
||||||
│
|
│
|
||||||
├── admin/
|
├── admin/
|
||||||
│ ├── controller.php # Admin panel logic
|
│ ├── controller.php
|
||||||
│ └── view.blade.php # Admin panel UI
|
│ └── view.blade.php
|
||||||
│
|
│
|
||||||
├── database/
|
├── database/migrations/
|
||||||
│ └── migrations/
|
│ └── 2026_04_06_000000_create_modpackchecker_servers_table.php
|
||||||
│ └── 2026_04_06_000000_create_modpackchecker_servers_table.php
|
|
||||||
│
|
│
|
||||||
├── routes/
|
├── routes/
|
||||||
│ └── client.php # API route definitions
|
│ └── client.php
|
||||||
│
|
│
|
||||||
└── views/
|
└── views/
|
||||||
├── server/
|
├── server/wrapper.tsx
|
||||||
│ └── wrapper.tsx # Console "Check for Updates" widget
|
└── dashboard/UpdateBadge.tsx
|
||||||
└── dashboard/
|
|
||||||
└── UpdateBadge.tsx # Dashboard status dot component
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 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
|
## Installation
|
||||||
|
|
||||||
### Prerequisites
|
### Standard Installation (BuiltByBit)
|
||||||
- Pterodactyl Panel v1.11+
|
|
||||||
- Blueprint Framework (beta-2026-01 or newer)
|
|
||||||
- PHP 8.1+
|
|
||||||
- Node.js 18+
|
|
||||||
|
|
||||||
### 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
|
```bash
|
||||||
cp -r blueprint-extension /var/www/pterodactyl/.blueprint/extensions/modpackchecker
|
blueprint -install modpackchecker
|
||||||
chown -R www-data:www-data /var/www/pterodactyl/.blueprint/extensions/modpackchecker
|
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **Build the extension:**
|
3. The framework will automatically inject the frontend components and rebuild the panel assets.
|
||||||
```bash
|
|
||||||
cd /var/www/pterodactyl
|
|
||||||
blueprint -build
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Compile frontend assets:**
|
4. Set up the cron job for automated checks:
|
||||||
```bash
|
```bash
|
||||||
export NODE_OPTIONS=--openssl-legacy-provider
|
# Add to crontab
|
||||||
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
|
|
||||||
0 */6 * * * www-data cd /var/www/pterodactyl && php artisan modpackchecker:check >> /dev/null 2>&1
|
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
|
## Configuration
|
||||||
@@ -218,9 +171,9 @@ For modpack detection, set these variables in your server's egg:
|
|||||||
|
|
||||||
### CurseForge API Key
|
### 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**
|
2. Go to **Admin Panel → Extensions → ModpackChecker**
|
||||||
3. Enter your API key and save
|
3. Enter your API key and save
|
||||||
|
|
||||||
@@ -229,7 +182,7 @@ CurseForge requires an API key. To configure:
|
|||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
### Dashboard Badge
|
### Dashboard Badge
|
||||||
No action needed — badges appear automatically for servers that:
|
Badges appear automatically for servers that:
|
||||||
- Have `MODPACK_PLATFORM` egg variable set
|
- Have `MODPACK_PLATFORM` egg variable set
|
||||||
- Have been checked by the cron job at least once
|
- 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
|
### Cron Command
|
||||||
Run manually for testing:
|
Run manually for testing:
|
||||||
```bash
|
```bash
|
||||||
cd /var/www/pterodactyl
|
|
||||||
php artisan modpackchecker:check
|
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
|
## Development
|
||||||
|
|
||||||
### Local Development
|
### Adding a New Platform
|
||||||
1. Enable Blueprint developer mode in admin panel
|
|
||||||
2. Make changes in `.blueprint/dev/` or `.blueprint/extensions/modpackchecker/`
|
1. Open `app/Services/ModpackApiService.php`
|
||||||
3. Run `blueprint -build` after changes
|
2. Add your new platform check method (e.g., `private function checkNewPlatform(string $id): array`)
|
||||||
4. Run `yarn build:production` for frontend changes
|
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
|
### Testing API Endpoints
|
||||||
```bash
|
```bash
|
||||||
# Manual check (requires auth token)
|
# 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}"
|
-H "Authorization: Bearer {token}"
|
||||||
|
|
||||||
# Get all statuses (requires auth token)
|
# Get all statuses
|
||||||
curl "https://panel.example.com/api/client/extensions/modpackchecker/status" \
|
curl "https://panel.example.com/api/client/extensions/modpackchecker/status" \
|
||||||
-H "Authorization: Bearer {token}"
|
-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
|
## 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:**
|
**Response:**
|
||||||
```json
|
```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.
|
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",
|
"modpack_name": "All The Mods 9",
|
||||||
"current_version": "0.2.51",
|
"current_version": "0.2.51",
|
||||||
"latest_version": "0.2.60"
|
"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`
|
2. Verify controller namespace: `Pterodactyl\Http\Controllers`
|
||||||
3. Restart PHP-FPM: `systemctl restart php8.3-fpm`
|
3. Restart PHP-FPM: `systemctl restart php8.3-fpm`
|
||||||
|
|
||||||
### Build.sh not running
|
### "Rate limit reached" message
|
||||||
1. Ensure file is executable: `chmod +x build.sh`
|
The manual check is limited to 2 requests per minute per server. Wait 60 seconds and try again.
|
||||||
2. Check Blueprint version supports build scripts
|
|
||||||
3. Run manually from panel root: `bash .blueprint/extensions/modpackchecker/build.sh`
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Design Decisions
|
## Support
|
||||||
|
|
||||||
### Why cache instead of live checks?
|
**Need help?** Join our Discord for support:
|
||||||
Rate limits. CurseForge allows ~1000 requests/day for personal keys. A busy panel could exhaust that in hours without caching.
|
- **Discord:** [discord.firefrostgaming.com](https://discord.firefrostgaming.com)
|
||||||
|
- **Email:** dev@firefrostgaming.com
|
||||||
### Why 2-second sleep in cron?
|
- **Website:** [firefrostgaming.com](https://firefrostgaming.com)
|
||||||
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
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Credits
|
## Credits
|
||||||
|
|
||||||
**Developed by:** Firefrost Gaming / Frostystyle
|
**Developed by:** Firefrost Gaming / Frostystyle
|
||||||
**Contact:** dev@firefrostgaming.com
|
**Architecture Review:** Gemini AI
|
||||||
**Website:** https://firefrostgaming.com
|
|
||||||
|
|
||||||
**Part of Firefrost Gaming**
|
**Part of Firefrost Gaming**
|
||||||
*Fire + Frost + Foundation = Where Love Builds Legacy* 🔥❄️💙
|
*Fire + Frost + Foundation = Where Love Builds Legacy* 🔥❄️💙
|
||||||
|
|||||||
@@ -92,32 +92,26 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Check Interval -->
|
<!-- Check Interval (PRO TIER) -->
|
||||||
<div class="col-xs-12 col-md-6">
|
<div class="col-xs-12 col-md-6">
|
||||||
<div class="box box-info">
|
<div class="box box-info">
|
||||||
<div class="box-header with-border">
|
<div class="box-header with-border">
|
||||||
<h3 class="box-title">
|
<h3 class="box-title">
|
||||||
<i class="fa fa-clock-o"></i> Check Interval
|
<i class="fa fa-clock-o"></i> Check Interval
|
||||||
|
<span class="label label-warning" style="margin-left: 10px;">PRO TIER</span>
|
||||||
</h3>
|
</h3>
|
||||||
<span class="label label-warning" style="margin-left: 10px;">Professional</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="box-body">
|
<div class="box-body">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="control-label">Automatic Check Frequency</label>
|
<label class="control-label">Automatic Check Frequency</label>
|
||||||
<select class="form-control" name="check_interval" id="check_interval">
|
<select class="form-control" name="check_interval" id="check_interval" disabled>
|
||||||
<option value="daily" @if($check_interval == 'daily') selected @endif>
|
<option value="daily" selected>Daily (24 Hours)</option>
|
||||||
Daily (Recommended)
|
<option value="12h">Every 12 Hours</option>
|
||||||
</option>
|
<option value="6h">Every 6 Hours</option>
|
||||||
<option value="12h" @if($check_interval == '12h') selected @endif>
|
|
||||||
Every 12 Hours
|
|
||||||
</option>
|
|
||||||
<option value="6h" @if($check_interval == '6h') selected @endif>
|
|
||||||
Every 6 Hours
|
|
||||||
</option>
|
|
||||||
</select>
|
</select>
|
||||||
<p class="text-muted small" style="margin-top: 8px;">
|
<p class="text-muted small" style="margin-top: 8px;">
|
||||||
How often to automatically check for modpack updates.
|
Standard tier is locked to daily cron checks.
|
||||||
More frequent checks use more API quota.
|
Upgrade to Professional for more frequent automated checks.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -126,14 +120,14 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<!-- Discord Webhook -->
|
<!-- Discord Webhook (PRO TIER) -->
|
||||||
<div class="col-xs-12 col-md-6">
|
<div class="col-xs-12 col-md-6">
|
||||||
<div class="box box-success">
|
<div class="box box-success">
|
||||||
<div class="box-header with-border">
|
<div class="box-header with-border">
|
||||||
<h3 class="box-title">
|
<h3 class="box-title">
|
||||||
<i class="fa fa-bell"></i> Discord Notifications
|
<i class="fa fa-bell"></i> Discord Notifications
|
||||||
|
<span class="label label-warning" style="margin-left: 10px;">PRO TIER</span>
|
||||||
</h3>
|
</h3>
|
||||||
<span class="label label-warning" style="margin-left: 10px;">Professional</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="box-body">
|
<div class="box-body">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
@@ -145,10 +139,10 @@
|
|||||||
value="{{ $discord_webhook_url }}"
|
value="{{ $discord_webhook_url }}"
|
||||||
placeholder="https://discord.com/api/webhooks/..."
|
placeholder="https://discord.com/api/webhooks/..."
|
||||||
class="form-control"
|
class="form-control"
|
||||||
|
disabled
|
||||||
/>
|
/>
|
||||||
<p class="text-muted small" style="margin-top: 8px;">
|
<p class="text-muted small" style="margin-top: 8px;">
|
||||||
Receive alerts when modpack updates are available.
|
Upgrade to Professional to receive automated update alerts in your Discord server.
|
||||||
Create a webhook in your Discord server settings.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -205,4 +199,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Support -->
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-12">
|
||||||
|
<div class="callout callout-warning">
|
||||||
|
<h4><i class="fa fa-life-ring"></i> Need Help?</h4>
|
||||||
|
<p style="margin-bottom: 0;">
|
||||||
|
Join our Discord for support: <a href="https://discord.firefrostgaming.com" target="_blank">discord.firefrostgaming.com</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -11,307 +11,113 @@
|
|||||||
* - 🟠 Fire (#FF6B35): Update available for this modpack
|
* - 🟠 Fire (#FF6B35): Update available for this modpack
|
||||||
* - No dot: Server has no modpack configured or not yet checked
|
* - No dot: Server has no modpack configured or not yet checked
|
||||||
*
|
*
|
||||||
* Colors match Firefrost Gaming brand palette.
|
* CACHING:
|
||||||
*
|
* Uses a global cache with 60-second TTL to prevent excessive API calls
|
||||||
* CRITICAL ARCHITECTURE DECISION (Gemini-approved):
|
* while ensuring reasonably fresh data during navigation.
|
||||||
* 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
|
|
||||||
*
|
*
|
||||||
* @package ModpackChecker Blueprint Extension
|
* @package ModpackChecker Blueprint Extension
|
||||||
* @author Firefrost Gaming / Frostystyle <dev@firefrostgaming.com>
|
* @author Firefrost Gaming / Frostystyle <dev@firefrostgaming.com>
|
||||||
* @version 1.0.0
|
* @version 1.0.0
|
||||||
* @see CheckModpackUpdates.php (cron that populates the cache)
|
|
||||||
* @see ModpackAPIController::getStatus() (backend endpoint)
|
|
||||||
* =============================================================================
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import http from '@/api/http';
|
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 {
|
interface ServerStatus {
|
||||||
/** True if latest_version differs from current_version */
|
|
||||||
update_available: boolean;
|
update_available: boolean;
|
||||||
/** Human-readable modpack name (e.g., "All The Mods 9") */
|
|
||||||
modpack_name?: string;
|
modpack_name?: string;
|
||||||
/** Version currently installed on the server */
|
|
||||||
current_version?: string;
|
current_version?: string;
|
||||||
/** Latest version available from the platform */
|
|
||||||
latest_version?: string;
|
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 {
|
interface StatusCache {
|
||||||
[serverUuid: string]: ServerStatus;
|
[serverUuid: string]: ServerStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
// =============================================================================
|
// Global cache with TTL support
|
||||||
// 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. */
|
|
||||||
let globalCache: StatusCache | null = null;
|
let globalCache: StatusCache | null = null;
|
||||||
|
let cacheTimestamp: number = 0;
|
||||||
/** Promise for in-flight fetch. Prevents duplicate requests. */
|
|
||||||
let fetchPromise: Promise<StatusCache> | null = null;
|
let fetchPromise: Promise<StatusCache> | null = null;
|
||||||
|
|
||||||
|
const CACHE_TTL_MS = 60000; // 60 seconds
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch all server statuses from the backend.
|
* Fetch all server statuses with 60-second TTL caching.
|
||||||
*
|
|
||||||
* 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)
|
|
||||||
*/
|
*/
|
||||||
const fetchAllStatuses = async (): Promise<StatusCache> => {
|
const fetchAllStatuses = async (): Promise<StatusCache> => {
|
||||||
// FAST PATH: Return cached data if available
|
const now = Date.now();
|
||||||
if (globalCache !== null) {
|
|
||||||
|
// Return cached data if it exists AND is less than 60 seconds old
|
||||||
|
if (globalCache !== null && (now - cacheTimestamp < CACHE_TTL_MS)) {
|
||||||
return globalCache;
|
return globalCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
// DEDUP PATH: If a fetch is already in progress, wait for it
|
// If a fetch is already in progress, wait for it
|
||||||
// instead of starting another request
|
|
||||||
if (fetchPromise !== null) {
|
if (fetchPromise !== null) {
|
||||||
return fetchPromise;
|
return fetchPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
// FETCH PATH: Start a new API request
|
// Start new fetch
|
||||||
// This is the only code path that actually makes an HTTP call
|
|
||||||
fetchPromise = http.get('/api/client/extensions/modpackchecker/status')
|
fetchPromise = http.get('/api/client/extensions/modpackchecker/status')
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
// Store the response data in the global cache
|
|
||||||
globalCache = response.data || {};
|
globalCache = response.data || {};
|
||||||
|
cacheTimestamp = Date.now();
|
||||||
return globalCache;
|
return globalCache;
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
// Log the error for debugging
|
|
||||||
console.error('ModpackChecker: Failed to fetch status', error);
|
console.error('ModpackChecker: Failed to fetch status', error);
|
||||||
// Cache empty object to prevent retry spam
|
|
||||||
// Users can refresh the page to try again
|
|
||||||
globalCache = {};
|
globalCache = {};
|
||||||
return globalCache;
|
return globalCache;
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
// Clear the promise reference
|
|
||||||
// This allows future retries if cache is manually cleared
|
|
||||||
fetchPromise = null;
|
fetchPromise = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
return fetchPromise;
|
return fetchPromise;
|
||||||
};
|
};
|
||||||
|
|
||||||
// =============================================================================
|
|
||||||
// COMPONENT
|
|
||||||
// =============================================================================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Props for the UpdateBadge component.
|
|
||||||
*/
|
|
||||||
interface UpdateBadgeProps {
|
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;
|
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
|
|
||||||
* <p>{server.name}<UpdateBadge serverUuid={server.uuid} /></p>
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* 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<UpdateBadgeProps> = ({ serverUuid }) => {
|
const UpdateBadge: React.FC<UpdateBadgeProps> = ({ serverUuid }) => {
|
||||||
// =========================================================================
|
|
||||||
// STATE
|
|
||||||
// =========================================================================
|
|
||||||
|
|
||||||
/** This specific server's status (extracted from global cache) */
|
|
||||||
const [status, setStatus] = useState<ServerStatus | null>(null);
|
const [status, setStatus] = useState<ServerStatus | null>(null);
|
||||||
|
|
||||||
/** Loading state - true until we've checked the cache */
|
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
// =========================================================================
|
|
||||||
// DATA FETCHING
|
|
||||||
// =========================================================================
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Fetch from global cache (makes API call only on first badge mount)
|
|
||||||
fetchAllStatuses()
|
fetchAllStatuses()
|
||||||
.then((cache) => {
|
.then((cache) => {
|
||||||
// Extract this server's status from the cache
|
|
||||||
// Will be null/undefined if server not in cache
|
|
||||||
setStatus(cache[serverUuid] || null);
|
setStatus(cache[serverUuid] || null);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
});
|
});
|
||||||
}, [serverUuid]); // Re-run if serverUuid changes (unlikely in practice)
|
}, [serverUuid]);
|
||||||
|
|
||||||
// =========================================================================
|
// Don't render while loading or if no status data
|
||||||
// RENDER CONDITIONS
|
if (loading || !status || !status.modpack_name) {
|
||||||
// =========================================================================
|
|
||||||
|
|
||||||
// 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) {
|
|
||||||
return null;
|
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 = {
|
const dotStyle: React.CSSProperties = {
|
||||||
// Layout
|
|
||||||
display: 'inline-block',
|
display: 'inline-block',
|
||||||
width: '8px',
|
width: '8px',
|
||||||
height: '8px',
|
height: '8px',
|
||||||
borderRadius: '50%', // Perfect circle
|
borderRadius: '50%',
|
||||||
marginLeft: '8px', // Space from server name
|
marginLeft: '8px',
|
||||||
|
|
||||||
// Color based on update status
|
|
||||||
backgroundColor: status.update_available ? '#FF6B35' : '#4ECDC4',
|
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
|
boxShadow: status.update_available
|
||||||
? '0 0 4px rgba(255, 107, 53, 0.5)' // Fire glow
|
? '0 0 4px rgba(255, 107, 53, 0.5)'
|
||||||
: '0 0 4px rgba(78, 205, 196, 0.5)', // Frost glow
|
: '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
|
const tooltipText = status.update_available
|
||||||
? `Update available: ${status.latest_version}`
|
? `Update available: ${status.latest_version}`
|
||||||
: `Up to date: ${status.latest_version}`;
|
: `Up to date: ${status.latest_version}`;
|
||||||
|
|
||||||
// =========================================================================
|
|
||||||
// RENDER
|
|
||||||
// =========================================================================
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
style={dotStyle}
|
style={dotStyle}
|
||||||
title={tooltipText} // Native browser tooltip
|
title={tooltipText}
|
||||||
aria-label={tooltipText} // Accessibility for screen readers
|
aria-label={tooltipText}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -24,14 +24,23 @@ const ModpackVersionCard: React.FC = () => {
|
|||||||
|
|
||||||
setStatus('loading');
|
setStatus('loading');
|
||||||
try {
|
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);
|
setData(response.data);
|
||||||
setStatus(response.data.success ? 'success' : 'error');
|
setStatus(response.data.success ? 'success' : 'error');
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
setData({
|
// Handle 429 Rate Limit responses with user-friendly message
|
||||||
success: false,
|
if (error.response?.status === 429) {
|
||||||
error: error.response?.data?.message || 'Failed to check for updates',
|
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');
|
setStatus('error');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user