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. 159
      src/Controller/MainController.php
  7. 202
      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. 237
      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"
# Masters list, server:port comma separated # Masters list, server:port comma separated
APP_MASTERS="" 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 @@
"twig/extra-bundle": "^3.8", "twig/extra-bundle": "^3.8",
"twig/intl-extra": "^3.8", "twig/intl-extra": "^3.8",
"twig/twig": "^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": { "config": {
"allow-plugins": { "allow-plugins": {

1
config/services.yaml

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

31
migrations/Version20240111202908.php

@ -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;
use App\Entity\Online; use App\Entity\Online;
use App\Entity\Player; use App\Entity\Player;
use App\Entity\Server;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
class CrontabController extends AbstractController class CrontabController extends AbstractController
@ -25,37 +27,133 @@ class CrontabController extends AbstractController
)] )]
public function index( public function index(
?Request $request, ?Request $request,
TranslatorInterface $translatorInterface,
EntityManagerInterface $entityManagerInterface EntityManagerInterface $entityManagerInterface
): Response ): Response
{ {
// Get HLServers config // Prevent multi-thread execution
if ($hlservers = file_get_contents($this->getParameter('app.hlservers'))) $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)
{ {
$hlservers = []; 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)
{
$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 // Collect servers info
$servers = []; $servers = [];
foreach ($hlservers as $hlserver) foreach ((array) $entityManagerInterface->getRepository(Server::class)->findBy(
[
'crc32server' => (int) $request->get('crc32server')
],
[
'id' => 'ASC'
],
) as $server)
{ {
try try
{ {
$server = new \xPaw\SourceQuery\SourceQuery(); $query = new \xPaw\SourceQuery\SourceQuery();
$server->Connect( $query->Connect(
$hlserver->host, false === filter_var($server->getHost(), FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) ? $server->getHost() : "[{$server->getHost()}]",
$hlserver->port $server->port
); );
if ($server->Ping()) if ($query->Ping())
{ {
if ($info = (array) $server->GetInfo()) if ($info = (array) $query->GetInfo())
{ {
// Filter response // Filter response
$bots = isset($info['Bots']) && $info['Bots'] > 0 ? (int) $info['Bots'] : 0; $bots = isset($info['Bots']) && $info['Bots'] > 0 ? (int) $info['Bots'] : 0;
@ -64,7 +162,7 @@ class CrontabController extends AbstractController
// Generate CRC32 server ID // Generate CRC32 server ID
$crc32server = crc32( $crc32server = crc32(
$hlserver->host . ':' . $hlserver->port $server->host . ':' . $server->port
); );
// Get last online value // Get last online value
@ -121,7 +219,7 @@ class CrontabController extends AbstractController
// Update player stats // Update player stats
if ($players) if ($players)
{ {
foreach ((array) $server->GetPlayers() as $session) foreach ((array) $query->GetPlayers() as $session)
{ {
// Validate fields // Validate fields
if if
@ -231,7 +329,7 @@ class CrontabController extends AbstractController
finally finally
{ {
$server->Disconnect(); $query->Disconnect();
} }
} }

159
src/Controller/MainController.php

@ -11,6 +11,8 @@ use Symfony\Component\HttpFoundation\Request;
use App\Entity\Online; use App\Entity\Online;
use App\Entity\Player; use App\Entity\Player;
use App\Entity\Server;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
class MainController extends AbstractController class MainController extends AbstractController
@ -28,139 +30,56 @@ class MainController extends AbstractController
EntityManagerInterface $entityManagerInterface EntityManagerInterface $entityManagerInterface
): Response ): 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 // Collect servers info
$servers = []; $servers = [];
foreach ($hlservers as $hlserver) foreach ((array) $entityManagerInterface->getRepository(Server::class)->findAll() as $server)
{ {
// Init defaults // Init defaults
$info = []; $status = false;
$session = [];
$online = [];
$players = [];
// Generate CRC32 ID
$crc32server = crc32(
$hlserver->host . ':' . $hlserver->port
);
// Prepare aliases
$aliases = [];
foreach ($hlserver->alias as $value) $info =
{ [
$alias = new \xPaw\SourceQuery\SourceQuery(); 'Protocol' => null,
'HostName' => null,
$alias->Connect( 'Map' => null,
$value->host, 'ModDir' => null,
$value->port 'ModDesc' => null,
); 'AppID' => null,
'Players' => null,
$aliases[] = [ 'MaxPlayers' => null,
'host' => $value->host, 'Bots' => null,
'port' => $value->port, 'Dedicated' => null,
'status' => $alias->Ping() 'Os' => null,
]; 'Password' => null,
} 'Secure' => null,
'Version' => null
];
// Request server info // Request server info
try try
{ {
$server = new \xPaw\SourceQuery\SourceQuery(); $node = new \xPaw\SourceQuery\SourceQuery();
$server->Connect( $node->Connect(
$hlserver->host, false === filter_var($server->getHost(), FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) ? $server->getHost() : "[{$server->getHost()}]",
$hlserver->port $server->getPort()
); );
if ($server->Ping()) if ($node->Ping())
{ {
if ($info = (array) $server->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' => $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; $status = true;
}
else foreach ((array) $node->GetInfo() as $key => $value)
{ {
$status = false; $info[$key] = $value;
}
} }
} }
catch (Exception $error) catch (Exception $error)
{ {
continue; throw $this->createNotFoundException();
} }
catch (\Throwable $error) catch (\Throwable $error)
@ -170,20 +89,18 @@ class MainController extends AbstractController
finally finally
{ {
$server->Disconnect(); $node->Disconnect();
} }
// Add server // Add server
$servers[] = [ $servers[] = [
'crc32server' => $crc32server, 'crc32server' => $server->getCrc32server(),
'host' => $hlserver->host, 'host' => $server->getHost(),
'port' => $hlserver->port, 'port' => $server->getPort(),
'description' => $hlserver->description, 'added' => $server->getAdded(),
'aliases' => $aliases, 'updated' => $server->getUpdated(),
'online' => $server->getOnline(),
'info' => $info, 'info' => $info,
'session' => $session,
'online' => $online,
'players' => $players,
'status' => $status 'status' => $status
]; ];
} }

202
src/Controller/RssController.php

@ -11,6 +11,8 @@ use Symfony\Component\HttpFoundation\Request;
use App\Entity\Online; use App\Entity\Online;
use App\Entity\Player; use App\Entity\Player;
use App\Entity\Server;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
class RssController extends AbstractController class RssController extends AbstractController
@ -32,78 +34,47 @@ class RssController extends AbstractController
EntityManagerInterface $entityManagerInterface EntityManagerInterface $entityManagerInterface
): Response ): Response
{ {
// Get HLServers config $online = [];
if ($hlservers = file_get_contents($this->getParameter('app.hlservers')))
foreach ($entityManagerInterface->getRepository(Online::class)->findBy(
[
'crc32server' => $request->get('crc32server')
],
[
'id' => 'DESC' // same as online.time but faster
],
10
) as $value)
{ {
$hlservers = json_decode($hlservers); $online[] =
[
'id' => $value->getId(),
'bots' => $value->getBots(),
'players' => $value->getPlayers(),
'total' => $value->getTotal(),
'time' => $value->getTime()
];
} }
else // Response
{ $response = new Response();
$hlservers = [];
}
// 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(
[
'crc32server' => $crc32server
],
[
'id' => 'DESC' // same as online.time but faster
],
10
);
$result = []; $response->headers->set(
'Content-Type',
'text/xml'
);
foreach ($online as $value) return $this->render(
{ 'default/rss/online.xml.twig',
$result[] = [
[ 'server' =>
'id' => $value->getId(),
'bots' => $value->getBots(),
'players' => $value->getPlayers(),
'total' => $value->getTotal(),
'time' => $value->getTime()
];
}
// Response
$response = new Response();
$response->headers->set(
'Content-Type',
'text/xml'
);
return $this->render(
'default/rss/online.xml.twig',
[ [
'server' => 'crc32server' => $request->get('crc32server'),
[ 'online' => $online
'crc32' => $crc32server, ]
'host' => $hlserver->host, ],
'port' => $hlserver->port, $response
], );
'online' => $result
],
$response
);
}
throw $this->createNotFoundException(); throw $this->createNotFoundException();
} }
@ -125,77 +96,44 @@ class RssController extends AbstractController
EntityManagerInterface $entityManagerInterface EntityManagerInterface $entityManagerInterface
): Response ): Response
{ {
// Get HLServers config $players = [];
if ($hlservers = file_get_contents($this->getParameter('app.hlservers')))
foreach ($entityManagerInterface->getRepository(Player::class)->findBy(
[
'crc32server' => $request->get('crc32server')
],
[
'id' => 'DESC'
],
10
) as $value)
{ {
$hlservers = json_decode($hlservers); $result[] =
[
'id' => $value->getId(),
'name' => $value->getName(),
'joined' => $value->getJoined()
];
} }
else // Response
{ $response = new Response();
$hlservers = [];
}
// 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(
[
'crc32server' => $crc32server
],
[
'id' => 'DESC'
],
10
);
$result = []; $response->headers->set(
'Content-Type',
'text/xml'
);
foreach ($players as $value) return $this->render(
{ 'default/rss/players.xml.twig',
$result[] = [
'server' =>
[ [
'id' => $value->getId(), 'crc32server' => $request->get('crc32server'),
'name' => $value->getName(), 'players' => $players
'joined' => $value->getJoined() ]
]; ],
} $response
);
// Response
$response = new Response();
$response->headers->set(
'Content-Type',
'text/xml'
);
return $this->render(
'default/rss/players.xml.twig',
[
'server' =>
[
'crc32' => $crc32server,
'host' => $hlserver->host,
'port' => $hlserver->port,
],
'players' => $result
],
$response
);
}
throw $this->createNotFoundException();
} }
} }

183
src/Controller/ServerController.php

@ -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 @@
<?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 @@
<?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 @@
{% block header_container %} {% block header_container %}
<header> <header>
{% block header_content %} {% 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"> <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="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"/> <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 @@
{% block footer_content %} {% block footer_content %}
{% if app.masters %} {% if app.masters %}
<small class="float-left"> <small class="float-left">
<strong>{{ 'Master' | trans }}:</strong> <strong>{{ 'Master tracker' | trans }}:</strong>
{% for i, master in app.masters | split(',') %} {% for i, master in app.masters | split(',') %}
{% if i %}&bull;{% endif %} {{ master }} {% if i %}&bull;{% endif %} {{ master }}
{% endfor %} {% endfor %}

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

@ -1,96 +1,23 @@
{% 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' %} {% extends 'default/layout.html.twig' %}
{% block main_content %} {% block main_content %}
{% for server in servers %} <h2>{{ 'Servers' | trans }}</h2>
<div class="border-default padding-8-px margin-y-8-px"> <table class="margin-y-8-px">
{% if server.info.HostName is defined %} <tr>
<h2>{{ server.info.HostName }} [#{{ server.crc32server }}]</h2> <th class="text-align-center">{{ 'Status' | trans }}</th>
{% else %} <th class="text-align-left">{{ 'Name' | trans }}</th>
<h2>#{{ server.crc32server }}</h2> <th class="text-align-left">{{ 'Map' | trans }}</th>
{% endif %} <th class="text-align-left">{{ 'Added' | trans }}</th>
<hr /> <th class="text-align-left">{{ 'Online' | trans }}</th>
{% if server.description.title is defined %} <th class="text-align-center">{{ 'Players' | trans }}</th>
<div class="margin-y-8-px"> <th class="text-align-center">{{ 'Max' | trans }}</th>
{{ server.description.title }} <th class="text-align-center">{{ 'Bots' | trans }}</th>
</div> <th class="text-align-center">{{ 'Actions' | trans }}</th>
{% endif %} </tr>
{% if server.description.links %} {% if servers %}
<div class="margin-y-8-px"> {% for server in servers %}
<ul> <tr>
{% for link in server.description.links %} <td class="text-align-center">
<li class="margin-y-8-px"><a href="{{ link }}">{{ link }}</a></li> {% if server.status %}
{% endfor %}
</ul>
</div>
{% endif %}
<h3 class="padding-y-8-px text-align-right">{{ 'Address' | trans }}</h3>
<div>
{{ server.host }}:{{ server.port }}
{% 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 %}
</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 }}"> <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"> <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"/> <path fill-rule="evenodd" d="M8 13A5 5 0 1 0 8 3a5 5 0 0 0 0 10"/>
@ -103,111 +30,25 @@
</svg> </svg>
</span> </span>
{% endif %} {% endif %}
</div> </td>
{% endfor %} <td class="text-align-left">{{ server.info.HostName }}</td>
{% endif %} <td class="text-align-left">{{ server.info.Map }}</td>
{% if server.info %} <td class="text-align-left">{{ server.added | format_date }}</td>
<h3 class="padding-y-8-px text-align-right">{{ 'Info' | trans }}</h3> <td class="text-align-left">{{ server.online | format_date }}</td>
<table> <td class="text-align-center">{{ server.info.Players }}</td>
{% for key, value in server.info %} <td class="text-align-center">{{ server.info.MaxPlayers }}</td>
<tr> <td class="text-align-center">{{ server.info.Bots }}</td>
<td class="text-align-left">{{ key }}</td> <td class="text-align-center">
<td class="text-align-left">{{ value }}</td> <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">
</tr> <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"/>
{% endfor %} </svg></a>
</table> </td>
{% endif %} </tr>
{% if server.session %} {% endfor %}
<h3 class="padding-y-8-px text-align-right">{{ 'Session' | trans }}</h3> {% else %}
<table> <tr>
<tr> <td class="text-align-center" colspan="9">{{ 'Not found' | trans }}</td>
<th class="text-align-left">{{ 'Player' | trans }}</th> </tr>
<th class="text-align-center">{{ 'Frags' | trans }}</th> {% endif %}
<th class="text-align-center">{{ 'Time' | trans }}</th> </table>
</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="{{ '%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 %}
<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="{{ '%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 %} {% endblock %}

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

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

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

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

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

@ -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