rename frontend-v2 to frontend

This commit is contained in:
mbecker20
2024-01-06 15:16:24 -08:00
parent d46741c5ae
commit 57ed905140
278 changed files with 1208 additions and 11739 deletions

View File

@@ -1,18 +0,0 @@
module.exports = {
root: true,
env: { browser: true, es2020: true },
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react-hooks/recommended',
],
ignorePatterns: ['dist', '.eslintrc.cjs'],
parser: '@typescript-eslint/parser',
plugins: ['react-refresh'],
rules: {
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
}

View File

@@ -1,24 +0,0 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View File

@@ -1,11 +0,0 @@
FROM node:20.5-alpine
WORKDIR /app
COPY ./frontend-v2 ./frontend
COPY ./client/ts ./client
RUN cd client && yarn && yarn build && yarn link
RUN cd frontend && yarn link @monitor/client && yarn && yarn build
CMD cd frontend && yarn preview --host --port 4174

View File

@@ -1,16 +0,0 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "tailwind.config.js",
"css": "src/globals.css",
"baseColor": "neutral",
"cssVariables": true
},
"aliases": {
"components": "@/",
"utils": "@lib/utils"
}
}

View File

@@ -1,13 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Monitor</title>
</head>
<body class="min-h-screen">
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

View File

@@ -1,55 +0,0 @@
{
"name": "frontend",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview",
"regen-client": "cd ../client/ts && yarn build"
},
"dependencies": {
"@radix-ui/react-dialog": "^1.0.4",
"@radix-ui/react-dropdown-menu": "^2.0.5",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-progress": "^1.0.3",
"@radix-ui/react-select": "^1.2.2",
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-switch": "^1.0.3",
"@radix-ui/react-tabs": "^1.0.4",
"@radix-ui/react-toast": "^1.1.4",
"@tanstack/react-query": "^4.33.0",
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
"cmdk": "^0.2.0",
"jotai": "^2.4.1",
"lightweight-charts": "^4.0.1",
"lucide-react": "^0.274.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-minimal-pie-chart": "^8.4.0",
"react-router-dom": "^6.15.0",
"reconnecting-websocket": "^4.4.0",
"tailwind-merge": "^1.14.0",
"tailwindcss-animate": "^1.0.7"
},
"devDependencies": {
"@types/react": "^18.2.15",
"@types/react-dom": "^18.2.7",
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0",
"@vitejs/plugin-react": "^4.0.3",
"autoprefixer": "^10.4.15",
"eslint": "^8.45.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.3",
"postcss": "^8.4.29",
"tailwindcss": "^3.3.3",
"typescript": "^5.0.2",
"vite": "^4.4.5",
"vite-tsconfig-paths": "^4.2.0"
}
}

View File

@@ -1,6 +0,0 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

View File

