License Server
Analytics
Licenses
Components
Edit Component
Update the component metadata, code, and availability.
View Component
Back
Component Name
Component name cannot be changed after creation.
Display Name
*
Version
*
Active
Description
Component: Log Interpreter
Controller Code (Optional)
<?php namespace App\Http\Controllers\Components\LogInterpreter; use App\Http\Controllers\Controller; use App\Models\Setting; use Illuminate\Http\Request; use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Log; use Illuminate\Validation\ValidationException; class ComponentLogInterpreterController extends Controller { /** * Display the log interpreter interface. */ public function index() { return view('components.log-interpreter.index'); } /** * Analyze logs using OpenAI * POST /dashboard/analyze-logs */ public function analyzeLogs(Request $request) { // Force JSON response - this is an API endpoint if (!$request->wantsJson() && !$request->expectsJson()) { $request->headers->set('Accept', 'application/json'); } // Always return JSON for API endpoints try { $validated = $request->validate([ 'log_content' => 'required|string|max:50000', 'log_type' => 'nullable|string', 'log_level' => 'nullable|string', ]); } catch (ValidationException $e) { return response()->json([ 'success' => false, 'error' => 'Validation failed: ' . implode(', ', $e->validator->errors()->all()), 'errors' => $e->errors(), ], 422); } $logContent = $validated['log_content']; $logType = $validated['log_type'] ?? 'general'; $logLevel = $validated['log_level'] ?? 'info'; // Build prompt for OpenAI $prompt = "Analyze the following {$logType} log (level: {$logLevel}) and provide:\n\n"; $prompt .= "1. A summary of what happened\n"; $prompt .= "2. Identified issues or errors\n"; $prompt .= "3. Recommended solutions\n\n"; $prompt .= "Log content:\n{$logContent}"; // Call OpenAI API directly (controller runs on license server) $user = auth()->user(); if (!$user || !$user->license_key) { return response()->json([ 'success' => false, 'error' => 'License key not found. Please configure your license in settings.', ], 403); } try { // Get OpenAI API key from database $apiKey = Setting::get('openai_api_key'); if (!$apiKey) { return response()->json([ 'success' => false, 'error' => 'OpenAI API key not configured on license server', ], 500); } $response = Http::withHeaders([ 'Authorization' => 'Bearer ' . $apiKey, 'Content-Type' => 'application/json', ])->timeout(90)->post('https://api.openai.com/v1/chat/completions', [ 'model' => Setting::get('openai_model', 'gpt-4'), 'messages' => [ [ 'role' => 'user', 'content' => $prompt ] ], 'max_tokens' => (int) Setting::get('openai_max_tokens', 2000), 'temperature' => (float) Setting::get('openai_temperature', 0.1), ]); if (!$response->successful()) { Log::error('OpenAI API error', [ 'status' => $response->status(), 'body' => $response->body(), ]); return response()->json([ 'success' => false, 'error' => 'OpenAI API request failed: ' . $response->body() ], $response->status()); } $data = $response->json(); if (!isset($data['choices'][0]['message']['content'])) { return response()->json([ 'success' => false, 'error' => 'Invalid response from OpenAI API' ], 500); } return response()->json([ 'success' => true, 'data' => $data['choices'][0]['message']['content'], ]); } catch (\Exception $e) { Log::error('Log analysis exception', [ 'error' => $e->getMessage(), ]); return response()->json([ 'success' => false, 'error' => 'Analysis failed: ' . $e->getMessage(), ], 500); } } }
PHP code for the component controller.
Routes Code (Optional)
<?php use Illuminate\Support\Facades\Route; use App\Http\Controllers\Components\LogInterpreter\ComponentLogInterpreterController; Route::middleware(['auth', 'component.visible'])->group(function () { Route::get('/log-interpreter', [ComponentLogInterpreterController::class, 'index'])->name('dashboard.log-interpreter'); // API route - always returns JSON Route::post('/analyze-logs', [ComponentLogInterpreterController::class, 'analyzeLogs']) ->name('dashboard.analyze-logs') ->middleware('api'); // Ensure JSON response });
Route definitions for the component.
Views (Optional - JSON)
{ "index.blade.php": "@extends('layouts.app')\n\n@section('title', 'Log Interpreter - Matomo Tools')\n\n@section('styles')\n@include('dashboard._floating_container_styles')\n@endsection\n\n@section('content')\n<div class=\"floating-container\">\n<div class=\"row\">\n <!-- Log Interpreter Header -->\n <div class=\"col-12 mb-4\">\n <div class=\"card\">\n <div class=\"card-header d-flex align-items-center justify-content-between\">\n <h5 class=\"card-title mb-0\">Log Interpreter & Solutions<\/h5>\n <div class=\"d-flex gap-2\">\n <button class=\"btn btn-outline-primary\" onclick=\"clearLogs()\">\n <i class=\"fas fa-eraser me-1\"><\/i> Clear\n <\/button>\n <button class=\"btn btn-outline-success\" onclick=\"uploadLogFile()\">\n <i class=\"fas fa-upload me-1\"><\/i> Upload Log\n <\/button>\n <button class=\"btn btn-primary\" onclick=\"analyzeLogs()\">\n <i class=\"fas fa-search me-1\"><\/i> Analyze\n <\/button>\n <\/div>\n <\/div>\n <\/div>\n <\/div>\n <!-- \/ Log Interpreter Header -->\n\n <!-- Log Input Card -->\n <div class=\"col-lg-6 mb-4\">\n <div class=\"card\">\n <div class=\"card-header\">\n <h6 class=\"card-title mb-0\">Log Input<\/h6>\n <\/div>\n <div class=\"card-body\">\n <div class=\"mb-4\">\n <h6 class=\"text-primary mb-3\">Paste Log Content<\/h6>\n \n <!-- Privacy Notice -->\n <div class=\"alert alert-warning alert-dismissible fade show\" role=\"alert\">\n <i class=\"fas fa-shield-alt me-2\"><\/i>\n <strong>Privacy Notice:<\/strong> Please remove or redact sensitive information before uploading logs, including:\n <ul class=\"mb-0 mt-2\">\n <li>IP addresses and hostnames<\/li>\n <li>Confidential domains and subdomains<\/li>\n <li>API keys, passwords, and tokens<\/li>\n <li>Personal data and user information<\/li>\n <li>Internal file paths and server details<\/li>\n <\/ul>\n <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"alert\" aria-label=\"Close\"><\/button>\n <\/div>\n \n <textarea \n id=\"logInput\" \n class=\"form-control\" \n rows=\"15\" \n placeholder=\"Paste your Matomo error logs, PHP errors, or system logs here... (Remember to redact sensitive information)\"\n style=\"font-family: 'Courier New', monospace; font-size: 0.9rem;\"\n ><\/textarea>\n <\/div>\n \n <div class=\"mb-4\">\n <h6 class=\"text-primary mb-3\">Log Type<\/h6>\n <div class=\"row\">\n <div class=\"col-md-6\">\n <select class=\"form-select\" id=\"logType\">\n <option value=\"auto\">Auto-detect<\/option>\n <option value=\"matomo\">Matomo Error Log<\/option>\n <option value=\"php\">PHP Error Log<\/option>\n <option value=\"apache\">Apache Error Log<\/option>\n <option value=\"nginx\">Nginx Error Log<\/option>\n <option value=\"mysql\">MySQL Error Log<\/option>\n <option value=\"system\">System Log<\/option>\n <\/select>\n <\/div>\n <div class=\"col-md-6\">\n <select class=\"form-select\" id=\"logLevel\">\n <option value=\"all\">All Levels<\/option>\n <option value=\"error\">Errors Only<\/option>\n <option value=\"warning\">Warnings & Errors<\/option>\n <option value=\"info\">Info, Warnings & Errors<\/option>\n <\/select>\n <\/div>\n <\/div>\n <\/div>\n <\/div>\n <\/div>\n <\/div>\n <!-- \/ Log Input Card -->\n\n <!-- Analysis Results Card -->\n <div class=\"col-lg-6 mb-4\">\n <div class=\"card analysis-results-card\">\n <div class=\"card-header\">\n <h6 class=\"card-title mb-0\">Analysis Results<\/h6>\n <\/div>\n <div class=\"card-body\">\n <div class=\"log-analysis-results\">\n <div id=\"analysisResults\" class=\"bg-light p-3 rounded\" style=\"max-height: 500px; overflow-y: auto;\">\n <p class=\"text-muted text-center\">Paste logs and click Analyze to see results<\/p>\n <\/div>\n <\/div>\n <\/div>\n <\/div>\n <\/div>\n <!-- \/ Analysis Results Card -->\n\n <!-- Solutions Panel Card -->\n <div class=\"col-12 mb-4\">\n <div class=\"card\">\n <div class=\"card-header\">\n <h6 class=\"card-title mb-0\">Recommended Solutions<\/h6>\n <\/div>\n <div class=\"card-body\">\n <div id=\"solutionsPanel\">\n <p class=\"text-muted text-center\">Analyze logs to see recommended solutions<\/p>\n <\/div>\n <\/div>\n <\/div>\n <\/div>\n <!-- \/ Solutions Panel Card -->\n<\/div>\n<\/div>\n@endsection\n\n@section('styles')\n<style>\n.log-entry {\n border-left: 4px solid var(--border-color);\n padding: 0.75rem;\n margin-bottom: 0.5rem;\n border-radius: 0 var(--radius-md) var(--radius-md) 0;\n background: var(--surface-color);\n}\n\n.log-entry.error {\n border-left-color: #dc3545;\n background: rgba(220, 53, 69, 0.1);\n}\n\n.log-entry.warning {\n border-left-color: #ffc107;\n background: rgba(255, 193, 7, 0.1);\n}\n\n.log-entry.info {\n border-left-color: #17a2b8;\n background: rgba(23, 162, 184, 0.1);\n}\n\n.log-timestamp {\n font-size: 0.8rem;\n color: var(--text-muted);\n font-weight: 600;\n}\n\n.log-level {\n display: inline-block;\n padding: 0.25rem 0.5rem;\n border-radius: var(--radius-sm);\n font-size: 0.75rem;\n font-weight: 600;\n text-transform: uppercase;\n}\n\n.log-level.error {\n background: #dc3545;\n color: white;\n}\n\n.log-level.warning {\n background: #ffc107;\n color: #212529;\n}\n\n.log-level.info {\n background: #17a2b8;\n color: white;\n}\n\n.log-message {\n margin-top: 0.5rem;\n font-family: 'Courier New', monospace;\n font-size: 0.9rem;\n white-space: pre-wrap;\n word-break: break-word;\n}\n\n.solution-card {\n border: 1px solid var(--border-color);\n border-radius: var(--radius-lg);\n padding: 1rem;\n margin-bottom: 1rem;\n background: var(--surface-color);\n}\n\n.solution-card.high-priority {\n border-left: 4px solid #dc3545;\n}\n\n.solution-card.medium-priority {\n border-left: 4px solid #ffc107;\n}\n\n.solution-card.low-priority {\n border-left: 4px solid #28a745;\n}\n\n.solution-title {\n font-weight: 600;\n margin-bottom: 0.5rem;\n color: var(--text-primary);\n}\n\n.solution-description {\n color: var(--text-muted);\n margin-bottom: 0.75rem;\n}\n\n.solution-actions {\n display: flex;\n gap: 0.5rem;\n flex-wrap: wrap;\n}\n\n.solution-actions .btn {\n font-size: 0.8rem;\n padding: 0.25rem 0.5rem;\n}\n\n.error-pattern {\n background: rgba(220, 53, 69, 0.1);\n border: 1px solid rgba(220, 53, 69, 0.3);\n border-radius: var(--radius-sm);\n padding: 0.5rem;\n margin: 0.5rem 0;\n font-family: 'Courier New', monospace;\n font-size: 0.85rem;\n}\n\n.stats-card {\n background: var(--card-color);\n border: 1px solid var(--border-color);\n border-radius: var(--radius-lg);\n padding: 1rem;\n text-align: center;\n}\n\n.stats-number {\n font-size: 2rem;\n font-weight: 700;\n color: var(--accent-color);\n}\n\n.stats-label {\n font-size: 0.9rem;\n color: var(--text-muted);\n text-transform: uppercase;\n letter-spacing: 0.5px;\n}\n<\/style>\n@endsection\n\n@section('scripts')\n<script>\n\/\/ Error patterns and solutions database\nconst errorPatterns = {\n \/\/ Matomo specific errors\n 'matomo': {\n 'Database connection failed': {\n priority: 'high',\n solutions: [\n {\n title: 'Check Database Configuration',\n description: 'Verify database credentials in config\/config.ini.php',\n actions: [\n { text: 'View Config', action: 'viewConfig' },\n { text: 'Test Connection', action: 'testDbConnection' }\n ]\n },\n {\n title: 'Check Database Server Status',\n description: 'Ensure MySQL\/MariaDB server is running and accessible',\n actions: [\n { text: 'Check Service Status', action: 'checkDbService' },\n { text: 'View Server Logs', action: 'viewDbLogs' }\n ]\n }\n ]\n },\n 'Plugin.*not found': {\n priority: 'medium',\n solutions: [\n {\n title: 'Reinstall Plugin',\n description: 'The plugin may be corrupted or missing files',\n actions: [\n { text: 'Reinstall Plugin', action: 'reinstallPlugin' },\n { text: 'Check Plugin Files', action: 'checkPluginFiles' }\n ]\n }\n ]\n },\n 'Archive.*failed': {\n priority: 'high',\n solutions: [\n {\n title: 'Check Archive Process',\n description: 'Archive process may have failed due to memory or timeout issues',\n actions: [\n { text: 'Run Manual Archive', action: 'runManualArchive' },\n { text: 'Check Memory Settings', action: 'checkMemorySettings' }\n ]\n }\n ]\n },\n 'option does not exist': {\n priority: 'high',\n solutions: [\n {\n title: 'Fix Invalid Command Option',\n description: 'The command option you used is not valid. Check the correct syntax.',\n actions: [\n { text: 'View Command Help', action: 'showCommandHelp' },\n { text: 'Get Correct Syntax', action: 'getCorrectSyntax' }\n ]\n },\n {\n title: 'Update Matomo Version',\n description: 'The option might not be available in your Matomo version',\n actions: [\n { text: 'Check Matomo Version', action: 'checkMatomoVersion' },\n { text: 'Update Matomo', action: 'updateMatomo' }\n ]\n }\n ]\n },\n 'force-all-periods': {\n priority: 'high',\n solutions: [\n {\n title: 'Use Correct Option: --force-periods',\n description: 'Replace --force-all-periods with --force-periods and specify the periods',\n actions: [\n { text: 'Show Correct Command', action: 'showCorrectCommand' },\n { text: 'Copy Fixed Command', action: 'copyFixedCommand' }\n ]\n }\n ]\n }\n },\n \n \/\/ PHP errors\n 'php': {\n 'Fatal error.*memory': {\n priority: 'high',\n solutions: [\n {\n title: 'Increase PHP Memory Limit',\n description: 'PHP memory limit is too low for the operation',\n actions: [\n { text: 'Update php.ini', action: 'updatePhpIni' },\n { text: 'Check Current Limit', action: 'checkMemoryLimit' }\n ]\n }\n ]\n },\n 'Fatal error.*timeout': {\n priority: 'high',\n solutions: [\n {\n title: 'Increase PHP Timeout',\n description: 'PHP execution time limit exceeded',\n actions: [\n { text: 'Update Timeout Settings', action: 'updateTimeout' },\n { text: 'Optimize Query', action: 'optimizeQuery' }\n ]\n }\n ]\n },\n 'Class.*not found': {\n priority: 'medium',\n solutions: [\n {\n title: 'Check Autoloader',\n description: 'Class autoloading may be broken',\n actions: [\n { text: 'Regenerate Autoloader', action: 'regenerateAutoloader' },\n { text: 'Check Class Path', action: 'checkClassPath' }\n ]\n }\n ]\n }\n },\n \n \/\/ Database errors\n 'mysql': {\n 'Table.*doesn\\'t exist': {\n priority: 'high',\n solutions: [\n {\n title: 'Run Database Migration',\n description: 'Database tables may be missing or outdated',\n actions: [\n { text: 'Run Migration', action: 'runMigration' },\n { text: 'Check Schema', action: 'checkSchema' }\n ]\n }\n ]\n },\n 'Connection.*refused': {\n priority: 'high',\n solutions: [\n {\n title: 'Check Database Service',\n description: 'Database service may be down or not accessible',\n actions: [\n { text: 'Start Service', action: 'startDbService' },\n { text: 'Check Port', action: 'checkDbPort' }\n ]\n }\n ]\n }\n },\n \n \/\/ Web server errors\n 'apache': {\n 'Permission denied': {\n priority: 'medium',\n solutions: [\n {\n title: 'Fix File Permissions',\n description: 'Insufficient permissions for file access',\n actions: [\n { text: 'Fix Permissions', action: 'fixPermissions' },\n { text: 'Check Ownership', action: 'checkOwnership' }\n ]\n }\n ]\n }\n },\n \n 'nginx': {\n '502 Bad Gateway': {\n priority: 'high',\n solutions: [\n {\n title: 'Check PHP-FPM Status',\n description: 'PHP-FPM service may be down or misconfigured',\n actions: [\n { text: 'Check PHP-FPM', action: 'checkPhpFpm' },\n { text: 'Restart Services', action: 'restartServices' }\n ]\n }\n ]\n }\n }\n};\n\n\/\/ Analyze logs using OpenAI API\nfunction analyzeLogs() {\n const logContent = document.getElementById('logInput').value.trim();\n const logType = document.getElementById('logType').value;\n const logLevel = document.getElementById('logLevel').value;\n \n if (!logContent) {\n showNotification('Please paste some log content first', 'warning');\n return;\n }\n \n const resultsDiv = document.getElementById('analysisResults');\n const solutionsDiv = document.getElementById('solutionsPanel');\n \n resultsDiv.innerHTML = '<div class=\"text-center\"><i class=\"fas fa-spinner fa-spin\"><\/i> Analyzing logs with AI...<\/div>';\n solutionsDiv.innerHTML = '<div class=\"text-center\"><i class=\"fas fa-spinner fa-spin\"><\/i> Finding intelligent solutions...<\/div>';\n \n \/\/ Call the backend OpenAI API\n fetch('\/dashboard\/analyze-logs', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application\/json',\n 'Accept': 'application\/json',\n 'X-CSRF-TOKEN': document.querySelector('meta[name=\"csrf-token\"]').getAttribute('content')\n },\n body: JSON.stringify({\n log_content: logContent,\n log_type: logType,\n log_level: logLevel\n })\n })\n .then(response => {\n console.log('Response status:', response.status);\n const contentType = response.headers.get('content-type') || '';\n \n return response.text().then(text => {\n console.log('Raw response:', text.substring(0, 200));\n \n \/\/ Check if response is HTML (error page or redirect)\n if (contentType.includes('text\/html') || text.trim().startsWith('<!DOCTYPE') || text.trim().startsWith('<!doctype')) {\n console.error('Server returned HTML instead of JSON:', text.substring(0, 500));\n throw new Error('Server returned HTML error page. Route may not be registered on client. Please reload the component from license server: POST \/dashboard\/analyze-logs');\n }\n \n if (!response.ok) {\n throw new Error(`HTTP error! status: ${response.status}. Response: ${text.substring(0, 200)}`);\n }\n \n try {\n return JSON.parse(text);\n } catch (e) {\n console.error('JSON parse error:', e);\n console.error('Response text:', text);\n throw new Error('Server returned invalid JSON: ' + text.substring(0, 200));\n }\n });\n })\n .then(data => {\n console.log('Parsed data:', data);\n if (data.success) {\n displayOpenAIAnalysis(data.data);\n } else {\n showNotification('Analysis failed: ' + data.error, 'error');\n resultsDiv.innerHTML = '<div class=\"text-center text-danger\">Analysis failed. Please try again.<\/div>';\n solutionsDiv.innerHTML = '<div class=\"text-center text-danger\">Unable to generate solutions.<\/div>';\n }\n })\n .catch(error => {\n console.error('Error:', error);\n showNotification('Error during analysis: ' + error.message, 'error');\n resultsDiv.innerHTML = '<div class=\"text-center text-danger\">' + error.message + '<\/div>';\n solutionsDiv.innerHTML = '<div class=\"text-center text-danger\">Unable to generate solutions.<\/div>';\n });\n}\n\n\/\/ Enhanced Matomo command analysis\nfunction analyzeMatomoCommand(logContent) {\n const lines = logContent.split('\\n');\n const commandLine = lines.find(line => line.includes('php') && line.includes('console'));\n \n if (!commandLine) return null;\n \n const analysis = {\n originalCommand: commandLine.trim(),\n issues: [],\n suggestions: [],\n correctedCommand: null,\n explanation: ''\n };\n \n \/\/ Check for common Matomo command issues\n if (commandLine.includes('--force-all-periods')) {\n analysis.issues.push({\n type: 'error',\n message: 'Invalid option: --force-all-periods does not exist',\n severity: 'high'\n });\n \n analysis.suggestions.push({\n type: 'fix',\n message: 'Replace --force-all-periods with --force-periods=day,week,month,year',\n action: 'replaceOption'\n });\n \n \/\/ Generate corrected command\n analysis.correctedCommand = commandLine.replace('--force-all-periods=1', '--force-periods=day,week,month,year');\n analysis.explanation = 'The --force-all-periods option was removed in newer Matomo versions. Use --force-periods instead and specify which periods you want to archive.';\n }\n \n \/\/ Check for other common issues\n if (commandLine.includes('--force-idsites') && !commandLine.includes('--force-periods')) {\n analysis.suggestions.push({\n type: 'info',\n message: 'Consider adding --force-periods to specify which periods to archive',\n action: 'addPeriods'\n });\n }\n \n \/\/ Check for URL issues\n if (commandLine.includes('--url=') && !commandLine.includes('https:\/\/') && !commandLine.includes('http:\/\/')) {\n analysis.suggestions.push({\n type: 'warning',\n message: 'URL should include protocol (http:\/\/ or https:\/\/)',\n action: 'fixUrl'\n });\n }\n \n \/\/ Analyze successful execution\n if (logContent.includes('Archived website') && logContent.includes('Done archiving!')) {\n analysis.executionStatus = 'success';\n analysis.summary = extractArchiveSummary(logContent);\n } else if (logContent.includes('ERROR') || logContent.includes('Exception')) {\n analysis.executionStatus = 'failed';\n }\n \n return analysis;\n}\n\n\/\/ Extract archive summary from successful execution\nfunction extractArchiveSummary(logContent) {\n const lines = logContent.split('\\n');\n const summary = {\n processedArchives: 0,\n totalRequests: 0,\n timeElapsed: '',\n websites: []\n };\n \n lines.forEach(line => {\n if (line.includes('Processed') && line.includes('archives')) {\n const match = line.match(\/Processed (\\d+) archives\/);\n if (match) summary.processedArchives = parseInt(match[1]);\n }\n \n if (line.includes('Total API requests:')) {\n const match = line.match(\/Total API requests: (\\d+)\/);\n if (match) summary.totalRequests = parseInt(match[1]);\n }\n \n if (line.includes('Time elapsed:')) {\n const match = line.match(\/Time elapsed: ([\\d.]+s)\/);\n if (match) summary.timeElapsed = match[1];\n }\n \n if (line.includes('Archived website id')) {\n const match = line.match(\/Archived website id (\\d+)\/);\n if (match && !summary.websites.includes(match[1])) {\n summary.websites.push(match[1]);\n }\n }\n });\n \n return summary;\n}\n\n\/\/ Parse log content\nfunction parseLogs(content, logType, logLevel) {\n const lines = content.split('\\n').filter(line => line.trim());\n const parsedEntries = [];\n const errorCounts = { error: 0, warning: 0, info: 0 };\n const patterns = [];\n \n \/\/ First pass: collect all entries and unique error messages\n const uniqueErrorMessages = new Set();\n \n lines.forEach((line, index) => {\n const entry = parseLogLine(line, logType);\n if (entry) {\n parsedEntries.push(entry);\n errorCounts[entry.level]++;\n \n \/\/ Collect unique error messages for pattern matching\n if (entry.level === 'error' || entry.level === 'warning') {\n uniqueErrorMessages.add(entry.message);\n }\n }\n });\n \n \/\/ Second pass: generate patterns only for unique error messages\n uniqueErrorMessages.forEach(message => {\n const matchedPatterns = findErrorPatterns(message, logType);\n patterns.push(...matchedPatterns);\n });\n \n \/\/ Count unclassified lines\n const unclassifiedCount = lines.length - parsedEntries.length;\n \n \/\/ Filter out duplicate error messages (stack trace continuation)\n const filteredEntries = [];\n const seenMessages = new Set();\n \n parsedEntries.forEach(entry => {\n \/\/ Normalize the message for better deduplication\n const normalizedMessage = entry.message\n .replace(\/^Uncaught exception:.*?:\\s*\/, '') \/\/ Remove \"Uncaught exception: ... :\" prefix\n .replace(\/^\\s+|\\s+$\/g, '') \/\/ Trim whitespace\n .toLowerCase();\n \n \/\/ Skip if we've seen this normalized message before\n if (!seenMessages.has(normalizedMessage)) {\n filteredEntries.push(entry);\n seenMessages.add(normalizedMessage);\n }\n });\n \n \/\/ Recalculate counts after filtering\n const filteredCounts = { error: 0, warning: 0, info: 0 };\n filteredEntries.forEach(entry => {\n filteredCounts[entry.level]++;\n });\n \n \/\/ Deduplicate patterns to avoid showing same solutions multiple times\n const deduplicatedPatterns = deduplicatePatterns(patterns);\n \n return {\n entries: filteredEntries,\n stats: filteredCounts,\n patterns: deduplicatedPatterns,\n totalLines: lines.length,\n unclassifiedLines: unclassifiedCount\n };\n}\n\n\/\/ Parse individual log line\nfunction parseLogLine(line, logType) {\n \/\/ Enhanced log patterns for better detection\n const patterns = {\n matomo: \/^\\[(\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2})\\] (ERROR|WARNING|INFO): (.+)$\/,\n php: \/^\\[(\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2})\\] PHP (Fatal error|Warning|Notice): (.+)$\/,\n apache: \/^\\[(\\w{3} \\w{3} \\d{2} \\d{2}:\\d{2}:\\d{2} \\d{4})\\] \\[(error|warn|info)\\]: (.+)$\/,\n nginx: \/^(\\d{4}\\\/\\d{2}\\\/\\d{2} \\d{2}:\\d{2}:\\d{2}) \\[(error|warn|info)\\] (.+)$\/,\n mysql: \/^(\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}.\\d+Z) \\[(ERROR|WARNING|INFO)\\] (.+)$\/,\n \/\/ Additional patterns for different log formats\n console: \/^(\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2})\\s+(ERROR|WARNING|INFO)\\s+(.+)$\/,\n \/\/ Pattern for lines like \"ERROR [2025-09-20 09:03:05] 1977372 Uncaught exception:\"\n error_with_timestamp: \/^(ERROR|WARNING|INFO)\\s+\\[(\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2})\\]\\s+\\d+\\s+(.+)$\/\n };\n \n \/\/ Try to match with the specified pattern first\n let pattern = patterns[logType] || patterns.matomo;\n let match = line.match(pattern);\n \n \/\/ If no match with specified pattern, try all patterns\n if (!match) {\n for (const [patternName, patternRegex] of Object.entries(patterns)) {\n match = line.match(patternRegex);\n if (match) {\n \/\/ Handle different pattern formats\n if (patternName === 'error_with_timestamp') {\n return {\n timestamp: match[2],\n level: match[1].toLowerCase(),\n message: match[3],\n originalLine: line\n };\n } else if (match.length >= 3) {\n return {\n timestamp: match[1],\n level: match[2].toLowerCase().replace('fatal error', 'error'),\n message: match[3],\n originalLine: line\n };\n }\n }\n }\n }\n \n \/\/ If no match, try to detect error level from content\n if (!match) {\n \/\/ Skip stack trace lines (lines starting with #)\n if (line.trim().startsWith('#')) {\n return null;\n }\n \n \/\/ Look for common error indicators in the line\n if (line.includes('ERROR') || line.includes('Fatal error') || line.includes('Exception') || \n line.includes('Error:') || line.includes('Uncaught exception') || line.includes('does not exist')) {\n return {\n timestamp: extractTimestamp(line) || new Date().toISOString(),\n level: 'error',\n message: line,\n originalLine: line\n };\n }\n \n \/\/ Special handling for lines that are clearly error messages but don't match patterns\n if (line.includes('The \"--force-all-periods\" option does not exist')) {\n return {\n timestamp: extractTimestamp(line) || new Date().toISOString(),\n level: 'error',\n message: line,\n originalLine: line\n };\n }\n \n \/\/ Skip lines that are just continuation of error messages (standalone error text)\n if (line.trim() === 'The \"--force-all-periods\" option does not exist.' || \n line.trim() === 'The \"--force-all-periods\" option does not exist. ') {\n return null; \/\/ Skip this line as it's a duplicate\n } else if (line.includes('WARNING') || line.includes('Warning') || line.includes('WARN')) {\n return {\n timestamp: extractTimestamp(line) || new Date().toISOString(),\n level: 'warning',\n message: line,\n originalLine: line\n };\n } else if (line.includes('INFO') || line.includes('Info') || line.includes('NOTICE') || line.includes('Notice')) {\n return {\n timestamp: extractTimestamp(line) || new Date().toISOString(),\n level: 'info',\n message: line,\n originalLine: line\n };\n }\n }\n \n if (match) {\n return {\n timestamp: match[1],\n level: match[2].toLowerCase().replace('fatal error', 'error'),\n message: match[3],\n originalLine: line\n };\n }\n \n \/\/ Only classify as info if the line actually contains INFO\/INFO: pattern\n if (line.match(\/\\b(INFO|INFO:)\\b\/)) {\n return {\n timestamp: extractTimestamp(line) || new Date().toISOString(),\n level: 'info',\n message: line,\n originalLine: line\n };\n }\n \n \/\/ For unrecognized format, return null instead of defaulting to info\n return null;\n}\n\n\/\/ Extract timestamp from line\nfunction extractTimestamp(line) {\n const timestampPatterns = [\n \/(\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2})\/,\n \/(\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2})\/,\n \/(\\d{4}\\\/\\d{2}\\\/\\d{2} \\d{2}:\\d{2}:\\d{2})\/,\n \/(\\w{3} \\w{3} \\d{2} \\d{2}:\\d{2}:\\d{2} \\d{4})\/\n ];\n \n for (const pattern of timestampPatterns) {\n const match = line.match(pattern);\n if (match) {\n return match[1];\n }\n }\n \n return null;\n}\n\n\/\/ Find error patterns in message\nfunction findErrorPatterns(message, logType) {\n const patterns = errorPatterns[logType] || errorPatterns.matomo;\n const foundPatterns = [];\n \n Object.keys(patterns).forEach(pattern => {\n const regex = new RegExp(pattern, 'i');\n if (regex.test(message)) {\n foundPatterns.push({\n pattern: pattern,\n solutions: patterns[pattern].solutions,\n priority: patterns[pattern].priority\n });\n }\n });\n \n \/\/ If no specific patterns found, generate generic solutions\n if (foundPatterns.length === 0) {\n foundPatterns.push(generateGenericSolutions(message));\n }\n \n return foundPatterns;\n}\n\n\/\/ Deduplicate patterns to avoid showing same solutions multiple times\nfunction deduplicatePatterns(patterns) {\n const seenPatterns = new Set();\n const seenSolutions = new Set();\n const deduplicated = [];\n \n patterns.forEach(pattern => {\n \/\/ Create a unique key for this pattern based on its solutions\n const solutionKey = pattern.solutions.map(s => s.title).sort().join('|');\n \n if (!seenSolutions.has(solutionKey)) {\n seenSolutions.add(solutionKey);\n seenPatterns.add(pattern.pattern);\n deduplicated.push(pattern);\n }\n });\n \n return deduplicated;\n}\n\n\/\/ Generate generic solutions for unrecognized errors\nfunction generateGenericSolutions(message) {\n const solutions = [];\n \n \/\/ Analyze the error message for common keywords\n const lowerMessage = message.toLowerCase();\n \n \/\/ Database-related errors\n if (lowerMessage.includes('database') || lowerMessage.includes('mysql') || lowerMessage.includes('sql')) {\n solutions.push({\n title: 'Database Issue Detected',\n description: 'This appears to be a database-related error. Check your database configuration and connectivity.',\n actions: [\n { text: 'Check DB Config', action: 'checkDbConfig' },\n { text: 'Test Connection', action: 'testDbConnection' },\n { text: 'View DB Logs', action: 'viewDbLogs' }\n ]\n });\n }\n \n \/\/ Permission-related errors\n if (lowerMessage.includes('permission') || lowerMessage.includes('access denied') || lowerMessage.includes('forbidden')) {\n solutions.push({\n title: 'Permission Issue Detected',\n description: 'This appears to be a file or directory permission error.',\n actions: [\n { text: 'Check Permissions', action: 'checkPermissions' },\n { text: 'Fix Ownership', action: 'fixOwnership' },\n { text: 'View File Info', action: 'viewFileInfo' }\n ]\n });\n }\n \n \/\/ Memory-related errors\n if (lowerMessage.includes('memory') || lowerMessage.includes('out of memory') || lowerMessage.includes('fatal error')) {\n solutions.push({\n title: 'Memory Issue Detected',\n description: 'This appears to be a memory-related error. Consider increasing PHP memory limits.',\n actions: [\n { text: 'Check Memory Limit', action: 'checkMemoryLimit' },\n { text: 'Update PHP Config', action: 'updatePhpIni' },\n { text: 'Optimize Code', action: 'optimizeCode' }\n ]\n });\n }\n \n \/\/ Network-related errors\n if (lowerMessage.includes('connection') || lowerMessage.includes('timeout') || lowerMessage.includes('network')) {\n solutions.push({\n title: 'Network Issue Detected',\n description: 'This appears to be a network connectivity or timeout error.',\n actions: [\n { text: 'Test Connectivity', action: 'testConnectivity' },\n { text: 'Check Firewall', action: 'checkFirewall' },\n { text: 'Increase Timeout', action: 'increaseTimeout' }\n ]\n });\n }\n \n \/\/ Configuration errors\n if (lowerMessage.includes('config') || lowerMessage.includes('setting') || lowerMessage.includes('parameter')) {\n solutions.push({\n title: 'Configuration Issue Detected',\n description: 'This appears to be a configuration or parameter error.',\n actions: [\n { text: 'Check Config Files', action: 'checkConfigFiles' },\n { text: 'Validate Settings', action: 'validateSettings' },\n { text: 'Reset to Defaults', action: 'resetToDefaults' }\n ]\n });\n }\n \n \/\/ Generic fallback solution\n if (solutions.length === 0) {\n solutions.push({\n title: 'General Error Analysis',\n description: 'This error is not in our database, but here are some general troubleshooting steps.',\n actions: [\n { text: 'Search Online', action: 'searchOnline' },\n { text: 'Check Documentation', action: 'checkDocumentation' },\n { text: 'Contact Support', action: 'contactSupport' }\n ]\n });\n }\n \n return {\n pattern: 'generic',\n priority: 'medium',\n solutions: solutions\n };\n}\n\n\/\/ Display analysis results\nfunction displayAnalysis(analysis) {\n const resultsDiv = document.getElementById('analysisResults');\n \n let html = '';\n \n \/\/ Show Matomo command analysis first if available\n if (analysis.matomoCommand) {\n html += displayMatomoCommandAnalysis(analysis.matomoCommand);\n }\n \n html += `\n <div class=\"row mb-3\">\n <div class=\"col-md-4\">\n <div class=\"stats-card\">\n <div class=\"stats-number text-danger\">${analysis.stats.error}<\/div>\n <div class=\"stats-label\">Errors<\/div>\n <\/div>\n <\/div>\n <div class=\"col-md-4\">\n <div class=\"stats-card\">\n <div class=\"stats-number text-warning\">${analysis.stats.warning}<\/div>\n <div class=\"stats-label\">Warnings<\/div>\n <\/div>\n <\/div>\n <div class=\"col-md-4\">\n <div class=\"stats-card\">\n <div class=\"stats-number text-success\">${analysis.stats.error + analysis.stats.warning === 0 ? '\u2713' : analysis.stats.error + analysis.stats.warning}<\/div>\n <div class=\"stats-label\">${analysis.stats.error + analysis.stats.warning === 0 ? 'All Good!' : 'Issues Found'}<\/div>\n <\/div>\n <\/div>\n <\/div>\n \n <div class=\"row mb-3\">\n <div class=\"col-md-6\">\n <div class=\"stats-card\">\n <div class=\"stats-number text-secondary\">${analysis.unclassifiedLines || 0}<\/div>\n <div class=\"stats-label\">Unclassified Lines<\/div>\n <\/div>\n <\/div>\n <div class=\"col-md-6\">\n <div class=\"stats-card\">\n <div class=\"stats-number text-success\">${analysis.stats.error + analysis.stats.warning + analysis.stats.info}<\/div>\n <div class=\"stats-label\">Classified Lines<\/div>\n <\/div>\n <\/div>\n <\/div>\n `;\n \n \/\/ Only show ERROR and WARNING entries, not INFO\n const importantEntries = analysis.entries.filter(entry => \n entry.level === 'error' || entry.level === 'warning'\n );\n \n if (importantEntries.length > 0) {\n html += '<h6 class=\"text-primary mb-3\">Issues Found<\/h6>';\n importantEntries.forEach(entry => {\n html += `\n <div class=\"log-entry ${entry.level}\">\n <div class=\"d-flex justify-content-between align-items-start\">\n <span class=\"log-timestamp\">${entry.timestamp}<\/span>\n <span class=\"log-level ${entry.level}\">${entry.level.toUpperCase()}<\/span>\n <\/div>\n <div class=\"log-message\">${entry.message}<\/div>\n <\/div>\n `;\n });\n } else if (!analysis.matomoCommand) {\n html += '<div class=\"alert alert-success\"><i class=\"fas fa-check-circle me-2\"><\/i>No errors or warnings found in the log!<\/div>';\n }\n \n resultsDiv.innerHTML = html;\n}\n\n\/\/ Display Matomo command analysis\nfunction displayMatomoCommandAnalysis(matomoAnalysis) {\n let html = `\n <div class=\"matomo-command-analysis mb-4\">\n <h6 class=\"text-primary mb-3\"><i class=\"fas fa-terminal me-2\"><\/i>Matomo Command Analysis<\/h6>\n \n <div class=\"command-section mb-3\">\n <strong>Original Command:<\/strong>\n <div class=\"bg-dark p-2 rounded mt-1\">\n <code class=\"text-light\">${matomoAnalysis.originalCommand}<\/code>\n <\/div>\n <\/div>\n `;\n \n \/\/ Show issues\n if (matomoAnalysis.issues.length > 0) {\n html += '<div class=\"command-issues mb-3\">';\n html += '<strong class=\"text-danger\">Issues Found:<\/strong>';\n matomoAnalysis.issues.forEach(issue => {\n html += `\n <div class=\"alert alert-${issue.severity === 'high' ? 'danger' : 'warning'} mt-2\">\n <i class=\"fas fa-exclamation-triangle me-2\"><\/i>${issue.message}\n <\/div>\n `;\n });\n html += '<\/div>';\n }\n \n \/\/ Show corrected command\n if (matomoAnalysis.correctedCommand) {\n html += `\n <div class=\"command-fix mb-3\">\n <strong class=\"text-success\">Corrected Command:<\/strong>\n <div class=\"bg-dark p-2 rounded mt-1\">\n <code class=\"text-light\">${matomoAnalysis.correctedCommand}<\/code>\n <button class=\"btn btn-sm btn-outline-light ms-2\" onclick=\"copyToClipboard('${matomoAnalysis.correctedCommand}')\">\n <i class=\"fas fa-copy\"><\/i> Copy\n <\/button>\n <\/div>\n <div class=\"text-muted small mt-1\">${matomoAnalysis.explanation}<\/div>\n <\/div>\n `;\n }\n \n \/\/ Show suggestions\n if (matomoAnalysis.suggestions.length > 0) {\n html += '<div class=\"command-suggestions mb-3\">';\n html += '<strong class=\"text-info\">Suggestions:<\/strong>';\n matomoAnalysis.suggestions.forEach(suggestion => {\n const alertClass = suggestion.type === 'error' ? 'danger' : suggestion.type === 'warning' ? 'warning' : 'info';\n html += `\n <div class=\"alert alert-${alertClass} mt-2\">\n <i class=\"fas fa-lightbulb me-2\"><\/i>${suggestion.message}\n <\/div>\n `;\n });\n html += '<\/div>';\n }\n \n \/\/ Show execution summary\n if (matomoAnalysis.executionStatus === 'success' && matomoAnalysis.summary) {\n const summary = matomoAnalysis.summary;\n html += `\n <div class=\"execution-summary mb-3\">\n <strong class=\"text-success\">Execution Summary:<\/strong>\n <div class=\"row mt-2\">\n <div class=\"col-md-3\">\n <div class=\"stats-card\">\n <div class=\"stats-number text-primary\">${summary.processedArchives}<\/div>\n <div class=\"stats-label\">Archives Processed<\/div>\n <\/div>\n <\/div>\n <div class=\"col-md-3\">\n <div class=\"stats-card\">\n <div class=\"stats-number text-info\">${summary.totalRequests}<\/div>\n <div class=\"stats-label\">API Requests<\/div>\n <\/div>\n <\/div>\n <div class=\"col-md-3\">\n <div class=\"stats-card\">\n <div class=\"stats-number text-success\">${summary.timeElapsed}<\/div>\n <div class=\"stats-label\">Time Elapsed<\/div>\n <\/div>\n <\/div>\n <div class=\"col-md-3\">\n <div class=\"stats-card\">\n <div class=\"stats-number text-warning\">${summary.websites.length}<\/div>\n <div class=\"stats-label\">Websites<\/div>\n <\/div>\n <\/div>\n <\/div>\n <\/div>\n `;\n }\n \n html += '<\/div>';\n return html;\n}\n\n\/\/ Copy to clipboard function\nfunction copyToClipboard(text) {\n navigator.clipboard.writeText(text).then(() => {\n showNotification('Command copied to clipboard!', 'success');\n }).catch(() => {\n showNotification('Failed to copy to clipboard', 'error');\n });\n}\n\n\/\/ Display solutions\nfunction displaySolutions(analysis) {\n const solutionsDiv = document.getElementById('solutionsPanel');\n \n \/\/ Only show solutions if there are errors or warnings\n const hasIssues = analysis.stats.error > 0 || analysis.stats.warning > 0;\n \n if (!hasIssues) {\n solutionsDiv.innerHTML = '<div class=\"alert alert-success\"><i class=\"fas fa-check-circle me-2\"><\/i>No issues detected! Your log appears to be clean.<\/div>';\n return;\n }\n \n if (analysis.patterns.length === 0) {\n solutionsDiv.innerHTML = '<p class=\"text-muted text-center\">No specific solutions found. Check the issues above for general guidance.<\/p>';\n return;\n }\n \n let html = '';\n \n \/\/ Group solutions by priority\n const highPriority = analysis.patterns.filter(p => p.priority === 'high');\n const mediumPriority = analysis.patterns.filter(p => p.priority === 'medium');\n const lowPriority = analysis.patterns.filter(p => p.priority === 'low');\n \n [highPriority, mediumPriority, lowPriority].forEach((group, index) => {\n if (group.length === 0) return;\n \n const priorityClass = ['high-priority', 'medium-priority', 'low-priority'][index];\n const priorityTitle = ['High Priority', 'Medium Priority', 'Low Priority'][index];\n \n html += `<h6 class=\"text-primary mb-3\">${priorityTitle}<\/h6>`;\n \n group.forEach(pattern => {\n pattern.solutions.forEach(solution => {\n html += `\n <div class=\"solution-card ${priorityClass}\">\n <div class=\"solution-title\">${solution.title}<\/div>\n <div class=\"solution-description\">${solution.description}<\/div>\n <div class=\"error-pattern\">\n <strong>Pattern:<\/strong> ${pattern.pattern}\n <\/div>\n <div class=\"solution-actions\">\n ${solution.actions.map(action => \n `<button class=\"btn btn-outline-primary btn-sm\" onclick=\"executeSolution('${action.action}')\">${action.text}<\/button>`\n ).join('')}\n <\/div>\n <\/div>\n `;\n });\n });\n });\n \n solutionsDiv.innerHTML = html;\n}\n\n\/\/ Display OpenAI analysis results\nfunction displayOpenAIAnalysis(analysis) {\n const resultsDiv = document.getElementById('analysisResults');\n const solutionsDiv = document.getElementById('solutionsPanel');\n \n \/\/ Display analysis results\n let html = `\n <div class=\"alert alert-info\">\n <h6><i class=\"fas fa-robot me-2\"><\/i>AI-Powered Analysis<\/h6>\n <p class=\"mb-0\">${analysis.summary || 'Log analysis completed using artificial intelligence.'}<\/p>\n <\/div>\n \n <div class=\"row mb-3\">\n <div class=\"col-md-3\">\n <div class=\"stats-card\">\n <div class=\"stats-number text-danger\">${analysis.statistics?.error_count || 0}<\/div>\n <div class=\"stats-label\">Errors<\/div>\n <\/div>\n <\/div>\n <div class=\"col-md-3\">\n <div class=\"stats-card\">\n <div class=\"stats-number text-warning\">${analysis.statistics?.warning_count || 0}<\/div>\n <div class=\"stats-label\">Warnings<\/div>\n <\/div>\n <\/div>\n <div class=\"col-md-3\">\n <div class=\"stats-card\">\n <div class=\"stats-number text-info\">${analysis.statistics?.info_count || 0}<\/div>\n <div class=\"stats-label\">Info<\/div>\n <\/div>\n <\/div>\n <div class=\"col-md-3\">\n <div class=\"stats-card\">\n <div class=\"stats-number text-secondary\">${analysis.statistics?.total_lines || 0}<\/div>\n <div class=\"stats-label\">Total Lines<\/div>\n <\/div>\n <\/div>\n <\/div>\n `;\n \n \/\/ Display issues\n if (analysis.issues && analysis.issues.length > 0) {\n html += '<h6 class=\"text-primary mb-3\">Issues Found<\/h6>';\n analysis.issues.forEach((issue, index) => {\n const severityClass = issue.severity === 'error' ? 'danger' : issue.severity === 'warning' ? 'warning' : 'info';\n html += `\n <div class=\"alert alert-${severityClass} mb-3\">\n <div class=\"d-flex justify-content-between align-items-start\">\n <div class=\"flex-grow-1\">\n <h6 class=\"alert-heading\">${issue.title || 'Issue ' + (index + 1)}<\/h6>\n <p class=\"mb-2\">${issue.description || 'No description available'}<\/p>\n ${issue.timestamp ? `<small class=\"text-muted\">Time: ${issue.timestamp}<\/small><br>` : ''}\n ${issue.raw_message ? `<code class=\"small\">${issue.raw_message}<\/code>` : ''}\n <\/div>\n <span class=\"badge bg-${severityClass}\">${issue.severity?.toUpperCase() || 'UNKNOWN'}<\/span>\n <\/div>\n <\/div>\n `;\n });\n }\n \n resultsDiv.innerHTML = html;\n \n \/\/ Display solutions\n let solutionsHtml = '';\n \n if (analysis.issues && analysis.issues.length > 0) {\n solutionsHtml += '<h6 class=\"text-primary mb-3\">Recommended Solutions<\/h6>';\n \n analysis.issues.forEach((issue, index) => {\n if (issue.solution) {\n solutionsHtml += `\n <div class=\"solution-card mb-3\">\n <div class=\"solution-title\">${issue.solution.title || 'Solution for: ' + issue.title}<\/div>\n <div class=\"solution-description\">${issue.solution.description || 'No solution description available'}<\/div>\n \n ${issue.solution.commands && issue.solution.commands.length > 0 ? `\n <div class=\"mt-2\">\n <strong>Commands to run:<\/strong>\n <div class=\"mt-1\">\n ${issue.solution.commands.map(cmd => `<code class=\"d-block bg-light p-2 mb-1\">${cmd}<\/code>`).join('')}\n <\/div>\n <\/div>\n ` : ''}\n \n ${issue.solution.files_to_check && issue.solution.files_to_check.length > 0 ? `\n <div class=\"mt-2\">\n <strong>Files to check:<\/strong>\n <ul class=\"mb-0\">\n ${issue.solution.files_to_check.map(file => `<li><code>${file}<\/code><\/li>`).join('')}\n <\/ul>\n <\/div>\n ` : ''}\n \n ${issue.solution.prevention ? `\n <div class=\"mt-2\">\n <strong>Prevention:<\/strong>\n <p class=\"mb-0\">${issue.solution.prevention}<\/p>\n <\/div>\n ` : ''}\n <\/div>\n `;\n }\n });\n }\n \n \/\/ Display patterns if available\n if (analysis.patterns && analysis.patterns.length > 0) {\n solutionsHtml += '<h6 class=\"text-primary mb-3 mt-4\">Pattern Analysis<\/h6>';\n analysis.patterns.forEach(pattern => {\n solutionsHtml += `\n <div class=\"alert alert-light mb-2\">\n <strong>Pattern:<\/strong> ${pattern.pattern}<br>\n <strong>Frequency:<\/strong> ${pattern.frequency}<br>\n <strong>Suggested Fix:<\/strong> ${pattern.suggested_fix}\n <\/div>\n `;\n });\n }\n \n \/\/ Display recommendations\n if (analysis.recommendations && analysis.recommendations.length > 0) {\n solutionsHtml += '<h6 class=\"text-primary mb-3 mt-4\">General Recommendations<\/h6>';\n solutionsHtml += '<ul class=\"list-group\">';\n analysis.recommendations.forEach(rec => {\n solutionsHtml += `<li class=\"list-group-item\">${rec}<\/li>`;\n });\n solutionsHtml += '<\/ul>';\n }\n \n \/\/ Display next steps\n if (analysis.next_steps && analysis.next_steps.length > 0) {\n solutionsHtml += '<h6 class=\"text-primary mb-3 mt-4\">Next Steps<\/h6>';\n solutionsHtml += '<ol class=\"list-group list-group-numbered\">';\n analysis.next_steps.forEach(step => {\n solutionsHtml += `<li class=\"list-group-item\">${step}<\/li>`;\n });\n solutionsHtml += '<\/ol>';\n }\n \n if (!solutionsHtml) {\n solutionsHtml = '<div class=\"alert alert-success\"><i class=\"fas fa-check-circle me-2\"><\/i>No specific issues found in the logs!<\/div>';\n }\n \n solutionsDiv.innerHTML = solutionsHtml;\n}\n\n\/\/ Execute solution action\nfunction executeSolution(action) {\n const actions = {\n viewConfig: () => showNotification('Opening config file...', 'info'),\n testDbConnection: () => showNotification('Testing database connection...', 'info'),\n checkDbService: () => showNotification('Checking database service status...', 'info'),\n viewDbLogs: () => showNotification('Opening database logs...', 'info'),\n reinstallPlugin: () => showNotification('Reinstalling plugin...', 'info'),\n checkPluginFiles: () => showNotification('Checking plugin files...', 'info'),\n runManualArchive: () => showNotification('Running manual archive...', 'info'),\n checkMemorySettings: () => showNotification('Checking memory settings...', 'info'),\n updatePhpIni: () => showNotification('Opening php.ini editor...', 'info'),\n checkMemoryLimit: () => showNotification('Checking current memory limit...', 'info'),\n updateTimeout: () => showNotification('Updating timeout settings...', 'info'),\n optimizeQuery: () => showNotification('Optimizing query...', 'info'),\n showCommandHelp: () => showCommandHelp(),\n getCorrectSyntax: () => getCorrectSyntax(),\n checkMatomoVersion: () => checkMatomoVersion(),\n updateMatomo: () => showNotification('Opening Matomo update guide...', 'info'),\n showCorrectCommand: () => showCorrectCommand(),\n copyFixedCommand: () => copyFixedCommand(),\n regenerateAutoloader: () => showNotification('Regenerating autoloader...', 'info'),\n checkClassPath: () => showNotification('Checking class path...', 'info'),\n runMigration: () => showNotification('Running database migration...', 'info'),\n checkSchema: () => showNotification('Checking database schema...', 'info'),\n startDbService: () => showNotification('Starting database service...', 'info'),\n checkDbPort: () => showNotification('Checking database port...', 'info'),\n fixPermissions: () => showNotification('Fixing file permissions...', 'info'),\n checkOwnership: () => showNotification('Checking file ownership...', 'info'),\n checkPhpFpm: () => showNotification('Checking PHP-FPM status...', 'info'),\n restartServices: () => showNotification('Restarting services...', 'info'),\n \/\/ Generic solution actions\n checkDbConfig: () => showNotification('Checking database configuration...', 'info'),\n testConnectivity: () => showNotification('Testing network connectivity...', 'info'),\n checkFirewall: () => showNotification('Checking firewall settings...', 'info'),\n increaseTimeout: () => showNotification('Increasing timeout settings...', 'info'),\n checkConfigFiles: () => showNotification('Checking configuration files...', 'info'),\n validateSettings: () => showNotification('Validating settings...', 'info'),\n resetToDefaults: () => showNotification('Resetting to default configuration...', 'info'),\n searchOnline: () => searchOnline(),\n checkDocumentation: () => checkDocumentation(),\n contactSupport: () => contactSupport(),\n optimizeCode: () => showNotification('Optimizing code...', 'info')\n };\n \n if (actions[action]) {\n actions[action]();\n } else {\n showNotification(`Action \"${action}\" not implemented yet`, 'warning');\n }\n}\n\n\/\/ Upload log file\nfunction uploadLogFile() {\n const input = document.createElement('input');\n input.type = 'file';\n input.accept = '.log,.txt';\n input.onchange = function(e) {\n const file = e.target.files[0];\n if (file) {\n const reader = new FileReader();\n reader.onload = function(e) {\n document.getElementById('logInput').value = e.target.result;\n showNotification('Log file uploaded successfully!', 'success');\n };\n reader.readAsText(file);\n }\n };\n input.click();\n}\n\n\/\/ Clear logs\nfunction clearLogs() {\n document.getElementById('logInput').value = '';\n document.getElementById('analysisResults').innerHTML = '<p class=\"text-muted text-center\">Paste logs and click Analyze to see results<\/p>';\n document.getElementById('solutionsPanel').innerHTML = '<p class=\"text-muted text-center\">Analyze logs to see recommended solutions<\/p>';\n}\n\n\/\/ Show notification\nfunction showNotification(message, type = 'info') {\n const notification = document.createElement('div');\n notification.className = `alert alert-${type} alert-dismissible fade show position-fixed`;\n notification.style.cssText = 'top: 20px; right: 20px; z-index: 9999; min-width: 300px;';\n notification.innerHTML = `\n ${message}\n <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"alert\" aria-label=\"Close\"><\/button>\n `;\n \n document.body.appendChild(notification);\n \n setTimeout(() => {\n if (notification.parentNode) {\n notification.remove();\n }\n }, 5000);\n}\n\n\/\/ Command-specific solution functions\nfunction showCommandHelp() {\n const helpText = `\n <div class=\"alert alert-info\">\n <h6><i class=\"fas fa-info-circle me-2\"><\/i>Matomo Console Commands Help<\/h6>\n <p><strong>core:archive<\/strong> - Runs the CLI archiver<\/p>\n <p><strong>Available Options:<\/strong><\/p>\n <ul>\n <li><code>--url=URL<\/code> - Matomo URL<\/li>\n <li><code>--force-idsites=ID1,ID2<\/code> - Process specific sites<\/li>\n <li><code>--force-periods=day,week,month,year<\/code> - Process specific periods<\/li>\n <li><code>--force-date-range=YYYY-MM-DD,YYYY-MM-DD<\/code> - Process date range<\/li>\n <li><code>--force-date-last-n=N<\/code> - Process last N days<\/li>\n <\/ul>\n <p><strong>Note:<\/strong> --force-all-periods is not a valid option. Use --force-periods instead.<\/p>\n <\/div>\n `;\n \n showModal('Command Help', helpText);\n}\n\nfunction getCorrectSyntax() {\n const syntaxText = `\n <div class=\"alert alert-success\">\n <h6><i class=\"fas fa-check-circle me-2\"><\/i>Correct Command Syntax<\/h6>\n <p><strong>Your Command:<\/strong><\/p>\n <code class=\"text-danger\">php \/var\/www\/html\/console core:archive --url=https:\/\/analytics.grandlisboapalace.com --force-idsites=21 --force-date-last-n=2 --force-all-periods=1<\/code>\n \n <p class=\"mt-3\"><strong>Corrected Command:<\/strong><\/p>\n <code class=\"text-success\">php \/var\/www\/html\/console core:archive --url=https:\/\/analytics.grandlisboapalace.com --force-idsites=21 --force-date-last-n=2 --force-periods=day,week,month,year<\/code>\n \n <p class=\"mt-3\"><strong>Key Changes:<\/strong><\/p>\n <ul>\n <li>Replace <code>--force-all-periods=1<\/code> with <code>--force-periods=day,week,month,year<\/code><\/li>\n <li>Specify the actual periods you want to process<\/li>\n <li>Use comma-separated values for multiple periods<\/li>\n <\/ul>\n <\/div>\n `;\n \n showModal('Correct Syntax', syntaxText);\n}\n\nfunction checkMatomoVersion() {\n const versionText = `\n <div class=\"alert alert-warning\">\n <h6><i class=\"fas fa-exclamation-triangle me-2\"><\/i>Matomo Version Check<\/h6>\n <p>The <code>--force-all-periods<\/code> option was removed in newer versions of Matomo.<\/p>\n <p><strong>To check your Matomo version:<\/strong><\/p>\n <code>php \/var\/www\/html\/console core:version<\/code>\n \n <p class=\"mt-3\"><strong>To update Matomo:<\/strong><\/p>\n <ol>\n <li>Backup your current installation<\/li>\n <li>Download the latest version from matomo.org<\/li>\n <li>Follow the update instructions<\/li>\n <li>Use the new command syntax<\/li>\n <\/ol>\n <\/div>\n `;\n \n showModal('Version Information', versionText);\n}\n\nfunction showCorrectCommand() {\n const commandText = `\n <div class=\"alert alert-success\">\n <h6><i class=\"fas fa-terminal me-2\"><\/i>Corrected Command<\/h6>\n <p><strong>Copy and run this command:<\/strong><\/p>\n <div class=\"input-group\">\n <input type=\"text\" class=\"form-control\" id=\"correctCommand\" value=\"php \/var\/www\/html\/console core:archive --url=https:\/\/analytics.grandlisboapalace.com --force-idsites=21 --force-date-last-n=2 --force-periods=day,week,month,year\" readonly>\n <button class=\"btn btn-outline-secondary\" type=\"button\" onclick=\"copyToClipboard('correctCommand')\">\n <i class=\"fas fa-copy\"><\/i>\n <\/button>\n <\/div>\n \n <p class=\"mt-3\"><strong>Alternative commands:<\/strong><\/p>\n <ul>\n <li><code>--force-periods=day<\/code> - Process only daily reports<\/li>\n <li><code>--force-periods=day,week<\/code> - Process daily and weekly reports<\/li>\n <li><code>--force-periods=day,week,month,year<\/code> - Process all report types<\/li>\n <\/ul>\n <\/div>\n `;\n \n showModal('Correct Command', commandText);\n}\n\nfunction copyFixedCommand() {\n const command = 'php \/var\/www\/html\/console core:archive --url=https:\/\/analytics.grandlisboapalace.com --force-idsites=21 --force-date-last-n=2 --force-periods=day,week,month,year';\n \n navigator.clipboard.writeText(command).then(() => {\n showNotification('Command copied to clipboard!', 'success');\n }).catch(() => {\n \/\/ Fallback for older browsers\n const textArea = document.createElement('textarea');\n textArea.value = command;\n document.body.appendChild(textArea);\n textArea.select();\n document.execCommand('copy');\n document.body.removeChild(textArea);\n showNotification('Command copied to clipboard!', 'success');\n });\n}\n\nfunction copyToClipboard(elementId) {\n const element = document.getElementById(elementId);\n element.select();\n document.execCommand('copy');\n showNotification('Copied to clipboard!', 'success');\n}\n\nfunction showModal(title, content) {\n const modal = document.createElement('div');\n modal.className = 'modal fade';\n modal.innerHTML = `\n <div class=\"modal-dialog modal-lg\">\n <div class=\"modal-content\">\n <div class=\"modal-header\">\n <h5 class=\"modal-title\">${title}<\/h5>\n <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\"><\/button>\n <\/div>\n <div class=\"modal-body\">\n ${content}\n <\/div>\n <div class=\"modal-footer\">\n <button type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\">Close<\/button>\n <\/div>\n <\/div>\n <\/div>\n `;\n \n document.body.appendChild(modal);\n const bsModal = new bootstrap.Modal(modal);\n bsModal.show();\n \n \/\/ Clean up modal when hidden\n modal.addEventListener('hidden.bs.modal', function() {\n this.remove();\n });\n}\n\n\/\/ Generic solution functions\nfunction searchOnline() {\n const searchText = document.getElementById('logInput').value;\n const errorLines = searchText.split('\\n').filter(line => \n line.toLowerCase().includes('error') || \n line.toLowerCase().includes('fatal') || \n line.toLowerCase().includes('exception')\n );\n \n const errorMessage = errorLines[0] || 'error';\n const searchQuery = encodeURIComponent(`Matomo ${errorMessage}`);\n const searchUrl = `https:\/\/www.google.com\/search?q=${searchQuery}`;\n \n window.open(searchUrl, '_blank');\n showNotification('Opening Google search in new tab...', 'info');\n}\n\nfunction checkDocumentation() {\n const docText = `\n <div class=\"alert alert-info\">\n <h6><i class=\"fas fa-book me-2\"><\/i>Matomo Documentation Resources<\/h6>\n <ul class=\"mb-0\">\n <li><a href=\"https:\/\/matomo.org\/docs\/\" target=\"_blank\">Official Matomo Documentation<\/a><\/li>\n <li><a href=\"https:\/\/matomo.org\/faq\/\" target=\"_blank\">Frequently Asked Questions<\/a><\/li>\n <li><a href=\"https:\/\/matomo.org\/help\/\" target=\"_blank\">Help Center<\/a><\/li>\n <li><a href=\"https:\/\/forum.matomo.org\/\" target=\"_blank\">Community Forum<\/a><\/li>\n <li><a href=\"https:\/\/github.com\/matomo-org\/matomo\/issues\" target=\"_blank\">GitHub Issues<\/a><\/li>\n <\/ul>\n <\/div>\n `;\n \n showModal('Documentation Resources', docText);\n}\n\nfunction contactSupport() {\n const supportText = `\n <div class=\"alert alert-warning\">\n <h6><i class=\"fas fa-life-ring me-2\"><\/i>Get Support<\/h6>\n <p><strong>Free Support Options:<\/strong><\/p>\n <ul>\n <li><a href=\"https:\/\/forum.matomo.org\/\" target=\"_blank\">Community Forum<\/a> - Ask questions and get help from other users<\/li>\n <li><a href=\"https:\/\/github.com\/matomo-org\/matomo\/issues\" target=\"_blank\">GitHub Issues<\/a> - Report bugs and feature requests<\/li>\n <li><a href=\"https:\/\/matomo.org\/faq\/\" target=\"_blank\">FAQ<\/a> - Check common questions and answers<\/li>\n <\/ul>\n \n <p class=\"mt-3\"><strong>Commercial Support:<\/strong><\/p>\n <ul>\n <li><a href=\"https:\/\/matomo.org\/support\/\" target=\"_blank\">Professional Support<\/a> - Get expert help from the Matomo team<\/li>\n <li><a href=\"https:\/\/matomo.org\/consulting\/\" target=\"_blank\">Consulting Services<\/a> - Custom implementation and optimization<\/li>\n <\/ul>\n <\/div>\n `;\n \n showModal('Support Options', supportText);\n}\n<\/script>\n@endsection\n" }
JSON object with view file paths or inline content.
Assets (Optional - JSON)
[]
JSON array of asset file paths.
Update Component
Delete Component