`resources` was reachable from the SDK and the API and nowhere else, and `node_queued` was published to nothing at all — so a node waiting for a machine looked exactly like a node that had hung, which is the failure the event was added for. The node panel gets a Resources section: a size by name, the numbers for a node that wants its own, and how long it is expected to take. Picking a flavor drops the numbers, because saying both is two answers and the engine refuses it. A queued node draws a neutral dot rather than one of the three status colours — it is not running, it did not go well, and it did not go wrong; it is idle with a reason, which the tooltip gives. And a Workers screen, which is the first UI for any of this: every machine the engine can reach with what is free of each, what is attached, what a cluster has been asked for, and the sizes, editable. `fluksio status` grew a line of the same. The demo's training node already preferred a GPU worker, which was exactly the declaration that used to be dropped, so it now says how much of that machine it takes as well.
104 lines
3.6 KiB
TypeScript
104 lines
3.6 KiB
TypeScript
import {
|
|
ArrowLeft,
|
|
Bell,
|
|
FlaskConical,
|
|
Home,
|
|
KeyRound,
|
|
LayoutDashboard,
|
|
LogOut,
|
|
Package,
|
|
Server,
|
|
Settings,
|
|
Users,
|
|
Workflow,
|
|
} from "lucide-react"
|
|
|
|
import { Logo } from "@/components/Common/Logo"
|
|
import {
|
|
Sidebar,
|
|
SidebarContent,
|
|
SidebarFooter,
|
|
SidebarHeader,
|
|
SidebarTrigger,
|
|
} from "@/components/ui/sidebar"
|
|
import useAuth from "@/hooks/useAuth"
|
|
import { portalConfig } from "@/lib/portal"
|
|
import { type Item, Main } from "./Main"
|
|
|
|
const baseItems: Item[] = [
|
|
// "Home" rather than "Dashboard": dashboards are their own thing now, and
|
|
// the brain and the health screens are sections of Home rather than routes.
|
|
{ icon: Home, title: "Home", path: "/" },
|
|
{ icon: Workflow, title: "Flows", path: "/flows" },
|
|
{ icon: LayoutDashboard, title: "Dashboards", path: "/dashboards" },
|
|
// What a batch flow leaves behind. Beside the flows rather than under Home:
|
|
// an experiment log is browsed, not glanced at.
|
|
{ icon: FlaskConical, title: "Runs", path: "/runs" },
|
|
// Both are engine-wide operator settings rather than personal ones, so they
|
|
// sit here and not among the per-user tabs under Settings.
|
|
{ icon: KeyRound, title: "Secrets", path: "/secrets" },
|
|
{ icon: Package, title: "Modules", path: "/modules" },
|
|
{ icon: Server, title: "Workers", path: "/workers" },
|
|
{ icon: Bell, title: "Alerts", path: "/alerts" },
|
|
]
|
|
|
|
export function AppSidebar() {
|
|
const { user: currentUser, logout } = useAuth()
|
|
const portal = portalConfig()
|
|
|
|
const withAdmin = currentUser?.is_superuser
|
|
? [...baseItems, { icon: Users, title: "Admin", path: "/admin" }]
|
|
: baseItems
|
|
|
|
// Reached through a portal, the way out is back to the installations list —
|
|
// and the session belongs to the portal, so logging out is its business.
|
|
const items: Item[] = portal
|
|
? [
|
|
{
|
|
icon: ArrowLeft,
|
|
title: "All installations",
|
|
onClick: () => window.location.assign(portal.portalUrl),
|
|
},
|
|
...withAdmin,
|
|
]
|
|
: withAdmin
|
|
|
|
const footerItems: Item[] = portal
|
|
? [{ icon: Settings, title: "Settings", path: "/settings" }]
|
|
: [
|
|
{ icon: Settings, title: "Settings", path: "/settings" },
|
|
{ icon: LogOut, title: "Log Out", onClick: logout },
|
|
]
|
|
|
|
return (
|
|
// Floating frosted chrome over whatever surface the shell paints; see the
|
|
// root DESIGN-GUIDELINES.md → Shells and → Overlay surfaces & content chips.
|
|
<Sidebar
|
|
collapsible="icon"
|
|
variant="floating"
|
|
className="[&>[data-sidebar=sidebar]]:bg-card/80 [&>[data-sidebar=sidebar]]:backdrop-blur-md [&>[data-sidebar=sidebar]]:shadow-e2"
|
|
>
|
|
<SidebarHeader className="px-4 py-6 group-data-[collapsible=icon]:px-0 group-data-[collapsible=icon]:items-center">
|
|
<div className="flex w-full items-center justify-between gap-2 group-data-[collapsible=icon]:justify-center">
|
|
{/* Collapsed, the rail has room for one thing, and that is the way
|
|
back out. */}
|
|
<span className="group-data-[collapsible=icon]:hidden">
|
|
<Logo variant="responsive" />
|
|
</span>
|
|
{/* On a phone the sidebar is a sheet with its own way in and out. */}
|
|
<SidebarTrigger className="hidden shrink-0 text-muted-foreground md:inline-flex" />
|
|
</div>
|
|
</SidebarHeader>
|
|
<SidebarContent>
|
|
<Main items={items} />
|
|
</SidebarContent>
|
|
{/* Main already pads horizontally; the footer only adds the bottom gap. */}
|
|
<SidebarFooter className="px-0">
|
|
<Main items={footerItems} />
|
|
</SidebarFooter>
|
|
</Sidebar>
|
|
)
|
|
}
|
|
|
|
export default AppSidebar
|