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

Skip to content

Commit 616fe7a

Browse files
feat: Redesign resources table (#4600)
1 parent 61683f1 commit 616fe7a

19 files changed

+904
-374
lines changed

site/src/components/CopyButton/CopyButton.tsx

+2-34
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import IconButton from "@material-ui/core/Button"
22
import { makeStyles } from "@material-ui/core/styles"
33
import Tooltip from "@material-ui/core/Tooltip"
44
import Check from "@material-ui/icons/Check"
5-
import React, { useState } from "react"
5+
import { useClipboard } from "hooks/useClipboard"
66
import { combineClasses } from "../../util/combineClasses"
77
import { FileCopyIcon } from "../Icons/FileCopyIcon"
88

@@ -30,39 +30,7 @@ export const CopyButton: React.FC<React.PropsWithChildren<CopyButtonProps>> = ({
3030
tooltipTitle = Language.tooltipTitle,
3131
}) => {
3232
const styles = useStyles()
33-
const [isCopied, setIsCopied] = useState<boolean>(false)
34-
35-
const copyToClipboard = async (): Promise<void> => {
36-
try {
37-
await window.navigator.clipboard.writeText(text)
38-
setIsCopied(true)
39-
window.setTimeout(() => {
40-
setIsCopied(false)
41-
}, 1000)
42-
} catch (err) {
43-
const input = document.createElement("input")
44-
input.value = text
45-
document.body.appendChild(input)
46-
input.focus()
47-
input.select()
48-
const result = document.execCommand("copy")
49-
document.body.removeChild(input)
50-
if (result) {
51-
setIsCopied(true)
52-
window.setTimeout(() => {
53-
setIsCopied(false)
54-
}, 1000)
55-
} else {
56-
const wrappedErr = new Error(
57-
"copyToClipboard: failed to copy text to clipboard",
58-
)
59-
if (err instanceof Error) {
60-
wrappedErr.stack = err.stack
61-
}
62-
console.error(wrappedErr)
63-
}
64-
}
65-
}
33+
const { isCopied, copy: copyToClipboard } = useClipboard(text)
6634

6735
return (
6836
<Tooltip title={tooltipTitle} placement="top">
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { makeStyles } from "@material-ui/core/styles"
2+
import Tooltip from "@material-ui/core/Tooltip"
3+
import { useClickable } from "hooks/useClickable"
4+
import { useClipboard } from "hooks/useClipboard"
5+
import React, { HTMLProps } from "react"
6+
import { combineClasses } from "util/combineClasses"
7+
8+
interface CopyableValueProps extends HTMLProps<HTMLDivElement> {
9+
value: string
10+
}
11+
12+
export const CopyableValue: React.FC<CopyableValueProps> = ({
13+
value,
14+
className,
15+
...props
16+
}) => {
17+
const { isCopied, copy } = useClipboard(value)
18+
const clickableProps = useClickable(copy)
19+
const styles = useStyles()
20+
21+
return (
22+
<Tooltip
23+
title={isCopied ? "Copied!" : "Click to copy"}
24+
placement="bottom-start"
25+
>
26+
<span
27+
{...props}
28+
{...clickableProps}
29+
className={combineClasses([styles.value, className])}
30+
/>
31+
</Tooltip>
32+
)
33+
}
34+
35+
const useStyles = makeStyles(() => ({
36+
value: {
37+
cursor: "pointer",
38+
},
39+
}))

site/src/components/PageHeader/PageHeader.tsx

+5-2
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,17 @@ export const PageHeader: React.FC<React.PropsWithChildren<PageHeaderProps>> = ({
1515
const styles = useStyles({})
1616

1717
return (
18-
<div className={combineClasses([styles.root, className])}>
18+
<header
19+
className={combineClasses([styles.root, className])}
20+
data-testid="header"
21+
>
1922
<hgroup>{children}</hgroup>
2023
{actions && (
2124
<Stack direction="row" className={styles.actions}>
2225
{actions}
2326
</Stack>
2427
)}
25-
</div>
28+
</header>
2629
)
2730
}
2831

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import { useRef, useState, FC } from "react"
2+
import { makeStyles, Theme, useTheme } from "@material-ui/core/styles"
3+
import {
4+
HelpTooltipText,
5+
HelpPopover,
6+
HelpTooltipTitle,
7+
} from "components/Tooltips/HelpTooltip"
8+
import { Stack } from "components/Stack/Stack"
9+
import { WorkspaceAgent, DERPRegion } from "api/typesGenerated"
10+
11+
const getDisplayLatency = (theme: Theme, agent: WorkspaceAgent) => {
12+
// Find the right latency to display
13+
const latencyValues = Object.values(agent.latency ?? {})
14+
const latency =
15+
latencyValues.find((derp) => derp.preferred) ??
16+
// Accessing an array index can return undefined as well
17+
// for some reason TS does not handle that
18+
(latencyValues[0] as DERPRegion | undefined)
19+
20+
if (!latency) {
21+
return undefined
22+
}
23+
24+
// Get the color
25+
let color = theme.palette.success.light
26+
if (latency.latency_ms >= 150 && latency.latency_ms < 300) {
27+
color = theme.palette.warning.light
28+
} else if (latency.latency_ms >= 300) {
29+
color = theme.palette.error.light
30+
}
31+
32+
return {
33+
...latency,
34+
color,
35+
}
36+
}
37+
38+
export const AgentLatency: FC<{ agent: WorkspaceAgent }> = ({ agent }) => {
39+
const theme: Theme = useTheme()
40+
const anchorRef = useRef<HTMLButtonElement>(null)
41+
const [isOpen, setIsOpen] = useState(false)
42+
const id = isOpen ? "latency-popover" : undefined
43+
const latency = getDisplayLatency(theme, agent)
44+
const styles = useStyles()
45+
46+
if (!latency || !agent.latency) {
47+
return null
48+
}
49+
50+
return (
51+
<>
52+
<span
53+
role="presentation"
54+
aria-label="latency"
55+
ref={anchorRef}
56+
onMouseEnter={() => setIsOpen(true)}
57+
className={styles.trigger}
58+
style={{ color: latency.color }}
59+
>
60+
{Math.round(Math.round(latency.latency_ms))}ms
61+
</span>
62+
<HelpPopover
63+
id={id}
64+
open={isOpen}
65+
anchorEl={anchorRef.current}
66+
onOpen={() => setIsOpen(true)}
67+
onClose={() => setIsOpen(false)}
68+
>
69+
<HelpTooltipTitle>Latency</HelpTooltipTitle>
70+
<HelpTooltipText>
71+
Latency from relay servers, used when connections cannot connect
72+
peer-to-peer. Star indicates the preferred relay.
73+
</HelpTooltipText>
74+
75+
<HelpTooltipText>
76+
<Stack direction="column" spacing={1} className={styles.regions}>
77+
{Object.keys(agent.latency).map((regionName) => {
78+
if (!agent.latency) {
79+
throw new Error("No latency found on agent")
80+
}
81+
82+
const region = agent.latency[regionName]
83+
84+
return (
85+
<Stack
86+
direction="row"
87+
key={regionName}
88+
spacing={0.5}
89+
justifyContent="space-between"
90+
className={region.preferred ? styles.preferred : undefined}
91+
>
92+
<strong>{regionName}</strong>
93+
{Math.round(region.latency_ms)}ms
94+
</Stack>
95+
)
96+
})}
97+
</Stack>
98+
</HelpTooltipText>
99+
</HelpPopover>
100+
</>
101+
)
102+
}
103+
104+
const useStyles = makeStyles((theme) => ({
105+
trigger: {
106+
cursor: "pointer",
107+
},
108+
regions: {
109+
marginTop: theme.spacing(2),
110+
},
111+
preferred: {
112+
color: theme.palette.text.primary,
113+
},
114+
}))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import Tooltip from "@material-ui/core/Tooltip"
2+
import { makeStyles } from "@material-ui/core/styles"
3+
import { combineClasses } from "util/combineClasses"
4+
import { WorkspaceAgent } from "api/typesGenerated"
5+
import { ChooseOne, Cond } from "components/Conditionals/ChooseOne"
6+
import { useTranslation } from "react-i18next"
7+
8+
const ConnectedStatus: React.FC = () => {
9+
const styles = useStyles()
10+
const { t } = useTranslation("workspacePage")
11+
12+
return (
13+
<Tooltip title={t("agentStatus.connected")}>
14+
<div
15+
role="status"
16+
aria-label={t("agentStatus.connected")}
17+
className={combineClasses([styles.status, styles.connected])}
18+
/>
19+
</Tooltip>
20+
)
21+
}
22+
23+
const DisconnectedStatus: React.FC = () => {
24+
const styles = useStyles()
25+
const { t } = useTranslation("workspacePage")
26+
27+
return (
28+
<Tooltip title={t("agentStatus.disconnected")}>
29+
<div
30+
role="status"
31+
aria-label={t("agentStatus.disconnected")}
32+
className={combineClasses([styles.status, styles.disconnected])}
33+
/>
34+
</Tooltip>
35+
)
36+
}
37+
38+
const ConnectingStatus: React.FC = () => {
39+
const styles = useStyles()
40+
const { t } = useTranslation("workspacePage")
41+
42+
return (
43+
<Tooltip title={t("agentStatus.connecting")}>
44+
<div
45+
role="status"
46+
aria-label={t("agentStatus.connecting")}
47+
className={combineClasses([styles.status, styles.connecting])}
48+
/>
49+
</Tooltip>
50+
)
51+
}
52+
53+
export const AgentStatus: React.FC<{ agent: WorkspaceAgent }> = ({ agent }) => {
54+
return (
55+
<ChooseOne>
56+
<Cond condition={agent.status === "connected"}>
57+
<ConnectedStatus />
58+
</Cond>
59+
<Cond condition={agent.status === "disconnected"}>
60+
<DisconnectedStatus />
61+
</Cond>
62+
<Cond>
63+
<ConnectingStatus />
64+
</Cond>
65+
</ChooseOne>
66+
)
67+
}
68+
69+
const useStyles = makeStyles((theme) => ({
70+
status: {
71+
width: theme.spacing(1),
72+
height: theme.spacing(1),
73+
borderRadius: "100%",
74+
},
75+
76+
connected: {
77+
backgroundColor: theme.palette.success.light,
78+
},
79+
80+
disconnected: {
81+
backgroundColor: theme.palette.text.secondary,
82+
},
83+
84+
"@keyframes pulse": {
85+
"0%": {
86+
opacity: 0.25,
87+
},
88+
"50%": {
89+
opacity: 1,
90+
},
91+
"100%": {
92+
opacity: 0.25,
93+
},
94+
},
95+
96+
connecting: {
97+
backgroundColor: theme.palette.info.light,
98+
animation: "$pulse 1s ease-in-out forwards infinite",
99+
},
100+
}))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { useRef, useState, FC } from "react"
2+
import { makeStyles } from "@material-ui/core/styles"
3+
import {
4+
HelpTooltipText,
5+
HelpPopover,
6+
HelpTooltipTitle,
7+
} from "components/Tooltips/HelpTooltip"
8+
import { WorkspaceAgent } from "api/typesGenerated"
9+
import { getDisplayVersionStatus } from "util/workspace"
10+
11+
export const AgentVersion: FC<{
12+
agent: WorkspaceAgent
13+
serverVersion: string
14+
}> = ({ agent, serverVersion }) => {
15+
const styles = useStyles()
16+
const anchorRef = useRef<HTMLButtonElement>(null)
17+
const [isOpen, setIsOpen] = useState(false)
18+
const id = isOpen ? "version-outdated-popover" : undefined
19+
const { displayVersion, outdated } = getDisplayVersionStatus(
20+
agent.version,
21+
serverVersion,
22+
)
23+
24+
if (!outdated) {
25+
return <span>{displayVersion}</span>
26+
}
27+
28+
return (
29+
<>
30+
<span
31+
role="presentation"
32+
aria-label="latency"
33+
ref={anchorRef}
34+
onMouseEnter={() => setIsOpen(true)}
35+
className={styles.trigger}
36+
>
37+
Agent Outdated
38+
</span>
39+
<HelpPopover
40+
id={id}
41+
open={isOpen}
42+
anchorEl={anchorRef.current}
43+
onOpen={() => setIsOpen(true)}
44+
onClose={() => setIsOpen(false)}
45+
>
46+
<HelpTooltipTitle>Agent Outdated</HelpTooltipTitle>
47+
<HelpTooltipText>
48+
This agent is an older version than the Coder server. This can happen
49+
after you update Coder with running workspaces. To fix this, you can
50+
stop and start the workspace.
51+
</HelpTooltipText>
52+
</HelpPopover>
53+
</>
54+
)
55+
}
56+
57+
const useStyles = makeStyles(() => ({
58+
trigger: {
59+
cursor: "pointer",
60+
},
61+
}))

0 commit comments

Comments
 (0)