mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
chore: use emotion for styling (pt. 5) (#10261)
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { css } from "@emotion/css";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import Popover, { PopoverProps } from "@mui/material/Popover";
|
||||
import Popover, { type PopoverProps } from "@mui/material/Popover";
|
||||
import type { FC, PropsWithChildren } from "react";
|
||||
|
||||
type BorderedMenuVariant = "user-dropdown";
|
||||
@@ -18,7 +18,7 @@ export const BorderedMenu: FC<PropsWithChildren<BorderedMenuProps>> = ({
|
||||
|
||||
const paper = css`
|
||||
width: 260px;
|
||||
border-radius: ${theme.shape.borderRadius};
|
||||
border-radius: ${theme.shape.borderRadius}px;
|
||||
box-shadow: ${theme.shadows[6]};
|
||||
`;
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ const SidebarNavItem: FC<
|
||||
font-size: 14px;
|
||||
text-decoration: none;
|
||||
padding: ${theme.spacing(1.5, 1.5, 1.5, 2)};
|
||||
border-radius: ${theme.shape.borderRadius / 2};
|
||||
border-radius: ${theme.shape.borderRadius / 2}px;
|
||||
transition: background-color 0.15s ease-in-out;
|
||||
margin-bottom: 1;
|
||||
position: relative;
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import Button from "@mui/material/Button";
|
||||
import Link from "@mui/material/Link";
|
||||
import { makeStyles } from "@mui/styles";
|
||||
import RefreshOutlined from "@mui/icons-material/RefreshOutlined";
|
||||
import { BuildInfoResponse } from "api/typesGenerated";
|
||||
import { type FC, useEffect, useState } from "react";
|
||||
import { Helmet } from "react-helmet-async";
|
||||
import { css } from "@emotion/css";
|
||||
import { useTheme, type Interpolation, type Theme } from "@emotion/react";
|
||||
import type { BuildInfoResponse } from "api/typesGenerated";
|
||||
import { CopyButton } from "components/CopyButton/CopyButton";
|
||||
import { CoderIcon } from "components/Icons/CoderIcon";
|
||||
import { FullScreenLoader } from "components/Loader/FullScreenLoader";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import { FC, useEffect, useState } from "react";
|
||||
import { Helmet } from "react-helmet-async";
|
||||
import { Margins } from "components/Margins/Margins";
|
||||
|
||||
const fetchDynamicallyImportedModuleError =
|
||||
@@ -17,7 +18,7 @@ const fetchDynamicallyImportedModuleError =
|
||||
export type RuntimeErrorStateProps = { error: Error };
|
||||
|
||||
export const RuntimeErrorState: FC<RuntimeErrorStateProps> = ({ error }) => {
|
||||
const styles = useStyles();
|
||||
const theme = useTheme();
|
||||
const [checkingError, setCheckingError] = useState(true);
|
||||
const [staticBuildInfo, setStaticBuildInfo] = useState<BuildInfoResponse>();
|
||||
const coderVersion = staticBuildInfo?.version;
|
||||
@@ -52,11 +53,11 @@ export const RuntimeErrorState: FC<RuntimeErrorStateProps> = ({ error }) => {
|
||||
<title>Something went wrong...</title>
|
||||
</Helmet>
|
||||
{!checkingError ? (
|
||||
<Margins className={styles.root}>
|
||||
<div className={styles.innerRoot}>
|
||||
<CoderIcon className={styles.logo} />
|
||||
<h1 className={styles.title}>Something went wrong...</h1>
|
||||
<p className={styles.text}>
|
||||
<Margins css={styles.root}>
|
||||
<div css={{ width: "100%" }}>
|
||||
<CoderIcon css={styles.logo} />
|
||||
<h1 css={styles.title}>Something went wrong...</h1>
|
||||
<p css={styles.text}>
|
||||
Please try reloading the page, if that doesn‘t work, you can
|
||||
ask for help in the{" "}
|
||||
<Link href="https://discord.gg/coder">
|
||||
@@ -93,20 +94,33 @@ export const RuntimeErrorState: FC<RuntimeErrorStateProps> = ({ error }) => {
|
||||
</Button>
|
||||
</Stack>
|
||||
{error.stack && (
|
||||
<div className={styles.stack}>
|
||||
<div className={styles.stackHeader}>
|
||||
<div css={styles.stack}>
|
||||
<div css={styles.stackHeader}>
|
||||
Stacktrace
|
||||
<CopyButton
|
||||
buttonClassName={styles.copyButton}
|
||||
buttonClassName={css`
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
border-radius: 999px;
|
||||
min-height: ${theme.spacing(4)};
|
||||
min-width: ${theme.spacing(4)};
|
||||
height: ${theme.spacing(4)};
|
||||
width: ${theme.spacing(4)};
|
||||
|
||||
& svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
`}
|
||||
text={error.stack}
|
||||
tooltipTitle="Copy stacktrace"
|
||||
/>
|
||||
</div>
|
||||
<pre className={styles.stackCode}>{error.stack}</pre>
|
||||
<pre css={styles.stackCode}>{error.stack}</pre>
|
||||
</div>
|
||||
)}
|
||||
{coderVersion && (
|
||||
<div className={styles.version}>Version: {coderVersion}</div>
|
||||
<div css={styles.version}>Version: {coderVersion}</div>
|
||||
)}
|
||||
</div>
|
||||
</Margins>
|
||||
@@ -132,8 +146,8 @@ const getStaticBuildInfo = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
root: {
|
||||
const styles = {
|
||||
root: (theme) => ({
|
||||
paddingTop: theme.spacing(4),
|
||||
paddingBottom: theme.spacing(4),
|
||||
textAlign: "center",
|
||||
@@ -142,36 +156,34 @@ const useStyles = makeStyles((theme) => ({
|
||||
justifyContent: "center",
|
||||
minHeight: "100%",
|
||||
maxWidth: theme.spacing(75),
|
||||
},
|
||||
}),
|
||||
|
||||
innerRoot: { width: "100%" },
|
||||
|
||||
logo: {
|
||||
logo: (theme) => ({
|
||||
fontSize: theme.spacing(8),
|
||||
},
|
||||
}),
|
||||
|
||||
title: {
|
||||
title: (theme) => ({
|
||||
fontSize: theme.spacing(4),
|
||||
fontWeight: 400,
|
||||
},
|
||||
}),
|
||||
|
||||
text: {
|
||||
text: (theme) => ({
|
||||
fontSize: 16,
|
||||
color: theme.palette.text.secondary,
|
||||
lineHeight: "160%",
|
||||
marginBottom: theme.spacing(4),
|
||||
},
|
||||
}),
|
||||
|
||||
stack: {
|
||||
stack: (theme) => ({
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
border: `1px solid ${theme.palette.divider}`,
|
||||
borderRadius: 4,
|
||||
marginTop: theme.spacing(8),
|
||||
display: "block",
|
||||
textAlign: "left",
|
||||
},
|
||||
}),
|
||||
|
||||
stackHeader: {
|
||||
stackHeader: (theme) => ({
|
||||
fontSize: 10,
|
||||
textTransform: "uppercase",
|
||||
fontWeight: 600,
|
||||
@@ -184,33 +196,18 @@ const useStyles = makeStyles((theme) => ({
|
||||
flexAlign: "center",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
},
|
||||
}),
|
||||
|
||||
stackCode: {
|
||||
stackCode: (theme) => ({
|
||||
padding: theme.spacing(2),
|
||||
margin: 0,
|
||||
wordWrap: "break-word",
|
||||
whiteSpace: "break-spaces",
|
||||
},
|
||||
}),
|
||||
|
||||
copyButton: {
|
||||
backgroundColor: "transparent",
|
||||
border: 0,
|
||||
borderRadius: 999,
|
||||
minHeight: theme.spacing(4),
|
||||
minWidth: theme.spacing(4),
|
||||
height: theme.spacing(4),
|
||||
width: theme.spacing(4),
|
||||
|
||||
"& svg": {
|
||||
width: 16,
|
||||
height: 16,
|
||||
},
|
||||
},
|
||||
|
||||
version: {
|
||||
version: (theme) => ({
|
||||
marginTop: theme.spacing(4),
|
||||
fontSize: 12,
|
||||
color: theme.palette.text.secondary,
|
||||
},
|
||||
}));
|
||||
}),
|
||||
} satisfies Record<string, Interpolation<Theme>>;
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import { makeStyles } from "@mui/styles";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import { FC, DragEvent, useRef, ReactNode } from "react";
|
||||
import { type FC, type DragEvent, useRef, type ReactNode } from "react";
|
||||
import UploadIcon from "@mui/icons-material/CloudUploadOutlined";
|
||||
import { useClickable } from "hooks/useClickable";
|
||||
import CircularProgress from "@mui/material/CircularProgress";
|
||||
import { combineClasses } from "utils/combineClasses";
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import RemoveIcon from "@mui/icons-material/DeleteOutline";
|
||||
import FileIcon from "@mui/icons-material/FolderOutlined";
|
||||
import { css, type Interpolation, type Theme } from "@emotion/react";
|
||||
|
||||
const useFileDrop = (
|
||||
callback: (file: File) => void,
|
||||
@@ -62,7 +61,6 @@ export const FileUpload: FC<FileUploadProps> = ({
|
||||
extension,
|
||||
fileTypeRequired,
|
||||
}) => {
|
||||
const styles = useStyles();
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const tarDrop = useFileDrop(onUpload, fileTypeRequired);
|
||||
|
||||
@@ -75,7 +73,7 @@ export const FileUpload: FC<FileUploadProps> = ({
|
||||
if (!isUploading && file) {
|
||||
return (
|
||||
<Stack
|
||||
className={styles.file}
|
||||
css={styles.file}
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
@@ -95,10 +93,7 @@ export const FileUpload: FC<FileUploadProps> = ({
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={combineClasses({
|
||||
[styles.root]: true,
|
||||
[styles.disabled]: isUploading,
|
||||
})}
|
||||
css={[styles.root, isUploading && styles.disabled]}
|
||||
{...clickable}
|
||||
{...tarDrop}
|
||||
>
|
||||
@@ -106,12 +101,12 @@ export const FileUpload: FC<FileUploadProps> = ({
|
||||
{isUploading ? (
|
||||
<CircularProgress size={32} />
|
||||
) : (
|
||||
<UploadIcon className={styles.icon} />
|
||||
<UploadIcon css={styles.icon} />
|
||||
)}
|
||||
|
||||
<Stack alignItems="center" spacing={0.5}>
|
||||
<span className={styles.title}>{title}</span>
|
||||
<span className={styles.description}>{description}</span>
|
||||
<span css={styles.title}>{title}</span>
|
||||
<span css={styles.description}>{description}</span>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</div>
|
||||
@@ -120,7 +115,7 @@ export const FileUpload: FC<FileUploadProps> = ({
|
||||
type="file"
|
||||
data-testid="file-upload"
|
||||
ref={inputRef}
|
||||
className={styles.input}
|
||||
css={styles.input}
|
||||
accept={extension}
|
||||
onChange={(event) => {
|
||||
const file = event.currentTarget.files?.[0];
|
||||
@@ -133,48 +128,48 @@ export const FileUpload: FC<FileUploadProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
root: {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
border: `2px dashed ${theme.palette.divider}`,
|
||||
padding: theme.spacing(6),
|
||||
cursor: "pointer",
|
||||
const styles = {
|
||||
root: (theme) => css`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: ${theme.shape.borderRadius}px;
|
||||
border: 2px dashed ${theme.palette.divider};
|
||||
padding: ${theme.spacing(6)};
|
||||
cursor: pointer;
|
||||
|
||||
"&:hover": {
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
},
|
||||
},
|
||||
&:hover {
|
||||
background-color: ${theme.palette.background.paper};
|
||||
}
|
||||
`,
|
||||
|
||||
disabled: {
|
||||
pointerEvents: "none",
|
||||
opacity: 0.75,
|
||||
},
|
||||
|
||||
icon: {
|
||||
icon: (theme) => ({
|
||||
fontSize: theme.spacing(8),
|
||||
},
|
||||
}),
|
||||
|
||||
title: {
|
||||
title: (theme) => ({
|
||||
fontSize: theme.spacing(2),
|
||||
},
|
||||
}),
|
||||
|
||||
description: {
|
||||
description: (theme) => ({
|
||||
color: theme.palette.text.secondary,
|
||||
textAlign: "center",
|
||||
maxWidth: theme.spacing(50),
|
||||
},
|
||||
}),
|
||||
|
||||
input: {
|
||||
display: "none",
|
||||
},
|
||||
|
||||
file: {
|
||||
file: (theme) => ({
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
border: `1px solid ${theme.palette.divider}`,
|
||||
padding: theme.spacing(2),
|
||||
background: theme.palette.background.paper,
|
||||
},
|
||||
}));
|
||||
}),
|
||||
} satisfies Record<string, Interpolation<Theme>>;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { Margins } from "components/Margins/Margins";
|
||||
import { FC, ReactNode } from "react";
|
||||
import { type FC, type ReactNode } from "react";
|
||||
import {
|
||||
PageHeader,
|
||||
PageHeaderTitle,
|
||||
PageHeaderSubtitle,
|
||||
} from "components/PageHeader/PageHeader";
|
||||
import { makeStyles } from "@mui/styles";
|
||||
import { useTheme } from "@emotion/react";
|
||||
|
||||
export interface FullPageFormProps {
|
||||
title: string;
|
||||
@@ -17,11 +17,11 @@ export const FullPageForm: FC<React.PropsWithChildren<FullPageFormProps>> = ({
|
||||
detail,
|
||||
children,
|
||||
}) => {
|
||||
const styles = useStyles();
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<Margins size="small">
|
||||
<PageHeader className={styles.pageHeader}>
|
||||
<PageHeader css={{ paddingBottom: theme.spacing(3) }}>
|
||||
<PageHeaderTitle>{title}</PageHeaderTitle>
|
||||
{detail && <PageHeaderSubtitle>{detail}</PageHeaderSubtitle>}
|
||||
</PageHeader>
|
||||
@@ -30,9 +30,3 @@ export const FullPageForm: FC<React.PropsWithChildren<FullPageFormProps>> = ({
|
||||
</Margins>
|
||||
);
|
||||
};
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
pageHeader: {
|
||||
paddingBottom: theme.spacing(3),
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -2,10 +2,10 @@ import IconButton from "@mui/material/IconButton";
|
||||
import Snackbar, {
|
||||
SnackbarProps as MuiSnackbarProps,
|
||||
} from "@mui/material/Snackbar";
|
||||
import { makeStyles } from "@mui/styles";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import { FC } from "react";
|
||||
import { combineClasses } from "utils/combineClasses";
|
||||
import { type FC } from "react";
|
||||
import { css } from "@emotion/css";
|
||||
import { type Interpolation, type Theme, useTheme } from "@emotion/react";
|
||||
|
||||
type EnterpriseSnackbarVariant = "error" | "info" | "success";
|
||||
|
||||
@@ -30,7 +30,18 @@ export interface EnterpriseSnackbarProps extends MuiSnackbarProps {
|
||||
export const EnterpriseSnackbar: FC<
|
||||
React.PropsWithChildren<EnterpriseSnackbarProps>
|
||||
> = ({ onClose, variant = "info", ContentProps = {}, action, ...rest }) => {
|
||||
const styles = useStyles();
|
||||
const theme = useTheme();
|
||||
|
||||
const snackbarContentStyles = css`
|
||||
border: 1px solid ${theme.palette.divider};
|
||||
border-left: 4px solid ${variantColor(variant, theme)};
|
||||
border-radius: ${theme.shape.borderRadius}px;
|
||||
padding: ${theme.spacing(1, 3, 1, 2)};
|
||||
box-shadow: ${theme.shadows[6]};
|
||||
align-items: inherit;
|
||||
background-color: ${theme.palette.background.paper};
|
||||
color: ${theme.palette.text.secondary};
|
||||
`;
|
||||
|
||||
return (
|
||||
<Snackbar
|
||||
@@ -40,67 +51,41 @@ export const EnterpriseSnackbar: FC<
|
||||
}}
|
||||
{...rest}
|
||||
action={
|
||||
<div className={styles.actionWrapper}>
|
||||
<div css={styles.actionWrapper}>
|
||||
{action}
|
||||
<IconButton
|
||||
onClick={onClose}
|
||||
className={styles.iconButton}
|
||||
size="large"
|
||||
>
|
||||
<CloseIcon className={styles.closeIcon} aria-label="close" />
|
||||
<IconButton onClick={onClose} css={{ padding: 0 }} size="large">
|
||||
<CloseIcon css={styles.closeIcon} aria-label="close" />
|
||||
</IconButton>
|
||||
</div>
|
||||
}
|
||||
ContentProps={{
|
||||
...ContentProps,
|
||||
className: combineClasses({
|
||||
[styles.snackbarContent]: true,
|
||||
[styles.snackbarContentInfo]: variant === "info",
|
||||
[styles.snackbarContentError]: variant === "error",
|
||||
[styles.snackbarContentSuccess]: variant === "success",
|
||||
}),
|
||||
className: snackbarContentStyles,
|
||||
}}
|
||||
onClose={onClose}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
const variantColor = (variant: EnterpriseSnackbarVariant, theme: Theme) => {
|
||||
switch (variant) {
|
||||
case "error":
|
||||
return theme.palette.error.main;
|
||||
case "info":
|
||||
return theme.palette.info.main;
|
||||
case "success":
|
||||
return theme.palette.success.main;
|
||||
}
|
||||
};
|
||||
|
||||
const styles = {
|
||||
actionWrapper: {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
},
|
||||
iconButton: {
|
||||
padding: 0,
|
||||
},
|
||||
closeIcon: {
|
||||
closeIcon: (theme) => ({
|
||||
width: 25,
|
||||
height: 25,
|
||||
color: theme.palette.primary.contrastText,
|
||||
},
|
||||
snackbarContent: {
|
||||
border: `1px solid ${theme.palette.divider}`,
|
||||
borderLeft: `4px solid ${theme.palette.primary.main}`,
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
padding: `
|
||||
${theme.spacing(1)}px
|
||||
${theme.spacing(3)}px
|
||||
${theme.spacing(1)}px
|
||||
${theme.spacing(2)}px
|
||||
`,
|
||||
boxShadow: theme.shadows[6],
|
||||
alignItems: "inherit",
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
color: theme.palette.text.secondary,
|
||||
},
|
||||
snackbarContentInfo: {
|
||||
// Use success color as a highlight
|
||||
borderLeftColor: theme.palette.primary.main,
|
||||
},
|
||||
snackbarContentError: {
|
||||
borderLeftColor: theme.palette.error.main,
|
||||
},
|
||||
snackbarContentSuccess: {
|
||||
borderLeftColor: theme.palette.success.main,
|
||||
},
|
||||
}));
|
||||
}),
|
||||
} satisfies Record<string, Interpolation<Theme>>;
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import { makeStyles } from "@mui/styles";
|
||||
import { useCallback, useState, FC } from "react";
|
||||
import { type FC, useCallback, useState } from "react";
|
||||
import { useCustomEvent } from "hooks/events";
|
||||
import { CustomEventListener } from "utils/events";
|
||||
import type { CustomEventListener } from "utils/events";
|
||||
import { EnterpriseSnackbar } from "./EnterpriseSnackbar";
|
||||
import { ErrorIcon } from "../Icons/ErrorIcon";
|
||||
import { Typography } from "../Typography/Typography";
|
||||
import {
|
||||
AdditionalMessage,
|
||||
type AdditionalMessage,
|
||||
isNotificationList,
|
||||
isNotificationText,
|
||||
isNotificationTextPrefixed,
|
||||
MsgType,
|
||||
NotificationMsg,
|
||||
type NotificationMsg,
|
||||
SnackbarEventType,
|
||||
} from "./utils";
|
||||
import { type Interpolation, type Theme } from "@emotion/react";
|
||||
|
||||
const variantFromMsgType = (type: MsgType) => {
|
||||
if (type === MsgType.Error) {
|
||||
@@ -26,7 +26,6 @@ const variantFromMsgType = (type: MsgType) => {
|
||||
};
|
||||
|
||||
export const GlobalSnackbar: FC = () => {
|
||||
const styles = useStyles();
|
||||
const [open, setOpen] = useState<boolean>(false);
|
||||
const [notification, setNotification] = useState<NotificationMsg>();
|
||||
|
||||
@@ -47,7 +46,7 @@ export const GlobalSnackbar: FC = () => {
|
||||
key={idx}
|
||||
gutterBottom
|
||||
variant="body2"
|
||||
className={styles.messageSubtitle}
|
||||
css={styles.messageSubtitle}
|
||||
>
|
||||
{msg}
|
||||
</Typography>
|
||||
@@ -58,17 +57,17 @@ export const GlobalSnackbar: FC = () => {
|
||||
key={idx}
|
||||
gutterBottom
|
||||
variant="body2"
|
||||
className={styles.messageSubtitle}
|
||||
css={styles.messageSubtitle}
|
||||
>
|
||||
<strong>{msg.prefix}:</strong> {msg.text}
|
||||
</Typography>
|
||||
);
|
||||
} else if (isNotificationList(msg)) {
|
||||
return (
|
||||
<ul className={styles.list} key={idx}>
|
||||
<ul css={styles.list} key={idx}>
|
||||
{msg.map((item, idx) => (
|
||||
<li key={idx}>
|
||||
<Typography variant="body2" className={styles.messageSubtitle}>
|
||||
<Typography variant="body2" css={styles.messageSubtitle}>
|
||||
{item}
|
||||
</Typography>
|
||||
</li>
|
||||
@@ -89,12 +88,12 @@ export const GlobalSnackbar: FC = () => {
|
||||
open={open}
|
||||
variant={variantFromMsgType(notification.msgType)}
|
||||
message={
|
||||
<div className={styles.messageWrapper}>
|
||||
<div css={styles.messageWrapper}>
|
||||
{notification.msgType === MsgType.Error && (
|
||||
<ErrorIcon className={styles.errorIcon} />
|
||||
<ErrorIcon css={styles.errorIcon} />
|
||||
)}
|
||||
<div className={styles.message}>
|
||||
<Typography variant="body1" className={styles.messageTitle}>
|
||||
<div css={styles.message}>
|
||||
<Typography variant="body1" css={styles.messageTitle}>
|
||||
{notification.msg}
|
||||
</Typography>
|
||||
{notification.additionalMsgs &&
|
||||
@@ -112,7 +111,7 @@ export const GlobalSnackbar: FC = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
const styles = {
|
||||
list: {
|
||||
paddingLeft: 0,
|
||||
},
|
||||
@@ -126,11 +125,11 @@ const useStyles = makeStyles((theme) => ({
|
||||
fontSize: 14,
|
||||
fontWeight: 600,
|
||||
},
|
||||
messageSubtitle: {
|
||||
messageSubtitle: (theme) => ({
|
||||
marginTop: theme.spacing(1.5),
|
||||
},
|
||||
errorIcon: {
|
||||
}),
|
||||
errorIcon: (theme) => ({
|
||||
color: theme.palette.error.contrastText,
|
||||
marginRight: theme.spacing(2),
|
||||
},
|
||||
}));
|
||||
}),
|
||||
} satisfies Record<string, Interpolation<Theme>>;
|
||||
|
||||
@@ -1,16 +1,39 @@
|
||||
import { makeStyles } from "@mui/styles";
|
||||
import { FC, PropsWithChildren } from "react";
|
||||
import { combineClasses } from "utils/combineClasses";
|
||||
import { type CSSObject, useTheme } from "@emotion/react";
|
||||
import { type FC, type PropsWithChildren } from "react";
|
||||
|
||||
export const FullWidthPageHeader: FC<
|
||||
PropsWithChildren & { sticky?: boolean }
|
||||
> = ({ children, sticky = true }) => {
|
||||
const styles = useStyles();
|
||||
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<header
|
||||
className={combineClasses([styles.header, sticky ? styles.sticky : ""])}
|
||||
data-testid="header"
|
||||
css={[
|
||||
{
|
||||
...(theme.typography.body2 as CSSObject),
|
||||
padding: theme.spacing(3),
|
||||
background: theme.palette.background.paper,
|
||||
borderBottom: `1px solid ${theme.palette.divider}`,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: theme.spacing(6),
|
||||
|
||||
zIndex: 10,
|
||||
flexWrap: "wrap",
|
||||
|
||||
[theme.breakpoints.down("lg")]: {
|
||||
position: "unset",
|
||||
alignItems: "flex-start",
|
||||
},
|
||||
[theme.breakpoints.down("md")]: {
|
||||
flexDirection: "column",
|
||||
},
|
||||
},
|
||||
sticky && {
|
||||
position: "sticky",
|
||||
top: 0,
|
||||
},
|
||||
]}
|
||||
>
|
||||
{children}
|
||||
</header>
|
||||
@@ -18,60 +41,47 @@ export const FullWidthPageHeader: FC<
|
||||
};
|
||||
|
||||
export const PageHeaderActions: FC<PropsWithChildren> = ({ children }) => {
|
||||
const styles = useStyles();
|
||||
return <div className={styles.actions}>{children}</div>;
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<div
|
||||
css={{
|
||||
marginLeft: "auto",
|
||||
[theme.breakpoints.down("md")]: {
|
||||
marginLeft: "unset",
|
||||
},
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const PageHeaderTitle: FC<PropsWithChildren> = ({ children }) => {
|
||||
const styles = useStyles();
|
||||
return <h1 className={styles.title}>{children}</h1>;
|
||||
return (
|
||||
<h1
|
||||
css={{
|
||||
fontSize: 18,
|
||||
fontWeight: 500,
|
||||
margin: 0,
|
||||
lineHeight: "24px",
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</h1>
|
||||
);
|
||||
};
|
||||
|
||||
export const PageHeaderSubtitle: FC<PropsWithChildren> = ({ children }) => {
|
||||
const styles = useStyles();
|
||||
return <span className={styles.subtitle}>{children}</span>;
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<span
|
||||
css={{
|
||||
fontSize: 14,
|
||||
color: theme.palette.text.secondary,
|
||||
display: "block",
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
header: {
|
||||
...theme.typography.body2,
|
||||
padding: theme.spacing(3),
|
||||
background: theme.palette.background.paper,
|
||||
borderBottom: `1px solid ${theme.palette.divider}`,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: theme.spacing(6),
|
||||
|
||||
zIndex: 10,
|
||||
flexWrap: "wrap",
|
||||
|
||||
[theme.breakpoints.down("lg")]: {
|
||||
position: "unset",
|
||||
alignItems: "flex-start",
|
||||
},
|
||||
[theme.breakpoints.down("md")]: {
|
||||
flexDirection: "column",
|
||||
},
|
||||
},
|
||||
sticky: {
|
||||
position: "sticky",
|
||||
top: 0,
|
||||
},
|
||||
actions: {
|
||||
marginLeft: "auto",
|
||||
[theme.breakpoints.down("md")]: {
|
||||
marginLeft: "unset",
|
||||
},
|
||||
},
|
||||
title: {
|
||||
fontSize: 18,
|
||||
fontWeight: 500,
|
||||
margin: 0,
|
||||
lineHeight: "24px",
|
||||
},
|
||||
subtitle: {
|
||||
fontSize: 14,
|
||||
color: theme.palette.text.secondary,
|
||||
display: "block",
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import Button from "@mui/material/Button";
|
||||
import { makeStyles } from "@mui/styles";
|
||||
import { css, useTheme } from "@emotion/react";
|
||||
|
||||
interface PageButtonProps {
|
||||
activePage?: number;
|
||||
@@ -18,14 +18,20 @@ export const PageButton = ({
|
||||
onPageClick,
|
||||
disabled = false,
|
||||
}: PageButtonProps): JSX.Element => {
|
||||
const styles = useStyles();
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<Button
|
||||
className={
|
||||
activePage === page
|
||||
? `${styles.pageButton} ${styles.activePageButton}`
|
||||
: styles.pageButton
|
||||
}
|
||||
css={[
|
||||
css`
|
||||
&:not(:last-of-type) {
|
||||
margin-right: ${theme.spacing(0.5)};
|
||||
}
|
||||
`,
|
||||
activePage === page && {
|
||||
borderColor: `${theme.palette.info.main}`,
|
||||
backgroundColor: `${theme.palette.info.dark}`,
|
||||
},
|
||||
]}
|
||||
aria-label={`${page === activePage ? "Current Page" : ""} ${
|
||||
page === numPages ? "Last Page" : ""
|
||||
} Page${page}`}
|
||||
@@ -37,16 +43,3 @@ export const PageButton = ({
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
pageButton: {
|
||||
"&:not(:last-of-type)": {
|
||||
marginRight: theme.spacing(0.5),
|
||||
},
|
||||
},
|
||||
|
||||
activePageButton: {
|
||||
borderColor: `${theme.palette.info.main}`,
|
||||
backgroundColor: `${theme.palette.info.dark}`,
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import Button from "@mui/material/Button";
|
||||
import { makeStyles, useTheme } from "@mui/styles";
|
||||
import useMediaQuery from "@mui/material/useMediaQuery";
|
||||
import KeyboardArrowLeft from "@mui/icons-material/KeyboardArrowLeft";
|
||||
import KeyboardArrowRight from "@mui/icons-material/KeyboardArrowRight";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { PageButton } from "./PageButton";
|
||||
import { buildPagedList } from "./utils";
|
||||
|
||||
@@ -21,7 +21,6 @@ export const PaginationWidgetBase = ({
|
||||
}: PaginationWidgetBaseProps): JSX.Element | null => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down("md"));
|
||||
const styles = useStyles();
|
||||
const numPages = Math.ceil(count / limit);
|
||||
const isFirstPage = page === 0;
|
||||
const isLastPage = page === numPages - 1;
|
||||
@@ -31,9 +30,19 @@ export const PaginationWidgetBase = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.defaultContainerStyles}>
|
||||
<div
|
||||
css={{
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
padding: "20px",
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
className={styles.prevLabelStyles}
|
||||
css={{
|
||||
marginRight: theme.spacing(0.5),
|
||||
}}
|
||||
aria-label="Previous page"
|
||||
disabled={isFirstPage}
|
||||
onClick={() => {
|
||||
@@ -84,17 +93,3 @@ export const PaginationWidgetBase = ({
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
defaultContainerStyles: {
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
padding: "20px",
|
||||
},
|
||||
|
||||
prevLabelStyles: {
|
||||
marginRight: theme.spacing(0.5),
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import Box from "@mui/material/Box";
|
||||
import Chip from "@mui/material/Chip";
|
||||
import { makeStyles } from "@mui/styles";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import { type FC, type ReactNode } from "react";
|
||||
import { type Interpolation, type Theme } from "@emotion/react";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import { FC, ReactNode } from "react";
|
||||
|
||||
export interface PaywallProps {
|
||||
message: string;
|
||||
@@ -13,17 +13,16 @@ export interface PaywallProps {
|
||||
|
||||
export const Paywall: FC<React.PropsWithChildren<PaywallProps>> = (props) => {
|
||||
const { message, description, cta } = props;
|
||||
const styles = useStyles();
|
||||
|
||||
return (
|
||||
<Box className={styles.root}>
|
||||
<div className={styles.header}>
|
||||
<Box css={styles.root}>
|
||||
<div css={styles.header}>
|
||||
<Stack direction="row" alignItems="center" justifyContent="center">
|
||||
<Typography variant="h5" className={styles.title}>
|
||||
<Typography variant="h5" css={styles.title}>
|
||||
{message}
|
||||
</Typography>
|
||||
<Chip
|
||||
className={styles.enterpriseChip}
|
||||
css={styles.enterpriseChip}
|
||||
label="Enterprise"
|
||||
size="small"
|
||||
color="primary"
|
||||
@@ -34,7 +33,7 @@ export const Paywall: FC<React.PropsWithChildren<PaywallProps>> = (props) => {
|
||||
<Typography
|
||||
variant="body2"
|
||||
color="textSecondary"
|
||||
className={styles.description}
|
||||
css={styles.description}
|
||||
>
|
||||
{description}
|
||||
</Typography>
|
||||
@@ -45,8 +44,8 @@ export const Paywall: FC<React.PropsWithChildren<PaywallProps>> = (props) => {
|
||||
);
|
||||
};
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
root: {
|
||||
const styles = {
|
||||
root: (theme) => ({
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
@@ -57,24 +56,24 @@ const useStyles = makeStyles((theme) => ({
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
border: `1px solid ${theme.palette.divider}`,
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
},
|
||||
header: {
|
||||
}),
|
||||
header: (theme) => ({
|
||||
marginBottom: theme.spacing(3),
|
||||
},
|
||||
}),
|
||||
title: {
|
||||
fontWeight: 600,
|
||||
fontFamily: "inherit",
|
||||
},
|
||||
description: {
|
||||
description: (theme) => ({
|
||||
marginTop: theme.spacing(1),
|
||||
fontFamily: "inherit",
|
||||
maxWidth: 420,
|
||||
lineHeight: "160%",
|
||||
},
|
||||
enterpriseChip: {
|
||||
}),
|
||||
enterpriseChip: (theme) => ({
|
||||
background: theme.palette.success.dark,
|
||||
color: theme.palette.success.contrastText,
|
||||
border: `1px solid ${theme.palette.success.light}`,
|
||||
fontSize: 13,
|
||||
},
|
||||
}));
|
||||
}),
|
||||
} satisfies Record<string, Interpolation<Theme>>;
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { useRef, useState, FC } from "react";
|
||||
import { makeStyles, useTheme } from "@mui/styles";
|
||||
import { type FC, useRef, useState } from "react";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { Theme } from "@mui/material/styles";
|
||||
import type { WorkspaceAgent, DERPRegion } from "api/typesGenerated";
|
||||
import {
|
||||
HelpTooltipText,
|
||||
HelpPopover,
|
||||
HelpTooltipTitle,
|
||||
} from "components/HelpTooltip/HelpTooltip";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import { WorkspaceAgent, DERPRegion } from "api/typesGenerated";
|
||||
import { getLatencyColor } from "utils/latency";
|
||||
|
||||
const getDisplayLatency = (theme: Theme, agent: WorkspaceAgent) => {
|
||||
@@ -30,12 +30,11 @@ const getDisplayLatency = (theme: Theme, agent: WorkspaceAgent) => {
|
||||
};
|
||||
|
||||
export const AgentLatency: FC<{ agent: WorkspaceAgent }> = ({ agent }) => {
|
||||
const theme: Theme = useTheme();
|
||||
const theme = useTheme();
|
||||
const anchorRef = useRef<HTMLButtonElement>(null);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const id = isOpen ? "latency-popover" : undefined;
|
||||
const latency = getDisplayLatency(theme, agent);
|
||||
const styles = useStyles();
|
||||
|
||||
if (!latency || !agent.latency) {
|
||||
return null;
|
||||
@@ -49,8 +48,7 @@ export const AgentLatency: FC<{ agent: WorkspaceAgent }> = ({ agent }) => {
|
||||
ref={anchorRef}
|
||||
onMouseEnter={() => setIsOpen(true)}
|
||||
onMouseLeave={() => setIsOpen(false)}
|
||||
className={styles.trigger}
|
||||
style={{ color: latency.color }}
|
||||
css={{ cursor: "pointer", color: latency.color }}
|
||||
>
|
||||
{Math.round(Math.round(latency.latency_ms))}ms
|
||||
</span>
|
||||
@@ -67,7 +65,11 @@ export const AgentLatency: FC<{ agent: WorkspaceAgent }> = ({ agent }) => {
|
||||
first row is the preferred relay.
|
||||
</HelpTooltipText>
|
||||
<HelpTooltipText>
|
||||
<Stack direction="column" spacing={1} className={styles.regions}>
|
||||
<Stack
|
||||
direction="column"
|
||||
spacing={1}
|
||||
css={{ marginTop: theme.spacing(2) }}
|
||||
>
|
||||
{Object.entries(agent.latency)
|
||||
.sort(([, a], [, b]) => a.latency_ms - b.latency_ms)
|
||||
.map(([regionName, region]) => (
|
||||
@@ -76,7 +78,11 @@ export const AgentLatency: FC<{ agent: WorkspaceAgent }> = ({ agent }) => {
|
||||
key={regionName}
|
||||
spacing={0.5}
|
||||
justifyContent="space-between"
|
||||
className={region.preferred ? styles.preferred : undefined}
|
||||
css={
|
||||
region.preferred && {
|
||||
color: theme.palette.text.primary,
|
||||
}
|
||||
}
|
||||
>
|
||||
<strong>{regionName}</strong>
|
||||
{Math.round(region.latency_ms)}ms
|
||||
@@ -88,15 +94,3 @@ export const AgentLatency: FC<{ agent: WorkspaceAgent }> = ({ agent }) => {
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
trigger: {
|
||||
cursor: "pointer",
|
||||
},
|
||||
regions: {
|
||||
marginTop: theme.spacing(2),
|
||||
},
|
||||
preferred: {
|
||||
color: theme.palette.text.primary,
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import Popover from "@mui/material/Popover";
|
||||
import { makeStyles } from "@mui/styles";
|
||||
import { SecondaryAgentButton } from "components/Resources/AgentButton";
|
||||
import { useRef, useState } from "react";
|
||||
import { CodeExample } from "../../CodeExample/CodeExample";
|
||||
import { Stack } from "../../Stack/Stack";
|
||||
import { css } from "@emotion/css";
|
||||
import { type Interpolation, type Theme, useTheme } from "@emotion/react";
|
||||
import { type FC, type PropsWithChildren, useRef, useState } from "react";
|
||||
import {
|
||||
HelpTooltipLink,
|
||||
HelpTooltipLinksGroup,
|
||||
HelpTooltipText,
|
||||
} from "components/HelpTooltip/HelpTooltip";
|
||||
import { docs } from "utils/docs";
|
||||
import { CodeExample } from "../../CodeExample/CodeExample";
|
||||
import { Stack } from "../../Stack/Stack";
|
||||
import { SecondaryAgentButton } from "../AgentButton";
|
||||
|
||||
export interface SSHButtonProps {
|
||||
workspaceName: string;
|
||||
@@ -18,16 +19,16 @@ export interface SSHButtonProps {
|
||||
sshPrefix?: string;
|
||||
}
|
||||
|
||||
export const SSHButton: React.FC<React.PropsWithChildren<SSHButtonProps>> = ({
|
||||
export const SSHButton: FC<PropsWithChildren<SSHButtonProps>> = ({
|
||||
workspaceName,
|
||||
agentName,
|
||||
defaultIsOpen = false,
|
||||
sshPrefix,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const anchorRef = useRef<HTMLButtonElement>(null);
|
||||
const [isOpen, setIsOpen] = useState(defaultIsOpen);
|
||||
const id = isOpen ? "schedule-popover" : undefined;
|
||||
const styles = useStyles();
|
||||
|
||||
const onClose = () => {
|
||||
setIsOpen(false);
|
||||
@@ -45,7 +46,14 @@ export const SSHButton: React.FC<React.PropsWithChildren<SSHButtonProps>> = ({
|
||||
</SecondaryAgentButton>
|
||||
|
||||
<Popover
|
||||
classes={{ paper: styles.popoverPaper }}
|
||||
classes={{
|
||||
paper: css`
|
||||
padding: ${theme.spacing(2, 3, 3)};
|
||||
width: ${theme.spacing(38)};
|
||||
color: ${theme.palette.text.secondary};
|
||||
margin-top: ${theme.spacing(0.25)};
|
||||
`,
|
||||
}}
|
||||
id={id}
|
||||
open={isOpen}
|
||||
anchorEl={anchorRef.current}
|
||||
@@ -63,10 +71,10 @@ export const SSHButton: React.FC<React.PropsWithChildren<SSHButtonProps>> = ({
|
||||
Run the following commands to connect with SSH:
|
||||
</HelpTooltipText>
|
||||
|
||||
<Stack spacing={0.5} className={styles.codeExamples}>
|
||||
<Stack spacing={0.5} css={styles.codeExamples}>
|
||||
<div>
|
||||
<HelpTooltipText>
|
||||
<strong className={styles.codeExampleLabel}>
|
||||
<strong css={styles.codeExampleLabel}>
|
||||
Configure SSH hosts on machine:
|
||||
</strong>
|
||||
</HelpTooltipText>
|
||||
@@ -75,7 +83,7 @@ export const SSHButton: React.FC<React.PropsWithChildren<SSHButtonProps>> = ({
|
||||
|
||||
<div>
|
||||
<HelpTooltipText>
|
||||
<strong className={styles.codeExampleLabel}>
|
||||
<strong css={styles.codeExampleLabel}>
|
||||
Connect to the agent:
|
||||
</strong>
|
||||
</HelpTooltipText>
|
||||
@@ -104,23 +112,12 @@ export const SSHButton: React.FC<React.PropsWithChildren<SSHButtonProps>> = ({
|
||||
);
|
||||
};
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
popoverPaper: {
|
||||
padding: `${theme.spacing(2)} ${theme.spacing(3)} ${theme.spacing(3)}`,
|
||||
width: theme.spacing(38),
|
||||
color: theme.palette.text.secondary,
|
||||
marginTop: theme.spacing(0.25),
|
||||
},
|
||||
|
||||
codeExamples: {
|
||||
const styles = {
|
||||
codeExamples: (theme) => ({
|
||||
marginTop: theme.spacing(1.5),
|
||||
},
|
||||
}),
|
||||
|
||||
codeExampleLabel: {
|
||||
fontSize: 12,
|
||||
},
|
||||
|
||||
textHelper: {
|
||||
fontWeight: 400,
|
||||
},
|
||||
}));
|
||||
} satisfies Record<string, Interpolation<Theme>>;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import { makeStyles } from "@mui/styles";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import VisibilityOffOutlined from "@mui/icons-material/VisibilityOffOutlined";
|
||||
import VisibilityOutlined from "@mui/icons-material/VisibilityOutlined";
|
||||
import { CopyableValue } from "components/CopyableValue/CopyableValue";
|
||||
import { useState } from "react";
|
||||
import { css } from "@emotion/react";
|
||||
import { CopyableValue } from "components/CopyableValue/CopyableValue";
|
||||
|
||||
const Language = {
|
||||
showLabel: "Show value",
|
||||
@@ -13,7 +13,6 @@ const Language = {
|
||||
|
||||
export const SensitiveValue: React.FC<{ value: string }> = ({ value }) => {
|
||||
const [shouldDisplay, setShouldDisplay] = useState(false);
|
||||
const styles = useStyles();
|
||||
const displayValue = shouldDisplay ? value : "••••••••";
|
||||
const buttonLabel = shouldDisplay ? Language.hideLabel : Language.showLabel;
|
||||
const icon = shouldDisplay ? (
|
||||
@@ -23,13 +22,35 @@ export const SensitiveValue: React.FC<{ value: string }> = ({ value }) => {
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={styles.sensitiveValue}>
|
||||
<CopyableValue value={value} className={styles.value}>
|
||||
<div
|
||||
css={(theme) => ({
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: theme.spacing(0.5),
|
||||
})}
|
||||
>
|
||||
<CopyableValue
|
||||
value={value}
|
||||
css={{
|
||||
// 22px is the button width
|
||||
width: "calc(100% - 22px)",
|
||||
overflow: "hidden",
|
||||
whiteSpace: "nowrap",
|
||||
textOverflow: "ellipsis",
|
||||
}}
|
||||
>
|
||||
{displayValue}
|
||||
</CopyableValue>
|
||||
<Tooltip title={buttonLabel}>
|
||||
<IconButton
|
||||
className={styles.button}
|
||||
css={css`
|
||||
color: inherit;
|
||||
|
||||
& .MuiSvgIcon-root {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
`}
|
||||
onClick={() => {
|
||||
setShouldDisplay((value) => !value);
|
||||
}}
|
||||
@@ -42,28 +63,3 @@ export const SensitiveValue: React.FC<{ value: string }> = ({ value }) => {
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
value: {
|
||||
// 22px is the button width
|
||||
width: "calc(100% - 22px)",
|
||||
overflow: "hidden",
|
||||
whiteSpace: "nowrap",
|
||||
textOverflow: "ellipsis",
|
||||
},
|
||||
|
||||
sensitiveValue: {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: theme.spacing(0.5),
|
||||
},
|
||||
|
||||
button: {
|
||||
color: "inherit",
|
||||
|
||||
"& .MuiSvgIcon-root": {
|
||||
width: 16,
|
||||
height: 16,
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -1,40 +1,40 @@
|
||||
import { makeStyles } from "@mui/styles";
|
||||
import { FC, ReactNode } from "react";
|
||||
|
||||
export const useStyles = makeStyles((theme) => ({
|
||||
root: {
|
||||
flex: 1,
|
||||
height: "-webkit-fill-available",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
},
|
||||
layout: {
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
},
|
||||
container: {
|
||||
maxWidth: 385,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
},
|
||||
footer: {
|
||||
fontSize: 12,
|
||||
color: theme.palette.text.secondary,
|
||||
marginTop: theme.spacing(3),
|
||||
},
|
||||
}));
|
||||
import { type FC, type ReactNode } from "react";
|
||||
|
||||
export const SignInLayout: FC<{ children: ReactNode }> = ({ children }) => {
|
||||
const styles = useStyles();
|
||||
|
||||
return (
|
||||
<div className={styles.root}>
|
||||
<div className={styles.layout}>
|
||||
<div className={styles.container}>{children}</div>
|
||||
<div className={styles.footer}>
|
||||
<div
|
||||
css={{
|
||||
flex: 1,
|
||||
height: "-webkit-fill-available",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
css={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
css={{
|
||||
maxWidth: 385,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
<div
|
||||
css={(theme) => ({
|
||||
fontSize: 12,
|
||||
color: theme.palette.text.secondary,
|
||||
marginTop: theme.spacing(3),
|
||||
})}
|
||||
>
|
||||
{`\u00a9 ${new Date().getFullYear()} Coder Technologies, Inc.`}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,28 +1,27 @@
|
||||
import { makeStyles } from "@mui/styles";
|
||||
import TableCell from "@mui/material/TableCell";
|
||||
import TableRow, { TableRowProps } from "@mui/material/TableRow";
|
||||
import { FC, ReactNode, cloneElement, isValidElement } from "react";
|
||||
import TableRow, { type TableRowProps } from "@mui/material/TableRow";
|
||||
import { type FC, type ReactNode, cloneElement, isValidElement } from "react";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { Loader } from "../Loader/Loader";
|
||||
|
||||
export const TableLoader: FC = () => {
|
||||
const styles = useStyles();
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<TableRow>
|
||||
<TableCell colSpan={999} className={styles.cell}>
|
||||
<TableCell
|
||||
colSpan={999}
|
||||
css={{
|
||||
textAlign: "center",
|
||||
height: theme.spacing(20),
|
||||
}}
|
||||
>
|
||||
<Loader />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
};
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
cell: {
|
||||
textAlign: "center",
|
||||
height: theme.spacing(20),
|
||||
},
|
||||
}));
|
||||
|
||||
export const TableLoaderSkeleton = ({
|
||||
rows = 4,
|
||||
children,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { makeStyles } from "@mui/styles";
|
||||
import TableCell from "@mui/material/TableCell";
|
||||
import TableRow from "@mui/material/TableRow";
|
||||
import { FC } from "react";
|
||||
import { type FC } from "react";
|
||||
import { css, useTheme } from "@emotion/react";
|
||||
import { createDisplayDate } from "./utils";
|
||||
|
||||
export interface TimelineDateRow {
|
||||
@@ -9,32 +9,31 @@ export interface TimelineDateRow {
|
||||
}
|
||||
|
||||
export const TimelineDateRow: FC<TimelineDateRow> = ({ date }) => {
|
||||
const styles = useStyles();
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<TableRow className={styles.dateRow}>
|
||||
<TableCell className={styles.dateCell} title={date.toLocaleDateString()}>
|
||||
<TableRow
|
||||
css={css`
|
||||
background: ${theme.palette.background.paper};
|
||||
|
||||
&:not(:first-of-type) td {
|
||||
border-top: 1px solid ${theme.palette.divider};
|
||||
}
|
||||
`}
|
||||
>
|
||||
<TableCell
|
||||
css={{
|
||||
padding: `${theme.spacing(1, 4)} !important`,
|
||||
background: `${theme.palette.background.paperLight} !important`,
|
||||
fontSize: 12,
|
||||
position: "relative",
|
||||
color: theme.palette.text.secondary,
|
||||
textTransform: "capitalize",
|
||||
}}
|
||||
title={date.toLocaleDateString()}
|
||||
>
|
||||
{createDisplayDate(date)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
};
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
dateRow: {
|
||||
background: theme.palette.background.paper,
|
||||
|
||||
"&:not(:first-of-type) td": {
|
||||
borderTop: `1px solid ${theme.palette.divider}`,
|
||||
},
|
||||
},
|
||||
|
||||
dateCell: {
|
||||
padding: `${theme.spacing(1, 4)} !important`,
|
||||
background: `${theme.palette.background.paperLight} !important`,
|
||||
fontSize: 12,
|
||||
position: "relative",
|
||||
color: theme.palette.text.secondary,
|
||||
textTransform: "capitalize",
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { makeStyles } from "@mui/styles";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import { FC, PropsWithChildren } from "react";
|
||||
import { type FC, type PropsWithChildren } from "react";
|
||||
import { css, useTheme } from "@emotion/react";
|
||||
import { CoderIcon } from "../Icons/CoderIcon";
|
||||
|
||||
const Language = {
|
||||
@@ -14,40 +14,41 @@ const Language = {
|
||||
export const Welcome: FC<
|
||||
PropsWithChildren<{ message?: JSX.Element | string }>
|
||||
> = ({ message = Language.defaultMessage }) => {
|
||||
const styles = useStyles();
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={styles.logoBox}>
|
||||
<CoderIcon className={styles.logo} />
|
||||
<div
|
||||
css={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<CoderIcon
|
||||
css={{
|
||||
color: theme.palette.text.primary,
|
||||
fontSize: theme.spacing(8),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<Typography className={styles.title} variant="h1">
|
||||
<Typography
|
||||
css={css`
|
||||
text-align: center;
|
||||
font-size: ${theme.spacing(4)};
|
||||
font-weight: 400;
|
||||
margin: 0;
|
||||
margin-bottom: ${theme.spacing(2)};
|
||||
margin-top: ${theme.spacing(2)};
|
||||
line-height: 1.25;
|
||||
|
||||
& strong {
|
||||
font-weight: 600;
|
||||
}
|
||||
`}
|
||||
variant="h1"
|
||||
>
|
||||
{message}
|
||||
</Typography>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
logoBox: {
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
},
|
||||
logo: {
|
||||
color: theme.palette.text.primary,
|
||||
fontSize: theme.spacing(8),
|
||||
},
|
||||
title: {
|
||||
textAlign: "center",
|
||||
fontSize: theme.spacing(4),
|
||||
fontWeight: 400,
|
||||
margin: 0,
|
||||
marginBottom: theme.spacing(2),
|
||||
marginTop: theme.spacing(2),
|
||||
lineHeight: 1.25,
|
||||
|
||||
"& strong": {
|
||||
fontWeight: 600,
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import { makeStyles } from "@mui/styles";
|
||||
import { useOrganizationId } from "hooks/useOrganizationId";
|
||||
import { createContext, FC, Suspense, useContext } from "react";
|
||||
import { css } from "@emotion/css";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { createContext, type FC, Suspense, useContext } from "react";
|
||||
import { useQuery } from "react-query";
|
||||
import { NavLink, Outlet, useNavigate, useParams } from "react-router-dom";
|
||||
import { combineClasses } from "utils/combineClasses";
|
||||
import { Margins } from "components/Margins/Margins";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import { Loader } from "components/Loader/Loader";
|
||||
import { TemplatePageHeader } from "./TemplatePageHeader";
|
||||
import type { AuthorizationRequest } from "api/typesGenerated";
|
||||
import {
|
||||
checkAuthorization,
|
||||
getTemplateByName,
|
||||
getTemplateVersion,
|
||||
} from "api/api";
|
||||
import { useQuery } from "react-query";
|
||||
import { AuthorizationRequest } from "api/typesGenerated";
|
||||
import { ErrorAlert } from "components/Alert/ErrorAlert";
|
||||
import { Margins } from "components/Margins/Margins";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import { Loader } from "components/Loader/Loader";
|
||||
import { useOrganizationId } from "hooks/useOrganizationId";
|
||||
import { TemplatePageHeader } from "./TemplatePageHeader";
|
||||
|
||||
const templatePermissions = (
|
||||
templateId: string,
|
||||
@@ -63,8 +63,8 @@ export const useTemplateLayoutContext = (): TemplateLayoutContextValue => {
|
||||
export const TemplateLayout: FC<{ children?: JSX.Element }> = ({
|
||||
children = <Outlet />,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const navigate = useNavigate();
|
||||
const styles = useStyles();
|
||||
const orgId = useOrganizationId();
|
||||
const { template: templateName } = useParams() as { template: string };
|
||||
const { data, error, isLoading } = useQuery({
|
||||
@@ -75,7 +75,7 @@ export const TemplateLayout: FC<{ children?: JSX.Element }> = ({
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className={styles.error}>
|
||||
<div css={{ margin: theme.spacing(2) }}>
|
||||
<ErrorAlert error={error} />
|
||||
</div>
|
||||
);
|
||||
@@ -85,6 +85,34 @@ export const TemplateLayout: FC<{ children?: JSX.Element }> = ({
|
||||
return <Loader />;
|
||||
}
|
||||
|
||||
const itemStyles = css`
|
||||
text-decoration: none;
|
||||
color: ${theme.palette.text.secondary};
|
||||
font-size: 14;
|
||||
display: block;
|
||||
padding: ${theme.spacing(0, 2, 2)};
|
||||
|
||||
&:hover {
|
||||
color: ${theme.palette.text.primary};
|
||||
}
|
||||
`;
|
||||
|
||||
const activeItemStyles = css`
|
||||
${itemStyles}
|
||||
color: ${theme.palette.text.primary};
|
||||
position: relative;
|
||||
|
||||
&:before {
|
||||
content: "";
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
height: 2;
|
||||
width: 100%;
|
||||
background: ${theme.palette.secondary.dark};
|
||||
position: absolute;
|
||||
}
|
||||
`;
|
||||
|
||||
return (
|
||||
<>
|
||||
<TemplatePageHeader
|
||||
@@ -96,17 +124,19 @@ export const TemplateLayout: FC<{ children?: JSX.Element }> = ({
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className={styles.tabs}>
|
||||
<div
|
||||
css={{
|
||||
borderBottom: `1px solid ${theme.palette.divider}`,
|
||||
marginBottom: theme.spacing(5),
|
||||
}}
|
||||
>
|
||||
<Margins>
|
||||
<Stack direction="row" spacing={0.25}>
|
||||
<NavLink
|
||||
end
|
||||
to={`/templates/${templateName}`}
|
||||
className={({ isActive }) =>
|
||||
combineClasses([
|
||||
styles.tabItem,
|
||||
isActive ? styles.tabItemActive : undefined,
|
||||
])
|
||||
isActive ? activeItemStyles : itemStyles
|
||||
}
|
||||
>
|
||||
Summary
|
||||
@@ -115,10 +145,7 @@ export const TemplateLayout: FC<{ children?: JSX.Element }> = ({
|
||||
end
|
||||
to={`/templates/${templateName}/docs`}
|
||||
className={({ isActive }) =>
|
||||
combineClasses([
|
||||
styles.tabItem,
|
||||
isActive ? styles.tabItemActive : undefined,
|
||||
])
|
||||
isActive ? activeItemStyles : itemStyles
|
||||
}
|
||||
>
|
||||
Docs
|
||||
@@ -127,10 +154,7 @@ export const TemplateLayout: FC<{ children?: JSX.Element }> = ({
|
||||
<NavLink
|
||||
to={`/templates/${templateName}/files`}
|
||||
className={({ isActive }) =>
|
||||
combineClasses([
|
||||
styles.tabItem,
|
||||
isActive ? styles.tabItemActive : undefined,
|
||||
])
|
||||
isActive ? activeItemStyles : itemStyles
|
||||
}
|
||||
>
|
||||
Source Code
|
||||
@@ -139,10 +163,7 @@ export const TemplateLayout: FC<{ children?: JSX.Element }> = ({
|
||||
<NavLink
|
||||
to={`/templates/${templateName}/versions`}
|
||||
className={({ isActive }) =>
|
||||
combineClasses([
|
||||
styles.tabItem,
|
||||
isActive ? styles.tabItemActive : undefined,
|
||||
])
|
||||
isActive ? activeItemStyles : itemStyles
|
||||
}
|
||||
>
|
||||
Versions
|
||||
@@ -150,10 +171,7 @@ export const TemplateLayout: FC<{ children?: JSX.Element }> = ({
|
||||
<NavLink
|
||||
to={`/templates/${templateName}/embed`}
|
||||
className={({ isActive }) =>
|
||||
combineClasses([
|
||||
styles.tabItem,
|
||||
isActive ? styles.tabItemActive : undefined,
|
||||
])
|
||||
isActive ? activeItemStyles : itemStyles
|
||||
}
|
||||
>
|
||||
Embed
|
||||
@@ -162,10 +180,7 @@ export const TemplateLayout: FC<{ children?: JSX.Element }> = ({
|
||||
<NavLink
|
||||
to={`/templates/${templateName}/insights`}
|
||||
className={({ isActive }) =>
|
||||
combineClasses([
|
||||
styles.tabItem,
|
||||
isActive ? styles.tabItemActive : undefined,
|
||||
])
|
||||
isActive ? activeItemStyles : itemStyles
|
||||
}
|
||||
>
|
||||
Insights
|
||||
@@ -183,42 +198,3 @@ export const TemplateLayout: FC<{ children?: JSX.Element }> = ({
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const useStyles = makeStyles((theme) => {
|
||||
return {
|
||||
error: {
|
||||
margin: theme.spacing(2),
|
||||
},
|
||||
tabs: {
|
||||
borderBottom: `1px solid ${theme.palette.divider}`,
|
||||
marginBottom: theme.spacing(5),
|
||||
},
|
||||
|
||||
tabItem: {
|
||||
textDecoration: "none",
|
||||
color: theme.palette.text.secondary,
|
||||
fontSize: 14,
|
||||
display: "block",
|
||||
padding: theme.spacing(0, 2, 2),
|
||||
|
||||
"&:hover": {
|
||||
color: theme.palette.text.primary,
|
||||
},
|
||||
},
|
||||
|
||||
tabItemActive: {
|
||||
color: theme.palette.text.primary,
|
||||
position: "relative",
|
||||
|
||||
"&:before": {
|
||||
content: `""`,
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
height: 2,
|
||||
width: "100%",
|
||||
background: theme.palette.secondary.dark,
|
||||
position: "absolute",
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
+5
-21
@@ -1,15 +1,14 @@
|
||||
import {
|
||||
import { type ComponentProps, type FC } from "react";
|
||||
import type {
|
||||
CreateTemplateVersionRequest,
|
||||
TemplateVersion,
|
||||
TemplateVersionVariable,
|
||||
} from "api/typesGenerated";
|
||||
import { Alert } from "components/Alert/Alert";
|
||||
import { ComponentProps, FC } from "react";
|
||||
import { TemplateVariablesForm } from "./TemplateVariablesForm";
|
||||
import { makeStyles } from "@mui/styles";
|
||||
import { PageHeader, PageHeaderTitle } from "components/PageHeader/PageHeader";
|
||||
import { ErrorAlert } from "components/Alert/ErrorAlert";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import { TemplateVariablesForm } from "./TemplateVariablesForm";
|
||||
|
||||
export interface TemplateVariablesPageViewProps {
|
||||
templateVersion?: TemplateVersion;
|
||||
@@ -41,16 +40,15 @@ export const TemplateVariablesPageView: FC<TemplateVariablesPageViewProps> = ({
|
||||
errors = {},
|
||||
initialTouched,
|
||||
}) => {
|
||||
const classes = useStyles();
|
||||
const hasError = Object.values(errors).some((error) => Boolean(error));
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageHeader className={classes.pageHeader}>
|
||||
<PageHeader css={{ paddingTop: 0 }}>
|
||||
<PageHeaderTitle>Template variables</PageHeaderTitle>
|
||||
</PageHeader>
|
||||
{hasError && (
|
||||
<Stack className={classes.errorContainer}>
|
||||
<Stack css={(theme) => ({ marginBottom: theme.spacing(8) })}>
|
||||
{Boolean(errors.buildError) && (
|
||||
<ErrorAlert error={errors.buildError} />
|
||||
)}
|
||||
@@ -78,17 +76,3 @@ export const TemplateVariablesPageView: FC<TemplateVariablesPageViewProps> = ({
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
errorContainer: {
|
||||
marginBottom: theme.spacing(8),
|
||||
},
|
||||
goBackSection: {
|
||||
display: "flex",
|
||||
width: "100%",
|
||||
marginTop: 32,
|
||||
},
|
||||
pageHeader: {
|
||||
paddingTop: 0,
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { type Interpolation, type Theme } from "@emotion/react";
|
||||
import Button from "@mui/material/Button";
|
||||
import Link from "@mui/material/Link";
|
||||
import { makeStyles } from "@mui/styles";
|
||||
import { TemplateExample } from "api/typesGenerated";
|
||||
import { Link as RouterLink } from "react-router-dom";
|
||||
import { type FC } from "react";
|
||||
import type { TemplateExample } from "api/typesGenerated";
|
||||
import { CodeExample } from "components/CodeExample/CodeExample";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import { TableEmpty } from "components/TableEmpty/TableEmpty";
|
||||
import { TemplateExampleCard } from "components/TemplateExampleCard/TemplateExampleCard";
|
||||
import { FC } from "react";
|
||||
import { Link as RouterLink } from "react-router-dom";
|
||||
import { docs } from "utils/docs";
|
||||
|
||||
// Those are from https://github.com/coder/coder/tree/main/examples/templates
|
||||
@@ -39,7 +39,6 @@ export const EmptyTemplates: FC<{
|
||||
canCreateTemplates: boolean;
|
||||
examples: TemplateExample[];
|
||||
}> = ({ canCreateTemplates, examples }) => {
|
||||
const styles = useStyles();
|
||||
const featuredExamples = findFeaturedExamples(examples);
|
||||
|
||||
if (canCreateTemplates) {
|
||||
@@ -63,12 +62,12 @@ export const EmptyTemplates: FC<{
|
||||
}
|
||||
cta={
|
||||
<Stack alignItems="center" spacing={4}>
|
||||
<div className={styles.featuredExamples}>
|
||||
<div css={styles.featuredExamples}>
|
||||
{featuredExamples.map((example) => (
|
||||
<TemplateExampleCard
|
||||
example={example}
|
||||
key={example.id}
|
||||
className={styles.template}
|
||||
css={styles.template}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
@@ -77,7 +76,7 @@ export const EmptyTemplates: FC<{
|
||||
size="small"
|
||||
component={RouterLink}
|
||||
to="/starter-templates"
|
||||
className={styles.viewAllButton}
|
||||
css={styles.viewAllButton}
|
||||
>
|
||||
View all starter templates
|
||||
</Button>
|
||||
@@ -89,12 +88,12 @@ export const EmptyTemplates: FC<{
|
||||
|
||||
return (
|
||||
<TableEmpty
|
||||
className={styles.withImage}
|
||||
css={styles.withImage}
|
||||
message="Create a Template"
|
||||
description="Contact your Coder administrator to create a template. You can share the code below."
|
||||
cta={<CodeExample code="coder templates init" />}
|
||||
image={
|
||||
<div className={styles.emptyImage}>
|
||||
<div css={styles.emptyImage}>
|
||||
<img src="/featured/templates.webp" alt="" />
|
||||
</div>
|
||||
}
|
||||
@@ -102,12 +101,12 @@ export const EmptyTemplates: FC<{
|
||||
);
|
||||
};
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
const styles = {
|
||||
withImage: {
|
||||
paddingBottom: 0,
|
||||
},
|
||||
|
||||
emptyImage: {
|
||||
emptyImage: (theme) => ({
|
||||
maxWidth: "50%",
|
||||
height: theme.spacing(40),
|
||||
overflow: "hidden",
|
||||
@@ -116,25 +115,25 @@ const useStyles = makeStyles((theme) => ({
|
||||
"& img": {
|
||||
maxWidth: "100%",
|
||||
},
|
||||
},
|
||||
}),
|
||||
|
||||
featuredExamples: {
|
||||
featuredExamples: (theme) => ({
|
||||
maxWidth: theme.spacing(100),
|
||||
display: "grid",
|
||||
gridTemplateColumns: "repeat(2, minmax(0, 1fr))",
|
||||
gap: theme.spacing(2),
|
||||
gridAutoRows: "min-content",
|
||||
},
|
||||
}),
|
||||
|
||||
template: {
|
||||
template: (theme) => ({
|
||||
backgroundColor: theme.palette.background.paperLight,
|
||||
|
||||
"&:hover": {
|
||||
backgroundColor: theme.palette.divider,
|
||||
},
|
||||
},
|
||||
}),
|
||||
|
||||
viewAllButton: {
|
||||
borderRadius: 9999,
|
||||
},
|
||||
}));
|
||||
} satisfies Record<string, Interpolation<Theme>>;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { makeStyles } from "@mui/styles";
|
||||
import Box from "@mui/material/Box";
|
||||
import Button from "@mui/material/Button";
|
||||
import CircularProgress from "@mui/material/CircularProgress";
|
||||
import { GitSSHKey } from "api/typesGenerated";
|
||||
import { type FC, type PropsWithChildren } from "react";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import type { GitSSHKey } from "api/typesGenerated";
|
||||
import { CodeExample } from "components/CodeExample/CodeExample";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import { FC } from "react";
|
||||
import { ErrorAlert } from "components/Alert/ErrorAlert";
|
||||
|
||||
export interface SSHKeysPageViewProps {
|
||||
@@ -16,16 +16,14 @@ export interface SSHKeysPageViewProps {
|
||||
onRegenerateClick: () => void;
|
||||
}
|
||||
|
||||
export const SSHKeysPageView: FC<
|
||||
React.PropsWithChildren<SSHKeysPageViewProps>
|
||||
> = ({
|
||||
export const SSHKeysPageView: FC<PropsWithChildren<SSHKeysPageViewProps>> = ({
|
||||
isLoading,
|
||||
getSSHKeyError,
|
||||
regenerateSSHKeyError,
|
||||
sshKey,
|
||||
onRegenerateClick,
|
||||
}) => {
|
||||
const styles = useStyles();
|
||||
const theme = useTheme();
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
@@ -45,11 +43,28 @@ export const SSHKeysPageView: FC<
|
||||
)}
|
||||
{sshKey && (
|
||||
<>
|
||||
<p className={styles.description}>
|
||||
<p
|
||||
css={{
|
||||
fontSize: 14,
|
||||
color: theme.palette.text.secondary,
|
||||
margin: 0,
|
||||
}}
|
||||
>
|
||||
The following public key is used to authenticate Git in workspaces.
|
||||
You may add it to Git services (such as GitHub) that you need to
|
||||
access from your workspace. Coder configures authentication via{" "}
|
||||
<code className={styles.code}>$GIT_SSH_COMMAND</code>.
|
||||
<code
|
||||
css={{
|
||||
background: theme.palette.divider,
|
||||
fontSize: 12,
|
||||
padding: "2px 4px",
|
||||
color: theme.palette.text.primary,
|
||||
borderRadius: 2,
|
||||
}}
|
||||
>
|
||||
$GIT_SSH_COMMAND
|
||||
</code>
|
||||
.
|
||||
</p>
|
||||
<CodeExample code={sshKey.public_key.trim()} />
|
||||
<div>
|
||||
@@ -62,18 +77,3 @@ export const SSHKeysPageView: FC<
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
description: {
|
||||
fontSize: 14,
|
||||
color: theme.palette.text.secondary,
|
||||
margin: 0,
|
||||
},
|
||||
code: {
|
||||
background: theme.palette.divider,
|
||||
fontSize: 12,
|
||||
padding: "2px 4px",
|
||||
color: theme.palette.text.primary,
|
||||
borderRadius: 2,
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { makeStyles } from "@mui/styles";
|
||||
import { FC } from "react";
|
||||
import * as TypesGen from "api/typesGenerated";
|
||||
import { type FC } from "react";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import type * as TypesGen from "api/typesGenerated";
|
||||
import { CodeExample } from "components/CodeExample/CodeExample";
|
||||
import { ConfirmDialog } from "components/Dialogs/ConfirmDialog/ConfirmDialog";
|
||||
|
||||
@@ -26,12 +26,20 @@ export const Language = {
|
||||
export const ResetPasswordDialog: FC<
|
||||
React.PropsWithChildren<ResetPasswordDialogProps>
|
||||
> = ({ open, onClose, onConfirm, user, newPassword, loading }) => {
|
||||
const styles = useStyles();
|
||||
const theme = useTheme();
|
||||
|
||||
const description = (
|
||||
<>
|
||||
<p>{Language.message(user?.username)}</p>
|
||||
<CodeExample code={newPassword ?? ""} className={styles.codeExample} />
|
||||
<CodeExample
|
||||
code={newPassword ?? ""}
|
||||
css={{
|
||||
minHeight: "auto",
|
||||
userSelect: "all",
|
||||
width: "100%",
|
||||
marginTop: theme.spacing(3),
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -49,12 +57,3 @@ export const ResetPasswordDialog: FC<
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
codeExample: {
|
||||
minHeight: "auto",
|
||||
userSelect: "all",
|
||||
width: "100%",
|
||||
marginTop: theme.spacing(3),
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -1,32 +1,30 @@
|
||||
import Box, { BoxProps } from "@mui/material/Box";
|
||||
import { makeStyles, useTheme } from "@mui/styles";
|
||||
import Box, { type BoxProps } from "@mui/material/Box";
|
||||
import TableCell from "@mui/material/TableCell";
|
||||
import TableRow from "@mui/material/TableRow";
|
||||
import Skeleton from "@mui/material/Skeleton";
|
||||
import { type Interpolation, type Theme, useTheme } from "@emotion/react";
|
||||
import { type FC } from "react";
|
||||
import dayjs from "dayjs";
|
||||
import relativeTime from "dayjs/plugin/relativeTime";
|
||||
import type * as TypesGen from "api/typesGenerated";
|
||||
import { ChooseOne, Cond } from "components/Conditionals/ChooseOne";
|
||||
import { Pill } from "components/Pill/Pill";
|
||||
import { type FC } from "react";
|
||||
import * as TypesGen from "api/typesGenerated";
|
||||
import { combineClasses } from "utils/combineClasses";
|
||||
import { AvatarData } from "components/AvatarData/AvatarData";
|
||||
import { AvatarDataSkeleton } from "components/AvatarData/AvatarDataSkeleton";
|
||||
import { EmptyState } from "components/EmptyState/EmptyState";
|
||||
import {
|
||||
TableLoaderSkeleton,
|
||||
TableRowSkeleton,
|
||||
} from "components/TableLoader/TableLoader";
|
||||
import { TableRowMenu } from "components/TableRowMenu/TableRowMenu";
|
||||
import { EditRolesButton } from "./EditRolesButton";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import { EnterpriseBadge } from "components/DeploySettingsLayout/Badges";
|
||||
import dayjs from "dayjs";
|
||||
import { SxProps, Theme } from "@mui/material/styles";
|
||||
import { EditRolesButton } from "./EditRolesButton";
|
||||
import HideSourceOutlined from "@mui/icons-material/HideSourceOutlined";
|
||||
import KeyOutlined from "@mui/icons-material/KeyOutlined";
|
||||
import GitHub from "@mui/icons-material/GitHub";
|
||||
import PasswordOutlined from "@mui/icons-material/PasswordOutlined";
|
||||
import relativeTime from "dayjs/plugin/relativeTime";
|
||||
import ShieldOutlined from "@mui/icons-material/ShieldOutlined";
|
||||
import Skeleton from "@mui/material/Skeleton";
|
||||
import { AvatarDataSkeleton } from "components/AvatarData/AvatarDataSkeleton";
|
||||
|
||||
dayjs.extend(relativeTime);
|
||||
|
||||
@@ -89,8 +87,6 @@ export const UsersTableBody: FC<
|
||||
actorID,
|
||||
oidcRoleSyncEnabled,
|
||||
}) => {
|
||||
const styles = useStyles();
|
||||
|
||||
return (
|
||||
<ChooseOne>
|
||||
<Cond condition={Boolean(isLoading)}>
|
||||
@@ -185,10 +181,10 @@ export const UsersTableBody: FC<
|
||||
<Pill
|
||||
key={role.name}
|
||||
text={role.display_name}
|
||||
className={combineClasses({
|
||||
[styles.rolePill]: true,
|
||||
[styles.rolePillOwner]: isOwnerRole(role),
|
||||
})}
|
||||
css={[
|
||||
styles.rolePill,
|
||||
isOwnerRole(role) && styles.rolePillOwner,
|
||||
]}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
@@ -200,12 +196,10 @@ export const UsersTableBody: FC<
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell
|
||||
className={combineClasses([
|
||||
css={[
|
||||
styles.status,
|
||||
user.status === "suspended"
|
||||
? styles.suspended
|
||||
: undefined,
|
||||
])}
|
||||
user.status === "suspended" && styles.suspended,
|
||||
]}
|
||||
>
|
||||
<Box>{user.status}</Box>
|
||||
<LastSeen value={user.last_seen_at} sx={{ fontSize: 12 }} />
|
||||
@@ -273,9 +267,9 @@ const LoginType = ({
|
||||
authMethods: TypesGen.AuthMethods;
|
||||
value: TypesGen.LoginType;
|
||||
}) => {
|
||||
let displayName = value as string;
|
||||
let displayName: string = value;
|
||||
let icon = <></>;
|
||||
const iconStyles: SxProps = { width: 14, height: 14 };
|
||||
const iconStyles = { width: 14, height: 14 };
|
||||
|
||||
if (value === "password") {
|
||||
displayName = "Password";
|
||||
@@ -314,7 +308,7 @@ const LoginType = ({
|
||||
};
|
||||
|
||||
const LastSeen = ({ value, ...boxProps }: { value: string } & BoxProps) => {
|
||||
const theme: Theme = useTheme();
|
||||
const theme = useTheme();
|
||||
const t = dayjs(value);
|
||||
const now = dayjs();
|
||||
|
||||
@@ -348,19 +342,19 @@ const LastSeen = ({ value, ...boxProps }: { value: string } & BoxProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
const styles = {
|
||||
status: {
|
||||
textTransform: "capitalize",
|
||||
},
|
||||
suspended: {
|
||||
suspended: (theme) => ({
|
||||
color: theme.palette.text.secondary,
|
||||
},
|
||||
rolePill: {
|
||||
}),
|
||||
rolePill: (theme) => ({
|
||||
backgroundColor: theme.palette.background.paperLight,
|
||||
borderColor: theme.palette.divider,
|
||||
},
|
||||
rolePillOwner: {
|
||||
}),
|
||||
rolePillOwner: (theme) => ({
|
||||
backgroundColor: theme.palette.info.dark,
|
||||
borderColor: theme.palette.info.light,
|
||||
},
|
||||
}));
|
||||
}),
|
||||
} satisfies Record<string, Interpolation<Theme>>;
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
import { makeStyles } from "@mui/styles";
|
||||
import TableCell from "@mui/material/TableCell";
|
||||
import { WorkspaceBuild } from "api/typesGenerated";
|
||||
import { type CSSObject, type Interpolation, type Theme } from "@emotion/react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import type { WorkspaceBuild } from "api/typesGenerated";
|
||||
import { BuildAvatar } from "components/BuildAvatar/BuildAvatar";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import { TimelineEntry } from "components/Timeline/TimelineEntry";
|
||||
import { useClickable } from "hooks/useClickable";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { MONOSPACE_FONT_FAMILY } from "theme/constants";
|
||||
import {
|
||||
displayWorkspaceBuildDuration,
|
||||
getDisplayWorkspaceBuildInitiatedBy,
|
||||
} from "utils/workspace";
|
||||
import { BuildAvatar } from "components/BuildAvatar/BuildAvatar";
|
||||
|
||||
export interface BuildRowProps {
|
||||
build: WorkspaceBuild;
|
||||
@@ -23,7 +22,6 @@ const transitionMessages = {
|
||||
};
|
||||
|
||||
export const BuildRow: React.FC<BuildRowProps> = ({ build }) => {
|
||||
const styles = useStyles();
|
||||
const initiatedBy = getDisplayWorkspaceBuildInitiatedBy(build);
|
||||
const navigate = useNavigate();
|
||||
const clickableProps = useClickable<HTMLTableRowElement>(() =>
|
||||
@@ -32,26 +30,18 @@ export const BuildRow: React.FC<BuildRowProps> = ({ build }) => {
|
||||
|
||||
return (
|
||||
<TimelineEntry hover data-testid={`build-${build.id}`} {...clickableProps}>
|
||||
<TableCell className={styles.buildCell}>
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
className={styles.buildWrapper}
|
||||
>
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
className={styles.fullWidth}
|
||||
>
|
||||
<TableCell css={styles.buildCell}>
|
||||
<Stack direction="row" alignItems="center" css={styles.buildWrapper}>
|
||||
<Stack direction="row" alignItems="center" css={styles.fullWidth}>
|
||||
<BuildAvatar build={build} />
|
||||
<Stack
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
className={styles.fullWidth}
|
||||
css={styles.fullWidth}
|
||||
>
|
||||
<Stack
|
||||
className={styles.buildSummary}
|
||||
css={styles.buildSummary}
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
spacing={1}
|
||||
@@ -63,22 +53,22 @@ export const BuildRow: React.FC<BuildRowProps> = ({ build }) => {
|
||||
workspace
|
||||
</span>
|
||||
|
||||
<span className={styles.buildTime}>
|
||||
<span css={styles.buildTime}>
|
||||
{new Date(build.created_at).toLocaleTimeString()}
|
||||
</span>
|
||||
</Stack>
|
||||
|
||||
<Stack direction="row" spacing={1}>
|
||||
<span className={styles.buildInfo}>
|
||||
<span css={styles.buildInfo}>
|
||||
Reason: <strong>{build.reason}</strong>
|
||||
</span>
|
||||
|
||||
<span className={styles.buildInfo}>
|
||||
<span css={styles.buildInfo}>
|
||||
Duration:{" "}
|
||||
<strong>{displayWorkspaceBuildDuration(build)}</strong>
|
||||
</span>
|
||||
|
||||
<span className={styles.buildInfo}>
|
||||
<span css={styles.buildInfo}>
|
||||
Version: <strong>{build.template_version_name}</strong>
|
||||
</span>
|
||||
</Stack>
|
||||
@@ -90,10 +80,10 @@ export const BuildRow: React.FC<BuildRowProps> = ({ build }) => {
|
||||
);
|
||||
};
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
buildWrapper: {
|
||||
const styles = {
|
||||
buildWrapper: (theme) => ({
|
||||
padding: theme.spacing(2, 4),
|
||||
},
|
||||
}),
|
||||
|
||||
buildCell: {
|
||||
padding: "0 !important",
|
||||
@@ -101,36 +91,25 @@ const useStyles = makeStyles((theme) => ({
|
||||
borderBottom: 0,
|
||||
},
|
||||
|
||||
buildSummary: {
|
||||
...theme.typography.body1,
|
||||
buildSummary: (theme) => ({
|
||||
...(theme.typography.body1 as CSSObject),
|
||||
fontFamily: "inherit",
|
||||
},
|
||||
}),
|
||||
|
||||
buildInfo: {
|
||||
...theme.typography.body2,
|
||||
buildInfo: (theme) => ({
|
||||
...(theme.typography.body2 as CSSObject),
|
||||
fontSize: 12,
|
||||
fontFamily: "inherit",
|
||||
color: theme.palette.text.secondary,
|
||||
display: "block",
|
||||
},
|
||||
}),
|
||||
|
||||
buildTime: {
|
||||
buildTime: (theme) => ({
|
||||
color: theme.palette.text.secondary,
|
||||
fontSize: 12,
|
||||
},
|
||||
|
||||
buildRight: {
|
||||
width: "auto",
|
||||
},
|
||||
|
||||
buildExtraInfo: {
|
||||
...theme.typography.body2,
|
||||
fontFamily: MONOSPACE_FONT_FAMILY,
|
||||
color: theme.palette.text.secondary,
|
||||
whiteSpace: "nowrap",
|
||||
},
|
||||
}),
|
||||
|
||||
fullWidth: {
|
||||
width: "100%",
|
||||
},
|
||||
}));
|
||||
} satisfies Record<string, Interpolation<Theme>>;
|
||||
|
||||
@@ -1,25 +1,26 @@
|
||||
import { makeStyles } from "@mui/styles";
|
||||
import Dialog from "@mui/material/Dialog";
|
||||
import DialogContent from "@mui/material/DialogContent";
|
||||
import DialogContentText from "@mui/material/DialogContentText";
|
||||
import DialogTitle from "@mui/material/DialogTitle";
|
||||
import { DialogProps } from "components/Dialogs/Dialog";
|
||||
import { FC } from "react";
|
||||
import DialogActions from "@mui/material/DialogActions";
|
||||
import Button from "@mui/material/Button";
|
||||
import { useFormik } from "formik";
|
||||
import * as Yup from "yup";
|
||||
import { type FC } from "react";
|
||||
import { css } from "@emotion/css";
|
||||
import { type Interpolation, type Theme, useTheme } from "@emotion/react";
|
||||
import { getFormHelpers } from "utils/formUtils";
|
||||
import type { DialogProps } from "components/Dialogs/Dialog";
|
||||
import { FormFields, VerticalForm } from "components/Form/Form";
|
||||
import {
|
||||
import type {
|
||||
TemplateVersionParameter,
|
||||
WorkspaceBuildParameter,
|
||||
} from "api/typesGenerated";
|
||||
import { RichParameterInput } from "components/RichParameterInput/RichParameterInput";
|
||||
import { useFormik } from "formik";
|
||||
import {
|
||||
getInitialRichParameterValues,
|
||||
useValidationSchemaForRichParameters,
|
||||
} from "utils/richParameters";
|
||||
import * as Yup from "yup";
|
||||
import DialogActions from "@mui/material/DialogActions";
|
||||
import Button from "@mui/material/Button";
|
||||
|
||||
export type UpdateBuildParametersDialogProps = DialogProps & {
|
||||
onClose: () => void;
|
||||
@@ -30,7 +31,7 @@ export type UpdateBuildParametersDialogProps = DialogProps & {
|
||||
export const UpdateBuildParametersDialog: FC<
|
||||
UpdateBuildParametersDialogProps
|
||||
> = ({ missedParameters, onUpdate, ...dialogProps }) => {
|
||||
const styles = useStyles();
|
||||
const theme = useTheme();
|
||||
const form = useFormik({
|
||||
initialValues: {
|
||||
rich_parameter_values: getInitialRichParameterValues(missedParameters),
|
||||
@@ -56,17 +57,26 @@ export const UpdateBuildParametersDialog: FC<
|
||||
>
|
||||
<DialogTitle
|
||||
id="update-build-parameters-title"
|
||||
classes={{ root: styles.title }}
|
||||
classes={{
|
||||
root: css`
|
||||
padding: ${theme.spacing(3, 5)};
|
||||
|
||||
& h2 {
|
||||
font-size: ${theme.spacing(2.5)};
|
||||
font-weight: 400;
|
||||
}
|
||||
`,
|
||||
}}
|
||||
>
|
||||
Workspace parameters
|
||||
</DialogTitle>
|
||||
<DialogContent className={styles.content}>
|
||||
<DialogContentText className={styles.info}>
|
||||
<DialogContent css={styles.content}>
|
||||
<DialogContentText css={{ margin: 0 }}>
|
||||
This template has new parameters that must be configured to complete
|
||||
the update
|
||||
</DialogContentText>
|
||||
<VerticalForm
|
||||
className={styles.form}
|
||||
css={styles.form}
|
||||
onSubmit={form.handleSubmit}
|
||||
id="updateParameters"
|
||||
>
|
||||
@@ -96,7 +106,7 @@ export const UpdateBuildParametersDialog: FC<
|
||||
)}
|
||||
</VerticalForm>
|
||||
</DialogContent>
|
||||
<DialogActions disableSpacing className={styles.dialogActions}>
|
||||
<DialogActions disableSpacing css={styles.dialogActions}>
|
||||
<Button fullWidth type="button" onClick={dialogProps.onClose}>
|
||||
Cancel
|
||||
</Button>
|
||||
@@ -114,48 +124,18 @@ export const UpdateBuildParametersDialog: FC<
|
||||
);
|
||||
};
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
title: {
|
||||
padding: theme.spacing(3, 5),
|
||||
|
||||
"& h2": {
|
||||
fontSize: theme.spacing(2.5),
|
||||
fontWeight: 400,
|
||||
},
|
||||
},
|
||||
|
||||
content: {
|
||||
const styles = {
|
||||
content: (theme) => ({
|
||||
padding: theme.spacing(0, 5, 0, 5),
|
||||
},
|
||||
}),
|
||||
|
||||
info: {
|
||||
margin: 0,
|
||||
},
|
||||
|
||||
form: {
|
||||
form: (theme) => ({
|
||||
paddingTop: theme.spacing(4),
|
||||
},
|
||||
}),
|
||||
|
||||
infoTitle: {
|
||||
fontSize: theme.spacing(2),
|
||||
fontWeight: 600,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: theme.spacing(1),
|
||||
},
|
||||
|
||||
warningIcon: {
|
||||
color: theme.palette.warning.light,
|
||||
fontSize: theme.spacing(1.5),
|
||||
},
|
||||
|
||||
formFooter: {
|
||||
flexDirection: "column",
|
||||
},
|
||||
|
||||
dialogActions: {
|
||||
dialogActions: (theme) => ({
|
||||
padding: theme.spacing(5),
|
||||
flexDirection: "column",
|
||||
gap: theme.spacing(1),
|
||||
},
|
||||
}));
|
||||
}),
|
||||
} satisfies Record<string, Interpolation<Theme>>;
|
||||
|
||||
@@ -1,22 +1,14 @@
|
||||
import { type Interpolation, type Theme } from "@emotion/react";
|
||||
import Button from "@mui/material/Button";
|
||||
import { makeStyles } from "@mui/styles";
|
||||
import { Avatar } from "components/Avatar/Avatar";
|
||||
import { AgentRow } from "components/Resources/AgentRow";
|
||||
import {
|
||||
ActiveTransition,
|
||||
WorkspaceBuildProgress,
|
||||
} from "./WorkspaceBuildProgress";
|
||||
import { FC, useEffect, useState } from "react";
|
||||
import AlertTitle from "@mui/material/AlertTitle";
|
||||
import { type FC, useEffect, useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import * as TypesGen from "api/typesGenerated";
|
||||
import dayjs from "dayjs";
|
||||
import type * as TypesGen from "api/typesGenerated";
|
||||
import { Alert, AlertDetail } from "components/Alert/Alert";
|
||||
import { BuildsTable } from "./BuildsTable";
|
||||
import { Margins } from "components/Margins/Margins";
|
||||
import { Resources } from "components/Resources/Resources";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import { WorkspaceActions } from "pages/WorkspacePage/WorkspaceActions/WorkspaceActions";
|
||||
import { WorkspaceDeletedBanner } from "./WorkspaceDeletedBanner";
|
||||
import { WorkspaceStats } from "./WorkspaceStats";
|
||||
import {
|
||||
FullWidthPageHeader,
|
||||
PageHeaderActions,
|
||||
@@ -26,9 +18,17 @@ import {
|
||||
import { TemplateVersionWarnings } from "components/TemplateVersionWarnings/TemplateVersionWarnings";
|
||||
import { ErrorAlert } from "components/Alert/ErrorAlert";
|
||||
import { DormantWorkspaceBanner } from "components/WorkspaceDeletion";
|
||||
import { Avatar } from "components/Avatar/Avatar";
|
||||
import { AgentRow } from "components/Resources/AgentRow";
|
||||
import { useLocalStorage } from "hooks";
|
||||
import AlertTitle from "@mui/material/AlertTitle";
|
||||
import dayjs from "dayjs";
|
||||
import { WorkspaceActions } from "pages/WorkspacePage/WorkspaceActions/WorkspaceActions";
|
||||
import {
|
||||
ActiveTransition,
|
||||
WorkspaceBuildProgress,
|
||||
} from "./WorkspaceBuildProgress";
|
||||
import { BuildsTable } from "./BuildsTable";
|
||||
import { WorkspaceDeletedBanner } from "./WorkspaceDeletedBanner";
|
||||
import { WorkspaceStats } from "./WorkspaceStats";
|
||||
|
||||
export enum WorkspaceErrors {
|
||||
GET_BUILDS_ERROR = "getBuildsError",
|
||||
@@ -112,7 +112,6 @@ export const Workspace: FC<React.PropsWithChildren<WorkspaceProps>> = ({
|
||||
isLoadingMoreBuilds,
|
||||
hasMoreBuilds,
|
||||
}) => {
|
||||
const styles = useStyles();
|
||||
const navigate = useNavigate();
|
||||
const serverVersion = buildInfo?.version || "";
|
||||
const { saveLocal, getLocal } = useLocalStorage();
|
||||
@@ -219,12 +218,8 @@ export const Workspace: FC<React.PropsWithChildren<WorkspaceProps>> = ({
|
||||
)}
|
||||
</FullWidthPageHeader>
|
||||
|
||||
<Margins className={styles.content}>
|
||||
<Stack
|
||||
direction="column"
|
||||
className={styles.firstColumnSpacer}
|
||||
spacing={4}
|
||||
>
|
||||
<Margins css={styles.content}>
|
||||
<Stack direction="column" css={styles.firstColumnSpacer} spacing={4}>
|
||||
{workspace.outdated && (
|
||||
<Alert severity="info">
|
||||
<AlertTitle>An update is available for your workspace</AlertTitle>
|
||||
@@ -282,7 +277,7 @@ export const Workspace: FC<React.PropsWithChildren<WorkspaceProps>> = ({
|
||||
<Alert severity="info">
|
||||
<AlertTitle>Workspace build is pending</AlertTitle>
|
||||
<AlertDetail>
|
||||
<div className={styles.alertPendingInQueue}>
|
||||
<div css={styles.alertPendingInQueue}>
|
||||
This workspace build job is waiting for a provisioner to
|
||||
become available. If you have been waiting for an extended
|
||||
period of time, please contact your administrator for
|
||||
@@ -364,58 +359,22 @@ export const Workspace: FC<React.PropsWithChildren<WorkspaceProps>> = ({
|
||||
);
|
||||
};
|
||||
|
||||
const spacerWidth = 300;
|
||||
const styles = {
|
||||
content: (theme) => ({
|
||||
marginTop: theme.spacing(4),
|
||||
}),
|
||||
|
||||
export const useStyles = makeStyles((theme) => {
|
||||
return {
|
||||
content: {
|
||||
marginTop: theme.spacing(4),
|
||||
actions: (theme) => ({
|
||||
[theme.breakpoints.down("md")]: {
|
||||
flexDirection: "column",
|
||||
},
|
||||
}),
|
||||
|
||||
statusBadge: {
|
||||
marginLeft: theme.spacing(2),
|
||||
},
|
||||
firstColumnSpacer: {
|
||||
flex: 2,
|
||||
},
|
||||
|
||||
actions: {
|
||||
[theme.breakpoints.down("md")]: {
|
||||
flexDirection: "column",
|
||||
},
|
||||
},
|
||||
|
||||
firstColumnSpacer: {
|
||||
flex: 2,
|
||||
},
|
||||
|
||||
secondColumnSpacer: {
|
||||
flex: `0 0 ${spacerWidth}px`,
|
||||
},
|
||||
|
||||
layout: {
|
||||
alignItems: "flex-start",
|
||||
},
|
||||
|
||||
main: {
|
||||
width: "100%",
|
||||
},
|
||||
|
||||
timelineContents: {
|
||||
margin: 0,
|
||||
},
|
||||
logs: {
|
||||
border: `1px solid ${theme.palette.divider}`,
|
||||
},
|
||||
|
||||
errorDetails: {
|
||||
color: theme.palette.text.secondary,
|
||||
fontSize: 12,
|
||||
},
|
||||
|
||||
fullWidth: {
|
||||
width: "100%",
|
||||
},
|
||||
|
||||
alertPendingInQueue: {
|
||||
marginBottom: 12,
|
||||
},
|
||||
};
|
||||
});
|
||||
alertPendingInQueue: {
|
||||
marginBottom: 12,
|
||||
},
|
||||
} satisfies Record<string, Interpolation<Theme>>;
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { makeStyles } from "@mui/styles";
|
||||
import { Sidebar } from "./Sidebar";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import { createContext, FC, Suspense, useContext } from "react";
|
||||
import { createContext, type FC, Suspense, useContext } from "react";
|
||||
import { Helmet } from "react-helmet-async";
|
||||
import { pageTitle } from "utils/page";
|
||||
import { Loader } from "components/Loader/Loader";
|
||||
import { Outlet, useParams } from "react-router-dom";
|
||||
import { Margins } from "components/Margins/Margins";
|
||||
import { workspaceByOwnerAndName } from "api/queries/workspaces";
|
||||
import { useQuery } from "react-query";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import type { Workspace } from "api/typesGenerated";
|
||||
import { workspaceByOwnerAndName } from "api/queries/workspaces";
|
||||
import { ErrorAlert } from "components/Alert/ErrorAlert";
|
||||
import { type Workspace } from "api/typesGenerated";
|
||||
import { Loader } from "components/Loader/Loader";
|
||||
import { Margins } from "components/Margins/Margins";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import { pageTitle } from "utils/page";
|
||||
import { Sidebar } from "./Sidebar";
|
||||
|
||||
const WorkspaceSettings = createContext<Workspace | undefined>(undefined);
|
||||
|
||||
@@ -26,7 +26,7 @@ export function useWorkspaceSettings() {
|
||||
}
|
||||
|
||||
export const WorkspaceSettingsLayout: FC = () => {
|
||||
const styles = useStyles();
|
||||
const theme = useTheme();
|
||||
const params = useParams() as {
|
||||
workspace: string;
|
||||
username: string;
|
||||
@@ -51,14 +51,18 @@ export const WorkspaceSettingsLayout: FC = () => {
|
||||
</Helmet>
|
||||
|
||||
<Margins>
|
||||
<Stack className={styles.wrapper} direction="row" spacing={10}>
|
||||
<Stack
|
||||
css={{ padding: theme.spacing(6, 0) }}
|
||||
direction="row"
|
||||
spacing={10}
|
||||
>
|
||||
{isError ? (
|
||||
<ErrorAlert error={error} />
|
||||
) : (
|
||||
<WorkspaceSettings.Provider value={workspace}>
|
||||
<Sidebar workspace={workspace} username={username} />
|
||||
<Suspense fallback={<Loader />}>
|
||||
<main className={styles.content}>
|
||||
<main css={{ width: "100%" }}>
|
||||
<Outlet />
|
||||
</main>
|
||||
</Suspense>
|
||||
@@ -69,13 +73,3 @@ export const WorkspaceSettingsLayout: FC = () => {
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
wrapper: {
|
||||
padding: theme.spacing(6, 0),
|
||||
},
|
||||
|
||||
content: {
|
||||
width: "100%",
|
||||
},
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user