From 0e96404302b6b3f7092de255700a3fbc7272333e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Gangloff?= Date: Mon, 9 Sep 2024 11:31:33 +0200 Subject: [PATCH] feat: add tracked domains table --- .../tracking/watchlist/DomainList.tsx | 6 -- .../tracking/watchlist/TrackedDomainTable.tsx | 79 +++++++++++++++++++ assets/pages/tracking/WatchlistPage.tsx | 17 ++-- assets/utils/api/index.ts | 1 + assets/utils/api/watchlist.ts | 14 +++- .../utils/functions/eppStatusCodeToColor.tsx | 6 +- src/Controller/WatchListController.php | 52 ++++++++++++ src/Entity/Domain.php | 9 ++- src/Entity/WatchList.php | 12 ++- .../UpdateDomainsFromWatchlistHandler.php | 12 +-- src/Service/RDAPService.php | 3 - translations/translations.pot | 29 +++++-- 12 files changed, 201 insertions(+), 39 deletions(-) delete mode 100644 assets/components/tracking/watchlist/DomainList.tsx create mode 100644 assets/components/tracking/watchlist/TrackedDomainTable.tsx diff --git a/assets/components/tracking/watchlist/DomainList.tsx b/assets/components/tracking/watchlist/DomainList.tsx deleted file mode 100644 index d7b3b88..0000000 --- a/assets/components/tracking/watchlist/DomainList.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import React from "react"; - -export function TrackedDomainList() { - return <> - -} \ No newline at end of file diff --git a/assets/components/tracking/watchlist/TrackedDomainTable.tsx b/assets/components/tracking/watchlist/TrackedDomainTable.tsx new file mode 100644 index 0000000..d4c79dd --- /dev/null +++ b/assets/components/tracking/watchlist/TrackedDomainTable.tsx @@ -0,0 +1,79 @@ +import React, {useEffect, useState} from "react"; +import {Domain, getTrackedDomainList} from "../../../utils/api"; +import {Table, Tag, Tooltip} from "antd"; +import {t} from "ttag"; +import useBreakpoint from "../../../hooks/useBreakpoint"; +import {ColumnType} from "antd/es/table"; +import {rdapStatusCodeDetailTranslation} from "../../../utils/functions/rdapTranslation"; +import {eppStatusCodeToColor} from "../../../utils/functions/eppStatusCodeToColor"; + +export function TrackedDomainTable() { + const sm = useBreakpoint('sm') + const [dataTable, setDataTable] = useState([]) + const [total, setTotal] = useState() + + + const rdapStatusCodeDetailTranslated = rdapStatusCodeDetailTranslation() + + const fetchData = (params: { page: number, itemsPerPage: number }) => { + getTrackedDomainList(params).then(data => { + setTotal(data['hydra:totalItems']) + setDataTable(data['hydra:member'].map((d: Domain) => { + const expirationDate = d.events.find(e => e.action === 'expiration' && !e.deleted)?.date + + return { + key: d.ldhName, + ldhName: d.ldhName, + expirationDate: expirationDate ? new Date(expirationDate).toLocaleString() : '-', + status: d.status.map(s => + {s} + + ), + updatedAt: new Date(d.updatedAt).toLocaleString() + } + })) + }) + } + + useEffect(() => { + fetchData({page: 1, itemsPerPage: 30}) + }, []) + + const columns: ColumnType[] = [ + { + title: t`Domain`, + dataIndex: "ldhName" + }, + { + title: t`Expiration date`, + dataIndex: 'expirationDate' + }, + { + title: t`Status`, + dataIndex: 'status' + }, + { + title: t`Updated at`, + dataIndex: 'updatedAt' + } + ] + + + return { + fetchData({page, itemsPerPage}) + } + }} + + {...(sm ? {scroll: {y: 'max-content'}} : {scroll: {y: 240}})} + /> +} \ No newline at end of file diff --git a/assets/pages/tracking/WatchlistPage.tsx b/assets/pages/tracking/WatchlistPage.tsx index 7e37796..ef495b8 100644 --- a/assets/pages/tracking/WatchlistPage.tsx +++ b/assets/pages/tracking/WatchlistPage.tsx @@ -8,6 +8,7 @@ import {WatchlistsList} from "../../components/tracking/watchlist/WatchlistsList import {Connector, getConnectors} from "../../utils/api/connectors"; import {showErrorAPI} from "../../utils/functions/showErrorAPI"; +import {TrackedDomainTable} from "../../components/tracking/watchlist/TrackedDomainTable"; export type Watchlist = { @@ -97,13 +98,15 @@ export default function WatchlistPage() { return {contextHolder} - { - - {connectors && - - } - - } + + {connectors && + + } + + + + + {connectors && watchlists && watchlists.length > 0 && & { toke data: watchlist, }) return response.data -} \ No newline at end of file +} + +export async function getTrackedDomainList(params: { page: number, itemsPerPage: number }): Promise { + const response = await request({ + method: 'GET', + url: 'tracked', + params + }) + return response.data +} + diff --git a/assets/utils/functions/eppStatusCodeToColor.tsx b/assets/utils/functions/eppStatusCodeToColor.tsx index cb5c897..8ad7afd 100644 --- a/assets/utils/functions/eppStatusCodeToColor.tsx +++ b/assets/utils/functions/eppStatusCodeToColor.tsx @@ -1,5 +1,5 @@ export const eppStatusCodeToColor = (s: string) => ['active', 'ok'].includes(s) ? 'green' : - s.startsWith('client') ? 'purple' : - s.startsWith('server') ? 'geekblue' : - s.includes('prohibited') ? 'red' : 'blue' \ No newline at end of file + ['pending delete', 'redemption period'].includes(s) ? 'red' : + s.startsWith('client') ? 'purple' : + s.startsWith('server') ? 'geekblue' : 'blue' \ No newline at end of file diff --git a/src/Controller/WatchListController.php b/src/Controller/WatchListController.php index d3bac85..6ab1e6b 100644 --- a/src/Controller/WatchListController.php +++ b/src/Controller/WatchListController.php @@ -328,4 +328,56 @@ class WatchListController extends AbstractController 'Content-Type' => 'text/calendar; charset=utf-8', ]); } + + /** + * @throws \Exception + */ + #[Route( + path: '/api/tracked', + name: 'watchlist_get_tracked_domains', + defaults: [ + '_api_resource_class' => WatchList::class, + '_api_operation_name' => 'get_tracked_domains', + ] + )] + public function getTrackedDomains(): array + { + /** @var User $user */ + $user = $this->getUser(); + + $domains = []; + /** @var WatchList $watchList */ + foreach ($user->getWatchLists()->getIterator() as $watchList) { + /** @var Domain $domain */ + foreach ($watchList->getDomains()->getIterator() as $domain) { + /** @var DomainEvent|null $exp */ + $exp = $domain->getEvents()->findFirst(fn (int $key, DomainEvent $e) => !$e->getDeleted() && 'expiration' === $e->getAction()); + + if (!$domain->getDeleted() + && null !== $exp && $exp->getDate() > new \DateTimeImmutable() + && count(array_filter($domain->getEvents()->toArray(), fn (DomainEvent $e) => !$e->getDeleted() && 'expiration' === $e->getAction())) > 0 + && !in_array($domain, $domains)) { + $domains[] = $domain; + } + } + } + + usort($domains, function (Domain $d1, Domain $d2) { + $IMPORTANT_STATUS = ['pending delete', 'redemption period', 'auto renew period']; + + /** @var \DateTimeImmutable $exp1 */ + $exp1 = $d1->getEvents()->findFirst(fn (int $key, DomainEvent $e) => !$e->getDeleted() && 'expiration' === $e->getAction())->getDate(); + /** @var \DateTimeImmutable $exp2 */ + $exp2 = $d2->getEvents()->findFirst(fn (int $key, DomainEvent $e) => !$e->getDeleted() && 'expiration' === $e->getAction())->getDate(); + $impStatus1 = count(array_intersect($IMPORTANT_STATUS, $d1->getStatus())) > 0; + $impStatus2 = count(array_intersect($IMPORTANT_STATUS, $d2->getStatus())) > 0; + + return $impStatus1 && !$impStatus2 ? -1 : ( + !$impStatus1 && $impStatus2 ? 2 : + $exp1 <=> $exp2 + ); + }); + + return $domains; + } } diff --git a/src/Entity/Domain.php b/src/Entity/Domain.php index f69ce7b..30d2b01 100644 --- a/src/Entity/Domain.php +++ b/src/Entity/Domain.php @@ -57,7 +57,7 @@ class Domain * @var Collection */ #[ORM\OneToMany(targetEntity: DomainEvent::class, mappedBy: 'domain', cascade: ['persist'], orphanRemoval: true)] - #[Groups(['domain:item'])] + #[Groups(['domain:item', 'domain:list'])] private Collection $events; /** @@ -69,7 +69,7 @@ class Domain private Collection $domainEntities; #[ORM\Column(type: Types::SIMPLE_ARRAY, nullable: true)] - #[Groups(['domain:item'])] + #[Groups(['domain:item', 'domain:list'])] private array $status = []; /** @@ -93,15 +93,16 @@ class Domain private ?\DateTimeImmutable $createdAt; #[ORM\Column(type: Types::DATETIME_IMMUTABLE)] + #[Groups(['domain:item', 'domain:list'])] private ?\DateTimeImmutable $updatedAt; #[ORM\ManyToOne] #[ORM\JoinColumn(referencedColumnName: 'tld', nullable: false)] - #[Groups(['domain:item'])] + #[Groups(['domain:item', 'domain:list'])] private ?Tld $tld = null; #[ORM\Column(nullable: false)] - #[Groups(['domain:item'])] + #[Groups(['domain:item', 'domain:list'])] private ?bool $deleted; public function __construct() diff --git a/src/Entity/WatchList.php b/src/Entity/WatchList.php index c08fa5b..5eaacf2 100644 --- a/src/Entity/WatchList.php +++ b/src/Entity/WatchList.php @@ -8,7 +8,6 @@ use ApiPlatform\Metadata\Get; use ApiPlatform\Metadata\GetCollection; use ApiPlatform\Metadata\Post; use ApiPlatform\Metadata\Put; -use App\Controller\WatchListController; use App\Repository\WatchListRepository; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; @@ -27,6 +26,16 @@ use Symfony\Component\Uid\Uuid; normalizationContext: ['groups' => 'watchlist:list'], name: 'get_all_mine', ), + new GetCollection( + uriTemplate: '/tracked', + routeName: 'watchlist_get_tracked_domains', + normalizationContext: ['groups' => [ + 'domain:list', + 'tld:list', + 'event:list', + ]], + name: 'get_tracked_domains' + ), new Get( normalizationContext: ['groups' => [ 'watchlist:item', @@ -42,7 +51,6 @@ use Symfony\Component\Uid\Uuid; ), new Get( routeName: 'watchlist_calendar', - controller: WatchListController::class, openapiContext: [ 'responses' => [ '200' => [ diff --git a/src/MessageHandler/UpdateDomainsFromWatchlistHandler.php b/src/MessageHandler/UpdateDomainsFromWatchlistHandler.php index 7ee3bf0..11a938d 100644 --- a/src/MessageHandler/UpdateDomainsFromWatchlistHandler.php +++ b/src/MessageHandler/UpdateDomainsFromWatchlistHandler.php @@ -63,16 +63,16 @@ final readonly class UpdateDomainsFromWatchlistHandler /** @var Domain $domain */ foreach ($watchList->getDomains() ->filter(fn ($domain) => $domain->getUpdatedAt() - ->diff( - new \DateTimeImmutable())->days >= 7 + ->diff(new \DateTimeImmutable())->days >= 7 || ( ($domain->getUpdatedAt() - ->diff( - new \DateTimeImmutable())->h * 60 + $domain->getUpdatedAt() - ->diff( - new \DateTimeImmutable())->i) >= 50 + ->diff(new \DateTimeImmutable())->h * 60 + $domain->getUpdatedAt() + ->diff(new \DateTimeImmutable())->i) >= 50 && $this->RDAPService::isToBeWatchClosely($domain) ) + || (count(array_intersect($domain->getStatus(), ['auto renew period', 'client hold', 'server hold'])) > 0 + && $domain->getUpdatedAt()->diff(new \DateTimeImmutable())->days >= 1 + ) ) as $domain ) { $updatedAt = $domain->getUpdatedAt(); diff --git a/src/Service/RDAPService.php b/src/Service/RDAPService.php index 9fa4673..5bd9587 100644 --- a/src/Service/RDAPService.php +++ b/src/Service/RDAPService.php @@ -76,7 +76,6 @@ readonly class RDAPService private const IMPORTANT_EVENTS = [EventAction::Deletion->value, EventAction::Expiration->value]; private const IMPORTANT_STATUS = [ - 'auto renew period', 'redemption period', 'pending delete', 'pending create', @@ -84,8 +83,6 @@ readonly class RDAPService 'pending restore', 'pending transfer', 'pending update', - 'client hold', - 'server hold', 'add period', ]; diff --git a/translations/translations.pot b/translations/translations.pot index 54d26b9..8f0f0b7 100644 --- a/translations/translations.pot +++ b/translations/translations.pot @@ -298,6 +298,23 @@ msgstr "" msgid "Are you sure to delete this Watchlist?" msgstr "" +#: assets/components/Sider.tsx:40 +#: assets/components/tracking/watchlist/TrackedDomainTable.tsx:46 +msgid "Domain" +msgstr "" + +#: assets/components/tracking/watchlist/TrackedDomainTable.tsx:50 +msgid "Expiration date" +msgstr "" + +#: assets/components/tracking/watchlist/TrackedDomainTable.tsx:54 +msgid "Status" +msgstr "" + +#: assets/components/tracking/watchlist/TrackedDomainTable.tsx:58 +msgid "Updated at" +msgstr "" + #: assets/components/tracking/watchlist/WatchlistCard.tsx:47 msgid "This Watchlist is not linked to a Connector." msgstr "" @@ -314,10 +331,6 @@ msgstr "" msgid "Search" msgstr "" -#: assets/components/Sider.tsx:40 -msgid "Domain" -msgstr "" - #: assets/components/Sider.tsx:41 msgid "Domain Finder" msgstr "" @@ -470,11 +483,11 @@ msgstr "" msgid "Create a Connector" msgstr "" -#: assets/pages/tracking/WatchlistPage.tsx:65 +#: assets/pages/tracking/WatchlistPage.tsx:66 msgid "Watchlist created !" msgstr "" -#: assets/pages/tracking/WatchlistPage.tsx:77 +#: assets/pages/tracking/WatchlistPage.tsx:78 msgid "Watchlist updated !" msgstr "" @@ -482,6 +495,10 @@ msgstr "" msgid "Create a Watchlist" msgstr "" +#: assets/pages/tracking/WatchlistPage.tsx:107 +msgid "Tracked Domains" +msgstr "" + #: assets/pages/NotFoundPage.tsx:10 msgid "Sorry, the page you visited does not exist." msgstr ""