Re-order fetch requests, and progress bar text to match the UI

This commit is contained in:
Alicia Sykes 2023-08-26 13:09:57 +01:00
parent 749a61358c
commit d805848dd7
2 changed files with 498 additions and 481 deletions

View File

@ -186,42 +186,42 @@ export interface LoadingJob {
const jobNames = [ const jobNames = [
'get-ip', 'get-ip',
'location', 'location',
'headers',
'domain',
'dns',
'dns-server',
'tech-stack',
'hosts',
'quality',
'cookies',
'ssl', 'ssl',
'domain',
'quality',
'tech-stack',
'server-info', 'server-info',
'redirects', 'cookies',
'robots-txt', 'headers',
'dnssec', 'dns',
'status', 'hosts',
'ports',
'screenshot',
'txt-records',
'sitemap',
'hsts',
'security-txt',
'social-tags',
'linked-pages',
'mail-config',
// 'whois',
'features',
'carbon',
'trace-route',
'firewall',
'http-security', 'http-security',
'rank', 'social-tags',
'archives', 'trace-route',
'block-lists', 'security-txt',
'dns-server',
'firewall',
'dnssec',
'hsts',
'threats', 'threats',
'mail-config',
'archives',
'rank',
'screenshot',
'tls-cipher-suites', 'tls-cipher-suites',
'tls-security-config', 'tls-security-config',
'tls-client-support', 'tls-client-support',
'redirects',
'linked-pages',
'robots-txt',
'status',
'ports',
// 'whois',
'txt-records',
'block-lists',
'features',
'sitemap',
'carbon',
] as const; ] as const;
export const initialJobs = jobNames.map((job: string) => { export const initialJobs = jobNames.map((job: string) => {

View File

@ -140,6 +140,15 @@ const FilterButtons = styled.div`
.toggle-filters { .toggle-filters {
font-size: 0.8rem; font-size: 0.8rem;
} }
.control-options {
display: flex;
flex-wrap: wrap;
gap: 1rem;
align-items: center;
a {
text-decoration: none;
}
}
`; `;
const Results = (): JSX.Element => { const Results = (): JSX.Element => {
@ -246,6 +255,16 @@ const Results = (): JSX.Element => {
} }
}, [address, addressType, setIpAddress]); }, [address, addressType, setIpAddress]);
// Get IP address location info
const [locationResults, updateLocationResults] = useMotherHook<ServerLocation>({
jobId: 'location',
updateLoadingJobs,
addressInfo: { address: ipAddress, addressType: 'ipV4', expectedAddressTypes: ['ipV4', 'ipV6'] },
fetchRequest: () => fetch(`https://ipapi.co/${ipAddress}/json/`)
.then(res => parseJson(res))
.then(res => getLocation(res)),
});
// Fetch and parse SSL certificate info // Fetch and parse SSL certificate info
const [sslResults, updateSslResults] = useMotherHook({ const [sslResults, updateSslResults] = useMotherHook({
jobId: 'ssl', jobId: 'ssl',
@ -254,6 +273,42 @@ const Results = (): JSX.Element => {
fetchRequest: () => fetch(`${api}/ssl?url=${address}`).then((res) => parseJson(res)), fetchRequest: () => fetch(`${api}/ssl?url=${address}`).then((res) => parseJson(res)),
}); });
// Run a manual whois lookup on the domain
const [domainLookupResults, updateDomainLookupResults] = useMotherHook({
jobId: 'domain',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/whois?url=${address}`).then(res => parseJson(res)),
});
// Fetch and parse Lighthouse performance data
const [lighthouseResults, updateLighthouseResults] = useMotherHook({
jobId: 'quality',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/quality?url=${address}`)
.then(res => parseJson(res))
.then(res => res?.lighthouseResult || { error: 'No Data'}),
});
// Get the technologies used to build site, using Wappalyzer
const [techStackResults, updateTechStackResults] = useMotherHook({
jobId: 'tech-stack',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/tech-stack?url=${address}`).then(res => parseJson(res)),
});
// Get hostnames and associated domains from Shodan
const [shoadnResults, updateShodanResults] = useMotherHook<ShodanResults>({
jobId: ['hosts', 'server-info'],
updateLoadingJobs,
addressInfo: { address: ipAddress, addressType: 'ipV4', expectedAddressTypes: ['ipV4', 'ipV6'] },
fetchRequest: () => fetch(`https://api.shodan.io/shodan/host/${ipAddress}?key=${keys.shodan}`)
.then(res => parseJson(res))
.then(res => parseShodanResults(res)),
});
// Fetch and parse cookies info // Fetch and parse cookies info
const [cookieResults, updateCookieResults] = useMotherHook<{cookies: Cookie[]}>({ const [cookieResults, updateCookieResults] = useMotherHook<{cookies: Cookie[]}>({
jobId: 'cookies', jobId: 'cookies',
@ -263,15 +318,6 @@ const Results = (): JSX.Element => {
.then(res => parseJson(res)), .then(res => parseJson(res)),
}); });
// Fetch and parse crawl rules from robots.txt
const [robotsTxtResults, updateRobotsTxtResults] = useMotherHook<{robots: RowProps[]}>({
jobId: 'robots-txt',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/robots-txt?url=${address}`)
.then(res => parseJson(res)),
});
// Fetch and parse headers // Fetch and parse headers
const [headersResults, updateHeadersResults] = useMotherHook({ const [headersResults, updateHeadersResults] = useMotherHook({
jobId: 'headers', jobId: 'headers',
@ -288,34 +334,149 @@ const Results = (): JSX.Element => {
fetchRequest: () => fetch(`${api}/dns?url=${address}`).then(res => parseJson(res)), fetchRequest: () => fetch(`${api}/dns?url=${address}`).then(res => parseJson(res)),
}); });
// Fetch and parse Lighthouse performance data // Get HTTP security
const [lighthouseResults, updateLighthouseResults] = useMotherHook({ const [httpSecurityResults, updateHttpSecurityResults] = useMotherHook({
jobId: 'quality', jobId: 'http-security',
updateLoadingJobs, updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly }, addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/quality?url=${address}`) fetchRequest: () => fetch(`${api}/http-security?url=${address}`).then(res => parseJson(res)),
.then(res => parseJson(res))
.then(res => res?.lighthouseResult || { error: 'No Data'}),
}); });
// Get IP address location info // Get social media previews, from a sites social meta tags
const [locationResults, updateLocationResults] = useMotherHook<ServerLocation>({ const [socialTagResults, updateSocialTagResults] = useMotherHook({
jobId: 'location', jobId: 'social-tags',
updateLoadingJobs, updateLoadingJobs,
addressInfo: { address: ipAddress, addressType: 'ipV4', expectedAddressTypes: ['ipV4', 'ipV6'] }, addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`https://ipapi.co/${ipAddress}/json/`) fetchRequest: () => fetch(`${api}/social-tags?url=${address}`).then(res => parseJson(res)),
.then(res => parseJson(res))
.then(res => getLocation(res)),
}); });
// Get hostnames and associated domains from Shodan // Get trace route for a given hostname
const [shoadnResults, updateShodanResults] = useMotherHook<ShodanResults>({ const [traceRouteResults, updateTraceRouteResults] = useMotherHook({
jobId: ['hosts', 'server-info'], jobId: 'trace-route',
updateLoadingJobs, updateLoadingJobs,
addressInfo: { address: ipAddress, addressType: 'ipV4', expectedAddressTypes: ['ipV4', 'ipV6'] }, addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`https://api.shodan.io/shodan/host/${ipAddress}?key=${keys.shodan}`) fetchRequest: () => fetch(`${api}/trace-route?url=${address}`).then(res => parseJson(res)),
.then(res => parseJson(res)) });
.then(res => parseShodanResults(res)),
// Get a websites listed pages, from sitemap
const [securityTxtResults, updateSecurityTxtResults] = useMotherHook({
jobId: 'security-txt',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/security-txt?url=${address}`).then(res => parseJson(res)),
});
// Get the DNS server(s) for a domain, and test DoH/DoT support
const [dnsServerResults, updateDnsServerResults] = useMotherHook({
jobId: 'dns-server',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/dns-server?url=${address}`).then(res => parseJson(res)),
});
// Get the WAF and Firewall info for a site
const [firewallResults, updateFirewallResults] = useMotherHook({
jobId: 'firewall',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/firewall?url=${address}`).then(res => parseJson(res)),
});
// Get DNSSEC info
const [dnsSecResults, updateDnsSecResults] = useMotherHook({
jobId: 'dnssec',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/dnssec?url=${address}`).then(res => parseJson(res)),
});
// Check if a site is on the HSTS preload list
const [hstsResults, updateHstsResults] = useMotherHook({
jobId: 'hsts',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/hsts?url=${address}`).then(res => parseJson(res)),
});
// Check if a host is present on the URLHaus malware list
const [threatResults, updateThreatResults] = useMotherHook({
jobId: 'threats',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/threats?url=${address}`).then(res => parseJson(res)),
});
// Get mail config for server, based on DNS records
const [mailConfigResults, updateMailConfigResults] = useMotherHook({
jobId: 'mail-config',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/mail-config?url=${address}`).then(res => parseJson(res)),
});
// Get list of archives from the Wayback Machine
const [archivesResults, updateArchivesResults] = useMotherHook({
jobId: 'archives',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/archives?url=${address}`).then(res => parseJson(res)),
});
// Get website's global ranking, from Tranco
const [rankResults, updateRankResults] = useMotherHook({
jobId: 'rank',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/rank?url=${address}`).then(res => parseJson(res)),
});
// Take a screenshot of the website
const [screenshotResult, updateScreenshotResult] = useMotherHook({
jobId: 'screenshot',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/screenshot?url=${address}`).then(res => parseJson(res)),
});
// Get TLS security info, from Mozilla Observatory
const [tlsResults, updateTlsResults] = useMotherHook({
jobId: ['tls-cipher-suites', 'tls-security-config', 'tls-client-support'],
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/tls?url=${address}`).then(res => parseJson(res)),
});
// Fetches URL redirects
const [redirectResults, updateRedirectResults] = useMotherHook({
jobId: 'redirects',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/redirects?url=${address}`).then(res => parseJson(res)),
});
// Get list of links included in the page content
const [linkedPagesResults, updateLinkedPagesResults] = useMotherHook({
jobId: 'linked-pages',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/linked-pages?url=${address}`).then(res => parseJson(res)),
});
// Fetch and parse crawl rules from robots.txt
const [robotsTxtResults, updateRobotsTxtResults] = useMotherHook<{robots: RowProps[]}>({
jobId: 'robots-txt',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/robots-txt?url=${address}`)
.then(res => parseJson(res)),
});
// Get current status and response time of server
const [serverStatusResults, updateServerStatusResults] = useMotherHook({
jobId: 'status',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/status?url=${address}`).then(res => parseJson(res)),
}); });
// Check for open ports // Check for open ports
@ -345,52 +506,12 @@ const Results = (): JSX.Element => {
fetchRequest: () => fetch(`${api}/txt-records?url=${address}`).then(res => parseJson(res)), fetchRequest: () => fetch(`${api}/txt-records?url=${address}`).then(res => parseJson(res)),
}); });
// Fetches URL redirects // Check site against DNS blocklists
const [redirectResults, updateRedirectResults] = useMotherHook({ const [blockListsResults, updateBlockListsResults] = useMotherHook({
jobId: 'redirects', jobId: 'block-lists',
updateLoadingJobs, updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly }, addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/redirects?url=${address}`).then(res => parseJson(res)), fetchRequest: () => fetch(`${api}/block-lists?url=${address}`).then(res => parseJson(res)),
});
// Get current status and response time of server
const [serverStatusResults, updateServerStatusResults] = useMotherHook({
jobId: 'status',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/status?url=${address}`).then(res => parseJson(res)),
});
// Get current status and response time of server
const [techStackResults, updateTechStackResults] = useMotherHook({
jobId: 'tech-stack',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/tech-stack?url=${address}`).then(res => parseJson(res)),
});
// Get trace route for a given hostname
const [traceRouteResults, updateTraceRouteResults] = useMotherHook({
jobId: 'trace-route',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/trace-route?url=${address}`).then(res => parseJson(res)),
});
// Fetch carbon footprint data for a given site
const [carbonResults, updateCarbonResults] = useMotherHook({
jobId: 'carbon',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/carbon?url=${address}`).then(res => parseJson(res)),
});
// Check if a site is on the HSTS preload list
const [hstsResults, updateHstsResults] = useMotherHook({
jobId: 'hsts',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/hsts?url=${address}`).then(res => parseJson(res)),
}); });
// Get a websites listed pages, from sitemap // Get a websites listed pages, from sitemap
@ -401,28 +522,12 @@ const Results = (): JSX.Element => {
fetchRequest: () => fetch(`${api}/sitemap?url=${address}`).then(res => parseJson(res)), fetchRequest: () => fetch(`${api}/sitemap?url=${address}`).then(res => parseJson(res)),
}); });
// Get a websites listed pages, from sitemap // Fetch carbon footprint data for a given site
const [screenshotResult, updateScreenshotResult] = useMotherHook({ const [carbonResults, updateCarbonResults] = useMotherHook({
jobId: 'screenshot', jobId: 'carbon',
updateLoadingJobs, updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly }, addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/screenshot?url=${address}`).then(res => parseJson(res)), fetchRequest: () => fetch(`${api}/carbon?url=${address}`).then(res => parseJson(res)),
});
// Get a websites listed pages, from sitemap
const [securityTxtResults, updateSecurityTxtResults] = useMotherHook({
jobId: 'security-txt',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/security-txt?url=${address}`).then(res => parseJson(res)),
});
// Get social media previews, from a sites social meta tags
const [socialTagResults, updateSocialTagResults] = useMotherHook({
jobId: 'social-tags',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/social-tags?url=${address}`).then(res => parseJson(res)),
}); });
// Get site features from BuiltWith // Get site features from BuiltWith
@ -440,102 +545,6 @@ const Results = (): JSX.Element => {
}), }),
}); });
// Get DNSSEC info
const [dnsSecResults, updateDnsSecResults] = useMotherHook({
jobId: 'dnssec',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/dnssec?url=${address}`).then(res => parseJson(res)),
});
// Run a manual whois lookup on the domain
const [domainLookupResults, updateDomainLookupResults] = useMotherHook({
jobId: 'domain',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/whois?url=${address}`).then(res => parseJson(res)),
});
// Get the DNS server(s) for a domain, and test DoH/DoT support
const [dnsServerResults, updateDnsServerResults] = useMotherHook({
jobId: 'dns-server',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/dns-server?url=${address}`).then(res => parseJson(res)),
});
// Get list of links included in the page content
const [linkedPagesResults, updateLinkedPagesResults] = useMotherHook({
jobId: 'linked-pages',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/linked-pages?url=${address}`).then(res => parseJson(res)),
});
// Get mail config for server, based on DNS records
const [mailConfigResults, updateMailConfigResults] = useMotherHook({
jobId: 'mail-config',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/mail-config?url=${address}`).then(res => parseJson(res)),
});
// Get the WAF and Firewall info for a site
const [firewallResults, updateFirewallResults] = useMotherHook({
jobId: 'firewall',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/firewall?url=${address}`).then(res => parseJson(res)),
});
// Get the WAF and Firewall info for a site
const [httpSecurityResults, updateHttpSecurityResults] = useMotherHook({
jobId: 'firewall',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/http-security?url=${address}`).then(res => parseJson(res)),
});
// Get list of archives from the Wayback Machine
const [archivesResults, updateArchivesResults] = useMotherHook({
jobId: 'archives',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/archives?url=${address}`).then(res => parseJson(res)),
});
// Get website's global ranking, from Tranco
const [rankResults, updateRankResults] = useMotherHook({
jobId: 'rank',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/rank?url=${address}`).then(res => parseJson(res)),
});
// Check site against DNS blocklists
const [blockListsResults, updateBlockListsResults] = useMotherHook({
jobId: 'block-lists',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/block-lists?url=${address}`).then(res => parseJson(res)),
});
// Check if a host is present on the URLHaus malware list
const [threatResults, updateThreatResults] = useMotherHook({
jobId: 'threats',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/threats?url=${address}`).then(res => parseJson(res)),
});
// Get TLS security info, from Mozilla Observatory
const [tlsResults, updateTlsResults] = useMotherHook({
jobId: ['tls-cipher-suites', 'tls-security-config', 'tls-client-support'],
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/tls?url=${address}`).then(res => parseJson(res)),
});
/* Cancel remaining jobs after 10 second timeout */ /* Cancel remaining jobs after 10 second timeout */
useEffect(() => { useEffect(() => {
const checkJobs = () => { const checkJobs = () => {
@ -560,7 +569,8 @@ const Results = (): JSX.Element => {
} }
// A list of state sata, corresponding component and title for each card // A list of state sata, corresponding component and title for each card
const resultCardData = [{ const resultCardData = [
{
id: 'location', id: 'location',
title: 'Server Location', title: 'Server Location',
result: locationResults, result: locationResults,
@ -820,7 +830,7 @@ const Results = (): JSX.Element => {
refresh: updateCarbonResults, refresh: updateCarbonResults,
tags: ['meta'], tags: ['meta'],
}, },
]; ];
const makeActionButtons = (title: string, refresh: () => void, showInfo: (id: string) => void): ReactNode => { const makeActionButtons = (title: string, refresh: () => void, showInfo: (id: string) => void): ReactNode => {
const actions = [ const actions = [
@ -869,16 +879,23 @@ const Results = (): JSX.Element => {
{(tags.length > 0 || searchTerm.length > 0) && <span onClick={clearFilters} className="clear">Clear Filters</span> } {(tags.length > 0 || searchTerm.length > 0) && <span onClick={clearFilters} className="clear">Clear Filters</span> }
</div> </div>
<div className="one-half"> <div className="one-half">
<span className="group-label">Search</span>
<input <input
type="text" type="text"
placeholder="Filter Results" placeholder="Filter Results"
value={searchTerm} value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)} onChange={(e) => setSearchTerm(e.target.value)}
/> />
<span className="group-label">Search</span> <span className="toggle-filters" onClick={() => setShowFilters(false)}>Hide</span>
</div> </div>
</> : ( </> : (
<div className="control-options">
<span className="toggle-filters" onClick={() => setShowFilters(true)}>Show Filters</span> <span className="toggle-filters" onClick={() => setShowFilters(true)}>Show Filters</span>
<a href="#view-download-raw-data"><span className="toggle-filters">Export Data</span></a>
<a href="/about"><span className="toggle-filters">Learn about the Results</span></a>
<a href="/about#additional-resources"><span className="toggle-filters">More tools</span></a>
<a href="https://github.com/lissy93/web-check"><span className="toggle-filters">View GitHub</span></a>
</div>
) } ) }
</FilterButtons> </FilterButtons>
<ResultsContent> <ResultsContent>