From 08da62c1b5b45d0f1be25c9bebf877d3b21e68e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Gangloff?= Date: Thu, 25 Jul 2024 02:09:49 +0200 Subject: [PATCH] feat: update front layout --- assets/components/DrawerBox.tsx | 15 ++- .../{StickyHeadTable.tsx => HeadTable.tsx} | 25 ++-- assets/index.tsx | 23 +++- assets/pages/DomainFinderPage.tsx | 42 +++++- assets/pages/EntityFinderPage.tsx | 17 +-- assets/pages/LoginPage.tsx | 4 +- assets/pages/NameserverFinderPage.tsx | 17 +-- assets/pages/NameserverPage.tsx | 27 ---- assets/pages/ReverseDirectoryPage.tsx | 17 +-- assets/pages/TextPage.tsx | 2 + assets/pages/TldPage.tsx | 123 +++++++++++++++--- assets/pages/WatchlistsPage.tsx | 54 +++++--- assets/utils/api.ts | 31 ----- assets/utils/api/domain.ts | 9 ++ assets/utils/api/index.ts | 69 ++++++++++ assets/utils/api/tld.ts | 28 ++++ assets/utils/api/user.ts | 18 +++ assets/utils/api/watchlist.ts | 52 ++++++++ 18 files changed, 405 insertions(+), 168 deletions(-) rename assets/components/{StickyHeadTable.tsx => HeadTable.tsx} (78%) delete mode 100644 assets/pages/NameserverPage.tsx delete mode 100644 assets/utils/api.ts create mode 100644 assets/utils/api/domain.ts create mode 100644 assets/utils/api/index.ts create mode 100644 assets/utils/api/tld.ts create mode 100644 assets/utils/api/user.ts create mode 100644 assets/utils/api/watchlist.ts diff --git a/assets/components/DrawerBox.tsx b/assets/components/DrawerBox.tsx index 725df50..c225305 100644 --- a/assets/components/DrawerBox.tsx +++ b/assets/components/DrawerBox.tsx @@ -4,6 +4,7 @@ import {Bookmark, ChevronLeft, Dns, Explore, LocalPolice, MenuBook, People} from import {Divider, List, ListItemButton, ListItemIcon, ListItemText, ListSubheader, styled} from "@mui/material"; import React from "react"; import MuiDrawer from "@mui/material/Drawer"; +import {useNavigate} from "react-router-dom"; const Drawer = styled(MuiDrawer, {shouldForwardProp: (prop) => prop !== 'open'})( @@ -37,6 +38,8 @@ export default function DrawerBox() { const toggleDrawer = () => { setOpen(!open); }; + const navigate = useNavigate() + return Domain names - + navigate('/finder/domain')}> - + navigate('/tld')}> - + navigate('/finder/nameserver')}> @@ -79,13 +82,13 @@ export default function DrawerBox() { Entities - + navigate('/finder/entity')}> - + navigate('/reverse')}> @@ -96,7 +99,7 @@ export default function DrawerBox() { Tracking - + navigate('/watchlist')}> diff --git a/assets/components/StickyHeadTable.tsx b/assets/components/HeadTable.tsx similarity index 78% rename from assets/components/StickyHeadTable.tsx rename to assets/components/HeadTable.tsx index 05f9761..10b17dd 100644 --- a/assets/components/StickyHeadTable.tsx +++ b/assets/components/HeadTable.tsx @@ -6,18 +6,10 @@ interface Column { label: string; minWidth?: number; align?: 'right'; - format?: (value: number) => string; + format?: (value: any) => any; } -interface Data { - name: string; - code: string; - population: number; - size: number; - density: number; -} - -export default function StickyHeadTable({columns, rows}: { rows: Data[], columns: Column[] }) { +export default function HeadTable({columns, rows}: { rows: any[], columns: Column[] }) { const [page, setPage] = React.useState(0); const [rowsPerPage, setRowsPerPage] = React.useState(10); @@ -32,8 +24,8 @@ export default function StickyHeadTable({columns, rows}: { rows: Data[], columns return ( - - + +
{columns.map((column) => ( @@ -50,16 +42,15 @@ export default function StickyHeadTable({columns, rows}: { rows: Data[], columns {rows .slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) - .map((row: Data) => { + .map((row: any) => { return ( - + {columns.map((column) => { const value = row[column.id as keyof typeof row] return ( - {column.format && typeof value === 'number' - ? column.format(value) - : value} + {column.format ? column.format(value) : value} ); })} diff --git a/assets/index.tsx b/assets/index.tsx index df6194b..09a2cc6 100644 --- a/assets/index.tsx +++ b/assets/index.tsx @@ -1,7 +1,7 @@ import React, {useEffect, useState} from "react"; import ReactDOM from "react-dom/client"; import TextPage from "./pages/TextPage"; -import {HashRouter, Route, Routes} from "react-router-dom"; +import {HashRouter, Navigate, Route, Routes} from "react-router-dom"; import tosContent from "./content/tos.md" import privacyContent from "./content/privacy.md" @@ -9,11 +9,15 @@ import LoginPage from "./pages/LoginPage"; import {createTheme, PaletteMode, ThemeProvider} from "@mui/material"; import CssBaseline from "@mui/material/CssBaseline"; import AppAppBar from "./components/AppAppBar"; -import DashboardPage from "./pages/DashboardPage"; -import {getUser} from "./utils/api"; -import Footer from "./components/Footer"; +import {getUser} from "./utils/api/user"; import DrawerBox from "./components/DrawerBox"; import Box from "@mui/material/Box"; +import DomainFinderPage from "./pages/DomainFinderPage"; +import EntityFinderPage from "./pages/EntityFinderPage"; +import NameserverFinderPage from "./pages/NameserverFinderPage"; +import ReverseDirectoryPage from "./pages/ReverseDirectoryPage"; +import TldPage from "./pages/TldPage"; +import WatchlistsPage from "./pages/WatchlistsPage"; const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement); @@ -41,7 +45,15 @@ function App() { {isAuthenticated ? - }/> + <> + }/> + }/> + }/> + }/> + }/> + }/> + }/> + : }/> } @@ -50,7 +62,6 @@ function App() { -
diff --git a/assets/pages/DomainFinderPage.tsx b/assets/pages/DomainFinderPage.tsx index ed37b83..7200379 100644 --- a/assets/pages/DomainFinderPage.tsx +++ b/assets/pages/DomainFinderPage.tsx @@ -1,12 +1,25 @@ -import React from 'react'; +import React, {ChangeEvent, useState} from 'react'; import Container from "@mui/material/Container"; -import {Grid, Paper} from "@mui/material"; +import {Grid, InputAdornment, Paper} from "@mui/material"; +import TextField from "@mui/material/TextField"; +import Typography from "@mui/material/Typography"; +import {Explore} from "@mui/icons-material"; +import Footer from "../components/Footer"; -export default function DashboardPage() { +export default function DomainFinderPage() { + const [ldhName, setLdhName] = useState("") + const [error, setError] = useState(false) + + const onChangeDomain = (e: ChangeEvent) => { + setLdhName(e.currentTarget.value); + const regex = /^[a-zA-Z0-9][a-zA-Z0-9-]{1,61}[a-zA-Z0-9]\.[a-zA-Z]{2,}$/ + setError(!regex.test(e.currentTarget.value)) + } + return ( <> - + + + + + ), + }} + /> + + This tool allows you to search for a domain name in the database. + As a reminder, if a domain name is unknown to Domain Watchdog or if the data is + more + than a week old, an RDAP search will be performed. The RDAP search is an operation worth + a token. + +
); diff --git a/assets/pages/EntityFinderPage.tsx b/assets/pages/EntityFinderPage.tsx index 97cd799..222262e 100644 --- a/assets/pages/EntityFinderPage.tsx +++ b/assets/pages/EntityFinderPage.tsx @@ -1,26 +1,19 @@ import React from 'react'; import Container from "@mui/material/Container"; -import {Grid, Paper} from "@mui/material"; +import {Grid} from "@mui/material"; +import Footer from "../components/Footer"; -export default function DomainFinderPage() { +export default function EntityFinderPage() { return ( <> - + - - +
); diff --git a/assets/pages/LoginPage.tsx b/assets/pages/LoginPage.tsx index e6b6747..f60488d 100644 --- a/assets/pages/LoginPage.tsx +++ b/assets/pages/LoginPage.tsx @@ -11,6 +11,7 @@ import {login} from "../utils/api"; import {useNavigate} from "react-router-dom"; import {Alert} from "@mui/material"; import Container from "@mui/material/Container"; +import Footer from "../components/Footer"; interface Props { setIsAuthenticated: (val: boolean) => void @@ -30,7 +31,7 @@ export default function LoginPage({setIsAuthenticated}: Props) { navigate('/'); } catch (e: any) { - setCredentials({email: "", password: ""}) + setCredentials({...credentials, password: ""}) setError(e.response.data.message) } }; @@ -102,6 +103,7 @@ export default function LoginPage({setIsAuthenticated}: Props) { Single Sign-On +
); diff --git a/assets/pages/NameserverFinderPage.tsx b/assets/pages/NameserverFinderPage.tsx index fe0bac7..29ad7f4 100644 --- a/assets/pages/NameserverFinderPage.tsx +++ b/assets/pages/NameserverFinderPage.tsx @@ -1,26 +1,19 @@ import React from 'react'; import Container from "@mui/material/Container"; -import {Grid, Paper} from "@mui/material"; +import {Grid} from "@mui/material"; +import Footer from "../components/Footer"; -export default function NameserverPage() { +export default function NameserverFinderPage() { return ( <> - + - - +
); diff --git a/assets/pages/NameserverPage.tsx b/assets/pages/NameserverPage.tsx deleted file mode 100644 index 97cd799..0000000 --- a/assets/pages/NameserverPage.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import React from 'react'; -import Container from "@mui/material/Container"; -import {Grid, Paper} from "@mui/material"; - - -export default function DomainFinderPage() { - return ( - <> - - - - - - - - - - - ); -}; diff --git a/assets/pages/ReverseDirectoryPage.tsx b/assets/pages/ReverseDirectoryPage.tsx index 97cd799..22c924a 100644 --- a/assets/pages/ReverseDirectoryPage.tsx +++ b/assets/pages/ReverseDirectoryPage.tsx @@ -1,26 +1,19 @@ import React from 'react'; import Container from "@mui/material/Container"; -import {Grid, Paper} from "@mui/material"; +import {Grid} from "@mui/material"; +import Footer from "../components/Footer"; -export default function DomainFinderPage() { +export default function ReverseDirectoryPage() { return ( <> - + - - +
); diff --git a/assets/pages/TextPage.tsx b/assets/pages/TextPage.tsx index 64e48c8..279320f 100644 --- a/assets/pages/TextPage.tsx +++ b/assets/pages/TextPage.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import Box from '@mui/material/Box'; import Container from "@mui/material/Container"; +import Footer from "../components/Footer"; interface Props { content: string @@ -30,6 +31,7 @@ export default function Index({content}: Props) { >
+
) diff --git a/assets/pages/TldPage.tsx b/assets/pages/TldPage.tsx index 97cd799..84c5cea 100644 --- a/assets/pages/TldPage.tsx +++ b/assets/pages/TldPage.tsx @@ -1,27 +1,110 @@ -import React from 'react'; +import React, {useEffect, useState} from 'react'; import Container from "@mui/material/Container"; -import {Grid, Paper} from "@mui/material"; +import {Accordion, AccordionDetails, AccordionSummary, Grid, Typography} from "@mui/material"; +import {ExpandMore} from "@mui/icons-material"; +import HeadTable from "../components/HeadTable"; +import {getTldList} from "../utils/api"; +import Footer from "../components/Footer"; +const gTldColumns = [ + {id: 'tld', label: 'TLD'}, + {id: 'registryOperator', label: 'Operator'} +] + +const sTldColumns = [ + {id: 'tld', label: 'TLD'} +] + +const toEmoji = (tld: string) => String.fromCodePoint( + ...getCountryCode(tld) + .toUpperCase() + .split('') + .map((char) => 127397 + char.charCodeAt(0) + ) +) + +const getCountryCode = (tld: string): string => { + const exceptions = {uk: 'gb', su: 'ru', tp: 'tl'} + if (tld in exceptions) return exceptions[tld as keyof typeof exceptions] + return tld +} + +const regionNames = new Intl.DisplayNames(['en'], {type: 'region'}) + +const ccTldColumns = [ + {id: 'tld', label: 'TLD'}, + { + id: 'tld', + label: 'Flag', + format: (tld: string) => toEmoji(tld) + }, + {id: 'tld', label: 'Country name', format: (tld: string) => regionNames.of(getCountryCode(tld)) ?? '-'}, +] + +export default function TldPage() { + const [sTld, setSTld] = useState([]) + const [gTld, setGTld] = useState([]) + const [ccTld, setCcTld] = useState([]) + const [brandGTld, setBrandGTld] = useState([]) + + useEffect(() => { + getTldList({type: 'sTLD'}).then(setSTld) + getTldList({type: 'gTLD', contractTerminated: 0, specification13: 0}).then(setGTld) + getTldList({type: 'gTLD', contractTerminated: 0, specification13: 1}).then(setBrandGTld) + getTldList({type: 'ccTLD'}).then(setCcTld) + }, []) -export default function DomainFinderPage() { return ( - <> - - - - - - - + + + + + }> + + sTLD + + Sponsored Top-Level Domains + + + + + + + }> + + gTLD + + Generic Top-Level Domains + + + + + + + }> + + Brand gTLD + + Brand Generic Top-Level Domains + + + + + + + }> + + ccTLD + + Country-Code Top-Level Domains + + + + + - - + +
+ ); }; diff --git a/assets/pages/WatchlistsPage.tsx b/assets/pages/WatchlistsPage.tsx index 97cd799..192d184 100644 --- a/assets/pages/WatchlistsPage.tsx +++ b/assets/pages/WatchlistsPage.tsx @@ -1,27 +1,41 @@ -import React from 'react'; +import React, {useEffect, useState} from 'react'; import Container from "@mui/material/Container"; -import {Grid, Paper} from "@mui/material"; +import {Grid, List, ListItem, ListItemText} from "@mui/material"; +import Footer from "../components/Footer"; +import {deleteWatchlist, getWatchlists, Watchlist} from "../utils/api"; +import IconButton from "@mui/material/IconButton"; +import {DeleteForever} from '@mui/icons-material' +export default function WatchlistsPage() { + const [watchlists, setWatchlists] = useState<(Partial & { token: string })[]>([]) + const [refreshKey, setRefreshKey] = useState(0) + + useEffect(() => { + getWatchlists().then(setWatchlists) + }, [refreshKey]) -export default function DomainFinderPage() { return ( - <> - - - - - - - + + + + + {watchlists.map((w) => ( + deleteWatchlist(w.token).then(() => setRefreshKey(refreshKey + 1))}> + + + } + > + + + ))} + - - + +
+ ); }; diff --git a/assets/utils/api.ts b/assets/utils/api.ts deleted file mode 100644 index 0f40f2a..0000000 --- a/assets/utils/api.ts +++ /dev/null @@ -1,31 +0,0 @@ -import axios, {AxiosRequestConfig, AxiosResponse} from "axios"; - - -export async function login(email: string, password: string): Promise { - const response = await request({ - method: 'POST', - url: 'login', - data: {email, password} - }) - return response.status === 200 -} - -export async function getUser(): Promise { - const response = await request({ - url: 'me' - }) - return response.data -} - - -async function request, D = any>(config: AxiosRequestConfig): Promise { - const axiosConfig: AxiosRequestConfig = { - ...config, - baseURL: '/api', - withCredentials: true, - headers: { - Accept: 'application/ld+json' - } - } - return await axios.request(axiosConfig) -} \ No newline at end of file diff --git a/assets/utils/api/domain.ts b/assets/utils/api/domain.ts new file mode 100644 index 0000000..83fdee7 --- /dev/null +++ b/assets/utils/api/domain.ts @@ -0,0 +1,9 @@ +import {Domain, request} from "."; + + +export async function getDomain(ldhName: string): Promise { + const response = await request({ + url: 'domains/' + ldhName + }) + return response.data +} diff --git a/assets/utils/api/index.ts b/assets/utils/api/index.ts new file mode 100644 index 0000000..8aff77a --- /dev/null +++ b/assets/utils/api/index.ts @@ -0,0 +1,69 @@ +import axios, {AxiosRequestConfig, AxiosResponse} from "axios"; + + +export interface Event { + action: string + date: string +} + +export interface Entity { + handle: string + events: Event[] + roles: string[] +} + +export interface Nameserver { + ldhName: string + entities: Entity[] +} + +export interface Tld { + tld: string + contractTerminated: boolean + dateOfContractSignature: string + registryOperator: string + delegationDate: string + removalDate: string + specification13: boolean + type: string +} + +export interface Domain { + ldhName: string + handle: string + status: string[] + events: Event[] + entities: Entity[] + nameservers: Nameserver[] + tld: Tld +} + +export interface User { + email: string + roles: string[] +} + +export interface Watchlist { + domains: string[] + triggers: Event[] +} + +export async function request, D = any>(config: AxiosRequestConfig): Promise { + const axiosConfig: AxiosRequestConfig = { + ...config, + baseURL: '/api', + withCredentials: true, + headers: { + ...config.headers, + Accept: 'application/json' + } + } + return await axios.request(axiosConfig) +} + +export * from './domain' +export * from './tld' +export * from './user' +export * from './watchlist' + + diff --git a/assets/utils/api/tld.ts b/assets/utils/api/tld.ts new file mode 100644 index 0000000..ce95564 --- /dev/null +++ b/assets/utils/api/tld.ts @@ -0,0 +1,28 @@ +import {request} from "./index"; + +interface Tld { + tld: string + contractTerminated: boolean + registryOperator: string + specification13: boolean +} + +export async function getTldList(params: object): Promise { + let page = 1 + let response = (await request({ + url: 'tld', + params: {...params, page}, + })).data + const tldList: Tld[] = response; + + while (response.length !== 0) { + page++ + response = (await request({ + url: 'tld', + params: {...params, page}, + })).data + + tldList.push(...response) + } + return tldList +} \ No newline at end of file diff --git a/assets/utils/api/user.ts b/assets/utils/api/user.ts new file mode 100644 index 0000000..1de9380 --- /dev/null +++ b/assets/utils/api/user.ts @@ -0,0 +1,18 @@ +import {request, User} from "./index"; + + +export async function login(email: string, password: string): Promise { + const response = await request({ + method: 'POST', + url: 'login', + data: {email, password} + }) + return response.status === 200 +} + +export async function getUser(): Promise { + const response = await request({ + url: 'me' + }) + return response.data +} diff --git a/assets/utils/api/watchlist.ts b/assets/utils/api/watchlist.ts new file mode 100644 index 0000000..0a244a6 --- /dev/null +++ b/assets/utils/api/watchlist.ts @@ -0,0 +1,52 @@ +import {Event, request, Watchlist} from "./index"; + +export async function getWatchlists() { + const response = await request<{ token: string }[]>({ + url: 'watchlists' + }) + return response.data +} + +export async function getWatchlist(token: string) { + const response = await request({ + url: 'watchlists/' + token + }) + return response.data +} + +export async function postWatchlist(domains: string[], triggers: Event[]) { + const response = await request<{ token: string }>({ + method: 'POST', + url: 'watchlists', + data: { + domains, + triggers + }, + headers: { + "Content-Type": 'application/json' + } + }) + return response.data +} + +export async function deleteWatchlist(token: string): Promise { + await request({ + method: 'DELETE', + url: 'watchlists/' + token + }) +} + +export async function patchWatchlist(domains: string[], triggers: Event[]) { + const response = await request({ + method: 'PATCH', + url: 'watchlists', + data: { + domains, + triggers + }, + headers: { + "Content-Type": 'application/merge-patch+json' + } + }) + return response.data +} \ No newline at end of file