mirror of
https://github.com/YGGverse/HLState.git
synced 2026-03-31 17:15:39 +00:00
implement players online monthly chart
This commit is contained in:
parent
4d95117912
commit
6403b2fa8c
8 changed files with 285 additions and 10 deletions
8
.env
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"
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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,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)%'
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -189,3 +215,83 @@ 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;
|
||||||
|
}
|
||||||
|
|
@ -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()
|
||||||
|
]
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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…
Add table
Add a link
Reference in a new issue