@@ -1,300 +0,0 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { useRead } from "@lib/hooks";
import { Types } from "@monitor/client";
import {
Select,
SelectTrigger,
SelectValue,
SelectContent,
SelectItem,
SelectGroup,
} from "@ui/select";
import { Button } from "@ui/button";
import { Input } from "@ui/input";
import { Switch } from "@ui/switch";
import { MinusCircle, PlusCircle, Save } from "lucide-react";
import { ReactNode, useState } from "react";
import { cn } from "@lib/utils";
import {
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@ui/dialog";
export const ConfigItem = ({
label,
children,
className,
}: {
label: string;
children: ReactNode;
className?: string;
}) => (
<div
className={cn(
"flex justify-between items-center border-b pb-2 min-h-[60px] last:border-none last:pb-0",
className
)}
>
<div className="capitalize"> {label} </div>
{children}
</div>
);
export const ConfigInput = ({
label,
value,
onChange,
}: {
label: string;
value: string | number | undefined;
onChange: (value: string) => void;
}) => (
<ConfigItem label={label}>
<Input
className="max-w-[400px]"
type={typeof value === "number" ? "number" : undefined}
value={value}
onChange={(e) => onChange(e.target.value)}
// disabled={loading}
/>
</ConfigItem>
);
export const ConfigSwitch = ({
label,
value,
onChange,
}: {
label: string;
value: boolean | undefined;
onChange: (value: boolean) => void;
}) => (
<ConfigItem label={label}>
<Switch checked={value} onCheckedChange={onChange} />
</ConfigItem>
);
export const DoubleInput = <
T extends object,
K extends keyof T,
L extends T[K] extends string | number | undefined ? K : never,
R extends T[K] extends string | number | undefined ? K : never
>({
values,
leftval,
leftpl,
rightval,
rightpl,
addName,
onLeftChange,
onRightChange,
onAdd,
onRemove,
}: {
values: T[] | undefined;
leftval: L;
leftpl: string;
rightval: R;
rightpl: string;
addName: string;
onLeftChange: (value: T[L], i: number) => void;
onRightChange: (value: T[R], i: number) => void;
onAdd: () => void;
onRemove: (i: number) => void;
}) => {
return (
<div className="flex flex-col gap-4">
{values?.map((value, i) => (
<div className="flex items-center justify-between gap-4" key={i}>
<Input
value={value[leftval] as any}
placeholder={leftpl}
onChange={(e) => onLeftChange(e.target.value as T[L], i)}
/>
:
<Input
value={value[rightval] as any}
placeholder={rightpl}
onChange={(e) => onRightChange(e.target.value as T[R], i)}
/>
<Button
variant="outline"
// intent="warning"
onClick={() => onRemove(i)}
>
<MinusCircle className="w-4 h-4" />
</Button>
</div>
))}
<Button
variant="outline"
// intent="success"
className="flex items-center gap-2 w-[200px] place-self-end"
onClick={onAdd}
>
<PlusCircle className="w-4 h-4" />
Add {addName}
</Button>
</div>
);
};
type UsableResources = Exclude<Types.ResourceTarget["type"], "System">;
export const ResourceSelector = ({
type,
selected,
onSelect,
}: {
type: UsableResources;
selected: string | undefined;
onSelect: (id: string) => void;
}) => {
const resources = useRead(`List${type}s`, {}).data;
return (
<Select value={selected ?? undefined} onValueChange={onSelect}>
<SelectTrigger className="w-full lg:w-[300px]">
<SelectValue placeholder={`Select ${type}`} />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{resources?.map((resource) => (
<SelectItem key={resource.id} value={resource.id}>
{resource.name}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
);
};
export const AccountSelector = ({
id,
type,
account_type,
selected,
onSelect,
}: {
id: string | undefined;
type: "Server" | "Builder";
account_type: keyof Types.GetBuilderAvailableAccountsResponse;
selected: string | undefined;
onSelect: (id: string) => void;
}) => {
const request = type === "Server" ? "GetAvailableAccounts" : "GetBuilderAvailableAccounts";
const accounts = useRead(
request,
{ id: id! },
{ enabled: !!id }
).data;
return (
<ConfigItem label={`${account_type} Account`}>
<Select
value={type === "Builder" ? selected || undefined : selected}
onValueChange={onSelect}
>
<SelectTrigger className="w-full lg:w-[300px]" disabled={!id}>
<SelectValue placeholder="Select Account" />
</SelectTrigger>
<SelectContent>
{type === "Server" && (
<SelectItem value={""}>Same as build</SelectItem>
)}
{accounts?.[account_type]?.map((account) => (
<SelectItem key={account} value={account}>
{account}
</SelectItem>
))}
</SelectContent>
</Select>
</ConfigItem>
);
};
export const InputList = <T extends { [key: string]: unknown }>({
field,
values,
set,
}: {
field: keyof T;
values: string[];
set: (update: Partial<T>) => void;
}) => (
<ConfigItem label={field as string} className="items-start">
<div className="flex flex-col gap-4 w-full max-w-[400px]">
{values.map((arg, i) => (
<div className="w-full flex gap-4" key={i}>
<Input
// placeholder="--extra-arg=value"
value={arg}
onChange={(e) => {
values[i] = e.target.value;
set({ [field]: [...values] } as Partial<T>);
}}
/>
<Button
variant="outline"
// intent="warning"
onClick={() =>
set({
[field]: [...values.filter((_, idx) => idx !== i)],
} as Partial<T>)
}
>
<MinusCircle className="w-4 h-4" />
</Button>
</div>
))}
<Button
variant="outline"
// intent="success"
onClick={() => set({ [field]: [...values, ""] } as Partial<T>)}
>
Add Docker Account
</Button>
</div>
</ConfigItem>
);
interface ConfirmUpdateProps {
content: string;
onConfirm: () => void;
}
export const ConfirmUpdate = ({ content, onConfirm }: ConfirmUpdateProps) => {
const [open, set] = useState(false);
return (
<Dialog open={open} onOpenChange={set}>
<DialogTrigger asChild>
<Button onClick={() => set(true)}>
<Save className="w-4 h-4" />
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Confirm Update</DialogTitle>
</DialogHeader>
<div className="flex flex-col gap-4 py-4 my-4">
New configuration to be applied:
<pre className="h-[300px] overflow-auto">{content}</pre>
</div>
<DialogFooter>
<Button
onClick={() => {
onConfirm();
set(false);
}}
>
Confirm
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
};

View File

@@ -1,140 +0,0 @@
import {
Sheet,
SheetContent,
SheetDescription,
SheetHeader,
SheetTitle,
SheetTrigger,
} from "@ui/sheet";
import { Calendar, Clock, Milestone, User } from "lucide-react";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@ui/card";
import { ReactNode } from "react";
import { useRead } from "@lib/hooks";
import { fmt_duration, fmt_verison } from "@lib/utils";
import { ResourceComponents } from "@components/resources";
export const UpdateUser = ({ user_id }: { user_id: string }) => {
const username = useRead("GetUsername", { user_id }).data;
if (user_id === "github") return <>GitHub</>;
if (user_id === "auto redeploy") return <>Auto Redeploy</>;
return <>{username?.username}</>;
};
export const UpdateDetails = ({
id,
children,
}: {
id: string;
children: ReactNode;
}) => {
const update = useRead("GetUpdate", { id }).data;
if (!update) return null;
const Components =
update.target.type === "System"
? null
: ResourceComponents[update.target.type];
if (!Components) return null;
return (
<Sheet>
<SheetTrigger asChild>{children}</SheetTrigger>
<SheetContent
side="right"
className="overflow-y-auto w-[100vw] md:w-[75vw] lg:w-[50vw]"
>
<SheetHeader className="mb-4">
<SheetTitle>
{update.operation
.split("_")
.map((s) => s[0].toUpperCase() + s.slice(1))
.join(" ")}{" "}
{fmt_verison(update.version)}
</SheetTitle>
<SheetDescription className="flex flex-col gap-2">
<div className="flex items-center gap-2">
<User className="w-4 h-4" />
<UpdateUser user_id={update.operator} />
</div>
<div className="flex gap-4">
<div className="flex items-center gap-2">
<Components.Icon id={update.target.id} />
<Components.Name id={update.target.id} />
</div>
{update.version && (
<div className="flex items-center gap-2">
<Milestone className="w-4 h-4" />
{fmt_verison(update.version)}
</div>
)}
</div>
<div className="flex gap-4">
<div className="flex items-center gap-2">
<Calendar className="w-4 h-4" />
{new Date(update.start_ts).toLocaleString()}
</div>
<div className="flex items-center gap-2">
<Clock className="w-4 h-4" />
{update.end_ts
? fmt_duration(update.start_ts, update.end_ts)
: "ongoing"}
</div>
</div>
</SheetDescription>
</SheetHeader>
<div className="grid gap-2">
{update.logs?.map((log, i) => (
<Card key={i}>
<CardHeader className="flex-col">
<CardTitle>{log.stage}</CardTitle>
<CardDescription className="flex gap-2">
<span>
Stage {i + 1} of {update.logs.length}
</span>
<span>|</span>
<span className="flex items-center gap-2">
<Clock className="w-4 h-4" />
{fmt_duration(log.start_ts, log.end_ts)}
</span>
</CardDescription>
</CardHeader>
<CardContent className="flex flex-col gap-2">
{log.command && (
<div>
<CardDescription>command</CardDescription>
<pre className="max-h-[500px] overflow-y-auto">
{log.command}
</pre>
</div>
)}
{log.stdout && (
<div>
<CardDescription>stdout</CardDescription>
<pre className="max-h-[500px] overflow-y-auto">
{log.stdout}
</pre>
</div>
)}
{log.stderr && (
<div>
<CardDescription>stdout</CardDescription>
<pre className="max-h-[500px] overflow-y-auto">
{log.stderr}
</pre>
</div>
)}
</CardContent>
</Card>
))}
</div>
</SheetContent>
</Sheet>
);
};

View File

@@ -1,100 +0,0 @@
import { useRead } from "@lib/hooks";
import { Button } from "@ui/button";
import {
Card,
CardHeader,
CardTitle,
CardContent,
CardDescription,
} from "@ui/card";
import {
Bell,
ExternalLink,
User,
Calendar,
Check,
X,
Loader2,
} from "lucide-react";
import { Link } from "react-router-dom";
import { Types } from "@monitor/client";
import { Section } from "@components/layouts";
import { fmt_update_date } from "@lib/utils";
import { UpdateDetails, UpdateUser } from "./details";
import { UpdateStatus } from "@monitor/client/dist/types";
const UpdatePlaceHolder = () => (
<Card>
<CardHeader>
<CardTitle>...</CardTitle>
<CardContent>
<CardDescription className="flex items-center gap-2">
<User className="w-4 h-4" /> ...
</CardDescription>
<CardDescription className="flex items-center gap-2">
<Calendar className="w-4 h-4" /> ...
</CardDescription>
</CardContent>
</CardHeader>
</Card>
);
const UpdateCard = ({ update }: { update: Types.UpdateListItem }) => {
const Icon = () => {
if (update.status === UpdateStatus.Complete) {
if (update.success) return <Check className="w-4 h-4 stroke-green-500" />;
else return <X className="w-4 h-4 stroke-red-500" />;
} else return <Loader2 className="w-4 h-4 animate-spin" />;
};
return (
<UpdateDetails id={update.id}>
<Card className="cursor-pointer hover:translate-y-[-2.5%] hover:bg-accent/50 transition-all">
<CardHeader className="flex-row justify-between">
<CardTitle>{update.operation}</CardTitle>
<Icon />
</CardHeader>
<CardContent>
<CardDescription className="flex items-center gap-2">
<User className="w-4 h-4" />{" "}
<UpdateUser user_id={update.operator} />
</CardDescription>
<CardDescription className="flex items-center gap-2">
<Calendar className="w-4 h-4" />
{fmt_update_date(new Date(update.start_ts))}
</CardDescription>
</CardContent>
</Card>
</UpdateDetails>
);
};
export const ResourceUpdates = ({ type, id }: Types.ResourceTarget) => {
const { data, isLoading } = useRead("ListUpdates", {
query: {
"target.type": type,
"target.id": id,
},
});
return (
<Section
title="Updates"
icon={<Bell className="w-4 h-4" />}
actions={
<Link to={`/deployments/${id}/updates`}>
<Button variant="secondary">
<ExternalLink className="w-4 h-4" />
</Button>
</Link>
}
>
<div className="grid md:grid-cols-3 gap-4">
{isLoading && <UpdatePlaceHolder />}
{data?.updates
.slice(0, 3)
.map((update) => <UpdateCard update={update} key={update.id} />)}
</div>
</Section>
);
};

View File

@@ -1,390 +0,0 @@
import { ReactNode, forwardRef, useEffect, useState } from "react";
import { Button } from "../ui/button";
import {
Box,
Check,
Copy,
Loader2,
LogOut,
Moon,
SunMedium,
} from "lucide-react";
import { Input } from "../ui/input";
import {
Dialog,
DialogHeader,
DialogTitle,
DialogTrigger,
DialogContent,
DialogFooter,
} from "@ui/dialog";
import { toast } from "@ui/use-toast";
import { RESOURCE_TARGETS, cn } from "@lib/utils";
import {
useInvalidate,
useRead,
useResourceParamType,
useWrite,
} from "@lib/hooks";
import { Link, useNavigate, useParams } from "react-router-dom";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@ui/dropdown-menu";
import { ResourceComponents } from "./resources";
import { WsStatusIndicator } from "@lib/socket";
export const WithLoading = ({
children,
isLoading,
loading,
isError,
error,
}: {
children: ReactNode;
isLoading: boolean;
loading?: ReactNode;
isError: boolean;
error?: ReactNode;
}) => {
if (isLoading) return <>{loading ?? "loading"}</>;
if (isError) return <>{error ?? null}</>;
return <>{children}</>;
};
export const ConfigInput = ({
placeholder,
value,
onChange,
}: {
placeholder: string;
value: string | undefined;
onChange: (s: string) => void;
}) => (
<Input
placeholder={placeholder}
className="max-w-[500px]"
value={value}
onChange={({ target }) => onChange(target.value)}
/>
);
export const ThemeToggle = () => {
const [theme, set] = useState(localStorage.getItem("theme"));
useEffect(() => {
localStorage.setItem("theme", theme ?? "dark");
if (theme === "dark") document.body.classList.remove("dark");
else document.body.classList.add("dark");
}, [theme]);
return (
<Button
variant="ghost"
onClick={() => set(theme === "dark" ? "light" : "dark")}
>
<SunMedium className="w-4 h-4 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
<Moon className="w-4 h-4 absolute rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
</Button>
);
};
export const ActionButton = forwardRef<
HTMLButtonElement,
{
title: string;
icon: ReactNode;
disabled?: boolean;
className?: string;
onClick?: () => void;
loading?: boolean;
}
>(({ title, icon, disabled, className, loading, onClick }, ref) => (
<Button
variant="outline"
className={cn("flex items-center justify-between w-[150px]", className)}
onClick={onClick}
disabled={disabled}
ref={ref}
>
{title} {loading ? <Loader2 className="w-4 h-4 animate-spin" /> : icon}
</Button>
));
export const ActionWithDialog = ({
name,
title,
icon,
disabled,
loading,
onClick,
}: {
name: string;
title: string;
icon: ReactNode;
disabled?: boolean;
loading?: boolean;
onClick?: () => void;
}) => {
const [open, setOpen] = useState(false);
const [input, setInput] = useState("");
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<ActionButton
title={title}
icon={icon}
disabled={disabled}
onClick={() => setOpen(true)}
loading={loading}
/>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Confirm {title}</DialogTitle>
</DialogHeader>
<div className="flex flex-col gap-4 my-4">
<p
onClick={() => {
navigator.clipboard.writeText(name);
toast({ title: `Copied "${name}" to clipboard!` });
}}
className="cursor-pointer"
>
Please enter <b>{name}</b> below to confirm this action.
<br />
<span className="text-xs text-muted-foreground">
You may click the name in bold to copy it
</span>
</p>
<Input value={input} onChange={(e) => setInput(e.target.value)} />
</div>
<DialogFooter>
<ActionButton
title={title}
icon={icon}
disabled={name !== input}
onClick={() => {
onClick && onClick();
setOpen(false);
}}
/>
</DialogFooter>
</DialogContent>
</Dialog>
);
};
export const CopyResource = ({
id,
disabled,
type,
}: {
id: string;
disabled?: boolean;
type: "Deployment" | "Build";
}) => {
const [open, setOpen] = useState(false);
const [name, setName] = useState("");
const nav = useNavigate();
const inv = useInvalidate();
const { mutate } = useWrite(`Copy${type}`, {
onSuccess: (res) => {
inv([`List${type}s`]);
nav(`/${type.toLowerCase()}s/${res._id?.$oid}`);
},
});
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<ActionButton
title="Copy"
icon={<Copy className="w-4 h-4" />}
disabled={disabled}
onClick={() => setOpen(true)}
/>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Copy {type}</DialogTitle>
</DialogHeader>
<div className="flex flex-col gap-4 my-4">
<p>Provide a name for the newly created {type.toLowerCase()}.</p>
<Input value={name} onChange={(e) => setName(e.target.value)} />
</div>
<DialogFooter>
<ActionButton
title="Confirm"
icon={<Check className="w-4 h-4" />}
disabled={!name}
onClick={() => {
mutate({ id, name });
setOpen(false);
}}
/>
</DialogFooter>
</DialogContent>
</Dialog>
);
};
export const ConfirmButton = ({
title,
icon,
disabled,
loading,
onClick,
}: {
title: string;
icon: ReactNode;
onClick: () => void;
loading?: boolean;
disabled?: boolean;
}) => {
const [confirmed, set] = useState(false);
return (
<>
<ActionButton
title={confirmed ? "Confirm" : title}
icon={confirmed ? <Check className="w-4 h-4" /> : icon}
disabled={disabled}
onClick={
confirmed
? () => {
onClick();
set(false);
}
: () => set(true)
}
className={confirmed ? "z-50" : ""}
loading={loading}
/>
{confirmed && (
<div
className="fixed z-40 top-0 left-0 w-[100vw] h-[100vh]"
onClick={() => set(false)}
/>
)}
</>
);
};
export const ResourceTypeDropdown = () => {
const type = useResourceParamType();
const Components = ResourceComponents[type];
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" className="w-36 justify-between px-3">
<div className="flex items-center gap-2">
{type ? <Components.Icon /> : <Box className="w-4 h-4" />}
{type ? type + "s" : "Dashboard"}
</div>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-36" side="bottom">
<DropdownMenuGroup>
<Link to="/">
<DropdownMenuItem className="flex items-center gap-2">
<Box className="w-4 h-4" />
Dashboard
</DropdownMenuItem>
</Link>
{RESOURCE_TARGETS.map((rt) => {
const RTIcon = ResourceComponents[rt].Icon;
return (
<Link key={rt} to={`/${rt.toLowerCase()}s`}>
<DropdownMenuItem className="flex items-center gap-2">
<RTIcon />
{rt}s
</DropdownMenuItem>
</Link>
);
})}
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
);
};
export const ResourcesDropdown = () => {
const type = useResourceParamType();
const id = useParams().id as string;
const list = useRead(`List${type}s`, {}).data;
const selected = list?.find((i) => i.id === id);
const Components = ResourceComponents[type];
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" className="w-48 justify-between px-3">
<div className="flex items-center gap-2">
<Components.Icon id={selected?.id} />
{selected ? selected.name : `All ${type}s`}
</div>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-48" side="bottom">
<DropdownMenuGroup>
{!list?.length && (
<DropdownMenuItem disabled>No {type}s Found.</DropdownMenuItem>
)}
{list?.length && (
<Link to={`/${type.toLowerCase()}s`}>
<DropdownMenuItem className="flex items-center gap-2">
<Components.Icon />
All {type}s
</DropdownMenuItem>
</Link>
)}
{list?.map(({ id, name }) => (
<Link key={id} to={`/${type.toLowerCase()}s/${id}`}>
<DropdownMenuItem className="flex items-center gap-2">
<Components.Icon id={id} />
{name}
</DropdownMenuItem>
</Link>
))}
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
);
};
export const Logout = () => (
<Button
variant="ghost"
size="icon"
onClick={() => {
localStorage.removeItem("monitor-auth-token");
window.location.reload();
}}
>
<LogOut className="w-4 h-4" />
</Button>
);
export const UserDropdown = () => {
const user = useRead("GetUser", {}).data;
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button className="gap-2" variant="outline">
<WsStatusIndicator />
{user?.username}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent></DropdownMenuContent>
</DropdownMenu>
);
};

