mirror of https://github.com/coder/coder.git
309 lines
8.0 KiB
TypeScript
309 lines
8.0 KiB
TypeScript
import Tooltip from "@material-ui/core/Tooltip"
|
||
import { makeStyles } from "@material-ui/core/styles"
|
||
import { combineClasses } from "util/combineClasses"
|
||
import { WorkspaceAgent } from "api/typesGenerated"
|
||
import { ChooseOne, Cond } from "components/Conditionals/ChooseOne"
|
||
import { useTranslation } from "react-i18next"
|
||
import WarningRounded from "@material-ui/icons/WarningRounded"
|
||
import {
|
||
HelpPopover,
|
||
HelpTooltipText,
|
||
HelpTooltipTitle,
|
||
} from "components/Tooltips/HelpTooltip"
|
||
import { useRef, useState } from "react"
|
||
import Link from "@material-ui/core/Link"
|
||
|
||
// If we think in the agent status and lifecycle into a single enum/state I’d
|
||
// say we would have: connecting, timeout, disconnected, connected:created,
|
||
// connected:starting, connected:start_timeout, connected:start_error,
|
||
// connected:ready
|
||
|
||
const ReadyLifeCycle: React.FC = () => {
|
||
const styles = useStyles()
|
||
const { t } = useTranslation("workspacePage")
|
||
|
||
return (
|
||
<div
|
||
role="status"
|
||
aria-label={t("agentStatus.connected.ready")}
|
||
className={combineClasses([styles.status, styles.connected])}
|
||
/>
|
||
)
|
||
}
|
||
|
||
const StartingLifecycle: React.FC = () => {
|
||
const styles = useStyles()
|
||
const { t } = useTranslation("workspacePage")
|
||
|
||
return (
|
||
<Tooltip title={t("agentStatus.connected.starting")}>
|
||
<div
|
||
role="status"
|
||
aria-label={t("agentStatus.connected.starting")}
|
||
className={combineClasses([styles.status, styles.connecting])}
|
||
/>
|
||
</Tooltip>
|
||
)
|
||
}
|
||
|
||
const StartTimeoutLifecycle: React.FC<{
|
||
agent: WorkspaceAgent
|
||
}> = ({ agent }) => {
|
||
const { t } = useTranslation("agent")
|
||
const styles = useStyles()
|
||
const anchorRef = useRef<SVGSVGElement>(null)
|
||
const [isOpen, setIsOpen] = useState(false)
|
||
const id = isOpen ? "timeout-popover" : undefined
|
||
|
||
return (
|
||
<>
|
||
<WarningRounded
|
||
ref={anchorRef}
|
||
onMouseEnter={() => setIsOpen(true)}
|
||
onMouseLeave={() => setIsOpen(false)}
|
||
role="status"
|
||
aria-label={t("status.startTimeout")}
|
||
className={styles.timeoutWarning}
|
||
/>
|
||
<HelpPopover
|
||
id={id}
|
||
open={isOpen}
|
||
anchorEl={anchorRef.current}
|
||
onOpen={() => setIsOpen(true)}
|
||
onClose={() => setIsOpen(false)}
|
||
>
|
||
<HelpTooltipTitle>{t("startTimeoutTooltip.title")}</HelpTooltipTitle>
|
||
<HelpTooltipText>
|
||
{t("startTimeoutTooltip.message")}{" "}
|
||
<Link
|
||
target="_blank"
|
||
rel="noreferrer"
|
||
href={agent.troubleshooting_url}
|
||
>
|
||
{t("startTimeoutTooltip.link")}
|
||
</Link>
|
||
.
|
||
</HelpTooltipText>
|
||
</HelpPopover>
|
||
</>
|
||
)
|
||
}
|
||
|
||
const StartErrorLifecycle: React.FC<{
|
||
agent: WorkspaceAgent
|
||
}> = ({ agent }) => {
|
||
const { t } = useTranslation("agent")
|
||
const styles = useStyles()
|
||
const anchorRef = useRef<SVGSVGElement>(null)
|
||
const [isOpen, setIsOpen] = useState(false)
|
||
const id = isOpen ? "timeout-popover" : undefined
|
||
|
||
return (
|
||
<>
|
||
<WarningRounded
|
||
ref={anchorRef}
|
||
onMouseEnter={() => setIsOpen(true)}
|
||
onMouseLeave={() => setIsOpen(false)}
|
||
role="status"
|
||
aria-label={t("status.error")}
|
||
className={styles.errorWarning}
|
||
/>
|
||
<HelpPopover
|
||
id={id}
|
||
open={isOpen}
|
||
anchorEl={anchorRef.current}
|
||
onOpen={() => setIsOpen(true)}
|
||
onClose={() => setIsOpen(false)}
|
||
>
|
||
<HelpTooltipTitle>{t("startErrorTooltip.title")}</HelpTooltipTitle>
|
||
<HelpTooltipText>
|
||
{t("startErrorTooltip.message")}{" "}
|
||
<Link
|
||
target="_blank"
|
||
rel="noreferrer"
|
||
href={agent.troubleshooting_url}
|
||
>
|
||
{t("startErrorTooltip.link")}
|
||
</Link>
|
||
.
|
||
</HelpTooltipText>
|
||
</HelpPopover>
|
||
</>
|
||
)
|
||
}
|
||
|
||
const ConnectedStatus: React.FC<{
|
||
agent: WorkspaceAgent
|
||
}> = ({ agent }) => {
|
||
// NOTE(mafredri): Keep this behind feature flag for the time-being,
|
||
// if login_before_ready is false, the user has updated to
|
||
// terraform-provider-coder v0.6.10 and opted in to the functionality.
|
||
//
|
||
// Remove check once documentation is in place and we do a breaking
|
||
// release indicating startup script behavior has changed.
|
||
// https://github.com/coder/coder/issues/5749
|
||
if (agent.login_before_ready) {
|
||
return <ReadyLifeCycle />
|
||
}
|
||
return (
|
||
<ChooseOne>
|
||
<Cond condition={agent.lifecycle_state === "ready"}>
|
||
<ReadyLifeCycle />
|
||
</Cond>
|
||
<Cond condition={agent.lifecycle_state === "start_timeout"}>
|
||
<StartTimeoutLifecycle agent={agent} />
|
||
</Cond>
|
||
<Cond condition={agent.lifecycle_state === "start_error"}>
|
||
<StartErrorLifecycle agent={agent} />
|
||
</Cond>
|
||
<Cond>
|
||
<StartingLifecycle />
|
||
</Cond>
|
||
</ChooseOne>
|
||
)
|
||
}
|
||
|
||
const DisconnectedStatus: React.FC = () => {
|
||
const styles = useStyles()
|
||
const { t } = useTranslation("workspacePage")
|
||
|
||
return (
|
||
<Tooltip title={t("agentStatus.disconnected")}>
|
||
<div
|
||
role="status"
|
||
aria-label={t("agentStatus.disconnected")}
|
||
className={combineClasses([styles.status, styles.disconnected])}
|
||
/>
|
||
</Tooltip>
|
||
)
|
||
}
|
||
|
||
const ConnectingStatus: React.FC = () => {
|
||
const styles = useStyles()
|
||
const { t } = useTranslation("workspacePage")
|
||
|
||
return (
|
||
<Tooltip title={t("agentStatus.connecting")}>
|
||
<div
|
||
role="status"
|
||
aria-label={t("agentStatus.connecting")}
|
||
className={combineClasses([styles.status, styles.connecting])}
|
||
/>
|
||
</Tooltip>
|
||
)
|
||
}
|
||
|
||
const TimeoutStatus: React.FC<{
|
||
agent: WorkspaceAgent
|
||
}> = ({ agent }) => {
|
||
const { t } = useTranslation("agent")
|
||
const styles = useStyles()
|
||
const anchorRef = useRef<SVGSVGElement>(null)
|
||
const [isOpen, setIsOpen] = useState(false)
|
||
const id = isOpen ? "timeout-popover" : undefined
|
||
|
||
return (
|
||
<>
|
||
<WarningRounded
|
||
ref={anchorRef}
|
||
onMouseEnter={() => setIsOpen(true)}
|
||
onMouseLeave={() => setIsOpen(false)}
|
||
role="status"
|
||
aria-label={t("status.timeout")}
|
||
className={styles.timeoutWarning}
|
||
/>
|
||
<HelpPopover
|
||
id={id}
|
||
open={isOpen}
|
||
anchorEl={anchorRef.current}
|
||
onOpen={() => setIsOpen(true)}
|
||
onClose={() => setIsOpen(false)}
|
||
>
|
||
<HelpTooltipTitle>{t("timeoutTooltip.title")}</HelpTooltipTitle>
|
||
<HelpTooltipText>
|
||
{t("timeoutTooltip.message")}{" "}
|
||
<Link
|
||
target="_blank"
|
||
rel="noreferrer"
|
||
href={agent.troubleshooting_url}
|
||
>
|
||
{t("timeoutTooltip.link")}
|
||
</Link>
|
||
.
|
||
</HelpTooltipText>
|
||
</HelpPopover>
|
||
</>
|
||
)
|
||
}
|
||
|
||
export const AgentStatus: React.FC<{
|
||
agent: WorkspaceAgent
|
||
}> = ({ agent }) => {
|
||
return (
|
||
<ChooseOne>
|
||
<Cond condition={agent.status === "connected"}>
|
||
<ConnectedStatus agent={agent} />
|
||
</Cond>
|
||
<Cond condition={agent.status === "disconnected"}>
|
||
<DisconnectedStatus />
|
||
</Cond>
|
||
<Cond condition={agent.status === "timeout"}>
|
||
<TimeoutStatus agent={agent} />
|
||
</Cond>
|
||
<Cond>
|
||
<ConnectingStatus />
|
||
</Cond>
|
||
</ChooseOne>
|
||
)
|
||
}
|
||
|
||
const useStyles = makeStyles((theme) => ({
|
||
status: {
|
||
width: theme.spacing(1),
|
||
height: theme.spacing(1),
|
||
borderRadius: "100%",
|
||
},
|
||
|
||
connected: {
|
||
backgroundColor: theme.palette.success.light,
|
||
boxShadow: `0 0 12px 0 ${theme.palette.success.light}`,
|
||
},
|
||
|
||
disconnected: {
|
||
backgroundColor: theme.palette.text.secondary,
|
||
},
|
||
|
||
"@keyframes pulse": {
|
||
"0%": {
|
||
opacity: 1,
|
||
},
|
||
"50%": {
|
||
opacity: 0.4,
|
||
},
|
||
"100%": {
|
||
opacity: 1,
|
||
},
|
||
},
|
||
|
||
connecting: {
|
||
backgroundColor: theme.palette.info.light,
|
||
animation: "$pulse 1.5s 0.5s ease-in-out forwards infinite",
|
||
},
|
||
|
||
timeoutWarning: {
|
||
color: theme.palette.warning.light,
|
||
width: theme.spacing(2.5),
|
||
height: theme.spacing(2.5),
|
||
position: "relative",
|
||
top: theme.spacing(1),
|
||
},
|
||
|
||
errorWarning: {
|
||
color: theme.palette.error.main,
|
||
width: theme.spacing(2.5),
|
||
height: theme.spacing(2.5),
|
||
position: "relative",
|
||
top: theme.spacing(1),
|
||
},
|
||
}))
|