diff --git a/src/components/Form/Card.tsx b/src/components/Form/Card.tsx
index 60a0cdd..c7f66c7 100644
--- a/src/components/Form/Card.tsx
+++ b/src/components/Form/Card.tsx
@@ -8,7 +8,6 @@ export const Card = styled.section`
box-shadow: 4px 4px 0px ${colors.bgShadowColor};
border-radius: 8px;
padding: 1rem;
- margin: 1rem;
`;
// interface CardProps {
diff --git a/src/components/Results/BuiltWith.tsx b/src/components/Results/BuiltWith.tsx
new file mode 100644
index 0000000..781c1d5
--- /dev/null
+++ b/src/components/Results/BuiltWith.tsx
@@ -0,0 +1,69 @@
+
+import styled from 'styled-components';
+import { TechnologyGroup, Technology } from 'utils/result-processor';
+import colors from 'styles/colors';
+import Card from 'components/Form/Card';
+import Heading from 'components/Form/Heading';
+
+const Outer = styled(Card)`
+ grid-row: span 2
+`;
+
+const Row = styled.div`
+ display: flex;
+ justify-content: space-between;
+ padding: 0.25rem;
+ &:not(:last-child) { border-bottom: 1px solid ${colors.primary}; }
+ span.lbl { font-weight: bold; }
+ span.val {
+ max-width: 200px;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+`;
+
+const DataRow = (props: { lbl: string, val: string }) => {
+ const { lbl, val } = props;
+ return (
+
+ {lbl}
+ {val}
+
+ );
+};
+
+const ListRow = (props: { list: Technology[], title: string }) => {
+ const { list, title } = props;
+ return (
+ <>
+ {title}
+ { list.map((entry: Technology, index: number) => {
+ return (
+ { entry.Name }
+ )}
+ )}
+ >
+);
+}
+
+const BuiltWithCard = (props: { technologies: TechnologyGroup[] }): JSX.Element => {
+ // const { created, updated, expires, nameservers } = whois;
+ const { technologies } = props;
+ return (
+
+ Technologies
+ { technologies.map((group: TechnologyGroup) => {
+ return (
+
+ );
+ })}
+ {/* { created && }
+ { updated && }
+ { expires && }
+ { nameservers && } */}
+
+ );
+}
+
+export default BuiltWithCard;
diff --git a/src/components/Results/HostNames.tsx b/src/components/Results/HostNames.tsx
index 60db257..2720c00 100644
--- a/src/components/Results/HostNames.tsx
+++ b/src/components/Results/HostNames.tsx
@@ -6,7 +6,8 @@ import Card from 'components/Form/Card';
import Heading from 'components/Form/Heading';
const Outer = styled(Card)`
- max-width: 24rem;
+ max-height: 20rem;
+ overflow: auto;
`;
const Row = styled.div`
diff --git a/src/components/Results/ServerInfo.tsx b/src/components/Results/ServerInfo.tsx
index 376d7b2..ddac52e 100644
--- a/src/components/Results/ServerInfo.tsx
+++ b/src/components/Results/ServerInfo.tsx
@@ -5,9 +5,7 @@ import colors from 'styles/colors';
import Card from 'components/Form/Card';
import Heading from 'components/Form/Heading';
-const Outer = styled(Card)`
- max-width: 24rem;
-`;
+const Outer = styled(Card)``;
const Row = styled.div`
display: flex;
@@ -34,7 +32,7 @@ const DataRow = (props: { lbl: string, val: string }) => {
};
const ServerInfoCard = (info: ServerInfo): JSX.Element => {
- const { org, asn, isp, os } = info;
+ const { org, asn, isp, os, ports } = info;
return (
Server Info
@@ -42,6 +40,7 @@ const ServerInfoCard = (info: ServerInfo): JSX.Element => {
{ (isp && isp !== org) && }
{ os && }
{ asn && }
+ { ports && }
);
}
diff --git a/src/components/Results/ServerLocation.tsx b/src/components/Results/ServerLocation.tsx
index cbbdb50..50bebf5 100644
--- a/src/components/Results/ServerLocation.tsx
+++ b/src/components/Results/ServerLocation.tsx
@@ -9,7 +9,7 @@ import Flag from 'components/misc/Flag';
import { TextSizes } from 'styles/typography';
const Outer = styled(Card)`
- max-width: 24rem;
+ grid-row: span 2
`;
const Row = styled.div`
diff --git a/src/components/Results/WhoIs.tsx b/src/components/Results/WhoIs.tsx
new file mode 100644
index 0000000..df41820
--- /dev/null
+++ b/src/components/Results/WhoIs.tsx
@@ -0,0 +1,71 @@
+
+import styled from 'styled-components';
+import { Whois } from 'utils/result-processor';
+import colors from 'styles/colors';
+import Card from 'components/Form/Card';
+import Heading from 'components/Form/Heading';
+
+const Outer = styled(Card)``;
+
+const Row = styled.div`
+ display: flex;
+ justify-content: space-between;
+ padding: 0.25rem;
+ &:not(:last-child) { border-bottom: 1px solid ${colors.primary}; }
+ span.lbl { font-weight: bold; }
+ span.val {
+ max-width: 200px;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+`;
+
+const formatDate = (dateString: string): string => {
+ const date = new Date(dateString);
+ const formatter = new Intl.DateTimeFormat('en-GB', {
+ day: 'numeric',
+ month: 'long',
+ year: 'numeric'
+ });
+ return formatter.format(date);
+}
+
+const DataRow = (props: { lbl: string, val: string }) => {
+ const { lbl, val } = props;
+ return (
+
+ {lbl}
+ {val}
+
+ );
+};
+
+const ListRow = (props: { list: string[], title: string }) => {
+ const { list, title } = props;
+ return (
+ <>
+ {title}
+ { list.map((entry: string, index: number) => {
+ return (
+ { entry }
+ )}
+ )}
+ >
+);
+}
+
+const ServerInfoCard = (whois: Whois): JSX.Element => {
+ const { created, updated, expires, nameservers } = whois;
+ return (
+
+ Who Is Info
+ { created && }
+ { updated && }
+ { expires && }
+ { nameservers && }
+
+ );
+}
+
+export default ServerInfoCard;
diff --git a/src/pages/Results.tsx b/src/pages/Results.tsx
index b319a92..b06e12c 100644
--- a/src/pages/Results.tsx
+++ b/src/pages/Results.tsx
@@ -8,6 +8,8 @@ import Card from 'components/Form/Card';
import ServerLocationCard from 'components/Results/ServerLocation';
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 keys from 'utils/get-keys';
import { determineAddressType, AddressType } from 'utils/address-type-checker';
@@ -15,6 +17,8 @@ import {
getLocation, ServerLocation,
getServerInfo, ServerInfo,
getHostNames, HostNames,
+ makeTechnologies, TechnologyGroup,
+ Whois,
} from 'utils/result-processor';
const ResultsOuter = styled.div`
@@ -24,13 +28,19 @@ const ResultsOuter = styled.div`
const ResultsContent = styled.section`
width: 95vw;
- display: flex;
- flex-wrap: wrap;
+
+ display: grid;
+ grid-auto-flow: dense;
+ grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
+ gap: 1rem;
+ margin: auto;
+ width: calc(100% - 2rem);
`;
const Header = styled(Card)`
margin: 1rem;
display: flex;
+ flex-wrap: wrap;
align-items: baseline;
justify-content: space-between;
padding: 0.5rem 1rem;
@@ -45,6 +55,8 @@ interface ResultsType {
const Results = (): JSX.Element => {
const [ results, setResults ] = useState({});
const [ locationResults, setLocationResults ] = useState();
+ const [ whoIsResults, setWhoIsResults ] = useState();
+ const [ technologyResults, setTechnologyResults ] = useState();
const [ ipAddress, setIpAddress ] = useState(undefined);
const [ addressType, setAddressType ] = useState('empt');
const { address } = useParams();
@@ -62,7 +74,6 @@ const Results = (): JSX.Element => {
const fetchIpAddress = () => {
fetch(`/find-url-ip?address=${address}`)
.then(function(response) {
- console.log(response);
response.json().then(jsonData => {
console.log('Get IP Address', jsonData);
setIpAddress(jsonData.ip);
@@ -107,6 +118,7 @@ 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));
@@ -117,11 +129,29 @@ const Results = (): JSX.Element => {
fetchShodanData();
}
}, [ipAddress]);
+
+ /* Get BuiltWith tech stack */
+ useEffect(() => {
+ const apiKey = keys.builtWith;
+ const endpoint = `https://api.builtwith.com/v21/api.json?KEY=${apiKey}&LOOKUP=${address}`;
+ fetch(endpoint)
+ .then(response => response.json())
+ .then(response => {
+ console.log(response);
+ setTechnologyResults(makeTechnologies(response));
+ });
+ }, [address]);
/* Get WhoIs info for a given domain name */
useEffect(() => {
const applyWhoIsResults = (response: any) => {
- console.log('WhoIs Response', response);
+ const whoIsResults: Whois = {
+ created: response.date_created,
+ expires: response.date_expires,
+ updated: response.date_updated,
+ nameservers: response.nameservers,
+ };
+ setWhoIsResults(whoIsResults);
}
const fetchWhoIsData = () => {
const apiKey = keys.whoApi;
@@ -148,6 +178,8 @@ const Results = (): JSX.Element => {
{ locationResults && }
{ results.serverInfo && }
{ results.hostNames && }
+ { whoIsResults && }
+ { technologyResults && }
);
diff --git a/src/utils/get-keys.ts b/src/utils/get-keys.ts
index 301f29e..4ef07ec 100644
--- a/src/utils/get-keys.ts
+++ b/src/utils/get-keys.ts
@@ -1,7 +1,8 @@
const keys = {
- shodan: process.env.SHODAN_API_KEY,
- whoApi: process.env.WHO_API_KEY,
+ shodan: process.env.REACT_APP_SHODAN_API_KEY,
+ whoApi: process.env.REACT_APP_WHO_API_KEY,
+ builtWith: process.env.REACT_APP_BUILT_WITH_API_KEY,
};
export default keys;
diff --git a/src/utils/result-processor.ts b/src/utils/result-processor.ts
index 0f390ee..f5b84ce 100644
--- a/src/utils/result-processor.ts
+++ b/src/utils/result-processor.ts
@@ -19,6 +19,13 @@ export interface ServerLocation {
countryPopulation: number,
};
+export interface Whois {
+ created: string,
+ expires: string,
+ updated: string,
+ nameservers: string[],
+}
+
export const getLocation = (response: any): ServerLocation => {
return {
city: response.city,
@@ -48,6 +55,8 @@ export interface ServerInfo {
asn: string,
isp: string,
os?: string,
+ ip?: string,
+ ports?: string,
};
export const getServerInfo = (response: any): ServerInfo => {
@@ -56,6 +65,8 @@ export const getServerInfo = (response: any): ServerInfo => {
asn: response.asn,
isp: response.isp,
os: response.os,
+ ip: response.ip_str,
+ ports: response.ports.toString(),
};
};
@@ -67,17 +78,37 @@ export interface HostNames {
export const getHostNames = (response: any): HostNames => {
const { hostnames, domains } = response;
const results: HostNames = {
- domains: [],
- hostnames: [],
+ domains: domains || [],
+ hostnames: hostnames || [],
};
- if (!hostnames || !domains) return results;
- hostnames.forEach((host: string) => {
- if (domains.includes(host)) {
- results.domains.push(host);
- } else {
- results.hostnames.push(host);
- }
- });
-
return results;
};
+
+export interface Technology {
+ Categories?: string[];
+ Parent?: string;
+ Name: string;
+ Description: string;
+ Link: string;
+ Tag: string;
+ FirstDetected: number;
+ LastDetected: number;
+ IsPremium: string;
+}
+
+export interface TechnologyGroup {
+ tag: string;
+ technologies: Technology[];
+}
+
+export const makeTechnologies = (response: any): TechnologyGroup[] => {
+ let flatArray = response.Results[0].Result.Paths
+ .reduce((accumulator: any, obj: any) => accumulator.concat(obj.Technologies), []);
+ let technologies = flatArray.reduce((groups: any, item: any) => {
+ let tag = item.Tag;
+ if (!groups[tag]) groups[tag] = [];
+ groups[tag].push(item);
+ return groups;
+ }, {});
+ return technologies;
+};