View File

@@ -1,175 +0,0 @@
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap");
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
* {
@apply border-border;
}
*::-webkit-scrollbar {
background-color: hsl(var(--border));
@apply w-2;
}
*::-webkit-scrollbar-thumb {
background-color: hsl(var(--primary));
@apply rounded-lg;
}
html {
scrollbar-gutter: stable;
}
body {
@apply bg-background text-foreground;
font-family: Inter;
}
pre {
@apply bg-accent/50 min-h-full text-xs p-4 whitespace-pre-wrap scroll-m-4 break-all rounded-md;
}
}
/* blue theme */
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%;
--popover: 0 0% 100%;
--popover-foreground: 222.2 84% 4.9%;
--primary: 221.2 83.2% 53.3%;
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%;
--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;
--accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 40% 98%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--ring: 221.2 83.2% 53.3%;
--radius: 0.5rem;
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--card: 222.2 84% 4.9%;
--card-foreground: 210 40% 98%;
--popover: 222.2 84% 4.9%;
--popover-foreground: 210 40% 98%;
--primary: 217.2 91.2% 59.8%;
--primary-foreground: 222.2 47.4% 11.2%;
--secondary: 217.2 32.6% 17.5%;
--secondary-foreground: 210 40% 98%;
--muted: 217.2 32.6% 17.5%;
--muted-foreground: 215 20.2% 65.1%;
--accent: 217.2 32.6% 17.5%;
--accent-foreground: 210 40% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 210 40% 98%;
--border: 217.2 32.6% 17.5%;
--input: 217.2 32.6% 17.5%;
--ring: 224.3 76.3% 48%;
}
}
/* green theme */
/*
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 240 10% 3.9%;
--card: 0 0% 100%;
--card-foreground: 240 10% 3.9%;
--popover: 0 0% 100%;
--popover-foreground: 240 10% 3.9%;
--primary: 142.1 76.2% 36.3%;
--primary-foreground: 355.7 100% 97.3%;
--secondary: 240 4.8% 95.9%;
--secondary-foreground: 240 5.9% 10%;
--muted: 240 4.8% 95.9%;
--muted-foreground: 240 3.8% 46.1%;
--accent: 240 4.8% 95.9%;
--accent-foreground: 240 5.9% 10%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 0% 98%;
--border: 240 5.9% 90%;
--input: 240 5.9% 90%;
--ring: 142.1 76.2% 36.3%;
--radius: 0.5rem;
}
.dark {
--background: 20 14.3% 4.1%;
--foreground: 0 0% 95%;
--card: 24 9.8% 10%;
--card-foreground: 0 0% 95%;
--popover: 0 0% 9%;
--popover-foreground: 0 0% 95%;
--primary: 142.1 70.6% 45.3%;
--primary-foreground: 144.9 80.4% 10%;
--secondary: 240 3.7% 15.9%;
--secondary-foreground: 0 0% 98%;
--muted: 0 0% 15%;
--muted-foreground: 240 5% 64.9%;
--accent: 12 6.5% 15.1%;
--accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 85.7% 97.3%;
--border: 240 3.7% 15.9%;
--input: 240 3.7% 15.9%;
--ring: 142.4 71.8% 29.2%;
}
} */
/* neutral theme */
/* @layer base {
:root {
--background: 0 0% 100%;
--foreground: 0 0% 3.9%;
--card: 0 0% 100%;
--card-foreground: 0 0% 3.9%;
--popover: 0 0% 100%;
--popover-foreground: 0 0% 3.9%;
--primary: 0 0% 9%;
--primary-foreground: 0 0% 98%;
--secondary: 0 0% 96.1%;
--secondary-foreground: 0 0% 9%;
--muted: 0 0% 96.1%;
--muted-foreground: 0 0% 45.1%;
--accent: 0 0% 96.1%;
--accent-foreground: 0 0% 9%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 0% 98%;
--border: 0 0% 89.8%;
--input: 0 0% 89.8%;
--ring: 0 0% 3.9%;
--radius: 0.5rem;
}
.dark {
--background: 0 0% 3.9%;
--foreground: 0 0% 98%;
--card: 0 0% 3.9%;
--card-foreground: 0 0% 98%;
--popover: 0 0% 3.9%;
--popover-foreground: 0 0% 98%;
--primary: 0 0% 98%;
--primary-foreground: 0 0% 9%;
--secondary: 0 0% 14.9%;
--secondary-foreground: 0 0% 98%;
--muted: 0 0% 14.9%;
--muted-foreground: 0 0% 63.9%;
--accent: 0 0% 14.9%;
--accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 0% 98%;
--border: 0 0% 14.9%;
--input: 0 0% 14.9%;
--ring: 0 0% 83.1%;
}
} */

