import Head from 'next/head' import React, { useEffect, useState } from 'react' import axios from 'axios' import Loader from '../components/Loader' import Layout, { siteTitle } from '../components/Layout' import { matomoConfig } from '../lib/matomoConfig' import getMatomo from '../lib/getMatomo' import { GetServerSideProps, InferGetServerSidePropsType } from 'next' import { nodeResponseSchema } from '../types/NodeResponse' import SoftwareBadge from '../components/badges/SoftwareBadge' import SortToggle from '../components/SortToggle' import { StatsRequestSortBy } from '../types/StatsRequest' import { Sort } from '../types/Sort' import { faSearch, faAngleDoubleDown } from '@fortawesome/free-solid-svg-icons' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' let source = axios.CancelToken.source() const Nodes: React.FC> = ({ matomoConfig }) => { const [query, setQuery] = useState('') const [submitted, setSubmitted] = useState(null) const [loading, setLoading] = useState(false) const [results, setResults] = useState([]) const [page, setPage] = useState(0) const [hasMore, setHasMore] = useState(false) const [loaded, setLoaded] = useState(false) const [sort, setSort] = useState({ sortBy: 'refreshedAt', sortWay: 'desc' }) const search = async () => { setLoading(true) try { console.info('Retrieving results', { query, page }) source = axios.CancelToken.source() const response = await axios.get('/api/node', { params: { search: query, page, sortBy: sort.sortBy, sortWay: sort.sortWay }, cancelToken: source.token }) const responseData = await nodeResponseSchema.parseAsync(response.data) setHasMore(responseData.hasMore) setResults([ ...(page > 0 ? results : []), ...responseData.nodes ]) setLoaded(true) } catch (e) { console.warn('Search failed', e) setLoaded(true) } setLoading(false) } const loadNewQueryResults = () => { console.info('Cancelling searches') source.cancel('New query on the way') setResults([]) setHasMore(false) setLoaded(false) console.info('Loading new query search', { query, page }) setLoading(true) setTimeout(search) getMatomo(matomoConfig).trackEvent({ category: 'nodes', action: 'new-search' }) } const loadNextPageResults = () => { setHasMore(false) if (page === 0) { return } console.info('Loading next page', { query, page }) setTimeout(search) getMatomo(matomoConfig).trackEvent({ category: 'nodes', action: 'next-page', customDimensions: [ { value: page.toString(), id: 1 } ] }) } const handleQueryChange = (event) => { const value = event.target.value console.info('Query changed', { query: value }) setQuery(value) setPage(0) } const handleSearchSubmit = event => { event.preventDefault() setQuery(query) setSubmitted(new Date()) setPage(0) } const handleLoadMore = event => { event.preventDefault() setPage(page + 1) } const toggleSort = (sortBy: StatsRequestSortBy) => { const sortWay = sort.sortBy === sortBy && sort.sortWay === 'asc' ? 'desc' : 'asc' getMatomo(matomoConfig).trackEvent({ category: 'nodes', action: 'sort', customDimensions: [ { value: `${sortBy} ${sortWay}`, id: 2 } ] }) setSort({ sortBy: sortBy, sortWay: sortWay }) } useEffect(loadNewQueryResults, [query, submitted, sort]) useEffect(loadNextPageResults, [page]) return ( {siteTitle}

Search servers

{ loaded ? (
{results.length ? results.map((node, index) => { return ( ) }) : ( )}
Domain Software User count Statuses Registrations Last refreshed
Total Month active Half year active
{node.domain}
{node.softwareVersion ?? ''}
{node.totalUserCount ?? '?'} {node.monthActiveUserCount ?? '?'} {node.halfYearActiveUserCount ?? '?'} {node.statusesCount ?? '?'} {node.openRegistrations === null ? '?' : (node.openRegistrations ? 'Opened' : 'Closed')} {node.refreshedAt ? (new Date(node.refreshedAt)).toLocaleDateString() : 'Never'}
No servers found
) : '' }
{hasMore && !loading ? (
) : ''}
) } export const getServerSideProps: GetServerSideProps = async (context) => { console.info('Loading matomo config', matomoConfig) return { props: { matomoConfig } } } export default Nodes