Merge branch 'develop' into feat/openprovider-provider

This commit is contained in:
Maël Gangloff
2025-11-17 11:01:50 +01:00
121 changed files with 10460 additions and 1014 deletions

View File

@@ -1,6 +1,6 @@
import {Button, Form, Input, message, Space} from 'antd'
import {Button, Flex, Form, Input, message} from 'antd'
import {t} from 'ttag'
import React, {useContext, useEffect} from 'react'
import React, {useContext, useEffect, useState} from 'react'
import {getUser, login} from '../utils/api'
import {AuthenticatedContext} from '../pages/LoginPage'
import {useNavigate} from 'react-router-dom'
@@ -16,6 +16,7 @@ export function LoginForm({ssoLogin}: { ssoLogin?: boolean }) {
const navigate = useNavigate()
const [messageApi, contextHolder] = message.useMessage()
const {setIsAuthenticated} = useContext(AuthenticatedContext)
const [loading, setLoading] = useState(false)
useEffect(() => {
getUser().then(() => {
@@ -25,12 +26,15 @@ export function LoginForm({ssoLogin}: { ssoLogin?: boolean }) {
}, [])
const onFinish = (data: FieldType) => {
setLoading(true)
login(data.username, data.password).then(() => {
setIsAuthenticated(true)
navigate('/home')
}).catch((e) => {
setIsAuthenticated(false)
showErrorAPI(e, messageApi)
setLoading(false)
})
}
return (
@@ -43,6 +47,7 @@ export function LoginForm({ssoLogin}: { ssoLogin?: boolean }) {
style={{maxWidth: 600}}
onFinish={onFinish}
autoComplete='off'
disabled={loading}
>
<Form.Item
label={t`Email address`}
@@ -60,18 +65,15 @@ export function LoginForm({ssoLogin}: { ssoLogin?: boolean }) {
<Input.Password/>
</Form.Item>
<Space>
<Form.Item wrapperCol={{offset: 8, span: 16}}>
<Flex wrap justify="center" gap="middle">
<Button type='primary' htmlType='submit'>
{t`Submit`}
</Button>
</Form.Item>
{ssoLogin && <Form.Item wrapperCol={{offset: 8, span: 16}}>
{ssoLogin &&
<Button type='dashed' htmlType='button' href='/login/oauth'>
{t`Log in with SSO`}
</Button>
</Form.Item>}
</Space>
</Button>}
</Flex>
</Form>
</>
)

View File

@@ -22,16 +22,16 @@ export function ConnectorsList({connectors, onDelete}: { connectors: ConnectorEl
<>
<Divider/>
{connectors.map(connector => {
const createdAt = <Typography.Text strong>
const createdAt = <Typography.Text strong key={"createdAt"}>
{new Date(connector.createdAt).toLocaleString()}
</Typography.Text>
const {watchlistCount} = connector
const connectorName = Object.keys(ConnectorProvider).find(p => ConnectorProvider[p as keyof typeof ConnectorProvider] === connector.provider)
return <>
{contextHolder}
<Card
hoverable title={<Space>
return <Card
hoverable
key={connector.id}
title={<Space>
{t`Connector ${connectorName}`}<Typography.Text code>{connector.id}</Typography.Text>
</Space>}
size='small'
@@ -45,6 +45,7 @@ export function ConnectorsList({connectors, onDelete}: { connectors: ConnectorEl
><DeleteFilled style={{color: token.colorError}}/>
</Popconfirm>}
>
{contextHolder}
<Typography.Paragraph>{jt`Creation date: ${createdAt}`}</Typography.Paragraph>
<Typography.Paragraph>{t`Used in: ${watchlistCount} Watchlist`}</Typography.Paragraph>
<Card.Meta description={
@@ -58,7 +59,6 @@ The creation date corresponds to the date on which you consented to the creation
</>
}/>
</Card>
</>
}
)}
</>

View File

@@ -42,7 +42,6 @@ export function CreateWatchlistButton({onUpdateWatchlist, connectors}: {
paddingBottom: 80
}
}}
extra={<Button onClick={onClose}>{t`Cancel`}</Button>}
>
<WatchlistForm
form={form}

View File

@@ -22,11 +22,13 @@ import {
} from '@ant-design/icons'
import {DomainToTag} from '../../../utils/functions/DomainToTag'
import {isDomainLocked} from "../../../utils/functions/isDomainLocked"
import useBreakpoint from "../../../hooks/useBreakpoint"
export function TrackedDomainTable() {
const REDEMPTION_NOTICE = (
<Tooltip
title={t`At least one domain name is in redemption period and will potentially be deleted soon`}
key="redeptionNotice"
>
<Tag color={eppStatusCodeToColor('redemption period')}>redemption period</Tag>
</Tooltip>
@@ -35,6 +37,7 @@ export function TrackedDomainTable() {
const PENDING_DELETE_NOTICE = (
<Tooltip
title={t`At least one domain name is pending deletion and will soon become available for registration again`}
key="pendingDeleteNotice"
>
<Tag color={eppStatusCodeToColor('pending delete')}>pending delete</Tag>
</Tooltip>
@@ -53,6 +56,7 @@ export function TrackedDomainTable() {
const [dataTable, setDataTable] = useState<TableRow[]>([])
const [total, setTotal] = useState<number>()
const [specialNotice, setSpecialNotice] = useState<ReactElement[]>([])
const sm = useBreakpoint('sm')
const rdapStatusCodeDetailTranslated = rdapStatusCodeDetailTranslation()
@@ -220,6 +224,7 @@ export function TrackedDomainTable() {
text: <Tooltip
placement='bottomLeft'
title={rdapStatusCodeDetailTranslated[s as keyof typeof rdapStatusCodeDetailTranslated] || undefined}
key={s}
>
<Tag color={eppStatusCodeToColor(s)}>{s}</Tag>
</Tooltip>,
@@ -268,7 +273,8 @@ export function TrackedDomainTable() {
fetchData({page, itemsPerPage})
}
}}
scroll={{y: '50vh'}}
scroll={sm ? {} : {y: '50vh'}}
size={sm ? 'small' : 'large'}
/>
</Skeleton>
}

View File

@@ -1,4 +1,4 @@
import {Button, Drawer, Form, Typography} from 'antd'
import {Drawer, Form, Typography} from 'antd'
import {t} from 'ttag'
import {WatchlistForm} from './WatchlistForm'
import React, {useState} from 'react'
@@ -53,7 +53,6 @@ export function UpdateWatchlistButton({watchlist, onUpdateWatchlist, connectors}
paddingBottom: 80
}
}}
extra={<Button onClick={onClose}>{t`Cancel`}</Button>}
>
<WatchlistForm
form={form}

View File

@@ -0,0 +1,101 @@
import React, {useEffect, useState} from "react"
import type {ModalProps} from "antd"
import {Tag, Tooltip} from "antd"
import {Flex, Modal, Select, Typography} from "antd"
import type {Domain, Watchlist} from "../../../utils/api"
import {getWatchlists} from "../../../utils/api"
import {t} from 'ttag'
import {DomainToTag} from "../../../utils/functions/DomainToTag"
import {EllipsisOutlined} from '@ant-design/icons'
const MAX_DOMAIN_TAGS = 25
function WatchlistOption({watchlist}: {watchlist: Watchlist}) {
let domains = watchlist.domains
let rest: Domain[]|undefined = undefined
if (domains.length > MAX_DOMAIN_TAGS) {
rest = domains.slice(MAX_DOMAIN_TAGS)
domains = domains.slice(0, MAX_DOMAIN_TAGS)
}
return <Flex vertical>
<Typography.Text strong>{watchlist.name}</Typography.Text>
<Flex wrap gap='4px'>
{domains.map(d => <DomainToTag link={false} domain={d} key={d.ldhName} />)}
{rest
&& <Tooltip title={rest.map(d => <DomainToTag link={false} domain={d} key={d.ldhName} />)}>
<Tag icon={<EllipsisOutlined/>} color='processing'>
{t`${rest.length} more`}
</Tag>
</Tooltip>
}
</Flex>
</Flex>
}
interface WatchlistSelectionModalProps {
onFinish: (watchlist: Watchlist) => Promise<void>|void
description?: string
open?: boolean
modalProps?: Partial<ModalProps>
}
export default function WatchlistSelectionModal(props: WatchlistSelectionModalProps) {
const [watchlists, setWatchlists] = useState<Watchlist[] | undefined>()
const [selectedWatchlist, setSelectedWatchlist] = useState<Watchlist | undefined>()
const [validationLoading, setValidationLoading] = useState(false)
useEffect(() => {
if (props.open && !watchlists) {
getWatchlists().then(list => setWatchlists(list["hydra:member"]))
}
}, [props.open])
const onFinish = () => {
const promise = props.onFinish(selectedWatchlist as Watchlist)
if (promise) {
setValidationLoading(true)
promise.finally(() => {
setSelectedWatchlist(undefined)
setValidationLoading(false)
})
} else {
setSelectedWatchlist(undefined)
}
}
return <Modal
open={props.open}
onOk={onFinish}
okButtonProps={{
disabled: !selectedWatchlist,
loading: validationLoading,
}}
{...props.modalProps ?? {}}
>
<Flex vertical>
<Typography.Paragraph>
{
props.description
|| t`Select one of your available Watchlists`
}
</Typography.Paragraph>
<Select
placeholder={t`Watchlist`}
style={{width: '100%'}}
onChange={(_, option) => setSelectedWatchlist(option as Watchlist)}
options={watchlists}
value={selectedWatchlist?.token}
fieldNames={{
label: 'name',
value: 'token',
}}
loading={!watchlists}
status={selectedWatchlist ? '' : 'error'}
optionRender={(watchlist) => <WatchlistOption watchlist={watchlist.data}/>}
/>
</Flex>
</Modal>
}