feat(logging): add HTTP page fetch logging and enhance click-TT proxy functionality

- Introduced a new logging mechanism for HTTP requests to click-TT/HTTV pages, improving traceability and debugging capabilities.
- Implemented a proxy endpoint for iframe embedding, allowing direct HTML retrieval with enhanced error handling and validation for input URLs.
- Updated the frontend to include a new navigation link for the click-TT feature, accessible to admin users.
- Added a new route for the click-TT view in the router configuration.
This commit is contained in:
Torsten Schulz (local)
2026-03-10 21:27:40 +01:00
parent 055dbf115c
commit 13379d6b24
6 changed files with 477 additions and 3 deletions

View File

@@ -30,6 +30,10 @@
<span class="dropdown-icon">📋</span>
{{ $t('navigation.logs') }}
</router-link>
<router-link v-if="isAdmin" to="/clicktt" class="dropdown-item" @click="userDropdownOpen = false">
<span class="dropdown-icon">🌐</span>
HTTV / click-TT
</router-link>
<div class="dropdown-divider"></div>
<router-link to="/personal-settings" class="dropdown-item" @click="userDropdownOpen = false">
<span class="dropdown-icon"></span>

View File

@@ -19,6 +19,7 @@ import MyTischtennisAccount from './views/MyTischtennisAccount.vue';
import TeamManagementView from './views/TeamManagementView.vue';
import PermissionsView from './views/PermissionsView.vue';
import LogsView from './views/LogsView.vue';
import ClickTtView from './views/ClickTtView.vue';
import MemberTransferSettingsView from './views/MemberTransferSettingsView.vue';
import PersonalSettings from './views/PersonalSettings.vue';
import Impressum from './views/Impressum.vue';
@@ -45,6 +46,7 @@ const routes = [
{ path: '/team-management', component: TeamManagementView },
{ path: '/permissions', component: PermissionsView },
{ path: '/logs', component: LogsView },
{ path: '/clicktt', component: ClickTtView },
{ path: '/member-transfer-settings', component: MemberTransferSettingsView },
{ path: '/personal-settings', component: PersonalSettings },
{ path: '/impressum', component: Impressum },

View File

@@ -0,0 +1,283 @@
<template>
<div class="clicktt-view">
<div class="header">
<h1>HTTV / click-TT Seiten</h1>
<p class="subtitle">
Lade und bediene click-TT-Seiten (Ligenübersicht, Vereinsinfo, Spielplan) im iframe.
Alle Aufrufe werden geloggt.
</p>
</div>
<div class="controls-section">
<div class="form-row">
<div class="form-group">
<label>Seitentyp</label>
<select v-model="pageType" class="form-select">
<option value="leaguePage">Ligenübersicht (leaguePage)</option>
<option value="clubInfo">Vereinsinfo (clubInfoDisplay)</option>
<option value="regionMeetings">Regionsspielplan (regionMeetingFilter)</option>
<option value="url">Direkte URL</option>
</select>
</div>
<div class="form-group" v-if="pageType !== 'url'">
<label>Verband</label>
<select v-model="association" class="form-select">
<option value="HeTTV">HeTTV (Hessen)</option>
<option value="RTTV">RTTV</option>
<option value="WTTV">WTTV</option>
<option value="TTVNw">TTVNw</option>
</select>
</div>
<div class="form-group" v-if="pageType !== 'url' && pageType !== 'clubInfo'">
<label>Championship / Saison</label>
<input
v-model="championship"
type="text"
class="form-input"
placeholder="z.B. HTTV 25/26, K43 25/26"
/>
</div>
<div class="form-group" v-if="pageType === 'clubInfo'">
<label>Vereins-ID</label>
<input
v-model="clubId"
type="text"
class="form-input"
placeholder="z.B. 1060"
/>
</div>
<div class="form-group" v-if="pageType === 'url'">
<label>URL</label>
<input
v-model="directUrl"
type="text"
class="form-input form-input-wide"
placeholder="https://httv.click-tt.de/..."
/>
</div>
<div class="form-group form-group-actions">
<button @click="loadPage" class="btn-primary" :disabled="!canLoad">
Laden
</button>
<button @click="clearIframe" class="btn-secondary">Leeren</button>
</div>
</div>
</div>
<div class="iframe-container" v-if="iframeSrc">
<iframe
:src="iframeSrc"
class="clicktt-iframe"
title="click-TT Seite"
sandbox="allow-same-origin allow-scripts allow-forms allow-popups allow-popups-to-escape-sandbox"
/>
</div>
<div v-else class="placeholder">
<p>Wähle einen Seitentyp, fülle die Parameter und klicke auf Laden, um die Seite anzuzeigen.</p>
</div>
</div>
</template>
<script>
import { ref, computed } from 'vue';
const backendBaseUrl = import.meta.env.VITE_BACKEND || 'http://localhost:3005';
export default {
name: 'ClickTtView',
setup() {
const pageType = ref('leaguePage');
const association = ref('HeTTV');
const championship = ref('HTTV 25/26');
const clubId = ref('');
const directUrl = ref('');
const iframeSrc = ref('');
const canLoad = computed(() => {
if (pageType.value === 'url') {
return directUrl.value.trim().length > 0;
}
if (pageType.value === 'clubInfo') {
return clubId.value.trim().length > 0;
}
return true;
});
function buildProxyUrl() {
const base = `${backendBaseUrl}/api/clicktt/proxy`;
const params = new URLSearchParams();
if (pageType.value === 'url') {
params.set('url', directUrl.value.trim());
} else {
params.set('type', pageType.value);
params.set('association', association.value);
if (pageType.value !== 'clubInfo') {
params.set('championship', championship.value);
}
if (pageType.value === 'clubInfo') {
params.set('clubId', clubId.value.trim());
}
}
return `${base}?${params.toString()}`;
}
function loadPage() {
if (!canLoad.value) return;
iframeSrc.value = buildProxyUrl();
}
function clearIframe() {
iframeSrc.value = '';
}
return {
pageType,
association,
championship,
clubId,
directUrl,
iframeSrc,
canLoad,
loadPage,
clearIframe,
};
},
};
</script>
<style scoped>
.clicktt-view {
padding: 1.5rem;
max-width: 1400px;
margin: 0 auto;
}
.header {
margin-bottom: 1.5rem;
}
.header h1 {
margin: 0 0 0.5rem 0;
color: var(--text-color, #333);
font-size: 1.5rem;
}
.subtitle {
color: var(--text-secondary, #666);
margin: 0;
font-size: 0.95em;
}
.controls-section {
background: var(--background-light, #f8f9fa);
padding: 1rem;
border-radius: 8px;
margin-bottom: 1rem;
}
.form-row {
display: flex;
flex-wrap: wrap;
gap: 1rem;
align-items: flex-end;
}
.form-group {
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.form-group label {
font-size: 0.85em;
font-weight: 500;
color: var(--text-color, #333);
}
.form-select,
.form-input {
padding: 0.5rem;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 0.9em;
min-width: 140px;
}
.form-input-wide {
min-width: 320px;
}
.form-group-actions {
flex-direction: row;
align-items: flex-end;
gap: 0.5rem;
}
.btn-primary {
background: var(--primary-color, #2563eb);
color: white;
border: none;
padding: 0.5rem 1rem;
border-radius: 4px;
cursor: pointer;
font-size: 0.9em;
}
.btn-primary:hover:not(:disabled) {
background: var(--primary-hover, #1d4ed8);
}
.btn-primary:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.btn-secondary {
background: #6b7280;
color: white;
border: none;
padding: 0.5rem 1rem;
border-radius: 4px;
cursor: pointer;
font-size: 0.9em;
}
.btn-secondary:hover {
background: #4b5563;
}
.iframe-container {
border: 1px solid #ddd;
border-radius: 8px;
overflow: hidden;
min-height: 500px;
background: #fff;
}
.clicktt-iframe {
width: 100%;
height: 70vh;
min-height: 500px;
border: none;
display: block;
}
.placeholder {
padding: 3rem;
text-align: center;
color: var(--text-secondary, #666);
background: var(--background-light, #f8f9fa);
border-radius: 8px;
border: 1px dashed #ccc;
}
.placeholder p {
margin: 0;
}
</style>