mirror of
https://github.com/bluewave-labs/Checkmate.git
synced 2026-05-21 00:48:45 -05:00
pagespeed v2 refactor
This commit is contained in:
Generated
+1
-29
@@ -26,7 +26,6 @@
|
||||
"joi": "17.13.3",
|
||||
"lucide-react": "^0.562.0",
|
||||
"mui-color-input": "^6.0.0",
|
||||
"pretty-ms": "9.3.0",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-hook-form": "7.63.0",
|
||||
@@ -38,7 +37,7 @@
|
||||
"react-toastify": "^10.0.5",
|
||||
"recharts": "2.15.2",
|
||||
"redux-persist": "6.0.0",
|
||||
"swr": "2.3.6",
|
||||
"swr": "^2.3.6",
|
||||
"vite-plugin-svgr": "^4.2.0",
|
||||
"zod": "4.1.11"
|
||||
},
|
||||
@@ -5780,18 +5779,6 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/parse-ms": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz",
|
||||
"integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/path-exists": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
|
||||
@@ -5919,21 +5906,6 @@
|
||||
"url": "https://github.com/prettier/prettier?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/pretty-ms": {
|
||||
"version": "9.3.0",
|
||||
"resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.3.0.tgz",
|
||||
"integrity": "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"parse-ms": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/prop-types": {
|
||||
"version": "15.8.1",
|
||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||
|
||||
@@ -31,7 +31,6 @@
|
||||
"joi": "17.13.3",
|
||||
"lucide-react": "^0.562.0",
|
||||
"mui-color-input": "^6.0.0",
|
||||
"pretty-ms": "9.3.0",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-hook-form": "7.63.0",
|
||||
|
||||
@@ -34,7 +34,6 @@ export const MonitorStatus = ({ monitor }: { monitor: Monitor }) => {
|
||||
>
|
||||
<PulseDot color={getStatusColor(monitor.status, theme)} />
|
||||
<Typography
|
||||
color={theme.palette.text.secondary}
|
||||
fontSize={typographyLevels.l}
|
||||
fontWeight={"bolder"}
|
||||
fontFamily={"monospace"}
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
import { BaseChart } from "@/Components/v2/design-elements";
|
||||
import { TrendingUp } from "lucide-react";
|
||||
import { XTick } from "@/Components/v2/monitors/";
|
||||
import {
|
||||
XAxis,
|
||||
AreaChart,
|
||||
Area,
|
||||
Tooltip,
|
||||
CartesianGrid,
|
||||
ResponsiveContainer,
|
||||
} from "recharts";
|
||||
import { HistogramPageSpeedScoresTooltip } from "@/Components/v2/monitors";
|
||||
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import type { Check } from "@/Types/Check";
|
||||
import type { Palette } from "@mui/material/styles";
|
||||
type PaletteColorKey = Extract<
|
||||
keyof Palette,
|
||||
"primary" | "error" | "success" | "warning"
|
||||
>;
|
||||
|
||||
export interface ConfigItem {
|
||||
id: string;
|
||||
text: string;
|
||||
palette: PaletteColorKey;
|
||||
}
|
||||
|
||||
const config: Record<string, ConfigItem> = {
|
||||
seo: {
|
||||
id: "seo",
|
||||
text: "SEO",
|
||||
palette: "primary",
|
||||
},
|
||||
performance: {
|
||||
id: "performance",
|
||||
text: "performance",
|
||||
palette: "success",
|
||||
},
|
||||
bestPractices: {
|
||||
id: "bestPractices",
|
||||
text: "best practices",
|
||||
palette: "warning",
|
||||
},
|
||||
accessibility: {
|
||||
id: "accessibility",
|
||||
text: "accessibility",
|
||||
palette: "error",
|
||||
},
|
||||
};
|
||||
|
||||
export const HistogramPageSpeedDetails = ({
|
||||
checks,
|
||||
range,
|
||||
}: {
|
||||
checks: Check[];
|
||||
range: string;
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<BaseChart
|
||||
icon={
|
||||
<TrendingUp
|
||||
size={20}
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
}
|
||||
title="Score history"
|
||||
>
|
||||
<ResponsiveContainer
|
||||
width="100%"
|
||||
minWidth={25}
|
||||
height={215}
|
||||
>
|
||||
<AreaChart data={checks}>
|
||||
<XAxis
|
||||
dataKey={"createdAt"}
|
||||
tick={(props) => (
|
||||
<XTick
|
||||
{...props}
|
||||
range={range}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<CartesianGrid
|
||||
stroke={theme.palette.divider}
|
||||
strokeWidth={1}
|
||||
strokeOpacity={1}
|
||||
fill="transparent"
|
||||
vertical={false}
|
||||
/>
|
||||
<Tooltip
|
||||
cursor={{ stroke: theme.palette.divider }}
|
||||
content={<HistogramPageSpeedScoresTooltip config={config} />}
|
||||
/>
|
||||
<defs>
|
||||
{Object.values(config).map(({ id, palette }) => {
|
||||
const startColor = theme.palette[palette].main;
|
||||
const endColor = theme.palette[palette].light;
|
||||
|
||||
return (
|
||||
<linearGradient
|
||||
id={id}
|
||||
x1="0"
|
||||
y1="0"
|
||||
x2="0"
|
||||
y2="1"
|
||||
key={id}
|
||||
>
|
||||
<stop
|
||||
offset="0%"
|
||||
stopColor={startColor}
|
||||
stopOpacity={0.8}
|
||||
/>
|
||||
<stop
|
||||
offset="100%"
|
||||
stopColor={endColor}
|
||||
stopOpacity={0}
|
||||
/>
|
||||
</linearGradient>
|
||||
);
|
||||
})}
|
||||
</defs>
|
||||
{Object.keys(config).map((key) => {
|
||||
const { palette } = config[key];
|
||||
const strokeColor = theme.palette[palette].main;
|
||||
const bgColor = theme.palette.primary.main;
|
||||
|
||||
return (
|
||||
<Area
|
||||
connectNulls
|
||||
key={key}
|
||||
dataKey={key}
|
||||
stackId={1}
|
||||
stroke={strokeColor}
|
||||
fill={`url(#${config[key].id})`}
|
||||
activeDot={{ stroke: bgColor, fill: strokeColor, r: 4.5 }}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</AreaChart>
|
||||
</ResponsiveContainer>
|
||||
</BaseChart>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,78 @@
|
||||
import Box from "@mui/material/Box";
|
||||
import Stack from "@mui/material/Stack";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import { formatDateWithTz } from "@/Utils/TimeUtils";
|
||||
import type { ConfigItem } from "@/Components/v2/monitors";
|
||||
import type { TooltipProps } from "recharts";
|
||||
import { useSelector } from "react-redux";
|
||||
import type { RootState } from "@/Types/state";
|
||||
|
||||
interface HistogramPageSpeedScoresTooltipProps
|
||||
extends Partial<TooltipProps<number, string>> {
|
||||
config: Record<string, ConfigItem>;
|
||||
}
|
||||
export const HistogramPageSpeedScoresTooltip = ({
|
||||
active,
|
||||
payload,
|
||||
label,
|
||||
config,
|
||||
}: HistogramPageSpeedScoresTooltipProps) => {
|
||||
const theme = useTheme();
|
||||
const uiTimezone = useSelector((state: RootState) => state.ui.timezone);
|
||||
|
||||
if (active && payload && payload.length) {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
border: 1,
|
||||
borderColor: theme.palette.divider,
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
py: theme.spacing(2),
|
||||
px: theme.spacing(4),
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
color: theme.palette.text.secondary,
|
||||
fontSize: 12,
|
||||
fontWeight: 500,
|
||||
}}
|
||||
>
|
||||
{formatDateWithTz(label, "ddd, MMMM D, YYYY, h:mm A", uiTimezone)}
|
||||
</Typography>
|
||||
{Object.keys(config)
|
||||
.reverse()
|
||||
.map((key) => {
|
||||
const { palette } = config[key];
|
||||
const dotColor = theme.palette[palette].main;
|
||||
|
||||
return (
|
||||
<Stack
|
||||
key={`${key}-tooltip`}
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
gap={theme.spacing(3)}
|
||||
mt={theme.spacing(1)}
|
||||
>
|
||||
<Box
|
||||
width={theme.spacing(4)}
|
||||
height={theme.spacing(4)}
|
||||
sx={{ borderRadius: "50%", backgroundColor: dotColor }}
|
||||
/>
|
||||
<Typography
|
||||
textTransform="capitalize"
|
||||
sx={{ opacity: 0.8 }}
|
||||
>
|
||||
{config[key].text}
|
||||
</Typography>
|
||||
<Typography>{Math.floor(payload[0].payload[key])}</Typography>
|
||||
</Stack>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
+17
-15
@@ -1,5 +1,6 @@
|
||||
import { BaseChart } from "@/Components/v2/design-elements";
|
||||
import { FileText } from "lucide-react";
|
||||
import type { Check, CheckAudits } from "@/Types/Check";
|
||||
import { Pie, PieChart, ResponsiveContainer, Label } from "recharts";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import Box from "@mui/material/Box";
|
||||
@@ -29,19 +30,20 @@ const CenterLabel = ({ viewBox, value }: any) => {
|
||||
);
|
||||
};
|
||||
|
||||
export const PiePageSpeed = ({ latestCheck }: { latestCheck: any }) => {
|
||||
export const PiePageSpeed = ({ latestCheck }: { latestCheck?: Check }) => {
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
const [hoverTitle, setHoverTitle] = useState<string | null>(null);
|
||||
|
||||
if (!latestCheck) return null;
|
||||
const LABELS: Record<string, string> = {
|
||||
FCP: t("common.charts.pageSpeed.fcp"),
|
||||
SI: t("common.charts.pageSpeed.si"),
|
||||
LCP: t("common.charts.pageSpeed.lcp"),
|
||||
TBT: t("common.charts.pageSpeed.tbt"),
|
||||
CLS: t("common.charts.pageSpeed.cls"),
|
||||
FCP: t("pages.pageSpeed.charts.common.fcp"),
|
||||
SI: t("pages.pageSpeed.charts.common.si"),
|
||||
LCP: t("pages.pageSpeed.charts.common.lcp"),
|
||||
TBT: t("pages.pageSpeed.charts.common.tbt"),
|
||||
CLS: t("pages.pageSpeed.charts.common.cls"),
|
||||
};
|
||||
const metrics = [
|
||||
const metrics: { key: keyof CheckAudits; color: string; weight: number }[] = [
|
||||
{ key: "fcp", color: alpha("#1DE9B6", 0.6), weight: 0.1 },
|
||||
{ key: "si", color: alpha("#7C4DFF", 0.6), weight: 0.1 },
|
||||
{ key: "lcp", color: alpha("#FFB200", 0.6), weight: 0.25 },
|
||||
@@ -50,7 +52,8 @@ export const PiePageSpeed = ({ latestCheck }: { latestCheck: any }) => {
|
||||
];
|
||||
|
||||
const scores = metrics.flatMap(({ key, color, weight }) => {
|
||||
const val = Math.floor((latestCheck?.[key] ?? 0) * 100);
|
||||
const audit = latestCheck?.audits?.[key];
|
||||
const val = Math.floor((audit?.score ?? 0) * 100);
|
||||
const inverse = 100 - val;
|
||||
return [
|
||||
{
|
||||
@@ -71,12 +74,11 @@ export const PiePageSpeed = ({ latestCheck }: { latestCheck: any }) => {
|
||||
});
|
||||
|
||||
const totalScore =
|
||||
(latestCheck?.fcp || 0) * 0.1 +
|
||||
(latestCheck?.si || 0) * 0.1 +
|
||||
(latestCheck?.lcp || 0) * 0.25 +
|
||||
(latestCheck?.tbt || 0) * 0.3 +
|
||||
(latestCheck?.cls || 0) * 0.25;
|
||||
|
||||
(latestCheck.audits?.fcp?.score || 0) * 0.1 +
|
||||
(latestCheck.audits?.si?.score || 0) * 0.1 +
|
||||
(latestCheck.audits?.lcp?.score || 0) * 0.25 +
|
||||
(latestCheck.audits?.tbt?.score || 0) * 0.3 +
|
||||
(latestCheck.audits?.cls?.score || 0) * 0.25;
|
||||
const pageSpeedPalette = getPageSpeedPalette(Math.floor(totalScore * 100));
|
||||
|
||||
const score = [
|
||||
@@ -95,7 +97,7 @@ export const PiePageSpeed = ({ latestCheck }: { latestCheck: any }) => {
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
}
|
||||
title={t("common.charts.pageSpeed.title")}
|
||||
title={t("pages.pageSpeed.charts.pie.title")}
|
||||
>
|
||||
<Tooltip
|
||||
open={Boolean(hoverTitle)}
|
||||
@@ -0,0 +1,105 @@
|
||||
import Stack from "@mui/material/Stack";
|
||||
import Box from "@mui/material/Box";
|
||||
import { BarChart3 } from "lucide-react";
|
||||
import { BaseChart } from "@/Components/v2/design-elements";
|
||||
import Typography from "@mui/material/Typography";
|
||||
|
||||
import type { Check } from "@/Types/Check";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { getPageSpeedPalette } from "@/Utils/MonitorUtils";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
|
||||
const MetricBox = ({
|
||||
label,
|
||||
value,
|
||||
weight,
|
||||
}: {
|
||||
label: string;
|
||||
value: number;
|
||||
weight: number;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
const palette = getPageSpeedPalette(value);
|
||||
return (
|
||||
<Stack
|
||||
direction={"row"}
|
||||
sx={{
|
||||
border: 1,
|
||||
borderStyle: "solid",
|
||||
borderColor: theme.palette.divider,
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
}}
|
||||
>
|
||||
<Stack
|
||||
flex={1}
|
||||
p={theme.spacing(4)}
|
||||
>
|
||||
<Typography textTransform={"uppercase"}>{label}</Typography>
|
||||
<Stack
|
||||
direction="row"
|
||||
justifyContent={"space-between"}
|
||||
>
|
||||
<Typography>{`${value}%`}</Typography>
|
||||
<Typography>{`${t("pages.pageSpeed.charts.legend.weight")}: ${weight}%`}</Typography>
|
||||
</Stack>
|
||||
</Stack>
|
||||
<Box
|
||||
width={4}
|
||||
bgcolor={theme.palette[palette].light}
|
||||
sx={{
|
||||
borderTopRightRadius: theme.shape.borderRadius,
|
||||
borderBottomRightRadius: theme.shape.borderRadius,
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export const PiePageSpeedLegend = ({ latestCheck }: { latestCheck?: Check }) => {
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (!latestCheck) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<BaseChart
|
||||
icon={
|
||||
<BarChart3
|
||||
size={20}
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
}
|
||||
title={t("pages.pageSpeed.charts.legend.title")}
|
||||
>
|
||||
<Stack gap={theme.spacing(4)}>
|
||||
<MetricBox
|
||||
label={t("pages.pageSpeed.charts.common.si")}
|
||||
value={Math.floor((latestCheck.audits?.si?.score || 0) * 100)}
|
||||
weight={10}
|
||||
/>
|
||||
<MetricBox
|
||||
label={t("pages.pageSpeed.charts.common.fcp")}
|
||||
value={Math.floor((latestCheck.audits?.fcp?.score || 0) * 100)}
|
||||
weight={10}
|
||||
/>
|
||||
<MetricBox
|
||||
label={t("pages.pageSpeed.charts.common.cls")}
|
||||
value={Math.floor((latestCheck.audits?.cls?.score || 0) * 100)}
|
||||
weight={25}
|
||||
/>
|
||||
<MetricBox
|
||||
label={t("pages.pageSpeed.charts.common.tbt")}
|
||||
value={Math.floor((latestCheck.audits?.tbt?.score || 0) * 100)}
|
||||
weight={30}
|
||||
/>
|
||||
<MetricBox
|
||||
label={t("pages.pageSpeed.charts.common.lcp")}
|
||||
value={Math.floor((latestCheck.audits?.lcp?.score || 0) * 100)}
|
||||
weight={25}
|
||||
/>
|
||||
</Stack>
|
||||
</BaseChart>
|
||||
);
|
||||
};
|
||||
@@ -5,4 +5,7 @@ export * from "./charts/RadialAvgResponse";
|
||||
export * from "./charts/HistogramDetails";
|
||||
export * from "./charts/HistogramPageSpeed";
|
||||
export * from "./charts/HistogramPageSpeedTooltip";
|
||||
export * from "./charts/PiePagespeed";
|
||||
export * from "./charts/PiePageSpeed";
|
||||
export * from "./charts/PiePageSpeedLegend";
|
||||
export * from "./charts/HistogramPageSpeedDetails";
|
||||
export * from "./charts/HistogramPageSpeedDetailsTooltip";
|
||||
|
||||
@@ -1,7 +1,62 @@
|
||||
import { BasePage } from "@/Components/v2/design-elements";
|
||||
import type { PageSpeedDetailsResponse } from "@/Types/Monitor";
|
||||
import { HeaderMonitorControls } from "@/Components/v2/common";
|
||||
import Stack from "@mui/material/Stack";
|
||||
import {
|
||||
HistogramPageSpeedDetails,
|
||||
PiePageSpeed,
|
||||
PiePageSpeedLegend,
|
||||
MonitorStatBoxes,
|
||||
} from "@/Components/v2/monitors";
|
||||
|
||||
import { useIsAdmin } from "@/Hooks/useIsAdmin";
|
||||
import { useGet } from "@/Hooks/UseApi";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useTheme } from "@mui/material";
|
||||
|
||||
const PageSpeedDetails = () => {
|
||||
return <BasePage>PageSpeed Details - Work in Progress</BasePage>;
|
||||
};
|
||||
const { monitorId } = useParams();
|
||||
const isAdmin = useIsAdmin();
|
||||
const theme = useTheme();
|
||||
const {
|
||||
data: monitorData,
|
||||
isLoading,
|
||||
error,
|
||||
refetch,
|
||||
} = useGet<PageSpeedDetailsResponse>(
|
||||
monitorId ? `/monitors/pagespeed/details/${monitorId}?dateRange=day` : null
|
||||
);
|
||||
|
||||
const monitor = monitorData?.monitor;
|
||||
const monitorStats = monitorData?.monitorStats || null;
|
||||
|
||||
return (
|
||||
<BasePage
|
||||
loading={isLoading}
|
||||
error={error}
|
||||
>
|
||||
<HeaderMonitorControls
|
||||
path="pagespeed"
|
||||
monitor={monitor}
|
||||
isAdmin={isAdmin}
|
||||
refetch={refetch}
|
||||
/>
|
||||
<MonitorStatBoxes
|
||||
monitor={monitor}
|
||||
monitorStats={monitorStats}
|
||||
/>
|
||||
<HistogramPageSpeedDetails
|
||||
checks={monitor?.checks || []}
|
||||
range="day"
|
||||
/>
|
||||
<Stack
|
||||
direction={{ xs: "column", md: "row" }}
|
||||
gap={theme.spacing(10)}
|
||||
>
|
||||
<PiePageSpeed latestCheck={monitor?.checks?.[0]} />
|
||||
<PiePageSpeedLegend latestCheck={monitor?.checks?.[0]} />
|
||||
</Stack>
|
||||
</BasePage>
|
||||
);
|
||||
};
|
||||
export default PageSpeedDetails;
|
||||
|
||||
@@ -133,7 +133,13 @@ const Routes = () => {
|
||||
/>
|
||||
<Route
|
||||
path="pagespeed/:monitorId"
|
||||
element={<PageSpeedDetails />}
|
||||
element={
|
||||
<>
|
||||
<ThemeProvider theme={v2theme}>
|
||||
<PageSpeedDetails />
|
||||
</ThemeProvider>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="pagespeed/configure/:monitorId"
|
||||
|
||||
@@ -76,3 +76,8 @@ export interface MonitorDetailsResponse {
|
||||
monitorData: MonitorData;
|
||||
monitorStats: MonitorStats | null;
|
||||
}
|
||||
|
||||
export interface PageSpeedDetailsResponse {
|
||||
monitor: MonitorWithChecks;
|
||||
monitorStats: MonitorStats | null;
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
"status": "Status",
|
||||
"type": "Type"
|
||||
},
|
||||
"empty": "Nothing h"
|
||||
"empty": "Nothing here"
|
||||
},
|
||||
"charts": {
|
||||
"labels": {
|
||||
@@ -228,6 +228,21 @@
|
||||
"headers": {
|
||||
"pageSpeedScore": "PageSpeed score"
|
||||
}
|
||||
},
|
||||
"charts": {
|
||||
"common": {
|
||||
"cls": "Cumulative Layout Shift (CLS)",
|
||||
"fcp": "First Contentful Paint (FCP)",
|
||||
"lcp": "Largest Contentful Paint (LCP)",
|
||||
"si": "Speed Index (SI)",
|
||||
"tbt": "Total Blocking Time (TBT)"
|
||||
},
|
||||
"pie": { "title": "Performance report" },
|
||||
"legend": {
|
||||
"title": "PageSpeed report",
|
||||
|
||||
"weight": "Weight"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user