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, } pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy