mirror of
https://github.com/maelgangloff/domain-watchdog.git
synced 2025-12-29 16:15:04 +00:00
feat: add qr-code for iCalendar export
This commit is contained in:
@@ -0,0 +1,22 @@
|
|||||||
|
import {CalendarFilled} from "@ant-design/icons";
|
||||||
|
import {t} from "ttag";
|
||||||
|
import {Popover, QRCode, Typography} from "antd";
|
||||||
|
import React from "react";
|
||||||
|
import {Watchlist} from "../../../pages/tracking/WatchlistPage";
|
||||||
|
|
||||||
|
export function CalendarWatchlistButton({watchlist}: { watchlist: Watchlist }) {
|
||||||
|
|
||||||
|
const icsResourceLink = `${window.location.origin}/api/watchlists/${watchlist.token}/calendar`
|
||||||
|
|
||||||
|
return <Typography.Link href={icsResourceLink}>
|
||||||
|
<Popover content={<QRCode value={icsResourceLink}
|
||||||
|
bordered={false}
|
||||||
|
title={t`QR Code for iCalendar export`}
|
||||||
|
type='svg'
|
||||||
|
/>}>
|
||||||
|
<CalendarFilled title={t`Export events to iCalendar format`}
|
||||||
|
style={{color: 'limegreen'}}
|
||||||
|
/>
|
||||||
|
</Popover>
|
||||||
|
</Typography.Link>
|
||||||
|
}
|
||||||
87
assets/components/tracking/watchlist/WatchlistCard.tsx
Normal file
87
assets/components/tracking/watchlist/WatchlistCard.tsx
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
import {Card, Divider, Flex, Space, Table, Tag, Typography} from "antd";
|
||||||
|
import {DisconnectOutlined, LinkOutlined} from "@ant-design/icons";
|
||||||
|
import {t} from "ttag";
|
||||||
|
import {ViewDiagramWatchlistButton} from "./diagram/ViewDiagramWatchlistButton";
|
||||||
|
import {UpdateWatchlistButton} from "./UpdateWatchlistButton";
|
||||||
|
import {DeleteWatchlistButton} from "./DeleteWatchlistButton";
|
||||||
|
import punycode from "punycode/punycode";
|
||||||
|
import {actionToColor, domainEvent} from "../../search/EventTimeline";
|
||||||
|
import React, {useState} from "react";
|
||||||
|
import {Watchlist} from "../../../pages/tracking/WatchlistPage";
|
||||||
|
import {Connector} from "../../../utils/api/connectors";
|
||||||
|
import useBreakpoint from "../../../hooks/useBreakpoint";
|
||||||
|
import {CalendarWatchlistButton} from "./CalendarWatchlistButton";
|
||||||
|
|
||||||
|
export function WatchlistCard({watchlist, onUpdateWatchlist, connectors, onDelete}: {
|
||||||
|
watchlist: Watchlist,
|
||||||
|
onUpdateWatchlist: (values: { domains: string[], triggers: string[], token: string }) => Promise<void>,
|
||||||
|
connectors: (Connector & { id: string })[],
|
||||||
|
onDelete: () => void
|
||||||
|
}) {
|
||||||
|
const sm = useBreakpoint('sm')
|
||||||
|
const domainEventTranslated = domainEvent()
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: t`Domain names`,
|
||||||
|
dataIndex: 'domains'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t`Tracked events`,
|
||||||
|
dataIndex: 'events'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
return <>
|
||||||
|
<Card
|
||||||
|
type='inner'
|
||||||
|
title={<>
|
||||||
|
{
|
||||||
|
watchlist.connector ?
|
||||||
|
<Tag icon={<LinkOutlined/>} color="lime-inverse" title={watchlist.connector.id}/> :
|
||||||
|
<Tag icon={<DisconnectOutlined/>} color="default"
|
||||||
|
title={t`This Watchlist is not linked to a Connector.`}/>
|
||||||
|
}
|
||||||
|
<Typography.Text title={new Date(watchlist.createdAt).toLocaleString()}>
|
||||||
|
{t`Watchlist` + (watchlist.name ? ` (${watchlist.name})` : '')}
|
||||||
|
</Typography.Text>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
size='small'
|
||||||
|
style={{width: '100%'}}
|
||||||
|
extra={
|
||||||
|
<Space size='middle'>
|
||||||
|
<ViewDiagramWatchlistButton token={watchlist.token}/>
|
||||||
|
|
||||||
|
<CalendarWatchlistButton watchlist={watchlist}/>
|
||||||
|
|
||||||
|
<UpdateWatchlistButton
|
||||||
|
watchlist={watchlist}
|
||||||
|
onUpdateWatchlist={onUpdateWatchlist}
|
||||||
|
connectors={connectors}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<DeleteWatchlistButton watchlist={watchlist} onDelete={onDelete}/>
|
||||||
|
</Space>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Card.Meta description={watchlist.token} style={{marginBottom: '1em'}}/>
|
||||||
|
<Table
|
||||||
|
size='small'
|
||||||
|
columns={columns}
|
||||||
|
pagination={false}
|
||||||
|
style={{width: '100%'}}
|
||||||
|
dataSource={[{
|
||||||
|
domains: watchlist.domains.map(d => <Tag>{punycode.toUnicode(d.ldhName)}</Tag>),
|
||||||
|
events: watchlist.triggers?.filter(t => t.action === 'email')
|
||||||
|
.map(t => <Tag color={actionToColor(t.event)}>
|
||||||
|
{domainEventTranslated[t.event as keyof typeof domainEventTranslated]}
|
||||||
|
</Tag>
|
||||||
|
)
|
||||||
|
}]}
|
||||||
|
{...(sm ? {scroll: {y: 'max-content'}} : {scroll: {y: 240}})}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
<Divider/>
|
||||||
|
</>
|
||||||
|
}
|
||||||
@@ -1,15 +1,7 @@
|
|||||||
import {Card, Divider, Space, Table, Tag, Typography} from "antd";
|
|
||||||
import {t} from "ttag";
|
|
||||||
import {CalendarFilled, DisconnectOutlined, LinkOutlined} from "@ant-design/icons";
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import useBreakpoint from "../../../hooks/useBreakpoint";
|
|
||||||
import {actionToColor, domainEvent} from "../../search/EventTimeline";
|
|
||||||
import {Watchlist} from "../../../pages/tracking/WatchlistPage";
|
import {Watchlist} from "../../../pages/tracking/WatchlistPage";
|
||||||
import punycode from "punycode/punycode";
|
|
||||||
import {Connector} from "../../../utils/api/connectors";
|
import {Connector} from "../../../utils/api/connectors";
|
||||||
import {UpdateWatchlistButton} from "./UpdateWatchlistButton";
|
import {WatchlistCard} from "./WatchlistCard";
|
||||||
import {DeleteWatchlistButton} from "./DeleteWatchlistButton";
|
|
||||||
import {ViewDiagramWatchlistButton} from "./diagram/ViewDiagramWatchlistButton";
|
|
||||||
|
|
||||||
export function WatchlistsList({watchlists, onDelete, onUpdateWatchlist, connectors}: {
|
export function WatchlistsList({watchlists, onDelete, onUpdateWatchlist, connectors}: {
|
||||||
watchlists: Watchlist[],
|
watchlists: Watchlist[],
|
||||||
@@ -17,79 +9,15 @@ export function WatchlistsList({watchlists, onDelete, onUpdateWatchlist, connect
|
|||||||
onUpdateWatchlist: (values: { domains: string[], triggers: string[], token: string }) => Promise<void>,
|
onUpdateWatchlist: (values: { domains: string[], triggers: string[], token: string }) => Promise<void>,
|
||||||
connectors: (Connector & { id: string })[]
|
connectors: (Connector & { id: string })[]
|
||||||
}) {
|
}) {
|
||||||
const sm = useBreakpoint('sm')
|
|
||||||
const domainEventTranslated = domainEvent()
|
|
||||||
|
|
||||||
|
|
||||||
const columns = [
|
|
||||||
{
|
|
||||||
title: t`Domain names`,
|
|
||||||
dataIndex: 'domains'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: t`Tracked events`,
|
|
||||||
dataIndex: 'events'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
return <>
|
return <>
|
||||||
{watchlists.map(watchlist =>
|
{watchlists.map(watchlist =>
|
||||||
<>
|
<WatchlistCard watchlist={watchlist}
|
||||||
<Card
|
onUpdateWatchlist={onUpdateWatchlist}
|
||||||
hoverable
|
connectors={connectors}
|
||||||
title={<>
|
onDelete={onDelete}/>
|
||||||
{
|
)
|
||||||
watchlist.connector ?
|
}
|
||||||
<Tag icon={<LinkOutlined/>} color="lime-inverse" title={watchlist.connector.id}/> :
|
|
||||||
<Tag icon={<DisconnectOutlined/>} color="default"
|
|
||||||
title={t`This Watchlist is not linked to a Connector.`}/>
|
|
||||||
}
|
|
||||||
<Typography.Text title={new Date(watchlist.createdAt).toLocaleString()}>
|
|
||||||
{t`Watchlist` + (watchlist.name ? ` (${watchlist.name})` : '')}
|
|
||||||
</Typography.Text>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
size='small'
|
|
||||||
style={{width: '100%'}}
|
|
||||||
extra={<Space size='middle'>
|
|
||||||
|
|
||||||
<ViewDiagramWatchlistButton token={watchlist.token}/>
|
|
||||||
|
|
||||||
<Typography.Link href={`/api/watchlists/${watchlist.token}/calendar`}>
|
|
||||||
<CalendarFilled title={t`Export events to iCalendar format`}
|
|
||||||
style={{color: 'limegreen'}}/>
|
|
||||||
</Typography.Link>
|
|
||||||
|
|
||||||
<UpdateWatchlistButton
|
|
||||||
watchlist={watchlist}
|
|
||||||
onUpdateWatchlist={onUpdateWatchlist}
|
|
||||||
connectors={connectors}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<DeleteWatchlistButton watchlist={watchlist} onDelete={onDelete}/>
|
|
||||||
|
|
||||||
</Space>}
|
|
||||||
>
|
|
||||||
<Card.Meta description={watchlist.token} style={{marginBottom: '1em'}}/>
|
|
||||||
<Table
|
|
||||||
size='small'
|
|
||||||
columns={columns}
|
|
||||||
pagination={false}
|
|
||||||
style={{width: '100%'}}
|
|
||||||
dataSource={[{
|
|
||||||
domains: watchlist.domains.map(d => <Tag>{punycode.toUnicode(d.ldhName)}</Tag>),
|
|
||||||
events: watchlist.triggers?.filter(t => t.action === 'email')
|
|
||||||
.map(t => <Tag color={actionToColor(t.event)}>
|
|
||||||
{domainEventTranslated[t.event as keyof typeof domainEventTranslated]}
|
|
||||||
</Tag>
|
|
||||||
)
|
|
||||||
}]}
|
|
||||||
{...(sm ? {scroll: {y: 'max-content'}} : {scroll: {y: 240}})}
|
|
||||||
/>
|
|
||||||
</Card>
|
|
||||||
<Divider/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
@@ -10,12 +10,6 @@ import {getLayoutedElements} from "./getLayoutedElements";
|
|||||||
import {watchlistToNodes} from "./watchlistToNodes";
|
import {watchlistToNodes} from "./watchlistToNodes";
|
||||||
import {watchlistToEdges} from "./watchlistToEdges";
|
import {watchlistToEdges} from "./watchlistToEdges";
|
||||||
|
|
||||||
export type DiagramConfig = {
|
|
||||||
tld?: boolean
|
|
||||||
nameserver?: boolean
|
|
||||||
entities?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ViewDiagramWatchlistButton({token}: { token: string }) {
|
export function ViewDiagramWatchlistButton({token}: { token: string }) {
|
||||||
|
|
||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
@@ -24,9 +18,11 @@ export function ViewDiagramWatchlistButton({token}: { token: string }) {
|
|||||||
const [edges, setEdges, onEdgesChange] = useEdgesState([])
|
const [edges, setEdges, onEdgesChange] = useEdgesState([])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setNodes([])
|
|
||||||
setEdges([])
|
setEdges([])
|
||||||
|
setNodes([])
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
if (!open) return
|
if (!open) return
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
getWatchlist(token).then(w => {
|
getWatchlist(token).then(w => {
|
||||||
@@ -58,19 +54,22 @@ export function ViewDiagramWatchlistButton({token}: { token: string }) {
|
|||||||
}
|
}
|
||||||
onOk={() => setOpen(false)}
|
onOk={() => setOpen(false)}
|
||||||
onCancel={() => setOpen(false)}
|
onCancel={() => setOpen(false)}
|
||||||
width='85vw'
|
width='90vw'
|
||||||
|
height='100%'
|
||||||
>
|
>
|
||||||
<Flex style={{width: '80vw', height: '80vh'}}>
|
<Flex style={{width: '85vw', height: '85vh'}}>
|
||||||
<ReactFlow
|
<ReactFlow
|
||||||
fitView
|
fitView
|
||||||
colorMode='system'
|
colorMode='system'
|
||||||
|
defaultEdges={[]}
|
||||||
|
defaultNodes={[]}
|
||||||
nodesConnectable={false}
|
nodesConnectable={false}
|
||||||
edgesReconnectable={false}
|
edgesReconnectable={false}
|
||||||
nodes={nodes}
|
nodes={nodes}
|
||||||
edges={edges}
|
edges={edges}
|
||||||
onNodesChange={onNodesChange}
|
onNodesChange={onNodesChange}
|
||||||
onEdgesChange={onEdgesChange}
|
onEdgesChange={onEdgesChange}
|
||||||
style={{width: '100%', height: '100vh'}}
|
style={{width: '100%', height: '100%'}}
|
||||||
>
|
>
|
||||||
<MiniMap/>
|
<MiniMap/>
|
||||||
<Controls/>
|
<Controls/>
|
||||||
|
|||||||
@@ -40,9 +40,10 @@ export const tldToEdge = (d: Domain) => ({
|
|||||||
label: t`Registry`
|
label: t`Registry`
|
||||||
})
|
})
|
||||||
|
|
||||||
export function watchlistToEdges(watchlist: Watchlist) {
|
export function watchlistToEdges(watchlist: Watchlist, withRegistrar = false, withTld = false) {
|
||||||
const entitiesEdges = watchlist.domains.map(d => domainEntitiesToEdges(d)).flat()
|
const entitiesEdges = watchlist.domains.map(d => domainEntitiesToEdges(d, withRegistrar)).flat()
|
||||||
const nameserversEdges = watchlist.domains.map(domainNSToEdges).flat()
|
const nameserversEdges = watchlist.domains.map(domainNSToEdges).flat()
|
||||||
|
const tldEdge = watchlist.domains.map(tldToEdge)
|
||||||
|
|
||||||
return [...entitiesEdges, ...nameserversEdges]
|
return [...entitiesEdges, ...nameserversEdges, ...(withTld ? tldEdge : [])]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ export const domainEntitiesToNode = (d: Domain, withRegistrar = false) => d.enti
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
id: e.entity.handle,
|
id: e.entity.handle,
|
||||||
|
type: e.roles.includes('registrant') || e.roles.includes('registrar') ? 'input' : 'output',
|
||||||
data: {label},
|
data: {label},
|
||||||
style: {
|
style: {
|
||||||
width: 200
|
width: 200
|
||||||
@@ -30,6 +31,7 @@ export const domainEntitiesToNode = (d: Domain, withRegistrar = false) => d.enti
|
|||||||
export const tldToNode = (tld: Tld) => ({
|
export const tldToNode = (tld: Tld) => ({
|
||||||
id: tld.tld,
|
id: tld.tld,
|
||||||
data: {label: t`.${tld.tld} Registry`},
|
data: {label: t`.${tld.tld} Registry`},
|
||||||
|
type: 'input',
|
||||||
style: {
|
style: {
|
||||||
width: 200
|
width: 200
|
||||||
}
|
}
|
||||||
@@ -38,17 +40,18 @@ export const tldToNode = (tld: Tld) => ({
|
|||||||
export const nsToNode = (ns: Nameserver) => ({
|
export const nsToNode = (ns: Nameserver) => ({
|
||||||
id: ns.ldhName,
|
id: ns.ldhName,
|
||||||
data: {label: ns.ldhName},
|
data: {label: ns.ldhName},
|
||||||
|
type: 'output',
|
||||||
style: {
|
style: {
|
||||||
width: 200
|
width: 200
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
export function watchlistToNodes(watchlist: Watchlist) {
|
export function watchlistToNodes(watchlist: Watchlist, withRegistrar = false, withTld = false) {
|
||||||
|
|
||||||
const domains = watchlist.domains.map(domainToNode)
|
const domains = watchlist.domains.map(domainToNode)
|
||||||
const entities = [...new Set(watchlist.domains.map(d => domainEntitiesToNode(d)).flat())]
|
const entities = [...new Set(watchlist.domains.map(d => domainEntitiesToNode(d, withRegistrar)).flat())]
|
||||||
const tlds = [...new Set(watchlist.domains.map(d => d.tld))].map(tldToNode)
|
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)
|
const nameservers = [...new Set(watchlist.domains.map(d => d.nameservers))].flat().map(nsToNode, withRegistrar)
|
||||||
|
|
||||||
return [...domains, ...entities, ...nameservers]
|
return [...domains, ...entities, ...nameservers, ...(withTld ? tlds : [])]
|
||||||
}
|
}
|
||||||
@@ -252,8 +252,8 @@ msgstr ""
|
|||||||
msgid "At least one domain name"
|
msgid "At least one domain name"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: assets/components/tracking/watchlist/WatchlistCard.tsx:26
|
||||||
#: assets/components/tracking/watchlist/WatchlistForm.tsx:100
|
#: assets/components/tracking/watchlist/WatchlistForm.tsx:100
|
||||||
#: assets/components/tracking/watchlist/WatchlistsList.tsx:26
|
|
||||||
msgid "Domain names"
|
msgid "Domain names"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -265,8 +265,8 @@ msgstr ""
|
|||||||
msgid "Add a Domain name"
|
msgid "Add a Domain name"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: assets/components/tracking/watchlist/WatchlistCard.tsx:30
|
||||||
#: assets/components/tracking/watchlist/WatchlistForm.tsx:142
|
#: assets/components/tracking/watchlist/WatchlistForm.tsx:142
|
||||||
#: assets/components/tracking/watchlist/WatchlistsList.tsx:30
|
|
||||||
msgid "Tracked events"
|
msgid "Tracked events"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -317,11 +317,11 @@ msgstr ""
|
|||||||
msgid "Cancel"
|
msgid "Cancel"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: assets/components/tracking/watchlist/diagram/ViewDiagramWatchlistButton.tsx:43
|
#: assets/components/tracking/watchlist/diagram/ViewDiagramWatchlistButton.tsx:39
|
||||||
msgid "View the Watchlist Entity Diagram"
|
msgid "View the Watchlist Entity Diagram"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: assets/components/tracking/watchlist/diagram/ViewDiagramWatchlistButton.tsx:48
|
#: assets/components/tracking/watchlist/diagram/ViewDiagramWatchlistButton.tsx:44
|
||||||
msgid "Watchlist Entity Diagram"
|
msgid "Watchlist Entity Diagram"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -329,11 +329,19 @@ msgstr ""
|
|||||||
msgid "Registry"
|
msgid "Registry"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: assets/components/tracking/watchlist/diagram/watchlistToNodes.tsx:32
|
#: assets/components/tracking/watchlist/diagram/watchlistToNodes.tsx:33
|
||||||
#, javascript-format
|
#, javascript-format
|
||||||
msgid ".${ tld.tld } Registry"
|
msgid ".${ tld.tld } Registry"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: assets/components/tracking/watchlist/CalendarWatchlistButton.tsx:14
|
||||||
|
msgid "QR Code for iCalendar export"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: assets/components/tracking/watchlist/CalendarWatchlistButton.tsx:17
|
||||||
|
msgid "Export events to iCalendar format"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: assets/components/tracking/watchlist/DeleteWatchlistButton.tsx:12
|
#: assets/components/tracking/watchlist/DeleteWatchlistButton.tsx:12
|
||||||
#: assets/components/tracking/watchlist/DeleteWatchlistButton.tsx:19
|
#: assets/components/tracking/watchlist/DeleteWatchlistButton.tsx:19
|
||||||
msgid "Delete the Watchlist"
|
msgid "Delete the Watchlist"
|
||||||
@@ -343,18 +351,14 @@ msgstr ""
|
|||||||
msgid "Are you sure to delete this Watchlist?"
|
msgid "Are you sure to delete this Watchlist?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: assets/components/tracking/watchlist/WatchlistsList.tsx:46
|
#: assets/components/tracking/watchlist/WatchlistCard.tsx:43
|
||||||
msgid "This Watchlist is not linked to a Connector."
|
msgid "This Watchlist is not linked to a Connector."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: assets/components/tracking/watchlist/WatchlistsList.tsx:49
|
#: assets/components/tracking/watchlist/WatchlistCard.tsx:46
|
||||||
msgid "Watchlist"
|
msgid "Watchlist"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: assets/components/tracking/watchlist/WatchlistsList.tsx:60
|
|
||||||
msgid "Export events to iCalendar format"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: assets/components/Sider.tsx:29
|
#: assets/components/Sider.tsx:29
|
||||||
msgid "Home"
|
msgid "Home"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|||||||
Reference in New Issue
Block a user