admin: auth and action logs
This commit is contained in:
parent
72ed74e789
commit
caeafc3f54
@ -1,6 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
const ROUTER_VERSION = 5;
|
const ROUTER_VERSION = 6;
|
||||||
const ROUTER_MC_KEY = '4in1/routes';
|
const ROUTER_MC_KEY = '4in1/routes';
|
||||||
|
|
||||||
$RouterInput = [];
|
$RouterInput = [];
|
||||||
|
@ -47,7 +47,7 @@ class AdminHandler extends request_handler {
|
|||||||
= input('i:ip, query, url_query, file_query, i:line_query, i:per_page');
|
= input('i:ip, query, url_query, file_query, i:line_query, i:per_page');
|
||||||
|
|
||||||
if (!$per_page)
|
if (!$per_page)
|
||||||
$per_page = 10;
|
$per_page = 100;
|
||||||
$db = DB();
|
$db = DB();
|
||||||
|
|
||||||
$query = trim($query ?? '');
|
$query = trim($query ?? '');
|
||||||
@ -148,6 +148,94 @@ class AdminHandler extends request_handler {
|
|||||||
...$vars);
|
...$vars);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function GET_auth_log() {
|
||||||
|
$db = DB();
|
||||||
|
$count = (int)$db->result($db->query("SELECT COUNT(*) FROM admin_log"));
|
||||||
|
$per_page = 100;
|
||||||
|
list($page, $pages, $offset) = get_page($per_page, $count);
|
||||||
|
|
||||||
|
$q = $db->query("SELECT *,
|
||||||
|
INET_NTOA(ip) AS ip,
|
||||||
|
admins.login AS login,
|
||||||
|
admins.activity_ts AS activitiy_ts
|
||||||
|
FROM admin_log
|
||||||
|
LEFT JOIN admins ON admins.id=admin_log.admin_id
|
||||||
|
ORDER BY admin_log.id DESC
|
||||||
|
LIMIT $offset, ".$per_page);
|
||||||
|
$list = $db->fetchAll($q);
|
||||||
|
|
||||||
|
if (!empty($list)) {
|
||||||
|
$list = array_map(function($item) {
|
||||||
|
$item['date'] = format_time($item['ts']);
|
||||||
|
$item['activity_ts_s'] = format_time($item['activity_ts']);
|
||||||
|
return $item;
|
||||||
|
}, $list);
|
||||||
|
}
|
||||||
|
|
||||||
|
$vars = [
|
||||||
|
'list' => $list,
|
||||||
|
'pn_page' => $page,
|
||||||
|
'pn_pages' => $pages
|
||||||
|
];
|
||||||
|
set_skin_opts(['wide' => true]);
|
||||||
|
set_title('$admin_auth_log');
|
||||||
|
render('admin/auth_log',
|
||||||
|
...$vars);
|
||||||
|
}
|
||||||
|
|
||||||
|
function GET_actions_log() {
|
||||||
|
$field_types = \AdminActions\Util\Logger::getFieldTypes();
|
||||||
|
foreach ($field_types as $type_prefix => $type_data) {
|
||||||
|
for ($i = 1; $i <= $type_data['count']; $i++) {
|
||||||
|
$name = $type_prefix.'arg'.$i;
|
||||||
|
if (isset($_REQUEST[$name]) && (string)$_REQUEST[$name] !== '')
|
||||||
|
$argument_filters[$name] = $type_data['unpacker']((string)$_REQUEST[$name]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$per_page = 100;
|
||||||
|
|
||||||
|
$count = \AdminActions\Util\Logger::getRecordsCount();
|
||||||
|
list($page, $pages, $offset) = get_page($per_page, $count);
|
||||||
|
|
||||||
|
$admin_ids = [];
|
||||||
|
$admin_logins = [];
|
||||||
|
|
||||||
|
$records = \AdminActions\Util\Logger::getRecords($offset, $per_page);
|
||||||
|
foreach ($records as $record) {
|
||||||
|
list($admin_id) = $record->getActorInfo();
|
||||||
|
if ($admin_id !== null)
|
||||||
|
$admin_ids[$admin_id] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($admin_ids))
|
||||||
|
$admin_logins = admin_get_logins_by_id(array_keys($admin_ids));
|
||||||
|
|
||||||
|
$url = '/admin/actions-log/?';
|
||||||
|
|
||||||
|
$filter_fields = [];
|
||||||
|
foreach ($field_types as $type_prefix => $type_data) {
|
||||||
|
for ($i = 1; $i <= $type_data['count']; $i++) {
|
||||||
|
$name = $type_prefix.'arg'.$i;
|
||||||
|
$filter_fields[$name] = $argument_filters[$name] ?? '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$vars = [
|
||||||
|
'list' => $records,
|
||||||
|
'pn_page' => $page,
|
||||||
|
'pn_pages' => $pages,
|
||||||
|
'admin_logins' => $admin_logins,
|
||||||
|
'url' => $url,
|
||||||
|
'action_types' => \AdminActions\Util\Logger::getActions(true),
|
||||||
|
];
|
||||||
|
|
||||||
|
set_skin_opts(['wide' => true]);
|
||||||
|
set_title('$admin_actions_log');
|
||||||
|
render('admin/actions_log',
|
||||||
|
...$vars);
|
||||||
|
}
|
||||||
|
|
||||||
function GET_uploads() {
|
function GET_uploads() {
|
||||||
list($error) = input('error');
|
list($error) = input('error');
|
||||||
$uploads = uploads::getAllUploads();
|
$uploads = uploads::getAllUploads();
|
||||||
|
@ -54,9 +54,9 @@ abstract class BaseAction {
|
|||||||
return $this->isCli;
|
return $this->isCli;
|
||||||
}
|
}
|
||||||
|
|
||||||
//public function getDate(): string {
|
public function getDate(): string {
|
||||||
// return formatTime($this->timeStamp, ['short_months' => true]);
|
return format_time($this->timeStamp, ['short_months' => true]);
|
||||||
//}
|
}
|
||||||
|
|
||||||
public function getTimeStamp(): int {
|
public function getTimeStamp(): int {
|
||||||
return $this->timeStamp;
|
return $this->timeStamp;
|
||||||
@ -70,7 +70,7 @@ abstract class BaseAction {
|
|||||||
return $this->recordId;
|
return $this->recordId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*function renderHtml(): string {
|
function renderHtml(): string {
|
||||||
$rc = new \ReflectionClass($this);
|
$rc = new \ReflectionClass($this);
|
||||||
$lines = [];
|
$lines = [];
|
||||||
$fields = $rc->getProperties(\ReflectionProperty::IS_PUBLIC);
|
$fields = $rc->getProperties(\ReflectionProperty::IS_PUBLIC);
|
||||||
@ -87,5 +87,5 @@ abstract class BaseAction {
|
|||||||
$lines[] = $name.'='.$val;
|
$lines[] = $name.'='.$val;
|
||||||
}
|
}
|
||||||
return implode('<br>', $lines);
|
return implode('<br>', $lines);
|
||||||
}*/
|
}
|
||||||
}
|
}
|
@ -54,6 +54,20 @@ function admin_delete(string $login): bool {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int[] $ids
|
||||||
|
* @return string[]
|
||||||
|
*/
|
||||||
|
function admin_get_logins_by_id(array $ids): array {
|
||||||
|
$db = DB();
|
||||||
|
$logins = [];
|
||||||
|
$q = $db->query("SELECT id, login FROM admins WHERE id IN (".implode(',', $ids).")");
|
||||||
|
while ($row = $db->fetch($q)) {
|
||||||
|
$logins[(int)$row['id']] = $row['login'];
|
||||||
|
}
|
||||||
|
return $logins;
|
||||||
|
}
|
||||||
|
|
||||||
function admin_get_id_by_login(string $login): ?int {
|
function admin_get_id_by_login(string $login): ?int {
|
||||||
$db = DB();
|
$db = DB();
|
||||||
$q = $db->query("SELECT id FROM admins WHERE login=?", $login);
|
$q = $db->query("SELECT id FROM admins WHERE login=?", $login);
|
||||||
|
@ -33,6 +33,7 @@ return (function() {
|
|||||||
'articles/([a-z0-9-]+)/{delete,edit}/' => 'post_${1} short_name=$(1)',
|
'articles/([a-z0-9-]+)/{delete,edit}/' => 'post_${1} short_name=$(1)',
|
||||||
'admin/markdown-preview.ajax' => 'ajax_md_preview',
|
'admin/markdown-preview.ajax' => 'ajax_md_preview',
|
||||||
'admin/{uploads,errors}/' => '${1}',
|
'admin/{uploads,errors}/' => '${1}',
|
||||||
|
'admin/{auth,actions}-log/' => '${1}_log',
|
||||||
'admin/uploads/{edit_note,delete}/(\d+)/' => 'upload_${1} id=$(1)'
|
'admin/uploads/{edit_note,delete}/(\d+)/' => 'upload_${1} id=$(1)'
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
|
139
skin/admin.phps
139
skin/admin.phps
@ -3,6 +3,7 @@
|
|||||||
namespace skin\admin;
|
namespace skin\admin;
|
||||||
|
|
||||||
use Stringable;
|
use Stringable;
|
||||||
|
use function skin\base\layout;
|
||||||
|
|
||||||
// login page
|
// login page
|
||||||
// ----------
|
// ----------
|
||||||
@ -53,6 +54,8 @@ function index($ctx, $admin_login) {
|
|||||||
<!--<a href="/admin/log/">Log</a><br/>-->
|
<!--<a href="/admin/log/">Log</a><br/>-->
|
||||||
<a href="/admin/uploads/">Uploads</a><br>
|
<a href="/admin/uploads/">Uploads</a><br>
|
||||||
<a href="/admin/errors/">{$ctx->lang('admin_errors')}</a><br>
|
<a href="/admin/errors/">{$ctx->lang('admin_errors')}</a><br>
|
||||||
|
<a href="/admin/auth-log/">{$ctx->lang('admin_auth_log')}</a><br>
|
||||||
|
<a href="/admin/actions-log/">{$ctx->lang('admin_actions_log')}</a><br>
|
||||||
</div>
|
</div>
|
||||||
HTML;
|
HTML;
|
||||||
}
|
}
|
||||||
@ -422,6 +425,12 @@ return <<<HTML
|
|||||||
HTML;
|
HTML;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ----------------------------------------------------
|
||||||
|
// ---------------------- ERRORS ----------------------
|
||||||
|
// ----------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
function errors($ctx,
|
function errors($ctx,
|
||||||
array $list,
|
array $list,
|
||||||
int $count,
|
int $count,
|
||||||
@ -545,3 +554,133 @@ return <<<HTML
|
|||||||
</div>
|
</div>
|
||||||
HTML;
|
HTML;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ------------------------------------------------------
|
||||||
|
// ---------------------- AUTH LOG ----------------------
|
||||||
|
// ------------------------------------------------------
|
||||||
|
|
||||||
|
function auth_log($ctx, array $list, int $pn_page, int $pn_pages) {
|
||||||
|
return <<<HTML
|
||||||
|
{$ctx->bc([
|
||||||
|
['text' => $ctx->lang('admin_title'), 'url' => '/admin/'],
|
||||||
|
['text' => $ctx->lang('admin_auth_log')],
|
||||||
|
])}
|
||||||
|
|
||||||
|
{$ctx->if_then_else(!empty($list),
|
||||||
|
fn() => $ctx->auth_log_table($list),
|
||||||
|
fn() => '<div class="empty_block">Auth log is empty.</div>')}
|
||||||
|
|
||||||
|
{$ctx->pagenav($pn_page, $pn_pages, '/admin/auth-log/?page={page}')}
|
||||||
|
HTML;
|
||||||
|
}
|
||||||
|
|
||||||
|
function auth_log_table($ctx, array $list) {
|
||||||
|
return <<<HTML
|
||||||
|
<table border="1" width="100%" cellpadding="0" cellspacing="0" class="admin-error-log">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th width="20%">Admin</th>
|
||||||
|
<th width="15%">Time</th>
|
||||||
|
<th width="10%">IP</th>
|
||||||
|
<th width="55%">User-Agent</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{$ctx->for_each($list,
|
||||||
|
fn($item) => $ctx->auth_log_table_item(
|
||||||
|
date: $item['date'],
|
||||||
|
ip: $item['ip'],
|
||||||
|
user_agent: $item['ua'],
|
||||||
|
admin_login: $item['login'],
|
||||||
|
admin_id: (int)$item['admin_id'],
|
||||||
|
activity_ts: $item['activity_ts_s']))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
HTML;
|
||||||
|
}
|
||||||
|
|
||||||
|
function auth_log_table_item($ctx, $date, $ip, $user_agent, string $admin_login, int $admin_id, string $activity_ts) {
|
||||||
|
return <<<HTML
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<span title="Last activity: {$activity_ts}">{$admin_login}</span> (id={$admin_id})
|
||||||
|
</td>
|
||||||
|
<td>{$date}</td>
|
||||||
|
<td>{$ip}</td>
|
||||||
|
<td>{$user_agent}</td>
|
||||||
|
</tr>
|
||||||
|
HTML;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ---------------------------------------------------------
|
||||||
|
// ---------------------- ACTIONS LOG ----------------------
|
||||||
|
// ---------------------------------------------------------
|
||||||
|
|
||||||
|
function actions_log($ctx,
|
||||||
|
array $list,
|
||||||
|
array $admin_logins,
|
||||||
|
string $url,
|
||||||
|
array $action_types,
|
||||||
|
int $pn_page,
|
||||||
|
int $pn_pages) {
|
||||||
|
return <<<HTML
|
||||||
|
{$ctx->bc([
|
||||||
|
['text' => $ctx->lang('admin_title'), 'url' => '/admin/'],
|
||||||
|
['text' => $ctx->lang('admin_actions_log')],
|
||||||
|
])}
|
||||||
|
|
||||||
|
{$ctx->if_then_else(!empty($list),
|
||||||
|
fn() => $ctx->actions_log_table($list, $admin_logins, $action_types),
|
||||||
|
fn() => '<div class="empty_block">Actions log is empty.</div>')}
|
||||||
|
|
||||||
|
{$ctx->pagenav($pn_page, $pn_pages, $url.'page={page}')}
|
||||||
|
HTML;
|
||||||
|
}
|
||||||
|
|
||||||
|
function actions_log_table($ctx, array $list, array $admin_logins, array $action_types) {
|
||||||
|
return <<<HTML
|
||||||
|
<table border="1" width="100%" cellpadding="0" cellspacing="0" class="admin-error-log">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th width="9%">Time</th>
|
||||||
|
<th width="14%">Who</th>
|
||||||
|
<th width="11%">Action</th>
|
||||||
|
<th>Data</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{$ctx->for_each($list,
|
||||||
|
fn(\AdminActions\BaseAction $item) => $ctx->actions_log_table_item(
|
||||||
|
date: $item->getDate(),
|
||||||
|
ip: $item->getIPv4(),
|
||||||
|
is_cli: $item->isCommandLineAction(),
|
||||||
|
admin_login: $admin_logins[$item->getAdminId()],
|
||||||
|
action_name: $item->getActionName(),
|
||||||
|
unsafe_data: $item->renderHtml()))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
HTML;
|
||||||
|
}
|
||||||
|
|
||||||
|
function actions_log_table_item($ctx,
|
||||||
|
string $date,
|
||||||
|
string $ip,
|
||||||
|
bool $is_cli,
|
||||||
|
string $admin_login,
|
||||||
|
string $action_name,
|
||||||
|
string $unsafe_data) {
|
||||||
|
return <<<HTML
|
||||||
|
<tr>
|
||||||
|
<td>{$date}</td>
|
||||||
|
<td>
|
||||||
|
{$ctx->if_then_else(!$is_cli,
|
||||||
|
fn() => $admin_login.', '.$ip,
|
||||||
|
fn() => 'console')}
|
||||||
|
</td>
|
||||||
|
<td>{$action_name}</td>
|
||||||
|
<td>{$unsafe_data}</td>
|
||||||
|
</tr>
|
||||||
|
HTML;
|
||||||
|
}
|
@ -97,6 +97,8 @@ admin_password: Password
|
|||||||
admin_login: Login
|
admin_login: Login
|
||||||
admin_books: Books
|
admin_books: Books
|
||||||
admin_errors: Errors
|
admin_errors: Errors
|
||||||
|
admin_auth_log: Auth log
|
||||||
|
admin_actions_log: Actions log
|
||||||
|
|
||||||
# /files
|
# /files
|
||||||
files: Files
|
files: Files
|
||||||
|
Loading…
x
Reference in New Issue
Block a user