improvement

This commit is contained in:
Carl-Gerhard Lindesvärd
2024-02-13 19:03:51 +01:00
parent 69523bc0d7
commit 2572da3456
10 changed files with 116 additions and 14 deletions

View File

@@ -0,0 +1,83 @@
import type { FastifyReply, FastifyRequest } from 'fastify';
import { redis } from '@mixan/redis';
interface GetFaviconParams {
url: string;
}
function toBuffer(arrayBuffer: ArrayBuffer) {
const buffer = Buffer.alloc(arrayBuffer.byteLength);
const view = new Uint8Array(arrayBuffer);
for (let i = 0; i < buffer.length; ++i) {
buffer[i] = view[i]!;
}
return buffer;
}
async function getUrlBuffer(url: string) {
const arrayBuffer = await fetch(url).then((res) => {
if (res.ok) {
return res.arrayBuffer();
}
});
if (arrayBuffer) {
return toBuffer(arrayBuffer);
}
return null;
}
export async function getFavicon(
request: FastifyRequest<{
Querystring: GetFaviconParams;
}>,
reply: FastifyReply
) {
if (!request.query.url) {
return reply.status(404).send('Not found');
}
function sendBuffer(buffer: Buffer, hostname?: string) {
if (hostname) {
redis.set(`favicon:${hostname}`, buffer.toString('base64'));
}
reply.type('image/png');
return reply.send(buffer);
}
const url = decodeURIComponent(request.query.url);
const { hostname, origin } = new URL(url);
const cache = await redis.get(`favicon:${hostname}`);
if (cache) {
return sendBuffer(Buffer.from(cache, 'base64'));
}
// Try just get the favicon.ico
const buffer = await getUrlBuffer(`${origin}/favicon.ico`);
if (buffer) {
return sendBuffer(buffer, hostname);
}
// If that didnt work try parse html
const res = await fetch(url).then((res) => res.text());
const favicon =
res.match(/<link.*?rel="icon".*?href="(.+?)".*?>/) ||
res.match(/<link.*?rel="shortcut icon".*?href="(.+?)".*?>/);
if (favicon?.[1]) {
const faviconUrl = favicon[1].startsWith('http')
? favicon[1]
: `${origin}${favicon[1]}`;
const buffer = await getUrlBuffer(faviconUrl);
if (buffer) {
return sendBuffer(buffer, hostname);
}
}
return reply.status(404).send('Not found');
}

View File

