Adds Lighthouse reporting

This commit is contained in:
Alicia Sykes 2023-06-18 21:01:13 +01:00
parent 920ab64410
commit f139256736
9 changed files with 28232 additions and 3 deletions

View File

@ -39,6 +39,11 @@
to = "/.netlify/functions/find-url-ip"
status = 301
force = true
[[redirects]]
from = "/lighthouse-report"
to = "/.netlify/functions/lighthouse-report"
status = 301
force = true
# For router history mode, ensure pages land on index
[[redirects]]

28019
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -3,6 +3,7 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@netlify/functions": "^1.6.0",
"@testing-library/jest-dom": "^5.16.4",
"@testing-library/react": "^13.3.0",
"@testing-library/user-event": "^13.5.0",
@ -12,8 +13,12 @@
"@types/react-dom": "^18.0.5",
"@types/react-router-dom": "^5.3.3",
"@types/styled-components": "^5.1.25",
"axios": "^1.4.0",
"chrome-aws-lambda": "^10.1.0",
"jest-styled-components": "^7.0.8",
"lighthouse": "^10.3.0",
"node-fetch": "2.6.1",
"puppeteer-core": "^20.7.2",
"react": "^18.1.0",
"react-dom": "^18.1.0",
"react-router-dom": "^6.3.0",
@ -51,5 +56,9 @@
},
"devDependencies": {
"@types/react-simple-maps": "^1.0.8"
},
"compilerOptions": {
"allowJs": true,
"outDir": "./dist"
}
}

View File

@ -0,0 +1,33 @@
const fetch = require('node-fetch');
exports.handler = function(event, context, callback) {
const { url } = event.queryStringParameters;
if (!url) {
callback(null, {
statusCode: 400,
body: JSON.stringify({ error: 'URL param is required'}),
});
}
const apiKey = process.env.GOOGLE_CLOUD_API_KEY;
const endpoint = `https://www.googleapis.com/pagespeedonline/v5/runPagespeed?url=${encodeURIComponent(url)}&category=PERFORMANCE&category=ACCESSIBILITY&category=BEST_PRACTICES&category=SEO&category=PWA&strategy=mobile&key=${apiKey}`;
fetch(endpoint)
.then((res) => res.json() )
.then(
(data) => {
callback(null, {
statusCode: 200,
body: JSON.stringify(data),
});
}
).catch(
() => {
callback(null, {
statusCode: 500,
body: JSON.stringify({ error: 'Error running Lighthouse'}),
});
}
);
};

View File

@ -0,0 +1,125 @@
import styled from 'styled-components';
import colors from 'styles/colors';
import Card from 'components/Form/Card';
import Heading from 'components/Form/Heading';
const processScore = (percentile: number) => {
return `${Math.round(percentile * 100)}%`;
}
const Outer = styled(Card)``;
const Row = styled.div`
details summary {
display: flex;
justify-content: space-between;
padding: 0.25rem;
cursor: pointer;
}
&:not(:last-child) { border-bottom: 1px solid ${colors.primary}; }
span.lbl {
font-weight: bold;
text-transform: capitalize;
}
span.val {
max-width: 200px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
`;
const ScoreRow = styled.li`
list-style: none;
margin: 0;
display: flex;
gap: 0.25rem;
padding: 0.25rem 0;
justify-content: space-between;
span { min-width: 5rem; max-width: 8rem; }
border-bottom: 1px dashed ${colors.primaryTransparent};
`;
const ScoreList = styled.ul`
margin: 0;
padding: 0.25rem;
border-top: 1px solid ${colors.primary};
background: ${colors.primaryTransparent};
`;
interface Audit {
id: string,
score?: number | string,
scoreDisplayMode?: string,
title?: string,
description?: string,
displayValue?: string,
};
const ScoreItem = (props: { scoreId: any, audits: Audit[] }) => {
const { scoreId, audits } = props;
console.log('Audits: ', props.audits)
const audit = audits[scoreId];
if (!audit.score) return null;
let score = audit.score;
if (audit.displayValue) {
score = audit.displayValue;
} else if (audit.scoreDisplayMode) {
score = audit.score === 1 ? '✅ Pass' : '❌ Fail';
}
return (
<ScoreRow title={audit.description}>
<b>{ audit.title }</b>
<span>{score}</span>
</ScoreRow>
);
};
const ExpandableRow = (props: { lbl: string, val: string, list: string[], audits: Audit[] }) => {
const { lbl, val, list, audits } = props;
return (
<Row>
<details>
<summary>
<span className="lbl">{lbl}</span>
<span className="val" title={val}>{val}</span>
</summary>
<ScoreList>
{ list.map((li: string) => {
return <ScoreItem scoreId={li} audits={audits} />
}) }
</ScoreList>
</details>
</Row>
);
};
const LighthouseCard = (props: { lighthouse: any }): JSX.Element => {
console.log('Props:', props.lighthouse);
const categories = props.lighthouse?.categories || {};
const audits = props.lighthouse?.audits || [];
return (
<Outer>
<Heading as="h3" size="small" align="left" color={colors.primary}>Performance</Heading>
{ Object.keys(categories).map((title: string, index: number) => {
const scoreIds = categories[title].auditRefs.map((ref: { id: string }) => ref.id);
return (
<ExpandableRow
key={`lighthouse-${index}`}
lbl={title}
val={processScore(categories[title].score)}
list={scoreIds}
audits={audits}
/>
);
}) }
</Outer>
);
}
export default LighthouseCard;

