Files
domnitor/app/Views/errors/debug.twig
Hosteroid 4818172bc6 Switch PHP views to Twig and add 2FA/UI enhancements
Migrate many view templates from raw PHP to Twig and modernize UI/UX for 2FA and settings. Controllers updated to provide avatar data and two-factor info (ProfileController, UserController) and SettingsController now includes timezone lists, notification preset selection, cron path, cached update state and rollback availability. ErrorHandler now attempts to render error pages via a new Core\TwigService with a safe fallback to raw PHP views. TwoFactorService generation silences deprecated warnings during QR code creation. Numerous .php view files were removed and replaced with .twig equivalents (2fa setup/verify/backup-codes and many auth, dashboard, domains, errors, layout, users, tags, tld-registry, etc.), and core/TwigService was added. These changes move the app toward a Twig-based templating system, improve 2FA flows, surface avatar images in lists/profiles, and make error rendering more robust.
2026-03-03 18:21:32 +02:00

531 lines
27 KiB
Twig

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Debug Error - {{ error_type|default('Application Error') }}</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" crossorigin="anonymous" referrerpolicy="no-referrer" />
<script>
tailwind.config = {
theme: {
extend: {
colors: {
primary: {
DEFAULT: '#4A90E2',
dark: '#357ABD',
light: '#6BA3E8',
}
}
}
}
}
</script>
<style>
body { background-color: #f8f9fa; }
@keyframes fadeIn {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
.animate-fade-in { animation: fadeIn 0.4s ease-out; }
.code-block { background-color: #1e1e1e; color: #d4d4d4; }
.line-number { color: #858585; user-select: none; }
</style>
</head>
<body class="min-h-screen p-6">
<!-- Header -->
<div class="max-w-7xl mx-auto mb-6">
<div class="bg-white rounded-lg shadow-sm border border-gray-200 p-5">
<div class="flex items-center justify-between flex-wrap gap-4">
<div class="flex items-center space-x-4">
<div class="w-12 h-12 bg-red-100 rounded-lg flex items-center justify-center">
<i class="fas fa-bug text-red-600 text-xl"></i>
</div>
<div>
<h1 class="text-2xl font-bold text-gray-900">Debug Mode</h1>
<p class="text-sm text-gray-600 mt-0.5">
<i class="fas fa-circle text-orange-500 mr-1 text-xs animate-pulse"></i>
Development Environment - Detailed Error Information
</p>
</div>
</div>
<button onclick="copyErrorReport()"
class="inline-flex items-center px-4 py-2.5 bg-primary hover:bg-primary-dark text-white rounded-lg transition-colors font-medium text-sm">
<i class="fas fa-clipboard mr-2"></i>
Copy Error Report
</button>
</div>
</div>
</div>
<div class="max-w-7xl mx-auto">
<!-- Primary Error Card -->
<div class="bg-white rounded-lg shadow-sm border-l-4 border-red-500 mb-6 animate-fade-in">
<div class="p-6">
<div class="flex items-start mb-6">
<div class="flex-shrink-0 w-14 h-14 bg-red-100 rounded-lg flex items-center justify-center mr-4">
<i class="fas fa-exclamation-triangle text-red-600 text-2xl"></i>
</div>
<div class="flex-1">
<h2 class="text-2xl font-bold text-gray-900 mb-2">
{{ error_type|default('Error') }}
</h2>
<p class="text-lg text-gray-700 mb-4">{{ error_message|default('An error occurred') }}</p>
<div class="bg-red-50 border border-red-200 rounded-lg p-4">
<h3 class="text-sm font-semibold text-gray-900 mb-3 flex items-center">
<i class="fas fa-map-marker-alt text-red-500 mr-2 text-xs"></i>
Error Location
</h3>
<div class="space-y-2">
<div>
<span class="text-xs font-medium text-gray-600">File:</span>
<code class="block mt-1 bg-white px-3 py-2 rounded text-sm text-gray-800 border border-gray-200 font-mono break-all">
{{ error_file|default('Unknown') }}
</code>
</div>
<div class="flex items-center">
<span class="text-xs font-medium text-gray-600 mr-2">Line:</span>
<span class="font-mono text-red-600 font-bold text-lg">{{ error_line|default('?') }}</span>
</div>
</div>
</div>
</div>
</div>
<!-- Quick Info Grid -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
<div class="bg-blue-50 rounded-lg border border-blue-200 p-4">
<div class="flex items-center justify-between mb-2">
<h4 class="text-xs font-semibold text-gray-700 uppercase tracking-wide">Error ID</h4>
<button onclick="copyToClipboard('{{ error_id|default('N/A') }}')"
class="text-primary hover:text-primary-dark" title="Copy Error ID">
<i class="fas fa-copy text-xs"></i>
</button>
</div>
<code class="text-sm font-mono font-bold text-primary">{{ error_id|default('N/A') }}</code>
<p class="text-xs text-gray-600 mt-2">
<i class="fas fa-info-circle mr-1"></i>
Use for bug reports
</p>
</div>
<div class="bg-gray-50 rounded-lg border border-gray-200 p-4">
<h4 class="text-xs font-semibold text-gray-700 uppercase tracking-wide mb-2">Request</h4>
<div class="space-y-1">
<p class="text-sm">
<span class="font-mono font-bold text-gray-900">{{ request_method|default('GET') }}</span>
</p>
<code class="text-xs text-gray-600 font-mono block truncate" title="{{ request_uri|default('/') }}">
{{ request_uri|default('/') }}
</code>
</div>
</div>
<div class="bg-gray-50 rounded-lg border border-gray-200 p-4">
<h4 class="text-xs font-semibold text-gray-700 uppercase tracking-wide mb-2">User</h4>
{% if user_info %}
<p class="text-sm font-semibold text-gray-900">{{ user_info.username }}</p>
<p class="text-xs text-gray-600">
<i class="fas fa-user mr-1"></i>
{{ user_info.role }}
</p>
{% else %}
<p class="text-sm text-gray-500">
<i class="fas fa-user-slash mr-1"></i>
Guest (Not logged in)
</p>
{% endif %}
</div>
<div class="bg-gray-50 rounded-lg border border-gray-200 p-4">
<h4 class="text-xs font-semibold text-gray-700 uppercase tracking-wide mb-2">System</h4>
<div class="space-y-1">
<p class="text-xs text-gray-600">
<i class="fas fa-code mr-1"></i>
PHP {{ php_version|default('unknown') }}
</p>
<p class="text-xs text-gray-600">
<i class="fas fa-memory mr-1"></i>
{{ memory_usage_mb|default(0) }}MB
</p>
<p class="text-xs text-gray-600">
<i class="fas fa-clock mr-1"></i>
{{ occurred_at|default('now')|date("H:i:s") }}
</p>
</div>
</div>
</div>
</div>
</div>
<!-- Two Column Layout -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<!-- Left Column -->
<div class="space-y-6">
{% if stack_trace is defined and stack_trace %}
<div class="bg-white rounded-lg shadow-sm border border-gray-200 overflow-hidden animate-fade-in">
<div class="px-6 py-4 border-b border-gray-200 bg-gray-50">
<div class="flex items-center justify-between">
<h3 class="text-lg font-semibold text-gray-900 flex items-center">
<i class="fas fa-layer-group text-primary mr-2 text-sm"></i>
Stack Trace
</h3>
<button onclick="copyStackTrace()"
class="text-sm text-primary hover:text-primary-dark font-medium">
<i class="fas fa-copy mr-1"></i>
Copy
</button>
</div>
</div>
<div class="p-6">
<div class="code-block rounded-lg p-4 overflow-x-auto max-h-96 overflow-y-auto border border-gray-700" id="stack-trace">
{% for line in stack_trace|split("\n") %}
{% if line|trim %}
<div class="flex font-mono text-sm">
<span class="line-number mr-4 text-right" style="min-width: 2rem">{{ '%02d'|format(loop.index0) }}</span>
<span class="flex-1 text-green-400">{{ line }}</span>
</div>
{% endif %}
{% endfor %}
</div>
</div>
</div>
{% endif %}
{% if request_data is defined and request_data %}
<div class="bg-white rounded-lg shadow-sm border border-gray-200 overflow-hidden animate-fade-in">
<button onclick="toggleSection('request-data')"
class="w-full px-6 py-4 border-b border-gray-200 bg-gray-50 text-left hover:bg-gray-100 transition-colors">
<h3 class="text-lg font-semibold text-gray-900 flex items-center justify-between">
<span class="flex items-center">
<i class="fas fa-paper-plane text-blue-500 mr-2 text-sm"></i>
Request Data
<span class="ml-2 text-xs bg-blue-100 text-blue-800 px-2 py-1 rounded font-medium">
{{ request_data|length }}
</span>
</span>
<i class="fas fa-chevron-down text-gray-400 text-xs transition-transform" id="request-data-chevron"></i>
</h3>
</button>
<div id="request-data" class="hidden p-6">
<div class="space-y-3">
{% for key, value in request_data %}
<div class="bg-gray-50 rounded-lg p-3 border border-gray-200">
<span class="text-xs font-semibold text-gray-700 uppercase tracking-wide block mb-1">
{{ key }}
</span>
<code class="text-sm text-gray-800 font-mono block break-all">
{% if value is iterable %}
{{ value|json_encode(constant('JSON_PRETTY_PRINT')) }}
{% else %}
{{ value }}
{% endif %}
</code>
</div>
{% endfor %}
</div>
</div>
</div>
{% endif %}
</div>
<!-- Right Column -->
<div class="space-y-6">
<!-- Request Details -->
<div class="bg-white rounded-lg shadow-sm border border-gray-200 overflow-hidden animate-fade-in">
<div class="px-6 py-4 border-b border-gray-200 bg-gray-50">
<h3 class="text-lg font-semibold text-gray-900 flex items-center">
<i class="fas fa-globe text-green-500 mr-2 text-sm"></i>
Request Details
</h3>
</div>
<div class="p-6">
<div class="space-y-3 text-sm">
<div class="flex items-center justify-between py-2 border-b border-gray-100">
<span class="font-medium text-gray-600">Method</span>
<span class="font-mono font-bold text-gray-900">{{ request_method|default('GET') }}</span>
</div>
<div class="py-2 border-b border-gray-100">
<span class="font-medium text-gray-600 block mb-1">URI</span>
<code class="text-xs text-gray-800 font-mono block break-all bg-gray-50 px-2 py-1 rounded">
{{ request_uri|default('/') }}
</code>
</div>
<div class="py-2 border-b border-gray-100">
<span class="font-medium text-gray-600 block mb-1">IP Address</span>
<code class="text-xs text-gray-800 font-mono block bg-gray-50 px-2 py-1 rounded">
{{ ip_address|default('Unknown') }}
</code>
</div>
<div class="py-2">
<span class="font-medium text-gray-600 block mb-1">User Agent</span>
<code class="text-xs text-gray-600 font-mono block break-all bg-gray-50 px-2 py-1 rounded">
{{ user_agent|default('Unknown') }}
</code>
</div>
</div>
</div>
</div>
<!-- System Information -->
<div class="bg-white rounded-lg shadow-sm border border-gray-200 overflow-hidden animate-fade-in">
<div class="px-6 py-4 border-b border-gray-200 bg-gray-50">
<h3 class="text-lg font-semibold text-gray-900 flex items-center">
<i class="fas fa-server text-indigo-500 mr-2 text-sm"></i>
System Information
</h3>
</div>
<div class="p-6">
<div class="space-y-3 text-sm">
<div class="flex items-center justify-between py-2 border-b border-gray-100">
<span class="font-medium text-gray-600">PHP Version</span>
<span class="font-mono text-gray-900">{{ php_version|default('unknown') }}</span>
</div>
<div class="flex items-center justify-between py-2 border-b border-gray-100">
<span class="font-medium text-gray-600">Memory Usage</span>
<span class="font-mono text-gray-900">{{ memory_usage_mb|default(0) }}MB</span>
</div>
<div class="flex items-center justify-between py-2 border-b border-gray-100">
<span class="font-medium text-gray-600">Peak Memory</span>
<span class="font-mono text-gray-900">{{ peak_memory_mb|default(0) }}MB</span>
</div>
<div class="flex items-center justify-between py-2">
<span class="font-medium text-gray-600">Timestamp</span>
<span class="font-mono text-gray-900 text-xs">{{ occurred_at|default('now')|date("Y-m-d H:i:s T") }}</span>
</div>
</div>
</div>
</div>
{% if session_data is defined and session_data %}
<div class="bg-white rounded-lg shadow-sm border border-gray-200 overflow-hidden animate-fade-in">
<button onclick="toggleSection('session-data')"
class="w-full px-6 py-4 border-b border-gray-200 bg-gray-50 text-left hover:bg-gray-100 transition-colors">
<h3 class="text-lg font-semibold text-gray-900 flex items-center justify-between">
<span class="flex items-center">
<i class="fas fa-user-lock text-orange-500 mr-2 text-sm"></i>
Session Data
<span class="ml-2 text-xs bg-orange-100 text-orange-800 px-2 py-1 rounded font-medium">
{{ session_data|length }}
</span>
</span>
<i class="fas fa-chevron-down text-gray-400 text-xs transition-transform" id="session-data-chevron"></i>
</h3>
</button>
<div id="session-data" class="hidden p-6">
<div class="max-h-80 overflow-y-auto">
<table class="min-w-full text-sm">
<thead class="bg-gray-50 sticky top-0">
<tr>
<th class="px-3 py-2 text-left text-xs font-semibold text-gray-600 uppercase">Key</th>
<th class="px-3 py-2 text-left text-xs font-semibold text-gray-600 uppercase">Value</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-100">
{% for key, value in session_data %}
<tr class="hover:bg-gray-50">
<td class="px-3 py-2 font-mono text-gray-700 align-top">{{ key }}</td>
<td class="px-3 py-2 font-mono text-gray-600 break-all">
{% if value is iterable %}
{{ value|json_encode }}
{% else %}
{{ value }}
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% endif %}
</div>
</div>
<!-- Help Card -->
<div class="bg-blue-50 border border-blue-200 rounded-lg p-5 mt-6 animate-fade-in">
<div class="flex items-start">
<div class="flex-shrink-0">
<div class="w-10 h-10 bg-blue-500 rounded-lg flex items-center justify-center">
<i class="fas fa-lightbulb text-white"></i>
</div>
</div>
<div class="ml-4">
<h3 class="text-sm font-semibold text-gray-900 mb-2">Debug Mode Active</h3>
<p class="text-sm text-gray-700 mb-3">
This detailed error page is only shown in development mode. In production, users will see a clean error page with just the error ID.
</p>
<div class="flex flex-wrap gap-2">
<a href="/" class="inline-flex items-center px-3 py-2 bg-white border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 transition-colors text-sm font-medium">
<i class="fas fa-home mr-2"></i>
Go to Dashboard
</a>
<button onclick="location.reload()" class="inline-flex items-center px-3 py-2 bg-white border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 transition-colors text-sm font-medium">
<i class="fas fa-redo mr-2"></i>
Reload Page
</button>
</div>
</div>
</div>
</div>
</div>
<!-- Footer -->
<div class="max-w-7xl mx-auto mt-8">
<div class="text-center text-sm text-gray-500">
<p>
<i class="fas fa-globe text-primary mr-1"></i>
Domain Monitor &copy; {{ "now"|date("Y") }} &bull; Development Mode
</p>
</div>
</div>
<script>
function toggleSection(sectionId) {
const section = document.getElementById(sectionId);
const chevron = document.getElementById(sectionId + '-chevron');
if (section.classList.contains('hidden')) {
section.classList.remove('hidden');
if (chevron) chevron.style.transform = 'rotate(180deg)';
} else {
section.classList.add('hidden');
if (chevron) chevron.style.transform = 'rotate(0deg)';
}
}
function copyToClipboard(text) {
if (navigator.clipboard && window.isSecureContext) {
navigator.clipboard.writeText(text).then(() => {
showCopySuccess();
}).catch(err => {
fallbackCopy(text);
});
} else {
fallbackCopy(text);
}
}
function fallbackCopy(text) {
const textArea = document.createElement('textarea');
textArea.value = text;
textArea.style.position = 'fixed';
textArea.style.left = '-999999px';
document.body.appendChild(textArea);
textArea.select();
try {
document.execCommand('copy');
showCopySuccess();
} catch (err) {
console.error('Copy failed:', err);
}
document.body.removeChild(textArea);
}
function copyStackTrace() {
const stackTraceElement = document.getElementById('stack-trace');
const lines = stackTraceElement.querySelectorAll('div');
let stackText = '';
lines.forEach(line => {
const textSpan = line.querySelector('span:last-child');
if (textSpan) stackText += textSpan.textContent + '\n';
});
copyToClipboard(stackText.trim());
}
function copyErrorReport() {
const errorType = {{ (error_type|default('Error'))|json_encode|raw }};
const errorMessage = {{ (error_message|default('Unknown error'))|json_encode|raw }};
const errorFile = {{ (error_file|default('Unknown'))|json_encode|raw }};
const errorLine = {{ (error_line|default('?'))|json_encode|raw }};
const errorId = {{ (error_id|default('N/A'))|json_encode|raw }};
const phpVersion = {{ (php_version|default('unknown'))|json_encode|raw }};
const requestMethod = {{ (request_method|default('GET'))|json_encode|raw }};
const requestUri = {{ (request_uri|default('/'))|json_encode|raw }};
const userAgent = {{ (user_agent|default('Unknown'))|json_encode|raw }};
const ipAddress = {{ (ip_address|default('Unknown'))|json_encode|raw }};
const timestamp = {{ (occurred_at|default('now'))|json_encode|raw }};
const userInfo = {{ (user_info|default(null))|json_encode|raw }};
const userText = userInfo ? `${userInfo.username} (${userInfo.role}, ID: ${userInfo.id})` : 'Guest (Not logged in)';
const stackTraceElement = document.getElementById('stack-trace');
let stackTrace = 'Not available';
if (stackTraceElement) {
const lines = stackTraceElement.querySelectorAll('div');
let stackText = '';
lines.forEach(line => {
const textSpan = line.querySelector('span:last-child');
if (textSpan) stackText += textSpan.textContent + '\n';
});
stackTrace = stackText.trim();
}
const errorReport = `=== DOMAIN MONITOR ERROR REPORT ===
ERROR INFORMATION:
- Error ID: ${errorId}
- Type: ${errorType}
- Message: ${errorMessage}
LOCATION:
- File: ${errorFile}
- Line: ${errorLine}
REQUEST DETAILS:
- Method: ${requestMethod}
- URI: ${requestUri}
- Timestamp: ${timestamp}
USER CONTEXT:
- User: ${userText}
- IP Address: ${ipAddress}
- User Agent: ${userAgent}
SYSTEM INFORMATION:
- PHP Version: ${phpVersion}
- Memory Usage: {{ memory_usage_mb|default(0) }}MB
- Peak Memory: {{ peak_memory_mb|default(0) }}MB
STACK TRACE:
${stackTrace}
=== END OF ERROR REPORT ===
Reference ID: ${errorId}
Please include this report when reporting bugs.`;
copyToClipboard(errorReport);
}
function showCopySuccess() {
const message = document.createElement('div');
message.className = 'fixed top-4 right-4 bg-green-500 text-white px-4 py-3 rounded-lg shadow-lg z-50 flex items-center animate-fade-in';
message.innerHTML = '<i class="fas fa-check mr-2"></i>Copied to clipboard!';
document.body.appendChild(message);
setTimeout(() => {
message.style.opacity = '0';
message.style.transform = 'translateY(-20px)';
message.style.transition = 'all 0.3s ease-out';
setTimeout(() => message.remove(), 300);
}, 2000);
}
</script>
</body>
</html>