diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index a7e3996..9cea7cc 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -13,6 +13,7 @@ import TrainingPlanningPage from './pages/TrainingPlanningPage' import AdminCatalogsPage from './pages/AdminCatalogsPage' import AdminHierarchyPage from './pages/AdminHierarchyPage' import TrainerContextsPage from './pages/TrainerContextsPage' +import MediaWikiImportPage from './pages/MediaWikiImportPage' import './app.css' // Bottom Navigation (Mobile) @@ -199,6 +200,14 @@ function AppRoutes() { } /> + + + + } + /> { + if (activeTab === 'history') { + loadLogs() + } + }, [activeTab]) + + // Polling cleanup + useEffect(() => { + return () => { + if (pollingInterval) { + clearInterval(pollingInterval) + } + } + }, [pollingInterval]) + + const loadLogs = async () => { + try { + setLoading(true) + const data = await api.listMediaWikiImportLogs() + setLogs(data) + } catch (err) { + setError(err.message) + } finally { + setLoading(false) + } + } + + const handlePreview = async () => { + try { + setLoading(true) + setError(null) + const data = await api.previewMediaWikiImport(previewCategory, previewType, previewLimit) + setPreviewData(data) + } catch (err) { + setError(err.message) + } finally { + setLoading(false) + } + } + + const handleExecute = async () => { + try { + setLoading(true) + setError(null) + const result = await api.executeMediaWikiImport({ + category: executeCategory, + import_type: executeType, + reimport_existing: executeReimport, + dry_run: executeDryRun, + limit: executeLimit || null + }) + setCurrentImport(result) + + // Start polling + const interval = setInterval(async () => { + try { + const status = await api.getMediaWikiImportStatus(result.log_id) + setCurrentImport(status) + + if (status.import_status === 'completed' || status.import_status === 'failed') { + clearInterval(interval) + setPollingInterval(null) + setLoading(false) + } + } catch (err) { + console.error('Polling error:', err) + } + }, 2000) + + setPollingInterval(interval) + } catch (err) { + setError(err.message) + setLoading(false) + } + } + + return ( +
+

MediaWiki Import (Semantic MediaWiki)

+

+ Importiere Übungen, Fähigkeiten und Methoden aus karatetrainer.net +

