feat: add Registry Lock and Registrar Lock badges

This commit is contained in:
Maël Gangloff 2024-12-29 22:31:40 +01:00
parent f86153049b
commit 23fca8602e
No known key found for this signature in database
GPG Key ID: 11FDC81C24A7F629
10 changed files with 98 additions and 66 deletions

View File

@ -11,21 +11,32 @@ import {regionNames} from "../../i18n";
import {getCountryCode} from "../../utils/functions/getCountryCode"; import {getCountryCode} from "../../utils/functions/getCountryCode";
import {eppStatusCodeToColor} from "../../utils/functions/eppStatusCodeToColor"; import {eppStatusCodeToColor} from "../../utils/functions/eppStatusCodeToColor";
import {DomainLifecycleSteps} from "./DomainLifecycleSteps"; import {DomainLifecycleSteps} from "./DomainLifecycleSteps";
import useBreakpoint from "../../hooks/useBreakpoint"; import {BankOutlined, SafetyCertificateOutlined} from '@ant-design/icons'
export function DomainResult({domain}: { domain: Domain }) { export function DomainResult({domain}: { domain: Domain }) {
const rdapStatusCodeDetailTranslated = rdapStatusCodeDetailTranslation() const rdapStatusCodeDetailTranslated = rdapStatusCodeDetailTranslation()
const {tld, events} = domain const {tld, events} = domain
const domainEvents = events.sort((e1, e2) => new Date(e2.date).getTime() - new Date(e1.date).getTime()) const domainEvents = events.sort((e1, e2) => new Date(e2.date).getTime() - new Date(e1.date).getTime())
const xxl = useBreakpoint('xxl') const clientStatus = domain.status.filter(s => s.startsWith('client'))
const serverStatus = domain.status.filter(s => !clientStatus.includes(s))
const isLocked = (type: 'client' | 'server'): boolean =>
(domain.status.includes(type + ' update prohibited') && domain.status.includes(type + ' delete prohibited'))
|| domain.status.includes(type + ' transfer prohibited')
const statusToTag = (s: string) => <Tooltip
placement='bottomLeft'
title={rdapStatusCodeDetailTranslated[s as keyof typeof rdapStatusCodeDetailTranslated] || undefined}>
<Tag color={eppStatusCodeToColor(s)}>{s}</Tag>
</Tooltip>
return <Space direction="vertical" size="middle" style={{width: '100%'}}> return <Space direction="vertical" size="middle" style={{width: '100%'}}>
<Badge.Ribbon text={ <Badge.Ribbon text={
<Tooltip <Tooltip
title={tld.type === 'ccTLD' ? regionNames.of(getCountryCode(tld.tld)) : tld.type === 'gTLD' ? tld?.registryOperator : undefined}> title={tld.type === 'ccTLD' ? regionNames.of(getCountryCode(tld.tld)) : tld.type === 'gTLD' ? tld?.registryOperator : undefined}>
{`${(domain.tld.tld === '.' ? '' : '.') + domain.tld.tld.toUpperCase()} (${tld.type})`} {`${domain.tld.tld.toUpperCase()} (${tld.type})`}
</Tooltip> </Tooltip>
} }
color={ color={
@ -43,21 +54,34 @@ export function DomainResult({domain}: { domain: Domain }) {
domain.events.length > 0 && <DomainLifecycleSteps status={domain.status}/> domain.events.length > 0 && <DomainLifecycleSteps status={domain.status}/>
} }
<Row gutter={8}> <Row gutter={8}>
<Col span={xxl ? 24 : 12}> <Col span={24} xl={12} xxl={12}>
<Flex justify='center' align='center' style={{margin: 10}}>
<Tooltip
title={t`Registry-level protection, ensuring the highest level of security by preventing unauthorized, unwanted, or accidental changes to the domain name at the registry level`}>
<Tag bordered={false} color={isLocked('server') ? 'green' : 'default'}
icon={<SafetyCertificateOutlined
style={{fontSize: '16px'}}/>}>{t`Registry Lock`}</Tag>
</Tooltip>
<Tooltip
title={t`Registrar-level protection, safeguarding the domain from unauthorized, unwanted, or accidental changes through registrar controls`}>
<Tag bordered={false} color={isLocked('client') ? 'green' : 'default'}
icon={<BankOutlined
style={{fontSize: '16px'}}/>}>{t`Registrar Lock`}</Tag>
</Tooltip>
</Flex>
{domain.status.length > 0 && {domain.status.length > 0 &&
<> <>
<Divider orientation="left">{t`EPP Status Codes`}</Divider> <Divider orientation="left">{t`EPP Status Codes`}</Divider>
<Flex gap="4px 0" wrap> {
{ serverStatus && <Flex gap="4px 0" wrap>
domain.status.map(s => {serverStatus.map(statusToTag)}
<Tooltip </Flex>
placement='bottomLeft' }
title={s in rdapStatusCodeDetailTranslated ? rdapStatusCodeDetailTranslated[s as keyof typeof rdapStatusCodeDetailTranslated] : undefined}> {
<Tag color={eppStatusCodeToColor(s)}>{s}</Tag> clientStatus && <Flex gap="4px 0" wrap>
</Tooltip> {clientStatus.map(statusToTag)}
) </Flex>
} }
</Flex>
</> </>
} }
{ {
@ -74,14 +98,11 @@ export function DomainResult({domain}: { domain: Domain }) {
</> </>
} }
</Col> </Col>
{!xxl && <Col span={24} xl={12} xxl={12}>
<Col span={12}> <DomainDiagram domain={domain}/>
<DomainDiagram domain={domain}/> </Col>
</Col>
}
</Row> </Row>
</Card> </Card>
</Badge.Ribbon> </Badge.Ribbon>
{xxl && <DomainDiagram domain={domain}/>}
</Space> </Space>
} }

View File

@ -12,9 +12,8 @@ export function EntitiesList({domain}: { domain: Domain }) {
const rdapRoleDetailTranslated = rdapRoleDetailTranslation() const rdapRoleDetailTranslated = rdapRoleDetailTranslation()
const roleToTag = (r: string) => <Tooltip const roleToTag = (r: string) => <Tooltip
title={r in rdapRoleDetailTranslated ? rdapRoleDetailTranslated[r as keyof typeof rdapRoleDetailTranslated] : undefined}> title={rdapRoleDetailTranslated[r as keyof typeof rdapRoleDetailTranslated] || undefined}>
<Tag color={rolesToColor([r])}>{ <Tag color={rolesToColor([r])}>{rdapRoleTranslated[r as keyof typeof rdapRoleTranslated] || r
r in rdapRoleTranslated ? rdapRoleTranslated[r as keyof typeof rdapRoleTranslated] : r
}</Tag> }</Tag>
</Tooltip> </Tooltip>

View File

@ -18,14 +18,14 @@ export function EventTimeline({events}: { events: Event[] }) {
mode={sm ? "left" : "right"} mode={sm ? "left" : "right"}
items={events.map(e => { items={events.map(e => {
const eventName = <Typography.Text style={{color: e.deleted ? 'grey' : 'default'}}> const eventName = <Typography.Text style={{color: e.deleted ? 'grey' : 'default'}}>
{e.action in rdapEventNameTranslated ? rdapEventNameTranslated[e.action as keyof typeof rdapEventNameTranslated] : e.action} {rdapEventNameTranslated[e.action as keyof typeof rdapEventNameTranslated] || e.action}
</Typography.Text> </Typography.Text>
const dateStr = <Typography.Text const dateStr = <Typography.Text
style={{color: e.deleted ? 'grey' : 'default'}}>{new Date(e.date).toLocaleString(locale)} style={{color: e.deleted ? 'grey' : 'default'}}>{new Date(e.date).toLocaleString(locale)}
</Typography.Text> </Typography.Text>
const eventDetail = e.action in rdapEventDetailTranslated ? rdapEventDetailTranslated[e.action as keyof typeof rdapEventDetailTranslated] : undefined const eventDetail = rdapEventDetailTranslated[e.action as keyof typeof rdapEventDetailTranslated] || undefined
const text = sm ? { const text = sm ? {
children: <Tooltip placement='bottom' title={eventDetail}> children: <Tooltip placement='bottom' title={eventDetail}>

View File

@ -47,7 +47,7 @@ export function TrackedDomainTable() {
expirationDate: expirationDate ? new Date(expirationDate).toLocaleString() : '-', expirationDate: expirationDate ? new Date(expirationDate).toLocaleString() : '-',
status: d.status.map(s => <Tooltip status: d.status.map(s => <Tooltip
placement='bottomLeft' placement='bottomLeft'
title={s in rdapStatusCodeDetailTranslated ? rdapStatusCodeDetailTranslated[s as keyof typeof rdapStatusCodeDetailTranslated] : undefined}> title={rdapStatusCodeDetailTranslated[s as keyof typeof rdapStatusCodeDetailTranslated] || undefined}>
<Tag color={eppStatusCodeToColor(s)}>{s}</Tag> <Tag color={eppStatusCodeToColor(s)}>{s}</Tag>
</Tooltip> </Tooltip>
), ),
@ -96,7 +96,7 @@ export function TrackedDomainTable() {
filters: [...new Set(dataTable.map((d: any) => d.domain.status).flat())].map(s => ({ filters: [...new Set(dataTable.map((d: any) => d.domain.status).flat())].map(s => ({
text: <Tooltip text: <Tooltip
placement='bottomLeft' placement='bottomLeft'
title={s in rdapStatusCodeDetailTranslated ? rdapStatusCodeDetailTranslated[s as keyof typeof rdapStatusCodeDetailTranslated] : undefined}> title={rdapStatusCodeDetailTranslated[s as keyof typeof rdapStatusCodeDetailTranslated] || undefined}>
<Tag color={eppStatusCodeToColor(s)}>{s}</Tag> <Tag color={eppStatusCodeToColor(s)}>{s}</Tag>
</Tooltip>, </Tooltip>,
value: s, value: s,

View File

@ -20,21 +20,9 @@ export function WatchlistCard({watchlist, onUpdateWatchlist, connectors, onDelet
connectors: (Connector & { id: string })[], connectors: (Connector & { id: string })[],
onDelete: () => void onDelete: () => void
}) { }) {
const sm = useBreakpoint('sm')
const rdapEventNameTranslated = rdapEventNameTranslation() const rdapEventNameTranslated = rdapEventNameTranslation()
const rdapEventDetailTranslated = rdapEventDetailTranslation() const rdapEventDetailTranslated = rdapEventDetailTranslation()
const columns = [
{
title: t`Domain names`,
dataIndex: 'domains'
},
{
title: t`Tracked events`,
dataIndex: 'events'
}
]
return <> return <>
<Card <Card
type='inner' type='inner'
@ -79,7 +67,7 @@ export function WatchlistCard({watchlist, onUpdateWatchlist, connectors, onDelet
<Col span={8}> <Col span={8}>
{watchlist.triggers?.filter(t => t.action === 'email') {watchlist.triggers?.filter(t => t.action === 'email')
.map(t => <Tooltip .map(t => <Tooltip
title={t.event in rdapEventDetailTranslated ? rdapEventDetailTranslated[t.event as keyof typeof rdapEventDetailTranslated] : undefined}> title={rdapEventDetailTranslated[t.event as keyof typeof rdapEventDetailTranslated] || undefined}>
<Tag color={actionToColor(t.event)}> <Tag color={actionToColor(t.event)}>
{rdapEventNameTranslated[t.event as keyof typeof rdapEventNameTranslated]} {rdapEventNameTranslated[t.event as keyof typeof rdapEventNameTranslated]}
</Tag> </Tag>

View File

@ -43,7 +43,7 @@ export function WatchlistForm({form, connectors, onFinish, isCreation}: {
event.stopPropagation() event.stopPropagation()
} }
return (<Tooltip return (<Tooltip
title={value in rdapEventDetailTranslated ? rdapEventDetailTranslated[value as keyof typeof rdapEventDetailTranslated] : undefined}> title={rdapEventDetailTranslated[value as keyof typeof rdapEventDetailTranslated] || undefined}>
<Tag <Tag
icon={actionToIcon(value)} icon={actionToIcon(value)}
color={actionToColor(value)} color={actionToColor(value)}
@ -164,7 +164,7 @@ export function WatchlistForm({form, connectors, onFinish, isCreation}: {
style={{width: '100%'}} style={{width: '100%'}}
options={Object.keys(rdapEventNameTranslated).map(e => ({ options={Object.keys(rdapEventNameTranslated).map(e => ({
value: e, value: e,
title: e in rdapEventDetailTranslated ? rdapEventDetailTranslated[e as keyof typeof rdapEventDetailTranslated] : undefined, title: rdapEventDetailTranslated[e as keyof typeof rdapEventDetailTranslated] || undefined,
label: rdapEventNameTranslated[e as keyof typeof rdapEventNameTranslated] label: rdapEventNameTranslated[e as keyof typeof rdapEventNameTranslated]
}))} }))}
/> />

View File

@ -19,7 +19,7 @@ export function domainEntitiesToEdges(d: Domain, withRegistrar = false) {
target: e.roles.includes('registrant') || e.roles.includes('registrar') ? d.ldhName : e.entity.handle, target: e.roles.includes('registrant') || e.roles.includes('registrar') ? d.ldhName : e.entity.handle,
style: {stroke: rolesToColor(e.roles), strokeWidth: 3}, style: {stroke: rolesToColor(e.roles), strokeWidth: 3},
label: e.roles label: e.roles
.map(r => r in rdapRoleTranslated ? rdapRoleTranslated[r as keyof typeof rdapRoleTranslated] : r) .map(r => rdapRoleTranslated[r as keyof typeof rdapRoleTranslated] || r)
.join(', '), .join(', '),
animated: e.roles.includes('registrant'), animated: e.roles.includes('registrant'),
})) }))

View File

@ -9,15 +9,18 @@ import {showErrorAPI} from "../../utils/functions/showErrorAPI";
import {useNavigate, useParams} from "react-router-dom"; import {useNavigate, useParams} from "react-router-dom";
export default function DomainSearchPage() { export default function DomainSearchPage() {
const {query} = useParams()
const [domain, setDomain] = useState<Domain | null>() const [domain, setDomain] = useState<Domain | null>()
const [loading, setLoading] = useState<boolean>(false)
const [messageApi, contextHolder] = message.useMessage() const [messageApi, contextHolder] = message.useMessage()
const navigate = useNavigate() const navigate = useNavigate()
const {query} = useParams()
const onFinish: FormProps<FieldType>['onFinish'] = (values) => { const onFinish: FormProps<FieldType>['onFinish'] = (values) => {
navigate('/search/domain/' + values.ldhName) navigate('/search/domain/' + values.ldhName)
if (loading) return
setLoading(true)
setDomain(null) setDomain(null)
getDomain(values.ldhName).then(d => { getDomain(values.ldhName).then(d => {
setDomain(d) setDomain(d)
@ -25,14 +28,13 @@ export default function DomainSearchPage() {
}).catch((e: AxiosError) => { }).catch((e: AxiosError) => {
setDomain(undefined) setDomain(undefined)
showErrorAPI(e, messageApi) showErrorAPI(e, messageApi)
}) }).finally(() => setLoading(false))
} }
useEffect(() => { useEffect(() => {
if (query === undefined) return if (query === undefined) return
onFinish({ldhName: query}) onFinish({ldhName: query})
}, [query]) }, [])
return <Flex gap="middle" align="center" justify="center" vertical> return <Flex gap="middle" align="center" justify="center" vertical>
{contextHolder} {contextHolder}

View File

@ -444,6 +444,9 @@ readonly class RDAPService
->setEntity($entity) ->setEntity($entity)
->setStatus(array_unique($rdapNameserver['status'])) ->setStatus(array_unique($rdapNameserver['status']))
->setRoles($roles)); ->setRoles($roles));
$this->em->persist($nameserverEntity);
$this->em->flush();
} }
} }

View File

@ -97,15 +97,36 @@ msgstr ""
msgid "Pending Delete" msgid "Pending Delete"
msgstr "" msgstr ""
#: assets/components/search/DomainResult.tsx:49 #: assets/components/search/DomainResult.tsx:60
msgid ""
"Registry-level protection, ensuring the highest level of security by "
"preventing unauthorized, unwanted, or accidental changes to the domain name "
"at the registry level"
msgstr ""
#: assets/components/search/DomainResult.tsx:63
msgid "Registry Lock"
msgstr ""
#: assets/components/search/DomainResult.tsx:66
msgid ""
"Registrar-level protection, safeguarding the domain from unauthorized, "
"unwanted, or accidental changes through registrar controls"
msgstr ""
#: assets/components/search/DomainResult.tsx:69
msgid "Registrar Lock"
msgstr ""
#: assets/components/search/DomainResult.tsx:74
msgid "EPP Status Codes" msgid "EPP Status Codes"
msgstr "" msgstr ""
#: assets/components/search/DomainResult.tsx:65 #: assets/components/search/DomainResult.tsx:89
msgid "Timeline" msgid "Timeline"
msgstr "" msgstr ""
#: assets/components/search/DomainResult.tsx:72 #: assets/components/search/DomainResult.tsx:96
msgid "Entities" msgid "Entities"
msgstr "" msgstr ""
@ -429,21 +450,11 @@ msgstr ""
msgid "Cancel" msgid "Cancel"
msgstr "" msgstr ""
#: assets/components/tracking/watchlist/WatchlistCard.tsx:29 #: assets/components/tracking/watchlist/WatchlistCard.tsx:35
#: assets/components/tracking/watchlist/WatchlistForm.tsx:106
msgid "Domain names"
msgstr ""
#: assets/components/tracking/watchlist/WatchlistCard.tsx:33
#: assets/components/tracking/watchlist/WatchlistForm.tsx:148
msgid "Tracked events"
msgstr ""
#: assets/components/tracking/watchlist/WatchlistCard.tsx:47
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:52 #: assets/components/tracking/watchlist/WatchlistCard.tsx:40
msgid "Watchlist" msgid "Watchlist"
msgstr "" msgstr ""
@ -463,6 +474,10 @@ msgstr ""
msgid "At least one domain name" msgid "At least one domain name"
msgstr "" msgstr ""
#: assets/components/tracking/watchlist/WatchlistForm.tsx:106
msgid "Domain names"
msgstr ""
#: assets/components/tracking/watchlist/WatchlistForm.tsx:124 #: assets/components/tracking/watchlist/WatchlistForm.tsx:124
msgid "Domain name" msgid "Domain name"
msgstr "" msgstr ""
@ -471,6 +486,10 @@ msgstr ""
msgid "Add a Domain name" msgid "Add a Domain name"
msgstr "" msgstr ""
#: assets/components/tracking/watchlist/WatchlistForm.tsx:148
msgid "Tracked events"
msgstr ""
#: assets/components/tracking/watchlist/WatchlistForm.tsx:150 #: assets/components/tracking/watchlist/WatchlistForm.tsx:150
msgid "At least one trigger" msgid "At least one trigger"
msgstr "" msgstr ""
@ -514,11 +533,11 @@ msgstr ""
msgid "Sorry, the page you visited does not exist." msgid "Sorry, the page you visited does not exist."
msgstr "" msgstr ""
#: assets/pages/search/DomainSearchPage.tsx:24 #: assets/pages/search/DomainSearchPage.tsx:27
msgid "Found !" msgid "Found !"
msgstr "" msgstr ""
#: assets/pages/search/DomainSearchPage.tsx:46 #: assets/pages/search/DomainSearchPage.tsx:48
msgid "" msgid ""
"Although the domain exists in my database, it has been deleted from the " "Although the domain exists in my database, it has been deleted from the "
"WHOIS by its registrar." "WHOIS by its registrar."