Browse Source

implement separated server pages, replace hlservers to masters tracking

main
ghost 11 months ago
parent
commit
dc0048d32d
  1. 4
      .env
  2. 3
      composer.json
  3. 1
      config/services.yaml
  4. 31
      migrations/Version20240111202908.php
  5. 128
      src/Controller/CrontabController.php
  6. 155
      src/Controller/MainController.php
  7. 96
      src/Controller/RssController.php
  8. 183
      src/Controller/ServerController.php
  9. 111
      src/Entity/Server.php
  10. 23
      src/Repository/ServerRepository.php
  11. 4
      templates/default/layout.html.twig
  12. 217
      templates/default/main/index.html.twig
  13. 11
      templates/default/rss/online.xml.twig
  14. 11
      templates/default/rss/players.xml.twig
  15. 171
      templates/default/server/index.html.twig

4
.env

@ -58,7 +58,3 @@ APP_THEME="default" @@ -58,7 +58,3 @@ APP_THEME="default"
# Masters list, server:port comma separated
APP_MASTERS=""
# Servers registry, URL or filepath
# https://github.com/YGGverse/HLServers
APP_HLSERVERS="https://raw.githubusercontent.com/YGGverse/HLServers/main/config.json"

3
composer.json

@ -44,7 +44,8 @@ @@ -44,7 +44,8 @@
"twig/extra-bundle": "^3.8",
"twig/intl-extra": "^3.8",
"twig/twig": "^3.8",
"xpaw/php-source-query-class": "^2.1"
"xpaw/php-source-query-class": "^2.1",
"yggverse/hl": "^1.0"
},
"config": {
"allow-plugins": {

1
config/services.yaml

@ -8,7 +8,6 @@ parameters: @@ -8,7 +8,6 @@ parameters:
app.name: '%env(APP_NAME)%'
app.theme: '%env(APP_THEME)%'
app.masters: '%env(APP_MASTERS)%'
app.hlservers: '%env(APP_HLSERVERS)%'
app.meta.title: '%env(APP_META_TITLE)%'
app.meta.description: '%env(APP_META_DESCRIPTION)%'
app.meta.keywords: '%env(APP_META_KEYWORDS)%'

31
migrations/Version20240111202908.php

@ -0,0 +1,31 @@ @@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20240111202908 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE TABLE server (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, crc32server BIGINT NOT NULL, added BIGINT NOT NULL, updated BIGINT NOT NULL, online BIGINT NOT NULL, host VARCHAR(255) NOT NULL, port INTEGER NOT NULL)');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('DROP TABLE server');
}
}

128
src/Controller/CrontabController.php

