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

309 lines
8.0 KiB
TypeScript
Raw Normal View History

2022-10-18 13:44:58 +00:00
import Tooltip from "@material-ui/core/Tooltip"
import { makeStyles } from "@material-ui/core/styles"
import { combineClasses } from "util/combineClasses"
import { WorkspaceAgent } from "api/typesGenerated"
2022-10-18 13:44:58 +00:00
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"
2022-10-18 13:44:58 +00:00
// 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 = () => {
2022-10-18 13:44:58 +00:00
const styles = useStyles()
const { t } = useTranslation("workspacePage")
return (
<div
role="status"
aria-label={t("agentStatus.connected.ready")}
className={combineClasses([styles.status, styles.connected])}
/>
2022-10-18 13:44:58 +00:00
)
}
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>
)
}
2022-10-18 13:44:58 +00:00
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 }) => {
2022-10-18 13:44:58 +00:00
return (
<ChooseOne>
<Cond condition={agent.status === "connected"}>
<ConnectedStatus agent={agent} />
2022-10-18 13:44:58 +00:00
</Cond>
<Cond condition={agent.status === "disconnected"}>
<DisconnectedStatus />
</Cond>
<Cond condition={agent.status === "timeout"}>
<TimeoutStatus agent={agent} />
</Cond>
2022-10-18 13:44:58 +00:00
<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}`,
2022-10-18 13:44:58 +00:00
},
disconnected: {
backgroundColor: theme.palette.text.secondary,
},
"@keyframes pulse": {
"0%": {
opacity: 1,
2022-10-18 13:44:58 +00:00
},
"50%": {
opacity: 0.4,
2022-10-18 13:44:58 +00:00
},
"100%": {
opacity: 1,
2022-10-18 13:44:58 +00:00
},
},
connecting: {
backgroundColor: theme.palette.info.light,
animation: "$pulse 1.5s 0.5s ease-in-out forwards infinite",
2022-10-18 13:44:58 +00:00
},
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),
},
2022-10-18 13:44:58 +00:00
}))