- Add intelligent content-game compatibility system with visual badges - Fix Adventure Reader to work with Dragon's Pearl content structure - Implement multi-column games grid for faster navigation - Add pronunciation display for Chinese vocabulary and sentences - Fix navigation breadcrumb to show proper hierarchy (Home > Levels > Content) - Add back buttons to all navigation pages - Improve JSONContentLoader to preserve story structure - Add comprehensive debugging and diagnostic tools 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
374 lines
14 KiB
HTML
374 lines
14 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="fr">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Test DigitalOcean Spaces Connection</title>
|
|
<style>
|
|
body {
|
|
font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
|
|
max-width: 1200px;
|
|
margin: 0 auto;
|
|
padding: 20px;
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
min-height: 100vh;
|
|
}
|
|
.container {
|
|
background: white;
|
|
border-radius: 10px;
|
|
padding: 30px;
|
|
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
|
|
}
|
|
h1 {
|
|
color: #333;
|
|
border-bottom: 3px solid #667eea;
|
|
padding-bottom: 10px;
|
|
}
|
|
.test-section {
|
|
margin: 20px 0;
|
|
padding: 20px;
|
|
background: #f8f9fa;
|
|
border-radius: 8px;
|
|
border-left: 4px solid #667eea;
|
|
}
|
|
.test-result {
|
|
margin: 10px 0;
|
|
padding: 15px;
|
|
border-radius: 5px;
|
|
font-family: 'Courier New', monospace;
|
|
font-size: 14px;
|
|
white-space: pre-wrap;
|
|
word-break: break-all;
|
|
}
|
|
.success {
|
|
background: #d4edda;
|
|
border: 1px solid #c3e6cb;
|
|
color: #155724;
|
|
}
|
|
.error {
|
|
background: #f8d7da;
|
|
border: 1px solid #f5c6cb;
|
|
color: #721c24;
|
|
}
|
|
.warning {
|
|
background: #fff3cd;
|
|
border: 1px solid #ffeeba;
|
|
color: #856404;
|
|
}
|
|
.info {
|
|
background: #d1ecf1;
|
|
border: 1px solid #bee5eb;
|
|
color: #0c5460;
|
|
}
|
|
button {
|
|
background: #667eea;
|
|
color: white;
|
|
border: none;
|
|
padding: 12px 24px;
|
|
border-radius: 5px;
|
|
cursor: pointer;
|
|
font-size: 16px;
|
|
margin: 5px;
|
|
transition: all 0.3s;
|
|
}
|
|
button:hover {
|
|
background: #5a67d8;
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
|
|
}
|
|
button:disabled {
|
|
background: #ccc;
|
|
cursor: not-allowed;
|
|
transform: none;
|
|
}
|
|
.spinner {
|
|
display: inline-block;
|
|
width: 20px;
|
|
height: 20px;
|
|
border: 3px solid rgba(255,255,255,.3);
|
|
border-radius: 50%;
|
|
border-top-color: white;
|
|
animation: spin 1s ease-in-out infinite;
|
|
}
|
|
@keyframes spin {
|
|
to { transform: rotate(360deg); }
|
|
}
|
|
.config-display {
|
|
background: #2d3748;
|
|
color: #48bb78;
|
|
padding: 15px;
|
|
border-radius: 5px;
|
|
margin: 10px 0;
|
|
font-family: 'Courier New', monospace;
|
|
}
|
|
.test-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
|
gap: 20px;
|
|
margin: 20px 0;
|
|
}
|
|
.test-card {
|
|
background: white;
|
|
border: 1px solid #e2e8f0;
|
|
border-radius: 8px;
|
|
padding: 15px;
|
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
}
|
|
.test-card h3 {
|
|
margin-top: 0;
|
|
color: #4a5568;
|
|
}
|
|
.status-indicator {
|
|
display: inline-block;
|
|
width: 12px;
|
|
height: 12px;
|
|
border-radius: 50%;
|
|
margin-right: 8px;
|
|
}
|
|
.status-indicator.success { background: #48bb78; }
|
|
.status-indicator.error { background: #f56565; }
|
|
.status-indicator.pending { background: #ed8936; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<h1>🔧 Test de Connexion DigitalOcean Spaces</h1>
|
|
|
|
<div class="test-section">
|
|
<h2>📋 Configuration Actuelle</h2>
|
|
<div id="config" class="config-display">Chargement...</div>
|
|
</div>
|
|
|
|
<div class="test-section">
|
|
<h2>🧪 Tests de Connexion</h2>
|
|
<div>
|
|
<button onclick="runAllTests()">🚀 Lancer Tous les Tests</button>
|
|
<button onclick="testPublicAccess()">🌐 Test Accès Public</button>
|
|
<button onclick="testWithAuth()">🔐 Test avec Authentification</button>
|
|
<button onclick="testCORS()">🔄 Test CORS</button>
|
|
<button onclick="clearResults()">🗑️ Effacer Résultats</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="test-grid" id="testGrid"></div>
|
|
|
|
<div class="test-section">
|
|
<h2>📊 Résultats Détaillés</h2>
|
|
<div id="results"></div>
|
|
</div>
|
|
|
|
<div class="test-section">
|
|
<h2>💡 Recommandations</h2>
|
|
<div id="recommendations" class="info">
|
|
Les recommandations apparaîtront après les tests...
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Charger les scripts nécessaires -->
|
|
<script src="js/core/env-config.js"></script>
|
|
<script>
|
|
// Désactiver temporairement les logs pour éviter les erreurs
|
|
if (typeof logSh === 'undefined') {
|
|
window.logSh = function(msg, level) {
|
|
console.log(`[${level}] ${msg}`);
|
|
};
|
|
}
|
|
|
|
// Afficher la configuration
|
|
function displayConfig() {
|
|
const config = window.envConfig.getDiagnostics();
|
|
document.getElementById('config').innerHTML = JSON.stringify(config, null, 2);
|
|
}
|
|
|
|
// Fonction pour ajouter un résultat
|
|
function addResult(message, type = 'info') {
|
|
const resultsDiv = document.getElementById('results');
|
|
const resultDiv = document.createElement('div');
|
|
resultDiv.className = `test-result ${type}`;
|
|
resultDiv.textContent = `[${new Date().toLocaleTimeString()}] ${message}`;
|
|
resultsDiv.appendChild(resultDiv);
|
|
}
|
|
|
|
// Fonction pour ajouter une carte de test
|
|
function addTestCard(title, status, details) {
|
|
const grid = document.getElementById('testGrid');
|
|
const card = document.createElement('div');
|
|
card.className = 'test-card';
|
|
card.innerHTML = `
|
|
<h3><span class="status-indicator ${status}"></span>${title}</h3>
|
|
<div>${details}</div>
|
|
`;
|
|
grid.appendChild(card);
|
|
}
|
|
|
|
// Test d'accès public (sans auth)
|
|
async function testPublicAccess() {
|
|
addResult('Test d\'accès public sans authentification...', 'info');
|
|
|
|
try {
|
|
const url = window.envConfig.getRemoteContentUrl() + 'test.json';
|
|
addResult(`URL testée: ${url}`, 'info');
|
|
|
|
const response = await fetch(url, {
|
|
method: 'HEAD',
|
|
mode: 'cors'
|
|
});
|
|
|
|
if (response.ok) {
|
|
addResult('✅ Accès public réussi!', 'success');
|
|
addTestCard('Accès Public', 'success', 'Le bucket est accessible publiquement');
|
|
} else if (response.status === 403) {
|
|
addResult('🔒 Accès refusé (403) - Le bucket est privé', 'warning');
|
|
addTestCard('Accès Public', 'error', 'Bucket privé - Authentification requise');
|
|
} else {
|
|
addResult(`❌ Erreur: Status ${response.status}`, 'error');
|
|
addTestCard('Accès Public', 'error', `Status HTTP: ${response.status}`);
|
|
}
|
|
} catch (error) {
|
|
addResult(`❌ Erreur réseau: ${error.message}`, 'error');
|
|
|
|
if (error.message.includes('CORS')) {
|
|
addResult('⚠️ Problème CORS détecté - Vérifiez la configuration CORS du bucket', 'warning');
|
|
addTestCard('Accès Public', 'error', 'Erreur CORS');
|
|
} else {
|
|
addTestCard('Accès Public', 'error', error.message);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Test avec authentification
|
|
async function testWithAuth() {
|
|
addResult('Test avec authentification AWS Signature v4...', 'info');
|
|
|
|
try {
|
|
const testUrl = window.envConfig.getRemoteContentUrl() + 'test.json';
|
|
const authHeaders = await window.envConfig.getAuthHeaders('GET', testUrl);
|
|
|
|
addResult('Headers d\'authentification générés:', 'info');
|
|
addResult(JSON.stringify(authHeaders, null, 2), 'info');
|
|
|
|
const response = await fetch(testUrl, {
|
|
method: 'GET',
|
|
headers: authHeaders,
|
|
mode: 'cors'
|
|
});
|
|
|
|
if (response.ok) {
|
|
addResult('✅ Authentification réussie!', 'success');
|
|
const data = await response.text();
|
|
addResult(`Contenu reçu: ${data.substring(0, 200)}...`, 'success');
|
|
addTestCard('Authentification', 'success', 'Connexion authentifiée réussie');
|
|
} else {
|
|
addResult(`❌ Authentification échouée: Status ${response.status}`, 'error');
|
|
addResult(`Message: ${response.statusText}`, 'error');
|
|
addTestCard('Authentification', 'error', `Status: ${response.status}`);
|
|
}
|
|
} catch (error) {
|
|
addResult(`❌ Erreur: ${error.message}`, 'error');
|
|
addTestCard('Authentification', 'error', error.message);
|
|
}
|
|
}
|
|
|
|
// Test CORS
|
|
async function testCORS() {
|
|
addResult('Test de la configuration CORS...', 'info');
|
|
|
|
try {
|
|
const url = window.envConfig.getRemoteContentUrl();
|
|
|
|
// Test OPTIONS (preflight)
|
|
const response = await fetch(url, {
|
|
method: 'OPTIONS',
|
|
headers: {
|
|
'Origin': window.location.origin,
|
|
'Access-Control-Request-Method': 'GET',
|
|
'Access-Control-Request-Headers': 'authorization'
|
|
}
|
|
});
|
|
|
|
const corsHeaders = {
|
|
'Access-Control-Allow-Origin': response.headers.get('Access-Control-Allow-Origin'),
|
|
'Access-Control-Allow-Methods': response.headers.get('Access-Control-Allow-Methods'),
|
|
'Access-Control-Allow-Headers': response.headers.get('Access-Control-Allow-Headers')
|
|
};
|
|
|
|
addResult('Headers CORS reçus:', 'info');
|
|
addResult(JSON.stringify(corsHeaders, null, 2), 'info');
|
|
|
|
if (corsHeaders['Access-Control-Allow-Origin']) {
|
|
addResult('✅ CORS configuré', 'success');
|
|
addTestCard('Configuration CORS', 'success', 'Headers CORS présents');
|
|
} else {
|
|
addResult('⚠️ Headers CORS manquants', 'warning');
|
|
addTestCard('Configuration CORS', 'error', 'Headers CORS non configurés');
|
|
}
|
|
} catch (error) {
|
|
addResult(`❌ Test CORS échoué: ${error.message}`, 'error');
|
|
addTestCard('Configuration CORS', 'error', 'Test échoué');
|
|
}
|
|
}
|
|
|
|
// Lancer tous les tests
|
|
async function runAllTests() {
|
|
clearResults();
|
|
addResult('🚀 Démarrage de la suite de tests complète...', 'info');
|
|
|
|
await testPublicAccess();
|
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
|
|
await testWithAuth();
|
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
|
|
await testCORS();
|
|
|
|
// Test de connexion via EnvConfig
|
|
addResult('Test via EnvConfig.testRemoteConnection()...', 'info');
|
|
const result = await window.envConfig.testRemoteConnection();
|
|
addResult(`Résultat: ${JSON.stringify(result, null, 2)}`, result.success ? 'success' : 'error');
|
|
|
|
generateRecommendations();
|
|
}
|
|
|
|
// Générer des recommandations
|
|
function generateRecommendations() {
|
|
const recoDiv = document.getElementById('recommendations');
|
|
let recommendations = [];
|
|
|
|
const results = document.getElementById('results').textContent;
|
|
|
|
if (results.includes('403')) {
|
|
recommendations.push('🔐 Le bucket est privé. Assurez-vous que les clés d\'API sont correctes.');
|
|
}
|
|
|
|
if (results.includes('CORS')) {
|
|
recommendations.push('🔄 Configurez CORS sur votre bucket DigitalOcean Spaces:');
|
|
recommendations.push(' - Allez dans les paramètres du bucket');
|
|
recommendations.push(' - Ajoutez une règle CORS pour autoriser votre origine');
|
|
recommendations.push(' - Origine: * ou file:// pour les fichiers locaux');
|
|
}
|
|
|
|
if (results.includes('Authentification échouée')) {
|
|
recommendations.push('🔑 Vérifiez vos clés d\'accès DigitalOcean dans env-config.js');
|
|
}
|
|
|
|
if (recommendations.length === 0) {
|
|
recommendations.push('✅ Tout semble fonctionner correctement!');
|
|
}
|
|
|
|
recoDiv.innerHTML = recommendations.join('<br>');
|
|
}
|
|
|
|
// Effacer les résultats
|
|
function clearResults() {
|
|
document.getElementById('results').innerHTML = '';
|
|
document.getElementById('testGrid').innerHTML = '';
|
|
document.getElementById('recommendations').innerHTML = 'Les recommandations apparaîtront après les tests...';
|
|
}
|
|
|
|
// Initialisation
|
|
displayConfig();
|
|
addResult('Page de test prête. Cliquez sur "Lancer Tous les Tests" pour commencer.', 'info');
|
|
</script>
|
|
</body>
|
|
</html> |