Files
SamiAhmed7777 0b7e2d0a5b feat: Add comprehensive documentation suite and reorganize project structure
- Created complete documentation in docs/ directory
- Added PROJECT_OVERVIEW.md with feature highlights and getting started guide
- Added ARCHITECTURE.md with system design and technical details
- Added SECURITY.md with comprehensive security implementation guide
- Added DEVELOPMENT.md with development workflows and best practices
- Added DEPLOYMENT.md with production deployment instructions
- Added API.md with complete REST API documentation
- Added CONTRIBUTING.md with contribution guidelines
- Added CHANGELOG.md with version history and migration notes
- Reorganized all documentation files into docs/ directory for better organization
- Updated README.md with proper documentation links and quick navigation
- Enhanced project structure with professional documentation standards
2025-10-21 00:39:45 -07:00

238 lines
10 KiB
PHP

<?php
/*******************************************************************************************************************
| Software Name : EasyStream
| Software Description : High End YouTube Clone Script with Videos, Shorts, Streams, Images, Audio, Documents, Blogs
| Software Author : (c) Sami Ahmed
|*******************************************************************************************************************
|
|*******************************************************************************************************************
| This source file is subject to the EasyStream Proprietary License Agreement.
|
| By using this software, you acknowledge having read this Agreement and agree to be bound thereby.
|*******************************************************************************************************************
| Copyright (c) 2025 Sami Ahmed. All rights reserved.
|*******************************************************************************************************************/
define('_ISVALID', true);
include_once '../../f_core/config.core.php';
// Check admin access
if (!VSession::isLoggedIn() || !VLogin::checkBackendAccess()) {
header('Location: /error');
exit;
}
$logger = VLogger::getInstance();
// Handle AJAX requests
if (isset($_GET['action'])) {
header('Content-Type: application/json');
switch ($_GET['action']) {
case 'get_logs':
$level = VSecurity::getParam('level', 'alpha');
$limit = VSecurity::getParam('limit', 'int', 100, ['min' => 1, 'max' => 1000]);
$page = VSecurity::getParam('page', 'int', 1, ['min' => 1]);
$q = VSecurity::getParam('q', 'string', '');
$since = VSecurity::getParam('since', 'string', ''); // ISO datetime or date
$offset = ($page - 1) * $limit;
$logs = $logger->getRecentLogs($level, $limit, $offset);
// Optional filtering by keyword and since timestamp
if ($q !== '') {
$qq = strtolower($q);
$logs = array_values(array_filter($logs, function($row) use ($qq) {
$hay = strtolower(($row['message'] ?? '') . ' ' . json_encode($row['context'] ?? []));
return strpos($hay, $qq) !== false || ($row['request_id'] ?? '') === $qq;
}));
}
if ($since !== '') {
$sinceTs = strtotime($since);
if ($sinceTs !== false) {
$logs = array_values(array_filter($logs, function($row) use ($sinceTs) {
$ts = strtotime($row['timestamp'] ?? '');
return $ts !== false && $ts >= $sinceTs;
}));
}
}
echo json_encode($logs);
break;
case 'clear_logs':
if (VSecurity::validateCSRFFromPost('clear_logs')) {
$logFiles = glob('f_data/logs/*.log*');
foreach ($logFiles as $file) {
unlink($file);
}
echo json_encode(['success' => true, 'message' => 'Logs cleared successfully']);
} else {
echo json_encode(['success' => false, 'message' => 'Invalid CSRF token']);
}
break;
}
exit;
}
?>
<!DOCTYPE html>
<html>
<head>
<title>Log Viewer - EasyStream Admin</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; background: #f5f5f5; }
.container { max-width: 1200px; margin: 0 auto; background: white; padding: 20px; border-radius: 8px; }
.controls { margin-bottom: 20px; padding: 15px; background: #f8f9fa; border-radius: 4px; }
.controls select, .controls button { margin-right: 10px; padding: 8px 12px; }
.log-entry { margin-bottom: 15px; padding: 15px; border-left: 4px solid #ddd; background: #fafafa; }
.log-entry.error { border-left-color: #f44336; }
.log-entry.warning { border-left-color: #ff9800; }
.log-entry.info { border-left-color: #2196f3; }
.log-entry.debug { border-left-color: #9e9e9e; }
.log-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; }
.log-level { padding: 2px 8px; border-radius: 3px; color: white; font-size: 12px; font-weight: bold; }
.log-level.error { background: #f44336; }
.log-level.warning { background: #ff9800; }
.log-level.info { background: #2196f3; }
.log-level.debug { background: #9e9e9e; }
.log-message { font-weight: bold; margin-bottom: 10px; }
.log-details { font-size: 12px; color: #666; }
.log-context { background: #f0f0f0; padding: 10px; margin-top: 10px; border-radius: 4px; font-family: monospace; font-size: 11px; }
.loading { text-align: center; padding: 20px; }
.btn { background: #1976d2; color: white; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer; }
.btn:hover { background: #1565c0; }
.btn.danger { background: #f44336; }
.btn.danger:hover { background: #d32f2f; }
</style>
</head>
<body>
<div class="container">
<h1>System Logs</h1>
<div class="controls">
<input id="logQuery" type="text" placeholder="Search keyword or request id" style="padding:8px 12px; width: 260px; margin-right:10px;" />
<input id="logSince" type="datetime-local" style="padding:8px 12px; margin-right:10px;" />
<select id="logLevel">
<option value="">All Levels</option>
<option value="error">Errors</option>
<option value="warning">Warnings</option>
<option value="info">Info</option>
<option value="debug">Debug</option>
</select>
<select id="logLimit">
<option value="50">50 entries</option>
<option value="100" selected>100 entries</option>
<option value="200">200 entries</option>
<option value="500">500 entries</option>
</select>
<button class="btn" onclick="loadLogs()">Refresh</button>
<button class="btn" onclick="prevPage()">Prev</button>
<button class="btn" onclick="nextPage()">Next</button>
<button class="btn danger" onclick="clearLogs()">Clear All Logs</button>
</div>
<div id="logContainer">
<div class="loading">Loading logs...</div>
</div>
</div>
<script>
let currentPage = 1;
function loadLogs() {
const level = document.getElementById('logLevel').value;
const limit = document.getElementById('logLimit').value;
const q = encodeURIComponent(document.getElementById('logQuery').value || '');
const since = document.getElementById('logSince').value ? new Date(document.getElementById('logSince').value).toISOString() : '';
document.getElementById('logContainer').innerHTML = '<div class="loading">Loading logs...</div>';
const url = `?action=get_logs&level=${level}&limit=${limit}&page=${currentPage}&q=${q}&since=${encodeURIComponent(since)}`;
fetch(url)
.then(response => response.json())
.then(logs => {
displayLogs(logs);
})
.catch(error => {
document.getElementById('logContainer').innerHTML = '<div class="error">Error loading logs: ' + error.message + '</div>';
});
}
function nextPage(){ currentPage++; loadLogs(); }
function prevPage(){ if (currentPage>1){ currentPage--; loadLogs(); } }
function displayLogs(logs) {
const container = document.getElementById('logContainer');
if (logs.length === 0) {
container.innerHTML = '<div class="loading">No logs found</div>';
return;
}
let html = '';
logs.forEach(log => {
html += `
<div class="log-entry ${log.level}">
<div class="log-header">
<span class="log-level ${log.level}">${log.level.toUpperCase()}</span>
<span class="log-details">
${log.timestamp} | IP: ${log.ip} | Request: ${log.request_id}
${log.user_id ? ' | User: ' + log.user_id : ''}
</span>
</div>
<div class="log-message">${escapeHtml(log.message)}</div>
<div class="log-details">
URI: ${log.request_uri || 'N/A'} | Method: ${log.request_method || 'N/A'}
</div>
${log.context && Object.keys(log.context).length > 0 ?
`<div class="log-context">${escapeHtml(JSON.stringify(log.context, null, 2))}</div>` :
''
}
</div>
`;
});
container.innerHTML = html;
}
function clearLogs() {
if (!confirm('Are you sure you want to clear all logs? This action cannot be undone.')) {
return;
}
const formData = new FormData();
formData.append('csrf_token', '<?= VSecurity::generateCSRFToken('clear_logs') ?>');
fetch('?action=clear_logs', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(result => {
if (result.success) {
alert(result.message);
loadLogs();
} else {
alert('Error: ' + result.message);
}
})
.catch(error => {
alert('Error clearing logs: ' + error.message);
});
}
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
// Auto-refresh every 30 seconds
setInterval(loadLogs, 30000);
// Load logs on page load
loadLogs();
</script>
</body>
</html>