add a god damn sidebar

This commit is contained in:
mbecker20
2024-05-10 17:07:51 -07:00
parent 1829a7da34
commit 5e7445b10d
17 changed files with 243 additions and 65 deletions

View File

@@ -13,7 +13,7 @@
<title>Monitor</title>
</head>
<body class="min-h-screen pb-12">
<body class="min-h-screen">
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>

View File

@@ -19,6 +19,7 @@
"@radix-ui/react-popover": "1.0.7",
"@radix-ui/react-progress": "1.0.3",
"@radix-ui/react-select": "2.0.0",
"@radix-ui/react-separator": "^1.0.3",
"@radix-ui/react-slot": "1.0.2",
"@radix-ui/react-switch": "1.0.3",
"@radix-ui/react-tabs": "1.0.4",

View File

@@ -144,7 +144,7 @@ export const Config = <T,>({
<CardTitle>{snake_case_to_upper_space_case(k)}</CardTitle>
</CardHeader>
)}
<CardContent className="flex flex-col gap-4 mt-4">
<CardContent className="flex flex-col gap-2 mt-4">
<ConfigAgain
config={config}
update={update}

View File

@@ -227,7 +227,10 @@ export const InputList = <T extends { [key: string]: unknown }>({
disabled: boolean;
set: (update: Partial<T>) => void;
}) => (
<ConfigItem label={field as string} className="items-start">
<ConfigItem
label={field as string}
className={values.length > 0 ? "items-start" : undefined}
>
<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}>
@@ -242,7 +245,7 @@ export const InputList = <T extends { [key: string]: unknown }>({
/>
{!disabled && (
<Button
variant="outline"
variant="secondary"
onClick={() =>
set({
[field]: [...values.filter((_, idx) => idx !== i)],
@@ -257,7 +260,7 @@ export const InputList = <T extends { [key: string]: unknown }>({
{!disabled && (
<Button
variant="outline"
variant="secondary"
onClick={() => set({ [field]: [...values, ""] } as Partial<T>)}
>
Add {snake_case_to_upper_space_case(field as string).slice(0, -1)}

View File

@@ -16,12 +16,20 @@ import { Card, CardHeader, CardTitle, CardContent, CardFooter } from "@ui/card";
import { ResourceTags } from "./tags";
import { Topbar } from "./topbar";
import { usableResourcePath } from "@lib/utils";
import { Sidebar } from "./sidebar";
export const Layout = () => {
return (
<>
<Topbar />
<div className="flex gap-2">
<Sidebar />
<div className="w-full h-[calc(100vh-70px)] overflow-y-auto">
<div className="pb-24">
<Outlet />
</div>
</div>
</div>
</>
);
};

View File

@@ -213,7 +213,10 @@ const ExtraArgs = ({
disabled: boolean;
}) => {
return (
<ConfigItem label="Extra Args" className="items-start">
<ConfigItem
label="Extra Args"
className={args.length > 0 ? "items-start" : undefined}
>
<div className="flex flex-col gap-4 w-full max-w-[400px]">
{args.map((arg, i) => (
<div className="w-full flex gap-4" key={i}>

View File

@@ -284,7 +284,10 @@ export const LabelsConfig = ({
set: (input: Partial<Types.DeploymentConfig | Types.BuildConfig>) => void;
disabled: boolean;
}) => (
<ConfigItem label="Labels" className="items-start">
<ConfigItem
label="Labels"
className={labels.length > 0 ? "items-start" : undefined}
>
<DoubleInput
disabled={disabled}
values={labels}

View File

@@ -25,7 +25,10 @@ export const ExtraArgs = ({
disabled: boolean;
}) => {
return (
<ConfigItem label="Extra Args" className="items-start">
<ConfigItem
label="Extra Args"
className={args.length > 0 ? "items-start" : undefined}
>
<div className="flex flex-col gap-4 w-full max-w-[400px]">
{args.map((arg, i) => (
<div className="w-full flex gap-4" key={i}>

View File

@@ -10,7 +10,10 @@ export const PortsConfig = ({
set: (input: Partial<Types.DeploymentConfig>) => void;
disabled: boolean;
}) => (
<ConfigItem label="Ports" className="items-start">
<ConfigItem
label="Ports"
className={ports.length > 0 ? "items-start" : undefined}
>
<DoubleInput
disabled={disabled}
values={ports}

View File

@@ -10,7 +10,10 @@ export const VolumesConfig = ({
set: (input: Partial<Types.DeploymentConfig>) => void;
disabled: boolean;
}) => (
<ConfigItem label="Volumes" className="items-start">
<ConfigItem
label="Volumes"
className={volumes.length > 0 ? "items-start" : undefined}
>
<DoubleInput
disabled={disabled}
inputClassName="w-[300px] max-w-full"

View File

@@ -66,7 +66,10 @@ export const AwsServerTemplateConfig = ({ id }: { id: string }) => {
),
volumes: (volumes, set) => {
return (
<ConfigItem label="EBS Volumes" className="items-start">
<ConfigItem
label="EBS Volumes"
className={volumes.length > 0 ? "items-start" : undefined}
>
<div className="flex flex-col gap-4 w-full max-w-[400px]">
{volumes.map((_, index) => (
<div
@@ -81,7 +84,7 @@ export const AwsServerTemplateConfig = ({ id }: { id: string }) => {
/>
{!disabled && (
<Button
variant="outline"
variant="secondary"
disabled={disabled}
onClick={() =>
set({
@@ -96,7 +99,7 @@ export const AwsServerTemplateConfig = ({ id }: { id: string }) => {
))}
{!disabled && (
<Button
variant="outline"
variant="secondary"
onClick={() =>
set({
volumes: [...volumes, newVolume(volumes.length)],

View File

@@ -0,0 +1,93 @@
import { RESOURCE_TARGETS, cn, usableResourcePath } from "@lib/utils";
import { Button } from "@ui/button";
import { Card, CardContent } from "@ui/card";
import { AlertTriangle, Bell, Box, Home, Tag, UserCircle2 } from "lucide-react";
import { Link, useLocation } from "react-router-dom";
import { ResourceComponents } from "./resources";
import { Separator } from "@ui/separator";
import { ReactNode } from "react";
export const Sidebar = () => {
return (
<Card className="h-fit m-4 hidden lg:flex">
<CardContent className="h-fit grid gap-2 px-6 py-4">
<SidebarLink label="Home" to="/" icon={<Home className="w-4 h-4" />} />
<Separator />
{RESOURCE_TARGETS.map((type) => {
const RTIcon = ResourceComponents[type].Icon;
const name = type === "ServerTemplate" ? "Template" : type;
return (
<SidebarLink
key={type}
label={`${name}s`}
to={`/${usableResourcePath(type)}`}
icon={<RTIcon />}
/>
);
})}
<Separator />
<SidebarLink
label="Alerts"
to="/alerts"
icon={<AlertTriangle className="w-4 h-4" />}
/>
<SidebarLink
label="Updates"
to="/updates"
icon={<Bell className="w-4 h-4" />}
/>
<SidebarLink
label="Tags"
to="/tags"
icon={<Tag className="w-4 h-4" />}
/>
<Separator />
<SidebarLink
label="Api Keys"
to="/keys"
icon={<Box className="w-4 h-4" />}
/>
<SidebarLink
label="Users"
to="/users"
icon={<UserCircle2 className="w-4 h-4" />}
/>
</CardContent>
</Card>
);
};
const SidebarLink = ({
to,
icon,
label,
}: {
to: string;
icon: ReactNode;
label: string;
}) => {
const location = useLocation();
return (
<Link to={to} className="w-full">
<Button
variant="link"
className={cn(
"flex justify-start items-center gap-2 w-full hover:bg-accent",
"/" + location.pathname.split("/")[1] === to && "bg-accent"
)}
>
{icon}
{label}
</Button>
</Link>
);
};

View File

@@ -1,4 +1,4 @@
import { useRead, useResourceParamType } from "@lib/hooks";
import { useRead, useResourceParamType, useUser } from "@lib/hooks";
import { ResourceComponents } from "./resources";
import {
AlertTriangle,
@@ -34,7 +34,7 @@ import { UsableResource } from "@types";
import { atomWithStorage } from "jotai/utils";
import { useAtom } from "jotai";
import { Popover, PopoverContent, PopoverTrigger } from "@ui/popover";
import { useState } from "react";
import { ReactNode, useState } from "react";
import {
Command,
CommandEmpty,
@@ -47,10 +47,10 @@ import { ResourceLink } from "./resources/common";
export const Topbar = () => {
return (
<div className="sticky top-0 border-b z-50 w-full bg-card text-card-foreground shadow">
<div className="sticky top-0 h-[70px] border-b z-50 w-full bg-card text-card-foreground shadow flex items-center">
<div className="container flex items-center justify-between py-4 gap-8">
<div className="flex items-center gap-4">
<Link to={"/"} className="text-2xl tracking-widest">
<Link to={"/"} className="text-2xl tracking-widest mx-6">
MONITOR
</Link>
<div className="flex gap-2">
@@ -74,6 +74,8 @@ export const Topbar = () => {
};
const PrimaryDropdown = () => {
const user = useUser().data;
const type = useResourceParamType();
const Components = type && ResourceComponents[type];
@@ -102,7 +104,7 @@ const PrimaryDropdown = () => {
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<DropdownMenuTrigger asChild className="lg:hidden">
<Button
variant="ghost"
className="flex justify-start items-center gap-2 w-36 px-3"
@@ -113,12 +115,11 @@ const PrimaryDropdown = () => {
</DropdownMenuTrigger>
<DropdownMenuContent className="w-36" side="bottom">
<DropdownMenuGroup>
<Link to="/">
<DropdownMenuItem className="flex items-center gap-2 cursor-pointer">
<Home className="w-4 h-4" />
Home
</DropdownMenuItem>
</Link>
<DropdownLinkItem
label="Home"
icon={<Home className="w-4 h-4" />}
to="/"
/>
<DropdownMenuSeparator />
@@ -126,58 +127,75 @@ const PrimaryDropdown = () => {
const RTIcon = ResourceComponents[type].Icon;
const name = type === "ServerTemplate" ? "Template" : type;
return (
<Link key={type} to={`/${usableResourcePath(type)}`}>
<DropdownMenuItem className="flex items-center gap-2 cursor-pointer">
<RTIcon />
{name}s
</DropdownMenuItem>
</Link>
<DropdownLinkItem
key={type}
label={`${name}s`}
icon={<RTIcon />}
to={`/${usableResourcePath(type)}`}
/>
);
})}
<DropdownMenuSeparator />
<Link to="/alerts">
<DropdownMenuItem className="flex items-center gap-2 cursor-pointer">
<AlertTriangle className="w-4 h-4" />
Alerts
</DropdownMenuItem>
</Link>
<DropdownLinkItem
label="Alerts"
icon={<AlertTriangle className="w-4 h-4" />}
to="/alerts"
/>
<Link to="/updates">
<DropdownMenuItem className="flex items-center gap-2 cursor-pointer">
<Bell className="w-4 h-4" />
Updates
</DropdownMenuItem>
</Link>
<DropdownLinkItem
label="Updates"
icon={<Bell className="w-4 h-4" />}
to="/updates"
/>
<Link to="/tags">
<DropdownMenuItem className="flex items-center gap-2 cursor-pointer">
<Tag className="w-4 h-4" />
Tags
</DropdownMenuItem>
</Link>
<DropdownLinkItem
label="Tags"
icon={<Tag className="w-4 h-4" />}
to="/tags"
/>
<DropdownMenuSeparator />
<Link to="/keys">
<DropdownMenuItem className="flex items-center gap-2 cursor-pointer">
<Box className="w-4 h-4" />
Api Keys
</DropdownMenuItem>
</Link>
<Link to="/users">
<DropdownMenuItem className="flex items-center gap-2 cursor-pointer">
<UserCircle2 className="w-4 h-4" />
Users
</DropdownMenuItem>
</Link>
<DropdownLinkItem
label="Api Keys"
icon={<Box className="w-4 h-4" />}
to="/keys"
/>
{user?.admin && (
<DropdownLinkItem
label="Users"
icon={<UserCircle2 className="w-4 h-4" />}
to="/users"
/>
)}
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
);
};
const DropdownLinkItem = ({
label,
icon,
to,
}: {
label: string;
icon: ReactNode;
to: string;
}) => {
return (
<Link to={to}>
<DropdownMenuItem className="flex items-center gap-2 cursor-pointer">
{icon}
{label}
</DropdownMenuItem>
</Link>
);
};
export type HomeView = "Dashboard" | "Tree" | "Resources";
export const homeViewAtom = atomWithStorage<HomeView>(
@@ -260,7 +278,7 @@ const ResourcesDropdown = ({ type }: { type: UsableResource }) => {
{selected ? selected.name : `All ${type}s`}
</Button>
</PopoverTrigger>
<PopoverContent className="w-[300px] max-h-[400px] p-0" sideOffset={12}>
<PopoverContent className="w-[300px] max-h-[400px] p-0" align="start">
<Command>
<CommandInput
placeholder={`Search ${type}s`}

View File

@@ -14,11 +14,11 @@ export const object_keys = <T extends object>(o: T): (keyof T)[] =>
Object.keys(o) as (keyof T)[];
export const RESOURCE_TARGETS: UsableResource[] = [
"Deployment",
"Server",
"Deployment",
"Build",
"Procedure",
"Repo",
"Procedure",
"Builder",
"Alerter",
"ServerTemplate"

View File

@@ -0,0 +1,29 @@
import * as React from "react"
import * as SeparatorPrimitive from "@radix-ui/react-separator"
import { cn } from "@lib/utils"
const Separator = React.forwardRef<
React.ElementRef<typeof SeparatorPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
>(
(
{ className, orientation = "horizontal", decorative = true, ...props },
ref
) => (
<SeparatorPrimitive.Root
ref={ref}
decorative={decorative}
orientation={orientation}
className={cn(
"shrink-0 bg-border",
orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
className
)}
{...props}
/>
)
)
Separator.displayName = SeparatorPrimitive.Root.displayName
export { Separator }

View File

@@ -13,7 +13,7 @@ module.exports = {
center: true,
padding: "2rem",
screens: {
"2xl": "1400px",
"2xl": "w-full",
},
},
extend: {

View File

@@ -825,6 +825,14 @@
aria-hidden "^1.1.1"
react-remove-scroll "2.5.5"
"@radix-ui/react-separator@^1.0.3":
version "1.0.3"
resolved "https://registry.yarnpkg.com/@radix-ui/react-separator/-/react-separator-1.0.3.tgz#be5a931a543d5726336b112f465f58585c04c8aa"
integrity sha512-itYmTy/kokS21aiV5+Z56MZB54KrhPgn6eHDKkFeOLR34HMN2s8PaN47qZZAGnvupcjxHaFZnW4pQEh0BvvVuw==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-primitive" "1.0.3"
"@radix-ui/react-slot@1.0.2":
version "1.0.2"
resolved "https://registry.yarnpkg.com/@radix-ui/react-slot/-/react-slot-1.0.2.tgz#a9ff4423eade67f501ffb32ec22064bc9d3099ab"