mirror of
https://github.com/coder/coder.git
synced 2026-09-22 05:05:20 +08:00
refactor: Refactor template resources (#4789)
This commit is contained in:
@@ -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<AppLinkProps> = (args) => <AppLink {...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,
|
||||
}
|
||||
|
||||
@@ -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<PropsWithChildren<AppLinkProps>> = ({
|
||||
export const AppLink: FC<AppLinkProps> = ({
|
||||
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 ? (
|
||||
<img alt={`${appName} Icon`} src={appIcon} />
|
||||
) : (
|
||||
<ComputerIcon />
|
||||
)
|
||||
|
||||
let shareIcon = <LockOutlinedIcon />
|
||||
let shareTooltip = "Private, only accessible by you"
|
||||
if (appSharingLevel === "authenticated") {
|
||||
shareIcon = <GroupOutlinedIcon />
|
||||
shareTooltip = "Shared with all authenticated users"
|
||||
}
|
||||
if (appSharingLevel === "public") {
|
||||
shareIcon = <PublicOutlinedIcon />
|
||||
shareTooltip = "Shared publicly"
|
||||
}
|
||||
let icon = <BaseIcon app={app} />
|
||||
|
||||
let primaryTooltip = ""
|
||||
if (health === "initializing") {
|
||||
if (app.health === "initializing") {
|
||||
canClick = false
|
||||
icon = <CircularProgress size={16} />
|
||||
primaryTooltip = "Initializing..."
|
||||
}
|
||||
if (health === "unhealthy") {
|
||||
if (app.health === "unhealthy") {
|
||||
canClick = false
|
||||
icon = <ErrorOutlineIcon className={styles.unhealthyIcon} />
|
||||
primaryTooltip = "Unhealthy"
|
||||
}
|
||||
if (!appsHost && appSubdomain) {
|
||||
if (!appsHost && app.subdomain) {
|
||||
canClick = false
|
||||
icon = <ErrorOutlineIcon className={styles.notConfiguredIcon} />
|
||||
primaryTooltip =
|
||||
@@ -99,11 +71,11 @@ export const AppLink: FC<PropsWithChildren<AppLinkProps>> = ({
|
||||
<Button
|
||||
size="small"
|
||||
startIcon={icon}
|
||||
endIcon={<Tooltip title={shareTooltip}>{shareIcon}</Tooltip>}
|
||||
endIcon={<ShareIcon app={app} />}
|
||||
className={styles.button}
|
||||
disabled={!canClick}
|
||||
>
|
||||
<span className={styles.appName}>{appName}</span>
|
||||
<span className={styles.appName}>{app.name}</span>
|
||||
</Button>
|
||||
)
|
||||
|
||||
@@ -120,7 +92,7 @@ export const AppLink: FC<PropsWithChildren<AppLinkProps>> = ({
|
||||
event.preventDefault()
|
||||
window.open(
|
||||
href,
|
||||
Language.appTitle(appName, generateRandomString(12)),
|
||||
Language.appTitle(app.name, generateRandomString(12)),
|
||||
"width=900,height=600",
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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<AppPreviewProps> = ({ app }) => {
|
||||
const styles = useStyles()
|
||||
|
||||
return (
|
||||
<Stack
|
||||
className={styles.appPreviewLink}
|
||||
alignItems="center"
|
||||
direction="row"
|
||||
spacing={1}
|
||||
>
|
||||
<BaseIcon app={app} />
|
||||
{app.name}
|
||||
<ShareIcon app={app} />
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
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,
|
||||
},
|
||||
},
|
||||
}))
|
||||
@@ -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 ? (
|
||||
<img alt={`${app.name} Icon`} src={app.icon} />
|
||||
) : (
|
||||
<ComputerIcon />
|
||||
)
|
||||
}
|
||||
@@ -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<ShareIconProps> = ({ app }) => {
|
||||
const { t } = useTranslation("agent")
|
||||
|
||||
let shareIcon = <LockOutlinedIcon />
|
||||
let shareTooltip = t("shareTooltip.private")
|
||||
if (app.sharing_level === "authenticated") {
|
||||
shareIcon = <GroupOutlinedIcon />
|
||||
shareTooltip = t("shareTooltip.authenticated")
|
||||
}
|
||||
if (app.sharing_level === "public") {
|
||||
shareIcon = <PublicOutlinedIcon />
|
||||
shareTooltip = t("shareTooltip.public")
|
||||
}
|
||||
|
||||
return <Tooltip title={shareTooltip}>{shareIcon}</Tooltip>
|
||||
}
|
||||
@@ -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<React.PropsWithChildren<BuildsTableProps>> = ({
|
||||
builds,
|
||||
className,
|
||||
}) => {
|
||||
const isLoading = !builds
|
||||
const buildsByDate = groupBuildsByDate(builds)
|
||||
|
||||
return (
|
||||
<TableContainer className={className}>
|
||||
<TableContainer>
|
||||
<Table data-testid="builds-table" aria-describedby="builds table">
|
||||
<TableBody>
|
||||
{isLoading && <TableLoader />}
|
||||
|
||||
@@ -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<AgentRowProps> = (args) => <AgentRow {...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,
|
||||
}
|
||||
@@ -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<AgentRowProps> = ({
|
||||
agent,
|
||||
workspace,
|
||||
applicationsHost,
|
||||
showApps,
|
||||
hideSSHButton,
|
||||
serverVersion,
|
||||
}) => {
|
||||
const styles = useStyles()
|
||||
|
||||
return (
|
||||
<Stack
|
||||
key={agent.id}
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
justifyContent="space-between"
|
||||
className={styles.agentRow}
|
||||
spacing={4}
|
||||
>
|
||||
<Stack direction="row" alignItems="baseline">
|
||||
<div className={styles.agentStatusWrapper}>
|
||||
<AgentStatus agent={agent} />
|
||||
</div>
|
||||
<div>
|
||||
<div className={styles.agentName}>{agent.name}</div>
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="baseline"
|
||||
className={styles.agentData}
|
||||
spacing={1}
|
||||
>
|
||||
<span className={styles.agentOS}>{agent.operating_system}</span>
|
||||
|
||||
<Maybe condition={agent.status === "connected"}>
|
||||
<AgentVersion agent={agent} serverVersion={serverVersion} />
|
||||
</Maybe>
|
||||
|
||||
<AgentLatency agent={agent} />
|
||||
</Stack>
|
||||
</div>
|
||||
</Stack>
|
||||
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
spacing={0.5}
|
||||
wrap="wrap"
|
||||
maxWidth="750px"
|
||||
>
|
||||
{showApps && agent.status === "connected" && (
|
||||
<>
|
||||
{agent.apps.map((app) => (
|
||||
<AppLink
|
||||
key={app.name}
|
||||
appsHost={applicationsHost}
|
||||
app={app}
|
||||
agent={agent}
|
||||
workspace={workspace}
|
||||
/>
|
||||
))}
|
||||
|
||||
<TerminalLink
|
||||
workspaceName={workspace.name}
|
||||
agentName={agent.name}
|
||||
userName={workspace.owner_name}
|
||||
/>
|
||||
{!hideSSHButton && (
|
||||
<SSHButton
|
||||
workspaceName={workspace.name}
|
||||
agentName={agent.name}
|
||||
/>
|
||||
)}
|
||||
{applicationsHost !== undefined && (
|
||||
<PortForwardButton
|
||||
host={applicationsHost}
|
||||
workspaceName={workspace.name}
|
||||
agentId={agent.id}
|
||||
agentName={agent.name}
|
||||
username={workspace.owner_name}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{showApps && agent.status === "connecting" && (
|
||||
<>
|
||||
<Skeleton width={80} height={36} variant="rect" />
|
||||
<Skeleton width={120} height={36} variant="rect" />
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
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),
|
||||
},
|
||||
}))
|
||||
@@ -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<AgentRowPreviewProps> = (args) => (
|
||||
<AgentRowPreview {...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,
|
||||
],
|
||||
},
|
||||
}
|
||||
@@ -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<AgentRowPreviewProps> = ({ agent }) => {
|
||||
const styles = useStyles()
|
||||
const { t } = useTranslation("agent")
|
||||
|
||||
return (
|
||||
<Stack
|
||||
key={agent.id}
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
justifyContent="space-between"
|
||||
className={styles.agentRow}
|
||||
>
|
||||
<Stack direction="row" alignItems="baseline">
|
||||
<div className={styles.agentStatusWrapper}>
|
||||
<div className={styles.agentStatusPreview}></div>
|
||||
</div>
|
||||
<Stack
|
||||
alignItems="baseline"
|
||||
direction="row"
|
||||
spacing={4}
|
||||
className={styles.agentData}
|
||||
>
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="baseline"
|
||||
spacing={1}
|
||||
className={combineClasses([styles.noShrink, styles.agentDataItem])}
|
||||
>
|
||||
<span>{t("labels.agent").toString()}:</span>
|
||||
<span className={styles.agentDataValue}>{agent.name}</span>
|
||||
</Stack>
|
||||
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="baseline"
|
||||
spacing={1}
|
||||
className={combineClasses([styles.noShrink, styles.agentDataItem])}
|
||||
>
|
||||
<span>{t("labels.os").toString()}:</span>
|
||||
<span
|
||||
className={combineClasses([
|
||||
styles.agentDataValue,
|
||||
styles.agentOS,
|
||||
])}
|
||||
>
|
||||
{agent.operating_system}
|
||||
</span>
|
||||
</Stack>
|
||||
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
spacing={1}
|
||||
className={styles.agentDataItem}
|
||||
>
|
||||
<span>{t("labels.apps").toString()}:</span>
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
spacing={0.5}
|
||||
wrap="wrap"
|
||||
>
|
||||
{agent.apps.map((app) => (
|
||||
<AppPreviewLink key={app.name} app={app} />
|
||||
))}
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
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",
|
||||
},
|
||||
},
|
||||
}))
|
||||
@@ -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<ResourceCardProps> = (args) => <ResourceCard {...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) => (
|
||||
<AgentRow
|
||||
showApps
|
||||
key={agent.id}
|
||||
agent={agent}
|
||||
workspace={MockWorkspace}
|
||||
applicationsHost=""
|
||||
serverVersion=""
|
||||
/>
|
||||
),
|
||||
}
|
||||
|
||||
export const BunchOfMetadata = Template.bind({})
|
||||
@@ -102,4 +68,14 @@ BunchOfMetadata.args = {
|
||||
},
|
||||
],
|
||||
},
|
||||
agentRow: (agent) => (
|
||||
<AgentRow
|
||||
showApps
|
||||
key={agent.id}
|
||||
agent={agent}
|
||||
workspace={MockWorkspace}
|
||||
applicationsHost=""
|
||||
serverVersion=""
|
||||
/>
|
||||
),
|
||||
}
|
||||
|
||||
@@ -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<ResourceCardProps> = ({
|
||||
resource,
|
||||
workspace,
|
||||
applicationsHost,
|
||||
showApps,
|
||||
hideSSHButton,
|
||||
serverVersion,
|
||||
}) => {
|
||||
export const ResourceCard: FC<ResourceCardProps> = ({ resource, agentRow }) => {
|
||||
const [shouldDisplayAllMetadata, setShouldDisplayAllMetadata] =
|
||||
useState(false)
|
||||
const styles = useStyles()
|
||||
@@ -113,102 +94,7 @@ export const ResourceCard: FC<ResourceCardProps> = ({
|
||||
</Stack>
|
||||
|
||||
{resource.agents && resource.agents.length > 0 && (
|
||||
<div>
|
||||
{resource.agents.map((agent) => {
|
||||
return (
|
||||
<Stack
|
||||
key={agent.id}
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
justifyContent="space-between"
|
||||
className={styles.agentRow}
|
||||
>
|
||||
<Stack direction="row" alignItems="baseline">
|
||||
<div className={styles.agentStatusWrapper}>
|
||||
<AgentStatus agent={agent} />
|
||||
</div>
|
||||
<div>
|
||||
<div className={styles.agentName}>{agent.name}</div>
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="baseline"
|
||||
className={styles.agentData}
|
||||
spacing={1}
|
||||
>
|
||||
<span className={styles.agentOS}>
|
||||
{agent.operating_system}
|
||||
</span>
|
||||
|
||||
<Maybe condition={agent.status === "connected"}>
|
||||
<AgentVersion
|
||||
agent={agent}
|
||||
serverVersion={serverVersion}
|
||||
/>
|
||||
</Maybe>
|
||||
|
||||
<AgentLatency agent={agent} />
|
||||
</Stack>
|
||||
</div>
|
||||
</Stack>
|
||||
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
spacing={0.5}
|
||||
wrap="wrap"
|
||||
maxWidth="750px"
|
||||
>
|
||||
{showApps && agent.status === "connected" && (
|
||||
<>
|
||||
{agent.apps.map((app) => (
|
||||
<AppLink
|
||||
key={app.name}
|
||||
appsHost={applicationsHost}
|
||||
appIcon={app.icon}
|
||||
appName={app.name}
|
||||
appCommand={app.command}
|
||||
appSubdomain={app.subdomain}
|
||||
username={workspace.owner_name}
|
||||
workspaceName={workspace.name}
|
||||
agentName={agent.name}
|
||||
health={app.health}
|
||||
appSharingLevel={app.sharing_level}
|
||||
/>
|
||||
))}
|
||||
|
||||
<TerminalLink
|
||||
workspaceName={workspace.name}
|
||||
agentName={agent.name}
|
||||
userName={workspace.owner_name}
|
||||
/>
|
||||
{!hideSSHButton && (
|
||||
<SSHButton
|
||||
workspaceName={workspace.name}
|
||||
agentName={agent.name}
|
||||
/>
|
||||
)}
|
||||
{applicationsHost !== undefined && (
|
||||
<PortForwardButton
|
||||
host={applicationsHost}
|
||||
workspaceName={workspace.name}
|
||||
agentId={agent.id}
|
||||
agentName={agent.name}
|
||||
username={workspace.owner_name}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{showApps && agent.status === "connecting" && (
|
||||
<>
|
||||
<Skeleton width={80} height={36} variant="rect" />
|
||||
<Skeleton width={120} height={36} variant="rect" />
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
</Stack>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
<div>{resource.agents.map(agentRow)}</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
@@ -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),
|
||||
},
|
||||
}))
|
||||
|
||||
@@ -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<React.PropsWithChildren<ResourcesProps>> = ({
|
||||
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<React.PropsWithChildren<ResourcesProps>> = ({
|
||||
.sort((a, b) => countAgents(b) - countAgents(a))
|
||||
const hasHideResources = resources.some((r) => r.hide)
|
||||
|
||||
if (getResourcesError) {
|
||||
return <AlertBanner severity="error" error={getResourcesError} />
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack direction="column" spacing={0}>
|
||||
{displayResources.map((resource) => {
|
||||
return (
|
||||
<ResourceCard
|
||||
key={resource.id}
|
||||
resource={resource}
|
||||
workspace={workspace}
|
||||
applicationsHost={applicationsHost}
|
||||
showApps={canUpdateWorkspace}
|
||||
hideSSHButton={hideSSHButton}
|
||||
serverVersion={serverVersion}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
|
||||
{displayResources.map((resource) => (
|
||||
<ResourceCard
|
||||
key={resource.id}
|
||||
resource={resource}
|
||||
agentRow={agentRow}
|
||||
/>
|
||||
))}
|
||||
{hasHideResources && (
|
||||
<div className={styles.buttonWrapper}>
|
||||
<Button
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
import { fireEvent, render, screen } from "@testing-library/react"
|
||||
import { FC } from "react"
|
||||
import { WrapperComponent } from "../../testHelpers/renderHelpers"
|
||||
import { Language as AgentTooltipLanguage } from "../Tooltips/AgentHelpTooltip"
|
||||
import { Language as ResourceTooltipLanguage } from "../Tooltips/ResourcesHelpTooltip"
|
||||
import {
|
||||
TemplateResourcesProps,
|
||||
TemplateResourcesTable,
|
||||
} from "./TemplateResourcesTable"
|
||||
|
||||
const Component: FC<React.PropsWithChildren<TemplateResourcesProps>> = (
|
||||
props,
|
||||
) => (
|
||||
<WrapperComponent>
|
||||
<TemplateResourcesTable {...props} />
|
||||
</WrapperComponent>
|
||||
)
|
||||
|
||||
describe("TemplateResourcesTable", () => {
|
||||
it("displays resources tooltip", () => {
|
||||
const props: TemplateResourcesProps = {
|
||||
resources: [],
|
||||
}
|
||||
render(<Component {...props} />)
|
||||
const resourceTooltipButton = screen.getAllByRole("button")[0]
|
||||
fireEvent.click(resourceTooltipButton)
|
||||
const resourceTooltipTitle = screen.getByText(
|
||||
ResourceTooltipLanguage.resourceTooltipTitle,
|
||||
)
|
||||
expect(resourceTooltipTitle).toBeDefined()
|
||||
})
|
||||
it("displays agent tooltip", () => {
|
||||
const props: TemplateResourcesProps = {
|
||||
resources: [],
|
||||
}
|
||||
render(<Component {...props} />)
|
||||
const agentTooltipButton = screen.getAllByRole("button")[1]
|
||||
fireEvent.click(agentTooltipButton)
|
||||
const agentTooltipTitle = screen.getByText(
|
||||
AgentTooltipLanguage.agentTooltipTitle,
|
||||
)
|
||||
expect(agentTooltipTitle).toBeDefined()
|
||||
})
|
||||
})
|
||||
@@ -1,23 +1,7 @@
|
||||
import { makeStyles } 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 { AvatarData } from "components/AvatarData/AvatarData"
|
||||
import { ResourceAvatar } from "components/Resources/ResourceAvatar"
|
||||
import { AgentRowPreview } from "components/Resources/AgentRowPreview"
|
||||
import { Resources } from "components/Resources/Resources"
|
||||
import { FC } from "react"
|
||||
import { WorkspaceResource } from "../../api/typesGenerated"
|
||||
import { Stack } from "../Stack/Stack"
|
||||
import { TableHeaderRow } from "../TableHeaders/TableHeaders"
|
||||
import { AgentHelpTooltip } from "../Tooltips/AgentHelpTooltip"
|
||||
import { ResourcesHelpTooltip } from "../Tooltips/ResourcesHelpTooltip"
|
||||
|
||||
export const Language = {
|
||||
resourceLabel: "Resource",
|
||||
agentLabel: "Agent",
|
||||
}
|
||||
|
||||
export interface TemplateResourcesProps {
|
||||
resources: WorkspaceResource[]
|
||||
@@ -26,109 +10,10 @@ export interface TemplateResourcesProps {
|
||||
export const TemplateResourcesTable: FC<
|
||||
React.PropsWithChildren<TemplateResourcesProps>
|
||||
> = ({ resources }) => {
|
||||
const styles = useStyles()
|
||||
|
||||
return (
|
||||
<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>
|
||||
</TableHeaderRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{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 (
|
||||
<TableRow key={resource.id}>
|
||||
<TableCell className={styles.resourceNameCell}>
|
||||
<AvatarData
|
||||
title={resource.name}
|
||||
subtitle={resource.type}
|
||||
highlightTitle
|
||||
avatar={<ResourceAvatar resource={resource} />}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell colSpan={3}></TableCell>
|
||||
</TableRow>
|
||||
)
|
||||
}
|
||||
|
||||
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}
|
||||
>
|
||||
<AvatarData
|
||||
title={resource.name}
|
||||
subtitle={resource.type}
|
||||
highlightTitle
|
||||
avatar={<ResourceAvatar resource={resource} />}
|
||||
/>
|
||||
</TableCell>
|
||||
)}
|
||||
|
||||
<TableCell className={styles.agentColumn}>
|
||||
{agent.name}
|
||||
<span className={styles.operatingSystem}>
|
||||
{agent.operating_system}
|
||||
</span>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)
|
||||
})
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
<Resources
|
||||
resources={resources}
|
||||
agentRow={(agent) => <AgentRowPreview key={agent.id} agent={agent} />}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
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",
|
||||
},
|
||||
}))
|
||||
|
||||
@@ -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<React.PropsWithChildren<WorkspaceProps>> = ({
|
||||
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<React.PropsWithChildren<WorkspaceProps>> = ({
|
||||
/>
|
||||
)}
|
||||
|
||||
{typeof resources !== "undefined" && resources.length > 0 && (
|
||||
<Resources
|
||||
resources={resources}
|
||||
getResourcesError={
|
||||
workspaceErrors[WorkspaceErrors.GET_RESOURCES_ERROR]
|
||||
}
|
||||
workspace={workspace}
|
||||
canUpdateWorkspace={canUpdateWorkspace}
|
||||
buildInfo={buildInfo}
|
||||
hideSSHButton={hideSSHButton}
|
||||
applicationsHost={applicationsHost}
|
||||
{Boolean(workspaceErrors[WorkspaceErrors.GET_RESOURCES_ERROR]) && (
|
||||
<AlertBanner
|
||||
severity="error"
|
||||
error={workspaceErrors[WorkspaceErrors.GET_RESOURCES_ERROR]}
|
||||
/>
|
||||
)}
|
||||
|
||||
<WorkspaceSection
|
||||
contentsProps={{ className: styles.timelineContents }}
|
||||
>
|
||||
{workspaceErrors[WorkspaceErrors.GET_BUILDS_ERROR] ? (
|
||||
<AlertBanner
|
||||
severity="error"
|
||||
error={workspaceErrors[WorkspaceErrors.GET_BUILDS_ERROR]}
|
||||
/>
|
||||
) : (
|
||||
<BuildsTable builds={builds} className={styles.timelineTable} />
|
||||
)}
|
||||
</WorkspaceSection>
|
||||
{typeof resources !== "undefined" && resources.length > 0 && (
|
||||
<Resources
|
||||
resources={resources}
|
||||
agentRow={(agent) => (
|
||||
<AgentRow
|
||||
key={agent.id}
|
||||
agent={agent}
|
||||
workspace={workspace}
|
||||
applicationsHost={applicationsHost}
|
||||
showApps={canUpdateWorkspace}
|
||||
hideSSHButton={hideSSHButton}
|
||||
serverVersion={serverVersion}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{workspaceErrors[WorkspaceErrors.GET_BUILDS_ERROR] ? (
|
||||
<AlertBanner
|
||||
severity="error"
|
||||
error={workspaceErrors[WorkspaceErrors.GET_BUILDS_ERROR]}
|
||||
/>
|
||||
) : (
|
||||
<BuildsTable builds={builds} />
|
||||
)}
|
||||
</Stack>
|
||||
</Margins>
|
||||
)
|
||||
@@ -276,9 +283,5 @@ export const useStyles = makeStyles((theme) => {
|
||||
timelineContents: {
|
||||
margin: 0,
|
||||
},
|
||||
|
||||
timelineTable: {
|
||||
border: 0,
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user