From 26ae67411747664b2ec8559af0d857c5766bf044 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Gangloff?= Date: Thu, 22 Aug 2024 19:26:34 +0200 Subject: [PATCH] feat: add stats page --- assets/App.tsx | 11 +- assets/components/Sider.tsx | 6 +- assets/pages/StatisticsPage.tsx | 117 ++++++++++++++++++++++ assets/pages/{watchdog => }/UserPage.tsx | 2 +- assets/pages/info/StatisticsPage.tsx | 7 -- assets/pages/{info => search}/TldPage.tsx | 0 assets/utils/api/index.ts | 10 ++ assets/utils/api/user.ts | 9 +- src/Controller/StatisticsController.php | 13 ++- src/Entity/Statistics.php | 42 ++++---- 10 files changed, 174 insertions(+), 43 deletions(-) create mode 100644 assets/pages/StatisticsPage.tsx rename assets/pages/{watchdog => }/UserPage.tsx (94%) delete mode 100644 assets/pages/info/StatisticsPage.tsx rename assets/pages/{info => search}/TldPage.tsx (100%) diff --git a/assets/App.tsx b/assets/App.tsx index 2bd4a37..72e13a3 100644 --- a/assets/App.tsx +++ b/assets/App.tsx @@ -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() { }/> }/> }/> - - }/> - }/> + }/> }/> }/> + }/> }/> }/> diff --git a/assets/components/Sider.tsx b/assets/components/Sider.tsx index 96baa19..416ea9e 100644 --- a/assets/components/Sider.tsx +++ b/assets/components/Sider.tsx @@ -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: , label: t`Statistics`, - disabled: true, - onClick: () => navigate('/info/stats') + disabled: !isAuthenticated, + onClick: () => navigate('/stats') } ] diff --git a/assets/pages/StatisticsPage.tsx b/assets/pages/StatisticsPage.tsx new file mode 100644 index 0000000..dd1b64c --- /dev/null +++ b/assets/pages/StatisticsPage.tsx @@ -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() + + useEffect(() => { + getStatistics().then(setStats) + }, []) + + const successRatio = stats !== undefined ? + (stats.domainPurchaseFailed === 0 ? undefined : stats.domainPurchased / stats.domainPurchaseFailed) + : undefined + + return <> + + + + } + title={t`RDAP queries`} + value={stats?.rdapQueries} + /> + + + + + } + value={stats?.alertSent} + valueStyle={{color: 'blueviolet'}} + /> + + + + + + + + } + value={stats?.rdapQueries} + valueStyle={{color: 'darkblue'}} + /> + + + + + } + value={stats?.domainTracked} + valueStyle={{color: 'darkviolet'}} + /> + + + + + + + + } + value={stats?.domainPurchased} + valueStyle={{color: '#3f8600'}} + /> + + + + + + = 0.5 ? 'darkgreen' : 'orange'}} + /> + + + + + + + {stats?.domainCount.map(({domain, tld}) => + + + + )} + + +} \ No newline at end of file diff --git a/assets/pages/watchdog/UserPage.tsx b/assets/pages/UserPage.tsx similarity index 94% rename from assets/pages/watchdog/UserPage.tsx rename to assets/pages/UserPage.tsx index a934b26..11c74ed 100644 --- a/assets/pages/watchdog/UserPage.tsx +++ b/assets/pages/UserPage.tsx @@ -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() { diff --git a/assets/pages/info/StatisticsPage.tsx b/assets/pages/info/StatisticsPage.tsx deleted file mode 100644 index d7ae8d6..0000000 --- a/assets/pages/info/StatisticsPage.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import React from "react"; - -export default function StatisticsPage() { - return

- Not implemented -

-} \ No newline at end of file diff --git a/assets/pages/info/TldPage.tsx b/assets/pages/search/TldPage.tsx similarity index 100% rename from assets/pages/info/TldPage.tsx rename to assets/pages/search/TldPage.tsx diff --git a/assets/utils/api/index.ts b/assets/utils/api/index.ts index f8e35db..6d003b9 100644 --- a/assets/utils/api/index.ts +++ b/assets/utils/api/index.ts @@ -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, D = any>(config: AxiosRequestConfig): Promise { const axiosConfig: AxiosRequestConfig = { ...config, diff --git a/assets/utils/api/user.ts b/assets/utils/api/user.ts index 6ad5463..a42fe87 100644 --- a/assets/utils/api/user.ts +++ b/assets/utils/api/user.ts @@ -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 { @@ -32,4 +32,11 @@ export async function getConfiguration(): Promise { url: 'config' }) return response.data +} + +export async function getStatistics(): Promise { + const response = await request({ + url: 'stats' + }) + return response.data } \ No newline at end of file diff --git a/src/Controller/StatisticsController.php b/src/Controller/StatisticsController.php index a702f4c..928867e 100644 --- a/src/Controller/StatisticsController.php +++ b/src/Controller/StatisticsController.php @@ -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; diff --git a/src/Entity/Statistics.php b/src/Entity/Statistics.php index e68bb3e..0106f53 100644 --- a/src/Entity/Statistics.php +++ b/src/Entity/Statistics.php @@ -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; } }