mirror of
https://github.com/vitodeploy/vito.git
synced 2025-07-03 06:56:15 +00:00
Add app command/search (#622)
This commit is contained in:
@ -1,12 +1,11 @@
|
||||
import * as React from 'react';
|
||||
import { Area, AreaChart, XAxis, YAxis } from 'recharts';
|
||||
import { Area, AreaChart, XAxis } from 'recharts';
|
||||
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from '@/components/ui/chart';
|
||||
import { Metric } from '@/types/metric';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { router } from '@inertiajs/react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Formatter } from 'recharts/types/component/DefaultTooltipContent';
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
@ -14,12 +13,10 @@ interface Props {
|
||||
dataKey: 'load' | 'memory_used' | 'disk_used';
|
||||
label: string;
|
||||
chartData: Metric[];
|
||||
link: string;
|
||||
formatter?: (value: unknown, name: unknown) => string | number;
|
||||
single?: boolean;
|
||||
formatter?: Formatter<number | string, string>;
|
||||
}
|
||||
|
||||
export function ResourceUsageChart({ title, color, dataKey, label, chartData, link, formatter, single }: Props) {
|
||||
export function ResourceUsageChart({ title, color, dataKey, label, chartData, formatter }: Props) {
|
||||
const chartConfig = {
|
||||
[dataKey]: {
|
||||
label: label,
|
||||
@ -27,37 +24,37 @@ export function ResourceUsageChart({ title, color, dataKey, label, chartData, li
|
||||
},
|
||||
} satisfies ChartConfig;
|
||||
|
||||
const getCurrentValue = () => {
|
||||
if (chartData.length === 0) return 'N/A';
|
||||
|
||||
const value = chartData[chartData.length - 1][dataKey];
|
||||
if (formatter) {
|
||||
return formatter(value, dataKey);
|
||||
}
|
||||
|
||||
return typeof value === 'number' ? value.toLocaleString() : String(value);
|
||||
};
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardContent className="overflow-hidden p-0">
|
||||
<div className="flex items-start justify-between p-4">
|
||||
<div className="space-y-2 py-[7px]">
|
||||
<h2 className="text-muted-foreground text-sm">{title}</h2>
|
||||
<span className="text-3xl font-bold">
|
||||
{chartData.length > 0
|
||||
? formatter
|
||||
? formatter(chartData[chartData.length - 1][dataKey], dataKey)
|
||||
: chartData[chartData.length - 1][dataKey].toLocaleString()
|
||||
: 'N/A'}
|
||||
</span>
|
||||
<span className="text-3xl font-bold">{getCurrentValue()}</span>
|
||||
</div>
|
||||
{!single && (
|
||||
<Button variant="ghost" onClick={() => router.visit(link)}>
|
||||
View
|
||||
</Button>
|
||||
)}
|
||||
<Button variant="ghost">View</Button>
|
||||
</div>
|
||||
<ChartContainer config={chartConfig} className={cn('aspect-auto w-full overflow-hidden rounded-b-xl', single ? 'h-[400px]' : 'h-[100px]')}>
|
||||
<AreaChart accessibilityLayer data={chartData} margin={{ left: 0, right: 0, top: 0, bottom: 0 }}>
|
||||
<ChartContainer config={chartConfig} className="aspect-auto h-[100px] w-full overflow-hidden rounded-b-xl">
|
||||
<AreaChart data={chartData} margin={{ left: 0, right: 0, top: 0, bottom: 0 }}>
|
||||
<defs>
|
||||
<linearGradient id={`fill-${dataKey}`} x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="5%" stopColor={color} stopOpacity={0.8} />
|
||||
<stop offset="95%" stopColor={color} stopOpacity={0.1} />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<YAxis dataKey={dataKey} hide />
|
||||
<XAxis
|
||||
hide={!single}
|
||||
hide
|
||||
dataKey="date"
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
@ -74,7 +71,7 @@ export function ResourceUsageChart({ title, color, dataKey, label, chartData, li
|
||||
}}
|
||||
/>
|
||||
<ChartTooltip
|
||||
cursor={true}
|
||||
cursor={false}
|
||||
content={
|
||||
<ChartTooltipContent
|
||||
labelFormatter={(value) => {
|
||||
@ -90,7 +87,7 @@ export function ResourceUsageChart({ title, color, dataKey, label, chartData, li
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Area dataKey={dataKey} type="monotone" fill={`url(#fill-${dataKey})`} stroke={color} />
|
||||
<Area dataKey={dataKey} type="natural" fill={`url(#fill-${dataKey})`} stroke={color} />
|
||||
</AreaChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
|
@ -8,7 +8,7 @@ import {
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from '@/components/ui/dialog';
|
||||
import { FormEventHandler, ReactNode, useState } from 'react';
|
||||
import { FormEventHandler, ReactNode, useEffect, useState } from 'react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { LoaderCircle } from 'lucide-react';
|
||||
import { useForm } from '@inertiajs/react';
|
||||
@ -19,8 +19,30 @@ import InputError from '@/components/ui/input-error';
|
||||
import { Project } from '@/types/project';
|
||||
import FormSuccessful from '@/components/form-successful';
|
||||
|
||||
export default function ProjectForm({ project, children }: { project?: Project; children: ReactNode }) {
|
||||
const [open, setOpen] = useState(false);
|
||||
export default function ProjectForm({
|
||||
project,
|
||||
defaultOpen,
|
||||
onOpenChange,
|
||||
children,
|
||||
}: {
|
||||
project?: Project;
|
||||
defaultOpen?: boolean;
|
||||
onOpenChange?: (open: boolean) => void;
|
||||
children: ReactNode;
|
||||
}) {
|
||||
const [open, setOpen] = useState(defaultOpen || false);
|
||||
useEffect(() => {
|
||||
if (defaultOpen) {
|
||||
setOpen(defaultOpen);
|
||||
}
|
||||
}, [setOpen, defaultOpen]);
|
||||
|
||||
const handleOpenChange = (open: boolean) => {
|
||||
setOpen(open);
|
||||
if (onOpenChange) {
|
||||
onOpenChange(open);
|
||||
}
|
||||
};
|
||||
|
||||
const form = useForm({
|
||||
name: project?.name || '',
|
||||
@ -42,7 +64,7 @@ export default function ProjectForm({ project, children }: { project?: Project;
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<Dialog open={open} onOpenChange={handleOpenChange}>
|
||||
<DialogTrigger asChild>{children}</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
|
@ -2,7 +2,7 @@ import { ClipboardCheckIcon, ClipboardIcon, LoaderCircle, TriangleAlert, WifiIco
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger } from '@/components/ui/sheet';
|
||||
import { useForm, usePage } from '@inertiajs/react';
|
||||
import React, { FormEventHandler, useState } from 'react';
|
||||
import React, { FormEventHandler, useEffect, useState } from 'react';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||
import InputError from '@/components/ui/input-error';
|
||||
@ -25,9 +25,31 @@ type CreateServerForm = {
|
||||
plan: string;
|
||||
};
|
||||
|
||||
export default function CreateServer({ children }: { children: React.ReactNode }) {
|
||||
export default function CreateServer({
|
||||
defaultOpen,
|
||||
onOpenChange,
|
||||
children,
|
||||
}: {
|
||||
defaultOpen?: boolean;
|
||||
onOpenChange?: (open: boolean) => void;
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
const page = usePage<SharedData>();
|
||||
|
||||
const [open, setOpen] = useState(defaultOpen || false);
|
||||
useEffect(() => {
|
||||
if (defaultOpen) {
|
||||
setOpen(defaultOpen);
|
||||
}
|
||||
}, [defaultOpen]);
|
||||
|
||||
const handleOpenChange = (open: boolean) => {
|
||||
setOpen(open);
|
||||
if (onOpenChange) {
|
||||
onOpenChange(open);
|
||||
}
|
||||
};
|
||||
|
||||
const form = useForm<Required<CreateServerForm>>({
|
||||
provider: 'custom',
|
||||
server_provider: 0,
|
||||
@ -97,7 +119,7 @@ export default function CreateServer({ children }: { children: React.ReactNode }
|
||||
};
|
||||
|
||||
return (
|
||||
<Sheet>
|
||||
<Sheet open={open} onOpenChange={handleOpenChange} modal>
|
||||
<SheetTrigger asChild>{children}</SheetTrigger>
|
||||
<SheetContent className="w-full lg:max-w-4xl">
|
||||
<SheetHeader>
|
||||
|
Reference in New Issue
Block a user