@ -11,6 +11,8 @@ use Symfony\Component\HttpFoundation\Request; @@ -11,6 +11,8 @@ use Symfony\Component\HttpFoundation\Request;
use App\Entity\Online;
use App\Entity\Player;
use App\Entity\Server;
use Doctrine\ORM\EntityManagerInterface;
class CrontabController extends AbstractController
@ -25,37 +27,133 @@ class CrontabController extends AbstractController @@ -25,37 +27,133 @@ class CrontabController extends AbstractController
)]
public function index(
?Request $request,
TranslatorInterface $translatorInterface,
EntityManagerInterface $entityManagerInterface
): Response
{
// Get HLServers config
if ($hlservers = file_get_contents($this->getParameter('app.hlservers')))
// Prevent multi-thread execution
$semaphore = sem_get(
crc32(
__DIR__ . '.controller.crontab.index',
), 1
);
if (false === sem_acquire($semaphore, true))
{
$hlservers = json_decode($hlservers);
return new Response(
$translatorInterface->trans('Process locked by another thread')
);
}
else
// Get new servers from masters
foreach ((array) explode(',', $this->getParameter('app.masters')) as $master)
{
if (!$host = parse_url($master, PHP_URL_HOST)) // @TODO IPv6 https://bugs.php.net/bug.php?id=72811
{
continue;
}
if (!$port = parse_url($master, PHP_URL_PORT))
{
continue;
}
// Connect master node
$node = new \Yggverse\Hl\Xash3D\Master($host, $port, 1);
foreach ((array) $node->getServersIPv6() as $key => $value)
{
// Generate server identity
$crc32server = crc32(
$key
);
// Check server does not exist yet
$server = $entityManagerInterface->getRepository(Server::class)->findOneBy(
[
'crc32server' => $crc32server
]
);
// Server exist, just update
if ($server)
{
$hlservers = [];
$server->setUpdated(
time()
);
$server->setOnline(
time()
);
$entityManagerInterface->persist(
$server
);
$entityManagerInterface->flush();
continue;
}
// Server does not exist, create new record
$server = new Server();
$server->setCrc32server(
$crc32server
);
$server->setHost(
$value['host']
);
$server->setPort(
$value['port']
);
$server->setAdded(
time()
);
$server->setUpdated(
time()
);
$server->setOnline(
time()
);
$entityManagerInterface->persist(
$server
);
$entityManagerInterface->flush();
}
}
// Collect servers info
$servers = [];
foreach ($hlservers as $hlserver)
foreach ((array) $entityManagerInterface->getRepository(Server::class)->findBy(
[
'crc32server' => (int) $request->get('crc32server')
],
[
'id' => 'ASC'
],
) as $server)
{
try
{
$server = new \xPaw\SourceQuery\SourceQuery();
$query = new \xPaw\SourceQuery\SourceQuery();
$server->Connect(
$hlserver->host,
$hlserver->port
$query->Connect(
false === filter_var($server->getHost(), FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) ? $server->getHost() : "[{$server->getHost()}]",
$server->port
);
if ($server->Ping())
if ($query->Ping())
{
if ($info = (array) $server->GetInfo())
if ($info = (array) $query->GetInfo())
{
// Filter response
$bots = isset($info['Bots']) && $info['Bots'] > 0 ? (int) $info['Bots'] : 0;
@ -64,7 +162,7 @@ class CrontabController extends AbstractController @@ -64,7 +162,7 @@ class CrontabController extends AbstractController
// Generate CRC32 server ID
$crc32server = crc32(
$hlserver->host . ':' . $hlserver->port
$server->host . ':' . $server->port
);
// Get last online value
@ -121,7 +219,7 @@ class CrontabController extends AbstractController @@ -121,7 +219,7 @@ class CrontabController extends AbstractController
// Update player stats
if ($players)
{
foreach ((array) $server->GetPlayers() as $session)
foreach ((array) $query->GetPlayers() as $session)
{
// Validate fields
if
@ -231,7 +329,7 @@ class CrontabController extends AbstractController @@ -231,7 +329,7 @@ class CrontabController extends AbstractController
finally
{
$server->Disconnect();
$query->Disconnect();
}
}

155
src/Controller/MainController.php

@ -11,6 +11,8 @@ use Symfony\Component\HttpFoundation\Request; @@ -11,6 +11,8 @@ use Symfony\Component\HttpFoundation\Request;
use App\Entity\Online;
use App\Entity\Player;
use App\Entity\Server;
use Doctrine\ORM\EntityManagerInterface;
class MainController extends AbstractController
@ -28,139 +30,56 @@ class MainController extends AbstractController @@ -28,139 +30,56 @@ class MainController extends AbstractController
EntityManagerInterface $entityManagerInterface
): Response
{
// Prepare request
if ('online' == $request->get('sort') && in_array($request->get('field'), ['time','players','bots','total']))
{
$field = $request->get('field');
}
else if ('players' == $request->get('sort') && in_array($request->get('field'), ['name','frags','joined','online']))
{
$field = $request->get('field');
}
else
{
$field = 'time';
}
if (in_array($request->get('order'), ['asc','desc']))
{
$order = $request->get('order');
}
else
{
$order = 'desc';
}
// Get HLServers config
if ($hlservers = file_get_contents($this->getParameter('app.hlservers')))
{
$hlservers = json_decode($hlservers);
}
else
{
$hlservers = [];
}
// Collect servers info
$servers = [];
foreach ($hlservers as $hlserver)
foreach ((array) $entityManagerInterface->getRepository(Server::class)->findAll() as $server)
{
// Init defaults
$info = [];
$session = [];
$online = [];
$players = [];
// Generate CRC32 ID
$crc32server = crc32(
$hlserver->host . ':' . $hlserver->port
);
// Prepare aliases
$aliases = [];
foreach ($hlserver->alias as $value)
{
$alias = new \xPaw\SourceQuery\SourceQuery();
$alias->Connect(
$value->host,
$value->port
);
$status = false;
$aliases[] = [
'host' => $value->host,
'port' => $value->port,
'status' => $alias->Ping()
$info =
[
'Protocol' => null,
'HostName' => null,
'Map' => null,
'ModDir' => null,
'ModDesc' => null,
'AppID' => null,
'Players' => null,
'MaxPlayers' => null,
'Bots' => null,
'Dedicated' => null,
'Os' => null,
'Password' => null,
'Secure' => null,
'Version' => null
];
}
// Request server info
try
{
$server = new \xPaw\SourceQuery\SourceQuery();
$node = new \xPaw\SourceQuery\SourceQuery();
$server->Connect(
$hlserver->host,
$hlserver->port
$node->Connect(
false === filter_var($server->getHost(), FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) ? $server->getHost() : "[{$server->getHost()}]",
$server->getPort()
);
if ($server->Ping())
{
if ($info = (array) $server->GetInfo())
{
// Get session
$session = empty($info['Players']) ? [] : (array) $server->GetPlayers();
// Sort by players by frags
if ($session)
if ($node->Ping())
{
array_multisort(
array_column(
$session,
'Frags'
),
SORT_DESC,
$session
);
}
// Get online
$online = $entityManagerInterface->getRepository(Online::class)->findBy(
[
'crc32server' => $crc32server
],
'online' == $request->get('sort') && $crc32server == $request->get('crc32server') ? [$field => $order] : ['time' => 'DESC'],
10
);
// Get players
$players = $entityManagerInterface->getRepository(Player::class)->findBy(
[
'crc32server' => $crc32server
],
'players' == $request->get('sort') && $crc32server == $request->get('crc32server') ? [$field => $order] : ['frags' => 'DESC'],
10
);
}
$status = true;
}
else
foreach ((array) $node->GetInfo() as $key => $value)
{
$status = false;
$info[$key] = $value;
}
}
}
catch (Exception $error)
{
continue;
throw $this->createNotFoundException();
}
catch (\Throwable $error)
@ -170,20 +89,18 @@ class MainController extends AbstractController @@ -170,20 +89,18 @@ class MainController extends AbstractController
finally
{
$server->Disconnect();
$node->Disconnect();
}
// Add server
$servers[] = [
'crc32server' => $crc32server,
'host' => $hlserver->host,
'port' => $hlserver->port,
'description' => $hlserver->description,
'aliases' => $aliases,
'crc32server' => $server->getCrc32server(),
'host' => $server->getHost(),
'port' => $server->getPort(),
'added' => $server->getAdded(),
'updated' => $server->getUpdated(),
'online' => $server->getOnline(),
'info' => $info,
'session' => $session,
'online' => $online,
'players' => $players,
'status' => $status
];
}

96
src/Controller/RssController.php

@ -11,6 +11,8 @@ use Symfony\Component\HttpFoundation\Request; @@ -11,6 +11,8 @@ use Symfony\Component\HttpFoundation\Request;
use App\Entity\Online;
use App\Entity\Player;
use App\Entity\Server;
use Doctrine\ORM\EntityManagerInterface;
class RssController extends AbstractController
@ -32,47 +34,19 @@ class RssController extends AbstractController @@ -32,47 +34,19 @@ class RssController extends AbstractController
EntityManagerInterface $entityManagerInterface
): Response
{
// Get HLServers config
if ($hlservers = file_get_contents($this->getParameter('app.hlservers')))
{
$hlservers = json_decode($hlservers);
}
else
{
$hlservers = [];
}
$online = [];
// Find server info
foreach ($hlservers as $hlserver)
{
// Generate CRC32 server ID
$crc32server = crc32(
$hlserver->host . ':' . $hlserver->port
);
// Skip servers not registered in HLServers
if ($crc32server != $request->get('crc32server'))
{
continue;
}
// Get last online value
$online = $entityManagerInterface->getRepository(Online::class)->findBy(
foreach ($entityManagerInterface->getRepository(Online::class)->findBy(
[
'crc32server' => $crc32server
'crc32server' => $request->get('crc32server')
],
[
'id' => 'DESC' // same as online.time but faster
],
10
);
$result = [];
foreach ($online as $value)
) as $value)
{
$result[] =
$online[] =
[
'id' => $value->getId(),
'bots' => $value->getBots(),
@ -95,15 +69,12 @@ class RssController extends AbstractController @@ -95,15 +69,12 @@ class RssController extends AbstractController
[
'server' =>
[
'crc32' => $crc32server,
'host' => $hlserver->host,
'port' => $hlserver->port,
],
'online' => $result
'crc32server' => $request->get('crc32server'),
'online' => $online
]
],
$response
);
}
throw $this->createNotFoundException();
}
@ -125,45 +96,17 @@ class RssController extends AbstractController @@ -125,45 +96,17 @@ class RssController extends AbstractController
EntityManagerInterface $entityManagerInterface
): Response
{
// Get HLServers config
if ($hlservers = file_get_contents($this->getParameter('app.hlservers')))
{
$hlservers = json_decode($hlservers);
}
else
{
$hlservers = [];
}
$players = [];
// Find server info
foreach ($hlservers as $hlserver)
{
// Generate CRC32 server ID
$crc32server = crc32(
$hlserver->host . ':' . $hlserver->port
);
// Skip servers not registered in HLServers
if ($crc32server != $request->get('crc32server'))
{
continue;
}
// Get last players
$players = $entityManagerInterface->getRepository(Player::class)->findBy(
foreach ($entityManagerInterface->getRepository(Player::class)->findBy(
[
'crc32server' => $crc32server
'crc32server' => $request->get('crc32server')
],
[
'id' => 'DESC'
],
10
);
$result = [];
foreach ($players as $value)
) as $value)
{
$result[] =
[
@ -186,16 +129,11 @@ class RssController extends AbstractController @@ -186,16 +129,11 @@ class RssController extends AbstractController
[
'server' =>
[
'crc32' => $crc32server,
'host' => $hlserver->host,
'port' => $hlserver->port,
],
'players' => $result
'crc32server' => $request->get('crc32server'),
'players' => $players
]
],
$response
);
}
throw $this->createNotFoundException();
}
}

183
src/Controller/ServerController.php

@ -0,0 +1,183 @@ @@ -0,0 +1,183 @@
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Contracts\Translation\TranslatorInterface;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use App\Entity\Online;
use App\Entity\Player;
use App\Entity\Server;
use Doctrine\ORM\EntityManagerInterface;
class ServerController extends AbstractController
{
#[Route(
'/server/{crc32server}',
name: 'server_index',
requirements:
[
'crc32server' => '\d+',
],
methods:
[
'GET'
]
)]
public function index(
?Request $request,
EntityManagerInterface $entityManagerInterface
): Response
{
// Validate server
$server = $entityManagerInterface->getRepository(Server::class)->findOneBy(
[
'crc32server' => $request->get('crc32server')
]
);
if (!$server)
{
throw $this->createNotFoundException();
}
// Prepare request
if ('online' == $request->get('sort') && in_array($request->get('field'), ['time','players','bots','total']))
{
$field = $request->get('field');
}
else if ('players' == $request->get('sort') && in_array($request->get('field'), ['name','frags','joined','online']))
{
$field = $request->get('field');
}
else
{
$field = 'time';
}
if (in_array($request->get('order'), ['asc','desc']))
{
$order = $request->get('order');
}
else
{
$order = 'desc';
}
// Init defaults
$info = [];
$session = [];
$online = [];
$players = [];
// Format name
if (false === filter_var($server->getHost(), FILTER_VALIDATE_IP, FILTER_FLAG_IPV6))
{
$name = "{$server->getHost()}:{$server->getPort()}";
}
else
{
$name = "[{$server->getHost()}]:{$server->getPort()}";
}
// Request server info
try
{
$node = new \xPaw\SourceQuery\SourceQuery();
$node->Connect(
false === filter_var($server->getHost(), FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) ? $server->getHost() : "[{$server->getHost()}]",
$server->getPort()
);
if ($node->Ping())
{
if ($info = (array) $node->GetInfo())
{
// Get session
$session = empty($info['Players']) ? [] : (array) $server->GetPlayers();
// Sort by players by frags
if ($session)
{
array_multisort(
array_column(
$session,
'Frags'
),
SORT_DESC,
$session
);
}
// Get online
$online = $entityManagerInterface->getRepository(Online::class)->findBy(
[
'crc32server' => $server->getCrc32Server()
],
'online' == $request->get('sort') ? [$field => $order] : ['time' => 'DESC'],
10
);
// Get players
$players = $entityManagerInterface->getRepository(Player::class)->findBy(
[
'crc32server' => $server->getCrc32Server()
],
'players' == $request->get('sort') ? [$field => $order] : ['frags' => 'DESC'],
10
);
}
$status = true;
}
else
{
$status = false;
}
}
catch (Exception $error)
{
throw $this->createNotFoundException();
}
catch (\Throwable $error)
{
$status = false;
}
finally
{
$node->Disconnect();
}
return $this->render(
'default/server/index.html.twig',
[
'request' => $request,
'server' =>
[
'name' => $name,
'crc32server' => $server->getCrc32Server(),
'host' => $server->getHost(),
'port' => $server->getPort(),
'info' => $info,
'session' => $session,
'online' => $online,
'players' => $players,
'status' => $status
]
]
);
}
}

111
src/Entity/Server.php

@ -0,0 +1,111 @@ @@ -0,0 +1,111 @@
<?php
namespace App\Entity;
use App\Repository\ServerRepository;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: ServerRepository::class)]
class Server
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(type: Types::BIGINT)]
private ?string $crc32server = null;
#[ORM\Column(type: Types::BIGINT)]
private ?string $added = null;
#[ORM\Column(type: Types::BIGINT)]
private ?string $updated = null;
#[ORM\Column(type: Types::BIGINT)]
private ?string $online = null;
#[ORM\Column(length: 255)]
private ?string $host = null;
#[ORM\Column(type: Types::INTEGER)]
private ?int $port = null;
public function getId(): ?int
{
return $this->id;
}
public function getCrc32server(): ?string
{
return $this->crc32server;
}
public function setCrc32server(string $crc32server): static
{
$this->crc32server = $crc32server;
return $this;
}
public function getAdded(): ?int
{
return $this->added;
}
public function setAdded(int $added): static
{
$this->added = $added;
return $this;
}
public function getUpdated(): ?int
{
return $this->updated;
}
public function setUpdated(int $updated): static
{
$this->updated = $updated;
return $this;
}
public function getOnline(): ?int
{
return $this->online;
}
public function setOnline(int $online): static
{
$this->online = $online;
return $this;
}
public function getHost(): ?string
{
return $this->host;
}
public function setHost(string $host): static
{
$this->host = $host;
return $this;
}
public function getPort(): ?int
{
return $this->port;
}
public function setPort(int $port): static
{
$this->port = $port;
return $this;
}
}

