mirror of https://github.com/coder/coder.git
237 lines
7.7 KiB
TypeScript
237 lines
7.7 KiB
TypeScript
import CloudQueue from "@mui/icons-material/CloudQueue";
|
|
import DeleteOutlined from "@mui/icons-material/DeleteOutlined";
|
|
import KeyboardArrowDownOutlined from "@mui/icons-material/KeyboardArrowDownOutlined";
|
|
import PlayArrowOutlined from "@mui/icons-material/PlayArrowOutlined";
|
|
import StopOutlined from "@mui/icons-material/StopOutlined";
|
|
import LoadingButton from "@mui/lab/LoadingButton";
|
|
import Button from "@mui/material/Button";
|
|
import Divider from "@mui/material/Divider";
|
|
import type { ComponentProps } from "react";
|
|
import type { UseQueryResult } from "react-query";
|
|
import { hasError, isApiValidationError } from "api/errors";
|
|
import type { Template, Workspace } from "api/typesGenerated";
|
|
import { ErrorAlert } from "components/Alert/ErrorAlert";
|
|
import { EmptyState } from "components/EmptyState/EmptyState";
|
|
import { Margins } from "components/Margins/Margins";
|
|
import {
|
|
MoreMenu,
|
|
MoreMenuContent,
|
|
MoreMenuItem,
|
|
MoreMenuTrigger,
|
|
} from "components/MoreMenu/MoreMenu";
|
|
import { PageHeader, PageHeaderTitle } from "components/PageHeader/PageHeader";
|
|
import { PaginationHeader } from "components/PaginationWidget/PaginationHeader";
|
|
import { PaginationWidgetBase } from "components/PaginationWidget/PaginationWidgetBase";
|
|
import { Stack } from "components/Stack/Stack";
|
|
import { TableToolbar } from "components/TableToolbar/TableToolbar";
|
|
import { WorkspacesTable } from "pages/WorkspacesPage/WorkspacesTable";
|
|
import { mustUpdateWorkspace } from "utils/workspace";
|
|
import { WorkspacesFilter } from "./filter/filter";
|
|
import { WorkspaceHelpTooltip } from "./WorkspaceHelpTooltip";
|
|
import { WorkspacesButton } from "./WorkspacesButton";
|
|
|
|
export const Language = {
|
|
pageTitle: "Workspaces",
|
|
yourWorkspacesButton: "Your workspaces",
|
|
allWorkspacesButton: "All workspaces",
|
|
runningWorkspacesButton: "Running workspaces",
|
|
createWorkspace: <>Create Workspace…</>,
|
|
seeAllTemplates: "See all templates",
|
|
template: "Template",
|
|
};
|
|
|
|
type TemplateQuery = UseQueryResult<Template[]>;
|
|
|
|
export interface WorkspacesPageViewProps {
|
|
error: unknown;
|
|
workspaces?: readonly Workspace[];
|
|
checkedWorkspaces: readonly Workspace[];
|
|
count?: number;
|
|
filterProps: ComponentProps<typeof WorkspacesFilter>;
|
|
page: number;
|
|
limit: number;
|
|
onPageChange: (page: number) => void;
|
|
onUpdateWorkspace: (workspace: Workspace) => void;
|
|
onCheckChange: (checkedWorkspaces: readonly Workspace[]) => void;
|
|
isRunningBatchAction: boolean;
|
|
onDeleteAll: () => void;
|
|
onUpdateAll: () => void;
|
|
onStartAll: () => void;
|
|
onStopAll: () => void;
|
|
canCheckWorkspaces: boolean;
|
|
templatesFetchStatus: TemplateQuery["status"];
|
|
templates: TemplateQuery["data"];
|
|
canCreateTemplate: boolean;
|
|
canChangeVersions: boolean;
|
|
}
|
|
|
|
export const WorkspacesPageView = ({
|
|
workspaces,
|
|
error,
|
|
limit,
|
|
count,
|
|
filterProps,
|
|
onPageChange,
|
|
onUpdateWorkspace,
|
|
page,
|
|
checkedWorkspaces,
|
|
onCheckChange,
|
|
onDeleteAll,
|
|
onUpdateAll,
|
|
onStopAll,
|
|
onStartAll,
|
|
isRunningBatchAction,
|
|
canCheckWorkspaces,
|
|
templates,
|
|
templatesFetchStatus,
|
|
canCreateTemplate,
|
|
canChangeVersions,
|
|
}: WorkspacesPageViewProps) => {
|
|
// Let's say the user has 5 workspaces, but tried to hit page 100, which does
|
|
// not exist. In this case, the page is not valid and we want to show a better
|
|
// error message.
|
|
const invalidPageNumber = page !== 1 && workspaces?.length === 0;
|
|
|
|
return (
|
|
<Margins>
|
|
<PageHeader
|
|
actions={
|
|
<WorkspacesButton
|
|
templates={templates}
|
|
templatesFetchStatus={templatesFetchStatus}
|
|
>
|
|
{Language.createWorkspace}
|
|
</WorkspacesButton>
|
|
}
|
|
>
|
|
<PageHeaderTitle>
|
|
<Stack direction="row" spacing={1} alignItems="center">
|
|
<span>{Language.pageTitle}</span>
|
|
<WorkspaceHelpTooltip />
|
|
</Stack>
|
|
</PageHeaderTitle>
|
|
</PageHeader>
|
|
|
|
<Stack>
|
|
{hasError(error) && !isApiValidationError(error) && (
|
|
<ErrorAlert error={error} />
|
|
)}
|
|
<WorkspacesFilter error={error} {...filterProps} />
|
|
</Stack>
|
|
|
|
<TableToolbar>
|
|
{checkedWorkspaces.length > 0 ? (
|
|
<>
|
|
<div>
|
|
Selected <strong>{checkedWorkspaces.length}</strong> of{" "}
|
|
<strong>{workspaces?.length}</strong>{" "}
|
|
{workspaces?.length === 1 ? "workspace" : "workspaces"}
|
|
</div>
|
|
|
|
<MoreMenu>
|
|
<MoreMenuTrigger>
|
|
<LoadingButton
|
|
loading={isRunningBatchAction}
|
|
loadingPosition="end"
|
|
variant="text"
|
|
size="small"
|
|
css={{ borderRadius: 9999, marginLeft: "auto" }}
|
|
endIcon={<KeyboardArrowDownOutlined />}
|
|
>
|
|
Actions
|
|
</LoadingButton>
|
|
</MoreMenuTrigger>
|
|
<MoreMenuContent>
|
|
<MoreMenuItem
|
|
onClick={onStartAll}
|
|
disabled={
|
|
!checkedWorkspaces?.every(
|
|
(w) =>
|
|
w.latest_build.status === "stopped" &&
|
|
!mustUpdateWorkspace(w, canChangeVersions),
|
|
)
|
|
}
|
|
>
|
|
<PlayArrowOutlined /> Start
|
|
</MoreMenuItem>
|
|
<MoreMenuItem
|
|
onClick={onStopAll}
|
|
disabled={
|
|
!checkedWorkspaces?.every(
|
|
(w) => w.latest_build.status === "running",
|
|
)
|
|
}
|
|
>
|
|
<StopOutlined /> Stop
|
|
</MoreMenuItem>
|
|
<Divider />
|
|
<MoreMenuItem onClick={onUpdateAll}>
|
|
<CloudQueue /> Update…
|
|
</MoreMenuItem>
|
|
<MoreMenuItem danger onClick={onDeleteAll}>
|
|
<DeleteOutlined /> Delete…
|
|
</MoreMenuItem>
|
|
</MoreMenuContent>
|
|
</MoreMenu>
|
|
</>
|
|
) : (
|
|
!invalidPageNumber && (
|
|
<PaginationHeader
|
|
paginationUnitLabel="workspaces"
|
|
limit={limit}
|
|
totalRecords={count}
|
|
currentOffsetStart={(page - 1) * limit + 1}
|
|
css={{ paddingBottom: "0" }}
|
|
/>
|
|
)
|
|
)}
|
|
</TableToolbar>
|
|
|
|
{invalidPageNumber ? (
|
|
<EmptyState
|
|
css={(theme) => ({
|
|
border: `1px solid ${theme.palette.divider}`,
|
|
borderRadius: theme.shape.borderRadius,
|
|
})}
|
|
message="Page not found"
|
|
description="The page you are trying to access does not exist."
|
|
cta={
|
|
<Button
|
|
onClick={() => {
|
|
onPageChange(1);
|
|
}}
|
|
>
|
|
Back to the first page
|
|
</Button>
|
|
}
|
|
/>
|
|
) : (
|
|
<WorkspacesTable
|
|
canCreateTemplate={canCreateTemplate}
|
|
workspaces={workspaces}
|
|
isUsingFilter={filterProps.filter.used}
|
|
onUpdateWorkspace={onUpdateWorkspace}
|
|
checkedWorkspaces={checkedWorkspaces}
|
|
onCheckChange={onCheckChange}
|
|
canCheckWorkspaces={canCheckWorkspaces}
|
|
templates={templates}
|
|
/>
|
|
)}
|
|
|
|
{count !== undefined && (
|
|
// Temporary styling stopgap before component is migrated to using
|
|
// PaginationContainer (which renders PaginationWidgetBase using CSS
|
|
// flexbox gaps)
|
|
<div css={{ paddingTop: "16px" }}>
|
|
<PaginationWidgetBase
|
|
totalRecords={count}
|
|
pageSize={limit}
|
|
onPageChange={onPageChange}
|
|
currentPage={page}
|
|
/>
|
|
</div>
|
|
)}
|
|
</Margins>
|
|
);
|
|
};
|