diff --git a/site/src/modules/resources/AgentRow.tsx b/site/src/modules/resources/AgentRow.tsx index 601086907f..add04620b5 100644 --- a/site/src/modules/resources/AgentRow.tsx +++ b/site/src/modules/resources/AgentRow.tsx @@ -1,4 +1,3 @@ -import type { Interpolation, Theme } from "@emotion/react"; import Collapse from "@mui/material/Collapse"; import Divider from "@mui/material/Divider"; import Skeleton from "@mui/material/Skeleton"; @@ -25,6 +24,7 @@ import { import { Link as RouterLink } from "react-router"; import AutoSizer from "react-virtualized-auto-sizer"; import type { FixedSizeList as List, ListOnScrollProps } from "react-window"; +import { cn } from "utils/cn"; import { AgentApps, organizeAgentApps } from "./AgentApps/AgentApps"; import { AgentDevcontainerCard } from "./AgentDevcontainerCard"; import { AgentExternal } from "./AgentExternal"; @@ -52,6 +52,43 @@ interface AgentRowProps { onUpdateAgent: () => void; } +const statusBorderClassByStatus: Partial< + Record +> = { + connected: "border-border-success", + connecting: "border-border-pending", + timeout: "border-border-warning", +}; + +const statusBorderClassByLifecycle: Partial< + Record +> = { + starting: "border-border-pending", + shutting_down: "border-border-pending", + ready: "border-border-success", + start_timeout: "border-border-warning", + shutdown_timeout: "border-border-warning", + start_error: "border-border-destructive", + shutdown_error: "border-border-destructive", + off: "border-border", +}; + +const getAgentBorderClass = ( + agent: WorkspaceAgent, + hasErrorState: boolean, +): string => { + if (hasErrorState) { + return "border-border"; + } + + // Lifecycle state is more specific than connection status, so it wins. + return ( + statusBorderClassByLifecycle[agent.lifecycle_state] ?? + statusBorderClassByStatus[agent.status] ?? + "border-border" + ); +}; + export const AgentRow: FC = ({ agent, subAgents, @@ -143,24 +180,26 @@ export const AgentRow: FC = ({ const hasSubdomainApps = agent.apps?.some((app) => app.subdomain); const shouldShowWildcardWarning = hasSubdomainApps && !proxy.proxy?.wildcard_hostname; + const borderClass = getAgentBorderClass( + agent, + Boolean(hasDevcontainerErrors || shouldShowWildcardWarning), + ); return (
-
-
-
+
+
+
- {agent.name} + + {agent.name} +
{agent.status === "connected" && ( <> @@ -207,7 +246,7 @@ export const AgentRow: FC = ({
-
+
{workspace.latest_app_status?.agent_id === agent.id && (

App statuses

@@ -229,7 +268,7 @@ export const AgentRow: FC = ({ {shouldShowWildcardWarning && } {shouldDisplayAppsSection && ( -
+
{shouldDisplayAgentApps && ( <> {showVSCode && ( @@ -263,18 +302,18 @@ export const AgentRow: FC = ({ )} {agent.status === "connecting" && !isExternalAgent && ( -
+
)} @@ -307,11 +346,7 @@ export const AgentRow: FC = ({
{hasStartupFeatures && ( -
({ - borderTop: `1px solid ${theme.palette.divider}`, - })} - > +
{({ width }) => ( @@ -320,7 +355,7 @@ export const AgentRow: FC = ({ innerRef={logListDivRef} height={256} width={width} - css={styles.startupLogs} + className="max-h-[420px] border-0 border-b border-solid border-border" onScroll={handleLogScroll} overflowed={agent.logs_overflowed} logs={agentLogs.map((l) => ({ @@ -353,201 +388,3 @@ export const AgentRow: FC = ({
); }; - -const styles = { - agentRow: (theme) => ({ - fontSize: 14, - border: `1px solid ${theme.palette.text.secondary}`, - backgroundColor: theme.palette.background.default, - borderRadius: 8, - boxShadow: theme.shadows[3], - }), - - "agentRow-connected": (theme) => ({ - borderColor: theme.palette.success.light, - }), - - "agentRow-disconnected": (theme) => ({ - borderColor: theme.palette.divider, - }), - - "agentRow-connecting": (theme) => ({ - borderColor: theme.palette.info.light, - }), - - "agentRow-timeout": (theme) => ({ - borderColor: theme.palette.warning.light, - }), - - "agentRow-lifecycle-created": {}, - - "agentRow-lifecycle-starting": (theme) => ({ - borderColor: theme.palette.info.light, - }), - - "agentRow-lifecycle-ready": (theme) => ({ - borderColor: theme.palette.success.light, - }), - - "agentRow-lifecycle-start_timeout": (theme) => ({ - borderColor: theme.palette.warning.light, - }), - - "agentRow-lifecycle-start_error": (theme) => ({ - borderColor: theme.palette.error.light, - }), - - "agentRow-lifecycle-shutting_down": (theme) => ({ - borderColor: theme.palette.info.light, - }), - - "agentRow-lifecycle-shutdown_timeout": (theme) => ({ - borderColor: theme.palette.warning.light, - }), - - "agentRow-lifecycle-shutdown_error": (theme) => ({ - borderColor: theme.palette.error.light, - }), - - "agentRow-lifecycle-off": (theme) => ({ - borderColor: theme.palette.divider, - }), - - header: (theme) => ({ - padding: "16px 16px 0 32px", - display: "flex", - gap: 24, - alignItems: "center", - justifyContent: "space-between", - flexWrap: "wrap", - lineHeight: "1.5", - - "&:has(+ [role='alert'])": { - paddingBottom: 16, - }, - - [theme.breakpoints.down("md")]: { - gap: 16, - }, - }), - - agentInfo: (theme) => ({ - display: "flex", - alignItems: "center", - gap: 24, - color: theme.palette.text.secondary, - fontSize: 14, - }), - - agentNameAndInfo: (theme) => ({ - display: "flex", - alignItems: "center", - gap: 24, - flexWrap: "wrap", - - [theme.breakpoints.down("md")]: { - gap: 12, - }, - }), - - content: { - padding: 32, - display: "flex", - flexDirection: "column", - gap: 32, - }, - - apps: (theme) => ({ - display: "flex", - gap: 16, - flexWrap: "wrap", - - "&:empty": { - display: "none", - }, - - [theme.breakpoints.down("md")]: { - marginLeft: 0, - justifyContent: "flex-start", - }, - }), - - agentDescription: (theme) => ({ - fontSize: 14, - color: theme.palette.text.secondary, - }), - - agentNameAndStatus: (theme) => ({ - display: "flex", - alignItems: "center", - gap: 16, - - [theme.breakpoints.down("md")]: { - width: "100%", - }, - }), - - agentName: (theme) => ({ - whiteSpace: "nowrap", - overflow: "hidden", - textOverflow: "ellipsis", - maxWidth: 260, - fontWeight: 600, - flexShrink: 0, - width: "fit-content", - fontSize: 16, - color: theme.palette.text.primary, - - [theme.breakpoints.down("md")]: { - overflow: "unset", - }, - }), - - agentDataGroup: { - display: "flex", - alignItems: "baseline", - gap: 48, - }, - - agentData: (theme) => ({ - display: "flex", - flexDirection: "column", - fontSize: 12, - - "& > *:first-of-type": { - fontWeight: 500, - color: theme.palette.text.secondary, - }, - }), - - buttonSkeleton: { - borderRadius: 4, - }, - - agentErrorMessage: (theme) => ({ - fontSize: 12, - fontWeight: 400, - marginTop: 4, - color: theme.palette.warning.light, - }), - - agentOS: { - textTransform: "capitalize", - }, - - startupLogs: (theme) => ({ - maxHeight: 420, - borderBottom: `1px solid ${theme.palette.divider}`, - backgroundColor: theme.palette.background.paper, - paddingTop: 16, - - // We need this to be able to apply the padding top from startupLogs - "& > div": { - position: "relative", - }, - }), - - agentRowWithErrors: (theme) => ({ - borderColor: theme.palette.divider, - }), -} satisfies Record>; diff --git a/site/src/pages/WorkspacePage/Workspace.tsx b/site/src/pages/WorkspacePage/Workspace.tsx index 90a0530e28..0b98ed6c17 100644 --- a/site/src/pages/WorkspacePage/Workspace.tsx +++ b/site/src/pages/WorkspacePage/Workspace.tsx @@ -159,16 +159,7 @@ export const Workspace: FC = ({ )}
-
+
{selectedResource && (