diff --git a/site/src/components/InfoTooltip/InfoTooltip.stories.tsx b/site/src/components/InfoTooltip/InfoTooltip.stories.tsx new file mode 100644 index 0000000000000..561d393aa6d05 --- /dev/null +++ b/site/src/components/InfoTooltip/InfoTooltip.stories.tsx @@ -0,0 +1,23 @@ +import { InfoTooltip } from "./InfoTooltip"; +import type { Meta, StoryObj } from "@storybook/react"; + +const meta: Meta = { + title: "components/InfoTooltip", + component: InfoTooltip, + args: { + type: "info", + title: "Hello, friend!", + message: "Today is a lovely day :^)", + }, +}; + +export default meta; +type Story = StoryObj; + +export const Example: Story = {}; +export const Warning: Story = { + args: { + type: "warning", + message: "Unfortunately, there's a radio connected to my brain", + }, +}; diff --git a/site/src/components/InfoTooltip/InfoTooltip.tsx b/site/src/components/InfoTooltip/InfoTooltip.tsx new file mode 100644 index 0000000000000..528dc30d9f23d --- /dev/null +++ b/site/src/components/InfoTooltip/InfoTooltip.tsx @@ -0,0 +1,47 @@ +import { type FC, type ReactNode } from "react"; +import { + HelpTooltip, + HelpTooltipText, + HelpTooltipTitle, +} from "components/HelpTooltip/HelpTooltip"; +import InfoIcon from "@mui/icons-material/InfoOutlined"; +import { makeStyles } from "@mui/styles"; +import { colors } from "theme/colors"; + +interface InfoTooltipProps { + type?: "warning" | "info"; + title: ReactNode; + message: ReactNode; +} + +export const InfoTooltip: FC = (props) => { + const { title, message, type = "info" } = props; + + const styles = useStyles({ type }); + + return ( + + {title} + {message} + + ); +}; + +const useStyles = makeStyles>(() => ({ + icon: ({ type }) => ({ + color: type === "info" ? colors.blue[5] : colors.yellow[5], + }), + + button: { + opacity: 1, + + "&:hover": { + opacity: 1, + }, + }, +})); diff --git a/site/src/pages/TemplatePage/TemplateVersionsPage/VersionRow.tsx b/site/src/pages/TemplatePage/TemplateVersionsPage/VersionRow.tsx index ebed1af7517bf..0f29d902dcd45 100644 --- a/site/src/pages/TemplatePage/TemplateVersionsPage/VersionRow.tsx +++ b/site/src/pages/TemplatePage/TemplateVersionsPage/VersionRow.tsx @@ -6,6 +6,7 @@ import { Pill } from "components/Pill/Pill"; import { Stack } from "components/Stack/Stack"; import { TimelineEntry } from "components/Timeline/TimelineEntry"; import { UserAvatar } from "components/UserAvatar/UserAvatar"; +import { InfoTooltip } from "components/InfoTooltip/InfoTooltip"; import { useClickableTableRow } from "hooks/useClickableTableRow"; import { useNavigate } from "react-router-dom"; import { colors } from "theme/colors"; @@ -63,6 +64,10 @@ export const VersionRow: React.FC = ({ version {version.name} + {version.message && ( + + )} + {new Date(version.created_at).toLocaleTimeString()} diff --git a/site/src/pages/WorkspacePage/ChangeVersionDialog.tsx b/site/src/pages/WorkspacePage/ChangeVersionDialog.tsx index 04dfdeb33e135..99b08caac85ad 100644 --- a/site/src/pages/WorkspacePage/ChangeVersionDialog.tsx +++ b/site/src/pages/WorkspacePage/ChangeVersionDialog.tsx @@ -2,6 +2,7 @@ import { DialogProps } from "components/Dialogs/Dialog"; import { FC, useRef, useState } from "react"; import { FormFields } from "components/Form/Form"; import TextField from "@mui/material/TextField"; +import { makeStyles } from "@mui/styles"; import { ConfirmDialog } from "components/Dialogs/ConfirmDialog/ConfirmDialog"; import { Stack } from "components/Stack/Stack"; import { Template, TemplateVersion } from "api/typesGenerated"; @@ -13,6 +14,9 @@ import { Pill } from "components/Pill/Pill"; import { Avatar } from "components/Avatar/Avatar"; import CircularProgress from "@mui/material/CircularProgress"; import Box from "@mui/material/Box"; +import { Alert, AlertDetail } from "components/Alert/Alert"; +import AlertTitle from "@mui/material/AlertTitle"; +import InfoIcon from "@mui/icons-material/InfoOutlined"; export type ChangeVersionDialogProps = DialogProps & { template: Template | undefined; @@ -32,6 +36,9 @@ export const ChangeVersionDialog: FC = ({ }) => { const [isAutocompleteOpen, setIsAutocompleteOpen] = useState(false); const selectedTemplateVersion = useRef(); + const version = selectedTemplateVersion.current; + + const styles = useStyles(); return ( = ({

You are about to change the version of this workspace.

{templateVersions ? ( - - { - selectedTemplateVersion.current = - newTemplateVersion ?? undefined; - }} - onOpen={() => { - setIsAutocompleteOpen(true); - }} - onClose={() => { - setIsAutocompleteOpen(false); - }} - isOptionEqualToValue={( - option: TemplateVersion, - value: TemplateVersion, - ) => option.id === value.id} - getOptionLabel={(option) => option.name} - renderOption={(props, option: TemplateVersion) => ( - - - {option.name} - - } - title={ - - {option.name} - {template?.active_version_id === option.id && ( - - )} - - } - subtitle={createDayString(option.created_at)} - /> - - )} - renderInput={(params) => ( - <> - - {!templateVersions ? ( - - ) : null} - {params.InputProps.endAdornment} - - ), - }} - /> - - )} - /> - + <> + + { + selectedTemplateVersion.current = + newTemplateVersion ?? undefined; + }} + onOpen={() => { + setIsAutocompleteOpen(true); + }} + onClose={() => { + setIsAutocompleteOpen(false); + }} + isOptionEqualToValue={( + option: TemplateVersion, + value: TemplateVersion, + ) => option.id === value.id} + getOptionLabel={(option) => option.name} + renderOption={(props, option: TemplateVersion) => ( + + + {option.name} + + } + title={ + + + {option.name} + {option.message && ( + ({ + width: theme.spacing(1.5), + height: theme.spacing(1.5), + })} + /> + )} + + {template?.active_version_id === option.id && ( + + )} + + } + subtitle={createDayString(option.created_at)} + /> + + )} + renderInput={(params) => ( + <> + + {!templateVersions ? ( + + ) : null} + {params.InputProps.endAdornment} + + ), + classes: { + root: styles.inputRoot, + }, + }} + /> + + )} + /> + + {version && ( + + + Published by {version.created_by.username} + + {version.message && ( + {version.message} + )} + + )} + ) : ( )} @@ -127,3 +163,9 @@ export const ChangeVersionDialog: FC = ({ /> ); }; + +export const useStyles = makeStyles((theme) => ({ + inputRoot: { + paddingLeft: `${theme.spacing(1.75)} !important`, // Same padding left as input + }, +})); diff --git a/site/src/pages/WorkspacesPage/WorkspacesTable.tsx b/site/src/pages/WorkspacesPage/WorkspacesTable.tsx index b59e0f15c2db3..259708a3e11e4 100644 --- a/site/src/pages/WorkspacesPage/WorkspacesTable.tsx +++ b/site/src/pages/WorkspacesPage/WorkspacesTable.tsx @@ -16,13 +16,6 @@ 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/HelpTooltip/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"; @@ -36,6 +29,7 @@ import { getDisplayWorkspaceTemplateName } from "utils/workspace"; import Checkbox from "@mui/material/Checkbox"; import { AvatarDataSkeleton } from "components/AvatarData/AvatarDataSkeleton"; import Skeleton from "@mui/material/Skeleton"; +import { InfoTooltip } from "components/InfoTooltip/InfoTooltip"; export interface WorkspacesTableProps { workspaces?: Workspace[]; @@ -215,7 +209,13 @@ export const WorkspacesTable: FC = ({ {workspace.latest_build.status === "running" && - !workspace.health.healthy && } + !workspace.health.healthy && ( + + )} @@ -269,24 +269,6 @@ const WorkspacesRow: FC<{ ); }; -export const UnhealthyTooltip = () => { - const styles = useUnhealthyTooltipStyles(); - - return ( - - Workspace is unhealthy - - Your workspace is running but some agents are unhealthy. - - - ); -}; - const TableLoader = ({ canCheckWorkspaces, }: { @@ -324,20 +306,6 @@ const cantBeChecked = (workspace: Workspace) => { return ["deleting", "pending"].includes(workspace.latest_build.status); }; -const useUnhealthyTooltipStyles = makeStyles(() => ({ - unhealthyIcon: { - color: colors.yellow[5], - }, - - unhealthyButton: { - opacity: 1, - - "&:hover": { - opacity: 1, - }, - }, -})); - const useStyles = makeStyles((theme) => ({ withImage: { paddingBottom: 0,