+ + {/* Tabs */} +
+
+ {['preview', 'execute', 'history'].map(tab => ( + + ))} +
+
+ + {/* Error Display */} + {error && ( +
+ Fehler: {error} +
+ )} + + {/* Preview Tab */} + {activeTab === 'preview' && ( +
+
+

Import-Vorschau

+
+
+ + setPreviewCategory(e.target.value)} + placeholder="Übungen" + style={{ + width: '100%', + padding: '12px', + fontSize: '16px', + border: '1px solid var(--border)', + borderRadius: '8px' + }} + /> +
+
+ + +
+
+ + setPreviewLimit(parseInt(e.target.value) || 10)} + min="1" + max="100" + style={{ + width: '100%', + padding: '12px', + fontSize: '16px', + border: '1px solid var(--border)', + borderRadius: '8px' + }} + /> +
+ +
+
+ + {/* Preview Results */} + {previewData && ( +
+

+ {previewData.total_found} Einträge gefunden in Kategorie "{previewData.category}" +

+
+ {previewData.preview.map((item, idx) => ( +
0 ? '#C00' : item.warnings.length > 0 ? '#F90' : 'var(--border)'}` + }} + > + + {item.already_imported && '✅ '} + {item.wiki_page_title} + {item.errors.length > 0 && ' ❌'} + {item.warnings.length > 0 && ' ⚠️'} + +
+ {item.already_imported && ( +

Bereits importiert: {new Date(item.last_imported_at).toLocaleString('de-DE')}

+ )} + {item.warnings.length > 0 && ( +
+ Warnungen: +
    {item.warnings.map((w, i) =>
  • {w}
  • )}
+
+ )} + {item.errors.length > 0 && ( +
+ Fehler: +
    {item.errors.map((e, i) =>
  • {e}
  • )}
+
+ )} +
+ + Gemappte Felder anzeigen + +
+                          {JSON.stringify(item.mapped_fields, null, 2)}
+                        
+
+
+
+ ))} +
+
+ )} +
+ )} + + {/* Execute Tab */} + {activeTab === 'execute' && ( +
+
+

Import ausführen

+
+
+ + setExecuteCategory(e.target.value)} + placeholder="Übungen" + style={{ + width: '100%', + padding: '12px', + fontSize: '16px', + border: '1px solid var(--border)', + borderRadius: '8px' + }} + /> +
+
+ + +
+
+ +
+
+ +
+
+ + setExecuteLimit(e.target.value ? parseInt(e.target.value) : null)} + placeholder="Kein Limit" + min="1" + style={{ + width: '100%', + padding: '12px', + fontSize: '16px', + border: '1px solid var(--border)', + borderRadius: '8px' + }} + /> +
+ +
+
+ + {/* Import Status */} + {currentImport && ( +
+

+ {currentImport.import_status === 'running' && '⏳ Import läuft...'} + {currentImport.import_status === 'completed' && '✅ Import abgeschlossen'} + {currentImport.import_status === 'failed' && '❌ Import fehlgeschlagen'} +

+
+
Log ID: {currentImport.id}
+
Kategorie: {currentImport.category}
+
Typ: {currentImport.import_type}
+
Total: {currentImport.items_total}
+
✅ Importiert: {currentImport.items_imported}
+
⏭️ Übersprungen: {currentImport.items_skipped}
+
❌ Fehlgeschlagen: {currentImport.items_failed}
+ {currentImport.started_at && ( +
Gestartet: {new Date(currentImport.started_at).toLocaleString('de-DE')}
+ )} + {currentImport.finished_at && ( +
Beendet: {new Date(currentImport.finished_at).toLocaleString('de-DE')}
+ )} +
+ + {currentImport.error_log && currentImport.error_log.length > 0 && ( +
+ + ❌ Fehler anzeigen ({currentImport.error_log.length}) + +
+ {currentImport.error_log.map((err, idx) => ( +
+ {err.item}: {err.error} +
+ ))} +
+
+ )} +
+ )} +
+ )} + + {/* History Tab */} + {activeTab === 'history' && ( +
+
+

Import-Historie

+ +
+ + {logs.length === 0 && !loading && ( +

+ Noch keine Imports durchgeführt +

+ )} + +
+ {logs.map((log) => ( +
+
+
+
+ {log.import_status === 'completed' && '✅ '} + {log.import_status === 'failed' && '❌ '} + {log.import_status === 'running' && '⏳ '} + {log.category} ({log.import_type}) + {log.dry_run && ' [Dry-Run]'} +
+
+
{new Date(log.started_at).toLocaleString('de-DE')}
+
+ ✅ {log.items_imported} + {' | '} + ⏭️ {log.items_skipped} + {' | '} + ❌ {log.items_failed} + {' | '} + Total: {log.items_total} +
+
+
+
+
+ ))} +
+
+ )} +
+ ) +} diff --git a/frontend/src/utils/api.js b/frontend/src/utils/api.js index 150aad3..65d036e 100644 --- a/frontend/src/utils/api.js +++ b/frontend/src/utils/api.js @@ -595,7 +595,26 @@ export const api = { // System getVersion, - healthCheck + healthCheck, + + // MediaWiki Import + previewMediaWikiImport: (category, importType = 'exercise', limit = 10) => + request(`/api/import/mediawiki/preview?category=${encodeURIComponent(category)}&import_type=${importType}&limit=${limit}`), + + executeMediaWikiImport: (data) => + request('/api/import/mediawiki/execute', { + method: 'POST', + body: JSON.stringify(data) + }), + + getMediaWikiImportStatus: (logId) => + request(`/api/import/mediawiki/status/${logId}`), + + listMediaWikiImportLogs: () => + request('/api/import/mediawiki/logs'), + + deleteMediaWikiImportReference: (refId) => + request(`/api/import/mediawiki/references/${refId}`, { method: 'DELETE' }) } export default api