feat: improve ui

This commit is contained in:
Maël Gangloff
2024-12-20 19:41:48 +01:00
parent 1dc16d769b
commit 425071bc23
6 changed files with 124 additions and 55 deletions

View File

@@ -1,4 +1,4 @@
import {Badge, Card, Divider, Flex, Space, Tag, Tooltip, Typography} from "antd"; import {Badge, Card, Col, Divider, Flex, Row, Space, Tag, Tooltip, Typography} from "antd";
import {t} from "ttag"; import {t} from "ttag";
import {EventTimeline} from "./EventTimeline"; import {EventTimeline} from "./EventTimeline";
import {EntitiesList} from "./EntitiesList"; import {EntitiesList} from "./EntitiesList";
@@ -11,12 +11,14 @@ 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";
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')
return <Space direction="vertical" size="middle" style={{width: '100%'}}> return <Space direction="vertical" size="middle" style={{width: '100%'}}>
@@ -40,37 +42,46 @@ export function DomainResult({domain}: { domain: Domain }) {
{ {
domain.events.length > 0 && <DomainLifecycleSteps status={domain.status}/> domain.events.length > 0 && <DomainLifecycleSteps status={domain.status}/>
} }
{domain.status.length > 0 && <Row gutter={8}>
<> <Col span={xxl ? 24 : 12}>
<Divider orientation="left">{t`EPP Status Codes`}</Divider> {domain.status.length > 0 &&
<Flex gap="4px 0" wrap> <>
{ <Divider orientation="left">{t`EPP Status Codes`}</Divider>
domain.status.map(s => <Flex gap="4px 0" wrap>
<Tooltip {
placement='bottomLeft' domain.status.map(s =>
title={s in rdapStatusCodeDetailTranslated ? rdapStatusCodeDetailTranslated[s as keyof typeof rdapStatusCodeDetailTranslated] : undefined}> <Tooltip
<Tag color={eppStatusCodeToColor(s)}>{s}</Tag> placement='bottomLeft'
</Tooltip> title={s in rdapStatusCodeDetailTranslated ? rdapStatusCodeDetailTranslated[s as keyof typeof rdapStatusCodeDetailTranslated] : undefined}>
) <Tag color={eppStatusCodeToColor(s)}>{s}</Tag>
} </Tooltip>
</Flex> )
</> }
} </Flex>
{ </>
domain.events.length > 0 && <> }
<Divider orientation="left">{t`Timeline`}</Divider> {
<EventTimeline events={domainEvents}/> domain.events.length > 0 && <>
</> <Divider orientation="left">{t`Timeline`}</Divider>
} <EventTimeline events={domainEvents}/>
{ </>
domain.entities.length > 0 && }
<> {
<Divider orientation="left">{t`Entities`}</Divider> domain.entities.length > 0 &&
<EntitiesList domain={domain}/> <>
</> <Divider orientation="left">{t`Entities`}</Divider>
} <EntitiesList domain={domain}/>
</>
}
</Col>
{!xxl &&
<Col span={12}>
<DomainDiagram domain={domain}/>
</Col>
}
</Row>
</Card> </Card>
</Badge.Ribbon> </Badge.Ribbon>
<DomainDiagram domain={domain}/> {xxl && <DomainDiagram domain={domain}/>}
</Space> </Space>
} }

View File

