refactor: reorganize project structure for better maintainability

- Move 45 debug/test files from root to organized scripts/ directories
- Secure public/ directory by removing debug files (security improvement)
- Create structured scripts organization:
  • scripts/debug/      (20 files) - Framework debugging tools
  • scripts/test/       (18 files) - Test and validation scripts
  • scripts/maintenance/ (5 files) - Maintenance utilities
  • scripts/dev/         (2 files) - Development tools

Security improvements:
- Removed all debug/test files from public/ directory
- Only production files remain: index.php, health.php

Root directory cleanup:
- Reduced from 47 to 2 PHP files in root
- Only essential production files: console.php, worker.php

This improves:
 Security (no debug code in public/)
 Organization (clear separation of concerns)
 Maintainability (easy to find and manage scripts)
 Professional structure (clean root directory)
This commit is contained in:
2025-10-05 10:59:15 +02:00
parent 03e5188644
commit 887847dde6
77 changed files with 3902 additions and 787 deletions

View File

@@ -1,96 +0,0 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?= $title ?></title>
<link rel="stylesheet" href="/css/admin.css">
</head>
<body class="admin-page">
<div class="admin-header">
<h1>Framework Admin Dashboard</h1>
</div>
<div class="admin-nav">
<a href="/admin" class="active">Dashboard</a>
<a href="/admin/routes">Routen</a>
<a href="/admin/services">Dienste</a>
<a href="/admin/environment">Umgebung</a>
<a href="/admin/performance">Performance</a>
<a href="/admin/redis">Redis</a>
<a href="/admin/phpinfo">PHP Info</a>
</div>
<div class="admin-content">
<div class="dashboard-stats">
<div class="stat-box">
<h3>Framework Version</h3>
<div class="stat-value"><?= $stats['frameworkVersion'] ?></div>
</div>
<div class="stat-box">
<h3>PHP Version</h3>
<div class="stat-value"><?= $stats['phpVersion'] ?></div>
</div>
<div class="stat-box">
<h3>Speicherverbrauch</h3>
<div class="stat-value"><?= $stats['memoryUsage'] ?></div>
</div>
<div class="stat-box">
<h3>Max. Speicherverbrauch</h3>
<div class="stat-value"><?= $stats['peakMemoryUsage'] ?></div>
</div>
<div class="stat-box">
<h3>Server</h3>
<div class="stat-value"><?= $stats['serverInfo'] ?></div>
</div>
<div class="stat-box">
<h3>Serverzeit</h3>
<div class="stat-value"><?= $stats['serverTime'] ?></div>
</div>
<div class="stat-box">
<h3>Zeitzone</h3>
<div class="stat-value"><?= $stats['timezone'] ?></div>
</div>
<div class="stat-box">
<h3>Betriebssystem</h3>
<div class="stat-value"><?= $stats['operatingSystem'] ?></div>
</div>
<div class="stat-box">
<h3>Server Uptime</h3>
<div class="stat-value"><?= $stats['uptime'] ?></div>
</div>
<div class="stat-box">
<h3>Aktive Sessions</h3>
<div class="stat-value"><?= $stats['sessionCount'] ?></div>
</div>
<div class="stat-box">
<h3>Registrierte Dienste</h3>
<div class="stat-value"><?= $stats['servicesCount'] ?></div>
</div>
</div>
<div class="admin-section">
<h2>PHP Erweiterungen</h2>
<div class="extensions-list">
<?php foreach ($stats['loadedExtensions'] as $extension): ?>
<span class="extension-badge"><?= $extension ?></span>
<?php endforeach; ?>
</div>
</div>
</div>
<div class="admin-footer">
<p>&copy; <?= date('Y') ?> Framework Admin</p>
</div>
</body>
</html>

View File