View File

@@ -1,34 +0,0 @@
import "globals.css";
import ReactDOM from "react-dom/client";
import { MonitorClient } from "@monitor/client";
import { ThemeProvider } from "@ui/theme";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { Router } from "@router";
import { WebsocketProvider } from "@lib/socket";
import { Toaster } from "@ui/toaster";
export const MONITOR_BASE_URL =
import.meta.env.VITE_MONITOR_HOST ?? "https://v1.api.monitor.dev";
export const UPDATE_WS_URL =
MONITOR_BASE_URL.replace("http", "ws") + "/ws/update";
const token = localStorage.getItem("monitor-auth-token");
export const client = MonitorClient(MONITOR_BASE_URL, token ?? undefined);
const query_client = new QueryClient({
defaultOptions: { queries: { retry: false } },
});
ReactDOM.createRoot(document.getElementById("root")!).render(
// <React.StrictMode>
<QueryClientProvider client={query_client}>
<WebsocketProvider url={UPDATE_WS_URL}>
<ThemeProvider>
<Router />
<Toaster />
</ThemeProvider>
</WebsocketProvider>
</QueryClientProvider>
// </React.StrictMode>
);

View File

@@ -1,33 +0,0 @@
import { Layout } from "@components/layouts";
import { useRead } from "@lib/hooks";
import { Dashboard } from "@pages/dashboard";
import { Login } from "@pages/login";
import { Resource } from "@pages/resource";
import { Resources } from "@pages/resources";
import { RouterProvider, createBrowserRouter } from "react-router-dom";
const router = createBrowserRouter([
{
path: "/",
element: <Layout />,
children: [
{ path: "", element: <Dashboard /> },
{
path: ":type",
children: [
{ path: "", element: <Resources /> },
{ path: ":id", element: <Resource /> },
],
},
],
},
]);
export const Router = () => {
const { data: user, isLoading } = useRead("GetUser", {});
if (isLoading) return null;
if (!user) return <Login />;
return <RouterProvider router={router} />;
};

