loreal-global-kickoff/logs-viewer.php
Vadym Samoilenko 9036bafc0d Log every user login to Activity Logs
Track all logins (not just first) via ApplicationLogger user_login action.
Add User Login filter option to logs-viewer.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 20:54:07 +00:00

349 lines
10 KiB
PHP

<?php
/**
* Application Logs Viewer
* View and export application activity logs
*/
// Load dependencies
require_once __DIR__ . '/vendor/autoload.php';
require_once __DIR__ . '/AuthMiddleware.php';
require_once __DIR__ . '/ApplicationLogger.php';
// Initialize authentication
$auth = new AuthMiddleware();
$user = $auth->requireAdmin();
$pageTitle = 'Activity Logs - L\'Oréal OMG';
// Initialize logger
$logger = new ApplicationLogger();
// Get filter parameters
$action = $_GET['action'] ?? 'all';
$userFilter = $_GET['user'] ?? 'all';
$limit = isset($_GET['limit']) ? (int)$_GET['limit'] : 100;
// Get logs based on filters
if ($action !== 'all') {
$logs = $logger->getLogsByAction($action, $limit);
} elseif ($userFilter !== 'all') {
$logs = $logger->getLogsByUser($userFilter, $limit);
} else {
$logs = $logger->getRecentLogs($limit);
}
// Get statistics
$stats = $logger->getStatistics();
// Export functionality
if (isset($_GET['export']) && $_GET['export'] === 'csv') {
header('Content-Type: text/csv; charset=utf-8');
header('Content-Disposition: attachment; filename="application_logs_' . date('Y-m-d') . '.csv"');
$output = fopen('php://output', 'w');
fputcsv($output, ['Timestamp', 'Action', 'User Email', 'User Name', 'Status', 'Data', 'IP Address']);
foreach ($logs as $log) {
fputcsv($output, [
$log['timestamp'],
$log['action'],
$log['user_email'],
$log['user_name'],
$log['status'],
json_encode($log['data']),
$log['ip_address']
]);
}
fclose($output);
exit;
}
// Include shared header
require_once __DIR__ . '/header.php';
?>
<div class="logs-container">
<div class="logs-section">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 30px;">
<h2>Application Activity Logs</h2>
<a href="?export=csv&action=<?php echo urlencode($action); ?>&user=<?php echo urlencode($userFilter); ?>&limit=<?php echo $limit; ?>" class="action-btn download-btn">
Export to CSV
</a>
</div>
<!-- Statistics Dashboard -->
<div class="stats-dashboard">
<div class="stat-card">
<div class="stat-label">Total Actions</div>
<div class="stat-value"><?php echo $stats['total_actions']; ?></div>
</div>
<div class="stat-card">
<div class="stat-label">Successful</div>
<div class="stat-value" style="color: #4CAF50;"><?php echo $stats['by_status']['success'] ?? 0; ?></div>
</div>
<div class="stat-card">
<div class="stat-label">Errors</div>
<div class="stat-value" style="color: #ff4444;"><?php echo $stats['by_status']['error'] ?? 0; ?></div>
</div>
<div class="stat-card">
<div class="stat-label">Unique Users</div>
<div class="stat-value"><?php echo count($stats['by_user']); ?></div>
</div>
</div>
<!-- Filters -->
<form method="GET" class="filters-form">
<div class="filter-group">
<label for="actionFilter">Action Type:</label>
<select name="action" id="actionFilter" class="filter-select">
<option value="all" <?php echo $action === 'all' ? 'selected' : ''; ?>>All Actions</option>
<option value="user_login" <?php echo $action === 'user_login' ? 'selected' : ''; ?>>User Login</option>
<option value="master_asset_submission" <?php echo $action === 'master_asset_submission' ? 'selected' : ''; ?>>Master Asset Submission</option>
<option value="global_to_local_transform" <?php echo $action === 'global_to_local_transform' ? 'selected' : ''; ?>>Global to Local Transform</option>
<option value="box_upload" <?php echo $action === 'box_upload' ? 'selected' : ''; ?>>Box Upload</option>
<option value="omg_api_lookup" <?php echo $action === 'omg_api_lookup' ? 'selected' : ''; ?>>OMG API Lookup</option>
</select>
</div>
<div class="filter-group">
<label for="limitFilter">Show:</label>
<select name="limit" id="limitFilter" class="filter-select">
<option value="50" <?php echo $limit === 50 ? 'selected' : ''; ?>>Last 50</option>
<option value="100" <?php echo $limit === 100 ? 'selected' : ''; ?>>Last 100</option>
<option value="500" <?php echo $limit === 500 ? 'selected' : ''; ?>>Last 500</option>
<option value="1000" <?php echo $limit === 1000 ? 'selected' : ''; ?>>Last 1000</option>
</select>
</div>
<button type="submit" class="action-btn download-btn">Apply Filters</button>
</form>
<!-- Logs Table -->
<div class="logs-table-container">
<table class="logs-table">
<thead>
<tr>
<th>Timestamp</th>
<th>Action</th>
<th>User</th>
<th>Status</th>
<th>Details</th>
</tr>
</thead>
<tbody>
<?php if (empty($logs)): ?>
<tr>
<td colspan="5" style="text-align: center; padding: 40px; color: #999;">
No logs found
</td>
</tr>
<?php else: ?>
<?php foreach ($logs as $log): ?>
<tr class="log-row log-<?php echo $log['status']; ?>">
<td class="log-timestamp"><?php echo htmlspecialchars($log['timestamp']); ?></td>
<td class="log-action"><?php echo htmlspecialchars(str_replace('_', ' ', ucwords($log['action'], '_'))); ?></td>
<td class="log-user"><?php echo htmlspecialchars($log['user_email']); ?></td>
<td class="log-status">
<span class="status-badge status-<?php echo $log['status']; ?>">
<?php echo htmlspecialchars($log['status']); ?>
</span>
</td>
<td class="log-details">
<button type="button" class="details-btn" onclick="toggleDetails(this)">
View Details
</button>
<div class="log-details-content" style="display: none;">
<pre><?php echo htmlspecialchars(json_encode($log['data'], JSON_PRETTY_PRINT)); ?></pre>
</div>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
<script>
function toggleDetails(btn) {
const detailsDiv = btn.nextElementSibling;
if (detailsDiv.style.display === 'none') {
detailsDiv.style.display = 'block';
btn.textContent = 'Hide Details';
} else {
detailsDiv.style.display = 'none';
btn.textContent = 'View Details';
}
}
</script>
<style>
.logs-container {
max-width: 1400px;
margin: 0 auto;
}
.logs-section {
background-color: #ffffff;
padding: 40px;
border-radius: 10px;
box-shadow: 0 4px 6px rgba(255, 196, 7, 0.3);
}
.stats-dashboard {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.stat-card {
background-color: #f9f9f9;
padding: 20px;
border-radius: 8px;
border-left: 4px solid #FFC407;
}
.stat-label {
font-size: 12px;
color: #666;
text-transform: uppercase;
margin-bottom: 8px;
font-weight: 600;
}
.stat-value {
font-size: 32px;
color: #000000;
font-weight: 700;
}
.filters-form {
display: flex;
gap: 20px;
margin-bottom: 30px;
padding: 20px;
background-color: #f9f9f9;
border-radius: 8px;
align-items: flex-end;
}
.filter-group {
display: flex;
flex-direction: column;
gap: 8px;
}
.filter-group label {
font-weight: 600;
font-size: 14px;
color: #000000;
}
.filter-select {
padding: 10px 15px;
border: 2px solid #FFC407;
border-radius: 5px;
font-family: 'Montserrat', sans-serif;
font-size: 14px;
min-width: 200px;
}
.logs-table-container {
border: 2px solid #FFC407;
border-radius: 8px;
overflow: auto;
max-height: 600px;
}
.logs-table {
width: 100%;
border-collapse: collapse;
font-size: 13px;
}
.logs-table th {
position: sticky;
top: 0;
background-color: #000000;
color: #FFC407;
padding: 12px 10px;
text-align: left;
font-weight: 700;
border-bottom: 2px solid #FFC407;
z-index: 10;
}
.logs-table td {
padding: 12px 10px;
border-bottom: 1px solid #eee;
}
.log-row:hover {
background-color: rgba(255, 196, 7, 0.05);
}
.log-row.log-error {
background-color: rgba(255, 68, 68, 0.05);
}
.status-badge {
padding: 4px 12px;
border-radius: 12px;
font-size: 11px;
font-weight: 700;
text-transform: uppercase;
}
.status-badge.status-success {
background-color: #4CAF50;
color: white;
}
.status-badge.status-error {
background-color: #ff4444;
color: white;
}
.status-badge.status-warning {
background-color: #ff9800;
color: white;
}
.details-btn {
padding: 6px 12px;
background-color: #FFC407;
color: #000000;
border: none;
border-radius: 4px;
font-size: 12px;
font-weight: 600;
cursor: pointer;
font-family: 'Montserrat', sans-serif;
}
.details-btn:hover {
background-color: #000000;
color: #FFC407;
}
.log-details-content {
margin-top: 10px;
padding: 15px;
background-color: #f5f5f5;
border-radius: 5px;
max-width: 600px;
}
.log-details-content pre {
margin: 0;
font-size: 11px;
white-space: pre-wrap;
word-wrap: break-word;
}
</style>
</body>
</html>