mirror of
https://github.com/maelgangloff/domain-watchdog.git
synced 2025-12-29 16:15:04 +00:00
feat: add stats page
This commit is contained in:
@@ -4,10 +4,10 @@ import TextPage from "./pages/TextPage";
|
||||
import DomainSearchPage from "./pages/search/DomainSearchPage";
|
||||
import EntitySearchPage from "./pages/search/EntitySearchPage";
|
||||
import NameserverSearchPage from "./pages/search/NameserverSearchPage";
|
||||
import TldPage from "./pages/info/TldPage";
|
||||
import StatisticsPage from "./pages/info/StatisticsPage";
|
||||
import TldPage from "./pages/search/TldPage";
|
||||
import StatisticsPage from "./pages/StatisticsPage";
|
||||
import WatchlistPage from "./pages/tracking/WatchlistPage";
|
||||
import UserPage from "./pages/watchdog/UserPage";
|
||||
import UserPage from "./pages/UserPage";
|
||||
import React, {useCallback, useEffect, useMemo, useState} from "react";
|
||||
import {getUser} from "./utils/api";
|
||||
import LoginPage, {AuthenticatedContext} from "./pages/LoginPage";
|
||||
@@ -77,13 +77,12 @@ export default function App() {
|
||||
<Route path="/search/domain" element={<DomainSearchPage/>}/>
|
||||
<Route path="/search/entity" element={<EntitySearchPage/>}/>
|
||||
<Route path="/search/nameserver" element={<NameserverSearchPage/>}/>
|
||||
|
||||
<Route path="/info/tld" element={<TldPage/>}/>
|
||||
<Route path="/info/stats" element={<StatisticsPage/>}/>
|
||||
<Route path="/search/tld" element={<TldPage/>}/>
|
||||
|
||||
<Route path="/tracking/watchlist" element={<WatchlistPage/>}/>
|
||||
<Route path="/tracking/connectors" element={<ConnectorsPage/>}/>
|
||||
|
||||
<Route path="/stats" element={<StatisticsPage/>}/>
|
||||
<Route path="/user" element={<UserPage/>}/>
|
||||
|
||||
<Route path="/faq" element={<TextPage resource='faq.md'/>}/>
|
||||
|
||||
@@ -48,7 +48,7 @@ export function Sider({isAuthenticated}: { isAuthenticated: boolean }) {
|
||||
label: t`TLD`,
|
||||
title: t`TLD list`,
|
||||
disabled: !isAuthenticated,
|
||||
onClick: () => navigate('/info/tld')
|
||||
onClick: () => navigate('/search/tld')
|
||||
},
|
||||
{
|
||||
key: 'entity-finder',
|
||||
@@ -93,8 +93,8 @@ export function Sider({isAuthenticated}: { isAuthenticated: boolean }) {
|
||||
key: 'stats',
|
||||
icon: <LineChartOutlined/>,
|
||||
label: t`Statistics`,
|
||||
disabled: true,
|
||||
onClick: () => navigate('/info/stats')
|
||||
disabled: !isAuthenticated,
|
||||
onClick: () => navigate('/stats')
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
117
assets/pages/StatisticsPage.tsx
Normal file
117
assets/pages/StatisticsPage.tsx
Normal file
@@ -0,0 +1,117 @@
|
||||
import React, {useEffect, useState} from "react";
|
||||
import {getStatistics, Statistics} from "../utils/api";
|
||||
import {Card, Col, Divider, Row, Statistic, Tooltip} from "antd";
|
||||
import {t} from "ttag";
|
||||
import {
|
||||
AimOutlined,
|
||||
CompassOutlined,
|
||||
DatabaseOutlined,
|
||||
FieldTimeOutlined,
|
||||
NotificationOutlined
|
||||
} from "@ant-design/icons";
|
||||
|
||||
export default function StatisticsPage() {
|
||||
|
||||
const [stats, setStats] = useState<Statistics>()
|
||||
|
||||
useEffect(() => {
|
||||
getStatistics().then(setStats)
|
||||
}, [])
|
||||
|
||||
const successRatio = stats !== undefined ?
|
||||
(stats.domainPurchaseFailed === 0 ? undefined : stats.domainPurchased / stats.domainPurchaseFailed)
|
||||
: undefined
|
||||
|
||||
return <>
|
||||
<Row gutter={16}>
|
||||
<Col span={12}>
|
||||
<Card bordered={false}>
|
||||
<Statistic
|
||||
loading={stats === undefined}
|
||||
prefix={<CompassOutlined/>}
|
||||
title={t`RDAP queries`}
|
||||
value={stats?.rdapQueries}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Card bordered={false}>
|
||||
<Statistic
|
||||
loading={stats === undefined}
|
||||
title={t`Alert sent`}
|
||||
prefix={<NotificationOutlined/>}
|
||||
value={stats?.alertSent}
|
||||
valueStyle={{color: 'blueviolet'}}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
<Divider/>
|
||||
<Row gutter={16}>
|
||||
<Col span={12}>
|
||||
<Card bordered={false}>
|
||||
<Statistic
|
||||
loading={stats === undefined}
|
||||
title={t`Domain name in database`}
|
||||
prefix={<DatabaseOutlined/>}
|
||||
value={stats?.rdapQueries}
|
||||
valueStyle={{color: 'darkblue'}}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Card bordered={false}>
|
||||
<Statistic
|
||||
loading={stats === undefined}
|
||||
title={t`Domain name tracked`}
|
||||
prefix={<AimOutlined/>}
|
||||
value={stats?.domainTracked}
|
||||
valueStyle={{color: 'darkviolet'}}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
<Divider/>
|
||||
<Row gutter={16}>
|
||||
<Col span={12}>
|
||||
<Card bordered={false}>
|
||||
<Statistic
|
||||
loading={stats === undefined}
|
||||
title={t`Domain name purchased`}
|
||||
prefix={<FieldTimeOutlined/>}
|
||||
value={stats?.domainPurchased}
|
||||
valueStyle={{color: '#3f8600'}}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Card bordered={false}>
|
||||
<Tooltip
|
||||
title={t`This value is based on the status code of the HTTP response from the providers following the order.`}>
|
||||
<Statistic
|
||||
loading={stats === undefined}
|
||||
title={t`Success ratio`}
|
||||
value={successRatio === undefined ? '-' : successRatio * 100}
|
||||
suffix='%'
|
||||
precision={2}
|
||||
valueStyle={{color: successRatio === undefined ? 'black' : successRatio >= 0.5 ? 'darkgreen' : 'orange'}}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
<Divider/>
|
||||
<Row gutter={16}>
|
||||
{stats?.domainCount.map(({domain, tld}) => <Col span={4}>
|
||||
<Card bordered={false}>
|
||||
<Statistic
|
||||
loading={stats === undefined}
|
||||
title={`.${tld}`}
|
||||
value={domain}
|
||||
valueStyle={{color: 'darkblue'}}
|
||||
/>
|
||||
</Card>
|
||||
</Col>)}
|
||||
</Row>
|
||||
</>
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, {useEffect, useState} from "react";
|
||||
import {Card, Flex, Skeleton, Typography} from "antd";
|
||||
import {getUser, User} from "../../utils/api";
|
||||
import {getUser, User} from "../utils/api";
|
||||
import {t} from 'ttag'
|
||||
|
||||
export default function UserPage() {
|
||||
@@ -1,7 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
export default function StatisticsPage() {
|
||||
return <p>
|
||||
Not implemented
|
||||
</p>
|
||||
}
|
||||
@@ -88,6 +88,16 @@ export interface InstanceConfig {
|
||||
registerEnabled: boolean
|
||||
}
|
||||
|
||||
export interface Statistics {
|
||||
rdapQueries: number
|
||||
alertSent: number
|
||||
domainPurchased: number
|
||||
domainPurchaseFailed: number
|
||||
domainCount: {tld: string, domain: number}[]
|
||||
domainCountTotal: number
|
||||
domainTracked: number
|
||||
}
|
||||
|
||||
export async function request<T = any, R = AxiosResponse<T>, D = any>(config: AxiosRequestConfig): Promise<R> {
|
||||
const axiosConfig: AxiosRequestConfig = {
|
||||
...config,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {InstanceConfig, request, User} from "./index";
|
||||
import {InstanceConfig, request, Statistics, User} from "./index";
|
||||
|
||||
|
||||
export async function login(email: string, password: string): Promise<boolean> {
|
||||
@@ -32,4 +32,11 @@ export async function getConfiguration(): Promise<InstanceConfig> {
|
||||
url: 'config'
|
||||
})
|
||||
return response.data
|
||||
}
|
||||
|
||||
export async function getStatistics(): Promise<Statistics> {
|
||||
const response = await request<Statistics>({
|
||||
url: 'stats'
|
||||
})
|
||||
return response.data
|
||||
}
|
||||
@@ -32,21 +32,26 @@ class StatisticsController extends AbstractController
|
||||
->setDomainPurchased($this->pool->getItem('stats.domain.purchased')->get() ?? 0)
|
||||
->setDomainPurchaseFailed($this->pool->getItem('stats.domain.purchase.failed')->get() ?? 0)
|
||||
->setAlertSent($this->pool->getItem('stats.alert.sent')->get() ?? 0)
|
||||
->setWatchlistCount(
|
||||
$this->getCachedItem('stats.watchlist.count', fn () => $this->watchListRepository->count()
|
||||
))
|
||||
->setDomainTracked(
|
||||
$this->watchListRepository->createQueryBuilder('w')
|
||||
->join('w.domains', 'd')
|
||||
->select('COUNT(DISTINCT d.ldhName)')
|
||||
->where('d.deleted = FALSE')
|
||||
->getQuery()->getSingleColumnResult()[0]
|
||||
)
|
||||
->setDomainCount(
|
||||
$this->getCachedItem('stats.domain.count', fn () => $this->domainRepository->createQueryBuilder('d')
|
||||
->join('d.tld', 't')
|
||||
->select('t.tld tld')
|
||||
->addSelect('COUNT(d.ldhName) AS domain')
|
||||
->addGroupBy('t.tld')
|
||||
->where('d.deleted = FALSE')
|
||||
->orderBy('domain', 'DESC')
|
||||
->setMaxResults(5)
|
||||
->getQuery()->getArrayResult())
|
||||
)
|
||||
->setDomainCountTotal(
|
||||
$this->getCachedItem('stats.domain.total', fn () => $this->domainRepository->count()
|
||||
$this->getCachedItem('stats.domain.total', fn () => $this->domainRepository->count(['deleted' => false])
|
||||
));
|
||||
|
||||
return $stats;
|
||||
|
||||
@@ -25,8 +25,21 @@ class Statistics
|
||||
private ?int $domainPurchaseFailed = null;
|
||||
|
||||
private ?array $domainCount = null;
|
||||
private ?int $domainTracked = null;
|
||||
private ?int $domainCountTotal = null;
|
||||
private ?int $watchlistCount = null;
|
||||
|
||||
public static function updateRDAPQueriesStat(CacheItemPoolInterface $pool, string $key): bool
|
||||
{
|
||||
try {
|
||||
$item = $pool->getItem($key);
|
||||
$item->set(($item->get() ?? 0) + 1);
|
||||
|
||||
return $pool->save($item);
|
||||
} catch (\Throwable) {
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getRdapQueries(): ?int
|
||||
{
|
||||
@@ -76,18 +89,6 @@ class Statistics
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getWatchlistCount(): ?int
|
||||
{
|
||||
return $this->watchlistCount;
|
||||
}
|
||||
|
||||
public function setWatchlistCount(?int $watchlistCount): static
|
||||
{
|
||||
$this->watchlistCount = $watchlistCount;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDomainCountTotal(): ?int
|
||||
{
|
||||
return $this->domainCountTotal;
|
||||
@@ -110,16 +111,15 @@ class Statistics
|
||||
return $this;
|
||||
}
|
||||
|
||||
public static function updateRDAPQueriesStat(CacheItemPoolInterface $pool, string $key): bool
|
||||
public function getDomainTracked(): ?int
|
||||
{
|
||||
try {
|
||||
$item = $pool->getItem($key);
|
||||
$item->set(($item->get() ?? 0) + 1);
|
||||
return $this->domainTracked;
|
||||
}
|
||||
|
||||
return $pool->save($item);
|
||||
} catch (\Throwable) {
|
||||
}
|
||||
public function setDomainTracked(?int $domainTracked): static
|
||||
{
|
||||
$this->domainTracked = $domainTracked;
|
||||
|
||||
return false;
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user