This commit is contained in:
Nicolas Debrigode 2024-01-29 15:41:39 +01:00
parent efd321a775
commit d2bf49f76e
12 changed files with 212 additions and 8 deletions

View File

@ -33,3 +33,8 @@
.card-title {
font-weight: var(--tblr-font-weight-bold) !important;
}
.auto-search-wrapper input {
padding: .5625rem .75rem !important;
font-size: .875rem !important;
}

View File

@ -0,0 +1 @@
:root{--close-button:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M18.984 6.422 13.406 12l5.578 5.578-1.406 1.406L12 13.406l-5.578 5.578-1.406-1.406L10.594 12 5.016 6.422l1.406-1.406L12 10.594l5.578-5.578z'/%3E%3C/svg%3E");--loupe-icon:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23929292' d='M16.041 15.856a.995.995 0 0 0-.186.186A6.97 6.97 0 0 1 11 18c-1.933 0-3.682-.782-4.95-2.05S4 12.933 4 11s.782-3.682 2.05-4.95S9.067 4 11 4s3.682.782 4.95 2.05S18 9.067 18 11a6.971 6.971 0 0 1-1.959 4.856zm5.666 4.437-3.675-3.675A8.967 8.967 0 0 0 20 11c0-2.485-1.008-4.736-2.636-6.364S13.485 2 11 2 6.264 3.008 4.636 4.636 2 8.515 2 11s1.008 4.736 2.636 6.364S8.515 20 11 20a8.967 8.967 0 0 0 5.618-1.968l3.675 3.675a.999.999 0 1 0 1.414-1.414z'/%3E%3C/svg%3E")}.auto-search-wrapper{display:block;position:relative;width:100%}.auto-search-wrapper input{border:1px solid #d7d7d7;box-shadow:none;box-sizing:border-box;font-size:16px;padding:12px 45px 12px 10px;width:100%}.auto-search-wrapper input:focus{border:1px solid #858585;outline:none}.auto-search-wrapper input::-ms-clear{display:none}.auto-search-wrapper ul{list-style:none;margin:0;overflow:auto;padding:0}.auto-search-wrapper ul li{cursor:pointer;margin:0;overflow:hidden;padding:10px;position:relative}.auto-search-wrapper ul li:not(:last-child){border-top:none}.auto-search-wrapper ul li[disabled]{background:#ececec;opacity:.5;pointer-events:none}.auto-search-wrapper .auto-expanded{border:1px solid #858585;outline:none}.auto-search-wrapper.loupe:before{filter:invert(60%)}.auto-is-loading:after{animation:auto-spinner .6s linear infinite;border-color:#d9d9d9 grey grey #d9d9d9;border-radius:50%;border-style:solid;border-width:2px;bottom:0;box-sizing:border-box;content:"";height:20px;margin:auto;position:absolute;right:10px;top:0;width:20px}.auto-is-loading .auto-clear{display:none}@keyframes auto-spinner{to{transform:rotate(1turn)}}li.loupe:before{bottom:auto;top:15px}.loupe input{padding:12px 45px 12px 35px}.loupe:before{background-image:var(--loupe-icon);bottom:0;content:"";height:17px;left:10px;margin:auto;position:absolute;top:0;width:17px}.auto-selected:before{opacity:1}.auto-clear{align-items:center;background-color:transparent;border:none;bottom:0;cursor:pointer;display:flex;height:auto;justify-content:center;margin:auto;position:absolute;right:0;top:0;width:40px}.auto-clear:before{content:var(--close-button);height:24px;line-height:100%;width:24px}.auto-clear span{display:none}.auto-results-wrapper{background-color:#fff;border:1px solid #858585;border-top:none;box-sizing:border-box;display:none;overflow:hidden}.auto-results-wrapper ul>.loupe{padding-left:40px}.auto-results-wrapper.auto-is-active{display:block;margin-top:-1px;position:absolute;width:100%;z-index:99999}.auto-selected{background-color:#e6e6e6}.auto-selected+li:before{border-top:none}.auto-error{border:1px solid #ff3838}.auto-error::-moz-placeholder{color:#f66;opacity:1}.auto-error::placeholder{color:#f66;opacity:1}.hidden{display:none}

File diff suppressed because one or more lines are too long

2
public/assets/js/axios/axios.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -13,7 +13,9 @@ use App\Repository\GetAsDataRepository;
use App\Repository\KnowlinksRepository;
use App\Repository\PeeringDBRepository;
use App\Util\Annotation\Menu;
use App\Util\GetJsonParameters;
use Doctrine\DBAL\Exception;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
@ -93,6 +95,9 @@ class IXStatsController extends BaseController
)]
public function searchIX(
Request $request,
PeeringDBRepository $peeringDBRepository,
GetAsDataRepository $asDataRepository,
ConfigApplication $Config,
): Response {
$this->base_data['content_wrapper']['titre'] = 'Search IX Stats';
@ -100,7 +105,35 @@ class IXStatsController extends BaseController
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$form->getData();
$this->base_data['content_wrapper']['titre'] = \sprintf(
'Top %s (%s)',
$this->base_data['top'],
'24 hours'
);
$this->base_data['content_wrapper']['small'] = $form->get('ix')->getData();
$this->data['data'] = $asDataRepository::get(
$this->base_data['top'],
'',
[],
$peeringDBRepository->getIXMembers((int) $form->get('ix_hidden')->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/search_ix/show.html.twig', [
'base_data' => $this->base_data,
'data' => $this->data,
'knownlinks' => KnowlinksRepository::get(),
'form' => $form->createView(),
]);
}
return $this->render('pages/ix/search_ix/index.html.twig', [
@ -108,4 +141,40 @@ class IXStatsController extends BaseController
'form' => $form->createView(),
]);
}
#[Route(
path: '/search/get-ixname',
name: 'ix.search.get_ixname',
methods: ['POST'],
)]
public function getIXName(
Request $request,
PeeringDBRepository $peeringDBRepository,
): JsonResponse {
$req = GetJsonParameters::getAll($request);
if (!\array_key_exists('name', $req)) {
return new JsonResponse(['message' => 'Bad JSON request.'], Response::HTTP_BAD_REQUEST);
}
try {
$data = $peeringDBRepository->getIXName($req['name']);
if (200 !== $data['status_code']) {
throw new \Exception('No data from API.');
}
$return = [];
foreach ($data['response']['data'] as $value) {
$return[] = [
'id' => $value['id'],
'name' => \sprintf('%s (%s / %s', $value['name'], $value['city'], $value['country']),
];
}
return new JsonResponse($return);
} catch (\Exception $e) {
return new JsonResponse(['message' => $e->getMessage()], Response::HTTP_NOT_FOUND);
}
}
}

