Thanks to visit codestin.com
Credit goes to github.com

Skip to content

feat: Redesign resources table #4600

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Oct 18, 2022
36 changes: 2 additions & 34 deletions site/src/components/CopyButton/CopyButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import IconButton from "@material-ui/core/Button"
import { makeStyles } from "@material-ui/core/styles"
import Tooltip from "@material-ui/core/Tooltip"
import Check from "@material-ui/icons/Check"
import React, { useState } from "react"
import { useClipboard } from "hooks/useClipboard"
import { combineClasses } from "../../util/combineClasses"
import { FileCopyIcon } from "../Icons/FileCopyIcon"

Expand Down Expand Up @@ -30,39 +30,7 @@ export const CopyButton: React.FC<React.PropsWithChildren<CopyButtonProps>> = ({
tooltipTitle = Language.tooltipTitle,
}) => {
const styles = useStyles()
const [isCopied, setIsCopied] = useState<boolean>(false)

const copyToClipboard = async (): Promise<void> => {
try {
await window.navigator.clipboard.writeText(text)
setIsCopied(true)
window.setTimeout(() => {
setIsCopied(false)
}, 1000)
} catch (err) {
const input = document.createElement("input")
input.value = text
document.body.appendChild(input)
input.focus()
input.select()
const result = document.execCommand("copy")
document.body.removeChild(input)
if (result) {
setIsCopied(true)
window.setTimeout(() => {
setIsCopied(false)
}, 1000)
} else {
const wrappedErr = new Error(
"copyToClipboard: failed to copy text to clipboard",
)
if (err instanceof Error) {
wrappedErr.stack = err.stack
}
console.error(wrappedErr)
}
}
}
const { isCopied, copy: copyToClipboard } = useClipboard(text)

return (
<Tooltip title={tooltipTitle} placement="top">
Expand Down
39 changes: 39 additions & 0 deletions site/src/components/CopyableValue/CopyableValue.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { makeStyles } from "@material-ui/core/styles"
import Tooltip from "@material-ui/core/Tooltip"
import { useClickable } from "hooks/useClickable"
import { useClipboard } from "hooks/useClipboard"
import React, { HTMLProps } from "react"
import { combineClasses } from "util/combineClasses"

interface CopyableValueProps extends HTMLProps<HTMLDivElement> {
value: string
}

export const CopyableValue: React.FC<CopyableValueProps> = ({
value,
className,
...props
}) => {
const { isCopied, copy } = useClipboard(value)
const clickableProps = useClickable(copy)
const styles = useStyles()

return (
<Tooltip
title={isCopied ? "Copied!" : "Click to copy"}
placement="bottom-start"
>
<span
{...props}
{...clickableProps}
className={combineClasses([styles.value, className])}
/>
</Tooltip>
)
}

const useStyles = makeStyles(() => ({
value: {
cursor: "pointer",
},
}))
7 changes: 5 additions & 2 deletions site/src/components/PageHeader/PageHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,17 @@ export const PageHeader: React.FC<React.PropsWithChildren<PageHeaderProps>> = ({
const styles = useStyles({})

return (
<div className={combineClasses([styles.root, className])}>
<header
className={combineClasses([styles.root, className])}
data-testid="header"
>
<hgroup>{children}</hgroup>
{actions && (
<Stack direction="row" className={styles.actions}>
{actions}
</Stack>
)}
</div>
</header>
)
}

Expand Down
114 changes: 114 additions & 0 deletions site/src/components/Resources/AgentLatency.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { useRef, useState, FC } from "react"
import { makeStyles, Theme, useTheme } from "@material-ui/core/styles"
import {
HelpTooltipText,
HelpPopover,
HelpTooltipTitle,
} from "components/Tooltips/HelpTooltip"
import { Stack } from "components/Stack/Stack"
import { WorkspaceAgent, DERPRegion } from "api/typesGenerated"

const getDisplayLatency = (theme: Theme, agent: WorkspaceAgent) => {
// Find the right latency to display
const latencyValues = Object.values(agent.latency ?? {})
const latency =
latencyValues.find((derp) => derp.preferred) ??
// Accessing an array index can return undefined as well
// for some reason TS does not handle that
(latencyValues[0] as DERPRegion | undefined)

if (!latency) {
return undefined
}

// Get the color
let color = theme.palette.success.light
if (latency.latency_ms >= 150 && latency.latency_ms < 300) {
color = theme.palette.warning.light
} else if (latency.latency_ms >= 300) {
color = theme.palette.error.light
}

return {
...latency,
color,
}
}

export const AgentLatency: FC<{ agent: WorkspaceAgent }> = ({ agent }) => {
const theme: Theme = useTheme()
const anchorRef = useRef<HTMLButtonElement>(null)
const [isOpen, setIsOpen] = useState(false)
const id = isOpen ? "latency-popover" : undefined
const latency = getDisplayLatency(theme, agent)
const styles = useStyles()

if (!latency || !agent.latency) {
return null
}

return (
<>
<span
role="presentation"
aria-label="latency"
ref={anchorRef}
onMouseEnter={() => setIsOpen(true)}
className={styles.trigger}
style={{ color: latency.color }}
>
{Math.round(Math.round(latency.latency_ms))}ms
</span>
<HelpPopover
id={id}
open={isOpen}
anchorEl={anchorRef.current}
onOpen={() => setIsOpen(true)}
onClose={() => setIsOpen(false)}
>
<HelpTooltipTitle>Latency</HelpTooltipTitle>
<HelpTooltipText>
Latency from relay servers, used when connections cannot connect
peer-to-peer. Star indicates the preferred relay.
</HelpTooltipText>

<HelpTooltipText>
<Stack direction="column" spacing={1} className={styles.regions}>
{Object.keys(agent.latency).map((regionName) => {
if (!agent.latency) {
throw new Error("No latency found on agent")
}

const region = agent.latency[regionName]

return (
<Stack
direction="row"
key={regionName}
spacing={0.5}
justifyContent="space-between"
className={region.preferred ? styles.preferred : undefined}
>
<strong>{regionName}</strong>
{Math.round(region.latency_ms)}ms
</Stack>
)
})}
</Stack>
</HelpTooltipText>
</HelpPopover>
</>
)
}

const useStyles = makeStyles((theme) => ({
trigger: {
cursor: "pointer",
},
regions: {
marginTop: theme.spacing(2),
},
preferred: {
color: theme.palette.text.primary,
},
}))
100 changes: 100 additions & 0 deletions site/src/components/Resources/AgentStatus.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
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"

const ConnectedStatus: React.FC = () => {
const styles = useStyles()
const { t } = useTranslation("workspacePage")

return (
<Tooltip title={t("agentStatus.connected")}>
<div
role="status"
aria-label={t("agentStatus.connected")}
className={combineClasses([styles.status, styles.connected])}
/>
</Tooltip>
)
}

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>
)
}

export const AgentStatus: React.FC<{ agent: WorkspaceAgent }> = ({ agent }) => {
return (
<ChooseOne>
<Cond condition={agent.status === "connected"}>
<ConnectedStatus />
</Cond>
<Cond condition={agent.status === "disconnected"}>
<DisconnectedStatus />
</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,
},

disconnected: {
backgroundColor: theme.palette.text.secondary,
},

"@keyframes pulse": {
"0%": {
opacity: 0.25,
},
"50%": {
opacity: 1,
},
"100%": {
opacity: 0.25,
},
},

connecting: {
backgroundColor: theme.palette.info.light,
animation: "$pulse 1s ease-in-out forwards infinite",
},
}))
61 changes: 61 additions & 0 deletions site/src/components/Resources/AgentVersion.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { useRef, useState, FC } from "react"
import { makeStyles } from "@material-ui/core/styles"
import {
HelpTooltipText,
HelpPopover,
HelpTooltipTitle,
} from "components/Tooltips/HelpTooltip"
import { WorkspaceAgent } from "api/typesGenerated"
import { getDisplayVersionStatus } from "util/workspace"

