mirror of
https://github.com/coder/coder.git
synced 2026-10-29 17:44:33 +08:00
fix: implement proper overflow behavior for workspace history (#19340)
Before: https://github.com/user-attachments/assets/2f1ff75c-4916-4d0a-a657-004d46691ea0 After: https://github.com/user-attachments/assets/a8e575b5-84f9-4eea-b318-93d2a3d60aa5 Also converts a lot of emotion components to tailwind
This commit is contained in:
@@ -1,30 +1,28 @@
|
|||||||
import { type Interpolation, type Theme, useTheme } from "@emotion/react";
|
|
||||||
import type { ComponentProps, FC, HTMLAttributes } from "react";
|
import type { ComponentProps, FC, HTMLAttributes } from "react";
|
||||||
import { Link, type LinkProps } from "react-router";
|
import { Link, type LinkProps } from "react-router";
|
||||||
|
import { cn } from "utils/cn";
|
||||||
import { TopbarIconButton } from "./Topbar";
|
import { TopbarIconButton } from "./Topbar";
|
||||||
|
|
||||||
export const Sidebar: FC<HTMLAttributes<HTMLDivElement>> = (props) => {
|
export const Sidebar: FC<HTMLAttributes<HTMLDivElement>> = (props) => {
|
||||||
const theme = useTheme();
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
css={{
|
// TODO: Remove extra border classes once MUI is removed
|
||||||
width: 260,
|
className="flex flex-col gap-px w-64 border-solid border-0 border-r border-r-border h-full py-2 shrink-0 overflow-y-auto"
|
||||||
borderRight: `1px solid ${theme.palette.divider}`,
|
|
||||||
height: "100%",
|
|
||||||
overflow: "auto",
|
|
||||||
flexShrink: 0,
|
|
||||||
padding: "8px 0",
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
gap: 1,
|
|
||||||
}}
|
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SidebarLink: FC<LinkProps> = (props) => {
|
export const SidebarLink: FC<LinkProps> = ({ className, ...props }) => {
|
||||||
return <Link css={styles.sidebarItem} {...props} />;
|
return (
|
||||||
|
<Link
|
||||||
|
className={cn(
|
||||||
|
"text-[13px] text-content-primary py-2 px-4 text-left bg-transparent hover:divide-surface-tertiary cursor-pointer border-0 no-underline",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
interface SidebarItemProps extends HTMLAttributes<HTMLButtonElement> {
|
interface SidebarItemProps extends HTMLAttributes<HTMLButtonElement> {
|
||||||
@@ -33,21 +31,16 @@ interface SidebarItemProps extends HTMLAttributes<HTMLButtonElement> {
|
|||||||
|
|
||||||
export const SidebarItem: FC<SidebarItemProps> = ({
|
export const SidebarItem: FC<SidebarItemProps> = ({
|
||||||
isActive,
|
isActive,
|
||||||
|
className,
|
||||||
...buttonProps
|
...buttonProps
|
||||||
}) => {
|
}) => {
|
||||||
const theme = useTheme();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
css={[
|
className={cn(
|
||||||
styles.sidebarItem,
|
"text-[13px] text-content-primary py-2 px-4 text-left bg-transparent hover:divide-surface-tertiary opacity-75 hover:opacity-100 cursor-pointer border-0",
|
||||||
{ opacity: "0.75", "&:hover": { opacity: 1 } },
|
isActive && "opacity-100 bg-surface-tertiary",
|
||||||
isActive && {
|
className,
|
||||||
background: theme.palette.action.selected,
|
)}
|
||||||
opacity: 1,
|
|
||||||
pointerEvents: "none",
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
{...buttonProps}
|
{...buttonProps}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -56,15 +49,7 @@ export const SidebarItem: FC<SidebarItemProps> = ({
|
|||||||
export const SidebarCaption: FC<HTMLAttributes<HTMLSpanElement>> = (props) => {
|
export const SidebarCaption: FC<HTMLAttributes<HTMLSpanElement>> = (props) => {
|
||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
css={{
|
className="text-[10px] leading-tight py-3 px-4 uppercase font-medium text-content-primary tracking-widest"
|
||||||
fontSize: 10,
|
|
||||||
lineHeight: 1.2,
|
|
||||||
padding: "12px 16px",
|
|
||||||
display: "block",
|
|
||||||
textTransform: "uppercase",
|
|
||||||
fontWeight: 500,
|
|
||||||
letterSpacing: "0.1em",
|
|
||||||
}}
|
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -76,49 +61,17 @@ interface SidebarIconButton extends ComponentProps<typeof TopbarIconButton> {
|
|||||||
|
|
||||||
export const SidebarIconButton: FC<SidebarIconButton> = ({
|
export const SidebarIconButton: FC<SidebarIconButton> = ({
|
||||||
isActive,
|
isActive,
|
||||||
|
className,
|
||||||
...buttonProps
|
...buttonProps
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<TopbarIconButton
|
<TopbarIconButton
|
||||||
css={[
|
className={cn(
|
||||||
{ opacity: 0.75, "&:hover": { opacity: 1 } },
|
"opacity-75 hover:opacity-100 border-0 border-x-2 border-x-transparent border-solid",
|
||||||
isActive && styles.activeSidebarIconButton,
|
isActive && "opacity-100 relative border-l-sky-400",
|
||||||
]}
|
className,
|
||||||
|
)}
|
||||||
{...buttonProps}
|
{...buttonProps}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const styles = {
|
|
||||||
sidebarItem: (theme) => ({
|
|
||||||
fontSize: 13,
|
|
||||||
lineHeight: 1.2,
|
|
||||||
color: theme.palette.text.primary,
|
|
||||||
textDecoration: "none",
|
|
||||||
padding: "8px 16px",
|
|
||||||
display: "block",
|
|
||||||
textAlign: "left",
|
|
||||||
background: "none",
|
|
||||||
border: 0,
|
|
||||||
cursor: "pointer",
|
|
||||||
|
|
||||||
"&:hover": {
|
|
||||||
backgroundColor: theme.palette.action.hover,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
|
|
||||||
activeSidebarIconButton: (theme) => ({
|
|
||||||
opacity: 1,
|
|
||||||
position: "relative",
|
|
||||||
"&::before": {
|
|
||||||
content: '""',
|
|
||||||
position: "absolute",
|
|
||||||
left: 0,
|
|
||||||
top: 0,
|
|
||||||
bottom: 0,
|
|
||||||
width: 2,
|
|
||||||
backgroundColor: theme.palette.primary.main,
|
|
||||||
height: "100%",
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
} satisfies Record<string, Interpolation<Theme>>;
|
|
||||||
|
|||||||
@@ -23,10 +23,10 @@ export const DashboardLayout: FC = () => {
|
|||||||
{canViewDeployment && <LicenseBanner />}
|
{canViewDeployment && <LicenseBanner />}
|
||||||
<AnnouncementBanners />
|
<AnnouncementBanners />
|
||||||
|
|
||||||
<div className="flex flex-col min-h-full">
|
<div className="flex flex-col h-screen">
|
||||||
<Navbar />
|
<Navbar />
|
||||||
|
|
||||||
<div className="flex flex-col flex-1 pb-12">
|
<div className="flex flex-col flex-1 min-h-0">
|
||||||
<Suspense fallback={<Loader />}>
|
<Suspense fallback={<Loader />}>
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ export const NavbarView: FC<NavbarViewProps> = ({
|
|||||||
const webPush = useWebpushNotifications();
|
const webPush = useWebpushNotifications();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="border-0 border-b border-solid h-[72px] flex items-center leading-none px-6">
|
<div className="border-0 border-b border-solid h-[72px] min-h-[72px] flex items-center leading-none px-6">
|
||||||
<NavLink to="/workspaces">
|
<NavLink to="/workspaces">
|
||||||
{logo_url ? (
|
{logo_url ? (
|
||||||
<ExternalImage className="h-7" src={logo_url} alt="Custom Logo" />
|
<ExternalImage className="h-7" src={logo_url} alt="Custom Logo" />
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
SidebarItem,
|
SidebarItem,
|
||||||
SidebarLink,
|
SidebarLink,
|
||||||
} from "components/FullPageLayout/Sidebar";
|
} from "components/FullPageLayout/Sidebar";
|
||||||
|
import { ScrollArea } from "components/ScrollArea/ScrollArea";
|
||||||
import { Spinner } from "components/Spinner/Spinner";
|
import { Spinner } from "components/Spinner/Spinner";
|
||||||
import {
|
import {
|
||||||
WorkspaceBuildData,
|
WorkspaceBuildData,
|
||||||
@@ -30,36 +31,40 @@ export const HistorySidebar: FC<HistorySidebarProps> = ({ workspace }) => {
|
|||||||
return (
|
return (
|
||||||
<Sidebar>
|
<Sidebar>
|
||||||
<SidebarCaption>History</SidebarCaption>
|
<SidebarCaption>History</SidebarCaption>
|
||||||
{builds
|
<ScrollArea>
|
||||||
? builds.map((build) => (
|
<div className="flex flex-col gap-px">
|
||||||
<SidebarLink
|
{builds
|
||||||
target="_blank"
|
? builds.map((build) => (
|
||||||
key={build.id}
|
<SidebarLink
|
||||||
to={`/@${build.workspace_owner_name}/${build.workspace_name}/builds/${build.build_number}`}
|
target="_blank"
|
||||||
>
|
key={build.id}
|
||||||
<WorkspaceBuildData build={build} />
|
to={`/@${build.workspace_owner_name}/${build.workspace_name}/builds/${build.build_number}`}
|
||||||
</SidebarLink>
|
>
|
||||||
))
|
<WorkspaceBuildData build={build} />
|
||||||
: Array.from({ length: 15 }, (_, i) => (
|
</SidebarLink>
|
||||||
<SidebarItem key={i}>
|
))
|
||||||
<WorkspaceBuildDataSkeleton />
|
: Array.from({ length: 15 }, (_, i) => (
|
||||||
</SidebarItem>
|
<SidebarItem key={i}>
|
||||||
))}
|
<WorkspaceBuildDataSkeleton />
|
||||||
{buildsQuery.hasNextPage && (
|
</SidebarItem>
|
||||||
<div css={{ padding: 16 }}>
|
))}
|
||||||
<Button
|
{buildsQuery.hasNextPage && (
|
||||||
onClick={() => buildsQuery.fetchNextPage()}
|
<div css={{ padding: 16 }}>
|
||||||
disabled={buildsQuery.isFetchingNextPage}
|
<Button
|
||||||
variant="outline"
|
onClick={() => buildsQuery.fetchNextPage()}
|
||||||
className="w-full"
|
disabled={buildsQuery.isFetchingNextPage}
|
||||||
>
|
variant="outline"
|
||||||
<Spinner loading={buildsQuery.isFetchingNextPage}>
|
className="w-full"
|
||||||
<ArrowDownwardOutlined />
|
>
|
||||||
</Spinner>
|
<Spinner loading={buildsQuery.isFetchingNextPage}>
|
||||||
Show more builds
|
<ArrowDownwardOutlined />
|
||||||
</Button>
|
</Spinner>
|
||||||
|
Show more builds
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
</ScrollArea>
|
||||||
</Sidebar>
|
</Sidebar>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import type { Interpolation, Theme } from "@emotion/react";
|
|
||||||
import { useTheme } from "@emotion/react";
|
|
||||||
import HistoryOutlined from "@mui/icons-material/HistoryOutlined";
|
import HistoryOutlined from "@mui/icons-material/HistoryOutlined";
|
||||||
import HubOutlined from "@mui/icons-material/HubOutlined";
|
import HubOutlined from "@mui/icons-material/HubOutlined";
|
||||||
import AlertTitle from "@mui/material/AlertTitle";
|
import AlertTitle from "@mui/material/AlertTitle";
|
||||||
@@ -68,7 +66,6 @@ export const Workspace: FC<WorkspaceProps> = ({
|
|||||||
handleDebug,
|
handleDebug,
|
||||||
}) => {
|
}) => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const theme = useTheme();
|
|
||||||
|
|
||||||
const transitionStats =
|
const transitionStats =
|
||||||
template !== undefined ? ActiveTransition(template, workspace) : undefined;
|
template !== undefined ? ActiveTransition(template, workspace) : undefined;
|
||||||
@@ -100,18 +97,7 @@ export const Workspace: FC<WorkspaceProps> = ({
|
|||||||
workspacePending && !haveBuildLogs && !provisionersHealthy && !isRestarting;
|
workspacePending && !haveBuildLogs && !provisionersHealthy && !isRestarting;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div className="flex flex-col flex-1 min-h-0">
|
||||||
css={{
|
|
||||||
flex: 1,
|
|
||||||
display: "grid",
|
|
||||||
gridTemplate: `
|
|
||||||
"topbar topbar topbar" auto
|
|
||||||
"leftbar sidebar content" 1fr / auto auto 1fr
|
|
||||||
`,
|
|
||||||
// We need this to make the sidebar scrollable
|
|
||||||
overflow: "hidden",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<WorkspaceTopbar
|
<WorkspaceTopbar
|
||||||
workspace={workspace}
|
workspace={workspace}
|
||||||
template={template}
|
template={template}
|
||||||
@@ -130,153 +116,133 @@ export const Workspace: FC<WorkspaceProps> = ({
|
|||||||
handleToggleFavorite={handleToggleFavorite}
|
handleToggleFavorite={handleToggleFavorite}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div
|
<div className="flex flex-1 min-h-0">
|
||||||
css={{
|
<div className="flex">
|
||||||
gridArea: "leftbar",
|
<div className="flex flex-col h-full overflow-y-auto border-solid border-0 border-r border-r-border">
|
||||||
height: "100%",
|
<SidebarIconButton
|
||||||
overflowY: "auto",
|
isActive={sidebarOption.value === "resources"}
|
||||||
borderRight: `1px solid ${theme.palette.divider}`,
|
onClick={() => {
|
||||||
display: "flex",
|
setSidebarOption("resources");
|
||||||
flexDirection: "column",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<SidebarIconButton
|
|
||||||
isActive={sidebarOption.value === "resources"}
|
|
||||||
onClick={() => {
|
|
||||||
setSidebarOption("resources");
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<HubOutlined />
|
|
||||||
</SidebarIconButton>
|
|
||||||
<SidebarIconButton
|
|
||||||
isActive={sidebarOption.value === "history"}
|
|
||||||
onClick={() => {
|
|
||||||
setSidebarOption("history");
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<HistoryOutlined />
|
|
||||||
</SidebarIconButton>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{sidebarOption.value === "resources" && (
|
|
||||||
<ResourcesSidebar
|
|
||||||
failed={workspace.latest_build.status === "failed"}
|
|
||||||
resources={resources}
|
|
||||||
isSelected={resourcesNav.isSelected}
|
|
||||||
onChange={resourcesNav.select}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{sidebarOption.value === "history" && (
|
|
||||||
<HistorySidebar workspace={workspace} />
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div css={[styles.content, styles.dotsBackground]}>
|
|
||||||
{selectedResource && (
|
|
||||||
<ResourceMetadata
|
|
||||||
resource={selectedResource}
|
|
||||||
css={{ margin: "-32px -32px 0 -32px", marginBottom: 24 }}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<div
|
|
||||||
css={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
gap: 24,
|
|
||||||
maxWidth: 24 * 50,
|
|
||||||
margin: "auto",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{workspace.latest_build.status === "deleted" && (
|
|
||||||
<WorkspaceDeletedBanner
|
|
||||||
handleClick={() => navigate("/templates")}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{shouldShowProvisionerAlert && (
|
|
||||||
<ProvisionerStatusAlert
|
|
||||||
matchingProvisioners={
|
|
||||||
workspace.latest_build.matched_provisioners?.count
|
|
||||||
}
|
|
||||||
availableProvisioners={
|
|
||||||
workspace.latest_build.matched_provisioners?.available ?? 0
|
|
||||||
}
|
|
||||||
tags={workspace.latest_build.job.tags}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{workspace.latest_build.job.error && (
|
|
||||||
<Alert severity="error">
|
|
||||||
<AlertTitle>Workspace build failed</AlertTitle>
|
|
||||||
<AlertDetail>{workspace.latest_build.job.error}</AlertDetail>
|
|
||||||
</Alert>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{transitionStats !== undefined && (
|
|
||||||
<WorkspaceBuildProgress
|
|
||||||
workspace={workspace}
|
|
||||||
transitionStats={transitionStats}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{shouldShowBuildLogs && (
|
|
||||||
<WorkspaceBuildLogsSection logs={buildLogs} />
|
|
||||||
)}
|
|
||||||
|
|
||||||
{selectedResource && (
|
|
||||||
<section
|
|
||||||
css={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
gap: 24,
|
|
||||||
flexGrow: 1,
|
|
||||||
minWidth: 0 /* Prevent overflow */,
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{selectedResource.agents
|
<HubOutlined />
|
||||||
// If an agent has a `parent_id`, that means it is
|
</SidebarIconButton>
|
||||||
// child of another agent. We do not want these agents
|
<SidebarIconButton
|
||||||
// to be displayed at the top-level on this page. We
|
isActive={sidebarOption.value === "history"}
|
||||||
// want them to display _as children_ of their parents.
|
onClick={() => {
|
||||||
?.filter((agent) => agent.parent_id === null)
|
setSidebarOption("history");
|
||||||
.map((agent) => (
|
}}
|
||||||
<AgentRow
|
>
|
||||||
key={agent.id}
|
<HistoryOutlined />
|
||||||
agent={agent}
|
</SidebarIconButton>
|
||||||
subAgents={selectedResource.agents?.filter(
|
</div>
|
||||||
(a) => a.parent_id === agent.id,
|
|
||||||
)}
|
|
||||||
workspace={workspace}
|
|
||||||
template={template}
|
|
||||||
onUpdateAgent={handleUpdate} // On updating the workspace the agent version is also updated
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
|
|
||||||
{(!selectedResource.agents ||
|
{sidebarOption.value === "resources" && (
|
||||||
selectedResource.agents?.length === 0) && (
|
<ResourcesSidebar
|
||||||
<div
|
failed={workspace.latest_build.status === "failed"}
|
||||||
css={{
|
resources={resources}
|
||||||
display: "flex",
|
isSelected={resourcesNav.isSelected}
|
||||||
justifyContent: "center",
|
onChange={resourcesNav.select}
|
||||||
alignItems: "center",
|
/>
|
||||||
width: "100%",
|
|
||||||
height: "100%",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<h4 css={{ fontSize: 16, fontWeight: 500 }}>
|
|
||||||
No agents are currently assigned to this resource.
|
|
||||||
</h4>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</section>
|
|
||||||
)}
|
)}
|
||||||
|
{sidebarOption.value === "history" && (
|
||||||
|
<HistorySidebar workspace={workspace} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
<WorkspaceTimings
|
<div
|
||||||
provisionerTimings={timings?.provisioner_timings}
|
style={{
|
||||||
agentScriptTimings={timings?.agent_script_timings}
|
background: `radial-gradient(
|
||||||
agentConnectionTimings={timings?.agent_connection_timings}
|
circle at 1px 1px,
|
||||||
/>
|
hsl(var(--surface-invert-secondary)) 0,
|
||||||
|
transparent 1px
|
||||||
|
) -2px -2px / 16px 16px`,
|
||||||
|
}}
|
||||||
|
className="p-8 overflow-y-auto relative w-full"
|
||||||
|
>
|
||||||
|
{selectedResource && (
|
||||||
|
<ResourceMetadata
|
||||||
|
resource={selectedResource}
|
||||||
|
className="-mx-8 -mt-8 mb-6"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<div className="flex flex-col gap-6 max-w-[1200px] m-auto">
|
||||||
|
{workspace.latest_build.status === "deleted" && (
|
||||||
|
<WorkspaceDeletedBanner
|
||||||
|
handleClick={() => navigate("/templates")}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{shouldShowProvisionerAlert && (
|
||||||
|
<ProvisionerStatusAlert
|
||||||
|
matchingProvisioners={
|
||||||
|
workspace.latest_build.matched_provisioners?.count
|
||||||
|
}
|
||||||
|
availableProvisioners={
|
||||||
|
workspace.latest_build.matched_provisioners?.available ?? 0
|
||||||
|
}
|
||||||
|
tags={workspace.latest_build.job.tags}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{workspace.latest_build.job.error && (
|
||||||
|
<Alert severity="error">
|
||||||
|
<AlertTitle>Workspace build failed</AlertTitle>
|
||||||
|
<AlertDetail>{workspace.latest_build.job.error}</AlertDetail>
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{transitionStats !== undefined && (
|
||||||
|
<WorkspaceBuildProgress
|
||||||
|
workspace={workspace}
|
||||||
|
transitionStats={transitionStats}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{shouldShowBuildLogs && (
|
||||||
|
<WorkspaceBuildLogsSection logs={buildLogs} />
|
||||||
|
)}
|
||||||
|
|
||||||
|
{selectedResource && (
|
||||||
|
<section className="flex flex-col gap-6 flex-grow min-w-0">
|
||||||
|
{selectedResource.agents
|
||||||
|
// If an agent has a `parent_id`, that means it is
|
||||||
|
// child of another agent. We do not want these agents
|
||||||
|
// to be displayed at the top-level on this page. We
|
||||||
|
// want them to display _as children_ of their parents.
|
||||||
|
?.filter((agent) => agent.parent_id === null)
|
||||||
|
.map((agent) => (
|
||||||
|
<AgentRow
|
||||||
|
key={agent.id}
|
||||||
|
agent={agent}
|
||||||
|
subAgents={selectedResource.agents?.filter(
|
||||||
|
(a) => a.parent_id === agent.id,
|
||||||
|
)}
|
||||||
|
workspace={workspace}
|
||||||
|
template={template}
|
||||||
|
onUpdateAgent={handleUpdate} // On updating the workspace the agent version is also updated
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{(!selectedResource.agents ||
|
||||||
|
selectedResource.agents?.length === 0) && (
|
||||||
|
<div className="flex justify-center items-center w-full h-full">
|
||||||
|
<div>
|
||||||
|
<h4 className="text-base font-medium">
|
||||||
|
No agents are currently assigned to this resource.
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<WorkspaceTimings
|
||||||
|
provisionerTimings={timings?.provisioner_timings}
|
||||||
|
agentScriptTimings={timings?.agent_script_timings}
|
||||||
|
agentConnectionTimings={timings?.agent_connection_timings}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -286,33 +252,3 @@ export const Workspace: FC<WorkspaceProps> = ({
|
|||||||
const countAgents = (resource: TypesGen.WorkspaceResource) => {
|
const countAgents = (resource: TypesGen.WorkspaceResource) => {
|
||||||
return resource.agents ? resource.agents.length : 0;
|
return resource.agents ? resource.agents.length : 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
const styles = {
|
|
||||||
content: {
|
|
||||||
padding: 32,
|
|
||||||
gridArea: "content",
|
|
||||||
overflowY: "auto",
|
|
||||||
position: "relative",
|
|
||||||
},
|
|
||||||
|
|
||||||
dotsBackground: (theme) => ({
|
|
||||||
"--d": "1px",
|
|
||||||
background: `
|
|
||||||
radial-gradient(
|
|
||||||
circle at
|
|
||||||
var(--d)
|
|
||||||
var(--d),
|
|
||||||
|
|
||||||
${theme.palette.dots} calc(var(--d) - 1px),
|
|
||||||
${theme.palette.background.default} var(--d)
|
|
||||||
)
|
|
||||||
-2px -2px / 16px 16px
|
|
||||||
`,
|
|
||||||
}),
|
|
||||||
|
|
||||||
actions: (theme) => ({
|
|
||||||
[theme.breakpoints.down("md")]: {
|
|
||||||
flexDirection: "column",
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
} satisfies Record<string, Interpolation<Theme>>;
|
|
||||||
|
|||||||
Reference in New Issue
Block a user