@@ -1,86 +0,0 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ title }}</title>
<link rel="stylesheet" href="/css/admin.css">
</head>
<body class="admin-page">
<div class="admin-header">
<h1>Umgebungsvariablen</h1>
</div>
<div class="admin-nav">
<a href="/admin">Dashboard</a>
<a href="/admin/routes">Routen</a>
<a href="/admin/services">Dienste</a>
<a href="/admin/environment" class="active">Umgebung</a>
<a href="/admin/performance">Performance</a>
<a href="/admin/redis">Redis</a>
<a href="/admin/phpinfo">PHP Info</a>
</div>
<div class="admin-content">
<div class="admin-tools">
<input type="text" id="envFilter" placeholder="Variablen filtern..." class="search-input">
<div class="filter-tags">
<button class="filter-tag" data-prefix="APP_">APP_</button>
<button class="filter-tag" data-prefix="DB_">DB_</button>
<button class="filter-tag" data-prefix="REDIS_">REDIS_</button>
<button class="filter-tag" data-prefix="RATE_LIMIT_">RATE_LIMIT_</button>
<button class="filter-tag" data-prefix="PHP_">PHP_</button>
<button class="filter-tag active" data-prefix="">Alle</button>
</div>
</div>
<table class="admin-table" id="envTable">
<thead>
<tr>
<th>Variable</th>
<th>Wert</th>
</tr>
</thead>
<tbody>
<for var="envVar" in="env">
<tr>
<td>{{ envVar.key }}</td>
<td>{{ envVar.value }}</td>
</tr>
</for>
</tbody>
</table>
</div>
<div class="admin-footer">
<p>&copy; {{ current_year }} Framework Admin</p>
</div>
<script>
// Filterung der Umgebungsvariablen
document.getElementById('envFilter').addEventListener('input', filterTable);
// Tag-Filter
document.querySelectorAll('.filter-tag').forEach(tag => {
tag.addEventListener('click', function() {
document.querySelectorAll('.filter-tag').forEach(t => t.classList.remove('active'));
this.classList.add('active');
const prefix = this.getAttribute('data-prefix');
document.getElementById('envFilter').value = prefix;
filterTable();
});
});
function filterTable() {
const filterValue = document.getElementById('envFilter').value.toLowerCase();
const rows = document.querySelectorAll('#envTable tbody tr');
rows.forEach(row => {
const key = row.cells[0].textContent.toLowerCase();
row.style.display = key.includes(filterValue) ? '' : 'none';
});
}
</script>
</body>
</html>

View File

