feat: add diagram in domain search page

This commit is contained in:
Maël Gangloff
2024-08-17 21:52:40 +02:00
parent 2ac9849f86
commit 38c9641661
10 changed files with 178 additions and 112 deletions

View File

@@ -0,0 +1,47 @@
import React, {useEffect} from "react";
import {Background, Controls, MiniMap, ReactFlow, useEdgesState, useNodesState} from "@xyflow/react";
import {Flex} from "antd";
import {Domain} from "../../utils/api";
import {getLayoutedElements} from "../tracking/watchlist/diagram/getLayoutedElements";
import {domainEntitiesToNode, domainToNode, nsToNode, tldToNode} from "../tracking/watchlist/diagram/watchlistToNodes";
import {domainEntitiesToEdges, domainNSToEdges, tldToEdge} from "../tracking/watchlist/diagram/watchlistToEdges";
export function DomainDiagram({domain}: { domain: Domain }) {
const [nodes, setNodes, onNodesChange] = useNodesState([])
const [edges, setEdges, onEdgesChange] = useEdgesState([])
useEffect(() => {
const e = getLayoutedElements([
domainToNode(domain),
...domainEntitiesToNode(domain),
tldToNode(domain.tld),
...domain.nameservers.map(nsToNode)
].flat(), [
domainEntitiesToEdges(domain),
tldToEdge(domain),
...domainNSToEdges(domain)
].flat())
setNodes(e.nodes)
setEdges(e.edges)
}, [])
return <Flex style={{width: '80vw', height: '80vh'}}>
<ReactFlow
fitView
colorMode='system'
nodesConnectable={false}
edgesReconnectable={false}
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
style={{width: '100%', height: '100%'}}
>
<MiniMap/>
<Controls/>
<Background/>
</ReactFlow>
</Flex>
}

View File

@@ -1,36 +0,0 @@
import {Watchlist} from "../../../utils/api";
import {translateRoles} from "../../search/EntitiesList";
const rolesToColor = (roles: string[]) => roles.includes('registrant') ? 'green' :
roles.includes('administrative') ? 'blue' :
roles.includes('technical') ? 'orange' : 'violet'
export function watchlistToEdges(watchlist: Watchlist) {
const domainRole = translateRoles()
const entitiesEdges = watchlist.domains
.map(d => d.entities
.filter(e => !e.roles.includes('registrar')) //
.map(e => ({
id: `e-${d.ldhName}-${e.entity.handle}`,
source: e.roles.includes('registrant') ? e.entity.handle : d.ldhName,
target: e.roles.includes('registrant') ? d.ldhName : e.entity.handle,
style: {stroke: rolesToColor(e.roles), strokeWidth: 3},
label: e.roles.map(r => Object.keys(domainRole).includes(r) ? domainRole[r as keyof typeof domainRole] : r).join(', '),
animated: e.roles.includes('registrant'),
}))
).flat()
const nameserversEdges = watchlist.domains
.map(d => d.nameservers
.map(ns => ({
id: `ns-${d.ldhName}-${ns.ldhName}`,
source: d.ldhName,
target: ns.ldhName,
style: {stroke: 'grey', strokeWidth: 3},
label: 'DNS'
}))).flat()
return [...entitiesEdges, ...nameserversEdges]
}

View File

@@ -1,52 +0,0 @@
import {Watchlist} from "../../../utils/api";
import vCard from "vcf";
import React from "react";
import {t} from 'ttag'
export function watchlistToNodes(watchlist: Watchlist) {
const domains = watchlist.domains.map(d => ({
id: d.ldhName,
data: {label: <b>{d.ldhName}</b>},
style: {
width: 200
},
parentId: d.tld.tld,
extent: 'parent'
}))
const entities = [...new Set(watchlist.domains
.map(d => d.entities
.filter(e => !e.roles.includes('registrar')) //
.map(e => {
const jCard = vCard.fromJSON(e.entity.jCard)
let label = e.entity.handle
if (jCard.data.fn !== undefined && !Array.isArray(jCard.data.fn)) label = jCard.data.fn.valueOf()
return {
id: e.entity.handle,
data: {label},
style: {
width: 200
},
parentId: d.tld.tld,
extent: 'parent'
}
})).flat())]
const tlds = [...new Set(watchlist.domains.map(d => d.tld))].map(tld => ({
id: tld.tld,
data: {label: t`.${tld.tld} Registry`},
style: {
width: 200
}
}))
const nameservers = [...new Set(watchlist.domains.map(d => d.nameservers))].flat().map(ns => ({
id: ns.ldhName,
data: {label: ns.ldhName},
style: {
width: 200
}
}))
return [...domains, ...entities, ...nameservers]
}

