mirror of
https://github.com/moghtech/komodo.git
synced 2026-08-24 08:00:17 +00:00
combine all resources / table view into dashboard
This commit is contained in:
@@ -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");
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"]} />
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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 />;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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>
|
||||
}
|
||||
|
||||
@@ -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 />} />
|
||||
|
||||
Reference in New Issue
Block a user