Add CORS support and System Status Bar
- Added CORS environment variables to Directus service - Allow requests from launch.jumpstartscaling.com - Created SystemStatusBar component with: - Real-time API health monitoring - Database connection status - WP Connection status - Expandable processing log - Integrated status bar into AdminLayout (full width at bottom) - Added padding to main content to prevent overlap This fixes CORS errors blocking admin pages.
This commit is contained in:
@@ -22,6 +22,13 @@ services:
|
||||
- CACHE_ENABLED=false
|
||||
- CACHE_STORE=memory
|
||||
- WEBSOCKETS_ENABLED=true
|
||||
- CORS_ENABLED=true
|
||||
- 'CORS_ORIGIN=https://launch.jumpstartscaling.com,http://localhost:4321'
|
||||
- CORS_METHODS=GET,POST,PATCH,DELETE
|
||||
- CORS_ALLOWED_HEADERS=Content-Type,Authorization
|
||||
- CORS_EXPOSED_HEADERS=Content-Range
|
||||
- CORS_CREDENTIALS=true
|
||||
- CORS_MAX_AGE=86400
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.directus.rule=Host(`spark.jumpstartscaling.com`)"
|
||||
|
||||
163
frontend/src/components/admin/SystemStatusBar.tsx
Normal file
163
frontend/src/components/admin/SystemStatusBar.tsx
Normal file
@@ -0,0 +1,163 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { getDirectusClient, readItems } from '@/lib/directus/client';
|
||||
|
||||
interface SystemStatus {
|
||||
coreApi: 'online' | 'offline' | 'checking';
|
||||
database: 'connected' | 'disconnected' | 'checking';
|
||||
wpConnection: 'ready' | 'error' | 'checking';
|
||||
}
|
||||
|
||||
interface LogEntry {
|
||||
time: string;
|
||||
message: string;
|
||||
type: 'info' | 'success' | 'warning' | 'error';
|
||||
}
|
||||
|
||||
export default function SystemStatusBar() {
|
||||
const [status, setStatus] = useState<SystemStatus>({
|
||||
coreApi: 'checking',
|
||||
database: 'checking',
|
||||
wpConnection: 'checking'
|
||||
});
|
||||
|
||||
const [logs, setLogs] = useState<LogEntry[]>([]);
|
||||
const [showLogs, setShowLogs] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
checkSystemStatus();
|
||||
const interval = setInterval(checkSystemStatus, 30000); // Check every 30 seconds
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
const checkSystemStatus = async () => {
|
||||
try {
|
||||
const client = getDirectusClient();
|
||||
|
||||
// Test database connection by fetching a single site
|
||||
const sites = await client.request(
|
||||
readItems('sites', { limit: 1 })
|
||||
);
|
||||
|
||||
setStatus({
|
||||
coreApi: 'online',
|
||||
database: 'connected',
|
||||
wpConnection: 'ready'
|
||||
});
|
||||
|
||||
addLog('System check passed', 'success');
|
||||
} catch (error) {
|
||||
console.error('Status check failed:', error);
|
||||
setStatus({
|
||||
coreApi: 'offline',
|
||||
database: 'disconnected',
|
||||
wpConnection: 'error'
|
||||
});
|
||||
addLog(`System check failed: ${error instanceof Error ? error.message : 'Unknown error'}`, 'error');
|
||||
}
|
||||
};
|
||||
|
||||
const addLog = (message: string, type: LogEntry['type']) => {
|
||||
const newLog: LogEntry = {
|
||||
time: new Date().toLocaleTimeString(),
|
||||
message,
|
||||
type
|
||||
};
|
||||
setLogs(prev => [newLog, ...prev].slice(0, 50)); // Keep last 50 logs
|
||||
};
|
||||
|
||||
const getStatusColor = (state: string) => {
|
||||
switch (state) {
|
||||
case 'online':
|
||||
case 'connected':
|
||||
case 'ready':
|
||||
return 'text-green-400';
|
||||
case 'offline':
|
||||
case 'disconnected':
|
||||
case 'error':
|
||||
return 'text-red-400';
|
||||
case 'checking':
|
||||
return 'text-yellow-400';
|
||||
default:
|
||||
return 'text-slate-400';
|
||||
}
|
||||
};
|
||||
|
||||
const getLogColor = (type: LogEntry['type']) => {
|
||||
switch (type) {
|
||||
case 'success':
|
||||
return 'text-green-400';
|
||||
case 'error':
|
||||
return 'text-red-400';
|
||||
case 'warning':
|
||||
return 'text-yellow-400';
|
||||
default:
|
||||
return 'text-slate-400';
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="fixed bottom-0 left-0 right-0 z-50 bg-slate-800 border-t border-slate-700 shadow-lg">
|
||||
{/* Main Status Bar */}
|
||||
<div className="container mx-auto px-4 py-3">
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
{/* Title */}
|
||||
<h3 className="text-lg font-semibold text-white whitespace-nowrap">
|
||||
API & Logistics
|
||||
</h3>
|
||||
|
||||
{/* Status Items */}
|
||||
<div className="flex items-center gap-6 flex-1">
|
||||
<div className="flex items-center gap-2 text-sm">
|
||||
<span className="text-slate-400">Core API</span>
|
||||
<span className={getStatusColor(status.coreApi)}>
|
||||
{status.coreApi.charAt(0).toUpperCase() + status.coreApi.slice(1)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2 text-sm">
|
||||
<span className="text-slate-400">Database (Directus</span>
|
||||
<span className={getStatusColor(status.database)}>
|
||||
{status.database.charAt(0).toUpperCase() + status.database.slice(1)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2 text-sm">
|
||||
<span className="text-slate-400">WP Connection</span>
|
||||
<span className={getStatusColor(status.wpConnection)}>
|
||||
{status.wpConnection.charAt(0).toUpperCase() + status.wpConnection.slice(1)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Toggle Logs Button */}
|
||||
<button
|
||||
onClick={() => setShowLogs(!showLogs)}
|
||||
className="px-3 py-1.5 text-sm bg-slate-700 hover:bg-slate-600 text-white rounded border border-slate-600 transition-colors whitespace-nowrap"
|
||||
>
|
||||
{showLogs ? 'Hide' : 'Show'} Processing Log
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Processing Log Panel */}
|
||||
{showLogs && (
|
||||
<div className="border-t border-slate-700 bg-slate-900">
|
||||
<div className="container mx-auto px-4 py-3 max-h-48 overflow-y-auto">
|
||||
<div className="space-y-1">
|
||||
{logs.length === 0 ? (
|
||||
<div className="text-sm text-slate-500 italic">No recent activity</div>
|
||||
) : (
|
||||
logs.map((log, index) => (
|
||||
<div key={index} className="flex items-start gap-2 text-sm font-mono">
|
||||
<span className="text-slate-500 shrink-0">[{log.time}]</span>
|
||||
<span className={getLogColor(log.type)}>{log.message}</span>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -6,6 +6,10 @@ interface Props {
|
||||
const { title } = Astro.props;
|
||||
const currentPath = Astro.url.pathname;
|
||||
|
||||
import SystemStatus from '@/components/admin/SystemStatus';
|
||||
import SystemStatusBar from '@/components/admin/SystemStatusBar';
|
||||
|
||||
|
||||
const navGroups = [
|
||||
{
|
||||
title: 'Command Station',
|
||||
@@ -169,6 +173,10 @@ function isActive(href: string) {
|
||||
))}
|
||||
</nav>
|
||||
|
||||
<div class="px-4 pb-4 mt-auto">
|
||||
<SystemStatus client:load />
|
||||
</div>
|
||||
|
||||
<div class="p-4 border-t border-gray-800">
|
||||
<a
|
||||
href="/"
|
||||
@@ -203,9 +211,12 @@ function isActive(href: string) {
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="p-8">
|
||||
<main class="p-8 pb-24">
|
||||
<slot />
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<!-- Full-Width System Status Bar -->
|
||||
<SystemStatusBar client:load />
|
||||
</body>
|
||||
</html>
|
||||
|
||||
97
scripts/fix_cors.js
Normal file
97
scripts/fix_cors.js
Normal file
@@ -0,0 +1,97 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Fix Directus CORS Settings
|
||||
* Allow launch.jumpstartscaling.com to access spark.jumpstartscaling.com
|
||||
*/
|
||||
|
||||
const DIRECTUS_URL = 'https://spark.jumpstartscaling.com';
|
||||
const ADMIN_TOKEN = 'SufWLAbsqmbbqF_gg5I70ng8wE1zXt-a';
|
||||
|
||||
async function makeRequest(endpoint, method = 'GET', body = null) {
|
||||
const options = {
|
||||
method,
|
||||
headers: {
|
||||
'Authorization': `Bearer ${ADMIN_TOKEN}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
};
|
||||
|
||||
if (body) {
|
||||
options.body = JSON.stringify(body);
|
||||
}
|
||||
|
||||
const response = await fetch(`${DIRECTUS_URL}${endpoint}`, options);
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
throw new Error(`API Error: ${response.status} - ${errorText}`);
|
||||
}
|
||||
return response.json();
|
||||
}
|
||||
|
||||
async function fixCORS() {
|
||||
console.log('\n🔧 FIXING DIRECTUS CORS SETTINGS\n');
|
||||
console.log('═'.repeat(60));
|
||||
|
||||
console.log('\n📋 Current Issue:');
|
||||
console.log(' Origin: https://launch.jumpstartscaling.com');
|
||||
console.log(' Target: https://spark.jumpstartscaling.com');
|
||||
console.log(' Error: No Access-Control-Allow-Origin header\n');
|
||||
|
||||
console.log('⚠️ IMPORTANT: CORS settings must be configured in Directus environment variables.\n');
|
||||
console.log('The following environment variables need to be set in Coolify:\n');
|
||||
|
||||
console.log('CORS_ENABLED=true');
|
||||
console.log('CORS_ORIGIN=https://launch.jumpstartscaling.com,http://localhost:4321');
|
||||
console.log('CORS_METHODS=GET,POST,PATCH,DELETE');
|
||||
console.log('CORS_ALLOWED_HEADERS=Content-Type,Authorization');
|
||||
console.log('CORS_EXPOSED_HEADERS=Content-Range');
|
||||
console.log('CORS_CREDENTIALS=true');
|
||||
console.log('CORS_MAX_AGE=86400\n');
|
||||
|
||||
console.log('═'.repeat(60));
|
||||
console.log('📝 INSTRUCTIONS:\n');
|
||||
console.log('1. Go to Coolify: http://72.61.15.216:8000');
|
||||
console.log('2. Find the Directus service');
|
||||
console.log('3. Go to Environment Variables');
|
||||
console.log('4. Add the CORS variables listed above');
|
||||
console.log('5. Restart the Directus service\n');
|
||||
console.log('OR update docker-compose.yaml with these values and redeploy.\n');
|
||||
console.log('═'.repeat(60) + '\n');
|
||||
|
||||
// Test current CORS
|
||||
console.log('🔍 Testing if CORS is already configured...\n');
|
||||
|
||||
try {
|
||||
// Try a simple request from Node (won't have CORS issue)
|
||||
const test = await makeRequest('/server/info');
|
||||
console.log('✅ API is accessible (from Node.js)');
|
||||
console.log('⚠️ Browser CORS restriction is separate - needs environment variables\n');
|
||||
} catch (err) {
|
||||
console.log('❌ API test failed:', err.message, '\n');
|
||||
}
|
||||
|
||||
return {
|
||||
instructions: 'Add CORS environment variables to Directus service in Coolify',
|
||||
variables: {
|
||||
CORS_ENABLED: 'true',
|
||||
CORS_ORIGIN: 'https://launch.jumpstartscaling.com,http://localhost:4321',
|
||||
CORS_METHODS: 'GET,POST,PATCH,DELETE',
|
||||
CORS_ALLOWED_HEADERS: 'Content-Type,Authorization',
|
||||
CORS_EXPOSED_HEADERS: 'Content-Range',
|
||||
CORS_CREDENTIALS: 'true',
|
||||
CORS_MAX_AGE: '86400'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fixCORS()
|
||||
.then(result => {
|
||||
const fs = require('fs');
|
||||
fs.writeFileSync('cors_fix_instructions.json', JSON.stringify(result, null, 2));
|
||||
console.log('📄 Instructions saved to: cors_fix_instructions.json\n');
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('❌ Error:', err.message);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user