@@ -1,250 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Image Manager</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container-fluid">
<h1>Image Manager</h1>
<div class="row">
<!-- Image Slots Section -->
<div class="col-md-6">
<h2>Image Slots</h2>
<div id="image-slots" class="list-group">
<for var="slot" in="slots">
<div class="list-group-item slot-item" data-slot-id="{{ slot.id }}">
<div class="d-flex justify-content-between align-items-center">
<div>
<h5>{{ slot.slotName }}</h5>
<small class="text-muted">ID: {{ slot.id }}</small>
</div>
<div class="slot-image-container" style="width: 100px; height: 100px;">
<div class="border border-dashed d-flex align-items-center justify-content-center h-100"
ondrop="handleDrop(event, '{{ slot.id }}')"
ondragover="handleDragOver(event)"
ondragleave="handleDragLeave(event)">
<span class="text-muted">Drop image here or click to select</span>
</div>
</div>
</div>
</div>
</for>
</div>
</div>
<!-- Available Images Section -->
<div class="col-md-6">
<h2>Available Images</h2>
<!-- Search Bar -->
<div class="mb-3">
<input type="text"
id="image-search"
class="form-control"
placeholder="Search images..."
onkeyup="searchImages()">
</div>
<!-- Images Grid -->
<div id="images-grid" class="row g-2">
<for var="image" in="images">
<div class="col-md-4 image-item"
data-filename="{{ image.originalFilename }}"
data-alt="{{ image.altText }}">
<div class="card">
<img src="/media/images/{{ image.path }}"
alt="{{ image.altText }}"
class="card-img-top"
style="height: 150px; object-fit: cover; cursor: move;"
draggable="true"
ondragstart="handleDragStart(event, '{{ image.ulid }}')"
onclick="selectImage('{{ image.ulid }}')">
<div class="card-body p-2">
<small class="text-truncate d-block">
{{ image.originalFilename }}
</small>
<small class="text-muted">
{{ image.width }}x{{ image.height }} {{ image.fileSize }}KB
</small>
</div>
</div>
</div>
</for>
</div>
</div>
</div>
</div>
<!-- Image Selection Modal -->
<div class="modal fade" id="imageSelectModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Select Image for <span id="modal-slot-name"></span></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div id="modal-images" class="row g-2">
<!-- Images will be loaded here -->
</div>
</div>
</div>
</div>
</div>
<script>
// Current dragging image
let draggedImageUlid = null;
let selectedSlotId = null;
// Handle drag start
function handleDragStart(event, imageUlid) {
draggedImageUlid = imageUlid;
event.dataTransfer.effectAllowed = 'copy';
}
// Handle drag over
function handleDragOver(event) {
event.preventDefault();
event.currentTarget.classList.add('bg-light');
}
// Handle drag leave
function handleDragLeave(event) {
event.currentTarget.classList.remove('bg-light');
}
// Handle drop
async function handleDrop(event, slotId) {
event.preventDefault();
event.currentTarget.classList.remove('bg-light');
if (draggedImageUlid) {
await assignImageToSlot(slotId, draggedImageUlid);
}
}
// Assign image to slot
async function assignImageToSlot(slotId, imageUlid) {
try {
const response = await fetch(`/api/image-slots/${slotId}/image`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ image_ulid: imageUlid })
});
if (response.ok) {
location.reload(); // Simple reload for now
} else {
alert('Failed to assign image');
}
} catch (error) {
console.error('Error:', error);
alert('Error assigning image');
}
}
// Remove image from slot
async function removeImage(slotId) {
if (!confirm('Remove image from this slot?')) return;
try {
const response = await fetch(`/api/image-slots/${slotId}/image`, {
method: 'DELETE'
});
if (response.ok) {
location.reload();
} else {
alert('Failed to remove image');
}
} catch (error) {
console.error('Error:', error);
alert('Error removing image');
}
}
// Select image (click handler)
function selectImage(imageUlid) {
// Find which slot was clicked if any
const clickedSlot = document.querySelector('.slot-item.selecting');
if (clickedSlot) {
const slotId = clickedSlot.dataset.slotId;
assignImageToSlot(slotId, imageUlid);
clickedSlot.classList.remove('selecting');
}
}
// Search images
function searchImages() {
const searchTerm = document.getElementById('image-search').value.toLowerCase();
const imageItems = document.querySelectorAll('.image-item');
imageItems.forEach(item => {
const filename = item.dataset.filename.toLowerCase();
const alt = item.dataset.alt.toLowerCase();
if (filename.includes(searchTerm) || alt.includes(searchTerm)) {
item.style.display = '';
} else {
item.style.display = 'none';
}
});
}
// Add click handler to slots for selection mode
document.addEventListener('DOMContentLoaded', function() {
document.querySelectorAll('.slot-item').forEach(slot => {
const container = slot.querySelector('.slot-image-container');
if (container && !container.querySelector('img')) {
container.style.cursor = 'pointer';
container.addEventListener('click', function() {
// Remove previous selection
document.querySelectorAll('.slot-item').forEach(s => s.classList.remove('selecting'));
// Mark as selecting
slot.classList.add('selecting');
// Highlight available images
document.getElementById('images-grid').classList.add('selecting-mode');
});
}
});
});
// Add some CSS
const style = document.createElement('style');
style.textContent = `
.slot-item.selecting {
border: 2px solid #0d6efd;
background-color: #e7f1ff;
}
.selecting-mode .card {
cursor: pointer;
}
.selecting-mode .card:hover {
border-color: #0d6efd;
box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25);
}
.border-dashed {
border-style: dashed !important;
}
.object-fit-cover {
object-fit: cover;
}
`;
document.head.appendChild(style);
</script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

View File

