Skip to content

Commit

Permalink
feat(monitors): added ram and cpu monitor, added reponsive chart
Browse files Browse the repository at this point in the history
  • Loading branch information
christopherpickering committed Jul 21, 2023
1 parent a447852 commit 73b46af
Show file tree
Hide file tree
Showing 18 changed files with 776 additions and 221 deletions.
36 changes: 36 additions & 0 deletions app/components/charts/DateFilter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Fragment } from 'react';
import {
Select,
SelectContent,
SelectItem,
SelectItemNoIndicator,
SelectTrigger,
SelectValue,
} from '~/components/ui/select';
import { Separator } from '~/components/ui/separator';
import { dateOptions } from '~/models/dates';

export const DateFilter = ({ value, onChange }) => {
return (
<Select value={value || 'last_24_hours'} onValueChange={onChange}>
<SelectTrigger className="h-8 w-[150px] focus:ring-0 focus:ring-offset-0">
<SelectValue
placeholder={
dateOptions.filter((x) => x.value === value)?.[0]?.name ||
'Last 24 hours'
}
/>
</SelectTrigger>
<SelectContent side="top">
{dateOptions.map((option) => (
<Fragment key={option.value}>
{option.divider && <Separator className="my-1" />}
<SelectItemNoIndicator value={option.value}>
{option.name}
</SelectItemNoIndicator>
</Fragment>
))}
</SelectContent>
</Select>
);
};
193 changes: 144 additions & 49 deletions app/components/charts/driveBar.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import type { Usage } from '@prisma/client';
import { cn } from '@/lib/utils';