View File

@@ -1,57 +0,0 @@
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@lib/utils"
const buttonVariants = cva(
"inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
default:
"bg-primary text-primary-foreground shadow hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
outline:
"border border-input bg-transparent shadow-sm hover:bg-accent hover:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-9 px-4 py-2",
sm: "h-8 rounded-md px-3 text-xs",
lg: "h-10 rounded-md px-8",
icon: "h-9 w-9",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
)
}
)
Button.displayName = "Button"
export { Button, buttonVariants }

View File

@@ -1,82 +0,0 @@
import * as React from "react";
import { cn } from "@lib/utils";
const Card = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(
"rounded-xl border bg-card text-card-foreground shadow",
className
)}
{...props}
/>
));
Card.displayName = "Card";
const CardHeader = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div ref={ref} className={cn("flex p-6", className)} {...props} />
));
CardHeader.displayName = "CardHeader";
const CardTitle = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLHeadingElement>
>(({ className, ...props }, ref) => (
<h3
ref={ref}
className={cn(
"font-semibold text-lg leading-none tracking-tight",
className
)}
{...props}
/>
));
CardTitle.displayName = "CardTitle";
const CardDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
<p
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
));
CardDescription.displayName = "CardDescription";
const CardContent = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
));
CardContent.displayName = "CardContent";
const CardFooter = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex items-center p-6 pt-0", className)}
{...props}
/>
));
CardFooter.displayName = "CardFooter";
export {
Card,
CardHeader,
CardFooter,
CardTitle,
CardDescription,
CardContent,
};

View File

