feat: add options column on Domain table

This commit is contained in:
Maël Gangloff
2025-01-01 02:42:51 +01:00
parent e9c43b3b79
commit bc9bef36bd
8 changed files with 131 additions and 91 deletions

View File

@@ -10,7 +10,8 @@ import {regionNames} from '../../i18n'
import {getCountryCode} from '../../utils/functions/getCountryCode' import {getCountryCode} from '../../utils/functions/getCountryCode'
import {DomainLifecycleSteps} from './DomainLifecycleSteps' import {DomainLifecycleSteps} from './DomainLifecycleSteps'
import {BankOutlined, KeyOutlined, SafetyCertificateOutlined} from '@ant-design/icons' import {BankOutlined, KeyOutlined, SafetyCertificateOutlined} from '@ant-design/icons'
import {statusToTag} from '../tracking/StatusToTag' import {statusToTag} from '../../utils/functions/StatusToTag'
import {isDomainLocked} from "../../utils/functions/isDomainLocked"
export function DomainResult({domain}: { domain: Domain }) { export function DomainResult({domain}: { domain: Domain }) {
const {tld, events} = domain const {tld, events} = domain
@@ -18,10 +19,6 @@ export function DomainResult({domain}: { domain: Domain }) {
const clientStatus = domain.status.filter(s => s.startsWith('client')) const clientStatus = domain.status.filter(s => s.startsWith('client'))
const serverStatus = domain.status.filter(s => !clientStatus.includes(s)) const serverStatus = domain.status.filter(s => !clientStatus.includes(s))
const isDomainLocked = (type: 'client' | 'server'): boolean =>
(domain.status.includes(type + ' update prohibited') && domain.status.includes(type + ' delete prohibited')) ||
domain.status.includes(type + ' transfer prohibited')
return ( return (
<Space direction='vertical' size='middle' style={{width: '100%'}}> <Space direction='vertical' size='middle' style={{width: '100%'}}>
@@ -60,7 +57,7 @@ export function DomainResult({domain}: { domain: Domain }) {
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`} 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 <Tag
bordered={false} color={isDomainLocked('server') ? 'green' : 'default'} bordered={false} color={isDomainLocked(domain.status, 'server') ? 'green' : 'default'}
icon={<SafetyCertificateOutlined icon={<SafetyCertificateOutlined
style={{fontSize: '16px'}} style={{fontSize: '16px'}}
/>} />}
@@ -71,7 +68,7 @@ export function DomainResult({domain}: { domain: Domain }) {
title={t`Registrar-level protection, safeguarding the domain from unauthorized, unwanted, or accidental changes through registrar controls`} title={t`Registrar-level protection, safeguarding the domain from unauthorized, unwanted, or accidental changes through registrar controls`}
> >
<Tag <Tag
bordered={false} color={isDomainLocked('client') ? 'green' : 'default'} bordered={false} color={isDomainLocked(domain.status, 'client') ? 'green' : 'default'}
icon={<BankOutlined icon={<BankOutlined
style={{fontSize: '16px'}} style={{fontSize: '16px'}}
/>} />}

View File

@@ -1,15 +1,22 @@
import type {ReactElement} from 'react' import type {ReactElement} from 'react'
import React, { useEffect, useState} from 'react' import React, {useEffect, useState} from 'react'
import type {Domain} from '../../../utils/api' import type {Domain} from '../../../utils/api'
import { getTrackedDomainList} from '../../../utils/api' import {getTrackedDomainList} from '../../../utils/api'
import {Button, Empty, Result, Skeleton, Table, Tag, Tooltip} from 'antd' import {Button, Empty, Flex, Result, Skeleton, Table, Tag, Tooltip} from 'antd'
import {t} from 'ttag' import {t} from 'ttag'
import type {ColumnType} from 'antd/es/table' import type {ColumnType} from 'antd/es/table'
import {rdapStatusCodeDetailTranslation} from '../../../utils/functions/rdapTranslation' import {rdapStatusCodeDetailTranslation} from '../../../utils/functions/rdapTranslation'
import {eppStatusCodeToColor} from '../../../utils/functions/eppStatusCodeToColor' import {eppStatusCodeToColor} from '../../../utils/functions/eppStatusCodeToColor'
import {Link} from 'react-router-dom' import {Link} from 'react-router-dom'
import {ExceptionOutlined, MonitorOutlined} from '@ant-design/icons' import {
import {DomainToTag} from '../DomainToTag' BankOutlined,
ExceptionOutlined,
KeyOutlined,
MonitorOutlined,
SafetyCertificateOutlined
} from '@ant-design/icons'
import {DomainToTag} from '../../../utils/functions/DomainToTag'
import {isDomainLocked} from "../../../utils/functions/isDomainLocked"
export function TrackedDomainTable() { export function TrackedDomainTable() {
const REDEMPTION_NOTICE = ( const REDEMPTION_NOTICE = (
@@ -34,7 +41,7 @@ export function TrackedDomainTable() {
expirationDate: string expirationDate: string
status: ReactElement[] status: ReactElement[]
updatedAt: string updatedAt: string
domain: Domain rawDomain: Domain
} }
const [dataTable, setDataTable] = useState<TableRow[]>([]) const [dataTable, setDataTable] = useState<TableRow[]>([])
@@ -70,7 +77,27 @@ export function TrackedDomainTable() {
</Tooltip> </Tooltip>
), ),
updatedAt: new Date(d.updatedAt).toLocaleString(), updatedAt: new Date(d.updatedAt).toLocaleString(),
domain: d rawDomain: d,
options: <Flex gap='4px 0' wrap>
<Tooltip title={t`Registry Lock`}>
<Tag
bordered={false} color={isDomainLocked(d.status, 'server') ? 'green' : 'default'}
icon={<SafetyCertificateOutlined/>}
/>
</Tooltip>
<Tooltip title={t`Registrar Lock`}>
<Tag
bordered={false} color={isDomainLocked(d.status, 'client') ? 'green' : 'default'}
icon={<BankOutlined/>}
/>
</Tooltip>
<Tooltip title={t`DNSSEC`}>
<Tag
bordered={false} color={d.delegationSigned ? 'green' : 'default'}
icon={<KeyOutlined/>}
/>
</Tooltip>
</Flex>
} }
})) }))
setSpecialNotice(notices) setSpecialNotice(notices)
@@ -82,36 +109,47 @@ export function TrackedDomainTable() {
}, []) }, [])
interface RecordType { interface RecordType {
domain: Domain rawDomain: Domain
} }
const columns: Array<ColumnType<RecordType>> = [ const columns: Array<ColumnType<RecordType>> = [
{ {
title: t`Domain`, title: t`Domain`,
dataIndex: 'ldhName' dataIndex: 'ldhName',
width: '30%',
align: 'left'
},
{
title: t`Options`,
dataIndex: 'options',
width: '10%',
}, },
{ {
title: t`Expiration date`, title: t`Expiration date`,
dataIndex: 'expirationDate', dataIndex: 'expirationDate',
sorter: (a: RecordType, b: RecordType) => { sorter: (a: RecordType, b: RecordType) => {
const expirationDate1 = a.domain.events.find(e => e.action === 'expiration' && !e.deleted)?.date const expirationDate1 = a.rawDomain.events.find(e => e.action === 'expiration' && !e.deleted)?.date
const expirationDate2 = b.domain.events.find(e => e.action === 'expiration' && !e.deleted)?.date const expirationDate2 = b.rawDomain.events.find(e => e.action === 'expiration' && !e.deleted)?.date
if (expirationDate1 === undefined || expirationDate2 === undefined) return 0 if (expirationDate1 === undefined || expirationDate2 === undefined) return 0
return new Date(expirationDate1).getTime() - new Date(expirationDate2).getTime() return new Date(expirationDate1).getTime() - new Date(expirationDate2).getTime()
} },
width: '15%'
}, },
{ {
title: t`Updated at`, title: t`Updated at`,
dataIndex: 'updatedAt', dataIndex: 'updatedAt',
sorter: (a: RecordType, b: RecordType) => new Date(a.domain.updatedAt).getTime() - new Date(b.domain.updatedAt).getTime() responsive: ['md'],
sorter: (a: RecordType, b: RecordType) => new Date(a.rawDomain.updatedAt).getTime() - new Date(b.rawDomain.updatedAt).getTime(),
width: '15%'
}, },
{ {
title: t`Status`, title: t`Status`,
dataIndex: 'status', dataIndex: 'status',
responsive: ['md'],
showSorterTooltip: {target: 'full-header'}, showSorterTooltip: {target: 'full-header'},
filters: [...new Set(dataTable.map((d: RecordType) => d.domain.status).flat())].map(s => ({ filters: [...new Set(dataTable.map((d: RecordType) => d.rawDomain.status).flat())].map(s => ({
text: <Tooltip text: <Tooltip
placement='bottomLeft' placement='bottomLeft'
title={rdapStatusCodeDetailTranslated[s as keyof typeof rdapStatusCodeDetailTranslated] || undefined} title={rdapStatusCodeDetailTranslated[s as keyof typeof rdapStatusCodeDetailTranslated] || undefined}
@@ -120,55 +158,50 @@ export function TrackedDomainTable() {
</Tooltip>, </Tooltip>,
value: s value: s
})), })),
onFilter: (value, record: RecordType) => record.domain.status.includes(value as string) onFilter: (value, record: RecordType) => record.rawDomain.status.includes(value as string),
width: '30%'
} }
] ]
return ( return total === 0
<> ? <Empty
{ description={t`No tracked domain names were found, please create your first Watchlist`}
total === 0 >
? <Empty <Link to='/tracking/watchlist'>
description={t`No tracked domain names were found, please create your first Watchlist`} <Button type='primary'>Create Now</Button>
> </Link>
<Link to='/tracking/watchlist'> </Empty>
<Button type='primary'>Create Now</Button> : <Skeleton loading={total === undefined}>
</Link> <Result
</Empty> style={{paddingTop: 0}}
: <Skeleton loading={total === undefined}> subTitle={t`Please note that this table does not include domain names marked as expired or those with an unknown expiration date`}
<Result {...(specialNotice.length > 0
style={{paddingTop: 0}} ? {
subTitle={t`Please note that this table does not include domain names marked as expired or those with an unknown expiration date`} icon: <ExceptionOutlined/>,
{...(specialNotice.length > 0 status: 'warning',
? { title: t`At least one domain name you are tracking requires special attention`,
icon: <ExceptionOutlined/>, extra: specialNotice
status: 'warning', }
title: t`At least one domain name you are tracking requires special attention`, : {
extra: specialNotice icon: <MonitorOutlined/>,
} status: 'info',
: { title: t`The domain names below are subject to special monitoring`
icon: <MonitorOutlined/>, })}
status: 'info', />
title: t`The domain names below are subject to special monitoring`
})}
/>
<Table <Table
loading={total === undefined} loading={total === undefined}
columns={columns} columns={columns}
dataSource={dataTable} dataSource={dataTable}
pagination={{ pagination={{
total, total,
hideOnSinglePage: true, hideOnSinglePage: true,
defaultPageSize: 30, defaultPageSize: 30,
onChange: (page, itemsPerPage) => { onChange: (page, itemsPerPage) => {
fetchData({page, itemsPerPage}) fetchData({page, itemsPerPage})
} }
}} }}
scroll={{y: '50vh'}} scroll={{y: '50vh'}}
/> />
</Skeleton> </Skeleton>
}
</>
)
} }

View File

@@ -10,7 +10,7 @@ import {CalendarWatchlistButton} from './CalendarWatchlistButton'
import {rdapEventDetailTranslation, rdapEventNameTranslation} from '../../../utils/functions/rdapTranslation' import {rdapEventDetailTranslation, rdapEventNameTranslation} from '../../../utils/functions/rdapTranslation'
import {actionToColor} from '../../../utils/functions/actionToColor' import {actionToColor} from '../../../utils/functions/actionToColor'
import {DomainToTag} from '../DomainToTag' import {DomainToTag} from '../../../utils/functions/DomainToTag'
import type {Watchlist} from '../../../utils/api' import type {Watchlist} from '../../../utils/api'
export function WatchlistCard({watchlist, onUpdateWatchlist, connectors, onDelete}: { export function WatchlistCard({watchlist, onUpdateWatchlist, connectors, onDelete}: {

View File

@@ -1,7 +1,7 @@
import {Tag, Tooltip} from 'antd' import {Tag, Tooltip} from 'antd'
import {eppStatusCodeToColor} from '../../utils/functions/eppStatusCodeToColor' import {eppStatusCodeToColor} from './eppStatusCodeToColor'
import React from 'react' import React from 'react'
import {rdapStatusCodeDetailTranslation} from '../../utils/functions/rdapTranslation' import {rdapStatusCodeDetailTranslation} from './rdapTranslation'
export function statusToTag(s: string) { export function statusToTag(s: string) {
const rdapStatusCodeDetailTranslated = rdapStatusCodeDetailTranslation() const rdapStatusCodeDetailTranslated = rdapStatusCodeDetailTranslation()

View File

@@ -0,0 +1,3 @@
export const isDomainLocked = (status: string[], type: 'client' | 'server'): boolean =>
(status.includes(type + ' update prohibited') && status.includes(type + ' delete prohibited')) ||
status.includes(type + ' transfer prohibited')

View File

@@ -118,7 +118,7 @@ class Domain
private Collection $domainStatuses; private Collection $domainStatuses;
#[ORM\Column(nullable: false)] #[ORM\Column(nullable: false)]
#[Groups(['domain:item'])] #[Groups(['domain:item', 'domain:list'])]
private ?bool $delegationSigned = null; private ?bool $delegationSigned = null;
private const IMPORTANT_EVENTS = [EventAction::Deletion->value, EventAction::Expiration->value]; private const IMPORTANT_EVENTS = [EventAction::Deletion->value, EventAction::Expiration->value];

View File

@@ -101,46 +101,49 @@ msgstr ""
msgid "Pending Delete" msgid "Pending Delete"
msgstr "" msgstr ""
#: assets/components/search/DomainResult.tsx:60 #: assets/components/search/DomainResult.tsx:57
msgid "" msgid ""
"Registry-level protection, ensuring the highest level of security by " "Registry-level protection, ensuring the highest level of security by "
"preventing unauthorized, unwanted, or accidental changes to the domain name " "preventing unauthorized, unwanted, or accidental changes to the domain name "
"at the registry level" "at the registry level"
msgstr "" msgstr ""
#: assets/components/search/DomainResult.tsx:67 #: assets/components/search/DomainResult.tsx:64
#: assets/components/tracking/watchlist/TrackedDomainTable.tsx:82
msgid "Registry Lock" msgid "Registry Lock"
msgstr "" msgstr ""
#: assets/components/search/DomainResult.tsx:71 #: assets/components/search/DomainResult.tsx:68
msgid "" msgid ""
"Registrar-level protection, safeguarding the domain from unauthorized, " "Registrar-level protection, safeguarding the domain from unauthorized, "
"unwanted, or accidental changes through registrar controls" "unwanted, or accidental changes through registrar controls"
msgstr "" msgstr ""
#: assets/components/search/DomainResult.tsx:78 #: assets/components/search/DomainResult.tsx:75
#: assets/components/tracking/watchlist/TrackedDomainTable.tsx:88
msgid "Registrar Lock" msgid "Registrar Lock"
msgstr "" msgstr ""
#: assets/components/search/DomainResult.tsx:82 #: assets/components/search/DomainResult.tsx:79
msgid "" msgid ""
"DNSSEC secures DNS by adding cryptographic signatures to DNS records, " "DNSSEC secures DNS by adding cryptographic signatures to DNS records, "
"ensuring authenticity and integrity of responses" "ensuring authenticity and integrity of responses"
msgstr "" msgstr ""
#: assets/components/search/DomainResult.tsx:87 #: assets/components/search/DomainResult.tsx:84
#: assets/components/tracking/watchlist/TrackedDomainTable.tsx:94
msgid "DNSSEC" msgid "DNSSEC"
msgstr "" msgstr ""
#: assets/components/search/DomainResult.tsx:93 #: assets/components/search/DomainResult.tsx:90
msgid "EPP Status Codes" msgid "EPP Status Codes"
msgstr "" msgstr ""
#: assets/components/search/DomainResult.tsx:101 #: assets/components/search/DomainResult.tsx:98
msgid "Timeline" msgid "Timeline"
msgstr "" msgstr ""
#: assets/components/search/DomainResult.tsx:108 #: assets/components/search/DomainResult.tsx:105
msgid "Entities" msgid "Entities"
msgstr "" msgstr ""
@@ -158,7 +161,7 @@ msgid "Search"
msgstr "" msgstr ""
#: assets/components/Sider.tsx:41 #: assets/components/Sider.tsx:41
#: assets/components/tracking/watchlist/TrackedDomainTable.tsx:90 #: assets/components/tracking/watchlist/TrackedDomainTable.tsx:117
msgid "Domain" msgid "Domain"
msgstr "" msgstr ""
@@ -410,45 +413,49 @@ msgstr ""
msgid ".${ tld.tld } Registry" msgid ".${ tld.tld } Registry"
msgstr "" msgstr ""
#: assets/components/tracking/watchlist/TrackedDomainTable.tsx:17 #: assets/components/tracking/watchlist/TrackedDomainTable.tsx:24
msgid "" msgid ""
"At least one domain name is in redemption period and will potentially be " "At least one domain name is in redemption period and will potentially be "
"deleted soon" "deleted soon"
msgstr "" msgstr ""
#: assets/components/tracking/watchlist/TrackedDomainTable.tsx:25 #: assets/components/tracking/watchlist/TrackedDomainTable.tsx:32
msgid "" msgid ""
"At least one domain name is pending deletion and will soon become available " "At least one domain name is pending deletion and will soon become available "
"for registration again" "for registration again"
msgstr "" msgstr ""
#: assets/components/tracking/watchlist/TrackedDomainTable.tsx:94 #: assets/components/tracking/watchlist/TrackedDomainTable.tsx:123
msgid "Options"
msgstr ""
#: assets/components/tracking/watchlist/TrackedDomainTable.tsx:128
msgid "Expiration date" msgid "Expiration date"
msgstr "" msgstr ""
#: assets/components/tracking/watchlist/TrackedDomainTable.tsx:106 #: assets/components/tracking/watchlist/TrackedDomainTable.tsx:141
msgid "Updated at" msgid "Updated at"
msgstr "" msgstr ""
#: assets/components/tracking/watchlist/TrackedDomainTable.tsx:111 #: assets/components/tracking/watchlist/TrackedDomainTable.tsx:148
msgid "Status" msgid "Status"
msgstr "" msgstr ""
#: assets/components/tracking/watchlist/TrackedDomainTable.tsx:132 #: assets/components/tracking/watchlist/TrackedDomainTable.tsx:168
msgid "No tracked domain names were found, please create your first Watchlist" msgid "No tracked domain names were found, please create your first Watchlist"
msgstr "" msgstr ""
#: assets/components/tracking/watchlist/TrackedDomainTable.tsx:141 #: assets/components/tracking/watchlist/TrackedDomainTable.tsx:177
msgid "" msgid ""
"Please note that this table does not include domain names marked as expired " "Please note that this table does not include domain names marked as expired "
"or those with an unknown expiration date" "or those with an unknown expiration date"
msgstr "" msgstr ""
#: assets/components/tracking/watchlist/TrackedDomainTable.tsx:146 #: assets/components/tracking/watchlist/TrackedDomainTable.tsx:182
msgid "At least one domain name you are tracking requires special attention" msgid "At least one domain name you are tracking requires special attention"
msgstr "" msgstr ""
#: assets/components/tracking/watchlist/TrackedDomainTable.tsx:152 #: assets/components/tracking/watchlist/TrackedDomainTable.tsx:188
msgid "The domain names below are subject to special monitoring" msgid "The domain names below are subject to special monitoring"
msgstr "" msgstr ""