Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4128ed2653 |
@@ -1,6 +1,11 @@
|
||||
import type { Interpolation, Theme } from "@emotion/react";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "components/Tooltip/Tooltip";
|
||||
import {
|
||||
type FC,
|
||||
forwardRef,
|
||||
@@ -69,17 +74,31 @@ export const NotHealthyBadge: FC = () => {
|
||||
|
||||
export const NotRegisteredBadge: FC = () => {
|
||||
return (
|
||||
<Tooltip title="Workspace Proxy has never come online and needs to be started.">
|
||||
<span css={[styles.badge, styles.warnBadge]}>Never seen</span>
|
||||
</Tooltip>
|
||||
<TooltipProvider delayDuration={100}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span css={[styles.badge, styles.warnBadge]}>Never seen</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
Workspace Proxy has never come online and needs to be started.
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export const NotReachableBadge: FC = () => {
|
||||
return (
|
||||
<Tooltip title="Workspace Proxy not responding to http(s) requests.">
|
||||
<span css={[styles.badge, styles.warnBadge]}>Not reachable</span>
|
||||
</Tooltip>
|
||||
<TooltipProvider delayDuration={100}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span css={[styles.badge, styles.warnBadge]}>Not reachable</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
Workspace Proxy not responding to http(s) requests.
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
import { useTheme } from "@emotion/react";
|
||||
import CircularProgress from "@mui/material/CircularProgress";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import { TooltipProvider } from "@radix-ui/react-tooltip";
|
||||
import { Abbr } from "components/Abbr/Abbr";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "components/Tooltip/Tooltip";
|
||||
import { CircleHelpIcon } from "lucide-react";
|
||||
import type { FC } from "react";
|
||||
import { cn } from "utils/cn";
|
||||
@@ -26,23 +31,35 @@ export const Latency: FC<LatencyProps> = ({
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<Tooltip title="Loading latency..." className={className}>
|
||||
<CircularProgress
|
||||
className={cn("!size-icon-xs", iconClassName)}
|
||||
style={{ color }}
|
||||
/>
|
||||
</Tooltip>
|
||||
<TooltipProvider delayDuration={100}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div className="!size-icon-xs">
|
||||
<CircularProgress
|
||||
className={cn("!size-icon-xs", iconClassName)}
|
||||
style={{ color }}
|
||||
/>
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Loading latency...</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
);
|
||||
}
|
||||
|
||||
if (!latency) {
|
||||
return (
|
||||
<Tooltip title="Latency not available" className={className}>
|
||||
<CircleHelpIcon
|
||||
className={cn("!size-icon-sm", iconClassName)}
|
||||
style={{ color }}
|
||||
/>
|
||||
</Tooltip>
|
||||
<TooltipProvider delayDuration={100}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<CircleHelpIcon
|
||||
className={cn("!size-icon-sm", iconClassName)}
|
||||
style={{ color }}
|
||||
/>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Latency not available</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import { Button } from "components/Button/Button";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "components/Tooltip/Tooltip";
|
||||
import {
|
||||
type ButtonHTMLAttributes,
|
||||
type ReactNode,
|
||||
@@ -52,22 +57,27 @@ function PaginationNavButtonCore({
|
||||
}, [showDisabledMessage, disabledMessageTimeout]);
|
||||
|
||||
return (
|
||||
<Tooltip title={disabledMessage} open={showDisabledMessage}>
|
||||
{/*
|
||||
* Going more out of the way to avoid attaching the disabled prop directly
|
||||
* to avoid unwanted side effects of using the prop:
|
||||
* - Not being focusable/keyboard-navigable
|
||||
* - Not being able to call functions in response to invalid actions
|
||||
* (mostly for giving direct UI feedback to those actions)
|
||||
*/}
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
disabled={disabled}
|
||||
onClick={onClick}
|
||||
{...delegatedProps}
|
||||
/>
|
||||
</Tooltip>
|
||||
<TooltipProvider delayDuration={100}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
{/*
|
||||
* Going more out of the way to avoid attaching the disabled prop directly
|
||||
* to avoid unwanted side effects of using the prop:
|
||||
* - Not being focusable/keyboard-navigable
|
||||
* - Not being able to call functions in response to invalid actions
|
||||
* (mostly for giving direct UI feedback to those actions)
|
||||
*/}
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
disabled={disabled}
|
||||
onClick={onClick}
|
||||
{...delegatedProps}
|
||||
/>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>{disabledMessage}</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,13 +5,18 @@ import type { InputBaseComponentProps } from "@mui/material/InputBase";
|
||||
import Radio from "@mui/material/Radio";
|
||||
import RadioGroup from "@mui/material/RadioGroup";
|
||||
import TextField, { type TextFieldProps } from "@mui/material/TextField";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import type { TemplateVersionParameter } from "api/typesGenerated";
|
||||
import { Button } from "components/Button/Button";
|
||||
import { ExternalImage } from "components/ExternalImage/ExternalImage";
|
||||
import { MemoizedMarkdown } from "components/Markdown/Markdown";
|
||||
import { Pill } from "components/Pill/Pill";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "components/Tooltip/Tooltip";
|
||||
import { CircleAlertIcon, SettingsIcon } from "lucide-react";
|
||||
import { type FC, type ReactNode, useState } from "react";
|
||||
import type {
|
||||
@@ -136,26 +141,50 @@ const ParameterLabel: FC<ParameterLabelProps> = ({ parameter, isPreset }) => {
|
||||
{displayName}
|
||||
|
||||
{!parameter.required && (
|
||||
<Tooltip title="If no value is specified, the system will default to the value set by the administrator.">
|
||||
<span css={styles.optionalLabel}>(optional)</span>
|
||||
</Tooltip>
|
||||
<TooltipProvider delayDuration={100}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span css={styles.optionalLabel}>(optional)</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
If no value is specified, the system will default to the value set
|
||||
by the administrator.
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
)}
|
||||
{!parameter.mutable && (
|
||||
<Tooltip title="This value cannot be modified after the workspace has been created.">
|
||||
<Pill
|
||||
type="warning"
|
||||
icon={<CircleAlertIcon className="size-icon-xs" />}
|
||||
>
|
||||
Immutable
|
||||
</Pill>
|
||||
</Tooltip>
|
||||
<TooltipProvider delayDuration={100}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Pill
|
||||
type="warning"
|
||||
icon={<CircleAlertIcon className="size-icon-xs" />}
|
||||
>
|
||||
Immutable
|
||||
</Pill>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
This value cannot be modified after the workspace has been
|
||||
created.
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
)}
|
||||
{isPreset && (
|
||||
<Tooltip title="This value was set by a preset">
|
||||
<Pill type="info" icon={<SettingsIcon className="size-icon-xs" />}>
|
||||
Preset
|
||||
</Pill>
|
||||
</Tooltip>
|
||||
<TooltipProvider delayDuration={100}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Pill
|
||||
type="info"
|
||||
icon={<SettingsIcon className="size-icon-xs" />}
|
||||
>
|
||||
Preset
|
||||
</Pill>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>This value was set by a preset.</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
@@ -328,15 +357,18 @@ const RichParameterField: FC<RichParameterInputProps> = ({
|
||||
css={{ padding: small ? undefined : "4px 0" }}
|
||||
>
|
||||
{small ? (
|
||||
<Tooltip
|
||||
title={
|
||||
<MemoizedMarkdown>
|
||||
{option.description}
|
||||
</MemoizedMarkdown>
|
||||
}
|
||||
>
|
||||
<div>{option.name}</div>
|
||||
</Tooltip>
|
||||
<TooltipProvider delayDuration={100}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span>{option.name}</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<MemoizedMarkdown className="max-w-xs">
|
||||
{option.description}
|
||||
</MemoizedMarkdown>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
) : (
|
||||
<>
|
||||
<span>{option.name}</span>
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import InputAdornment from "@mui/material/InputAdornment";
|
||||
import TextField, { type TextFieldProps } from "@mui/material/TextField";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "components/Tooltip/Tooltip";
|
||||
import { useEffectEvent } from "hooks/hookPolyfills";
|
||||
import { SearchIcon, XIcon } from "lucide-react";
|
||||
import { type FC, useLayoutEffect, useRef } from "react";
|
||||
@@ -47,17 +52,22 @@ export const SearchField: FC<SearchFieldProps> = ({
|
||||
),
|
||||
endAdornment: value !== "" && (
|
||||
<InputAdornment position="end">
|
||||
<Tooltip title="Clear search">
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={() => {
|
||||
onChange("");
|
||||
}}
|
||||
>
|
||||
<XIcon className="size-icon-xs" />
|
||||
<span className="sr-only">Clear search</span>
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<TooltipProvider delayDuration={100}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={() => {
|
||||
onChange("");
|
||||
}}
|
||||
>
|
||||
<XIcon className="size-icon-xs" />
|
||||
<span className="sr-only">Clear search</span>
|
||||
</IconButton>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Clear search</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</InputAdornment>
|
||||
),
|
||||
...InputProps,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { css, type Interpolation, type Theme, useTheme } from "@emotion/react";
|
||||
import Link from "@mui/material/Link";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import type {
|
||||
DeploymentStats,
|
||||
HealthcheckReport,
|
||||
@@ -13,6 +12,12 @@ import { RocketIcon } from "components/Icons/RocketIcon";
|
||||
import { TerminalIcon } from "components/Icons/TerminalIcon";
|
||||
import { VSCodeIcon } from "components/Icons/VSCodeIcon";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "components/Tooltip/Tooltip";
|
||||
import dayjs from "dayjs";
|
||||
import {
|
||||
AppWindowIcon,
|
||||
@@ -124,7 +129,8 @@ export const DeploymentBannerView: FC<DeploymentBannerViewProps> = ({
|
||||
whiteSpace: "nowrap",
|
||||
}}
|
||||
>
|
||||
<Tooltip
|
||||
{/* <Tooltip
|
||||
// TODO: Clean this up
|
||||
classes={{
|
||||
tooltip:
|
||||
"ml-3 mb-1 w-[400px] p-4 text-sm text-content-primary bg-surface-secondary border border-solid border-border pointer-events-none",
|
||||
@@ -161,7 +167,43 @@ export const DeploymentBannerView: FC<DeploymentBannerViewProps> = ({
|
||||
<RocketIcon />
|
||||
</div>
|
||||
)}
|
||||
</Tooltip>
|
||||
</Tooltip> */}
|
||||
|
||||
<TooltipProvider delayDuration={100}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
{healthErrors.length > 0 ? (
|
||||
<Link
|
||||
component={RouterLink}
|
||||
to="/health"
|
||||
css={[styles.statusBadge, styles.unhealthy]}
|
||||
>
|
||||
<CircleAlertIcon className="size-icon-sm" />
|
||||
</Link>
|
||||
) : (
|
||||
<div css={styles.statusBadge}>
|
||||
<RocketIcon />
|
||||
</div>
|
||||
)}
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
{healthErrors.length > 0 ? (
|
||||
<>
|
||||
<HelpTooltipTitle>
|
||||
We have detected problems with your Coder deployment.
|
||||
</HelpTooltipTitle>
|
||||
<Stack spacing={1}>
|
||||
{healthErrors.map((error) => (
|
||||
<HealthIssue key={error}>{error}</HealthIssue>
|
||||
))}
|
||||
</Stack>
|
||||
</>
|
||||
) : (
|
||||
"Status of your Coder deployment. Only visible for admins!"
|
||||
)}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
|
||||
<div css={styles.group}>
|
||||
<div css={styles.category}>Workspaces</div>
|
||||
@@ -194,37 +236,59 @@ export const DeploymentBannerView: FC<DeploymentBannerViewProps> = ({
|
||||
</div>
|
||||
|
||||
<div css={styles.group}>
|
||||
<Tooltip title={`Activity in the last ~${aggregatedMinutes} minutes`}>
|
||||
<div css={styles.category}>Transmission</div>
|
||||
</Tooltip>
|
||||
<TooltipProvider delayDuration={100}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div css={styles.category}>Transmission</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
Activity in the last ~{aggregatedMinutes} minutes
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
|
||||
<div css={styles.values}>
|
||||
<Tooltip title="Data sent to workspaces">
|
||||
<div css={styles.value}>
|
||||
<CloudDownloadIcon className="size-icon-xs" />
|
||||
{stats ? prettyBytes(stats.workspaces.rx_bytes) : "-"}
|
||||
</div>
|
||||
</Tooltip>
|
||||
<TooltipProvider delayDuration={100}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div css={styles.value}>
|
||||
<CloudDownloadIcon className="size-icon-xs" />
|
||||
{stats ? prettyBytes(stats.workspaces.rx_bytes) : "-"}
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Data sent to workspaces</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
<ValueSeparator />
|
||||
<Tooltip title="Data sent from workspaces">
|
||||
<div css={styles.value}>
|
||||
<CloudUploadIcon className="size-icon-xs" />
|
||||
{stats ? prettyBytes(stats.workspaces.tx_bytes) : "-"}
|
||||
</div>
|
||||
</Tooltip>
|
||||
<TooltipProvider delayDuration={100}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div css={styles.value}>
|
||||
<CloudUploadIcon className="size-icon-xs" />
|
||||
{stats ? prettyBytes(stats.workspaces.tx_bytes) : "-"}
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Data sent from workspaces</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
<ValueSeparator />
|
||||
<Tooltip
|
||||
title={
|
||||
displayLatency < 0
|
||||
? "No recent workspace connections have been made"
|
||||
: "The average latency of user connections to workspaces"
|
||||
}
|
||||
>
|
||||
<div css={styles.value}>
|
||||
<GaugeIcon className="size-icon-xs" />
|
||||
{displayLatency > 0 ? `${displayLatency?.toFixed(2)} ms` : "-"}
|
||||
</div>
|
||||
</Tooltip>
|
||||
<TooltipProvider delayDuration={100}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div css={styles.value}>
|
||||
<GaugeIcon className="size-icon-xs" />
|
||||
{displayLatency > 0
|
||||
? `${displayLatency?.toFixed(2)} ms`
|
||||
: "-"}
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
{displayLatency < 0
|
||||
? "No recent workspace connections have been made"
|
||||
: "The average latency of user connections to workspaces"}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -232,53 +296,75 @@ export const DeploymentBannerView: FC<DeploymentBannerViewProps> = ({
|
||||
<div css={styles.category}>Active Connections</div>
|
||||
|
||||
<div css={styles.values}>
|
||||
<Tooltip title="VS Code Editors with the Coder Remote Extension">
|
||||
<div css={styles.value}>
|
||||
<VSCodeIcon
|
||||
css={css`
|
||||
<TooltipProvider delayDuration={100}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div css={styles.value}>
|
||||
<VSCodeIcon
|
||||
css={css`
|
||||
& * {
|
||||
fill: currentColor;
|
||||
}
|
||||
`}
|
||||
/>
|
||||
{typeof stats?.session_count.vscode === "undefined"
|
||||
? "-"
|
||||
: stats?.session_count.vscode}
|
||||
</div>
|
||||
</Tooltip>
|
||||
/>
|
||||
{typeof stats?.session_count.vscode === "undefined"
|
||||
? "-"
|
||||
: stats?.session_count.vscode}
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
VS Code Editors with the Coder Remote Extension
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
<ValueSeparator />
|
||||
<Tooltip title="JetBrains Editors">
|
||||
<div css={styles.value}>
|
||||
<JetBrainsIcon
|
||||
css={css`
|
||||
<TooltipProvider delayDuration={100}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div css={styles.value}>
|
||||
<JetBrainsIcon
|
||||
css={css`
|
||||
& * {
|
||||
fill: currentColor;
|
||||
}
|
||||
`}
|
||||
/>
|
||||
{typeof stats?.session_count.jetbrains === "undefined"
|
||||
? "-"
|
||||
: stats?.session_count.jetbrains}
|
||||
</div>
|
||||
</Tooltip>
|
||||
/>
|
||||
{typeof stats?.session_count.jetbrains === "undefined"
|
||||
? "-"
|
||||
: stats?.session_count.jetbrains}
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>JetBrains Editors</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
<ValueSeparator />
|
||||
<Tooltip title="SSH Sessions">
|
||||
<div css={styles.value}>
|
||||
<TerminalIcon />
|
||||
{typeof stats?.session_count.ssh === "undefined"
|
||||
? "-"
|
||||
: stats?.session_count.ssh}
|
||||
</div>
|
||||
</Tooltip>
|
||||
<TooltipProvider delayDuration={100}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div css={styles.value}>
|
||||
<TerminalIcon />
|
||||
{typeof stats?.session_count.ssh === "undefined"
|
||||
? "-"
|
||||
: stats?.session_count.ssh}
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>SSH Sessions</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
<ValueSeparator />
|
||||
<Tooltip title="Web Terminal Sessions">
|
||||
<div css={styles.value}>
|
||||
<AppWindowIcon className="size-icon-xs" />
|
||||
{typeof stats?.session_count.reconnecting_pty === "undefined"
|
||||
? "-"
|
||||
: stats?.session_count.reconnecting_pty}
|
||||
</div>
|
||||
</Tooltip>
|
||||
<TooltipProvider delayDuration={100}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div css={styles.value}>
|
||||
<AppWindowIcon className="size-icon-xs" />
|
||||
{typeof stats?.session_count.reconnecting_pty === "undefined"
|
||||
? "-"
|
||||
: stats?.session_count.reconnecting_pty}
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Web Terminal Sessions</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -291,18 +377,28 @@ export const DeploymentBannerView: FC<DeploymentBannerViewProps> = ({
|
||||
gap: 16,
|
||||
}}
|
||||
>
|
||||
<Tooltip title="The last time stats were aggregated. Workspaces report statistics periodically, so it may take a bit for these to update!">
|
||||
<div css={styles.value}>
|
||||
<GitCompareArrowsIcon className="size-icon-xs" />
|
||||
{lastAggregated}
|
||||
</div>
|
||||
</Tooltip>
|
||||
<TooltipProvider delayDuration={100}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div css={styles.value}>
|
||||
<GitCompareArrowsIcon className="size-icon-xs" />
|
||||
{lastAggregated}
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="max-w-xs">
|
||||
The last time stats were aggregated. Workspaces report statistics
|
||||
periodically, so it may take a bit for these to update!
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
|
||||
<Tooltip title="A countdown until stats are fetched again. Click to refresh!">
|
||||
<Button
|
||||
css={[
|
||||
styles.value,
|
||||
css`
|
||||
<TooltipProvider delayDuration={100}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
css={[
|
||||
styles.value,
|
||||
css`
|
||||
margin: 0;
|
||||
padding: 0 8px;
|
||||
height: unset;
|
||||
@@ -317,18 +413,23 @@ export const DeploymentBannerView: FC<DeploymentBannerViewProps> = ({
|
||||
margin-right: 4px;
|
||||
}
|
||||
`,
|
||||
]}
|
||||
onClick={() => {
|
||||
if (fetchStats) {
|
||||
fetchStats();
|
||||
}
|
||||
}}
|
||||
variant="subtle"
|
||||
>
|
||||
<RotateCwIcon />
|
||||
{timeUntilRefresh}s
|
||||
</Button>
|
||||
</Tooltip>
|
||||
]}
|
||||
onClick={() => {
|
||||
if (fetchStats) {
|
||||
fetchStats();
|
||||
}
|
||||
}}
|
||||
variant="subtle"
|
||||
>
|
||||
<RotateCwIcon />
|
||||
{timeUntilRefresh}s
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
A countdown until stats are fetched again. Click to refresh!
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -352,17 +453,22 @@ const WorkspaceBuildValue: FC<WorkspaceBuildValueProps> = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<Tooltip title={`${statusText} Workspaces`}>
|
||||
<Link
|
||||
component={RouterLink}
|
||||
to={`/workspaces?filter=${encodeURIComponent(`status:${status}`)}`}
|
||||
>
|
||||
<div css={styles.value}>
|
||||
{icon}
|
||||
{typeof count === "undefined" ? "-" : count}
|
||||
</div>
|
||||
</Link>
|
||||
</Tooltip>
|
||||
<TooltipProvider delayDuration={100}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Link
|
||||
component={RouterLink}
|
||||
to={`/workspaces?filter=${encodeURIComponent(`status:${status}`)}`}
|
||||
>
|
||||
<div css={styles.value}>
|
||||
{icon}
|
||||
{typeof count === "undefined" ? "-" : count}
|
||||
</div>
|
||||
</Link>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>{`${statusText} Workspaces`}</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -6,11 +6,16 @@ import {
|
||||
} from "@emotion/react";
|
||||
import Divider from "@mui/material/Divider";
|
||||
import MenuItem from "@mui/material/MenuItem";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import { PopoverClose } from "@radix-ui/react-popover";
|
||||
import type * as TypesGen from "api/typesGenerated";
|
||||
import { CopyButton } from "components/CopyButton/CopyButton";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "components/Tooltip/Tooltip";
|
||||
import {
|
||||
CircleUserIcon,
|
||||
LogOutIcon,
|
||||
@@ -102,24 +107,34 @@ export const UserDropdownContent: FC<UserDropdownContentProps> = ({
|
||||
<Divider css={{ marginBottom: "0 !important" }} />
|
||||
|
||||
<Stack css={styles.info} spacing={0}>
|
||||
<Tooltip title="Browse the source code">
|
||||
<a
|
||||
css={[styles.footerText, styles.buildInfo]}
|
||||
href={buildInfo?.external_url}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
{buildInfo?.version} <SquareArrowOutUpRightIcon />
|
||||
</a>
|
||||
</Tooltip>
|
||||
<TooltipProvider delayDuration={100}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<a
|
||||
css={[styles.footerText, styles.buildInfo]}
|
||||
href={buildInfo?.external_url}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
{buildInfo?.version} <SquareArrowOutUpRightIcon />
|
||||
</a>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Browse the source code</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
|
||||
{buildInfo?.deployment_id && (
|
||||
<div className="flex items-center text-xs">
|
||||
<Tooltip title="Deployment Identifier">
|
||||
<span className="whitespace-nowrap overflow-hidden text-ellipsis">
|
||||
{buildInfo.deployment_id}
|
||||
</span>
|
||||
</Tooltip>
|
||||
<TooltipProvider delayDuration={100}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span className="whitespace-nowrap overflow-hidden text-ellipsis">
|
||||
{buildInfo.deployment_id}
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Deployment Identifier</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
<CopyButton
|
||||
text={buildInfo.deployment_id}
|
||||
label="Copy deployment ID"
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
import { useTheme } from "@emotion/react";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import type { HealthMessage, ProvisionerDaemon } from "api/typesGenerated";
|
||||
import { Pill } from "components/Pill/Pill";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "components/Tooltip/Tooltip";
|
||||
import { Building2Icon, UserIcon } from "lucide-react";
|
||||
import type { FC } from "react";
|
||||
import { createDayString } from "utils/createDayString";
|
||||
@@ -74,17 +79,22 @@ export const Provisioner: FC<ProvisionerProps> = ({
|
||||
justifyContent: "right",
|
||||
}}
|
||||
>
|
||||
<Tooltip title="Scope">
|
||||
<Pill size="lg" icon={iconScope}>
|
||||
<span
|
||||
css={{
|
||||
":first-letter": { textTransform: "uppercase" },
|
||||
}}
|
||||
>
|
||||
{daemonScope}
|
||||
</span>
|
||||
</Pill>
|
||||
</Tooltip>
|
||||
<TooltipProvider delayDuration={100}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Pill size="lg" icon={iconScope}>
|
||||
<span
|
||||
css={{
|
||||
":first-letter": { textTransform: "uppercase" },
|
||||
}}
|
||||
>
|
||||
{daemonScope}
|
||||
</span>
|
||||
</Pill>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Scope</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
{extraTags.map(([key, value]) => (
|
||||
<ProvisionerTag key={key} tagName={key} tagValue={value} />
|
||||
))}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import Skeleton from "@mui/material/Skeleton";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import { watchAgentMetadata } from "api/api";
|
||||
import type {
|
||||
ServerSentEvent,
|
||||
@@ -8,6 +7,12 @@ import type {
|
||||
} from "api/typesGenerated";
|
||||
import { displayError } from "components/GlobalSnackbar/utils";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "components/Tooltip/Tooltip";
|
||||
import dayjs from "dayjs";
|
||||
import {
|
||||
type FC,
|
||||
@@ -183,11 +188,18 @@ const MetadataItem: FC<MetadataItemProps> = ({ item }) => {
|
||||
status === "loading" ? (
|
||||
<Skeleton width={65} height={12} variant="text" className="mt-[6px]" />
|
||||
) : status === "stale" ? (
|
||||
<Tooltip title="This data is stale and no longer up to date">
|
||||
<StaticWidth className="text-ellipsis overflow-hidden whitespace-nowrap max-w-64 text-sm text-content-disabled cursor-pointer">
|
||||
{item.result.value}
|
||||
</StaticWidth>
|
||||
</Tooltip>
|
||||
<TooltipProvider delayDuration={100}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<StaticWidth className="text-ellipsis overflow-hidden whitespace-nowrap max-w-64 text-sm text-content-disabled cursor-pointer">
|
||||
{item.result.value}
|
||||
</StaticWidth>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
This data is stale and no longer up to date
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
) : (
|
||||
<StaticWidth
|
||||
className={cn(
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { Interpolation, Theme } from "@emotion/react";
|
||||
import Link from "@mui/material/Link";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import type {
|
||||
WorkspaceAgent,
|
||||
WorkspaceAgentDevcontainer,
|
||||
@@ -13,6 +12,12 @@ import {
|
||||
HelpTooltipTitle,
|
||||
HelpTooltipTrigger,
|
||||
} from "components/HelpTooltip/HelpTooltip";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "components/Tooltip/Tooltip";
|
||||
import { TriangleAlertIcon } from "lucide-react";
|
||||
import type { FC } from "react";
|
||||
|
||||
@@ -35,13 +40,18 @@ const ReadyLifecycle: FC = () => {
|
||||
|
||||
const StartingLifecycle: FC = () => {
|
||||
return (
|
||||
<Tooltip title="Starting...">
|
||||
<div
|
||||
role="status"
|
||||
aria-label="Starting..."
|
||||
css={[styles.status, styles.connecting]}
|
||||
/>
|
||||
</Tooltip>
|
||||
<TooltipProvider delayDuration={100}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div
|
||||
role="status"
|
||||
aria-label="Starting..."
|
||||
css={[styles.status, styles.connecting]}
|
||||
/>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Starting...</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -110,13 +120,18 @@ const StartErrorLifecycle: FC<AgentStatusProps> = ({ agent }) => {
|
||||
|
||||
const ShuttingDownLifecycle: FC = () => {
|
||||
return (
|
||||
<Tooltip title="Stopping...">
|
||||
<div
|
||||
role="status"
|
||||
aria-label="Stopping..."
|
||||
css={[styles.status, styles.connecting]}
|
||||
/>
|
||||
</Tooltip>
|
||||
<TooltipProvider delayDuration={100}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div
|
||||
role="status"
|
||||
aria-label="Stopping..."
|
||||
css={[styles.status, styles.connecting]}
|
||||
/>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Stopping...</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -170,13 +185,18 @@ const ShutdownErrorLifecycle: FC<AgentStatusProps> = ({ agent }) => {
|
||||
|
||||
const OffLifecycle: FC = () => {
|
||||
return (
|
||||
<Tooltip title="Stopped">
|
||||
<div
|
||||
role="status"
|
||||
aria-label="Stopped"
|
||||
css={[styles.status, styles.disconnected]}
|
||||
/>
|
||||
</Tooltip>
|
||||
<TooltipProvider delayDuration={100}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div
|
||||
role="status"
|
||||
aria-label="Stopped"
|
||||
css={[styles.status, styles.disconnected]}
|
||||
/>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Stopped</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -218,25 +238,35 @@ const ConnectedStatus: FC<AgentStatusProps> = ({ agent }) => {
|
||||
|
||||
const DisconnectedStatus: FC = () => {
|
||||
return (
|
||||
<Tooltip title="Disconnected">
|
||||
<div
|
||||
role="status"
|
||||
aria-label="Disconnected"
|
||||
css={[styles.status, styles.disconnected]}
|
||||
/>
|
||||
</Tooltip>
|
||||
<TooltipProvider delayDuration={100}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div
|
||||
role="status"
|
||||
aria-label="Disconnected"
|
||||
css={[styles.status, styles.disconnected]}
|
||||
/>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Disconnected</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
);
|
||||
};
|
||||
|
||||
const ConnectingStatus: FC = () => {
|
||||
return (
|
||||
<Tooltip title="Connecting...">
|
||||
<div
|
||||
role="status"
|
||||
aria-label="Connecting..."
|
||||
css={[styles.status, styles.connecting]}
|
||||
/>
|
||||
</Tooltip>
|
||||
<TooltipProvider delayDuration={100}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div
|
||||
role="status"
|
||||
aria-label="Connecting..."
|
||||
css={[styles.status, styles.connecting]}
|
||||
/>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Connecting...</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import type * as TypesGen from "api/typesGenerated";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "components/Tooltip/Tooltip";
|
||||
import {
|
||||
Building2Icon,
|
||||
GlobeIcon,
|
||||
@@ -14,30 +19,50 @@ interface ShareIconProps {
|
||||
export const ShareIcon = ({ app }: ShareIconProps) => {
|
||||
if (app.external) {
|
||||
return (
|
||||
<Tooltip title="Open external URL">
|
||||
<SquareArrowOutUpRightIcon />
|
||||
</Tooltip>
|
||||
<TooltipProvider delayDuration={100}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<SquareArrowOutUpRightIcon />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Open external URL</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
);
|
||||
}
|
||||
if (app.sharing_level === "authenticated") {
|
||||
return (
|
||||
<Tooltip title="Shared with all authenticated users">
|
||||
<UsersIcon />
|
||||
</Tooltip>
|
||||
<TooltipProvider delayDuration={100}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<UsersIcon />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Shared with all authenticated users</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
);
|
||||
}
|
||||
if (app.sharing_level === "organization") {
|
||||
return (
|
||||
<Tooltip title="Shared with organization members">
|
||||
<Building2Icon />
|
||||
</Tooltip>
|
||||
<TooltipProvider delayDuration={100}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Building2Icon />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Shared with organization members</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
);
|
||||
}
|
||||
if (app.sharing_level === "public") {
|
||||
return (
|
||||
<Tooltip title="Shared publicly">
|
||||
<GlobeIcon />
|
||||
</Tooltip>
|
||||
<TooltipProvider delayDuration={100}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<GlobeIcon />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Shared publicly</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
import type { Interpolation, Theme } from "@emotion/react";
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import type { WorkspaceAgent, WorkspaceResource } from "api/typesGenerated";
|
||||
import { CopyableValue } from "components/CopyableValue/CopyableValue";
|
||||
import { DropdownArrow } from "components/DropdownArrow/DropdownArrow";
|
||||
import { MemoizedInlineMarkdown } from "components/Markdown/Markdown";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "components/Tooltip/Tooltip";
|
||||
import { Children, type FC, type JSX, useState } from "react";
|
||||
import { ResourceAvatar } from "./ResourceAvatar";
|
||||
import { SensitiveValue } from "./SensitiveValue";
|
||||
@@ -162,20 +167,28 @@ export const ResourceCard: FC<ResourceCardProps> = ({ resource, agentRow }) => {
|
||||
})}
|
||||
</div>
|
||||
{mLength > 4 && (
|
||||
<Tooltip
|
||||
title={
|
||||
shouldDisplayAllMetadata ? "Hide metadata" : "Show all metadata"
|
||||
}
|
||||
>
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
setShouldDisplayAllMetadata((value) => !value);
|
||||
}}
|
||||
size="large"
|
||||
>
|
||||
<DropdownArrow margin={false} close={shouldDisplayAllMetadata} />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<TooltipProvider delayDuration={100}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
setShouldDisplayAllMetadata((value) => !value);
|
||||
}}
|
||||
size="large"
|
||||
>
|
||||
<DropdownArrow
|
||||
margin={false}
|
||||
close={shouldDisplayAllMetadata}
|
||||
/>
|
||||
</IconButton>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
{shouldDisplayAllMetadata
|
||||
? "Hide metadata"
|
||||
: "Show all metadata"}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
)}
|
||||
</Stack>
|
||||
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
import { css, type Interpolation, type Theme } from "@emotion/react";
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import { CopyableValue } from "components/CopyableValue/CopyableValue";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "components/Tooltip/Tooltip";
|
||||
import { EyeIcon, EyeOffIcon } from "lucide-react";
|
||||
import { type FC, useState } from "react";
|
||||
|
||||
@@ -35,18 +40,23 @@ export const SensitiveValue: FC<SensitiveValueProps> = ({ value }) => {
|
||||
<CopyableValue value={value} css={styles.value}>
|
||||
{displayValue}
|
||||
</CopyableValue>
|
||||
<Tooltip title={buttonLabel}>
|
||||
<IconButton
|
||||
css={styles.button}
|
||||
onClick={() => {
|
||||
setShouldDisplay((value) => !value);
|
||||
}}
|
||||
size="small"
|
||||
aria-label={buttonLabel}
|
||||
>
|
||||
{icon}
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<TooltipProvider delayDuration={100}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<IconButton
|
||||
css={styles.button}
|
||||
onClick={() => {
|
||||
setShouldDisplay((value) => !value);
|
||||
}}
|
||||
size="small"
|
||||
aria-label={buttonLabel}
|
||||
>
|
||||
{icon}
|
||||
</IconButton>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>{buttonLabel}</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
import { type Interpolation, type Theme, useTheme } from "@emotion/react";
|
||||
import Skeleton from "@mui/material/Skeleton";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import type { WorkspaceBuild } from "api/typesGenerated";
|
||||
import { BuildIcon } from "components/BuildIcon/BuildIcon";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "components/Tooltip/Tooltip";
|
||||
import { InfoIcon } from "lucide-react";
|
||||
import { createDayString } from "utils/createDayString";
|
||||
import {
|
||||
@@ -45,14 +50,21 @@ export const WorkspaceBuildData = ({ build }: { build: WorkspaceBuild }) => {
|
||||
</span>
|
||||
{!systemBuildReasons.includes(build.reason) &&
|
||||
build.transition === "start" && (
|
||||
<Tooltip title={buildReasonLabels[build.reason]}>
|
||||
<InfoIcon
|
||||
css={(theme) => ({
|
||||
color: theme.palette.info.light,
|
||||
})}
|
||||
className="size-icon-xs -mt-px"
|
||||
/>
|
||||
</Tooltip>
|
||||
<TooltipProvider delayDuration={100}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<InfoIcon
|
||||
css={(theme) => ({
|
||||
color: theme.palette.info.light,
|
||||
})}
|
||||
className="size-icon-xs -mt-px"
|
||||
/>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
{buildReasonLabels[build.reason]}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import type { Workspace } from "api/typesGenerated";
|
||||
import { Badge } from "components/Badge/Badge";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "components/Tooltip/Tooltip";
|
||||
import type { FC } from "react";
|
||||
import {
|
||||
DATE_FORMAT,
|
||||
@@ -16,34 +21,36 @@ export const WorkspaceDormantBadge: FC<WorkspaceDormantBadgeProps> = ({
|
||||
workspace,
|
||||
}) => {
|
||||
return workspace.deleting_at ? (
|
||||
<Tooltip
|
||||
title={
|
||||
<>
|
||||
<TooltipProvider delayDuration={100}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Badge role="status" variant="destructive" size="xs">
|
||||
Deletion Pending
|
||||
</Badge>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="max-w-xs">
|
||||
This workspace has not been used for{" "}
|
||||
{relativeTimeWithoutSuffix(workspace.last_used_at)} and has been
|
||||
marked dormant. It is scheduled to be deleted on{" "}
|
||||
{formatDateTime(workspace.deleting_at, DATE_FORMAT.FULL_DATETIME)}.
|
||||
</>
|
||||
}
|
||||
>
|
||||
<Badge role="status" variant="destructive" size="xs">
|
||||
Deletion Pending
|
||||
</Badge>
|
||||
</Tooltip>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
) : (
|
||||
<Tooltip
|
||||
title={
|
||||
<>
|
||||
<TooltipProvider delayDuration={100}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Badge role="status" variant="warning" size="xs">
|
||||
Dormant
|
||||
</Badge>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="max-w-xs">
|
||||
This workspace has not been used for{" "}
|
||||
{relativeTimeWithoutSuffix(workspace.last_used_at)} and has been
|
||||
marked dormant. It is not scheduled for auto-deletion but will become
|
||||
a candidate if auto-deletion is enabled on this template.
|
||||
</>
|
||||
}
|
||||
>
|
||||
<Badge role="status" variant="warning" size="xs">
|
||||
Dormant
|
||||
</Badge>
|
||||
</Tooltip>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import type { CSSObject, Interpolation, Theme } from "@emotion/react";
|
||||
import Collapse from "@mui/material/Collapse";
|
||||
import Link from "@mui/material/Link";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import type { AuditLog, BuildReason } from "api/typesGenerated";
|
||||
import { Avatar } from "components/Avatar/Avatar";
|
||||
import { DropdownArrow } from "components/DropdownArrow/DropdownArrow";
|
||||
@@ -9,6 +8,12 @@ import { Stack } from "components/Stack/Stack";
|
||||
import { StatusPill } from "components/StatusPill/StatusPill";
|
||||
import { TableCell } from "components/Table/Table";
|
||||
import { TimelineEntry } from "components/Timeline/TimelineEntry";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "components/Tooltip/Tooltip";
|
||||
import { InfoIcon, NetworkIcon } from "lucide-react";
|
||||
import { type FC, useState } from "react";
|
||||
import { Link as RouterLink } from "react-router";
|
||||
@@ -126,69 +131,76 @@ export const AuditLogRow: FC<AuditLogRowProps> = ({
|
||||
<StatusPill isHttpCode={true} code={auditLog.status_code} />
|
||||
|
||||
{/* With multi-org, there is not enough space so show
|
||||
everything in a tooltip. */}
|
||||
everything in a tooltip. */}
|
||||
{showOrgDetails ? (
|
||||
<Tooltip
|
||||
title={
|
||||
<div css={styles.auditLogInfoTooltip}>
|
||||
{auditLog.ip && (
|
||||
<div>
|
||||
<h4 css={styles.auditLogInfoHeader}>IP:</h4>
|
||||
<div>{auditLog.ip}</div>
|
||||
</div>
|
||||
)}
|
||||
{userAgent?.os.name && (
|
||||
<div>
|
||||
<h4 css={styles.auditLogInfoHeader}>OS:</h4>
|
||||
<div>{userAgent.os.name}</div>
|
||||
</div>
|
||||
)}
|
||||
{userAgent?.browser.name && (
|
||||
<div>
|
||||
<h4 css={styles.auditLogInfoHeader}>Browser:</h4>
|
||||
<TooltipProvider delayDuration={100}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<InfoIcon
|
||||
css={(theme) => ({
|
||||
color: theme.palette.info.light,
|
||||
})}
|
||||
/>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<div css={styles.auditLogInfoTooltip}>
|
||||
{auditLog.ip && (
|
||||
<div>
|
||||
{userAgent.browser.name}{" "}
|
||||
{userAgent.browser.version}
|
||||
<h4 css={styles.auditLogInfoHeader}>IP:</h4>
|
||||
<div>{auditLog.ip}</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{auditLog.organization && (
|
||||
<div>
|
||||
<h4 css={styles.auditLogInfoHeader}>
|
||||
Organization:
|
||||
</h4>
|
||||
<Link
|
||||
component={RouterLink}
|
||||
to={`/organizations/${auditLog.organization.name}`}
|
||||
>
|
||||
{auditLog.organization.display_name ||
|
||||
auditLog.organization.name}
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
{auditLog.additional_fields?.build_reason &&
|
||||
auditLog.action === "start" && (
|
||||
)}
|
||||
{userAgent?.os.name && (
|
||||
<div>
|
||||
<h4 css={styles.auditLogInfoHeader}>Reason:</h4>
|
||||
<h4 css={styles.auditLogInfoHeader}>OS:</h4>
|
||||
<div>{userAgent.os.name}</div>
|
||||
</div>
|
||||
)}
|
||||
{userAgent?.browser.name && (
|
||||
<div>
|
||||
<h4 css={styles.auditLogInfoHeader}>
|
||||
Browser:
|
||||
</h4>
|
||||
<div>
|
||||
{
|
||||
buildReasonLabels[
|
||||
auditLog.additional_fields
|
||||
.build_reason as BuildReason
|
||||
]
|
||||
}
|
||||
{userAgent.browser.name}{" "}
|
||||
{userAgent.browser.version}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<InfoIcon
|
||||
css={(theme) => ({
|
||||
color: theme.palette.info.light,
|
||||
})}
|
||||
/>
|
||||
</Tooltip>
|
||||
{auditLog.organization && (
|
||||
<div>
|
||||
<h4 css={styles.auditLogInfoHeader}>
|
||||
Organization:
|
||||
</h4>
|
||||
<Link
|
||||
component={RouterLink}
|
||||
to={`/organizations/${auditLog.organization.name}`}
|
||||
>
|
||||
{auditLog.organization.display_name ||
|
||||
auditLog.organization.name}
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
{auditLog.additional_fields?.build_reason &&
|
||||
auditLog.action === "start" && (
|
||||
<div>
|
||||
<h4 css={styles.auditLogInfoHeader}>
|
||||
Reason:
|
||||
</h4>
|
||||
<div>
|
||||
{
|
||||
buildReasonLabels[
|
||||
auditLog.additional_fields
|
||||
.build_reason as BuildReason
|
||||
]
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
) : (
|
||||
<Stack direction="row" spacing={1} alignItems="baseline">
|
||||
{auditLog.ip && (
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
import type { CSSObject, Interpolation, Theme } from "@emotion/react";
|
||||
import Link from "@mui/material/Link";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import type { ConnectionLog } from "api/typesGenerated";
|
||||
import { Avatar } from "components/Avatar/Avatar";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import { StatusPill } from "components/StatusPill/StatusPill";
|
||||
import { TableCell } from "components/Table/Table";
|
||||
import { TimelineEntry } from "components/Timeline/TimelineEntry";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "components/Tooltip/Tooltip";
|
||||
import { InfoIcon, NetworkIcon } from "lucide-react";
|
||||
import type { FC } from "react";
|
||||
import { Link as RouterLink } from "react-router";
|
||||
@@ -87,52 +92,71 @@ export const ConnectionLogRow: FC<ConnectionLogRowProps> = ({
|
||||
label={isWeb ? "HTTP Status Code" : "SSH Exit Code"}
|
||||
/>
|
||||
)}
|
||||
<Tooltip
|
||||
title={
|
||||
<div css={styles.connectionLogInfoTooltip}>
|
||||
{connectionLog.ip && (
|
||||
<div>
|
||||
<h4 css={styles.connectionLogInfoheader}>IP:</h4>
|
||||
<div>{connectionLog.ip}</div>
|
||||
</div>
|
||||
)}
|
||||
{userAgent?.os.name && (
|
||||
<div>
|
||||
<h4 css={styles.connectionLogInfoheader}>OS:</h4>
|
||||
<div>{userAgent.os.name}</div>
|
||||
</div>
|
||||
)}
|
||||
{userAgent?.browser.name && (
|
||||
<div>
|
||||
<h4 css={styles.connectionLogInfoheader}>Browser:</h4>
|
||||
<TooltipProvider delayDuration={100}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<InfoIcon
|
||||
css={(theme) => ({
|
||||
color: theme.palette.info.light,
|
||||
})}
|
||||
/>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<div css={styles.connectionLogInfoTooltip}>
|
||||
{connectionLog.ip && (
|
||||
<div>
|
||||
{userAgent.browser.name} {userAgent.browser.version}
|
||||
<h4 css={styles.connectionLogInfoheader}>IP:</h4>
|
||||
<div>{connectionLog.ip}</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{connectionLog.organization && (
|
||||
<div>
|
||||
<h4 css={styles.connectionLogInfoheader}>
|
||||
Organization:
|
||||
</h4>
|
||||
<Link
|
||||
component={RouterLink}
|
||||
to={`/organizations/${connectionLog.organization.name}`}
|
||||
>
|
||||
{connectionLog.organization.display_name ||
|
||||
connectionLog.organization.name}
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
{connectionLog.ssh_info?.disconnect_reason && (
|
||||
<div>
|
||||
<h4 css={styles.connectionLogInfoheader}>
|
||||
Close Reason:
|
||||
</h4>
|
||||
<div>{connectionLog.ssh_info?.disconnect_reason}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{userAgent?.os.name && (
|
||||
<div>
|
||||
<h4 css={styles.connectionLogInfoheader}>OS:</h4>
|
||||
<div>{userAgent.os.name}</div>
|
||||
</div>
|
||||
)}
|
||||
{userAgent?.browser.name && (
|
||||
<div>
|
||||
<h4 css={styles.connectionLogInfoheader}>
|
||||
Browser:
|
||||
</h4>
|
||||
<div>
|
||||
{userAgent.browser.name}{" "}
|
||||
{userAgent.browser.version}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{connectionLog.organization && (
|
||||
<div>
|
||||
<h4 css={styles.connectionLogInfoheader}>
|
||||
Organization:
|
||||
</h4>
|
||||
<Link
|
||||
component={RouterLink}
|
||||
to={`/organizations/${connectionLog.organization.name}`}
|
||||
>
|
||||
{connectionLog.organization.display_name ||
|
||||
connectionLog.organization.name}
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
{connectionLog.ssh_info?.disconnect_reason && (
|
||||
<div>
|
||||
<h4 css={styles.connectionLogInfoheader}>
|
||||
Close Reason:
|
||||
</h4>
|
||||
<div>
|
||||
{connectionLog.ssh_info?.disconnect_reason}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
{/* <Tooltip
|
||||
title={
|
||||
|
||||
}
|
||||
>
|
||||
<InfoIcon
|
||||
@@ -140,7 +164,7 @@ export const ConnectionLogRow: FC<ConnectionLogRowProps> = ({
|
||||
color: theme.palette.info.light,
|
||||
})}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Tooltip> */}
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
+26
-13
@@ -1,7 +1,6 @@
|
||||
import { type Interpolation, type Theme, useTheme } from "@emotion/react";
|
||||
import MuiLink from "@mui/material/Link";
|
||||
import Skeleton from "@mui/material/Skeleton";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import type { GetLicensesResponse } from "api/api";
|
||||
import type { Feature, UserStatusChangeCount } from "api/typesGenerated";
|
||||
import { Button } from "components/Button/Button";
|
||||
@@ -12,6 +11,12 @@ import {
|
||||
} from "components/SettingsHeader/SettingsHeader";
|
||||
import { Spinner } from "components/Spinner/Spinner";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "components/Tooltip/Tooltip";
|
||||
import { useWindowSize } from "hooks/useWindowSize";
|
||||
import { PlusIcon, RotateCwIcon } from "lucide-react";
|
||||
import type { FC } from "react";
|
||||
@@ -80,18 +85,26 @@ const LicensesSettingsPageView: FC<Props> = ({
|
||||
Add a license
|
||||
</Link>
|
||||
</Button>
|
||||
<Tooltip title="Refresh license entitlements. This is done automatically every 10 minutes.">
|
||||
<Button
|
||||
disabled={isRefreshing}
|
||||
onClick={refreshEntitlements}
|
||||
variant="outline"
|
||||
>
|
||||
<Spinner loading={isRefreshing}>
|
||||
<RotateCwIcon className="size-icon-xs" />
|
||||
</Spinner>
|
||||
Refresh
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<TooltipProvider delayDuration={100}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
disabled={isRefreshing}
|
||||
onClick={refreshEntitlements}
|
||||
variant="outline"
|
||||
>
|
||||
<Spinner loading={isRefreshing}>
|
||||
<RotateCwIcon className="size-icon-xs" />
|
||||
</Spinner>
|
||||
Refresh
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="max-w-xs">
|
||||
Refresh license entitlements. This is done automatically every
|
||||
10 minutes.
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import ListItem from "@mui/material/ListItem";
|
||||
import ListItemText, { listItemTextClasses } from "@mui/material/ListItemText";
|
||||
import ToggleButton from "@mui/material/ToggleButton";
|
||||
import ToggleButtonGroup from "@mui/material/ToggleButtonGroup";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import { getErrorMessage } from "api/errors";
|
||||
import {
|
||||
type selectTemplatesByGroup,
|
||||
@@ -17,6 +16,12 @@ import { Alert } from "components/Alert/Alert";
|
||||
import { Button } from "components/Button/Button";
|
||||
import { displayError, displaySuccess } from "components/GlobalSnackbar/utils";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "components/Tooltip/Tooltip";
|
||||
import {
|
||||
castNotificationMethod,
|
||||
methodIcons,
|
||||
@@ -186,23 +191,30 @@ const MethodToggleGroup: FC<MethodToggleGroupProps> = ({
|
||||
const Icon = methodIcons[method];
|
||||
const label = methodLabels[method];
|
||||
return (
|
||||
<Tooltip key={method} title={label}>
|
||||
<ToggleButton
|
||||
value={method}
|
||||
css={styles.toggleButton}
|
||||
onClick={(e) => {
|
||||
// Retain the value if the user clicks the same button, ensuring
|
||||
// at least one value remains selected.
|
||||
if (method === value) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
return;
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Icon aria-label={label} />
|
||||
</ToggleButton>
|
||||
</Tooltip>
|
||||
<Fragment key={method}>
|
||||
<TooltipProvider delayDuration={100}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<ToggleButton
|
||||
value={method}
|
||||
css={styles.toggleButton}
|
||||
onClick={(e) => {
|
||||
// Retain the value if the user clicks the same button, ensuring
|
||||
// at least one value remains selected.
|
||||
if (method === value) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
return;
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Icon aria-label={label} />
|
||||
</ToggleButton>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>{label}</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
</ToggleButtonGroup>
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
import type { Interpolation, Theme } from "@emotion/react";
|
||||
import Link from "@mui/material/Link";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import { TooltipProvider } from "@radix-ui/react-tooltip";
|
||||
import type { ApiErrorResponse } from "api/errors";
|
||||
import type { ExternalAuth, ExternalAuthDevice } from "api/typesGenerated";
|
||||
import { Alert } from "components/Alert/Alert";
|
||||
import { Avatar } from "components/Avatar/Avatar";
|
||||
import { GitDeviceAuth } from "components/GitDeviceAuth/GitDeviceAuth";
|
||||
import { SignInLayout } from "components/SignInLayout/SignInLayout";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "components/Tooltip/Tooltip";
|
||||
import { Welcome } from "components/Welcome/Welcome";
|
||||
import { ExternalLinkIcon, RotateCwIcon } from "lucide-react";
|
||||
import type { FC, ReactNode } from "react";
|
||||
import { type FC, Fragment, type ReactNode } from "react";
|
||||
|
||||
interface ExternalAuthPageViewProps {
|
||||
externalAuth: ExternalAuth;
|
||||
@@ -82,18 +87,25 @@ const ExternalAuthPageView: FC<ExternalAuthPageViewProps> = ({
|
||||
return;
|
||||
}
|
||||
return (
|
||||
<Tooltip key={install.id} title={install.account.login}>
|
||||
<Link
|
||||
href={install.account.profile_url}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<Avatar
|
||||
src={install.account.avatar_url}
|
||||
fallback={install.account.login}
|
||||
/>
|
||||
</Link>
|
||||
</Tooltip>
|
||||
<Fragment key={install.id}>
|
||||
<TooltipProvider delayDuration={100}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Link
|
||||
href={install.account.profile_url}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<Avatar
|
||||
src={install.account.avatar_url}
|
||||
fallback={install.account.login}
|
||||
/>
|
||||
</Link>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>{install.account.login}</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { type Interpolation, type Theme, useTheme } from "@emotion/react";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import type {
|
||||
DERPNodeReport,
|
||||
DERPRegionReport,
|
||||
@@ -7,6 +6,12 @@ import type {
|
||||
HealthSeverity,
|
||||
} from "api/typesGenerated";
|
||||
import { Alert } from "components/Alert/Alert";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "components/Tooltip/Tooltip";
|
||||
import { ChevronLeftIcon, CodeIcon, HashIcon } from "lucide-react";
|
||||
import type { FC } from "react";
|
||||
import { Link, useOutletContext, useParams } from "react-router";
|
||||
@@ -85,19 +90,27 @@ const DERPRegionPage: FC = () => {
|
||||
|
||||
<section>
|
||||
<div css={{ display: "flex", flexWrap: "wrap", gap: 12 }}>
|
||||
<Tooltip title="Region ID">
|
||||
<Pill icon={<HashIcon className="size-icon-sm" />}>
|
||||
{region!.RegionID}
|
||||
</Pill>
|
||||
</Tooltip>
|
||||
<Tooltip title="Region Code">
|
||||
<Pill icon={<CodeIcon className="size-icon-sm" />}>
|
||||
{region!.RegionCode}
|
||||
</Pill>
|
||||
</Tooltip>
|
||||
<BooleanPill value={region!.EmbeddedRelay}>
|
||||
Embedded Relay
|
||||
</BooleanPill>
|
||||
<TooltipProvider delayDuration={100}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Pill icon={<HashIcon className="size-icon-sm" />}>
|
||||
{region!.RegionID}
|
||||
</Pill>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Region ID</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Pill icon={<CodeIcon className="size-icon-sm" />}>
|
||||
{region!.RegionCode}
|
||||
</Pill>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Region Code</TooltipContent>
|
||||
</Tooltip>
|
||||
<BooleanPill value={region!.EmbeddedRelay}>
|
||||
Embedded Relay
|
||||
</BooleanPill>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -127,14 +140,19 @@ const DERPRegionPage: FC = () => {
|
||||
</div>
|
||||
|
||||
<div css={reportStyles.pills}>
|
||||
<Tooltip title="Round trip ping">
|
||||
<Pill
|
||||
css={{ color: latencyColor }}
|
||||
icon={<StatusCircle color={latencyColor} />}
|
||||
>
|
||||
{report.round_trip_ping_ms}ms
|
||||
</Pill>
|
||||
</Tooltip>
|
||||
<TooltipProvider delayDuration={100}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Pill
|
||||
css={{ color: latencyColor }}
|
||||
icon={<StatusCircle color={latencyColor} />}
|
||||
>
|
||||
{report.round_trip_ping_ms}ms
|
||||
</Pill>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Round trip ping</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
<BooleanPill value={report.can_exchange_messages}>
|
||||
Exchange Messages
|
||||
</BooleanPill>
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
import CircularProgress from "@mui/material/CircularProgress";
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import { health, refreshHealth } from "api/queries/debug";
|
||||
import type { HealthSeverity } from "api/typesGenerated";
|
||||
import { ErrorAlert } from "components/Alert/ErrorAlert";
|
||||
import { Loader } from "components/Loader/Loader";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "components/Tooltip/Tooltip";
|
||||
import kebabCase from "lodash/fp/kebabCase";
|
||||
import { BellOffIcon, RotateCcwIcon } from "lucide-react";
|
||||
import { DashboardFullPage } from "modules/dashboard/DashboardLayout";
|
||||
@@ -76,22 +81,27 @@ export const HealthLayout: FC = () => {
|
||||
<div className="flex items-center justify-between">
|
||||
<HealthIcon size={32} severity={healthStatus.severity} />
|
||||
|
||||
<Tooltip title="Refresh health checks">
|
||||
<IconButton
|
||||
size="small"
|
||||
disabled={isRefreshing}
|
||||
data-testid="healthcheck-refresh-button"
|
||||
onClick={() => {
|
||||
forceRefresh();
|
||||
}}
|
||||
>
|
||||
{isRefreshing ? (
|
||||
<CircularProgress size={16} />
|
||||
) : (
|
||||
<RotateCcwIcon className="size-5" />
|
||||
)}
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<TooltipProvider delayDuration={100}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<IconButton
|
||||
size="small"
|
||||
disabled={isRefreshing}
|
||||
data-testid="healthcheck-refresh-button"
|
||||
onClick={() => {
|
||||
forceRefresh();
|
||||
}}
|
||||
>
|
||||
{isRefreshing ? (
|
||||
<CircularProgress size={16} />
|
||||
) : (
|
||||
<RotateCcwIcon className="size-5" />
|
||||
)}
|
||||
</IconButton>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Refresh health checks</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
<div className="font-medium mt-4">
|
||||
{healthStatus.healthy ? "Healthy" : "Unhealthy"}
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
import { useTheme } from "@emotion/react";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import type { HealthcheckReport } from "api/typesGenerated";
|
||||
import { Alert } from "components/Alert/Alert";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "components/Tooltip/Tooltip";
|
||||
import { CodeIcon } from "lucide-react";
|
||||
import { useOutletContext } from "react-router";
|
||||
import { MONOSPACE_FONT_FAMILY } from "theme/constants";
|
||||
@@ -45,11 +50,16 @@ const WebsocketPage = () => {
|
||||
})}
|
||||
|
||||
<section>
|
||||
<Tooltip title="Code">
|
||||
<Pill icon={<CodeIcon className="size-icon-sm" />}>
|
||||
{websocket.code}
|
||||
</Pill>
|
||||
</Tooltip>
|
||||
<TooltipProvider delayDuration={100}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Pill icon={<CodeIcon className="size-icon-sm" />}>
|
||||
{websocket.code}
|
||||
</Pill>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Code</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
import { useTheme } from "@emotion/react";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import type { HealthcheckReport } from "api/typesGenerated";
|
||||
import { Alert } from "components/Alert/Alert";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "components/Tooltip/Tooltip";
|
||||
import { GlobeIcon, HashIcon } from "lucide-react";
|
||||
import type { FC } from "react";
|
||||
import { useOutletContext } from "react-router";
|
||||
@@ -105,33 +110,41 @@ const WorkspaceProxyPage: FC = () => {
|
||||
</div>
|
||||
|
||||
<div css={{ display: "flex", flexWrap: "wrap", gap: 12 }}>
|
||||
{region.wildcard_hostname && (
|
||||
<Tooltip title="Wildcard Hostname">
|
||||
<Pill icon={<GlobeIcon />}>
|
||||
{region.wildcard_hostname}
|
||||
</Pill>
|
||||
</Tooltip>
|
||||
)}
|
||||
{region.version && (
|
||||
<Tooltip title="Version">
|
||||
<Pill icon={<HashIcon className="size-icon-sm" />}>
|
||||
{region.version}
|
||||
</Pill>
|
||||
</Tooltip>
|
||||
)}
|
||||
{region.derp_enabled && (
|
||||
<BooleanPill value={region.derp_enabled}>
|
||||
DERP Enabled
|
||||
</BooleanPill>
|
||||
)}
|
||||
{region.derp_only && (
|
||||
<BooleanPill value={region.derp_only}>
|
||||
DERP Only
|
||||
</BooleanPill>
|
||||
)}
|
||||
{region.deleted && (
|
||||
<BooleanPill value={region.deleted}>Deleted</BooleanPill>
|
||||
)}
|
||||
<TooltipProvider delayDuration={100}>
|
||||
{region.wildcard_hostname && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<Pill icon={<GlobeIcon />}>
|
||||
{region.wildcard_hostname}
|
||||
</Pill>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Wildcard Hostname</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
{region.version && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Pill icon={<HashIcon className="size-icon-sm" />}>
|
||||
{region.version}
|
||||
</Pill>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Version</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
{region.derp_enabled && (
|
||||
<BooleanPill value={region.derp_enabled}>
|
||||
DERP Enabled
|
||||
</BooleanPill>
|
||||
)}
|
||||
{region.derp_only && (
|
||||
<BooleanPill value={region.derp_only}>
|
||||
DERP Only
|
||||
</BooleanPill>
|
||||
)}
|
||||
{region.deleted && (
|
||||
<BooleanPill value={region.deleted}>Deleted</BooleanPill>
|
||||
)}
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ import IconButton from "@mui/material/IconButton";
|
||||
import InputAdornment from "@mui/material/InputAdornment";
|
||||
import Link from "@mui/material/Link";
|
||||
import TextField from "@mui/material/TextField";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import { CopyableValue } from "components/CopyableValue/CopyableValue";
|
||||
import { EmptyState } from "components/EmptyState/EmptyState";
|
||||
import { Margins } from "components/Margins/Margins";
|
||||
@@ -13,6 +12,12 @@ import {
|
||||
PageHeaderTitle,
|
||||
} from "components/PageHeader/PageHeader";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "components/Tooltip/Tooltip";
|
||||
import { SearchIcon, XIcon } from "lucide-react";
|
||||
import { type FC, type ReactNode, useMemo, useState } from "react";
|
||||
import {
|
||||
@@ -76,27 +81,21 @@ const IconsPage: FC = () => {
|
||||
<Margins>
|
||||
<PageHeader
|
||||
actions={
|
||||
<Tooltip
|
||||
placement="bottom-end"
|
||||
title={
|
||||
<p
|
||||
css={{
|
||||
padding: 8,
|
||||
fontSize: 13,
|
||||
lineHeight: 1.5,
|
||||
}}
|
||||
>
|
||||
<TooltipProvider delayDuration={100}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Link href="https://github.com/coder/coder/tree/main/site/static/icon">
|
||||
Suggest an icon
|
||||
</Link>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="max-w-xs">
|
||||
You can suggest a new icon by submitting a Pull Request to our
|
||||
public GitHub repository. Just keep in mind that it should be
|
||||
relevant to many Coder users, and redistributable under a
|
||||
permissive license.
|
||||
</p>
|
||||
}
|
||||
>
|
||||
<Link href="https://github.com/coder/coder/tree/main/site/static/icon">
|
||||
Suggest an icon
|
||||
</Link>
|
||||
</Tooltip>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
}
|
||||
>
|
||||
<PageHeaderTitle>Icons</PageHeaderTitle>
|
||||
@@ -134,14 +133,19 @@ const IconsPage: FC = () => {
|
||||
),
|
||||
endAdornment: searchInputText && (
|
||||
<InputAdornment position="end">
|
||||
<Tooltip title="Clear filter">
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={() => setSearchInputText("")}
|
||||
>
|
||||
<XIcon className="size-icon-xs" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<TooltipProvider delayDuration={100}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={() => setSearchInputText("")}
|
||||
>
|
||||
<XIcon className="size-icon-xs" />
|
||||
</IconButton>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Clear filter</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import Checkbox from "@mui/material/Checkbox";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import type { SlimRole } from "api/typesGenerated";
|
||||
import { Button } from "components/Button/Button";
|
||||
import { CollapsibleSummary } from "components/CollapsibleSummary/CollapsibleSummary";
|
||||
@@ -16,6 +15,12 @@ import {
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "components/Popover/Popover";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "components/Tooltip/Tooltip";
|
||||
import { UserIcon } from "lucide-react";
|
||||
import { type FC, useEffect, useState } from "react";
|
||||
|
||||
@@ -129,68 +134,76 @@ const EnabledEditRolesButton: FC<EditRolesButtonProps> = ({
|
||||
}, [selectedRoleNames]);
|
||||
|
||||
return (
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Tooltip title="Edit user roles">
|
||||
<Button
|
||||
variant="subtle"
|
||||
aria-label="Edit user roles"
|
||||
size="icon"
|
||||
className="text-content-secondary hover:text-content-primary"
|
||||
>
|
||||
<EditSquare />
|
||||
</Button>
|
||||
<TooltipProvider delayDuration={100}>
|
||||
<Popover>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="subtle"
|
||||
aria-label="Edit user roles"
|
||||
size="icon"
|
||||
className="text-content-secondary hover:text-content-primary"
|
||||
>
|
||||
<EditSquare />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Edit user roles</TooltipContent>
|
||||
</Tooltip>
|
||||
</PopoverTrigger>
|
||||
|
||||
<PopoverContent
|
||||
align="start"
|
||||
className="w-96 bg-surface-secondary border-surface-quaternary"
|
||||
>
|
||||
<fieldset
|
||||
className="border-0 m-0 p-0 disabled:opacity-50"
|
||||
disabled={isLoading}
|
||||
title="Available roles"
|
||||
<PopoverContent
|
||||
align="start"
|
||||
className="w-96 bg-surface-secondary border-surface-quaternary"
|
||||
>
|
||||
<div className="flex flex-col gap-4 p-6 w-96">
|
||||
{filteredRoles.map((role) => (
|
||||
<Option
|
||||
key={role.name}
|
||||
onChange={handleChange}
|
||||
isChecked={selectedRoleNames.has(role.name)}
|
||||
value={role.name}
|
||||
name={role.display_name || role.name}
|
||||
description={roleDescriptions[role.name] ?? ""}
|
||||
/>
|
||||
))}
|
||||
{advancedRoles.length > 0 && (
|
||||
<CollapsibleSummary label="advanced" defaultOpen={isAdvancedOpen}>
|
||||
{advancedRoles.map((role) => (
|
||||
<Option
|
||||
key={role.name}
|
||||
onChange={handleChange}
|
||||
isChecked={selectedRoleNames.has(role.name)}
|
||||
value={role.name}
|
||||
name={role.display_name || role.name}
|
||||
description={roleDescriptions[role.name] ?? ""}
|
||||
/>
|
||||
))}
|
||||
</CollapsibleSummary>
|
||||
)}
|
||||
</div>
|
||||
</fieldset>
|
||||
<div className="p-6 border-t-1 border-solid border-border text-sm">
|
||||
<div className="flex gap-4">
|
||||
<UserIcon />
|
||||
<div className="flex flex-col">
|
||||
<strong>Member</strong>
|
||||
<span className="text-xs text-content-secondary">
|
||||
{roleDescriptions.member}
|
||||
</span>
|
||||
<fieldset
|
||||
className="border-0 m-0 p-0 disabled:opacity-50"
|
||||
disabled={isLoading}
|
||||
title="Available roles"
|
||||
>
|
||||
<div className="flex flex-col gap-4 p-6 w-96">
|
||||
{filteredRoles.map((role) => (
|
||||
<Option
|
||||
key={role.name}
|
||||
onChange={handleChange}
|
||||
isChecked={selectedRoleNames.has(role.name)}
|
||||
value={role.name}
|
||||
name={role.display_name || role.name}
|
||||
description={roleDescriptions[role.name] ?? ""}
|
||||
/>
|
||||
))}
|
||||
{advancedRoles.length > 0 && (
|
||||
<CollapsibleSummary
|
||||
label="advanced"
|
||||
defaultOpen={isAdvancedOpen}
|
||||
>
|
||||
{advancedRoles.map((role) => (
|
||||
<Option
|
||||
key={role.name}
|
||||
onChange={handleChange}
|
||||
isChecked={selectedRoleNames.has(role.name)}
|
||||
value={role.name}
|
||||
name={role.display_name || role.name}
|
||||
description={roleDescriptions[role.name] ?? ""}
|
||||
/>
|
||||
))}
|
||||
</CollapsibleSummary>
|
||||
)}
|
||||
</div>
|
||||
</fieldset>
|
||||
<div className="p-6 border-t-1 border-solid border-border text-sm">
|
||||
<div className="flex gap-4">
|
||||
<UserIcon />
|
||||
<div className="flex flex-col">
|
||||
<strong>Member</strong>
|
||||
<span className="text-xs text-content-secondary">
|
||||
{roleDescriptions.member}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</TooltipProvider>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useTheme } from "@emotion/react";
|
||||
import LinearProgress from "@mui/material/LinearProgress";
|
||||
import Link from "@mui/material/Link";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import { entitlements } from "api/queries/entitlements";
|
||||
import {
|
||||
insightsTemplate,
|
||||
@@ -33,6 +32,12 @@ import {
|
||||
} from "components/HelpTooltip/HelpTooltip";
|
||||
import { Loader } from "components/Loader/Loader";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "components/Tooltip/Tooltip";
|
||||
import { useEmbeddedMetadata } from "hooks/useEmbeddedMetadata";
|
||||
import {
|
||||
CircleCheck as CircleCheckIcon,
|
||||
@@ -495,25 +500,29 @@ const TemplateUsagePanel: FC<TemplateUsagePanelProps> = ({
|
||||
{usage.display_name}
|
||||
</div>
|
||||
</div>
|
||||
<Tooltip
|
||||
title={`${Math.floor(percentage)}%`}
|
||||
placement="top"
|
||||
arrow
|
||||
>
|
||||
<LinearProgress
|
||||
value={percentage}
|
||||
variant="determinate"
|
||||
css={{
|
||||
width: "100%",
|
||||
height: 8,
|
||||
backgroundColor: theme.palette.divider,
|
||||
"& .MuiLinearProgress-bar": {
|
||||
backgroundColor: usageColors[i],
|
||||
borderRadius: 999,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
<TooltipProvider delayDuration={100}>
|
||||
{/* TODO: Add `arrow` prop to the tooltip */}
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<LinearProgress
|
||||
value={percentage}
|
||||
variant="determinate"
|
||||
css={{
|
||||
width: "100%",
|
||||
height: 8,
|
||||
backgroundColor: theme.palette.divider,
|
||||
"& .MuiLinearProgress-bar": {
|
||||
backgroundColor: usageColors[i],
|
||||
borderRadius: 999,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side={"top"}>
|
||||
{`${Math.floor(percentage)}%`}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
<Stack
|
||||
spacing={0}
|
||||
css={{
|
||||
@@ -612,12 +621,16 @@ const TemplateParametersUsagePanel: FC<TemplateParametersUsagePanelProps> = ({
|
||||
}}
|
||||
>
|
||||
<div>Value</div>
|
||||
<Tooltip
|
||||
title="The number of workspaces using this value"
|
||||
placement="top"
|
||||
>
|
||||
<div>Count</div>
|
||||
</Tooltip>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div>Count</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top">
|
||||
The number of workspaces using this value
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</ParameterUsageRow>
|
||||
{[...parameter.values]
|
||||
.sort((a, b) => b.count - a.count)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { type Interpolation, type Theme, useTheme } from "@emotion/react";
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import { getErrorDetail, getErrorMessage } from "api/errors";
|
||||
import type {
|
||||
ProvisionerJobLog,
|
||||
@@ -23,6 +22,12 @@ import {
|
||||
} from "components/FullPageLayout/Topbar";
|
||||
import { displayError, displaySuccess } from "components/GlobalSnackbar/utils";
|
||||
import { Loader } from "components/Loader/Loader";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "components/Tooltip/Tooltip";
|
||||
import {
|
||||
ChevronLeftIcon,
|
||||
ExternalLinkIcon,
|
||||
@@ -217,11 +222,16 @@ export const TemplateVersionEditor: FC<TemplateVersionEditorProps> = ({
|
||||
data-testid="topbar"
|
||||
>
|
||||
<div>
|
||||
<Tooltip title="Back to the template">
|
||||
<TopbarIconButton component={RouterLink} to={templateLink}>
|
||||
<ChevronLeftIcon className="size-icon-sm" />
|
||||
</TopbarIconButton>
|
||||
</Tooltip>
|
||||
<TooltipProvider delayDuration={100}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<TopbarIconButton component={RouterLink} to={templateLink}>
|
||||
<ChevronLeftIcon className="size-icon-sm" />
|
||||
</TopbarIconButton>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Back to the template</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
|
||||
<TopbarData>
|
||||
@@ -367,17 +377,22 @@ export const TemplateVersionEditor: FC<TemplateVersionEditorProps> = ({
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Tooltip title="Create File" placement="top">
|
||||
<IconButton
|
||||
aria-label="Create File"
|
||||
onClick={(event) => {
|
||||
setCreateFileOpen(true);
|
||||
event.currentTarget.blur();
|
||||
}}
|
||||
>
|
||||
<PlusIcon className="size-icon-xs" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<TooltipProvider delayDuration={100}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<IconButton
|
||||
aria-label="Create File"
|
||||
onClick={(event) => {
|
||||
setCreateFileOpen(true);
|
||||
event.currentTarget.blur();
|
||||
}}
|
||||
>
|
||||
<PlusIcon className="size-icon-xs" />
|
||||
</IconButton>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top">Create File</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
<CreateFileDialog
|
||||
fileTree={fileTree}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useTheme } from "@emotion/react";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import { TooltipProvider } from "@radix-ui/react-tooltip";
|
||||
import { externalAuthProvider } from "api/queries/externalAuth";
|
||||
import type {
|
||||
ExternalAuthLink,
|
||||
@@ -27,6 +27,11 @@ import {
|
||||
TableRow,
|
||||
} from "components/Table/Table";
|
||||
import { TableEmpty } from "components/TableEmpty/TableEmpty";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "components/Tooltip/Tooltip";
|
||||
import type { ExternalAuthPollingState } from "hooks/useExternalAuth";
|
||||
import { EllipsisVertical, RefreshCcwIcon } from "lucide-react";
|
||||
import { type FC, useCallback, useEffect, useState } from "react";
|
||||
@@ -136,12 +141,16 @@ const ExternalAuthRow: FC<ExternalAuthRowProps> = ({
|
||||
* attempt to authenticate when the token expires.
|
||||
*/}
|
||||
{link?.has_refresh_token && authenticated && (
|
||||
<Tooltip
|
||||
title="Authentication token will automatically refresh when expired."
|
||||
placement="right"
|
||||
>
|
||||
<RefreshCcwIcon className="size-3" />
|
||||
</Tooltip>
|
||||
<TooltipProvider delayDuration={100}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<RefreshCcwIcon className="size-3" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent align={"start"} side={"bottom"}>
|
||||
Authentication token will automatically refresh when expired.
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
)}
|
||||
|
||||
{link?.validate_error && (
|
||||
|
||||
@@ -6,7 +6,6 @@ import ListItem from "@mui/material/ListItem";
|
||||
import ListItemIcon from "@mui/material/ListItemIcon";
|
||||
import ListItemText, { listItemTextClasses } from "@mui/material/ListItemText";
|
||||
import Switch from "@mui/material/Switch";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import {
|
||||
customNotificationTemplates,
|
||||
disableNotification,
|
||||
@@ -23,6 +22,12 @@ import type {
|
||||
import { displayError, displaySuccess } from "components/GlobalSnackbar/utils";
|
||||
import { Loader } from "components/Loader/Loader";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "components/Tooltip/Tooltip";
|
||||
import { useAuthenticated } from "hooks";
|
||||
import {
|
||||
castNotificationMethod,
|
||||
@@ -199,9 +204,16 @@ const NotificationsPage: FC = () => {
|
||||
css={styles.listItemEndIcon}
|
||||
aria-label="Delivery method"
|
||||
>
|
||||
<Tooltip title={`Delivery via ${label}`}>
|
||||
<Icon aria-label={label} />
|
||||
</Tooltip>
|
||||
<TooltipProvider delayDuration={100}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Icon aria-label={label} />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
Delivery via {label}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</ListItemIcon>
|
||||
</ListItem>
|
||||
{!isLastItem && <Divider />}
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import type { Workspace, WorkspaceBuildParameter } from "api/typesGenerated";
|
||||
import { TopbarButton } from "components/FullPageLayout/Topbar";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "components/Tooltip/Tooltip";
|
||||
import {
|
||||
BanIcon,
|
||||
CircleStopIcon,
|
||||
@@ -30,30 +35,33 @@ export const UpdateButton: FC<ActionButtonProps> = ({
|
||||
requireActiveVersion,
|
||||
}) => {
|
||||
return (
|
||||
<Tooltip
|
||||
title={
|
||||
requireActiveVersion
|
||||
? "This template requires automatic updates on workspace startup. Contact your administrator if you want to preserve the template version."
|
||||
: isRunning
|
||||
? "Stop workspace and restart it with the latest template version."
|
||||
: "Start workspace with the latest template version."
|
||||
}
|
||||
>
|
||||
<TopbarButton
|
||||
data-testid="workspace-update-button"
|
||||
disabled={loading}
|
||||
onClick={() => handleAction()}
|
||||
>
|
||||
{requireActiveVersion ? <PlayIcon /> : <CloudIcon />}
|
||||
{loading ? (
|
||||
<>Updating…</>
|
||||
) : isRunning ? (
|
||||
<>Update and restart…</>
|
||||
) : (
|
||||
<>Update and start…</>
|
||||
)}
|
||||
</TopbarButton>
|
||||
</Tooltip>
|
||||
<TooltipProvider delayDuration={100}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<TopbarButton
|
||||
data-testid="workspace-update-button"
|
||||
disabled={loading}
|
||||
onClick={() => handleAction()}
|
||||
>
|
||||
{requireActiveVersion ? <PlayIcon /> : <CloudIcon />}
|
||||
{loading ? (
|
||||
<>Updating…</>
|
||||
) : isRunning ? (
|
||||
<>Update and restart…</>
|
||||
) : (
|
||||
<>Update and start…</>
|
||||
)}
|
||||
</TopbarButton>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
{requireActiveVersion
|
||||
? "This template requires automatic updates on workspace startup. Contact your administrator if you want to preserve the template version."
|
||||
: isRunning
|
||||
? "Stop workspace and restart it with the latest template version."
|
||||
: "Start workspace with the latest template version."}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -88,7 +96,14 @@ export const StartButton: FC<ActionButtonPropsWithWorkspace> = ({
|
||||
);
|
||||
|
||||
if (tooltipText) {
|
||||
mainButton = <Tooltip title={tooltipText}>{mainButton}</Tooltip>;
|
||||
mainButton = (
|
||||
<TooltipProvider delayDuration={100}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>{mainButton}</TooltipTrigger>
|
||||
<TooltipContent>{tooltipText}</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import type { Interpolation, Theme } from "@emotion/react";
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import Link, { type LinkProps } from "@mui/material/Link";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import { visuallyHidden } from "@mui/utils";
|
||||
import { getErrorMessage } from "api/errors";
|
||||
import {
|
||||
@@ -11,6 +10,12 @@ import {
|
||||
import type { Template, Workspace } from "api/typesGenerated";
|
||||
import { TopbarData, TopbarIcon } from "components/FullPageLayout/Topbar";
|
||||
import { displayError, displaySuccess } from "components/GlobalSnackbar/utils";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "components/Tooltip/Tooltip";
|
||||
import dayjs, { type Dayjs } from "dayjs";
|
||||
import { useTime } from "hooks/useTime";
|
||||
import { ClockIcon, MinusIcon, PlusIcon } from "lucide-react";
|
||||
@@ -45,20 +50,25 @@ const WorkspaceScheduleContainer: FC<WorkspaceScheduleContainerProps> = ({
|
||||
|
||||
return (
|
||||
<TopbarData>
|
||||
<Tooltip title="Schedule">
|
||||
{onClickIcon ? (
|
||||
<button
|
||||
type="button"
|
||||
data-testid="schedule-icon-button"
|
||||
onClick={onClickIcon}
|
||||
css={styles.scheduleIconButton}
|
||||
>
|
||||
{icon}
|
||||
</button>
|
||||
) : (
|
||||
icon
|
||||
)}
|
||||
</Tooltip>
|
||||
<TooltipProvider delayDuration={100}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
{onClickIcon ? (
|
||||
<button
|
||||
type="button"
|
||||
data-testid="schedule-icon-button"
|
||||
onClick={onClickIcon}
|
||||
css={styles.scheduleIconButton}
|
||||
>
|
||||
{icon}
|
||||
</button>
|
||||
) : (
|
||||
icon
|
||||
)}
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Schedule</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
{children}
|
||||
</TopbarData>
|
||||
);
|
||||
@@ -200,39 +210,54 @@ const AutostopDisplay: FC<AutostopDisplayProps> = ({
|
||||
|
||||
const controls = canUpdateSchedule && canEditDeadline(workspace) && (
|
||||
<div css={styles.scheduleControls}>
|
||||
<Tooltip title="Subtract 1 hour from deadline">
|
||||
<IconButton
|
||||
disabled={!deadlineMinusEnabled}
|
||||
size="small"
|
||||
css={styles.scheduleButton}
|
||||
onClick={() => {
|
||||
handleDeadlineChange(deadline.subtract(1, "h"));
|
||||
}}
|
||||
>
|
||||
<MinusIcon className="size-icon-xs" />
|
||||
<span style={visuallyHidden}>Subtract 1 hour</span>
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip title="Add 1 hour to deadline">
|
||||
<IconButton
|
||||
disabled={!deadlinePlusEnabled}
|
||||
size="small"
|
||||
css={styles.scheduleButton}
|
||||
onClick={() => {
|
||||
handleDeadlineChange(deadline.add(1, "h"));
|
||||
}}
|
||||
>
|
||||
<PlusIcon className="size-icon-xs" />
|
||||
<span style={visuallyHidden}>Add 1 hour</span>
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<TooltipProvider delayDuration={100}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<IconButton
|
||||
disabled={!deadlineMinusEnabled}
|
||||
size="small"
|
||||
css={styles.scheduleButton}
|
||||
onClick={() => {
|
||||
handleDeadlineChange(deadline.subtract(1, "h"));
|
||||
}}
|
||||
>
|
||||
<MinusIcon className="size-icon-xs" />
|
||||
<span style={visuallyHidden}>Subtract 1 hour</span>
|
||||
</IconButton>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Subtract 1 hour from deadline</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
<TooltipProvider delayDuration={100}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<IconButton
|
||||
disabled={!deadlinePlusEnabled}
|
||||
size="small"
|
||||
css={styles.scheduleButton}
|
||||
onClick={() => {
|
||||
handleDeadlineChange(deadline.add(1, "h"));
|
||||
}}
|
||||
>
|
||||
<PlusIcon className="size-icon-xs" />
|
||||
<span style={visuallyHidden}>Add 1 hour</span>
|
||||
</IconButton>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Add 1 hour to deadline</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
);
|
||||
|
||||
if (tooltip) {
|
||||
return (
|
||||
<WorkspaceScheduleContainer onClickIcon={onClickScheduleIcon}>
|
||||
<Tooltip title={tooltip}>{display}</Tooltip>
|
||||
<TooltipProvider delayDuration={100}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>{display}</TooltipTrigger>
|
||||
<TooltipContent>{tooltip}</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
{controls}
|
||||
</WorkspaceScheduleContainer>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { type Interpolation, type Theme, useTheme } from "@emotion/react";
|
||||
import Link from "@mui/material/Link";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import { workspaceQuota } from "api/queries/workspaceQuota";
|
||||
import type * as TypesGen from "api/typesGenerated";
|
||||
import { Avatar } from "components/Avatar/Avatar";
|
||||
@@ -19,6 +18,12 @@ import {
|
||||
HelpTooltipContent,
|
||||
HelpTooltipTrigger,
|
||||
} from "components/HelpTooltip/HelpTooltip";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "components/Tooltip/Tooltip";
|
||||
import { ChevronLeftIcon, CircleDollarSign, TrashIcon } from "lucide-react";
|
||||
import { useDashboard } from "modules/dashboard/useDashboard";
|
||||
import { linkToTemplate, useLinks } from "modules/navigation";
|
||||
@@ -108,11 +113,16 @@ export const WorkspaceTopbar: FC<WorkspaceProps> = ({
|
||||
|
||||
return (
|
||||
<Topbar css={{ gridArea: "topbar" }}>
|
||||
<Tooltip title="Back to workspaces">
|
||||
<TopbarIconButton component={RouterLink} to="/workspaces">
|
||||
<ChevronLeftIcon className="size-icon-sm" />
|
||||
</TopbarIconButton>
|
||||
</Tooltip>
|
||||
<TooltipProvider delayDuration={100}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<TopbarIconButton component={RouterLink} to="/workspaces">
|
||||
<ChevronLeftIcon className="size-icon-sm" />
|
||||
</TopbarIconButton>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Back to workspaces</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
|
||||
<div css={styles.topbarLeft}>
|
||||
<TopbarData>
|
||||
|
||||
Reference in New Issue
Block a user