combine all resources / table view into dashboard

This commit is contained in:
mbecker20
2025-10-20 01:40:27 -07:00
parent d3c464c05d
commit cf7623b1fc
23 changed files with 235 additions and 374 deletions
+2
View File
@@ -32,6 +32,8 @@ export const Layout = () => {
useShiftKeyListener("B", () => nav("/builds"));
useShiftKeyListener("R", () => nav("/repos"));
useShiftKeyListener("P", () => nav("/procedures"));
useShiftKeyListener("J", () => nav("/terminals"));
useShiftKeyListener("C", () => nav("/schedules"));
useShiftKeyListener("V", () => {
setSettingsView("Variables");
nav("/settings");
+24 -2
View File
@@ -1,4 +1,10 @@
import { useAllResources, useLocalStorage, useRead, useSettingsView, useUser } from "@lib/hooks";
import {
useAllResources,
useLocalStorage,
useRead,
useSettingsView,
useUser,
} from "@lib/hooks";
import { Button } from "@ui/button";
import {
CommandDialog,
@@ -9,7 +15,7 @@ import {
CommandSeparator,
CommandItem,
} from "@ui/command";
import { Box, Home, Search, User } from "lucide-react";
import { Box, CalendarDays, Home, Search, Terminal, User } from "lucide-react";
import { Fragment, ReactNode, useMemo, useState } from "react";
import { useNavigate } from "react-router-dom";
import { cn, RESOURCE_TARGETS, usableResourcePath } from "@lib/utils";
@@ -162,6 +168,22 @@ const useOmniItems = (
onSelect: () => nav("/containers"),
template: false,
},
{
key: "Terminals",
type: "Server" as UsableResource,
label: "Terminals",
icon: <Terminal className="w-4 h-4" />,
onSelect: () => nav("/terminals"),
template: false,
},
{
key: "Schedules",
type: "Server" as UsableResource,
label: "Schedules",
icon: <CalendarDays className="w-4 h-4" />,
onSelect: () => nav("/schedules"),
template: false,
},
(user?.admin && {
key: "Users",
type: "Server" as UsableResource,
@@ -11,7 +11,7 @@ import {
} from "@lib/color";
import { cn, updateLogToHtml } from "@lib/utils";
import { Types } from "komodo_client";
import { DashboardPieChart } from "@pages/home/dashboard";
import { DashboardPieChart } from "@pages/dashboard";
import { GroupActions } from "@components/group-actions";
import { Tooltip, TooltipContent, TooltipTrigger } from "@ui/tooltip";
import { Card } from "@ui/card";
@@ -17,7 +17,7 @@ import {
} from "@lib/color";
import { cn } from "@lib/utils";
import { Types } from "komodo_client";
import { DashboardPieChart } from "@pages/home/dashboard";
import { DashboardPieChart } from "@pages/dashboard";
import { StatusBadge } from "@components/util";
import { Card } from "@ui/card";
import { Badge } from "@ui/badge";
@@ -24,7 +24,7 @@ import {
ResourcePageHeader,
} from "../common";
import { RunBuild } from "../build/actions";
import { DashboardPieChart } from "@pages/home/dashboard";
import { DashboardPieChart } from "@pages/dashboard";
import {
ContainerPortsTableView,
DockerResourceLink,
@@ -11,7 +11,7 @@ import {
} from "@lib/color";
import { cn, updateLogToHtml } from "@lib/utils";
import { Types } from "komodo_client";
import { DashboardPieChart } from "@pages/home/dashboard";
import { DashboardPieChart } from "@pages/dashboard";
import { GroupActions } from "@components/group-actions";
import { Tooltip, TooltipContent, TooltipTrigger } from "@ui/tooltip";
import { Card } from "@ui/card";
@@ -18,7 +18,7 @@ import {
import { cn } from "@lib/utils";
import { useServer } from "../server";
import { Types } from "komodo_client";
import { DashboardPieChart } from "@pages/home/dashboard";
import { DashboardPieChart } from "@pages/dashboard";
import { RepoLink, StatusBadge } from "@components/util";
import { Badge } from "@ui/badge";
import { useToast } from "@ui/use-toast";
@@ -25,7 +25,7 @@ import { ServerTable } from "./table";
import { DeleteResource, NewResource, ResourcePageHeader } from "../common";
import { ActionWithDialog, ConfirmButton, StatusBadge } from "@components/util";
import { Card } from "@ui/card";
import { DashboardPieChart } from "@pages/home/dashboard";
import { DashboardPieChart } from "@pages/dashboard";
import { ServerStatsMini } from "./stats-mini";
import { GroupActions } from "@components/group-actions";
import { usePermissions } from "@lib/hooks";
@@ -4,12 +4,9 @@ import { DataTable, SortableHeader } from "@ui/data-table";
import {
useRead,
useSelectedResources,
useServerMonitoringTable,
} from "@lib/hooks";
import { useIsServerAvailable } from ".";
import { Types } from "komodo_client";
import { Button } from "@ui/button";
import { Eye, EyeOff } from "lucide-react";
export const ServerMonitoringTable = ({
servers,
@@ -73,29 +70,6 @@ export const ServerMonitoringTable = ({
);
};
export const ToggleServerMonitoringTableButton = () => {
const [monitoring, toggleMonitoring] = useServerMonitoringTable();
return (
<Button
variant="outline"
className="flex items-center gap-2"
onClick={toggleMonitoring}
>
{monitoring ? (
<>
<EyeOff className="w-4 h-4" />
Hide Server Stats
</>
) : (
<>
<Eye className="w-4 h-4" />
Show Server Stats
</>
)}
</Button>
);
};
const useStats = (id: string) => {
const isServerAvailable = useIsServerAvailable(id);
return useRead(
@@ -2,7 +2,6 @@ import { TableTags } from "@components/tags";
import {
useRead,
useSelectedResources,
useServerMonitoringTable,
} from "@lib/hooks";
import { DataTable, SortableHeader } from "@ui/data-table";
import { ServerComponents, ServerVersion } from ".";
@@ -10,14 +9,15 @@ import { ResourceLink } from "../common";
import { Types } from "komodo_client";
import { useCallback } from "react";
import { ServerMonitoringTable } from "./monitoring-table";
import { useDashboardPreferences } from "@lib/dashboard-preferences";
export const ServerTable = ({
servers,
}: {
servers: Types.ServerListItem[];
}) => {
const [monitoring] = useServerMonitoringTable();
if (monitoring) {
const { preferences } = useDashboardPreferences();
if (preferences.showServerStats) {
return <ServerMonitoringTable servers={servers} />;
} else {
return <StandardTable servers={servers} />;
@@ -35,7 +35,7 @@ import {
import { Badge } from "@ui/badge";
import { Button } from "@ui/button";
import { useToast } from "@ui/use-toast";
import { DashboardPieChart } from "@pages/home/dashboard";
import { DashboardPieChart } from "@pages/dashboard";
import { StatusBadge } from "@components/util";
import { GroupActions } from "@components/group-actions";
import { Tooltip, TooltipTrigger, TooltipContent } from "@ui/tooltip";
@@ -18,7 +18,7 @@ import {
} from "@lib/color";
import { cn } from "@lib/utils";
import { fmt_date } from "@lib/formatting";
import { DashboardPieChart } from "@pages/home/dashboard";
import { DashboardPieChart } from "@pages/dashboard";
import { StatusBadge } from "@components/util";
import { Badge } from "@ui/badge";
import { GroupActions } from "@components/group-actions";
+8 -20
View File
@@ -4,7 +4,6 @@ import {
AlertTriangle,
Bell,
Box,
Boxes,
CalendarDays,
LayoutDashboard,
Settings,
@@ -14,34 +13,29 @@ 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);
return (
<div className="fixed top-0 pt-[84px] w-[200px] border-r hidden lg:block pr-8 pb-8 h-screen overflow-y-auto">
<div className="flex flex-col gap-1">
<div className="flex flex-col gap-0.5">
<SidebarLink
label="Dashboard"
to="/"
icon={<LayoutDashboard 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="Containers"
to="/containers"
icon={<Box className="w-4 h-4" />}
/>
<SidebarLink
label="Terminals"
to="/terminals"
icon={<Terminal className="w-4 h-4" />}
/>
<Separator className="my-3" />
<p className="pl-4 pb-1 text-xs text-muted-foreground">Resources</p>
@@ -74,12 +68,6 @@ export const Sidebar = () => {
<Separator className="my-3" />
<SidebarLink
label="Terminals"
to="/terminals"
icon={<Terminal className="w-4 h-4" />}
/>
<SidebarLink
label="Schedules"
to="/schedules"
+28 -41
View File
@@ -12,13 +12,11 @@ import {
ArrowLeftRight,
Bell,
Box,
Boxes,
Calendar,
CalendarDays,
Check,
Circle,
FileQuestion,
FolderTree,
Keyboard,
KeyRound,
LayoutDashboard,
@@ -47,9 +45,7 @@ import {
usableResourcePath,
version_is_none,
} from "@lib/utils";
import { useAtom } from "jotai";
import { ReactNode, useState } from "react";
import { HomeView, homeViewAtom } from "@main";
import {
Dialog,
DialogContent,
@@ -72,33 +68,28 @@ import { HoverCard, HoverCardContent, HoverCardTrigger } from "@ui/hover-card";
export const MobileDropdown = () => {
const type = useResourceParamType();
const Components = type && ResourceComponents[type];
const [view, setView] = useAtom<HomeView>(homeViewAtom);
const [icon, title] = Components
? [<Components.Icon />, (type === "ResourceSync" ? "Sync" : type) + "s"]
: location.pathname === "/" && view === "Dashboard"
: location.pathname === "/"
? [<LayoutDashboard className="w-4 h-4" />, "Dashboard"]
: location.pathname === "/" && view === "Resources"
? [<Boxes className="w-4 h-4" />, "Resources"]
: location.pathname === "/" && view === "Tree"
? [<FolderTree className="w-4 h-4" />, "Tree"]
: location.pathname === "/containers"
? [<Box className="w-4 h-4" />, "Containers"]
: location.pathname === "/settings"
? [<Settings className="w-4 h-4" />, "Settings"]
: location.pathname === "/terminals"
? [<Terminal className="w-4 h-4" />, "Terminals"]
: location.pathname === "/schedules"
? [<CalendarDays className="w-4 h-4" />, "Schedules"]
: location.pathname === "/alerts"
? [<AlertTriangle className="w-4 h-4" />, "Alerts"]
: location.pathname === "/updates"
? [<Bell className="w-4 h-4" />, "Updates"]
: location.pathname.split("/")[1] === "user-groups"
? [<Users className="w-4 h-4" />, "User Groups"]
: location.pathname.split("/")[1] === "users"
? [<User className="w-4 h-4" />, "Users"]
: [<FileQuestion className="w-4 h-4" />, "Unknown"];
: location.pathname === "/containers"
? [<Box className="w-4 h-4" />, "Containers"]
: location.pathname === "/settings"
? [<Settings className="w-4 h-4" />, "Settings"]
: location.pathname === "/terminals"
? [<Terminal className="w-4 h-4" />, "Terminals"]
: location.pathname === "/schedules"
? [<CalendarDays className="w-4 h-4" />, "Schedules"]
: location.pathname === "/alerts"
? [<AlertTriangle className="w-4 h-4" />, "Alerts"]
: location.pathname === "/updates"
? [<Bell className="w-4 h-4" />, "Updates"]
: location.pathname.split("/")[1] === "user-groups"
? [<Users className="w-4 h-4" />, "User Groups"]
: location.pathname.split("/")[1] === "users"
? [<User className="w-4 h-4" />, "Users"]
: [<FileQuestion className="w-4 h-4" />, "Unknown"];
return (
<DropdownMenu>
@@ -117,20 +108,20 @@ export const MobileDropdown = () => {
label="Dashboard"
icon={<LayoutDashboard className="w-4 h-4" />}
to="/"
onClick={() => setView("Dashboard")}
/>
<DropdownLinkItem
label="Resources"
icon={<Boxes className="w-4 h-4" />}
to="/"
onClick={() => setView("Resources")}
/>
<DropdownLinkItem
label="Containers"
icon={<Box className="w-4 h-4" />}
to="/containers"
/>
<DropdownLinkItem
label="Terminals"
icon={<Terminal className="w-4 h-4" />}
to="/terminals"
/>
<DropdownMenuSeparator />
{SIDEBAR_RESOURCES.map((type) => {
@@ -162,12 +153,6 @@ export const MobileDropdown = () => {
<DropdownMenuSeparator />
<DropdownLinkItem
label="Terminals"
icon={<Terminal className="w-4 h-4" />}
to="/terminals"
/>
<DropdownLinkItem
label="Schedules"
icon={<CalendarDays className="w-4 h-4" />}
@@ -599,7 +584,7 @@ export const KeyboardShortcuts = () => {
<DialogHeader>
<DialogTitle>Keyboard Shortcuts</DialogTitle>
</DialogHeader>
<div className="grid gap-3 grid-cols-2 pt-8">
<div className="grid gap-3 grid-cols-2 pt-8 max-h-[calc(100vh-100px)] overflow-auto">
<KeyboardShortcut label="Save" keys={["Ctrl / Cmd", "Enter"]} />
<KeyboardShortcut label="Go Home" keys={["Shift", "H"]} />
@@ -610,6 +595,8 @@ export const KeyboardShortcuts = () => {
<KeyboardShortcut label="Go to Repos" keys={["Shift", "R"]} />
<KeyboardShortcut label="Go to Procedures" keys={["Shift", "P"]} />
<KeyboardShortcut label="Go to Variables" keys={["Shift", "V"]} />
<KeyboardShortcut label="Go to Terminals" keys={["Shift", "J"]} />
<KeyboardShortcut label="Go to Schedules" keys={["Shift", "C"]} />
<KeyboardShortcut label="Search" keys={["Shift", "S"]} />
<KeyboardShortcut label="Add Filter Tag" keys={["Shift", "T"]} />
+11 -2
View File
@@ -3,19 +3,23 @@ import { useAtom } from "jotai";
interface DashboardPreferences {
showServerStats: boolean;
showTables: boolean;
}
const DEFAULT_PREFERENCES: DashboardPreferences = {
showServerStats: false,
showTables: false,
};
export const dashboardPreferencesAtom = atomWithStorage<DashboardPreferences>(
"komodo-dashboard-preferences",
"komodo-dashboard-preferences-v2",
DEFAULT_PREFERENCES
);
export const useDashboardPreferences = () => {
const [preferences, setPreferences] = useAtom(dashboardPreferencesAtom);
const [preferences, setPreferences] = useAtom<DashboardPreferences>(
dashboardPreferencesAtom
);
const updatePreference = <K extends keyof DashboardPreferences>(
key: K,
@@ -24,8 +28,13 @@ export const useDashboardPreferences = () => {
setPreferences({ ...preferences, [key]: value });
};
const togglePreference = <K extends keyof DashboardPreferences>(key: K) => {
updatePreference(key, !preferences[key]);
};
return {
preferences,
updatePreference,
togglePreference,
};
};
-9
View File
@@ -712,15 +712,6 @@ export const useFilterByUpdateAvailable: () => [boolean, () => void] = () => {
return [filter, () => set(!filter)];
};
const server_monitoring_table = atomWithStorage<boolean>(
"servers-monitoring-toggle-v1",
false
);
export const useServerMonitoringTable: () => [boolean, () => void] = () => {
const [filter, set] = useAtom<boolean>(server_monitoring_table);
return [filter, () => set(!filter)];
};
export const usePermissions = ({ type, id }: Types.ResourceTarget) => {
const user = useUser().data;
const perms = useRead("GetPermission", { target: { type, id } }).data as
-8
View File
@@ -5,7 +5,6 @@ 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";
// Run monaco setup
import "./monaco";
import { init_monaco } from "./monaco/init";
@@ -18,13 +17,6 @@ const query_client = new QueryClient({
defaultOptions: { queries: { retry: false } },
});
export type HomeView = "Dashboard" | "Tree" | "Resources";
export const homeViewAtom = atomWithStorage<HomeView>(
"home-view-v1",
"Dashboard"
);
// Don't need to await this to render.
init_monaco();
@@ -3,11 +3,11 @@ import { Page, Section } from "@components/layouts";
import { ResourceComponents } from "@components/resources";
import { ResourceLink, ResourceNameSimple } from "@components/resources/common";
import { ServerStatsMini } from "@components/resources/server";
import { TagsWithBadge } from "@components/tags";
import { StatusBadge, TemplateMarker } from "@components/util";
import { TagsFilter, TagsWithBadge } from "@components/tags";
import { ShowHideButton, StatusBadge, TemplateMarker } from "@components/util";
import { useDashboardPreferences } from "@lib/dashboard-preferences";
import { Button } from "@ui/button";
import { Eye, EyeOff, Settings } from "lucide-react";
import { Eye, EyeOff, Settings, Table } from "lucide-react";
import {
action_state_intention,
build_state_intention,
@@ -17,21 +17,26 @@ import {
repo_state_intention,
text_color_class_by_intention,
} from "@lib/color";
import { useNoResources, useRead, useUser } from "@lib/hooks";
import {
useFilterResources,
useNoResources,
useRead,
useUser,
} from "@lib/hooks";
import { cn, usableResourcePath } from "@lib/utils";
import { Types } from "komodo_client";
import { UsableResource } from "@types";
import { RequiredResourceComponents, UsableResource } from "@types";
import { DataTable, SortableHeader } from "@ui/data-table";
import { AlertTriangle, Box, Circle, History } from "lucide-react";
import { PieChart } from "react-minimal-pie-chart";
import { Link } from "react-router-dom";
import { UpdateAvailable as StackUpdateAvailable } from "@components/resources/stack";
import { UpdateAvailable as DeploymentUpdateAvailable } from "@components/resources/deployment";
import { useState } from "react";
import { Input } from "@ui/input";
export default function Dashboard() {
const noResources = useNoResources();
const user = useUser().data!;
const { preferences } = useDashboardPreferences();
return (
<>
<ActiveResources />
@@ -39,38 +44,123 @@ export default function Dashboard() {
title="Dashboard"
icon={<Box className="w-8 h-8" />}
actions={
<div className="flex items-center gap-2">
<div className="flex items-center gap-2 flex-wrap">
<ShowTables />
<ShowServerStats />
<ExportButton />
</div>
}
>
<div className="flex flex-col gap-6 w-full">
{noResources && (
<div className="flex items-center gap-4 px-2 text-muted-foreground">
<AlertTriangle className="w-4 h-4" />
<p className="text-lg">
No resources found.{" "}
{user.admin
? "To get started, create a server."
: "Contact an admin for access to resources."}
</p>
</div>
)}
<ResourceRow type="Server" />
<ResourceRow type="Stack" />
<ResourceRow type="Deployment" />
<ResourceRow type="Build" />
<ResourceRow type="Repo" />
<ResourceRow type="Procedure" />
<ResourceRow type="Action" />
<ResourceRow type="ResourceSync" />
</div>
{preferences.showTables ? <TablesDashboard /> : <RecentsDashboard />}
</Page>
</>
);
}
const TablesDashboard = () => {
const [search, setSearch] = useState("");
const noResources = useNoResources();
const user = useUser().data!;
return (
<div className="flex flex-col gap-6">
<div className="flex items-center justify-end gap-2 flex-wrap">
<TagsFilter />
<Input
value={search}
onChange={(e) => setSearch(e.target.value)}
placeholder="search..."
className="w-[200px] lg:w-[300px]"
/>
</div>
{noResources && (
<div className="flex items-center gap-4 px-2 text-muted-foreground">
<AlertTriangle className="w-4 h-4" />
<p className="text-lg">
No resources found.{" "}
{user.admin
? "To get started, create a server."
: "Contact an admin for access to resources."}
</p>
</div>
)}
{Object.entries(ResourceComponents).map(([type, Components]) => (
<TableSection
key={type}
type={type}
Components={Components}
search={search}
/>
))}
</div>
);
};
const TableSection = ({
type,
Components,
search,
}: {
type: string;
Components: RequiredResourceComponents;
search?: string;
}) => {
const resources = useRead(`List${type as UsableResource}s`, {}).data;
const filtered = useFilterResources(
resources as Types.ResourceListItem<unknown>[],
search
);
let count = filtered.length;
const [show, setShow] = useState(true);
if (!count) return;
return (
<Section
key={type}
title={type + "s"}
icon={<Components.Icon />}
actions={<ShowHideButton show={show} setShow={setShow} />}
>
<div className={cn("border-b", show && "pb-8")}>
{show && <Components.Table resources={filtered ?? []} />}
</div>
</Section>
);
};
const RecentsDashboard = () => {
const noResources = useNoResources();
const user = useUser().data!;
return (
<div className="flex flex-col gap-6 w-full">
{noResources && (
<div className="flex items-center gap-4 px-2 text-muted-foreground">
<AlertTriangle className="w-4 h-4" />
<p className="text-lg">
No resources found.{" "}
{user.admin
? "To get started, create a server."
: "Contact an admin for access to resources."}
</p>
</div>
)}
<ResourceRow type="Server" />
<ResourceRow type="Stack" />
<ResourceRow type="Deployment" />
<ResourceRow type="Build" />
<ResourceRow type="Repo" />
<ResourceRow type="Procedure" />
<ResourceRow type="Action" />
<ResourceRow type="ResourceSync" />
</div>
);
};
const ResourceRow = ({ type }: { type: UsableResource }) => {
const _recents = useUser().data?.recents?.[type]?.slice(0, 6);
const _resources = useRead(`List${type}s`, {}).data;
@@ -353,15 +443,13 @@ const ActiveResources = () => {
);
};
const ShowServerStats = () => {
const { preferences, updatePreference } = useDashboardPreferences();
export const ShowServerStats = () => {
const { preferences, togglePreference } = useDashboardPreferences();
return (
<Button
variant="outline"
onClick={() =>
updatePreference("showServerStats", !preferences.showServerStats)
}
className="flex items-center gap-2"
onClick={() => togglePreference("showServerStats")}
className="flex items-center gap-2 w-[180px]"
>
{preferences.showServerStats ? (
<>
@@ -377,3 +465,26 @@ const ShowServerStats = () => {
</Button>
);
};
const ShowTables = () => {
const { preferences, updatePreference } = useDashboardPreferences();
return (
<Button
variant="outline"
onClick={() => updatePreference("showTables", !preferences.showTables)}
className="flex items-center gap-2 w-[150px]"
>
{preferences.showTables ? (
<>
<History className="w-4 h-4" />
<span>Show Recents</span>
</>
) : (
<>
<Table className="w-4 h-4" />
<span>Show Tables</span>
</>
)}
</Button>
);
};
-102
View File
@@ -1,102 +0,0 @@
import { ExportButton } from "@components/export";
import { Page, Section } from "@components/layouts";
import { ResourceComponents } from "@components/resources";
import { TagsFilter } from "@components/tags";
import { ShowHideButton } from "@components/util";
import {
useFilterResources,
useNoResources,
useRead,
useTagsFilter,
useUser,
} from "@lib/hooks";
import { cn } from "@lib/utils";
import { Types } from "komodo_client";
import { RequiredResourceComponents, UsableResource } from "@types";
import { Input } from "@ui/input";
import { AlertTriangle } from "lucide-react";
import { useState } from "react";
export default function AllResources() {
const [search, setSearch] = useState("");
const tags = useTagsFilter();
const noResources = useNoResources();
const user = useUser().data!;
return (
<Page
titleOther={
<div className="flex items-center justify-between gap-2 flex-wrap">
<Input
value={search}
onChange={(e) => setSearch(e.target.value)}
placeholder="search..."
className="w-[200px] lg:w-[300px]"
/>
<div className="flex items-center gap-2 flex-wrap">
<TagsFilter />
<ExportButton tags={tags} />
</div>
</div>
}
>
{noResources && (
<div className="flex items-center gap-4 px-2 text-muted-foreground">
<AlertTriangle className="w-4 h-4" />
<p className="text-lg">
No resources found.{" "}
{user.admin
? "To get started, create a server."
: "Contact an admin for access to resources."}
</p>
</div>
)}
<div className="flex flex-col gap-6">
{Object.entries(ResourceComponents).map(([type, Components]) => (
<TableSection
key={type}
type={type}
Components={Components}
search={search}
/>
))}
</div>
</Page>
);
}
const TableSection = ({
type,
Components,
search,
}: {
type: string;
Components: RequiredResourceComponents;
search?: string;
}) => {
const resources = useRead(`List${type as UsableResource}s`, {}).data;
const filtered = useFilterResources(
resources as Types.ResourceListItem<unknown>[],
search
);
let count = filtered.length;
const [show, setShow] = useState(true);
if (!count) return;
return (
<Section
key={type}
title={type + "s"}
icon={<Components.Icon />}
actions={<ShowHideButton show={show} setShow={setShow} />}
>
<div className={cn("border-b", show && "pb-8")}>
{show && <Components.Table resources={filtered ?? []} />}
</div>
</Section>
);
};
-21
View File
@@ -1,21 +0,0 @@
import { homeViewAtom } from "@main";
import { useAtom } from "jotai";
import { useSetTitle } from "@lib/hooks";
import { lazy } from "react";
const Dashboard = lazy(() => import("./dashboard"));
const AllResources = lazy(() => import("./all_resources"));
const Tree = lazy(() => import("./tree"));
export default function Home() {
useSetTitle();
const [view] = useAtom(homeViewAtom);
switch (view) {
case "Dashboard":
return <Dashboard />;
case "Resources":
return <AllResources />;
case "Tree":
return <Tree />;
}
}
-88
View File
@@ -1,88 +0,0 @@
import { ExportButton } from "@components/export";
import { Page, Section } from "@components/layouts";
import { ResourceComponents } from "@components/resources";
import { DeploymentTable } from "@components/resources/deployment/table";
import { ServerComponents } from "@components/resources/server";
import { TagsFilter, TagsWithBadge } from "@components/tags";
import { useFilterResources, useRead, useTagsFilter } from "@lib/hooks";
import { Button } from "@ui/button";
import { Card, CardHeader, CardTitle } from "@ui/card";
import { Input } from "@ui/input";
import { atom, useAtom } from "jotai";
import { Fragment, useState } from "react";
import { Link } from "react-router-dom";
const searchAtom = atom("");
export default function Tree() {
const [search, setSearch] = useAtom(searchAtom);
const tags = useTagsFilter();
const servers = useRead("ListServers", { query: { tags } }).data;
return (
<Page
titleOther={
<div className="flex items-center justify-between">
<div className="flex gap-4 items-center">
<Input
value={search}
onChange={(e) => setSearch(e.target.value)}
placeholder="search..."
className="w-[200px] lg:w-[300px]"
/>
<ExportButton tags={tags} />
</div>
<TagsFilter />
</div>
}
>
<Section>
<div className="grid gap-6">
{servers?.map((server) => <Server key={server.id} id={server.id} />)}
</div>
</Section>
</Page>
);
}
const Server = ({ id }: { id: string }) => {
const [search] = useAtom(searchAtom);
// const [open, setOpen] = useLocalStorage(`server-tree-open-${id}`, false);
const [open, setOpen] = useState(false);
const server = useRead("ListServers", {}).data?.find(
(server) => server.id === id
);
const deployments = useRead("ListDeployments", {}).data?.filter(
(deployment) => deployment.info.server_id === id
);
const filtered = useFilterResources(deployments, search);
return (
<div className="grid gap-2">
<Card
className="h-full hover:bg-accent/50 group-focus:bg-accent/50 transition-colors cursor-pointer"
onClick={() => setOpen(!open)}
>
<CardHeader className="p-4 flex-row justify-between items-center">
<CardTitle>{server?.name}</CardTitle>
<div className="flex gap-3 justify-between items-center">
<TagsWithBadge tag_ids={server?.tags} />
{server?.id && (
<div className="flex gap-4 items-center">
{Object.entries(ServerComponents.Info).map(([key, Info], i) => (
<Fragment key={key}>
{i !== 0 && "|"} <Info id={server.id} />
</Fragment>
))}
</div>
)}
<Link to={`/servers/${server?.id}`}>
<Button variant="outline">
<ResourceComponents.Server.Icon id={server?.id} />
</Button>
</Link>
</div>
</CardHeader>
</Card>
{open && <DeploymentTable deployments={filtered ?? []} />}
</div>
);
};
+2 -2
View File
@@ -18,7 +18,7 @@ import { Search } from "lucide-react";
import { NotFound, TemplateQueryBehaviorSelector } from "@components/util";
import { Switch } from "@ui/switch";
import { UsableResource } from "@types";
import { ToggleServerMonitoringTableButton } from "@components/resources/server/monitoring-table";
import { ShowServerStats } from "./dashboard";
export default function Resources({ _type }: { _type?: UsableResource }) {
const is_admin = useUser().data?.admin ?? false;
@@ -70,7 +70,7 @@ export default function Resources({ _type }: { _type?: UsableResource }) {
icon={<Components.BigIcon />}
actions={
<div className="flex items-center gap-2">
{type === "Server" && <ToggleServerMonitoringTableButton />}
{type === "Server" && <ShowServerStats />}
<ExportButton targets={targets} />
</div>
}
+2 -6
View File
@@ -14,14 +14,12 @@ import {
} from "react-router-dom";
// Lazy import pages
const Dashboard = lazy(() => import("@pages/dashboard"));
const Resources = lazy(() => import("@pages/resources"));
const Resource = lazy(() => import("@pages/resource"));
const Login = lazy(() => import("@pages/login"));
const Tree = lazy(() => import("@pages/home/tree"));
const UpdatesPage = lazy(() => import("@pages/updates"));
const AllResources = lazy(() => import("@pages/home/all_resources"));
const UserDisabled = lazy(() => import("@pages/user_disabled"));
const Home = lazy(() => import("@pages/home"));
const AlertsPage = lazy(() => import("@pages/alerts"));
const UserPage = lazy(() => import("@pages/user"));
const UserGroupPage = lazy(() => import("@pages/user-group"));
@@ -94,11 +92,9 @@ export const Router = () => {
<Route path="login" element={<Login />} />
<Route element={<RequireAuth />}>
<Route path="/" element={<Layout />}>
<Route path="" element={<Home />} />
<Route path="" element={<Dashboard />} />
<Route path="settings" element={<Settings />} />
<Route path="tree" element={<Tree />} />
<Route path="containers" element={<ContainersPage />} />
<Route path="resources" element={<AllResources />} />
<Route path="schedules" element={<SchedulesPage />} />
<Route path="terminals" element={<TerminalsPage />} />
<Route path="alerts" element={<AlertsPage />} />