2024-08-16 03:54:48 +02:00
|
|
|
import {Button, Flex, Modal, Space, Typography} from "antd"
|
2024-08-15 22:58:15 +02:00
|
|
|
import {t} from "ttag"
|
2024-08-16 03:54:48 +02:00
|
|
|
import React, {useEffect, useState} from "react"
|
2024-08-15 22:58:15 +02:00
|
|
|
import {ApartmentOutlined} from "@ant-design/icons"
|
2024-08-16 13:56:52 +02:00
|
|
|
import vCard from "vcf";
|
2024-08-15 22:58:15 +02:00
|
|
|
|
2024-08-16 03:54:48 +02:00
|
|
|
import '@xyflow/react/dist/style.css'
|
|
|
|
|
import {Background, Controls, MiniMap, ReactFlow, useEdgesState, useNodesState} from "@xyflow/react";
|
2024-08-16 13:56:52 +02:00
|
|
|
import {getWatchlist, Watchlist} from "../../../utils/api";
|
|
|
|
|
import {translateRoles} from "../../search/EntitiesList";
|
|
|
|
|
import {getLayoutedElements} from "./getLayoutedElements";
|
2024-08-16 03:54:48 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
function watchlistToNodes(watchlist: Watchlist) {
|
|
|
|
|
const domains = watchlist.domains.map(d => ({
|
|
|
|
|
id: d.ldhName,
|
|
|
|
|
data: {label: <b>{d.ldhName}</b>},
|
|
|
|
|
style: {
|
|
|
|
|
width: 200
|
|
|
|
|
}
|
|
|
|
|
}))
|
|
|
|
|
const entities = [...new Set(watchlist.domains
|
|
|
|
|
.map(d => d.entities
|
|
|
|
|
.filter(e => !e.roles.includes('registrar'))
|
|
|
|
|
.map(e => e.entity
|
|
|
|
|
)
|
|
|
|
|
).flat())].map(e => {
|
|
|
|
|
const jCard = vCard.fromJSON(e.jCard)
|
|
|
|
|
let label = e.handle
|
|
|
|
|
if (jCard.data.fn !== undefined && !Array.isArray(jCard.data.fn)) label = jCard.data.fn.valueOf()
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
id: e.handle,
|
|
|
|
|
data: {label},
|
|
|
|
|
style: {
|
|
|
|
|
width: 200
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return [...domains, ...entities]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const rolesToColor = (roles: string[]) => roles.includes('registrant') ? 'green' :
|
2024-08-16 13:56:52 +02:00
|
|
|
roles.includes('administrative') ? 'blue' :
|
|
|
|
|
roles.includes('technical') ? 'orange' : 'violet'
|
2024-08-16 03:54:48 +02:00
|
|
|
|
|
|
|
|
function watchlistToEdges(watchlist: Watchlist) {
|
2024-08-16 13:56:52 +02:00
|
|
|
const domainRole = translateRoles()
|
|
|
|
|
|
2024-08-16 03:54:48 +02:00
|
|
|
return watchlist.domains
|
|
|
|
|
.map(d => d.entities
|
|
|
|
|
.filter(e => !e.roles.includes('registrar'))
|
|
|
|
|
.map(e => ({
|
|
|
|
|
id: `${d.ldhName}-${e.entity.handle}`,
|
|
|
|
|
source: e.roles.includes('technical') ? d.ldhName : e.entity.handle,
|
|
|
|
|
target: e.roles.includes('technical') ? e.entity.handle : d.ldhName,
|
|
|
|
|
style: {stroke: rolesToColor(e.roles), strokeWidth: 3},
|
2024-08-16 13:56:52 +02:00
|
|
|
label: e.roles.map(r => Object.keys(domainRole).includes(r) ? domainRole[r as keyof typeof domainRole] : r).join(', '),
|
2024-08-16 03:54:48 +02:00
|
|
|
animated: e.roles.includes('registrant'),
|
|
|
|
|
}))
|
|
|
|
|
).flat(2)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function ViewDiagramWatchlistButton({token}: { token: string }) {
|
2024-08-15 22:58:15 +02:00
|
|
|
const [open, setOpen] = useState(false)
|
|
|
|
|
|
2024-08-16 03:54:48 +02:00
|
|
|
const [loading, setLoading] = useState(false)
|
|
|
|
|
const [nodes, setNodes, onNodesChange] = useNodesState([]);
|
|
|
|
|
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (!open) return
|
|
|
|
|
setLoading(true)
|
|
|
|
|
getWatchlist(token).then(w => {
|
|
|
|
|
const e = getLayoutedElements(watchlistToNodes(w), watchlistToEdges(w))
|
|
|
|
|
setNodes(e.nodes)
|
|
|
|
|
setEdges(e.edges)
|
|
|
|
|
}).catch(() => setOpen(false)).finally(() => setLoading(false))
|
|
|
|
|
|
|
|
|
|
}, [open])
|
|
|
|
|
|
|
|
|
|
|
2024-08-15 22:58:15 +02:00
|
|
|
return <>
|
|
|
|
|
<Typography.Link>
|
|
|
|
|
<ApartmentOutlined title={t`View the Watchlist Entity Diagram`}
|
|
|
|
|
style={{color: 'darkviolet'}}
|
|
|
|
|
onClick={() => setOpen(true)}/>
|
|
|
|
|
</Typography.Link>
|
|
|
|
|
<Modal
|
|
|
|
|
title={t`Watchlist Entity Diagram`}
|
|
|
|
|
centered
|
|
|
|
|
open={open}
|
2024-08-16 03:54:48 +02:00
|
|
|
loading={loading}
|
2024-08-15 22:58:15 +02:00
|
|
|
footer={
|
|
|
|
|
<Space>
|
|
|
|
|
<Button type="default" onClick={() => setOpen(false)}>
|
|
|
|
|
Close
|
|
|
|
|
</Button>
|
|
|
|
|
</Space>
|
|
|
|
|
}
|
|
|
|
|
onOk={() => setOpen(false)}
|
|
|
|
|
onCancel={() => setOpen(false)}
|
2024-08-16 03:54:48 +02:00
|
|
|
width='80vw'
|
2024-08-15 22:58:15 +02:00
|
|
|
>
|
2024-08-16 03:54:48 +02:00
|
|
|
{nodes && edges && <Flex style={{width: '75vw', height: '80vh'}}>
|
|
|
|
|
<ReactFlow
|
2024-08-16 13:56:52 +02:00
|
|
|
fitView
|
|
|
|
|
colorMode='system'
|
|
|
|
|
nodesConnectable={false}
|
|
|
|
|
edgesReconnectable={false}
|
2024-08-16 03:54:48 +02:00
|
|
|
nodes={nodes}
|
|
|
|
|
edges={edges}
|
|
|
|
|
onNodesChange={onNodesChange}
|
|
|
|
|
onEdgesChange={onEdgesChange}
|
|
|
|
|
style={{width: '100%', height: '100vh'}}
|
|
|
|
|
>
|
|
|
|
|
<MiniMap/>
|
|
|
|
|
<Controls/>
|
|
|
|
|
<Background/>
|
|
|
|
|
</ReactFlow>
|
|
|
|
|
</Flex>}
|
2024-08-15 22:58:15 +02:00
|
|
|
</Modal>
|
|
|
|
|
</>
|
|
|
|
|
}
|