mirror of
https://github.com/coder/coder.git
synced 2025-07-12 00:14:10 +00:00
feat(site): add documentation links to webterminal notifications (#8019)
* feat(site): add documentation links to webterminal notifications * change button variant
This commit is contained in:
@ -1,18 +1,21 @@
|
|||||||
import { Story } from "@storybook/react"
|
import { Meta, StoryObj } from "@storybook/react"
|
||||||
import { WarningAlert, WarningAlertProps } from "./WarningAlert"
|
import { WarningAlert } from "./WarningAlert"
|
||||||
import Button from "@mui/material/Button"
|
import Button from "@mui/material/Button"
|
||||||
|
|
||||||
export default {
|
const meta: Meta<typeof WarningAlert> = {
|
||||||
title: "components/WarningAlert",
|
title: "components/WarningAlert",
|
||||||
component: WarningAlert,
|
component: WarningAlert,
|
||||||
}
|
}
|
||||||
|
|
||||||
const Template: Story<WarningAlertProps> = (args) => <WarningAlert {...args} />
|
export default meta
|
||||||
|
|
||||||
export const ExampleWithDismiss = Template.bind({})
|
type Story = StoryObj<typeof WarningAlert>
|
||||||
ExampleWithDismiss.args = {
|
|
||||||
text: "This is a warning",
|
export const ExampleWithDismiss: Story = {
|
||||||
dismissible: true,
|
args: {
|
||||||
|
text: "This is a warning",
|
||||||
|
dismissible: true,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const ExampleAction = (
|
const ExampleAction = (
|
||||||
@ -21,15 +24,17 @@ const ExampleAction = (
|
|||||||
</Button>
|
</Button>
|
||||||
)
|
)
|
||||||
|
|
||||||
export const ExampleWithAction = Template.bind({})
|
export const ExampleWithAction = {
|
||||||
ExampleWithAction.args = {
|
args: {
|
||||||
text: "This is a warning",
|
text: "This is a warning",
|
||||||
actions: [ExampleAction],
|
actions: [ExampleAction],
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ExampleWithActionAndDismiss = Template.bind({})
|
export const ExampleWithActionAndDismiss = {
|
||||||
ExampleWithActionAndDismiss.args = {
|
args: {
|
||||||
text: "This is a warning",
|
text: "This is a warning",
|
||||||
actions: [ExampleAction],
|
actions: [ExampleAction],
|
||||||
dismissible: true,
|
dismissible: true,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,4 @@
|
|||||||
import Button from "@mui/material/Button"
|
|
||||||
import { makeStyles, useTheme } from "@mui/styles"
|
import { makeStyles, useTheme } from "@mui/styles"
|
||||||
import WarningIcon from "@mui/icons-material/ErrorOutlineRounded"
|
|
||||||
import RefreshOutlined from "@mui/icons-material/RefreshOutlined"
|
|
||||||
import { useMachine } from "@xstate/react"
|
import { useMachine } from "@xstate/react"
|
||||||
import { portForwardURL } from "components/PortForwardButton/PortForwardButton"
|
import { portForwardURL } from "components/PortForwardButton/PortForwardButton"
|
||||||
import { Stack } from "components/Stack/Stack"
|
import { Stack } from "components/Stack/Stack"
|
||||||
@ -18,13 +15,13 @@ import { MONOSPACE_FONT_FAMILY } from "../../theme/constants"
|
|||||||
import { pageTitle } from "../../utils/page"
|
import { pageTitle } from "../../utils/page"
|
||||||
import { terminalMachine } from "../../xServices/terminal/terminalXService"
|
import { terminalMachine } from "../../xServices/terminal/terminalXService"
|
||||||
import { useProxy } from "contexts/ProxyContext"
|
import { useProxy } from "contexts/ProxyContext"
|
||||||
import { combineClasses } from "utils/combineClasses"
|
|
||||||
import Box from "@mui/material/Box"
|
import Box from "@mui/material/Box"
|
||||||
import { useDashboard } from "components/Dashboard/DashboardProvider"
|
import { useDashboard } from "components/Dashboard/DashboardProvider"
|
||||||
import { Region } from "api/typesGenerated"
|
import { Region } from "api/typesGenerated"
|
||||||
import { getLatencyColor } from "utils/latency"
|
import { getLatencyColor } from "utils/latency"
|
||||||
import Popover from "@mui/material/Popover"
|
import Popover from "@mui/material/Popover"
|
||||||
import { ProxyStatusLatency } from "components/ProxyStatusLatency/ProxyStatusLatency"
|
import { ProxyStatusLatency } from "components/ProxyStatusLatency/ProxyStatusLatency"
|
||||||
|
import TerminalPageAlert, { TerminalPageAlertType } from "./TerminalPageAlert"
|
||||||
|
|
||||||
export const Language = {
|
export const Language = {
|
||||||
workspaceErrorMessagePrefix: "Unable to fetch workspace: ",
|
workspaceErrorMessagePrefix: "Unable to fetch workspace: ",
|
||||||
@ -80,12 +77,26 @@ const TerminalPage: FC = () => {
|
|||||||
websocketError,
|
websocketError,
|
||||||
} = terminalState.context
|
} = terminalState.context
|
||||||
const reloading = useReloading(isDisconnected)
|
const reloading = useReloading(isDisconnected)
|
||||||
const shouldDisplayStartupWarning = workspaceAgent
|
const lifecycleState = workspaceAgent?.lifecycle_state
|
||||||
? ["starting", "starting_timeout"].includes(workspaceAgent.lifecycle_state)
|
const [startupWarning, setStartupWarning] = useState<
|
||||||
: false
|
TerminalPageAlertType | undefined
|
||||||
const shouldDisplayStartupError = workspaceAgent
|
>(undefined)
|
||||||
? workspaceAgent.lifecycle_state === "start_error"
|
|
||||||
: false
|
useEffect(() => {
|
||||||
|
if (lifecycleState === "start_error") {
|
||||||
|
setStartupWarning("error")
|
||||||
|
} else if (lifecycleState === "starting") {
|
||||||
|
setStartupWarning("starting")
|
||||||
|
} else {
|
||||||
|
setStartupWarning((prev) => {
|
||||||
|
if (prev === "starting") {
|
||||||
|
return "success"
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, [lifecycleState])
|
||||||
|
|
||||||
const dashboard = useDashboard()
|
const dashboard = useDashboard()
|
||||||
const proxyContext = useProxy()
|
const proxyContext = useProxy()
|
||||||
const selectedProxy = proxyContext.proxy.proxy
|
const selectedProxy = proxyContext.proxy.proxy
|
||||||
@ -305,49 +316,8 @@ const TerminalPage: FC = () => {
|
|||||||
</Stack>
|
</Stack>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{shouldDisplayStartupError && (
|
|
||||||
<div
|
|
||||||
className={combineClasses([styles.alert, styles.alertError])}
|
|
||||||
role="alert"
|
|
||||||
>
|
|
||||||
<WarningIcon className={styles.alertIcon} />
|
|
||||||
<div>
|
|
||||||
<div className={styles.alertTitle}>Startup script failed</div>
|
|
||||||
<div className={styles.alertMessage}>
|
|
||||||
You can continue using this terminal, but something may be missing
|
|
||||||
or not fully set up.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<Box display="flex" flexDirection="column" height="100vh">
|
<Box display="flex" flexDirection="column" height="100vh">
|
||||||
{shouldDisplayStartupWarning && (
|
{startupWarning && <TerminalPageAlert alertType={startupWarning} />}
|
||||||
<div className={styles.alert} role="alert">
|
|
||||||
<WarningIcon className={styles.alertIcon} />
|
|
||||||
<div>
|
|
||||||
<div className={styles.alertTitle}>
|
|
||||||
Startup script is still running
|
|
||||||
</div>
|
|
||||||
<div className={styles.alertMessage}>
|
|
||||||
You can continue using this terminal, but something may be
|
|
||||||
missing or not fully set up.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className={styles.alertActions}>
|
|
||||||
<Button
|
|
||||||
startIcon={<RefreshOutlined />}
|
|
||||||
size="small"
|
|
||||||
onClick={() => {
|
|
||||||
// By redirecting the user without the session in the URL we
|
|
||||||
// create a new one
|
|
||||||
window.location.href = window.location.pathname
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Refresh session
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div
|
<div
|
||||||
className={styles.terminal}
|
className={styles.terminal}
|
||||||
ref={xtermRef}
|
ref={xtermRef}
|
||||||
|
37
site/src/pages/TerminalPage/TerminalPageAlert.stories.tsx
Normal file
37
site/src/pages/TerminalPage/TerminalPageAlert.stories.tsx
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import type { Meta, StoryObj } from "@storybook/react"
|
||||||
|
|
||||||
|
import TerminalPageAlert from "./TerminalPageAlert"
|
||||||
|
|
||||||
|
const meta: Meta<typeof TerminalPageAlert> = {
|
||||||
|
component: TerminalPageAlert,
|
||||||
|
title: "components/TerminalPageAlert",
|
||||||
|
argTypes: {
|
||||||
|
alertType: {
|
||||||
|
control: {
|
||||||
|
type: "radio",
|
||||||
|
},
|
||||||
|
options: ["error", "starting", "success"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
type Story = StoryObj<typeof TerminalPageAlert>
|
||||||
|
|
||||||
|
export const Error: Story = {
|
||||||
|
args: {
|
||||||
|
alertType: "error",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Starting: Story = {
|
||||||
|
args: {
|
||||||
|
alertType: "starting",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Success: Story = {
|
||||||
|
args: {
|
||||||
|
alertType: "success",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default meta
|
113
site/src/pages/TerminalPage/TerminalPageAlert.tsx
Normal file
113
site/src/pages/TerminalPage/TerminalPageAlert.tsx
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
import { AlertColor } from "@mui/material/Alert/Alert"
|
||||||
|
import Button from "@mui/material/Button"
|
||||||
|
import Link from "@mui/material/Link"
|
||||||
|
import { Alert } from "components/Alert/Alert"
|
||||||
|
import { ReactNode } from "react"
|
||||||
|
|
||||||
|
export type TerminalPageAlertType = "error" | "starting" | "success"
|
||||||
|
|
||||||
|
type MapAlertTypeToComponent = {
|
||||||
|
[key in TerminalPageAlertType]: {
|
||||||
|
severity: AlertColor
|
||||||
|
children: ReactNode | undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapAlertTypeToText: MapAlertTypeToComponent = {
|
||||||
|
error: {
|
||||||
|
severity: "warning",
|
||||||
|
children: (
|
||||||
|
<>
|
||||||
|
The workspace{" "}
|
||||||
|
<Link
|
||||||
|
title="startup script has exited with an error"
|
||||||
|
href="https://coder.com/docs/v2/latest/templates#startup-script-exited-with-an-error"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
startup script has exited with an error
|
||||||
|
</Link>
|
||||||
|
, we recommend reloading this session and{" "}
|
||||||
|
<Link
|
||||||
|
title=" debugging the startup script"
|
||||||
|
href="https://coder.com/docs/v2/latest/templates#debugging-the-startup-script"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
debugging the startup script
|
||||||
|
</Link>{" "}
|
||||||
|
because{" "}
|
||||||
|
<Link
|
||||||
|
title="your workspace may be incomplete."
|
||||||
|
href="https://coder.com/docs/v2/latest/templates#your-workspace-may-be-incomplete"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
your workspace may be incomplete.
|
||||||
|
</Link>{" "}
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
starting: {
|
||||||
|
severity: "info",
|
||||||
|
children: (
|
||||||
|
<>
|
||||||
|
Startup script is still running. You can continue using this terminal,
|
||||||
|
but{" "}
|
||||||
|
<Link
|
||||||
|
title="your workspace may be incomplete."
|
||||||
|
href="https://coder.com/docs/v2/latest/templates#your-workspace-may-be-incomplete"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
{" "}
|
||||||
|
your workspace may be incomplete.
|
||||||
|
</Link>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
success: {
|
||||||
|
severity: "success",
|
||||||
|
children: (
|
||||||
|
<>
|
||||||
|
Startup script has completed successfully. The workspace is ready but
|
||||||
|
this{" "}
|
||||||
|
<Link
|
||||||
|
title="session was started before the startup script finished"
|
||||||
|
href="https://coder.com/docs/v2/latest/templates#your-workspace-may-be-incomplete"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
session was started before the startup script finished.
|
||||||
|
</Link>{" "}
|
||||||
|
To ensure your shell environment is up-to-date, we recommend reloading
|
||||||
|
this session.
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ({ alertType }: { alertType: TerminalPageAlertType }) => {
|
||||||
|
return (
|
||||||
|
<Alert
|
||||||
|
severity={mapAlertTypeToText[alertType].severity}
|
||||||
|
dismissible
|
||||||
|
actions={[
|
||||||
|
<Button
|
||||||
|
key="refresh-session"
|
||||||
|
size="small"
|
||||||
|
variant="text"
|
||||||
|
onClick={() => {
|
||||||
|
// By redirecting the user without the session in the URL we
|
||||||
|
// create a new one
|
||||||
|
window.location.href = window.location.pathname
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Refresh session
|
||||||
|
</Button>,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{mapAlertTypeToText[alertType].children}
|
||||||
|
</Alert>
|
||||||
|
)
|
||||||
|
}
|
Reference in New Issue
Block a user