diff --git a/back/lib/apiswagger.js b/back/lib/apiswagger.js
index d67409c..5d8d860 100644
--- a/back/lib/apiswagger.js
+++ b/back/lib/apiswagger.js
@@ -1,6 +1,5 @@
module.exports = (app, client) => {
const constant = require('../constant'),
- zlib = require('zlib'),
asyncHandler = require('express-async-handler'),
{ param, validationResult } = require('express-validator')
/**
@@ -513,33 +512,4 @@ module.exports = (app, client) => {
res.status(404).end()
}
}))
- /**
- * @swagger
- * /api/download_index:
- * get:
- * summary: Retrieve all content of ElasticSearch index.
- * description: Retrieve all content of ElasticSearch index.
- * responses:
- * 200:
- * description: A file compressed with gzip.
- * content:
- * application/gzip:
- */
- app.get('/api/download_index', asyncHandler(async (req, res) => {
- try {
- res.setHeader('Content-Type', 'application/gzip')
- res.setHeader('Content-disposition', 'attachment; filename=fediblock-index.json.gz')
- const result = await client.search({
- index: constant.index,
- size: 9999,
- query: {
- match_all: {}
- }
- }, { asStream: true, meta: false })
- result.pipe(zlib.createGzip()).pipe(res)
- } catch (e) {
- console.error(e)
- res.status(404).end()
- }
- }))
}
diff --git a/front/package.json b/front/package.json
index ea4fbcc..0b7b220 100644
--- a/front/package.json
+++ b/front/package.json
@@ -6,6 +6,7 @@
"cra-template": "1.2.0",
"dayjs": "^1.11.13",
"html2canvas": "^1.4.1",
+ "react-icons": "^5.3.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-scripts": "5.0.1",
diff --git a/front/src/App.css b/front/src/App.css
index 6ce1625..71ec954 100644
--- a/front/src/App.css
+++ b/front/src/App.css
@@ -1,405 +1,611 @@
+@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&display=swap');
+
:root[data-theme="dark"] {
- --background-color: #212529;
- --color: #fefefe;
- --instance-list: #333333;
- --footer: #cccccc;
+ --bg: #05060a;
+ --bg-soft: #0f1118;
+ --panel: rgba(255, 255, 255, 0.04);
+ --panel-strong: rgba(255, 255, 255, 0.08);
+ --text: #f7f7fb;
+ --muted: #a7adc7;
+ --accent: #7ef3e4;
+ --accent-strong: #5ad1ff;
+ --border: rgba(255, 255, 255, 0.1);
}
:root[data-theme="light"] {
- --background-color: #f5f5f5;
- --color: #333333;
- --instance-list: #f0f0f0;
- --footer: #666666;
+ --bg: #f6f7fb;
+ --bg-soft: #ffffff;
+ --panel: rgba(15, 17, 24, 0.04);
+ --panel-strong: rgba(15, 17, 24, 0.08);
+ --text: #0f1118;
+ --muted: #5a6078;
+ --accent: #0066ff;
+ --accent-strong: #111dff;
+ --border: rgba(15, 17, 24, 0.1);
+}
+
+*, *::before, *::after {
+ box-sizing: border-box;
}
body {
- background-color: var(--background-color);
- text-align: center;
- font-family: Verdana, Geneva, Tahoma, sans-serif;
+ margin: 0;
+ font-family: 'Space Grotesk', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
+ background: var(--bg);
+ color: var(--text);
+ min-height: 100vh;
scrollbar-width: thin;
}
-h1,
-h3,
-h4,
-.scan {
- margin: 0 auto;
- color: var(--color);
- text-align: center;
-}
-
-h3 {
- padding: 10px 0;
- border-bottom: 1px solid var(--footer);
- margin-bottom: 1rem;
-}
-
-a,
-a:hover {
- color: var(--color);
+a {
+ color: inherit;
text-decoration: none;
}
-input[type="text"] {
- padding: 10px;
+.muted {
+ color: var(--muted);
+}
+
+.eyebrow {
+ text-transform: uppercase;
+ letter-spacing: 0.2em;
+ font-size: 0.75rem;
+ color: var(--accent);
+}
+
+.subtitle {
+ margin: 0.25rem 0 0;
+ color: var(--muted);
+}
+
+.app-shell {
+ padding: 2rem clamp(1rem, 4vw, 4rem) 3rem;
+ background: radial-gradient(circle at top, rgba(126, 243, 228, 0.15), transparent 45%), var(--bg);
+ min-height: 100vh;
+ display: flex;
+ flex-direction: column;
+ gap: 2rem;
+}
+
+.hero-grid {
+ display: flex;
+ flex-direction: column;
+ gap: 1.5rem;
+}
+
+.content-layout {
+ display: grid;
+ grid-template-columns: minmax(0, 2fr) minmax(280px, 1fr);
+ gap: 2rem;
+ align-items: start;
+}
+
+@media (max-width: 900px) {
+ .content-layout {
+ grid-template-columns: 1fr;
+ }
+}
+
+.title-card {
+ display: flex;
+ justify-content: space-between;
+ gap: 1.5rem;
+ padding: 1.75rem;
+ border-radius: 1.5rem;
+ background: linear-gradient(135deg, rgba(126, 243, 228, 0.15), rgba(90, 209, 255, 0.05));
+ border: 1px solid var(--border);
+ flex-wrap: wrap;
+}
+
+.title-stack {
+ flex: 1;
+}
+
+.title-card h1 {
+ margin: 0.2rem 0 0.35rem;
+ font-size: clamp(2rem, 5vw, 3.4rem);
+}
+
+.ghost-button,
+.secondary-button,
+.footer-actions button,
+.footer-actions a,
+.ticker-chip,
+.suggestions button,
+.instance-card__header button,
+.instance-card__import button,
+.modal-actions button,
+.search-panel__header .ghost-button {
+ border: 1px solid transparent;
+ background: transparent;
+ color: var(--text);
+ padding: 0.65rem 1rem;
+ border-radius: 999px;
+ display: inline-flex;
+ align-items: center;
+ gap: 0.45rem;
+ font-weight: 600;
+ cursor: pointer;
+ transition: background 0.2s ease, border 0.2s ease, transform 0.2s ease;
+}
+
+.ghost-button {
+ border-color: var(--border);
+ background: var(--panel);
+}
+
+.ghost-button:hover,
+.secondary-button:hover,
+.footer-actions button:hover,
+.footer-actions a:hover,
+.ticker-chip:hover,
+.suggestions button:hover,
+.instance-card__import button:hover,
+.modal-actions button:hover {
+ border-color: var(--accent);
+ background: var(--panel-strong);
+ transform: translateY(-1px);
+}
+
+.ticker-card,
+.stats-card,
+.search-panel,
+.scan-card,
+.instance-card,
+.instance-card__import {
+ background: var(--bg-soft);
+ border: 1px solid var(--border);
+ border-radius: 1.25rem;
+ padding: 1.5rem;
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.12);
+}
+
+.ticker-card__header {
+ display: flex;
+ gap: 0.75rem;
+ align-items: center;
+ margin-bottom: 1rem;
+ font-weight: 600;
+}
+
+.ticker-card__title small {
+ display: block;
+ font-size: 0.8rem;
+ color: var(--muted);
+}
+
+.ticker-card__body {
+ overflow: hidden;
+}
+
+.ticker-marquee {
+ display: inline-flex;
+ gap: 0.75rem;
+ animation: ticker 45s linear infinite;
+ min-width: 100%;
+}
+
+.ticker-card:hover .ticker-marquee {
+ animation-play-state: paused;
+}
+
+@keyframes ticker {
+ from {
+ transform: translateX(0);
+ }
+ to {
+ transform: translateX(-50%);
+ }
+}
+
+.ticker-chip {
+ font-size: 0.9rem;
+ border-color: var(--border);
+ background: var(--panel);
+ white-space: nowrap;
+}
+
+.ticker-chip__time {
+ font-size: 0.75rem;
+ color: var(--muted);
+}
+
+.stats-card {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
+ gap: 1.5rem;
+ align-items: start;
+}
+
+.stats-card__main {
+ display: flex;
+ gap: 1rem;
+ align-items: center;
+}
+
+.stats-card__icon {
+ width: 3.5rem;
+ height: 3.5rem;
+ border-radius: 1rem;
+ display: grid;
+ place-items: center;
+ background: var(--panel);
+ color: var(--accent);
+}
+
+.stats-card__main h2 {
+ margin: 0.2rem 0;
+ font-size: 2.2rem;
+}
+
+.stats-card__links {
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+}
+
+.stats-card__links a {
+ color: var(--accent);
+ font-weight: 600;
+ display: inline-flex;
+ gap: 0.4rem;
+ align-items: center;
+}
+
+.stats-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
+ gap: 1rem;
+ margin: 0;
+}
+
+.stats-grid div {
+ padding: 0.75rem 1rem;
+ background: var(--panel);
+ border-radius: 0.85rem;
+}
+
+.stats-grid dt {
+ font-size: 0.8rem;
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+ color: var(--muted);
+ margin: 0 0 0.25rem;
+}
+
+.stats-grid dd {
+ margin: 0;
+ font-weight: 600;
+ font-size: 1.1rem;
+}
+
+.search-panel {
+ display: flex;
+ flex-direction: column;
+ gap: 1.25rem;
+}
+
+.search-panel__header {
+ display: flex;
+ justify-content: space-between;
+ gap: 1rem;
+ flex-wrap: wrap;
+}
+
+.input-stack {
+ display: flex;
+ gap: 0.75rem;
+ flex-wrap: wrap;
+}
+
+.input-field {
+ flex: 1;
+ display: inline-flex;
+ align-items: center;
+ gap: 0.5rem;
+ padding: 0.65rem 1rem;
+ border-radius: 999px;
+ background: var(--panel);
+ border: 1px solid var(--border);
+}
+
+.input-field input {
+ flex: 1;
border: none;
- border-bottom: 1px solid var(--footer);
- background-color: var(--instance-list);
- color: var(--color);
+ background: transparent;
+ color: var(--text);
+ font-size: 1rem;
+ outline: none;
}
-.placeholder {
- font-size: small;
- margin: 0 auto;
- color: var(--color);
+.secondary-button {
+ border-color: var(--accent);
+ color: var(--accent);
}
-.title, .placeholder a, footer a, .download-csv {
- cursor: pointer;
+.suggestions {
+ display: flex;
+ gap: 0.5rem;
+ flex-wrap: wrap;
}
-hr {
- margin: 1rem auto;
- border-bottom: 1px solid var(--footer);
- width: 50%;
+.suggestions button {
+ border-color: var(--border);
+ background: var(--panel);
}
-.tooltip {
- visibility: hidden;
- font-size: small;
- text-align: left;
- width: 2.2in;
- background-color: var(--background-color);
- color: var(--color);
- border-radius: 5px;
- padding: 1rem 1rem 0 0;
- position: absolute;
- z-index: 1;
- opacity: 0;
- transition: 0.5s;
- border: 1px solid var(--footer);
-}
-
-.count:hover .tooltip {
- visibility: visible;
- opacity: 1;
-}
-
-.reverse {
- margin: 0.5rem;
- padding: 0.5rem;
- border: 1px solid var(--footer);
- background-color: var(--color);
- color: var(--background-color);
-}
-
-.reverse:hover {
- color: var(--color);
- background-color: var(--background-color);
- cursor: pointer;
+.list-shell {
+ max-height: 65vh;
+ overflow: hidden;
+ border-radius: 1rem;
+ border: 1px solid var(--border);
}
.instancelist {
- list-style-type: none;
- margin: 20px 0;
+ list-style: none;
+ margin: 0;
padding: 0;
- color: var(--color);
- max-height: 58vh;
- overflow: auto;
+ max-height: inherit;
+ overflow-y: auto;
scrollbar-width: thin;
}
-@media (min-width: 992px) {
- .instancelist li {
- width: 30%;
- }
-
- .instancelist li:hover {
- width: 35%;
- }
-
- .instance,
- .placeholder,
- h4 {
- width: 30%;
- }
-
- .modal-content {
- width: 28%;
- }
+.instance-card,
+.instance-card__import {
+ border-radius: 0;
+ border: none;
+ box-shadow: none;
+ border-bottom: 1px solid var(--border);
}
-@media (min-width: 600px) and (max-width: 992px) {
- .instancelist li {
- width: 60%;
- }
-
- .instancelist li:hover {
- width: 65%;
- }
-
- .instance,
- .placeholder,
- h4 {
- width: 60%;
- }
-
- .modal-content {
- width: 58%;
- }
+.instance-card:last-child {
+ border-bottom: none;
}
-@media (max-width: 600px) {
- .instancelist li {
- width: 90%;
- }
-
- .instancelist li:hover {
- width: 95%;
- }
-
- .instance,
- .placeholder,
- h4 {
- width: 90%;
- }
-
- .api {
- display: none;
- }
-
- .modal-content {
- width: 88%;
- }
+.instance-card__header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ gap: 0.5rem;
+ margin-bottom: 0.5rem;
}
-.instancelist li {
- color: var(--color);
- padding: 10px;
- border-bottom: 1px solid var(--footer);
- margin: 0 auto;
- max-height: 1.5em;
- overflow: hidden;
- min-height: 1.5em;
- line-height: 1.5;
+.instance-card__header button {
+ padding: 0;
+ border: none;
+ background: none;
+ font-size: 1.05rem;
}
-.instancelist li:hover {
- background-color: var(--instance-list);
- cursor: pointer;
- max-height: fit-content;
+.instance-card__badge {
+ font-size: 0.8rem;
+ color: var(--accent);
}
-.instancelist li img {
- width: 70%;
- margin: 0 auto;
+.instance-card__body {
+ font-size: 0.85rem;
+ color: var(--muted);
+ display: grid;
+ gap: 0.25rem;
}
-@keyframes opacity {
- from {
- opacity: 0;
- }
-
- to {
- opacity: 1;
- }
+.instance-card__body img {
+ width: 100%;
+ border-radius: 0.75rem;
}
-footer {
+.instance-card__import {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ gap: 1rem;
+}
+
+.instance-card__order {
+ font-weight: 700;
+ margin-right: 1rem;
+ color: var(--accent);
+}
+
+.scan-card {
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+}
+
+.scan-card header {
+ display: flex;
+ gap: 1rem;
+ align-items: center;
+}
+
+.scan-card p {
+ margin: 0;
+ font-size: 1rem;
+}
+
+.app-footer {
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+ padding: 1.5rem;
+ border-radius: 1.25rem;
+ border: 1px solid var(--border);
+ background: var(--bg-soft);
+}
+
+.footer-metrics {
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+ font-size: 0.95rem;
+}
+
+.footer-metrics svg {
+ color: var(--accent);
+ margin-right: 0.5rem;
+}
+
+.footer-actions {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.75rem;
+}
+
+.footer-actions a {
+ text-decoration: none;
+}
+
+.footer-credit {
+ margin-left: auto;
+ font-size: 0.85rem;
+ color: var(--muted);
+}
+
+.footer-actions a.footer-credit {
+ border: none;
+ padding: 0;
+ background: transparent;
+ color: var(--muted);
+}
+
+.footer-actions a.footer-credit:hover {
+ color: var(--text);
+ border: none;
+ background: transparent;
+ transform: none;
+}
+
+.loader-content {
+ z-index: 100;
+ background: rgba(5, 6, 10, 0.75);
+ width: 100%;
+ height: 100%;
+ display: none;
+ position: fixed;
+ inset: 0;
+ backdrop-filter: blur(6px);
+}
+
+.loader {
+ background: var(--bg-soft);
+ padding: 2rem;
+ border-radius: 1.5rem;
+ border: 1px solid var(--border);
+ box-shadow: 0 20px 45px rgba(0, 0, 0, 0.35);
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
text-align: center;
- font-size: 12px;
- padding: 10px;
- color: var(--footer);
}
-.count {
- text-decoration: underline dotted var(--color);
- cursor: help;
- position: relative;
- display: inline-block;
- text-underline-offset: 2px;
-}
-
-.blocklist {
- width: 90%;
- text-align: left;
- list-style: none;
- padding: 1rem;
-}
-
-.blocklist li {
- color: var(--color);
- white-space: nowrap;
- word-wrap: break-word;
- overflow: hidden;
-}
-
-.blocklist li:hover {
- white-space: inherit;
- cursor: pointer;
- color: var(--color);
-}
-
-.blocklist li a,
-.blocklist li a:hover,
-.blocklist li a:visited {
- color: var(--color);
- text-decoration: underline;
-}
-
-.blockinstance a,
-.blockinstance a:hover {
- color: var(--color);
- text-decoration: underline;
- cursor: help;
-}
-
-.blockcount {
- font-weight: bolder;
+.loader p {
+ margin-top: 1rem;
+ color: var(--muted);
}
.modal {
display: none;
position: fixed;
- z-index: 1;
- left: 0;
- top: 0;
- width: 100%;
- height: 100%;
- overflow: auto;
- scrollbar-width: thin;
- background-color: rgba(0, 0, 0, 0.4);
+ inset: 0;
+ background: rgba(5, 6, 10, 0.8);
+ backdrop-filter: blur(8px);
+ z-index: 200;
+ padding: 1rem;
}
.modal-content {
- position: relative;
- background-color: var(--background-color);
- margin: 10px auto;
- padding: 0;
- border: 1px solid var(--footer);
- -webkit-animation-name: animatetop;
- -webkit-animation-duration: 0.4s;
- animation-name: animatetop;
+ max-width: 640px;
+ margin: auto;
+ background: var(--bg-soft);
+ border-radius: 1.5rem;
+ border: 1px solid var(--border);
+ overflow: hidden;
animation-duration: 0.4s;
+ animation-name: animatetop;
}
.modal-header {
- padding: 6px;
- background-color: var(--color);
- color: var(--background-color);
+ display: flex;
+ justify-content: space-between;
+ gap: 1rem;
+ padding: 1.5rem;
+ border-bottom: 1px solid var(--border);
+}
+
+.modal-actions {
+ display: flex;
+ gap: 0.5rem;
}
.modal-body {
- color: var(--color);
- background-color: var(--background-color);
- padding: 6px;
+ padding: 1.5rem;
+}
+
+.blocklist {
+ list-style: none;
+ padding: 0;
+ margin: 0;
+ display: flex;
+ flex-direction: column;
+ gap: 0.75rem;
+ max-height: 60vh;
+ overflow-y: auto;
+ scrollbar-width: thin;
+}
+
+.blocklist li {
+ line-height: 1.4;
+}
+
+.blocklist a {
+ color: var(--accent);
+ cursor: pointer;
+}
+
+.link-button {
+ border: none;
+ background: transparent;
+ color: var(--accent);
+ text-decoration: underline;
+ cursor: pointer;
+ padding: 0;
+ font: inherit;
+}
+
+.link-button:hover {
+ color: var(--accent-strong);
+}
+
+.blockcount {
+ font-size: 1.4rem;
+ color: var(--accent);
}
.modal-footer {
- padding: 6px;
- background-color: var(--color);
- color: var(--background-color);
-}
-
-.closemodal,
-.download,
-.capture {
- color: var(--footer);
- float: right;
- font-size: 3rem;
- font-weight: bolder;
- margin: 0px 15px;
-}
-
-.closemodal:hover,
-.closemodal:focus,
-.download:hover,
-.download:focus,
-.capture:hover,
-.capture:focus {
- color: var(--color);
- text-decoration: none;
- cursor: pointer;
+ border-top: 1px solid var(--border);
+ padding: 1rem 1.5rem;
}
@keyframes animatetop {
from {
- top: -300px;
opacity: 0;
+ transform: translateY(60px);
}
-
to {
- top: 0;
opacity: 1;
+ transform: translateY(0);
}
}
@keyframes animatebottom {
from {
- top: 0;
opacity: 1;
+ transform: translateY(0);
}
-
to {
- top: -300px;
- opacity: 0
+ opacity: 0;
+ transform: translateY(60px);
}
-}
-
-h4 {
- white-space: nowrap;
- overflow: hidden;
- position: relative;
- border-left: 1px solid var(--background-color);
- border-right: 1px solid var(--background-color);
- transition: 0.2s;
-}
-
-h4 p {
- margin: 0.3rem 0;
-}
-
-h4:hover {
- width: 90%;
-}
-
-.bounce {
- display: inline-block;
- animation: marquee 90s linear infinite;
-}
-
-.bounce:hover {
- animation-play-state: paused;
-}
-
-@keyframes marquee {
- 0% {
- transform: translateX(100vw);
- }
-
- 100% {
- transform: translateX(-100%);
- }
-}
-
-.loader-content {
- z-index: 0;
- background-color: rgba(0, 0, 0, 0.4);
- width: 100%;
- height: 100%;
- display: none;
- position: fixed;
- top: 0;
- left: 0;
- overflow: auto;
- scrollbar-width: thin;
-}
-
-.loader {
- background-color: var(--color);
- width: fit-content;
- margin: 50vh auto;
- z-index: 1;
- position: relative;
- color: var(--background-color);
}
\ No newline at end of file
diff --git a/front/src/App.js b/front/src/App.js
index e2258ef..d937eb2 100644
--- a/front/src/App.js
+++ b/front/src/App.js
@@ -12,16 +12,19 @@ function App() {
const [searchTerm, setSearchTerm] = useState(''),
[matrix, setMatrix] = useState('off')
return (
- <>
-
- setSearchTerm(d)} />
-
-
-
-
+
+
+
+ setSearchTerm(d)} />
+
+
+
+
+
+
);
}
diff --git a/front/src/component/Bar.js b/front/src/component/Bar.js
index dab139c..9b968a2 100644
--- a/front/src/component/Bar.js
+++ b/front/src/component/Bar.js
@@ -1,9 +1,11 @@
import { useEffect, useState, useCallback } from 'react';
-import dayjs from '../../node_modules/dayjs/';
-import relativeTime from '../../node_modules/dayjs/plugin/relativeTime';
+import dayjs from 'dayjs';
+import relativeTime from 'dayjs/plugin/relativeTime';
+import { FiActivity } from 'react-icons/fi';
+
dayjs.extend(relativeTime)
-const Bar = (prop) => {
+const Bar = ({ setSearch }) => {
const [bounce, setBounce] = useState([]),
fillBounce = useCallback(async () => {
try {
@@ -15,14 +17,47 @@ const Bar = (prop) => {
} catch (e) {
console.error(e)
}
- })
+ }, [])
+
useEffect(() => {
fillBounce()
- }, [])
+ }, [fillBounce])
+
return (
-
-
{bounce && bounce.length > 0 ? bounce.map(b => prop.setSearch(b.content.replace(/.*#/g, '').replace(/\<\/a\>\<\/p\>/, ''))}>{b.content.replace(/<[^>]*>/g, '').replace(new RegExp(new URL(window.location.href).host), '')} - {dayjs().to(b.published)}).reduce((prev, curr) => [prev, ' | ', curr]) : []}
-
+
+
+
+ {bounce?.length ? (
+
+ {bounce.map((b, index) => {
+ const sanitized = b.content.replace(/.*#/g, '').replace(/<\/a><\/p>/, ''),
+ plainText = b.content
+ .replace(/<[^>]*>/g, '')
+ .replace(new RegExp(new URL(window.location.href).host), '')
+ return (
+
+ )
+ })}
+
+ ) : (
+
No recent activity
+ )}
+
+
)
}
diff --git a/front/src/component/Count.js b/front/src/component/Count.js
index facdcff..dc60908 100644
--- a/front/src/component/Count.js
+++ b/front/src/component/Count.js
@@ -1,8 +1,9 @@
-import { useEffect, useState, useCallback } from 'react';
+import { useEffect, useState, useCallback, useMemo } from 'react';
+import { FiTrendingUp, FiExternalLink } from 'react-icons/fi';
const Count = () => {
- const [count, setCount] = useState('0'),
- [statsres, setStatsres] = useState(''),
+ const [count, setCount] = useState(0),
+ [statsres, setStatsres] = useState(null),
fillStats = useCallback(async () => {
try {
const [res, stats] = await Promise.all([(await fetch('/api/count')).json(), (await fetch('/api/stats')).json()])
@@ -11,30 +12,82 @@ const Count = () => {
} catch (e) {
console.error(e)
}
- })
+ }, [])
+
useEffect(() => {
- (async () => {
- await fillStats()
- })()
- }, [])
+ fillStats()
+ }, [fillStats])
+
+ const statLines = useMemo(() => {
+ if (!statsres) {
+ return []
+ }
+ const safeDiv = (numerator, denominator) => {
+ if (!denominator) return 0
+ return numerator / denominator
+ }
+ return [
+ { label: 'Statuses AVG', value: Math.round(statsres.status_avg ?? 0) },
+ { label: 'Statuses MAX', value: statsres.status_max },
+ { label: 'Domain AVG', value: Math.round(statsres.domain_avg ?? 0) },
+ { label: 'Domain MAX', value: statsres.domain_max },
+ { label: 'Users AVG', value: Math.round(statsres.user_avg ?? 0) },
+ { label: 'Users MAX', value: statsres.user_max },
+ { label: 'Stats Instances', value: statsres.stats_filtered },
+ { label: 'Total Instances', value: statsres.instance_count },
+ {
+ label: 'Users per Instance',
+ value: safeDiv(Math.round(statsres.user_avg ?? 0), statsres.instance_count ?? 1).toFixed(2)
+ },
+ {
+ label: 'Statuses per Domain',
+ value: safeDiv(Math.round(statsres.status_avg ?? 0), Math.round(statsres.domain_avg ?? 1)).toFixed(2)
+ },
+ {
+ label: 'Statuses per User',
+ value: safeDiv(Math.round(statsres.status_avg ?? 0), Math.round(statsres.user_avg ?? 1)).toFixed(2)
+ }
+ ]
+ }, [statsres])
+
+ const formatNumber = value => {
+ if (Number.isNaN(Number(value))) {
+ return value ?? '--'
+ }
+ return new Intl.NumberFormat().format(value ?? 0)
+ }
+
return (
-
+
+
+
+
+
+
+
Searching across
+
{formatNumber(count)}
+
public instances
+
+
+
+
+ {statLines.map(item => (
+
+
- {item.label}
+ - {formatNumber(item.value)}
+
+ ))}
+
+
)
}
diff --git a/front/src/component/Footer.js b/front/src/component/Footer.js
index 45b7e86..293d03f 100644
--- a/front/src/component/Footer.js
+++ b/front/src/component/Footer.js
@@ -1,24 +1,23 @@
import { useEffect, useState, useCallback } from 'react';
+import { FiSun, FiMoon, FiDownload, FiGrid, FiZap } from 'react-icons/fi';
import Messenger from '../random-text';
-const Footer = (prop) => {
+const Footer = ({ setCurrentMatrix }) => {
const [theme, setTheme] = useState('dark'),
[matrix, setMatrix] = useState('off'),
[served, setServed] = useState({ served: 0, lastscan: 0, server: 0, instances: 0, peers: 0, created: 0, updated: 0 }),
- loading = () => {
- document.querySelector('.loader-content').style.display = 'initial'
- setTimeout(() => {
- document.querySelector('.loader-content').style.display = 'none'
- }, 60 * 1000)
- },
- toggleTheme = useCallback(() => {
- let tm = document.documentElement.dataset.theme
- if (!theme || tm === 'light') {
- setTheme('dark')
- } else {
- setTheme('light')
+ toggleLoader = useCallback(() => {
+ const loader = document.querySelector('.loader-content')
+ if (loader) {
+ loader.style.display = 'initial'
+ setTimeout(() => {
+ loader.style.display = 'none'
+ }, 60 * 1000)
}
- }),
+ }, []),
+ toggleTheme = useCallback(() => {
+ setTheme(prev => (prev === 'light' ? 'dark' : 'light'))
+ }, []),
refreshServed = useCallback(async () => {
try {
const response = await fetch('/api/served')
@@ -28,38 +27,67 @@ const Footer = (prop) => {
} catch (e) {
console.error(e)
}
- }),
+ }, []),
toggleMatrix = useCallback(() => {
- if (matrix === 'off') {
- setMatrix('on')
- prop.setCurrentMatrix('on')
- } else {
- setMatrix('off')
- prop.setCurrentMatrix('off')
- }
- })
+ setMatrix(prev => {
+ const next = prev === 'off' ? 'on' : 'off'
+ setCurrentMatrix(next)
+ return next
+ })
+ }, [setCurrentMatrix])
+
useEffect(() => {
- setTheme(theme)
document.documentElement.dataset.theme = theme
+ }, [theme])
+
+ useEffect(() => {
refreshServed()
+ }, [refreshServed])
+
+ useEffect(() => {
if (matrix === 'on') {
- var walker = document.createTreeWalker(document.getElementById('root'), NodeFilter.SHOW_TEXT)
- while (walker.nextNode()) {
- if (walker.currentNode.textContent.length > 1) {
- new Messenger(walker.currentNode)
+ const root = document.getElementById('root')
+ if (root) {
+ const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT)
+ while (walker.nextNode()) {
+ if (walker.currentNode.textContent.length > 1) {
+ new Messenger(walker.currentNode)
+ }
}
}
}
- }, [theme])
+ }, [matrix])
+
return (
-