rename frontend-v2 to frontend
@@ -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 },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
24
frontend-v2/.gitignore
vendored
@@ -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?
|
|
||||||
@@ -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
|
|
||||||
@@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
@@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
export default {
|
|
||||||
plugins: {
|
|
||||||
tailwindcss: {},
|
|
||||||
autoprefixer: {},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -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>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -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>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -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>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -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%;
|
|
||||||
}
|
|
||||||
} */
|
|
||||||
@@ -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>
|
|
||||||
);
|
|
||||||
@@ -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} />;
|
|
||||||
};
|
|
||||||
@@ -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 }
|
|
||||||
@@ -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,
|
|
||||||
};
|
|
||||||
@@ -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,
|
|
||||||
}
|
|
||||||
@@ -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,
|
|
||||||
}
|
|
||||||
@@ -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 }
|
|
||||||
@@ -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 }
|
|
||||||
@@ -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 }
|
|
||||||
@@ -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,
|
|
||||||
}
|
|
||||||
@@ -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,
|
|
||||||
};
|
|
||||||
@@ -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 }
|
|
||||||
@@ -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 }
|
|
||||||
@@ -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 }
|
|
||||||
1
frontend-v2/src/vite-env.d.ts
vendored
@@ -1 +0,0 @@
|
|||||||
/// <reference types="vite/client" />
|
|
||||||
@@ -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")],
|
|
||||||
}
|
|
||||||
@@ -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" }]
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"composite": true,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"module": "ESNext",
|
|
||||||
"moduleResolution": "bundler",
|
|
||||||
"allowSyntheticDefaultImports": true
|
|
||||||
},
|
|
||||||
"include": ["vite.config.ts"]
|
|
||||||
}
|
|
||||||
@@ -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()],
|
|
||||||
});
|
|
||||||
@@ -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 },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 196 B |
|
Before Width: | Height: | Size: 599 B |
|
Before Width: | Height: | Size: 791 B |
|
Before Width: | Height: | Size: 227 B |
|
Before Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 326 B |
|
Before Width: | Height: | Size: 433 B |
|
Before Width: | Height: | Size: 453 B |
|
Before Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 494 B |
|
Before Width: | Height: | Size: 493 B |
|
Before Width: | Height: | Size: 555 B |
|
Before Width: | Height: | Size: 599 B |
|
Before Width: | Height: | Size: 640 B |
|
Before Width: | Height: | Size: 165 B |
|
Before Width: | Height: | Size: 681 B |
|
Before Width: | Height: | Size: 744 B |
|
Before Width: | Height: | Size: 791 B |
|
Before Width: | Height: | Size: 168 B |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 183 B |
|
Before Width: | Height: | Size: 203 B |
|
Before Width: | Height: | Size: 222 B |
|
Before Width: | Height: | Size: 249 B |
|
Before Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 273 B |
|
Before Width: | Height: | Size: 294 B |
|
Before Width: | Height: | Size: 280 B |
|
Before Width: | Height: | Size: 296 B |
|
Before Width: | Height: | Size: 326 B |
|
Before Width: | Height: | Size: 346 B |
|
Before Width: | Height: | Size: 361 B |
|
Before Width: | Height: | Size: 399 B |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 8.4 KiB |
|
Before Width: | Height: | Size: 377 B |
|
Before Width: | Height: | Size: 366 B |
|
Before Width: | Height: | Size: 395 B |
|
Before Width: | Height: | Size: 513 B |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 550 B |
|
Before Width: | Height: | Size: 705 B |
|
Before Width: | Height: | Size: 839 B |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 165 B |
|
Before Width: | Height: | Size: 168 B |
|
Before Width: | Height: | Size: 180 B |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 189 B |
|
Before Width: | Height: | Size: 203 B |
|
Before Width: | Height: | Size: 206 B |
|
Before Width: | Height: | Size: 222 B |
|
Before Width: | Height: | Size: 232 B |