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 useBreakpoint from "../../../hooks/useBreakpoint";
|
||||
import {actionToColor, domainEvent} from "../../search/EventTimeline";
|
||||
import {Watchlist} from "../../../pages/tracking/WatchlistPage";
|
||||
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 {WatchlistCard} from "./WatchlistCard";
|
||||
|
||||
export function WatchlistsList({watchlists, onDelete, onUpdateWatchlist, connectors}: {
|
||||
watchlists: Watchlist[],
|
||||
@@ -17,79 +9,15 @@ export function WatchlistsList({watchlists, onDelete, onUpdateWatchlist, connect
|
||||
onUpdateWatchlist: (values: { domains: string[], triggers: string[], token: string }) => Promise<void>,
|
||||
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 <>
|
||||
{watchlists.map(watchlist =>
|
||||
<>
|
||||
<Card
|
||||
hoverable
|
||||
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}/>
|
||||
|
||||
<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/>
|
||||
</>
|
||||
)}
|
||||
<WatchlistCard watchlist={watchlist}
|
||||
onUpdateWatchlist={onUpdateWatchlist}
|
||||
connectors={connectors}
|
||||
onDelete={onDelete}/>
|
||||
)
|
||||
}
|
||||
</>
|
||||
}
|
||||
@@ -10,12 +10,6 @@ import {getLayoutedElements} from "./getLayoutedElements";
|
||||
import {watchlistToNodes} from "./watchlistToNodes";
|
||||
import {watchlistToEdges} from "./watchlistToEdges";
|
||||
|
||||
export type DiagramConfig = {
|
||||
tld?: boolean
|
||||
nameserver?: boolean
|
||||
entities?: boolean
|
||||
}
|
||||
|
||||
export function ViewDiagramWatchlistButton({token}: { token: string }) {
|
||||
|
||||
const [open, setOpen] = useState(false)
|
||||
@@ -24,9 +18,11 @@ export function ViewDiagramWatchlistButton({token}: { token: string }) {
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState([])
|
||||
|
||||
useEffect(() => {
|
||||
setNodes([])
|
||||
setEdges([])
|
||||
setNodes([])
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return
|
||||
setLoading(true)
|
||||
getWatchlist(token).then(w => {
|
||||
@@ -58,19 +54,22 @@ export function ViewDiagramWatchlistButton({token}: { token: string }) {
|
||||
}
|
||||
onOk={() => setOpen(false)}
|
||||
onCancel={() => setOpen(false)}
|
||||
width='85vw'
|
||||
width='90vw'
|
||||
height='100%'
|
||||
>
|
||||
<Flex style={{width: '80vw', height: '80vh'}}>
|
||||
<Flex style={{width: '85vw', height: '85vh'}}>
|
||||
<ReactFlow
|
||||
fitView
|
||||
colorMode='system'
|
||||
defaultEdges={[]}
|
||||
defaultNodes={[]}
|
||||
nodesConnectable={false}
|
||||
edgesReconnectable={false}
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
onNodesChange={onNodesChange}
|
||||
onEdgesChange={onEdgesChange}
|
||||
style={{width: '100%', height: '100vh'}}
|
||||
style={{width: '100%', height: '100%'}}
|
||||
>
|
||||
<MiniMap/>
|
||||
<Controls/>
|
||||
|
||||
@@ -40,9 +40,10 @@ export const tldToEdge = (d: Domain) => ({
|
||||
label: t`Registry`
|
||||
})
|
||||
|
||||
export function watchlistToEdges(watchlist: Watchlist) {
|
||||
const entitiesEdges = watchlist.domains.map(d => domainEntitiesToEdges(d)).flat()
|
||||
export function watchlistToEdges(watchlist: Watchlist, withRegistrar = false, withTld = false) {
|
||||
const entitiesEdges = watchlist.domains.map(d => domainEntitiesToEdges(d, withRegistrar)).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 {
|
||||
id: e.entity.handle,
|
||||
type: e.roles.includes('registrant') || e.roles.includes('registrar') ? 'input' : 'output',
|
||||
data: {label},
|
||||
style: {
|
||||
width: 200
|
||||
@@ -30,6 +31,7 @@ export const domainEntitiesToNode = (d: Domain, withRegistrar = false) => d.enti
|
||||
export const tldToNode = (tld: Tld) => ({
|
||||
id: tld.tld,
|
||||
data: {label: t`.${tld.tld} Registry`},
|
||||
type: 'input',
|
||||
style: {
|
||||
width: 200
|
||||
}
|
||||
@@ -38,17 +40,18 @@ export const tldToNode = (tld: Tld) => ({
|
||||
export const nsToNode = (ns: Nameserver) => ({
|
||||
id: ns.ldhName,
|
||||
data: {label: ns.ldhName},
|
||||
type: 'output',
|
||||
style: {
|
||||
width: 200
|
||||
}
|
||||
})
|
||||
|
||||
export function watchlistToNodes(watchlist: Watchlist) {
|
||||
export function watchlistToNodes(watchlist: Watchlist, withRegistrar = false, withTld = false) {
|
||||
|
||||
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 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"
|
||||
msgstr ""
|
||||
|
||||
#: assets/components/tracking/watchlist/WatchlistCard.tsx:26
|
||||
#: assets/components/tracking/watchlist/WatchlistForm.tsx:100
|
||||
#: assets/components/tracking/watchlist/WatchlistsList.tsx:26
|
||||
msgid "Domain names"
|
||||
msgstr ""
|
||||
|
||||
@@ -265,8 +265,8 @@ msgstr ""
|
||||
msgid "Add a Domain name"
|
||||
msgstr ""
|
||||
|
||||
#: assets/components/tracking/watchlist/WatchlistCard.tsx:30
|
||||
#: assets/components/tracking/watchlist/WatchlistForm.tsx:142
|
||||
#: assets/components/tracking/watchlist/WatchlistsList.tsx:30
|
||||
msgid "Tracked events"
|
||||
msgstr ""
|
||||
|
||||
@@ -317,11 +317,11 @@ msgstr ""
|
||||
msgid "Cancel"
|
||||
msgstr ""
|
||||
|
||||
#: assets/components/tracking/watchlist/diagram/ViewDiagramWatchlistButton.tsx:43
|
||||
#: assets/components/tracking/watchlist/diagram/ViewDiagramWatchlistButton.tsx:39
|
||||
msgid "View the Watchlist Entity Diagram"
|
||||
msgstr ""
|
||||
|
||||
#: assets/components/tracking/watchlist/diagram/ViewDiagramWatchlistButton.tsx:48
|
||||
#: assets/components/tracking/watchlist/diagram/ViewDiagramWatchlistButton.tsx:44
|
||||
msgid "Watchlist Entity Diagram"
|
||||
msgstr ""
|
||||
|
||||
@@ -329,11 +329,19 @@ msgstr ""
|
||||
msgid "Registry"
|
||||
msgstr ""
|
||||
|
||||
#: assets/components/tracking/watchlist/diagram/watchlistToNodes.tsx:32
|
||||
#: assets/components/tracking/watchlist/diagram/watchlistToNodes.tsx:33
|
||||
#, javascript-format
|
||||
msgid ".${ tld.tld } Registry"
|
||||
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:19
|
||||
msgid "Delete the Watchlist"
|
||||
@@ -343,18 +351,14 @@ msgstr ""
|
||||
msgid "Are you sure to delete this Watchlist?"
|
||||
msgstr ""
|
||||
|
||||
#: assets/components/tracking/watchlist/WatchlistsList.tsx:46
|
||||
#: assets/components/tracking/watchlist/WatchlistCard.tsx:43
|
||||
msgid "This Watchlist is not linked to a Connector."
|
||||
msgstr ""
|
||||
|
||||
#: assets/components/tracking/watchlist/WatchlistsList.tsx:49
|
||||
#: assets/components/tracking/watchlist/WatchlistCard.tsx:46
|
||||
msgid "Watchlist"
|
||||
msgstr ""
|
||||
|
||||
#: assets/components/tracking/watchlist/WatchlistsList.tsx:60
|
||||
msgid "Export events to iCalendar format"
|
||||
msgstr ""
|
||||
|
||||
#: assets/components/Sider.tsx:29
|
||||
msgid "Home"
|
||||
msgstr ""
|
||||
|
||||
Reference in New Issue
Block a user