import bytes from 'bytes';
import {
Expand All @@ -13,10 +12,12 @@ import {
Tooltip,
TimeScale,
} from 'chart.js';
import React, { useEffect, useRef, useState } from 'react';
import { useCallback, useEffect, useRef, useState } from 'react';
import { Bar } from 'react-chartjs-2';
import { format } from 'date-fns';
import { createLinearGradient, darkGradient, lightGradient } from './functions';
import { useFetcher } from '@remix-run/react';
import { DateFilter } from './DateFilter';
import { dateOptions } from '~/models/dates';

ChartJS.register([
CategoryScale,
Expand All @@ -28,42 +29,97 @@ ChartJS.register([
TimeScale,
]);

export const options = {
responsive: true,
plugins: {
title: {
display: true,
text: 'Storage Usage',
},
},
scales: {
y: {
type: 'linear' as const,
display: true,
position: 'left' as const,
beginAtZero: true,
ticks: {
callback: function (value: string) {
return value + 'GB';
import 'chartjs-adapter-date-fns';
import { H2, H3 } from '../ui/typography';
import { Circle, Loader, RefreshCw } from 'lucide-react';
import { Button } from '../ui/button';

export const StorageChart = ({ url }: { url: string }) => {
const usageFetcher = useFetcher();
const [unit, setUnit] = useState('last_24_hours');
const chartRef = useRef<ChartJS>(null);
const getOptions = useCallback(() => {
return {
responsive: true,
maintainAspectRatio: false,
animation: {
duration: 300,
resize: {
duration: 0,
},
active: {
duration: 0,
},
},
stacked: true,
},
x: {
stacked: true,
},
},
};
plugins: {
title: {
display: false,
},
legend: {
display: false,
},
tooltip: {
callbacks: {
label: function (context) {
if (context.dataset.label) {
return context.datset.label + 'GB';
}
return '';
},
},
},
},
scales: {
y: {
type: 'linear' as const,
display: true,
position: 'left' as const,
beginAtZero: true,
ticks: {
callback: function (value: string) {
return value + 'GB';
},
},
stacked: true,
},
x: {
stacked: true,
type: 'time',
min: () => usageFetcher.data?.drive?.startDate,
max: () => usageFetcher.data?.drive?.endDate,
time: {
unit: () =>
dateOptions.filter((x) => x.value === unit)?.[0]?.unit ||
undefined,
},
grid: {
display: false,
},
},
},
};
}, [unit, usageFetcher.data]);

const BarChart = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> & any
>(({ className, data, ...props }, ref) => {
const chartRef = useRef<ChartJS>(null);
const [options, setOptions] = useState(getOptions());

useEffect(() => {
usageFetcher.load(
url +
`?range=${unit}&unit=${dateOptions.filter((x) => x.value === unit)?.[0]
?.unit}`,
);
}, [unit]);

const [chartData, setChartData] = useState<ChartData<'bar'>>({
useEffect(() => {
if (usageFetcher.state === 'loading') {
setChartData(emptyDataset);
}
}, [usageFetcher]);

const emptyDataset = {
datasets: [],
});
};
const [chartData, setChartData] = useState<ChartData<'bar'>>(emptyDataset);

useEffect(() => {
const chart = chartRef.current;
Expand All @@ -72,13 +128,11 @@ const BarChart = React.forwardRef<
return;
}
const chartData = {
labels: data.usage.map((x: Usage) =>
format(new Date(x.createdAt), 'MMM dd, yyyy'),
),
labels: usageFetcher.data?.drive?.usage?.map((x: Usage) => x.createdAt),
datasets: [
{
label: 'Used',
data: data.usage.map((x: Usage) =>
data: usageFetcher.data?.drive?.usage?.map((x: Usage) =>
bytes(Number(x.used), { unit: 'GB' }).replace('GB', ''),
),
borderColor: createLinearGradient(
Expand All @@ -104,7 +158,7 @@ const BarChart = React.forwardRef<
},
{
label: 'Free',
data: data.usage.map((x: Usage) =>
data: usageFetcher.data?.drive?.usage?.map((x: Usage) =>
bytes(Number(x.free), { unit: 'GB' }).replace('GB', ''),
),
borderColor: '#cbd5e1',
Expand All @@ -113,17 +167,58 @@ const BarChart = React.forwardRef<
},
],
};

setOptions(getOptions());
setChartData(chartData);
}, [data]);
}, [usageFetcher.data]);

return (
<div ref={ref} className={cn('m-auto max-h-[450px]', className)} {...props}>
<Bar ref={chartRef} options={options} data={chartData} />
</div>
<>
<div className="w-full space-y-5">
<div className="flex space-x-2 justify-between">
<H3 className="text-3xl">Storage History</H3>
<div className="space-x-2 flex">
<Button
variant="outline"
className="h-8"
onClick={() =>
usageFetcher.load(
url +
`?range=${unit}&unit=${dateOptions.filter(
(x) => x.value === unit,
)?.[0]?.unit}`,
)
}
>
<RefreshCw size={14} />
</Button>
<DateFilter value={unit} onChange={setUnit} />
</div>
</div>
<div className="h-[450px] relative">
<Bar ref={chartRef} options={options} data={chartData} />
{usageFetcher.state === 'loading' && (
<div className="absolute flex content-center top-0 bottom-0 right-0 left-0">
<Loader className="m-auto animate-spin" />
</div>
)}
</div>
<div className="flex space-x-4 text-muted-foreground">
<div className="flex space-x-2 items-center">
<Circle
className={`fill-[#e2e8f0] text-[#cbd5e1] h-3 w-3`}
size={10}
/>
<span>Free</span>
</div>
<div className="flex space-x-2 items-center">
<Circle
className={`fill-[#7dd3fc] text-[#0ea5e9] h-3 w-3`}
size={10}
/>
<span>Used</span>
</div>
</div>
</div>
</>
);
});

BarChart.displayName = 'Line Chart';

export { BarChart };
};
6 changes: 3 additions & 3 deletions app/components/charts/functions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ export function createLinearGradient(
const gradient = ctx.createLinearGradient(0, 0, 0, chartArea.height);

gradient.addColorStop(0, colors[3]);
gradient.addColorStop(0.15, colors[2]);
gradient.addColorStop(0.2, colors[1]);
gradient.addColorStop(0.25, colors[0]);
gradient.addColorStop(0.05, colors[2]);
gradient.addColorStop(0.1, colors[1]);
gradient.addColorStop(0.15, colors[0]);
return gradient;
}
18 changes: 18 additions & 0 deletions app/components/ui/select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,23 @@ const SelectLabel = React.forwardRef<
));
SelectLabel.displayName = SelectPrimitive.Label.displayName;

const SelectItemNoIndicator = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
>(({ className, children, ...props }, ref) => (
<SelectPrimitive.Item
ref={ref}
className={cn(
'relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 px-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
className,
)}
{...props}
>
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
</SelectPrimitive.Item>
));
SelectItemNoIndicator.displayName = 'SelectItemNoIndicator';

const SelectItem = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
Expand Down Expand Up @@ -117,4 +134,5 @@ export {
SelectLabel,
SelectItem,
SelectSeparator,
SelectItemNoIndicator,
};
55 changes: 55 additions & 0 deletions app/components/ui/tabs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
'use client';

import * as React from 'react';
import * as TabsPrimitive from '@radix-ui/react-tabs';

import { cn } from '@/lib/utils';

const Tabs = TabsPrimitive.Root;

const TabsList = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.List>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
>(({ className, ...props }, ref) => (
<TabsPrimitive.List
ref={ref}
className={cn(
'inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground',
className,
)}
{...props}
/>
));
TabsList.displayName = TabsPrimitive.List.displayName;

const TabsTrigger = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Trigger
ref={ref}
className={cn(
'inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm',
className,
)}
{...props}
/>
));
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;

const TabsContent = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Content
ref={ref}
className={cn(
'mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
className,
)}
{...props}
/>
));
TabsContent.displayName = TabsPrimitive.Content.displayName;

export { Tabs, TabsList, TabsTrigger, TabsContent };
Loading

0 comments on commit 73b46af

Please sign in to comment.