diff --git a/site/src/components/TableEmpty/TableEmpty.tsx b/site/src/components/TableEmpty/TableEmpty.tsx index 744e27a6fc..ea6fc866de 100644 --- a/site/src/components/TableEmpty/TableEmpty.tsx +++ b/site/src/components/TableEmpty/TableEmpty.tsx @@ -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 = (props) => { - const styles = useStyles() - return ( - + ) } - -const useStyles = makeStyles(() => ({ - tableCell: { - padding: "0 !important", - }, -})) diff --git a/site/src/components/WorkspacesTable/WorkspacesRow.tsx b/site/src/components/WorkspacesTable/WorkspacesRow.tsx deleted file mode 100644 index cbc0f0f9ff..0000000000 --- a/site/src/components/WorkspacesTable/WorkspacesRow.tsx +++ /dev/null @@ -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 ( - - - - {workspace.name} - {workspace.outdated && ( - { - onUpdateWorkspace(workspace) - }} - /> - )} - - } - subtitle={workspace.owner_name} - avatar={ - - {workspace.name} - - } - /> - - - {displayTemplateName} - - - - - - - - - {workspace.latest_build.status === "running" && - !workspace.health.healthy && } - - - - -
- -
-
-
- ) -} - -export const UnhealthyTooltip = () => { - const styles = useStyles() - - return ( - - Workspace is unhealthy - - Your workspace is running but some agents are unhealthy. - - - ) -} - -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, - }, - }, -})) diff --git a/site/src/components/WorkspacesTable/WorkspacesTable.tsx b/site/src/components/WorkspacesTable/WorkspacesTable.tsx deleted file mode 100644 index ece33930fe..0000000000 --- a/site/src/components/WorkspacesTable/WorkspacesTable.tsx +++ /dev/null @@ -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 = ({ - workspaces, - isUsingFilter, - onUpdateWorkspace, - error, -}) => { - return ( - - - - - {Language.name} - {Language.template} - {Language.lastUsed} - {Language.status} - - - - - - -
-
- ) -} diff --git a/site/src/components/WorkspacesTable/WorkspacesTableBody.tsx b/site/src/components/WorkspacesTable/WorkspacesTableBody.tsx deleted file mode 100644 index c610570ae0..0000000000 --- a/site/src/components/WorkspacesTable/WorkspacesTableBody.tsx +++ /dev/null @@ -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 -> = ({ workspaces, isUsingFilter, onUpdateWorkspace, error }) => { - const { t } = useTranslation("workspacesPage") - const styles = useStyles() - - if (error) { - return - } - - if (!workspaces) { - return - } - - if (workspaces.length === 0) { - return ( - - - - - - - } - variant="contained" - > - {t("createFromTemplateButton")} - - } - image={ -
- -
- } - /> -
-
- ) - } - - return ( - <> - {workspaces.map((workspace) => ( - - ))} - - ) -} - -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%", - }, - }, -})) diff --git a/site/src/pages/WorkspacesPage/WorkspacesPage.tsx b/site/src/pages/WorkspacesPage/WorkspacesPage.tsx index 440fa1c8fe..112a89ae5e 100644 --- a/site/src/pages/WorkspacesPage/WorkspacesPage.tsx +++ b/site/src/pages/WorkspacesPage/WorkspacesPage.tsx @@ -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 ( + <> + + {pageTitle("Workspaces")} + + + { + updateWorkspace.mutate(workspace) + }} + /> + + ) +} + +export default WorkspacesPage + +type UseWorkspacesFilterOptions = { + searchParamsResult: ReturnType + pagination: ReturnType +} + +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 ( - <> - - {pageTitle("Workspaces")} - - - { - updateWorkspace.mutate(workspace) - }} - /> - - ) + return { + filter, + menus: { + user: canFilterByUser ? userMenu : undefined, + template: templateMenu, + status: statusMenu, + }, + } } - -export default WorkspacesPage diff --git a/site/src/pages/WorkspacesPage/WorkspacesPageView.tsx b/site/src/pages/WorkspacesPage/WorkspacesPageView.tsx index bafe93770d..05350277d0 100644 --- a/site/src/pages/WorkspacesPage/WorkspacesPageView.tsx +++ b/site/src/pages/WorkspacesPage/WorkspacesPageView.tsx @@ -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 && ( void + error?: unknown +} + +export const WorkspacesTable: FC = ({ + workspaces, + isUsingFilter, + onUpdateWorkspace, +}) => { + const { t } = useTranslation("workspacesPage") + const styles = useStyles() + + return ( + + + + + Name + Template + Last used + Status + + + + + {!workspaces && } + {workspaces && workspaces.length === 0 && ( + + + + + + + } + variant="contained" + > + {t("createFromTemplateButton")} + + } + image={ +
+ +
+ } + /> +
+
+ )} + {workspaces && + workspaces.map((workspace) => ( + + + + {workspace.name} + {workspace.outdated && ( + { + onUpdateWorkspace(workspace) + }} + /> + )} + + } + subtitle={workspace.owner_name} + avatar={ + + {workspace.name} + + } + /> + + + + {getDisplayWorkspaceTemplateName(workspace)} + + + + + + + + + + {workspace.latest_build.status === "running" && + !workspace.health.healthy && } + + + + + theme.spacing(2), + }} + > + theme.palette.text.secondary, + width: 20, + height: 20, + }} + /> + + + + ))} +
+
+
+ ) +} + +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 ( + + {children} + + ) +} + +export const UnhealthyTooltip = () => { + const styles = useUnhealthyTooltipStyles() + + return ( + + Workspace is unhealthy + + Your workspace is running but some agents are unhealthy. + + + ) +} + +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%", + }, + }, +}))