@@ -4,9 +4,8 @@ import {Domain} from "../../utils/api";
import {rdapRoleDetailTranslation, rdapRoleTranslation} from "../../utils/functions/rdapTranslation"; import {rdapRoleDetailTranslation, rdapRoleTranslation} from "../../utils/functions/rdapTranslation";
import {roleToAvatar} from "../../utils/functions/roleToAvatar"; import {roleToAvatar} from "../../utils/functions/roleToAvatar";
import {rolesToColor} from "../../utils/functions/rolesToColor"; import {rolesToColor} from "../../utils/functions/rolesToColor";
import {entityToName} from "../../utils/functions/entityToName";
import {sortDomainEntities} from "../../utils/functions/sortDomainEntities"; import {sortDomainEntities} from "../../utils/functions/sortDomainEntities";
import {extractDetailsFromJCard} from "../../utils/functions/extractDetailsFromJCard";
export function EntitiesList({domain}: { domain: Domain }) { export function EntitiesList({domain}: { domain: Domain }) {
const rdapRoleTranslated = rdapRoleTranslation() const rdapRoleTranslated = rdapRoleTranslation()
@@ -23,15 +22,21 @@ export function EntitiesList({domain}: { domain: Domain }) {
className="demo-loadmore-list" className="demo-loadmore-list"
itemLayout="horizontal" itemLayout="horizontal"
dataSource={sortDomainEntities(domain)} dataSource={sortDomainEntities(domain)}
renderItem={(e) => renderItem={(e) => {
<List.Item> const details = extractDetailsFromJCard(e)
return <List.Item>
<List.Item.Meta <List.Item.Meta
avatar={roleToAvatar(e)} avatar={roleToAvatar(e)}
title={e.entity.handle} title={e.entity.handle}
description={entityToName(e)} description={<>
{details.fn && <div>👤 {details.fn}</div>}
{details.organization && <div>🏢 {details.organization}</div>}
</>}
/> />
{e.roles.map(roleToTag)} {e.roles.map(roleToTag)}
</List.Item> </List.Item>
} }
}
/> />
} }

View File

@@ -7,5 +7,7 @@ export const entityToName = (e: { entity: Entity }): string => {
const jCard = vCard.fromJSON(e.entity.jCard) const jCard = vCard.fromJSON(e.entity.jCard)
let name = e.entity.handle let name = e.entity.handle
if (jCard.data.fn && !Array.isArray(jCard.data.fn) && jCard.data.fn.valueOf() !== '') name = jCard.data.fn.valueOf() if (jCard.data.fn && !Array.isArray(jCard.data.fn) && jCard.data.fn.valueOf() !== '') name = jCard.data.fn.valueOf()
if (jCard.data.org && !Array.isArray(jCard.data.org) && jCard.data.org.valueOf() !== '') name = jCard.data.org.valueOf()
return name return name
} }

View File

@@ -0,0 +1,14 @@
import vCard from "vcf";
import {Entity} from "../api";
export const extractDetailsFromJCard = (e: { entity: Entity }): {
fn?: string
organization?: string;
} => {
if (e.entity.jCard.length === 0) return {fn: e.entity.handle}
const jCard = vCard.fromJSON(e.entity.jCard)
const fn = jCard.data.fn && !Array.isArray(jCard.data.fn) ? jCard.data.fn.valueOf() : undefined
const organization = jCard.data.org && !Array.isArray(jCard.data.org) ? jCard.data.org.valueOf() : undefined
return {fn, organization}
}

View File

@@ -4,6 +4,7 @@ namespace App\Repository;
use App\Entity\Entity; use App\Entity\Entity;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\DBAL\Exception;
use Doctrine\Persistence\ManagerRegistry; use Doctrine\Persistence\ManagerRegistry;
/** /**
@@ -16,6 +17,17 @@ class EntityRepository extends ServiceEntityRepository
parent::__construct($registry, Entity::class); parent::__construct($registry, Entity::class);
} }
/**
* @throws Exception
*/
public function findByjCard(array $vCardArray): array
{
return $this->getEntityManager()->getConnection()
->prepare('SELECT * FROM entity WHERE j_Card @> :data')
->executeQuery(['data' => json_encode($vCardArray)])
->fetchAllAssociative();
}
// /** // /**
// * @return Entity[] Returns an array of Entity objects // * @return Entity[] Returns an array of Entity objects
// */ // */

View File

