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 %}
+
+