feat: add trackedEppStatus field in Watchlist

This commit is contained in:
Maël Gangloff 2025-10-19 21:37:52 +02:00
parent 626cb47f03
commit 0888033bd8
No known key found for this signature in database
GPG Key ID: 11FDC81C24A7F629
22 changed files with 565 additions and 225 deletions

View File

@ -8,7 +8,7 @@ import type {Watchlist} from '../../../utils/api'
export function UpdateWatchlistButton({watchlist, onUpdateWatchlist, connectors}: { export function UpdateWatchlistButton({watchlist, onUpdateWatchlist, connectors}: {
watchlist: Watchlist watchlist: Watchlist
onUpdateWatchlist: (values: { domains: string[], trackedEvents: string[], token: string }) => Promise<void> onUpdateWatchlist: (values: { domains: string[], trackedEvents: string[], trackedEppStatus: string[], token: string }) => Promise<void>
connectors: Array<Connector & { id: string }> connectors: Array<Connector & { id: string }>
}) { }) {
const [form] = Form.useForm() const [form] = Form.useForm()
@ -36,6 +36,7 @@ export function UpdateWatchlistButton({watchlist, onUpdateWatchlist, connectors}
{name: 'connector', value: watchlist.connector?.id}, {name: 'connector', value: watchlist.connector?.id},
{name: 'domains', value: watchlist.domains.map(d => d.ldhName)}, {name: 'domains', value: watchlist.domains.map(d => d.ldhName)},
{name: 'trackedEvents', value: watchlist.trackedEvents}, {name: 'trackedEvents', value: watchlist.trackedEvents},
{name: 'trackedEppStatus', value: watchlist.trackedEppStatus},
{name: 'dsn', value: watchlist.dsn} {name: 'dsn', value: watchlist.dsn}
]) ])
}} }}

View File

