Adds nav component, intrgrates about page, improved error handling, removed unused features

This commit is contained in:
Alicia Sykes 2023-07-08 16:59:48 +01:00
parent efd528f198
commit cdca4b0058
11 changed files with 139 additions and 63 deletions

View File

@ -2,6 +2,7 @@ import { Route, Routes } from 'react-router-dom';
import Styled from 'styled-components'; import Styled from 'styled-components';
import Home from 'pages/Home'; import Home from 'pages/Home';
import Results from 'pages/Results'; import Results from 'pages/Results';
import About from 'pages/About';
import colors from 'styles/colors'; import colors from 'styles/colors';
const Container = Styled.main` const Container = Styled.main`
@ -18,6 +19,7 @@ function App() {
<Routes> <Routes>
<Route path="/" element={<Home />} /> <Route path="/" element={<Home />} />
<Route path="/results/:address" element={<Results />} /> <Route path="/results/:address" element={<Results />} />
<Route path="/about" element={<About />} />
</Routes> </Routes>
</Container> </Container>
); );

View File

@ -9,6 +9,7 @@ interface HeadingProps {
size?: 'xSmall' | 'small' | 'medium' | 'large'; size?: 'xSmall' | 'small' | 'medium' | 'large';
inline?: boolean; inline?: boolean;
children: React.ReactNode; children: React.ReactNode;
id?: string;
}; };
const StyledHeading = styled.h1<HeadingProps>` const StyledHeading = styled.h1<HeadingProps>`
@ -46,9 +47,9 @@ const StyledHeading = styled.h1<HeadingProps>`
`; `;
const Heading = (props: HeadingProps): JSX.Element => { const Heading = (props: HeadingProps): JSX.Element => {
const { children, as, size, align, color, inline } = props; const { children, as, size, align, color, inline, id } = props;
return ( return (
<StyledHeading as={as} size={size} align={align} color={color} inline={inline}> <StyledHeading as={as} size={size} align={align} color={color} inline={inline} id={id}>
{children} {children}
</StyledHeading> </StyledHeading>
); );

View File

@ -43,6 +43,9 @@ const ModalWindow = styled.div`
0% {opacity: 0; transform: scale(0.9);} 0% {opacity: 0; transform: scale(0.9);}
100% {opacity: 1; transform: scale(1);} 100% {opacity: 1; transform: scale(1);}
} }
pre {
white-space: break-spaces;
}
`; `;
const Modal: React.FC<ModalProps> = ({ children, isOpen, closeModal }) => { const Modal: React.FC<ModalProps> = ({ children, isOpen, closeModal }) => {

View File

@ -0,0 +1,30 @@
import styled from 'styled-components';
import { StyledCard } from 'components/Form/Card';
import Heading from 'components/Form/Heading';
import colors from 'styles/colors';
import { ReactNode } from 'react';
const Header = styled(StyledCard)`
margin: 1rem;
display: flex;
flex-wrap: wrap;
align-items: baseline;
justify-content: space-between;
padding: 0.5rem 1rem;
align-items: center;
`;
const Nav = (props: { children?: ReactNode}) => {
return (
<Header as="header">
<Heading color={colors.primary} size="large">
<img width="64" src="/web-check.png" alt="Web Check Icon" />
<a href="/">Web Check</a>
</Heading>
{props.children && props.children}
</Header>
);
};
export default Nav;

View File

@ -2,8 +2,10 @@ import { Card } from 'components/Form/Card';
import Row, { ListRow } from 'components/Form/Row'; import Row, { ListRow } from 'components/Form/Row';
const styles = ` const styles = `
grid-row: span 2;
.content { .content {
max-height: 32rem; max-height: 50rem;
overflow-x: hidden;
overflow-y: auto; overflow-y: auto;
} }
`; `;

View File

@ -2,7 +2,8 @@ import { Card } from 'components/Form/Card';
const cardStyles = ` const cardStyles = `
overflow: auto; overflow: auto;
max-height: 32rem; max-height: 40rem;
grid-row: span 2;
img { img {
border-radius: 6px; border-radius: 6px;
width: 100%; width: 100%;

View File

@ -7,7 +7,7 @@ import Flag from 'components/misc/Flag';
import { TextSizes } from 'styles/typography'; import { TextSizes } from 'styles/typography';
import Row, { StyledRow } from 'components/Form/Row'; import Row, { StyledRow } from 'components/Form/Row';
const cardStyles = 'grid-row: span 2'; const cardStyles = '';
const SmallText = styled.span` const SmallText = styled.span`
opacity: 0.5; opacity: 0.5;

View File

@ -1,6 +1,6 @@
import { Card } from 'components/Form/Card'; import { Card } from 'components/Form/Card';
import colors from 'styles/colors'; import colors from 'styles/colors';
import Row, { ListRow } from 'components/Form/Row'; import Row from 'components/Form/Row';
import Heading from 'components/Form/Heading'; import Heading from 'components/Form/Heading';
const styles = ` const styles = `
@ -17,7 +17,12 @@ const styles = `
`; `;
const formatDate = (timestamp: number): string => { const formatDate = (timestamp: number): string => {
if (isNaN(timestamp) || timestamp <= 0) return 'No Date';
const date = new Date(timestamp * 1000); const date = new Date(timestamp * 1000);
if (isNaN(date.getTime())) return 'Unknown';
const formatter = new Intl.DateTimeFormat('en-GB', { const formatter = new Intl.DateTimeFormat('en-GB', {
day: 'numeric', day: 'numeric',
month: 'long', month: 'long',
@ -26,16 +31,18 @@ const formatDate = (timestamp: number): string => {
minute: '2-digit', minute: '2-digit',
hour12: true hour12: true
}); });
return formatter.format(date); return formatter.format(date);
} }
const SiteFeaturesCard = (props: { data: any, title: string, actionButtons: any }): JSX.Element => { const SiteFeaturesCard = (props: { data: any, title: string, actionButtons: any }): JSX.Element => {
const features = props.data; const features = props.data;
return ( return (
<Card heading={props.title} actionButtons={props.actionButtons} styles={styles}> <Card heading={props.title} actionButtons={props.actionButtons} styles={styles}>
<div className="content"> <div className="content">
{ features.groups.filter((group: any) => group.categories.length > 0).map((group: any, index: number) => ( { (features?.groups || []).filter((group: any) => group.categories.length > 0).map((group: any, index: number) => (
<div key={`${group.name}-${index}`}> <div key={`${group.name}-${index}`}>
<Heading as="h4" size="small" color={colors.primary}>{group.name}</Heading> <Heading as="h4" size="small" color={colors.primary}>{group.name}</Heading>
{ group.categories.map((category: any, subIndex: number) => ( { group.categories.map((category: any, subIndex: number) => (

View File

@ -83,6 +83,10 @@ const StatusInfoWrapper = styled.div`
} }
`; `;
const AboutPageLink = styled.a`
color: ${colors.primary};
`;
const SummaryContainer = styled.div` const SummaryContainer = styled.div`
margin: 0.5rem 0; margin: 0.5rem 0;
b { b {
@ -178,24 +182,24 @@ export interface LoadingJob {
const jobNames = [ const jobNames = [
'get-ip', 'get-ip',
'location',
'ssl', 'ssl',
'dns', 'dns',
'cookies', 'whois',
'robots-txt',
'headers',
'lighthouse',
'location',
'hosts', 'hosts',
'lighthouse',
'cookies',
'trace-route',
'server-info',
'redirects', 'redirects',
'txt-records', 'robots-txt',
'dnssec',
'status', 'status',
'ports', 'ports',
'trace-route', 'txt-records',
'carbon',
'server-info',
'whois',
'features', 'features',
'dnssec', 'carbon',
'headers',
] as const; ] as const;
export const initialJobs = jobNames.map((job: string) => { export const initialJobs = jobNames.map((job: string) => {
@ -283,7 +287,7 @@ const SummaryText = (props: { state: LoadingJob[], count: number }): JSX.Element
if (loadingTasksCount > 0) { if (loadingTasksCount > 0) {
return ( return (
<SummaryContainer className="loading-info"> <SummaryContainer className="loading-info">
<b>Loading {loadingTasksCount} / {totalJobs} Jobs</b> <b>Loading {totalJobs - loadingTasksCount} / {totalJobs} Jobs</b>
{skippedInfo} {skippedInfo}
</SummaryContainer> </SummaryContainer>
); );
@ -409,6 +413,7 @@ const ProgressLoader = (props: { loadStatus: LoadingJob[], showModal: (err: Reac
It's normal for some jobs to fail, either because the host doesn't return the required info, It's normal for some jobs to fail, either because the host doesn't return the required info,
or restrictions in the lambda function, or hitting an API limit. or restrictions in the lambda function, or hitting an API limit.
</p>} </p>}
<AboutPageLink href="/about" target="_blank" rel="noreferer" >Learn More about Web-Check</AboutPageLink>
</Details> </Details>
<DismissButton onClick={() => setHideLoader(true)}>Dismiss</DismissButton> <DismissButton onClick={() => setHideLoader(true)}>Dismiss</DismissButton>
</LoadCard> </LoadCard>

View File

@ -117,7 +117,7 @@ const Home = (): JSX.Element => {
disabled={inputDisabled} disabled={inputDisabled}
handleChange={inputChange} handleChange={inputChange}
/> />
<FindIpButton onClick={findIpAddress}>Or, find my IP</FindIpButton> {/* <FindIpButton onClick={findIpAddress}>Or, find my IP</FindIpButton> */}
{ errorMsg && <ErrorMessage>{errorMsg}</ErrorMessage>} { errorMsg && <ErrorMessage>{errorMsg}</ErrorMessage>}
<Button size="large" onClick={submit}>Analyze!</Button> <Button size="large" onClick={submit}>Analyze!</Button>
</UserInputMain> </UserInputMain>

View File

@ -8,6 +8,7 @@ import Heading from 'components/Form/Heading';
import Card from 'components/Form/Card'; import Card from 'components/Form/Card';
import Modal from 'components/Form/Modal'; import Modal from 'components/Form/Modal';
import Footer from 'components/misc/Footer'; import Footer from 'components/misc/Footer';
import Nav from 'components/Form/Nav';
import { RowProps } from 'components/Form/Row'; import { RowProps } from 'components/Form/Row';
import ErrorBoundary from 'components/misc/ErrorBoundary'; import ErrorBoundary from 'components/misc/ErrorBoundary';
import docs from 'utils/docs'; import docs from 'utils/docs';
@ -67,15 +68,6 @@ const ResultsContent = styled.section`
padding-bottom: 1rem; padding-bottom: 1rem;
`; `;
const Header = styled(Card)`
margin: 1rem;
display: flex;
flex-wrap: wrap;
align-items: baseline;
justify-content: space-between;
padding: 0.5rem 1rem;
`;
const JobDocsContainer = styled.div` const JobDocsContainer = styled.div`
p.doc-desc, p.doc-uses, ul { p.doc-desc, p.doc-uses, ul {
margin: 0.25rem auto 1.5rem auto; margin: 0.25rem auto 1.5rem auto;
@ -148,6 +140,37 @@ const Results = (): JSX.Element => {
}); });
}, []); }, []);
const parseJson = (response: Response): Promise<any> => {
return new Promise((resolve) => {
if (response.ok) {
response.json()
.then(data => resolve(data))
.catch(error => resolve(
{ error: `Failed to process response, likely due to Netlify's 10-sec limit on lambda functions. Error: ${error}`}
));
} else {
resolve(
{ error: `Response returned with status: ${response.status} ${response.statusText}.`
+ `This is likely due to an incompatibility with the lambda function.` }
);
}
});
};
// const parseJson = (response: Response): Promise<any> => {
// if (response.status >= 400) {
// return new Promise((resolve) => resolve({ error: `Failed to fetch data: ${response.statusText}` }));
// }
// return new Promise((resolve) => {
// if (!response) { resolve({ error: 'No response from server' }); }
// response.json()
// .catch(error => resolve({ error: `Failed to process response, likely due to Netlify's 10-sec limit on lambda functions. Error: ${error}`}));
// });
// };
useEffect(() => { useEffect(() => {
if (!addressType || addressType === 'empt') { if (!addressType || addressType === 'empt') {
setAddressType(determineAddressType(address || '')); setAddressType(determineAddressType(address || ''));
@ -165,7 +188,7 @@ const Results = (): JSX.Element => {
updateLoadingJobs, updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly }, addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`/find-url-ip?address=${address}`) fetchRequest: () => fetch(`/find-url-ip?address=${address}`)
.then(res => res.json()) .then(res => parseJson(res))
.then(res => res.ip), .then(res => res.ip),
}); });
@ -174,7 +197,7 @@ const Results = (): JSX.Element => {
jobId: 'ssl', jobId: 'ssl',
updateLoadingJobs, updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly }, addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`/ssl-check?url=${address}`).then((res) => res.json()), fetchRequest: () => fetch(`/ssl-check?url=${address}`).then((res) => parseJson(res)),
}); });
// Fetch and parse cookies info // Fetch and parse cookies info
@ -183,7 +206,7 @@ const Results = (): JSX.Element => {
updateLoadingJobs, updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly }, addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`/get-cookies?url=${address}`) fetchRequest: () => fetch(`/get-cookies?url=${address}`)
.then(res => res.json()) .then(res => parseJson(res))
.then(res => parseCookies(res.cookies)), .then(res => parseCookies(res.cookies)),
}); });
@ -202,7 +225,7 @@ const Results = (): JSX.Element => {
jobId: 'headers', jobId: 'headers',
updateLoadingJobs, updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly }, addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`/get-headers?url=${address}`).then(res => res.json()), fetchRequest: () => fetch(`/get-headers?url=${address}`).then(res => parseJson(res)),
}); });
// Fetch and parse DNS records // Fetch and parse DNS records
@ -210,7 +233,7 @@ const Results = (): JSX.Element => {
jobId: 'dns', jobId: 'dns',
updateLoadingJobs, updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly }, addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`/get-dns?url=${address}`).then(res => res.json()), fetchRequest: () => fetch(`/get-dns?url=${address}`).then(res => parseJson(res)),
}); });
// Fetch and parse Lighthouse performance data // Fetch and parse Lighthouse performance data
@ -219,8 +242,8 @@ const Results = (): JSX.Element => {
updateLoadingJobs, updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly }, addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`/lighthouse-report?url=${address}`) fetchRequest: () => fetch(`/lighthouse-report?url=${address}`)
.then(res => res.json()) .then(res => parseJson(res))
.then(res => res.lighthouseResult), .then(res => res?.lighthouseResult || { error: 'No Data'}),
}); });
// Get IP address location info // Get IP address location info
@ -229,7 +252,7 @@ const Results = (): JSX.Element => {
updateLoadingJobs, updateLoadingJobs,
addressInfo: { address: ipAddress, addressType: 'ipV4', expectedAddressTypes: ['ipV4', 'ipV6'] }, addressInfo: { address: ipAddress, addressType: 'ipV4', expectedAddressTypes: ['ipV4', 'ipV6'] },
fetchRequest: () => fetch(`https://ipapi.co/${ipAddress}/json/`) fetchRequest: () => fetch(`https://ipapi.co/${ipAddress}/json/`)
.then(res => res.json()) .then(res => parseJson(res))
.then(res => getLocation(res)), .then(res => getLocation(res)),
}); });
@ -240,7 +263,7 @@ const Results = (): JSX.Element => {
updateLoadingJobs, updateLoadingJobs,
addressInfo: { address: ipAddress, addressType: 'ipV4', expectedAddressTypes: ['ipV4', 'ipV6'] }, addressInfo: { address: ipAddress, addressType: 'ipV4', expectedAddressTypes: ['ipV4', 'ipV6'] },
fetchRequest: () => fetch(`https://api.shodan.io/shodan/host/${ipAddress}?key=${keys.shodan}`) fetchRequest: () => fetch(`https://api.shodan.io/shodan/host/${ipAddress}?key=${keys.shodan}`)
.then(res => res.json()) .then(res => parseJson(res))
.then(res => parseShodanResults(res)), .then(res => parseShodanResults(res)),
}); });
@ -251,7 +274,7 @@ const Results = (): JSX.Element => {
updateLoadingJobs, updateLoadingJobs,
addressInfo: { address: ipAddress, addressType: 'ipV4', expectedAddressTypes: ['ipV4', 'ipV6'] }, addressInfo: { address: ipAddress, addressType: 'ipV4', expectedAddressTypes: ['ipV4', 'ipV6'] },
fetchRequest: () => fetch(`/check-ports?url=${ipAddress}`) fetchRequest: () => fetch(`/check-ports?url=${ipAddress}`)
.then(res => res.json()), .then(res => parseJson(res)),
}); });
// Fetch and parse domain whois results // Fetch and parse domain whois results
@ -260,7 +283,7 @@ const Results = (): JSX.Element => {
updateLoadingJobs, updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly }, addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`https://api.whoapi.com/?domain=${address}&r=whois&apikey=${keys.whoApi}`) fetchRequest: () => fetch(`https://api.whoapi.com/?domain=${address}&r=whois&apikey=${keys.whoApi}`)
.then(res => res.json()) .then(res => parseJson(res))
.then(res => applyWhoIsResults(res)), .then(res => applyWhoIsResults(res)),
}); });
@ -270,7 +293,7 @@ const Results = (): JSX.Element => {
// updateLoadingJobs, // updateLoadingJobs,
// addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly }, // addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
// fetchRequest: () => fetch(`https://api.builtwith.com/v21/api.json?KEY=${keys.builtWith}&LOOKUP=${address}`) // fetchRequest: () => fetch(`https://api.builtwith.com/v21/api.json?KEY=${keys.builtWith}&LOOKUP=${address}`)
// .then(res => res.json()) // .then(res => parseJson(res))
// .then(res => makeTechnologies(res)), // .then(res => makeTechnologies(res)),
// }); // });
@ -279,7 +302,7 @@ const Results = (): JSX.Element => {
jobId: 'txt-records', jobId: 'txt-records',
updateLoadingJobs, updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly }, addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`/get-txt?url=${address}`).then(res => res.json()), fetchRequest: () => fetch(`/get-txt?url=${address}`).then(res => parseJson(res)),
}); });
// Fetches URL redirects // Fetches URL redirects
@ -287,7 +310,7 @@ const Results = (): JSX.Element => {
jobId: 'redirects', jobId: 'redirects',
updateLoadingJobs, updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly }, addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`/follow-redirects?url=${address}`).then(res => res.json()), fetchRequest: () => fetch(`/follow-redirects?url=${address}`).then(res => parseJson(res)),
}); });
// Get current status and response time of server // Get current status and response time of server
@ -295,7 +318,7 @@ const Results = (): JSX.Element => {
jobId: 'status', jobId: 'status',
updateLoadingJobs, updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly }, addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`/server-status?url=${address}`).then(res => res.json()), fetchRequest: () => fetch(`/server-status?url=${address}`).then(res => parseJson(res)),
}); });
// Get trace route for a given hostname // Get trace route for a given hostname
@ -303,7 +326,7 @@ const Results = (): JSX.Element => {
jobId: 'trace-route', jobId: 'trace-route',
updateLoadingJobs, updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly }, addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`/trace-route?url=${address}`).then(res => res.json()), fetchRequest: () => fetch(`/trace-route?url=${address}`).then(res => parseJson(res)),
}); });
// Fetch carbon footprint data for a given site // Fetch carbon footprint data for a given site
@ -311,7 +334,7 @@ const Results = (): JSX.Element => {
jobId: 'carbon', jobId: 'carbon',
updateLoadingJobs, updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly }, addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`/get-carbon?url=${address}`).then(res => res.json()), fetchRequest: () => fetch(`/get-carbon?url=${address}`).then(res => parseJson(res)),
}); });
// Get site features from BuiltWith // Get site features from BuiltWith
@ -319,7 +342,14 @@ const Results = (): JSX.Element => {
jobId: 'features', jobId: 'features',
updateLoadingJobs, updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly }, addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`/site-features?url=${address}`).then(res => res.json()), fetchRequest: () => fetch(`/site-features?url=${address}`)
.then(res => parseJson(res))
.then(res => {
if (res.Errors && res.Errors.length > 0) {
return { error: `No data returned, because ${res.Errors[0].Message || 'API lookup failed'}` };
}
return res;
}),
}); });
// Get DNSSEC info // Get DNSSEC info
@ -327,7 +357,7 @@ const Results = (): JSX.Element => {
jobId: 'dnssec', jobId: 'dnssec',
updateLoadingJobs, updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly }, addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`/dns-sec?url=${address}`).then(res => res.json()), fetchRequest: () => fetch(`/dns-sec?url=${address}`).then(res => parseJson(res)),
}); });
/* Cancel remaining jobs after 10 second timeout */ /* Cancel remaining jobs after 10 second timeout */
@ -357,24 +387,23 @@ const Results = (): JSX.Element => {
const resultCardData = [ const resultCardData = [
{ id: 'location', title: 'Server Location', result: locationResults, Component: ServerLocationCard, refresh: updateLocationResults }, { id: 'location', title: 'Server Location', result: locationResults, Component: ServerLocationCard, refresh: updateLocationResults },
{ id: 'ssl', title: 'SSL Info', result: sslResults, Component: SslCertCard, refresh: updateSslResults }, { id: 'ssl', title: 'SSL Info', result: sslResults, Component: SslCertCard, refresh: updateSslResults },
{ id: 'dns', title: 'Headers', result: headersResults, Component: HeadersCard, refresh: updateHeadersResults }, { id: 'headers', title: 'Headers', result: headersResults, Component: HeadersCard, refresh: updateHeadersResults },
{ id: 'hosts', title: 'Host Names', result: shoadnResults?.hostnames, Component: HostNamesCard, refresh: updateShodanResults },
{ id: 'whois', title: 'Domain Info', result: whoIsResults, Component: WhoIsCard, refresh: updateWhoIsResults }, { id: 'whois', title: 'Domain Info', result: whoIsResults, Component: WhoIsCard, refresh: updateWhoIsResults },
{ id: 'dns', title: 'DNS Records', result: dnsResults, Component: DnsRecordsCard, refresh: updateDnsResults }, { id: 'dns', title: 'DNS Records', result: dnsResults, Component: DnsRecordsCard, refresh: updateDnsResults },
{ id: 'hosts', title: 'Host Names', result: shoadnResults?.hostnames, Component: HostNamesCard, refresh: updateShodanResults },
{ id: 'lighthouse', title: 'Performance', result: lighthouseResults, Component: LighthouseCard, refresh: updateLighthouseResults }, { id: 'lighthouse', title: 'Performance', result: lighthouseResults, Component: LighthouseCard, refresh: updateLighthouseResults },
{ id: 'cookies', title: 'Cookies', result: cookieResults, Component: CookiesCard, refresh: updateCookieResults }, { id: 'cookies', title: 'Cookies', result: cookieResults, Component: CookiesCard, refresh: updateCookieResults },
{ id: 'trace-route', title: 'Trace Route', result: traceRouteResults, Component: TraceRouteCard, refresh: updateTraceRouteResults }, { id: 'trace-route', title: 'Trace Route', result: traceRouteResults, Component: TraceRouteCard, refresh: updateTraceRouteResults },
{ id: '', title: 'Screenshot', result: lighthouseResults?.fullPageScreenshot?.screenshot, Component: ScreenshotCard, refresh: updateLighthouseResults },
// { title: 'Technologies', result: technologyResults, Component: BuiltWithCard, refresh: updateTechnologyResults },
{ id: 'robots-txt', title: 'Crawl Rules', result: robotsTxtResults, Component: RobotsTxtCard, refresh: updateRobotsTxtResults },
{ id: 'server-info', title: 'Server Info', result: shoadnResults?.serverInfo, Component: ServerInfoCard, refresh: updateShodanResults }, { id: 'server-info', title: 'Server Info', result: shoadnResults?.serverInfo, Component: ServerInfoCard, refresh: updateShodanResults },
{ id: 'redirects', title: 'Redirects', result: redirectResults, Component: RedirectsCard, refresh: updateRedirectResults }, { id: 'redirects', title: 'Redirects', result: redirectResults, Component: RedirectsCard, refresh: updateRedirectResults },
{ id: 'txt-records', title: 'TXT Records', result: txtRecordResults, Component: TxtRecordCard, refresh: updateTxtRecordResults }, { id: 'robots-txt', title: 'Crawl Rules', result: robotsTxtResults, Component: RobotsTxtCard, refresh: updateRobotsTxtResults },
{ id: 'dnssec', title: 'DNSSEC', result: dnsSecResults, Component: DnsSecCard, refresh: updateDnsSecResults },
{ id: 'status', title: 'Server Status', result: serverStatusResults, Component: ServerStatusCard, refresh: updateServerStatusResults }, { id: 'status', title: 'Server Status', result: serverStatusResults, Component: ServerStatusCard, refresh: updateServerStatusResults },
{ id: 'ports', title: 'Open Ports', result: portsResults, Component: OpenPortsCard, refresh: updatePortsResults }, { id: 'ports', title: 'Open Ports', result: portsResults, Component: OpenPortsCard, refresh: updatePortsResults },
{ id: 'carbon', title: 'Carbon Footprint', result: carbonResults, Component: CarbonFootprintCard, refresh: updateCarbonResults }, { id: 'screenshot', title: 'Screenshot', result: lighthouseResults?.fullPageScreenshot?.screenshot, Component: ScreenshotCard, refresh: updateLighthouseResults },
{ id: 'txt-records', title: 'TXT Records', result: txtRecordResults, Component: TxtRecordCard, refresh: updateTxtRecordResults },
{ id: 'features', title: 'Site Features', result: siteFeaturesResults, Component: SiteFeaturesCard, refresh: updateSiteFeaturesResults }, { id: 'features', title: 'Site Features', result: siteFeaturesResults, Component: SiteFeaturesCard, refresh: updateSiteFeaturesResults },
{ id: 'dnssec', title: 'DNSSEC', result: dnsSecResults, Component: DnsSecCard, refresh: updateDnsSecResults }, { id: 'carbon', title: 'Carbon Footprint', result: carbonResults, Component: CarbonFootprintCard, refresh: updateCarbonResults },
]; ];
const MakeActionButtons = (title: string, refresh: () => void, showInfo: (id: string) => void): ReactNode => { const MakeActionButtons = (title: string, refresh: () => void, showInfo: (id: string) => void): ReactNode => {
@ -418,18 +447,14 @@ const Results = (): JSX.Element => {
return ( return (
<ResultsOuter> <ResultsOuter>
<Header as="header"> <Nav>
<Heading color={colors.primary} size="large"> { address &&
<img width="64" src="/web-check.png" alt="Web Check Icon" />
<a href="/">Web Check</a>
</Heading>
{ address &&
<Heading color={colors.textColor} size="medium"> <Heading color={colors.textColor} size="medium">
{ addressType === 'url' && <img width="32px" src={`https://icon.horse/icon/${makeSiteName(address)}`} alt="" /> } { addressType === 'url' && <img width="32px" src={`https://icon.horse/icon/${makeSiteName(address)}`} alt="" /> }
{makeSiteName(address)} {makeSiteName(address)}
</Heading> </Heading>
} }
</Header> </Nav>
<ProgressBar loadStatus={loadingJobs} showModal={showErrorModal} showJobDocs={showInfo} /> <ProgressBar loadStatus={loadingJobs} showModal={showErrorModal} showJobDocs={showInfo} />
<ResultsContent> <ResultsContent>