Added counters table for storing numbers of things statically.

This commit is contained in:
flash 2023-07-28 23:17:33 +00:00
parent 8ef113f3a9
commit 934b016541
12 changed files with 310 additions and 258 deletions

View file

@ -9,6 +9,12 @@
} }
.manage__statistic__value { .manage__statistic__value {
text-align: right; text-align: right;
font-size: 1.5em; font-size: 1.4em;
line-height: 2em; line-height: 1.5em;
}
.manage__statistic__updated {
text-align: right;
font-size: .9em;
font-style: italic;
line-height: 1.5em;
} }

View file

@ -1,9 +1,14 @@
.manage__statistics { .manage__statistics {
display: grid; display: grid;
grid-template-columns: 1fr 1fr 1fr; grid-template-columns: 1fr 1fr 1fr 1fr;
padding: 5px; padding: 5px;
grid-gap: 5px; grid-gap: 5px;
} }
@media (max-width: 1100px) {
.manage__statistics {
grid-template-columns: 1fr 1fr 1fr;
}
}
@media (max-width: 900px) { @media (max-width: 900px) {
.manage__statistics { .manage__statistics {
grid-template-columns: 1fr 1fr; grid-template-columns: 1fr 1fr;

View file

@ -0,0 +1,16 @@
<?php
use Index\Data\IDbConnection;
use Index\Data\Migration\IDbMigration;
final class CreateCountersTable_20230728_212101 implements IDbMigration {
public function migrate(IDbConnection $conn): void {
$conn->execute('
CREATE TABLE msz_counters (
counter_name VARBINARY(64) NOT NULL,
counter_value BIGINT(20) NOT NULL DEFAULT "0",
counter_updated TIMESTAMP NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
PRIMARY KEY (counter_name)
) ENGINE=InnoDB COLLATE=utf8mb4_bin
');
}
}

View file

@ -1,183 +1,15 @@
<?php <?php
namespace Misuzu; namespace Misuzu;
$statistics = DB::query(' $counterInfos = $msz->getCounters()->getCounters(orderBy: 'name');
SELECT $counterNamesRaw = $msz->getConfig()->getArray('counters.names');
( $counterNamesCount = count($counterNamesRaw);
SELECT COUNT(*) $counterNames = [];
FROM `msz_users`
) AS `stat_users_total`, for($i = -1; $i < $counterNamesCount;)
( $counterNames[$counterNamesRaw[++$i] ?? ''] = $counterNamesRaw[++$i] ?? '';
SELECT COUNT(*)
FROM `msz_users`
WHERE `user_deleted` IS NOT NULL
) AS `stat_users_deleted`,
(
SELECT COUNT(*)
FROM `msz_users`
WHERE `user_active` IS NOT NULL
AND `user_deleted` IS NULL
) AS `stat_users_active`,
(
SELECT COUNT(*)
FROM `msz_audit_log`
) AS `stat_audit_logs`,
(
SELECT COUNT(*)
FROM `msz_changelog_changes`
) AS `stat_changelog_entries`,
(
SELECT COUNT(*)
FROM `msz_comments_categories`
) AS `stat_comment_categories_total`,
(
SELECT COUNT(*)
FROM `msz_comments_categories`
WHERE `category_locked` IS NOT NULL
) AS `stat_comment_categories_locked`,
(
SELECT COUNT(*)
FROM `msz_comments_posts`
) AS `stat_comment_posts_total`,
(
SELECT COUNT(*)
FROM `msz_comments_posts`
WHERE `comment_deleted` IS NOT NULL
) AS `stat_comment_posts_deleted`,
(
SELECT COUNT(*)
FROM `msz_comments_posts`
WHERE `comment_reply_to` IS NOT NULL
) AS `stat_comment_posts_replies`,
(
SELECT COUNT(*)
FROM `msz_comments_posts`
WHERE `comment_pinned` IS NOT NULL
) AS `stat_comment_posts_pinned`,
(
SELECT COUNT(*)
FROM `msz_comments_posts`
WHERE `comment_edited` IS NOT NULL
) AS `stat_comment_posts_edited`,
(
SELECT COUNT(*)
FROM `msz_comments_votes`
WHERE `comment_vote` > 0
) AS `stat_comment_likes`,
(
SELECT COUNT(*)
FROM `msz_comments_votes`
WHERE `comment_vote` < 0
) AS `stat_comment_dislikes`,
(
SELECT COUNT(*)
FROM `msz_forum_posts`
) AS `stat_forum_posts_total`,
(
SELECT COUNT(*)
FROM `msz_forum_posts`
WHERE `post_deleted` IS NOT NULL
) AS `stat_forum_posts_deleted`,
(
SELECT COUNT(*)
FROM `msz_forum_posts`
WHERE `post_edited` IS NOT NULL
) AS `stat_forum_posts_edited`,
(
SELECT COUNT(*)
FROM `msz_forum_posts`
WHERE `post_parse` = 0
) AS `stat_forum_posts_plain`,
(
SELECT COUNT(*)
FROM `msz_forum_posts`
WHERE `post_parse` = 1
) AS `stat_forum_posts_bbcode`,
(
SELECT COUNT(*)
FROM `msz_forum_posts`
WHERE `post_parse` = 2
) AS `stat_forum_posts_markdown`,
(
SELECT COUNT(*)
FROM `msz_forum_posts`
WHERE `post_display_signature` != 0
) AS `stat_forum_posts_signature`,
(
SELECT COUNT(*)
FROM `msz_forum_topics`
) AS `stat_forum_topics_total`,
(
SELECT COUNT(*)
FROM `msz_forum_topics`
WHERE `topic_type` = 0
) AS `stat_forum_topics_normal`,
(
SELECT COUNT(*)
FROM `msz_forum_topics`
WHERE `topic_type` = 1
) AS `stat_forum_topics_pinned`,
(
SELECT COUNT(*)
FROM `msz_forum_topics`
WHERE `topic_type` = 2
) AS `stat_forum_topics_announce`,
(
SELECT COUNT(*)
FROM `msz_forum_topics`
WHERE `topic_type` = 3
) AS `stat_forum_topics_global_announce`,
(
SELECT COUNT(*)
FROM `msz_forum_topics`
WHERE `topic_deleted` IS NOT NULL
) AS `stat_forum_topics_deleted`,
(
SELECT COUNT(*)
FROM `msz_forum_topics`
WHERE `topic_locked` IS NOT NULL
) AS `stat_forum_topics_locked`,
(
SELECT COUNT(*)
FROM `msz_login_attempts`
) AS `stat_login_attempts_total`,
(
SELECT COUNT(*)
FROM `msz_login_attempts`
WHERE `attempt_success` = 0
) AS `stat_login_attempts_failed`,
(
SELECT COUNT(*)
FROM `msz_sessions`
) AS `stat_user_sessions`,
(
SELECT COUNT(*)
FROM `msz_users_password_resets`
) AS `stat_user_password_resets`,
(
SELECT COUNT(*)
FROM `msz_users_modnotes`
) AS `stat_user_modnotes`,
(
SELECT COUNT(*)
FROM `msz_users_warnings`
) AS `stat_user_warnings_total`,
(
SELECT COUNT(*)
FROM `msz_users_warnings`
WHERE warn_created > NOW() - INTERVAL 90 DAY
) AS `stat_user_warnings_visible`,
(
SELECT COUNT(*)
FROM `msz_users_bans`
) AS `stat_user_bans_total`,
(
SELECT COUNT(*)
FROM `msz_users_bans`
WHERE ban_expires IS NULL OR ban_expires > NOW()
) AS `stat_user_bans_active`
')->fetch();
Template::render('manage.general.overview', [ Template::render('manage.general.overview', [
'statistics' => $statistics, 'counter_infos' => $counterInfos,
'counter_names' => $counterNames,
]); ]);

View file

@ -0,0 +1,33 @@
<?php
namespace Misuzu\Counters;
use Index\DateTime;
use Index\Data\IDbResult;
class CounterInfo {
private string $name;
private int $value;
private int $updated;
public function __construct(IDbResult $result) {
$this->name = $result->getString(0);
$this->value = $result->getInteger(1);
$this->updated = $result->getInteger(2);
}
public function getName(): string {
return $this->name;
}
public function getValue(): int {
return $this->value;
}
public function getUpdatedTime(): int {
return $this->updated;
}
public function getUpdatedAt(): DateTime {
return DateTime::fromUnixTimeSeconds($this->updated);
}
}

139
src/Counters/Counters.php Normal file
View file

@ -0,0 +1,139 @@
<?php
namespace Misuzu\Counters;
use InvalidArgumentException;
use Index\Data\DbStatementCache;
use Index\Data\DbTools;
use Index\Data\IDbConnection;
use Misuzu\Pagination;
// insert increment and decrement calls in places someday
class Counters {
private DbStatementCache $cache;
public function __construct(IDbConnection $dbConn) {
$this->cache = new DbStatementCache($dbConn);
}
private const GET_COUNTERS_SORT = [
'name' => 'counter_name',
'value' => 'counter_value',
'updated' => 'counter_updated',
];
public function getCounters(
?string $orderBy = null,
?Pagination $pagination = null
): array {
$hasOrderBy = $orderBy !== null;
$hasPagination = $pagination !== null;
$query = 'SELECT counter_name, counter_value, UNIX_TIMESTAMP(counter_updated) FROM msz_counters';
if($hasOrderBy) {
if(!array_key_exists($orderBy, self::GET_COUNTERS_SORT))
throw new InvalidArgumentException('Invalid sort specified.');
$query .= ' ORDER BY ' . self::GET_COUNTERS_SORT[$orderBy];
}
if($hasPagination)
$query .= ' LIMIT ? OFFSET ?';
$args = 0;
$stmt = $this->cache->get($query);
if($hasPagination) {
$stmt->addParameter(++$args, $pagination->getRange());
$stmt->addParameter(++$args, $pagination->getOffset());
}
$stmt->execute();
$result = $stmt->getResult();
$counters = [];
while($result->next())
$counters[] = new CounterInfo($result);
return $counters;
}
public function get(array|string $names): array|int {
if(is_string($names)) {
$returnFirst = true;
$names = [$names];
} else $returnFirst = false;
$args = 0;
$stmt = $this->cache->get(sprintf(
'SELECT counter_name, counter_value FROM msz_counters WHERE counter_name IN (%s)',
DbTools::prepareListString($names)
));
foreach($names as $name)
$stmt->addParameter(++$args, (string)$name);
$stmt->execute();
$values = [];
$result = $stmt->getResult();
while($result->next())
$values[$result->getString(0)] = $result->getInteger(1);
return $returnFirst ? $values[array_key_first($values)] : $values;
}
public function set(string|array $nameOrValues, ?int $value = null): void {
if(empty($nameOrValues))
throw new InvalidArgumentException('$nameOrValues may not be empty.');
if(is_string($nameOrValues)) {
if($value === null)
throw new InvalidArgumentException('$value may not be null.');
$values = [$nameOrValues => $value];
} else $values = $nameOrValues;
$args = 0;
$stmt = $this->cache->get(sprintf(
'REPLACE INTO msz_counters (counter_name, counter_value) VALUES %s',
DbTools::prepareListString($values, '(?, ?)')
));
foreach($values as $name => $value) {
$stmt->addParameter(++$args, (string)$name);
$stmt->addParameter(++$args, (int)$value);
}
$stmt->execute();
}
public function reset(string|array $names): void {
if(empty($names))
throw new InvalidArgumentException('$names may not be empty.');
if(is_string($names))
$names = [$names];
$args = 0;
$stmt = $this->cache->get(sprintf(
'DELETE FROM msz_counters WHERE counter_name IN (%s)',
DbTools::prepareListString($names)
));
foreach($names as $name)
$stmt->addParameter(++$args, (string)$name);
$stmt->execute();
}
public function increment(string $name, int $step = 1): void {
$stmt = $this->cache->get('INSERT INTO msz_counters (counter_name, counter_value) VALUES (?, ?) ON DUPLICATE KEY UPDATE counter_value = counter_value + ?');
$stmt->addParameter(1, $name);
$stmt->addParameter(2, $step);
$stmt->addParameter(3, $step);
$stmt->execute();
}
public function decrement(string $name, int $step = 1): void {
$stmt = $this->cache->get('INSERT INTO msz_counters (counter_name, counter_value) VALUES (?, ?) ON DUPLICATE KEY UPDATE counter_value = counter_value - ?');
$stmt->addParameter(1, $name);
$stmt->addParameter(2, -$step);
$stmt->addParameter(3, $step);
$stmt->execute();
}
}

View file

@ -9,6 +9,15 @@ use Misuzu\Comments\CommentsCategory;
use Misuzu\Users\User; use Misuzu\Users\User;
final class HomeHandler extends Handler { final class HomeHandler extends Handler {
private const STATS = [
'users:active',
'users:online:recent',
'users:online:today',
'comments:posts:visible',
'forum:topics:visible',
'forum:posts:visible',
];
public function index($response, $request): void { public function index($response, $request): void {
if(User::hasCurrent()) if(User::hasCurrent())
$this->home($response, $request); $this->home($response, $request);
@ -18,6 +27,7 @@ final class HomeHandler extends Handler {
public function landing($response, $request): void { public function landing($response, $request): void {
$config = $this->context->getConfig(); $config = $this->context->getConfig();
$counters = $this->context->getCounters();
if($config->getBoolean('social.embed_linked')) { if($config->getBoolean('social.embed_linked')) {
$ldr = $config->getValues([ $ldr = $config->getValues([
@ -39,16 +49,7 @@ final class HomeHandler extends Handler {
pagination: new Pagination(3) pagination: new Pagination(3)
); );
$stats = DB::query( $stats = $counters->get(self::STATS);
'SELECT'
. ' (SELECT COUNT(`user_id`) FROM `msz_users` WHERE `user_deleted` IS NULL) AS `count_users_all`,'
. ' (SELECT COUNT(`user_id`) FROM `msz_users` WHERE `user_active` >= DATE_SUB(NOW(), INTERVAL 5 MINUTE)) AS `count_users_online`,'
. ' (SELECT COUNT(`user_id`) FROM `msz_users` WHERE `user_active` >= DATE_SUB(NOW(), INTERVAL 24 HOUR)) AS `count_users_active`,'
. ' (SELECT COUNT(`comment_id`) FROM `msz_comments_posts` WHERE `comment_deleted` IS NULL) AS `count_comments`,'
. ' (SELECT COUNT(`topic_id`) FROM `msz_forum_topics` WHERE `topic_deleted` IS NULL) AS `count_forum_topics`,'
. ' (SELECT COUNT(`post_id`) FROM `msz_forum_posts` WHERE `post_deleted` IS NULL) AS `count_forum_posts`'
)->fetch();
$onlineUsers = DB::query( $onlineUsers = DB::query(
'SELECT u.`user_id`, u.`username`, COALESCE(u.`user_colour`, r.`role_colour`) AS `user_colour`' 'SELECT u.`user_id`, u.`username`, COALESCE(u.`user_colour`, r.`role_colour`) AS `user_colour`'
. ' FROM `msz_users` AS u' . ' FROM `msz_users` AS u'
@ -56,7 +57,7 @@ final class HomeHandler extends Handler {
. ' ON r.`role_id` = u.`display_role`' . ' ON r.`role_id` = u.`display_role`'
. ' WHERE u.`user_active` >= DATE_SUB(NOW(), INTERVAL 5 MINUTE)' . ' WHERE u.`user_active` >= DATE_SUB(NOW(), INTERVAL 5 MINUTE)'
. ' ORDER BY u.`user_active` DESC, RAND()' . ' ORDER BY u.`user_active` DESC, RAND()'
. ' LIMIT 100' . ' LIMIT 50'
)->fetchAll(); )->fetchAll();
// TODO: don't hardcode forum ids // TODO: don't hardcode forum ids
@ -113,6 +114,7 @@ final class HomeHandler extends Handler {
public function home($response, $request): void { public function home($response, $request): void {
$news = $this->context->getNews(); $news = $this->context->getNews();
$comments = $this->context->getComments(); $comments = $this->context->getComments();
$counters = $this->context->getCounters();
$featuredNews = []; $featuredNews = [];
$userInfos = []; $userInfos = [];
$categoryInfos = []; $categoryInfos = [];
@ -153,16 +155,7 @@ final class HomeHandler extends Handler {
]; ];
} }
$stats = DB::query( $stats = $counters->get(self::STATS);
'SELECT'
. ' (SELECT COUNT(`user_id`) FROM `msz_users` WHERE `user_deleted` IS NULL) AS `count_users_all`,'
. ' (SELECT COUNT(`user_id`) FROM `msz_users` WHERE `user_active` >= DATE_SUB(NOW(), INTERVAL 5 MINUTE)) AS `count_users_online`,'
. ' (SELECT COUNT(`user_id`) FROM `msz_users` WHERE `user_active` >= DATE_SUB(NOW(), INTERVAL 24 HOUR)) AS `count_users_active`,'
. ' (SELECT COUNT(`comment_id`) FROM `msz_comments_posts` WHERE `comment_deleted` IS NULL) AS `count_comments`,'
. ' (SELECT COUNT(`topic_id`) FROM `msz_forum_topics` WHERE `topic_deleted` IS NULL) AS `count_forum_topics`,'
. ' (SELECT COUNT(`post_id`) FROM `msz_forum_posts` WHERE `post_deleted` IS NULL) AS `count_forum_posts`'
)->fetch();
$changelog = $this->context->getChangelog()->getAllChanges(pagination: new Pagination(10)); $changelog = $this->context->getChangelog()->getAllChanges(pagination: new Pagination(10));
$birthdays = User::byBirthdate(); $birthdays = User::byBirthdate();
@ -175,9 +168,11 @@ final class HomeHandler extends Handler {
. ' ON r.`role_id` = u.`display_role`' . ' ON r.`role_id` = u.`display_role`'
. ' WHERE u.`user_active` >= DATE_SUB(NOW(), INTERVAL 5 MINUTE)' . ' WHERE u.`user_active` >= DATE_SUB(NOW(), INTERVAL 5 MINUTE)'
. ' ORDER BY u.`user_active` DESC, RAND()' . ' ORDER BY u.`user_active` DESC, RAND()'
. ' LIMIT 104'
)->fetchAll(); )->fetchAll();
// today we cheat
$stats['users:online:recent'] = count($onlineUsers);
$response->setContent(Template::renderRaw('home.home', [ $response->setContent(Template::renderRaw('home.home', [
'statistics' => $stats, 'statistics' => $stats,
'latest_user' => $latestUser, 'latest_user' => $latestUser,

View file

@ -10,6 +10,7 @@ use Misuzu\AuditLog\AuditLog;
use Misuzu\Changelog\Changelog; use Misuzu\Changelog\Changelog;
use Misuzu\Comments\Comments; use Misuzu\Comments\Comments;
use Misuzu\Config\IConfig; use Misuzu\Config\IConfig;
use Misuzu\Counters\Counters;
use Misuzu\Emoticons\Emotes; use Misuzu\Emoticons\Emotes;
use Misuzu\News\News; use Misuzu\News\News;
use Misuzu\SharpChat\SharpChatRoutes; use Misuzu\SharpChat\SharpChatRoutes;
@ -52,6 +53,7 @@ class MisuzuContext {
private Roles $roles; private Roles $roles;
private Users $users; private Users $users;
private Sessions $sessions; private Sessions $sessions;
private Counters $counters;
public function __construct(IDbConnection $dbConn, IConfig $config) { public function __construct(IDbConnection $dbConn, IConfig $config) {
$this->dbConn = $dbConn; $this->dbConn = $dbConn;
@ -70,6 +72,7 @@ class MisuzuContext {
$this->roles = new Roles($this->dbConn); $this->roles = new Roles($this->dbConn);
$this->users = new Users($this->dbConn); $this->users = new Users($this->dbConn);
$this->sessions = new Sessions($this->dbConn); $this->sessions = new Sessions($this->dbConn);
$this->counters = new Counters($this->dbConn);
} }
public function getDbConn(): IDbConnection { public function getDbConn(): IDbConnection {
@ -153,6 +156,10 @@ class MisuzuContext {
return $this->sessions; return $this->sessions;
} }
public function getCounters(): Counters {
return $this->counters;
}
private array $activeBansCache = []; private array $activeBansCache = [];
public function tryGetActiveBan(User|string|null $userInfo = null): ?BanInfo { public function tryGetActiveBan(User|string|null $userInfo = null): ?BanInfo {

View file

@ -9,32 +9,32 @@
{ {
icon: 'fas fa-users fa-fw', icon: 'fas fa-users fa-fw',
name: 'Members', name: 'Members',
value: statistics.count_users_all|number_format, value: statistics['users:active']|number_format,
}, },
{ {
icon: 'fas fa-comment-dots fa-fw', icon: 'fas fa-comment-dots fa-fw',
name: 'Comments', name: 'Comments',
value: statistics.count_comments|number_format, value: statistics['comments:posts:visible']|number_format,
}, },
{ {
icon: 'fas fa-user-check fa-fw', icon: 'fas fa-user-check fa-fw',
name: 'Online', name: 'Online',
value: statistics.count_users_online|number_format, value: statistics['users:online:recent']|number_format,
}, },
{ {
icon: 'fas fa-user-clock fa-fw', icon: 'fas fa-user-clock fa-fw',
name: 'Active (24 hr)', name: 'Active (24 hr)',
value: statistics.count_users_active|number_format, value: statistics['users:online:today']|number_format,
}, },
{ {
icon: 'fas fa-list fa-fw', icon: 'fas fa-list fa-fw',
name: 'Topics', name: 'Topics',
value: statistics.count_forum_topics|number_format, value: statistics['forum:topics:visible']|number_format,
}, },
{ {
icon: 'fas fa-comments fa-fw', icon: 'fas fa-comments fa-fw',
name: 'Posts', name: 'Posts',
value: statistics.count_forum_posts|number_format, value: statistics['forum:posts:visible']|number_format,
}, },
] %} ] %}

View file

@ -7,32 +7,32 @@
{ {
icon: 'fas fa-users fa-fw', icon: 'fas fa-users fa-fw',
name: 'members', name: 'members',
value: statistics.count_users_all|number_format, value: statistics['users:active']|number_format,
}, },
{ {
icon: 'fas fa-user-check fa-fw', icon: 'fas fa-user-check fa-fw',
name: 'online', name: 'online',
value: statistics.count_users_online|number_format, value: statistics['users:online:recent']|number_format,
}, },
{ {
icon: 'fas fa-user-clock fa-fw', icon: 'fas fa-user-clock fa-fw',
name: 'active (24 hr)', name: 'active (24 hr)',
value: statistics.count_users_active|number_format, value: statistics['users:online:today']|number_format,
}, },
{ {
icon: 'fas fa-list fa-fw', icon: 'fas fa-list fa-fw',
name: 'topics', name: 'topics',
value: statistics.count_forum_topics|number_format, value: statistics['forum:topics:visible']|number_format,
}, },
{ {
icon: 'fas fa-comments fa-fw', icon: 'fas fa-comments fa-fw',
name: 'posts', name: 'posts',
value: statistics.count_forum_posts|number_format, value: statistics['forum:posts:visible']|number_format,
}, },
{ {
icon: 'fas fa-comment-dots fa-fw', icon: 'fas fa-comment-dots fa-fw',
name: 'comments', name: 'comments',
value: statistics.count_comments|number_format, value: statistics['comments:posts:visible']|number_format,
}, },
] %} ] %}

View file

@ -1,46 +1,6 @@
{% extends 'manage/general/master.twig' %} {% extends 'manage/general/master.twig' %}
{% from 'macros.twig' import container_title %} {% from 'macros.twig' import container_title %}
{% set stat_names = {
'stat_users_total': 'Total Users',
'stat_users_deleted': 'Deleted Users',
'stat_users_active': 'Active Users',
'stat_audit_logs': 'Logged Actions',
'stat_changelog_entries': 'Changelogs',
'stat_comment_categories_total': 'Comment Sections',
'stat_comment_categories_locked': 'Locked Comment Sections',
'stat_comment_posts_total': 'Total Comments',
'stat_comment_posts_deleted': 'Deleted Comments',
'stat_comment_posts_replies': 'Comment Replies',
'stat_comment_posts_pinned': 'Pinned Comments',
'stat_comment_posts_edited': 'Edited Comments',
'stat_comment_likes': 'Comments Like Votes',
'stat_comment_dislikes': 'Comments Dislike Votes',
'stat_forum_posts_total': 'Total Forum Posts',
'stat_forum_posts_deleted': 'Deleted Forum Posts',
'stat_forum_posts_edited': 'Edited Forum Posts',
'stat_forum_posts_plain': 'Forum Posts using Plain Text',
'stat_forum_posts_bbcode': 'Forum Posts using BBCode',
'stat_forum_posts_markdown': 'Forum Posts using Markdown',
'stat_forum_posts_signature': 'Forum Posts with Visible Signature',
'stat_forum_topics_total': 'Total Forum Topics',
'stat_forum_topics_normal': 'Regular Forum Topics',
'stat_forum_topics_pinned': 'Pinned Forum Topics',
'stat_forum_topics_announce': 'Announcement Forum Topics',
'stat_forum_topics_global_announce': 'Global Announcement Forum Topics',
'stat_forum_topics_deleted': 'Deleted Forum Topics',
'stat_forum_topics_locked': 'Locked Forum Topics',
'stat_login_attempts_total': 'Total Login Attempts',
'stat_login_attempts_failed': 'Failed Login Attempts',
'stat_user_sessions': 'Active User Sessions',
'stat_user_password_resets': 'Pending Password Resets',
'stat_user_modnotes': 'Moderator Notes',
'stat_user_warnings_total': 'Total User Warnings',
'stat_user_warnings_visible': 'Visible User Warnings',
'stat_user_bans_total': 'Total User Bans',
'stat_user_bans_active': 'Active User Bans',
} %}
{% block manage_content %} {% block manage_content %}
<div class="container container--lazy"> <div class="container container--lazy">
{{ container_title('Overview') }} {{ container_title('Overview') }}
@ -54,10 +14,11 @@
{{ container_title('Statistics') }} {{ container_title('Statistics') }}
<div class="manage__statistics"> <div class="manage__statistics">
{% for name, value in statistics %} {% for counter in counter_infos %}
<div class="manage__statistic" id="{{ name }}"> <div class="manage__statistic">
<div class="manage__statistic__name">{{ stat_names[name]|default(name) }}</div> <div class="manage__statistic__name">{{ counter_names[counter.name]|default(counter.name) }}</div>
<div class="manage__statistic__value">{{ value|number_format }}</div> <div class="manage__statistic__value">{{ counter.value|number_format }}</div>
<div class="manage__statistic__updated">as of <time datetime="{{ counter.updatedTime|date('c') }}" title="{{ counter.updatedTime|date('r') }}">{{ counter.updatedTime|time_format }}</time></div>
</div> </div>
{% endfor %} {% endfor %}
</div> </div>

View file

@ -80,6 +80,64 @@ msz_sched_task_func('Recount forum topics and posts.', true,
msz_sched_task_sql('Clean up expired 2fa tokens.', false, msz_sched_task_sql('Clean up expired 2fa tokens.', false,
'DELETE FROM msz_auth_tfa WHERE tfa_created < NOW() - INTERVAL 15 MINUTE'); 'DELETE FROM msz_auth_tfa WHERE tfa_created < NOW() - INTERVAL 15 MINUTE');
// make sure this one remains last
msz_sched_task_func('Resync statistics counters.', true, function() use ($msz) {
$dbConn = $msz->getDbConn();
$counters = $msz->getCounters();
$stats = [
'users:total' => 'SELECT COUNT(*) FROM msz_users',
'users:active' => 'SELECT COUNT(*) FROM msz_users WHERE user_active IS NOT NULL AND user_deleted IS NULL',
'users:inactive' => 'SELECT COUNT(*) FROM msz_users WHERE user_active IS NULL OR user_deleted IS NOT NULL',
'users:online:recent' => 'SELECT COUNT(*) FROM msz_users WHERE user_active >= NOW() - INTERVAL 1 HOUR', // used to be 5 minutes, but this script runs every hour soooo
'users:online:today' => 'SELECT COUNT(*) FROM msz_users WHERE user_active >= NOW() - INTERVAL 24 HOUR',
'auditlogs:total' => 'SELECT COUNT(*) FROM msz_audit_log',
'changelog:changes:total' => 'SELECT COUNT(*) FROM msz_changelog_changes',
'changelog:tags:total' => 'SELECT COUNT(*) FROM msz_changelog_tags',
'comments:cats:total' => 'SELECT COUNT(*) FROM msz_comments_categories',
'comments:cats:locked' => 'SELECT COUNT(*) FROM msz_comments_categories WHERE category_locked IS NOT NULL',
'comments:posts:total' => 'SELECT COUNT(*) FROM msz_comments_posts',
'comments:posts:visible' => 'SELECT COUNT(*) FROM msz_comments_posts WHERE comment_deleted IS NULL',
'comments:posts:deleted' => 'SELECT COUNT(*) FROM msz_comments_posts WHERE comment_deleted IS NOT NULL',
'comments:posts:replies' => 'SELECT COUNT(*) FROM msz_comments_posts WHERE comment_reply_to IS NOT NULL',
'comments:posts:pinned' => 'SELECT COUNT(*) FROM msz_comments_posts WHERE comment_pinned IS NOT NULL',
'comments:posts:edited' => 'SELECT COUNT(*) FROM msz_comments_posts WHERE comment_edited IS NOT NULL',
'comments:votes:likes' => 'SELECT COUNT(*) FROM msz_comments_votes WHERE comment_vote > 0',
'comments:votes:dislikes' => 'SELECT COUNT(*) FROM msz_comments_votes WHERE comment_vote < 0',
'forum:posts:total' => 'SELECT COUNT(*) FROM msz_forum_posts',
'forum:posts:visible' => 'SELECT COUNT(*) FROM msz_forum_posts WHERE post_deleted IS NULL',
'forum:posts:deleted' => 'SELECT COUNT(*) FROM msz_forum_posts WHERE post_deleted IS NOT NULL',
'forum:posts:edited' => 'SELECT COUNT(*) FROM msz_forum_posts WHERE post_edited IS NOT NULL',
'forum:posts:parse:plain' => 'SELECT COUNT(*) FROM msz_forum_posts WHERE post_parse = 0',
'forum:posts:parse:bbcode' => 'SELECT COUNT(*) FROM msz_forum_posts WHERE post_parse = 1',
'forum:posts:parse:markdown' => 'SELECT COUNT(*) FROM msz_forum_posts WHERE post_parse = 2',
'forum:posts:signature' => 'SELECT COUNT(*) FROM msz_forum_posts WHERE post_display_signature <> 0',
'forum:topics:total' => 'SELECT COUNT(*) FROM msz_forum_topics',
'forum:topics:type:normal' => 'SELECT COUNT(*) FROM msz_forum_topics WHERE topic_type = 0',
'forum:topics:type:pinned' => 'SELECT COUNT(*) FROM msz_forum_topics WHERE topic_type = 1',
'forum:topics:type:announce' => 'SELECT COUNT(*) FROM msz_forum_topics WHERE topic_type = 2',
'forum:topics:type:global' => 'SELECT COUNT(*) FROM msz_forum_topics WHERE topic_type = 3',
'forum:topics:visible' => 'SELECT COUNT(*) FROM msz_forum_topics WHERE topic_deleted IS NULL',
'forum:topics:deleted' => 'SELECT COUNT(*) FROM msz_forum_topics WHERE topic_deleted IS NOT NULL',
'forum:topics:locked' => 'SELECT COUNT(*) FROM msz_forum_topics WHERE topic_locked IS NOT NULL',
'auth:attempts:total' => 'SELECT COUNT(*) FROM msz_login_attempts',
'auth:attempts:failed' => 'SELECT COUNT(*) FROM msz_login_attempts WHERE attempt_success = 0',
'auth:sessions:total' => 'SELECT COUNT(*) FROM msz_sessions',
'auth:tfasessions:total' => 'SELECT COUNT(*) FROM msz_auth_tfa',
'auth:recovery:total' => 'SELECT COUNT(*) FROM msz_users_password_resets',
'users:modnotes:total' => 'SELECT COUNT(*) FROM msz_users_modnotes',
'users:warnings:total' => 'SELECT COUNT(*) FROM msz_users_warnings',
'users:warnings:visible' => 'SELECT COUNT(*) FROM msz_users_warnings WHERE warn_created > NOW() - INTERVAL 90 DAY',
'users:bans:total' => 'SELECT COUNT(*) FROM msz_users_bans',
'users:bans:active' => 'SELECT COUNT(*) FROM msz_users_bans WHERE ban_expires IS NULL OR ban_expires > NOW()',
];
foreach($stats as $name => $query) {
$result = $dbConn->query($query);
$counters->set($name, $result->next() ? $result->getInteger(0) : 0);
}
});
echo 'Running ' . count($schedTasks) . ' tasks...' . PHP_EOL; echo 'Running ' . count($schedTasks) . ' tasks...' . PHP_EOL;
$dbConn = $msz->getDbConn(); $dbConn = $msz->getDbConn();