diff --git a/src/components/Form/Heading.tsx b/src/components/Form/Heading.tsx index 582ae3a..cdd40e8 100644 --- a/src/components/Form/Heading.tsx +++ b/src/components/Form/Heading.tsx @@ -28,6 +28,7 @@ const StyledHeading = styled.h1` a { // If a title is a link, keep title styles color: inherit; text-decoration: none; + display: flex; } ${props => { switch (props.size) { diff --git a/src/components/Results/TlsCipherSuites.tsx b/src/components/Results/TlsCipherSuites.tsx index 57cc581..bfa3de7 100644 --- a/src/components/Results/TlsCipherSuites.tsx +++ b/src/components/Results/TlsCipherSuites.tsx @@ -1,10 +1,8 @@ import { useState } from 'react'; -import styled from 'styled-components'; -import colors from 'styles/colors'; import { Card } from 'components/Form/Card'; import Button from 'components/Form/Button'; -import Row, { ExpandableRow } from 'components/Form/Row'; +import { ExpandableRow } from 'components/Form/Row'; const makeCipherSuites = (results: any) => { if (!results || !results.connection_info || (results.connection_info.ciphersuite || [])?.length === 0) { diff --git a/src/components/Results/TlsClientSupport.tsx b/src/components/Results/TlsClientSupport.tsx index 609e69b..84464c5 100644 --- a/src/components/Results/TlsClientSupport.tsx +++ b/src/components/Results/TlsClientSupport.tsx @@ -1,22 +1,8 @@ import { useState } from 'react'; -import styled from 'styled-components'; -import colors from 'styles/colors'; import { Card } from 'components/Form/Card'; import Button from 'components/Form/Button'; -import Row, { ExpandableRow } from 'components/Form/Row'; - - -// "name": "Android", -// "curve": "secp256r1", -// "version": "6.0", -// "platform": "", -// "protocol": "TLSv1.2", -// "curve_code": 23, -// "ciphersuite": "ECDHE-RSA-CHACHA20-POLY1305-OLD", -// "is_supported": true, -// "protocol_code": 771, -// "ciphersuite_code": 52243 +import { ExpandableRow } from 'components/Form/Row'; const makeClientSupport = (results: any) => { if (!results?.analysis) return []; diff --git a/src/pages/Results.tsx b/src/pages/Results.tsx index 03cf14d..ff36926 100644 --- a/src/pages/Results.tsx +++ b/src/pages/Results.tsx @@ -1,5 +1,5 @@ import { useState, useEffect, useCallback, ReactNode } from 'react'; -import { useParams } from 'react-router-dom'; +import { useParams } from 'react-router-dom'; import styled from 'styled-components'; import { ToastContainer } from 'react-toastify'; import Masonry from 'react-masonry-css' @@ -89,6 +89,56 @@ const ResultsContent = styled.section` padding-bottom: 1rem; `; +const FilterButtons = styled.div` + width: 95vw; + margin: auto; + display: flex; + flex-wrap: wrap; + justify-content: space-between; + gap: 1rem; + .one-half { + display: flex; + flex-wrap: wrap; + gap: 1rem; + align-items: center; + } + button, input { + background: ${colors.backgroundLighter}; + color: ${colors.textColor}; + border: none; + border-radius: 4px; + font-family: 'PTMono'; + padding: 0.25rem 0.5rem; + border: 1px solid transparent; + transition: all 0.2s ease-in-out; + } + button { + cursor: pointer; + text-transform: capitalize; + box-shadow: 2px 2px 0px ${colors.bgShadowColor}; + transition: all 0.2s ease-in-out; + &:hover { + box-shadow: 4px 4px 0px ${colors.bgShadowColor}; + color: ${colors.primary}; + } + &.selected { + border: 1px solid ${colors.primary}; + color: ${colors.primary}; + } + } + input:focus { + border: 1px solid ${colors.primary}; + outline: none; + } + .clear { + color: ${colors.textColor}; + text-decoration: underline; + cursor: pointer; + font-size: 0.8rem; + opacity: 0.8; + } +`; + const Results = (): JSX.Element => { const startTime = new Date().getTime(); @@ -98,6 +148,18 @@ const Results = (): JSX.Element => { const [loadingJobs, setLoadingJobs] = useState(initialJobs); const [modalOpen, setModalOpen] = useState(false); const [modalContent, setModalContent] = useState(<>); + const [searchTerm, setSearchTerm] = useState(''); + const [tags, setTags] = useState([]); + + const clearFilters = () => { + setTags([]); + setSearchTerm(''); + }; + const updateTags = (tag: string) => { + // Remove current tag if it exists, otherwise add it + // setTags(tags.includes(tag) ? tags.filter(t => t !== tag) : [...tags, tag]); + setTags(tags.includes(tag) ? tags.filter(t => t !== tag) : [tag]); + }; const updateLoadingJobs = useCallback((jobs: string | string[], newState: LoadingState, error?: string, retry?: () => void, data?: any) => { (typeof jobs === 'string' ? [jobs] : jobs).forEach((job: string) => { @@ -494,47 +556,269 @@ const Results = (): JSX.Element => { } // A list of state sata, corresponding component and title for each card - const resultCardData = [ - { id: 'location', title: 'Server Location', result: locationResults, Component: ServerLocationCard, refresh: updateLocationResults }, - { id: 'ssl', title: 'SSL Certificate', result: sslResults, Component: SslCertCard, refresh: updateSslResults }, - { id: 'domain', title: 'Domain Whois', result: domainLookupResults, Component: DomainLookup, refresh: updateDomainLookupResults }, - { id: 'quality', title: 'Quality Summary', result: lighthouseResults, Component: LighthouseCard, refresh: updateLighthouseResults }, - { id: 'tech-stack', title: 'Tech Stack', result: techStackResults, Component: TechStackCard, refresh: updateTechStackResults }, - { id: 'server-info', title: 'Server Info', result: shoadnResults?.serverInfo, Component: ServerInfoCard, refresh: updateShodanResults }, - { id: 'cookies', title: 'Cookies', result: cookieResults, Component: CookiesCard, refresh: updateCookieResults }, - { id: 'headers', title: 'Headers', result: headersResults, Component: HeadersCard, refresh: updateHeadersResults }, - { id: 'dns', title: 'DNS Records', result: dnsResults, Component: DnsRecordsCard, refresh: updateDnsResults }, - { id: 'hosts', title: 'Host Names', result: shoadnResults?.hostnames, Component: HostNamesCard, refresh: updateShodanResults }, - { id: 'http-security', title: 'HTTP Security', result: httpSecurityResults, Component: HttpSecurityCard, refresh: updateHttpSecurityResults }, - { id: 'social-tags', title: 'Social Tags', result: socialTagResults, Component: SocialTagsCard, refresh: updateSocialTagResults }, - { id: 'trace-route', title: 'Trace Route', result: traceRouteResults, Component: TraceRouteCard, refresh: updateTraceRouteResults }, - { id: 'screenshot', title: 'Screenshot', result: screenshotResult || lighthouseResults?.fullPageScreenshot?.screenshot, Component: ScreenshotCard, refresh: updateScreenshotResult }, - { id: 'security-txt', title: 'Security.Txt', result: securityTxtResults, Component: SecurityTxtCard, refresh: updateSecurityTxtResults }, - { id: 'dns-server', title: 'DNS Server', result: dnsServerResults, Component: DnsServerCard, refresh: updateDnsServerResults }, - { id: 'firewall', title: 'Firewall', result: firewallResults, Component: FirewallCard, refresh: updateFirewallResults }, - { id: 'dnssec', title: 'DNSSEC', result: dnsSecResults, Component: DnsSecCard, refresh: updateDnsSecResults }, - { id: 'hsts', title: 'HSTS Check', result: hstsResults, Component: HstsCard, refresh: updateHstsResults }, - { id: 'threats', title: 'Threats', result: threatResults, Component: ThreatsCard, refresh: updateThreatResults }, - { id: 'mail-config', title: 'Email Configuration', result: mailConfigResults, Component: MailConfigCard, refresh: updateMailConfigResults }, - { id: 'archives', title: 'Archive History', result: archivesResults, Component: ArchivesCard, refresh: updateArchivesResults }, - { id: 'rank', title: 'Global Ranking', result: rankResults, Component: RankCard, refresh: updateRankResults }, - { id: 'tls-cipher-suites', title: 'TLS Cipher Suites', result: tlsResults, Component: TlsCipherSuitesCard, refresh: updateTlsResults }, - { id: 'tls-security-config', title: 'TLS Security Issues', result: tlsResults, Component: TlsIssueAnalysisCard, refresh: updateTlsResults }, - { id: 'tls-client-support', title: 'TLS Handshake Simulation', result: tlsResults, Component: TlsClientSupportCard, refresh: updateTlsResults }, - { id: 'redirects', title: 'Redirects', result: redirectResults, Component: RedirectsCard, refresh: updateRedirectResults }, - { id: 'linked-pages', title: 'Linked Pages', result: linkedPagesResults, Component: ContentLinksCard, refresh: updateLinkedPagesResults }, - { id: 'robots-txt', title: 'Crawl Rules', result: robotsTxtResults, Component: RobotsTxtCard, refresh: updateRobotsTxtResults }, - { id: 'status', title: 'Server Status', result: serverStatusResults, Component: ServerStatusCard, refresh: updateServerStatusResults }, - { id: 'ports', title: 'Open Ports', result: portsResults, Component: OpenPortsCard, refresh: updatePortsResults }, - { id: 'whois', title: 'Domain Info', result: whoIsResults, Component: WhoIsCard, refresh: updateWhoIsResults }, - { id: 'txt-records', title: 'TXT Records', result: txtRecordResults, Component: TxtRecordCard, refresh: updateTxtRecordResults }, - { id: 'block-lists', title: 'Block Lists', result: blockListsResults, Component: BlockListsCard, refresh: updateBlockListsResults }, - { id: 'features', title: 'Site Features', result: siteFeaturesResults, Component: SiteFeaturesCard, refresh: updateSiteFeaturesResults }, - { id: 'sitemap', title: 'Pages', result: sitemapResults, Component: SitemapCard, refresh: updateSitemapResults }, - { id: 'carbon', title: 'Carbon Footprint', result: carbonResults, Component: CarbonFootprintCard, refresh: updateCarbonResults }, - ]; + const resultCardData = [{ + id: 'location', + title: 'Server Location', + result: locationResults, + Component: ServerLocationCard, + refresh: updateLocationResults, + tags: ['server'], + }, { + id: 'ssl', + title: 'SSL Certificate', + result: sslResults, + Component: SslCertCard, + refresh: updateSslResults, + tags: ['server', 'security'], + }, { + id: 'domain', + title: 'Domain Whois', + result: domainLookupResults, + Component: DomainLookup, + refresh: updateDomainLookupResults, + tags: ['server'], + }, { + id: 'quality', + title: 'Quality Summary', + result: lighthouseResults, + Component: LighthouseCard, + refresh: updateLighthouseResults, + tags: ['client'], + }, { + id: 'tech-stack', + title: 'Tech Stack', + result: techStackResults, + Component: TechStackCard, + refresh: updateTechStackResults, + tags: ['client', 'meta'], + }, { + id: 'server-info', + title: 'Server Info', + result: shoadnResults?.serverInfo, + Component: ServerInfoCard, + refresh: updateShodanResults, + tags: ['server'], + }, { + id: 'cookies', + title: 'Cookies', + result: cookieResults, + Component: CookiesCard, + refresh: updateCookieResults, + tags: ['client', 'security'], + }, { + id: 'headers', + title: 'Headers', + result: headersResults, + Component: HeadersCard, + refresh: updateHeadersResults, + tags: ['client', 'security'], + }, { + id: 'dns', + title: 'DNS Records', + result: dnsResults, + Component: DnsRecordsCard, + refresh: updateDnsResults, + tags: ['server'], + }, { + id: 'hosts', + title: 'Host Names', + result: shoadnResults?.hostnames, + Component: HostNamesCard, + refresh: updateShodanResults, + tags: ['server'], + }, { + id: 'http-security', + title: 'HTTP Security', + result: httpSecurityResults, + Component: HttpSecurityCard, + refresh: updateHttpSecurityResults, + tags: ['security'], + }, { + id: 'social-tags', + title: 'Social Tags', + result: socialTagResults, + Component: SocialTagsCard, + refresh: updateSocialTagResults, + tags: ['client', 'meta'], + }, { + id: 'trace-route', + title: 'Trace Route', + result: traceRouteResults, + Component: TraceRouteCard, + refresh: updateTraceRouteResults, + tags: ['server'], + }, { + id: 'screenshot', + title: 'Screenshot', + result: screenshotResult || lighthouseResults?.fullPageScreenshot?.screenshot, + Component: ScreenshotCard, + refresh: updateScreenshotResult, + tags: ['client', 'meta'], + }, { + id: 'security-txt', + title: 'Security.Txt', + result: securityTxtResults, + Component: SecurityTxtCard, + refresh: updateSecurityTxtResults, + tags: ['security'], + }, { + id: 'dns-server', + title: 'DNS Server', + result: dnsServerResults, + Component: DnsServerCard, + refresh: updateDnsServerResults, + tags: ['server'], + }, { + id: 'firewall', + title: 'Firewall', + result: firewallResults, + Component: FirewallCard, + refresh: updateFirewallResults, + tags: ['server', 'security'], + }, { + id: 'dnssec', + title: 'DNSSEC', + result: dnsSecResults, + Component: DnsSecCard, + refresh: updateDnsSecResults, + tags: ['security'], + }, { + id: 'hsts', + title: 'HSTS Check', + result: hstsResults, + Component: HstsCard, + refresh: updateHstsResults, + tags: ['security'], + }, { + id: 'threats', + title: 'Threats', + result: threatResults, + Component: ThreatsCard, + refresh: updateThreatResults, + tags: ['security'], + }, { + id: 'mail-config', + title: 'Email Configuration', + result: mailConfigResults, + Component: MailConfigCard, + refresh: updateMailConfigResults, + tags: ['server'], + }, { + id: 'archives', + title: 'Archive History', + result: archivesResults, + Component: ArchivesCard, + refresh: updateArchivesResults, + tags: ['meta'], + }, { + id: 'rank', + title: 'Global Ranking', + result: rankResults, + Component: RankCard, + refresh: updateRankResults, + tags: ['meta'], + }, { + id: 'tls-cipher-suites', + title: 'TLS Cipher Suites', + result: tlsResults, + Component: TlsCipherSuitesCard, + refresh: updateTlsResults, + tags: ['server', 'security'], + }, { + id: 'tls-security-config', + title: 'TLS Security Issues', + result: tlsResults, + Component: TlsIssueAnalysisCard, + refresh: updateTlsResults, + tags: ['security'], + }, { + id: 'tls-client-support', + title: 'TLS Handshake Simulation', + result: tlsResults, + Component: TlsClientSupportCard, + refresh: updateTlsResults, + tags: ['security'], + }, { + id: 'redirects', + title: 'Redirects', + result: redirectResults, + Component: RedirectsCard, + refresh: updateRedirectResults, + tags: ['meta'], + }, { + id: 'linked-pages', + title: 'Linked Pages', + result: linkedPagesResults, + Component: ContentLinksCard, + refresh: updateLinkedPagesResults, + tags: ['client', 'meta'], + }, { + id: 'robots-txt', + title: 'Crawl Rules', + result: robotsTxtResults, + Component: RobotsTxtCard, + refresh: updateRobotsTxtResults, + tags: ['meta'], + }, { + id: 'status', + title: 'Server Status', + result: serverStatusResults, + Component: ServerStatusCard, + refresh: updateServerStatusResults, + tags: ['server'], + }, { + id: 'ports', + title: 'Open Ports', + result: portsResults, + Component: OpenPortsCard, + refresh: updatePortsResults, + tags: ['server'], + }, { + id: 'whois', + title: 'Domain Info', + result: whoIsResults, + Component: WhoIsCard, + refresh: updateWhoIsResults, + tags: ['server'], + }, { + id: 'txt-records', + title: 'TXT Records', + result: txtRecordResults, + Component: TxtRecordCard, + refresh: updateTxtRecordResults, + tags: ['server'], + }, { + id: 'block-lists', + title: 'Block Lists', + result: blockListsResults, + Component: BlockListsCard, + refresh: updateBlockListsResults, + tags: ['security', 'meta'], + }, { + id: 'features', + title: 'Site Features', + result: siteFeaturesResults, + Component: SiteFeaturesCard, + refresh: updateSiteFeaturesResults, + tags: ['meta'], + }, { + id: 'sitemap', + title: 'Pages', + result: sitemapResults, + Component: SitemapCard, + refresh: updateSitemapResults, + tags: ['meta'], + }, { + id: 'carbon', + title: 'Carbon Footprint', + result: carbonResults, + Component: CarbonFootprintCard, + refresh: updateCarbonResults, + 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 = [ { label: `Info about ${title}`, onClick: showInfo, icon: 'ⓘ'}, { label: `Re-fetch ${title} data`, onClick: refresh, icon: '↻'}, @@ -559,7 +843,7 @@ const Results = (): JSX.Element => {