initial commit
Todas las comprobaciones han sido exitosas
continuous-integration/drone Build is passing
Todas las comprobaciones han sido exitosas
continuous-integration/drone Build is passing
Signed-off-by: ale <ale@manalejandro.com>
Este commit está contenido en:
405
front/src/App.css
Archivo normal
405
front/src/App.css
Archivo normal
@@ -0,0 +1,405 @@
|
||||
:root[data-theme="dark"] {
|
||||
--background-color: #212529;
|
||||
--color: #fefefe;
|
||||
--instance-list: #333333;
|
||||
--footer: #cccccc;
|
||||
}
|
||||
|
||||
:root[data-theme="light"] {
|
||||
--background-color: #f5f5f5;
|
||||
--color: #333333;
|
||||
--instance-list: #f0f0f0;
|
||||
--footer: #666666;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--background-color);
|
||||
text-align: center;
|
||||
font-family: Verdana, Geneva, Tahoma, sans-serif;
|
||||
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);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
input[type="text"] {
|
||||
padding: 10px;
|
||||
border: none;
|
||||
border-bottom: 1px solid var(--footer);
|
||||
background-color: var(--instance-list);
|
||||
color: var(--color);
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
font-size: small;
|
||||
margin: 0 auto;
|
||||
color: var(--color);
|
||||
}
|
||||
|
||||
.title, .placeholder a, footer a, .download-csv {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
hr {
|
||||
margin: 1rem auto;
|
||||
border-bottom: 1px solid var(--footer);
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.instancelist {
|
||||
list-style-type: none;
|
||||
margin: 20px 0;
|
||||
padding: 0;
|
||||
color: var(--color);
|
||||
max-height: 58vh;
|
||||
overflow: 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%;
|
||||
}
|
||||
}
|
||||
|
||||
@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%;
|
||||
}
|
||||
}
|
||||
|
||||
@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%;
|
||||
}
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.instancelist li:hover {
|
||||
background-color: var(--instance-list);
|
||||
cursor: pointer;
|
||||
max-height: fit-content;
|
||||
}
|
||||
|
||||
.instancelist li img {
|
||||
width: 70%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
@keyframes opacity {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
footer {
|
||||
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;
|
||||
}
|
||||
|
||||
.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);
|
||||
}
|
||||
|
||||
.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;
|
||||
animation-duration: 0.4s;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
padding: 6px;
|
||||
background-color: var(--color);
|
||||
color: var(--background-color);
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
color: var(--color);
|
||||
background-color: var(--background-color);
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
@keyframes animatetop {
|
||||
from {
|
||||
top: -300px;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
to {
|
||||
top: 0;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes animatebottom {
|
||||
from {
|
||||
top: 0;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
to {
|
||||
top: -300px;
|
||||
opacity: 0
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
28
front/src/App.js
Archivo normal
28
front/src/App.js
Archivo normal
@@ -0,0 +1,28 @@
|
||||
import { useState } from 'react';
|
||||
import './App.css';
|
||||
import Title from './component/Title';
|
||||
import Bar from './component/Bar';
|
||||
import Count from './component/Count';
|
||||
import Form from './component/Form';
|
||||
import Scan from './component/Scan';
|
||||
import Footer from './component/Footer';
|
||||
import Loader from './component/Loader';
|
||||
|
||||
function App() {
|
||||
const [searchTerm, setSearchTerm] = useState(''),
|
||||
[matrix, setMatrix] = useState('off')
|
||||
return (
|
||||
<>
|
||||
<Title />
|
||||
<Bar setSearch={d => setSearchTerm(d)} />
|
||||
<Count />
|
||||
<Form searchTerm={searchTerm} matrix={matrix} />
|
||||
<Scan />
|
||||
<hr />
|
||||
<Footer setCurrentMatrix={m => setMatrix(m)} />
|
||||
<Loader />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
8
front/src/App.test.js
Archivo normal
8
front/src/App.test.js
Archivo normal
@@ -0,0 +1,8 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import App from './App';
|
||||
|
||||
test('renders learn react link', () => {
|
||||
render(<App />);
|
||||
const linkElement = screen.getByText(/learn react/i);
|
||||
expect(linkElement).toBeInTheDocument();
|
||||
});
|
||||
29
front/src/component/Bar.js
Archivo normal
29
front/src/component/Bar.js
Archivo normal
@@ -0,0 +1,29 @@
|
||||
import { useEffect, useState, useCallback } from 'react';
|
||||
import dayjs from '../../node_modules/dayjs/';
|
||||
import relativeTime from '../../node_modules/dayjs/plugin/relativeTime';
|
||||
dayjs.extend(relativeTime)
|
||||
|
||||
const Bar = (prop) => {
|
||||
const [bounce, setBounce] = useState([]),
|
||||
fillBounce = useCallback(async () => {
|
||||
try {
|
||||
const response = await fetch('/api/outbox'),
|
||||
result = await response.json()
|
||||
if (result && result.length > 0) {
|
||||
setBounce(result)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
})
|
||||
useEffect(() => {
|
||||
fillBounce()
|
||||
}, [])
|
||||
return (
|
||||
<h4>
|
||||
<p className="bounce">{bounce && bounce.length > 0 ? bounce.map(b => <a onClick={() => 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)}</a>).reduce((prev, curr) => [prev, ' | ', curr]) : []}</p>
|
||||
</h4>
|
||||
)
|
||||
}
|
||||
|
||||
export default Bar;
|
||||
41
front/src/component/Count.js
Archivo normal
41
front/src/component/Count.js
Archivo normal
@@ -0,0 +1,41 @@
|
||||
import { useEffect, useState, useCallback } from 'react';
|
||||
|
||||
const Count = () => {
|
||||
const [count, setCount] = useState('0'),
|
||||
[statsres, setStatsres] = useState(''),
|
||||
fillStats = useCallback(async () => {
|
||||
try {
|
||||
const [res, stats] = await Promise.all([(await fetch('/api/count')).json(), (await fetch('/api/stats')).json()])
|
||||
setCount(res.count)
|
||||
setStatsres(stats)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
})
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
await fillStats()
|
||||
})()
|
||||
}, [])
|
||||
return (
|
||||
<h3>Search in <a href="/api/stats" target="_blank">
|
||||
<span className="count">{count}<div className="tooltip">
|
||||
<u><strong><center>STATS</center></strong></u>
|
||||
<ul><li>Statuses AVG: {Math.round(statsres.status_avg)}</li>
|
||||
<li>Statuses MAX: {statsres.status_max}</li>
|
||||
<li>Domain AVG: {Math.round(statsres.domain_avg)}</li>
|
||||
<li>Domain MAX: {statsres.domain_max}</li>
|
||||
<li>Users AVG: {Math.round(statsres.user_avg)}</li>
|
||||
<li>Users MAX: {statsres.user_max}</li>
|
||||
<li>Stats Instances: {statsres.stats_filtered}</li>
|
||||
<li>Total Instances: {statsres.instance_count}</li>
|
||||
<li>Users by Instance: {(Math.round(statsres.user_avg) / statsres.instance_count).toFixed(2)}</li>
|
||||
<li>Statuses by Domain: {(Math.round(statsres.status_avg) / Math.round(statsres.domain_avg)).toFixed(2)}</li>
|
||||
<li>Statuses by User: {(Math.round(statsres.status_avg) / Math.round(statsres.user_avg)).toFixed(2)}</li>
|
||||
</ul></div></span>
|
||||
</a> public instances<span className="api"> - <a href="/api-docs" target="_blank">API docs</a></span>
|
||||
</h3>
|
||||
)
|
||||
}
|
||||
|
||||
export default Count;
|
||||
67
front/src/component/Footer.js
Archivo normal
67
front/src/component/Footer.js
Archivo normal
@@ -0,0 +1,67 @@
|
||||
import { useEffect, useState, useCallback } from 'react';
|
||||
import Messenger from '../random-text';
|
||||
|
||||
const Footer = (prop) => {
|
||||
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')
|
||||
}
|
||||
}),
|
||||
refreshServed = useCallback(async () => {
|
||||
try {
|
||||
const response = await fetch('/api/served')
|
||||
if (response.ok) {
|
||||
setServed(await response.json())
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}),
|
||||
toggleMatrix = useCallback(() => {
|
||||
if (matrix === 'off') {
|
||||
setMatrix('on')
|
||||
prop.setCurrentMatrix('on')
|
||||
} else {
|
||||
setMatrix('off')
|
||||
prop.setCurrentMatrix('off')
|
||||
}
|
||||
})
|
||||
useEffect(() => {
|
||||
setTheme(theme)
|
||||
document.documentElement.dataset.theme = theme
|
||||
refreshServed()
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [theme])
|
||||
return (
|
||||
<footer>Served <span className="served">{served.served}</span> times - Last scan <span className="lastscan">{served.lastscan}</span> peers of <span
|
||||
className="server">{served.server}</span><br />
|
||||
Total scanned <span id="instances">{served.instances}</span> instances with <span className="peers">{served.peers}</span> peers - <span
|
||||
className="created">{served.created}</span> created - <span className="updated">{served.updated}</span> updated<br />
|
||||
matrix <a className="matrix" onClick={() => toggleMatrix()}>{matrix}</a> - download json <a className="download_index" href="/api/download_index"
|
||||
onClick={() => loading()} download="fediblock-index.json.gz" target="_blank">index</a> -
|
||||
by <a href="https://about.manalejandro.com" target="_blank">ale</a> <s>©</s>2025
|
||||
<a className="darklight" onClick={() => toggleTheme()}>{!theme || theme === 'dark' ? '☼' : '☽'}</a>
|
||||
</footer>
|
||||
)
|
||||
}
|
||||
|
||||
export default Footer;
|
||||
167
front/src/component/Form.js
Archivo normal
167
front/src/component/Form.js
Archivo normal
@@ -0,0 +1,167 @@
|
||||
import { useEffect, useState, useCallback, useRef } from 'react';
|
||||
import Modal from './Modal';
|
||||
import Messenger from '../random-text';
|
||||
|
||||
const Form = (prop) => {
|
||||
let csv
|
||||
const [searchTerm, setSearchTerm] = useState(''),
|
||||
[list, setList] = useState([]),
|
||||
[typeList, setTypeList] = useState('ranking'),
|
||||
[domain, setDomain] = useState({ domain: '' }),
|
||||
[reverse, setReverse] = useState(false),
|
||||
refList = useRef(null),
|
||||
refDownload = useRef(null),
|
||||
loading = () => {
|
||||
document.querySelector('.loader-content').style.display = 'initial'
|
||||
},
|
||||
download = () => {
|
||||
if (csv.split('\n').length > 2) {
|
||||
refDownload.current.href = window.URL.createObjectURL(new Blob([csv], { type: 'text/csv' }))
|
||||
refDownload.current.download = 'fediblock-top100.csv'
|
||||
}
|
||||
},
|
||||
listinstance = useCallback(async content => {
|
||||
loading()
|
||||
if (content && content.length > 0) {
|
||||
try {
|
||||
const result = await fetch('/api/list/' + content),
|
||||
res = await result.json()
|
||||
if (res && Array.isArray(res.instances) && Array.isArray(res.suggests)) {
|
||||
setTypeList('list')
|
||||
setList(res)
|
||||
document.querySelector('.loader-content').style.display = 'none'
|
||||
} else {
|
||||
document.querySelector('.loader-content').style.display = 'none'
|
||||
window.alert('Error: No response')
|
||||
}
|
||||
} catch (e) {
|
||||
document.querySelector('.loader-content').style.display = 'none'
|
||||
window.alert('Error: ' + e.message)
|
||||
}
|
||||
} else {
|
||||
document.querySelector('.loader-content').style.display = 'none'
|
||||
}
|
||||
}),
|
||||
ranking = useCallback(async () => {
|
||||
loading()
|
||||
try {
|
||||
const result = await fetch('/api/ranking'),
|
||||
res = await result.json()
|
||||
if (Array.isArray(res) && res.length > 0) {
|
||||
setTypeList('ranking')
|
||||
setList(res)
|
||||
document.querySelector('.loader-content').style.display = 'none'
|
||||
} else {
|
||||
document.querySelector('.loader-content').style.display = 'none'
|
||||
window.alert('Error: No response')
|
||||
}
|
||||
} catch (e) {
|
||||
document.querySelector('.loader-content').style.display = 'none'
|
||||
window.alert('Error: ' + e.message)
|
||||
}
|
||||
}),
|
||||
filterKeys = useCallback(event => {
|
||||
if (event.key && !event.ctrlKey && !event.altKey) {
|
||||
if ((event.key.length === 1 && /[a-z0-9.\-*:]/i.test(event.key)) || (event.key === 'Backspace' && event.target.value !== '')) {
|
||||
if (event.key === '.' && event.target.value === '') {
|
||||
return
|
||||
} else if (event.key === 'Backspace' && event.target.value !== '') {
|
||||
setSearchTerm(searchTerm.substring(0, searchTerm.length - 1))
|
||||
} else {
|
||||
setSearchTerm(searchTerm + event.key)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
useEffect(() => {
|
||||
if (searchTerm && searchTerm.length > 0) {
|
||||
listinstance(searchTerm)
|
||||
window.location.hash = searchTerm
|
||||
} else {
|
||||
(async () => {
|
||||
await ranking()
|
||||
})()
|
||||
}
|
||||
if (prop.matrix === 'on') {
|
||||
var walker = document.createTreeWalker(refList.current, NodeFilter.SHOW_TEXT)
|
||||
while (walker.nextNode()) {
|
||||
if (walker.currentNode.textContent.length > 1) {
|
||||
new Messenger(walker.currentNode)
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [searchTerm, prop.matrix])
|
||||
useEffect(() => {
|
||||
if (prop.searchTerm && prop.searchTerm.length > 0) {
|
||||
setSearchTerm(prop.searchTerm)
|
||||
}
|
||||
}, [prop.searchTerm])
|
||||
useEffect(() => {
|
||||
if (window.location.hash && window.location.hash !== '#') {
|
||||
setSearchTerm(window.location.hash.substring(1))
|
||||
}
|
||||
}, [])
|
||||
return (
|
||||
<>
|
||||
<section>
|
||||
<input
|
||||
autoFocus
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
className="instance"
|
||||
placeholder="Type the name of the instance"
|
||||
onKeyUp={(e) => filterKeys(e)}
|
||||
value={searchTerm} />
|
||||
<button className="reverse" title="Reverse search..." onClick={() => {
|
||||
if (searchTerm && searchTerm.length > 0) {
|
||||
setReverse(true)
|
||||
setDomain({ domain: searchTerm })
|
||||
loading()
|
||||
}
|
||||
}}>Reverse</button>
|
||||
<div className="placeholder">{typeList === 'list' ? list.suggests && list.suggests.length > 0 ? list.suggests.map(suggest => (<a onClick={() => setSearchTerm(suggest)}>{suggest}</a>)).reduce((prev, curr) => [prev, ', ', curr]) : [] : []}</div>
|
||||
<ul className="instancelist" ref={refList}>{typeList === 'list' ? list.instances.map(r =>
|
||||
(<li><a onClick={() => {
|
||||
if (r.blocks) {
|
||||
setReverse(false)
|
||||
setDomain({ domain: r.domain })
|
||||
loading()
|
||||
} else {
|
||||
var a = document.createElement('a')
|
||||
a.href = '/api/detail_api/' + r.domain
|
||||
a.title = 'API info for ' + r.domain
|
||||
a.target = '_blank'
|
||||
a.dispatchEvent(new MouseEvent('click'))
|
||||
}
|
||||
}}>{r.domain}</a>
|
||||
<span dangerouslySetInnerHTML={{ __html: r.blocks ? ` - ` + r.blocks + ` blocks ` : ` ` }}></span>
|
||||
<span dangerouslySetInnerHTML={{
|
||||
__html: r.nodeinfo ? `<a href="/api/detail_nodeinfo/${r.domain}" title="Nodeinfo for ${r.domain}" target="_blank">ⓘ</a>` + `<br />` : `<br />`
|
||||
}}></span>
|
||||
<span dangerouslySetInnerHTML={{
|
||||
__html: r.api?.title ? `<br /><br />` + r.api.title + ` - ` + r.api.uri + `<br />` : `<br /><br />`
|
||||
}}></span>
|
||||
<span dangerouslySetInnerHTML={{ __html: r.last ? `Last update: ` + (new Date(r.last)).toLocaleString() + `<br />` : `` }}></span>
|
||||
<span dangerouslySetInnerHTML={{ __html: r.api?.email ? `Email: ` + r.api.email + `<br />` : `` }}></span>
|
||||
<span dangerouslySetInnerHTML={{ __html: `Registration: ` + (r.api?.registrations ? `open` : `closed`) + ` - Version: ` + r.api?.version + `<br />` }}></span>
|
||||
<span dangerouslySetInnerHTML={{ __html: r.api?.stats ? `Users: ` + r.api.stats.user_count + ` - Statuses: ` + r.api.stats.status_count + ` - Domains: ` + r.api.stats.domain_count + `<br />` : `` }}></span>
|
||||
<span dangerouslySetInnerHTML={{ __html: r.api?.description ? `Description: ` + r.api.description + `<br />` : `` }}></span>
|
||||
<span dangerouslySetInnerHTML={{ __html: r.api?.thumbnail ? `<img domain="${r.domain}" loading="lazy" src="${r.api.thumbnail}" />` + `<br />` : `` }}></span>
|
||||
</li>
|
||||
)) : typeList === 'ranking' ? list.map((r, i) => {
|
||||
if (i === 0) {
|
||||
csv = '#domain,#severity,#reject_media,#reject_reports,#public_comment,#obfuscate\n'
|
||||
return (<><li><strong>Top 100</strong><br /><small className="download-top"><a ref={refDownload} onClick={() => download()}>(Import CSV)</a></small></li><li>{i + 1} - {r.domain} - {r.count} blocks</li></>)
|
||||
} else {
|
||||
csv += !r.domain.match(/\*/) ? r.domain + ',suspend,False,False,"suspended by top 100 of ' + new URL(window.location.href).host + '",False\n' : ''
|
||||
return (<li>{i + 1} - {r.domain} - {r.count} blocks</li>)
|
||||
}
|
||||
}) : ''}
|
||||
</ul>
|
||||
</section >
|
||||
<Modal domain={domain} reverse={reverse} setSearch={d => setSearchTerm(d)} matrix={prop.matrix} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Form;
|
||||
19
front/src/component/Loader.js
Archivo normal
19
front/src/component/Loader.js
Archivo normal
@@ -0,0 +1,19 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import '../loaders.css';
|
||||
|
||||
const Loader = () => {
|
||||
const loaders = ['loader-pong', 'loader-pacman', 'loader-abyss', 'loader-jump', 'loader-loading', 'loader-avenger', 'loader-mario'],
|
||||
[load, setLoad] = useState('load ' + loaders[Math.floor(Math.random() * loaders.length)])
|
||||
useEffect(() => {
|
||||
setLoad('load ' + loaders[Math.floor(Math.random() * loaders.length)])
|
||||
}, [])
|
||||
return (
|
||||
<div className="loader-content">
|
||||
<div className="loader">
|
||||
<div className={load}></div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Loader;
|
||||
142
front/src/component/Modal.js
Archivo normal
142
front/src/component/Modal.js
Archivo normal
@@ -0,0 +1,142 @@
|
||||
import { useEffect, useCallback, useState, useRef } from 'react';
|
||||
import html2canvas from '../../node_modules/html2canvas/dist/html2canvas.js';
|
||||
import Messenger from '../random-text.js';
|
||||
|
||||
const Modal = (prop) => {
|
||||
let csv
|
||||
const [blocktitle, setBlocktitle] = useState(''),
|
||||
[blockcount, setBlockcount] = useState(''),
|
||||
[blockinstance, setBlockinstance] = useState(''),
|
||||
[blocktook, setBlocktook] = useState(''),
|
||||
[blocklist, setBlocklist] = useState([]),
|
||||
hrefCanvas = useRef(null),
|
||||
refList = useRef(null),
|
||||
loading = () => {
|
||||
document.querySelector('.loader-content').style.display = 'initial'
|
||||
},
|
||||
closeModal = event => {
|
||||
if (event.target.classList.contains('modal') || event.target.classList.contains('closemodal')) {
|
||||
document.querySelector('.modal-content').style.animationName = 'animatebottom'
|
||||
}
|
||||
},
|
||||
animationEnd = event => {
|
||||
if (event.animationName === 'animatebottom') {
|
||||
document.querySelector('.modal').style.display = 'none'
|
||||
}
|
||||
},
|
||||
capture = useCallback(async () => {
|
||||
const canvas = await html2canvas(hrefCanvas.current, { useCORS: true }),
|
||||
capture = document.querySelector('.capture')
|
||||
capture.download = 'fediblock-' + Date.now() + '.png'
|
||||
capture.href = canvas.toDataURL('image/png', 1.0)
|
||||
capture.dispatchEvent(new MouseEvent('click'))
|
||||
}),
|
||||
download = () => {
|
||||
const download = document.querySelector('.download')
|
||||
if (csv.split('\n').length > 2) {
|
||||
download.href = window.URL.createObjectURL(new Blob([csv], { type: 'text/csv' }))
|
||||
download.download = 'fediblock-' + prop.domain.domain + '.csv'
|
||||
}
|
||||
},
|
||||
reverse = useCallback(async content => {
|
||||
if (content && content.length > 0) {
|
||||
loading()
|
||||
const result = await fetch('/api/block_count/' + content),
|
||||
res = await result.json()
|
||||
if (res && Array.isArray(res.instances)) {
|
||||
setBlocktitle('Reverse List')
|
||||
setBlockcount(res.block_count)
|
||||
setBlockinstance('ing ' + content)
|
||||
setBlocktook('took ' + res.took + 'ms')
|
||||
setBlocklist(res.instances)
|
||||
document.querySelector('.loader-content').style.display = 'none'
|
||||
document.querySelector('.modal-content').style.animationName = 'animatetop'
|
||||
document.querySelector('.modal').style.display = 'block'
|
||||
if (prop.matrix === 'on') {
|
||||
var walker = document.createTreeWalker(refList.current, NodeFilter.SHOW_TEXT)
|
||||
while (walker.nextNode()) {
|
||||
if (walker.currentNode.textContent.length > 1) {
|
||||
new Messenger(walker.currentNode)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
listblock = useCallback(async (domain) => {
|
||||
const result = await fetch('/api/detail/' + domain),
|
||||
res = await result.json()
|
||||
if (res.blocks && Array.isArray(res.blocks)) {
|
||||
csv = '#domain,#severity,#reject_media,#reject_reports,#public_comment,#obfuscate\n'
|
||||
setBlocktitle('Blocked List')
|
||||
setBlockcount(res.blocks.length)
|
||||
setBlocktook('took ' + res.took + 'ms')
|
||||
setBlockinstance(`ed by ` + (res.api ? `<a href="/api/detail_api/${res.instance}" title="API info for ${res.instance}" target="_blank">${res.instance}</a>`
|
||||
: res.instance) + (res.nodeinfo ? ` <a href="/api/detail_nodeinfo/${res.instance}" title="Nodeinfo for ${res.instance}" target="_blank">ⓘ</a><br />` : `<br />`)
|
||||
+ `Last update: ` + (new Date(res.last)).toLocaleString())
|
||||
setBlocklist(res.blocks)
|
||||
if (prop.matrix === 'on') {
|
||||
var walker = document.createTreeWalker(refList.current, NodeFilter.SHOW_TEXT)
|
||||
while (walker.nextNode()) {
|
||||
if (walker.currentNode.textContent.length > 1) {
|
||||
new Messenger(walker.currentNode)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var a = document.createElement('a')
|
||||
a.href = '/api/detail_api/' + domain
|
||||
a.title = 'API info for ' + domain
|
||||
a.target = '_blank'
|
||||
a.dispatchEvent(new MouseEvent('click'))
|
||||
}
|
||||
document.querySelector('.loader-content').style.display = 'none'
|
||||
document.querySelector('.modal-content').style.animationName = 'animatetop'
|
||||
document.querySelector('.modal').style.display = 'block'
|
||||
})
|
||||
useEffect(() => {
|
||||
if (prop.domain && prop.domain.domain.length > 0) {
|
||||
if (prop.reverse) {
|
||||
reverse(prop.domain.domain)
|
||||
} else {
|
||||
listblock(prop.domain.domain)
|
||||
}
|
||||
}
|
||||
}, [prop.domain, prop.reverse])
|
||||
return (
|
||||
<div className="modal" onClick={(e) => closeModal(e)}>
|
||||
<div className="modal-content" onAnimationEnd={(e) => animationEnd(e)} ref={refList}>
|
||||
<div className="modal-header">
|
||||
<span className="closemodal" title="Close" onClick={(e) => closeModal(e)}>×</span>
|
||||
<a className="capture" title="Take snapshot" onClick={capture} type="image/png" target="_blank">📷</a>
|
||||
{!prop.reverse ? <a className="download" title="Download fediblock CSV mastodon file" onClick={download} type="text/csv" target="_blank">⇩</a> : ''}
|
||||
<h2>{blocktitle}</h2>
|
||||
</div>
|
||||
<div className="modal-body" ref={hrefCanvas}>
|
||||
<p>
|
||||
<span className="blockcount">{blockcount}</span> public instances are block<span className="blockinstance" dangerouslySetInnerHTML={{ __html: blockinstance }}></span>
|
||||
<br /><small className="blocktook">{blocktook}</small>
|
||||
</p>
|
||||
<ul className="blocklist">{prop.reverse ? blocklist.map((instance, index) => {
|
||||
return (<li>{index + 1}. <a onClick={() => {
|
||||
prop.setSearch(instance.instance)
|
||||
document.querySelector('.modal-content').style.animationName = 'animatebottom'
|
||||
}}>{instance.instance}</a>{instance.comment ? ' - ' + instance.comment : ''}</li>)
|
||||
}) : blocklist.map((r, i) => {
|
||||
if (r?.domain) {
|
||||
csv += !r.domain.match(/\*/) ? r.domain + ',' + (r.severity ? r.severity : '') + ',False,False,' + (r.comment ? '"' + r.comment + '"' : '') + ',False\n' : ''
|
||||
return (<li>{i + 1}. <a onClick={() => {
|
||||
prop.setSearch(r.domain)
|
||||
document.querySelector('.modal-content').style.animationName = 'animatebottom'
|
||||
}}>{r.domain}</a>{r.severity ? ' - ' + r.severity : ''}{r.comment ? ' - ' + r.comment : ''}</li>)
|
||||
}
|
||||
})}</ul>
|
||||
</div>
|
||||
<div className="modal-footer">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Modal;
|
||||
31
front/src/component/Scan.js
Archivo normal
31
front/src/component/Scan.js
Archivo normal
@@ -0,0 +1,31 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
|
||||
const Scan = () => {
|
||||
const [scan, setScan] = useState('Scanning...'),
|
||||
load = useRef(false)
|
||||
useEffect(() => {
|
||||
if (!load.current) {
|
||||
const source = new window.EventSource('/api/scan')
|
||||
source.onmessage = event => {
|
||||
if (event.data) {
|
||||
if (event.data.length > 0) {
|
||||
setScan('Async Scanning' + event.data)
|
||||
} else {
|
||||
setScan('Async Scanning...')
|
||||
}
|
||||
}
|
||||
}
|
||||
source.onerror = error => {
|
||||
console.error(error)
|
||||
}
|
||||
load.current = true
|
||||
}
|
||||
})
|
||||
return (
|
||||
<div>
|
||||
<span className="scan">{scan}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Scan;
|
||||
13
front/src/component/Title.js
Archivo normal
13
front/src/component/Title.js
Archivo normal
@@ -0,0 +1,13 @@
|
||||
import { useCallback } from 'react';
|
||||
|
||||
const Title = () => {
|
||||
const click = useCallback(() => {
|
||||
window.location.hash = ''
|
||||
window.location.reload(false)
|
||||
})
|
||||
return (
|
||||
<h1><a onClick={() => click()} className="title">Fediblock Instance Φ</a></h1>
|
||||
)
|
||||
}
|
||||
|
||||
export default Title;
|
||||
12
front/src/index.js
Archivo normal
12
front/src/index.js
Archivo normal
@@ -0,0 +1,12 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import App from './App';
|
||||
import reportWebVitals from './reportWebVitals';
|
||||
|
||||
const root = ReactDOM.createRoot(document.getElementById('root'));
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>
|
||||
);
|
||||
reportWebVitals();
|
||||
411
front/src/loaders.css
Archivo normal
411
front/src/loaders.css
Archivo normal
@@ -0,0 +1,411 @@
|
||||
/* HTML: <div class="loader"></div> */
|
||||
.loader-pong {
|
||||
width: 80px;
|
||||
height: 70px;
|
||||
border: 5px solid #000;
|
||||
padding: 0 8px;
|
||||
box-sizing: border-box;
|
||||
background:
|
||||
linear-gradient(#fff 0 0) 0 0/8px 20px,
|
||||
linear-gradient(#fff 0 0) 100% 0/8px 20px,
|
||||
radial-gradient(farthest-side, #fff 90%, #0000) 0 5px/8px 8px content-box,
|
||||
#000;
|
||||
background-repeat: no-repeat;
|
||||
animation: l3 2s infinite linear;
|
||||
}
|
||||
|
||||
@keyframes l3 {
|
||||
25% {
|
||||
background-position: 0 0, 100% 100%, 100% calc(100% - 5px)
|
||||
}
|
||||
|
||||
50% {
|
||||
background-position: 0 100%, 100% 100%, 0 calc(100% - 5px)
|
||||
}
|
||||
|
||||
75% {
|
||||
background-position: 0 100%, 100% 0, 100% 5px
|
||||
}
|
||||
}
|
||||
|
||||
/* HTML: <div class="loader"></div> */
|
||||
.loader-pacman {
|
||||
width: 90px;
|
||||
height: 24px;
|
||||
padding: 2px 0;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
animation: l5-0 3s infinite steps(6);
|
||||
background:
|
||||
linear-gradient(#000 0 0) 0 0/0% 100% no-repeat,
|
||||
radial-gradient(circle 3px, #eeee89 90%, #0000) 0 0/20% 100% #000;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.loader-pacman::before {
|
||||
content: "";
|
||||
width: 20px;
|
||||
transform: translate(-100%);
|
||||
border-radius: 50%;
|
||||
background: #ffff2d;
|
||||
animation:
|
||||
l5-1 .25s .153s infinite steps(5) alternate,
|
||||
l5-2 3s infinite linear;
|
||||
}
|
||||
|
||||
@keyframes l5-1 {
|
||||
0% {
|
||||
clip-path: polygon(50% 50%, 100% 0, 100% 0, 0 0, 0 100%, 100% 100%, 100% 100%)
|
||||
}
|
||||
|
||||
100% {
|
||||
clip-path: polygon(50% 50%, 100% 65%, 100% 0, 0 0, 0 100%, 100% 100%, 100% 35%)
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes l5-2 {
|
||||
100% {
|
||||
transform: translate(90px)
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes l5-0 {
|
||||
100% {
|
||||
background-size: 120% 100%, 20% 100%
|
||||
}
|
||||
}
|
||||
|
||||
/* HTML: <div class="loader"></div> */
|
||||
.loader-abyss {
|
||||
width: 80px;
|
||||
height: 60px;
|
||||
box-sizing: border-box;
|
||||
background:
|
||||
linear-gradient(#fff 0 0) left /calc(50% - 15px) 8px no-repeat,
|
||||
linear-gradient(#fff 0 0) right/calc(50% - 15px) 8px no-repeat,
|
||||
conic-gradient(from 135deg at top, #0000, red 1deg 90deg, #0000 91deg) bottom/14px 8px repeat-x,
|
||||
#000;
|
||||
border-bottom: 2px solid red;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
animation: l6-0 1s infinite linear;
|
||||
}
|
||||
|
||||
.loader-abyss::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 10px;
|
||||
height: 14px;
|
||||
background: lightblue;
|
||||
left: -5px;
|
||||
animation:
|
||||
l6-1 2s infinite cubic-bezier(0, 100, 1, 100),
|
||||
l6-2 2s infinite linear;
|
||||
}
|
||||
|
||||
@keyframes l6-0 {
|
||||
50% {
|
||||
background-position: left, right, bottom -2px left -4px
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes l6-1 {
|
||||
|
||||
0%,
|
||||
27% {
|
||||
bottom: calc(50% + 4px)
|
||||
}
|
||||
|
||||
65%,
|
||||
100% {
|
||||
bottom: calc(50% + 4.1px)
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes l6-2 {
|
||||
100% {
|
||||
left: 100%
|
||||
}
|
||||
}
|
||||
|
||||
/* HTML: <div class="loader"></div> */
|
||||
.loader-jump {
|
||||
width: 70px;
|
||||
height: 50px;
|
||||
box-sizing: border-box;
|
||||
background:
|
||||
conic-gradient(from 135deg at top, #0000, #fff 1deg 90deg, #0000 91deg) right -20px bottom 8px/18px 9px,
|
||||
linear-gradient(#fff 0 0) bottom/100% 8px,
|
||||
#000;
|
||||
background-repeat: no-repeat;
|
||||
border-bottom: 8px solid #000;
|
||||
position: relative;
|
||||
animation: l7-0 2s infinite linear;
|
||||
}
|
||||
|
||||
.loader-jump::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 10px;
|
||||
height: 14px;
|
||||
background: lightblue;
|
||||
left: 10px;
|
||||
animation: l7-1 2s infinite cubic-bezier(0, 200, 1, 200);
|
||||
}
|
||||
|
||||
@keyframes l7-0 {
|
||||
100% {
|
||||
background-position: left -20px bottom 8px, bottom
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes l7-1 {
|
||||
|
||||
0%,
|
||||
50% {
|
||||
bottom: 8px
|
||||
}
|
||||
|
||||
90%,
|
||||
100% {
|
||||
bottom: 8.1px
|
||||
}
|
||||
}
|
||||
|
||||
/* HTML: <div class="loader"></div> */
|
||||
.loader-loading {
|
||||
width: fit-content;
|
||||
font-size: 17px;
|
||||
font-family: monospace;
|
||||
line-height: 1.4;
|
||||
font-weight: bold;
|
||||
--c: no-repeat linear-gradient(#000 0 0);
|
||||
background: var(--c), var(--c), var(--c), var(--c), var(--c), var(--c), var(--c);
|
||||
background-size: calc(1ch + 1px) 100%;
|
||||
border-bottom: 10px solid #0000;
|
||||
position: relative;
|
||||
animation: l8-0 3s infinite linear;
|
||||
clip-path: inset(-20px 0);
|
||||
}
|
||||
|
||||
.loader-loading::before {
|
||||
content: "Loading";
|
||||
}
|
||||
|
||||
.loader-loading::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 10px;
|
||||
height: 14px;
|
||||
background: #25adda;
|
||||
left: -10px;
|
||||
bottom: 100%;
|
||||
animation: l8-1 3s infinite linear;
|
||||
}
|
||||
|
||||
@keyframes l8-0 {
|
||||
|
||||
0%,
|
||||
12.5% {
|
||||
background-position: calc(0*100%/6) 0, calc(1*100%/6) 0, calc(2*100%/6) 0, calc(3*100%/6) 0, calc(4*100%/6) 0, calc(5*100%/6) 0, calc(6*100%/6) 0
|
||||
}
|
||||
|
||||
25% {
|
||||
background-position: calc(0*100%/6) 40px, calc(1*100%/6) 0, calc(2*100%/6) 0, calc(3*100%/6) 0, calc(4*100%/6) 0, calc(5*100%/6) 0, calc(6*100%/6) 0
|
||||
}
|
||||
|
||||
37.5% {
|
||||
background-position: calc(0*100%/6) 40px, calc(1*100%/6) 40px, calc(2*100%/6) 0, calc(3*100%/6) 0, calc(4*100%/6) 0, calc(5*100%/6) 0, calc(6*100%/6) 0
|
||||
}
|
||||
|
||||
50% {
|
||||
background-position: calc(0*100%/6) 40px, calc(1*100%/6) 40px, calc(2*100%/6) 40px, calc(3*100%/6) 0, calc(4*100%/6) 0, calc(5*100%/6) 0, calc(6*100%/6) 0
|
||||
}
|
||||
|
||||
62.5% {
|
||||
background-position: calc(0*100%/6) 40px, calc(1*100%/6) 40px, calc(2*100%/6) 40px, calc(3*100%/6) 40px, calc(4*100%/6) 0, calc(5*100%/6) 0, calc(6*100%/6) 0
|
||||
}
|
||||
|
||||
75% {
|
||||
background-position: calc(0*100%/6) 40px, calc(1*100%/6) 40px, calc(2*100%/6) 40px, calc(3*100%/6) 40px, calc(4*100%/6) 40px, calc(5*100%/6) 0, calc(6*100%/6) 0
|
||||
}
|
||||
|
||||
87.4% {
|
||||
background-position: calc(0*100%/6) 40px, calc(1*100%/6) 40px, calc(2*100%/6) 40px, calc(3*100%/6) 40px, calc(4*100%/6) 40px, calc(5*100%/6) 40px, calc(6*100%/6) 0
|
||||
}
|
||||
|
||||
100% {
|
||||
background-position: calc(0*100%/6) 40px, calc(1*100%/6) 40px, calc(2*100%/6) 40px, calc(3*100%/6) 40px, calc(4*100%/6) 40px, calc(5*100%/6) 40px, calc(6*100%/6) 40px
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes l8-1 {
|
||||
100% {
|
||||
left: 115%
|
||||
}
|
||||
}
|
||||
|
||||
/* HTML: <div class="loader"></div> */
|
||||
.loader-avenger {
|
||||
width: fit-content;
|
||||
font-size: 17px;
|
||||
font-family: monospace;
|
||||
line-height: 1.4;
|
||||
font-weight: bold;
|
||||
background:
|
||||
linear-gradient(#000 0 0) left,
|
||||
linear-gradient(#000 0 0) right;
|
||||
background-repeat: no-repeat;
|
||||
border-right: 5px solid #0000;
|
||||
border-left: 5px solid #0000;
|
||||
background-origin: border-box;
|
||||
position: relative;
|
||||
animation: l9-0 2s infinite;
|
||||
}
|
||||
|
||||
.loader-avenger::before {
|
||||
content: "Loading";
|
||||
}
|
||||
|
||||
.loader-avenger::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
width: 22px;
|
||||
height: 60px;
|
||||
background:
|
||||
linear-gradient(90deg, #000 4px, #0000 0 calc(100% - 4px), #000 0) bottom /22px 20px,
|
||||
linear-gradient(90deg, red 4px, #0000 0 calc(100% - 4px), red 0) bottom 10px left 0/22px 6px,
|
||||
linear-gradient(#000 0 0) bottom 3px left 0 /22px 8px,
|
||||
linear-gradient(#000 0 0) bottom 0 left 50%/8px 16px;
|
||||
background-repeat: no-repeat;
|
||||
animation: l9-1 2s infinite;
|
||||
}
|
||||
|
||||
@keyframes l9-0 {
|
||||
|
||||
0%,
|
||||
25% {
|
||||
background-size: 50% 100%
|
||||
}
|
||||
|
||||
25.1%,
|
||||
75% {
|
||||
background-size: 0 0, 50% 100%
|
||||
}
|
||||
|
||||
75.1%,
|
||||
100% {
|
||||
background-size: 0 0, 0 0
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes l9-1 {
|
||||
25% {
|
||||
background-position: bottom, bottom 54px left 0, bottom 3px left 0, bottom 0 left 50%;
|
||||
left: 0
|
||||
}
|
||||
|
||||
25.1% {
|
||||
background-position: bottom, bottom 10px left 0, bottom 3px left 0, bottom 0 left 50%;
|
||||
left: 0
|
||||
}
|
||||
|
||||
50% {
|
||||
background-position: bottom, bottom 10px left 0, bottom 3px left 0, bottom 0 left 50%;
|
||||
left: calc(100% - 22px)
|
||||
}
|
||||
|
||||
75% {
|
||||
background-position: bottom, bottom 54px left 0, bottom 3px left 0, bottom 0 left 50%;
|
||||
left: calc(100% - 22px)
|
||||
}
|
||||
|
||||
75.1% {
|
||||
background-position: bottom, bottom 10px left 0, bottom 3px left 0, bottom 0 left 50%;
|
||||
left: calc(100% - 22px)
|
||||
}
|
||||
}
|
||||
|
||||
/* HTML: <div class="loader"></div> */
|
||||
.loader-mario {
|
||||
width: fit-content;
|
||||
font-size: 17px;
|
||||
font-family: monospace;
|
||||
line-height: 1.4;
|
||||
font-weight: bold;
|
||||
padding: 30px 2px 50px;
|
||||
background: linear-gradient(#000 0 0) 0 0/100% 100% content-box padding-box no-repeat;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
animation: l10-0 2s infinite cubic-bezier(1, 175, .5, 175);
|
||||
}
|
||||
|
||||
.loader-mario::before {
|
||||
content: "Loading";
|
||||
display: inline-block;
|
||||
animation: l10-2 2s infinite;
|
||||
}
|
||||
|
||||
.loader-mario::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 34px;
|
||||
height: 28px;
|
||||
top: 110%;
|
||||
left: calc(50% - 16px);
|
||||
background:
|
||||
linear-gradient(90deg, #0000 12px, #f92033 0 22px, #0000 0 26px, #fdc98d 0 32px, #0000) bottom 26px left 50%,
|
||||
linear-gradient(90deg, #0000 10px, #f92033 0 28px, #fdc98d 0 32px, #0000 0) bottom 24px left 50%,
|
||||
linear-gradient(90deg, #0000 10px, #643700 0 16px, #fdc98d 0 20px, #000 0 22px, #fdc98d 0 24px, #000 0 26px, #f92033 0 32px, #0000 0) bottom 22px left 50%,
|
||||
linear-gradient(90deg, #0000 8px, #643700 0 10px, #fdc98d 0 12px, #643700 0 14px, #fdc98d 0 20px, #000 0 22px, #fdc98d 0 28px, #f92033 0 32px, #0000 0) bottom 20px left 50%,
|
||||
linear-gradient(90deg, #0000 8px, #643700 0 10px, #fdc98d 0 12px, #643700 0 16px, #fdc98d 0 22px, #000 0 24px, #fdc98d 0 30px, #f92033 0 32px, #0000 0) bottom 18px left 50%,
|
||||
linear-gradient(90deg, #0000 8px, #643700 0 12px, #fdc98d 0 20px, #000 0 28px, #f92033 0 30px, #0000 0) bottom 16px left 50%,
|
||||
linear-gradient(90deg, #0000 12px, #fdc98d 0 26px, #f92033 0 30px, #0000 0) bottom 14px left 50%,
|
||||
linear-gradient(90deg, #fdc98d 6px, #f92033 0 14px, #222a87 0 16px, #f92033 0 22px, #222a87 0 24px, #f92033 0 28px, #0000 0 32px, #643700 0) bottom 12px left 50%,
|
||||
linear-gradient(90deg, #fdc98d 6px, #f92033 0 16px, #222a87 0 18px, #f92033 0 24px, #f92033 0 26px, #0000 0 30px, #643700 0) bottom 10px left 50%,
|
||||
linear-gradient(90deg, #0000 10px, #f92033 0 16px, #222a87 0 24px, #feee49 0 26px, #222a87 0 30px, #643700 0) bottom 8px left 50%,
|
||||
linear-gradient(90deg, #0000 12px, #222a87 0 18px, #feee49 0 20px, #222a87 0 30px, #643700 0) bottom 6px left 50%,
|
||||
linear-gradient(90deg, #0000 8px, #643700 0 12px, #222a87 0 30px, #643700 0) bottom 4px left 50%,
|
||||
linear-gradient(90deg, #0000 6px, #643700 0 14px, #222a87 0 26px, #0000 0) bottom 2px left 50%,
|
||||
linear-gradient(90deg, #0000 6px, #643700 0 10px, #0000 0) bottom 0px left 50%;
|
||||
background-size: 34px 2px;
|
||||
background-repeat: no-repeat;
|
||||
animation: inherit;
|
||||
animation-name: l10-1;
|
||||
}
|
||||
|
||||
@keyframes l10-0 {
|
||||
|
||||
0%,
|
||||
30% {
|
||||
background-position: 0 0px
|
||||
}
|
||||
|
||||
50%,
|
||||
100% {
|
||||
background-position: 0 -0.1px
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes l10-1 {
|
||||
|
||||
50%,
|
||||
100% {
|
||||
top: 109.5%
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes l10-2 {
|
||||
|
||||
0%,
|
||||
30% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
80%,
|
||||
100% {
|
||||
transform: translateY(-260%);
|
||||
}
|
||||
}
|
||||
1
front/src/logo.svg
Archivo normal
1
front/src/logo.svg
Archivo normal
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>
|
||||
|
Después Anchura: | Altura: | Tamaño: 2.6 KiB |
69
front/src/random-text.js
Archivo normal
69
front/src/random-text.js
Archivo normal
@@ -0,0 +1,69 @@
|
||||
var Messenger = function (el) {
|
||||
'use strict';
|
||||
var m = this;
|
||||
|
||||
m.init = function () {
|
||||
m.codeletters = "&#*+%?£@§$";
|
||||
m.current_length = 0;
|
||||
m.fadeBuffer = false;
|
||||
m.message = el.textContent.length > 0 ? el.textContent : ''
|
||||
|
||||
setTimeout(m.animateIn, 300);
|
||||
};
|
||||
|
||||
m.generateRandomString = function (length) {
|
||||
var random_text = '';
|
||||
while (random_text.length < length) {
|
||||
random_text += m.codeletters.charAt(Math.floor(Math.random() * m.codeletters.length));
|
||||
}
|
||||
|
||||
return random_text;
|
||||
};
|
||||
|
||||
m.animateIn = function () {
|
||||
if (m.current_length < m.message.length) {
|
||||
m.current_length = m.current_length + 2;
|
||||
if (m.current_length > m.message.length) {
|
||||
m.current_length = m.message.length;
|
||||
}
|
||||
|
||||
var message = m.generateRandomString(m.current_length);
|
||||
el.textContent = message;
|
||||
|
||||
setTimeout(m.animateIn, 60);
|
||||
} else {
|
||||
setTimeout(m.animateFadeBuffer, 60);
|
||||
}
|
||||
};
|
||||
|
||||
m.animateFadeBuffer = function () {
|
||||
if (m.fadeBuffer === false) {
|
||||
m.fadeBuffer = [];
|
||||
for (var i = 0; i < m.message.length; i++) {
|
||||
m.fadeBuffer.push({ c: (Math.floor(Math.random() * 12)) + 1, l: m.message.charAt(i) });
|
||||
}
|
||||
}
|
||||
|
||||
var do_cycles = false;
|
||||
var message = '';
|
||||
|
||||
for (var i = 0; i < m.fadeBuffer.length; i++) {
|
||||
var fader = m.fadeBuffer[i];
|
||||
if (fader.c > 0) {
|
||||
do_cycles = true;
|
||||
fader.c--;
|
||||
message += m.codeletters.charAt(Math.floor(Math.random() * m.codeletters.length));
|
||||
} else {
|
||||
message += fader.l;
|
||||
}
|
||||
}
|
||||
|
||||
el.textContent = message;
|
||||
|
||||
setTimeout(m.animateFadeBuffer, 150);
|
||||
};
|
||||
|
||||
m.init();
|
||||
}
|
||||
|
||||
module.exports = Messenger;
|
||||
13
front/src/reportWebVitals.js
Archivo normal
13
front/src/reportWebVitals.js
Archivo normal
@@ -0,0 +1,13 @@
|
||||
const reportWebVitals = onPerfEntry => {
|
||||
if (onPerfEntry && onPerfEntry instanceof Function) {
|
||||
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
|
||||
getCLS(onPerfEntry);
|
||||
getFID(onPerfEntry);
|
||||
getFCP(onPerfEntry);
|
||||
getLCP(onPerfEntry);
|
||||
getTTFB(onPerfEntry);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export default reportWebVitals;
|
||||
5
front/src/setupTests.js
Archivo normal
5
front/src/setupTests.js
Archivo normal
@@ -0,0 +1,5 @@
|
||||
// jest-dom adds custom jest matchers for asserting on DOM nodes.
|
||||
// allows you to do things like:
|
||||
// expect(element).toHaveTextContent(/react/i)
|
||||
// learn more: https://github.com/testing-library/jest-dom
|
||||
import '@testing-library/jest-dom';
|
||||
Referencia en una nueva incidencia
Block a user