@@ -1,153 +0,0 @@
import * as React from "react"
import { DialogProps } from "@radix-ui/react-dialog"
import { MagnifyingGlassIcon } from "@radix-ui/react-icons"
import { Command as CommandPrimitive } from "cmdk"
import { cn } from "@lib/utils"
import { Dialog, DialogContent } from "@//ui/dialog"
const Command = React.forwardRef<
React.ElementRef<typeof CommandPrimitive>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive>
>(({ className, ...props }, ref) => (
<CommandPrimitive
ref={ref}
className={cn(
"flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground",
className
)}
{...props}
/>
))
Command.displayName = CommandPrimitive.displayName
interface CommandDialogProps extends DialogProps {}
const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
return (
<Dialog {...props}>
<DialogContent className="overflow-hidden p-0">
<Command className="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
{children}
</Command>
</DialogContent>
</Dialog>
)
}
const CommandInput = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Input>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
>(({ className, ...props }, ref) => (
<div className="flex items-center border-b px-3" cmdk-input-wrapper="">
<MagnifyingGlassIcon className="mr-2 h-4 w-4 shrink-0 opacity-50" />
<CommandPrimitive.Input
ref={ref}
className={cn(
"flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
className
)}
{...props}
/>
</div>
))
CommandInput.displayName = CommandPrimitive.Input.displayName
const CommandList = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.List>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>
>(({ className, ...props }, ref) => (
<CommandPrimitive.List
ref={ref}
className={cn("max-h-[300px] overflow-y-auto overflow-x-hidden", className)}
{...props}
/>
))
CommandList.displayName = CommandPrimitive.List.displayName
const CommandEmpty = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Empty>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>
>((props, ref) => (
<CommandPrimitive.Empty
ref={ref}
className="py-6 text-center text-sm"
{...props}
/>
))
CommandEmpty.displayName = CommandPrimitive.Empty.displayName
const CommandGroup = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Group>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group>
>(({ className, ...props }, ref) => (
<CommandPrimitive.Group
ref={ref}
className={cn(
"overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground",
className
)}
{...props}
/>
))
CommandGroup.displayName = CommandPrimitive.Group.displayName
const CommandSeparator = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator>
>(({ className, ...props }, ref) => (
<CommandPrimitive.Separator
ref={ref}
className={cn("-mx-1 h-px bg-border", className)}
{...props}
/>
))
CommandSeparator.displayName = CommandPrimitive.Separator.displayName
const CommandItem = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>
>(({ className, ...props }, ref) => (
<CommandPrimitive.Item
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none aria-selected:bg-accent aria-selected:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
{...props}
/>
))
CommandItem.displayName = CommandPrimitive.Item.displayName
const CommandShortcut = ({
className,
...props
}: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span
className={cn(
"ml-auto text-xs tracking-widest text-muted-foreground",
className
)}
{...props}
/>
)
}
CommandShortcut.displayName = "CommandShortcut"
export {
Command,
CommandDialog,
CommandInput,
CommandList,
CommandEmpty,
CommandGroup,
CommandItem,
CommandShortcut,
CommandSeparator,
}

View File

@@ -1,121 +0,0 @@
import * as React from "react"
import * as DialogPrimitive from "@radix-ui/react-dialog"
import { Cross2Icon } from "@radix-ui/react-icons"
import { cn } from "@lib/utils"
const Dialog = DialogPrimitive.Root
const DialogTrigger = DialogPrimitive.Trigger
const DialogPortal = ({
className,
...props
}: DialogPrimitive.DialogPortalProps) => (
<DialogPrimitive.Portal className={cn(className)} {...props} />
)
DialogPortal.displayName = DialogPrimitive.Portal.displayName
const DialogOverlay = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Overlay
ref={ref}
className={cn(
"fixed inset-0 z-50 bg-background/80 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className
)}
{...props}
/>
))
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<DialogPortal>
<DialogOverlay />
<DialogPrimitive.Content
ref={ref}
className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg md:w-full",
className
)}
{...props}
>
{children}
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
<Cross2Icon className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</DialogPortal>
))
DialogContent.displayName = DialogPrimitive.Content.displayName
const DialogHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col space-y-1.5 text-center sm:text-left",
className
)}
{...props}
/>
)
DialogHeader.displayName = "DialogHeader"
const DialogFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className
)}
{...props}
/>
)
DialogFooter.displayName = "DialogFooter"
const DialogTitle = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Title
ref={ref}
className={cn(
"text-lg font-semibold leading-none tracking-tight",
className
)}
{...props}
/>
))
DialogTitle.displayName = DialogPrimitive.Title.displayName
const DialogDescription = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Description
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
))
DialogDescription.displayName = DialogPrimitive.Description.displayName
export {
Dialog,
DialogTrigger,
DialogContent,
DialogHeader,
DialogFooter,
DialogTitle,
DialogDescription,
}

View File

@@ -1,25 +0,0 @@
import * as React from "react"
import { cn } from "@lib/utils"
export interface InputProps
extends React.InputHTMLAttributes<HTMLInputElement> {}
const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, type, ...props }, ref) => {
return (
<input
type={type}
className={cn(
"flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
className
)}
ref={ref}
{...props}
/>
)
}
)
Input.displayName = "Input"
export { Input }

View File

@@ -1,24 +0,0 @@
import * as React from "react"
import * as LabelPrimitive from "@radix-ui/react-label"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@lib/utils"
const labelVariants = cva(
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
)
const Label = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
VariantProps<typeof labelVariants>
>(({ className, ...props }, ref) => (
<LabelPrimitive.Root
ref={ref}
className={cn(labelVariants(), className)}
{...props}
/>
))
Label.displayName = LabelPrimitive.Root.displayName
export { Label }

View File

@@ -1,26 +0,0 @@
import * as React from "react"
import * as ProgressPrimitive from "@radix-ui/react-progress"
import { cn } from "@lib/utils"
const Progress = React.forwardRef<
React.ElementRef<typeof ProgressPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root>
>(({ className, value, ...props }, ref) => (
<ProgressPrimitive.Root
ref={ref}
className={cn(
"relative h-2 w-full overflow-hidden rounded-full bg-primary/20",
className
)}
{...props}
>
<ProgressPrimitive.Indicator
className="h-full w-full flex-1 bg-primary transition-all"
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
/>
</ProgressPrimitive.Root>
))
Progress.displayName = ProgressPrimitive.Root.displayName
export { Progress }

View File

@@ -1,118 +0,0 @@
import * as React from "react"
import { CaretSortIcon, CheckIcon } from "@radix-ui/react-icons"
import * as SelectPrimitive from "@radix-ui/react-select"
import { cn } from "@lib/utils"
const Select = SelectPrimitive.Root
const SelectGroup = SelectPrimitive.Group
const SelectValue = SelectPrimitive.Value
const SelectTrigger = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<SelectPrimitive.Trigger
ref={ref}
className={cn(
"flex h-9 w-full items-center justify-between rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
className
)}
{...props}
>
{children}
<SelectPrimitive.Icon asChild>
<CaretSortIcon className="h-4 w-4 opacity-50" />
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
))
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
const SelectContent = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
>(({ className, children, position = "popper", ...props }, ref) => (
<SelectPrimitive.Portal>
<SelectPrimitive.Content
ref={ref}
className={cn(
"relative z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
position === "popper" &&
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
className
)}
position={position}
{...props}
>
<SelectPrimitive.Viewport
className={cn(
"p-1",
position === "popper" &&
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
)}
>
{children}
</SelectPrimitive.Viewport>
</SelectPrimitive.Content>
</SelectPrimitive.Portal>
))
SelectContent.displayName = SelectPrimitive.Content.displayName
const SelectLabel = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Label
ref={ref}
className={cn("px-2 py-1.5 text-sm font-semibold", className)}
{...props}
/>
))
SelectLabel.displayName = SelectPrimitive.Label.displayName
const SelectItem = 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 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
{...props}
>
<span className="absolute right-2 flex h-3.5 w-3.5 items-center justify-center">
<SelectPrimitive.ItemIndicator>
<CheckIcon className="h-4 w-4" />
</SelectPrimitive.ItemIndicator>
</span>
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
</SelectPrimitive.Item>
))
SelectItem.displayName = SelectPrimitive.Item.displayName
const SelectSeparator = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Separator
ref={ref}
className={cn("-mx-1 my-1 h-px bg-muted", className)}
{...props}
/>
))
SelectSeparator.displayName = SelectPrimitive.Separator.displayName
export {
Select,
SelectGroup,
SelectValue,
SelectTrigger,
SelectContent,
SelectLabel,
SelectItem,
SelectSeparator,
}

