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>
126 lines
3.6 KiB
TypeScript
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;
|