Files
firefrost-services/services/modpack-version-checker/blueprint-extension/views/dashboard/UpdateBadge.tsx
Claude (Chronicler #63) 5a607c8c8b 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>
2026-04-06 11:47:20 +00:00

126 lines
3.6 KiB
TypeScript

/**
* =============================================================================
* MODPACK VERSION CHECKER - DASHBOARD BADGE COMPONENT
* =============================================================================
*
* React component that displays a colored indicator dot next to server names
* on the Pterodactyl dashboard, showing modpack update status at a glance.
*
* VISUAL DESIGN:
* - 🟢 Frost (#4ECDC4): Server's modpack is up to date
* - 🟠 Fire (#FF6B35): Update available for this modpack
* - No dot: Server has no modpack configured or not yet checked
*
* 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 <dev@firefrostgaming.com>
* @version 1.0.0
*/
import React, { useEffect, useState } from 'react';
import http from '@/api/http';
interface ServerStatus {
update_available: boolean;
modpack_name?: string;
current_version?: string;
latest_version?: string;
}
interface StatusCache {
[serverUuid: string]: ServerStatus;
}
// Global cache with TTL support
let globalCache: StatusCache | null = null;
let cacheTimestamp: number = 0;
let fetchPromise: Promise<StatusCache> | null = null;
const CACHE_TTL_MS = 60000; // 60 seconds
/**
* Fetch all server statuses with 60-second TTL caching.
*/
const fetchAllStatuses = async (): Promise<StatusCache> => {
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;
}
// If a fetch is already in progress, wait for it
if (fetchPromise !== null) {
return fetchPromise;
}
// Start new fetch
fetchPromise = http.get('/api/client/extensions/modpackchecker/status')
.then((response) => {
globalCache = response.data || {};
cacheTimestamp = Date.now();
return globalCache;
})
.catch((error) => {
console.error('ModpackChecker: Failed to fetch status', error);
globalCache = {};
return globalCache;
})
.finally(() => {
fetchPromise = null;
});
return fetchPromise;
};
interface UpdateBadgeProps {
serverUuid: string;
}
const UpdateBadge: React.FC<UpdateBadgeProps> = ({ serverUuid }) => {
const [status, setStatus] = useState<ServerStatus | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchAllStatuses()
.then((cache) => {
setStatus(cache[serverUuid] || null);
setLoading(false);
});
}, [serverUuid]);
// Don't render while loading or if no status data
if (loading || !status || !status.modpack_name) {
return null;
}
const dotStyle: React.CSSProperties = {
display: 'inline-block',
width: '8px',
height: '8px',
borderRadius: '50%',
marginLeft: '8px',
backgroundColor: status.update_available ? '#FF6B35' : '#4ECDC4',
boxShadow: status.update_available
? '0 0 4px rgba(255, 107, 53, 0.5)'
: '0 0 4px rgba(78, 205, 196, 0.5)',
};
const tooltipText = status.update_available
? `Update available: ${status.latest_version}`
: `Up to date: ${status.latest_version}`;
return (
<span
style={dotStyle}
title={tooltipText}
aria-label={tooltipText}
/>
);
};
export default UpdateBadge;