View File

@@ -1,142 +0,0 @@
import * as React from "react";
import * as SheetPrimitive from "@radix-ui/react-dialog";
import { Cross2Icon } from "@radix-ui/react-icons";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@lib/utils";
const Sheet = SheetPrimitive.Root;
const SheetTrigger = SheetPrimitive.Trigger;
const SheetClose = SheetPrimitive.Close;
const SheetPortal = ({
className,
...props
}: SheetPrimitive.DialogPortalProps) => (
<SheetPrimitive.Portal className={cn(className)} {...props} />
);
SheetPortal.displayName = SheetPrimitive.Portal.displayName;
const SheetOverlay = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<SheetPrimitive.Overlay
className={cn(
"fixed inset-0 z-50 bg-background/80 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className
)}
{...props}
ref={ref}
/>
));
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName;
const sheetVariants = cva(
"fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-150 data-[state=open]:duration-300",
{
variants: {
side: {
top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
bottom:
"inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left",
right:
"inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right",
},
},
defaultVariants: {
side: "right",
},
}
);
interface SheetContentProps
extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
VariantProps<typeof sheetVariants> {}
const SheetContent = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Content>,
SheetContentProps
>(({ side = "right", className, children, ...props }, ref) => (
<SheetPortal>
<SheetOverlay />
<SheetPrimitive.Content
ref={ref}
className={cn(sheetVariants({ side }), className)}
{...props}
>
{children}
<SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
<Cross2Icon className="h-4 w-4" />
<span className="sr-only">Close</span>
</SheetPrimitive.Close>
</SheetPrimitive.Content>
</SheetPortal>
));
SheetContent.displayName = SheetPrimitive.Content.displayName;
const SheetHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col space-y-2 text-center sm:text-left",
className
)}
{...props}
/>
);
SheetHeader.displayName = "SheetHeader";
const SheetFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className
)}
{...props}
/>
);
SheetFooter.displayName = "SheetFooter";
const SheetTitle = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>
>(({ className, ...props }, ref) => (
<SheetPrimitive.Title
ref={ref}
className={cn("text-lg font-semibold text-foreground", className)}
{...props}
/>
));
SheetTitle.displayName = SheetPrimitive.Title.displayName;
const SheetDescription = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>
>(({ className, ...props }, ref) => (
<SheetPrimitive.Description
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
));
SheetDescription.displayName = SheetPrimitive.Description.displayName;
export {
Sheet,
SheetTrigger,
SheetClose,
SheetContent,
SheetHeader,
SheetFooter,
SheetTitle,
SheetDescription,
};

View File

@@ -1,27 +0,0 @@
import * as React from "react"
import * as SwitchPrimitives from "@radix-ui/react-switch"
import { cn } from "@lib/utils"
const Switch = React.forwardRef<
React.ElementRef<typeof SwitchPrimitives.Root>,
React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>
>(({ className, ...props }, ref) => (
<SwitchPrimitives.Root
className={cn(
"peer inline-flex h-[20px] w-[36px] shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
className
)}
{...props}
ref={ref}
>
<SwitchPrimitives.Thumb
className={cn(
"pointer-events-none block h-4 w-4 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0"
)}
/>
</SwitchPrimitives.Root>
))
Switch.displayName = SwitchPrimitives.Root.displayName
export { Switch }

View File

@@ -1,53 +0,0 @@
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-9 items-center justify-center rounded-lg 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-md px-3 py-1 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",
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 }

View File

@@ -1,24 +0,0 @@
import * as React from "react"
import { cn } from "@lib/utils"
export interface TextareaProps
extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
({ className, ...props }, ref) => {
return (
<textarea
className={cn(
"flex min-h-[60px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
className
)}
ref={ref}
{...props}
/>
)
}
)
Textarea.displayName = "Textarea"
export { Textarea }

View File

@@ -1 +0,0 @@
/// <reference types="vite/client" />

View File

@@ -1,76 +0,0 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
darkMode: ["class"],
content: [
'./pages/**/*.{ts,tsx}',
'./components/**/*.{ts,tsx}',
'./app/**/*.{ts,tsx}',
'./src/**/*.{ts,tsx}',
],
theme: {
container: {
center: true,
padding: "2rem",
screens: {
"2xl": "1400px",
},
},
extend: {
colors: {
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
primary: {
DEFAULT: "hsl(var(--primary))",
foreground: "hsl(var(--primary-foreground))",
},
secondary: {
DEFAULT: "hsl(var(--secondary))",
foreground: "hsl(var(--secondary-foreground))",
},
destructive: {
DEFAULT: "hsl(var(--destructive))",
foreground: "hsl(var(--destructive-foreground))",
},
muted: {
DEFAULT: "hsl(var(--muted))",
foreground: "hsl(var(--muted-foreground))",
},
accent: {
DEFAULT: "hsl(var(--accent))",
foreground: "hsl(var(--accent-foreground))",
},
popover: {
DEFAULT: "hsl(var(--popover))",
foreground: "hsl(var(--popover-foreground))",
},
card: {
DEFAULT: "hsl(var(--card))",
foreground: "hsl(var(--card-foreground))",
},
},
borderRadius: {
lg: "var(--radius)",
md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)",
},
keyframes: {
"accordion-down": {
from: { height: 0 },
to: { height: "var(--radix-accordion-content-height)" },
},
"accordion-up": {
from: { height: "var(--radix-accordion-content-height)" },
to: { height: 0 },
},
},
animation: {
"accordion-down": "accordion-down 0.2s ease-out",
"accordion-up": "accordion-up 0.2s ease-out",
},
},
},
plugins: [require("tailwindcss-animate")],
}

View File

@@ -1,29 +0,0 @@
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"lib": ["ESNext", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
/* Paths */
"baseUrl": "./src",
"paths": { "@*": ["./*"] }
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}

View File

@@ -1,10 +0,0 @@
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}

View File

