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: Cron Generator
Controller Code (Optional)
<?php namespace App\Http\Controllers\Components\CronGenerator; 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 ComponentCronGeneratorController extends Controller { /** * Display the cron generator interface. */ public function index() { return view('components.cron-generator.index'); } /** * Generate cron jobs using OpenAI * POST /dashboard/generate-cron-jobs */ public function generateCronJobs(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([ 'requirements' => 'required|string|max:10000', 'matomo_path' => 'nullable|string|max:255', 'cron_user' => 'nullable|string|max:100', 'context' => 'nullable|string|max:5000', ]); } catch (ValidationException $e) { return response()->json([ 'success' => false, 'error' => 'Validation failed: ' . implode(', ', $e->validator->errors()->all()), 'errors' => $e->errors(), ], 422); } $requirements = $validated['requirements']; $matomoPath = $validated['matomo_path'] ?? '/var/www/matomo'; $cronUser = $validated['cron_user'] ?? 'www-data'; $context = $validated['context'] ?? ''; // Build prompt for OpenAI $prompt = "Generate cron job configuration for Matomo analytics archiving based on these requirements:\n\n"; $prompt .= "Requirements:\n{$requirements}\n\n"; if ($context) { $prompt .= "Additional context:\n{$context}\n\n"; } $prompt .= "Matomo path: {$matomoPath}\n"; $prompt .= "Cron user: {$cronUser}\n\n"; $prompt .= "Provide a properly formatted crontab entry with explanation."; // 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('Cron generation exception', [ 'error' => $e->getMessage(), ]); return response()->json([ 'success' => false, 'error' => 'Generation failed: ' . $e->getMessage(), ], 500); } } }
PHP code for the component controller.
Routes Code (Optional)
<?php use Illuminate\Support\Facades\Route; use App\Http\Controllers\Components\CronGenerator\ComponentCronGeneratorController; Route::middleware(['auth', 'component.visible'])->group(function () { Route::get('/cron-generator', [ComponentCronGeneratorController::class, 'index'])->name('dashboard.cron-generator'); // API route - always returns JSON Route::post('/generate-cron-jobs', [ComponentCronGeneratorController::class, 'generateCronJobs']) ->name('dashboard.generate-cron-jobs') ->middleware('api'); // Ensure JSON response });
Route definitions for the component.
Views (Optional - JSON)
{ "index.blade.php": "@extends('layouts.app')\n\n@section('title', 'Cron Job Generator - 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 <!-- Cron Job Generator 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\">Cron Jobs Generator for Matomo Archiving<\/h5>\n <div class=\"d-flex gap-2\">\n <button class=\"btn btn-outline-primary\" onclick=\"clearGenerator()\">\n <i class=\"fas fa-eraser me-1\"><\/i> Clear\n <\/button>\n <button class=\"btn btn-outline-success\" onclick=\"saveCronJobs()\">\n <i class=\"fas fa-save me-1\"><\/i> Save\n <\/button>\n <button class=\"btn btn-primary\" onclick=\"generateCronJobs()\">\n <i class=\"fas fa-magic me-1\"><\/i> Generate\n <\/button>\n <\/div>\n <\/div>\n <\/div>\n <\/div>\n <!-- \/ Cron Job Generator Header -->\n\n <!-- Configuration Panel 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\">Configuration Panel<\/h6>\n <\/div>\n <div class=\"card-body\">\n <div class=\"cron-configuration\">\n <!-- Basic Settings -->\n <div class=\"mb-4\">\n <h6 class=\"text-primary mb-3\">Basic Settings<\/h6>\n <div class=\"row\">\n <div class=\"col-md-6\">\n <label class=\"form-label\">Matomo Path<\/label>\n <input type=\"text\" class=\"form-control\" id=\"matomoPath\" value=\"\/var\/www\/matomo\" placeholder=\"\/path\/to\/matomo\">\n <\/div>\n <div class=\"col-md-6\">\n <label class=\"form-label\">PHP Path<\/label>\n <input type=\"text\" class=\"form-control\" id=\"phpPath\" value=\"\/usr\/bin\/php\" placeholder=\"\/usr\/bin\/php\">\n <\/div>\n <\/div>\n <div class=\"row mt-3\">\n <div class=\"col-md-6\">\n <label class=\"form-label\">User<\/label>\n <input type=\"text\" class=\"form-control\" id=\"cronUser\" value=\"www-data\" placeholder=\"www-data\">\n <\/div>\n <div class=\"col-md-6\">\n <label class=\"form-label\">Log File<\/label>\n <input type=\"text\" class=\"form-control\" id=\"logFile\" value=\"\/var\/log\/matomo-cron.log\" placeholder=\"\/var\/log\/matomo-cron.log\">\n <\/div>\n <\/div>\n <\/div>\n\n <!-- Archive Settings -->\n <div class=\"mb-4\">\n <h6 class=\"text-primary mb-3\">Archive Settings<\/h6>\n <div class=\"row\">\n <div class=\"col-md-6\">\n <label class=\"form-label\">Archive Frequency<\/label>\n <select class=\"form-select\" id=\"archiveFrequency\">\n <option value=\"hourly\">Hourly<\/option>\n <option value=\"daily\" selected>Daily<\/option>\n <option value=\"weekly\">Weekly<\/option>\n <option value=\"monthly\">Monthly<\/option>\n <\/select>\n <\/div>\n <div class=\"col-md-6\">\n <label class=\"form-label\">Archive Time<\/label>\n <input type=\"time\" class=\"form-control\" id=\"archiveTime\" value=\"02:00\">\n <\/div>\n <\/div>\n <div class=\"row mt-3\">\n <div class=\"col-md-6\">\n <label class=\"form-label\">Max Execution Time (minutes)<\/label>\n <input type=\"number\" class=\"form-control\" id=\"maxExecTime\" value=\"60\" min=\"1\" max=\"1440\">\n <\/div>\n <div class=\"col-md-6\">\n <label class=\"form-label\">Memory Limit (MB)<\/label>\n <input type=\"number\" class=\"form-control\" id=\"memoryLimit\" value=\"512\" min=\"64\" max=\"8192\">\n <\/div>\n <\/div>\n <\/div>\n\n <!-- Advanced Settings -->\n <div class=\"mb-4\">\n <h6 class=\"text-primary mb-3\">Advanced Settings<\/h6>\n <div class=\"row\">\n <div class=\"col-md-6\">\n <label class=\"form-label\">Force All Websites<\/label>\n <div class=\"form-check\">\n <input class=\"form-check-input\" type=\"checkbox\" id=\"forceAllWebsites\" checked>\n <label class=\"form-check-label\" for=\"forceAllWebsites\">\n Archive all websites\n <\/label>\n <\/div>\n <\/div>\n <div class=\"col-md-6\">\n <label class=\"form-label\">Force All Periods<\/label>\n <div class=\"form-check\">\n <input class=\"form-check-input\" type=\"checkbox\" id=\"forceAllPeriods\" checked>\n <label class=\"form-check-label\" for=\"forceAllPeriods\">\n Archive all periods\n <\/label>\n <\/div>\n <\/div>\n <\/div>\n <div class=\"row mt-3\">\n <div class=\"col-md-6\">\n <label class=\"form-label\">Specific Website IDs<\/label>\n <input type=\"text\" class=\"form-control\" id=\"websiteIds\" placeholder=\"1,2,3 (leave empty for all)\">\n <\/div>\n <div class=\"col-md-6\">\n <label class=\"form-label\">Specific Periods<\/label>\n <select class=\"form-select\" id=\"periods\">\n <option value=\"\">All periods<\/option>\n <option value=\"day\">Day only<\/option>\n <option value=\"week\">Week only<\/option>\n <option value=\"month\">Month only<\/option>\n <option value=\"year\">Year only<\/option>\n <\/select>\n <\/div>\n <\/div>\n <\/div>\n\n <!-- Additional Jobs -->\n <div class=\"mb-4\">\n <h6 class=\"text-primary mb-3\">Additional Jobs<\/h6>\n <div class=\"form-check mb-2\">\n <input class=\"form-check-input\" type=\"checkbox\" id=\"enableOptimize\" checked>\n <label class=\"form-check-label\" for=\"enableOptimize\">\n Database optimization (daily at 3 AM)\n <\/label>\n <\/div>\n <div class=\"form-check mb-2\">\n <input class=\"form-check-input\" type=\"checkbox\" id=\"enableCleanup\" checked>\n <label class=\"form-check-label\" for=\"enableCleanup\">\n Log cleanup (weekly on Sunday at 4 AM)\n <\/label>\n <\/div>\n <div class=\"form-check mb-2\">\n <input class=\"form-check-input\" type=\"checkbox\" id=\"enableBackup\" checked>\n <label class=\"form-check-label\" for=\"enableBackup\">\n Database backup (daily at 1 AM)\n <\/label>\n <\/div>\n <div class=\"form-check mb-2\">\n <input class=\"form-check-input\" type=\"checkbox\" id=\"enableUpdate\">\n <label class=\"form-check-label\" for=\"enableUpdate\">\n Auto-update check (weekly on Monday at 5 AM)\n <\/label>\n <\/div>\n <\/div>\n <\/div>\n <\/div>\n <\/div>\n <\/div>\n <!-- \/ Configuration Panel Card -->\n\n <!-- AI Cron Generator Section -->\n <div class=\"col-12 mb-4\">\n <div class=\"card\">\n <div class=\"card-header\">\n <h6 class=\"card-title mb-0\">\n <i class=\"fas fa-robot me-2\"><\/i>AI Cron Generator\n <\/h6>\n <\/div>\n <div class=\"card-body\">\n <div class=\"alert alert-info\">\n <i class=\"fas fa-lightbulb me-2\"><\/i>\n <strong>Need help with cron configuration?<\/strong> Describe your requirements in natural language and AI will generate the optimal cron jobs for you.\n <\/div>\n \n <div class=\"ai-cron-container\">\n <div class=\"mb-3\">\n <label for=\"aiRequirements\" class=\"form-label\">What do you need your cron jobs to do?<\/label>\n <textarea \n id=\"aiRequirements\" \n class=\"form-control\" \n rows=\"4\" \n placeholder=\"e.g., Archive data every 2 hours for high-traffic sites, optimize database weekly, backup daily at 3 AM, cleanup old logs monthly...\"\n ><\/textarea>\n <\/div>\n \n <div class=\"row mb-3\">\n <div class=\"col-md-6\">\n <label for=\"aiMatomoPath\" class=\"form-label\">Matomo Path (Optional)<\/label>\n <input type=\"text\" class=\"form-control\" id=\"aiMatomoPath\" value=\"\/var\/www\/matomo\" placeholder=\"\/path\/to\/matomo\">\n <\/div>\n <div class=\"col-md-6\">\n <label for=\"aiCronUser\" class=\"form-label\">Cron User (Optional)<\/label>\n <input type=\"text\" class=\"form-control\" id=\"aiCronUser\" value=\"www-data\" placeholder=\"www-data\">\n <\/div>\n <\/div>\n \n <div class=\"mb-3\">\n <label for=\"aiContext\" class=\"form-label\">Additional Context (Optional)<\/label>\n <textarea \n id=\"aiContext\" \n class=\"form-control\" \n rows=\"2\" \n placeholder=\"e.g., High-traffic production server, limited resources, need to avoid peak hours...\"\n ><\/textarea>\n <\/div>\n \n <div class=\"d-flex gap-2 mb-3\">\n <button class=\"btn btn-primary\" onclick=\"generateCronWithAI()\" id=\"generateCronBtn\">\n <i class=\"fas fa-magic me-1\"><\/i> Generate Cron Jobs\n <\/button>\n <button class=\"btn btn-outline-secondary\" onclick=\"clearAICron()\">\n <i class=\"fas fa-eraser me-1\"><\/i> Clear\n <\/button>\n <button class=\"btn btn-outline-info\" onclick=\"testAICron()\">\n <i class=\"fas fa-bug me-1\"><\/i> Test\n <\/button>\n <\/div>\n \n <!-- AI Generated Cron Results -->\n <div id=\"aiCronResults\" class=\"d-none\">\n <div class=\"card\">\n <div class=\"card-header bg-success text-white\">\n <h6 class=\"mb-0\">\n <i class=\"fas fa-check-circle me-2\"><\/i>AI Generated Cron Jobs\n <\/h6>\n <\/div>\n <div class=\"card-body\">\n <div class=\"mb-3\">\n <label class=\"form-label fw-bold\">Generated Cron Jobs:<\/label>\n <div class=\"input-group\">\n <textarea id=\"generatedCronJobs\" class=\"form-control bg-dark text-light\" rows=\"8\" style=\"font-family: 'Courier New', monospace;\" readonly><\/textarea>\n <button class=\"btn btn-outline-light\" onclick=\"copyGeneratedCron()\" title=\"Copy to clipboard\">\n <i class=\"fas fa-copy\"><\/i>\n <\/button>\n <\/div>\n <\/div>\n \n <div class=\"mb-3\">\n <label class=\"form-label fw-bold\">Explanation:<\/label>\n <p id=\"cronExplanation\" class=\"text-muted\"><\/p>\n <\/div>\n \n <div id=\"cronInstallation\" class=\"mb-3 d-none\">\n <label class=\"form-label fw-bold\">Installation Instructions:<\/label>\n <ul id=\"installationList\" class=\"list-group\"><\/ul>\n <\/div>\n \n <div id=\"cronRecommendations\" class=\"mb-3 d-none\">\n <label class=\"form-label fw-bold\">Recommendations:<\/label>\n <ul id=\"recommendationsList\" class=\"list-group\"><\/ul>\n <\/div>\n \n <div id=\"cronMonitoring\" class=\"mb-3 d-none\">\n <label class=\"form-label fw-bold\">Monitoring:<\/label>\n <ul id=\"monitoringList\" class=\"list-group\"><\/ul>\n <\/div>\n \n <div id=\"cronTroubleshooting\" class=\"mb-3 d-none\">\n <label class=\"form-label fw-bold\">Troubleshooting:<\/label>\n <ul id=\"troubleshootingList\" class=\"list-group\"><\/ul>\n <\/div>\n <\/div>\n <\/div>\n <\/div>\n <\/div>\n <\/div>\n <\/div>\n <\/div>\n <!-- \/ AI Cron Generator Section -->\n\n <!-- Generated Cron Jobs 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\">Generated Cron Jobs<\/h6>\n <\/div>\n <div class=\"card-body\">\n <div class=\"cron-output\">\n <h6 class=\"text-primary mb-3\">Generated Cron Jobs<\/h6>\n <div class=\"bg-dark p-3 rounded mb-3 cron-scroll-area\" style=\"max-height: 400px; overflow-y: auto;\">\n <code id=\"generatedCronJobs\" class=\"text-light\" style=\"font-size: 0.9rem; white-space: pre-wrap;\"># Generated Matomo cron jobs will appear here<\/code>\n <\/div>\n \n <div class=\"mb-3\">\n <button class=\"btn btn-outline-secondary btn-sm\" onclick=\"copyCronJobs()\">\n <i class=\"fas fa-copy me-1\"><\/i> Copy to Clipboard\n <\/button>\n <button class=\"btn btn-outline-info btn-sm\" onclick=\"downloadCronFile()\">\n <i class=\"fas fa-download me-1\"><\/i> Download .sh File\n <\/button>\n <button class=\"btn btn-outline-warning btn-sm\" onclick=\"validateCronJobs()\">\n <i class=\"fas fa-check-circle me-1\"><\/i> Validate\n <\/button>\n <\/div>\n\n <!-- Installation Instructions -->\n <div class=\"card\">\n <div class=\"card-header\">\n <h6 class=\"card-title mb-0\">Installation Instructions<\/h6>\n <\/div>\n <div class=\"card-body\">\n <div id=\"installationSteps\">\n <p class=\"text-muted\">Generate cron jobs to see installation instructions<\/p>\n <\/div>\n <\/div>\n <\/div>\n <\/div>\n <\/div>\n <\/div>\n <\/div>\n <!-- \/ Generated Cron Jobs Card -->\n\n <!-- Monitoring 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\">Cron Job Monitoring<\/h6>\n <\/div>\n <div class=\"card-body\">\n <div class=\"row\">\n <div class=\"col-md-6\">\n <h6 class=\"text-primary mb-3\">Status Check<\/h6>\n <div id=\"cronStatus\" class=\"bg-light p-3 rounded\">\n <p class=\"text-muted text-center\">Click \"Check Status\" to verify cron jobs<\/p>\n <\/div>\n <button class=\"btn btn-outline-primary btn-sm mt-2\" onclick=\"checkCronStatus()\">\n <i class=\"fas fa-search me-1\"><\/i> Check Status\n <\/button>\n <\/div>\n <div class=\"col-md-6\">\n <h6 class=\"text-primary mb-3\">Log Viewer<\/h6>\n <div id=\"cronLogs\" class=\"bg-dark p-3 rounded cron-scroll-area\" style=\"max-height: 200px; overflow-y: auto; font-family: 'Courier New', monospace; font-size: 0.8rem;\">\n <code class=\"text-light\">Click \"View Logs\" to see cron execution logs<\/code>\n <\/div>\n <button class=\"btn btn-outline-info btn-sm mt-2\" onclick=\"viewCronLogs()\">\n <i class=\"fas fa-file-alt me-1\"><\/i> View Logs\n <\/button>\n <\/div>\n <\/div>\n <\/div>\n <\/div>\n <\/div>\n <!-- \/ Monitoring Panel Card -->\n<\/div>\n<\/div>\n@endsection\n\n@section('styles')\n<style>\n.cron-configuration {\n max-height: 600px;\n overflow-y: auto;\n padding-right: 4px;\n}\n\n.cron-output {\n position: sticky;\n top: 1rem;\n}\n\n#generatedCronJobs {\n white-space: pre-wrap;\n word-break: break-all;\n}\n\n.installation-step {\n padding: 0.75rem;\n margin-bottom: 0.5rem;\n border-left: 4px solid var(--accent-color);\n background: var(--surface-color);\n border-radius: 0 var(--radius-md) var(--radius-md) 0;\n}\n\n.installation-step .step-number {\n display: inline-block;\n width: 24px;\n height: 24px;\n background: var(--accent-color);\n color: white;\n border-radius: 50%;\n text-align: center;\n line-height: 24px;\n font-size: 0.8rem;\n font-weight: 600;\n margin-right: 0.5rem;\n}\n\n.status-item {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 0.5rem;\n margin-bottom: 0.25rem;\n border-radius: var(--radius-sm);\n background: var(--surface-color);\n}\n\n.status-item.success {\n border-left: 4px solid #28a745;\n}\n\n.status-item.warning {\n border-left: 4px solid #ffc107;\n}\n\n.status-item.error {\n border-left: 4px solid #dc3545;\n}\n\n.status-indicator {\n width: 12px;\n height: 12px;\n border-radius: 50%;\n margin-right: 0.5rem;\n}\n\n.status-indicator.success {\n background: #28a745;\n}\n\n.status-indicator.warning {\n background: #ffc107;\n}\n\n.status-indicator.error {\n background: #dc3545;\n}\n\n.log-entry {\n color: #28a745;\n margin-bottom: 0.25rem;\n font-size: 0.8rem;\n}\n\n.log-entry.error {\n color: #dc3545;\n}\n\n.log-entry.warning {\n color: #ffc107;\n}\n<\/style>\n@endsection\n\n@section('scripts')\n<script>\n\/\/ Generate cron jobs\nfunction generateCronJobs() {\n const config = getConfiguration();\n const cronJobs = buildCronJobs(config);\n const installationSteps = getInstallationSteps(config);\n \n document.getElementById('generatedCronJobs').textContent = cronJobs;\n document.getElementById('installationSteps').innerHTML = installationSteps;\n \n showNotification('Cron jobs generated successfully!', 'success');\n}\n\n\/\/ Get configuration from form\nfunction getConfiguration() {\n return {\n matomoPath: document.getElementById('matomoPath').value || '\/var\/www\/matomo',\n phpPath: document.getElementById('phpPath').value || '\/usr\/bin\/php',\n cronUser: document.getElementById('cronUser').value || 'www-data',\n logFile: document.getElementById('logFile').value || '\/var\/log\/matomo-cron.log',\n archiveFrequency: document.getElementById('archiveFrequency').value,\n archiveTime: document.getElementById('archiveTime').value,\n maxExecTime: document.getElementById('maxExecTime').value || '60',\n memoryLimit: document.getElementById('memoryLimit').value || '512',\n forceAllWebsites: document.getElementById('forceAllWebsites').checked,\n forceAllPeriods: document.getElementById('forceAllPeriods').checked,\n websiteIds: document.getElementById('websiteIds').value,\n periods: document.getElementById('periods').value,\n enableOptimize: document.getElementById('enableOptimize').checked,\n enableCleanup: document.getElementById('enableCleanup').checked,\n enableBackup: document.getElementById('enableBackup').checked,\n enableUpdate: document.getElementById('enableUpdate').checked\n };\n}\n\n\/\/ Build cron jobs based on configuration\nfunction buildCronJobs(config) {\n let cronJobs = `# Matomo Cron Jobs\n# Generated on ${new Date().toLocaleString()}\n# \n# Installation: Add these lines to your crontab using 'crontab -e'\n# or place in \/etc\/cron.d\/matomo\n\n`;\n\n \/\/ Main archiving job\n const archiveCron = getArchiveCron(config);\n cronJobs += `# Main archiving job\n${archiveCron}\n\n`;\n\n \/\/ Additional jobs\n if (config.enableOptimize) {\n cronJobs += `# Database optimization (daily at 3 AM)\n0 3 * * * ${config.cronUser} ${config.phpPath} ${config.matomoPath}\/console database:optimize >> ${config.logFile} 2>&1\n\n`;\n }\n\n if (config.enableCleanup) {\n cronJobs += `# Log cleanup (weekly on Sunday at 4 AM)\n0 4 * * 0 ${config.cronUser} ${config.phpPath} ${config.matomoPath}\/console database:cleanup --older-than=365 >> ${config.logFile} 2>&1\n\n`;\n }\n\n if (config.enableBackup) {\n cronJobs += `# Database backup (daily at 1 AM)\n0 1 * * * ${config.cronUser} ${config.phpPath} ${config.matomoPath}\/console database:backup --output=\/var\/backups\/matomo-$(date +\\\\%Y\\\\%m\\\\%d).sql >> ${config.logFile} 2>&1\n\n`;\n }\n\n if (config.enableUpdate) {\n cronJobs += `# Auto-update check (weekly on Monday at 5 AM)\n0 5 * * 1 ${config.cronUser} ${config.phpPath} ${config.matomoPath}\/console core:update --yes >> ${config.logFile} 2>&1\n\n`;\n }\n\n \/\/ Add environment variables\n cronJobs += `# Environment variables (add to crontab or .bashrc)\n# export MATOMO_PATH=\"${config.matomoPath}\"\n# export PHP_PATH=\"${config.phpPath}\"\n# export MATOMO_LOG=\"${config.logFile}\"\n\n`;\n\n return cronJobs;\n}\n\n\/\/ Get archive cron expression\nfunction getArchiveCron(config) {\n const [hours, minutes] = config.archiveTime.split(':');\n let cronExpression = '';\n \n switch (config.archiveFrequency) {\n case 'hourly':\n cronExpression = `${minutes} * * * *`;\n break;\n case 'daily':\n cronExpression = `${minutes} ${hours} * * *`;\n break;\n case 'weekly':\n cronExpression = `${minutes} ${hours} * * 0`; \/\/ Sunday\n break;\n case 'monthly':\n cronExpression = `${minutes} ${hours} 1 * *`; \/\/ 1st of month\n break;\n default:\n cronExpression = `${minutes} ${hours} * * *`;\n }\n \n let command = `${config.phpPath} ${config.matomoPath}\/console core:archive`;\n \n \/\/ Add options\n if (config.forceAllWebsites) {\n command += ' --force-all-websites';\n }\n if (config.forceAllPeriods) {\n command += ' --force-all-periods';\n }\n if (config.websiteIds) {\n command += ` --urls=${config.websiteIds.split(',').map(id => `https:\/\/site${id}.com`).join(',')}`;\n }\n if (config.periods) {\n command += ` --period=${config.periods}`;\n }\n \n command += ` --max-execution-time=${config.maxExecTime}`;\n command += ` --memory-limit=${config.memoryLimit}M`;\n command += ` >> ${config.logFile} 2>&1`;\n \n return `${cronExpression} ${config.cronUser} ${command}`;\n}\n\n\/\/ Get installation steps\nfunction getInstallationSteps(config) {\n return `\n <div class=\"installation-step\">\n <span class=\"step-number\">1<\/span>\n <strong>Copy the generated cron jobs<\/strong>\n <p class=\"mb-0 mt-2\">Copy the cron jobs from the output above to your clipboard.<\/p>\n <\/div>\n \n <div class=\"installation-step\">\n <span class=\"step-number\">2<\/span>\n <strong>Open crontab editor<\/strong>\n <p class=\"mb-0 mt-2\">Run <code>crontab -e<\/code> as the user who will run the cron jobs (${config.cronUser}).<\/p>\n <\/div>\n \n <div class=\"installation-step\">\n <span class=\"step-number\">3<\/span>\n <strong>Paste the cron jobs<\/strong>\n <p class=\"mb-0 mt-2\">Paste the generated cron jobs at the end of the crontab file.<\/p>\n <\/div>\n \n <div class=\"installation-step\">\n <span class=\"step-number\">4<\/span>\n <strong>Set proper permissions<\/strong>\n <p class=\"mb-0 mt-2\">Ensure the log file is writable: <code>sudo touch ${config.logFile} && sudo chown ${config.cronUser}:${config.cronUser} ${config.logFile}<\/code><\/p>\n <\/div>\n \n <div class=\"installation-step\">\n <span class=\"step-number\">5<\/span>\n <strong>Test the setup<\/strong>\n <p class=\"mb-0 mt-2\">Run a test command: <code>${config.phpPath} ${config.matomoPath}\/console core:archive --help<\/code><\/p>\n <\/div>\n \n <div class=\"installation-step\">\n <span class=\"step-number\">6<\/span>\n <strong>Monitor execution<\/strong>\n <p class=\"mb-0 mt-2\">Check logs regularly: <code>tail -f ${config.logFile}<\/code><\/p>\n <\/div>\n `;\n}\n\n\/\/ Copy cron jobs to clipboard\nfunction copyCronJobs() {\n const cronJobs = document.getElementById('generatedCronJobs').textContent;\n navigator.clipboard.writeText(cronJobs).then(() => {\n showNotification('Cron jobs copied to clipboard!', 'success');\n });\n}\n\n\/\/ Download cron file\nfunction downloadCronFile() {\n const cronJobs = document.getElementById('generatedCronJobs').textContent;\n const blob = new Blob([cronJobs], { type: 'text\/plain' });\n const url = window.URL.createObjectURL(blob);\n const a = document.createElement('a');\n a.href = url;\n a.download = 'matomo-cron-jobs.sh';\n document.body.appendChild(a);\n a.click();\n document.body.removeChild(a);\n window.URL.revokeObjectURL(url);\n showNotification('Cron jobs file downloaded!', 'success');\n}\n\n\/\/ Validate cron jobs\nfunction validateCronJobs() {\n const cronJobs = document.getElementById('generatedCronJobs').textContent;\n const lines = cronJobs.split('\\n').filter(line => line.trim() && !line.startsWith('#'));\n \n let isValid = true;\n let errors = [];\n \n lines.forEach((line, index) => {\n const parts = line.trim().split(\/\\s+\/);\n if (parts.length < 6) {\n isValid = false;\n errors.push(`Line ${index + 1}: Invalid cron format`);\n }\n });\n \n if (isValid) {\n showNotification('Cron jobs validation passed!', 'success');\n } else {\n showNotification(`Validation failed: ${errors.join(', ')}`, 'error');\n }\n}\n\n\/\/ Check cron status\nfunction checkCronStatus() {\n const statusDiv = document.getElementById('cronStatus');\n statusDiv.innerHTML = '<div class=\"text-center\"><i class=\"fas fa-spinner fa-spin\"><\/i> Checking status...<\/div>';\n \n setTimeout(() => {\n \/\/ Mock status check\n const statusItems = [\n { name: 'Cron Service', status: 'running', type: 'success' },\n { name: 'Matomo Path', status: 'exists', type: 'success' },\n { name: 'PHP Path', status: 'valid', type: 'success' },\n { name: 'Log File', status: 'writable', type: 'success' },\n { name: 'Last Archive', status: '2 hours ago', type: 'warning' },\n { name: 'Memory Usage', status: '45%', type: 'success' }\n ];\n \n let html = '';\n statusItems.forEach(item => {\n html += `\n <div class=\"status-item ${item.type}\">\n <div class=\"d-flex align-items-center\">\n <div class=\"status-indicator ${item.type}\"><\/div>\n <span>${item.name}<\/span>\n <\/div>\n <span class=\"badge bg-${item.type === 'success' ? 'success' : item.type === 'warning' ? 'warning' : 'danger'}\">${item.status}<\/span>\n <\/div>\n `;\n });\n \n statusDiv.innerHTML = html;\n }, 1500);\n}\n\n\/\/ View cron logs\nfunction viewCronLogs() {\n const logsDiv = document.getElementById('cronLogs');\n logsDiv.innerHTML = '<code class=\"text-light\">Loading logs...<\/code>';\n \n setTimeout(() => {\n \/\/ Mock log entries\n const logEntries = [\n { time: '2024-01-15 02:00:01', level: 'info', message: 'Starting Matomo archiving process' },\n { time: '2024-01-15 02:00:02', level: 'info', message: 'Archiving website ID 1 for period day' },\n { time: '2024-01-15 02:00:15', level: 'info', message: 'Archiving website ID 2 for period day' },\n { time: '2024-01-15 02:00:30', level: 'info', message: 'Archiving completed successfully' },\n { time: '2024-01-15 02:00:31', level: 'info', message: 'Memory usage: 45MB' },\n { time: '2024-01-15 02:00:32', level: 'info', message: 'Execution time: 31 seconds' }\n ];\n \n let html = '';\n logEntries.forEach(entry => {\n html += `\n <div class=\"log-entry ${entry.level}\">\n [${entry.time}] ${entry.message}\n <\/div>\n `;\n });\n \n logsDiv.innerHTML = html;\n }, 1000);\n}\n\n\/\/ Clear generator\nfunction clearGenerator() {\n document.getElementById('matomoPath').value = '\/var\/www\/matomo';\n document.getElementById('phpPath').value = '\/usr\/bin\/php';\n document.getElementById('cronUser').value = 'www-data';\n document.getElementById('logFile').value = '\/var\/log\/matomo-cron.log';\n document.getElementById('archiveFrequency').value = 'daily';\n document.getElementById('archiveTime').value = '02:00';\n document.getElementById('maxExecTime').value = '60';\n document.getElementById('memoryLimit').value = '512';\n document.getElementById('forceAllWebsites').checked = true;\n document.getElementById('forceAllPeriods').checked = true;\n document.getElementById('websiteIds').value = '';\n document.getElementById('periods').value = '';\n document.getElementById('enableOptimize').checked = true;\n document.getElementById('enableCleanup').checked = true;\n document.getElementById('enableBackup').checked = true;\n document.getElementById('enableUpdate').checked = false;\n \n document.getElementById('generatedCronJobs').textContent = '# Generated Matomo cron jobs will appear here';\n document.getElementById('installationSteps').innerHTML = '<p class=\"text-muted\">Generate cron jobs to see installation instructions<\/p>';\n document.getElementById('cronStatus').innerHTML = '<p class=\"text-muted text-center\">Click \"Check Status\" to verify cron jobs<\/p>';\n document.getElementById('cronLogs').innerHTML = '<code class=\"text-light\">Click \"View Logs\" to see cron execution logs<\/code>';\n}\n\n\/\/ Save cron jobs\nfunction saveCronJobs() {\n const cronJobs = document.getElementById('generatedCronJobs').textContent;\n const name = prompt('Enter a name for this cron configuration:');\n if (name) {\n \/\/ In a real app, save to database\n showNotification(`Cron configuration \"${name}\" saved successfully!`, 'success');\n }\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\/\/ AI Cron Generator Functions\nfunction generateCronWithAI() {\n const requirements = document.getElementById('aiRequirements').value.trim();\n const matomoPath = document.getElementById('aiMatomoPath').value.trim() || '\/var\/www\/matomo';\n const cronUser = document.getElementById('aiCronUser').value.trim() || 'www-data';\n const context = document.getElementById('aiContext').value.trim();\n \n if (!requirements) {\n showNotification('Please describe what you need your cron jobs to do', 'warning');\n return;\n }\n \n \/\/ Show loading state\n const generateBtn = document.getElementById('generateCronBtn');\n if (!generateBtn) {\n showNotification('AI Cron Generator not available', 'error');\n return;\n }\n \n const originalText = generateBtn.innerHTML;\n generateBtn.innerHTML = '<i class=\"fas fa-spinner fa-spin me-1\"><\/i> Generating...';\n generateBtn.disabled = true;\n \n \/\/ Get CSRF token\n const csrfToken = document.querySelector('meta[name=\"csrf-token\"]');\n if (!csrfToken) {\n showNotification('CSRF token not found. Please refresh the page.', 'error');\n generateBtn.innerHTML = originalText;\n generateBtn.disabled = false;\n return;\n }\n \n console.log('Making AI cron request with:', {\n requirements,\n matomo_path: matomoPath,\n cron_user: cronUser,\n context\n });\n \n \/\/ Call the backend AI API\n fetch('\/dashboard\/generate-cron-jobs', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application\/json',\n 'X-CSRF-TOKEN': csrfToken.getAttribute('content'),\n 'Accept': 'application\/json'\n },\n body: JSON.stringify({\n requirements: requirements,\n matomo_path: matomoPath,\n cron_user: cronUser,\n context: context\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\/generate-cron-jobs');\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 displayAICronResults(data.data);\n } else {\n showNotification('AI cron generation failed: ' + data.error, 'error');\n }\n })\n .catch(error => {\n console.error('Error:', error);\n showNotification('Error during cron generation: ' + error.message, 'error');\n })\n .finally(() => {\n \/\/ Restore button state\n generateBtn.innerHTML = originalText;\n generateBtn.disabled = false;\n });\n}\n\n\/\/ Display AI cron results\nfunction displayAICronResults(data) {\n const resultsDiv = document.getElementById('aiCronResults');\n \n \/\/ Show the results\n resultsDiv.classList.remove('d-none');\n \n \/\/ Build cron jobs text\n let cronJobsText = '# AI Generated Matomo Cron Jobs\\n';\n cronJobsText += '# Generated on ' + new Date().toLocaleString() + '\\n\\n';\n \n if (data.cron_jobs && data.cron_jobs.length > 0) {\n data.cron_jobs.forEach(job => {\n cronJobsText += `# ${job.name}\\n`;\n cronJobsText += `${job.schedule} ${job.command}\\n\\n`;\n });\n } else {\n cronJobsText += '# No cron jobs generated\\n';\n }\n \n \/\/ Update generated cron jobs\n document.getElementById('generatedCronJobs').value = cronJobsText;\n document.getElementById('cronExplanation').textContent = data.explanation || 'No explanation available';\n \n \/\/ Update installation instructions\n const installationDiv = document.getElementById('cronInstallation');\n const installationList = document.getElementById('installationList');\n if (data.installation_instructions && data.installation_instructions.length > 0) {\n installationDiv.classList.remove('d-none');\n installationList.innerHTML = data.installation_instructions.map(instruction => \n `<li class=\"list-group-item\">${instruction}<\/li>`\n ).join('');\n } else {\n installationDiv.classList.add('d-none');\n }\n \n \/\/ Update recommendations\n const recommendationsDiv = document.getElementById('cronRecommendations');\n const recommendationsList = document.getElementById('recommendationsList');\n if (data.recommendations && data.recommendations.length > 0) {\n recommendationsDiv.classList.remove('d-none');\n recommendationsList.innerHTML = data.recommendations.map(rec => \n `<li class=\"list-group-item\">${rec}<\/li>`\n ).join('');\n } else {\n recommendationsDiv.classList.add('d-none');\n }\n \n \/\/ Update monitoring\n const monitoringDiv = document.getElementById('cronMonitoring');\n const monitoringList = document.getElementById('monitoringList');\n if (data.monitoring && data.monitoring.length > 0) {\n monitoringDiv.classList.remove('d-none');\n monitoringList.innerHTML = data.monitoring.map(item => \n `<li class=\"list-group-item\">${item}<\/li>`\n ).join('');\n } else {\n monitoringDiv.classList.add('d-none');\n }\n \n \/\/ Update troubleshooting\n const troubleshootingDiv = document.getElementById('cronTroubleshooting');\n const troubleshootingList = document.getElementById('troubleshootingList');\n if (data.troubleshooting && data.troubleshooting.length > 0) {\n troubleshootingDiv.classList.remove('d-none');\n troubleshootingList.innerHTML = data.troubleshooting.map(item => \n `<li class=\"list-group-item\">${item}<\/li>`\n ).join('');\n } else {\n troubleshootingDiv.classList.add('d-none');\n }\n \n \/\/ Scroll to results\n resultsDiv.scrollIntoView({ behavior: 'smooth' });\n}\n\n\/\/ Clear AI cron generator\nfunction clearAICron() {\n document.getElementById('aiRequirements').value = '';\n document.getElementById('aiContext').value = '';\n document.getElementById('aiCronResults').classList.add('d-none');\n}\n\n\/\/ Test AI cron generator\nfunction testAICron() {\n console.log('Testing AI Cron Generator...');\n \n const requirements = document.getElementById('aiRequirements');\n const matomoPath = document.getElementById('aiMatomoPath');\n const cronUser = document.getElementById('aiCronUser');\n const context = document.getElementById('aiContext');\n const generateBtn = document.getElementById('generateCronBtn');\n \n console.log('Requirements field:', requirements);\n console.log('Matomo path field:', matomoPath);\n console.log('Cron user field:', cronUser);\n console.log('Context field:', context);\n console.log('Generate button:', generateBtn);\n \n if (!requirements) {\n showNotification('Requirements field not found!', 'error');\n return;\n }\n \n if (!generateBtn) {\n showNotification('Generate button not found!', 'error');\n return;\n }\n \n showNotification('AI Cron Generator test passed! All elements found.', 'success');\n}\n\n\/\/ Copy generated cron jobs to clipboard\nfunction copyGeneratedCron() {\n const cronText = document.getElementById('generatedCronJobs').value;\n navigator.clipboard.writeText(cronText).then(() => {\n showNotification('Cron jobs copied to clipboard!', 'success');\n }).catch(err => {\n console.error('Failed to copy: ', err);\n showNotification('Failed to copy cron jobs', 'error');\n });\n}\n<\/script>\n@endsection\n\n\n\n" }
JSON object with view file paths or inline content.
Assets (Optional - JSON)
[]
JSON array of asset file paths.
Update Component
Delete Component