View File

@@ -9,7 +9,7 @@ import punycode from "punycode/punycode";
import {Connector} from "../../../utils/api/connectors";
import {UpdateWatchlistButton} from "./UpdateWatchlistButton";
import {DeleteWatchlistButton} from "./DeleteWatchlistButton";
import {ViewDiagramWatchlistButton} from "../diagram/ViewDiagramWatchlistButton";
import {ViewDiagramWatchlistButton} from "./diagram/ViewDiagramWatchlistButton";
export function WatchlistsList({watchlists, onDelete, onUpdateWatchlist, connectors}: {
watchlists: Watchlist[],

View File

@@ -5,10 +5,10 @@ import {ApartmentOutlined} from "@ant-design/icons"
import '@xyflow/react/dist/style.css'
import {Background, Controls, MiniMap, ReactFlow, useEdgesState, useNodesState} from "@xyflow/react";
import {getWatchlist} from "../../../utils/api";
import {getWatchlist} from "../../../../utils/api";
import {getLayoutedElements} from "./getLayoutedElements";
import {watchlistToNodes} from "./WatchlistToNodes";
import {watchlistToEdges} from "./WatchlistToEdges";
import {watchlistToNodes} from "./watchlistToNodes";
import {watchlistToEdges} from "./watchlistToEdges";
export type DiagramConfig = {
tld?: boolean
@@ -17,8 +17,8 @@ export type DiagramConfig = {
}
export function ViewDiagramWatchlistButton({token}: { token: string }) {
const [open, setOpen] = useState(false)
const [open, setOpen] = useState(false)
const [loading, setLoading] = useState(false)
const [nodes, setNodes, onNodesChange] = useNodesState([])
const [edges, setEdges, onEdgesChange] = useEdgesState([])

View File

@@ -0,0 +1,47 @@
import {Domain, Watchlist} from "../../../../utils/api";
import {translateRoles} from "../../../search/EntitiesList";
import {t} from "ttag";
const rolesToColor = (roles: string[]) => roles.includes('registrant') ? 'green' :
roles.includes('administrative') ? 'blue' :
roles.includes('technical') ? 'orange' : 'violet'
export function domainEntitiesToEdges(d: Domain) {
const domainRole = translateRoles()
return d.entities
.filter(e => !e.roles.includes('registrar')) //
.map(e => ({
id: `e-${d.ldhName}-${e.entity.handle}`,
source: e.roles.includes('registrant') ? e.entity.handle : d.ldhName,
target: e.roles.includes('registrant') ? d.ldhName : e.entity.handle,
style: {stroke: rolesToColor(e.roles), strokeWidth: 3},
label: e.roles
.map(r => Object.keys(domainRole).includes(r) ? domainRole[r as keyof typeof domainRole] : r)
.join(', '),
animated: e.roles.includes('registrant'),
}))
}
export const domainNSToEdges = (d: Domain) => d.nameservers
.map(ns => ({
id: `ns-${d.ldhName}-${ns.ldhName}`,
source: d.ldhName,
target: ns.ldhName,
style: {stroke: 'grey', strokeWidth: 3},
label: 'DNS'
}))
export const tldToEdge = (d: Domain) => ({
id: `tld-${d.ldhName}-${d.tld.tld}`,
source: d.tld.tld,
target: d.ldhName,
style: {stroke: 'yellow', strokeWidth: 3},
label: t`Registry`
})
export function watchlistToEdges(watchlist: Watchlist) {
const entitiesEdges = watchlist.domains.map(domainEntitiesToEdges).flat()
const nameserversEdges = watchlist.domains.map(domainNSToEdges).flat()
return [...entitiesEdges, ...nameserversEdges]
}

View File

@@ -0,0 +1,54 @@
import {Domain, Nameserver, Tld, Watchlist} from "../../../../utils/api";
import vCard from "vcf";
import React from "react";
import {t} from 'ttag'
export const domainToNode = (d: Domain) => ({
id: d.ldhName,
data: {label: <b>{d.ldhName}</b>},
style: {
width: 200
}
})
export const domainEntitiesToNode = (d: Domain) => d.entities
.filter(e => !e.roles.includes('registrar')) //
.map(e => {
const jCard = vCard.fromJSON(e.entity.jCard)
let label = e.entity.handle
if (jCard.data.fn !== undefined && !Array.isArray(jCard.data.fn)) label = jCard.data.fn.valueOf()
return {
id: e.entity.handle,
data: {label},
style: {
width: 200
}
}
})
export const tldToNode = (tld: Tld) => ({
id: tld.tld,
data: {label: t`.${tld.tld} Registry`},
style: {
width: 200
}
})
export const nsToNode = (ns: Nameserver) => ({
id: ns.ldhName,
data: {label: ns.ldhName},
style: {
width: 200
}
})
export function watchlistToNodes(watchlist: Watchlist) {
const domains = watchlist.domains.map(domainToNode)
const entities = [...new Set(watchlist.domains.map(domainEntitiesToNode).flat())]
const tlds = [...new Set(watchlist.domains.map(d => d.tld))].map(tldToNode)
const nameservers = [...new Set(watchlist.domains.map(d => d.nameservers))].flat().map(nsToNode)
return [...domains, ...entities, ...nameservers]
}

View File

@@ -7,6 +7,7 @@ import {DomainSearchBar, FieldType} from "../../components/search/DomainSearchBa
import {EventTimeline} from "../../components/search/EventTimeline";
import {EntitiesList} from "../../components/search/EntitiesList";
import {showErrorAPI} from "../../utils";
import {DomainDiagram} from "../../components/search/DomainDiagram";
const {Text} = Typography;
@@ -68,6 +69,7 @@ export default function DomainSearchPage() {
}
</Card>
</Badge.Ribbon>
<DomainDiagram domain={domain}/>
</Space>
: <Empty
description={t`Although the domain exists in my database, it has been deleted from the WHOIS by its registrar.`}/>)

View File

@@ -235,19 +235,6 @@ msgstr ""
msgid "No"
msgstr ""
#: assets/components/tracking/diagram/WatchlistToNodes.tsx:37
#, javascript-format
msgid ".${ tld.tld } Registry"
msgstr ""
#: assets/components/tracking/diagram/ViewDiagramWatchlistButton.tsx:40
msgid "View the Watchlist Entity Diagram"
msgstr ""
#: assets/components/tracking/diagram/ViewDiagramWatchlistButton.tsx:45
msgid "Watchlist Entity Diagram"
msgstr ""
#: assets/components/tracking/watchlist/WatchlistForm.tsx:66
msgid "Name"
msgstr ""
@@ -313,6 +300,23 @@ msgstr ""
msgid "Cancel"
msgstr ""
#: assets/components/tracking/watchlist/diagram/ViewDiagramWatchlistButton.tsx:40
msgid "View the Watchlist Entity Diagram"
msgstr ""
#: assets/components/tracking/watchlist/diagram/ViewDiagramWatchlistButton.tsx:45
msgid "Watchlist Entity Diagram"
msgstr ""
#: assets/components/tracking/watchlist/diagram/watchlistToEdges.tsx:39
msgid "Registry"
msgstr ""
#: assets/components/tracking/watchlist/diagram/watchlistToNodes.tsx:32
#, javascript-format
msgid ".${ tld.tld } Registry"
msgstr ""
#: assets/components/tracking/watchlist/DeleteWatchlistButton.tsx:12
#: assets/components/tracking/watchlist/DeleteWatchlistButton.tsx:19
msgid "Delete the Watchlist"
@@ -411,27 +415,27 @@ msgstr ""
msgid "Register"
msgstr ""
#: assets/pages/search/DomainSearchPage.tsx:21
#: assets/pages/search/DomainSearchPage.tsx:22
msgid "Found !"
msgstr ""
#: assets/pages/search/DomainSearchPage.tsx:29
#: assets/pages/search/DomainSearchPage.tsx:30
msgid "Domain finder"
msgstr ""
#: assets/pages/search/DomainSearchPage.tsx:50
#: assets/pages/search/DomainSearchPage.tsx:51
msgid "EPP Status Codes"
msgstr ""
#: assets/pages/search/DomainSearchPage.tsx:60
#: assets/pages/search/DomainSearchPage.tsx:61
msgid "Timeline"
msgstr ""
#: assets/pages/search/DomainSearchPage.tsx:65
#: assets/pages/search/DomainSearchPage.tsx:66
msgid "Entities"
msgstr ""
#: assets/pages/search/DomainSearchPage.tsx:73
#: assets/pages/search/DomainSearchPage.tsx:75
msgid ""
"Although the domain exists in my database, it has been deleted from the "
"WHOIS by its registrar."