@ -7,20 +7,31 @@ import {DeleteWatchlistButton} from './DeleteWatchlistButton'
import React from 'react' import React from 'react'
import type {Connector} from '../../../utils/api/connectors' import type {Connector} from '../../../utils/api/connectors'
import {CalendarWatchlistButton} from './CalendarWatchlistButton' import {CalendarWatchlistButton} from './CalendarWatchlistButton'
import {rdapEventDetailTranslation, rdapEventNameTranslation} from '../../../utils/functions/rdapTranslation' import {
rdapDomainStatusCodeDetailTranslation,
rdapEventDetailTranslation,
rdapEventNameTranslation
} from '../../../utils/functions/rdapTranslation'
import {actionToColor} from '../../../utils/functions/actionToColor' import {actionToColor} from '../../../utils/functions/actionToColor'
import {DomainToTag} from '../../../utils/functions/DomainToTag' import {DomainToTag} from '../../../utils/functions/DomainToTag'
import type {Watchlist} from '../../../utils/api' import type {Watchlist} from '../../../utils/api'
import {eppStatusCodeToColor} from "../../../utils/functions/eppStatusCodeToColor"
export function WatchlistCard({watchlist, onUpdateWatchlist, connectors, onDelete}: { export function WatchlistCard({watchlist, onUpdateWatchlist, connectors, onDelete}: {
watchlist: Watchlist watchlist: Watchlist
onUpdateWatchlist: (values: { domains: string[], trackedEvents: string[], token: string }) => Promise<void> onUpdateWatchlist: (values: {
domains: string[],
trackedEvents: string[],
trackedEppStatus: string[],
token: string
}) => Promise<void>
connectors: Array<Connector & { id: string }> connectors: Array<Connector & { id: string }>
onDelete: () => void onDelete: () => void
}) { }) {
const rdapEventNameTranslated = rdapEventNameTranslation() const rdapEventNameTranslated = rdapEventNameTranslation()
const rdapEventDetailTranslated = rdapEventDetailTranslation() const rdapEventDetailTranslated = rdapEventDetailTranslation()
const rdapDomainStatusCodeDetailTranslated = rdapDomainStatusCodeDetailTranslation()
return ( return (
<> <>
@ -61,18 +72,56 @@ export function WatchlistCard({watchlist, onUpdateWatchlist, connectors, onDelet
<Card.Meta description={watchlist.token} style={{marginBottom: '1em'}}/> <Card.Meta description={watchlist.token} style={{marginBottom: '1em'}}/>
<Row gutter={16}> <Row gutter={16}>
<Col span={16}> <Col span={16}>
{watchlist.domains.map(d => <DomainToTag key={d.ldhName} domain={d}/>)} {watchlist.domains.map(d => (
<DomainToTag key={d.ldhName} domain={d}/>
))}
</Col> </Col>
<Col span={8}> <Col span={8}>
{watchlist.trackedEvents?.map(t => <Tooltip <>
key={t} <div style={{
title={rdapEventDetailTranslated[t as keyof typeof rdapEventDetailTranslated] || undefined} fontWeight: 500,
> marginBottom: '0.5em',
<Tag color={actionToColor(t)}> color: '#555',
{rdapEventNameTranslated[t as keyof typeof rdapEventNameTranslated]} fontSize: '0.9em'
</Tag> }}>
</Tooltip> {t`Tracked events`}
)} </div>
<div style={{marginBottom: '1em'}}>
{watchlist.trackedEvents?.map(t => (
<Tooltip
key={t}
title={rdapEventDetailTranslated[t as keyof typeof rdapEventDetailTranslated]}
>
<Tag color={actionToColor(t)} style={{marginBottom: 4}}>
{rdapEventNameTranslated[t as keyof typeof rdapEventNameTranslated]}
</Tag>
</Tooltip>
))}
</div>
</>
<>
<div style={{
fontWeight: 500,
marginBottom: '0.5em',
color: '#555',
fontSize: '0.9em'
}}>
{t`Tracked EPP status`}
</div>
<div>
{watchlist.trackedEppStatus?.map(t => (
<Tooltip
key={t}
title={rdapDomainStatusCodeDetailTranslated[t as keyof typeof rdapDomainStatusCodeDetailTranslated]}
>
<Tag color={eppStatusCodeToColor(t)} style={{marginBottom: 4}}>
{t}
</Tag>
</Tooltip>
))}
</div>
</>
</Col> </Col>
</Row> </Row>
</Card> </Card>

View File

@ -4,11 +4,16 @@ import {t} from 'ttag'
import {ApiOutlined, MinusCircleOutlined, PlusOutlined} from '@ant-design/icons' import {ApiOutlined, MinusCircleOutlined, PlusOutlined} from '@ant-design/icons'
import React from 'react' import React from 'react'
import type {Connector} from '../../../utils/api/connectors' import type {Connector} from '../../../utils/api/connectors'
import {rdapEventDetailTranslation, rdapEventNameTranslation} from '../../../utils/functions/rdapTranslation' import {
rdapDomainStatusCodeDetailTranslation,
rdapEventDetailTranslation,
rdapEventNameTranslation
} from '../../../utils/functions/rdapTranslation'
import {actionToColor} from '../../../utils/functions/actionToColor' import {actionToColor} from '../../../utils/functions/actionToColor'
import {actionToIcon} from '../../../utils/functions/actionToIcon' import {actionToIcon} from '../../../utils/functions/actionToIcon'
import type {EventAction, Watchlist} from '../../../utils/api' import type {EventAction, Watchlist} from '../../../utils/api'
import {formItemLayoutWithOutLabel} from "../../../utils/providers" import {formItemLayoutWithOutLabel} from "../../../utils/providers"
import {eppStatusCodeToColor} from "../../../utils/functions/eppStatusCodeToColor"
type TagRender = SelectProps['tagRender'] type TagRender = SelectProps['tagRender']
@ -26,14 +31,15 @@ const formItemLayout = {
export function WatchlistForm({form, connectors, onFinish, isCreation}: { export function WatchlistForm({form, connectors, onFinish, isCreation}: {
form: FormInstance form: FormInstance
connectors: Array<Connector & { id: string }> connectors: Array<Connector & { id: string }>
onFinish: (values: { domains: string[], trackedEvents: string[], token: string }) => void onFinish: (values: { domains: string[], trackedEvents: string[], trackedEppStatus: string[], token: string }) => void
isCreation: boolean, isCreation: boolean,
watchList?: Watchlist, watchList?: Watchlist,
}) { }) {
const rdapEventNameTranslated = rdapEventNameTranslation() const rdapEventNameTranslated = rdapEventNameTranslation()
const rdapEventDetailTranslated = rdapEventDetailTranslation() const rdapEventDetailTranslated = rdapEventDetailTranslation()
const rdapDomainStatusCodeDetailTranslated = rdapDomainStatusCodeDetailTranslation()
const triggerTagRenderer: TagRender = ({value, closable, onClose}: { const eventActionTagRenderer: TagRender = ({value, closable, onClose}: {
value: EventAction value: EventAction
closable: boolean closable: boolean
onClose: () => void onClose: () => void
@ -60,12 +66,41 @@ export function WatchlistForm({form, connectors, onFinish, isCreation}: {
) )
} }
const domainStatusTagRenderer: TagRender = ({value, closable, onClose}: {
value: EventAction
closable: boolean
onClose: () => void
}) => {
const onPreventMouseDown = (event: React.MouseEvent<HTMLSpanElement>) => {
event.preventDefault()
event.stopPropagation()
}
return (
<Tooltip
title={rdapDomainStatusCodeDetailTranslated[value as keyof typeof rdapDomainStatusCodeDetailTranslated] || undefined}
>
<Tag
color={eppStatusCodeToColor(value)}
onMouseDown={onPreventMouseDown}
closable={closable}
onClose={onClose}
style={{marginInlineEnd: 4}}
>
{value}
</Tag>
</Tooltip>
)
}
return ( return (
<Form <Form
{...formItemLayoutWithOutLabel} {...formItemLayoutWithOutLabel}
form={form} form={form}
onFinish={onFinish} onFinish={onFinish}
initialValues={{trackedEvents: ['last changed', 'transfer', 'expiration', 'deletion']}} initialValues={{
trackedEvents: ['last changed', 'transfer', 'expiration', 'deletion'],
trackedEppStatus: ['redemption period', 'pending delete', 'client hold', 'server hold']
}}
> >
<Form.Item name='token' hidden> <Form.Item name='token' hidden>
@ -155,7 +190,7 @@ export function WatchlistForm({form, connectors, onFinish, isCreation}: {
<Form.Item <Form.Item
label={t`Tracked events`} label={t`Tracked events`}
name='trackedEvents' name='trackedEvents'
rules={[{required: true, message: t`At least one trigger`, type: 'array'}]} rules={[{required: true, message: t`At least one event`, type: 'array'}]}
labelCol={{ labelCol={{
xs: {span: 24}, xs: {span: 24},
sm: {span: 4} sm: {span: 4}
@ -168,7 +203,7 @@ export function WatchlistForm({form, connectors, onFinish, isCreation}: {
> >
<Select <Select
mode='multiple' mode='multiple'
tagRender={triggerTagRenderer} tagRender={eventActionTagRenderer}
style={{width: '100%'}} style={{width: '100%'}}
options={Object.keys(rdapEventNameTranslated).map(e => ({ options={Object.keys(rdapEventNameTranslated).map(e => ({
value: e, value: e,
@ -178,6 +213,32 @@ export function WatchlistForm({form, connectors, onFinish, isCreation}: {
/> />
</Form.Item> </Form.Item>
<Form.Item
label={t`Tracked EPP status`}
name='trackedEppStatus'
rules={[{required: true, message: t`At least one EPP status`, type: 'array'}]}
labelCol={{
xs: {span: 24},
sm: {span: 4}
}}
wrapperCol={{
md: {span: 12},
sm: {span: 20}
}}
required
>
<Select
mode='multiple'
tagRender={domainStatusTagRenderer}
style={{width: '100%'}}
options={Object.keys(rdapDomainStatusCodeDetailTranslated).map(e => ({
value: e,
title: rdapDomainStatusCodeDetailTranslated[e as keyof typeof rdapDomainStatusCodeDetailTranslated] || undefined,
label: e
}))}
/>
</Form.Item>
<Form.Item <Form.Item
label={t`Connector`} label={t`Connector`}
name='connector' name='connector'

View File

@ -6,7 +6,7 @@ import type {Watchlist} from '../../../utils/api'
export function WatchlistsList({watchlists, onDelete, onUpdateWatchlist, connectors}: { export function WatchlistsList({watchlists, onDelete, onUpdateWatchlist, connectors}: {
watchlists: Watchlist[] watchlists: Watchlist[]
onDelete: () => void onDelete: () => void
onUpdateWatchlist: (values: { domains: string[], trackedEvents: string[], token: string }) => Promise<void> onUpdateWatchlist: (values: { domains: string[], trackedEvents: string[], trackedEppStatus: string[], token: string }) => Promise<void>
connectors: Array<Connector & { id: string }> connectors: Array<Connector & { id: string }>
}) { }) {
return ( return (

View File

@ -15,6 +15,7 @@ interface FormValuesType {
name?: string name?: string
domains: string[] domains: string[]
trackedEvents: string[] trackedEvents: string[]
trackedEppStatus: string[]
connector?: string connector?: string
dsn?: string[] dsn?: string[]
} }
@ -25,6 +26,7 @@ const getRequestDataFromFormCreation = (values: FormValuesType) => {
name: values.name, name: values.name,
domains: domainsURI, domains: domainsURI,
trackedEvents: values.trackedEvents, trackedEvents: values.trackedEvents,
trackedEppStatus: values.trackedEppStatus,
connector: values.connector !== undefined ? ('/api/connectors/' + values.connector) : undefined, connector: values.connector !== undefined ? ('/api/connectors/' + values.connector) : undefined,
dsn: values.dsn dsn: values.dsn
} }

View File

@ -81,6 +81,7 @@ export interface WatchlistRequest {
name?: string name?: string
domains: string[] domains: string[]
trackedEvents?: string[] trackedEvents?: string[]
trackedEppStatus?: string[]
connector?: string connector?: string
dsn?: string[] dsn?: string[]
} }
@ -91,6 +92,7 @@ export interface Watchlist {
token: string token: string
domains: Domain[] domains: Domain[]
trackedEvents?: string[] trackedEvents?: string[]
trackedEppStatus?: string[]
dsn?: string[] dsn?: string[]
connector?: { connector?: {
id: string id: string

View File

@ -1,8 +1,9 @@
export const eppStatusCodeToColor = (s: string) => export const eppStatusCodeToColor = (s?: string) =>
['active', 'ok'].includes(s) s === undefined ? 'default' :
? 'green' ['active', 'ok'].includes(s)
: ['pending delete', 'redemption period'].includes(s) ? 'green'
? 'red' : ['pending delete', 'redemption period'].includes(s)
: s.startsWith('client') ? 'red'
? 'purple' : s.startsWith('client')
: s.startsWith('server') ? 'geekblue' : 'blue' ? 'purple'
: s.startsWith('server') ? 'geekblue' : 'blue'

View File

@ -68,23 +68,7 @@ export const rdapEventDetailTranslation = () => ({
'enum validation expiration': t`Association of phone number represented by this ENUM domain to registrant has expired or will expire at a predetermined date and time.` 'enum validation expiration': t`Association of phone number represented by this ENUM domain to registrant has expired or will expire at a predetermined date and time.`
}) })
/** export const rdapDomainStatusCodeDetailTranslation = () => ({
* @see https://www.iana.org/assignments/rdap-json-values/rdap-json-values.xhtml
* @see https://www.icann.org/resources/pages/epp-status-codes-2014-06-16-en
*/
export const rdapStatusCodeDetailTranslation = () => ({
validated: t`Signifies that the data of the object instance has been found to be accurate.`,
'renew prohibited': t`Renewal or reregistration of the object instance is forbidden.`,
'update prohibited': t`Updates to the object instance are forbidden.`,
'transfer prohibited': t`Transfers of the registration from one registrar to another are forbidden.`,
'delete prohibited': t`Deletion of the registration of the object instance is forbidden.`,
proxy: t`The registration of the object instance has been performed by a third party.`,
private: t`The information of the object instance is not designated for public consumption.`,
removed: t`Some of the information of the object instance has not been made available and has been removed.`,
obscured: t`Some of the information of the object instance has been altered for the purposes of not readily revealing the actual information of the object instance.`,
associated: t`The object instance is associated with other object instances in the registry.`,
locked: t`Changes to the object instance cannot be made, including the association of other object instances.`,
active: t`This is the standard status for a domain, meaning it has no pending operations or prohibitions.`, active: t`This is the standard status for a domain, meaning it has no pending operations or prohibitions.`,
inactive: t`This status code indicates that delegation information (name servers) has not been associated with your domain. Your domain is not activated in the DNS and will not resolve.`, inactive: t`This status code indicates that delegation information (name servers) has not been associated with your domain. Your domain is not activated in the DNS and will not resolve.`,
'pending create': t`This status code indicates that a request to create your domain has been received and is being processed.`, 'pending create': t`This status code indicates that a request to create your domain has been received and is being processed.`,
@ -110,6 +94,27 @@ export const rdapStatusCodeDetailTranslation = () => ({
'server hold': t`This status code is set by your domain's Registry Operator. Your domain is not activated in the DNS.`, 'server hold': t`This status code is set by your domain's Registry Operator. Your domain is not activated in the DNS.`,
'transfer period': t`This grace period is provided after the successful transfer of a domain name from one registrar to another. If the new registrar deletes the domain name during this period, the registry provides a credit to the registrar for the cost of the transfer.`, 'transfer period': t`This grace period is provided after the successful transfer of a domain name from one registrar to another. If the new registrar deletes the domain name during this period, the registry provides a credit to the registrar for the cost of the transfer.`,
})
/**
* @see https://www.iana.org/assignments/rdap-json-values/rdap-json-values.xhtml
* @see https://www.icann.org/resources/pages/epp-status-codes-2014-06-16-en
*/
export const rdapStatusCodeDetailTranslation = () => ({
validated: t`Signifies that the data of the object instance has been found to be accurate.`,
'renew prohibited': t`Renewal or reregistration of the object instance is forbidden.`,
'update prohibited': t`Updates to the object instance are forbidden.`,
'transfer prohibited': t`Transfers of the registration from one registrar to another are forbidden.`,
'delete prohibited': t`Deletion of the registration of the object instance is forbidden.`,
proxy: t`The registration of the object instance has been performed by a third party.`,
private: t`The information of the object instance is not designated for public consumption.`,
removed: t`Some of the information of the object instance has not been made available and has been removed.`,
obscured: t`Some of the information of the object instance has been altered for the purposes of not readily revealing the actual information of the object instance.`,
associated: t`The object instance is associated with other object instances in the registry.`,
locked: t`Changes to the object instance cannot be made, including the association of other object instances.`,
...rdapDomainStatusCodeDetailTranslation(),
administrative: t`The object instance has been allocated administratively (i.e., not for use by the recipient in their own right in operational networks).`, administrative: t`The object instance has been allocated administratively (i.e., not for use by the recipient in their own right in operational networks).`,
reserved: t`The object instance has been allocated to an IANA special-purpose address registry.` reserved: t`The object instance has been allocated to an IANA special-purpose address registry.`
}) })

View File

@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20251019211214 extends AbstractMigration
{
public function getDescription(): string
{
return 'Add tracked_epp_status on watchlist';
}
public function up(Schema $schema): void
{
$this->addSql("ALTER TABLE watch_list ADD tracked_epp_status JSONB DEFAULT '[]'::jsonb");
$this->addSql('ALTER TABLE watch_list ALTER tracked_epp_status DROP DEFAULT ');
$this->addSql('ALTER TABLE watch_list ALTER tracked_epp_status SET NOT NULL');
}
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE watch_list DROP tracked_epp_status');
}
}

View File

@ -199,6 +199,15 @@ class WatchList
])] ])]
private array $trackedEvents = []; private array $trackedEvents = [];
#[ORM\Column(type: Types::JSON)]
#[Groups(['watchlist:list', 'watchlist:item', 'watchlist:create', 'watchlist:update'])]
#[Assert\Unique]
#[Assert\All([
new Assert\Type('string'),
new Assert\NotBlank(),
])]
private array $trackedEppStatus = [];
public function __construct() public function __construct()
{ {
$this->token = Uuid::v4(); $this->token = Uuid::v4();
@ -311,4 +320,16 @@ class WatchList
return $this; return $this;
} }
public function getTrackedEppStatus(): array
{
return $this->trackedEppStatus;
}
public function setTrackedEppStatus(array $trackedEppStatus): static
{
$this->trackedEppStatus = $trackedEppStatus;
return $this;
}
} }

View File

@ -4,10 +4,14 @@ namespace App\MessageHandler;
use App\Entity\Domain; use App\Entity\Domain;
use App\Entity\DomainEvent; use App\Entity\DomainEvent;
use App\Entity\DomainStatus;
use App\Entity\WatchList; use App\Entity\WatchList;
use App\Message\SendDomainEventNotif; use App\Message\SendDomainEventNotif;
use App\Notifier\DomainStatusUpdateNotification;
use App\Notifier\DomainUpdateNotification; use App\Notifier\DomainUpdateNotification;
use App\Repository\DomainEventRepository;
use App\Repository\DomainRepository; use App\Repository\DomainRepository;
use App\Repository\DomainStatusRepository;
use App\Repository\WatchListRepository; use App\Repository\WatchListRepository;
use App\Service\ChatNotificationService; use App\Service\ChatNotificationService;
use App\Service\InfluxdbService; use App\Service\InfluxdbService;
@ -37,6 +41,8 @@ final readonly class SendDomainEventNotifHandler
#[Autowire(param: 'influxdb_enabled')] #[Autowire(param: 'influxdb_enabled')]
private bool $influxdbEnabled, private bool $influxdbEnabled,
private InfluxdbService $influxdbService, private InfluxdbService $influxdbService,
private DomainEventRepository $domainEventRepository,
private DomainStatusRepository $domainStatusRepository,
) { ) {
$this->sender = new Address($mailerSenderEmail, $mailerSenderName); $this->sender = new Address($mailerSenderEmail, $mailerSenderName);
} }
@ -51,20 +57,28 @@ final readonly class SendDomainEventNotifHandler
$watchList = $this->watchListRepository->findOneBy(['token' => $message->watchListToken]); $watchList = $this->watchListRepository->findOneBy(['token' => $message->watchListToken]);
/** @var Domain $domain */ /** @var Domain $domain */
$domain = $this->domainRepository->findOneBy(['ldhName' => $message->ldhName]); $domain = $this->domainRepository->findOneBy(['ldhName' => $message->ldhName]);
$recipient = new Recipient($watchList->getUser()->getEmail());
/* /*
* For each new event whose date is after the domain name update date (before the current domain name update) * For each new event whose date is after the domain name update date (before the current domain name update)
*/ */
/** @var DomainEvent $event */ /** @var DomainEvent[] $newEvents */
foreach ($domain->getEvents()->filter( $newEvents = $this->domainEventRepository->createQueryBuilder('de')
fn ($event) => $message->updatedAt < $event->getDate() && $event->getDate() < new \DateTimeImmutable()) as $event ->select()
) { ->where('de.domain = :domain')
->andWhere('de.date > :updatedAt')
->andWhere('de.date < :now')
->setParameter('domain', $domain)
->setParameter('updatedAt', $domain->getUpdatedAt())
->setParameter('now', new \DateTimeImmutable())
->getQuery()->getResult();
foreach ($newEvents as $event) {
if (!in_array($event->getAction(), $watchList->getTrackedEvents())) { if (!in_array($event->getAction(), $watchList->getTrackedEvents())) {
continue; continue;
} }
$recipient = new Recipient($watchList->getUser()->getEmail());
$notification = new DomainUpdateNotification($this->sender, $event); $notification = new DomainUpdateNotification($this->sender, $event);
$this->logger->info('New action has been detected on this domain name : an email is sent to user', [ $this->logger->info('New action has been detected on this domain name : an email is sent to user', [
@ -75,6 +89,10 @@ final readonly class SendDomainEventNotifHandler
$this->mailer->send($notification->asEmailMessage($recipient)->getMessage()); $this->mailer->send($notification->asEmailMessage($recipient)->getMessage());
if ($this->influxdbEnabled) {
$this->influxdbService->addDomainNotificationPoint($domain, 'email', true);
}
$webhookDsn = $watchList->getWebhookDsn(); $webhookDsn = $watchList->getWebhookDsn();
if (null !== $webhookDsn && 0 !== count($webhookDsn)) { if (null !== $webhookDsn && 0 !== count($webhookDsn)) {
$this->logger->info('New action has been detected on this domain name : a notification is sent to user', [ $this->logger->info('New action has been detected on this domain name : a notification is sent to user', [
@ -84,11 +102,64 @@ final readonly class SendDomainEventNotifHandler
]); ]);
$this->chatNotificationService->sendChatNotification($watchList, $notification); $this->chatNotificationService->sendChatNotification($watchList, $notification);
if ($this->influxdbEnabled) {
$this->influxdbService->addDomainNotificationPoint($domain, 'chat', true);
}
} }
$this->statService->incrementStat('stats.alert.sent');
}
/** @var DomainStatus $domainStatus */
$domainStatus = $this->domainStatusRepository->createQueryBuilder('ds')
->select()
->where('ds.domain = :domain')
->andWhere('ds.createdAt > :createdAt')
->andWhere('ds.createdAt < :now')
->orderBy('ds.createdAt DESC')
->setParameter('domain', $domain)
->setParameter('createdAt', $domain->getUpdatedAt())
->setParameter('now', new \DateTimeImmutable())
->getQuery()
->getOneOrNullResult();
if (null !== $domainStatus && count(array_intersect(
$watchList->getTrackedEppStatus(),
[...$domainStatus->getAddStatus(), ...$domainStatus->getDeleteStatus()]
))) {
$notification = new DomainStatusUpdateNotification($this->sender, $domain, $domainStatus);
$this->logger->info('New domain status has been detected on this domain name : an email is sent to user', [
'addStatus' => $domainStatus->getAddStatus(),
'deleteStatus' => $domainStatus->getDeleteStatus(),
'status' => $domain->getStatus(),
'ldhName' => $message->ldhName,
'username' => $watchList->getUser()->getUserIdentifier(),
]);
$this->mailer->send($notification->asEmailMessage($recipient)->getMessage());
if ($this->influxdbEnabled) { if ($this->influxdbEnabled) {
$this->influxdbService->addDomainNotificationPoint($domain, 'chat', true); $this->influxdbService->addDomainNotificationPoint($domain, 'email', true);
} }
$webhookDsn = $watchList->getWebhookDsn();
if (null !== $webhookDsn && 0 !== count($webhookDsn)) {
$this->logger->info('New domain status has been detected on this domain name : a notification is sent to user', [
'addStatus' => $domainStatus->getAddStatus(),
'deleteStatus' => $domainStatus->getDeleteStatus(),
'status' => $domain->getStatus(),
'ldhName' => $message->ldhName,
'username' => $watchList->getUser()->getUserIdentifier(),
]);
$this->chatNotificationService->sendChatNotification($watchList, $notification);
if ($this->influxdbEnabled) {
$this->influxdbService->addDomainNotificationPoint($domain, 'chat', true);
}
}
$this->statService->incrementStat('stats.alert.sent'); $this->statService->incrementStat('stats.alert.sent');
} }
} }

View File

@ -0,0 +1,69 @@
<?php
namespace App\Notifier;
use App\Entity\Domain;
use App\Entity\DomainStatus;
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
use Symfony\Component\Mime\Address;
use Symfony\Component\Mime\Email;
use Symfony\Component\Notifier\Message\ChatMessage;
use Symfony\Component\Notifier\Message\EmailMessage;
use Symfony\Component\Notifier\Message\PushMessage;
use Symfony\Component\Notifier\Notification\Notification;
use Symfony\Component\Notifier\Recipient\EmailRecipientInterface;
use Symfony\Component\Notifier\Recipient\RecipientInterface;
class DomainStatusUpdateNotification extends DomainWatchdogNotification
{
public function __construct(
private readonly Address $sender,
private readonly Domain $domain,
private readonly DomainStatus $domainStatus,
) {
parent::__construct();
}
public function asChatMessage(?RecipientInterface $recipient = null, ?string $transport = null): ?ChatMessage
{
$ldhName = $this->domain->getLdhName();
$this->subject("Domain EPP status changed $ldhName")
->content("Domain name $ldhName EPP status has been updated.")
->importance(Notification::IMPORTANCE_HIGH);
return ChatMessage::fromNotification($this);
}
public function asPushMessage(?RecipientInterface $recipient = null, ?string $transport = null): ?PushMessage
{
$ldhName = $this->domain->getLdhName();
$this->subject("Domain EPP status changed $ldhName")
->content("Domain name $ldhName EPP status has been updated.")
->importance(Notification::IMPORTANCE_HIGH);
return PushMessage::fromNotification($this);
}
public function asEmailMessage(EmailRecipientInterface $recipient): EmailMessage
{
$ldhName = $this->domain->getLdhName();
$email = (new TemplatedEmail())
->from($this->sender)
->to($recipient->getEmail())
->priority(Email::PRIORITY_HIGH)
->subject("Domain EPP status changed $ldhName")
->htmlTemplate('emails/success/domain_updated.html.twig')
->locale('en')
->context([
'domain' => $this->domain,
'domainStatus' => $this->domainStatus,
]);
$email->getHeaders()
->addTextHeader('In-Reply-To', "<$ldhName+updated@domain-watchdog>")
->addTextHeader('References', "<$ldhName+updated@domain-watchdog>");
return new EmailMessage($email);
}
}

View File

@ -17,6 +17,6 @@
<br/><br/> <br/><br/>
<p>To resolve this issue, please verify the login credentials associated with the connector and update them if necessary.</p> <p>To resolve this issue, please verify the login credentials associated with the connector and update them if necessary.</p>
<p>Thank you for your attention,<br/> <p>Thank you for your attention,<br/>
Sincerely,<br/> Best regards<br/>
</p> </p>
{% endblock %} {% endblock %}

View File

@ -9,7 +9,7 @@
<strong>Detection Date:</strong> {{ domain.updatedAt | date("c") }}<br/> <strong>Detection Date:</strong> {{ domain.updatedAt | date("c") }}<br/>
<br/> <br/>
This domain has been identified as deleted and is potentially available for registration until it is registered by another party.<br/><br/> This domain has been identified as deleted and is potentially available for registration until it is registered by another party.<br/><br/>
Thank you for your understanding,<br/> Thank you for your attention.<br/>
Sincerely,<br/> Best regards<br/>
</p> </p>
{% endblock %} {% endblock %}

View File

@ -14,7 +14,7 @@
<li>A temporary interruption is affecting the registration of this domain name.</li> <li>A temporary interruption is affecting the registration of this domain name.</li>
</ul> </ul>
<br/><br/> <br/><br/>
<p>Thank you for your understanding,<br/> <p>Thank you for your attention.<br/>
Sincerely,<br/> Best regards<br/>
</p> </p>
{% endblock %} {% endblock %}

View File

@ -14,7 +14,7 @@
<li>A temporary outage affects the provision of domain name information.</li> <li>A temporary outage affects the provision of domain name information.</li>
</ul> </ul>
<br/><br/> <br/><br/>
<p>Thank you for your understanding,<br/> <p>Thank you for your attention.<br/>
Sincerely,<br/> Best regards<br/>
</p> </p>
{% endblock %} {% endblock %}

View File

@ -8,7 +8,7 @@
<a href="{{ signedUrl|raw }}">Confirm my Email</a>. <a href="{{ signedUrl|raw }}">Confirm my Email</a>.
This link will expire in {{ expiresAtMessageKey|trans(expiresAtMessageData, 'VerifyEmailBundle') }}. This link will expire in {{ expiresAtMessageKey|trans(expiresAtMessageData, 'VerifyEmailBundle') }}.
<br/><br/> <br/><br/>
Thank you for your understanding,<br/> Thank you for your attention.<br/>
Sincerely,<br/> Best regards<br/>
</p> </p>
{% endblock %} {% endblock %}

View File

@ -9,7 +9,7 @@
<strong>Domain name:</strong> {{ domain.ldhName }}<br/> <strong>Domain name:</strong> {{ domain.ldhName }}<br/>
<strong>Connector provider :</strong> {{ provider }}<br/> <strong>Connector provider :</strong> {{ provider }}<br/>
<br/><br/> <br/><br/>
Thank you for your understanding,<br/> Thank you for your attention.<br/>
Sincerely,<br/> Best regards<br/>
</p> </p>
{% endblock %} {% endblock %}

View File

@ -0,0 +1,23 @@
{% extends "emails/base.html.twig" %}
{% set email_color = '#0056b3' %}
{% set title = 'Domain Watchdog Alert' %}
{% block content %}
<p>Hello,</p>
<p>We would like to inform you that a change has been detected in the EPP status of one of the domains on your watchlist.</p>
<p>
<strong>Domain name:</strong> {{ domain.ldhName }}<br/>
<strong>Current EPP status:</strong> {{ domain.status | join(' | ') }}<br/>
{% if domainStatus.addStatus is not empty %}
<strong>[+] Added status:</strong> {{ domainStatus.addStatus | join(' | ') }}<br/>
{% endif %}
{% if domainStatus.deleteStatus is not empty %}
<strong>[-] Removed status:</strong> {{ domainStatus.deleteStatus | join(' | ') }}<br/>
{% endif %}
</p>
<p>
Thank you for your attention.<br/>
Best regards<br/>
</p>
{% endblock %}

View File

@ -9,7 +9,7 @@
<strong>Action:</strong> {{ event.action }}<br/> <strong>Action:</strong> {{ event.action }}<br/>
<strong>Effective Date:</strong> {{ event.date | date("c") }}<br/> <strong>Effective Date:</strong> {{ event.date | date("c") }}<br/>
<br/><br/> <br/><br/>
Thank you for your understanding,<br/> Thank you for your attention.<br/>
Sincerely,<br/> Best regards<br/>
</p> </p>
{% endblock %} {% endblock %}

View File

@ -45,6 +45,7 @@ final class WatchListUpdateProcessorTest extends ApiTestCase
'domains' => ['/api/domains/iana.org', '/api/domains/example.com'], 'domains' => ['/api/domains/iana.org', '/api/domains/example.com'],
'name' => 'My modified Watchlist', 'name' => 'My modified Watchlist',
'trackedEvents' => ['last changed'], 'trackedEvents' => ['last changed'],
'trackedEppStatus' => [],
]]); ]]);
$this->assertResponseIsSuccessful(); $this->assertResponseIsSuccessful();
$this->assertMatchesResourceItemJsonSchema(WatchList::class); $this->assertMatchesResourceItemJsonSchema(WatchList::class);
@ -61,6 +62,7 @@ final class WatchListUpdateProcessorTest extends ApiTestCase
'domains' => $domains, 'domains' => $domains,
'name' => 'My Watchlist', 'name' => 'My Watchlist',
'trackedEvents' => ['last changed', 'transfer', 'expiration', 'deletion'], 'trackedEvents' => ['last changed', 'transfer', 'expiration', 'deletion'],
'trackedEppStatus' => ['redemption period', 'pending delete', 'client hold', 'server hold'],
'connector' => $connectorId, 'connector' => $connectorId,
]]); ]]);

View File

@ -44,8 +44,8 @@ msgstr ""
#: assets/components/RegisterForm.tsx:39 #: assets/components/RegisterForm.tsx:39
#: assets/components/RegisterForm.tsx:47 #: assets/components/RegisterForm.tsx:47
#: assets/components/search/DomainSearchBar.tsx:28 #: assets/components/search/DomainSearchBar.tsx:28
#: assets/components/tracking/watchlist/WatchlistForm.tsx:120 #: assets/components/tracking/watchlist/WatchlistForm.tsx:155
#: assets/components/tracking/watchlist/WatchlistForm.tsx:223 #: assets/components/tracking/watchlist/WatchlistForm.tsx:284
#: assets/utils/providers/forms/AutoDnsConnectorForm.tsx:21 #: assets/utils/providers/forms/AutoDnsConnectorForm.tsx:21
#: assets/utils/providers/forms/AutoDnsConnectorForm.tsx:32 #: assets/utils/providers/forms/AutoDnsConnectorForm.tsx:32
#: assets/utils/providers/forms/AutoDnsConnectorForm.tsx:47 #: assets/utils/providers/forms/AutoDnsConnectorForm.tsx:47
@ -164,72 +164,64 @@ msgid "Entities"
msgstr "" msgstr ""
#: assets/components/search/DomainSearchBar.tsx:31 #: assets/components/search/DomainSearchBar.tsx:31
#: assets/components/tracking/watchlist/WatchlistForm.tsx:123 #: assets/components/tracking/watchlist/WatchlistForm.tsx:158
msgid "This domain name does not appear to be valid" msgid "This domain name does not appear to be valid"
msgstr "" msgstr ""
#: assets/components/Sider.tsx:32 #: assets/components/Sider.tsx:31
msgid "Home" msgid "Home"
msgstr "" msgstr ""
#: assets/components/Sider.tsx:38 #: assets/components/Sider.tsx:37
msgid "Search" msgid "Search"
msgstr "" msgstr ""
#: assets/components/Sider.tsx:44 #: assets/components/Sider.tsx:43
#: assets/components/tracking/watchlist/TrackedDomainTable.tsx:175 #: assets/components/tracking/watchlist/TrackedDomainTable.tsx:175
msgid "Domain" msgid "Domain"
msgstr "" msgstr ""
#: assets/components/Sider.tsx:45 #: assets/components/Sider.tsx:44
msgid "Domain Finder" msgid "Domain Finder"
msgstr "" msgstr ""
#: assets/components/Sider.tsx:52 #: assets/components/Sider.tsx:63
msgid "Entity" #: assets/components/Sider.tsx:64
msgstr ""
#: assets/components/Sider.tsx:53
msgid "Entity Finder"
msgstr ""
#: assets/components/Sider.tsx:72
#: assets/components/Sider.tsx:73
msgid "Infrastructure" msgid "Infrastructure"
msgstr "" msgstr ""
#: assets/components/Sider.tsx:79 #: assets/components/Sider.tsx:70
#: assets/pages/StatisticsPage.tsx:114 #: assets/pages/StatisticsPage.tsx:114
#: assets/pages/infrastructure/TldPage.tsx:79 #: assets/pages/infrastructure/TldPage.tsx:79
msgid "TLD" msgid "TLD"
msgstr "" msgstr ""
#: assets/components/Sider.tsx:80 #: assets/components/Sider.tsx:71
msgid "TLD list" msgid "TLD list"
msgstr "" msgstr ""
#: assets/components/Sider.tsx:87 #: assets/components/Sider.tsx:78
#: assets/components/Sider.tsx:88 #: assets/components/Sider.tsx:79
msgid "ICANN list" msgid "ICANN list"
msgstr "" msgstr ""
#: assets/components/Sider.tsx:96 #: assets/components/Sider.tsx:87
msgid "Tracking" msgid "Tracking"
msgstr "" msgstr ""
#: assets/components/Sider.tsx:102 #: assets/components/Sider.tsx:93
msgid "My Watchlists" msgid "My Watchlists"
msgstr "" msgstr ""
#: assets/components/Sider.tsx:109 #: assets/components/Sider.tsx:100
msgid "Tracking table" msgid "Tracking table"
msgstr "" msgstr ""
#: assets/components/Sider.tsx:116 #: assets/components/Sider.tsx:107
msgid "My Connectors" msgid "My Connectors"
msgstr "" msgstr ""
#: assets/components/Sider.tsx:140 #: assets/components/Sider.tsx:131
#. #.
#. { #. {
#. key: 'tools', #. key: 'tools',
@ -248,16 +240,16 @@ msgstr ""
msgid "Statistics" msgid "Statistics"
msgstr "" msgstr ""
#: assets/components/Sider.tsx:150 #: assets/components/Sider.tsx:141
#: assets/pages/UserPage.tsx:17 #: assets/pages/UserPage.tsx:17
msgid "My Account" msgid "My Account"
msgstr "" msgstr ""
#: assets/components/Sider.tsx:155 #: assets/components/Sider.tsx:146
msgid "Log out" msgid "Log out"
msgstr "" msgstr ""
#: assets/components/Sider.tsx:163 #: assets/components/Sider.tsx:154
#: assets/pages/LoginPage.tsx:35 #: assets/pages/LoginPage.tsx:35
#: assets/pages/LoginPage.tsx:45 #: assets/pages/LoginPage.tsx:45
msgid "Log in" msgid "Log in"
@ -291,7 +283,7 @@ msgid "Next"
msgstr "" msgstr ""
#: assets/components/tracking/connector/ConnectorForm.tsx:96 #: assets/components/tracking/connector/ConnectorForm.tsx:96
#: assets/components/tracking/watchlist/WatchlistForm.tsx:270 #: assets/components/tracking/watchlist/WatchlistForm.tsx:331
msgid "Create" msgid "Create"
msgstr "" msgstr ""
@ -459,90 +451,100 @@ msgstr ""
msgid "Edit the Watchlist" msgid "Edit the Watchlist"
msgstr "" msgstr ""
#: assets/components/tracking/watchlist/UpdateWatchlistButton.tsx:45 #: assets/components/tracking/watchlist/UpdateWatchlistButton.tsx:46
msgid "Update a Watchlist" msgid "Update a Watchlist"
msgstr "" msgstr ""
#: assets/components/tracking/watchlist/UpdateWatchlistButton.tsx:55 #: assets/components/tracking/watchlist/UpdateWatchlistButton.tsx:56
msgid "Cancel" msgid "Cancel"
msgstr "" msgstr ""
#: assets/components/tracking/watchlist/WatchlistCard.tsx:35 #: assets/components/tracking/watchlist/WatchlistCard.tsx:46
msgid "This Watchlist is not linked to a Connector." msgid "This Watchlist is not linked to a Connector."
msgstr "" msgstr ""
#: assets/components/tracking/watchlist/WatchlistCard.tsx:40 #: assets/components/tracking/watchlist/WatchlistCard.tsx:51
msgid "Watchlist" msgid "Watchlist"
msgstr "" msgstr ""
#: assets/components/tracking/watchlist/WatchlistForm.tsx:76 #: assets/components/tracking/watchlist/WatchlistCard.tsx:88
msgid "Name" #: assets/components/tracking/watchlist/WatchlistForm.tsx:191
msgstr ""
#: assets/components/tracking/watchlist/WatchlistForm.tsx:88
msgid "Watchlist Name"
msgstr ""
#: assets/components/tracking/watchlist/WatchlistForm.tsx:89
msgid "Naming the Watchlist makes it easier to find in the list below."
msgstr ""
#: assets/components/tracking/watchlist/WatchlistForm.tsx:100
msgid "At least one domain name"
msgstr ""
#: assets/components/tracking/watchlist/WatchlistForm.tsx:111
msgid "Domain names"
msgstr ""
#: assets/components/tracking/watchlist/WatchlistForm.tsx:129
msgid "Domain name"
msgstr ""
#: assets/components/tracking/watchlist/WatchlistForm.tsx:148
msgid "Add a Domain name"
msgstr ""
#: assets/components/tracking/watchlist/WatchlistForm.tsx:156
msgid "Tracked events" msgid "Tracked events"
msgstr "" msgstr ""
#: assets/components/tracking/watchlist/WatchlistForm.tsx:158 #: assets/components/tracking/watchlist/WatchlistCard.tsx:110
msgid "At least one trigger" #: assets/components/tracking/watchlist/WatchlistForm.tsx:217
msgid "Tracked EPP status"
msgstr "" msgstr ""
#: assets/components/tracking/watchlist/WatchlistForm.tsx:182 #: assets/components/tracking/watchlist/WatchlistForm.tsx:111
#: assets/components/tracking/watchlist/WatchlistForm.tsx:197 msgid "Name"
msgstr ""
#: assets/components/tracking/watchlist/WatchlistForm.tsx:123
msgid "Watchlist Name"
msgstr ""
#: assets/components/tracking/watchlist/WatchlistForm.tsx:124
msgid "Naming the Watchlist makes it easier to find in the list below."
msgstr ""
#: assets/components/tracking/watchlist/WatchlistForm.tsx:135
msgid "At least one domain name"
msgstr ""
#: assets/components/tracking/watchlist/WatchlistForm.tsx:146
msgid "Domain names"
msgstr ""
#: assets/components/tracking/watchlist/WatchlistForm.tsx:164
msgid "Domain name"
msgstr ""
#: assets/components/tracking/watchlist/WatchlistForm.tsx:183
msgid "Add a Domain name"
msgstr ""
#: assets/components/tracking/watchlist/WatchlistForm.tsx:193
msgid "At least one event"
msgstr ""
#: assets/components/tracking/watchlist/WatchlistForm.tsx:219
msgid "At least one EPP status"
msgstr ""
#: assets/components/tracking/watchlist/WatchlistForm.tsx:243
#: assets/components/tracking/watchlist/WatchlistForm.tsx:258
msgid "Connector" msgid "Connector"
msgstr "" msgstr ""
#: assets/components/tracking/watchlist/WatchlistForm.tsx:192 #: assets/components/tracking/watchlist/WatchlistForm.tsx:253
msgid "" msgid ""
"Please make sure the connector information is valid to purchase a domain " "Please make sure the connector information is valid to purchase a domain "
"that may be available soon." "that may be available soon."
msgstr "" msgstr ""
#: assets/components/tracking/watchlist/WatchlistForm.tsx:214 #: assets/components/tracking/watchlist/WatchlistForm.tsx:275
msgid "DSN" msgid "DSN"
msgstr "" msgstr ""
#: assets/components/tracking/watchlist/WatchlistForm.tsx:226 #: assets/components/tracking/watchlist/WatchlistForm.tsx:287
msgid "This DSN does not appear to be valid" msgid "This DSN does not appear to be valid"
msgstr "" msgstr ""
#: assets/components/tracking/watchlist/WatchlistForm.tsx:250 #: assets/components/tracking/watchlist/WatchlistForm.tsx:311
msgid "Check out this link to the Symfony documentation to help you build the DSN" msgid "Check out this link to the Symfony documentation to help you build the DSN"
msgstr "" msgstr ""
#: assets/components/tracking/watchlist/WatchlistForm.tsx:260 #: assets/components/tracking/watchlist/WatchlistForm.tsx:321
msgid "Add a Webhook" msgid "Add a Webhook"
msgstr "" msgstr ""
#: assets/components/tracking/watchlist/WatchlistForm.tsx:270 #: assets/components/tracking/watchlist/WatchlistForm.tsx:331
msgid "Update" msgid "Update"
msgstr "" msgstr ""
#: assets/components/tracking/watchlist/WatchlistForm.tsx:273 #: assets/components/tracking/watchlist/WatchlistForm.tsx:334
msgid "Reset" msgid "Reset"
msgstr "" msgstr ""
@ -579,17 +581,17 @@ msgid ""
msgstr "" msgstr ""
#: assets/pages/infrastructure/IcannRegistrarPage.tsx:111 #: assets/pages/infrastructure/IcannRegistrarPage.tsx:111
#: assets/utils/functions/rdapTranslation.ts:120 #: assets/utils/functions/rdapTranslation.ts:125
msgid "Accredited" msgid "Accredited"
msgstr "" msgstr ""
#: assets/pages/infrastructure/IcannRegistrarPage.tsx:116 #: assets/pages/infrastructure/IcannRegistrarPage.tsx:116
#: assets/utils/functions/rdapTranslation.ts:121 #: assets/utils/functions/rdapTranslation.ts:126
msgid "Reserved" msgid "Reserved"
msgstr "" msgstr ""
#: assets/pages/infrastructure/IcannRegistrarPage.tsx:121 #: assets/pages/infrastructure/IcannRegistrarPage.tsx:121
#: assets/utils/functions/rdapTranslation.ts:119 #: assets/utils/functions/rdapTranslation.ts:124
msgid "Terminated" msgid "Terminated"
msgstr "" msgstr ""
@ -723,15 +725,15 @@ msgstr ""
msgid "Create a Connector" msgid "Create a Connector"
msgstr "" msgstr ""
#: assets/pages/tracking/WatchlistPage.tsx:43 #: assets/pages/tracking/WatchlistPage.tsx:45
msgid "Watchlist created !" msgid "Watchlist created !"
msgstr "" msgstr ""
#: assets/pages/tracking/WatchlistPage.tsx:55 #: assets/pages/tracking/WatchlistPage.tsx:57
msgid "Watchlist updated !" msgid "Watchlist updated !"
msgstr "" msgstr ""
#: assets/pages/tracking/WatchlistPage.tsx:79 #: assets/pages/tracking/WatchlistPage.tsx:81
msgid "Create a Watchlist" msgid "Create a Watchlist"
msgstr "" msgstr ""
@ -953,102 +955,45 @@ msgid ""
"has expired or will expire at a predetermined date and time." "has expired or will expire at a predetermined date and time."
msgstr "" msgstr ""
#: assets/utils/functions/rdapTranslation.ts:76 #: assets/utils/functions/rdapTranslation.ts:72
msgid ""
"Signifies that the data of the object instance has been found to be "
"accurate."
msgstr ""
#: assets/utils/functions/rdapTranslation.ts:77
msgid "Renewal or reregistration of the object instance is forbidden."
msgstr ""
#: assets/utils/functions/rdapTranslation.ts:78
msgid "Updates to the object instance are forbidden."
msgstr ""
#: assets/utils/functions/rdapTranslation.ts:79
msgid "Transfers of the registration from one registrar to another are forbidden."
msgstr ""
#: assets/utils/functions/rdapTranslation.ts:80
msgid "Deletion of the registration of the object instance is forbidden."
msgstr ""
#: assets/utils/functions/rdapTranslation.ts:81 #: assets/utils/functions/rdapTranslation.ts:81
msgid "The registration of the object instance has been performed by a third party."
msgstr ""
#: assets/utils/functions/rdapTranslation.ts:82
msgid ""
"The information of the object instance is not designated for public "
"consumption."
msgstr ""
#: assets/utils/functions/rdapTranslation.ts:83
msgid ""
"Some of the information of the object instance has not been made available "
"and has been removed."
msgstr ""
#: assets/utils/functions/rdapTranslation.ts:84
msgid ""
"Some of the information of the object instance has been altered for the "
"purposes of not readily revealing the actual information of the object "
"instance."
msgstr ""
#: assets/utils/functions/rdapTranslation.ts:85
msgid ""
"The object instance is associated with other object instances in the "
"registry."
msgstr ""
#: assets/utils/functions/rdapTranslation.ts:86
msgid ""
"Changes to the object instance cannot be made, including the association of "
"other object instances."
msgstr ""
#: assets/utils/functions/rdapTranslation.ts:88
#: assets/utils/functions/rdapTranslation.ts:97
msgid "" msgid ""
"This is the standard status for a domain, meaning it has no pending " "This is the standard status for a domain, meaning it has no pending "
"operations or prohibitions." "operations or prohibitions."
msgstr "" msgstr ""
#: assets/utils/functions/rdapTranslation.ts:89 #: assets/utils/functions/rdapTranslation.ts:73
msgid "" msgid ""
"This status code indicates that delegation information (name servers) has " "This status code indicates that delegation information (name servers) has "
"not been associated with your domain. Your domain is not activated in the " "not been associated with your domain. Your domain is not activated in the "
"DNS and will not resolve." "DNS and will not resolve."
msgstr "" msgstr ""
#: assets/utils/functions/rdapTranslation.ts:90 #: assets/utils/functions/rdapTranslation.ts:74
msgid "" msgid ""
"This status code indicates that a request to create your domain has been " "This status code indicates that a request to create your domain has been "
"received and is being processed." "received and is being processed."
msgstr "" msgstr ""
#: assets/utils/functions/rdapTranslation.ts:91 #: assets/utils/functions/rdapTranslation.ts:75
msgid "" msgid ""
"This status code indicates that a request to renew your domain has been " "This status code indicates that a request to renew your domain has been "
"received and is being processed." "received and is being processed."
msgstr "" msgstr ""
#: assets/utils/functions/rdapTranslation.ts:92 #: assets/utils/functions/rdapTranslation.ts:76
msgid "" msgid ""
"This status code indicates that a request to transfer your domain to a new " "This status code indicates that a request to transfer your domain to a new "
"registrar has been received and is being processed." "registrar has been received and is being processed."
msgstr "" msgstr ""
#: assets/utils/functions/rdapTranslation.ts:93 #: assets/utils/functions/rdapTranslation.ts:77
msgid "" msgid ""
"This status code indicates that a request to update your domain has been " "This status code indicates that a request to update your domain has been "
"received and is being processed." "received and is being processed."
msgstr "" msgstr ""
#: assets/utils/functions/rdapTranslation.ts:94 #: assets/utils/functions/rdapTranslation.ts:78
msgid "" msgid ""
"This status code may be mixed with redemptionPeriod or pendingRestore. In " "This status code may be mixed with redemptionPeriod or pendingRestore. In "
"such case, depending on the status (i.e. redemptionPeriod or " "such case, depending on the status (i.e. redemptionPeriod or "
@ -1063,7 +1008,7 @@ msgid ""
"policies." "policies."
msgstr "" msgstr ""
#: assets/utils/functions/rdapTranslation.ts:95 #: assets/utils/functions/rdapTranslation.ts:79
msgid "" msgid ""
"This grace period is provided after the initial registration of a domain " "This grace period is provided after the initial registration of a domain "
"name. If the registrar deletes the domain name during this period, the " "name. If the registrar deletes the domain name during this period, the "
@ -1071,7 +1016,7 @@ msgid ""
"registration." "registration."
msgstr "" msgstr ""
#: assets/utils/functions/rdapTranslation.ts:96 #: assets/utils/functions/rdapTranslation.ts:80
msgid "" msgid ""
"This grace period is provided after a domain name registration period " "This grace period is provided after a domain name registration period "
"expires and is extended (renewed) automatically by the registry. If the " "expires and is extended (renewed) automatically by the registry. If the "
@ -1079,13 +1024,13 @@ msgid ""
"a credit to the registrar for the cost of the renewal." "a credit to the registrar for the cost of the renewal."
msgstr "" msgstr ""
#: assets/utils/functions/rdapTranslation.ts:98 #: assets/utils/functions/rdapTranslation.ts:82
msgid "" msgid ""
"This status code tells your domain's registry to reject requests to delete " "This status code tells your domain's registry to reject requests to delete "
"the domain." "the domain."
msgstr "" msgstr ""
#: assets/utils/functions/rdapTranslation.ts:99 #: assets/utils/functions/rdapTranslation.ts:83
msgid "" msgid ""
"This status code tells your domain's registry to not activate your domain " "This status code tells your domain's registry to not activate your domain "
"in the DNS and as a consequence, it will not resolve. It is an uncommon " "in the DNS and as a consequence, it will not resolve. It is an uncommon "
@ -1093,26 +1038,26 @@ msgid ""
"your domain is subject to deletion." "your domain is subject to deletion."
msgstr "" msgstr ""
#: assets/utils/functions/rdapTranslation.ts:100 #: assets/utils/functions/rdapTranslation.ts:84
msgid "" msgid ""
"This status code tells your domain's registry to reject requests to renew " "This status code tells your domain's registry to reject requests to renew "
"your domain. It is an uncommon status that is usually enacted during legal " "your domain. It is an uncommon status that is usually enacted during legal "
"disputes or when your domain is subject to deletion." "disputes or when your domain is subject to deletion."
msgstr "" msgstr ""
#: assets/utils/functions/rdapTranslation.ts:101 #: assets/utils/functions/rdapTranslation.ts:85
msgid "" msgid ""
"This status code tells your domain's registry to reject requests to " "This status code tells your domain's registry to reject requests to "
"transfer the domain from your current registrar to another." "transfer the domain from your current registrar to another."
msgstr "" msgstr ""
#: assets/utils/functions/rdapTranslation.ts:102 #: assets/utils/functions/rdapTranslation.ts:86
msgid "" msgid ""
"This status code tells your domain's registry to reject requests to update " "This status code tells your domain's registry to reject requests to update "
"the domain." "the domain."
msgstr "" msgstr ""
#: assets/utils/functions/rdapTranslation.ts:103 #: assets/utils/functions/rdapTranslation.ts:87
msgid "" msgid ""
"This status code indicates that your registrar has asked the registry to " "This status code indicates that your registrar has asked the registry to "
"restore your domain that was in redemptionPeriod status. Your registry will " "restore your domain that was in redemptionPeriod status. Your registry will "
@ -1122,7 +1067,7 @@ msgid ""
"the restoration request, the domain will revert to redemptionPeriod status." "the restoration request, the domain will revert to redemptionPeriod status."
msgstr "" msgstr ""
#: assets/utils/functions/rdapTranslation.ts:104 #: assets/utils/functions/rdapTranslation.ts:88
msgid "" msgid ""
"This status code indicates that your registrar has asked the registry to " "This status code indicates that your registrar has asked the registry to "
"delete your domain. Your domain will be held in this status for 30 days. " "delete your domain. Your domain will be held in this status for 30 days. "
@ -1131,7 +1076,7 @@ msgid ""
"registration." "registration."
msgstr "" msgstr ""
#: assets/utils/functions/rdapTranslation.ts:105 #: assets/utils/functions/rdapTranslation.ts:89
msgid "" msgid ""
"This grace period is provided after a domain name registration period is " "This grace period is provided after a domain name registration period is "
"explicitly extended (renewed) by the registrar. If the registrar deletes " "explicitly extended (renewed) by the registrar. If the registrar deletes "
@ -1139,14 +1084,14 @@ msgid ""
"registrar for the cost of the renewal." "registrar for the cost of the renewal."
msgstr "" msgstr ""
#: assets/utils/functions/rdapTranslation.ts:106 #: assets/utils/functions/rdapTranslation.ts:90
msgid "" msgid ""
"This status code prevents your domain from being deleted. It is an uncommon " "This status code prevents your domain from being deleted. It is an uncommon "
"status that is usually enacted during legal disputes, at your request, or " "status that is usually enacted during legal disputes, at your request, or "
"when a redemptionPeriod status is in place." "when a redemptionPeriod status is in place."
msgstr "" msgstr ""
#: assets/utils/functions/rdapTranslation.ts:107 #: assets/utils/functions/rdapTranslation.ts:91
msgid "" msgid ""
"This status code indicates your domain's Registry Operator will not allow " "This status code indicates your domain's Registry Operator will not allow "
"your registrar to renew your domain. It is an uncommon status that is " "your registrar to renew your domain. It is an uncommon status that is "
@ -1154,7 +1099,7 @@ msgid ""
"deletion." "deletion."
msgstr "" msgstr ""
#: assets/utils/functions/rdapTranslation.ts:108 #: assets/utils/functions/rdapTranslation.ts:92
msgid "" msgid ""
"This status code prevents your domain from being transferred from your " "This status code prevents your domain from being transferred from your "
"current registrar to another. It is an uncommon status that is usually " "current registrar to another. It is an uncommon status that is usually "
@ -1162,20 +1107,20 @@ msgid ""
"redemptionPeriod status is in place." "redemptionPeriod status is in place."
msgstr "" msgstr ""
#: assets/utils/functions/rdapTranslation.ts:109 #: assets/utils/functions/rdapTranslation.ts:93
msgid "" msgid ""
"This status code locks your domain preventing it from being updated. It is " "This status code locks your domain preventing it from being updated. It is "
"an uncommon status that is usually enacted during legal disputes, at your " "an uncommon status that is usually enacted during legal disputes, at your "
"request, or when a redemptionPeriod status is in place." "request, or when a redemptionPeriod status is in place."
msgstr "" msgstr ""
#: assets/utils/functions/rdapTranslation.ts:110 #: assets/utils/functions/rdapTranslation.ts:94
msgid "" msgid ""
"This status code is set by your domain's Registry Operator. Your domain is " "This status code is set by your domain's Registry Operator. Your domain is "
"not activated in the DNS." "not activated in the DNS."
msgstr "" msgstr ""
#: assets/utils/functions/rdapTranslation.ts:111 #: assets/utils/functions/rdapTranslation.ts:95
msgid "" msgid ""
"This grace period is provided after the successful transfer of a domain " "This grace period is provided after the successful transfer of a domain "
"name from one registrar to another. If the new registrar deletes the domain " "name from one registrar to another. If the new registrar deletes the domain "
@ -1183,13 +1128,70 @@ msgid ""
"for the cost of the transfer." "for the cost of the transfer."
msgstr "" msgstr ""
#: assets/utils/functions/rdapTranslation.ts:104
msgid ""
"Signifies that the data of the object instance has been found to be "
"accurate."
msgstr ""
#: assets/utils/functions/rdapTranslation.ts:105
msgid "Renewal or reregistration of the object instance is forbidden."
msgstr ""
#: assets/utils/functions/rdapTranslation.ts:106
msgid "Updates to the object instance are forbidden."
msgstr ""
#: assets/utils/functions/rdapTranslation.ts:107
msgid "Transfers of the registration from one registrar to another are forbidden."
msgstr ""
#: assets/utils/functions/rdapTranslation.ts:108
msgid "Deletion of the registration of the object instance is forbidden."
msgstr ""
#: assets/utils/functions/rdapTranslation.ts:109
msgid "The registration of the object instance has been performed by a third party."
msgstr ""
#: assets/utils/functions/rdapTranslation.ts:110
msgid ""
"The information of the object instance is not designated for public "
"consumption."
msgstr ""
#: assets/utils/functions/rdapTranslation.ts:111
msgid ""
"Some of the information of the object instance has not been made available "
"and has been removed."
msgstr ""
#: assets/utils/functions/rdapTranslation.ts:112
msgid ""
"Some of the information of the object instance has been altered for the "
"purposes of not readily revealing the actual information of the object "
"instance."
msgstr ""
#: assets/utils/functions/rdapTranslation.ts:113 #: assets/utils/functions/rdapTranslation.ts:113
msgid "" msgid ""
"The object instance is associated with other object instances in the "
"registry."
msgstr ""
#: assets/utils/functions/rdapTranslation.ts:114
msgid ""
"Changes to the object instance cannot be made, including the association of "
"other object instances."
msgstr ""
#: assets/utils/functions/rdapTranslation.ts:118
msgid ""
"The object instance has been allocated administratively (i.e., not for use " "The object instance has been allocated administratively (i.e., not for use "
"by the recipient in their own right in operational networks)." "by the recipient in their own right in operational networks)."
msgstr "" msgstr ""
#: assets/utils/functions/rdapTranslation.ts:114 #: assets/utils/functions/rdapTranslation.ts:119
msgid "" msgid ""
"The object instance has been allocated to an IANA special-purpose address " "The object instance has been allocated to an IANA special-purpose address "
"registry." "registry."