export const AgentVersion: FC<{
agent: WorkspaceAgent
serverVersion: string
}> = ({ agent, serverVersion }) => {
const styles = useStyles()
const anchorRef = useRef<HTMLButtonElement>(null)
const [isOpen, setIsOpen] = useState(false)
const id = isOpen ? "version-outdated-popover" : undefined
const { displayVersion, outdated } = getDisplayVersionStatus(
agent.version,
serverVersion,
)

if (!outdated) {
return <span>{displayVersion}</span>
}

return (
<>
<span
role="presentation"
aria-label="latency"
ref={anchorRef}
onMouseEnter={() => setIsOpen(true)}
className={styles.trigger}
>
Agent Outdated
</span>
<HelpPopover
id={id}
open={isOpen}
anchorEl={anchorRef.current}
onOpen={() => setIsOpen(true)}
onClose={() => setIsOpen(false)}
>
<HelpTooltipTitle>Agent Outdated</HelpTooltipTitle>
<HelpTooltipText>
This agent is an older version than the Coder server. This can happen
after you update Coder with running workspaces. To fix this, you can
stop and start the workspace.
</HelpTooltipText>
</HelpPopover>
</>
)
}

const useStyles = makeStyles(() => ({
trigger: {
cursor: "pointer",
},
}))
Loading