This commit is contained in:
Nicolas Debrigode 2024-01-12 15:51:42 +01:00
parent 3af1dc7460
commit b84fc03c4f
17 changed files with 339 additions and 15 deletions

View File

@ -0,0 +1,17 @@
config:
top: 20
daystatsfile: /home/nidebr/data/DEV/asstats/stats/asstats_day.txt"
top_intervals:
30days:
hours: 720 #30*24
statsfile: /home/nidebr/data/DEV/asstats/stats/asstats_month.txt
label: 30 days
3months:
hours: 2160 #30*24*3
statsfile: /home/nidebr/data/DEV/asstats/stats/asstats_3month.txt
label: 3 months
1year:
hours: 8760 #30*24*3
statsfile: /home/nidebr/data/DEV/asstats/stats/asstats_year.txt
label: 1 year

View File

@ -11,12 +11,12 @@
"symfony/console": "7.0.*",
"symfony/dotenv": "7.0.*",
"symfony/flex": "^2",
"symfony/form": "7.0.*",
"symfony/framework-bundle": "7.0.*",
"symfony/runtime": "7.0.*",
"symfony/twig-bundle": "7.0.*",
"symfony/yaml": "7.0.*",
"twig/extra-bundle": "^3.0",
"twig/intl-extra": "^3.0",
"twig/string-extra": "^3.0",
"twig/twig": "^3.0"
},

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -16,14 +16,43 @@ class ConfigApplication
return \sprintf('%s/../../', __DIR__);
}
private static function getConfig(): mixed
private static function getConfig(): array
{
$input = file_get_contents(self::getRootPathApp().self::CONFIG_APP_FILE);
return Yaml::parse((string) $input);
return (array) Yaml::parse((string) $input);
}
private static function getConfigAsStats(): array
{
$input = file_get_contents(self::getRootPathApp().self::CONFIG_ASSTATS_FILE);
return (array) Yaml::parse((string) $input);
}
public static function getRelease(): mixed
{
return self::getConfig()['application']['release'];
}
public static function getLocale(): mixed
{
return self::getConfig()['application']['locale'];
}
public static function getAsStatsTopInterval(): mixed
{
if (false === \array_key_exists('top_intervals', self::getConfigAsStats())) {
return false;
}
return self::getConfigAsStats()['top_intervals'];
}
public static function getAsStatsConfig(): mixed
{
if (false === \array_key_exists('config', self::getConfigAsStats())) {
return false;
}
return self::getConfigAsStats()['config'];
}
}

View File

@ -6,6 +6,7 @@ namespace App\Controller;
use App\Application\ConfigApplication;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\RequestStack;
class BaseController extends AbstractController
{
@ -13,7 +14,10 @@ class BaseController extends AbstractController
public function __construct(
ConfigApplication $Config,
RequestStack $requestStack,
) {
$this->base_data['release'] = $Config::getRelease();
$this->base_data['top_interval'] = $Config::getAsStatsTopInterval();
$this->base_data['request'] = $requestStack->getCurrentRequest()->query->all();
}
}

View File

