Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions server.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import fs from 'fs';
import path from 'path';
import { fileURLToPath, pathToFileURL } from 'node:url';
import cors from 'cors';
import dotenv from 'dotenv';
import express from 'express';
Expand Down Expand Up @@ -29,7 +30,7 @@ if (trustProxy) {
app.set('trust proxy', parsed);
}

const __filename = new URL(import.meta.url).pathname;
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

const port = process.env.PORT || 3000; // The port to run the server on
Expand Down Expand Up @@ -100,7 +101,7 @@ apiFiles.forEach(async (dirent) => {
const routeName = dirent.name.split('.')[0];
const route = `${API_DIR}/${routeName}`;

const handlerModule = await import(path.join(dirPath, dirent.name));
const handlerModule = await import(pathToFileURL(path.join(dirPath, dirent.name)).href);
const handler = handlerModule.default || handlerModule;
handlers[route] = handler;

Expand Down Expand Up @@ -210,7 +211,7 @@ if (process.env.DISABLE_GUI && process.env.DISABLE_GUI !== 'false') {
app.use(express.static('dist/client/'));
app.use(async (req, res, next) => {
const ssrHandlerPath = path.join(__dirname, 'dist', 'server', 'entry.mjs');
import(ssrHandlerPath)
import(pathToFileURL(ssrHandlerPath).href)
.then(({ handler: ssrHandler }) => {
ssrHandler(req, res, next);
})
Expand Down
26 changes: 24 additions & 2 deletions src/client/components/Form/Nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import type { ReactNode } from 'react';
import { StyledCard } from 'client/components/Form/Card';
import Heading from 'client/components/Form/Heading';
import colors from 'client/styles/colors';
import LanguageSwitcher from 'client/components/misc/LanguageSwitcher';
import { useLanguage } from 'client/i18n';

const Header = styled(StyledCard)`
margin: 0 auto;
Expand All @@ -16,16 +18,36 @@ const Header = styled(StyledCard)`
width: 95vw;
`;

const HeaderActions = styled.div`
display: flex;
align-items: center;
justify-content: flex-end;
gap: 0.75rem;
min-width: 0;
@media (max-width: 600px) {
width: 100%;
justify-content: space-between;
}
`;

const Nav = (props: { children?: ReactNode }) => {
const { language } = useLanguage();
return (
<Header as="header">
<Heading color={colors.primary} size="large">
<img width="64" src="/favicon.svg" alt="Web Check Icon" />
<img
width="64"
src="/favicon.svg"
alt={language === 'zh-CN' ? 'Web Check 图标' : 'Web Check icon'}
/>
<a href="/" target="_self">
Web Check
</a>
</Heading>
{props.children && props.children}
<HeaderActions>
{props.children && props.children}
<LanguageSwitcher />
</HeaderActions>
</Header>
);
};
Expand Down
28 changes: 16 additions & 12 deletions src/client/components/Form/Row.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { ReactNode } from 'react';
import styled from '@emotion/styled';
import colors from 'client/styles/colors';
import Heading from 'client/components/Form/Heading';
import { localizeText, useLanguage, type Language } from 'client/i18n';

export interface RowProps {
lbl: string;
Expand Down Expand Up @@ -107,18 +108,18 @@ const isDateLike = (value: any): boolean => {
return date >= new Date('1995-01-01') && date <= new Date('2030-12-31');
};

const formatDate = (dateString: string): string => {
return new Intl.DateTimeFormat('en-GB', {
const formatDate = (dateString: string, language: Language): string => {
return new Intl.DateTimeFormat(language === 'zh-CN' ? 'zh-CN' : 'en-GB', {
day: 'numeric',
month: 'long',
year: 'numeric',
}).format(new Date(dateString));
};

const formatValue = (value: any): string => {
if (isDateLike(value)) return formatDate(value);
const formatValue = (value: any, language: Language): string => {
if (isDateLike(value)) return formatDate(value, language);
if (typeof value === 'boolean') return value ? '✅' : '❌';
return value;
return localizeText(value, language);
};

const copyToClipboard = (text: string) => {
Expand All @@ -131,15 +132,16 @@ const snip = (text: string, length: number = 80) => {
};

export const ExpandableRow = (props: RowProps) => {
const { language } = useLanguage();
const { lbl, val, title, rowList, open } = props;
return (
<Details open={open}>
<StyledRow as="summary" key={`${lbl}-${val}`}>
<span className="lbl" title={title?.toString()}>
{lbl}
{localizeText(lbl, language)}
</span>
<span className="val" title={val?.toString()}>
{val.toString()}
{localizeText(val, language)}
</span>
</StyledRow>
{rowList && (
Expand All @@ -148,14 +150,14 @@ export const ExpandableRow = (props: RowProps) => {
return (
<StyledRow as="li" key={`${row.lbl}-${index}`}>
<span className="lbl" title={row.title?.toString()}>
{row.lbl}
{localizeText(row.lbl, language)}
</span>
<span
className="val"
title={row.val?.toString()}
onClick={() => copyToClipboard(row.val)}
>
{formatValue(row.val)}
{formatValue(row.val, language)}
</span>
{row.plaintext && <PlainText>{row.plaintext}</PlainText>}
{row.listResults && (
Expand All @@ -175,11 +177,12 @@ export const ExpandableRow = (props: RowProps) => {
};

export const ListRow = (props: { list: string[]; title: string }) => {
const { language } = useLanguage();
const { list, title } = props;
return (
<>
<Heading as="h4" size="small" align="left" color={colors.primary}>
{title}
{localizeText(title, language)}
</Heading>
{list.map((entry: string, index: number) => {
return (
Expand All @@ -193,17 +196,18 @@ export const ListRow = (props: { list: string[]; title: string }) => {
};

const Row = (props: RowProps) => {
const { language } = useLanguage();
const { lbl, val, title, plaintext, listResults, children } = props;
if (children) return <StyledRow key={`${lbl}-${val}`}>{children}</StyledRow>;
return (
<StyledRow key={`${lbl}-${val}`}>
{lbl && (
<span className="lbl" title={title?.toString()}>
{lbl}
{localizeText(lbl, language)}
</span>
)}
<span className="val" title={val?.toString()} onClick={() => copyToClipboard(val)}>
{formatValue(val)}
{formatValue(val, language)}
</span>
{plaintext && <PlainText>{plaintext}</PlainText>}
{listResults && (
Expand Down
8 changes: 6 additions & 2 deletions src/client/components/Results/CarbonFootprint.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import styled from '@emotion/styled';
import { Card } from 'client/components/Form/Card';
import Row from 'client/components/Form/Row';
import colors from 'client/styles/colors';
import { localizeText, useLanguage } from 'client/i18n';

const LearnMoreInfo = styled.p`
font-size: 0.8rem;
Expand Down Expand Up @@ -31,12 +32,15 @@ const formatKwh = (kwh: number): string => {
};

const CarbonCard = (props: { data: any; title: string; actionButtons: any }): JSX.Element => {
const { language } = useLanguage();
const carbons = props.data.statistics;
const cleanerThan = props.data.cleanerThan;

return (
<Card heading={props.title} actionButtons={props.actionButtons}>
{!carbons?.adjustedBytes && <p>Unable to calculate carbon footprint for host</p>}
{!carbons?.adjustedBytes && (
<p>{localizeText('Unable to calculate carbon footprint for host', language)}</p>
)}
{carbons?.adjustedBytes > 0 && (
<>
{props.data.bytes > 0 && (
Expand All @@ -52,7 +56,7 @@ const CarbonCard = (props: { data: any; title: string; actionButtons: any }): JS
)}
<br />
<LearnMoreInfo>
Calculated using the{' '}
{language === 'zh-CN' ? '根据以下模型计算:' : 'Calculated using the '}
<a
href="https://sustainablewebdesign.org/estimating-digital-emissions"
target="_blank"
Expand Down
4 changes: 3 additions & 1 deletion src/client/components/Results/DomainLookup.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import colors from 'client/styles/colors';
import { Card } from 'client/components/Form/Card';
import Row from 'client/components/Form/Row';
import { localizeText, useLanguage } from 'client/i18n';

const cardStyles = `
span.val {
Expand All @@ -10,6 +11,7 @@ span.val {
`;

const DomainLookupCard = (props: { data: any; title: string; actionButtons: any }): JSX.Element => {
const { language } = useLanguage();
const d = props.data || {};
return (
<Card heading={props.title} actionButtons={props.actionButtons} styles={cardStyles}>
Expand All @@ -21,7 +23,7 @@ const DomainLookupCard = (props: { data: any; title: string; actionButtons: any
{d.registrarWhoisServer && <Row lbl="Registrar WHOIS Server" val={d.registrarWhoisServer} />}
{d.registrar && (
<Row lbl="" val="">
<span className="lbl">Registrar</span>
<span className="lbl">{localizeText('Registrar', language)}</span>
<span className="val">
<a href={d.registrarUrl || '#'}>{d.registrar}</a>
</span>
Expand Down
7 changes: 5 additions & 2 deletions src/client/components/Results/Firewall.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import styled from '@emotion/styled';
import { Card } from 'client/components/Form/Card';
import Row from 'client/components/Form/Row';
import { useLanguage } from 'client/i18n';

const Note = styled.small`
opacity: 0.5;
Expand All @@ -9,15 +10,17 @@ const Note = styled.small`
`;

const FirewallCard = (props: { data: any; title: string; actionButtons: any }): JSX.Element => {
const { language } = useLanguage();
const data = props.data;
return (
<Card heading={props.title} actionButtons={props.actionButtons}>
<Row lbl="Firewall" val={data.hasWaf ? '✅ Yes' : '❌ No*'} />
{data.waf && <Row lbl="WAF" val={data.waf} />}
{!data.hasWaf && (
<Note>
*The domain may be protected with a proprietary or custom WAF which we were unable to
identify automatically
{language === 'zh-CN'
? '*该域名可能使用专有或自定义 WAF,当前检查无法自动识别。'
: '*The domain may be protected with a proprietary or custom WAF which we were unable to identify automatically'}
</Note>
)}
</Card>
Expand Down
4 changes: 3 additions & 1 deletion src/client/components/Results/OpenPorts.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { Card } from 'client/components/Form/Card';
import Row from 'client/components/Form/Row';
import { useLanguage } from 'client/i18n';

const cardStyles = `
small { margin-top: 1rem; opacity: 0.5; }
`;

const OpenPortsCard = (props: { data: any; title: string; actionButtons: any }): JSX.Element => {
const { language } = useLanguage();
const portData = props.data;
return (
<Card heading={props.title} actionButtons={props.actionButtons} styles={cardStyles}>
Expand All @@ -16,7 +18,7 @@ const OpenPortsCard = (props: { data: any; title: string; actionButtons: any }):
))}
<br />
<small>
Unable to establish connections to:
{language === 'zh-CN' ? '无法建立连接的端口:' : 'Unable to establish connections to:'}
<br />
{portData.failedPorts.join(', ')}
</small>
Expand Down
6 changes: 5 additions & 1 deletion src/client/components/Results/Redirects.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import colors from 'client/styles/colors';
import { Card } from 'client/components/Form/Card';
import Row from 'client/components/Form/Row';
import { useLanguage } from 'client/i18n';

const cardStyles = `
div {
Expand All @@ -20,6 +21,7 @@ const cardStyles = `
`;

const RedirectsCard = (props: { data: any; title: string; actionButtons: any }): JSX.Element => {
const { language } = useLanguage();
// The API chain includes the original URL as its first entry
const chain = props.data?.redirects || [];
const count = Math.max(chain.length - 1, 0);
Expand All @@ -29,7 +31,9 @@ const RedirectsCard = (props: { data: any; title: string; actionButtons: any }):
{count > 0 && (
<>
<p className="redirect-count">
Followed {count} redirect{count === 1 ? '' : 's'} when contacting host
{language === 'zh-CN'
? `访问目标时经历了 ${count} 次重定向`
: `Followed ${count} redirect${count === 1 ? '' : 's'} when contacting host`}
</p>
{chain.slice(1).map((redirect: any, index: number) => (
<Row lbl="" val="" key={index}>
Expand Down
4 changes: 3 additions & 1 deletion src/client/components/Results/RobotsTxt.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Card } from 'client/components/Form/Card';
import Row, { type RowProps } from 'client/components/Form/Row';
import { localizeText, useLanguage } from 'client/i18n';

const cardStyles = `
grid-row: span 2;
Expand All @@ -14,13 +15,14 @@ const RobotsTxtCard = (props: {
title: string;
actionButtons: any;
}): JSX.Element => {
const { language } = useLanguage();
const { data } = props;
const robots = data?.robots || [];

return (
<Card heading={props.title} actionButtons={props.actionButtons} styles={cardStyles}>
<div className="content">
{robots.length === 0 && <p>No crawl rules found.</p>}
{robots.length === 0 && <p>{localizeText('No crawl rules found.', language)}</p>}
{robots.map((row: RowProps, index: number) => {
return <Row key={`${row.lbl}-${index}`} lbl={row.lbl} val={row.val} />;
})}
Expand Down
11 changes: 5 additions & 6 deletions src/client/components/Results/Screenshot.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Card } from 'client/components/Form/Card';
import { useLanguage } from 'client/i18n';

const cardStyles = `
overflow: auto;
Expand All @@ -17,14 +18,12 @@ const ScreenshotCard = (props: {
actionButtons: any;
}): JSX.Element => {
const screenshot = props.data;
const { language } = useLanguage();
const alt = language === 'zh-CN' ? '完整页面截图' : 'Full-page screenshot';
return (
<Card heading={props.title} actionButtons={props.actionButtons} styles={cardStyles}>
{screenshot.image && (
<img src={`data:image/png;base64,${screenshot.image}`} alt="Full page screenshot" />
)}
{!screenshot.image && screenshot.data && (
<img src={screenshot.data} alt="Full page screenshot" />
)}
{screenshot.image && <img src={`data:image/png;base64,${screenshot.image}`} alt={alt} />}
{!screenshot.image && screenshot.data && <img src={screenshot.data} alt={alt} />}
</Card>
);
};
Expand Down
Loading