coder/site/src/components/Resources/AgentStatus.tsx

309 lines
8.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 Id
// 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),
},
}))