@@ -1,125 +0,0 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ title }}</title>
<link rel="stylesheet" href="/css/admin.css">
</head>
<body class="admin-page">
<div class="admin-header">
<h1>Performance-Übersicht</h1>
</div>
<div class="admin-nav">
<a href="/admin">Dashboard</a>
<a href="/admin/routes">Routen</a>
<a href="/admin/services">Dienste</a>
<a href="/admin/environment">Umgebung</a>
<a href="/admin/performance" class="active">Performance</a>
<a href="/admin/redis">Redis</a>
<a href="/admin/phpinfo">PHP Info</a>
</div>
<div class="admin-content">
<div class="dashboard-stats">
<div class="stat-box">
<h3>Aktueller Speicherverbrauch</h3>
<div class="stat-value">{{ performance.currentMemoryUsage }}</div>
</div>
<div class="stat-box">
<h3>Maximaler Speicherverbrauch</h3>
<div class="stat-value">{{ performance.peakMemoryUsage }}</div>
</div>
<div class="stat-box">
<h3>Speicherlimit</h3>
<div class="stat-value">{{ performance.memoryLimit }}</div>
</div>
<div class="stat-box">
<h3>Speicherauslastung</h3>
<div class="stat-value">
<div class="progress-bar">
<div class="progress" style="width: {{ performance.memoryUsagePercentage }}%"></div>
</div>
<div class="progress-value">{{ performance.memoryUsagePercentage }}%</div>
</div>
</div>
<div class="stat-box">
<h3>Systemlast (1/5/15 min)</h3>
<div class="stat-value">
{{ performance.loadAverage.0 }} /
{{ performance.loadAverage.1 }} /
{{ performance.loadAverage.2 }}
</div>
</div>
<div class="stat-box">
<h3>OPCache aktiviert</h3>
<div class="stat-value">{{ performance.opcacheEnabled }}</div>
</div>
<div if="performance.opcacheMemoryUsage">
<div class="stat-box">
<h3>OPCache Speicherverbrauch</h3>
<div class="stat-value">{{ performance.opcacheMemoryUsage }}</div>
</div>
<div class="stat-box">
<h3>OPCache Cache Hits</h3>
<div class="stat-value">{{ performance.opcacheCacheHits }}</div>
</div>
<div class="stat-box">
<h3>OPCache Miss Rate</h3>
<div class="stat-value">{{ performance.opcacheMissRate }}</div>
</div>
</div>
<div class="stat-box">
<h3>Ausführungszeit</h3>
<div class="stat-value">{{ performance.executionTime }}</div>
</div>
<div class="stat-box">
<h3>Geladene Dateien</h3>
<div class="stat-value">{{ performance.includedFiles }}</div>
</div>
</div>
<div class="admin-section">
<h2>Geladene Dateien</h2>
<div class="admin-tools">
<input type="text" id="fileFilter" placeholder="Dateien filtern..." class="search-input">
</div>
<div class="file-list" id="fileList">
<for var="file" in="performance.files">
<div class="file-item">
{{ file }}
</div>
</for>
</div>
</div>
</div>
<div class="admin-footer">
<p>&copy; {{ current_year }} Framework Admin</p>
</div>
<script>
document.getElementById('fileFilter').addEventListener('input', function() {
const filterValue = this.value.toLowerCase();
const items = document.querySelectorAll('#fileList .file-item');
items.forEach(item => {
const text = item.textContent.toLowerCase();
item.style.display = text.includes(filterValue) ? '' : 'none';
});
});
</script>
</body>
</html>

View File

@@ -1,94 +0,0 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ title }}</title>
<link rel="stylesheet" href="/css/admin.css">
</head>
<body class="admin-page">
<div class="admin-header">
<h1>Redis-Informationen</h1>
</div>
<div class="admin-nav">
<a href="/admin">Dashboard</a>
<a href="/admin/routes">Routen</a>
<a href="/admin/services">Dienste</a>
<a href="/admin/environment">Umgebung</a>
<a href="/admin/performance">Performance</a>
<a href="/admin/redis" class="active">Redis</a>
<a href="/admin/phpinfo">PHP Info</a>
</div>
<div class="admin-content">
<div class="dashboard-stats">
<div class="stat-box">
<h3>Status</h3>
<div class="stat-value status-connected">{{ redis.status }}</div>
</div>
<div class="stat-box">
<h3>Version</h3>
<div class="stat-value">{{ redis.version }}</div>
</div>
<div class="stat-box">
<h3>Uptime</h3>
<div class="stat-value">{{ redis.uptime }}</div>
</div>
<div class="stat-box">
<h3>Speicherverbrauch</h3>
<div class="stat-value">{{ redis.memory }}</div>
</div>
<div class="stat-box">
<h3>Max. Speicherverbrauch</h3>
<div class="stat-value">{{ redis.peak_memory }}</div>
</div>
<div class="stat-box">
<h3>Verbundene Clients</h3>
<div class="stat-value">{{ redis.clients }}</div>
</div>
<div class="stat-box">
<h3>Anzahl Schlüssel</h3>
<div class="stat-value">{{ redis.keys }}</div>
</div>
</div>
<div class="admin-section">
<h2>Schlüssel (max. 50 angezeigt)</h2>
<div class="admin-tools">
<input type="text" id="keyFilter" placeholder="Schlüssel filtern..." class="search-input">
</div>
<div class="key-list" id="keyList">
<for var="key" in="redis.key_sample">
<div class="key-item">
{{ key }}
</div>
</for>
</div>
</div>
</div>
<div class="admin-footer">
<p>&copy; {{ current_year }} Framework Admin</p>
</div>
<script>
document.getElementById('keyFilter')?.addEventListener('input', function() {
const filterValue = this.value.toLowerCase();
const items = document.querySelectorAll('#keyList .key-item');
items.forEach(item => {
const text = item.textContent.toLowerCase();
item.style.display = text.includes(filterValue) ? '' : 'none';
});
});
</script>
</body>
</html>