@@ -252,11 +252,11 @@ readonly class RDAPService
if (array_key_exists('entities', $res) && is_array($res['entities'])) { if (array_key_exists('entities', $res) && is_array($res['entities'])) {
foreach ($res['entities'] as $rdapEntity) { foreach ($res['entities'] as $rdapEntity) {
if ((!array_key_exists('handle', $rdapEntity) || '' === $rdapEntity['handle']) && !array_key_exists('vcardArray', $rdapEntity)) {
continue;
}
$entity = $this->registerEntity($rdapEntity); $entity = $this->registerEntity($rdapEntity);
$this->em->persist($entity);
$this->em->flush();
$domainEntity = $this->domainEntityRepository->findOneBy([ $domainEntity = $this->domainEntityRepository->findOneBy([
'domain' => $domain, 'domain' => $domain,
'entity' => $entity, 'entity' => $entity,
@@ -323,14 +323,11 @@ readonly class RDAPService
} }
foreach ($rdapNameserver['entities'] as $rdapEntity) { foreach ($rdapNameserver['entities'] as $rdapEntity) {
if (!array_key_exists('handle', $rdapEntity) || '' === $rdapEntity['handle']) { if ((!array_key_exists('handle', $rdapEntity) || '' === $rdapEntity['handle']) && !array_key_exists('vcardArray', $rdapEntity)) {
continue; continue;
} }
$entity = $this->registerEntity($rdapEntity); $entity = $this->registerEntity($rdapEntity);
$this->em->persist($entity);
$this->em->flush();
$nameserverEntity = $this->nameserverEntityRepository->findOneBy([ $nameserverEntity = $this->nameserverEntityRepository->findOneBy([
'nameserver' => $nameserver, 'nameserver' => $nameserver,
'entity' => $entity, 'entity' => $entity,
@@ -391,20 +388,31 @@ readonly class RDAPService
*/ */
private function registerEntity(array $rdapEntity): Entity private function registerEntity(array $rdapEntity): Entity
{ {
$conn = $this->em->getConnection(); if (array_key_exists('vcardArray', $rdapEntity)) {
$sql = 'SELECT * FROM entity WHERE j_Card @> :data'; $result = $this->entityRepository->findByjCard($rdapEntity['vcardArray']);
$stmt = $conn->prepare($sql)->executeQuery(['data' => json_encode($rdapEntity['vcardArray'])]);
$result = $stmt->fetchAllAssociative(); if (!array_key_exists('handle', $rdapEntity) || '' === $rdapEntity['handle']) {
if (count($result) > 0) {
$rdapEntity['handle'] = $result[0]['handle'];
} else {
$rdapEntity['handle'] = 'DW-NOHANDLE-'.hash('md5', json_encode($rdapEntity['vcardArray']));
}
} else {
if (count($result) > 0) {
$entity = $this->entityRepository->findOneBy(['handle' => $result[0]['handle']]);
$rdapEntity['handle'] = array_key_exists('handle', $rdapEntity) && '' !== $rdapEntity['handle'] if (null !== $entity) {
? $rdapEntity['handle'] $domainEntities = $this->domainEntityRepository->findBy([
: ( 'entity' => $entity,
count($result) > 0 ]);
? $result[0]['handle']
: 'DW-NOHANDLE-'.hash('md5', json_encode($rdapEntity) $nameserverEntities = $this->nameserverEntityRepository->findBy([
) 'entity' => $entity,
); ]);
}
}
}
}
$entity = $this->entityRepository->findOneBy([ $entity = $this->entityRepository->findOneBy([
'handle' => $rdapEntity['handle'], 'handle' => $rdapEntity['handle'],
@@ -468,6 +476,23 @@ readonly class RDAPService
->setDeleted(false)); ->setDeleted(false));
} }
$this->em->persist($entity);
$this->em->flush();
if (isset($domainEntities)) {
/** @var DomainEntity[] $domainEntities */
foreach ($domainEntities as $domainEntity) {
$domainEntity->setEntity($entity);
}
}
if (isset($nameserverEntities)) {
/** @var NameserverEntity[] $nameserverEntities */
foreach ($nameserverEntities as $nameserverEntity) {
$nameserverEntity->setEntity($entity);
}
}
return $entity; return $entity;
} }