Fügt Unterstützung für die neue nuscore API hinzu. Aktualisiert die Backend-Routen zur Verarbeitung von Anfragen an die nuscore API und integriert die neuen Dialogkomponenten im Frontend. Ermöglicht das Erstellen lokaler Kopien von nuscore-Daten und verbessert die Benutzeroberfläche durch neue Schaltflächen und Dialoge. Entfernt veraltete Konsolenausgaben und optimiert die Logik zur PIN-Verwaltung.

This commit is contained in:
Torsten Schulz (local)
2025-10-03 15:57:57 +02:00
parent cc964da9cf
commit 4b1a046149
28 changed files with 4325 additions and 6072 deletions

View File

@@ -10,6 +10,7 @@
"dependencies": {
"axios": "^1.7.3",
"core-js": "^3.8.3",
"crypto-js": "^4.2.0",
"html2canvas": "^1.4.1",
"jspdf": "^2.5.2",
"jspdf-autotable": "^5.0.2",
@@ -1375,6 +1376,12 @@
"node": ">= 8"
}
},
"node_modules/crypto-js": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz",
"integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==",
"license": "MIT"
},
"node_modules/css-line-break": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz",

View File

@@ -10,6 +10,7 @@
"dependencies": {
"axios": "^1.7.3",
"core-js": "^3.8.3",
"crypto-js": "^4.2.0",
"html2canvas": "^1.4.1",
"jspdf": "^2.5.2",
"jspdf-autotable": "^5.0.2",

View File

@@ -54,11 +54,13 @@
<script>
import { mapGetters, mapActions } from 'vuex';
import MatchReportDialog from './MatchReportDialog.vue';
import MatchReportApiDialog from './MatchReportApiDialog.vue';
import MatchReportHeaderActions from './MatchReportHeaderActions.vue';
export default {
components: {
MatchReportDialog,
MatchReportApiDialog,
MatchReportHeaderActions
},
name: 'DialogManager',
@@ -69,11 +71,15 @@ export default {
...mapActions(['closeDialog', 'minimizeDialog', 'restoreDialog', 'bringDialogToFront']),
getDialogComponent(componentName) {
console.log('🔍 Suche Komponente:', componentName);
const components = {
'MatchReportDialog': MatchReportDialog,
'MatchReportApiDialog': MatchReportApiDialog,
'MatchReportHeaderActions': MatchReportHeaderActions
};
return components[componentName] || null;
const component = components[componentName] || null;
console.log('🔍 Gefundene Komponente:', component ? component.name : 'null');
return component;
},
bringToFront(dialogId) {
@@ -93,101 +99,31 @@ export default {
// Behandle die verschiedenen Action-Types
if (action.type === 'insertPin') {
// PIN ins iframe einfügen
// PIN via postMessage an das zuletzt geöffnete MatchReport-iframe senden
this.insertPinIntoIframe(action.match);
}
},
insertPinIntoIframe(match) {
console.log('🔍 PIN-Einfügen gestartet für Match:', match);
console.log('📌 Verfügbare PINs:', {
homePin: match.homePin,
guestPin: match.guestPin
});
// Versuche direkten Zugriff auf die MatchReportDialog-Komponente
const matchReportDialogs = document.querySelectorAll('.match-report-dialog');
console.log('🖼️ Gefundene MatchReportDialogs:', matchReportDialogs.length);
if (matchReportDialogs.length > 0) {
// Versuche die insertPinManually Methode aufzurufen
console.log('🎯 Versuche direkten Zugriff auf MatchReportDialog');
// Finde das iframe im aktuellen Dialog
const iframe = matchReportDialogs[matchReportDialogs.length - 1].querySelector('iframe');
if (iframe) {
console.log('✅ Iframe gefunden, versuche PIN-Einfügung');
try {
const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
if (iframeDoc) {
console.log('✅ Direkter DOM-Zugriff möglich');
// Suche nach PIN-Feldern
const pinSelectors = [
'input[type="password"][placeholder*="Vereins"]',
'input[type="password"][placeholder*="Spiel-Pin"]',
'input[type="password"][placeholder*="PIN"]',
'input[type="password"]',
'input[placeholder*="Vereins"]',
'input[placeholder*="Spiel-Pin"]'
];
let pinField = null;
for (const selector of pinSelectors) {
pinField = iframeDoc.querySelector(selector);
if (pinField) {
console.log(`✅ PIN-Feld gefunden mit Selektor: ${selector}`);
break;
}
}
if (pinField && match.homePin) {
console.log('📝 Füge PIN ein:', match.homePin);
pinField.value = match.homePin;
pinField.dispatchEvent(new Event('input', { bubbles: true }));
pinField.dispatchEvent(new Event('change', { bubbles: true }));
pinField.dispatchEvent(new Event('blur', { bubbles: true }));
console.log('✅ PIN erfolgreich eingefügt');
return;
} else {
console.log('❌ PIN-Feld nicht gefunden');
console.log('🔍 Verfügbare Input-Felder:', iframeDoc.querySelectorAll('input'));
}
}
} catch (error) {
console.log('🚫 Cross-Origin-Zugriff blockiert (erwartet)');
}
}
}
// Fallback: PostMessage verwenden
console.log('🔍 PIN-Einfügen gestartet (postMessage)');
const iframes = document.querySelectorAll('iframe');
if (iframes.length > 0) {
const iframe = iframes[iframes.length - 1]; // Neuestes iframe
const message = {
action: 'fillPin',
pin: match.homePin,
timestamp: Date.now(),
source: 'trainingstagebuch'
};
console.log('📤 Sende PostMessage:', message);
const origins = ['https://ttde-apps.liga.nu', 'https://liga.nu', '*'];
origins.forEach(origin => {
try {
iframe.contentWindow.postMessage(message, origin);
console.log(`📤 PostMessage an ${origin} gesendet`);
} catch (e) {
console.log(`❌ PostMessage an ${origin} fehlgeschlagen:`, e.message);
}
});
if (iframes.length === 0) {
console.log('❌ Kein iframe gefunden');
return;
}
const iframe = iframes[iframes.length - 1];
const message = {
action: 'fillPin',
pin: match.homePin || match.guestPin || '',
timestamp: Date.now(),
source: 'trainingstagebuch'
};
try {
iframe.contentWindow.postMessage(message, 'http://localhost:3000');
console.log('📤 PostMessage an http://localhost:3000 gesendet');
} catch (e) {
console.log('PostMessage Fehler (nicht kritisch):', e.message);
}
console.log('💡 Alternative: Verwenden Sie den "📋 PIN kopieren" Button');
console.log('📋 PIN zum Kopieren:', match.homePin || match.guestPin);
},
handlePostMessage(event) {
@@ -195,8 +131,8 @@ export default {
console.log('- Origin:', event.origin);
console.log('- Data:', event.data);
// Nur Nachrichten von nuscore verarbeiten
if (event.origin !== 'https://ttde-apps.liga.nu' && event.origin !== 'https://liga.nu') {
// Nur Nachrichten von unserem Proxy verarbeiten
if (event.origin !== 'http://localhost:3000') {
console.log('🚫 Nachricht von unbekannter Origin ignoriert');
return;
}
@@ -306,7 +242,7 @@ export default {
.dialog-content {
flex: 1;
padding: 16px;
padding: 0;
overflow-y: auto;
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,18 @@
<template>
<div class="match-report-dialog">
<!-- nuscore Analyzer für lokale Kopie -->
<NuscoreAnalyzer v-if="showAnalyzer" @close="showAnalyzer = false" />
<!-- Toggle für Analyzer -->
<div class="analyzer-toggle">
<button @click="showAnalyzer = !showAnalyzer" class="toggle-btn">
{{ showAnalyzer ? '🔼 Analyzer verstecken' : '🔍 nuscore analysieren' }}
</button>
<button @click="createLocalCopy" class="toggle-btn" style="margin-left: 10px;">
💾 Lokale Kopie erstellen
</button>
</div>
<div class="report-content">
<iframe
ref="reportIframe"
@@ -15,27 +28,29 @@
</template>
<script>
import NuscoreAnalyzer from './NuscoreAnalyzer.vue';
import { generateSimpleNuscoreHTML } from '../utils/simpleNuscoreHTMLGenerator.js';
export default {
name: 'MatchReportDialog',
components: {
NuscoreAnalyzer
},
props: {
match: {
type: Object,
required: true
}
},
data() {
return {
showAnalyzer: false
};
},
computed: {
reportUrl() {
// Verschiedene URL-Parameter versuchen, die nuscore möglicherweise unterstützt
const baseUrl = 'https://ttde-apps.liga.nu/nuliga/nuscore-tt/meetings-list';
const params = new URLSearchParams();
// Verschiedene Parameter-Namen versuchen
params.set('code', this.match.code);
params.set('gamecode', this.match.code);
params.set('spielcode', this.match.code);
params.set('matchcode', this.match.code);
return `${baseUrl}?${params.toString()}`;
// Verwende die neue API-basierte Lösung
return `http://localhost:3000/api/nuscore/meetinginfo/${this.match.code}`;
}
},
mounted() {
@@ -64,158 +79,24 @@ export default {
onIframeLoad() {
console.log('🔄 Iframe geladen, URL:', this.$refs.reportIframe?.src);
console.log('📊 Iframe-Inhalt:', this.$refs.reportIframe?.contentDocument?.title || 'Kein Titel');
// Warte kurz, damit das iframe vollständig geladen ist
setTimeout(() => {
this.injectContentScript();
}, 2000);
// Überwache URL-Änderungen im iframe
// Sende den Code an das iframe über PostMessage
this.tryPostMessage();
// Überwache URL-Änderungen im iframe (nur Logging, da kein direkter Zugriff möglich)
this.startUrlMonitoring();
},
injectContentScript() {
try {
const iframe = this.$refs.reportIframe;
if (!iframe || !iframe.contentWindow) {
console.log('Iframe noch nicht bereit für Content Script');
return;
}
// Content Script als String definieren
const contentScript = `
(function() {
console.log('Content Script geladen');
// Warte bis die Seite vollständig geladen ist
function waitForElement(selector, callback) {
const element = document.querySelector(selector);
if (element) {
callback(element);
} else {
setTimeout(() => waitForElement(selector, callback), 100);
}
}
// Suche nach dem Input-Feld
waitForElement('#gamecode', function(input) {
console.log('Input-Feld gefunden:', input);
// Code einfügen
input.value = '${this.match.code}';
// Events auslösen
input.dispatchEvent(new Event('input', { bubbles: true }));
input.dispatchEvent(new Event('change', { bubbles: true }));
input.dispatchEvent(new Event('blur', { bubbles: true }));
console.log('Code eingefügt:', '${this.match.code}');
// Suche nach dem Button und klicke ihn
setTimeout(() => {
const button = document.querySelector('button.btn-primary');
if (button) {
console.log('Button gefunden, klicke ihn');
button.click();
} else {
console.log('Button nicht gefunden');
}
}, 500);
});
})();
`;
// Script in das iframe injizieren
const script = iframe.contentDocument.createElement('script');
script.textContent = contentScript;
iframe.contentDocument.head.appendChild(script);
console.log('Content Script injiziert');
} catch (error) {
console.log('Fehler beim Injizieren des Content Scripts:', error);
// Fallback zu PostMessage
this.tryPostMessage();
}
},
fillGameCode() {
try {
const iframe = this.$refs.reportIframe;
if (!iframe || !iframe.contentWindow) {
console.log('Iframe noch nicht bereit');
return;
}
// Versuche, das Input-Feld zu finden und zu füllen
const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
if (iframeDoc) {
const gameCodeInput = iframeDoc.getElementById('gamecode');
if (gameCodeInput) {
// Code in das Input-Feld einfügen
gameCodeInput.value = this.match.code;
// Event auslösen, damit Angular die Änderung erkennt
gameCodeInput.dispatchEvent(new Event('input', { bubbles: true }));
gameCodeInput.dispatchEvent(new Event('change', { bubbles: true }));
console.log('Spielcode erfolgreich eingefügt:', this.match.code);
// Optional: Automatisch den Button klicken
setTimeout(() => {
this.clickLoadButton(iframeDoc);
}, 500);
} else {
console.log('Input-Feld mit ID "gamecode" nicht gefunden');
}
} else {
console.log('Kein Zugriff auf iframe-Dokument (Cross-Origin)');
// Fallback: PostMessage verwenden
this.tryPostMessage();
}
} catch (error) {
console.log('Fehler beim Zugriff auf iframe:', error);
// Fallback: PostMessage verwenden
this.tryPostMessage();
}
},
clickLoadButton(iframeDoc) {
try {
// Suche nach dem Button (verschiedene Selektoren)
const buttonSelectors = [
'button.btn-primary',
'button[type="button"]',
'button:contains("Laden")'
];
let loadButton = null;
for (const selector of buttonSelectors) {
loadButton = iframeDoc.querySelector(selector);
if (loadButton) break;
}
if (loadButton) {
loadButton.click();
console.log('Laden-Button erfolgreich geklickt');
} else {
console.log('Laden-Button nicht gefunden');
}
} catch (error) {
console.log('Fehler beim Klicken des Buttons:', error);
}
},
tryPostMessage() {
try {
const iframe = this.$refs.reportIframe;
if (iframe && iframe.contentWindow) {
// Sende Nachricht an das iframe
// Sende Nachricht an das iframe (jetzt localhost:3000)
iframe.contentWindow.postMessage({
action: 'fillGameCode',
code: this.match.code
}, 'https://ttde-apps.liga.nu');
}, 'http://localhost:3000');
console.log('PostMessage gesendet mit Code:', this.match.code);
}
@@ -260,52 +141,18 @@ export default {
return;
}
// Kein DOM-Zugriff mehr; ausschließlich postMessage senden
const message = {
action: 'fillPin',
pin: this.match.homePin,
timestamp: Date.now(),
source: 'trainingstagebuch'
};
console.log('📤 Sende PostMessage:', message);
try {
const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
// Suche nach PIN-Feldern auf der Meeting-Seite
const pinSelectors = [
'input[type="password"][placeholder*="Vereins"]',
'input[type="password"][placeholder*="Spiel-Pin"]',
'input[type="password"][placeholder*="PIN"]',
'input[type="password"]',
'input[placeholder*="Vereins"]',
'input[placeholder*="Spiel-Pin"]'
];
let pinField = null;
for (const selector of pinSelectors) {
pinField = iframeDoc.querySelector(selector);
if (pinField) {
console.log(`✅ PIN-Feld gefunden mit Selektor: ${selector}`);
break;
}
}
if (pinField) {
console.log('📝 Füge PIN ein:', this.match.homePin);
pinField.value = this.match.homePin;
pinField.dispatchEvent(new Event('input', { bubbles: true }));
pinField.dispatchEvent(new Event('change', { bubbles: true }));
pinField.dispatchEvent(new Event('blur', { bubbles: true }));
console.log('✅ PIN erfolgreich eingefügt');
} else {
console.log('❌ PIN-Feld nicht gefunden');
console.log('🔍 Verfügbare Input-Felder:', iframeDoc.querySelectorAll('input'));
}
} catch (error) {
console.log('🚫 Cross-Origin-Zugriff blockiert (erwartet)');
// Fallback: PostMessage
const message = {
action: 'fillPin',
pin: this.match.homePin,
timestamp: Date.now(),
source: 'trainingstagebuch'
};
console.log('📤 Sende PostMessage:', message);
iframe.contentWindow.postMessage(message, 'https://ttde-apps.liga.nu');
} catch (e) {
console.log('PostMessage Fehler (nicht kritisch):', e.message);
iframe.contentWindow.postMessage(message, '*');
}
},
@@ -316,9 +163,28 @@ export default {
this.attemptPinInsertionAfterRedirect();
},
async createLocalCopy() {
console.log('🏗️ Erstelle lokale Kopie mit PIN...');
// Erstelle eine einfache HTML-Datei, die die ursprüngliche nuscore-Seite in einem iframe lädt
const html = generateSimpleNuscoreHTML(this.match);
const blob = new Blob([html], { type: 'text/html' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = 'nuscore-local.html';
link.click();
URL.revokeObjectURL(url);
console.log('✅ Lokale Kopie erstellt (iframe-basiert)');
},
handlePostMessage(event) {
// Nur Nachrichten von nuscore verarbeiten
if (event.origin !== 'https://ttde-apps.liga.nu') {
// Nur Nachrichten von unserem Proxy verarbeiten
if (event.origin !== 'http://localhost:3000') {
return;
}
@@ -342,6 +208,27 @@ export default {
padding: 0;
}
.analyzer-toggle {
padding: 10px;
background: #f8f9fa;
border-bottom: 1px solid #dee2e6;
}
.toggle-btn {
background: var(--primary-color);
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: background-color 0.2s;
}
.toggle-btn:hover {
background: var(--primary-hover);
}
.match-info {
background: #f8f9fa;
padding: 16px;

View File

@@ -1,8 +1,8 @@
<template>
<div class="match-report-header-actions">
<!-- <button @click="insertPin" class="header-action-btn" title="PIN automatisch einfügen">
<button @click="insertPin" class="header-action-btn" title="PIN automatisch einfügen">
📌 PIN einfügen
</button>-->
</button>
<button @click="copyPin" class="header-action-btn copy-button" title="PIN in Zwischenablage kopieren">
📋 PIN kopieren
</button>
@@ -31,7 +31,7 @@ export default {
});
},
async copyPin() {
async copyPin(event) {
const pin = this.match.homePin || this.match.guestPin;
if (!pin) {
console.warn('⚠️ Keine PIN verfügbar zum Kopieren');
@@ -43,7 +43,7 @@ export default {
console.log('✅ PIN erfolgreich kopiert:', pin);
// Visuelles Feedback
const button = event.target;
const button = event?.target;
const originalText = button.textContent;
button.textContent = '✅ Kopiert!';
button.style.backgroundColor = '#28a745';

View File

@@ -0,0 +1,528 @@
<template>
<div class="nuscore-analyzer">
<div class="analyzer-header">
<h3>🔍 nuscore Analyzer</h3>
<p>Analysiert und lädt nuscore-Ressourcen für lokale Nutzung herunter</p>
</div>
<div class="analyzer-controls">
<button @click="analyzeNuscore" :disabled="isAnalyzing" class="analyze-btn">
{{ isAnalyzing ? '🔄 Analysiere...' : '🔍 nuscore analysieren' }}
</button>
<button @click="downloadResources" :disabled="!resources.length || isDownloading" class="download-btn">
{{ isDownloading ? '⬇️ Lade herunter...' : `⬇️ ${resources.length} Ressourcen herunterladen` }}
</button>
<button @click="createLocalCopy" :disabled="!hasDownloadedResources || isCreating" class="create-btn">
{{ isCreating ? '🏗️ Erstelle...' : '🏗️ Lokale Kopie erstellen' }}
</button>
</div>
<div class="analyzer-results" v-if="resources.length > 0">
<h4>📋 Gefundene Ressourcen:</h4>
<div class="resource-list">
<div
v-for="resource in resources"
:key="resource.url"
class="resource-item"
:class="{ 'downloaded': resource.downloaded }"
>
<span class="resource-type">{{ resource.type }}</span>
<span class="resource-url">{{ resource.url }}</span>
<span class="resource-size">{{ formatSize(resource.size) }}</span>
<span v-if="resource.downloaded" class="status"></span>
</div>
</div>
</div>
<div class="analyzer-log" v-if="logs.length > 0">
<h4>📝 Log:</h4>
<div class="log-content">
<div v-for="(log, index) in logs" :key="index" class="log-entry">
{{ log }}
</div>
</div>
</div>
</div>
</template>
<script>
import { generateSimpleNuscoreHTML } from '../utils/simpleNuscoreHTMLGenerator.js';
export default {
name: 'NuscoreAnalyzer',
data() {
return {
isAnalyzing: false,
isDownloading: false,
isCreating: false,
resources: [],
downloadedResources: [],
logs: []
};
},
computed: {
hasDownloadedResources() {
return this.resources.some(resource => resource.downloaded) || this.downloadedResources.length > 0;
}
},
methods: {
async analyzeNuscore() {
this.isAnalyzing = true;
this.logs = [];
this.resources = [];
this.addLog('🔍 Starte nuscore-Analyse...');
try {
// Öffne nuscore in einem versteckten iframe
const iframe = document.createElement('iframe');
iframe.style.display = 'none';
iframe.src = 'https://ttde-apps.liga.nu/nuliga/nuscore-tt/meetings-list';
document.body.appendChild(iframe);
// Warte auf das Laden
await new Promise((resolve) => {
iframe.onload = resolve;
setTimeout(resolve, 5000); // Timeout nach 5 Sekunden
});
this.addLog('✅ nuscore-Seite geladen');
// Analysiere die Seite
await this.analyzePage(iframe);
// Entferne das iframe
document.body.removeChild(iframe);
this.addLog(`✅ Analyse abgeschlossen: ${this.resources.length} Ressourcen gefunden`);
} catch (error) {
this.addLog(`❌ Fehler bei der Analyse: ${error.message}`);
} finally {
this.isAnalyzing = false;
}
},
async analyzePage(iframe) {
try {
const doc = iframe.contentDocument || iframe.contentWindow.document;
// Analysiere Script-Tags
const scripts = doc.querySelectorAll('script[src]');
this.addLog(`📜 ${scripts.length} Script-Tags gefunden`);
scripts.forEach(script => {
this.resources.push({
type: 'script',
url: script.src,
size: 0,
downloaded: false
});
});
// Analysiere Link-Tags (CSS)
const stylesheets = doc.querySelectorAll('link[rel="stylesheet"][href]');
this.addLog(`🎨 ${stylesheets.length} Stylesheets gefunden`);
stylesheets.forEach(link => {
this.resources.push({
type: 'stylesheet',
url: link.href,
size: 0,
downloaded: false
});
});
// Analysiere andere Ressourcen
const images = doc.querySelectorAll('img[src]');
this.addLog(`🖼️ ${images.length} Bilder gefunden`);
images.forEach(img => {
this.resources.push({
type: 'image',
url: img.src,
size: 0,
downloaded: false
});
});
} catch (error) {
this.addLog(`⚠️ Cross-Origin-Zugriff blockiert: ${error.message}`);
// Fallback: Analysiere die ursprüngliche URL
await this.analyzeUrlDirectly();
}
},
async analyzeUrlDirectly() {
this.addLog('🔄 Fallback: Bekannte nuscore-Ressourcen verwenden');
try {
// Bekannte nuscore-Ressourcen basierend auf der URL-Struktur
const baseUrl = 'https://ttde-apps.liga.nu';
// Typische nuscore-Ressourcen
const knownResources = [
// JavaScript-Bundles (typische Angular/React-Bundles)
`${baseUrl}/nuliga/nuscore-tt/main.js`,
`${baseUrl}/nuliga/nuscore-tt/runtime.js`,
`${baseUrl}/nuliga/nuscore-tt/vendor.js`,
`${baseUrl}/nuliga/nuscore-tt/polyfills.js`,
`${baseUrl}/nuliga/nuscore-tt/styles.js`,
`${baseUrl}/nuliga/nuscore-tt/main.6c3da9dbb4871026b9a1.js`, // Aus den Logs
// CSS-Dateien
`${baseUrl}/nuliga/nuscore-tt/styles.css`,
`${baseUrl}/nuliga/nuscore-tt/main.css`,
// Weitere mögliche Ressourcen
`${baseUrl}/nuliga/nuscore-tt/assets/main.js`,
`${baseUrl}/nuliga/nuscore-tt/assets/runtime.js`,
`${baseUrl}/nuliga/nuscore-tt/assets/vendor.js`,
`${baseUrl}/nuliga/nuscore-tt/assets/styles.css`,
// Angular-spezifische Ressourcen
`${baseUrl}/nuliga/nuscore-tt/angular.js`,
`${baseUrl}/nuliga/nuscore-tt/angular.min.js`,
// Bootstrap/CSS-Frameworks
`${baseUrl}/nuliga/nuscore-tt/bootstrap.css`,
`${baseUrl}/nuliga/nuscore-tt/bootstrap.min.css`,
// Fonts
`${baseUrl}/nuliga/nuscore-tt/fonts/`,
// Bilder/Assets
`${baseUrl}/nuliga/nuscore-tt/assets/images/`,
`${baseUrl}/nuliga/nuscore-tt/assets/icons/`
];
// Füge bekannte Ressourcen hinzu
knownResources.forEach(url => {
this.resources.push({
type: url.includes('.css') ? 'stylesheet' :
url.includes('.js') ? 'script' :
url.includes('fonts') ? 'font' : 'asset',
url: url,
size: 0,
downloaded: false
});
});
this.addLog(`${this.resources.length} bekannte Ressourcen hinzugefügt`);
// Versuche zusätzlich, Ressourcen aus dem aktuellen iframe zu extrahieren
await this.extractResourcesFromCurrentPage();
} catch (error) {
this.addLog(`❌ Bekannte Ressourcen-Analyse fehlgeschlagen: ${error.message}`);
}
},
async extractResourcesFromCurrentPage() {
this.addLog('🔍 Versuche Ressourcen-Extraktion von der aktuellen Seite');
try {
// Analysiere die aktuelle Seite nach Ressourcen
const scripts = document.querySelectorAll('script[src]');
const stylesheets = document.querySelectorAll('link[rel="stylesheet"][href]');
this.addLog(`📜 ${scripts.length} Scripts auf aktueller Seite gefunden`);
this.addLog(`🎨 ${stylesheets.length} Stylesheets auf aktueller Seite gefunden`);
// Füge gefundene Ressourcen hinzu (falls sie nuscore-relevant sind)
scripts.forEach(script => {
if (script.src.includes('nuscore') || script.src.includes('liga.nu')) {
this.resources.push({
type: 'script',
url: script.src,
size: 0,
downloaded: false
});
}
});
stylesheets.forEach(link => {
if (link.href.includes('nuscore') || link.href.includes('liga.nu')) {
this.resources.push({
type: 'stylesheet',
url: link.href,
size: 0,
downloaded: false
});
}
});
this.addLog(`${this.resources.length} nuscore-relevante Ressourcen gefunden`);
} catch (error) {
this.addLog(`⚠️ Ressourcen-Extraktion fehlgeschlagen: ${error.message}`);
}
},
async downloadResources() {
this.isDownloading = true;
this.downloadedResources = [];
this.addLog('⬇️ Starte Download der Ressourcen...');
this.addLog('⚠️ Hinweis: CORS-Beschränkungen können Downloads blockieren');
for (const resource of this.resources) {
try {
this.addLog(`⬇️ Lade herunter: ${resource.url}`);
// Versuche verschiedene Download-Methoden
let blob = null;
// Methode 1: Direkter fetch
try {
const response = await fetch(resource.url, {
mode: 'cors',
credentials: 'omit'
});
blob = await response.blob();
} catch (corsError) {
this.addLog(`⚠️ CORS-Fehler für ${resource.url}, versuche Alternative...`);
// Methode 2: Proxy über iframe
blob = await this.downloadViaIframe(resource.url);
}
if (blob && blob.size > 0) {
resource.downloaded = true;
resource.size = blob.size;
resource.blob = blob;
resource.localUrl = URL.createObjectURL(blob);
this.downloadedResources.push(resource);
this.addLog(`✅ Heruntergeladen: ${this.formatSize(blob.size)}`);
} else {
this.addLog(`⚠️ Leere oder ungültige Ressource: ${resource.url}`);
}
} catch (error) {
this.addLog(`❌ Download fehlgeschlagen: ${resource.url} - ${error.message}`);
// Fallback: Markiere als "verfügbar aber nicht heruntergeladen"
resource.available = true;
resource.downloaded = false;
}
}
this.addLog(`✅ Download abgeschlossen: ${this.downloadedResources.length}/${this.resources.length} erfolgreich`);
this.addLog(`💡 Tipp: Verwenden Sie die lokale Kopie auch mit externen Ressourcen`);
this.isDownloading = false;
},
async downloadViaIframe(url) {
return new Promise((resolve, reject) => {
try {
const iframe = document.createElement('iframe');
iframe.style.display = 'none';
iframe.src = url;
document.body.appendChild(iframe);
iframe.onload = () => {
try {
const doc = iframe.contentDocument || iframe.contentWindow.document;
const content = doc.documentElement.outerHTML;
const blob = new Blob([content], { type: 'text/html' });
document.body.removeChild(iframe);
resolve(blob);
} catch (error) {
document.body.removeChild(iframe);
reject(error);
}
};
iframe.onerror = () => {
document.body.removeChild(iframe);
reject(new Error('Iframe load failed'));
};
// Timeout nach 10 Sekunden
setTimeout(() => {
if (document.body.contains(iframe)) {
document.body.removeChild(iframe);
reject(new Error('Download timeout'));
}
}, 10000);
} catch (error) {
reject(error);
}
});
},
async createLocalCopy() {
this.isCreating = true;
this.addLog('🏗️ Erstelle lokale Kopie...');
try {
// Erstelle eine einfache HTML-Datei, die die ursprüngliche nuscore-Seite in einem iframe lädt
const html = generateSimpleNuscoreHTML({ homePin: '', guestPin: '', code: '' });
const blob = new Blob([html], { type: 'text/html' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = 'nuscore-local.html';
link.click();
URL.revokeObjectURL(url);
this.addLog('✅ Lokale Kopie erstellt (iframe-basiert)');
this.addLog('💡 Die lokale Kopie lädt die ursprüngliche nuscore-Seite in einem iframe');
this.addLog('💡 PIN-Einfügung funktioniert über PostMessage');
} catch (error) {
this.addLog(`❌ Fehler beim Erstellen der lokalen Kopie: ${error.message}`);
} finally {
this.isCreating = false;
}
},
addLog(message) {
const timestamp = new Date().toLocaleTimeString();
this.logs.push(`[${timestamp}] ${message}`);
},
formatSize(bytes) {
if (bytes === 0) return '0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
}
};
</script>
<style scoped>
.nuscore-analyzer {
padding: 20px;
background: #f8f9fa;
border-radius: 8px;
margin: 20px 0;
}
.analyzer-header h3 {
margin: 0 0 10px 0;
color: var(--primary-color);
}
.analyzer-controls {
display: flex;
gap: 10px;
margin: 20px 0;
flex-wrap: wrap;
}
.analyze-btn, .download-btn, .create-btn {
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
font-weight: bold;
transition: background-color 0.2s;
}
.analyze-btn {
background: var(--primary-color);
color: white;
}
.analyze-btn:hover:not(:disabled) {
background: var(--primary-hover);
}
.download-btn {
background: var(--secondary-color);
color: white;
}
.download-btn:hover:not(:disabled) {
background: #0056b3;
}
.create-btn {
background: #28a745;
color: white;
}
.create-btn:hover:not(:disabled) {
background: #218838;
}
.analyze-btn:disabled, .download-btn:disabled, .create-btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.resource-list {
max-height: 300px;
overflow-y: auto;
border: 1px solid #ddd;
border-radius: 4px;
background: white;
}
.resource-item {
display: flex;
align-items: center;
padding: 8px 12px;
border-bottom: 1px solid #eee;
gap: 10px;
}
.resource-item:last-child {
border-bottom: none;
}
.resource-item.downloaded {
background: #d4edda;
}
.resource-type {
font-weight: bold;
min-width: 80px;
font-size: 12px;
}
.resource-url {
flex: 1;
font-family: monospace;
font-size: 12px;
color: #666;
}
.resource-size {
min-width: 60px;
text-align: right;
font-size: 12px;
}
.status {
color: #28a745;
font-weight: bold;
}
.log-content {
max-height: 200px;
overflow-y: auto;
background: #000;
color: #0f0;
padding: 10px;
border-radius: 4px;
font-family: monospace;
font-size: 12px;
}
.log-entry {
margin-bottom: 2px;
}
</style>

View File

@@ -0,0 +1,85 @@
// HTML-Generator für nuscore lokale Kopie
export function generateNuscoreHTML(scripts, stylesheets, useExternalUrls = false) {
const parts = [];
parts.push('<!DOCTYPE html>');
parts.push('<html lang="de">');
// Head
parts.push('<head>');
parts.push(' <meta charset="UTF-8">');
parts.push(' <meta name="viewport" content="width=device-width, initial-scale=1.0">');
parts.push(' <title>nuscore - Lokale Kopie</title>');
parts.push(' <base href="/">');
parts.push(' ');
parts.push(' <!-- Stylesheets -->');
stylesheets.forEach(sheet => {
const href = useExternalUrls ? sheet.url : sheet.localUrl;
parts.push(` <link rel="stylesheet" href="${href}">`);
});
parts.push(' ');
parts.push(' <!-- PIN-Einfügung Script -->');
parts.push(' <script>');
parts.push(' window.addEventListener("message", function(event) {');
parts.push(' if (event.data && event.data.action === "fillPin") {');
parts.push(' console.log("PIN-Einfügung empfangen:", event.data.pin);');
parts.push(' ');
parts.push(' const pinSelectors = [');
parts.push(' "input[type=\\"password\\"][placeholder*=\\"Vereins\\"]",');
parts.push(' "input[type=\\"password\\"][placeholder*=\\"Spiel-Pin\\"]",');
parts.push(' "input[type=\\"password\\"][placeholder*=\\"PIN\\"]",');
parts.push(' "input[type=\\"password\\"]"');
parts.push(' ];');
parts.push(' ');
parts.push(' let pinField = null;');
parts.push(' for (const selector of pinSelectors) {');
parts.push(' pinField = document.querySelector(selector);');
parts.push(' if (pinField) break;');
parts.push(' }');
parts.push(' ');
parts.push(' if (pinField) {');
parts.push(' pinField.value = event.data.pin;');
parts.push(' pinField.dispatchEvent(new Event("input", { bubbles: true }));');
parts.push(' pinField.dispatchEvent(new Event("change", { bubbles: true }));');
parts.push(' console.log("PIN erfolgreich eingefügt:", event.data.pin);');
parts.push(' }');
parts.push(' }');
parts.push(' });');
parts.push(' </script>');
parts.push('</head>');
// Body
parts.push('<body>');
parts.push(' <app-root></app-root>');
parts.push(' ');
parts.push(' <!-- Scripts -->');
scripts.forEach(script => {
const src = useExternalUrls ? script.url : script.localUrl;
parts.push(` <script src="${src}"></script>`);
});
parts.push(' ');
parts.push(' <!-- Initialisierung -->');
parts.push(' <script>');
parts.push(' console.log("nuscore lokale Kopie gestartet");');
parts.push(' console.log("PIN-Einfügung bereit");');
parts.push(' ');
parts.push(' const urlParams = new URLSearchParams(window.location.search);');
parts.push(' const code = urlParams.get("code");');
parts.push(' if (code) {');
parts.push(' console.log("Code aus URL gefunden:", code);');
parts.push(' }');
parts.push(' ');
parts.push(' // Angular Router Konfiguration für lokale Kopie');
parts.push(' if (window.ng && window.ng.core) {');
parts.push(' console.log("Angular gefunden, konfiguriere Router...");');
parts.push(' }');
parts.push(' </script>');
parts.push('</body>');
parts.push('</html>');
return parts.join('\n');
}

View File

@@ -0,0 +1,111 @@
// Einfacher HTML-Generator für nuscore lokale Kopie (iframe-basiert)
export function generateSimpleNuscoreHTML(match) {
const pin = match.homePin || match.guestPin || '';
const code = match.code || '';
return `<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>nuscore - Lokale Kopie</title>
<style>
body { margin: 0; padding: 0; font-family: Arial, sans-serif; }
.header { background: #f0f0f0; padding: 10px; border-bottom: 1px solid #ccc; }
.controls { margin: 10px 0; }
.controls button { margin-right: 10px; padding: 5px 10px; }
.iframe-container { width: 100%; height: calc(100vh - 100px); }
.iframe-container iframe { width: 100%; height: 100%; border: none; }
.status { padding: 10px; background: #e8f5e8; border: 1px solid #4caf50; margin: 10px 0; }
</style>
</head>
<body>
<div class="header">
<h1>nuscore - Lokale Kopie</h1>
<div class="controls">
<button onclick="insertPin()">📌 PIN einfügen</button>
<button onclick="copyPin()">📋 PIN kopieren</button>
<input type="password" id="pinInput" placeholder="PIN eingeben" style="margin-left: 10px;">
</div>
<div class="status" id="status">Bereit für PIN-Einfügung</div>
</div>
<div class="iframe-container">
<iframe id="nuscoreFrame" src="https://ttde-apps.liga.nu/nuliga/nuscore-tt/meetings-list"></iframe>
</div>
<script>
let currentPin = "";
// PIN aus Match-Objekt laden
const pin = "${pin}";
const code = "${code}";
if (pin) {
currentPin = pin;
document.getElementById("pinInput").value = pin;
document.getElementById("status").textContent = "PIN aus Match geladen: " + pin;
}
function insertPin() {
const pin = document.getElementById("pinInput").value || currentPin;
if (!pin) {
document.getElementById("status").textContent = "Keine PIN eingegeben";
return;
}
const iframe = document.getElementById("nuscoreFrame");
const message = {
action: "fillPin",
pin: pin,
timestamp: Date.now(),
source: "nuscore-local"
};
try {
iframe.contentWindow.postMessage(message, "https://ttde-apps.liga.nu");
document.getElementById("status").textContent = "PIN gesendet: " + pin;
} catch (error) {
document.getElementById("status").textContent = "Fehler beim Senden der PIN: " + error.message;
}
}
function copyPin() {
const pin = document.getElementById("pinInput").value || currentPin;
if (!pin) {
document.getElementById("status").textContent = "Keine PIN zum Kopieren";
return;
}
navigator.clipboard.writeText(pin).then(() => {
document.getElementById("status").textContent = "PIN kopiert: " + pin;
}).catch(() => {
// Fallback
const textArea = document.createElement("textarea");
textArea.value = pin;
document.body.appendChild(textArea);
textArea.select();
document.execCommand("copy");
document.body.removeChild(textArea);
document.getElementById("status").textContent = "PIN kopiert (Fallback): " + pin;
});
}
// PostMessage Listener für Antworten vom iframe
window.addEventListener("message", function(event) {
if (event.origin !== "https://ttde-apps.liga.nu") return;
if (event.data && event.data.action === "pinInserted") {
document.getElementById("status").textContent = "PIN erfolgreich eingefügt!";
} else if (event.data && event.data.action === "pinError") {
document.getElementById("status").textContent = "PIN-Einfügung fehlgeschlagen: " + event.data.error;
}
});
console.log("nuscore lokale Kopie gestartet");
if (code) console.log("Code aus Match:", code);
if (pin) console.log("PIN aus Match:", pin);
</script>
</body>
</html>`;
}

View File

@@ -93,13 +93,13 @@ import { mapGetters, mapActions } from 'vuex';
import apiClient from '../apiClient.js';
import PDFGenerator from '../components/PDFGenerator.js';
import SeasonSelector from '../components/SeasonSelector.vue';
import MatchReportDialog from '../components/MatchReportDialog.vue';
import MatchReportApiDialog from '../components/MatchReportApiDialog.vue';
export default {
name: 'ScheduleView',
components: {
SeasonSelector,
MatchReportDialog
MatchReportApiDialog
},
computed: {
...mapGetters(['isAuthenticated', 'currentClub', 'clubs', 'currentClubName']),
@@ -377,15 +377,9 @@ export default {
const title = `${match.homeTeam?.name || 'N/A'} vs ${match.guestTeam?.name || 'N/A'} - ${this.selectedLeague}`;
this.openDialog({
title,
component: 'MatchReportDialog',
component: 'MatchReportApiDialog',
props: {
match
},
headerActions: {
component: 'MatchReportHeaderActions',
props: {
match
}
}
});
},