@@ -1,8 +0,0 @@
import { defineConfig } from "vite";
import tspaths from "vite-tsconfig-paths";
import react from "@vitejs/plugin-react";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react(), tspaths()],
});

File diff suppressed because it is too large Load Diff

View File

@@ -1,14 +1,18 @@
module.exports = { module.exports = {
root: true,
env: { browser: true, es2020: true }, env: { browser: true, es2020: true },
extends: [ extends: [
'eslint:recommended', 'eslint:recommended',
'plugin:@typescript-eslint/recommended', 'plugin:@typescript-eslint/recommended',
'plugin:react-hooks/recommended', 'plugin:react-hooks/recommended',
], ],
ignorePatterns: ['dist', '.eslintrc.cjs'],
parser: '@typescript-eslint/parser', parser: '@typescript-eslint/parser',
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
plugins: ['react-refresh'], plugins: ['react-refresh'],
rules: { rules: {
'react-refresh/only-export-components': 'warn', 'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
}, },
} }

View File

@@ -2,10 +2,10 @@ FROM node:20.5-alpine
WORKDIR /app WORKDIR /app
COPY ./frontend ./frontend COPY ./frontend-v2 ./frontend
COPY ./client/ts ./client COPY ./client/ts ./client
RUN cd client && yarn && yarn build && yarn link RUN cd client && yarn && yarn build && yarn link
RUN cd frontend && yarn link @monitor/client && yarn && yarn build RUN cd frontend && yarn link @monitor/client && yarn && yarn build
CMD cd frontend && yarn preview --host CMD cd frontend && yarn preview --host --port 4174

View File

@@ -6,11 +6,11 @@
"tailwind": { "tailwind": {
"config": "tailwind.config.js", "config": "tailwind.config.js",
"css": "src/globals.css", "css": "src/globals.css",
"baseColor": "slate", "baseColor": "neutral",
"cssVariables": true "cssVariables": true
}, },
"aliases": { "aliases": {
"components": "@/", "components": "@/",
"utils": "@util/helpers" "utils": "@lib/utils"
} }
} }

View File

@@ -2,12 +2,11 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/png" href="favicon.png" /> <link rel="icon" type="image/svg+xml" href="/vite.svg" />
<link rel="manifest" href="manifest.json" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Monitor</title> <title>Monitor</title>
</head> </head>
<body class="min-h-screen bg-background font-sans antialiased"> <body class="min-h-screen">
<div id="root"></div> <div id="root"></div>
<script type="module" src="/src/main.tsx"></script> <script type="module" src="/src/main.tsx"></script>
</body> </body>

View File

@@ -1,63 +1,55 @@
{ {
"name": "@monitor/frontend", "name": "frontend",
"private": true, "private": true,
"version": "0.0.0", "version": "0.0.0",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "tsc && vite build", "build": "tsc && vite build",
"lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0", "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview", "preview": "vite preview",
"regen-client": "cd ../client/ts && yarn build" "regen-client": "cd ../client/ts && yarn build"
}, },
"dependencies": { "dependencies": {
"@monitor/client": "link:../client/ts",
"@radix-ui/react-accordion": "^1.1.2",
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-dialog": "^1.0.4", "@radix-ui/react-dialog": "^1.0.4",
"@radix-ui/react-dropdown-menu": "^2.0.5", "@radix-ui/react-dropdown-menu": "^2.0.5",
"@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-label": "^2.0.2", "@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-popover": "^1.0.6",
"@radix-ui/react-progress": "^1.0.3", "@radix-ui/react-progress": "^1.0.3",
"@radix-ui/react-select": "^1.2.2", "@radix-ui/react-select": "^1.2.2",
"@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-switch": "^1.0.3", "@radix-ui/react-switch": "^1.0.3",
"@radix-ui/react-tabs": "^1.0.3", "@radix-ui/react-tabs": "^1.0.4",
"@radix-ui/react-toast": "^1.1.4", "@radix-ui/react-toast": "^1.1.4",
"@tanstack/react-query": "^4.29.7", "@tanstack/react-query": "^4.33.0",
"autoprefixer": "^10.4.14",
"axios": "^1.4.0",
"class-variance-authority": "^0.7.0", "class-variance-authority": "^0.7.0",
"clsx": "^2.0.0", "clsx": "^2.0.0",
"cmdk": "^0.2.0", "cmdk": "^0.2.0",
"jotai": "^2.1.0", "jotai": "^2.4.1",
"js-file-download": "^0.4.12",
"lightweight-charts": "^4.0.1", "lightweight-charts": "^4.0.1",
"lucide-react": "^0.221.0", "lucide-react": "^0.274.0",
"postcss": "^8.4.23",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-minimal-pie-chart": "^8.4.0", "react-minimal-pie-chart": "^8.4.0",
"react-router-dom": "^6.11.2", "react-router-dom": "^6.15.0",
"reconnecting-websocket": "^4.4.0", "reconnecting-websocket": "^4.4.0",
"sanitize-html": "^2.10.0",
"tailwind-merge": "^1.14.0", "tailwind-merge": "^1.14.0",
"tailwindcss": "^3.3.2", "tailwindcss-animate": "^1.0.7"
"tailwindcss-animate": "^1.0.7",
"vite-tsconfig-paths": "^4.2.0"
}, },
"devDependencies": { "devDependencies": {
"@types/react": "^18.0.28", "@types/react": "^18.2.15",
"@types/react-dom": "^18.0.11", "@types/react-dom": "^18.2.7",
"@types/sanitize-html": "^2.9.0", "@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/eslint-plugin": "^5.57.1", "@typescript-eslint/parser": "^6.0.0",
"@typescript-eslint/parser": "^5.57.1", "@vitejs/plugin-react": "^4.0.3",
"@vitejs/plugin-react": "^4.0.0", "autoprefixer": "^10.4.15",
"eslint": "^8.38.0", "eslint": "^8.45.0",
"eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.3.4", "eslint-plugin-react-refresh": "^0.4.3",
"postcss": "^8.4.29",
"tailwindcss": "^3.3.3",
"typescript": "^5.0.2", "typescript": "^5.0.2",
"vite": "^4.3.2" "vite": "^4.4.5",
"vite-tsconfig-paths": "^4.2.0"
} }
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 196 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 599 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 791 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 227 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 326 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 433 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 453 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 494 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 493 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 555 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 599 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 640 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 165 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 681 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 744 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 791 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 168 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 183 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 203 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 222 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 249 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 273 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 294 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 280 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 296 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 326 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 346 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 361 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 399 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 377 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 366 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 395 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 513 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 550 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 705 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 839 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 165 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 168 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 180 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 189 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 203 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 206 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 222 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 232 B

Some files were not shown because too many files have changed in this diff Show More