Security fixes: - Add HTTP method validation to delete_comment.php (block CSRF via GET) - Remove $_GET fallback in comment deletion (was CSRF bypass vector) - Guard session_start() with session_status() check across API files - Escape json_encode() data attributes with htmlspecialchars in views - Escape inline APP_TIMEZONE config values in DashboardView/TicketView - Validate timezone param against DateTimeZone::listIdentifiers() in index.php - Remove Database::escape() (was using real_escape_string, not safe) - Fix AttachmentModel hardcoded connection; inject via constructor Backend fixes: - Fix CommentModel bind_param type for ticket_id (s→i) - Fix buildCommentThread orphan parent guard - Fix StatsModel JOIN→LEFT JOIN so unassigned tickets aren't excluded - Add ticket ID validation in BulkOperationsModel before implode() - Add duplicate key retry in TicketModel::createTicket() for race conditions - Wrap SavedFiltersModel default filter changes in transactions - Add null result guards in WorkflowModel query methods Frontend JS: - Rewrite toast.js as lt.toast shim (base.js dependency) - Delegate escapeHtml() to lt.escHtml() - Rewrite keyboard-shortcuts.js using lt.keys.on() - Migrate settings.js to lt.api.* and lt.modal.open/close() - Migrate advanced-search.js to lt.api.* and lt.modal.open/close() - Migrate dashboard.js fetch calls to lt.api.*; update all dynamic modals (bulk ops, quick actions, confirm/input) to lt-modal structure - Migrate ticket.js fetchMentionUsers to lt.api.get() - Remove console.log/error/warn calls from JS files Views: - Add /web_template/base.css and base.js to all 10 view files - Call lt.keys.initDefaults() in DashboardView, TicketView, admin views - Migrate all modal HTML from settings-modal/settings-content to lt-modal-overlay/lt-modal/lt-modal-header/lt-modal-body/lt-modal-footer - Replace style="display:none" with aria-hidden="true" on all modals - Replace modal open/close style.display with lt.modal.open/close() - Update modal buttons to lt-btn lt-btn-primary/lt-btn-ghost classes - Remove manual ESC keydown handlers (replaced by lt.keys.initDefaults) - Fix unescaped timezone values in TicketView inline script Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
138 lines
7.8 KiB
PHP
138 lines
7.8 KiB
PHP
<?php
|
|
// Admin view for user activity reports
|
|
// Receives $userStats, $dateRange from controller
|
|
?>
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>User Activity - Admin</title>
|
|
<link rel="icon" type="image/png" href="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/images/favicon.png">
|
|
<link rel="stylesheet" href="/web_template/base.css">
|
|
<link rel="stylesheet" href="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/css/dashboard.css">
|
|
<link rel="stylesheet" href="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/css/ticket.css">
|
|
<script src="/web_template/base.js"></script>
|
|
</head>
|
|
<body>
|
|
<div class="user-header">
|
|
<div class="user-header-left">
|
|
<a href="/" class="back-link">← Dashboard</a>
|
|
<span style="margin-left: 1rem; color: var(--terminal-amber);">Admin: User Activity</span>
|
|
</div>
|
|
<div class="user-header-right">
|
|
<?php if (isset($GLOBALS['currentUser'])): ?>
|
|
<span class="user-name"><?php echo htmlspecialchars($GLOBALS['currentUser']['display_name'] ?? $GLOBALS['currentUser']['username']); ?></span>
|
|
<span class="admin-badge">Admin</span>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="ascii-frame-outer" style="max-width: 1200px; margin: 2rem auto;">
|
|
<span class="bottom-left-corner">╚</span>
|
|
<span class="bottom-right-corner">╝</span>
|
|
|
|
<div class="ascii-section-header">User Activity Report</div>
|
|
<div class="ascii-content">
|
|
<div class="ascii-frame-inner">
|
|
<!-- Date Range Filter -->
|
|
<form method="GET" style="display: flex; gap: 1rem; margin-bottom: 1.5rem; align-items: flex-end;">
|
|
<div>
|
|
<label style="display: block; font-size: 0.8rem; color: var(--terminal-amber);">Date From</label>
|
|
<input type="date" name="date_from" value="<?php echo htmlspecialchars($dateRange['from'] ?? ''); ?>" class="setting-select">
|
|
</div>
|
|
<div>
|
|
<label style="display: block; font-size: 0.8rem; color: var(--terminal-amber);">Date To</label>
|
|
<input type="date" name="date_to" value="<?php echo htmlspecialchars($dateRange['to'] ?? ''); ?>" class="setting-select">
|
|
</div>
|
|
<button type="submit" class="btn">Apply</button>
|
|
<a href="?" class="btn btn-secondary">Reset</a>
|
|
</form>
|
|
|
|
<!-- User Activity Table -->
|
|
<table style="width: 100%;">
|
|
<thead>
|
|
<tr>
|
|
<th>User</th>
|
|
<th style="text-align: center;">Tickets Created</th>
|
|
<th style="text-align: center;">Tickets Resolved</th>
|
|
<th style="text-align: center;">Comments Added</th>
|
|
<th style="text-align: center;">Tickets Assigned</th>
|
|
<th style="text-align: center;">Last Activity</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php if (empty($userStats)): ?>
|
|
<tr>
|
|
<td colspan="6" style="text-align: center; padding: 2rem; color: var(--terminal-green-dim);">
|
|
No user activity data available.
|
|
</td>
|
|
</tr>
|
|
<?php else: ?>
|
|
<?php foreach ($userStats as $user): ?>
|
|
<tr>
|
|
<td>
|
|
<strong><?php echo htmlspecialchars($user['display_name'] ?? $user['username']); ?></strong>
|
|
<?php if ($user['is_admin']): ?>
|
|
<span class="admin-badge" style="font-size: 0.7rem;">Admin</span>
|
|
<?php endif; ?>
|
|
</td>
|
|
<td style="text-align: center;">
|
|
<span style="color: var(--terminal-green); font-weight: bold;"><?php echo $user['tickets_created'] ?? 0; ?></span>
|
|
</td>
|
|
<td style="text-align: center;">
|
|
<span style="color: var(--status-open); font-weight: bold;"><?php echo $user['tickets_resolved'] ?? 0; ?></span>
|
|
</td>
|
|
<td style="text-align: center;">
|
|
<span style="color: var(--terminal-cyan); font-weight: bold;"><?php echo $user['comments_added'] ?? 0; ?></span>
|
|
</td>
|
|
<td style="text-align: center;">
|
|
<span style="color: var(--terminal-amber); font-weight: bold;"><?php echo $user['tickets_assigned'] ?? 0; ?></span>
|
|
</td>
|
|
<td style="text-align: center; font-size: 0.9rem;">
|
|
<?php echo $user['last_activity'] ? date('M d, Y H:i', strtotime($user['last_activity'])) : 'Never'; ?>
|
|
</td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
<?php endif; ?>
|
|
</tbody>
|
|
</table>
|
|
|
|
<!-- Summary Stats -->
|
|
<?php if (!empty($userStats)): ?>
|
|
<div style="margin-top: 2rem; padding: 1rem; border: 1px solid var(--terminal-green);">
|
|
<h4 style="color: var(--terminal-amber); margin-bottom: 1rem;">Summary</h4>
|
|
<div style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 1rem; text-align: center;">
|
|
<div>
|
|
<div style="font-size: 1.5rem; color: var(--terminal-green); font-weight: bold;">
|
|
<?php echo array_sum(array_column($userStats, 'tickets_created')); ?>
|
|
</div>
|
|
<div style="font-size: 0.8rem; color: var(--terminal-green-dim);">Total Created</div>
|
|
</div>
|
|
<div>
|
|
<div style="font-size: 1.5rem; color: var(--status-open); font-weight: bold;">
|
|
<?php echo array_sum(array_column($userStats, 'tickets_resolved')); ?>
|
|
</div>
|
|
<div style="font-size: 0.8rem; color: var(--terminal-green-dim);">Total Resolved</div>
|
|
</div>
|
|
<div>
|
|
<div style="font-size: 1.5rem; color: var(--terminal-cyan); font-weight: bold;">
|
|
<?php echo array_sum(array_column($userStats, 'comments_added')); ?>
|
|
</div>
|
|
<div style="font-size: 0.8rem; color: var(--terminal-green-dim);">Total Comments</div>
|
|
</div>
|
|
<div>
|
|
<div style="font-size: 1.5rem; color: var(--terminal-amber); font-weight: bold;">
|
|
<?php echo count($userStats); ?>
|
|
</div>
|
|
<div style="font-size: 0.8rem; color: var(--terminal-green-dim);">Active Users</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</body>
|
|
</html>
|