@@ -6,6 +6,7 @@ import { redisPub } from '@mixan/redis';
import eventRouter from './routes/event.router'; import eventRouter from './routes/event.router';
import liveRouter from './routes/live.router'; import liveRouter from './routes/live.router';
import miscRouter from './routes/misc.router';
import profileRouter from './routes/profile.router'; import profileRouter from './routes/profile.router';
declare module 'fastify' { declare module 'fastify' {
@@ -32,6 +33,7 @@ const startServer = async () => {
fastify.register(eventRouter, { prefix: '/event' }); fastify.register(eventRouter, { prefix: '/event' });
fastify.register(profileRouter, { prefix: '/profile' }); fastify.register(profileRouter, { prefix: '/profile' });
fastify.register(liveRouter, { prefix: '/live' }); fastify.register(liveRouter, { prefix: '/live' });
fastify.register(miscRouter, { prefix: '/misc' });
fastify.setErrorHandler((error, request, reply) => { fastify.setErrorHandler((error, request, reply) => {
fastify.log.error(error); fastify.log.error(error);
}); });

View File

@@ -0,0 +1,14 @@
import * as controller from '@/controllers/misc.controller';
import type { FastifyPluginCallback } from 'fastify';
const miscRouter: FastifyPluginCallback = (fastify, opts, done) => {
fastify.route({
method: 'GET',
url: '/favicon',
handler: controller.getFavicon,
});
done();
};
export default miscRouter;

View File

@@ -22,7 +22,7 @@ export function ChartEmpty() {
return ( return (
<div <div
className={ className={
'aspect-video w-full max-h-[400px] min-h-[200px] flex justify-center items-center' 'aspect-video w-full max-h-[300px] min-h-[200px] flex justify-center items-center'
} }
> >
No data No data

View File

@@ -7,7 +7,7 @@ export function ChartLoading({ className }: ChartLoadingProps) {
return ( return (
<div <div
className={cn( className={cn(
'aspect-video w-full bg-slate-200 animate-pulse rounded max-h-[400px] min-h-[200px]', 'aspect-video w-full bg-slate-200 animate-pulse rounded max-h-[300px] min-h-[200px]',
className className
)} )}
/> />

View File

@@ -41,7 +41,7 @@ export function ReportAreaChart({
<> <>
<div <div
className={cn( className={cn(
'max-sm:-mx-3 aspect-video w-full max-h-[400px] min-h-[200px]', 'max-sm:-mx-3 aspect-video w-full max-h-[300px] min-h-[200px]',
editMode && 'border border-border bg-white rounded-md p-4' editMode && 'border border-border bg-white rounded-md p-4'
)} )}
> >
@@ -49,7 +49,7 @@ export function ReportAreaChart({
{({ width }) => ( {({ width }) => (
<AreaChart <AreaChart
width={width} width={width}
height={Math.min(Math.max(width * 0.5625, 250), 400)} height={Math.min(Math.max(width * 0.5625, 250), 300)}
data={rechartData} data={rechartData}
> >
<Tooltip content={<ReportChartTooltip />} /> <Tooltip content={<ReportChartTooltip />} />

View File

@@ -20,15 +20,15 @@ export function ReportBarChart({ data }: ReportBarChartProps) {
const { editMode, metric, onClick } = useChartContext(); const { editMode, metric, onClick } = useChartContext();
const number = useNumber(); const number = useNumber();
const series = useMemo( const series = useMemo(
() => (editMode ? data.series : data.series.slice(0, 20)), () => (editMode ? data.series : data.series.slice(0, 10)),
[data] [data, editMode]
); );
const maxCount = Math.max(...series.map((serie) => serie.metrics[metric])); const maxCount = Math.max(...series.map((serie) => serie.metrics[metric]));
return ( return (
<div <div
className={cn( className={cn(
'flex flex-col w-full divide-y text-xs', 'flex flex-col w-full text-xs -mx-2',
editMode && editMode &&
'text-base bg-white border border-border rounded-md p-4 pt-2' 'text-base bg-white border border-border rounded-md p-4 pt-2'
)} )}
@@ -45,8 +45,8 @@ export function ReportBarChart({ data }: ReportBarChartProps) {
<div <div
key={serie.name} key={serie.name}
className={cn( className={cn(
'py-2 flex flex-1 w-full gap-4 items-center', 'py-2 px-2 flex flex-1 w-full gap-4 items-center even:bg-slate-50 [&_[role=progressbar]]:even:bg-white [&_[role=progressbar]]:shadow-sm rounded',
isClickable && 'cursor-pointer hover:bg-gray-100' isClickable && 'cursor-pointer hover:!bg-slate-100'
)} )}
{...(isClickable ? { onClick: () => onClick(serie) } : {})} {...(isClickable ? { onClick: () => onClick(serie) } : {})}
> >

View File

@@ -45,7 +45,7 @@ export function ReportHistogramChart({
<> <>
<div <div
className={cn( className={cn(
'max-sm:-mx-3 aspect-video w-full max-h-[400px] min-h-[200px]', 'max-sm:-mx-3 aspect-video w-full max-h-[300px] min-h-[200px]',
editMode && 'border border-border bg-white rounded-md p-4' editMode && 'border border-border bg-white rounded-md p-4'
)} )}
> >
@@ -53,7 +53,7 @@ export function ReportHistogramChart({
{({ width }) => ( {({ width }) => (
<BarChart <BarChart
width={width} width={width}
height={Math.min(Math.max(width * 0.5625, 250), 400)} height={Math.min(Math.max(width * 0.5625, 250), 300)}
data={rechartData} data={rechartData}
> >
<CartesianGrid strokeDasharray="3 3" vertical={false} /> <CartesianGrid strokeDasharray="3 3" vertical={false} />

View File

@@ -43,7 +43,7 @@ export function ReportLineChart({
<> <>
<div <div
className={cn( className={cn(
'max-sm:-mx-3 aspect-video w-full max-h-[400px] min-h-[200px]', 'max-sm:-mx-3 aspect-video w-full max-h-[300px] min-h-[200px]',
editMode && 'border border-border bg-white rounded-md p-4' editMode && 'border border-border bg-white rounded-md p-4'
)} )}
> >
@@ -51,7 +51,7 @@ export function ReportLineChart({
{({ width }) => ( {({ width }) => (
<LineChart <LineChart
width={width} width={width}
height={Math.min(Math.max(width * 0.5625, 250), 400)} height={Math.min(Math.max(width * 0.5625, 250), 300)}
data={rechartData} data={rechartData}
> >
<CartesianGrid <CartesianGrid

View File

@@ -47,7 +47,10 @@ export function SerieIcon({ name, ...props }: SerieIconProps) {
if (name.includes('http')) { if (name.includes('http')) {
Icon = ((_props) => ( Icon = ((_props) => (
<SocialIcon network={networkFor(name)} /> <img
className="w-4 h-4 object-cover"
src={`${String(process.env.NEXT_PUBLIC_API_URL)}/misc/favicon?url=${encodeURIComponent(name)}`}
/>
)) as LucideIcon; )) as LucideIcon;
} }