diff --git a/engine/router.php b/engine/router.php index c1ec2ef..2cede72 100644 --- a/engine/router.php +++ b/engine/router.php @@ -1,6 +1,6 @@ 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() { list($error) = input('error'); $uploads = uploads::getAllUploads(); diff --git a/lib/AdminActions/BaseAction.php b/lib/AdminActions/BaseAction.php index 99adbf0..1bacbfb 100644 --- a/lib/AdminActions/BaseAction.php +++ b/lib/AdminActions/BaseAction.php @@ -54,9 +54,9 @@ abstract class BaseAction { return $this->isCli; } - //public function getDate(): string { - // return formatTime($this->timeStamp, ['short_months' => true]); - //} + public function getDate(): string { + return format_time($this->timeStamp, ['short_months' => true]); + } public function getTimeStamp(): int { return $this->timeStamp; @@ -70,7 +70,7 @@ abstract class BaseAction { return $this->recordId; } - /*function renderHtml(): string { + function renderHtml(): string { $rc = new \ReflectionClass($this); $lines = []; $fields = $rc->getProperties(\ReflectionProperty::IS_PUBLIC); @@ -87,5 +87,5 @@ abstract class BaseAction { $lines[] = $name.'='.$val; } return implode('
', $lines); - }*/ + } } \ No newline at end of file diff --git a/lib/admin.php b/lib/admin.php index f2ce49d..55e0d26 100644 --- a/lib/admin.php +++ b/lib/admin.php @@ -54,6 +54,20 @@ function admin_delete(string $login): bool { 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 { $db = DB(); $q = $db->query("SELECT id FROM admins WHERE login=?", $login); diff --git a/routes.php b/routes.php index df86f83..cf4cb14 100644 --- a/routes.php +++ b/routes.php @@ -25,15 +25,16 @@ return (function() { 'robots.txt' => 'robots_txt', ], 'Admin' => [ - 'admin/' => 'index', - 'admin/{login,logout,log}/' => '${1}', - '([a-z0-9-]+)/{delete,edit}/' => 'page_${1} short_name=$(1)', - '([a-z0-9-]+)/create/' => 'page_add short_name=$(1)', - 'articles/write/' => 'post_add', - 'articles/([a-z0-9-]+)/{delete,edit}/' => 'post_${1} short_name=$(1)', - 'admin/markdown-preview.ajax' => 'ajax_md_preview', - 'admin/{uploads,errors}/' => '${1}', - 'admin/uploads/{edit_note,delete}/(\d+)/' => 'upload_${1} id=$(1)' + 'admin/' => 'index', + 'admin/{login,logout,log}/' => '${1}', + '([a-z0-9-]+)/{delete,edit}/' => 'page_${1} short_name=$(1)', + '([a-z0-9-]+)/create/' => 'page_add short_name=$(1)', + 'articles/write/' => 'post_add', + 'articles/([a-z0-9-]+)/{delete,edit}/' => 'post_${1} short_name=$(1)', + 'admin/markdown-preview.ajax' => 'ajax_md_preview', + 'admin/{uploads,errors}/' => '${1}', + 'admin/{auth,actions}-log/' => '${1}_log', + 'admin/uploads/{edit_note,delete}/(\d+)/' => 'upload_${1} id=$(1)' ] ]; diff --git a/skin/admin.phps b/skin/admin.phps index 8a8d811..bc80c3b 100644 --- a/skin/admin.phps +++ b/skin/admin.phps @@ -3,6 +3,7 @@ namespace skin\admin; use Stringable; +use function skin\base\layout; // login page // ---------- @@ -53,6 +54,8 @@ function index($ctx, $admin_login) { Uploads
{$ctx->lang('admin_errors')}
+ {$ctx->lang('admin_auth_log')}
+ {$ctx->lang('admin_actions_log')}
HTML; } @@ -422,6 +425,12 @@ return <<{$nl2br_stacktrace} HTML; +} + + +// ------------------------------------------------------ +// ---------------------- AUTH LOG ---------------------- +// ------------------------------------------------------ + +function auth_log($ctx, array $list, int $pn_page, int $pn_pages) { +return <<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() => '
Auth log is empty.
')} + +{$ctx->pagenav($pn_page, $pn_pages, '/admin/auth-log/?page={page}')} +HTML; +} + +function auth_log_table($ctx, array $list) { +return << + + + Admin + Time + IP + User-Agent + + + + {$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']))} + + +HTML; +} + +function auth_log_table_item($ctx, $date, $ip, $user_agent, string $admin_login, int $admin_id, string $activity_ts) { +return << + + {$admin_login} (id={$admin_id}) + + {$date} + {$ip} + {$user_agent} + +HTML; +} + + +// --------------------------------------------------------- +// ---------------------- ACTIONS LOG ---------------------- +// --------------------------------------------------------- + +function actions_log($ctx, + array $list, + array $admin_logins, + string $url, + array $action_types, + int $pn_page, + int $pn_pages) { + return <<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() => '
Actions log is empty.
')} + +{$ctx->pagenav($pn_page, $pn_pages, $url.'page={page}')} +HTML; +} + +function actions_log_table($ctx, array $list, array $admin_logins, array $action_types) { + return << + + + Time + Who + Action + Data + + + + {$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()))} + + +HTML; +} + +function actions_log_table_item($ctx, + string $date, + string $ip, + bool $is_cli, + string $admin_login, + string $action_name, + string $unsafe_data) { +return << + {$date} + + {$ctx->if_then_else(!$is_cli, + fn() => $admin_login.', '.$ip, + fn() => 'console')} + + {$action_name} + {$unsafe_data} + +HTML; } \ No newline at end of file diff --git a/strings/main.yaml b/strings/main.yaml index c8d453f..e2b78ea 100644 --- a/strings/main.yaml +++ b/strings/main.yaml @@ -97,6 +97,8 @@ admin_password: Password admin_login: Login admin_books: Books admin_errors: Errors +admin_auth_log: Auth log +admin_actions_log: Actions log # /files files: Files