split sidebar home

This commit is contained in:
mbecker20
2024-05-12 12:59:45 -07:00
parent 1ff21d2986
commit 1ba288be79
10 changed files with 108 additions and 40 deletions

View File

@@ -3,11 +3,10 @@ import {
alert_level_intention,
text_color_class_by_intention,
} from "@lib/color";
import { useRead } from "@lib/hooks";
import { useRead, atomWithStorage } from "@lib/hooks";
import { Types } from "@monitor/client";
import { Button } from "@ui/button";
import { useAtom } from "jotai";
import { atomWithStorage } from "jotai/utils";
import { AlertTriangle } from "lucide-react";
import { AlertsTable } from "./table";

View File

@@ -1,7 +1,7 @@
import { Button } from "@ui/button";
import { PlusCircle } from "lucide-react";
import { ReactNode, useState } from "react";
import { Link, Outlet } from "react-router-dom";
import { ReactNode, useEffect, useState } from "react";
import { Link, Outlet, useNavigate } from "react-router-dom";
import {
Dialog,
DialogContent,
@@ -19,6 +19,21 @@ import { usableResourcePath } from "@lib/utils";
import { Sidebar } from "./sidebar";
export const Layout = () => {
const nav = useNavigate();
useEffect(() => {
const keydown = (e: KeyboardEvent) => {
// This will ignore Shift + S if it is sent from input / textarea
const target = e.target as any;
if (target.matches("input") || target.matches("textarea")) return;
if (e.shiftKey && e.key === "H") {
e.preventDefault();
nav("/");
}
};
document.addEventListener("keydown", keydown);
return () => document.removeEventListener("keydown", keydown);
});
return (
<>
<Topbar />

View File

@@ -1,6 +1,6 @@
import { atomWithStorage } from "@lib/hooks";
import { Types } from "@monitor/client";
import { useAtom } from "jotai";
import { atomWithStorage } from "jotai/utils";
const statsGranularityAtom = atomWithStorage(
"stats-granularity-v0",

View File

@@ -1,17 +1,49 @@
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 {
AlertTriangle,
Bell,
Box,
Boxes,
FolderTree,
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";
import { useAtom } from "jotai";
import { homeViewAtom } from "@main";
export const Sidebar = () => {
const [view, setView] = useAtom(homeViewAtom);
console.log(view);
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" />} />
<SidebarLink
label="Dashboard"
to="/"
icon={<Box className="w-4 h-4" />}
onClick={() => setView("Dashboard")}
highlighted={view === "Dashboard"}
/>
<SidebarLink
label="Resources"
to="/"
icon={<Boxes className="w-4 h-4" />}
onClick={() => setView("Resources")}
highlighted={view === "Resources"}
/>
<SidebarLink
label="Tree"
to="/"
icon={<FolderTree className="w-4 h-4" />}
onClick={() => setView("Tree")}
highlighted={view === "Tree"}
/>
<Separator />
@@ -70,20 +102,27 @@ const SidebarLink = ({
to,
icon,
label,
onClick,
highlighted,
}: {
to: string;
icon: ReactNode;
label: string;
onClick?: () => void;
highlighted?: boolean;
}) => {
const location = useLocation();
const hl =
"/" + location.pathname.split("/")[1] === to && (highlighted ?? true);
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"
hl && "bg-accent"
)}
onClick={onClick}
>
{icon}
{label}

View File

@@ -31,7 +31,6 @@ import { TopbarUpdates } from "./updates/topbar";
import { Logout } from "./util";
import { ThemeToggle } from "@ui/theme";
import { UsableResource } from "@types";
import { atomWithStorage } from "jotai/utils";
import { useAtom } from "jotai";
import { Popover, PopoverContent, PopoverTrigger } from "@ui/popover";
import { ReactNode, useEffect, useState } from "react";
@@ -44,11 +43,12 @@ import {
CommandList,
} from "@ui/command";
import { ResourceLink } from "./resources/common";
import { HomeView, homeViewAtom } from "@main";
export const Topbar = () => {
const [omniOpen, setOmniOpen] = useState(false);
useEffect(() => {
const down = (e: KeyboardEvent) => {
const keydown = (e: KeyboardEvent) => {
// This will ignore Shift + S if it is sent from input / textarea
const target = e.target as any;
if (target.matches("input") || target.matches("textarea")) return;
@@ -58,8 +58,8 @@ export const Topbar = () => {
setOmniOpen(true);
}
};
document.addEventListener("keydown", down);
return () => document.removeEventListener("keydown", down);
document.addEventListener("keydown", keydown);
return () => document.removeEventListener("keydown", keydown);
});
return (
<div className="sticky top-0 h-[70px] border-b z-50 w-full bg-card text-card-foreground shadow flex items-center">
@@ -209,17 +209,10 @@ const DropdownLinkItem = ({
);
};
export type HomeView = "Dashboard" | "Tree" | "Resources";
export const homeViewAtom = atomWithStorage<HomeView>(
"home-view-v1",
"Dashboard"
);
const ICONS = {
Dashboard: () => <Box className="w-4 h-4" />,
Tree: () => <FolderTree className="w-4 h-4" />,
Resources: () => <Boxes className="w-4 h-4" />,
Tree: () => <FolderTree className="w-4 h-4" />,
};
const SecondaryDropdown = () => {
@@ -236,7 +229,7 @@ const SecondaryDropdown = () => {
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
className="hidden sm:flex justify-start items-center gap-2 w-48 px-3"
className="hidden sm:flex lg:hidden justify-start items-center gap-2 w-48 px-3"
>
<Icon />
{view}

View File

@@ -15,8 +15,7 @@ import {
} from "@tanstack/react-query";
import { UsableResource } from "@types";
import { useToast } from "@ui/use-toast";
import { useAtom } from "jotai";
import { atomWithStorage } from "jotai/utils";
import { atom, useAtom } from "jotai";
import { useEffect } from "react";
import { useParams } from "react-router-dom";
@@ -175,6 +174,19 @@ export const useSetTitle = (more?: string) => {
}, [title]);
};
export const atomWithStorage = <T>(key: string, init: T) => {
const stored = localStorage.getItem(key);
const inner = atom(stored ? JSON.parse(stored) : init);
return atom(
(get) => get(inner),
(_, set, newValue) => {
set(inner, newValue);
localStorage.setItem(key, JSON.stringify(newValue));
}
);
};
export const tagsAtom = atomWithStorage<string[]>("tags-v0", []);
export const useTagsFilter = () => {
@@ -219,15 +231,17 @@ export const useCheckResourceExists = () => {
export const useFilterResources = <Info>(
resources?: Types.ResourceListItem<Info>[],
search?: string,
search?: string
) => {
const tags = useTagsFilter();
const searchSplit = search?.split(" ") || [];
return resources?.filter(
return (
resources?.filter(
(resource) =>
tags.every((tag) => resource.tags.includes(tag)) &&
(searchSplit.length > 0
? searchSplit.every((search) => resource.name.includes(search))
: true)
) ?? [];
) ?? []
);
};

View File

@@ -21,7 +21,7 @@ export const RESOURCE_TARGETS: UsableResource[] = [
"Procedure",
"Builder",
"Alerter",
"ServerTemplate"
"ServerTemplate",
];
export function env_to_text(envVars: Types.EnvironmentVar[] | undefined) {
@@ -95,18 +95,18 @@ export const convertTsMsToLocalUnixTsInSecs = (ts: number) =>
ts / 1000 - tzOffset;
export const usableResourcePath = (resource: UsableResource) => {
if (resource === "ServerTemplate") return "server-templates"
return `${resource.toLowerCase()}s`
}
if (resource === "ServerTemplate") return "server-templates";
return `${resource.toLowerCase()}s`;
};
export const sanitizeOnlySpan = (log: string) => {
return sanitizeHtml(log, {
allowedTags: ["span"],
allowedAttributes: {
"span": ["class"]
span: ["class"],
},
});
}
};
const convert = new Convert();
/**

View File

@@ -5,6 +5,7 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { Router } from "@router";
import { WebsocketProvider } from "@lib/socket";
import { Toaster } from "@ui/toaster";
import { atomWithStorage } from "@lib/hooks";
export const AUTH_TOKEN_STORAGE_KEY = "monitor-auth-token";
@@ -18,6 +19,13 @@ const query_client = new QueryClient({
defaultOptions: { queries: { retry: false } },
});
export type HomeView = "Dashboard" | "Tree" | "Resources";
export const homeViewAtom = atomWithStorage<HomeView>(
"home-view-v1",
"Dashboard"
);
ReactDOM.createRoot(document.getElementById("root")!).render(
// <React.StrictMode>
<QueryClientProvider client={query_client}>

View File

@@ -1,4 +1,4 @@
import { homeViewAtom } from "@components/topbar";
import { homeViewAtom } from "@main";
import { useAtom } from "jotai";
import { Dashboard } from "./dashboard";
import { AllResources } from "./all_resources";

View File

@@ -45,7 +45,7 @@ const CardDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
<p
<div
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}