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
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}: {
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 }>
}) {
const [form] = Form.useForm()
@@ -36,6 +36,7 @@ export function UpdateWatchlistButton({watchlist, onUpdateWatchlist, connectors}
{name: 'connector', value: watchlist.connector?.id},
{name: 'domains', value: watchlist.domains.map(d => d.ldhName)},
{name: 'trackedEvents', value: watchlist.trackedEvents},
{name: 'trackedEppStatus', value: watchlist.trackedEppStatus},
{name: 'dsn', value: watchlist.dsn}
])
}}

View File

@@ -7,20 +7,31 @@ import {DeleteWatchlistButton} from './DeleteWatchlistButton'
import React from 'react'
import type {Connector} from '../../../utils/api/connectors'
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 {DomainToTag} from '../../../utils/functions/DomainToTag'
import type {Watchlist} from '../../../utils/api'
import {eppStatusCodeToColor} from "../../../utils/functions/eppStatusCodeToColor"
export function WatchlistCard({watchlist, onUpdateWatchlist, connectors, onDelete}: {
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 }>
onDelete: () => void
}) {
const rdapEventNameTranslated = rdapEventNameTranslation()
const rdapEventDetailTranslated = rdapEventDetailTranslation()
const rdapDomainStatusCodeDetailTranslated = rdapDomainStatusCodeDetailTranslation()
return (
<>
@@ -61,18 +72,56 @@ export function WatchlistCard({watchlist, onUpdateWatchlist, connectors, onDelet
<Card.Meta description={watchlist.token} style={{marginBottom: '1em'}}/>
<Row gutter={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 span={8}>
{watchlist.trackedEvents?.map(t => <Tooltip
key={t}
title={rdapEventDetailTranslated[t as keyof typeof rdapEventDetailTranslated] || undefined}
>
<Tag color={actionToColor(t)}>
{rdapEventNameTranslated[t as keyof typeof rdapEventNameTranslated]}
</Tag>
</Tooltip>
)}
<>
<div style={{
fontWeight: 500,
marginBottom: '0.5em',
color: '#555',
fontSize: '0.9em'
}}>
{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>
</Row>
</Card>

View File

@@ -4,11 +4,16 @@ import {t} from 'ttag'
import {ApiOutlined, MinusCircleOutlined, PlusOutlined} from '@ant-design/icons'
import React from 'react'
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 {actionToIcon} from '../../../utils/functions/actionToIcon'
import type {EventAction, Watchlist} from '../../../utils/api'
import {formItemLayoutWithOutLabel} from "../../../utils/providers"
import {eppStatusCodeToColor} from "../../../utils/functions/eppStatusCodeToColor"
type TagRender = SelectProps['tagRender']
@@ -26,14 +31,15 @@ const formItemLayout = {
export function WatchlistForm({form, connectors, onFinish, isCreation}: {
form: FormInstance
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,
watchList?: Watchlist,
}) {
const rdapEventNameTranslated = rdapEventNameTranslation()
const rdapEventDetailTranslated = rdapEventDetailTranslation()
const rdapDomainStatusCodeDetailTranslated = rdapDomainStatusCodeDetailTranslation()
const triggerTagRenderer: TagRender = ({value, closable, onClose}: {
const eventActionTagRenderer: TagRender = ({value, closable, onClose}: {
value: EventAction
closable: boolean
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 (
<Form
{...formItemLayoutWithOutLabel}
form={form}
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>
@@ -155,7 +190,7 @@ export function WatchlistForm({form, connectors, onFinish, isCreation}: {
<Form.Item
label={t`Tracked events`}
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={{
xs: {span: 24},
sm: {span: 4}
@@ -168,7 +203,7 @@ export function WatchlistForm({form, connectors, onFinish, isCreation}: {
>
<Select
mode='multiple'
tagRender={triggerTagRenderer}
tagRender={eventActionTagRenderer}
style={{width: '100%'}}
options={Object.keys(rdapEventNameTranslated).map(e => ({
value: e,
@@ -178,6 +213,32 @@ export function WatchlistForm({form, connectors, onFinish, isCreation}: {
/>
</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
label={t`Connector`}
name='connector'

View File

@@ -6,7 +6,7 @@ import type {Watchlist} from '../../../utils/api'
export function WatchlistsList({watchlists, onDelete, onUpdateWatchlist, connectors}: {
watchlists: Watchlist[]
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 }>
}) {
return (

View File

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

View File

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

View File

@@ -1,8 +1,9 @@
export const eppStatusCodeToColor = (s: string) =>
['active', 'ok'].includes(s)
? 'green'
: ['pending delete', 'redemption period'].includes(s)
? 'red'
: s.startsWith('client')
? 'purple'
: s.startsWith('server') ? 'geekblue' : 'blue'
export const eppStatusCodeToColor = (s?: string) =>
s === undefined ? 'default' :
['active', 'ok'].includes(s)
? 'green'
: ['pending delete', 'redemption period'].includes(s)
? 'red'
: s.startsWith('client')
? '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.`
})
/**
* @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.`,
export const rdapDomainStatusCodeDetailTranslation = () => ({
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.`,
'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.`,
'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).`,
reserved: t`The object instance has been allocated to an IANA special-purpose address registry.`
})