mirror of
https://github.com/maelgangloff/domain-watchdog.git
synced 2025-12-29 16:15:04 +00:00
feat: add diagram in domain search page
This commit is contained in:
47
assets/components/search/DomainDiagram.tsx
Normal file
47
assets/components/search/DomainDiagram.tsx
Normal 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>
|
||||
}
|
||||
@@ -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]
|
||||
}
|
||||
@@ -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]
|
||||
}
|
||||
@@ -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[],
|
||||
|
||||
@@ -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([])
|
||||
@@ -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]
|
||||
}
|
||||
@@ -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]
|
||||
}
|
||||
@@ -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.`}/>)
|
||||
|
||||
@@ -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."
|
||||
|
||||
Reference in New Issue
Block a user