@ -4,9 +4,12 @@ declare(strict_types=1);
namespace App\Controller;
use App\Application\ConfigApplication;
use App\Util\Annotation\Menu;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
#[Menu('top_as')]
class IndexController extends BaseController
{
#[Route(
@ -14,11 +17,49 @@ class IndexController extends BaseController
name: 'index',
methods: ['GET'],
)]
public function index(): Response
public function index(
ConfigApplication $Config,
): Response
{
$this->base_data['content_wrapper']['titre'] = 'dede';
/*if(\isset($this->base_data['request']['top'])) {
$top = $this->base_data['request']['top'];
} else {
$top = $Config::getAsStatsConfig()['top'];
}*/
dump($this->base_data);
dump($this->base_data['request']);
/*if (\array_key_exists('top', $this->base_data['request'])) {
dump(1);
} else {
dump(2);
}*/
$this->base_data['content_wrapper']['titre'] = \sprintf(
'Top %s (%s)',
\array_key_exists('top', $this->base_data['request']) :: $this->base_data['request']['top'] ?? $Config::getAsStatsConfig()['top'],
'24 hours'
);
return $this->render('pages/index.html.twig', [
'base_data' => $this->base_data,
]);
}
#[Route(
path: '/{topinterval}',
name: 'index_topinterval',
methods: ['GET'],
)]
public function indexTopInterval(
ConfigApplication $Config,
string $topinterval,
): Response{
$this->base_data['content_wrapper']['titre'] = \sprintf(
'Top %s (%s)',
$Config::getAsStatsConfig()['top'],
$Config::getAsStatsTopInterval()[$topinterval]['label']
);
return $this->render('pages/index.html.twig', [
'base_data' => $this->base_data,

View File

@ -0,0 +1,102 @@
<?php
declare(strict_types=1);
namespace App\Twig\EventSubscriber;
use App\Util\Annotation\Menu;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\ControllerEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Twig\Environment;
readonly class MenuAnnotationControllerEventSubscriber implements EventSubscriberInterface
{
public function __construct(private Environment $twig)
{
}
/**
* @return array<string, string>
*/
public static function getSubscribedEvents(): array
{
return [
KernelEvents::CONTROLLER => 'handle',
];
}
public function handle(ControllerEvent $event): void
{
$controllers = $event->getController();
if (!is_array($controllers)) {
return;
}
[$controller, $methodName] = $controllers;
if (!$controller instanceof AbstractController) {
return;
}
$methodAttribute = $this->getMethodeAttribute($controller, $methodName);
if (null !== $methodAttribute) {
$this->registerMenuToTwig($methodAttribute);
return;
}
$classAttribute = $this->getClassAttribute($controller);
if (null !== $classAttribute) {
$this->registerMenuToTwig($classAttribute);
return;
}
$this->registerNullMenuToTwig();
}
private function registerNullMenuToTwig(): void
{
$this->twig->addGlobal('current_menu', null);
}
private function registerMenuToTwig(Menu $menu): void
{
$this->twig->addGlobal('current_menu', $menu->getDomain());
}
private function getClassAttribute(AbstractController $controller): ?Menu
{
$reflectionClass = new \ReflectionClass($controller);
$attributes = $reflectionClass->getAttributes(Menu::class);
if (1 !== \count($attributes)) {
return null;
}
return $attributes[0]->newInstance();
}
/**
* @throws \ReflectionException
*/
private function getMethodeAttribute(AbstractController $controller, string $methodName): ?Menu
{
$reflectionObject = new \ReflectionObject($controller);
$reflectionMethod = $reflectionObject->getMethod($methodName);
$attributes = $reflectionMethod->getAttributes(Menu::class);
if (1 !== \count($attributes)) {
return null;
}
return $attributes[0]->newInstance();
}
}

View File

@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace App\Twig;
use App\Util\Theme\Icon;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;
class IconExtension extends AbstractExtension
{
/**
* @return array<TwigFunction>
*/
public function getFunctions(): array
{
return [
new TwigFunction('icon', [$this, 'icon'], ['is_safe' => ['html']]),
];
}
public function icon(string $name, ?string $addClass = null): string
{
$icon = Icon::byName($name);
if ($addClass) {
return \sprintf('<i class="ti %s %s"></i>', $icon->value, $addClass);
}
return \sprintf('<i class="ti %s"></i>', $icon->value);
}
}

View File

@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
namespace App\Util\Annotation;
#[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD)]
class Menu
{
public function __construct(
private readonly string $domain,
) {
}
public function getDomain(): string
{
return $this->domain;
}
}

35
src/Util/Theme/Icon.php Normal file
View File

@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace App\Util\Theme;
enum Icon: string
{
// Menu
case menu_home = 'ti-home icon';
// Form
case filter = 'ti-filter';
/**
* @return array<string>
*/
public static function names(): array
{
return \array_map(static function (self $icon) {
return $icon->name;
}, self::cases());
}
public static function byName(string $name): self
{
foreach (self::cases() as $icon) {
if ($name === $icon->name) {
return $icon;
}
}
throw new \LogicException(\sprintf('Icône "%s" inconnue, merci dutiliser une des icônes suivantes : %s', $name, \implode(', ', self::names())));
}
}

View File

@ -1,4 +1,4 @@
<header class="navbar navbar-expand-md navbar-light d-print-none">
<header class="navbar navbar-expand-md d-print-none">
<div class="container-xl">
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbar-menu">
<span class="navbar-toggler-icon"></span>
@ -8,15 +8,20 @@
<span class="text-uppercase">AS-Stats</span>
</a>
</h1>
<div class="navbar-nav flex-row order-md-last">
<div class="nav-item">
<span class="nav-link text-danger fw-bold" id="branch"></span>
</div>
</div>
<div class="navbar-nav flex-row order-md-last">
<div class="d-none d-md-flex">
{#% include "navigation/menu/_user_menu.html.twig" %#}
<div class="my-1 flex-grow-1 flex-md-grow-0 order-first">
<form method="get" autocomplete="off" novalidate="">
<div class="input-icon">
<span class="input-icon-addon">
{{ icon('filter') }}
</span>
<input type="text" name='top' class="form-control" value="{{ base_data.request.top|default('') }}" placeholder="Top AS">
</div>
</form>
</div>
</div>
{% include "navigation/_menu.html.twig" %}
</div>
</header>

View File

@ -7,7 +7,7 @@
<title>AS-Stats | {% block title %}{{ title|default('') }}{% endblock %}</title>
<link rel="stylesheet" href="{{ asset('assets/templates/tabler.min.css') }}">
<link rel="stylesheet" href="{{ asset('assets/plugins/tabler-icons/tabler-icons.min.css') }}">
<link rel="stylesheet" href="{{ asset('assets/tabler-icons/tabler-icons.min.css') }}">
{% block css %}{% endblock %}
</head>

View File

@ -0,0 +1,39 @@
<div class="collapse navbar-collapse" id="navbar-menu">
<div class="d-flex flex-column flex-md-row flex-fill align-items-stretch align-items-md-center">
<ul class="navbar-nav">
{% if base_data.top_interval %}
<li class="nav-item dropdown {% if "top_as" == current_menu %}active{% endif %}">
<a class="nav-link dropdown-toggle" href="#" data-bs-toggle="dropdown" data-bs-auto-close="outside" role="button" aria-expanded="false" >
<span class="nav-link-icon d-md-none d-lg-inline-block">
{{ icon('menu_home') }}
</span>
<span class="nav-link-title">
Top AS
</span>
</a>
<div class="dropdown-menu">
<a class="dropdown-item" href="{{ path('index') }}">
Top AS - 24 hours
</a>
{% for key, item in base_data.top_interval %}
<a class="dropdown-item" href="{{ path('index_topinterval', {'topinterval': key}) }}">
Top AS - {{ item.label }}
</a>
{% endfor %}
</div>
</li>
{% else %}
<li class="nav-item">
<a class="nav-link" href="{{ path('index') }}">
<span class="nav-link-icon d-md-none d-lg-inline-block">
{{ icon('menu_home') }}
</span>
<span class="nav-link-title">
Top AS
</span>
</a>
</li>
{% endif %}
</ul>
</div>
</div>