mirror of
https://github.com/Lissy93/web-check.git
synced 2025-04-10 10:28:53 +02:00
Adds Lighthouse reporting
This commit is contained in:
parent
920ab64410
commit
f139256736
@ -39,6 +39,11 @@
|
|||||||
to = "/.netlify/functions/find-url-ip"
|
to = "/.netlify/functions/find-url-ip"
|
||||||
status = 301
|
status = 301
|
||||||
force = true
|
force = true
|
||||||
|
[[redirects]]
|
||||||
|
from = "/lighthouse-report"
|
||||||
|
to = "/.netlify/functions/lighthouse-report"
|
||||||
|
status = 301
|
||||||
|
force = true
|
||||||
|
|
||||||
# For router history mode, ensure pages land on index
|
# For router history mode, ensure pages land on index
|
||||||
[[redirects]]
|
[[redirects]]
|
||||||
|
28019
package-lock.json
generated
Normal file
28019
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -3,6 +3,7 @@
|
|||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@netlify/functions": "^1.6.0",
|
||||||
"@testing-library/jest-dom": "^5.16.4",
|
"@testing-library/jest-dom": "^5.16.4",
|
||||||
"@testing-library/react": "^13.3.0",
|
"@testing-library/react": "^13.3.0",
|
||||||
"@testing-library/user-event": "^13.5.0",
|
"@testing-library/user-event": "^13.5.0",
|
||||||
@ -12,8 +13,12 @@
|
|||||||
"@types/react-dom": "^18.0.5",
|
"@types/react-dom": "^18.0.5",
|
||||||
"@types/react-router-dom": "^5.3.3",
|
"@types/react-router-dom": "^5.3.3",
|
||||||
"@types/styled-components": "^5.1.25",
|
"@types/styled-components": "^5.1.25",
|
||||||
|
"axios": "^1.4.0",
|
||||||
|
"chrome-aws-lambda": "^10.1.0",
|
||||||
"jest-styled-components": "^7.0.8",
|
"jest-styled-components": "^7.0.8",
|
||||||
|
"lighthouse": "^10.3.0",
|
||||||
"node-fetch": "2.6.1",
|
"node-fetch": "2.6.1",
|
||||||
|
"puppeteer-core": "^20.7.2",
|
||||||
"react": "^18.1.0",
|
"react": "^18.1.0",
|
||||||
"react-dom": "^18.1.0",
|
"react-dom": "^18.1.0",
|
||||||
"react-router-dom": "^6.3.0",
|
"react-router-dom": "^6.3.0",
|
||||||
@ -51,5 +56,9 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/react-simple-maps": "^1.0.8"
|
"@types/react-simple-maps": "^1.0.8"
|
||||||
|
},
|
||||||
|
"compilerOptions": {
|
||||||
|
"allowJs": true,
|
||||||
|
"outDir": "./dist"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
33
server/lambda/lighthouse-report.js
Normal file
33
server/lambda/lighthouse-report.js
Normal 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'}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
125
src/components/Results/Lighthouse.tsx
Normal file
125
src/components/Results/Lighthouse.tsx
Normal 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;
|
22
src/components/Results/Screenshot.tsx
Normal file
22
src/components/Results/Screenshot.tsx
Normal 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;
|
@ -12,6 +12,7 @@ body {
|
|||||||
sans-serif;
|
sans-serif;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
background: #141d2b;
|
||||||
}
|
}
|
||||||
|
|
||||||
code {
|
code {
|
||||||
|
@ -10,6 +10,8 @@ import ServerInfoCard from 'components/Results/ServerInfo';
|
|||||||
import HostNamesCard from 'components/Results/HostNames';
|
import HostNamesCard from 'components/Results/HostNames';
|
||||||
import WhoIsCard from 'components/Results/WhoIs';
|
import WhoIsCard from 'components/Results/WhoIs';
|
||||||
import BuiltWithCard from 'components/Results/BuiltWith';
|
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 keys from 'utils/get-keys';
|
||||||
import { determineAddressType, AddressType } from 'utils/address-type-checker';
|
import { determineAddressType, AddressType } from 'utils/address-type-checker';
|
||||||
|
|
||||||
@ -57,6 +59,8 @@ const Results = (): JSX.Element => {
|
|||||||
const [ locationResults, setLocationResults ] = useState<ServerLocation>();
|
const [ locationResults, setLocationResults ] = useState<ServerLocation>();
|
||||||
const [ whoIsResults, setWhoIsResults ] = useState<Whois>();
|
const [ whoIsResults, setWhoIsResults ] = useState<Whois>();
|
||||||
const [ technologyResults, setTechnologyResults ] = useState<TechnologyGroup[]>();
|
const [ technologyResults, setTechnologyResults ] = useState<TechnologyGroup[]>();
|
||||||
|
const [ lighthouseResults, setLighthouseResults ] = useState<any>();
|
||||||
|
const [ screenshotResult, setScreenshotResult ] = useState<string>();
|
||||||
const [ ipAddress, setIpAddress ] = useState<undefined | string>(undefined);
|
const [ ipAddress, setIpAddress ] = useState<undefined | string>(undefined);
|
||||||
const [ addressType, setAddressType ] = useState<AddressType>('empt');
|
const [ addressType, setAddressType ] = useState<AddressType>('empt');
|
||||||
const { address } = useParams();
|
const { address } = useParams();
|
||||||
@ -75,7 +79,6 @@ const Results = (): JSX.Element => {
|
|||||||
fetch(`/find-url-ip?address=${address}`)
|
fetch(`/find-url-ip?address=${address}`)
|
||||||
.then(function(response) {
|
.then(function(response) {
|
||||||
response.json().then(jsonData => {
|
response.json().then(jsonData => {
|
||||||
console.log('Get IP Address', jsonData);
|
|
||||||
setIpAddress(jsonData.ip);
|
setIpAddress(jsonData.ip);
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
@ -88,6 +91,17 @@ const Results = (): JSX.Element => {
|
|||||||
}
|
}
|
||||||
}, [address]);
|
}, [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 */
|
/* Get IP address location info */
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchIpLocation = () => {
|
const fetchIpLocation = () => {
|
||||||
@ -118,7 +132,6 @@ const Results = (): JSX.Element => {
|
|||||||
fetch(`https://api.shodan.io/shodan/host/${ipAddress}?key=${apiKey}`)
|
fetch(`https://api.shodan.io/shodan/host/${ipAddress}?key=${apiKey}`)
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(response => {
|
.then(response => {
|
||||||
console.log(response);
|
|
||||||
if (!response.error) applyShodanResults(response)
|
if (!response.error) applyShodanResults(response)
|
||||||
})
|
})
|
||||||
.catch(err => console.error(err));
|
.catch(err => console.error(err));
|
||||||
@ -137,7 +150,6 @@ const Results = (): JSX.Element => {
|
|||||||
fetch(endpoint)
|
fetch(endpoint)
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(response => {
|
.then(response => {
|
||||||
console.log(response);
|
|
||||||
setTechnologyResults(makeTechnologies(response));
|
setTechnologyResults(makeTechnologies(response));
|
||||||
});
|
});
|
||||||
}, [address]);
|
}, [address]);
|
||||||
@ -179,6 +191,8 @@ const Results = (): JSX.Element => {
|
|||||||
{ results.serverInfo && <ServerInfoCard {...results.serverInfo} />}
|
{ results.serverInfo && <ServerInfoCard {...results.serverInfo} />}
|
||||||
{ results.hostNames && <HostNamesCard hosts={results.hostNames} />}
|
{ results.hostNames && <HostNamesCard hosts={results.hostNames} />}
|
||||||
{ whoIsResults && <WhoIsCard {...whoIsResults} />}
|
{ whoIsResults && <WhoIsCard {...whoIsResults} />}
|
||||||
|
{ lighthouseResults && <LighthouseCard lighthouse={lighthouseResults} />}
|
||||||
|
{ screenshotResult && <ScreenshotCard screenshot={screenshotResult} />}
|
||||||
{ technologyResults && <BuiltWithCard technologies={technologyResults} />}
|
{ technologyResults && <BuiltWithCard technologies={technologyResults} />}
|
||||||
</ResultsContent>
|
</ResultsContent>
|
||||||
</ResultsOuter>
|
</ResultsOuter>
|
||||||
|
@ -9,6 +9,7 @@ const colors = {
|
|||||||
backgroundLighter: '#1a2332',
|
backgroundLighter: '#1a2332',
|
||||||
bgShadowColor: '#0f1620',
|
bgShadowColor: '#0f1620',
|
||||||
fgShadowColor: '#456602',
|
fgShadowColor: '#456602',
|
||||||
|
primaryTransparent: '#9fef0012',
|
||||||
|
|
||||||
// Action Colors
|
// Action Colors
|
||||||
info: '#04e4f4',
|
info: '#04e4f4',
|
||||||
|
Loading…
Reference in New Issue
Block a user