mirror of
https://github.com/maelgangloff/domain-watchdog.git
synced 2025-12-29 16:15:04 +00:00
feat: add tracked domains table
This commit is contained in:
@@ -1,6 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
export function TrackedDomainList() {
|
||||
return <>
|
||||
</>
|
||||
}
|
||||
79
assets/components/tracking/watchlist/TrackedDomainTable.tsx
Normal file
79
assets/components/tracking/watchlist/TrackedDomainTable.tsx
Normal file
@@ -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<Domain[]>([])
|
||||
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 => <Tooltip
|
||||
placement='bottomLeft'
|
||||
title={s in rdapStatusCodeDetailTranslated ? rdapStatusCodeDetailTranslated[s as keyof typeof rdapStatusCodeDetailTranslated] : undefined}>
|
||||
<Tag color={eppStatusCodeToColor(s)}>{s}</Tag>
|
||||
</Tooltip>
|
||||
),
|
||||
updatedAt: new Date(d.updatedAt).toLocaleString()
|
||||
}
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
fetchData({page: 1, itemsPerPage: 30})
|
||||
}, [])
|
||||
|
||||
const columns: ColumnType<any>[] = [
|
||||
{
|
||||
title: t`Domain`,
|
||||
dataIndex: "ldhName"
|
||||
},
|
||||
{
|
||||
title: t`Expiration date`,
|
||||
dataIndex: 'expirationDate'
|
||||
},
|
||||
{
|
||||
title: t`Status`,
|
||||
dataIndex: 'status'
|
||||
},
|
||||
{
|
||||
title: t`Updated at`,
|
||||
dataIndex: 'updatedAt'
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
return <Table
|
||||
loading={total === undefined}
|
||||
columns={columns}
|
||||
dataSource={dataTable}
|
||||
pagination={{
|
||||
total,
|
||||
hideOnSinglePage: true,
|
||||
defaultPageSize: 30,
|
||||
onChange: (page, itemsPerPage) => {
|
||||
fetchData({page, itemsPerPage})
|
||||
}
|
||||
}}
|
||||
|
||||
{...(sm ? {scroll: {y: 'max-content'}} : {scroll: {y: 240}})}
|
||||
/>
|
||||
}
|
||||
@@ -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 <Flex gap="middle" align="center" justify="center" vertical>
|
||||
{contextHolder}
|
||||
{
|
||||
<Card loading={connectors === undefined} title={t`Create a Watchlist`} style={{width: '100%'}}>
|
||||
{connectors &&
|
||||
<WatchlistForm form={form} onFinish={onCreateWatchlist} connectors={connectors} isCreation={true}/>
|
||||
}
|
||||
</Card>
|
||||
}
|
||||
<Card loading={connectors === undefined} title={t`Create a Watchlist`} style={{width: '100%'}}>
|
||||
{connectors &&
|
||||
<WatchlistForm form={form} onFinish={onCreateWatchlist} connectors={connectors} isCreation={true}/>
|
||||
}
|
||||
</Card>
|
||||
<Divider/>
|
||||
<Card title={t`Tracked Domains`} style={{width: '100%'}}>
|
||||
<TrackedDomainTable/>
|
||||
</Card>
|
||||
<Divider/>
|
||||
{connectors && watchlists && watchlists.length > 0 &&
|
||||
<WatchlistsList watchlists={watchlists} onDelete={refreshWatchlists}
|
||||
|
||||
@@ -59,6 +59,7 @@ export interface Domain {
|
||||
nameservers: Nameserver[]
|
||||
tld: Tld
|
||||
deleted: boolean
|
||||
updatedAt: string
|
||||
}
|
||||
|
||||
export interface User {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {request, Watchlist, WatchlistRequest} from "./index";
|
||||
import {Domain, request, Watchlist, WatchlistRequest} from "./index";
|
||||
|
||||
export async function getWatchlists() {
|
||||
const response = await request({
|
||||
@@ -40,4 +40,14 @@ export async function putWatchlist(watchlist: Partial<WatchlistRequest> & { toke
|
||||
data: watchlist,
|
||||
})
|
||||
return response.data
|
||||
}
|
||||
}
|
||||
|
||||
export async function getTrackedDomainList(params: { page: number, itemsPerPage: number }): Promise<any> {
|
||||
const response = await request({
|
||||
method: 'GET',
|
||||
url: 'tracked',
|
||||
params
|
||||
})
|
||||
return response.data
|
||||
}
|
||||
|
||||
|
||||
@@ -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'
|
||||
['pending delete', 'redemption period'].includes(s) ? 'red' :
|
||||
s.startsWith('client') ? 'purple' :
|
||||
s.startsWith('server') ? 'geekblue' : 'blue'
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ class Domain
|
||||
* @var Collection<int, DomainEvent>
|
||||
*/
|
||||
#[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()
|
||||
|
||||
@@ -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' => [
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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',
|
||||
];
|
||||
|
||||
|
||||
@@ -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 ""
|
||||
|
||||
Reference in New Issue
Block a user