Added counters table for storing numbers of things statically.
This commit is contained in:
parent
8ef113f3a9
commit
934b016541
12 changed files with 310 additions and 258 deletions
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
16
database/2023_07_28_212101_create_counters_table.php
Normal file
16
database/2023_07_28_212101_create_counters_table.php
Normal 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
|
||||||
|
');
|
||||||
|
}
|
||||||
|
}
|
|
@ -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,
|
||||||
]);
|
]);
|
||||||
|
|
33
src/Counters/CounterInfo.php
Normal file
33
src/Counters/CounterInfo.php
Normal 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
139
src/Counters/Counters.php
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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,
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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,
|
||||||
},
|
},
|
||||||
] %}
|
] %}
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
},
|
},
|
||||||
] %}
|
] %}
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
58
tools/cron
58
tools/cron
|
@ -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();
|
||||||
|
|
Loading…
Reference in a new issue