Browse Source

implement players online monthly chart

main
ghost 10 months ago
parent
commit
6403b2fa8c
  1. 8
      .env
  2. 4
      README.md
  3. 2
      composer.json
  4. 4
      config/services.yaml
  5. 106
      public/css/default.css
  6. 111
      src/Controller/MainController.php
  7. 29
      src/Repository/OnlineRepository.php
  8. 31
      templates/default/main/index.html.twig

8
.env

@ -43,7 +43,13 @@ MESSENGER_TRANSPORT_DSN=doctrine://default?auto_setup=0
# HLState # HLState
# Application version, used for API and media cache # 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 # Application name
APP_NAME="HLState" APP_NAME="HLState"

4
README.md

@ -21,7 +21,7 @@ Project initially written to explore [Yggdrasil](https://github.com/yggdrasil-ne
## Install ## 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` * `git clone https://github.com/YGGverse/HLState.git`
* `cd HLState` * `cd HLState`
* `composer install` * `composer install`
@ -62,6 +62,8 @@ Please create new branch from main before make PR
* [SVG icons](https://icons.getbootstrap.com) * [SVG icons](https://icons.getbootstrap.com)
* [PHP Source Query](https://github.com/xPaw/PHP-Source-Query) * [PHP Source Query](https://github.com/xPaw/PHP-Source-Query)
* [HL-PHP](https://github.com/YGGverse/hl-php) * [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) * [Favicons](https://realfavicongenerator.net)
## Support ## Support

2
composer.json

@ -45,6 +45,8 @@
"twig/intl-extra": "^3.8", "twig/intl-extra": "^3.8",
"twig/twig": "^3.8", "twig/twig": "^3.8",
"xpaw/php-source-query-class": "dev-master", "xpaw/php-source-query-class": "dev-master",
"yggverse/cache": "^0.3.1",
"yggverse/graph": "^0.2.2",
"yggverse/hl": "^1.0" "yggverse/hl": "^1.0"
}, },
"config": { "config": {

4
config/services.yaml

@ -4,6 +4,10 @@
# Put parameters here that don't need to change on each machine where the app is deployed # 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 # https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration
parameters: 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.version: '%env(APP_VERSION)%'
app.name: '%env(APP_NAME)%' app.name: '%env(APP_NAME)%'
app.theme: '%env(APP_THEME)%' app.theme: '%env(APP_THEME)%'

106
public/css/default.css

@ -16,6 +16,8 @@
--color-warning: #f37b21; --color-warning: #f37b21;
--color-error: #ff6363; --color-error: #ff6363;
--color-default: #999; --color-default: #999;
--background-color-hover-default: rgba(125, 125, 125, 0.1);
} }
*::placeholder *::placeholder
@ -65,6 +67,10 @@ table td
padding: 4px; padding: 4px;
} }
table tr:hover td {
background-color: var(--background-color-hover-default);
}
ul ul
{ {
margin-left: 16px; margin-left: 16px;
@ -143,6 +149,26 @@ a.color-default:visited
color: var(--color-default); 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
{ {
text-align: left; text-align: left;
@ -188,4 +214,84 @@ a.color-default:visited
{ {
margin-bottom: 8px; margin-bottom: 8px;
margin-top: 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;
} }

111
src/Controller/MainController.php

@ -27,9 +27,18 @@ class MainController extends AbstractController
)] )]
public function index( public function index(
?Request $request, ?Request $request,
TranslatorInterface $translatorInterface,
EntityManagerInterface $entityManagerInterface EntityManagerInterface $entityManagerInterface
): Response ): 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 // Collect servers info
$servers = []; $servers = [];
@ -119,11 +128,111 @@ class MainController extends AbstractController
$servers $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( return $this->render(
'default/main/index.html.twig', 'default/main/index.html.twig',
[ [
'request' => $request, 'request' => $request,
'servers' => $servers 'servers' => $servers,
'month' =>
[
'online' => (array) $month->getNodes()
]
] ]
); );
} }

29
src/Repository/OnlineRepository.php

@ -25,12 +25,27 @@ class OnlineRepository extends ServiceEntityRepository
int $crc32server int $crc32server
): int ): int
{ {
return $this->createQueryBuilder('o') return
->select('count(o.id)') $this->createQueryBuilder('o')
->where('o.crc32server = :crc32server') ->select('count(o.id)')
->setParameter('crc32server', $crc32server) ->where('o.crc32server = :crc32server')
->getQuery() ->setParameter('crc32server', $crc32server)
->getSingleScalarResult() ->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();
} }
} }

31
templates/default/main/index.html.twig

@ -91,4 +91,35 @@
</tr> </tr>
{% endif %} {% endif %}
</table> </table>
<br />
<h2>{{ 'now' | date('M, Y') }}</h2>
<hr />
<div class="padding-y-8-px calendar__month">
{% for day, node in month.online %}
{% if day <= 'now' | date('j') %}
<div class="day">
<div class="number">
{{ day }}
</div>
{% for i, layers in node %}
<div class="layer layer-{{ i }}">
<div class="label">
{% for layer in layers %}
<div{# class="{{ layer.class }}"#}>
{{ layer.label }}
</div>
{% endfor %}
</div>
{% for layer in layers %}
<div title="{{ layer.label }}"
class="value {{ layer.class }}"
style="width:{{ layer.width }}%;height:{{ layer.height }}%;left:{{ layer.offset }}%"></div>
{% endfor %}
</div>
{% endfor %}
</div>
{% endif %}
{% endfor %}
</div>
<hr />
{% endblock %} {% endblock %}
Loading…
Cancel
Save