diff --git a/site/src/components/HelpTooltip/HelpTooltip.stories.tsx b/site/src/components/HelpTooltip/HelpTooltip.stories.tsx new file mode 100644 index 0000000000000..6780a5700a6d0 --- /dev/null +++ b/site/src/components/HelpTooltip/HelpTooltip.stories.tsx @@ -0,0 +1,35 @@ +import { ComponentMeta, Story } from "@storybook/react" +import { + HelpTooltip, + HelpTooltipLink, + HelpTooltipLinksGroup, + HelpTooltipProps, + HelpTooltipText, + HelpTooltipTitle, +} from "./HelpTooltip" + +export default { + title: "components/HelpTooltip", + component: HelpTooltip, +} as ComponentMeta + +const Template: Story = (args) => ( + + What is template? + + With templates you can create a common configuration for your workspaces using Terraform. So, you and your team + can use the same environment to deliver great software. + + + Creating a template + Updating a template + + +) + +export const Close = Template.bind({}) + +export const Open = Template.bind({}) +Open.args = { + open: true, +} diff --git a/site/src/components/HelpTooltip/HelpTooltip.tsx b/site/src/components/HelpTooltip/HelpTooltip.tsx new file mode 100644 index 0000000000000..8d14452ec2421 --- /dev/null +++ b/site/src/components/HelpTooltip/HelpTooltip.tsx @@ -0,0 +1,159 @@ +import Link from "@material-ui/core/Link" +import Popover from "@material-ui/core/Popover" +import { makeStyles } from "@material-ui/core/styles" +import HelpIcon from "@material-ui/icons/HelpOutline" +import OpenInNewIcon from "@material-ui/icons/OpenInNew" +import { useState } from "react" +import { Stack } from "../Stack/Stack" + +type Size = "small" | "medium" +export interface HelpTooltipProps { + // Useful to test on storybook + open?: boolean + size?: Size +} + +export const HelpTooltip: React.FC = ({ children, open, size = "medium" }) => { + const styles = useStyles({ size }) + const [anchorEl, setAnchorEl] = useState(null) + open = open ?? Boolean(anchorEl) + const id = open ? "help-popover" : undefined + + return ( + <> + + { + setAnchorEl(null) + }} + anchorOrigin={{ + vertical: "bottom", + horizontal: "left", + }} + transformOrigin={{ + vertical: "top", + horizontal: "left", + }} + > + {children} + + + ) +} + +export const HelpTooltipTitle: React.FC = ({ children }) => { + const styles = useStyles() + + return

{children}

+} + +export const HelpTooltipText: React.FC = ({ children }) => { + const styles = useStyles() + + return

{children}

+} + +export const HelpTooltipLink: React.FC<{ href: string }> = ({ children, href }) => { + const styles = useStyles() + + return ( + + + {children} + + ) +} + +export const HelpTooltipLinksGroup: React.FC = ({ children }) => { + const styles = useStyles() + + return ( + + {children} + + ) +} + +const getButtonSpacingFromSize = (size?: Size): number => { + switch (size) { + case "small": + return 2.75 + case "medium": + default: + return 3 + } +} + +const getIconSpacingFromSize = (size?: Size): number => { + switch (size) { + case "small": + return 1.75 + case "medium": + default: + return 2 + } +} + +const useStyles = makeStyles((theme) => ({ + button: { + display: "flex", + alignItems: "center", + justifyContent: "center", + width: ({ size }: { size?: Size }) => theme.spacing(getButtonSpacingFromSize(size)), + height: ({ size }: { size?: Size }) => theme.spacing(getButtonSpacingFromSize(size)), + padding: 0, + border: 0, + background: "transparent", + color: theme.palette.text.secondary, + cursor: "pointer", + + "&:hover": { + color: theme.palette.text.primary, + }, + }, + + icon: { + width: ({ size }: { size?: Size }) => theme.spacing(getIconSpacingFromSize(size)), + height: ({ size }: { size?: Size }) => theme.spacing(getIconSpacingFromSize(size)), + }, + + popoverPaper: { + marginTop: theme.spacing(0.5), + width: theme.spacing(38), + padding: theme.spacing(2.5), + color: theme.palette.text.secondary, + }, + + title: { + marginTop: 0, + marginBottom: theme.spacing(1), + color: theme.palette.text.primary, + }, + + text: { + marginTop: theme.spacing(0.5), + marginBottom: theme.spacing(0.5), + }, + + link: { + display: "flex", + alignItems: "center", + }, + + linkIcon: { + color: "inherit", + width: 14, + height: 14, + marginRight: theme.spacing(1), + }, + + linksGroup: { + marginTop: theme.spacing(2), + }, +})) diff --git a/site/src/components/PageHeader/PageHeader.stories.tsx b/site/src/components/PageHeader/PageHeader.stories.tsx new file mode 100644 index 0000000000000..be1a594fd4381 --- /dev/null +++ b/site/src/components/PageHeader/PageHeader.stories.tsx @@ -0,0 +1,15 @@ +import { ComponentMeta, Story } from "@storybook/react" +import { PageHeader, PageHeaderTitle } from "./PageHeader" + +export default { + title: "components/PageHeader", + component: PageHeader, +} as ComponentMeta + +const Template: Story = () => ( + + Templates + +) + +export const Example = Template.bind({}) diff --git a/site/src/components/PageHeader/PageHeader.tsx b/site/src/components/PageHeader/PageHeader.tsx new file mode 100644 index 0000000000000..9a6416972dd53 --- /dev/null +++ b/site/src/components/PageHeader/PageHeader.tsx @@ -0,0 +1,62 @@ +import { makeStyles } from "@material-ui/core/styles" +import { Stack } from "../Stack/Stack" + +export interface PageHeaderProps { + actions?: JSX.Element +} + +export const PageHeader: React.FC = ({ children, actions }) => { + const styles = useStyles() + + return ( +
+
{children}
+ + {actions} + +
+ ) +} + +export const PageHeaderTitle: React.FC = ({ children }) => { + const styles = useStyles() + + return

{children}

+} + +export const PageHeaderSubtitle: React.FC = ({ children }) => { + const styles = useStyles() + + return

{children}

+} + +const useStyles = makeStyles((theme) => ({ + root: { + display: "flex", + alignItems: "center", + paddingTop: theme.spacing(6), + paddingBottom: theme.spacing(5), + }, + + title: { + fontSize: theme.spacing(4), + fontWeight: 400, + margin: 0, + display: "flex", + alignItems: "center", + lineHeight: "140%", + }, + + subtitle: { + fontSize: theme.spacing(2.5), + color: theme.palette.text.secondary, + fontWeight: 400, + display: "block", + margin: 0, + marginTop: theme.spacing(1), + }, + + actions: { + marginLeft: "auto", + }, +})) diff --git a/site/src/components/Resources/Resources.tsx b/site/src/components/Resources/Resources.tsx index 73ae16713d1f5..8929457e1ef19 100644 --- a/site/src/components/Resources/Resources.tsx +++ b/site/src/components/Resources/Resources.tsx @@ -9,6 +9,13 @@ import { FC } from "react" import { Workspace, WorkspaceResource } from "../../api/typesGenerated" import { getDisplayAgentStatus } from "../../util/workspace" import { AppLink } from "../AppLink/AppLink" +import { + HelpTooltip, + HelpTooltipLink, + HelpTooltipLinksGroup, + HelpTooltipText, + HelpTooltipTitle, +} from "../HelpTooltip/HelpTooltip" import { Stack } from "../Stack/Stack" import { TableHeaderRow } from "../TableHeaders/TableHeaders" import { TerminalLink } from "../TerminalLink/TerminalLink" @@ -21,6 +28,35 @@ const Language = { agentLabel: "Agent", statusLabel: "Status", accessLabel: "Access", + resourceTooltipTitle: "What is a resource?", + resourceTooltipText: "A resource is an infrastructure object that is create when the workspace is provisioned.", + resourceTooltipLink: "Persistent and ephemeral resources", + agentTooltipTitle: "What is an agent?", + agentTooltipText: + "The Coder agent runs inside your resource and gives you direct access to the shell via the UI or CLI.", +} + +const ResourcesHelpTooltip: React.FC = () => { + return ( + + {Language.resourceTooltipTitle} + {Language.resourceTooltipText} + + + {Language.resourceTooltipLink} + + + + ) +} + +const AgentHelpTooltip: React.FC = () => { + return ( + + {Language.agentTooltipTitle} + {Language.agentTooltipTitle} + + ) } interface ResourcesProps { @@ -41,8 +77,18 @@ export const Resources: FC = ({ resources, getResourcesError, wo - {Language.resourceLabel} - {Language.agentLabel} + + + {Language.resourceLabel} + + + + + + {Language.agentLabel} + + + {Language.accessLabel} {Language.statusLabel} diff --git a/site/src/components/Stack/Stack.tsx b/site/src/components/Stack/Stack.tsx index dc91c716f863c..e0089c06f0382 100644 --- a/site/src/components/Stack/Stack.tsx +++ b/site/src/components/Stack/Stack.tsx @@ -1,4 +1,5 @@ import { makeStyles } from "@material-ui/core/styles" +import { CSSProperties } from "@material-ui/core/styles/withStyles" import { FC } from "react" import { combineClasses } from "../../util/combineClasses" @@ -7,6 +8,7 @@ type Direction = "column" | "row" interface StyleProps { direction: Direction spacing: number + alignItems?: CSSProperties["alignItems"] } const useStyles = makeStyles((theme) => ({ @@ -14,6 +16,7 @@ const useStyles = makeStyles((theme) => ({ display: "flex", flexDirection: ({ direction }: StyleProps) => direction, gap: ({ spacing }: StyleProps) => theme.spacing(spacing), + alignItems: ({ alignItems }: StyleProps) => alignItems, }, })) @@ -21,10 +24,11 @@ export interface StackProps { className?: string direction?: Direction spacing?: number + alignItems?: CSSProperties["alignItems"] } -export const Stack: FC = ({ children, className, direction = "column", spacing = 2 }) => { - const styles = useStyles({ spacing, direction }) +export const Stack: FC = ({ children, className, direction = "column", spacing = 2, alignItems }) => { + const styles = useStyles({ spacing, direction, alignItems }) return
{children}
} diff --git a/site/src/components/Workspace/Workspace.tsx b/site/src/components/Workspace/Workspace.tsx index 3903dcdd2a1c6..0646421e5be78 100644 --- a/site/src/components/Workspace/Workspace.tsx +++ b/site/src/components/Workspace/Workspace.tsx @@ -1,9 +1,10 @@ import { makeStyles } from "@material-ui/core/styles" -import Typography from "@material-ui/core/Typography" import { FC } from "react" import * as TypesGen from "../../api/typesGenerated" import { MONOSPACE_FONT_FAMILY } from "../../theme/constants" import { BuildsTable } from "../BuildsTable/BuildsTable" +import { Margins } from "../Margins/Margins" +import { PageHeader, PageHeaderSubtitle, PageHeaderTitle } from "../PageHeader/PageHeader" import { Resources } from "../Resources/Resources" import { Stack } from "../Stack/Stack" import { WorkspaceActions } from "../WorkspaceActions/WorkspaceActions" @@ -46,33 +47,22 @@ export const Workspace: FC = ({ const styles = useStyles() return ( -
- - -
-
- - {workspace.name} - - - - {workspace.owner_name} - -
- - -
-
- - -
+ + + } + > + {workspace.name} + {workspace.owner_name} + @@ -95,16 +85,12 @@ export const Workspace: FC = ({ -
+ ) } export const useStyles = makeStyles((theme) => { return { - root: { - display: "flex", - flexDirection: "column", - }, firstColumnSpacer: { flex: 2, }, diff --git a/site/src/pages/TemplatePage/TemplatePageView.tsx b/site/src/pages/TemplatePage/TemplatePageView.tsx index 83d9ac64f5608..c9b572041a877 100644 --- a/site/src/pages/TemplatePage/TemplatePageView.tsx +++ b/site/src/pages/TemplatePage/TemplatePageView.tsx @@ -1,7 +1,6 @@ import Button from "@material-ui/core/Button" import Link from "@material-ui/core/Link" import { makeStyles } from "@material-ui/core/styles" -import Typography from "@material-ui/core/Typography" import AddCircleOutline from "@material-ui/icons/AddCircleOutline" import frontMatter from "front-matter" import { FC } from "react" @@ -9,11 +8,11 @@ import ReactMarkdown from "react-markdown" import { Link as RouterLink } from "react-router-dom" import { Template, TemplateVersion, WorkspaceResource } from "../../api/typesGenerated" import { Margins } from "../../components/Margins/Margins" +import { PageHeader, PageHeaderSubtitle, PageHeaderTitle } from "../../components/PageHeader/PageHeader" import { Stack } from "../../components/Stack/Stack" import { TemplateResourcesTable } from "../../components/TemplateResourcesTable/TemplateResourcesTable" import { TemplateStats } from "../../components/TemplateStats/TemplateStats" import { WorkspaceSection } from "../../components/WorkspaceSection/WorkspaceSection" -import { MONOSPACE_FONT_FAMILY } from "../../theme/constants" const Language = { createButton: "Create workspace", @@ -38,23 +37,19 @@ export const TemplatePageView: FC = ({ template, activeTe return ( -
-
- - {template.name} - - - - {template.description === "" ? Language.noDescription : template.description} - -
- -
+ -
-
+ } + > + {template.name} + + {" "} + {template.description === "" ? Language.noDescription : template.description} + + @@ -83,38 +78,6 @@ export const TemplatePageView: FC = ({ template, activeTe export const useStyles = makeStyles((theme) => { return { - root: { - display: "flex", - flexDirection: "column", - }, - header: { - paddingTop: theme.spacing(5), - paddingBottom: theme.spacing(5), - fontFamily: MONOSPACE_FONT_FAMILY, - display: "flex", - alignItems: "center", - }, - headerActions: { - marginLeft: "auto", - }, - title: { - fontWeight: 600, - fontFamily: "inherit", - }, - subtitle: { - fontFamily: "inherit", - marginTop: theme.spacing(0.5), - }, - layout: { - alignItems: "flex-start", - }, - main: { - width: "100%", - }, - sidebar: { - width: theme.spacing(32), - flexShrink: 0, - }, readmeContents: { margin: 0, }, diff --git a/site/src/pages/TemplatesPage/TemplatesPageView.tsx b/site/src/pages/TemplatesPage/TemplatesPageView.tsx index e7629627d1216..c78e09be89f43 100644 --- a/site/src/pages/TemplatesPage/TemplatesPageView.tsx +++ b/site/src/pages/TemplatesPage/TemplatesPageView.tsx @@ -12,7 +12,15 @@ import * as TypesGen from "../../api/typesGenerated" import { AvatarData } from "../../components/AvatarData/AvatarData" import { CodeExample } from "../../components/CodeExample/CodeExample" import { EmptyState } from "../../components/EmptyState/EmptyState" +import { + HelpTooltip, + HelpTooltipLink, + HelpTooltipLinksGroup, + HelpTooltipText, + HelpTooltipTitle, +} from "../../components/HelpTooltip/HelpTooltip" import { Margins } from "../../components/Margins/Margins" +import { PageHeader, PageHeaderTitle } from "../../components/PageHeader/PageHeader" import { Stack } from "../../components/Stack/Stack" import { TableLoader } from "../../components/TableLoader/TableLoader" @@ -36,6 +44,23 @@ export const Language = { or use a built-in template using the following Coder CLI command: ), + templateTooltipTitle: "What is template?", + templateTooltipText: "With templates you can create a common configuration for your workspaces using Terraform.", + templateTooltipLink: "Manage templates", +} + +const TemplateHelpTooltip: React.FC = () => { + return ( + + {Language.templateTooltipTitle} + {Language.templateTooltipText} + + + {Language.templateTooltipLink} + + + + ) } export interface TemplatesPageViewProps { @@ -47,56 +72,60 @@ export interface TemplatesPageViewProps { export const TemplatesPageView: FC = (props) => { const styles = useStyles() return ( - - -
- + + + + + Templates + + + + + +
+ + + {Language.nameLabel} + {Language.usedByLabel} + {Language.lastUpdatedLabel} + + + + {props.loading && } + {!props.loading && !props.templates?.length && ( - {Language.nameLabel} - {Language.usedByLabel} - {Language.lastUpdatedLabel} + + } + /> + - - - {props.loading && } - {!props.loading && !props.templates?.length && ( - - - } - /> - - - )} - {props.templates?.map((template) => ( - - - - + )} + {props.templates?.map((template) => ( + + + + - {Language.developerCount(template.workspace_owner_count)} + {Language.developerCount(template.workspace_owner_count)} - {dayjs().to(dayjs(template.updated_at))} - - ))} - -
- - + {dayjs().to(dayjs(template.updated_at))} + + ))} + + + ) } const useStyles = makeStyles((theme) => ({ - root: { - marginTop: theme.spacing(10), - }, emptyDescription: { maxWidth: theme.spacing(62), }, diff --git a/site/src/pages/UsersPage/UsersPageView.tsx b/site/src/pages/UsersPage/UsersPageView.tsx index 59fceebf935b9..af315017c3462 100644 --- a/site/src/pages/UsersPage/UsersPageView.tsx +++ b/site/src/pages/UsersPage/UsersPageView.tsx @@ -1,11 +1,10 @@ import Button from "@material-ui/core/Button" -import { makeStyles } from "@material-ui/core/styles" import AddCircleOutline from "@material-ui/icons/AddCircleOutline" import { FC } from "react" import * as TypesGen from "../../api/typesGenerated" import { ErrorSummary } from "../../components/ErrorSummary/ErrorSummary" import { Margins } from "../../components/Margins/Margins" -import { Stack } from "../../components/Stack/Stack" +import { PageHeader, PageHeaderTitle } from "../../components/PageHeader/PageHeader" import { UsersTable } from "../../components/UsersTable/UsersTable" export const Language = { @@ -40,48 +39,34 @@ export const UsersPageView: FC = ({ canCreateUser, isLoading, }) => { - const styles = useStyles() - return ( - - -
-
- {canCreateUser && ( - - )} -
-
- {error ? ( - - ) : ( - - )} -
-
+ + }> + {Language.createButton} + + ) : undefined + } + > + Users + + + {error ? ( + + ) : ( + + )} + ) } - -const useStyles = makeStyles((theme) => ({ - actions: { - marginTop: theme.spacing(3), - marginBottom: theme.spacing(3), - display: "flex", - height: theme.spacing(6), - - "& > *": { - marginLeft: "auto", - }, - }, -})) diff --git a/site/src/pages/WorkspaceBuildPage/WorkspaceBuildPage.tsx b/site/src/pages/WorkspaceBuildPage/WorkspaceBuildPage.tsx index 85d6ddbc0ae7d..46550ac62225a 100644 --- a/site/src/pages/WorkspaceBuildPage/WorkspaceBuildPage.tsx +++ b/site/src/pages/WorkspaceBuildPage/WorkspaceBuildPage.tsx @@ -1,21 +1,10 @@ -import { makeStyles } from "@material-ui/core/styles" -import Typography from "@material-ui/core/Typography" import { useMachine } from "@xstate/react" import { FC } from "react" import { Helmet } from "react-helmet" import { useParams } from "react-router-dom" -import { ProvisionerJobLog } from "../../api/typesGenerated" -import { Loader } from "../../components/Loader/Loader" -import { Margins } from "../../components/Margins/Margins" -import { Stack } from "../../components/Stack/Stack" -import { WorkspaceBuildLogs } from "../../components/WorkspaceBuildLogs/WorkspaceBuildLogs" -import { WorkspaceBuildStats } from "../../components/WorkspaceBuildStats/WorkspaceBuildStats" import { pageTitle } from "../../util/page" import { workspaceBuildMachine } from "../../xServices/workspaceBuild/workspaceBuildXService" - -const sortLogsByCreatedAt = (logs: ProvisionerJobLog[]) => { - return [...logs].sort((a, b) => new Date(a.created_at).getTime() - new Date(b.created_at).getTime()) -} +import { WorkspaceBuildPageView } from "./WorkspaceBuildPageView" const useBuildId = () => { const { buildId } = useParams() @@ -32,29 +21,14 @@ export const WorkspaceBuildPage: FC = () => { const [buildState] = useMachine(workspaceBuildMachine, { context: { buildId } }) const { logs, build } = buildState.context const isWaitingForLogs = !buildState.matches("logs.loaded") - const styles = useStyles() return ( - + <> {build ? pageTitle(`Build #${build.build_number} ยท ${build.workspace_name}`) : ""} - - - Logs - - {build && } - {!logs && } - {logs && } - - + + ) } - -const useStyles = makeStyles((theme) => ({ - title: { - paddingTop: theme.spacing(5), - paddingBottom: theme.spacing(2), - }, -})) diff --git a/site/src/pages/WorkspaceBuildPage/WorkspaceBuildPageView.stories.tsx b/site/src/pages/WorkspaceBuildPage/WorkspaceBuildPageView.stories.tsx new file mode 100644 index 0000000000000..24e47c97918ce --- /dev/null +++ b/site/src/pages/WorkspaceBuildPage/WorkspaceBuildPageView.stories.tsx @@ -0,0 +1,17 @@ +import { ComponentMeta, Story } from "@storybook/react" +import { MockWorkspaceBuild, MockWorkspaceBuildLogs } from "../../testHelpers/entities" +import { WorkspaceBuildPageView, WorkspaceBuildPageViewProps } from "./WorkspaceBuildPageView" + +export default { + title: "pages/WorkspaceBuildPageView", + component: WorkspaceBuildPageView, +} as ComponentMeta + +const Template: Story = (args) => + +export const Example = Template.bind({}) +Example.args = { + build: MockWorkspaceBuild, + logs: MockWorkspaceBuildLogs, + isWaitingForLogs: false, +} diff --git a/site/src/pages/WorkspaceBuildPage/WorkspaceBuildPageView.tsx b/site/src/pages/WorkspaceBuildPage/WorkspaceBuildPageView.tsx new file mode 100644 index 0000000000000..5dd1e0a47c813 --- /dev/null +++ b/site/src/pages/WorkspaceBuildPage/WorkspaceBuildPageView.tsx @@ -0,0 +1,34 @@ +import { FC } from "react" +import { ProvisionerJobLog, WorkspaceBuild } from "../../api/typesGenerated" +import { Loader } from "../../components/Loader/Loader" +import { Margins } from "../../components/Margins/Margins" +import { PageHeader, PageHeaderTitle } from "../../components/PageHeader/PageHeader" +import { Stack } from "../../components/Stack/Stack" +import { WorkspaceBuildLogs } from "../../components/WorkspaceBuildLogs/WorkspaceBuildLogs" +import { WorkspaceBuildStats } from "../../components/WorkspaceBuildStats/WorkspaceBuildStats" + +const sortLogsByCreatedAt = (logs: ProvisionerJobLog[]) => { + return [...logs].sort((a, b) => new Date(a.created_at).getTime() - new Date(b.created_at).getTime()) +} + +export interface WorkspaceBuildPageViewProps { + logs: ProvisionerJobLog[] | undefined + build: WorkspaceBuild | undefined + isWaitingForLogs: boolean +} + +export const WorkspaceBuildPageView: FC = ({ logs, build, isWaitingForLogs }) => { + return ( + + + Logs + + + + {build && } + {!logs && } + {logs && } + + + ) +} diff --git a/site/src/pages/WorkspacePage/WorkspacePage.tsx b/site/src/pages/WorkspacePage/WorkspacePage.tsx index 8d9f3d30f08ab..a65fa95e55bc6 100644 --- a/site/src/pages/WorkspacePage/WorkspacePage.tsx +++ b/site/src/pages/WorkspacePage/WorkspacePage.tsx @@ -5,8 +5,6 @@ import { useNavigate, useParams } from "react-router-dom" import { DeleteWorkspaceDialog } from "../../components/DeleteWorkspaceDialog/DeleteWorkspaceDialog" import { ErrorSummary } from "../../components/ErrorSummary/ErrorSummary" import { FullScreenLoader } from "../../components/Loader/FullScreenLoader" -import { Margins } from "../../components/Margins/Margins" -import { Stack } from "../../components/Stack/Stack" import { Workspace } from "../../components/Workspace/Workspace" import { firstOrItem } from "../../util/array" import { pageTitle } from "../../util/page" @@ -37,40 +35,37 @@ export const WorkspacePage: React.FC = () => { return } else { return ( - + <> {pageTitle(`${workspace.owner_name}/${workspace.name}`)} - - <> - { - bannerSend({ type: "EXTEND_DEADLINE_DEFAULT", workspaceId: workspace.id }) - }, - }} - workspace={workspace} - handleStart={() => workspaceSend("START")} - handleStop={() => workspaceSend("STOP")} - handleDelete={() => workspaceSend("ASK_DELETE")} - handleUpdate={() => workspaceSend("UPDATE")} - handleCancel={() => workspaceSend("CANCEL")} - resources={resources} - getResourcesError={getResourcesError instanceof Error ? getResourcesError : undefined} - builds={builds} - /> - workspaceSend("CANCEL_DELETE")} - handleConfirm={() => { - workspaceSend("DELETE") - navigate("/workspaces") - }} - /> - - - + + { + bannerSend({ type: "EXTEND_DEADLINE_DEFAULT", workspaceId: workspace.id }) + }, + }} + workspace={workspace} + handleStart={() => workspaceSend("START")} + handleStop={() => workspaceSend("STOP")} + handleDelete={() => workspaceSend("ASK_DELETE")} + handleUpdate={() => workspaceSend("UPDATE")} + handleCancel={() => workspaceSend("CANCEL")} + resources={resources} + getResourcesError={getResourcesError instanceof Error ? getResourcesError : undefined} + builds={builds} + /> + workspaceSend("CANCEL_DELETE")} + handleConfirm={() => { + workspaceSend("DELETE") + navigate("/workspaces") + }} + /> + ) } } diff --git a/site/src/pages/WorkspacesPage/WorkspacesPage.tsx b/site/src/pages/WorkspacesPage/WorkspacesPage.tsx index 1df0d52342306..b3c47f173e46a 100644 --- a/site/src/pages/WorkspacesPage/WorkspacesPage.tsx +++ b/site/src/pages/WorkspacesPage/WorkspacesPage.tsx @@ -1,174 +1,32 @@ -import Button from "@material-ui/core/Button" -import Fade from "@material-ui/core/Fade" -import InputAdornment from "@material-ui/core/InputAdornment" -import Link from "@material-ui/core/Link" -import Menu from "@material-ui/core/Menu" -import MenuItem from "@material-ui/core/MenuItem" -import { makeStyles } from "@material-ui/core/styles" -import TextField from "@material-ui/core/TextField" -import AddCircleOutline from "@material-ui/icons/AddCircleOutline" -import SearchIcon from "@material-ui/icons/Search" import { useMachine } from "@xstate/react" -import { FormikErrors, useFormik } from "formik" -import { FC, useState } from "react" +import { FC } from "react" import { Helmet } from "react-helmet" -import { Link as RouterLink } from "react-router-dom" -import { CloseDropdown, OpenDropdown } from "../../components/DropdownArrows/DropdownArrows" -import { Margins } from "../../components/Margins/Margins" -import { Stack } from "../../components/Stack/Stack" -import { getFormHelpers, onChangeTrimmed } from "../../util/formUtils" import { pageTitle } from "../../util/page" import { workspacesMachine } from "../../xServices/workspaces/workspacesXService" import { WorkspacesPageView } from "./WorkspacesPageView" -interface FilterFormValues { - query: string -} - -const Language = { - filterName: "Filters", - createWorkspaceButton: "Create workspace", - yourWorkspacesButton: "Your workspaces", - allWorkspacesButton: "All workspaces", -} - -export type FilterFormErrors = FormikErrors - const WorkspacesPage: FC = () => { - const styles = useStyles() const [workspacesState, send] = useMachine(workspacesMachine) - const form = useFormik({ - initialValues: { - query: workspacesState.context.filter || "", - }, - onSubmit: (values) => { - send({ - type: "SET_FILTER", - query: values.query, - }) - }, - }) - - const getFieldHelpers = getFormHelpers(form) - - const [anchorEl, setAnchorEl] = useState(null) - - const handleClick = (event: React.MouseEvent) => { - setAnchorEl(event.currentTarget) - } - - const handleClose = () => { - setAnchorEl(null) - } - - const setYourWorkspaces = () => { - void form.setFieldValue("query", "owner:me") - void form.submitForm() - handleClose() - } - - const setAllWorkspaces = () => { - void form.setFieldValue("query", "") - void form.submitForm() - handleClose() - } - return ( - + <> {pageTitle("Workspaces")} - - - - -
- - - - ), - }} - /> - - - - {Language.yourWorkspacesButton} - {Language.allWorkspacesButton} - -
-
- - - - -
- -
+ { + send({ + type: "SET_FILTER", + query, + }) + }} + /> + ) } -const useStyles = makeStyles((theme) => ({ - workspacesHeaderContainer: { - marginTop: theme.spacing(3), - marginBottom: theme.spacing(3), - justifyContent: "space-between", - }, - filterColumn: { - width: "60%", - cursor: "text", - }, - filterContainer: { - border: `1px solid ${theme.palette.divider}`, - borderRadius: "6px", - }, - filterForm: { - width: "100%", - }, - buttonRoot: { - border: "none", - borderRight: `1px solid ${theme.palette.divider}`, - borderRadius: "6px 0px 0px 6px", - }, - textFieldRoot: { - margin: "0px", - "& fieldset": { - border: "none", - }, - }, -})) - export default WorkspacesPage diff --git a/site/src/pages/WorkspacesPage/WorkspacesPageView.tsx b/site/src/pages/WorkspacesPage/WorkspacesPageView.tsx index 2a7c8a22b4a40..555ea0d0d3a00 100644 --- a/site/src/pages/WorkspacesPage/WorkspacesPageView.tsx +++ b/site/src/pages/WorkspacesPage/WorkspacesPageView.tsx @@ -1,22 +1,40 @@ import Button from "@material-ui/core/Button" +import Fade from "@material-ui/core/Fade" +import InputAdornment from "@material-ui/core/InputAdornment" import Link from "@material-ui/core/Link" +import Menu from "@material-ui/core/Menu" +import MenuItem from "@material-ui/core/MenuItem" 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 TableHead from "@material-ui/core/TableHead" import TableRow from "@material-ui/core/TableRow" +import TextField from "@material-ui/core/TextField" import AddCircleOutline from "@material-ui/icons/AddCircleOutline" +import SearchIcon from "@material-ui/icons/Search" import useTheme from "@material-ui/styles/useTheme" import dayjs from "dayjs" import relativeTime from "dayjs/plugin/relativeTime" -import { FC } from "react" +import { FormikErrors, useFormik } from "formik" +import { FC, useState } from "react" import { Link as RouterLink } from "react-router-dom" import * as TypesGen from "../../api/typesGenerated" import { AvatarData } from "../../components/AvatarData/AvatarData" +import { CloseDropdown, OpenDropdown } from "../../components/DropdownArrows/DropdownArrows" import { EmptyState } from "../../components/EmptyState/EmptyState" +import { + HelpTooltip, + HelpTooltipLink, + HelpTooltipLinksGroup, + HelpTooltipText, + HelpTooltipTitle, +} from "../../components/HelpTooltip/HelpTooltip" +import { Margins } from "../../components/Margins/Margins" +import { PageHeader, PageHeaderTitle } from "../../components/PageHeader/PageHeader" import { Stack } from "../../components/Stack/Stack" import { TableLoader } from "../../components/TableLoader/TableLoader" +import { getFormHelpers, onChangeTrimmed } from "../../util/formUtils" import { getDisplayStatus } from "../../util/workspace" dayjs.extend(relativeTime) @@ -25,19 +43,150 @@ export const Language = { createButton: "Create workspace", emptyMessage: "Create your first workspace", emptyDescription: "Start editing your source code and building your software", + filterName: "Filters", + createWorkspaceButton: "Create workspace", + yourWorkspacesButton: "Your workspaces", + allWorkspacesButton: "All workspaces", + workspaceTooltipTitle: "What is workspace?", + workspaceTooltipText: + "It is your workstation. It is a workspace that will provide you the necessary compute and access to your development environment.", + workspaceTooltipLink1: "Create workspaces", + workspaceTooltipLink2: "Connect with SSH", + workspaceTooltipLink3: "Editors and IDEs", } +const WorkspaceHelpTooltip: React.FC = () => { + return ( + + {Language.workspaceTooltipTitle} + {Language.workspaceTooltipTitle} + + + {Language.workspaceTooltipLink1} + + + {Language.workspaceTooltipLink2} + + + {Language.workspaceTooltipLink3} + + + + ) +} + +interface FilterFormValues { + query: string +} + +export type FilterFormErrors = FormikErrors + export interface WorkspacesPageViewProps { loading?: boolean workspaces?: TypesGen.Workspace[] + filter?: string + onFilter: (query: string) => void } -export const WorkspacesPageView: FC = ({ loading, workspaces }) => { - useStyles() +export const WorkspacesPageView: FC = ({ loading, workspaces, filter, onFilter }) => { + const styles = useStyles() const theme: Theme = useTheme() + const form = useFormik({ + initialValues: { + query: filter ?? "", + }, + onSubmit: ({ query }) => { + onFilter(query) + }, + }) + + const getFieldHelpers = getFormHelpers(form) + + const [anchorEl, setAnchorEl] = useState(null) + + const handleClick = (event: React.MouseEvent) => { + setAnchorEl(event.currentTarget) + } + + const handleClose = () => { + setAnchorEl(null) + } + + const setYourWorkspaces = () => { + void form.setFieldValue("query", "owner:me") + void form.submitForm() + handleClose() + } + + const setAllWorkspaces = () => { + void form.setFieldValue("query", "") + void form.submitForm() + handleClose() + } + return ( - + + + + + } + > + + + Workspaces + + + + + + + + +
+ + + + ), + }} + /> + + + + {Language.yourWorkspacesButton} + {Language.allWorkspacesButton} + +
+ @@ -98,7 +247,7 @@ export const WorkspacesPageView: FC = ({ loading, works })}
-
+ ) } @@ -110,6 +259,7 @@ const useStyles = makeStyles((theme) => ({ flexDirection: "column", alignItems: "center", justifyContent: "center", + "& span": { maxWidth: 600, textAlign: "center", @@ -117,4 +267,23 @@ const useStyles = makeStyles((theme) => ({ lineHeight: `${theme.spacing(3)}px`, }, }, + filterContainer: { + border: `1px solid ${theme.palette.divider}`, + borderRadius: theme.shape.borderRadius, + marginBottom: theme.spacing(2), + }, + filterForm: { + width: "100%", + }, + buttonRoot: { + border: "none", + borderRight: `1px solid ${theme.palette.divider}`, + borderRadius: `${theme.shape.borderRadius}px 0px 0px ${theme.shape.borderRadius}px`, + }, + textFieldRoot: { + margin: "0px", + "& fieldset": { + border: "none", + }, + }, })) pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy