Skip to content

feat: Redesign resources table #4600

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Oct 18, 2022
Prev Previous commit
Next Next commit
Replace table
  • Loading branch information
BrunoQuaresma committed Oct 17, 2022
commit ad60b388b9cbe99cf471f36e94a5cdfca89da36d
238 changes: 20 additions & 218 deletions site/src/components/Resources/Resources.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,12 @@
import Button from "@material-ui/core/Button"
import { makeStyles, Theme } from "@material-ui/core/styles"
import Table from "@material-ui/core/Table"
import TableBody from "@material-ui/core/TableBody"
import TableCell from "@material-ui/core/TableCell"
import TableContainer from "@material-ui/core/TableContainer"
import TableHead from "@material-ui/core/TableHead"
import TableRow from "@material-ui/core/TableRow"
import { makeStyles } from "@material-ui/core/styles"
import { Skeleton } from "@material-ui/lab"
import useTheme from "@material-ui/styles/useTheme"
import {
CloseDropdown,
OpenDropdown,
} from "components/DropdownArrows/DropdownArrows"
import { PortForwardButton } from "components/PortForwardButton/PortForwardButton"
import { TableCellDataPrimary } from "components/TableCellData/TableCellData"
import { FC, useState } from "react"
import { getDisplayAgentStatus, getDisplayVersionStatus } from "util/workspace"
import {
BuildInfoResponse,
DERPRegion,
Expand All @@ -26,28 +17,27 @@ import {
import { AppLink } from "../AppLink/AppLink"
import { SSHButton } from "../SSHButton/SSHButton"
import { Stack } from "../Stack/Stack"
import { TableHeaderRow } from "../TableHeaders/TableHeaders"
import { TerminalLink } from "../TerminalLink/TerminalLink"
import { AgentHelpTooltip } from "../Tooltips/AgentHelpTooltip"
import { AgentOutdatedTooltip } from "../Tooltips/AgentOutdatedTooltip"
import { ResourcesHelpTooltip } from "../Tooltips/ResourcesHelpTooltip"
import { ResourceAgentLatency } from "./ResourceAgentLatency"
import { ResourceAvatarData } from "./ResourceAvatarData"
import { AlertBanner } from "components/AlertBanner/AlertBanner"
import { ResourceAvatar } from "./ResourceAvatar"
import { SensitiveValue } from "./SensitiveValue"
import { AgentLatency } from "./AgentLatency"

const Language = {
resources: "Resources",
resourceLabel: "Resource",
agentsLabel: "Agents",
agentLabel: "Agent",
statusLabel: "status: ",
versionLabel: "version: ",
osLabel: "os: ",
}
const getLatency = (agent: WorkspaceAgent) => {
// Find the right latency to display
const latencyValues = Object.values(agent.latency ?? {})
const latency =
latencyValues.find((derp) => derp.preferred) ??
// Accessing an array index can return undefined as well
// for some reason TS does not handle that
(latencyValues[0] as DERPRegion | undefined)

if (!latency) {
return undefined
}

return latency
}
interface ResourcesProps {
resources: WorkspaceResource[]
getResourcesError?: Error | unknown
Expand All @@ -63,50 +53,24 @@ export const Resources: FC<React.PropsWithChildren<ResourcesProps>> = ({
getResourcesError,
workspace,
canUpdateWorkspace,
buildInfo,
hideSSHButton,
applicationsHost,
}) => {
const styles = useStyles()
const theme: Theme = useTheme()
const serverVersion = buildInfo?.version || ""
const [shouldDisplayHideResources, setShouldDisplayHideResources] =
useState(false)
const displayResources = shouldDisplayHideResources
? resources
: resources.filter((resource) => !resource.hide)
const hasHideResources = resources.some((r) => r.hide)

const getDisplayLatency = (agent: WorkspaceAgent) => {
// Find the right latency to display
const latencyValues = Object.values(agent.latency ?? {})
const latency =
latencyValues.find((derp) => derp.preferred) ??
// Accessing an array index can return undefined as well
// for some reason TS does not handle that
(latencyValues[0] as DERPRegion | undefined)

if (!latency) {
return undefined
}

// Get the color
let color = theme.palette.success.light
if (latency.latency_ms >= 150 && latency.latency_ms < 300) {
color = theme.palette.warning.light
} else if (latency.latency_ms >= 300) {
color = theme.palette.error.light
}

return {
...latency,
color,
}
if (getResourcesError) {
return <AlertBanner severity="error" error={getResourcesError} />
}

return (
<Stack direction="column" spacing={1}>
{resources.map((resource) => {
<Stack direction="column" spacing={2}>
{displayResources.map((resource) => {
// Type is already displayed on top of the resource name
const metadataToDisplay =
resource.metadata?.filter((data) => data.key !== "type") ?? []
Expand Down Expand Up @@ -144,7 +108,7 @@ export const Resources: FC<React.PropsWithChildren<ResourcesProps>> = ({

<div>
{resource.agents?.map((agent) => {
const latency = getDisplayLatency(agent)
const latency = getLatency(agent)

return (
<Stack
Expand Down Expand Up @@ -226,163 +190,6 @@ export const Resources: FC<React.PropsWithChildren<ResourcesProps>> = ({
)
})}

<div aria-label={Language.resources} className={styles.wrapper}>
{getResourcesError ? (
<AlertBanner severity="error" error={getResourcesError} />
) : (
<TableContainer className={styles.tableContainer}>
<Table>
<TableHead>
<TableHeaderRow>
<TableCell>
<Stack direction="row" spacing={0.5} alignItems="center">
{Language.resourceLabel}
<ResourcesHelpTooltip />
</Stack>
</TableCell>
<TableCell className={styles.agentColumn}>
<Stack direction="row" spacing={0.5} alignItems="center">
{Language.agentLabel}
<AgentHelpTooltip />
</Stack>
</TableCell>
{canUpdateWorkspace && <TableCell></TableCell>}
</TableHeaderRow>
</TableHead>
<TableBody>
{displayResources.map((resource) => {
{
/* We need to initialize the agents to display the resource */
}
const agents = resource.agents ?? [null]
const resourceName = (
<ResourceAvatarData resource={resource} />
)

return agents.map((agent, agentIndex) => {
{
/* If there is no agent, just display the resource name */
}
if (
!agent ||
workspace.latest_build.transition === "stop"
) {
return (
<TableRow key={`${resource.id}-${agentIndex}`}>
<TableCell>{resourceName}</TableCell>
<TableCell colSpan={3}></TableCell>
</TableRow>
)
}
const { displayVersion, outdated } =
getDisplayVersionStatus(agent.version, serverVersion)
const agentStatus = getDisplayAgentStatus(theme, agent)
return (
<TableRow key={`${resource.id}-${agent.id}`}>
{/* 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 && (
<TableCell
className={styles.resourceNameCell}
rowSpan={agents.length}
>
{resourceName}
</TableCell>
)}

<TableCell className={styles.agentColumn}>
<TableCellDataPrimary highlight>
{agent.name}
</TableCellDataPrimary>
<div className={styles.data}>
<div className={styles.dataRow}>
<strong>{Language.statusLabel}</strong>
<span
style={{ color: agentStatus.color }}
className={styles.status}
>
{agentStatus.status}
</span>
</div>
<div className={styles.dataRow}>
<strong>{Language.osLabel}</strong>
<span className={styles.operatingSystem}>
{agent.operating_system}
</span>
</div>
<div className={styles.dataRow}>
<strong>{Language.versionLabel}</strong>
<span className={styles.agentVersion}>
{displayVersion}
</span>
<AgentOutdatedTooltip outdated={outdated} />
</div>
<div className={styles.dataRow}>
<ResourceAgentLatency latency={agent.latency} />
</div>
</div>
</TableCell>
<TableCell>
<div className={styles.accessLinks}>
{canUpdateWorkspace &&
agent.status === "connected" && (
<>
{applicationsHost !== undefined && (
<PortForwardButton
host={applicationsHost}
workspaceName={workspace.name}
agentId={agent.id}
agentName={agent.name}
username={workspace.owner_name}
/>
)}
{!hideSSHButton && (
<SSHButton
workspaceName={workspace.name}
agentName={agent.name}
/>
)}
<TerminalLink
workspaceName={workspace.name}
agentName={agent.name}
userName={workspace.owner_name}
/>
{agent.apps.map((app) => (
<AppLink
key={app.name}
appsHost={applicationsHost}
appIcon={app.icon}
appName={app.name}
appCommand={app.command}
appSubdomain={app.subdomain}
appSharingLevel={app.sharing_level}
username={workspace.owner_name}
workspaceName={workspace.name}
agentName={agent.name}
health={app.health}
/>
))}
</>
)}
{canUpdateWorkspace &&
agent.status === "connecting" && (
<>
<Skeleton width={80} height={60} />
<Skeleton width={120} height={60} />
</>
)}
</div>
</TableCell>
</TableRow>
)
})
})}
</TableBody>
</Table>
</TableContainer>
)}
</div>

{hasHideResources && (
<div className={styles.buttonWrapper}>
<Button
Expand All @@ -408,11 +215,6 @@ export const Resources: FC<React.PropsWithChildren<ResourcesProps>> = ({
}

const useStyles = makeStyles((theme) => ({
wrapper: {
borderRadius: theme.shape.borderRadius,
border: `1px solid ${theme.palette.divider}`,
},

tableContainer: {
border: 0,
},
Expand Down