23
src/Repository/ServerRepository.php

@ -0,0 +1,23 @@ @@ -0,0 +1,23 @@
<?php
namespace App\Repository;
use App\Entity\Server;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @extends ServiceEntityRepository<Server>
*
* @method Server|null find($id, $lockMode = null, $lockVersion = null)
* @method Server|null findOneBy(array $criteria, array $orderBy = null)
* @method Server[] findAll()
* @method Server[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class ServerRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, Server::class);
}
}

4
templates/default/layout.html.twig

@ -18,7 +18,7 @@ @@ -18,7 +18,7 @@
{% block header_container %}
<header>
{% block header_content %}
<a href="https://store.steampowered.com/app/70/HalfLife/" title="{{ 'Get Half-Life' | trans }}" target="_blank">
<a href="{{ path('main_index') }}">
<svg version="1.1" width="20" height="20" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 364.707 364.707" enable-background="new 0 0 364.707 364.707" xml:space="preserve">
<path fill="currentColor" d="M223.864,272.729l-38.608-97.848l-56.603,89.184H93.166l79.052-127.654l-8.875-25.229h-30.781V81.12h52.691l60.521,153.899l26.608-8.668l8.867,29.813L223.864,272.729z"/>
<path fill="none" stroke="currentColor" stroke-width="34" d="M337.623,182.198c0,85.579-69.363,154.934-154.934,154.934c-85.571,0-154.936-69.354-154.936-154.934c0-85.569,69.363-154.933,154.936-154.933C268.259,27.265,337.623,96.629,337.623,182.198z"/>
@ -40,7 +40,7 @@ @@ -40,7 +40,7 @@
{% block footer_content %}
{% if app.masters %}
<small class="float-left">
<strong>{{ 'Master' | trans }}:</strong>
<strong>{{ 'Master tracker' | trans }}:</strong>
{% for i, master in app.masters | split(',') %}
{% if i %}&bull;{% endif %} {{ master }}
{% endfor %}

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

@ -1,76 +1,22 @@ @@ -1,76 +1,22 @@
{% macro sort(request, crc32server, sort, field) %}
{% if sort == request.get('sort') and crc32server == request.get('crc32server') %}
{% if field == request.get('field') %}
{% if 'asc' == request.get('order') %}
<a href="{{ path('main_index', { sort : sort, field : field, order : 'desc', crc32server : crc32server, '_fragment' : '%s-%d' | format(sort, crc32server) }) }}" rel="nofollow"><svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" viewBox="0 0 16 16">
<path d="M0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2zm4 4a.5.5 0 0 0-.374.832l4 4.5a.5.5 0 0 0 .748 0l4-4.5A.5.5 0 0 0 12 6z"/>
</svg></a>
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" viewBox="0 0 16 16">
<path d="M0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2zm4 9h8a.5.5 0 0 0 .374-.832l-4-4.5a.5.5 0 0 0-.748 0l-4 4.5A.5.5 0 0 0 4 11"/>
</svg>
{% else %}
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" viewBox="0 0 16 16">
<path d="M0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2zm4 4a.5.5 0 0 0-.374.832l4 4.5a.5.5 0 0 0 .748 0l4-4.5A.5.5 0 0 0 12 6z"/>
</svg>
<a href="{{ path('main_index', { sort : sort, field : field, order : 'asc', crc32server : crc32server, '_fragment' : '%s-%d' | format(sort, crc32server) }) }}" rel="nofollow"><svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" viewBox="0 0 16 16">
<path d="M0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2zm4 9h8a.5.5 0 0 0 .374-.832l-4-4.5a.5.5 0 0 0-.748 0l-4 4.5A.5.5 0 0 0 4 11"/>
</svg></a>
{% endif %}
{% else %}
<a href="{{ path('main_index', { sort : sort, field : field, order : 'desc', crc32server : crc32server, '_fragment' : '%s-%d' | format(sort, crc32server) }) }}" rel="nofollow"><svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" viewBox="0 0 16 16">
<path d="M0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2zm4 4a.5.5 0 0 0-.374.832l4 4.5a.5.5 0 0 0 .748 0l4-4.5A.5.5 0 0 0 12 6z"/>
</svg></a>
<a href="{{ path('main_index', { sort : sort, field : field, order : 'asc', crc32server : crc32server, '_fragment' : '%s-%d' | format(sort, crc32server) }) }}" rel="nofollow"><svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" viewBox="0 0 16 16">
<path d="M0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2zm4 9h8a.5.5 0 0 0 .374-.832l-4-4.5a.5.5 0 0 0-.748 0l-4 4.5A.5.5 0 0 0 4 11"/>
</svg></a>
{% endif %}
{% else %}
{% if 'online' == sort and 'time' == field %}
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" viewBox="0 0 16 16">
<path d="M0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2zm4 4a.5.5 0 0 0-.374.832l4 4.5a.5.5 0 0 0 .748 0l4-4.5A.5.5 0 0 0 12 6z"/>
</svg>
{% elseif 'players' == sort and 'frags' == field %}
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" viewBox="0 0 16 16">
<path d="M0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2zm4 4a.5.5 0 0 0-.374.832l4 4.5a.5.5 0 0 0 .748 0l4-4.5A.5.5 0 0 0 12 6z"/>
</svg>
{% else %}
<a href="{{ path('main_index', { sort : sort, field : field, order : 'desc', crc32server : crc32server, '_fragment' : '%s-%d' | format(sort, crc32server) }) }}" rel="nofollow"><svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" viewBox="0 0 16 16">
<path d="M0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2zm4 4a.5.5 0 0 0-.374.832l4 4.5a.5.5 0 0 0 .748 0l4-4.5A.5.5 0 0 0 12 6z"/>
</svg></a>
{% endif %}
<a href="{{ path('main_index', { sort : sort, field : field, order : 'asc', crc32server : crc32server, '_fragment' : '%s-%d' | format(sort, crc32server) }) }}" rel="nofollow"><svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" viewBox="0 0 16 16">
<path d="M0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2zm4 9h8a.5.5 0 0 0 .374-.832l-4-4.5a.5.5 0 0 0-.748 0l-4 4.5A.5.5 0 0 0 4 11"/>
</svg></a>
{% endif %}
{% endmacro %}
{% from _self import sort %}
{% extends 'default/layout.html.twig' %}
{% block main_content %}
<h2>{{ 'Servers' | trans }}</h2>
<table class="margin-y-8-px">
<tr>
<th class="text-align-center">{{ 'Status' | trans }}</th>
<th class="text-align-left">{{ 'Name' | trans }}</th>
<th class="text-align-left">{{ 'Map' | trans }}</th>
<th class="text-align-left">{{ 'Added' | trans }}</th>
<th class="text-align-left">{{ 'Online' | trans }}</th>
<th class="text-align-center">{{ 'Players' | trans }}</th>
<th class="text-align-center">{{ 'Max' | trans }}</th>
<th class="text-align-center">{{ 'Bots' | trans }}</th>
<th class="text-align-center">{{ 'Actions' | trans }}</th>
</tr>
{% if servers %}
{% for server in servers %}
<div class="border-default padding-8-px margin-y-8-px">
{% if server.info.HostName is defined %}
<h2>{{ server.info.HostName }} [#{{ server.crc32server }}]</h2>
{% else %}
<h2>#{{ server.crc32server }}</h2>
{% endif %}
<hr />
{% if server.description.title is defined %}
<div class="margin-y-8-px">
{{ server.description.title }}
</div>
{% endif %}
{% if server.description.links %}
<div class="margin-y-8-px">
<ul>
{% for link in server.description.links %}
<li class="margin-y-8-px"><a href="{{ link }}">{{ link }}</a></li>
{% endfor %}
</ul>
</div>
{% endif %}
<h3 class="padding-y-8-px text-align-right">{{ 'Address' | trans }}</h3>
<div>
{{ server.host }}:{{ server.port }}
<tr>
<td class="text-align-center">
{% if server.status %}
<span class="color-success" title="{{ 'Online' | trans }}">
<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10" fill="currentColor" viewBox="0 0 16 16">
@ -84,130 +30,25 @@ @@ -84,130 +30,25 @@
</svg>
</span>
{% endif %}
</div>
{% if server.aliases %}
<h3 class="padding-y-8-px text-align-right">{{ 'Alias' | trans }}</h3>
{% for alias in server.aliases %}
<div class="margin-y-8-px">
{{ alias.host }}:{{ alias.port }}
{% if alias.status %}
<span class="color-success" title="{{ 'Online' | trans }}">
<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10" fill="currentColor" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M8 13A5 5 0 1 0 8 3a5 5 0 0 0 0 10"/>
</svg>
</span>
{% else %}
<span class="color-error" title="{{ 'Offline' | trans }}">
<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10" fill="currentColor" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M8 13A5 5 0 1 0 8 3a5 5 0 0 0 0 10"/>
</svg>
</span>
{% endif %}
</div>
{% endfor %}
{% endif %}
{% if server.info %}
<h3 class="padding-y-8-px text-align-right">{{ 'Info' | trans }}</h3>
<table>
{% for key, value in server.info %}
<tr>
<td class="text-align-left">{{ key }}</td>
<td class="text-align-left">{{ value }}</td>
</tr>
{% endfor %}
</table>
{% endif %}
{% if server.session %}
<h3 class="padding-y-8-px text-align-right">{{ 'Session' | trans }}</h3>
<table>
<tr>
<th class="text-align-left">{{ 'Player' | trans }}</th>
<th class="text-align-center">{{ 'Frags' | trans }}</th>
<th class="text-align-center">{{ 'Time' | trans }}</th>
</tr>
{% for player in server.session %}
<tr>
<td class="text-align-left">{{ player.Name }}</td>
<td class="text-align-center">{{ player.Frags }}</td>
</td>
<td class="text-align-left">{{ server.info.HostName }}</td>
<td class="text-align-left">{{ server.info.Map }}</td>
<td class="text-align-left">{{ server.added | format_date }}</td>
<td class="text-align-left">{{ server.online | format_date }}</td>
<td class="text-align-center">{{ server.info.Players }}</td>
<td class="text-align-center">{{ server.info.MaxPlayers }}</td>
<td class="text-align-center">{{ server.info.Bots }}</td>
<td class="text-align-center">
{% if player.TimeF == '59:59' %}
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" viewBox="0 0 16 16">
<path d="M5.68 5.792 7.345 7.75 5.681 9.708a2.75 2.75 0 1 1 0-3.916ZM8 6.978 6.416 5.113l-.014-.015a3.75 3.75 0 1 0 0 5.304l.014-.015L8 8.522l1.584 1.865.014.015a3.75 3.75 0 1 0 0-5.304l-.014.015zm.656.772 1.663-1.958a2.75 2.75 0 1 1 0 3.916z"/>
</svg>
{% else %}
{{ player.TimeF }}
{% endif %}
<a href="{{ path('server_index', { crc32server : server.crc32server }) }}" title="{{ 'Stats' | trans }}"><svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" viewBox="0 0 16 16">
<path d="M11 2a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v12h.5a.5.5 0 0 1 0 1H.5a.5.5 0 0 1 0-1H1v-3a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v3h1V7a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v7h1z"/>
</svg></a>
</td>
</tr>
{% endfor %}
</table>
{% endif %}
{% if server.online %}
<a name="{{ '%s-%d' | format('online', server.crc32server) }}"></a>
<h3 class="padding-y-8-px text-align-right">
{{ 'Online' | trans }}
<a href="{{ path('rss_online', { crc32server : server.crc32server }) }}" title="{{ 'RSS' | trans }}"><svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" viewBox="0 0 16 16">
<path d="M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2zm1.5 2.5c5.523 0 10 4.477 10 10a1 1 0 1 1-2 0 8 8 0 0 0-8-8 1 1 0 0 1 0-2m0 4a6 6 0 0 1 6 6 1 1 0 1 1-2 0 4 4 0 0 0-4-4 1 1 0 0 1 0-2m.5 7a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3"/>
</svg></a>
</h3>
<table>
<tr>
<th class="text-align-left">
{{ 'Time' | trans }} {{ sort(request, server.crc32server, 'online', 'time') }}
</th>
<th class="text-align-center">
{{ 'Players' | trans }} {{ sort(request, server.crc32server, 'online', 'players') }}
</th>
<th class="text-align-center">
{{ 'Bots' | trans }} {{ sort(request, server.crc32server, 'online', 'bots') }}
</th>
<th class="text-align-center">
{{ 'Total' | trans }} {{ sort(request, server.crc32server, 'online', 'total') }}
</th>
</tr>
{% for online in server.online %}
{% else %}
<tr>
<td class="text-align-left">{{ online.time | date('r') }}</td>
<td class="text-align-center">{{ online.players }}</td>
<td class="text-align-center">{{ online.bots }}</td>
<td class="text-align-center">{{ online.total }}</td>
<td class="text-align-center" colspan="9">{{ 'Not found' | trans }}</td>
</tr>
{% endfor %}
</table>
{% endif %}
{% if server.players %}
<a name="{{ '%s-%d' | format('players', server.crc32server) }}"></a>
<h3 class="padding-y-8-px text-align-right">
{{ 'Players' | trans }}
<a href="{{ path('rss_players', { crc32server : server.crc32server }) }}" title="{{ 'RSS' | trans }}"><svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" viewBox="0 0 16 16">
<path d="M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2zm1.5 2.5c5.523 0 10 4.477 10 10a1 1 0 1 1-2 0 8 8 0 0 0-8-8 1 1 0 0 1 0-2m0 4a6 6 0 0 1 6 6 1 1 0 1 1-2 0 4 4 0 0 0-4-4 1 1 0 0 1 0-2m.5 7a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3"/>
</svg></a>
</h3>
<table>
<tr>
<th class="text-align-center">
{{ 'Frags' | trans }} {{ sort(request, server.crc32server, 'players', 'frags') }}
</th>
<th class="text-align-left">
{{ 'Name' | trans }} {{ sort(request, server.crc32server, 'players', 'name') }}
</th>
<th class="text-align-left">
{{ 'Online' | trans }} {{ sort(request, server.crc32server, 'players', 'online') }}
</th>
<th class="text-align-left">
{{ 'Joined' | trans }} {{ sort(request, server.crc32server, 'players', 'joined') }}
</th>
</tr>
{% for player in server.players %}
<tr>
<td class="text-align-center">{{ player.frags }}</td>
<td class="text-align-left">{{ player.name }}</td>
<td class="text-align-left">{{ player.online | format_date }}</td>
<td class="text-align-left">{{ player.joined | format_date }}</td>
</tr>
{% endfor %}
</table>
{% endif %}
</div>
{% endfor %}
{% endblock %}

11
templates/default/rss/online.xml.twig

@ -1,13 +1,14 @@ @@ -1,13 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
<channel>
<atom:link href="{{ url('main_index') }}" rel="self" type="application/rss+xml"></atom:link>
<title>{{ server.host }}:{{ server.port }} - {{ app.name }}</title>
<link>{{ url('main_index') }}</link>
{% for value in online %}
<atom:link href="{{ url('server_index', { crc32server : server.crc32server }) }}" rel="self" type="application/rss+xml"></atom:link>
<title>#{{ server.crc32server }} - {{ 'Online' | trans }} - {{ app.name }}</title>
<link>{{ url('server_index', { crc32server : server.crc32server, _fragment : 'online' }) }}</link>
{% for value in server.online %}
<item>
<title>{{ 'Online changed!' | trans }}</title>
<guid>online#{{ value.id }}</guid>
<guid>{{ url('server_index', { crc32server : server.crc32server, _fragment : 'online' }) }}-{{ value.id }}</guid>
<link>{{ url('server_index', { crc32server : server.crc32server, _fragment : 'online' }) }}</link>
<description>
{{ 'Players' | trans }}: {{ value.players }}
{{ 'Bots' | trans }}: {{ value.bots }}

11
templates/default/rss/players.xml.twig

@ -1,13 +1,14 @@ @@ -1,13 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
<channel>
<atom:link href="{{ url('main_index') }}" rel="self" type="application/rss+xml"></atom:link>
<title>{{ server.host }}:{{ server.port }} - {{ 'Players' | trans }} - {{ app.name }}</title>
<link>{{ url('main_index') }}</link>
{% for player in players %}
<atom:link href="{{ url('server_index', { crc32server : server.crc32server }) }}" rel="self" type="application/rss+xml"></atom:link>
<title>#{{ server.crc32server }} - {{ 'Players' | trans }} - {{ app.name }}</title>
<link>{{ url('server_index', { crc32server : server.crc32server, _fragment : 'online' }) }}</link>
{% for player in server.players %}
<item>
<title>{{ player.name }} {{ 'joined' | trans }} {{ app.name }}!</title>
<guid>player#{{ player.id }}</guid>
<guid>{{ url('server_index', { crc32server : server.crc32server, _fragment : 'players' }) }}-{{ value.id }}</guid>
<link>{{ url('server_index', { crc32server : server.crc32server, _fragment : 'players' }) }}</link>
<pubDate>{{ player.joined | date("D, d M Y h:i:s O") }}</pubDate>
</item>
{% endfor %}

171
templates/default/server/index.html.twig

@ -0,0 +1,171 @@ @@ -0,0 +1,171 @@
{% macro sort(request, crc32server, sort, field) %}
{% if sort == request.get('sort') and crc32server == request.get('crc32server') %}
{% if field == request.get('field') %}
{% if 'asc' == request.get('order') %}
<a href="{{ path('main_index', { sort : sort, field : field, order : 'desc', crc32server : crc32server, '_fragment' : '%s-%d' | format(sort, crc32server) }) }}" rel="nofollow"><svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" viewBox="0 0 16 16">
<path d="M0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2zm4 4a.5.5 0 0 0-.374.832l4 4.5a.5.5 0 0 0 .748 0l4-4.5A.5.5 0 0 0 12 6z"/>
</svg></a>
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" viewBox="0 0 16 16">
<path d="M0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2zm4 9h8a.5.5 0 0 0 .374-.832l-4-4.5a.5.5 0 0 0-.748 0l-4 4.5A.5.5 0 0 0 4 11"/>
</svg>
{% else %}
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" viewBox="0 0 16 16">
<path d="M0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2zm4 4a.5.5 0 0 0-.374.832l4 4.5a.5.5 0 0 0 .748 0l4-4.5A.5.5 0 0 0 12 6z"/>
</svg>
<a href="{{ path('main_index', { sort : sort, field : field, order : 'asc', crc32server : crc32server, '_fragment' : '%s-%d' | format(sort, crc32server) }) }}" rel="nofollow"><svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" viewBox="0 0 16 16">
<path d="M0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2zm4 9h8a.5.5 0 0 0 .374-.832l-4-4.5a.5.5 0 0 0-.748 0l-4 4.5A.5.5 0 0 0 4 11"/>
</svg></a>
{% endif %}
{% else %}
<a href="{{ path('main_index', { sort : sort, field : field, order : 'desc', crc32server : crc32server, '_fragment' : '%s-%d' | format(sort, crc32server) }) }}" rel="nofollow"><svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" viewBox="0 0 16 16">
<path d="M0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2zm4 4a.5.5 0 0 0-.374.832l4 4.5a.5.5 0 0 0 .748 0l4-4.5A.5.5 0 0 0 12 6z"/>
</svg></a>
<a href="{{ path('main_index', { sort : sort, field : field, order : 'asc', crc32server : crc32server, '_fragment' : '%s-%d' | format(sort, crc32server) }) }}" rel="nofollow"><svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" viewBox="0 0 16 16">
<path d="M0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2zm4 9h8a.5.5 0 0 0 .374-.832l-4-4.5a.5.5 0 0 0-.748 0l-4 4.5A.5.5 0 0 0 4 11"/>
</svg></a>
{% endif %}
{% else %}
{% if 'online' == sort and 'time' == field %}
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" viewBox="0 0 16 16">
<path d="M0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2zm4 4a.5.5 0 0 0-.374.832l4 4.5a.5.5 0 0 0 .748 0l4-4.5A.5.5 0 0 0 12 6z"/>
</svg>
{% elseif 'players' == sort and 'frags' == field %}
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" viewBox="0 0 16 16">
<path d="M0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2zm4 4a.5.5 0 0 0-.374.832l4 4.5a.5.5 0 0 0 .748 0l4-4.5A.5.5 0 0 0 12 6z"/>
</svg>
{% else %}
<a href="{{ path('main_index', { sort : sort, field : field, order : 'desc', crc32server : crc32server, '_fragment' : '%s-%d' | format(sort, crc32server) }) }}" rel="nofollow"><svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" viewBox="0 0 16 16">
<path d="M0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2zm4 4a.5.5 0 0 0-.374.832l4 4.5a.5.5 0 0 0 .748 0l4-4.5A.5.5 0 0 0 12 6z"/>
</svg></a>
{% endif %}
<a href="{{ path('main_index', { sort : sort, field : field, order : 'asc', crc32server : crc32server, '_fragment' : '%s-%d' | format(sort, crc32server) }) }}" rel="nofollow"><svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" viewBox="0 0 16 16">
<path d="M0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2zm4 9h8a.5.5 0 0 0 .374-.832l-4-4.5a.5.5 0 0 0-.748 0l-4 4.5A.5.5 0 0 0 4 11"/>
</svg></a>
{% endif %}
{% endmacro %}
{% from _self import sort %}
{% extends 'default/layout.html.twig' %}
{% block head_title_content %}{{ server.name }} - {{ app.name }}{% endblock %}
{% block main_content %}
<div class="border-default padding-8-px margin-y-8-px">
<h2>
{{ server.name }}
{% if server.status %}
<span class="color-success" title="{{ 'Online' | trans }}">
<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10" fill="currentColor" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M8 13A5 5 0 1 0 8 3a5 5 0 0 0 0 10"/>
</svg>
</span>
{% else %}
<span class="color-error" title="{{ 'Offline' | trans }}">
<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10" fill="currentColor" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M8 13A5 5 0 1 0 8 3a5 5 0 0 0 0 10"/>
</svg>
</span>
{% endif %}
</h2>
<hr />
{% if server.info %}
<h3 class="padding-y-8-px text-align-right">{{ 'Info' | trans }}</h3>
<table>
{% for key, value in server.info %}
<tr>
<td class="text-align-left">{{ key }}</td>
<td class="text-align-left">{{ value }}</td>
</tr>
{% endfor %}
</table>
{% endif %}
{% if server.session %}
<h3 class="padding-y-8-px text-align-right">{{ 'Session' | trans }}</h3>
<table>
<tr>
<th class="text-align-left">{{ 'Player' | trans }}</th>
<th class="text-align-center">{{ 'Frags' | trans }}</th>
<th class="text-align-center">{{ 'Time' | trans }}</th>
</tr>
{% for player in server.session %}
<tr>
<td class="text-align-left">{{ player.Name }}</td>
<td class="text-align-center">{{ player.Frags }}</td>
<td class="text-align-center">
{% if player.TimeF == '59:59' %}
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" viewBox="0 0 16 16">
<path d="M5.68 5.792 7.345 7.75 5.681 9.708a2.75 2.75 0 1 1 0-3.916ZM8 6.978 6.416 5.113l-.014-.015a3.75 3.75 0 1 0 0 5.304l.014-.015L8 8.522l1.584 1.865.014.015a3.75 3.75 0 1 0 0-5.304l-.014.015zm.656.772 1.663-1.958a2.75 2.75 0 1 1 0 3.916z"/>
</svg>
{% else %}
{{ player.TimeF }}
{% endif %}
</td>
</tr>
{% endfor %}
</table>
{% endif %}
{% if server.online %}
<a name="online"></a>
<h3 class="padding-y-8-px text-align-right">
{{ 'Online' | trans }}
<a href="{{ path('rss_online', { crc32server : server.crc32server }) }}" title="{{ 'RSS' | trans }}"><svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" viewBox="0 0 16 16">
<path d="M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2zm1.5 2.5c5.523 0 10 4.477 10 10a1 1 0 1 1-2 0 8 8 0 0 0-8-8 1 1 0 0 1 0-2m0 4a6 6 0 0 1 6 6 1 1 0 1 1-2 0 4 4 0 0 0-4-4 1 1 0 0 1 0-2m.5 7a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3"/>
</svg></a>
</h3>
<table>
<tr>
<th class="text-align-left">
{{ 'Time' | trans }} {{ sort(request, server.crc32server, 'online', 'time') }}
</th>
<th class="text-align-center">
{{ 'Players' | trans }} {{ sort(request, server.crc32server, 'online', 'players') }}
</th>
<th class="text-align-center">
{{ 'Bots' | trans }} {{ sort(request, server.crc32server, 'online', 'bots') }}
</th>
<th class="text-align-center">
{{ 'Total' | trans }} {{ sort(request, server.crc32server, 'online', 'total') }}
</th>
</tr>
{% for online in server.online %}
<tr>
<td class="text-align-left">{{ online.time | date('r') }}</td>
<td class="text-align-center">{{ online.players }}</td>
<td class="text-align-center">{{ online.bots }}</td>
<td class="text-align-center">{{ online.total }}</td>
</tr>
{% endfor %}
</table>
{% endif %}
{% if server.players %}
<a name="players"></a>
<h3 class="padding-y-8-px text-align-right">
{{ 'Players' | trans }}
<a href="{{ path('rss_players', { crc32server : server.crc32server }) }}" title="{{ 'RSS' | trans }}"><svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" viewBox="0 0 16 16">
<path d="M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2zm1.5 2.5c5.523 0 10 4.477 10 10a1 1 0 1 1-2 0 8 8 0 0 0-8-8 1 1 0 0 1 0-2m0 4a6 6 0 0 1 6 6 1 1 0 1 1-2 0 4 4 0 0 0-4-4 1 1 0 0 1 0-2m.5 7a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3"/>
</svg></a>
</h3>
<table>
<tr>
<th class="text-align-center">
{{ 'Frags' | trans }} {{ sort(request, server.crc32server, 'players', 'frags') }}
</th>
<th class="text-align-left">
{{ 'Name' | trans }} {{ sort(request, server.crc32server, 'players', 'name') }}
</th>
<th class="text-align-left">
{{ 'Online' | trans }} {{ sort(request, server.crc32server, 'players', 'online') }}
</th>
<th class="text-align-left">
{{ 'Joined' | trans }} {{ sort(request, server.crc32server, 'players', 'joined') }}
</th>
</tr>
{% for player in server.players %}
<tr>
<td class="text-align-center">{{ player.frags }}</td>
<td class="text-align-left">{{ player.name }}</td>
<td class="text-align-left">{{ player.online | format_date }}</td>
<td class="text-align-left">{{ player.joined | format_date }}</td>
</tr>
{% endfor %}
</table>
{% endif %}
</div>
{% endblock %}
Loading…
Cancel
Save