Improve frontend
This commit is contained in:
parent
5ade1c0593
commit
89237c2b84
1
frontend/package-lock.json
generated
1
frontend/package-lock.json
generated
@ -15,6 +15,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ngneat/falso": "^7.3.0",
|
||||
"@parcel/transformer-css": "^2.13.3",
|
||||
"@testing-library/dom": "^10.4.0",
|
||||
"@testing-library/jest-dom": "^6.6.3",
|
||||
"@testing-library/react": "^16.2.0",
|
||||
|
@ -7,6 +7,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ngneat/falso": "^7.3.0",
|
||||
"@parcel/transformer-css": "^2.13.3",
|
||||
"@testing-library/dom": "^10.4.0",
|
||||
"@testing-library/jest-dom": "^6.6.3",
|
||||
"@testing-library/react": "^16.2.0",
|
||||
|
@ -14,7 +14,7 @@
|
||||
--table-row-even: #f3f3f3;
|
||||
}
|
||||
|
||||
.dark-mode {
|
||||
.dark_mode {
|
||||
--main-color-bg: #15141a;
|
||||
--main-color: white;
|
||||
/* Menu */
|
||||
@ -30,7 +30,6 @@
|
||||
--table-row-even: #232229;
|
||||
}
|
||||
|
||||
|
||||
#root {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
@ -55,6 +54,27 @@ a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
canvas {
|
||||
width: 100%;
|
||||
display: block;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
form {
|
||||
width: 100%;
|
||||
}
|
||||
input[type=text] {
|
||||
width: 100%;
|
||||
height: 30px;
|
||||
box-sizing: border-box;
|
||||
border: 2px solid var(--menu-color-bg);
|
||||
border-radius: 5px;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.navbar {
|
||||
background-color: var(--menu-color-bg);
|
||||
display: flex;
|
||||
@ -115,72 +135,76 @@ a {
|
||||
font-size: 20px;
|
||||
font-weight: 200;
|
||||
}
|
||||
.totals-key {
|
||||
.totals_key {
|
||||
grid-area: 1 / 1 / 2 / 2;
|
||||
text-align: right;
|
||||
}
|
||||
.totals-value {
|
||||
.totals_value {
|
||||
grid-area: 1 / 2 / 2 / 3;
|
||||
padding-left: 10px;
|
||||
text-align: left;
|
||||
}
|
||||
#menu-title {
|
||||
#menu_title {
|
||||
color: var(--menu-color-text-hover);
|
||||
font-family: "Secular One", sans-serif;
|
||||
font-size: 30px;
|
||||
font-weight: 300;
|
||||
}
|
||||
#menu-logo {
|
||||
#menu_logo {
|
||||
padding: 5px;
|
||||
content: url("../assets/apiary.svg");
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
}
|
||||
.menu-link {
|
||||
.menu_link {
|
||||
color: var(--menu-color-text);
|
||||
}
|
||||
.menu-link:hover {
|
||||
.menu_link:hover {
|
||||
color: var(--menu-color-text-hover);
|
||||
}
|
||||
.menu-link-active {
|
||||
.menu_link_active {
|
||||
color: var(--menu-color-text-hover);
|
||||
}
|
||||
.stats-pie {
|
||||
height: 50vh;
|
||||
width: 50vw;
|
||||
.stats_pie {
|
||||
max-height: 70vh;
|
||||
text-align: left;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.live-table {
|
||||
.live_table {
|
||||
border-collapse: collapse;
|
||||
margin: 25px 0;
|
||||
font-size: 0.9em;
|
||||
font-family: sans-serif;
|
||||
min-width: 400px;
|
||||
width: 100%;
|
||||
box-shadow: 0 0 20px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
.live-table thead tr {
|
||||
.live_table thead tr {
|
||||
background-color: var(--table-color-header-bg);
|
||||
color: var(--table-color-header-text);
|
||||
text-align: left;
|
||||
}
|
||||
.live-table th,
|
||||
.live-table td {
|
||||
.live_table th,
|
||||
.live_table td {
|
||||
padding: 12px 15px;
|
||||
}
|
||||
.live-table tbody tr {
|
||||
.live_table tbody tr {
|
||||
border-bottom: 1px solid var(--table-row-odd);
|
||||
}
|
||||
.live-table tbody tr:nth-of-type(even) {
|
||||
.live_table tbody tr:nth-of-type(even) {
|
||||
background-color: var(--table-row-even);
|
||||
}
|
||||
.live-table tbody tr:last-of-type {
|
||||
.live_table tbody tr:last-of-type {
|
||||
border-bottom: 2px solid var(--table-color-bg);
|
||||
}
|
||||
.live-table tbody tr.active-row {
|
||||
.live_table tbody tr.active-row {
|
||||
font-weight: bold;
|
||||
color: var(--table-color-bg);
|
||||
}
|
||||
|
||||
.content {
|
||||
margin-left: 10vw;
|
||||
width: 50vw;
|
||||
text-align: center;
|
||||
}
|
@ -3,7 +3,6 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title></title>
|
||||
<link rel="stylesheet" href="./css/style.css" />
|
||||
<link rel="stylesheet" href="./css/fonts.css" />
|
||||
<script type="module" src="./js/app.tsx"></script>
|
||||
</head>
|
||||
|
@ -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" />} />
|
||||
@ -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,14 +382,14 @@ 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 >
|
||||
@ -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>
|
||||
})}
|
||||
|
Loading…
x
Reference in New Issue
Block a user