View File

@ -0,0 +1,22 @@
import styled from 'styled-components';
import colors from 'styles/colors';
import Card from 'components/Form/Card';
import Heading from 'components/Form/Heading';
const Outer = styled(Card)`
overflow: auto;
max-height: 20rem;
`;
const ScreenshotCard = (props: { screenshot: string }): JSX.Element => {
console.log('Props:', props.screenshot);
return (
<Outer>
<Heading as="h3" size="small" align="left" color={colors.primary}>Screenshot</Heading>
<img src={props.screenshot} alt="Full page screenshot" />
</Outer>
);
}
export default ScreenshotCard;

View File

@ -12,6 +12,7 @@ body {
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
background: #141d2b;
}
code {

View File

@ -10,6 +10,8 @@ import ServerInfoCard from 'components/Results/ServerInfo';
import HostNamesCard from 'components/Results/HostNames';
import WhoIsCard from 'components/Results/WhoIs';
import BuiltWithCard from 'components/Results/BuiltWith';
import LighthouseCard from 'components/Results/Lighthouse';
import ScreenshotCard from 'components/Results/Screenshot';
import keys from 'utils/get-keys';
import { determineAddressType, AddressType } from 'utils/address-type-checker';
@ -57,6 +59,8 @@ const Results = (): JSX.Element => {
const [ locationResults, setLocationResults ] = useState<ServerLocation>();
const [ whoIsResults, setWhoIsResults ] = useState<Whois>();
const [ technologyResults, setTechnologyResults ] = useState<TechnologyGroup[]>();
const [ lighthouseResults, setLighthouseResults ] = useState<any>();
const [ screenshotResult, setScreenshotResult ] = useState<string>();
const [ ipAddress, setIpAddress ] = useState<undefined | string>(undefined);
const [ addressType, setAddressType ] = useState<AddressType>('empt');
const { address } = useParams();
@ -75,7 +79,6 @@ const Results = (): JSX.Element => {
fetch(`/find-url-ip?address=${address}`)
.then(function(response) {
response.json().then(jsonData => {
console.log('Get IP Address', jsonData);
setIpAddress(jsonData.ip);
});
})
@ -88,6 +91,17 @@ const Results = (): JSX.Element => {
}
}, [address]);
/* Get Lighthouse report */
useEffect(() => {
fetch(`/lighthouse-report?url=${address}`)
.then(response => response.json())
.then(response => {
setLighthouseResults(response.lighthouseResult);
setScreenshotResult(response.lighthouseResult?.fullPageScreenshot?.screenshot?.data);
})
.catch(err => console.error(err));
}, [address])
/* Get IP address location info */
useEffect(() => {
const fetchIpLocation = () => {
@ -118,7 +132,6 @@ const Results = (): JSX.Element => {
fetch(`https://api.shodan.io/shodan/host/${ipAddress}?key=${apiKey}`)
.then(response => response.json())
.then(response => {
console.log(response);
if (!response.error) applyShodanResults(response)
})
.catch(err => console.error(err));
@ -137,7 +150,6 @@ const Results = (): JSX.Element => {
fetch(endpoint)
.then(response => response.json())
.then(response => {
console.log(response);
setTechnologyResults(makeTechnologies(response));
});
}, [address]);
@ -179,6 +191,8 @@ const Results = (): JSX.Element => {
{ results.serverInfo && <ServerInfoCard {...results.serverInfo} />}
{ results.hostNames && <HostNamesCard hosts={results.hostNames} />}
{ whoIsResults && <WhoIsCard {...whoIsResults} />}
{ lighthouseResults && <LighthouseCard lighthouse={lighthouseResults} />}
{ screenshotResult && <ScreenshotCard screenshot={screenshotResult} />}
{ technologyResults && <BuiltWithCard technologies={technologyResults} />}
</ResultsContent>
</ResultsOuter>

View File

@ -9,6 +9,7 @@ const colors = {
backgroundLighter: '#1a2332',
bgShadowColor: '#0f1620',
fgShadowColor: '#456602',
primaryTransparent: '#9fef0012',
// Action Colors
info: '#04e4f4',