Updates design and fixes the logs page
parent
949ce27f63
commit
9fd2af6538
|
@ -21,7 +21,6 @@ class Log {
|
||||||
$this->db = $database->getConnection();
|
$this->db = $database->getConnection();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Insert a log event into the database.
|
* Insert a log event into the database.
|
||||||
*
|
*
|
||||||
|
@ -40,9 +39,9 @@ class Log {
|
||||||
|
|
||||||
$query = $this->db->prepare($sql);
|
$query = $this->db->prepare($sql);
|
||||||
$query->execute([
|
$query->execute([
|
||||||
':user_id' => $user_id,
|
':user_id' => $user_id,
|
||||||
':scope' => $scope,
|
':scope' => $scope,
|
||||||
':message' => $message,
|
':message' => $message,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -52,7 +51,6 @@ class Log {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve log entries from the database.
|
* Retrieve log entries from the database.
|
||||||
*
|
*
|
||||||
|
@ -60,36 +58,67 @@ class Log {
|
||||||
* @param string $scope The scope of the logs ('user' or 'system').
|
* @param string $scope The scope of the logs ('user' or 'system').
|
||||||
* @param int $offset The offset for pagination. Default is 0.
|
* @param int $offset The offset for pagination. Default is 0.
|
||||||
* @param int $items_per_page The number of log entries to retrieve per page. Default is no limit.
|
* @param int $items_per_page The number of log entries to retrieve per page. Default is no limit.
|
||||||
|
* @param array $filters Optional array of filters (from_time, until_time, message, id)
|
||||||
*
|
*
|
||||||
* @return array An array of log entries.
|
* @return array An array of log entries.
|
||||||
*/
|
*/
|
||||||
public function readLog($user_id, $scope, $offset=0, $items_per_page='') {
|
public function readLog($user_id, $scope, $offset=0, $items_per_page='', $filters=[]) {
|
||||||
|
$params = [];
|
||||||
|
$where_clauses = [];
|
||||||
|
|
||||||
|
// Base query with user join
|
||||||
|
$base_sql = 'SELECT l.*, u.username
|
||||||
|
FROM logs l
|
||||||
|
LEFT JOIN users u ON l.user_id = u.id';
|
||||||
|
|
||||||
|
// Add scope condition
|
||||||
if ($scope === 'user') {
|
if ($scope === 'user') {
|
||||||
$sql = 'SELECT * FROM logs WHERE user_id = :user_id ORDER BY time DESC';
|
$where_clauses[] = 'l.user_id = :user_id';
|
||||||
if ($items_per_page) {
|
$params[':user_id'] = $user_id;
|
||||||
$items_per_page = (int)$items_per_page;
|
|
||||||
$sql .= ' LIMIT ' . $offset . ',' . $items_per_page;
|
|
||||||
}
|
|
||||||
|
|
||||||
$query = $this->db->prepare($sql);
|
|
||||||
$query->execute([
|
|
||||||
':user_id' => $user_id,
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
if ($scope === 'system') {
|
|
||||||
$sql = 'SELECT * FROM logs ORDER BY time DESC';
|
|
||||||
if ($items_per_page) {
|
|
||||||
$items_per_page = (int)$items_per_page;
|
|
||||||
$sql .= ' LIMIT ' . $offset . ',' . $items_per_page;
|
|
||||||
}
|
|
||||||
|
|
||||||
$query = $this->db->prepare($sql);
|
// Add time range filters if specified
|
||||||
$query->execute();
|
if (!empty($filters['from_time'])) {
|
||||||
|
$where_clauses[] = 'l.time >= :from_time';
|
||||||
|
$params[':from_time'] = $filters['from_time'] . ' 00:00:00';
|
||||||
}
|
}
|
||||||
|
if (!empty($filters['until_time'])) {
|
||||||
|
$where_clauses[] = 'l.time <= :until_time';
|
||||||
|
$params[':until_time'] = $filters['until_time'] . ' 23:59:59';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add message search if specified
|
||||||
|
if (!empty($filters['message'])) {
|
||||||
|
$where_clauses[] = 'l.message LIKE :message';
|
||||||
|
$params[':message'] = '%' . $filters['message'] . '%';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add user ID search if specified
|
||||||
|
if (!empty($filters['id'])) {
|
||||||
|
$where_clauses[] = 'l.user_id = :search_user_id';
|
||||||
|
$params[':search_user_id'] = $filters['id'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Combine WHERE clauses
|
||||||
|
$sql = $base_sql;
|
||||||
|
if (!empty($where_clauses)) {
|
||||||
|
$sql .= ' WHERE ' . implode(' AND ', $where_clauses);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add ordering
|
||||||
|
$sql .= ' ORDER BY l.time DESC';
|
||||||
|
|
||||||
|
// Add pagination
|
||||||
|
if ($items_per_page) {
|
||||||
|
$items_per_page = (int)$items_per_page;
|
||||||
|
$sql .= ' LIMIT ' . $offset . ',' . $items_per_page;
|
||||||
|
}
|
||||||
|
|
||||||
|
$query = $this->db->prepare($sql);
|
||||||
|
$query->execute($params);
|
||||||
|
|
||||||
return $query->fetchAll(PDO::FETCH_ASSOC);
|
return $query->fetchAll(PDO::FETCH_ASSOC);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
?>
|
?>
|
||||||
|
|
|
@ -12,16 +12,36 @@ include '../app/includes/messages.php';
|
||||||
include '../app/includes/messages-show.php';
|
include '../app/includes/messages-show.php';
|
||||||
|
|
||||||
// Check for rights; user or system
|
// Check for rights; user or system
|
||||||
if (($userObject->hasRight($user_id, 'superuser') ||
|
$has_system_access = ($userObject->hasRight($user_id, 'superuser') ||
|
||||||
$userObject->hasRight($user_id, 'view app logs'))) {
|
$userObject->hasRight($user_id, 'view app logs'));
|
||||||
$scope = 'system';
|
|
||||||
} else {
|
// Get selected tab
|
||||||
$scope = 'user';
|
$selected_tab = $_REQUEST['tab'] ?? 'user';
|
||||||
|
if ($selected_tab === 'system' && !$has_system_access) {
|
||||||
|
$selected_tab = 'user';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set scope based on selected tab
|
||||||
|
$scope = ($selected_tab === 'system') ? 'system' : 'user';
|
||||||
|
|
||||||
// specify time range
|
// specify time range
|
||||||
include '../app/helpers/time_range.php';
|
include '../app/helpers/time_range.php';
|
||||||
|
|
||||||
|
// Prepare search filters
|
||||||
|
$filters = [];
|
||||||
|
if (isset($_REQUEST['from_time']) && !empty($_REQUEST['from_time'])) {
|
||||||
|
$filters['from_time'] = $_REQUEST['from_time'];
|
||||||
|
}
|
||||||
|
if (isset($_REQUEST['until_time']) && !empty($_REQUEST['until_time'])) {
|
||||||
|
$filters['until_time'] = $_REQUEST['until_time'];
|
||||||
|
}
|
||||||
|
if (isset($_REQUEST['message']) && !empty($_REQUEST['message'])) {
|
||||||
|
$filters['message'] = $_REQUEST['message'];
|
||||||
|
}
|
||||||
|
if ($scope === 'system' && isset($_REQUEST['id']) && !empty($_REQUEST['id'])) {
|
||||||
|
$filters['id'] = $_REQUEST['id'];
|
||||||
|
}
|
||||||
|
|
||||||
// pagination variables
|
// pagination variables
|
||||||
$items_per_page = 15;
|
$items_per_page = 15;
|
||||||
$browse_page = $_REQUEST['p'] ?? 1;
|
$browse_page = $_REQUEST['p'] ?? 1;
|
||||||
|
@ -29,8 +49,8 @@ $browse_page = (int)$browse_page;
|
||||||
$offset = ($browse_page -1) * $items_per_page;
|
$offset = ($browse_page -1) * $items_per_page;
|
||||||
|
|
||||||
// prepare the result
|
// prepare the result
|
||||||
$search = $logObject->readLog($user_id, $scope, $offset, $items_per_page);
|
$search = $logObject->readLog($user_id, $scope, $offset, $items_per_page, $filters);
|
||||||
$search_all = $logObject->readLog($user_id, $scope);
|
$search_all = $logObject->readLog($user_id, $scope, 0, '', $filters);
|
||||||
|
|
||||||
if (!empty($search)) {
|
if (!empty($search)) {
|
||||||
// we get total items and number of pages
|
// we get total items and number of pages
|
||||||
|
@ -41,20 +61,20 @@ if (!empty($search)) {
|
||||||
$logs['records'] = array();
|
$logs['records'] = array();
|
||||||
|
|
||||||
foreach ($search as $item) {
|
foreach ($search as $item) {
|
||||||
|
|
||||||
// when we show only user's logs, omit user_id column
|
// when we show only user's logs, omit user_id column
|
||||||
if ($scope === 'user') {
|
if ($scope === 'user') {
|
||||||
$log_record = array(
|
$log_record = array(
|
||||||
// assign title to the field in the array record
|
// assign title to the field in the array record
|
||||||
'time' => $item['time'],
|
'time' => $item['time'],
|
||||||
'log message' => $item['message']
|
'log message' => $item['message']
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
$log_record = array(
|
$log_record = array(
|
||||||
// assign title to the field in the array record
|
// assign title to the field in the array record
|
||||||
'userID' => $item['user_id'],
|
'userID' => $item['user_id'],
|
||||||
'time' => $item['time'],
|
'username' => $item['username'],
|
||||||
'log message' => $item['message']
|
'time' => $item['time'],
|
||||||
|
'log message' => $item['message']
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,8 +88,10 @@ $widget['full'] = false;
|
||||||
$widget['collapsible'] = false;
|
$widget['collapsible'] = false;
|
||||||
$widget['name'] = 'Logs';
|
$widget['name'] = 'Logs';
|
||||||
$username = $userObject->getUserDetails($user_id)[0]['username'];
|
$username = $userObject->getUserDetails($user_id)[0]['username'];
|
||||||
$widget['title'] = "Log events for user \"$username\"";
|
$widget['title'] = "Log events";
|
||||||
$widget['filter'] = true;
|
$widget['filter'] = true;
|
||||||
|
$widget['scope'] = $scope;
|
||||||
|
$widget['has_system_access'] = $has_system_access;
|
||||||
if (!empty($logs['records'])) {
|
if (!empty($logs['records'])) {
|
||||||
$widget['full'] = true;
|
$widget['full'] = true;
|
||||||
$widget['table_headers'] = array_keys($logs['records'][0]);
|
$widget['table_headers'] = array_keys($logs['records'][0]);
|
||||||
|
|
|
@ -1,26 +1,41 @@
|
||||||
|
|
||||||
<!-- Logs filter -->
|
<!-- Logs filter -->
|
||||||
<div class="card w-auto bg-light border-light card-body text-right" style="text-align: right;">
|
<div class="card mb-3">
|
||||||
<form method="POST" id="filter_form" class="filter-results" action="?page=logs">
|
<div class="card-body">
|
||||||
<label for="from_time">from</label>
|
<form method="get" action="" class="row g-3 align-items-end">
|
||||||
<input type="date" id="from_time" name="from_time"<?php if (isset($_REQUEST['from_time'])) echo " value=\"" . htmlspecialchars($from_time) . "\"" ?> />
|
<input type="hidden" name="page" value="logs">
|
||||||
<label for="until_time">until</label>
|
<input type="hidden" name="tab" value="<?= htmlspecialchars($widget['scope']) ?>">
|
||||||
<input type="date" id="until_time" name="until_time"<?php if (isset($_REQUEST['until_time'])) echo " value=\"" . htmlspecialchars($until_time) . "\"" ?> />
|
|
||||||
<input type="text" name="id" placeholder="user ID"<?php if (isset($_REQUEST['id'])) echo " value=\"" . htmlspecialchars($_REQUEST['id']) . "\"" ?> />
|
<div class="col-md-3">
|
||||||
<input type="text" name="message" placeholder="message"<?php if (isset($_REQUEST['message'])) echo " value=\"" . htmlspecialchars($_REQUEST['message']) . "\"" ?> />
|
<label for="from_time" class="form-label">From date</label>
|
||||||
<input type="button" onclick="clearFilter()" value="clear" />
|
<input type="date" class="form-control" id="from_time" name="from_time" value="<?= htmlspecialchars($_REQUEST['from_time'] ?? '') ?>">
|
||||||
<input type="submit" value="search" />
|
</div>
|
||||||
</form>
|
|
||||||
<script>
|
<div class="col-md-3">
|
||||||
function clearFilter() {
|
<label for="until_time" class="form-label">Until date</label>
|
||||||
document.getElementById("filter_form").reset();
|
<input type="date" class="form-control" id="until_time" name="until_time" value="<?= htmlspecialchars($_REQUEST['until_time'] ?? '') ?>">
|
||||||
const filterFields = document.querySelectorAll("#filter_form input");
|
</div>
|
||||||
filterFields.forEach(input => {
|
|
||||||
if (input.type === 'text' ||input.type === 'date') {
|
<?php if ($widget['scope'] === 'system') { ?>
|
||||||
input.value = '';
|
<div class="col-md-2">
|
||||||
}
|
<label for="id" class="form-label">User ID</label>
|
||||||
});
|
<input type="text" class="form-control" id="id" name="id" value="<?= htmlspecialchars($_REQUEST['id'] ?? '') ?>" placeholder="Enter user ID">
|
||||||
}
|
</div>
|
||||||
</script>
|
<?php } ?>
|
||||||
|
|
||||||
|
<div class="col-md">
|
||||||
|
<label for="message" class="form-label">Message</label>
|
||||||
|
<input type="text" class="form-control" id="message" name="message" value="<?= htmlspecialchars($_REQUEST['message'] ?? '') ?>" placeholder="Search in log messages">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-auto">
|
||||||
|
<button type="submit" class="btn btn-primary me-2">
|
||||||
|
<i class="fas fa-search me-2"></i>Search
|
||||||
|
</button>
|
||||||
|
<a href="?page=logs&tab=<?= htmlspecialchars($widget['scope']) ?>" class="btn btn-outline-secondary">
|
||||||
|
<i class="fas fa-times me-2"></i>Clear
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- /Logs filter -->
|
<!-- /Logs filter -->
|
||||||
|
|
|
@ -1,57 +1,72 @@
|
||||||
|
<!-- log events -->
|
||||||
<div class="row">
|
<div class="container-fluid mt-2">
|
||||||
<?php if ($widget['collapsible'] === true) { ?>
|
<div class="row mb-4">
|
||||||
<a style="text-decoration: none;" data-toggle="collapse" href="#collapse<?= htmlspecialchars($widget['name']) ?>" role="button" aria-expanded="true" aria-controls="collapse<?= htmlspecialchars($widget['name']) ?>">
|
<div class="col">
|
||||||
<div class="card w-auto bg-light card-body" style="flex-direction: row;"><?= htmlspecialchars($widget['title']) ?></div>
|
<h2 class="mb-3"><?= htmlspecialchars($widget['title']) ?></h2>
|
||||||
<?php } else { ?>
|
<ul class="nav nav-tabs mb-3">
|
||||||
<div class="card w-auto bg-light border-light card-body" style="flex-direction: row;"><?= htmlspecialchars($widget['title']) ?></div>
|
<li class="nav-item">
|
||||||
<?php } ?>
|
<a class="nav-link <?= $widget['scope'] === 'user' ? 'active' : '' ?>" href="?page=logs&tab=user">
|
||||||
<?php if ($widget['filter'] === true) {
|
Logs for current user
|
||||||
include '../app/templates/logs-filter.php'; } ?>
|
|
||||||
<?php if ($widget['collapsible'] === true) { ?>
|
|
||||||
</a>
|
</a>
|
||||||
|
</li>
|
||||||
|
<?php if ($widget['has_system_access']) { ?>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link <?= $widget['scope'] === 'system' ? 'active' : '' ?>" href="?page=logs&tab=system">
|
||||||
|
Logs for all users
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
<?php } ?>
|
<?php } ?>
|
||||||
</div>
|
</ul>
|
||||||
|
|
||||||
<!-- widget "<?= htmlspecialchars($widget['name']) ?>" -->
|
<?php if ($widget['filter'] === true) {
|
||||||
<div class="collapse show" id="collapse<?= htmlspecialchars($widget['name']) ?>">
|
include '../app/templates/logs-filter.php';
|
||||||
|
} ?>
|
||||||
|
|
||||||
|
<!-- widget "<?= htmlspecialchars($widget['name']) ?>" -->
|
||||||
|
<div class="collapse show" id="collapse<?= htmlspecialchars($widget['name']) ?>">
|
||||||
<?php if ($time_range_specified) { ?>
|
<?php if ($time_range_specified) { ?>
|
||||||
<p class="m-3">time period: <strong><?= htmlspecialchars($from_time) ?> - <?= htmlspecialchars($until_time) ?></strong></p>
|
<div class="alert alert-info m-3">
|
||||||
|
<i class="fas fa-calendar-alt me-2"></i>Time period: <strong><?= htmlspecialchars($from_time) ?> - <?= htmlspecialchars($until_time) ?></strong>
|
||||||
|
</div>
|
||||||
<?php } ?>
|
<?php } ?>
|
||||||
<div class="mb-5">
|
<div class="mb-5">
|
||||||
<?php if ($widget['full'] === true) { ?>
|
<?php if ($widget['full'] === true) { ?>
|
||||||
<table class="table table-results table-striped table-hover table-bordered">
|
<div class="table-responsive">
|
||||||
<thead class="thead-dark">
|
<table class="table table-hover align-middle mb-0">
|
||||||
|
<thead class="table-light">
|
||||||
<tr>
|
<tr>
|
||||||
<?php foreach ($widget['table_headers'] as $header) { ?>
|
<?php if ($widget['scope'] === 'system') { ?>
|
||||||
<th scope="col" class="th-<?= htmlspecialchars($header) ?>"><?= htmlspecialchars($header) ?></th>
|
<th>Username (id)</th>
|
||||||
<?php } ?>
|
<?php } ?>
|
||||||
|
<th>Time</th>
|
||||||
|
<th>Log message</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<?php foreach ($widget['table_records'] as $row) { ?>
|
<?php foreach ($widget['table_records'] as $row) { ?>
|
||||||
<tr>
|
<tr>
|
||||||
<?php
|
<?php if ($widget['scope'] === 'system') { ?>
|
||||||
foreach ($row as $key => $column) {
|
<td><strong><?= htmlspecialchars($row['username']) ?> (<?= htmlspecialchars($row['userID']) ?>)</strong></td>
|
||||||
if ($key === 'user ID' && isset($user_id) && $user_id === $column) { ?>
|
<?php } ?>
|
||||||
<td><strong><?= htmlspecialchars($column ?? '') ?></strong></td>
|
<td><span class="text-muted"><?= date('d M Y H:i', strtotime($row['time'])) ?></span></td>
|
||||||
<?php } else { ?>
|
<td><?= htmlspecialchars($row['log message']) ?></td>
|
||||||
<td><?= htmlspecialchars($column ?? '') ?></td>
|
|
||||||
<?php }
|
|
||||||
} ?>
|
|
||||||
</tr>
|
</tr>
|
||||||
<?php } ?>
|
<?php } ?>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<?php
|
|
||||||
if ($widget['pagination'] && $item_count > $items_per_page) {
|
|
||||||
$url = "$app_root?platform=$platform_id&page=$page";
|
|
||||||
include '../app/helpers/pagination.php';
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
<?php } else { ?>
|
|
||||||
<p class="m-3">No matching records found.</p>
|
|
||||||
<?php } ?>
|
|
||||||
</div>
|
</div>
|
||||||
|
<?php
|
||||||
|
if ($widget['pagination'] && $item_count > $items_per_page) {
|
||||||
|
include '../app/helpers/pagination.php';
|
||||||
|
}
|
||||||
|
} else { ?>
|
||||||
|
<div class="alert alert-info m-3">
|
||||||
|
<i class="fas fa-info-circle me-2"></i>No log entries found for the specified criteria.
|
||||||
|
</div>
|
||||||
|
<?php } ?>
|
||||||
</div>
|
</div>
|
||||||
<!-- /widget "<?= htmlspecialchars($widget['name']) ?>" -->
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- /log events -->
|
||||||
|
|
Loading…
Reference in New Issue