mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
refactor(site): simplify workspaces page component structure (#8793)
This commit is contained in:
@@ -1,4 +1,3 @@
|
||||
import { makeStyles } from "@mui/styles"
|
||||
import TableCell from "@mui/material/TableCell"
|
||||
import TableRow from "@mui/material/TableRow"
|
||||
import { FC } from "react"
|
||||
@@ -10,19 +9,11 @@ import {
|
||||
export type TableEmptyProps = EmptyStateProps
|
||||
|
||||
export const TableEmpty: FC<TableEmptyProps> = (props) => {
|
||||
const styles = useStyles()
|
||||
|
||||
return (
|
||||
<TableRow>
|
||||
<TableCell colSpan={999} className={styles.tableCell}>
|
||||
<TableCell colSpan={999} sx={{ padding: "0 !important" }}>
|
||||
<EmptyState {...props} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)
|
||||
}
|
||||
|
||||
const useStyles = makeStyles(() => ({
|
||||
tableCell: {
|
||||
padding: "0 !important",
|
||||
},
|
||||
}))
|
||||
|
||||
@@ -1,132 +0,0 @@
|
||||
import TableCell from "@mui/material/TableCell"
|
||||
import { makeStyles } from "@mui/styles"
|
||||
import TableRow from "@mui/material/TableRow"
|
||||
import KeyboardArrowRight from "@mui/icons-material/KeyboardArrowRight"
|
||||
import { AvatarData } from "components/AvatarData/AvatarData"
|
||||
import { WorkspaceStatusBadge } from "components/WorkspaceStatusBadge/WorkspaceStatusBadge"
|
||||
import { FC } from "react"
|
||||
import { useNavigate } from "react-router-dom"
|
||||
import { getDisplayWorkspaceTemplateName } from "utils/workspace"
|
||||
import { LastUsed } from "../LastUsed/LastUsed"
|
||||
import { Workspace } from "api/typesGenerated"
|
||||
import { WorkspaceOutdatedTooltip } from "components/Tooltips/WorkspaceOutdatedTooltip"
|
||||
import { Avatar } from "components/Avatar/Avatar"
|
||||
import { Stack } from "components/Stack/Stack"
|
||||
import { useClickableTableRow } from "hooks/useClickableTableRow"
|
||||
import {
|
||||
HelpTooltip,
|
||||
HelpTooltipText,
|
||||
HelpTooltipTitle,
|
||||
} from "components/Tooltips/HelpTooltip"
|
||||
import InfoIcon from "@mui/icons-material/InfoOutlined"
|
||||
import { colors } from "theme/colors"
|
||||
import Box from "@mui/material/Box"
|
||||
|
||||
export const WorkspacesRow: FC<{
|
||||
workspace: Workspace
|
||||
onUpdateWorkspace: (workspace: Workspace) => void
|
||||
}> = ({ workspace, onUpdateWorkspace }) => {
|
||||
const styles = useStyles()
|
||||
const navigate = useNavigate()
|
||||
const workspacePageLink = `/@${workspace.owner_name}/${workspace.name}`
|
||||
const displayTemplateName = getDisplayWorkspaceTemplateName(workspace)
|
||||
const clickable = useClickableTableRow(() => {
|
||||
navigate(workspacePageLink)
|
||||
})
|
||||
|
||||
return (
|
||||
<TableRow data-testid={`workspace-${workspace.id}`} {...clickable}>
|
||||
<TableCell>
|
||||
<AvatarData
|
||||
title={
|
||||
<Stack direction="row" spacing={0} alignItems="center">
|
||||
{workspace.name}
|
||||
{workspace.outdated && (
|
||||
<WorkspaceOutdatedTooltip
|
||||
templateName={workspace.template_name}
|
||||
templateId={workspace.template_id}
|
||||
onUpdateVersion={() => {
|
||||
onUpdateWorkspace(workspace)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
}
|
||||
subtitle={workspace.owner_name}
|
||||
avatar={
|
||||
<Avatar
|
||||
src={workspace.template_icon}
|
||||
variant={workspace.template_icon ? "square" : undefined}
|
||||
fitImage={Boolean(workspace.template_icon)}
|
||||
>
|
||||
{workspace.name}
|
||||
</Avatar>
|
||||
}
|
||||
/>
|
||||
</TableCell>
|
||||
|
||||
<TableCell>{displayTemplateName}</TableCell>
|
||||
|
||||
<TableCell>
|
||||
<LastUsed lastUsedAt={workspace.last_used_at} />
|
||||
</TableCell>
|
||||
|
||||
<TableCell>
|
||||
<Box sx={{ display: "flex", alignItems: "center", gap: 1 }}>
|
||||
<WorkspaceStatusBadge workspace={workspace} />
|
||||
{workspace.latest_build.status === "running" &&
|
||||
!workspace.health.healthy && <UnhealthyTooltip />}
|
||||
</Box>
|
||||
</TableCell>
|
||||
|
||||
<TableCell>
|
||||
<div className={styles.arrowCell}>
|
||||
<KeyboardArrowRight className={styles.arrowRight} />
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)
|
||||
}
|
||||
|
||||
export const UnhealthyTooltip = () => {
|
||||
const styles = useStyles()
|
||||
|
||||
return (
|
||||
<HelpTooltip
|
||||
size="small"
|
||||
icon={InfoIcon}
|
||||
iconClassName={styles.unhealthyIcon}
|
||||
buttonClassName={styles.unhealthyButton}
|
||||
>
|
||||
<HelpTooltipTitle>Workspace is unhealthy</HelpTooltipTitle>
|
||||
<HelpTooltipText>
|
||||
Your workspace is running but some agents are unhealthy.
|
||||
</HelpTooltipText>
|
||||
</HelpTooltip>
|
||||
)
|
||||
}
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
arrowRight: {
|
||||
color: theme.palette.text.secondary,
|
||||
width: 20,
|
||||
height: 20,
|
||||
},
|
||||
|
||||
arrowCell: {
|
||||
display: "flex",
|
||||
paddingLeft: theme.spacing(2),
|
||||
},
|
||||
|
||||
unhealthyIcon: {
|
||||
color: colors.yellow[5],
|
||||
},
|
||||
|
||||
unhealthyButton: {
|
||||
opacity: 1,
|
||||
|
||||
"&:hover": {
|
||||
opacity: 1,
|
||||
},
|
||||
},
|
||||
}))
|
||||
@@ -1,54 +0,0 @@
|
||||
import Table from "@mui/material/Table"
|
||||
import TableBody from "@mui/material/TableBody"
|
||||
import TableCell from "@mui/material/TableCell"
|
||||
import TableContainer from "@mui/material/TableContainer"
|
||||
import TableHead from "@mui/material/TableHead"
|
||||
import TableRow from "@mui/material/TableRow"
|
||||
import { Workspace } from "api/typesGenerated"
|
||||
import { FC } from "react"
|
||||
import { WorkspacesTableBody } from "./WorkspacesTableBody"
|
||||
|
||||
const Language = {
|
||||
name: "Name",
|
||||
template: "Template",
|
||||
lastUsed: "Last Used",
|
||||
status: "Status",
|
||||
}
|
||||
|
||||
export interface WorkspacesTableProps {
|
||||
workspaces?: Workspace[]
|
||||
isUsingFilter: boolean
|
||||
onUpdateWorkspace: (workspace: Workspace) => void
|
||||
error?: unknown
|
||||
}
|
||||
|
||||
export const WorkspacesTable: FC<WorkspacesTableProps> = ({
|
||||
workspaces,
|
||||
isUsingFilter,
|
||||
onUpdateWorkspace,
|
||||
error,
|
||||
}) => {
|
||||
return (
|
||||
<TableContainer>
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell width="40%">{Language.name}</TableCell>
|
||||
<TableCell width="25%">{Language.template}</TableCell>
|
||||
<TableCell width="20%">{Language.lastUsed}</TableCell>
|
||||
<TableCell width="15%">{Language.status}</TableCell>
|
||||
<TableCell width="1%" />
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
<WorkspacesTableBody
|
||||
workspaces={workspaces}
|
||||
isUsingFilter={isUsingFilter}
|
||||
onUpdateWorkspace={onUpdateWorkspace}
|
||||
error={error}
|
||||
/>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
)
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
import Button from "@mui/material/Button"
|
||||
import { makeStyles } from "@mui/styles"
|
||||
import AddOutlined from "@mui/icons-material/AddOutlined"
|
||||
import { Workspace } from "api/typesGenerated"
|
||||
import { ChooseOne, Cond } from "components/Conditionals/ChooseOne"
|
||||
import { TableEmpty } from "components/TableEmpty/TableEmpty"
|
||||
import { FC } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { Link as RouterLink } from "react-router-dom"
|
||||
import { TableLoaderSkeleton } from "../TableLoader/TableLoader"
|
||||
import { WorkspacesRow } from "./WorkspacesRow"
|
||||
|
||||
interface TableBodyProps {
|
||||
workspaces?: Workspace[]
|
||||
isUsingFilter: boolean
|
||||
onUpdateWorkspace: (workspace: Workspace) => void
|
||||
error?: unknown
|
||||
}
|
||||
|
||||
export const WorkspacesTableBody: FC<
|
||||
React.PropsWithChildren<TableBodyProps>
|
||||
> = ({ workspaces, isUsingFilter, onUpdateWorkspace, error }) => {
|
||||
const { t } = useTranslation("workspacesPage")
|
||||
const styles = useStyles()
|
||||
|
||||
if (error) {
|
||||
return <TableEmpty message={t("emptyResultsMessage")} />
|
||||
}
|
||||
|
||||
if (!workspaces) {
|
||||
return <TableLoaderSkeleton columns={5} useAvatarData />
|
||||
}
|
||||
|
||||
if (workspaces.length === 0) {
|
||||
return (
|
||||
<ChooseOne>
|
||||
<Cond condition={isUsingFilter}>
|
||||
<TableEmpty message={t("emptyResultsMessage")} />
|
||||
</Cond>
|
||||
|
||||
<Cond>
|
||||
<TableEmpty
|
||||
className={styles.withImage}
|
||||
message={t("emptyCreateWorkspaceMessage")}
|
||||
description={t("emptyCreateWorkspaceDescription")}
|
||||
cta={
|
||||
<Button
|
||||
component={RouterLink}
|
||||
to="/templates"
|
||||
startIcon={<AddOutlined />}
|
||||
variant="contained"
|
||||
>
|
||||
{t("createFromTemplateButton")}
|
||||
</Button>
|
||||
}
|
||||
image={
|
||||
<div className={styles.emptyImage}>
|
||||
<img src="/featured/workspaces.webp" alt="" />
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</Cond>
|
||||
</ChooseOne>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{workspaces.map((workspace) => (
|
||||
<WorkspacesRow
|
||||
workspace={workspace}
|
||||
key={workspace.id}
|
||||
onUpdateWorkspace={onUpdateWorkspace}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
withImage: {
|
||||
paddingBottom: 0,
|
||||
},
|
||||
emptyImage: {
|
||||
maxWidth: "50%",
|
||||
height: theme.spacing(34),
|
||||
overflow: "hidden",
|
||||
marginTop: theme.spacing(6),
|
||||
opacity: 0.85,
|
||||
|
||||
"& img": {
|
||||
maxWidth: "100%",
|
||||
},
|
||||
},
|
||||
}))
|
||||
@@ -11,12 +11,52 @@ import { useFilter } from "components/Filter/filter"
|
||||
import { useUserFilterMenu } from "components/Filter/UserFilter"
|
||||
|
||||
const WorkspacesPage: FC = () => {
|
||||
const orgId = useOrganizationId()
|
||||
// If we use a useSearchParams for each hook, the values will not be in sync.
|
||||
// So we have to use a single one, centralizing the values, and pass it to
|
||||
// each hook.
|
||||
const searchParamsResult = useSearchParams()
|
||||
const pagination = usePagination({ searchParamsResult })
|
||||
const filterProps = useWorkspacesFilter({ searchParamsResult, pagination })
|
||||
const { data, error, queryKey } = useWorkspacesData({
|
||||
...pagination,
|
||||
query: filterProps.filter.query,
|
||||
})
|
||||
const updateWorkspace = useWorkspaceUpdate(queryKey)
|
||||
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>{pageTitle("Workspaces")}</title>
|
||||
</Helmet>
|
||||
|
||||
<WorkspacesPageView
|
||||
workspaces={data?.workspaces}
|
||||
error={error}
|
||||
count={data?.count}
|
||||
page={pagination.page}
|
||||
limit={pagination.limit}
|
||||
onPageChange={pagination.goToPage}
|
||||
filterProps={filterProps}
|
||||
onUpdateWorkspace={(workspace) => {
|
||||
updateWorkspace.mutate(workspace)
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default WorkspacesPage
|
||||
|
||||
type UseWorkspacesFilterOptions = {
|
||||
searchParamsResult: ReturnType<typeof useSearchParams>
|
||||
pagination: ReturnType<typeof usePagination>
|
||||
}
|
||||
|
||||
const useWorkspacesFilter = ({
|
||||
searchParamsResult,
|
||||
pagination,
|
||||
}: UseWorkspacesFilterOptions) => {
|
||||
const orgId = useOrganizationId()
|
||||
const filter = useFilter({
|
||||
initialValue: `owner:me`,
|
||||
searchParamsResult,
|
||||
@@ -24,11 +64,6 @@ const WorkspacesPage: FC = () => {
|
||||
pagination.goToPage(1)
|
||||
},
|
||||
})
|
||||
const { data, error, queryKey } = useWorkspacesData({
|
||||
...pagination,
|
||||
query: filter.query,
|
||||
})
|
||||
const updateWorkspace = useWorkspaceUpdate(queryKey)
|
||||
const permissions = usePermissions()
|
||||
const canFilterByUser = permissions.viewDeploymentValues
|
||||
const userMenu = useUserFilterMenu({
|
||||
@@ -49,33 +84,12 @@ const WorkspacesPage: FC = () => {
|
||||
filter.update({ ...filter.values, status: option?.value }),
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>{pageTitle("Workspaces")}</title>
|
||||
</Helmet>
|
||||
|
||||
<WorkspacesPageView
|
||||
workspaces={data?.workspaces}
|
||||
error={error}
|
||||
count={data?.count}
|
||||
page={pagination.page}
|
||||
limit={pagination.limit}
|
||||
onPageChange={pagination.goToPage}
|
||||
filterProps={{
|
||||
filter,
|
||||
menus: {
|
||||
user: canFilterByUser ? userMenu : undefined,
|
||||
template: templateMenu,
|
||||
status: statusMenu,
|
||||
},
|
||||
}}
|
||||
onUpdateWorkspace={(workspace) => {
|
||||
updateWorkspace.mutate(workspace)
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
return {
|
||||
filter,
|
||||
menus: {
|
||||
user: canFilterByUser ? userMenu : undefined,
|
||||
template: templateMenu,
|
||||
status: statusMenu,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export default WorkspacesPage
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
} from "components/PageHeader/PageHeader"
|
||||
import { Stack } from "components/Stack/Stack"
|
||||
import { WorkspaceHelpTooltip } from "components/Tooltips"
|
||||
import { WorkspacesTable } from "components/WorkspacesTable/WorkspacesTable"
|
||||
import { WorkspacesTable } from "pages/WorkspacesPage/WorkspacesTable"
|
||||
import { useLocalStorage } from "hooks"
|
||||
import difference from "lodash/difference"
|
||||
import { ImpendingDeletionBanner, Count } from "components/WorkspaceDeletion"
|
||||
@@ -130,7 +130,6 @@ export const WorkspacesPageView: FC<
|
||||
workspaces={workspaces}
|
||||
isUsingFilter={filterProps.filter.used}
|
||||
onUpdateWorkspace={onUpdateWorkspace}
|
||||
error={error}
|
||||
/>
|
||||
{count !== undefined && (
|
||||
<PaginationWidgetBase
|
||||
|
||||
@@ -0,0 +1,230 @@
|
||||
import Table from "@mui/material/Table"
|
||||
import TableBody from "@mui/material/TableBody"
|
||||
import TableCell from "@mui/material/TableCell"
|
||||
import TableContainer from "@mui/material/TableContainer"
|
||||
import TableHead from "@mui/material/TableHead"
|
||||
import TableRow from "@mui/material/TableRow"
|
||||
import { Workspace } from "api/typesGenerated"
|
||||
import { FC, ReactNode } from "react"
|
||||
import { TableEmpty } from "components/TableEmpty/TableEmpty"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { TableLoaderSkeleton } from "components/TableLoader/TableLoader"
|
||||
import AddOutlined from "@mui/icons-material/AddOutlined"
|
||||
import Button from "@mui/material/Button"
|
||||
import { ChooseOne, Cond } from "components/Conditionals/ChooseOne"
|
||||
import { Link as RouterLink, useNavigate } from "react-router-dom"
|
||||
import { makeStyles } from "@mui/styles"
|
||||
import {
|
||||
HelpTooltip,
|
||||
HelpTooltipText,
|
||||
HelpTooltipTitle,
|
||||
} from "components/Tooltips/HelpTooltip"
|
||||
import InfoIcon from "@mui/icons-material/InfoOutlined"
|
||||
import { colors } from "theme/colors"
|
||||
import { useClickableTableRow } from "hooks/useClickableTableRow"
|
||||
import KeyboardArrowRight from "@mui/icons-material/KeyboardArrowRight"
|
||||
import Box from "@mui/material/Box"
|
||||
import { AvatarData } from "components/AvatarData/AvatarData"
|
||||
import { Avatar } from "components/Avatar/Avatar"
|
||||
import { Stack } from "components/Stack/Stack"
|
||||
import { LastUsed } from "components/LastUsed/LastUsed"
|
||||
import { WorkspaceOutdatedTooltip } from "components/Tooltips"
|
||||
import { WorkspaceStatusBadge } from "components/WorkspaceStatusBadge/WorkspaceStatusBadge"
|
||||
import { getDisplayWorkspaceTemplateName } from "utils/workspace"
|
||||
|
||||
export interface WorkspacesTableProps {
|
||||
workspaces?: Workspace[]
|
||||
isUsingFilter: boolean
|
||||
onUpdateWorkspace: (workspace: Workspace) => void
|
||||
error?: unknown
|
||||
}
|
||||
|
||||
export const WorkspacesTable: FC<WorkspacesTableProps> = ({
|
||||
workspaces,
|
||||
isUsingFilter,
|
||||
onUpdateWorkspace,
|
||||
}) => {
|
||||
const { t } = useTranslation("workspacesPage")
|
||||
const styles = useStyles()
|
||||
|
||||
return (
|
||||
<TableContainer>
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell width="40%">Name</TableCell>
|
||||
<TableCell width="25%">Template</TableCell>
|
||||
<TableCell width="20%">Last used</TableCell>
|
||||
<TableCell width="15%">Status</TableCell>
|
||||
<TableCell width="1%" />
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{!workspaces && <TableLoaderSkeleton columns={5} useAvatarData />}
|
||||
{workspaces && workspaces.length === 0 && (
|
||||
<ChooseOne>
|
||||
<Cond condition={isUsingFilter}>
|
||||
<TableEmpty message={t("emptyResultsMessage")} />
|
||||
</Cond>
|
||||
|
||||
<Cond>
|
||||
<TableEmpty
|
||||
className={styles.withImage}
|
||||
message={t("emptyCreateWorkspaceMessage")}
|
||||
description={t("emptyCreateWorkspaceDescription")}
|
||||
cta={
|
||||
<Button
|
||||
component={RouterLink}
|
||||
to="/templates"
|
||||
startIcon={<AddOutlined />}
|
||||
variant="contained"
|
||||
>
|
||||
{t("createFromTemplateButton")}
|
||||
</Button>
|
||||
}
|
||||
image={
|
||||
<div className={styles.emptyImage}>
|
||||
<img src="/featured/workspaces.webp" alt="" />
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</Cond>
|
||||
</ChooseOne>
|
||||
)}
|
||||
{workspaces &&
|
||||
workspaces.map((workspace) => (
|
||||
<WorkspacesRow workspace={workspace} key={workspace.id}>
|
||||
<TableCell>
|
||||
<AvatarData
|
||||
title={
|
||||
<Stack direction="row" spacing={0} alignItems="center">
|
||||
{workspace.name}
|
||||
{workspace.outdated && (
|
||||
<WorkspaceOutdatedTooltip
|
||||
templateName={workspace.template_name}
|
||||
templateId={workspace.template_id}
|
||||
onUpdateVersion={() => {
|
||||
onUpdateWorkspace(workspace)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
}
|
||||
subtitle={workspace.owner_name}
|
||||
avatar={
|
||||
<Avatar
|
||||
src={workspace.template_icon}
|
||||
variant={workspace.template_icon ? "square" : undefined}
|
||||
fitImage={Boolean(workspace.template_icon)}
|
||||
>
|
||||
{workspace.name}
|
||||
</Avatar>
|
||||
}
|
||||
/>
|
||||
</TableCell>
|
||||
|
||||
<TableCell>
|
||||
{getDisplayWorkspaceTemplateName(workspace)}
|
||||
</TableCell>
|
||||
|
||||
<TableCell>
|
||||
<LastUsed lastUsedAt={workspace.last_used_at} />
|
||||
</TableCell>
|
||||
|
||||
<TableCell>
|
||||
<Box sx={{ display: "flex", alignItems: "center", gap: 1 }}>
|
||||
<WorkspaceStatusBadge workspace={workspace} />
|
||||
{workspace.latest_build.status === "running" &&
|
||||
!workspace.health.healthy && <UnhealthyTooltip />}
|
||||
</Box>
|
||||
</TableCell>
|
||||
|
||||
<TableCell>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
paddingLeft: (theme) => theme.spacing(2),
|
||||
}}
|
||||
>
|
||||
<KeyboardArrowRight
|
||||
sx={{
|
||||
color: (theme) => theme.palette.text.secondary,
|
||||
width: 20,
|
||||
height: 20,
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</TableCell>
|
||||
</WorkspacesRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
)
|
||||
}
|
||||
|
||||
const WorkspacesRow: FC<{
|
||||
workspace: Workspace
|
||||
children: ReactNode
|
||||
}> = ({ workspace, children }) => {
|
||||
const navigate = useNavigate()
|
||||
const workspacePageLink = `/@${workspace.owner_name}/${workspace.name}`
|
||||
const clickable = useClickableTableRow(() => {
|
||||
navigate(workspacePageLink)
|
||||
})
|
||||
|
||||
return (
|
||||
<TableRow data-testid={`workspace-${workspace.id}`} {...clickable}>
|
||||
{children}
|
||||
</TableRow>
|
||||
)
|
||||
}
|
||||
|
||||
export const UnhealthyTooltip = () => {
|
||||
const styles = useUnhealthyTooltipStyles()
|
||||
|
||||
return (
|
||||
<HelpTooltip
|
||||
size="small"
|
||||
icon={InfoIcon}
|
||||
iconClassName={styles.unhealthyIcon}
|
||||
buttonClassName={styles.unhealthyButton}
|
||||
>
|
||||
<HelpTooltipTitle>Workspace is unhealthy</HelpTooltipTitle>
|
||||
<HelpTooltipText>
|
||||
Your workspace is running but some agents are unhealthy.
|
||||
</HelpTooltipText>
|
||||
</HelpTooltip>
|
||||
)
|
||||
}
|
||||
|
||||
const useUnhealthyTooltipStyles = makeStyles(() => ({
|
||||
unhealthyIcon: {
|
||||
color: colors.yellow[5],
|
||||
},
|
||||
|
||||
unhealthyButton: {
|
||||
opacity: 1,
|
||||
|
||||
"&:hover": {
|
||||
opacity: 1,
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
withImage: {
|
||||
paddingBottom: 0,
|
||||
},
|
||||
emptyImage: {
|
||||
maxWidth: "50%",
|
||||
height: theme.spacing(34),
|
||||
overflow: "hidden",
|
||||
marginTop: theme.spacing(6),
|
||||
opacity: 0.85,
|
||||
|
||||
"& img": {
|
||||
maxWidth: "100%",
|
||||
},
|
||||
},
|
||||
}))
|
||||
Reference in New Issue
Block a user