View File

@@ -1,69 +0,0 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?= $title ?></title>
<link rel="stylesheet" href="/css/admin.css">
</head>
<body class="admin-page">
<div class="admin-header">
<h1>Routen-Übersicht</h1>
</div>
<div class="admin-nav">
<a href="/admin">Dashboard</a>
<a href="/admin/routes" class="active">Routen</a>
<a href="/admin/services">Dienste</a>
<a href="/admin/environment">Umgebung</a>
<a href="/admin/performance">Performance</a>
<a href="/admin/redis">Redis</a>
<a href="/admin/phpinfo">PHP Info</a>
</div>
<div class="admin-content">
<div class="admin-tools">
<input type="text" id="routeFilter" placeholder="Routen filtern..." class="search-input">
</div>
<table class="admin-table" id="routesTable">
<thead>
<tr>
<th>Pfad</th>
<th>Methode</th>
<th>Controller</th>
<th>Aktion</th>
<th>Name</th>
</tr>
</thead>
<tbody>
<?php foreach ($routes as $route): ?>
<tr>
<td><?= $route->path ?></td>
<td><?= $route->method ?></td>
<td><?= $route->controllerClass ?></td>
<td><?= $route->methodName ?></td>
<td><?= $route->name ?? '-' ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<div class="admin-footer">
<p>&copy; <?= date('Y') ?> Framework Admin</p>
</div>
<script>
document.getElementById('routeFilter').addEventListener('input', function() {
const filterValue = this.value.toLowerCase();
const rows = document.querySelectorAll('#routesTable tbody tr');
rows.forEach(row => {
const text = row.textContent.toLowerCase();
row.style.display = text.includes(filterValue) ? '' : 'none';
});
});
</script>
</body>
</html>

View File

@@ -1,67 +0,0 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ title }}</title>
<link rel="stylesheet" href="/css/admin.css">
</head>
<body class="admin-page">
<div class="admin-header">
<h1>Registrierte Dienste</h1>
</div>
<div class="admin-nav">
<a href="/admin">Dashboard</a>
<a href="/admin/routes">Routen</a>
<a href="/admin/services" class="active">Dienste</a>
<a href="/admin/environment">Umgebung</a>
<a href="/admin/performance">Performance</a>
<a href="/admin/redis">Redis</a>
<a href="/admin/phpinfo">PHP Info</a>
</div>
<div class="admin-content">
<div class="admin-tools">
<input type="text" id="serviceFilter" placeholder="Dienste filtern..." class="search-input">
<span class="services-count">{{ servicesCount }} Dienste insgesamt</span>
</div>
<div class="service-list" id="serviceList">
<for var="service" in="services">
<div class="service-item">
<div class="service-name">{{ service.name }}</div>
<div class="service-category">
<span class="category-badge">{{ service.category }}</span>
<if condition="service.subCategory">
<span class="subcategory-badge">{{ service.subCategory }}</span>
</if>
</div>
</div>
</for>
</div>
</div>
<div class="admin-footer">
<p>&copy; {{ date('Y') }} Framework Admin</p>
</div>
<script>
document.getElementById('serviceFilter').addEventListener('input', function() {
const filterValue = this.value.toLowerCase();
const items = document.querySelectorAll('#serviceList .service-item');
let visibleCount = 0;
items.forEach(item => {
const text = item.textContent.toLowerCase();
const isVisible = text.includes(filterValue);
item.style.display = isVisible ? '' : 'none';
if (isVisible) visibleCount++;
});
document.querySelector('.services-count').textContent =
visibleCount + ' von ' + {{ servicesCount }} + ' Diensten';
});
</script>
</body>
</html>