Improve frontend

This commit is contained in:
2025-03-19 16:49:18 +01:00
parent 5ade1c0593
commit 89237c2b84
5 changed files with 99 additions and 58 deletions

View File

@@ -3,11 +3,13 @@ import { createRoot } from "react-dom/client";
import { useState, useEffect } from 'react';
import { ApiaryAPI, LoginAttempt, DummyApiaryAPIClient, ApiaryAPIClient, TotalStats, StatsResult, StatsType } from "./api";
import { BrowserRouter, NavLink, Routes, Route } from "react-router";
import { Chart as ChartJS, Tooltip, ArcElement, Legend } from "chart.js";
import { Chart as ChartJS, Tooltip, ArcElement, Legend, ChartOptions } from "chart.js";
import { Pie } from "react-chartjs-2";
import humanizeDuration from "humanize-duration";
import * as classes from "../css/style.module.css";
ChartJS.register(Tooltip, ArcElement, Legend);
console.log(classes);
interface AppProps {
api: ApiaryAPI
@@ -71,9 +73,9 @@ export function App({ api }: AppProps) {
setMode(mode);
if (mode === "dark") {
document.body.classList.add("dark-mode");
document.body.classList.add(classes.dark_mode);
} else {
document.body.classList.remove("dark-mode");
document.body.classList.remove(classes.dark_mode);
}
}
@@ -89,7 +91,7 @@ export function App({ api }: AppProps) {
<BrowserRouter>
<div id="app">
<HeaderMenu title={headerProps.title} items={headerProps.items} />
<div className="content">
<div className={classes.content}>
<Routes>
<Route path="/" element={<Home api={api} />} />
<Route path="/stats/password" element={<Stats api={api} type="password" />} />
@@ -100,7 +102,7 @@ export function App({ api }: AppProps) {
</Routes>
</div>
</div>
</BrowserRouter>
</BrowserRouter >
</>
);
}
@@ -167,9 +169,23 @@ export function StatsPie({ data }: StatsPieProps) {
borderWidth: 1
}]
};
const getTextColor = () => {
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'white' : 'black'
}
const options: ChartOptions<"pie"> = {
plugins: {
legend: {
display: true,
align: "start",
labels: {
color: getTextColor(),
}
}
}
}
return (
<div className="stats-pie">
<Pie data={piedata} options={{ plugins: { legend: {} } }} />
<div className={classes.stats_pie}>
<Pie data={piedata} options={options} />
</div>
)
}
@@ -207,14 +223,14 @@ export interface TotalsProps {
export function Totals({ totals }: TotalsProps) {
return (
<div className="totals">
<div className="totals-key">
<div className={classes.totals}>
<div className={classes.totals_key}>
<h2>Unique passwords</h2>
<h2>Unique username</h2>
<h2>Unique IPs</h2>
<h2>Total attempts</h2>
</div>
<div className="totals-value">
<div className={classes.totals_value}>
<h2>{totals.password}</h2>
<h2>{totals.username}</h2>
<h2>{totals.ip}</h2>
@@ -268,7 +284,7 @@ export function DateTD({ date, now }: DateTDProps) {
}, [displayDate, now])
return (
<td className="live-table-date">{displayDate}</td>
<td className={classes.live_table_date}>{displayDate}</td>
)
}
@@ -281,10 +297,10 @@ export function LiveList({ list }: LiveListProps) {
return (
<tr key={a.date}>
<DateTD date={attemptDate} now={now} />
<td className="live-table-username">{a.username}</td>
<td className="live-table-password">{a.password}</td>
<td className="live-table-ip">{a.remoteIP}</td>
<td className="live-table-country">{a.country}</td>
<td className={classes.live_table_username}>{a.username}</td>
<td className={classes.live_table_password}>{a.password}</td>
<td className={classes.live_table_ip}>{a.remoteIP}</td>
<td className={classes.live_table_country}>{a.country}</td>
</tr>
)
})
@@ -301,14 +317,14 @@ export function LiveList({ list }: LiveListProps) {
return (
<>
<table className="live-table">
<table className={classes.live_table}>
<thead>
<tr>
<th className="live-table-date">Date</th>
<th className="live-table-username">Username</th>
<th className="live-table-password">Password</th>
<th className="live-table-ip">IP</th>
<th className="live-table-country">Country</th>
<th className={classes.live_table_date}>Date</th>
<th className={classes.live_table_username}>Username</th>
<th className={classes.live_table_password}>Password</th>
<th className={classes.live_table_ip}>IP</th>
<th className={classes.live_table_country}>Country</th>
</tr>
</thead>
<tbody>
@@ -344,7 +360,7 @@ export function Query({ api }: AppProps) {
return (
<>
<form onSubmit={handleSubmit}>
<input placeholder="query" name="query" />
<input placeholder="Search..." name="query" type="text" />
</form>
{queryErr ? <ErrorBox message={queryErr.message} /> : null}
<LiveList list={liveList} />
@@ -358,7 +374,7 @@ interface ErrorBoxProps {
export function ErrorBox({ message }: ErrorBoxProps) {
return (
<div className="error-box">
<div className={classes.error_box}>
<p>Error: {message}</p>
</div>
)
@@ -366,17 +382,17 @@ export function ErrorBox({ message }: ErrorBoxProps) {
export function Header() {
return (
<div className="navbar">
<div className={classes.navbar}>
<h2 id="menu-title">apiary.home.2rjus.net</h2>
<nav className="nav-flex">
<nav className={classes.nav_flex}>
<ul>
<li><NavLink to="/" className={({ isActive }) => isActive ? "menu-link-active" : "menu-link"}>Home</NavLink></li>
<li><NavLink to="/stats/password" className={({ isActive }) => isActive ? "menu-link-active" : "menu-link"}>Stats</NavLink></li>
<li><NavLink to="/live" className={({ isActive }) => isActive ? "menu-link-active" : "menu-link"}>Live</NavLink></li>
<li><NavLink to="/query" className={({ isActive }) => isActive ? "menu-link-active" : "menu-link"}>Query</NavLink></li>
<li><NavLink to="/" className={({ isActive }) => isActive ? classes.menu_link_active : classes.menu_link}>Home</NavLink></li>
<li><NavLink to="/stats/password" className={({ isActive }) => isActive ? classes.menu_link_active : classes.menu_link}>Stats</NavLink></li>
<li><NavLink to="/live" className={({ isActive }) => isActive ? classes.menu_link_active : classes.menu_link}>Live</NavLink></li>
<li><NavLink to="/query" className={({ isActive }) => isActive ? classes.menu_link_active : classes.menu_link}>Query</NavLink></li>
</ul>
</nav>
</div>
</div >
);
}
@@ -394,16 +410,16 @@ export function HeaderMenu({ title, items }: HeaderMenuProps) {
const menuItems = items.map((item) => {
return (
<li key={item.path}>
<NavLink to={item.path} className={({ isActive }) => isActive ? "menu-link-active" : "menu-link"}>{item.name}</NavLink>
<NavLink to={item.path} className={({ isActive }) => isActive ? classes.menu_link_active : classes.menu_link}>{item.name}</NavLink>
</li>
)
})
return (
<div className="navbar">
<img id="menu-logo"></img>
<h2 id="menu-title">{title}</h2>
<nav className="nav-flex">
<div className={classes.navbar}>
<img id={classes.menu_logo}></img>
<h2 id={classes.menu_title}>{title}</h2>
<nav className={classes.nav_flex}>
<ul>
{menuItems}
</ul>
@@ -418,13 +434,13 @@ export interface SubMenuProps {
export function SubMenu({ items }: SubMenuProps) {
return (
<nav className="submenu">
<nav className={classes.submenu}>
<ul>
{items.map((item) => {
return <li>
<a
href="#"
className={item.active() ? "sub-menu-link-active" : "sub-menu-link"}
className={item.active() ? classes.sub_menu_active : classes.sub_menu_link}
onClick={item.onClick}>{item.name}</a>
</li>
})}