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>
349 lines
10 KiB
PHP
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>
|