View File

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace App\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
@ -20,7 +21,8 @@ class SearchIXForm extends AbstractType
->add('ix', TextType::class, [
'label' => false,
'translation_domain' => false,
]);
])
->add('ix_hidden', HiddenType::class);
}
public function configureOptions(OptionsResolver $resolver): void

View File

@ -41,4 +41,13 @@ class PeeringDBRepository
return $data['response']['data'][0];
}
public function getIXName(string $regex): array
{
if ('' === $regex || '0' === $regex) {
return [];
}
return $this->peeringDbClient->get(\sprintf('ix?name__contains=%s', $regex));
}
}

View File

@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace App\Util;
use Symfony\Component\HttpFoundation\Request;
class GetJsonParameters
{
public static function getAll(Request $request): array
{
if (\str_starts_with(\sprintf('%s', $request->headers->get('Content-Type')), 'application/json')) {
$data = json_decode($request->getContent(), true);
$request->request->replace(is_array($data) ? $data : []);
}
return $request->request->all();
}
}

View File

@ -29,13 +29,13 @@
</h3>
{% endblock %}
{#% block customlinks %}
{% block customlinks %}
<div class="col-md-auto">
<div class="mt-3 badges">
{{ custom_links_top(as)|raw }}
</div>
</div>
{% endblock %#}
{% endblock %}
{% endembed %}
{% endfor %}
</div>

View File

@ -4,8 +4,10 @@
<h3 class="card-title">Search IX</h3>
<div class="row g2">
<div class="col">
<div class="auto-search-wrapper">
{{ form_widget(form.ix) }}
</div>
</div>
<div class="col-auto">
<button type="submit" class="btn btn-icon">{{ icon('search', 'icon') }}</button>
</div>

View File

@ -1,5 +1,11 @@
{% extends "base/_layout.html.twig" %}
{% block css %}
{{ parent() }}
<link rel="stylesheet" href="{{ asset('assets/js/autocomplete/autocomplete.min.css') }}"/>
{% endblock %}
{% block content %}
<div class="row row-cards">
<div class="col-lg-2 col-sm-12 space-y">
@ -14,10 +20,46 @@
{% block js %}
{{ parent() }}
<script src="{{ asset('assets/js/autocomplete/autocomplete.min.js') }}"></script>
<script src="{{ asset('assets/js/axios/axios.min.js') }}"></script>
<script type="text/javascript">
var input = document.querySelector('#{{ form.ix.vars.id }}');
<script>
window.addEventListener('DOMContentLoaded', function () {
var hidden_ix_name = document.querySelector('#{{ form.ix_hidden.vars.id }}');
console.log(input);
new Autocomplete('{{ form.ix.vars.id }}', {
cache: true,
howManyCharacters: 2,
onSearch: ({ currentValue }) => {
const api = '{{ path('ix.search.get_ixname') }}';
var json_data = {name: currentValue};
return axios
.post(api, json_data)
.then((response) => {
if (!response.data) {
return [];
}
return response.data;
})
.catch(() => {
return [];
});
},
onResults: ({ matches }) => {
return matches
.map((el) => {
return `<li>${el.name}</li>`;
})
.join('');
},
onSelectedItem: ({ object }) => {
hidden_ix_name.value = object.id;
},
});
});
</script>
{% endblock %}

View File

@ -0,0 +1,44 @@
{% extends "pages/ix/search_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 %}