2024-12-31 13:55:42 +01:00
|
|
|
import type {ReactElement} from 'react'
|
2025-01-01 02:42:51 +01:00
|
|
|
import React, {useEffect, useState} from 'react'
|
2024-12-31 13:55:42 +01:00
|
|
|
import type {Domain} from '../../../utils/api'
|
2025-01-01 02:42:51 +01:00
|
|
|
import {getTrackedDomainList} from '../../../utils/api'
|
|
|
|
|
import {Button, Empty, Flex, Result, Skeleton, Table, Tag, Tooltip} from 'antd'
|
2024-12-30 23:50:15 +01:00
|
|
|
import {t} from 'ttag'
|
2024-12-31 13:55:42 +01:00
|
|
|
import type {ColumnType} from 'antd/es/table'
|
2024-12-30 23:50:15 +01:00
|
|
|
import {rdapStatusCodeDetailTranslation} from '../../../utils/functions/rdapTranslation'
|
|
|
|
|
import {eppStatusCodeToColor} from '../../../utils/functions/eppStatusCodeToColor'
|
|
|
|
|
import {Link} from 'react-router-dom'
|
2025-01-01 02:42:51 +01:00
|
|
|
import {
|
|
|
|
|
BankOutlined,
|
2025-01-01 22:40:38 +01:00
|
|
|
CheckOutlined,
|
|
|
|
|
DeleteOutlined,
|
2025-01-01 02:42:51 +01:00
|
|
|
ExceptionOutlined,
|
2025-01-01 22:40:38 +01:00
|
|
|
ExclamationCircleOutlined,
|
2025-01-04 17:35:12 +01:00
|
|
|
ExclamationOutlined,
|
2025-01-01 22:40:38 +01:00
|
|
|
FieldTimeOutlined,
|
2025-01-01 02:42:51 +01:00
|
|
|
KeyOutlined,
|
|
|
|
|
MonitorOutlined,
|
|
|
|
|
SafetyCertificateOutlined
|
|
|
|
|
} from '@ant-design/icons'
|
|
|
|
|
import {DomainToTag} from '../../../utils/functions/DomainToTag'
|
|
|
|
|
import {isDomainLocked} from "../../../utils/functions/isDomainLocked"
|
2025-10-31 23:35:10 +01:00
|
|
|
import useBreakpoint from "../../../hooks/useBreakpoint"
|
2024-09-09 11:31:33 +02:00
|
|
|
|
|
|
|
|
export function TrackedDomainTable() {
|
2024-12-30 23:50:15 +01:00
|
|
|
const REDEMPTION_NOTICE = (
|
|
|
|
|
<Tooltip
|
|
|
|
|
title={t`At least one domain name is in redemption period and will potentially be deleted soon`}
|
|
|
|
|
>
|
|
|
|
|
<Tag color={eppStatusCodeToColor('redemption period')}>redemption period</Tag>
|
|
|
|
|
</Tooltip>
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const PENDING_DELETE_NOTICE = (
|
|
|
|
|
<Tooltip
|
|
|
|
|
title={t`At least one domain name is pending deletion and will soon become available for registration again`}
|
|
|
|
|
>
|
|
|
|
|
<Tag color={eppStatusCodeToColor('pending delete')}>pending delete</Tag>
|
|
|
|
|
</Tooltip>
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
interface TableRow {
|
|
|
|
|
key: string
|
|
|
|
|
ldhName: ReactElement
|
|
|
|
|
expirationDate: string
|
|
|
|
|
status: ReactElement[]
|
2025-01-01 22:40:38 +01:00
|
|
|
state: ReactElement
|
2024-12-30 23:50:15 +01:00
|
|
|
updatedAt: string
|
2025-01-01 02:42:51 +01:00
|
|
|
rawDomain: Domain
|
2024-12-30 23:50:15 +01:00
|
|
|
}
|
2024-09-09 11:31:33 +02:00
|
|
|
|
2024-12-30 23:50:15 +01:00
|
|
|
const [dataTable, setDataTable] = useState<TableRow[]>([])
|
2024-12-25 22:43:34 +01:00
|
|
|
const [total, setTotal] = useState<number>()
|
|
|
|
|
const [specialNotice, setSpecialNotice] = useState<ReactElement[]>([])
|
2025-10-31 23:35:10 +01:00
|
|
|
const sm = useBreakpoint('sm')
|
2024-09-09 11:31:33 +02:00
|
|
|
|
|
|
|
|
const rdapStatusCodeDetailTranslated = rdapStatusCodeDetailTranslation()
|
|
|
|
|
|
|
|
|
|
const fetchData = (params: { page: number, itemsPerPage: number }) => {
|
|
|
|
|
getTrackedDomainList(params).then(data => {
|
|
|
|
|
setTotal(data['hydra:totalItems'])
|
2024-12-25 22:43:34 +01:00
|
|
|
|
|
|
|
|
const notices: ReactElement[] = []
|
2024-09-09 11:31:33 +02:00
|
|
|
setDataTable(data['hydra:member'].map((d: Domain) => {
|
|
|
|
|
const expirationDate = d.events.find(e => e.action === 'expiration' && !e.deleted)?.date
|
2025-01-04 17:35:12 +01:00
|
|
|
const expiresInDays = d.expiresInDays !== undefined && d.expiresInDays > 0 ? -d.expiresInDays : undefined
|
2024-09-09 11:31:33 +02:00
|
|
|
|
2024-12-25 22:43:34 +01:00
|
|
|
if (d.status.includes('redemption period')) {
|
|
|
|
|
if (!notices.includes(REDEMPTION_NOTICE)) notices.push(REDEMPTION_NOTICE)
|
|
|
|
|
} else if (d.status.includes('pending delete')) {
|
|
|
|
|
if (!notices.includes(PENDING_DELETE_NOTICE)) notices.push(PENDING_DELETE_NOTICE)
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-09 11:31:33 +02:00
|
|
|
return {
|
|
|
|
|
key: d.ldhName,
|
2024-12-27 21:37:19 +01:00
|
|
|
ldhName: <DomainToTag domain={d}/>,
|
2024-09-09 11:31:33 +02:00
|
|
|
expirationDate: expirationDate ? new Date(expirationDate).toLocaleString() : '-',
|
|
|
|
|
status: d.status.map(s => <Tooltip
|
2024-12-30 23:50:15 +01:00
|
|
|
key={s}
|
2024-09-09 11:31:33 +02:00
|
|
|
placement='bottomLeft'
|
2024-12-30 23:50:15 +01:00
|
|
|
title={rdapStatusCodeDetailTranslated[s as keyof typeof rdapStatusCodeDetailTranslated] || undefined}
|
|
|
|
|
>
|
2024-09-09 11:31:33 +02:00
|
|
|
<Tag color={eppStatusCodeToColor(s)}>{s}</Tag>
|
|
|
|
|
</Tooltip>
|
|
|
|
|
),
|
2024-12-03 23:24:00 +01:00
|
|
|
updatedAt: new Date(d.updatedAt).toLocaleString(),
|
2025-01-01 02:42:51 +01:00
|
|
|
rawDomain: d,
|
2025-01-01 22:48:56 +01:00
|
|
|
options: <Flex wrap justify='space-evenly' align='center' gap='4px 0'>
|
2025-01-01 02:42:51 +01:00
|
|
|
<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>
|
2025-01-01 22:40:38 +01:00
|
|
|
</Flex>,
|
2025-01-01 22:48:56 +01:00
|
|
|
state: <Flex wrap justify='space-evenly' align='center' gap='4px 0'>
|
2025-01-01 22:00:05 +01:00
|
|
|
{
|
2025-01-01 22:40:38 +01:00
|
|
|
d.status.includes('auto renew period') ?
|
|
|
|
|
<Tooltip title={t`Auto-Renew Grace Period`}>
|
|
|
|
|
<Tag
|
|
|
|
|
bordered={false}
|
|
|
|
|
color='palevioletred'
|
|
|
|
|
icon={<FieldTimeOutlined/>}
|
|
|
|
|
/>
|
|
|
|
|
</Tooltip> :
|
|
|
|
|
d.status.includes('redemption period') ?
|
|
|
|
|
<Tooltip title={t`Redemption Grace Period`}>
|
|
|
|
|
<Tag
|
|
|
|
|
bordered={false}
|
|
|
|
|
color='magenta'
|
|
|
|
|
icon={<ExclamationCircleOutlined/>}
|
|
|
|
|
/>
|
|
|
|
|
</Tooltip> :
|
|
|
|
|
!d.status.includes('redemption period') && d.status.includes('pending delete') ?
|
|
|
|
|
<Tooltip title={t`Pending Delete`}>
|
|
|
|
|
<Tag
|
|
|
|
|
bordered={false}
|
|
|
|
|
color='orangered'
|
|
|
|
|
icon={<DeleteOutlined/>}
|
|
|
|
|
/>
|
|
|
|
|
</Tooltip> : <Tooltip title={t`Active`}>
|
|
|
|
|
<Tag
|
|
|
|
|
bordered={false}
|
|
|
|
|
color='green'
|
|
|
|
|
icon={<CheckOutlined/>}
|
|
|
|
|
/>
|
|
|
|
|
</Tooltip>
|
|
|
|
|
}
|
|
|
|
|
{
|
2025-01-04 17:35:12 +01:00
|
|
|
expiresInDays !== undefined ?
|
|
|
|
|
<Tooltip title={t`Estimated number of days until WHOIS removal`}>
|
|
|
|
|
<Tag bordered={false}
|
|
|
|
|
color={expiresInDays >= -5 ? 'red' : expiresInDays >= -35 ? 'orange' : 'default'}>
|
|
|
|
|
{t`J ${expiresInDays}`}
|
|
|
|
|
</Tag>
|
|
|
|
|
</Tooltip> : undefined
|
|
|
|
|
}
|
|
|
|
|
{
|
|
|
|
|
d.expiresInDays !== undefined && d.expiresInDays <= 0 ?
|
|
|
|
|
<Tooltip title={t`Deletion is imminent`}>
|
|
|
|
|
<Tag bordered={false} color='red'>
|
|
|
|
|
<ExclamationOutlined/>
|
|
|
|
|
</Tag>
|
|
|
|
|
</Tooltip> : undefined
|
2025-01-01 22:00:05 +01:00
|
|
|
}
|
2025-01-01 02:42:51 +01:00
|
|
|
</Flex>
|
2024-09-09 11:31:33 +02:00
|
|
|
}
|
|
|
|
|
}))
|
2024-12-25 22:43:34 +01:00
|
|
|
setSpecialNotice(notices)
|
2024-09-09 11:31:33 +02:00
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
fetchData({page: 1, itemsPerPage: 30})
|
|
|
|
|
}, [])
|
|
|
|
|
|
2024-12-30 23:50:15 +01:00
|
|
|
interface RecordType {
|
2025-01-01 02:42:51 +01:00
|
|
|
rawDomain: Domain
|
2024-12-30 23:50:15 +01:00
|
|
|
}
|
2024-12-25 22:43:34 +01:00
|
|
|
|
2024-12-30 23:50:15 +01:00
|
|
|
const columns: Array<ColumnType<RecordType>> = [
|
2024-09-09 11:31:33 +02:00
|
|
|
{
|
|
|
|
|
title: t`Domain`,
|
2025-01-01 02:42:51 +01:00
|
|
|
dataIndex: 'ldhName',
|
2025-01-01 22:40:38 +01:00
|
|
|
width: '20%',
|
2025-01-01 02:42:51 +01:00
|
|
|
align: 'left'
|
|
|
|
|
},
|
2025-01-01 22:40:38 +01:00
|
|
|
{
|
|
|
|
|
title: t`Status`,
|
|
|
|
|
dataIndex: 'state',
|
|
|
|
|
width: '10%',
|
|
|
|
|
align: 'center'
|
|
|
|
|
},
|
2025-01-01 02:42:51 +01:00
|
|
|
{
|
|
|
|
|
title: t`Options`,
|
|
|
|
|
dataIndex: 'options',
|
2025-01-01 22:40:38 +01:00
|
|
|
width: '10%',
|
|
|
|
|
align: 'center',
|
2024-09-09 11:31:33 +02:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: t`Expiration date`,
|
2024-12-03 23:24:00 +01:00
|
|
|
dataIndex: 'expirationDate',
|
2024-12-30 23:50:15 +01:00
|
|
|
sorter: (a: RecordType, b: RecordType) => {
|
2025-01-01 02:42:51 +01:00
|
|
|
const expirationDate1 = a.rawDomain.events.find(e => e.action === 'expiration' && !e.deleted)?.date
|
|
|
|
|
const expirationDate2 = b.rawDomain.events.find(e => e.action === 'expiration' && !e.deleted)?.date
|
2024-12-03 23:24:00 +01:00
|
|
|
|
|
|
|
|
if (expirationDate1 === undefined || expirationDate2 === undefined) return 0
|
|
|
|
|
return new Date(expirationDate1).getTime() - new Date(expirationDate2).getTime()
|
2025-01-01 02:42:51 +01:00
|
|
|
},
|
2025-01-01 22:40:38 +01:00
|
|
|
width: '15%',
|
|
|
|
|
align: 'center'
|
2024-09-09 11:31:33 +02:00
|
|
|
},
|
2024-12-25 22:43:34 +01:00
|
|
|
|
|
|
|
|
{
|
|
|
|
|
title: t`Updated at`,
|
|
|
|
|
dataIndex: 'updatedAt',
|
2025-01-01 02:42:51 +01:00
|
|
|
responsive: ['md'],
|
|
|
|
|
sorter: (a: RecordType, b: RecordType) => new Date(a.rawDomain.updatedAt).getTime() - new Date(b.rawDomain.updatedAt).getTime(),
|
2025-01-01 22:40:38 +01:00
|
|
|
width: '15%',
|
|
|
|
|
align: 'center'
|
2024-12-25 22:43:34 +01:00
|
|
|
},
|
2024-09-09 11:31:33 +02:00
|
|
|
{
|
2025-01-01 22:40:38 +01:00
|
|
|
title: t`EPP Status Codes`,
|
2024-12-03 23:24:00 +01:00
|
|
|
dataIndex: 'status',
|
2025-01-01 02:42:51 +01:00
|
|
|
responsive: ['md'],
|
2024-12-03 23:24:00 +01:00
|
|
|
showSorterTooltip: {target: 'full-header'},
|
2025-01-01 02:42:51 +01:00
|
|
|
filters: [...new Set(dataTable.map((d: RecordType) => d.rawDomain.status).flat())].map(s => ({
|
2024-12-03 23:24:00 +01:00
|
|
|
text: <Tooltip
|
|
|
|
|
placement='bottomLeft'
|
2024-12-30 23:50:15 +01:00
|
|
|
title={rdapStatusCodeDetailTranslated[s as keyof typeof rdapStatusCodeDetailTranslated] || undefined}
|
|
|
|
|
>
|
2024-12-03 23:24:00 +01:00
|
|
|
<Tag color={eppStatusCodeToColor(s)}>{s}</Tag>
|
|
|
|
|
</Tooltip>,
|
2024-12-30 23:50:15 +01:00
|
|
|
value: s
|
2024-12-03 23:24:00 +01:00
|
|
|
})),
|
2025-01-01 02:42:51 +01:00
|
|
|
onFilter: (value, record: RecordType) => record.rawDomain.status.includes(value as string),
|
|
|
|
|
width: '30%'
|
2024-09-09 11:31:33 +02:00
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
|
2025-01-01 02:42:51 +01:00
|
|
|
return total === 0
|
|
|
|
|
? <Empty
|
|
|
|
|
description={t`No tracked domain names were found, please create your first Watchlist`}
|
|
|
|
|
>
|
|
|
|
|
<Link to='/tracking/watchlist'>
|
2025-10-16 14:16:58 +02:00
|
|
|
<Button type='primary'>{t`Create now`}</Button>
|
2025-01-01 02:42:51 +01:00
|
|
|
</Link>
|
|
|
|
|
</Empty>
|
|
|
|
|
: <Skeleton loading={total === undefined}>
|
|
|
|
|
<Result
|
|
|
|
|
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`}
|
|
|
|
|
{...(specialNotice.length > 0
|
|
|
|
|
? {
|
|
|
|
|
icon: <ExceptionOutlined/>,
|
|
|
|
|
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`
|
|
|
|
|
})}
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<Table
|
|
|
|
|
loading={total === undefined}
|
|
|
|
|
columns={columns}
|
|
|
|
|
dataSource={dataTable}
|
|
|
|
|
pagination={{
|
|
|
|
|
total,
|
|
|
|
|
hideOnSinglePage: true,
|
|
|
|
|
defaultPageSize: 30,
|
|
|
|
|
onChange: (page, itemsPerPage) => {
|
|
|
|
|
fetchData({page, itemsPerPage})
|
|
|
|
|
}
|
|
|
|
|
}}
|
2025-10-31 23:35:10 +01:00
|
|
|
scroll={sm ? {} : {y: '50vh'}}
|
|
|
|
|
size={sm ? 'small' : 'large'}
|
2025-01-01 02:42:51 +01:00
|
|
|
/>
|
|
|
|
|
</Skeleton>
|
2024-12-30 23:50:15 +01:00
|
|
|
}
|