mirror of
https://github.com/maelgangloff/domain-watchdog.git
synced 2025-12-29 16:15:04 +00:00
feat: update front layout
This commit is contained in:
@@ -14,7 +14,7 @@ import {
|
|||||||
TeamOutlined,
|
TeamOutlined,
|
||||||
UserOutlined
|
UserOutlined
|
||||||
} from "@ant-design/icons";
|
} from "@ant-design/icons";
|
||||||
import {Navigate, Route, Routes, useLocation, useNavigate} from "react-router-dom";
|
import {Link, Navigate, Route, Routes, useLocation, useNavigate} from "react-router-dom";
|
||||||
import TextPage from "./pages/TextPage";
|
import TextPage from "./pages/TextPage";
|
||||||
import tos from "./content/tos.md";
|
import tos from "./content/tos.md";
|
||||||
import privacy from "./content/privacy.md";
|
import privacy from "./content/privacy.md";
|
||||||
@@ -24,7 +24,7 @@ import NameserverSearchPage from "./pages/search/NameserverSearchPage";
|
|||||||
import TldPage from "./pages/info/TldPage";
|
import TldPage from "./pages/info/TldPage";
|
||||||
import StatisticsPage from "./pages/info/StatisticsPage";
|
import StatisticsPage from "./pages/info/StatisticsPage";
|
||||||
import WatchlistsPage from "./pages/tracking/WatchlistsPage";
|
import WatchlistsPage from "./pages/tracking/WatchlistsPage";
|
||||||
import UserPage from "./pages/UserPage";
|
import UserPage from "./pages/watchdog/UserPage";
|
||||||
import React, {useCallback, useEffect, useMemo, useState} from "react";
|
import React, {useCallback, useEffect, useMemo, useState} from "react";
|
||||||
import {getUser} from "./utils/api";
|
import {getUser} from "./utils/api";
|
||||||
import FAQPage from "./pages/FAQPage";
|
import FAQPage from "./pages/FAQPage";
|
||||||
@@ -38,9 +38,12 @@ export default function App() {
|
|||||||
} = theme.useToken()
|
} = theme.useToken()
|
||||||
|
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const [isAuthenticated, setIsAuthenticated] = useState(false)
|
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
|
|
||||||
|
|
||||||
|
const [isAuthenticated, setIsAuthenticated] = useState(false)
|
||||||
|
|
||||||
|
|
||||||
const authenticated = useCallback((authenticated: boolean) => {
|
const authenticated = useCallback((authenticated: boolean) => {
|
||||||
setIsAuthenticated(authenticated)
|
setIsAuthenticated(authenticated)
|
||||||
}, []);
|
}, []);
|
||||||
@@ -65,6 +68,7 @@ export default function App() {
|
|||||||
{
|
{
|
||||||
key: '1',
|
key: '1',
|
||||||
label: 'Search',
|
label: 'Search',
|
||||||
|
icon: <SearchOutlined/>,
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
key: '1-1',
|
key: '1-1',
|
||||||
@@ -95,11 +99,13 @@ export default function App() {
|
|||||||
{
|
{
|
||||||
key: '2',
|
key: '2',
|
||||||
label: 'Information',
|
label: 'Information',
|
||||||
|
icon: <InfoCircleOutlined/>,
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
key: '2-1',
|
key: '2-1',
|
||||||
icon: <BankOutlined/>,
|
icon: <BankOutlined/>,
|
||||||
label: 'TLD',
|
label: 'TLD',
|
||||||
|
title: 'TLD list',
|
||||||
disabled: !isAuthenticated,
|
disabled: !isAuthenticated,
|
||||||
onClick: () => navigate('/info/tld')
|
onClick: () => navigate('/info/tld')
|
||||||
},
|
},
|
||||||
@@ -115,11 +121,11 @@ export default function App() {
|
|||||||
{
|
{
|
||||||
key: '3',
|
key: '3',
|
||||||
label: 'Tracking',
|
label: 'Tracking',
|
||||||
|
icon: <FileSearchOutlined/>,
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
key: '3-1',
|
key: '3-1',
|
||||||
icon: <Badge count={0} size="small"><FileSearchOutlined
|
icon: <Badge count={0} size="small"><FileSearchOutlined/></Badge>,
|
||||||
shape="square"/></Badge>,
|
|
||||||
label: 'My Watchlists',
|
label: 'My Watchlists',
|
||||||
disabled: !isAuthenticated,
|
disabled: !isAuthenticated,
|
||||||
onClick: () => navigate('/tracking/watchlist')
|
onClick: () => navigate('/tracking/watchlist')
|
||||||
@@ -135,10 +141,29 @@ export default function App() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: '4',
|
key: '4',
|
||||||
|
label: 'My Watchdog',
|
||||||
icon: <UserOutlined/>,
|
icon: <UserOutlined/>,
|
||||||
label: 'My Account',
|
children: [
|
||||||
disabled: !isAuthenticated,
|
{
|
||||||
onClick: () => navigate('/user')
|
key: '4-1',
|
||||||
|
icon: <UserOutlined/>,
|
||||||
|
label: 'My Account',
|
||||||
|
disabled: !isAuthenticated,
|
||||||
|
onClick: () => navigate('/user')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '4-2',
|
||||||
|
icon: <InfoCircleOutlined/>,
|
||||||
|
label: 'TOS',
|
||||||
|
onClick: () => navigate('/tos')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '4-3',
|
||||||
|
icon: <FileProtectOutlined/>,
|
||||||
|
label: 'Privacy Policy',
|
||||||
|
onClick: () => navigate('/privacy')
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: '5',
|
key: '5',
|
||||||
@@ -146,27 +171,16 @@ export default function App() {
|
|||||||
label: 'FAQ',
|
label: 'FAQ',
|
||||||
onClick: () => navigate('/faq')
|
onClick: () => navigate('/faq')
|
||||||
},
|
},
|
||||||
{
|
|
||||||
key: '6',
|
|
||||||
icon: <InfoCircleOutlined/>,
|
|
||||||
label: 'TOS',
|
|
||||||
onClick: () => navigate('/tos')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: '7',
|
|
||||||
icon: <FileProtectOutlined/>,
|
|
||||||
label: 'Privacy Policy',
|
|
||||||
onClick: () => navigate('/privacy')
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
return <AuthenticatedContext.Provider value={contextValue}>
|
return <AuthenticatedContext.Provider value={contextValue}>
|
||||||
<Layout hasSider style={{minHeight: '100vh'}}>
|
<Layout hasSider style={{minHeight: '100vh'}}>
|
||||||
<Layout.Sider>
|
<Layout.Sider collapsible>
|
||||||
<Menu
|
<Menu
|
||||||
defaultSelectedKeys={['1-1']}
|
defaultSelectedKeys={['1-1']}
|
||||||
defaultOpenKeys={['1', '2', '3']}
|
defaultOpenKeys={['1', '2', '3', '4']}
|
||||||
mode="inline"
|
mode="inline"
|
||||||
theme="dark"
|
theme="dark"
|
||||||
items={[...menuItems, isAuthenticated ? {
|
items={[...menuItems, isAuthenticated ? {
|
||||||
@@ -220,7 +234,8 @@ export default function App() {
|
|||||||
</div>
|
</div>
|
||||||
</Layout.Content>
|
</Layout.Content>
|
||||||
<Layout.Footer style={{textAlign: 'center'}}>
|
<Layout.Footer style={{textAlign: 'center'}}>
|
||||||
Domain Watchdog ©{new Date().getFullYear()} Created by Maël Gangloff
|
<Link to='https://github.com/maelgangloff/domain-watchdog'>Domain
|
||||||
|
Watchdog</Link> ©{new Date().getFullYear()} Created by Maël Gangloff
|
||||||
</Layout.Footer>
|
</Layout.Footer>
|
||||||
</Layout>
|
</Layout>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import ReactDOM from "react-dom/client";
|
|||||||
import App from "./App";
|
import App from "./App";
|
||||||
import {HashRouter} from "react-router-dom";
|
import {HashRouter} from "react-router-dom";
|
||||||
|
|
||||||
import './index.css'
|
import 'antd/dist/reset.css';
|
||||||
|
|
||||||
|
|
||||||
const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement)
|
const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, {createContext, useContext, useEffect, useState} from "react";
|
import React, {createContext, useContext, useState} from "react";
|
||||||
import {Alert, Button, Card, Flex, Form, Input} from "antd";
|
import {Alert, Button, Card, Flex, Form, Input} from "antd";
|
||||||
import {login} from "../utils/api";
|
import {login} from "../utils/api";
|
||||||
import {useNavigate} from "react-router-dom";
|
import {useNavigate} from "react-router-dom";
|
||||||
@@ -28,7 +28,6 @@ export default function Page() {
|
|||||||
|
|
||||||
return <Flex gap="middle" align="center" justify="center" vertical><Card
|
return <Flex gap="middle" align="center" justify="center" vertical><Card
|
||||||
title="Log in"
|
title="Log in"
|
||||||
style={{width: 500}}
|
|
||||||
>
|
>
|
||||||
{error &&
|
{error &&
|
||||||
<Alert
|
<Alert
|
||||||
@@ -68,6 +67,11 @@ export default function Page() {
|
|||||||
Submit
|
Submit
|
||||||
</Button>
|
</Button>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
<Form.Item wrapperCol={{offset: 8, span: 16}}>
|
||||||
|
<Button type="primary" htmlType="button" href="/login/oauth">
|
||||||
|
Log in with SSO
|
||||||
|
</Button>
|
||||||
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
</Card>
|
</Card>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
|
|
||||||
export default function Page() {
|
|
||||||
return <p>
|
|
||||||
Hey
|
|
||||||
</p>
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,163 @@
|
|||||||
import React from "react";
|
import React, {useEffect, useState} from "react";
|
||||||
|
import {Collapse, Divider, Table, Typography} from "antd";
|
||||||
|
import {getTldList, Tld} from "../../utils/api";
|
||||||
|
|
||||||
|
const {Text, Paragraph} = Typography
|
||||||
|
|
||||||
|
type TldType = 'iTLD' | 'sTLD' | 'gTLD' | 'ccTLD'
|
||||||
|
type FiltersType = { type: TldType, contractTerminated?: boolean, specification13?: boolean }
|
||||||
|
|
||||||
|
|
||||||
|
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'})
|
||||||
|
|
||||||
|
|
||||||
|
function TldTable(filters: FiltersType) {
|
||||||
|
const [dataTable, setDataTable] = useState<Tld[]>([])
|
||||||
|
const [total, setTotal] = useState(0)
|
||||||
|
|
||||||
|
const fetchData = (params: FiltersType & { page: number }) => {
|
||||||
|
getTldList(params).then((data) => {
|
||||||
|
setTotal(data['hydra:totalItems'])
|
||||||
|
setDataTable(data['hydra:member'].map((tld: Tld) => {
|
||||||
|
switch (filters.type) {
|
||||||
|
case 'ccTLD':
|
||||||
|
return {
|
||||||
|
TLD: tld.tld,
|
||||||
|
Flag: toEmoji(tld.tld),
|
||||||
|
Country: regionNames.of(getCountryCode(tld.tld)) ?? '-'
|
||||||
|
}
|
||||||
|
case 'gTLD':
|
||||||
|
return {
|
||||||
|
TLD: tld.tld,
|
||||||
|
Operator: tld.registryOperator
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return {
|
||||||
|
TLD: tld.tld
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchData({...filters, page: 1})
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
let columns = [
|
||||||
|
{
|
||||||
|
title: "TLD",
|
||||||
|
dataIndex: "TLD",
|
||||||
|
width: 20
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
if (filters.type === 'ccTLD') columns = [...columns, {
|
||||||
|
title: "Flag",
|
||||||
|
dataIndex: "Flag",
|
||||||
|
width: 20
|
||||||
|
}, {
|
||||||
|
title: "Country",
|
||||||
|
dataIndex: "Country",
|
||||||
|
width: 200
|
||||||
|
}]
|
||||||
|
|
||||||
|
if (filters.type === 'gTLD') columns = [...columns, {
|
||||||
|
title: "Registry Operator",
|
||||||
|
dataIndex: "Operator",
|
||||||
|
width: 50
|
||||||
|
}]
|
||||||
|
|
||||||
|
|
||||||
|
return <Table
|
||||||
|
columns={columns}
|
||||||
|
dataSource={dataTable}
|
||||||
|
pagination={{
|
||||||
|
total,
|
||||||
|
pageSize: 30,
|
||||||
|
hideOnSinglePage: true,
|
||||||
|
onChange: (page, pageSize) => {
|
||||||
|
fetchData({...filters, page})
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
scroll={{y: 240}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export default function TldPage() {
|
export default function TldPage() {
|
||||||
return <p>
|
return <>
|
||||||
Tld Page
|
<Paragraph>
|
||||||
</p>
|
This page presents all active TLDs in the root zone database.
|
||||||
|
</Paragraph>
|
||||||
|
<Paragraph>
|
||||||
|
IANA provides the list of currently active
|
||||||
|
TLDs, regardless of their type, and ICANN provides the list of gTLDs.
|
||||||
|
In most cases, the two-letter ccTLD assigned to a country is made in accordance with the ISO 3166-1
|
||||||
|
standard.
|
||||||
|
This data is updated every month. Three HTTP requests are needed for the complete update of TLDs in Domain
|
||||||
|
Watchdog (two requests to IANA and one to ICANN).
|
||||||
|
At the same time, the list of root RDAP servers is updated.
|
||||||
|
</Paragraph>
|
||||||
|
<Divider/>
|
||||||
|
<Collapse
|
||||||
|
size="large"
|
||||||
|
items={[
|
||||||
|
{
|
||||||
|
key: 'sTLD',
|
||||||
|
label: 'Sponsored Top-Level-Domains',
|
||||||
|
children: <>
|
||||||
|
<Text>Top-level domains sponsored by specific organizations that set rules for
|
||||||
|
registration and use, often related to particular interest groups or
|
||||||
|
industries.</Text>
|
||||||
|
<Divider/>
|
||||||
|
<TldTable type='sTLD'/>
|
||||||
|
</>
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'gTLD',
|
||||||
|
label: 'Generic Top-Level-Domains',
|
||||||
|
children: <>
|
||||||
|
<Text>Generic top-level domains open to everyone, not restricted by specific
|
||||||
|
criteria, representing various themes or industries.</Text>
|
||||||
|
<Divider/>
|
||||||
|
<TldTable type='gTLD' contractTerminated={false} specification13={false}/>
|
||||||
|
</>
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'ngTLD',
|
||||||
|
label: 'Brand Generic Top-Level-Domains',
|
||||||
|
children: <>
|
||||||
|
<Text>Generic top-level domains associated with specific brands, allowing companies
|
||||||
|
to use their own brand names as domains.</Text>
|
||||||
|
<Divider/>
|
||||||
|
<TldTable type='gTLD' contractTerminated={false} specification13={true}/>
|
||||||
|
</>
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'ccTLD',
|
||||||
|
label: 'Country-Code Top-Level-Domains',
|
||||||
|
children: <>
|
||||||
|
<Text>Top-level domains based on country codes, identifying websites according to
|
||||||
|
their country of origin.</Text>
|
||||||
|
<Divider/><TldTable type='ccTLD'/>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
}
|
}
|
||||||
7
assets/pages/watchdog/UserPage.tsx
Normal file
7
assets/pages/watchdog/UserPage.tsx
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export default function UserPage() {
|
||||||
|
return <p>
|
||||||
|
My Account Page
|
||||||
|
</p>
|
||||||
|
}
|
||||||
@@ -55,7 +55,7 @@ export async function request<T = any, R = AxiosResponse<T>, D = any>(config: Ax
|
|||||||
withCredentials: true,
|
withCredentials: true,
|
||||||
headers: {
|
headers: {
|
||||||
...config.headers,
|
...config.headers,
|
||||||
Accept: 'application/json'
|
Accept: 'application/ld+json'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return await axios.request<T, R, D>(axiosConfig)
|
return await axios.request<T, R, D>(axiosConfig)
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ interface Tld {
|
|||||||
specification13: boolean
|
specification13: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getTldList(params: object): Promise<Tld[]> {
|
export async function getTldList(params: object): Promise<any> {
|
||||||
return (await request<Tld[]>({
|
return (await request<Tld[]>({
|
||||||
url: 'tld',
|
url: 'tld',
|
||||||
params,
|
params,
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ use Symfony\Component\Serializer\Attribute\Groups;
|
|||||||
operations: [
|
operations: [
|
||||||
new GetCollection(
|
new GetCollection(
|
||||||
uriTemplate: '/tld',
|
uriTemplate: '/tld',
|
||||||
paginationItemsPerPage: 200,
|
|
||||||
normalizationContext: ['groups' => ['tld:list']]
|
normalizationContext: ['groups' => ['tld:list']]
|
||||||
),
|
),
|
||||||
new Get(
|
new Get(
|
||||||
|
|||||||
Reference in New Issue
Block a user