mirror of
https://github.com/nidebr/as-stats-gui.git
synced 2025-02-20 11:23:18 +08:00
save
This commit is contained in:
parent
e81b652597
commit
fdc24a00c2
3
.env
3
.env
@ -17,3 +17,6 @@ APP_ENV=prod
|
||||
APP_SECRET=644192852c93fabc1bb219ad6458fe25
|
||||
|
||||
CORS_ALLOW_ORIGIN='^https?://(localhost|127\.0\.0\.1)(:[0-9]+)?$'
|
||||
|
||||
PEERINGDB_HOST='https://peeringdb.com'
|
||||
PEERINGDB_URI='/api/'
|
||||
|
@ -3,6 +3,7 @@ config:
|
||||
daystatsfile: '/home/nidebr/data/DEV/asstats/stats/asstats_day.txt'
|
||||
knownlinksfile: '/home/nidebr/data/DEV/asstats/conf/knownlinks'
|
||||
asinfofile: 'ressources/asinfo.db'
|
||||
myasn: 34863
|
||||
graph:
|
||||
showv6: true
|
||||
outispositive: false
|
||||
|
@ -15,6 +15,7 @@
|
||||
"symfony/flex": "^2",
|
||||
"symfony/form": "7.0.*",
|
||||
"symfony/framework-bundle": "7.0.*",
|
||||
"symfony/http-client": "7.0.*",
|
||||
"symfony/process": "7.0.*",
|
||||
"symfony/property-access": "7.0.*",
|
||||
"symfony/runtime": "7.0.*",
|
||||
|
@ -27,3 +27,8 @@ services:
|
||||
App\Application\ConfigApplication:
|
||||
arguments:
|
||||
- '%kernel.environment%'
|
||||
|
||||
## peeringdb client
|
||||
App\Client\PeeringDbClient:
|
||||
$host: '%env(PEERINGDB_HOST)%'
|
||||
$uri: '%env(PEERINGDB_URI)%'
|
||||
|
@ -15,3 +15,4 @@ parameters:
|
||||
|
||||
ignoreErrors:
|
||||
- '#Only booleans are allowed#'
|
||||
- '#Cannot cast mixed to int.#'
|
||||
|
@ -66,6 +66,19 @@ class ConfigApplication
|
||||
return self::getAsStatsConfig()['top'];
|
||||
}
|
||||
|
||||
public static function getAsStatsConfigMyAsn(): ?int
|
||||
{
|
||||
if (false === \array_key_exists('myasn', self::getAsStatsConfig())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!self::getAsStatsConfig()['myasn']) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return self::getAsStatsConfig()['myasn'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ConfigErrorException
|
||||
*/
|
||||
|
64
src/Client/PeeringDbClient.php
Normal file
64
src/Client/PeeringDbClient.php
Normal file
@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Client;
|
||||
|
||||
use App\Util\EndWithFunction;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
|
||||
use Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface;
|
||||
use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
|
||||
use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
|
||||
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
|
||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
|
||||
class PeeringDbClient extends AbstractController
|
||||
{
|
||||
public function __construct(
|
||||
readonly string $host,
|
||||
readonly string $uri,
|
||||
private HttpClientInterface $client,
|
||||
) {
|
||||
if (!EndWithFunction::endsWith($uri, '/')) {
|
||||
$uri = \sprintf('%s/', $uri);
|
||||
}
|
||||
|
||||
$this->client = $client->withOptions([
|
||||
'base_uri' => \sprintf('%s%s', $host, $uri),
|
||||
'verify_host' => false,
|
||||
'verify_peer' => false,
|
||||
'timeout' => 30,
|
||||
'max_duration' => 30,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws TransportExceptionInterface
|
||||
*/
|
||||
public function get(string $url): array
|
||||
{
|
||||
$response = $this->client->request('GET', $url, [
|
||||
'headers' => [
|
||||
'Content-Type' => 'application/json',
|
||||
],
|
||||
]);
|
||||
|
||||
try {
|
||||
return [
|
||||
'status_code' => $response->getStatusCode(),
|
||||
'response' => $response->toArray(),
|
||||
];
|
||||
} catch (ClientExceptionInterface|TransportExceptionInterface|DecodingExceptionInterface|ServerExceptionInterface|RedirectionExceptionInterface $e) {
|
||||
if (429 === $e->getCode()) {
|
||||
$this->addFlash('warning', 'Request was throttled by peeringdb.com API server.');
|
||||
}
|
||||
|
||||
return [
|
||||
'status_code' => $e->getCode(),
|
||||
'message' => $e->getMessage(),
|
||||
'response' => [],
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
87
src/Controller/IXStatsController.php
Normal file
87
src/Controller/IXStatsController.php
Normal file
@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Application\ConfigApplication;
|
||||
use App\Exception\ConfigErrorException;
|
||||
use App\Exception\DbErrorException;
|
||||
use App\Form\SelectMyIXForm;
|
||||
use App\Repository\GetAsDataRepository;
|
||||
use App\Repository\KnowlinksRepository;
|
||||
use App\Repository\PeeringDBRepository;
|
||||
use App\Util\Annotation\Menu;
|
||||
use Doctrine\DBAL\Exception;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
|
||||
#[Route(
|
||||
path: '/ix',
|
||||
)]
|
||||
#[Menu('view_ix')]
|
||||
class IXStatsController extends BaseController
|
||||
{
|
||||
protected array $data = [];
|
||||
|
||||
/**
|
||||
* @throws ConfigErrorException
|
||||
* @throws DbErrorException
|
||||
* @throws Exception
|
||||
*/
|
||||
#[Route(
|
||||
path: '/my-ix',
|
||||
name: 'my_ix',
|
||||
methods: ['GET|POST'],
|
||||
)]
|
||||
public function history(
|
||||
Request $request,
|
||||
PeeringDBRepository $peeringDBRepository,
|
||||
GetAsDataRepository $asDataRepository,
|
||||
ConfigApplication $Config,
|
||||
): Response {
|
||||
$this->base_data['content_wrapper']['titre'] = 'My IX Stats';
|
||||
|
||||
$form = $this->createForm(SelectMyIXForm::class);
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$ixInfo = $peeringDBRepository->getIXInfo((int) $form->get('myix')->getData());
|
||||
|
||||
$this->base_data['content_wrapper']['titre'] = \sprintf(
|
||||
'Top %s (%s)',
|
||||
$this->base_data['top'],
|
||||
'24 hours'
|
||||
);
|
||||
|
||||
$this->base_data['content_wrapper']['small'] = $ixInfo['name'];
|
||||
$this->data['data'] = $asDataRepository::get(
|
||||
$this->base_data['top'],
|
||||
'',
|
||||
[],
|
||||
$peeringDBRepository->getIXMembers((int) $form->get('myix')->getData()),
|
||||
);
|
||||
|
||||
$this->data['start'] = time() - 24 * 3600;
|
||||
$this->data['end'] = time();
|
||||
$this->data['graph_size'] = [
|
||||
'width' => $Config::getAsStatsConfigGraph()['top_graph_width'],
|
||||
'height' => $Config::getAsStatsConfigGraph()['top_graph_height'],
|
||||
];
|
||||
$this->data['selectedLinks'] = [];
|
||||
|
||||
return $this->render('pages/ix/my_ix/show.html.twig', [
|
||||
'base_data' => $this->base_data,
|
||||
'data' => $this->data,
|
||||
'knownlinks' => KnowlinksRepository::get(),
|
||||
'form' => $form->createView(),
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->render('pages/ix/my_ix/index.html.twig', [
|
||||
'base_data' => $this->base_data,
|
||||
'form' => $form->createView(),
|
||||
]);
|
||||
}
|
||||
}
|
@ -46,7 +46,7 @@ class IndexController extends BaseController
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$this->data['data'] = $asDataRepository::get($this->base_data['top'], null, (array) $form->getData());
|
||||
$this->data['data'] = $asDataRepository::get($this->base_data['top'], '', (array) $form->getData());
|
||||
$this->data['selectedLinks'] = KnowlinksRepository::select((array) $form->getData());
|
||||
} else {
|
||||
$this->data['data'] = $asDataRepository::get($this->base_data['top']);
|
||||
|
50
src/Form/SelectMyIXForm.php
Normal file
50
src/Form/SelectMyIXForm.php
Normal file
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Form;
|
||||
|
||||
use App\Application\ConfigApplication;
|
||||
use App\Client\PeeringDbClient;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class SelectMyIXForm extends AbstractType
|
||||
{
|
||||
public function __construct(private PeeringDbClient $peeringDbClient)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $options
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$peering_data = $this->peeringDbClient->get(\sprintf('netixlan?asn=%s', ConfigApplication::getAsStatsConfigMyAsn()));
|
||||
|
||||
$data = [];
|
||||
if (200 === $peering_data['status_code']) {
|
||||
foreach ($peering_data['response']['data'] as $myix) {
|
||||
$data[$myix['name']] = $myix['ix_id'];
|
||||
}
|
||||
}
|
||||
|
||||
ksort($data);
|
||||
|
||||
$builder
|
||||
->add('myix', ChoiceType::class, [
|
||||
'label' => false,
|
||||
'choices' => $data,
|
||||
'choice_translation_domain' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'csrf_protection' => false,
|
||||
]);
|
||||
}
|
||||
}
|
@ -66,7 +66,7 @@ class DbAsStatsRepository
|
||||
->setMaxResults($ntop)
|
||||
->fetchAllAssociative();
|
||||
}
|
||||
} catch (Exception) {
|
||||
} catch (Exception $e) {
|
||||
throw new DbErrorException(\sprintf('Problem with stats files %s', $this->dbname));
|
||||
}
|
||||
|
||||
|
@ -18,8 +18,9 @@ class GetAsDataRepository
|
||||
*/
|
||||
public static function get(
|
||||
int $top,
|
||||
?string $topInterval = null,
|
||||
array $selectedLinks = []
|
||||
string $topInterval = '',
|
||||
array $selectedLinks = [],
|
||||
array $listAsn = [],
|
||||
): array {
|
||||
if (0 === $top) {
|
||||
return [];
|
||||
@ -27,7 +28,7 @@ class GetAsDataRepository
|
||||
|
||||
$return = [];
|
||||
|
||||
if ($topInterval) {
|
||||
if ('' !== $topInterval && '0' !== $topInterval) {
|
||||
$dbName = ConfigApplication::getAsStatsConfigTopInterval()[$topInterval]['statsfile'];
|
||||
} else {
|
||||
$dbName = ConfigApplication::getAsStatsConfigDayStatsFile();
|
||||
@ -36,7 +37,7 @@ class GetAsDataRepository
|
||||
$data = new DbAsStatsRepository($dbName);
|
||||
$asInfoRepository = new DbAsInfoRepository();
|
||||
|
||||
foreach ($data->getASStatsTop($top, KnowlinksRepository::select($selectedLinks)) as $as => $nbytes) {
|
||||
foreach ($data->getASStatsTop($top, KnowlinksRepository::select($selectedLinks), $listAsn) as $as => $nbytes) {
|
||||
$return['asinfo'][$as]['info'] = $asInfoRepository->getAsInfo($as);
|
||||
|
||||
$return['asinfo'][$as]['v4'] = [
|
||||
|
44
src/Repository/PeeringDBRepository.php
Normal file
44
src/Repository/PeeringDBRepository.php
Normal file
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Repository;
|
||||
|
||||
use App\Client\PeeringDbClient;
|
||||
|
||||
class PeeringDBRepository
|
||||
{
|
||||
private PeeringDbClient $peeringDbClient;
|
||||
|
||||
public function __construct(PeeringDbClient $peeringDbClient)
|
||||
{
|
||||
$this->peeringDbClient = $peeringDbClient;
|
||||
}
|
||||
|
||||
public function getIXMembers(int $ix_id): array
|
||||
{
|
||||
$data = $this->peeringDbClient->get(\sprintf('net?ix_id=%s', $ix_id));
|
||||
|
||||
if (200 !== $data['status_code']) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$asn = [];
|
||||
foreach ($data['response']['data'] as $list) {
|
||||
$asn[] = $list['asn'];
|
||||
}
|
||||
|
||||
return $asn;
|
||||
}
|
||||
|
||||
public function getIXInfo(int $ix_id): array
|
||||
{
|
||||
$data = $this->peeringDbClient->get(\sprintf('ix?id=%s', $ix_id));
|
||||
|
||||
if (200 !== $data['status_code']) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $data['response']['data'][0];
|
||||
}
|
||||
}
|
@ -23,12 +23,13 @@ class ConfigApplicationExtension extends AbstractExtension
|
||||
{
|
||||
return [
|
||||
new TwigFunction('configapplication_graph', [$this, 'getConfigGraph']),
|
||||
new TwigFunction('configapplication_myasn', [$this, 'getConfigMyAsn']),
|
||||
];
|
||||
}
|
||||
|
||||
public function getConfigGraph(string $key): mixed
|
||||
{
|
||||
if ($key === '' || $key === '0') {
|
||||
if ('' === $key || '0' === $key) {
|
||||
return '';
|
||||
}
|
||||
|
||||
@ -38,4 +39,9 @@ class ConfigApplicationExtension extends AbstractExtension
|
||||
|
||||
return $this->configApplication::getAsStatsConfigGraph()[$key];
|
||||
}
|
||||
|
||||
public function getConfigMyAsn(): ?int
|
||||
{
|
||||
return $this->configApplication::getAsStatsConfigMyAsn();
|
||||
}
|
||||
}
|
||||
|
16
src/Util/EndWithFunction.php
Normal file
16
src/Util/EndWithFunction.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Util;
|
||||
|
||||
class EndWithFunction
|
||||
{
|
||||
public static function endsWith(string $FullStr, string $needle): bool
|
||||
{
|
||||
$StrLen = strlen($needle);
|
||||
$FullStrEnd = substr($FullStr, strlen($FullStr) - $StrLen);
|
||||
|
||||
return $FullStrEnd === $needle;
|
||||
}
|
||||
}
|
@ -9,6 +9,7 @@ enum Icon: string
|
||||
// Menu
|
||||
case menu_home = 'ti-home icon';
|
||||
case menu_view = 'ti-chart-histogram icon';
|
||||
case menu_ix = 'ti-list-details icon';
|
||||
|
||||
// Form
|
||||
case filter = 'ti-filter';
|
||||
|
@ -1,6 +1,7 @@
|
||||
<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" >
|
||||
@ -34,6 +35,7 @@
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
<li class="nav-item {% if "view_as" == current_menu %}active{% endif %}">
|
||||
<a class="nav-link" href="{{ path('history') }}">
|
||||
<span class="nav-link-icon d-md-none d-lg-inline-block">
|
||||
@ -44,6 +46,24 @@
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item dropdown {% if "view_ix" == 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_ix') }}
|
||||
</span>
|
||||
<span class="nav-link-title">
|
||||
IX Stats
|
||||
</span>
|
||||
</a>
|
||||
<div class="dropdown-menu">
|
||||
{% if configapplication_myasn() %}
|
||||
<a class="dropdown-item" href="{{ path('my_ix') }}">
|
||||
My IX
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
36
templates/core/legend_simple.html.twig
Normal file
36
templates/core/legend_simple.html.twig
Normal file
@ -0,0 +1,36 @@
|
||||
<div class="sticky-top sticky-top-legend">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h3 class="card-title">
|
||||
<strong>Legend</strong>
|
||||
</h3>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-borderless table-sm">
|
||||
<tbody>
|
||||
{% if knownlinks is defined %}
|
||||
{% for link in knownlinks %}
|
||||
<tr>
|
||||
<td class="align-middle">
|
||||
<small>{{ link.descr }}</small>
|
||||
</td>
|
||||
<td>
|
||||
<table class="table-legend">
|
||||
<tr>
|
||||
{% if configapplication_graph('brighten_negative') %}
|
||||
<td class="table-legend-td table-legend-td-brighten" style="background-color: #{{ link.color }}"> </td>
|
||||
<td class="table-legend-td table-legend-td-brighten" style="opacity: 0.73; background-color: #{{ link.color }}"> </td>
|
||||
{% else %}
|
||||
<td class="table-legend-td" style="background-color: #{{ link.color }}"> </td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
15
templates/pages/ix/my_ix/_search.html.twig
Normal file
15
templates/pages/ix/my_ix/_search.html.twig
Normal file
@ -0,0 +1,15 @@
|
||||
{{ form_start(form) }}
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h3 class="card-title">My IX</h3>
|
||||
<div class="row g2">
|
||||
<div class="col">
|
||||
{{ form_widget(form.myix) }}
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<button type="submit" class="btn btn-icon">{{ icon('search', 'icon') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ form_end(form) }}
|
13
templates/pages/ix/my_ix/index.html.twig
Normal file
13
templates/pages/ix/my_ix/index.html.twig
Normal file
@ -0,0 +1,13 @@
|
||||
{% extends "base/_layout.html.twig" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row row-cards">
|
||||
<div class="col-lg-2 col-sm-12 space-y">
|
||||
{% include 'pages/ix/my_ix/_search.html.twig' %}
|
||||
{% block legend %}{% endblock %}
|
||||
</div>
|
||||
<div class="col">
|
||||
{% block graph %}{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
44
templates/pages/ix/my_ix/show.html.twig
Normal file
44
templates/pages/ix/my_ix/show.html.twig
Normal file
@ -0,0 +1,44 @@
|
||||
{% extends "pages/ix/my_ix/index.html.twig" %}
|
||||
|
||||
{% set counter = 0 %}
|
||||
|
||||
{% block legend %}
|
||||
{% if knownlinks is defined %}
|
||||
{% include 'core/legend_simple.html.twig' %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block graph %}
|
||||
<div class="col">
|
||||
<div class="row row-cards">
|
||||
<div class="space-y">
|
||||
{% for as, as_data in data.data.asinfo %}
|
||||
{% set counter = counter + 1 %}
|
||||
{% embed 'core/card_top.html.twig' with {as: as, as_data: as_data, data: data, counter: counter, hours: hours|default('24 hours')} only %}
|
||||
{% block rank %}
|
||||
<div class="display-6 fw-bold rank">
|
||||
# {{ counter }}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block title %}
|
||||
<h3 class="mb-0">
|
||||
<span class="me-2 flag flag-{{ as_data.info.country|lower }}"></span>
|
||||
<strong>AS{{ as }}:</strong>
|
||||
<span class="small text-uppercase"><i>{{ as_data.info.name|default(as_data.info.description) }}</i></span>
|
||||
</h3>
|
||||
{% endblock %}
|
||||
|
||||
{#% block customlinks %}
|
||||
<div class="col-md-auto">
|
||||
<div class="mt-3 badges">
|
||||
{{ custom_links_top(as)|raw }}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %#}
|
||||
{% endembed %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
Loading…
x
Reference in New Issue
Block a user