diff --git a/site/src/components/AppLink/AppLink.stories.tsx b/site/src/components/AppLink/AppLink.stories.tsx index 845d1e350abc9..a95732b521728 100644 --- a/site/src/components/AppLink/AppLink.stories.tsx +++ b/site/src/components/AppLink/AppLink.stories.tsx @@ -1,5 +1,9 @@ import { Story } from "@storybook/react" -import { MockWorkspace } from "../../testHelpers/renderHelpers" +import { + MockWorkspace, + MockWorkspaceAgent, + MockWorkspaceApp, +} from "testHelpers/renderHelpers" import { AppLink, AppLinkProps } from "./AppLink" export default { @@ -11,44 +15,59 @@ const Template: Story = (args) => export const WithIcon = Template.bind({}) WithIcon.args = { - username: "developer", - workspaceName: MockWorkspace.name, - appName: "code-server", - appIcon: "/icon/code.svg", - appSharingLevel: "owner", - health: "healthy", + workspace: MockWorkspace, + app: { + ...MockWorkspaceApp, + name: "code-server", + icon: "/icon/code.svg", + sharing_level: "owner", + health: "healthy", + }, + agent: MockWorkspaceAgent, } export const WithoutIcon = Template.bind({}) WithoutIcon.args = { - username: "developer", - workspaceName: MockWorkspace.name, - appName: "code-server", - appSharingLevel: "owner", - health: "healthy", + workspace: MockWorkspace, + app: { + ...MockWorkspaceApp, + name: "code-server", + sharing_level: "owner", + health: "healthy", + }, + agent: MockWorkspaceAgent, } export const HealthDisabled = Template.bind({}) HealthDisabled.args = { - username: "developer", - workspaceName: MockWorkspace.name, - appName: "code-server", - appSharingLevel: "owner", - health: "disabled", + workspace: MockWorkspace, + app: { + ...MockWorkspaceApp, + name: "code-server", + sharing_level: "owner", + health: "disabled", + }, + agent: MockWorkspaceAgent, } export const HealthInitializing = Template.bind({}) HealthInitializing.args = { - username: "developer", - workspaceName: MockWorkspace.name, - appName: "code-server", - health: "initializing", + workspace: MockWorkspace, + app: { + ...MockWorkspaceApp, + name: "code-server", + health: "initializing", + }, + agent: MockWorkspaceAgent, } export const HealthUnhealthy = Template.bind({}) HealthUnhealthy.args = { - username: "developer", - workspaceName: MockWorkspace.name, - appName: "code-server", - health: "unhealthy", + workspace: MockWorkspace, + app: { + ...MockWorkspaceApp, + name: "code-server", + health: "unhealthy", + }, + agent: MockWorkspaceAgent, } diff --git a/site/src/components/AppLink/AppLink.tsx b/site/src/components/AppLink/AppLink.tsx index 50477c08362ee..de8c4d485d939 100644 --- a/site/src/components/AppLink/AppLink.tsx +++ b/site/src/components/AppLink/AppLink.tsx @@ -3,14 +3,12 @@ import CircularProgress from "@material-ui/core/CircularProgress" import Link from "@material-ui/core/Link" import { makeStyles } from "@material-ui/core/styles" import Tooltip from "@material-ui/core/Tooltip" -import ComputerIcon from "@material-ui/icons/Computer" -import PublicOutlinedIcon from "@material-ui/icons/PublicOutlined" -import LockOutlinedIcon from "@material-ui/icons/LockOutlined" -import GroupOutlinedIcon from "@material-ui/icons/GroupOutlined" import ErrorOutlineIcon from "@material-ui/icons/ErrorOutline" -import { FC, PropsWithChildren } from "react" +import { FC } from "react" import * as TypesGen from "../../api/typesGenerated" import { generateRandomString } from "../../util/random" +import { BaseIcon } from "./BaseIcon" +import { ShareIcon } from "./ShareIcon" export const Language = { appTitle: (appName: string, identifier: string): string => @@ -19,76 +17,50 @@ export const Language = { export interface AppLinkProps { appsHost?: string - username: TypesGen.User["username"] - workspaceName: TypesGen.Workspace["name"] - agentName: TypesGen.WorkspaceAgent["name"] - appName: TypesGen.WorkspaceApp["name"] - appIcon?: TypesGen.WorkspaceApp["icon"] - appCommand?: TypesGen.WorkspaceApp["command"] - appSubdomain: TypesGen.WorkspaceApp["subdomain"] - appSharingLevel: TypesGen.WorkspaceApp["sharing_level"] - health: TypesGen.WorkspaceApp["health"] + workspace: TypesGen.Workspace + app: TypesGen.WorkspaceApp + agent: TypesGen.WorkspaceAgent } -export const AppLink: FC> = ({ +export const AppLink: FC = ({ appsHost, - username, - workspaceName, - agentName, - appName, - appIcon, - appCommand, - appSubdomain, - appSharingLevel, - health, + app, + workspace, + agent, }) => { const styles = useStyles() + const username = workspace.owner_name // The backend redirects if the trailing slash isn't included, so we add it // here to avoid extra roundtrips. - let href = `/@${username}/${workspaceName}.${agentName}/apps/${encodeURIComponent( - appName, - )}/` - if (appCommand) { - href = `/@${username}/${workspaceName}.${agentName}/terminal?command=${encodeURIComponent( - appCommand, - )}` + let href = `/@${username}/${workspace.name}.${ + agent.name + }/apps/${encodeURIComponent(app.name)}/` + if (app.command) { + href = `/@${username}/${workspace.name}.${ + agent.name + }/terminal?command=${encodeURIComponent(app.command)}` } - if (appsHost && appSubdomain) { - const subdomain = `${appName}--${agentName}--${workspaceName}--${username}` + if (appsHost && app.subdomain) { + const subdomain = `${app.name}--${agent.name}--${workspace.name}--${username}` href = `${window.location.protocol}//${appsHost}/`.replace("*", subdomain) } let canClick = true - let icon = appIcon ? ( - {`${appName} - ) : ( - - ) - - let shareIcon = - let shareTooltip = "Private, only accessible by you" - if (appSharingLevel === "authenticated") { - shareIcon = - shareTooltip = "Shared with all authenticated users" - } - if (appSharingLevel === "public") { - shareIcon = - shareTooltip = "Shared publicly" - } + let icon = let primaryTooltip = "" - if (health === "initializing") { + if (app.health === "initializing") { canClick = false icon = primaryTooltip = "Initializing..." } - if (health === "unhealthy") { + if (app.health === "unhealthy") { canClick = false icon = primaryTooltip = "Unhealthy" } - if (!appsHost && appSubdomain) { + if (!appsHost && app.subdomain) { canClick = false icon = primaryTooltip = @@ -99,11 +71,11 @@ export const AppLink: FC> = ({ ) @@ -120,7 +92,7 @@ export const AppLink: FC> = ({ event.preventDefault() window.open( href, - Language.appTitle(appName, generateRandomString(12)), + Language.appTitle(app.name, generateRandomString(12)), "width=900,height=600", ) } diff --git a/site/src/components/AppLink/AppPreviewLink.tsx b/site/src/components/AppLink/AppPreviewLink.tsx new file mode 100644 index 0000000000000..4d434f8963a9e --- /dev/null +++ b/site/src/components/AppLink/AppPreviewLink.tsx @@ -0,0 +1,43 @@ +import { makeStyles } from "@material-ui/core/styles" +import { Stack } from "components/Stack/Stack" +import { FC } from "react" +import * as TypesGen from "api/typesGenerated" +import { BaseIcon } from "./BaseIcon" +import { ShareIcon } from "./ShareIcon" + +export interface AppPreviewProps { + app: TypesGen.WorkspaceApp +} + +export const AppPreviewLink: FC = ({ app }) => { + const styles = useStyles() + + return ( + + + {app.name} + + + ) +} + +const useStyles = makeStyles((theme) => ({ + appPreviewLink: { + padding: theme.spacing(0.25, 1.5), + borderRadius: 9999, + border: `1px solid ${theme.palette.divider}`, + color: theme.palette.text.primary, + background: theme.palette.background.paper, + flexShrink: 0, + width: "fit-content", + + "& img, & svg": { + width: 14, + }, + }, +})) diff --git a/site/src/components/AppLink/BaseIcon.tsx b/site/src/components/AppLink/BaseIcon.tsx new file mode 100644 index 0000000000000..9343817e9c536 --- /dev/null +++ b/site/src/components/AppLink/BaseIcon.tsx @@ -0,0 +1,11 @@ +import { WorkspaceApp } from "api/typesGenerated" +import { FC } from "react" +import ComputerIcon from "@material-ui/icons/Computer" + +export const BaseIcon: FC<{ app: WorkspaceApp }> = ({ app }) => { + return app.icon ? ( + {`${app.name} + ) : ( + + ) +} diff --git a/site/src/components/AppLink/ShareIcon.tsx b/site/src/components/AppLink/ShareIcon.tsx new file mode 100644 index 0000000000000..64e657b3a2420 --- /dev/null +++ b/site/src/components/AppLink/ShareIcon.tsx @@ -0,0 +1,28 @@ +import PublicOutlinedIcon from "@material-ui/icons/PublicOutlined" +import LockOutlinedIcon from "@material-ui/icons/LockOutlined" +import GroupOutlinedIcon from "@material-ui/icons/GroupOutlined" +import { FC } from "react" +import * as TypesGen from "../../api/typesGenerated" +import Tooltip from "@material-ui/core/Tooltip" +import { useTranslation } from "react-i18next" + +export interface ShareIconProps { + app: TypesGen.WorkspaceApp +} + +export const ShareIcon: FC = ({ app }) => { + const { t } = useTranslation("agent") + + let shareIcon = + let shareTooltip = t("shareTooltip.private") + if (app.sharing_level === "authenticated") { + shareIcon = + shareTooltip = t("shareTooltip.authenticated") + } + if (app.sharing_level === "public") { + shareIcon = + shareTooltip = t("shareTooltip.public") + } + + return {shareIcon} +} diff --git a/site/src/components/BuildsTable/BuildsTable.tsx b/site/src/components/BuildsTable/BuildsTable.tsx index 483af405a5a60..6bf20ef3d2a92 100644 --- a/site/src/components/BuildsTable/BuildsTable.tsx +++ b/site/src/components/BuildsTable/BuildsTable.tsx @@ -22,7 +22,6 @@ export const Language = { export interface BuildsTableProps { builds?: TypesGen.WorkspaceBuild[] - className?: string } const groupBuildsByDate = (builds?: TypesGen.WorkspaceBuild[]) => { @@ -48,13 +47,12 @@ const groupBuildsByDate = (builds?: TypesGen.WorkspaceBuild[]) => { export const BuildsTable: FC> = ({ builds, - className, }) => { const isLoading = !builds const buildsByDate = groupBuildsByDate(builds) return ( - + {isLoading && } diff --git a/site/src/components/Resources/AgentRow.stories.tsx b/site/src/components/Resources/AgentRow.stories.tsx new file mode 100644 index 0000000000000..f5c6b8246c0d0 --- /dev/null +++ b/site/src/components/Resources/AgentRow.stories.tsx @@ -0,0 +1,60 @@ +import { Story } from "@storybook/react" +import { + MockWorkspace, + MockWorkspaceAgent, + MockWorkspaceApp, +} from "testHelpers/entities" +import { AgentRow, AgentRowProps } from "./AgentRow" + +export default { + title: "components/AgentRow", + component: AgentRow, +} + +const Template: Story = (args) => + +export const Example = Template.bind({}) +Example.args = { + agent: MockWorkspaceAgent, + workspace: MockWorkspace, + applicationsHost: "", + showApps: true, +} + +export const HideSSHButton = Template.bind({}) +HideSSHButton.args = { + agent: MockWorkspaceAgent, + workspace: MockWorkspace, + applicationsHost: "", + showApps: true, + hideSSHButton: true, +} + +export const NotShowingApps = Template.bind({}) +NotShowingApps.args = { + agent: MockWorkspaceAgent, + workspace: MockWorkspace, + applicationsHost: "", + showApps: false, +} + +export const BunchOfApps = Template.bind({}) +BunchOfApps.args = { + ...Example.args, + agent: { + ...MockWorkspaceAgent, + apps: [ + MockWorkspaceApp, + MockWorkspaceApp, + MockWorkspaceApp, + MockWorkspaceApp, + MockWorkspaceApp, + MockWorkspaceApp, + MockWorkspaceApp, + MockWorkspaceApp, + ], + }, + workspace: MockWorkspace, + applicationsHost: "", + showApps: true, +} diff --git a/site/src/components/Resources/AgentRow.tsx b/site/src/components/Resources/AgentRow.tsx new file mode 100644 index 0000000000000..40acef8e737e0 --- /dev/null +++ b/site/src/components/Resources/AgentRow.tsx @@ -0,0 +1,148 @@ +import { makeStyles } from "@material-ui/core/styles" +import { Skeleton } from "@material-ui/lab" +import { PortForwardButton } from "components/PortForwardButton/PortForwardButton" +import { FC } from "react" +import { Workspace, WorkspaceAgent } from "../../api/typesGenerated" +import { AppLink } from "../AppLink/AppLink" +import { SSHButton } from "../SSHButton/SSHButton" +import { Stack } from "../Stack/Stack" +import { TerminalLink } from "../TerminalLink/TerminalLink" +import { AgentLatency } from "./AgentLatency" +import { AgentVersion } from "./AgentVersion" +import { Maybe } from "components/Conditionals/Maybe" +import { AgentStatus } from "./AgentStatus" + +export interface AgentRowProps { + agent: WorkspaceAgent + workspace: Workspace + applicationsHost: string | undefined + showApps: boolean + hideSSHButton?: boolean + serverVersion: string +} + +export const AgentRow: FC = ({ + agent, + workspace, + applicationsHost, + showApps, + hideSSHButton, + serverVersion, +}) => { + const styles = useStyles() + + return ( + + +
+ +
+
+
{agent.name}
+ + {agent.operating_system} + + + + + + + +
+
+ + + {showApps && agent.status === "connected" && ( + <> + {agent.apps.map((app) => ( + + ))} + + + {!hideSSHButton && ( + + )} + {applicationsHost !== undefined && ( + + )} + + )} + {showApps && agent.status === "connecting" && ( + <> + + + + )} + +
+ ) +} + +const useStyles = makeStyles((theme) => ({ + agentRow: { + padding: theme.spacing(3, 4), + backgroundColor: theme.palette.background.paperLight, + fontSize: 16, + + "&:not(:last-child)": { + borderBottom: `1px solid ${theme.palette.divider}`, + }, + }, + + agentStatusWrapper: { + width: theme.spacing(4.5), + display: "flex", + justifyContent: "center", + }, + + agentName: { + fontWeight: 600, + }, + + agentOS: { + textTransform: "capitalize", + }, + + agentData: { + fontSize: 14, + color: theme.palette.text.secondary, + marginTop: theme.spacing(0.5), + }, +})) diff --git a/site/src/components/Resources/AgentRowPreview.stories.tsx b/site/src/components/Resources/AgentRowPreview.stories.tsx new file mode 100644 index 0000000000000..86a92319e737e --- /dev/null +++ b/site/src/components/Resources/AgentRowPreview.stories.tsx @@ -0,0 +1,35 @@ +import { Story } from "@storybook/react" +import { MockWorkspaceAgent, MockWorkspaceApp } from "testHelpers/entities" +import { AgentRowPreview, AgentRowPreviewProps } from "./AgentRowPreview" + +export default { + title: "components/AgentRowPreview", + component: AgentRowPreview, +} + +const Template: Story = (args) => ( + +) + +export const Example = Template.bind({}) +Example.args = { + agent: MockWorkspaceAgent, +} + +export const BunchOfApps = Template.bind({}) +BunchOfApps.args = { + ...Example.args, + agent: { + ...MockWorkspaceAgent, + apps: [ + MockWorkspaceApp, + MockWorkspaceApp, + MockWorkspaceApp, + MockWorkspaceApp, + MockWorkspaceApp, + MockWorkspaceApp, + MockWorkspaceApp, + MockWorkspaceApp, + ], + }, +} diff --git a/site/src/components/Resources/AgentRowPreview.tsx b/site/src/components/Resources/AgentRowPreview.tsx new file mode 100644 index 0000000000000..53e6ce0a139b4 --- /dev/null +++ b/site/src/components/Resources/AgentRowPreview.tsx @@ -0,0 +1,161 @@ +import { makeStyles } from "@material-ui/core/styles" +import { AppPreviewLink } from "components/AppLink/AppPreviewLink" +import { FC } from "react" +import { useTranslation } from "react-i18next" +import { combineClasses } from "util/combineClasses" +import { WorkspaceAgent } from "../../api/typesGenerated" +import { Stack } from "../Stack/Stack" + +export interface AgentRowPreviewProps { + agent: WorkspaceAgent +} + +export const AgentRowPreview: FC = ({ agent }) => { + const styles = useStyles() + const { t } = useTranslation("agent") + + return ( + + +
+
+
+ + + {t("labels.agent").toString()}: + {agent.name} + + + + {t("labels.os").toString()}: + + {agent.operating_system} + + + + + {t("labels.apps").toString()}: + + {agent.apps.map((app) => ( + + ))} + + + +
+
+ ) +} + +const useStyles = makeStyles((theme) => ({ + agentRow: { + padding: theme.spacing(2, 4), + backgroundColor: theme.palette.background.paperLight, + fontSize: 16, + position: "relative", + + "&:not(:last-child)": { + paddingBottom: 0, + }, + + "&:after": { + content: "''", + height: "100%", + width: 2, + backgroundColor: theme.palette.divider, + position: "absolute", + top: 0, + left: 49, + }, + }, + + agentStatusWrapper: { + width: theme.spacing(4.5), + display: "flex", + justifyContent: "center", + flexShrink: 0, + }, + + agentStatusPreview: { + width: 10, + height: 10, + border: `2px solid ${theme.palette.text.secondary}`, + borderRadius: "100%", + position: "relative", + zIndex: 1, + background: theme.palette.background.paper, + }, + + agentName: { + fontWeight: 600, + }, + + agentOS: { + textTransform: "capitalize", + fontSize: 14, + color: theme.palette.text.secondary, + }, + + agentData: { + fontSize: 14, + color: theme.palette.text.secondary, + + [theme.breakpoints.down("sm")]: { + gap: theme.spacing(2), + flexWrap: "wrap", + }, + }, + + agentDataValue: { + color: theme.palette.text.primary, + }, + + noShrink: { + flexShrink: 0, + }, + + agentDataItem: { + [theme.breakpoints.down("sm")]: { + flexDirection: "column", + alignItems: "flex-start", + gap: theme.spacing(1), + width: "fit-content", + }, + }, +})) diff --git a/site/src/components/Resources/ResourceCard.stories.tsx b/site/src/components/Resources/ResourceCard.stories.tsx index 25c47aaa21efe..d131db78f1a3e 100644 --- a/site/src/components/Resources/ResourceCard.stories.tsx +++ b/site/src/components/Resources/ResourceCard.stories.tsx @@ -1,10 +1,6 @@ import { Story } from "@storybook/react" -import { - MockWorkspace, - MockWorkspaceAgent, - MockWorkspaceApp, - MockWorkspaceResource, -} from "testHelpers/entities" +import { MockWorkspace, MockWorkspaceResource } from "testHelpers/entities" +import { AgentRow } from "./AgentRow" import { ResourceCard, ResourceCardProps } from "./ResourceCard" export default { @@ -17,46 +13,16 @@ const Template: Story = (args) => export const Example = Template.bind({}) Example.args = { resource: MockWorkspaceResource, - workspace: MockWorkspace, - applicationsHost: "https://dev.coder.com", - hideSSHButton: false, - showApps: true, - serverVersion: MockWorkspaceAgent.version, -} - -export const NotShowingApps = Template.bind({}) -NotShowingApps.args = { - ...Example.args, - showApps: false, -} - -export const HideSSHButton = Template.bind({}) -HideSSHButton.args = { - ...Example.args, - hideSSHButton: true, -} - -export const BunchOfApps = Template.bind({}) -BunchOfApps.args = { - ...Example.args, - resource: { - ...MockWorkspaceResource, - agents: [ - { - ...MockWorkspaceAgent, - apps: [ - MockWorkspaceApp, - MockWorkspaceApp, - MockWorkspaceApp, - MockWorkspaceApp, - MockWorkspaceApp, - MockWorkspaceApp, - MockWorkspaceApp, - MockWorkspaceApp, - ], - }, - ], - }, + agentRow: (agent) => ( + + ), } export const BunchOfMetadata = Template.bind({}) @@ -102,4 +68,14 @@ BunchOfMetadata.args = { }, ], }, + agentRow: (agent) => ( + + ), } diff --git a/site/src/components/Resources/ResourceCard.tsx b/site/src/components/Resources/ResourceCard.tsx index d351822e21425..273a77c8ba2b6 100644 --- a/site/src/components/Resources/ResourceCard.tsx +++ b/site/src/components/Resources/ResourceCard.tsx @@ -1,16 +1,9 @@ import { makeStyles } from "@material-ui/core/styles" -import { Skeleton } from "@material-ui/lab" -import { PortForwardButton } from "components/PortForwardButton/PortForwardButton" import { FC, useState } from "react" -import { Workspace, WorkspaceResource } from "../../api/typesGenerated" -import { AppLink } from "../AppLink/AppLink" -import { SSHButton } from "../SSHButton/SSHButton" +import { WorkspaceAgent, WorkspaceResource } from "../../api/typesGenerated" import { Stack } from "../Stack/Stack" -import { TerminalLink } from "../TerminalLink/TerminalLink" import { ResourceAvatar } from "./ResourceAvatar" import { SensitiveValue } from "./SensitiveValue" -import { AgentLatency } from "./AgentLatency" -import { AgentVersion } from "./AgentVersion" import { OpenDropdown, CloseDropdown, @@ -19,25 +12,13 @@ import IconButton from "@material-ui/core/IconButton" import Tooltip from "@material-ui/core/Tooltip" import { Maybe } from "components/Conditionals/Maybe" import { CopyableValue } from "components/CopyableValue/CopyableValue" -import { AgentStatus } from "./AgentStatus" export interface ResourceCardProps { resource: WorkspaceResource - workspace: Workspace - applicationsHost: string | undefined - showApps: boolean - hideSSHButton?: boolean - serverVersion: string + agentRow: (agent: WorkspaceAgent) => JSX.Element } -export const ResourceCard: FC = ({ - resource, - workspace, - applicationsHost, - showApps, - hideSSHButton, - serverVersion, -}) => { +export const ResourceCard: FC = ({ resource, agentRow }) => { const [shouldDisplayAllMetadata, setShouldDisplayAllMetadata] = useState(false) const styles = useStyles() @@ -113,102 +94,7 @@ export const ResourceCard: FC = ({ {resource.agents && resource.agents.length > 0 && ( -
- {resource.agents.map((agent) => { - return ( - - -
- -
-
-
{agent.name}
- - - {agent.operating_system} - - - - - - - - -
-
- - - {showApps && agent.status === "connected" && ( - <> - {agent.apps.map((app) => ( - - ))} - - - {!hideSSHButton && ( - - )} - {applicationsHost !== undefined && ( - - )} - - )} - {showApps && agent.status === "connecting" && ( - <> - - - - )} - -
- ) - })} -
+
{resource.agents.map(agentRow)}
)} ) @@ -270,34 +156,4 @@ const useStyles = makeStyles((theme) => ({ overflow: "hidden", whiteSpace: "nowrap", }, - - agentRow: { - padding: theme.spacing(3, 4), - backgroundColor: theme.palette.background.paperLight, - fontSize: 16, - - "&:not(:last-child)": { - borderBottom: `1px solid ${theme.palette.divider}`, - }, - }, - - agentStatusWrapper: { - width: theme.spacing(4.5), - display: "flex", - justifyContent: "center", - }, - - agentName: { - fontWeight: 600, - }, - - agentOS: { - textTransform: "capitalize", - }, - - agentData: { - fontSize: 14, - color: theme.palette.text.secondary, - marginTop: theme.spacing(0.5), - }, })) diff --git a/site/src/components/Resources/Resources.tsx b/site/src/components/Resources/Resources.tsx index 8e7957d15b0f0..b1337ca728f65 100644 --- a/site/src/components/Resources/Resources.tsx +++ b/site/src/components/Resources/Resources.tsx @@ -5,13 +5,8 @@ import { OpenDropdown, } from "components/DropdownArrows/DropdownArrows" import { FC, useState } from "react" -import { - BuildInfoResponse, - Workspace, - WorkspaceResource, -} from "../../api/typesGenerated" +import { WorkspaceAgent, WorkspaceResource } from "../../api/typesGenerated" import { Stack } from "../Stack/Stack" -import { AlertBanner } from "components/AlertBanner/AlertBanner" import { ResourceCard } from "./ResourceCard" const countAgents = (resource: WorkspaceResource) => { @@ -20,24 +15,13 @@ const countAgents = (resource: WorkspaceResource) => { interface ResourcesProps { resources: WorkspaceResource[] - getResourcesError?: Error | unknown - workspace: Workspace - canUpdateWorkspace: boolean - buildInfo?: BuildInfoResponse | undefined - hideSSHButton?: boolean - applicationsHost?: string + agentRow: (agent: WorkspaceAgent) => JSX.Element } export const Resources: FC> = ({ resources, - getResourcesError, - workspace, - canUpdateWorkspace, - hideSSHButton, - applicationsHost, - buildInfo, + agentRow, }) => { - const serverVersion = buildInfo?.version || "" const styles = useStyles() const [shouldDisplayHideResources, setShouldDisplayHideResources] = useState(false) @@ -49,26 +33,15 @@ export const Resources: FC> = ({ .sort((a, b) => countAgents(b) - countAgents(a)) const hasHideResources = resources.some((r) => r.hide) - if (getResourcesError) { - return - } - return ( - {displayResources.map((resource) => { - return ( - - ) - })} - + {displayResources.map((resource) => ( + + ))} {hasHideResources && (
- - - - - {Language.resourceLabel} - - - - - - {Language.agentLabel} - - - - - - - {resources.map((resource) => { - // We need to initialize the agents to display the resource - const agents = resource.agents ?? [null] - return agents.map((agent, agentIndex) => { - // If there is no agent, just display the resource name - if (!agent) { - return ( - - - } - /> - - - - ) - } - - return ( - - {/* We only want to display the name in the first row because we are using rowSpan */} - {/* The rowspan should be the same than the number of agents */} - {agentIndex === 0 && ( - - } - /> - - )} - - - {agent.name} - - {agent.operating_system} - - - - ) - }) - })} - -
-
+ } + /> ) } - -const useStyles = makeStyles((theme) => ({ - sectionContents: { - margin: 0, - }, - - resourceNameCell: { - borderRight: `1px solid ${theme.palette.divider}`, - }, - - resourceType: { - fontSize: 14, - color: theme.palette.text.secondary, - marginTop: theme.spacing(0.5), - display: "block", - }, - - // Adds some left spacing - agentColumn: { - paddingLeft: `${theme.spacing(2)}px !important`, - }, - - operatingSystem: { - fontSize: 14, - color: theme.palette.text.secondary, - marginTop: theme.spacing(0.5), - display: "block", - textTransform: "capitalize", - }, -})) diff --git a/site/src/components/Workspace/Workspace.tsx b/site/src/components/Workspace/Workspace.tsx index 2353e30d74689..1a560d14d4715 100644 --- a/site/src/components/Workspace/Workspace.tsx +++ b/site/src/components/Workspace/Workspace.tsx @@ -16,7 +16,6 @@ import { WorkspaceActions } from "../WorkspaceActions/WorkspaceActions" import { WorkspaceDeletedBanner } from "../WorkspaceDeletedBanner/WorkspaceDeletedBanner" import { WorkspaceScheduleBanner } from "../WorkspaceScheduleBanner/WorkspaceScheduleBanner" import { WorkspaceScheduleButton } from "../WorkspaceScheduleButton/WorkspaceScheduleButton" -import { WorkspaceSection } from "../WorkspaceSection/WorkspaceSection" import { WorkspaceStats } from "../WorkspaceStats/WorkspaceStats" import { AlertBanner } from "../AlertBanner/AlertBanner" import { useTranslation } from "react-i18next" @@ -24,6 +23,7 @@ import { EstimateTransitionTime, WorkspaceBuildProgress, } from "components/WorkspaceBuildProgress/WorkspaceBuildProgress" +import { AgentRow } from "components/Resources/AgentRow" export enum WorkspaceErrors { GET_RESOURCES_ERROR = "getResourcesError", @@ -87,6 +87,7 @@ export const Workspace: FC> = ({ const { t } = useTranslation("workspacePage") const styles = useStyles() const navigate = useNavigate() + const serverVersion = buildInfo?.version || "" const hasTemplateIcon = workspace.template_icon && workspace.template_icon !== "" @@ -207,32 +208,38 @@ export const Workspace: FC> = ({ /> )} + {Boolean(workspaceErrors[WorkspaceErrors.GET_RESOURCES_ERROR]) && ( + + )} + {typeof resources !== "undefined" && resources.length > 0 && ( ( + + )} /> )} - - {workspaceErrors[WorkspaceErrors.GET_BUILDS_ERROR] ? ( - - ) : ( - - )} - + {workspaceErrors[WorkspaceErrors.GET_BUILDS_ERROR] ? ( + + ) : ( + + )} ) @@ -276,9 +283,5 @@ export const useStyles = makeStyles((theme) => { timelineContents: { margin: 0, }, - - timelineTable: { - border: 0, - }, } }) diff --git a/site/src/i18n/en/agent.json b/site/src/i18n/en/agent.json new file mode 100644 index 0000000000000..cd4203bb5f368 --- /dev/null +++ b/site/src/i18n/en/agent.json @@ -0,0 +1,12 @@ +{ + "shareTooltip": { + "private": "Private, only accessible by you", + "authenticated": "Shared with all authenticated users", + "public": "Shared publicly" + }, + "labels": { + "agent": "Agent", + "os": "OS", + "apps": "Apps" + } +} diff --git a/site/src/i18n/en/index.ts b/site/src/i18n/en/index.ts index 7fd2c233630fb..f5a8efe7f2be2 100644 --- a/site/src/i18n/en/index.ts +++ b/site/src/i18n/en/index.ts @@ -4,6 +4,7 @@ import createWorkspacePage from "./createWorkspacePage.json" import templatePage from "./templatePage.json" import templatesPage from "./templatesPage.json" import workspacePage from "./workspacePage.json" +import agent from "./agent.json" export const en = { common, @@ -12,4 +13,5 @@ export const en = { templatePage, templatesPage, createWorkspacePage, + agent, }