From 6403b2fa8c75e3e6752fda0e7fb09c0c33d84ed6 Mon Sep 17 00:00:00 2001 From: ghost Date: Wed, 24 Jan 2024 04:39:47 +0200 Subject: [PATCH] implement players online monthly chart --- .env | 8 +- README.md | 4 +- composer.json | 2 + config/services.yaml | 4 + public/css/default.css | 106 +++++++++++++++++++++++ src/Controller/MainController.php | 111 ++++++++++++++++++++++++- src/Repository/OnlineRepository.php | 29 +++++-- templates/default/main/index.html.twig | 31 +++++++ 8 files changed, 285 insertions(+), 10 deletions(-) diff --git a/.env b/.env index 22b6b03..84493ca 100644 --- a/.env +++ b/.env @@ -43,7 +43,13 @@ MESSENGER_TRANSPORT_DSN=doctrine://default?auto_setup=0 # HLState # Application version, used for API and media cache -APP_VERSION="1.3.0" +APP_VERSION="1.4.0" + +# Memcached server +APP_MEMCACHED_NAMESPACE="HLState" +APP_MEMCACHED_HOST="localhost" +APP_MEMCACHED_PORT=11211 +APP_MEMCACHED_TIMEOUT=3600 # Application name APP_NAME="HLState" diff --git a/README.md b/README.md index e95ebcb..cd88c2b 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Project initially written to explore [Yggdrasil](https://github.com/yggdrasil-ne ## Install -* `apt install git composer curl php php-xml php-intl php-mbstring php-curl php-sqlite3` +* `apt install git composer curl memcached php php-xml php-intl php-mbstring php-curl php-sqlite3 php-memcached` * `git clone https://github.com/YGGverse/HLState.git` * `cd HLState` * `composer install` @@ -62,6 +62,8 @@ Please create new branch from main before make PR * [SVG icons](https://icons.getbootstrap.com) * [PHP Source Query](https://github.com/xPaw/PHP-Source-Query) * [HL-PHP](https://github.com/YGGverse/hl-php) +* [JS-less Graphs PHP](https://github.com/YGGverse/graph-php) +* [Memcached API for PHP](https://github.com/YGGverse/cache-php) * [Favicons](https://realfavicongenerator.net) ## Support diff --git a/composer.json b/composer.json index cbb0f08..bf4ccc5 100644 --- a/composer.json +++ b/composer.json @@ -45,6 +45,8 @@ "twig/intl-extra": "^3.8", "twig/twig": "^3.8", "xpaw/php-source-query-class": "dev-master", + "yggverse/cache": "^0.3.1", + "yggverse/graph": "^0.2.2", "yggverse/hl": "^1.0" }, "config": { diff --git a/config/services.yaml b/config/services.yaml index 60e96c2..89f8b5c 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -4,6 +4,10 @@ # Put parameters here that don't need to change on each machine where the app is deployed # https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration parameters: + app.memcached.host: '%env(APP_MEMCACHED_HOST)%' + app.memcached.port: '%env(APP_MEMCACHED_PORT)%' + app.memcached.namespace: '%env(APP_MEMCACHED_NAMESPACE)%' + app.memcached.timeout: '%env(APP_MEMCACHED_TIMEOUT)%' app.version: '%env(APP_VERSION)%' app.name: '%env(APP_NAME)%' app.theme: '%env(APP_THEME)%' diff --git a/public/css/default.css b/public/css/default.css index 8b0e4e6..7fdc209 100644 --- a/public/css/default.css +++ b/public/css/default.css @@ -16,6 +16,8 @@ --color-warning: #f37b21; --color-error: #ff6363; --color-default: #999; + + --background-color-hover-default: rgba(125, 125, 125, 0.1); } *::placeholder @@ -65,6 +67,10 @@ table td padding: 4px; } +table tr:hover td { + background-color: var(--background-color-hover-default); +} + ul { margin-left: 16px; @@ -143,6 +149,26 @@ a.color-default:visited color: var(--color-default); } +.background-color-success +{ + background-color: var(--color-success); +} + +.background-color-warning +{ + background-color: var(--color-warning); +} + +.background-color-error +{ + background-color: var(--color-error); +} + +.background-color-default +{ + background-color: var(--color-default); +} + .text-align-left { text-align: left; @@ -188,4 +214,84 @@ a.color-default:visited { margin-bottom: 8px; margin-top: 8px; +} + +/* + * yggverse/graph UI + * + * for any feedback visit official page: + * https://github.com/YGGverse/graph-php + * + */ + +.calendar__month { + overflow: hidden +} + +.calendar__month > .day { + float: left; + height: 96px; + margin: 2px 0; + position: relative; + width: 14.285714286%; +} + +.calendar__month > .day:hover { + background-color: var(--background-color-hover-default); +} + +.calendar__month > .day > .number { + background-color: var(--background-color-hover-default); + border-radius: 50%; + font-size: 10px; + height: 16px; + left: 4px; + line-height: 16px; + opacity: 0.8; + position: absolute; + text-align: center; + top: 4px; + width: 16px; + z-index: 99; +} + +.calendar__month > .day:hover > .number { + opacity: 1; +} + +.calendar__month > .day > .layer-0 > .label { + background-color: var(--background-color-hover-default); + border-radius: 3px; + display: none; + font-size: 10px; + padding: 0 4px; + position: absolute; + right: 4px; + top: 6px; + z-index: 99; +} + +.calendar__month > .day:hover > .layer-0 > .label { + display: block; +} + +.calendar__month > .day > .layer-0 > .value { + bottom: 0; + opacity: 0.5; + position: absolute; + z-index: 0; +} + +.calendar__month > .day > .layer-1 > .label { + display: none +} + +.calendar__month > .day > .layer-1 > .value { + bottom: 0; + position: absolute; + z-index: 1; +} + +.calendar__month > .day > .layer-1 > .value:hover { + opacity: .8; } \ No newline at end of file diff --git a/src/Controller/MainController.php b/src/Controller/MainController.php index 8eb0ef0..7b71a5c 100644 --- a/src/Controller/MainController.php +++ b/src/Controller/MainController.php @@ -27,9 +27,18 @@ class MainController extends AbstractController )] public function index( ?Request $request, + TranslatorInterface $translatorInterface, EntityManagerInterface $entityManagerInterface ): Response { + // Init memory + $memory = new \Yggverse\Cache\Memory( + $this->getParameter('app.memcached.host'), + $this->getParameter('app.memcached.port'), + $this->getParameter('app.memcached.namespace'), + $this->getParameter('app.memcached.timeout') + time(), + ); + // Collect servers info $servers = []; @@ -119,11 +128,111 @@ class MainController extends AbstractController $servers ); + // Online calendar + $time = time(); + + $month = new \Yggverse\Graph\Calendar\Month($time); + + foreach ($month->getNodes() as $day => $node) + { + // Skip future days processing + if ($day > date('j')) + { + break; + } + + // Add daily stats + $total = $memory->getByMethodCallback( + $entityManagerInterface->getRepository(Online::class), + 'getMaxPlayersByTimeInterval', + [ + strtotime( + sprintf( + '%s-%s-%s 00:00', + date('Y', $time), + date('n', $time), + $day + ) + ), + strtotime( + '+1 day', + strtotime( + sprintf( + '%s-%s-%s 00:00', + date('Y', $time), + date('n', $time), + $day + ) + ) + ) + ], + time() + ($day == date('j') ? 60 : 2592000) + ); + + $month->addNode( + $day, + $total, + sprintf( + $translatorInterface->trans('online %d'), + $total + ), + null, + 0 + ); + + // Add hourly stats + for ($hour = 0; $hour < 24; $hour++) + { + $total = $memory->getByMethodCallback( + $entityManagerInterface->getRepository(Online::class), + 'getMaxPlayersByTimeInterval', + [ + strtotime( + sprintf( + '%s-%s-%s %s:00', + date('Y', $time), + date('n', $time), + $day, + $hour + ) + ), + strtotime( + sprintf( + '%s-%s-%s %s:00', + date('Y', $time), + date('n', $time), + $day, + $hour + 1 + ) + ) + ], + time() + ($day == date('j') ? 60 : 2592000) + ); + + $month->addNode( + $day, + $total, + sprintf( + $translatorInterface->trans('%s:00-%s:00 online %s'), + $hour, + $hour + 1, + $total + ), + 'background-color-default', + 1 + ); + } + } + return $this->render( 'default/main/index.html.twig', [ 'request' => $request, - 'servers' => $servers + 'servers' => $servers, + 'month' => + [ + 'online' => (array) $month->getNodes() + ] ] ); } diff --git a/src/Repository/OnlineRepository.php b/src/Repository/OnlineRepository.php index 24e8207..24eeec3 100644 --- a/src/Repository/OnlineRepository.php +++ b/src/Repository/OnlineRepository.php @@ -25,12 +25,27 @@ class OnlineRepository extends ServiceEntityRepository int $crc32server ): int { - return $this->createQueryBuilder('o') - ->select('count(o.id)') - ->where('o.crc32server = :crc32server') - ->setParameter('crc32server', $crc32server) - ->getQuery() - ->getSingleScalarResult() - ; + return + $this->createQueryBuilder('o') + ->select('count(o.id)') + ->where('o.crc32server = :crc32server') + ->setParameter('crc32server', $crc32server) + ->getQuery() + ->getSingleScalarResult(); + } + + public function getMaxPlayersByTimeInterval( + int $from, + int $to + ): int + { + return (int) + $this->createQueryBuilder('o') + ->select('max(o.players)') + ->where('o.time >= :from AND o.time <= :to') + ->setParameter('from', $from) + ->setParameter('to', $to) + ->getQuery() + ->getSingleScalarResult(); } } diff --git a/templates/default/main/index.html.twig b/templates/default/main/index.html.twig index 576b599..88ababe 100644 --- a/templates/default/main/index.html.twig +++ b/templates/default/main/index.html.twig @@ -91,4 +91,35 @@ {% endif %} +
+

{{ 'now' | date('M, Y') }}

+
+
+ {% for day, node in month.online %} + {% if day <= 'now' | date('j') %} +
+
+ {{ day }} +
+ {% for i, layers in node %} +
+
+ {% for layer in layers %} + + {{ layer.label }} +
+ {% endfor %} +
+ {% for layer in layers %} +
+ {% endfor %} +
+ {% endfor %} +
+ {% endif %} + {% endfor %} + +
{% endblock %} \ No newline at end of file