Файловый менеджер - Редактировать - /var/www/html/administrator/components/com_jdownloads/helpers/scan-interface.php
Ðазад
<?php /** * @package jDownloads * @version 4.1 * Modern Monitoring Interface with AJAX Progress */ define('_JEXEC', 1); if (!defined('DS')){ define( 'DS', DIRECTORY_SEPARATOR ); } define('JPATH', dirname(__FILE__) ); $parts = explode( DS, JPATH ); $script_root = implode( DS, $parts ) ; // check path $x = array_search ( 'administrator', $parts ); if (!$x) exit; $path = ''; for ($i=0; $i < $x; $i++){ $path = $path.$parts[$i].'/'; } // remove last DS $path = substr($path, 0, -1); if (!defined('JPATH_BASE')){ define('JPATH_BASE', $path ); } setlocale(LC_ALL, 'C.UTF-8', 'C'); // Run the application require_once JPATH_BASE . '/includes/defines.php'; require_once JPATH_BASE . '/includes/framework.php'; // Boot the DI container $container = \Joomla\CMS\Factory::getContainer(); // Alias session services $container->alias('session.web', 'session.web.site') ->alias('session', 'session.web.site') ->alias('JSession', 'session.web.site') ->alias(\Joomla\CMS\Session\Session::class, 'session.web.site') ->alias(\Joomla\Session\Session::class, 'session.web.site') ->alias(\Joomla\Session\SessionInterface::class, 'session.web.site'); // Instantiate the application. $app = $container->get(\Joomla\CMS\Application\AdministratorApplication::class); $app->createExtensionNamespaceMap(); \Joomla\CMS\Factory::$application = $app; use Joomla\CMS\Factory; use Joomla\CMS\Language\Text; use Joomla\CMS\Component\ComponentHelper; // Backend language to prefer for admin scripts $backend_lang = ComponentHelper::getParams('com_languages')->get('administrator', 'en-GB'); // Language loading $lang = Factory::getLanguage(); $lang->load('com_jdownloads', JPATH_ADMINISTRATOR, $backend_lang, true); // Get parameters $params = ComponentHelper::getParams('com_jdownloads'); $secret = $params->get('scan_secret_key'); $jinput = Factory::getApplication()->getInput(); $mode = 0; // Always Mode 0 - scan everything $testrun = $jinput->get('test', 0, 'int'); // $modeLabels = [ 0 => Text::_('COM_JDOWNLOADS_MONITORING_MODE_0'),]; // Create date string $date = Factory::getDate(); $tz = Factory::getConfig()->get( 'offset' ); $date->setTimezone(new DateTimeZone($tz)); $date = date(Text::_('DATE_FORMAT_LC2')); $date_string = Text::_('COM_JDOWNLOADS_LOGS_COL_DATE_LABEL') . ': ' . $date; // Create testrun string $testrunString = Text::_('COM_JDOWNLOADS_AUTO_CHECK_TEST_RUN_HINT'); // Log title string $logTitleString = Text::_('COM_JDOWNLOADS_MONITORING_LOG'); $runChangeStartString = Text::_('COM_JDOWNLOADS_AUTOCHECK_MAKE_CHANGES_PERMANENTLY'); // Create finished info string $finishedInfo = addslashes(Text::_('COM_JDOWNLOADS_AUTO_CHECK_FINISH_INFO_TEST')); $finishedInfo = strip_tags($finishedInfo); ?> <!DOCTYPE html> <html lang="<?php echo Factory::getLanguage()->getTag(); ?>"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title><?php echo Text::_('COM_JDOWNLOADS_RUN_MONITORING_BUTTON_TEXT'); ?></title> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"> <style> body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; padding: 20px; background: #f8f9fa; } .monitor-container { max-width: 900px; margin: 0 auto; background: white; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); padding: 30px; } .progress { height: 30px; font-size: 14px; margin-bottom: 20px; } .progress-bar { transition: width 0.3s ease; font-weight: 600; } .log-container { max-height: 400px; overflow-y: auto; background: #f8f9fa; border: 1px solid #dee2e6; border-radius: 4px; padding: 15px; font-family: 'Courier New', monospace; font-size: 12px; } .log-entry { padding: 4px 0; border-bottom: 1px solid #e9ecef; } .log-entry:last-child { border-bottom: none; } .stats-grid { display: grid; grid-template-columns: 1fr; /* Mobile: 1 Spalte */ gap: 15px; margin: 10px 0; } /* Tablet und größer: 2 Spalten */ @media (min-width: 576px) { .stats-grid { grid-template-columns: repeat(2, 1fr); } /* Wenn nur 1 Card: volle Breite über beide Spalten */ .stats-grid .stat-card:only-child { grid-column: 1 / -1; } } .stat-card { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 12px 15px; border-radius: 8px; text-align: center; } .stat-card.success { background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%); } .stat-card.warning { background: linear-gradient(135deg, #ff7664ff 0%, #f5576c 100%); } .stat-value { font-size: 28px; font-weight: bold; margin: 3px 0; } .stat-label { font-size: 13px; opacity: 0.9; } .btn:disabled { background-color: #6c757d !important; border-color: #6c757d !important; color: #f8f9fa !important; opacity: 0.65; cursor: not-allowed; } .btn:disabled:hover { background-color: #6c757d !important; border-color: #6c757d !important; } .phase-badge { display: inline-block; padding: 6px 12px; border-radius: 20px; font-size: 13px; font-weight: 600; margin-bottom: 15px; } .phase-initializing { background: #ffc107; color: #000; } .phase-scanning { background: #17a2b8; color: #fff; } .phase-completed { background: #28a745; color: #fff; } .spinner { display: inline-block; width: 16px; height: 16px; border: 2px solid #f3f3f3; border-top: 2px solid #3498db; border-radius: 50%; animation: spin 1s linear infinite; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } </style> </head> <body> <div class="monitor-container"> <h4 class="mb-2"> <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-search me-2" viewBox="0 0 16 16"> <path d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001c.03.04.062.078.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z"/> </svg> <?php echo Text::_('COM_JDOWNLOADS_RUN_MONITORING_TITLE'); ?> <?php if ($testrun): ?> <span class="badge bg-warning text-dark ms-2 small"><?php echo Text::_('COM_JDOWNLOADS_AUTO_CHECK_TEST_RUN_HINT'); ?></span> <?php endif; ?> </h4> <!-- <div class="alert alert-light"> <strong><?php echo Text::_('COM_JDOWNLOADS_MONITORING_MODE'); ?>:</strong> <?php echo $modeLabels[$mode] ?? 'Unknown'; ?> <?php if ($testrun): ?> <span class="badge bg-warning text-dark ms-2"><?php echo Text::_('COM_JDOWNLOADS_AUTO_CHECK_TEST_RUN_HINT'); ?></span> <?php endif; ?> </div> --> <div id="phaseIndicator"></div> <!-- Progress bar with two-line status --> <div class="progress mb-3" style="height: 50px; position: relative;"> <div id="progressBar" class="progress-bar progress-bar-striped progress-bar-animated bg-info" role="progressbar" style="width: 0%" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"> </div> <div style="position: absolute; left: 0; right: 0; top: 0; bottom: 0; display: flex; flex-direction: column; justify-content: center; align-items: center; z-index: 2; pointer-events: none;"> <div id="currentStatus" style="font-size: 13px; color: #495057; line-height: 1.2;"> <span class="spinner me-2"></span><?php echo Text::_('COM_JDOWNLOADS_MONITORING_INITIALIZING'); ?> </div> <div id="progressPercent" style="font-weight: 600; font-size: 14px; color: #212529; margin-top: 2px;"> 0% </div> </div> </div> <!-- Statistics --> <div class="stats-grid" id="statsGrid"> <div class="stat-card success"> <div class="stat-label"><?php echo Text::_('COM_JDOWNLOADS_MONITORING_NEW_CATS'); ?></div> <div class="stat-value" id="stat-new-cats">0</div> </div> <div class="stat-card success"> <div class="stat-label"><?php echo Text::_('COM_JDOWNLOADS_MONITORING_NEW_DOWNLOADS'); ?></div> <div class="stat-value" id="stat-new-downloads">0</div> </div> <div class="stat-card warning"> <div class="stat-label"><?php echo Text::_('COM_JDOWNLOADS_MONITORING_MISSING_CATS'); ?></div> <div class="stat-value" id="stat-missing-cats">0</div> </div> <div class="stat-card warning"> <div class="stat-label"><?php echo Text::_('COM_JDOWNLOADS_MONITORING_MISSING_FILES'); ?></div> <div class="stat-value" id="stat-missing-files">0</div> </div> </div> <!-- Log --> <div class="stats-grid" id="statsGrid"> <div class=""> <h5 class="mt-2"> <?php echo Text::_('COM_JDOWNLOADS_MONITORING_LOG'); ?> </h5> </div> <div class=""> <h5 class="mt-2"> <button id="btnDownloadLog" class="btn btn-outline-secondary btn-sm float-end" style="display:none;"> <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="currentColor" class="bi bi-download me-1" viewBox="0 0 16 16"> <path d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5z"/> <path d="M7.646 11.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 10.293V1.5a.5.5 0 0 0-1 0v8.793L5.354 8.146a.5.5 0 1 0-.708.708l3 3z"/> </svg> <?php echo Text::_('COM_JDOWNLOADS_MONITORING_LOG_DOWNLOAD'); ?> </button> </h5> </div> </div> <div id="btnDownloadLogHint" class="mb-2 small" style="display:none;"><?php echo Text::_('COM_JDOWNLOADS_MONITORING_LOG_DOWNLOAD_HINT'); ?></div> <div class="log-container" id="logContainer"> <div class="log-entry text-muted"><?php echo Text::_('COM_JDOWNLOADS_MONITORING_LOG_WAITING'); ?></div> </div> <!-- Controls --> <div class="text-center mt-4"> <button id="btnStart" class="btn btn-primary btn-sm"> <!--<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-play-fill me-2" viewBox="0 0 16 16"> <path d="m11.596 8.697-6.363 3.692c-.54.313-1.233-.066-1.233-.697V4.308c0-.63.692-1.01 1.233-.696l6.363 3.692a.802.802 0 0 1 0 1.393z"/> </svg>--> <?php echo Text::_('COM_JDOWNLOADS_MONITORING_START_SCAN'); ?> </button> <?php if ($testrun): ?> <button id="btnRunReal" class="btn btn-warning btn-sm" style="display:none;"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-check-circle-fill me-2" viewBox="0 0 16 16"> <path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zm-3.97-3.03a.75.75 0 0 0-1.08.022L7.477 9.417 5.384 7.323a.75.75 0 0 0-1.06 1.06L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-.01-1.05z"/> </svg> <?php echo $runChangeStartString; ?> </button> <?php endif; ?> <button id="btnReset" class="btn btn-secondary btn-sm" style="display:none;"> <?php echo Text::_('COM_JDOWNLOADS_MONITORING_RESET'); ?> </button> </div> </div> <script> const scanMonitor = { workerUrl: 'scan-worker.php', secret: '<?php echo $secret; ?>', mode: <?php echo $mode; ?>, testrun: <?php echo $testrun; ?>, dateStr: '<?php echo $date_string; ?>', testrunStr: '<?php echo $testrunString; ?>', logTitleStr: '<?php echo $logTitleString; ?>', runScanStr: '<?php echo Text::_('COM_JDOWNLOADS_MONITORING_START_SCAN') . '...'; ?>', scanCompleteStr: '✓ ' + '<?php echo Text::_('COM_JDOWNLOADS_MONITORING_LOG_FINISHED'); ?>', resultsHintStr: '<?php echo $finishedInfo; ?>', initStr: '<?php echo Text::_('COM_JDOWNLOADS_MONITORING_INITIALIZING'); ?>', processedSuffixStr: '<?php echo Text::_('COM_JDOWNLOADS_UPLOAD_PROCESSED'); ?>', checkOnlyExistCatsStr: '<?php echo Text::_('COM_JDOWNLOADS_RUN_MONITORING_INFO4'); ?>', checkOnlyExistFilesStr: '<?php echo Text::_('COM_JDOWNLOADS_RUN_MONITORING_INFO6'); ?>', searchOnlyNewCatsStr: '<?php echo Text::_('COM_JDOWNLOADS_RUN_MONITORING_INFO3'); ?>', searchOnlyNewFilesStr: '<?php echo Text::_('COM_JDOWNLOADS_RUN_MONITORING_INFO5'); ?>', pollInterval: null, init() { document.getElementById('btnStart').addEventListener('click', () => this.startScan()); document.getElementById('btnReset').addEventListener('click', () => this.resetScan()); document.getElementById('btnDownloadLog').addEventListener('click', () => this.downloadCompleteLog()); const btnRunReal = document.getElementById('btnRunReal'); if (btnRunReal) { btnRunReal.addEventListener('click', () => this.runRealScan()); } }, downloadCompleteLog() { // Fetch complete log from server (not just the 100 displayed entries) fetch(`${this.workerUrl}?action=export_log&key=${this.secret}`) .then(response => { if (!response.ok) { throw new Error('Failed to fetch log'); } return response.json(); }) .then(data => { if (!data.success || !data.log) { alert('No log available for download.'); return; } // Build text content with ALL log entries from server let logText = this.logTitleStr + '\n'; logText += '='.repeat(50) + '\n'; logText += this.dateStr + '\n'; //logText += this.modusString + '\n'; logText += this.testrunStr + '\n'; logText += '='.repeat(50) + '\n\n'; // data.log contains ALL entries (not limited to 100) data.log.forEach(entry => { logText += entry + '\n'; }); // Create download const blob = new Blob([logText], { type: 'text/plain;charset=utf-8' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'jdownloads-scan-' + new Date().toISOString().split('T')[0] + '.txt'; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); }) .catch(error => { console.error('Download failed:', error); alert('Failed to download log. Please try again.'); }); }, runRealScan() { // Reload page without test parameter const url = new URL(window.location); url.searchParams.set('test', '0'); window.location.href = url.toString(); }, async startScan() { document.getElementById('btnStart').disabled = true; try { // Initialize scan const response = await fetch(`${this.workerUrl}?action=start&key=${this.secret}&mode=${this.mode}&test=${this.testrun}`); const data = await response.json(); if (data.success) { this.addLog(this.runScanStr, 'info'); this.startPolling(); } else { this.addLog('ERROR: ' + data.error, 'danger'); } } catch (error) { this.addLog('ERROR: ' + error.message, 'danger'); } }, startPolling() { this.pollInterval = setInterval(() => this.pollStatus(), 1000); // Start first chunk immediately this.processChunk(); }, async processChunk() { try { const response = await fetch(`${this.workerUrl}?action=process&key=${this.secret}`); const data = await response.json(); if (data.success) { // Update UI immediately this.updateUI(data.data); // Continue processing if not completed if (!data.data.completed) { // Small delay before next chunk setTimeout(() => this.processChunk(), 500); } else { // Scan completed this.onScanComplete(data.data); } } else { console.error('Process error:', data.error); this.addLog('ERROR: ' + (data.error || 'Unknown Error'), 'danger'); } } catch (error) { console.error('Process error:', error); this.addLog('ERROR: ' + error.message, 'danger'); } }, async pollStatus() { try { const response = await fetch(`${this.workerUrl}?action=status&key=${this.secret}`); const data = await response.json(); if (data.success) { this.updateUI(data.data); } } catch (error) { console.error('Poll error:', error); } }, updateUI(scanData) { // Update progress bar const progress = Math.round(scanData.progress); const progressBar = document.getElementById('progressBar'); const progressPercent = document.getElementById('progressPercent'); progressBar.style.width = progress + '%'; progressPercent.textContent = progress + '%'; progressBar.setAttribute('aria-valuenow', progress); // Update phase with labels const phaseLabels = { 'initializing': this.initStr, 'scanning_folders': this.searchOnlyNewCatsStr, 'scanning_files': this.searchOnlyNewFilesStr, 'checking_missing_cats': this.checkOnlyExistCatsStr, 'checking_missing_files': this.checkOnlyExistFilesStr, 'completed': this.scanCompleteStr }; const phaseIndicator = document.getElementById('phaseIndicator'); const phaseLabel = phaseLabels[scanData.phase] || scanData.phase; phaseIndicator.innerHTML = `<span class="phase-badge phase-${scanData.phase}">${phaseLabel}</span>`; // Update status (two lines: status at the top, percentage + items at the bottom) const currentStatus = document.getElementById('currentStatus'); if (scanData.completed) { currentStatus.innerHTML = '<strong style="color: #ffffff;">' + this.scanCompleteStr + '</strong>'; progressPercent.style.color = '#ffffff'; progressPercent.textContent = '100%'; } else { currentStatus.innerHTML = `<span class="spinner me-2"></span>${scanData.current} / ${scanData.total} ${this.processedSuffixStr}`; progressPercent.textContent = progress + '%'; } // Update stats if (scanData.stats) { switch (this.mode) { case 0: // All modes document.getElementById('stat-new-cats').textContent = scanData.stats.new_cats || 0; document.getElementById('stat-new-downloads').textContent = scanData.stats.new_downloads || 0; document.getElementById('stat-missing-cats').textContent = scanData.stats.missing_cats || 0; document.getElementById('stat-missing-files').textContent = scanData.stats.missing_files || 0; break; case 1: // New Categories document.getElementById('stat-new-cats').textContent = scanData.stats.new_cats || 0; break; case 2: // New Downloads document.getElementById('stat-new-downloads').textContent = scanData.stats.new_downloads || 0; break; case 3: // Missing Categories document.getElementById('stat-missing-cats').textContent = scanData.stats.missing_cats || 0; break; case 4: // Missing Files document.getElementById('stat-missing-files').textContent = scanData.stats.missing_files || 0; break; } } // Update log (only add new entries, limit to last 100 for performance) if (scanData.log && scanData.log.length > 0) { const logContainer = document.getElementById('logContainer'); const currentLogCount = logContainer.children.length; const MAX_LOG_ENTRIES = 100; // Only add new log entries for (let i = currentLogCount; i < scanData.log.length; i++) { const entry = document.createElement('div'); entry.className = 'log-entry'; // Color code log entries const logText = scanData.log[i]; if (logText.includes('✓')) { entry.className += ' text-success'; } else if (logText.includes('✗') || logText.includes('ERROR')) { entry.className += ' text-danger'; } else if (logText.includes('→')) { entry.className += ' text-info'; } entry.textContent = logText; logContainer.appendChild(entry); // Remove old entries if exceeding limit if (logContainer.children.length > MAX_LOG_ENTRIES) { logContainer.removeChild(logContainer.firstChild); } } // Auto-scroll to bottom logContainer.scrollTop = logContainer.scrollHeight; } }, onScanComplete(scanData) { clearInterval(this.pollInterval); const progressBar = document.getElementById('progressBar'); progressBar.classList.remove('progress-bar-animated', 'bg-info'); progressBar.classList.add('bg-success'); document.getElementById('currentStatus').innerHTML = '<strong style="color: #ffffff;">' + this.scanCompleteStr + '</strong>'; document.getElementById('progressPercent').style.color = '#ffffff'; document.getElementById('btnReset').style.display = 'inline-block'; document.getElementById('btnDownloadLog').style.display = 'inline-block'; // Calculate total items found across all categories let totalItems = 0; if (scanData.stats) { totalItems = (scanData.stats.new_cats || 0) + (scanData.stats.new_downloads || 0) + (scanData.stats.missing_cats || 0) + (scanData.stats.missing_files || 0); } // Show hint only if more than 100 items were found if (totalItems > 100) { document.getElementById('btnDownloadLogHint').style.display = 'inline-block'; } // Show "Run Real" button if this was a test run and changes were found if (this.testrun && scanData.stats) { const hasChanges = (scanData.stats.new_cats > 0 || scanData.stats.new_downloads > 0 || scanData.stats.missing_cats > 0 || scanData.stats.missing_files > 0); const btnRunReal = document.getElementById('btnRunReal'); if (btnRunReal && hasChanges) { btnRunReal.style.display = 'inline-block'; this.addLog(this.resultsHintStr, 'danger'); } } }, async resetScan() { await fetch(`${this.workerUrl}?action=reset&key=${this.secret}`); location.reload(); }, addLog(message, type = 'info') { const logContainer = document.getElementById('logContainer'); const entry = document.createElement('div'); entry.className = 'log-entry text-' + type; entry.textContent = message; logContainer.appendChild(entry); logContainer.scrollTop = logContainer.scrollHeight; } }; scanMonitor.init(); </script> </body> </html>
| ver. 1.1 | |
.
| PHP 8.4.18 | Ð“ÐµÐ½ÐµÑ€Ð°Ñ†Ð¸Ñ Ñтраницы: 0 |
proxy
|
phpinfo
|
ÐаÑтройка