mirror of
https://github.com/coder/coder.git
synced 2025-07-13 21:36:50 +00:00
chore: use emotion for styling (pt. 7) (#10431)
This commit is contained in:
@ -1,6 +1,8 @@
|
|||||||
import makeStyles from "@mui/styles/makeStyles";
|
|
||||||
import { watchAgentMetadata } from "api/api";
|
import { watchAgentMetadata } from "api/api";
|
||||||
import { WorkspaceAgent, WorkspaceAgentMetadata } from "api/typesGenerated";
|
import type {
|
||||||
|
WorkspaceAgent,
|
||||||
|
WorkspaceAgentMetadata,
|
||||||
|
} from "api/typesGenerated";
|
||||||
import { Stack } from "components/Stack/Stack";
|
import { Stack } from "components/Stack/Stack";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import {
|
import {
|
||||||
@ -13,17 +15,15 @@ import {
|
|||||||
} from "react";
|
} from "react";
|
||||||
import Skeleton from "@mui/material/Skeleton";
|
import Skeleton from "@mui/material/Skeleton";
|
||||||
import { MONOSPACE_FONT_FAMILY } from "theme/constants";
|
import { MONOSPACE_FONT_FAMILY } from "theme/constants";
|
||||||
import { combineClasses } from "utils/combineClasses";
|
|
||||||
import Tooltip from "@mui/material/Tooltip";
|
import Tooltip from "@mui/material/Tooltip";
|
||||||
import Box, { BoxProps } from "@mui/material/Box";
|
import Box, { BoxProps } from "@mui/material/Box";
|
||||||
|
import { type Interpolation, type Theme } from "@emotion/react";
|
||||||
|
|
||||||
type ItemStatus = "stale" | "valid" | "loading";
|
type ItemStatus = "stale" | "valid" | "loading";
|
||||||
|
|
||||||
export const WatchAgentMetadataContext = createContext(watchAgentMetadata);
|
export const WatchAgentMetadataContext = createContext(watchAgentMetadata);
|
||||||
|
|
||||||
const MetadataItem: FC<{ item: WorkspaceAgentMetadata }> = ({ item }) => {
|
const MetadataItem: FC<{ item: WorkspaceAgentMetadata }> = ({ item }) => {
|
||||||
const styles = useStyles();
|
|
||||||
|
|
||||||
if (item.result === undefined) {
|
if (item.result === undefined) {
|
||||||
throw new Error("Metadata item result is undefined");
|
throw new Error("Metadata item result is undefined");
|
||||||
}
|
}
|
||||||
@ -56,41 +56,29 @@ const MetadataItem: FC<{ item: WorkspaceAgentMetadata }> = ({ item }) => {
|
|||||||
// could be buggy. But, how common is that anyways?
|
// could be buggy. But, how common is that anyways?
|
||||||
const value =
|
const value =
|
||||||
status === "loading" ? (
|
status === "loading" ? (
|
||||||
<Skeleton
|
<Skeleton width={65} height={12} variant="text" css={styles.skeleton} />
|
||||||
width={65}
|
|
||||||
height={12}
|
|
||||||
variant="text"
|
|
||||||
className={styles.skeleton}
|
|
||||||
/>
|
|
||||||
) : status === "stale" ? (
|
) : status === "stale" ? (
|
||||||
<Tooltip title="This data is stale and no longer up to date">
|
<Tooltip title="This data is stale and no longer up to date">
|
||||||
<StaticWidth
|
<StaticWidth css={[styles.metadataValue, styles.metadataStale]}>
|
||||||
className={combineClasses([
|
|
||||||
styles.metadataValue,
|
|
||||||
styles.metadataStale,
|
|
||||||
])}
|
|
||||||
>
|
|
||||||
{item.result.value}
|
{item.result.value}
|
||||||
</StaticWidth>
|
</StaticWidth>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
) : (
|
) : (
|
||||||
<StaticWidth
|
<StaticWidth
|
||||||
className={combineClasses([
|
css={[
|
||||||
styles.metadataValue,
|
styles.metadataValue,
|
||||||
item.result.error.length === 0
|
item.result.error.length === 0
|
||||||
? styles.metadataValueSuccess
|
? styles.metadataValueSuccess
|
||||||
: styles.metadataValueError,
|
: styles.metadataValueError,
|
||||||
])}
|
]}
|
||||||
>
|
>
|
||||||
{item.result.value}
|
{item.result.value}
|
||||||
</StaticWidth>
|
</StaticWidth>
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.metadata}>
|
<div css={styles.metadata}>
|
||||||
<div className={styles.metadataLabel}>
|
<div css={styles.metadataLabel}>{item.description.display_name}</div>
|
||||||
{item.description.display_name}
|
|
||||||
</div>
|
|
||||||
<Box>{value}</Box>
|
<Box>{value}</Box>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -101,12 +89,11 @@ export interface AgentMetadataViewProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const AgentMetadataView: FC<AgentMetadataViewProps> = ({ metadata }) => {
|
export const AgentMetadataView: FC<AgentMetadataViewProps> = ({ metadata }) => {
|
||||||
const styles = useStyles();
|
|
||||||
if (metadata.length === 0) {
|
if (metadata.length === 0) {
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div className={styles.root}>
|
<div css={styles.root}>
|
||||||
<Stack alignItems="baseline" direction="row" spacing={6}>
|
<Stack alignItems="baseline" direction="row" spacing={6}>
|
||||||
{metadata.map((m) => {
|
{metadata.map((m) => {
|
||||||
if (m.description === undefined) {
|
if (m.description === undefined) {
|
||||||
@ -127,7 +114,6 @@ export const AgentMetadata: FC<{
|
|||||||
WorkspaceAgentMetadata[] | undefined
|
WorkspaceAgentMetadata[] | undefined
|
||||||
>(undefined);
|
>(undefined);
|
||||||
const watchAgentMetadata = useContext(WatchAgentMetadataContext);
|
const watchAgentMetadata = useContext(WatchAgentMetadataContext);
|
||||||
const styles = useStyles();
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (storybookMetadata !== undefined) {
|
if (storybookMetadata !== undefined) {
|
||||||
@ -166,7 +152,7 @@ export const AgentMetadata: FC<{
|
|||||||
|
|
||||||
if (metadata === undefined) {
|
if (metadata === undefined) {
|
||||||
return (
|
return (
|
||||||
<div className={styles.root}>
|
<div css={styles.root}>
|
||||||
<AgentMetadataSkeleton />
|
<AgentMetadataSkeleton />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -176,21 +162,19 @@ export const AgentMetadata: FC<{
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const AgentMetadataSkeleton: FC = () => {
|
export const AgentMetadataSkeleton: FC = () => {
|
||||||
const styles = useStyles();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack alignItems="baseline" direction="row" spacing={6}>
|
<Stack alignItems="baseline" direction="row" spacing={6}>
|
||||||
<div className={styles.metadata}>
|
<div css={styles.metadata}>
|
||||||
<Skeleton width={40} height={12} variant="text" />
|
<Skeleton width={40} height={12} variant="text" />
|
||||||
<Skeleton width={65} height={14} variant="text" />
|
<Skeleton width={65} height={14} variant="text" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.metadata}>
|
<div css={styles.metadata}>
|
||||||
<Skeleton width={40} height={12} variant="text" />
|
<Skeleton width={40} height={12} variant="text" />
|
||||||
<Skeleton width={65} height={14} variant="text" />
|
<Skeleton width={65} height={14} variant="text" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.metadata}>
|
<div css={styles.metadata}>
|
||||||
<Skeleton width={40} height={12} variant="text" />
|
<Skeleton width={40} height={12} variant="text" />
|
||||||
<Skeleton width={65} height={14} variant="text" />
|
<Skeleton width={65} height={14} variant="text" />
|
||||||
</div>
|
</div>
|
||||||
@ -219,16 +203,16 @@ const StaticWidth = (props: BoxProps) => {
|
|||||||
|
|
||||||
// These are more or less copied from
|
// These are more or less copied from
|
||||||
// site/src/components/Resources/ResourceCard.tsx
|
// site/src/components/Resources/ResourceCard.tsx
|
||||||
const useStyles = makeStyles((theme) => ({
|
const styles = {
|
||||||
root: {
|
root: (theme) => ({
|
||||||
padding: theme.spacing(2.5, 4),
|
padding: theme.spacing(2.5, 4),
|
||||||
borderTop: `1px solid ${theme.palette.divider}`,
|
borderTop: `1px solid ${theme.palette.divider}`,
|
||||||
background: theme.palette.background.paper,
|
background: theme.palette.background.paper,
|
||||||
overflowX: "auto",
|
overflowX: "auto",
|
||||||
scrollPadding: theme.spacing(0, 4),
|
scrollPadding: theme.spacing(0, 4),
|
||||||
},
|
}),
|
||||||
|
|
||||||
metadata: {
|
metadata: (theme) => ({
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
lineHeight: "normal",
|
lineHeight: "normal",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
@ -240,15 +224,15 @@ const useStyles = makeStyles((theme) => ({
|
|||||||
"&:last-child": {
|
"&:last-child": {
|
||||||
paddingRight: theme.spacing(4),
|
paddingRight: theme.spacing(4),
|
||||||
},
|
},
|
||||||
},
|
}),
|
||||||
|
|
||||||
metadataLabel: {
|
metadataLabel: (theme) => ({
|
||||||
color: theme.palette.text.secondary,
|
color: theme.palette.text.secondary,
|
||||||
textOverflow: "ellipsis",
|
textOverflow: "ellipsis",
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
whiteSpace: "nowrap",
|
whiteSpace: "nowrap",
|
||||||
fontWeight: 500,
|
fontWeight: 500,
|
||||||
},
|
}),
|
||||||
|
|
||||||
metadataValue: {
|
metadataValue: {
|
||||||
textOverflow: "ellipsis",
|
textOverflow: "ellipsis",
|
||||||
@ -258,29 +242,29 @@ const useStyles = makeStyles((theme) => ({
|
|||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
},
|
},
|
||||||
|
|
||||||
metadataValueSuccess: {
|
metadataValueSuccess: (theme) => ({
|
||||||
color: theme.palette.success.light,
|
color: theme.palette.success.light,
|
||||||
},
|
}),
|
||||||
|
|
||||||
metadataValueError: {
|
metadataValueError: (theme) => ({
|
||||||
color: theme.palette.error.main,
|
color: theme.palette.error.main,
|
||||||
},
|
}),
|
||||||
|
|
||||||
metadataStale: {
|
metadataStale: (theme) => ({
|
||||||
color: theme.palette.text.disabled,
|
color: theme.palette.text.disabled,
|
||||||
cursor: "pointer",
|
cursor: "pointer",
|
||||||
},
|
}),
|
||||||
|
|
||||||
skeleton: {
|
skeleton: (theme) => ({
|
||||||
marginTop: theme.spacing(0.5),
|
marginTop: theme.spacing(0.5),
|
||||||
},
|
}),
|
||||||
|
|
||||||
inlineCommand: {
|
inlineCommand: (theme) => ({
|
||||||
fontFamily: MONOSPACE_FONT_FAMILY,
|
fontFamily: MONOSPACE_FONT_FAMILY,
|
||||||
display: "inline-block",
|
display: "inline-block",
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
margin: 0,
|
margin: 0,
|
||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
color: theme.palette.text.primary,
|
color: theme.palette.text.primary,
|
||||||
},
|
}),
|
||||||
}));
|
} satisfies Record<string, Interpolation<Theme>>;
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import Collapse from "@mui/material/Collapse";
|
import Collapse from "@mui/material/Collapse";
|
||||||
import Skeleton from "@mui/material/Skeleton";
|
import Skeleton from "@mui/material/Skeleton";
|
||||||
import Tooltip from "@mui/material/Tooltip";
|
import Tooltip from "@mui/material/Tooltip";
|
||||||
import { makeStyles } from "@mui/styles";
|
import { type Interpolation, type Theme } from "@emotion/react";
|
||||||
import * as API from "api/api";
|
import * as API from "api/api";
|
||||||
import {
|
import type {
|
||||||
Workspace,
|
Workspace,
|
||||||
WorkspaceAgent,
|
WorkspaceAgent,
|
||||||
WorkspaceAgentLogSource,
|
WorkspaceAgentLogSource,
|
||||||
@ -30,7 +30,6 @@ import {
|
|||||||
import AutoSizer from "react-virtualized-auto-sizer";
|
import AutoSizer from "react-virtualized-auto-sizer";
|
||||||
import { FixedSizeList as List, ListOnScrollProps } from "react-window";
|
import { FixedSizeList as List, ListOnScrollProps } from "react-window";
|
||||||
import { colors } from "theme/colors";
|
import { colors } from "theme/colors";
|
||||||
import { combineClasses } from "utils/combineClasses";
|
|
||||||
import { Stack } from "../Stack/Stack";
|
import { Stack } from "../Stack/Stack";
|
||||||
import { AgentLatency } from "./AgentLatency";
|
import { AgentLatency } from "./AgentLatency";
|
||||||
import { AgentMetadata } from "./AgentMetadata";
|
import { AgentMetadata } from "./AgentMetadata";
|
||||||
@ -75,7 +74,6 @@ export const AgentRow: FC<AgentRowProps> = ({
|
|||||||
sshPrefix,
|
sshPrefix,
|
||||||
storybookLogs,
|
storybookLogs,
|
||||||
}) => {
|
}) => {
|
||||||
const styles = useStyles();
|
|
||||||
const hasAppsToDisplay = !hideVSCodeDesktopButton || agent.apps.length > 0;
|
const hasAppsToDisplay = !hideVSCodeDesktopButton || agent.apps.length > 0;
|
||||||
const shouldDisplayApps =
|
const shouldDisplayApps =
|
||||||
showApps &&
|
showApps &&
|
||||||
@ -159,28 +157,26 @@ export const AgentRow: FC<AgentRowProps> = ({
|
|||||||
key={agent.id}
|
key={agent.id}
|
||||||
direction="column"
|
direction="column"
|
||||||
spacing={0}
|
spacing={0}
|
||||||
className={combineClasses([
|
css={[
|
||||||
styles.agentRow,
|
styles.agentRow,
|
||||||
styles[`agentRow-${agent.status}`],
|
styles[`agentRow-${agent.status}`],
|
||||||
styles[`agentRow-lifecycle-${agent.lifecycle_state}`],
|
styles[`agentRow-lifecycle-${agent.lifecycle_state}`],
|
||||||
])}
|
]}
|
||||||
>
|
>
|
||||||
<div className={styles.agentInfo}>
|
<div css={styles.agentInfo}>
|
||||||
<div className={styles.agentNameAndStatus}>
|
<div css={styles.agentNameAndStatus}>
|
||||||
<div className={styles.agentNameAndInfo}>
|
<div css={styles.agentNameAndInfo}>
|
||||||
<AgentStatus agent={agent} />
|
<AgentStatus agent={agent} />
|
||||||
<div className={styles.agentName}>{agent.name}</div>
|
<div css={styles.agentName}>{agent.name}</div>
|
||||||
<Stack
|
<Stack
|
||||||
direction="row"
|
direction="row"
|
||||||
spacing={2}
|
spacing={2}
|
||||||
alignItems="baseline"
|
alignItems="baseline"
|
||||||
className={styles.agentDescription}
|
css={styles.agentDescription}
|
||||||
>
|
>
|
||||||
{agent.status === "connected" && (
|
{agent.status === "connected" && (
|
||||||
<>
|
<>
|
||||||
<span className={styles.agentOS}>
|
<span css={styles.agentOS}>{agent.operating_system}</span>
|
||||||
{agent.operating_system}
|
|
||||||
</span>
|
|
||||||
<AgentVersion
|
<AgentVersion
|
||||||
agent={agent}
|
agent={agent}
|
||||||
serverVersion={serverVersion}
|
serverVersion={serverVersion}
|
||||||
@ -200,7 +196,7 @@ export const AgentRow: FC<AgentRowProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{agent.status === "connected" && (
|
{agent.status === "connected" && (
|
||||||
<div className={styles.agentButtons}>
|
<div css={styles.agentButtons}>
|
||||||
{shouldDisplayApps && (
|
{shouldDisplayApps && (
|
||||||
<>
|
<>
|
||||||
{(agent.display_apps.includes("vscode") ||
|
{(agent.display_apps.includes("vscode") ||
|
||||||
@ -258,18 +254,18 @@ export const AgentRow: FC<AgentRowProps> = ({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{agent.status === "connecting" && (
|
{agent.status === "connecting" && (
|
||||||
<div className={styles.agentButtons}>
|
<div css={styles.agentButtons}>
|
||||||
<Skeleton
|
<Skeleton
|
||||||
width={80}
|
width={80}
|
||||||
height={32}
|
height={32}
|
||||||
variant="rectangular"
|
variant="rectangular"
|
||||||
className={styles.buttonSkeleton}
|
css={styles.buttonSkeleton}
|
||||||
/>
|
/>
|
||||||
<Skeleton
|
<Skeleton
|
||||||
width={110}
|
width={110}
|
||||||
height={32}
|
height={32}
|
||||||
variant="rectangular"
|
variant="rectangular"
|
||||||
className={styles.buttonSkeleton}
|
css={styles.buttonSkeleton}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -278,7 +274,7 @@ export const AgentRow: FC<AgentRowProps> = ({
|
|||||||
<AgentMetadata storybookMetadata={storybookAgentMetadata} agent={agent} />
|
<AgentMetadata storybookMetadata={storybookAgentMetadata} agent={agent} />
|
||||||
|
|
||||||
{hasStartupFeatures && (
|
{hasStartupFeatures && (
|
||||||
<div className={styles.logsPanel}>
|
<div css={styles.logsPanel}>
|
||||||
<Collapse in={showLogs}>
|
<Collapse in={showLogs}>
|
||||||
<AutoSizer disableHeight>
|
<AutoSizer disableHeight>
|
||||||
{({ width }) => (
|
{({ width }) => (
|
||||||
@ -289,7 +285,7 @@ export const AgentRow: FC<AgentRowProps> = ({
|
|||||||
itemCount={startupLogs.length}
|
itemCount={startupLogs.length}
|
||||||
itemSize={logLineHeight}
|
itemSize={logLineHeight}
|
||||||
width={width}
|
width={width}
|
||||||
className={styles.startupLogs}
|
css={styles.startupLogs}
|
||||||
onScroll={handleLogScroll}
|
onScroll={handleLogScroll}
|
||||||
>
|
>
|
||||||
{({ index, style }) => {
|
{({ index, style }) => {
|
||||||
@ -323,7 +319,7 @@ export const AgentRow: FC<AgentRowProps> = ({
|
|||||||
alt=""
|
alt=""
|
||||||
width={16}
|
width={16}
|
||||||
height={16}
|
height={16}
|
||||||
style={{
|
css={{
|
||||||
marginRight: 8,
|
marginRight: 8,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -331,7 +327,7 @@ export const AgentRow: FC<AgentRowProps> = ({
|
|||||||
} else {
|
} else {
|
||||||
icon = (
|
icon = (
|
||||||
<div
|
<div
|
||||||
style={{
|
css={{
|
||||||
width: 16,
|
width: 16,
|
||||||
height: 16,
|
height: 16,
|
||||||
marginRight: 8,
|
marginRight: 8,
|
||||||
@ -363,7 +359,7 @@ export const AgentRow: FC<AgentRowProps> = ({
|
|||||||
) {
|
) {
|
||||||
icon = (
|
icon = (
|
||||||
<div
|
<div
|
||||||
style={{
|
css={{
|
||||||
minWidth: 16,
|
minWidth: 16,
|
||||||
width: 16,
|
width: 16,
|
||||||
height: 16,
|
height: 16,
|
||||||
@ -374,7 +370,7 @@ export const AgentRow: FC<AgentRowProps> = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
style={{
|
css={{
|
||||||
height: nextChangesSource ? "50%" : "100%",
|
height: nextChangesSource ? "50%" : "100%",
|
||||||
width: 4,
|
width: 4,
|
||||||
background: "hsl(222, 31%, 25%)",
|
background: "hsl(222, 31%, 25%)",
|
||||||
@ -383,7 +379,7 @@ export const AgentRow: FC<AgentRowProps> = ({
|
|||||||
/>
|
/>
|
||||||
{nextChangesSource && (
|
{nextChangesSource && (
|
||||||
<div
|
<div
|
||||||
style={{
|
css={{
|
||||||
height: 4,
|
height: 4,
|
||||||
width: "50%",
|
width: "50%",
|
||||||
top: "calc(50% - 2px)",
|
top: "calc(50% - 2px)",
|
||||||
@ -429,13 +425,10 @@ export const AgentRow: FC<AgentRowProps> = ({
|
|||||||
</AutoSizer>
|
</AutoSizer>
|
||||||
</Collapse>
|
</Collapse>
|
||||||
|
|
||||||
<div className={styles.logsPanelButtons}>
|
<div css={styles.logsPanelButtons}>
|
||||||
{showLogs ? (
|
{showLogs ? (
|
||||||
<button
|
<button
|
||||||
className={combineClasses([
|
css={[styles.logsPanelButton, styles.toggleLogsButton]}
|
||||||
styles.logsPanelButton,
|
|
||||||
styles.toggleLogsButton,
|
|
||||||
])}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setShowLogs((v) => !v);
|
setShowLogs((v) => !v);
|
||||||
}}
|
}}
|
||||||
@ -445,10 +438,7 @@ export const AgentRow: FC<AgentRowProps> = ({
|
|||||||
</button>
|
</button>
|
||||||
) : (
|
) : (
|
||||||
<button
|
<button
|
||||||
className={combineClasses([
|
css={[styles.logsPanelButton, styles.toggleLogsButton]}
|
||||||
styles.logsPanelButton,
|
|
||||||
styles.toggleLogsButton,
|
|
||||||
])}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setShowLogs((v) => !v);
|
setShowLogs((v) => !v);
|
||||||
}}
|
}}
|
||||||
@ -511,8 +501,8 @@ const useAgentLogs = (
|
|||||||
return logs;
|
return logs;
|
||||||
};
|
};
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const styles = {
|
||||||
agentRow: {
|
agentRow: (theme) => ({
|
||||||
backgroundColor: theme.palette.background.paperLight,
|
backgroundColor: theme.palette.background.paperLight,
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
borderLeft: `2px solid ${theme.palette.text.secondary}`,
|
borderLeft: `2px solid ${theme.palette.text.secondary}`,
|
||||||
@ -520,59 +510,59 @@ const useStyles = makeStyles((theme) => ({
|
|||||||
"&:not(:first-of-type)": {
|
"&:not(:first-of-type)": {
|
||||||
borderTop: `2px solid ${theme.palette.divider}`,
|
borderTop: `2px solid ${theme.palette.divider}`,
|
||||||
},
|
},
|
||||||
},
|
}),
|
||||||
|
|
||||||
"agentRow-connected": {
|
"agentRow-connected": (theme) => ({
|
||||||
borderLeftColor: theme.palette.success.light,
|
borderLeftColor: theme.palette.success.light,
|
||||||
},
|
}),
|
||||||
|
|
||||||
"agentRow-disconnected": {
|
"agentRow-disconnected": (theme) => ({
|
||||||
borderLeftColor: theme.palette.text.secondary,
|
borderLeftColor: theme.palette.text.secondary,
|
||||||
},
|
}),
|
||||||
|
|
||||||
"agentRow-connecting": {
|
"agentRow-connecting": (theme) => ({
|
||||||
borderLeftColor: theme.palette.info.light,
|
borderLeftColor: theme.palette.info.light,
|
||||||
},
|
}),
|
||||||
|
|
||||||
"agentRow-timeout": {
|
"agentRow-timeout": (theme) => ({
|
||||||
borderLeftColor: theme.palette.warning.light,
|
borderLeftColor: theme.palette.warning.light,
|
||||||
},
|
}),
|
||||||
|
|
||||||
"agentRow-lifecycle-created": {},
|
"agentRow-lifecycle-created": {},
|
||||||
|
|
||||||
"agentRow-lifecycle-starting": {
|
"agentRow-lifecycle-starting": (theme) => ({
|
||||||
borderLeftColor: theme.palette.info.light,
|
borderLeftColor: theme.palette.info.light,
|
||||||
},
|
}),
|
||||||
|
|
||||||
"agentRow-lifecycle-ready": {
|
"agentRow-lifecycle-ready": (theme) => ({
|
||||||
borderLeftColor: theme.palette.success.light,
|
borderLeftColor: theme.palette.success.light,
|
||||||
},
|
}),
|
||||||
|
|
||||||
"agentRow-lifecycle-start_timeout": {
|
"agentRow-lifecycle-start_timeout": (theme) => ({
|
||||||
borderLeftColor: theme.palette.warning.light,
|
borderLeftColor: theme.palette.warning.light,
|
||||||
},
|
}),
|
||||||
|
|
||||||
"agentRow-lifecycle-start_error": {
|
"agentRow-lifecycle-start_error": (theme) => ({
|
||||||
borderLeftColor: theme.palette.error.light,
|
borderLeftColor: theme.palette.error.light,
|
||||||
},
|
}),
|
||||||
|
|
||||||
"agentRow-lifecycle-shutting_down": {
|
"agentRow-lifecycle-shutting_down": (theme) => ({
|
||||||
borderLeftColor: theme.palette.info.light,
|
borderLeftColor: theme.palette.info.light,
|
||||||
},
|
}),
|
||||||
|
|
||||||
"agentRow-lifecycle-shutdown_timeout": {
|
"agentRow-lifecycle-shutdown_timeout": (theme) => ({
|
||||||
borderLeftColor: theme.palette.warning.light,
|
borderLeftColor: theme.palette.warning.light,
|
||||||
},
|
}),
|
||||||
|
|
||||||
"agentRow-lifecycle-shutdown_error": {
|
"agentRow-lifecycle-shutdown_error": (theme) => ({
|
||||||
borderLeftColor: theme.palette.error.light,
|
borderLeftColor: theme.palette.error.light,
|
||||||
},
|
}),
|
||||||
|
|
||||||
"agentRow-lifecycle-off": {
|
"agentRow-lifecycle-off": (theme) => ({
|
||||||
borderLeftColor: theme.palette.text.secondary,
|
borderLeftColor: theme.palette.text.secondary,
|
||||||
},
|
}),
|
||||||
|
|
||||||
agentInfo: {
|
agentInfo: (theme) => ({
|
||||||
padding: theme.spacing(2, 4),
|
padding: theme.spacing(2, 4),
|
||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
@ -582,9 +572,9 @@ const useStyles = makeStyles((theme) => ({
|
|||||||
[theme.breakpoints.down("md")]: {
|
[theme.breakpoints.down("md")]: {
|
||||||
gap: theme.spacing(2),
|
gap: theme.spacing(2),
|
||||||
},
|
},
|
||||||
},
|
}),
|
||||||
|
|
||||||
agentNameAndInfo: {
|
agentNameAndInfo: (theme) => ({
|
||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
gap: theme.spacing(3),
|
gap: theme.spacing(3),
|
||||||
@ -593,9 +583,9 @@ const useStyles = makeStyles((theme) => ({
|
|||||||
[theme.breakpoints.down("md")]: {
|
[theme.breakpoints.down("md")]: {
|
||||||
gap: theme.spacing(1.5),
|
gap: theme.spacing(1.5),
|
||||||
},
|
},
|
||||||
},
|
}),
|
||||||
|
|
||||||
agentButtons: {
|
agentButtons: (theme) => ({
|
||||||
display: "flex",
|
display: "flex",
|
||||||
gap: theme.spacing(1),
|
gap: theme.spacing(1),
|
||||||
justifyContent: "flex-end",
|
justifyContent: "flex-end",
|
||||||
@ -606,14 +596,14 @@ const useStyles = makeStyles((theme) => ({
|
|||||||
marginLeft: 0,
|
marginLeft: 0,
|
||||||
justifyContent: "flex-start",
|
justifyContent: "flex-start",
|
||||||
},
|
},
|
||||||
},
|
}),
|
||||||
|
|
||||||
agentDescription: {
|
agentDescription: (theme) => ({
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
color: theme.palette.text.secondary,
|
color: theme.palette.text.secondary,
|
||||||
},
|
}),
|
||||||
|
|
||||||
startupLogs: {
|
startupLogs: (theme) => ({
|
||||||
maxHeight: 256,
|
maxHeight: 256,
|
||||||
borderBottom: `1px solid ${theme.palette.divider}`,
|
borderBottom: `1px solid ${theme.palette.divider}`,
|
||||||
backgroundColor: theme.palette.background.paper,
|
backgroundColor: theme.palette.background.paper,
|
||||||
@ -623,9 +613,9 @@ const useStyles = makeStyles((theme) => ({
|
|||||||
"& > div": {
|
"& > div": {
|
||||||
position: "relative",
|
position: "relative",
|
||||||
},
|
},
|
||||||
},
|
}),
|
||||||
|
|
||||||
agentNameAndStatus: {
|
agentNameAndStatus: (theme) => ({
|
||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
gap: theme.spacing(4),
|
gap: theme.spacing(4),
|
||||||
@ -633,9 +623,9 @@ const useStyles = makeStyles((theme) => ({
|
|||||||
[theme.breakpoints.down("md")]: {
|
[theme.breakpoints.down("md")]: {
|
||||||
width: "100%",
|
width: "100%",
|
||||||
},
|
},
|
||||||
},
|
}),
|
||||||
|
|
||||||
agentName: {
|
agentName: (theme) => ({
|
||||||
whiteSpace: "nowrap",
|
whiteSpace: "nowrap",
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
textOverflow: "ellipsis",
|
textOverflow: "ellipsis",
|
||||||
@ -648,15 +638,15 @@ const useStyles = makeStyles((theme) => ({
|
|||||||
[theme.breakpoints.down("md")]: {
|
[theme.breakpoints.down("md")]: {
|
||||||
overflow: "unset",
|
overflow: "unset",
|
||||||
},
|
},
|
||||||
},
|
}),
|
||||||
|
|
||||||
agentDataGroup: {
|
agentDataGroup: (theme) => ({
|
||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "baseline",
|
alignItems: "baseline",
|
||||||
gap: theme.spacing(6),
|
gap: theme.spacing(6),
|
||||||
},
|
}),
|
||||||
|
|
||||||
agentData: {
|
agentData: (theme) => ({
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
@ -665,17 +655,17 @@ const useStyles = makeStyles((theme) => ({
|
|||||||
fontWeight: 500,
|
fontWeight: 500,
|
||||||
color: theme.palette.text.secondary,
|
color: theme.palette.text.secondary,
|
||||||
},
|
},
|
||||||
},
|
}),
|
||||||
|
|
||||||
logsPanel: {
|
logsPanel: (theme) => ({
|
||||||
borderTop: `1px solid ${theme.palette.divider}`,
|
borderTop: `1px solid ${theme.palette.divider}`,
|
||||||
},
|
}),
|
||||||
|
|
||||||
logsPanelButtons: {
|
logsPanelButtons: {
|
||||||
display: "flex",
|
display: "flex",
|
||||||
},
|
},
|
||||||
|
|
||||||
logsPanelButton: {
|
logsPanelButton: (theme) => ({
|
||||||
textAlign: "left",
|
textAlign: "left",
|
||||||
background: "transparent",
|
background: "transparent",
|
||||||
border: 0,
|
border: 0,
|
||||||
@ -696,7 +686,7 @@ const useStyles = makeStyles((theme) => ({
|
|||||||
"& svg": {
|
"& svg": {
|
||||||
color: "inherit",
|
color: "inherit",
|
||||||
},
|
},
|
||||||
},
|
}),
|
||||||
|
|
||||||
toggleLogsButton: {
|
toggleLogsButton: {
|
||||||
width: "100%",
|
width: "100%",
|
||||||
@ -706,17 +696,17 @@ const useStyles = makeStyles((theme) => ({
|
|||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
},
|
},
|
||||||
|
|
||||||
agentErrorMessage: {
|
agentErrorMessage: (theme) => ({
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: 400,
|
fontWeight: 400,
|
||||||
marginTop: theme.spacing(0.5),
|
marginTop: theme.spacing(0.5),
|
||||||
color: theme.palette.warning.light,
|
color: theme.palette.warning.light,
|
||||||
},
|
}),
|
||||||
|
|
||||||
agentOS: {
|
agentOS: {
|
||||||
textTransform: "capitalize",
|
textTransform: "capitalize",
|
||||||
},
|
},
|
||||||
}));
|
} satisfies Record<string, Interpolation<Theme>>;
|
||||||
|
|
||||||
// These colors were picked at random. Feel free
|
// These colors were picked at random. Feel free
|
||||||
// to add more, adjust, or change! Users will not
|
// to add more, adjust, or change! Users will not
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
import { makeStyles } from "@mui/styles";
|
import { type Interpolation, type Theme } from "@emotion/react";
|
||||||
import { AppPreviewLink } from "components/Resources/AppLink/AppPreviewLink";
|
import { type FC } from "react";
|
||||||
import { FC } from "react";
|
import type { WorkspaceAgent } from "api/typesGenerated";
|
||||||
import { combineClasses } from "utils/combineClasses";
|
|
||||||
import { WorkspaceAgent } from "api/typesGenerated";
|
|
||||||
import { Stack } from "../Stack/Stack";
|
import { Stack } from "../Stack/Stack";
|
||||||
|
import { AppPreviewLink } from "./AppLink/AppPreviewLink";
|
||||||
|
|
||||||
interface AgentRowPreviewStyles {
|
interface AgentRowPreviewStyles {
|
||||||
// Helpful when there are more than one row so the values are aligned
|
// Helpful when there are more than one row so the values are aligned
|
||||||
@ -18,57 +17,58 @@ export const AgentRowPreview: FC<AgentRowPreviewProps> = ({
|
|||||||
agent,
|
agent,
|
||||||
alignValues,
|
alignValues,
|
||||||
}) => {
|
}) => {
|
||||||
const styles = useStyles({ alignValues });
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack
|
<Stack
|
||||||
key={agent.id}
|
key={agent.id}
|
||||||
direction="row"
|
direction="row"
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
justifyContent="space-between"
|
justifyContent="space-between"
|
||||||
className={styles.agentRow}
|
css={styles.agentRow}
|
||||||
>
|
>
|
||||||
<Stack direction="row" alignItems="baseline">
|
<Stack direction="row" alignItems="baseline">
|
||||||
<div className={styles.agentStatusWrapper}>
|
<div css={styles.agentStatusWrapper}>
|
||||||
<div className={styles.agentStatusPreview}></div>
|
<div css={styles.agentStatusPreview}></div>
|
||||||
</div>
|
</div>
|
||||||
<Stack
|
<Stack
|
||||||
alignItems="baseline"
|
alignItems="baseline"
|
||||||
direction="row"
|
direction="row"
|
||||||
spacing={4}
|
spacing={4}
|
||||||
className={styles.agentData}
|
css={styles.agentData}
|
||||||
>
|
>
|
||||||
<Stack
|
<Stack
|
||||||
direction="row"
|
direction="row"
|
||||||
alignItems="baseline"
|
alignItems="baseline"
|
||||||
spacing={1}
|
spacing={1}
|
||||||
className={combineClasses([
|
css={[
|
||||||
styles.noShrink,
|
styles.noShrink,
|
||||||
styles.agentDataItem,
|
styles.agentDataItem,
|
||||||
styles.agentDataName,
|
(theme) => ({
|
||||||
])}
|
[theme.breakpoints.up("sm")]: {
|
||||||
|
minWidth: alignValues ? 240 : undefined,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]}
|
||||||
>
|
>
|
||||||
<span>Agent:</span>
|
<span>Agent:</span>
|
||||||
<span className={styles.agentDataValue}>{agent.name}</span>
|
<span css={styles.agentDataValue}>{agent.name}</span>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
<Stack
|
<Stack
|
||||||
direction="row"
|
direction="row"
|
||||||
alignItems="baseline"
|
alignItems="baseline"
|
||||||
spacing={1}
|
spacing={1}
|
||||||
className={combineClasses([
|
css={[
|
||||||
styles.noShrink,
|
styles.noShrink,
|
||||||
styles.agentDataItem,
|
styles.agentDataItem,
|
||||||
styles.agentDataOS,
|
(theme) => ({
|
||||||
])}
|
[theme.breakpoints.up("sm")]: {
|
||||||
|
minWidth: alignValues ? 100 : undefined,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]}
|
||||||
>
|
>
|
||||||
<span>OS:</span>
|
<span>OS:</span>
|
||||||
<span
|
<span css={[styles.agentDataValue, styles.agentOS]}>
|
||||||
className={combineClasses([
|
|
||||||
styles.agentDataValue,
|
|
||||||
styles.agentOS,
|
|
||||||
])}
|
|
||||||
>
|
|
||||||
{agent.operating_system}
|
{agent.operating_system}
|
||||||
</span>
|
</span>
|
||||||
</Stack>
|
</Stack>
|
||||||
@ -77,7 +77,7 @@ export const AgentRowPreview: FC<AgentRowPreviewProps> = ({
|
|||||||
direction="row"
|
direction="row"
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
spacing={1}
|
spacing={1}
|
||||||
className={styles.agentDataItem}
|
css={styles.agentDataItem}
|
||||||
>
|
>
|
||||||
<span>Apps:</span>
|
<span>Apps:</span>
|
||||||
<Stack
|
<Stack
|
||||||
@ -90,7 +90,7 @@ export const AgentRowPreview: FC<AgentRowPreviewProps> = ({
|
|||||||
<AppPreviewLink key={app.slug} app={app} />
|
<AppPreviewLink key={app.slug} app={app} />
|
||||||
))}
|
))}
|
||||||
{agent.apps.length === 0 && (
|
{agent.apps.length === 0 && (
|
||||||
<span className={styles.agentDataValue}>None</span>
|
<span css={styles.agentDataValue}>None</span>
|
||||||
)}
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
@ -100,8 +100,8 @@ export const AgentRowPreview: FC<AgentRowPreviewProps> = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const styles = {
|
||||||
agentRow: {
|
agentRow: (theme) => ({
|
||||||
padding: theme.spacing(2, 4),
|
padding: theme.spacing(2, 4),
|
||||||
backgroundColor: theme.palette.background.paperLight,
|
backgroundColor: theme.palette.background.paperLight,
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
@ -120,16 +120,16 @@ const useStyles = makeStyles((theme) => ({
|
|||||||
top: 0,
|
top: 0,
|
||||||
left: 49,
|
left: 49,
|
||||||
},
|
},
|
||||||
},
|
}),
|
||||||
|
|
||||||
agentStatusWrapper: {
|
agentStatusWrapper: (theme) => ({
|
||||||
width: theme.spacing(4.5),
|
width: theme.spacing(4.5),
|
||||||
display: "flex",
|
display: "flex",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
flexShrink: 0,
|
flexShrink: 0,
|
||||||
},
|
}),
|
||||||
|
|
||||||
agentStatusPreview: {
|
agentStatusPreview: (theme) => ({
|
||||||
width: 10,
|
width: 10,
|
||||||
height: 10,
|
height: 10,
|
||||||
border: `2px solid ${theme.palette.text.secondary}`,
|
border: `2px solid ${theme.palette.text.secondary}`,
|
||||||
@ -137,7 +137,7 @@ const useStyles = makeStyles((theme) => ({
|
|||||||
position: "relative",
|
position: "relative",
|
||||||
zIndex: 1,
|
zIndex: 1,
|
||||||
background: theme.palette.background.paper,
|
background: theme.palette.background.paper,
|
||||||
},
|
}),
|
||||||
|
|
||||||
agentName: {
|
agentName: {
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
@ -146,10 +146,9 @@ const useStyles = makeStyles((theme) => ({
|
|||||||
agentOS: {
|
agentOS: {
|
||||||
textTransform: "capitalize",
|
textTransform: "capitalize",
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
color: theme.palette.text.secondary,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
agentData: {
|
agentData: (theme) => ({
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
color: theme.palette.text.secondary,
|
color: theme.palette.text.secondary,
|
||||||
|
|
||||||
@ -157,36 +156,22 @@ const useStyles = makeStyles((theme) => ({
|
|||||||
gap: theme.spacing(2),
|
gap: theme.spacing(2),
|
||||||
flexWrap: "wrap",
|
flexWrap: "wrap",
|
||||||
},
|
},
|
||||||
},
|
}),
|
||||||
|
|
||||||
agentDataName: {
|
agentDataValue: (theme) => ({
|
||||||
[theme.breakpoints.up("sm")]: {
|
|
||||||
minWidth: ({ alignValues }: AgentRowPreviewStyles) =>
|
|
||||||
alignValues ? 240 : undefined,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
agentDataOS: {
|
|
||||||
[theme.breakpoints.up("sm")]: {
|
|
||||||
minWidth: ({ alignValues }: AgentRowPreviewStyles) =>
|
|
||||||
alignValues ? 100 : undefined,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
agentDataValue: {
|
|
||||||
color: theme.palette.text.primary,
|
color: theme.palette.text.primary,
|
||||||
},
|
}),
|
||||||
|
|
||||||
noShrink: {
|
noShrink: {
|
||||||
flexShrink: 0,
|
flexShrink: 0,
|
||||||
},
|
},
|
||||||
|
|
||||||
agentDataItem: {
|
agentDataItem: (theme) => ({
|
||||||
[theme.breakpoints.down("md")]: {
|
[theme.breakpoints.down("md")]: {
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
alignItems: "flex-start",
|
alignItems: "flex-start",
|
||||||
gap: theme.spacing(1),
|
gap: theme.spacing(1),
|
||||||
width: "fit-content",
|
width: "fit-content",
|
||||||
},
|
},
|
||||||
},
|
}),
|
||||||
}));
|
} satisfies Record<string, Interpolation<Theme>>;
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
|
import { type Interpolation, type Theme } from "@emotion/react";
|
||||||
import Tooltip from "@mui/material/Tooltip";
|
import Tooltip from "@mui/material/Tooltip";
|
||||||
import { makeStyles } from "@mui/styles";
|
|
||||||
import { combineClasses } from "utils/combineClasses";
|
|
||||||
import { WorkspaceAgent } from "api/typesGenerated";
|
import { WorkspaceAgent } from "api/typesGenerated";
|
||||||
import { ChooseOne, Cond } from "components/Conditionals/ChooseOne";
|
import { ChooseOne, Cond } from "components/Conditionals/ChooseOne";
|
||||||
import WarningRounded from "@mui/icons-material/WarningRounded";
|
import WarningRounded from "@mui/icons-material/WarningRounded";
|
||||||
@ -19,27 +18,23 @@ import Link from "@mui/material/Link";
|
|||||||
// connected:shutdown_error, connected:off.
|
// connected:shutdown_error, connected:off.
|
||||||
|
|
||||||
const ReadyLifecycle = () => {
|
const ReadyLifecycle = () => {
|
||||||
const styles = useStyles();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
role="status"
|
role="status"
|
||||||
data-testid="agent-status-ready"
|
data-testid="agent-status-ready"
|
||||||
aria-label="Ready"
|
aria-label="Ready"
|
||||||
className={combineClasses([styles.status, styles.connected])}
|
css={[styles.status, styles.connected]}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const StartingLifecycle: React.FC = () => {
|
const StartingLifecycle: React.FC = () => {
|
||||||
const styles = useStyles();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip title="Starting...">
|
<Tooltip title="Starting...">
|
||||||
<div
|
<div
|
||||||
role="status"
|
role="status"
|
||||||
aria-label="Starting..."
|
aria-label="Starting..."
|
||||||
className={combineClasses([styles.status, styles.connecting])}
|
css={[styles.status, styles.connecting]}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
@ -48,7 +43,6 @@ const StartingLifecycle: React.FC = () => {
|
|||||||
const StartTimeoutLifecycle: React.FC<{
|
const StartTimeoutLifecycle: React.FC<{
|
||||||
agent: WorkspaceAgent;
|
agent: WorkspaceAgent;
|
||||||
}> = ({ agent }) => {
|
}> = ({ agent }) => {
|
||||||
const styles = useStyles();
|
|
||||||
const anchorRef = useRef<SVGSVGElement>(null);
|
const anchorRef = useRef<SVGSVGElement>(null);
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const id = isOpen ? "timeout-popover" : undefined;
|
const id = isOpen ? "timeout-popover" : undefined;
|
||||||
@ -61,7 +55,7 @@ const StartTimeoutLifecycle: React.FC<{
|
|||||||
onMouseLeave={() => setIsOpen(false)}
|
onMouseLeave={() => setIsOpen(false)}
|
||||||
role="status"
|
role="status"
|
||||||
aria-label="Start timeout"
|
aria-label="Start timeout"
|
||||||
className={styles.timeoutWarning}
|
css={styles.timeoutWarning}
|
||||||
/>
|
/>
|
||||||
<HelpPopover
|
<HelpPopover
|
||||||
id={id}
|
id={id}
|
||||||
@ -90,7 +84,6 @@ const StartTimeoutLifecycle: React.FC<{
|
|||||||
const StartErrorLifecycle: React.FC<{
|
const StartErrorLifecycle: React.FC<{
|
||||||
agent: WorkspaceAgent;
|
agent: WorkspaceAgent;
|
||||||
}> = ({ agent }) => {
|
}> = ({ agent }) => {
|
||||||
const styles = useStyles();
|
|
||||||
const anchorRef = useRef<SVGSVGElement>(null);
|
const anchorRef = useRef<SVGSVGElement>(null);
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const id = isOpen ? "timeout-popover" : undefined;
|
const id = isOpen ? "timeout-popover" : undefined;
|
||||||
@ -103,7 +96,7 @@ const StartErrorLifecycle: React.FC<{
|
|||||||
onMouseLeave={() => setIsOpen(false)}
|
onMouseLeave={() => setIsOpen(false)}
|
||||||
role="status"
|
role="status"
|
||||||
aria-label="Start error"
|
aria-label="Start error"
|
||||||
className={styles.errorWarning}
|
css={styles.errorWarning}
|
||||||
/>
|
/>
|
||||||
<HelpPopover
|
<HelpPopover
|
||||||
id={id}
|
id={id}
|
||||||
@ -130,14 +123,12 @@ const StartErrorLifecycle: React.FC<{
|
|||||||
};
|
};
|
||||||
|
|
||||||
const ShuttingDownLifecycle: React.FC = () => {
|
const ShuttingDownLifecycle: React.FC = () => {
|
||||||
const styles = useStyles();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip title="Stopping...">
|
<Tooltip title="Stopping...">
|
||||||
<div
|
<div
|
||||||
role="status"
|
role="status"
|
||||||
aria-label="Stopping..."
|
aria-label="Stopping..."
|
||||||
className={combineClasses([styles.status, styles.connecting])}
|
css={[styles.status, styles.connecting]}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
@ -146,7 +137,6 @@ const ShuttingDownLifecycle: React.FC = () => {
|
|||||||
const ShutdownTimeoutLifecycle: React.FC<{
|
const ShutdownTimeoutLifecycle: React.FC<{
|
||||||
agent: WorkspaceAgent;
|
agent: WorkspaceAgent;
|
||||||
}> = ({ agent }) => {
|
}> = ({ agent }) => {
|
||||||
const styles = useStyles();
|
|
||||||
const anchorRef = useRef<SVGSVGElement>(null);
|
const anchorRef = useRef<SVGSVGElement>(null);
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const id = isOpen ? "timeout-popover" : undefined;
|
const id = isOpen ? "timeout-popover" : undefined;
|
||||||
@ -159,7 +149,7 @@ const ShutdownTimeoutLifecycle: React.FC<{
|
|||||||
onMouseLeave={() => setIsOpen(false)}
|
onMouseLeave={() => setIsOpen(false)}
|
||||||
role="status"
|
role="status"
|
||||||
aria-label="Stop timeout"
|
aria-label="Stop timeout"
|
||||||
className={styles.timeoutWarning}
|
css={styles.timeoutWarning}
|
||||||
/>
|
/>
|
||||||
<HelpPopover
|
<HelpPopover
|
||||||
id={id}
|
id={id}
|
||||||
@ -188,7 +178,6 @@ const ShutdownTimeoutLifecycle: React.FC<{
|
|||||||
const ShutdownErrorLifecycle: React.FC<{
|
const ShutdownErrorLifecycle: React.FC<{
|
||||||
agent: WorkspaceAgent;
|
agent: WorkspaceAgent;
|
||||||
}> = ({ agent }) => {
|
}> = ({ agent }) => {
|
||||||
const styles = useStyles();
|
|
||||||
const anchorRef = useRef<SVGSVGElement>(null);
|
const anchorRef = useRef<SVGSVGElement>(null);
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const id = isOpen ? "timeout-popover" : undefined;
|
const id = isOpen ? "timeout-popover" : undefined;
|
||||||
@ -201,7 +190,7 @@ const ShutdownErrorLifecycle: React.FC<{
|
|||||||
onMouseLeave={() => setIsOpen(false)}
|
onMouseLeave={() => setIsOpen(false)}
|
||||||
role="status"
|
role="status"
|
||||||
aria-label="Stop error"
|
aria-label="Stop error"
|
||||||
className={styles.errorWarning}
|
css={styles.errorWarning}
|
||||||
/>
|
/>
|
||||||
<HelpPopover
|
<HelpPopover
|
||||||
id={id}
|
id={id}
|
||||||
@ -228,14 +217,12 @@ const ShutdownErrorLifecycle: React.FC<{
|
|||||||
};
|
};
|
||||||
|
|
||||||
const OffLifecycle: React.FC = () => {
|
const OffLifecycle: React.FC = () => {
|
||||||
const styles = useStyles();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip title="Stopped">
|
<Tooltip title="Stopped">
|
||||||
<div
|
<div
|
||||||
role="status"
|
role="status"
|
||||||
aria-label="Stopped"
|
aria-label="Stopped"
|
||||||
className={combineClasses([styles.status, styles.disconnected])}
|
css={[styles.status, styles.disconnected]}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
@ -280,28 +267,24 @@ const ConnectedStatus: React.FC<{
|
|||||||
};
|
};
|
||||||
|
|
||||||
const DisconnectedStatus: React.FC = () => {
|
const DisconnectedStatus: React.FC = () => {
|
||||||
const styles = useStyles();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip title="Disconnected">
|
<Tooltip title="Disconnected">
|
||||||
<div
|
<div
|
||||||
role="status"
|
role="status"
|
||||||
aria-label="Disconnected"
|
aria-label="Disconnected"
|
||||||
className={combineClasses([styles.status, styles.disconnected])}
|
css={[styles.status, styles.disconnected]}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const ConnectingStatus: React.FC = () => {
|
const ConnectingStatus: React.FC = () => {
|
||||||
const styles = useStyles();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip title="Connecting...">
|
<Tooltip title="Connecting...">
|
||||||
<div
|
<div
|
||||||
role="status"
|
role="status"
|
||||||
aria-label="Connecting..."
|
aria-label="Connecting..."
|
||||||
className={combineClasses([styles.status, styles.connecting])}
|
css={[styles.status, styles.connecting]}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
@ -310,7 +293,6 @@ const ConnectingStatus: React.FC = () => {
|
|||||||
const TimeoutStatus: React.FC<{
|
const TimeoutStatus: React.FC<{
|
||||||
agent: WorkspaceAgent;
|
agent: WorkspaceAgent;
|
||||||
}> = ({ agent }) => {
|
}> = ({ agent }) => {
|
||||||
const styles = useStyles();
|
|
||||||
const anchorRef = useRef<SVGSVGElement>(null);
|
const anchorRef = useRef<SVGSVGElement>(null);
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const id = isOpen ? "timeout-popover" : undefined;
|
const id = isOpen ? "timeout-popover" : undefined;
|
||||||
@ -323,7 +305,7 @@ const TimeoutStatus: React.FC<{
|
|||||||
onMouseLeave={() => setIsOpen(false)}
|
onMouseLeave={() => setIsOpen(false)}
|
||||||
role="status"
|
role="status"
|
||||||
aria-label="Timeout"
|
aria-label="Timeout"
|
||||||
className={styles.timeoutWarning}
|
css={styles.timeoutWarning}
|
||||||
/>
|
/>
|
||||||
<HelpPopover
|
<HelpPopover
|
||||||
id={id}
|
id={id}
|
||||||
@ -370,22 +352,22 @@ export const AgentStatus: React.FC<{
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const styles = {
|
||||||
status: {
|
status: (theme) => ({
|
||||||
width: theme.spacing(1),
|
width: theme.spacing(1),
|
||||||
height: theme.spacing(1),
|
height: theme.spacing(1),
|
||||||
borderRadius: "100%",
|
borderRadius: "100%",
|
||||||
flexShrink: 0,
|
flexShrink: 0,
|
||||||
},
|
}),
|
||||||
|
|
||||||
connected: {
|
connected: (theme) => ({
|
||||||
backgroundColor: theme.palette.success.light,
|
backgroundColor: theme.palette.success.light,
|
||||||
boxShadow: `0 0 12px 0 ${theme.palette.success.light}`,
|
boxShadow: `0 0 12px 0 ${theme.palette.success.light}`,
|
||||||
},
|
}),
|
||||||
|
|
||||||
disconnected: {
|
disconnected: (theme) => ({
|
||||||
backgroundColor: theme.palette.text.secondary,
|
backgroundColor: theme.palette.text.secondary,
|
||||||
},
|
}),
|
||||||
|
|
||||||
"@keyframes pulse": {
|
"@keyframes pulse": {
|
||||||
"0%": {
|
"0%": {
|
||||||
@ -399,22 +381,22 @@ const useStyles = makeStyles((theme) => ({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
connecting: {
|
connecting: (theme) => ({
|
||||||
backgroundColor: theme.palette.info.light,
|
backgroundColor: theme.palette.info.light,
|
||||||
animation: "$pulse 1.5s 0.5s ease-in-out forwards infinite",
|
animation: "$pulse 1.5s 0.5s ease-in-out forwards infinite",
|
||||||
},
|
}),
|
||||||
|
|
||||||
timeoutWarning: {
|
timeoutWarning: (theme) => ({
|
||||||
color: theme.palette.warning.light,
|
color: theme.palette.warning.light,
|
||||||
width: theme.spacing(2),
|
width: theme.spacing(2),
|
||||||
height: theme.spacing(2),
|
height: theme.spacing(2),
|
||||||
position: "relative",
|
position: "relative",
|
||||||
},
|
}),
|
||||||
|
|
||||||
errorWarning: {
|
errorWarning: (theme) => ({
|
||||||
color: theme.palette.error.main,
|
color: theme.palette.error.main,
|
||||||
width: theme.spacing(2),
|
width: theme.spacing(2),
|
||||||
height: theme.spacing(2),
|
height: theme.spacing(2),
|
||||||
position: "relative",
|
position: "relative",
|
||||||
},
|
}),
|
||||||
}));
|
} satisfies Record<string, Interpolation<Theme>>;
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { makeStyles } from "@mui/styles";
|
|
||||||
import { Stack } from "components/Stack/Stack";
|
import { Stack } from "components/Stack/Stack";
|
||||||
import { FC } from "react";
|
import { type FC } from "react";
|
||||||
import * as TypesGen from "api/typesGenerated";
|
import type * as TypesGen from "api/typesGenerated";
|
||||||
import { BaseIcon } from "./BaseIcon";
|
import { BaseIcon } from "./BaseIcon";
|
||||||
import { ShareIcon } from "./ShareIcon";
|
import { ShareIcon } from "./ShareIcon";
|
||||||
|
|
||||||
@ -10,11 +9,22 @@ interface AppPreviewProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const AppPreviewLink: FC<AppPreviewProps> = ({ app }) => {
|
export const AppPreviewLink: FC<AppPreviewProps> = ({ app }) => {
|
||||||
const styles = useStyles();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack
|
<Stack
|
||||||
className={styles.appPreviewLink}
|
css={(theme) => ({
|
||||||
|
padding: theme.spacing(0.25, 1.5),
|
||||||
|
borderRadius: 9999,
|
||||||
|
border: `1px solid ${theme.palette.divider}`,
|
||||||
|
color: theme.palette.text.primary,
|
||||||
|
background: theme.palette.background.paper,
|
||||||
|
flexShrink: 0,
|
||||||
|
width: "fit-content",
|
||||||
|
fontSize: 12,
|
||||||
|
|
||||||
|
"& img, & svg": {
|
||||||
|
width: 13,
|
||||||
|
},
|
||||||
|
})}
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
direction="row"
|
direction="row"
|
||||||
spacing={1}
|
spacing={1}
|
||||||
@ -25,20 +35,3 @@ export const AppPreviewLink: FC<AppPreviewProps> = ({ app }) => {
|
|||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
|
||||||
appPreviewLink: {
|
|
||||||
padding: theme.spacing(0.25, 1.5),
|
|
||||||
borderRadius: 9999,
|
|
||||||
border: `1px solid ${theme.palette.divider}`,
|
|
||||||
color: theme.palette.text.primary,
|
|
||||||
background: theme.palette.background.paper,
|
|
||||||
flexShrink: 0,
|
|
||||||
width: "fit-content",
|
|
||||||
fontSize: 12,
|
|
||||||
|
|
||||||
"& img, & svg": {
|
|
||||||
width: 13,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
|
import { type Interpolation, type Theme } from "@emotion/react";
|
||||||
import Button from "@mui/material/Button";
|
import Button from "@mui/material/Button";
|
||||||
import { makeStyles } from "@mui/styles";
|
import { type FC, useState } from "react";
|
||||||
|
import type { WorkspaceAgent, WorkspaceResource } from "api/typesGenerated";
|
||||||
import { DropdownArrow } from "components/DropdownArrow/DropdownArrow";
|
import { DropdownArrow } from "components/DropdownArrow/DropdownArrow";
|
||||||
import { FC, useState } from "react";
|
|
||||||
import { WorkspaceAgent, WorkspaceResource } from "api/typesGenerated";
|
|
||||||
import { Stack } from "../Stack/Stack";
|
import { Stack } from "../Stack/Stack";
|
||||||
import { ResourceCard } from "./ResourceCard";
|
import { ResourceCard } from "./ResourceCard";
|
||||||
|
|
||||||
@ -19,7 +19,6 @@ export const Resources: FC<React.PropsWithChildren<ResourcesProps>> = ({
|
|||||||
resources,
|
resources,
|
||||||
agentRow,
|
agentRow,
|
||||||
}) => {
|
}) => {
|
||||||
const styles = useStyles();
|
|
||||||
const [shouldDisplayHideResources, setShouldDisplayHideResources] =
|
const [shouldDisplayHideResources, setShouldDisplayHideResources] =
|
||||||
useState(false);
|
useState(false);
|
||||||
const displayResources = shouldDisplayHideResources
|
const displayResources = shouldDisplayHideResources
|
||||||
@ -40,9 +39,9 @@ export const Resources: FC<React.PropsWithChildren<ResourcesProps>> = ({
|
|||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{hasHideResources && (
|
{hasHideResources && (
|
||||||
<div className={styles.buttonWrapper}>
|
<div css={styles.buttonWrapper}>
|
||||||
<Button
|
<Button
|
||||||
className={styles.showMoreButton}
|
css={styles.showMoreButton}
|
||||||
size="small"
|
size="small"
|
||||||
onClick={() => setShouldDisplayHideResources((v) => !v)}
|
onClick={() => setShouldDisplayHideResources((v) => !v)}
|
||||||
>
|
>
|
||||||
@ -55,17 +54,17 @@ export const Resources: FC<React.PropsWithChildren<ResourcesProps>> = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const styles = {
|
||||||
buttonWrapper: {
|
buttonWrapper: (theme) => ({
|
||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
marginTop: theme.spacing(2),
|
marginTop: theme.spacing(2),
|
||||||
},
|
}),
|
||||||
|
|
||||||
showMoreButton: {
|
showMoreButton: {
|
||||||
borderRadius: 9999,
|
borderRadius: 9999,
|
||||||
width: "100%",
|
width: "100%",
|
||||||
maxWidth: 260,
|
maxWidth: 260,
|
||||||
},
|
},
|
||||||
}));
|
} satisfies Record<string, Interpolation<Theme>>;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
|
import { type Interpolation, type Theme } from "@emotion/react";
|
||||||
import Chip from "@mui/material/Chip";
|
import Chip from "@mui/material/Chip";
|
||||||
import FormHelperText from "@mui/material/FormHelperText";
|
import FormHelperText from "@mui/material/FormHelperText";
|
||||||
import { makeStyles } from "@mui/styles";
|
import { type FC } from "react";
|
||||||
import { FC } from "react";
|
|
||||||
|
|
||||||
export type MultiTextFieldProps = {
|
export type MultiTextFieldProps = {
|
||||||
label: string;
|
label: string;
|
||||||
@ -16,11 +16,9 @@ export const MultiTextField: FC<MultiTextFieldProps> = ({
|
|||||||
values,
|
values,
|
||||||
onChange,
|
onChange,
|
||||||
}) => {
|
}) => {
|
||||||
const styles = useStyles();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<label className={styles.root}>
|
<label css={styles.root}>
|
||||||
{values.map((value, index) => (
|
{values.map((value, index) => (
|
||||||
<Chip
|
<Chip
|
||||||
key={index}
|
key={index}
|
||||||
@ -34,7 +32,7 @@ export const MultiTextField: FC<MultiTextFieldProps> = ({
|
|||||||
<input
|
<input
|
||||||
id={id}
|
id={id}
|
||||||
aria-label={label}
|
aria-label={label}
|
||||||
className={styles.input}
|
css={styles.input}
|
||||||
onKeyDown={(event) => {
|
onKeyDown={(event) => {
|
||||||
if (event.key === ",") {
|
if (event.key === ",") {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
@ -71,8 +69,8 @@ export const MultiTextField: FC<MultiTextFieldProps> = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const styles = {
|
||||||
root: {
|
root: (theme) => ({
|
||||||
border: `1px solid ${theme.palette.divider}`,
|
border: `1px solid ${theme.palette.divider}`,
|
||||||
borderRadius: theme.shape.borderRadius,
|
borderRadius: theme.shape.borderRadius,
|
||||||
minHeight: theme.spacing(6), // Chip height + paddings
|
minHeight: theme.spacing(6), // Chip height + paddings
|
||||||
@ -91,7 +89,7 @@ const useStyles = makeStyles((theme) => ({
|
|||||||
top: -1,
|
top: -1,
|
||||||
left: -1,
|
left: -1,
|
||||||
},
|
},
|
||||||
},
|
}),
|
||||||
|
|
||||||
input: {
|
input: {
|
||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
@ -104,4 +102,4 @@ const useStyles = makeStyles((theme) => ({
|
|||||||
outline: "none",
|
outline: "none",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}));
|
} satisfies Record<string, Interpolation<Theme>>;
|
||||||
|
@ -1,11 +1,4 @@
|
|||||||
import { makeStyles } from "@mui/styles";
|
import { type FC } from "react";
|
||||||
import { FC } from "react";
|
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
|
||||||
root: {
|
|
||||||
marginTop: theme.spacing(3),
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SectionAction is a content box that call to actions should be placed
|
* SectionAction is a content box that call to actions should be placed
|
||||||
@ -14,6 +7,13 @@ const useStyles = makeStyles((theme) => ({
|
|||||||
export const SectionAction: FC<React.PropsWithChildren<unknown>> = ({
|
export const SectionAction: FC<React.PropsWithChildren<unknown>> = ({
|
||||||
children,
|
children,
|
||||||
}) => {
|
}) => {
|
||||||
const styles = useStyles();
|
return (
|
||||||
return <div className={styles.root}>{children}</div>;
|
<div
|
||||||
|
css={(theme) => ({
|
||||||
|
marginTop: theme.spacing(3),
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,16 +1,14 @@
|
|||||||
import { makeStyles } from "@mui/styles";
|
import { type FC, Suspense } from "react";
|
||||||
import { Sidebar } from "./Sidebar";
|
import { Outlet } from "react-router-dom";
|
||||||
import { Stack } from "components/Stack/Stack";
|
|
||||||
import { FC, Suspense } from "react";
|
|
||||||
import { Helmet } from "react-helmet-async";
|
import { Helmet } from "react-helmet-async";
|
||||||
import { pageTitle } from "utils/page";
|
import { pageTitle } from "utils/page";
|
||||||
import { Margins } from "../Margins/Margins";
|
|
||||||
import { useMe } from "hooks/useMe";
|
import { useMe } from "hooks/useMe";
|
||||||
import { Loader } from "components/Loader/Loader";
|
import { Loader } from "components/Loader/Loader";
|
||||||
import { Outlet } from "react-router-dom";
|
import { Stack } from "components/Stack/Stack";
|
||||||
|
import { Margins } from "../Margins/Margins";
|
||||||
|
import { Sidebar } from "./Sidebar";
|
||||||
|
|
||||||
export const SettingsLayout: FC = () => {
|
export const SettingsLayout: FC = () => {
|
||||||
const styles = useStyles();
|
|
||||||
const me = useMe();
|
const me = useMe();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -20,10 +18,21 @@ export const SettingsLayout: FC = () => {
|
|||||||
</Helmet>
|
</Helmet>
|
||||||
|
|
||||||
<Margins>
|
<Margins>
|
||||||
<Stack className={styles.wrapper} direction="row" spacing={6}>
|
<Stack
|
||||||
|
css={(theme) => ({
|
||||||
|
padding: theme.spacing(6, 0),
|
||||||
|
})}
|
||||||
|
direction="row"
|
||||||
|
spacing={6}
|
||||||
|
>
|
||||||
<Sidebar user={me} />
|
<Sidebar user={me} />
|
||||||
<Suspense fallback={<Loader />}>
|
<Suspense fallback={<Loader />}>
|
||||||
<main className={styles.content}>
|
<main
|
||||||
|
css={{
|
||||||
|
maxWidth: 800,
|
||||||
|
width: "100%",
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</main>
|
</main>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
@ -32,14 +41,3 @@ export const SettingsLayout: FC = () => {
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
|
||||||
wrapper: {
|
|
||||||
padding: theme.spacing(6, 0),
|
|
||||||
},
|
|
||||||
|
|
||||||
content: {
|
|
||||||
maxWidth: 800,
|
|
||||||
width: "100%",
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
@ -1,28 +1,73 @@
|
|||||||
import { makeStyles } from "@mui/styles";
|
import { css } from "@emotion/css";
|
||||||
|
import {
|
||||||
|
type CSSObject,
|
||||||
|
type Interpolation,
|
||||||
|
type Theme,
|
||||||
|
useTheme,
|
||||||
|
} from "@emotion/react";
|
||||||
import VpnKeyOutlined from "@mui/icons-material/VpnKeyOutlined";
|
import VpnKeyOutlined from "@mui/icons-material/VpnKeyOutlined";
|
||||||
import FingerprintOutlinedIcon from "@mui/icons-material/FingerprintOutlined";
|
import FingerprintOutlinedIcon from "@mui/icons-material/FingerprintOutlined";
|
||||||
import { User } from "api/typesGenerated";
|
import {
|
||||||
import { Stack } from "components/Stack/Stack";
|
type FC,
|
||||||
import { UserAvatar } from "components/UserAvatar/UserAvatar";
|
type ComponentType,
|
||||||
import { FC, ElementType, PropsWithChildren, ReactNode } from "react";
|
type PropsWithChildren,
|
||||||
|
type ReactNode,
|
||||||
|
} from "react";
|
||||||
import { NavLink } from "react-router-dom";
|
import { NavLink } from "react-router-dom";
|
||||||
import { combineClasses } from "utils/combineClasses";
|
|
||||||
import AccountIcon from "@mui/icons-material/Person";
|
import AccountIcon from "@mui/icons-material/Person";
|
||||||
import ScheduleIcon from "@mui/icons-material/EditCalendarOutlined";
|
import ScheduleIcon from "@mui/icons-material/EditCalendarOutlined";
|
||||||
import SecurityIcon from "@mui/icons-material/LockOutlined";
|
import SecurityIcon from "@mui/icons-material/LockOutlined";
|
||||||
|
import type { User } from "api/typesGenerated";
|
||||||
|
import { Stack } from "components/Stack/Stack";
|
||||||
|
import { UserAvatar } from "components/UserAvatar/UserAvatar";
|
||||||
import { useDashboard } from "components/Dashboard/DashboardProvider";
|
import { useDashboard } from "components/Dashboard/DashboardProvider";
|
||||||
|
import { combineClasses } from "utils/combineClasses";
|
||||||
|
|
||||||
const SidebarNavItem: FC<
|
const SidebarNavItem: FC<
|
||||||
PropsWithChildren<{ href: string; icon: ReactNode }>
|
PropsWithChildren<{ href: string; icon: ReactNode }>
|
||||||
> = ({ children, href, icon }) => {
|
> = ({ children, href, icon }) => {
|
||||||
const styles = useStyles();
|
const theme = useTheme();
|
||||||
|
|
||||||
|
const sidebarNavItemStyles = css`
|
||||||
|
color: inherit;
|
||||||
|
display: block;
|
||||||
|
font-size: 14px;
|
||||||
|
text-decoration: none;
|
||||||
|
padding: ${theme.spacing(1.5, 1.5, 1.5, 2)};
|
||||||
|
border-radius: ${theme.shape.borderRadius / 2}px;
|
||||||
|
transition: background-color 0.15s ease-in-out;
|
||||||
|
margin-bottom: 1px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: theme.palette.action.hover;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const sidebarNavItemActiveStyles = css`
|
||||||
|
background-color: ${theme.palette.action.hover};
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
content: "";
|
||||||
|
display: block;
|
||||||
|
width: 3px;
|
||||||
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
background-color: ${theme.palette.secondary.dark};
|
||||||
|
border-top-left-radius: ${theme.shape.borderRadius};
|
||||||
|
border-bottom-left-radius: ${theme.shape.borderRadius};
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NavLink
|
<NavLink
|
||||||
to={href}
|
to={href}
|
||||||
className={({ isActive }) =>
|
className={({ isActive }) =>
|
||||||
combineClasses([
|
combineClasses([
|
||||||
styles.sidebarNavItem,
|
sidebarNavItemStyles,
|
||||||
isActive ? styles.sidebarNavItemActive : undefined,
|
isActive ? sidebarNavItemActiveStyles : undefined,
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@ -34,26 +79,31 @@ const SidebarNavItem: FC<
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const SidebarNavItemIcon: React.FC<{ icon: ElementType }> = ({
|
const SidebarNavItemIcon: React.FC<{
|
||||||
icon: Icon,
|
icon: ComponentType<{ className?: string }>;
|
||||||
}) => {
|
}> = ({ icon: Icon }) => {
|
||||||
const styles = useStyles();
|
return (
|
||||||
return <Icon className={styles.sidebarNavItemIcon} />;
|
<Icon
|
||||||
|
css={(theme) => ({
|
||||||
|
width: theme.spacing(2),
|
||||||
|
height: theme.spacing(2),
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Sidebar: React.FC<{ user: User }> = ({ user }) => {
|
export const Sidebar: React.FC<{ user: User }> = ({ user }) => {
|
||||||
const styles = useStyles();
|
|
||||||
const { entitlements } = useDashboard();
|
const { entitlements } = useDashboard();
|
||||||
const allowAutostopRequirement =
|
const allowAutostopRequirement =
|
||||||
entitlements.features.template_autostop_requirement.enabled;
|
entitlements.features.template_autostop_requirement.enabled;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<nav className={styles.sidebar}>
|
<nav css={styles.sidebar}>
|
||||||
<Stack direction="row" alignItems="center" className={styles.userInfo}>
|
<Stack direction="row" alignItems="center" css={styles.userInfo}>
|
||||||
<UserAvatar username={user.username} avatarURL={user.avatar_url} />
|
<UserAvatar username={user.username} avatarURL={user.avatar_url} />
|
||||||
<Stack spacing={0} className={styles.userData}>
|
<Stack spacing={0} css={styles.userData}>
|
||||||
<span className={styles.username}>{user.username}</span>
|
<span css={styles.username}>{user.username}</span>
|
||||||
<span className={styles.email}>{user.email}</span>
|
<span css={styles.email}>{user.email}</span>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
@ -93,50 +143,15 @@ export const Sidebar: React.FC<{ user: User }> = ({ user }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const styles = {
|
||||||
sidebar: {
|
sidebar: {
|
||||||
width: 245,
|
width: 245,
|
||||||
flexShrink: 0,
|
flexShrink: 0,
|
||||||
},
|
},
|
||||||
sidebarNavItem: {
|
userInfo: (theme) => ({
|
||||||
color: "inherit",
|
...(theme.typography.body2 as CSSObject),
|
||||||
display: "block",
|
|
||||||
fontSize: 14,
|
|
||||||
textDecoration: "none",
|
|
||||||
padding: theme.spacing(1.5, 1.5, 1.5, 2),
|
|
||||||
borderRadius: theme.shape.borderRadius / 2,
|
|
||||||
transition: "background-color 0.15s ease-in-out",
|
|
||||||
marginBottom: 1,
|
|
||||||
position: "relative",
|
|
||||||
|
|
||||||
"&:hover": {
|
|
||||||
backgroundColor: theme.palette.action.hover,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
sidebarNavItemActive: {
|
|
||||||
backgroundColor: theme.palette.action.hover,
|
|
||||||
|
|
||||||
"&:before": {
|
|
||||||
content: '""',
|
|
||||||
display: "block",
|
|
||||||
width: 3,
|
|
||||||
height: "100%",
|
|
||||||
position: "absolute",
|
|
||||||
left: 0,
|
|
||||||
top: 0,
|
|
||||||
backgroundColor: theme.palette.secondary.dark,
|
|
||||||
borderTopLeftRadius: theme.shape.borderRadius,
|
|
||||||
borderBottomLeftRadius: theme.shape.borderRadius,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
sidebarNavItemIcon: {
|
|
||||||
width: theme.spacing(2),
|
|
||||||
height: theme.spacing(2),
|
|
||||||
},
|
|
||||||
userInfo: {
|
|
||||||
...theme.typography.body2,
|
|
||||||
marginBottom: theme.spacing(2),
|
marginBottom: theme.spacing(2),
|
||||||
},
|
}),
|
||||||
userData: {
|
userData: {
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
},
|
},
|
||||||
@ -146,10 +161,10 @@ const useStyles = makeStyles((theme) => ({
|
|||||||
textOverflow: "ellipsis",
|
textOverflow: "ellipsis",
|
||||||
whiteSpace: "nowrap",
|
whiteSpace: "nowrap",
|
||||||
},
|
},
|
||||||
email: {
|
email: (theme) => ({
|
||||||
color: theme.palette.text.secondary,
|
color: theme.palette.text.secondary,
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
textOverflow: "ellipsis",
|
textOverflow: "ellipsis",
|
||||||
},
|
}),
|
||||||
}));
|
} satisfies Record<string, Interpolation<Theme>>;
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import { ComponentProps, FC } from "react";
|
import { type ComponentProps, type FC } from "react";
|
||||||
import Editor, { DiffEditor, loader } from "@monaco-editor/react";
|
import Editor, { DiffEditor, loader } from "@monaco-editor/react";
|
||||||
import * as monaco from "monaco-editor";
|
import * as monaco from "monaco-editor";
|
||||||
import { useCoderTheme } from "./coderTheme";
|
import { useCoderTheme } from "./coderTheme";
|
||||||
import { makeStyles } from "@mui/styles";
|
|
||||||
|
|
||||||
loader.config({ monaco });
|
loader.config({ monaco });
|
||||||
|
|
||||||
@ -13,7 +12,6 @@ export const SyntaxHighlighter: FC<{
|
|||||||
ComponentProps<typeof DiffEditor>;
|
ComponentProps<typeof DiffEditor>;
|
||||||
compareWith?: string;
|
compareWith?: string;
|
||||||
}> = ({ value, compareWith, language, editorProps }) => {
|
}> = ({ value, compareWith, language, editorProps }) => {
|
||||||
const styles = useStyles();
|
|
||||||
const hasDiff = compareWith && value !== compareWith;
|
const hasDiff = compareWith && value !== compareWith;
|
||||||
const coderTheme = useCoderTheme();
|
const coderTheme = useCoderTheme();
|
||||||
const commonProps = {
|
const commonProps = {
|
||||||
@ -35,7 +33,13 @@ export const SyntaxHighlighter: FC<{
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.wrapper}>
|
<div
|
||||||
|
css={(theme) => ({
|
||||||
|
padding: theme.spacing(1, 0),
|
||||||
|
background: theme.palette.background.paper,
|
||||||
|
height: "100%",
|
||||||
|
})}
|
||||||
|
>
|
||||||
{hasDiff ? (
|
{hasDiff ? (
|
||||||
<DiffEditor original={compareWith} modified={value} {...commonProps} />
|
<DiffEditor original={compareWith} modified={value} {...commonProps} />
|
||||||
) : (
|
) : (
|
||||||
@ -44,11 +48,3 @@ export const SyntaxHighlighter: FC<{
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
|
||||||
wrapper: {
|
|
||||||
padding: theme.spacing(1, 0),
|
|
||||||
background: theme.palette.background.paper,
|
|
||||||
height: "100%",
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import { makeStyles } from "@mui/styles";
|
import { type Interpolation, type Theme } from "@emotion/react";
|
||||||
import { TemplateExample } from "api/typesGenerated";
|
import type { TemplateExample } from "api/typesGenerated";
|
||||||
import { FC } from "react";
|
import { type FC } from "react";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { combineClasses } from "utils/combineClasses";
|
|
||||||
|
|
||||||
export interface TemplateExampleCardProps {
|
export interface TemplateExampleCardProps {
|
||||||
example: TemplateExample;
|
example: TemplateExample;
|
||||||
@ -13,29 +12,26 @@ export const TemplateExampleCard: FC<TemplateExampleCardProps> = ({
|
|||||||
example,
|
example,
|
||||||
className,
|
className,
|
||||||
}) => {
|
}) => {
|
||||||
const styles = useStyles();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
to={`/starter-templates/${example.id}`}
|
to={`/starter-templates/${example.id}`}
|
||||||
className={combineClasses([styles.template, className])}
|
css={styles.template}
|
||||||
|
className={className}
|
||||||
key={example.id}
|
key={example.id}
|
||||||
>
|
>
|
||||||
<div className={styles.templateIcon}>
|
<div css={styles.templateIcon}>
|
||||||
<img src={example.icon} alt="" />
|
<img src={example.icon} alt="" />
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.templateInfo}>
|
<div css={styles.templateInfo}>
|
||||||
<span className={styles.templateName}>{example.name}</span>
|
<span css={styles.templateName}>{example.name}</span>
|
||||||
<span className={styles.templateDescription}>
|
<span css={styles.templateDescription}>{example.description}</span>
|
||||||
{example.description}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const styles = {
|
||||||
template: {
|
template: (theme) => ({
|
||||||
border: `1px solid ${theme.palette.divider}`,
|
border: `1px solid ${theme.palette.divider}`,
|
||||||
borderRadius: theme.shape.borderRadius,
|
borderRadius: theme.shape.borderRadius,
|
||||||
background: theme.palette.background.paper,
|
background: theme.palette.background.paper,
|
||||||
@ -49,9 +45,9 @@ const useStyles = makeStyles((theme) => ({
|
|||||||
"&:hover": {
|
"&:hover": {
|
||||||
backgroundColor: theme.palette.background.paperLight,
|
backgroundColor: theme.palette.background.paperLight,
|
||||||
},
|
},
|
||||||
},
|
}),
|
||||||
|
|
||||||
templateIcon: {
|
templateIcon: (theme) => ({
|
||||||
width: theme.spacing(12),
|
width: theme.spacing(12),
|
||||||
height: theme.spacing(12),
|
height: theme.spacing(12),
|
||||||
display: "flex",
|
display: "flex",
|
||||||
@ -62,29 +58,29 @@ const useStyles = makeStyles((theme) => ({
|
|||||||
"& img": {
|
"& img": {
|
||||||
height: theme.spacing(4),
|
height: theme.spacing(4),
|
||||||
},
|
},
|
||||||
},
|
}),
|
||||||
|
|
||||||
templateInfo: {
|
templateInfo: (theme) => ({
|
||||||
padding: theme.spacing(2, 2, 2, 0),
|
padding: theme.spacing(2, 2, 2, 0),
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
},
|
}),
|
||||||
|
|
||||||
templateName: {
|
templateName: (theme) => ({
|
||||||
fontSize: theme.spacing(2),
|
fontSize: theme.spacing(2),
|
||||||
textOverflow: "ellipsis",
|
textOverflow: "ellipsis",
|
||||||
width: "100%",
|
width: "100%",
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
whiteSpace: "nowrap",
|
whiteSpace: "nowrap",
|
||||||
},
|
}),
|
||||||
|
|
||||||
templateDescription: {
|
templateDescription: (theme) => ({
|
||||||
fontSize: theme.spacing(1.75),
|
fontSize: theme.spacing(1.75),
|
||||||
color: theme.palette.text.secondary,
|
color: theme.palette.text.secondary,
|
||||||
textOverflow: "ellipsis",
|
textOverflow: "ellipsis",
|
||||||
width: "100%",
|
width: "100%",
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
whiteSpace: "nowrap",
|
whiteSpace: "nowrap",
|
||||||
},
|
}),
|
||||||
}));
|
} satisfies Record<string, Interpolation<Theme>>;
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
import { makeStyles } from "@mui/styles";
|
import { type Interpolation, type Theme } from "@emotion/react";
|
||||||
|
import { type FC } from "react";
|
||||||
import { DockerIcon } from "components/Icons/DockerIcon";
|
import { DockerIcon } from "components/Icons/DockerIcon";
|
||||||
import { MarkdownIcon } from "components/Icons/MarkdownIcon";
|
import { MarkdownIcon } from "components/Icons/MarkdownIcon";
|
||||||
import { TerraformIcon } from "components/Icons/TerraformIcon";
|
import { TerraformIcon } from "components/Icons/TerraformIcon";
|
||||||
import { SyntaxHighlighter } from "components/SyntaxHighlighter/SyntaxHighlighter";
|
import { SyntaxHighlighter } from "components/SyntaxHighlighter/SyntaxHighlighter";
|
||||||
import { UseTabResult } from "hooks/useTab";
|
import { UseTabResult } from "hooks/useTab";
|
||||||
import { FC } from "react";
|
|
||||||
import { combineClasses } from "utils/combineClasses";
|
|
||||||
import { AllowedExtension, TemplateVersionFiles } from "utils/templateVersion";
|
import { AllowedExtension, TemplateVersionFiles } from "utils/templateVersion";
|
||||||
import InsertDriveFileOutlined from "@mui/icons-material/InsertDriveFileOutlined";
|
import InsertDriveFileOutlined from "@mui/icons-material/InsertDriveFileOutlined";
|
||||||
|
|
||||||
@ -43,15 +42,14 @@ export const TemplateFiles: FC<{
|
|||||||
previousFiles?: TemplateVersionFiles;
|
previousFiles?: TemplateVersionFiles;
|
||||||
tab: UseTabResult;
|
tab: UseTabResult;
|
||||||
}> = ({ currentFiles, previousFiles, tab }) => {
|
}> = ({ currentFiles, previousFiles, tab }) => {
|
||||||
const styles = useStyles();
|
|
||||||
const filenames = Object.keys(currentFiles);
|
const filenames = Object.keys(currentFiles);
|
||||||
const selectedFilename = filenames[Number(tab.value)];
|
const selectedFilename = filenames[Number(tab.value)];
|
||||||
const currentFile = currentFiles[selectedFilename];
|
const currentFile = currentFiles[selectedFilename];
|
||||||
const previousFile = previousFiles && previousFiles[selectedFilename];
|
const previousFile = previousFiles && previousFiles[selectedFilename];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.files}>
|
<div css={styles.files}>
|
||||||
<div className={styles.tabs}>
|
<div css={styles.tabs}>
|
||||||
{filenames.map((filename, index) => {
|
{filenames.map((filename, index) => {
|
||||||
const tabValue = index.toString();
|
const tabValue = index.toString();
|
||||||
const extension = getExtension(filename) as AllowedExtension;
|
const extension = getExtension(filename) as AllowedExtension;
|
||||||
@ -63,10 +61,7 @@ export const TemplateFiles: FC<{
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
className={combineClasses({
|
css={[styles.tab, tabValue === tab.value && styles.tabActive]}
|
||||||
[styles.tab]: true,
|
|
||||||
[styles.tabActive]: tabValue === tab.value,
|
|
||||||
})}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
tab.set(tabValue);
|
tab.set(tabValue);
|
||||||
}}
|
}}
|
||||||
@ -74,7 +69,7 @@ export const TemplateFiles: FC<{
|
|||||||
>
|
>
|
||||||
{icon}
|
{icon}
|
||||||
{filename}
|
{filename}
|
||||||
{hasDiff && <div className={styles.tabDiff} />}
|
{hasDiff && <div css={styles.tabDiff} />}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@ -92,16 +87,16 @@ export const TemplateFiles: FC<{
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
const useStyles = makeStyles((theme) => ({
|
const styles = {
|
||||||
tabs: {
|
tabs: (theme) => ({
|
||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "baseline",
|
alignItems: "baseline",
|
||||||
borderBottom: `1px solid ${theme.palette.divider}`,
|
borderBottom: `1px solid ${theme.palette.divider}`,
|
||||||
gap: 1,
|
gap: 1,
|
||||||
overflowX: "auto",
|
overflowX: "auto",
|
||||||
},
|
}),
|
||||||
|
|
||||||
tab: {
|
tab: (theme) => ({
|
||||||
background: "transparent",
|
background: "transparent",
|
||||||
border: 0,
|
border: 0,
|
||||||
padding: theme.spacing(0, 3),
|
padding: theme.spacing(0, 3),
|
||||||
@ -123,9 +118,9 @@ const useStyles = makeStyles((theme) => ({
|
|||||||
"&:hover": {
|
"&:hover": {
|
||||||
backgroundColor: theme.palette.action.hover,
|
backgroundColor: theme.palette.action.hover,
|
||||||
},
|
},
|
||||||
},
|
}),
|
||||||
|
|
||||||
tabActive: {
|
tabActive: (theme) => ({
|
||||||
opacity: 1,
|
opacity: 1,
|
||||||
background: theme.palette.action.hover,
|
background: theme.palette.action.hover,
|
||||||
color: theme.palette.text.primary,
|
color: theme.palette.text.primary,
|
||||||
@ -140,26 +135,26 @@ const useStyles = makeStyles((theme) => ({
|
|||||||
backgroundColor: theme.palette.secondary.dark,
|
backgroundColor: theme.palette.secondary.dark,
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
},
|
},
|
||||||
},
|
}),
|
||||||
|
|
||||||
tabDiff: {
|
tabDiff: (theme) => ({
|
||||||
height: 6,
|
height: 6,
|
||||||
width: 6,
|
width: 6,
|
||||||
backgroundColor: theme.palette.warning.light,
|
backgroundColor: theme.palette.warning.light,
|
||||||
borderRadius: "100%",
|
borderRadius: "100%",
|
||||||
marginLeft: theme.spacing(0.5),
|
marginLeft: theme.spacing(0.5),
|
||||||
},
|
}),
|
||||||
|
|
||||||
codeWrapper: {
|
codeWrapper: (theme) => ({
|
||||||
background: theme.palette.background.paperLight,
|
background: theme.palette.background.paperLight,
|
||||||
},
|
}),
|
||||||
|
|
||||||
files: {
|
files: (theme) => ({
|
||||||
borderRadius: theme.shape.borderRadius,
|
borderRadius: theme.shape.borderRadius,
|
||||||
border: `1px solid ${theme.palette.divider}`,
|
border: `1px solid ${theme.palette.divider}`,
|
||||||
},
|
}),
|
||||||
|
|
||||||
prism: {
|
prism: {
|
||||||
borderRadius: 0,
|
borderRadius: 0,
|
||||||
},
|
},
|
||||||
}));
|
} satisfies Record<string, Interpolation<Theme>>;
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
import { makeStyles } from "@mui/styles";
|
import TableRow, { type TableRowProps } from "@mui/material/TableRow";
|
||||||
import TableRow, { TableRowProps } from "@mui/material/TableRow";
|
|
||||||
import { type PropsWithChildren, forwardRef } from "react";
|
import { type PropsWithChildren, forwardRef } from "react";
|
||||||
import { combineClasses } from "utils/combineClasses";
|
|
||||||
|
|
||||||
type TimelineEntryProps = PropsWithChildren<
|
type TimelineEntryProps = PropsWithChildren<
|
||||||
TableRowProps & {
|
TableRowProps & {
|
||||||
@ -13,47 +11,39 @@ export const TimelineEntry = forwardRef(function TimelineEntry(
|
|||||||
{ children, clickable = true, ...props }: TimelineEntryProps,
|
{ children, clickable = true, ...props }: TimelineEntryProps,
|
||||||
ref?: React.ForwardedRef<HTMLTableRowElement>,
|
ref?: React.ForwardedRef<HTMLTableRowElement>,
|
||||||
) {
|
) {
|
||||||
const styles = useStyles();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableRow
|
<TableRow
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={combineClasses({
|
css={(theme) => [
|
||||||
[styles.timelineEntry]: true,
|
{
|
||||||
[styles.clickable]: clickable,
|
position: "relative",
|
||||||
})}
|
"&:focus": {
|
||||||
|
outlineStyle: "solid",
|
||||||
|
outlineOffset: -1,
|
||||||
|
outlineWidth: 2,
|
||||||
|
outlineColor: theme.palette.secondary.dark,
|
||||||
|
},
|
||||||
|
"& td:before": {
|
||||||
|
position: "absolute",
|
||||||
|
left: 50,
|
||||||
|
display: "block",
|
||||||
|
content: "''",
|
||||||
|
height: "100%",
|
||||||
|
width: 2,
|
||||||
|
background: theme.palette.divider,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
clickable && {
|
||||||
|
cursor: "pointer",
|
||||||
|
|
||||||
|
"&:hover": {
|
||||||
|
backgroundColor: theme.palette.action.hover,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</TableRow>
|
</TableRow>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
|
||||||
clickable: {
|
|
||||||
cursor: "pointer",
|
|
||||||
|
|
||||||
"&:hover": {
|
|
||||||
backgroundColor: theme.palette.action.hover,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
timelineEntry: {
|
|
||||||
position: "relative",
|
|
||||||
"&:focus": {
|
|
||||||
outlineStyle: "solid",
|
|
||||||
outlineOffset: -1,
|
|
||||||
outlineWidth: 2,
|
|
||||||
outlineColor: theme.palette.secondary.dark,
|
|
||||||
},
|
|
||||||
"& td:before": {
|
|
||||||
position: "absolute",
|
|
||||||
left: 50,
|
|
||||||
display: "block",
|
|
||||||
content: "''",
|
|
||||||
height: "100%",
|
|
||||||
width: 2,
|
|
||||||
background: theme.palette.divider,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
@ -1,21 +1,19 @@
|
|||||||
import { makeStyles } from "@mui/styles";
|
import { type FC } from "react";
|
||||||
import { Template, TemplateExample } from "api/typesGenerated";
|
import type { Template, TemplateExample } from "api/typesGenerated";
|
||||||
import { Avatar } from "components/Avatar/Avatar";
|
import { Avatar } from "components/Avatar/Avatar";
|
||||||
import { Stack } from "components/Stack/Stack";
|
import { Stack } from "components/Stack/Stack";
|
||||||
import { FC } from "react";
|
import { type Interpolation, type Theme } from "@emotion/react";
|
||||||
|
|
||||||
export interface SelectedTemplateProps {
|
export interface SelectedTemplateProps {
|
||||||
template: Template | TemplateExample;
|
template: Template | TemplateExample;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SelectedTemplate: FC<SelectedTemplateProps> = ({ template }) => {
|
export const SelectedTemplate: FC<SelectedTemplateProps> = ({ template }) => {
|
||||||
const styles = useStyles();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack
|
<Stack
|
||||||
direction="row"
|
direction="row"
|
||||||
spacing={3}
|
spacing={3}
|
||||||
className={styles.template}
|
css={styles.template}
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
>
|
>
|
||||||
<Avatar
|
<Avatar
|
||||||
@ -27,35 +25,33 @@ export const SelectedTemplate: FC<SelectedTemplateProps> = ({ template }) => {
|
|||||||
</Avatar>
|
</Avatar>
|
||||||
|
|
||||||
<Stack direction="column" spacing={0}>
|
<Stack direction="column" spacing={0}>
|
||||||
<span className={styles.templateName}>
|
<span css={styles.templateName}>
|
||||||
{"display_name" in template && template.display_name.length > 0
|
{"display_name" in template && template.display_name.length > 0
|
||||||
? template.display_name
|
? template.display_name
|
||||||
: template.name}
|
: template.name}
|
||||||
</span>
|
</span>
|
||||||
{template.description && (
|
{template.description && (
|
||||||
<span className={styles.templateDescription}>
|
<span css={styles.templateDescription}>{template.description}</span>
|
||||||
{template.description}
|
|
||||||
</span>
|
|
||||||
)}
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const styles = {
|
||||||
template: {
|
template: (theme) => ({
|
||||||
padding: theme.spacing(2.5, 3),
|
padding: theme.spacing(2.5, 3),
|
||||||
borderRadius: theme.shape.borderRadius,
|
borderRadius: theme.shape.borderRadius,
|
||||||
backgroundColor: theme.palette.background.paper,
|
backgroundColor: theme.palette.background.paper,
|
||||||
border: `1px solid ${theme.palette.divider}`,
|
border: `1px solid ${theme.palette.divider}`,
|
||||||
},
|
}),
|
||||||
|
|
||||||
templateName: {
|
templateName: {
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
},
|
},
|
||||||
|
|
||||||
templateDescription: {
|
templateDescription: (theme) => ({
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
color: theme.palette.text.secondary,
|
color: theme.palette.text.secondary,
|
||||||
},
|
}),
|
||||||
}));
|
} satisfies Record<string, Interpolation<Theme>>;
|
||||||
|
@ -1,16 +1,15 @@
|
|||||||
|
import { type CSSObject, type Interpolation, type Theme } from "@emotion/react";
|
||||||
import Button from "@mui/material/Button";
|
import Button from "@mui/material/Button";
|
||||||
import { makeStyles } from "@mui/styles";
|
|
||||||
import TableCell from "@mui/material/TableCell";
|
import TableCell from "@mui/material/TableCell";
|
||||||
import { TemplateVersion } from "api/typesGenerated";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import type { TemplateVersion } from "api/typesGenerated";
|
||||||
import { Pill } from "components/Pill/Pill";
|
import { Pill } from "components/Pill/Pill";
|
||||||
import { Stack } from "components/Stack/Stack";
|
import { Stack } from "components/Stack/Stack";
|
||||||
import { TimelineEntry } from "components/Timeline/TimelineEntry";
|
import { TimelineEntry } from "components/Timeline/TimelineEntry";
|
||||||
import { UserAvatar } from "components/UserAvatar/UserAvatar";
|
import { UserAvatar } from "components/UserAvatar/UserAvatar";
|
||||||
import { InfoTooltip } from "components/InfoTooltip/InfoTooltip";
|
import { InfoTooltip } from "components/InfoTooltip/InfoTooltip";
|
||||||
import { useClickableTableRow } from "hooks/useClickableTableRow";
|
import { useClickableTableRow } from "hooks/useClickableTableRow";
|
||||||
import { useNavigate } from "react-router-dom";
|
|
||||||
import { colors } from "theme/colors";
|
import { colors } from "theme/colors";
|
||||||
import { combineClasses } from "utils/combineClasses";
|
|
||||||
|
|
||||||
export interface VersionRowProps {
|
export interface VersionRowProps {
|
||||||
version: TemplateVersion;
|
version: TemplateVersion;
|
||||||
@ -27,7 +26,6 @@ export const VersionRow: React.FC<VersionRowProps> = ({
|
|||||||
onPromoteClick,
|
onPromoteClick,
|
||||||
onArchiveClick,
|
onArchiveClick,
|
||||||
}) => {
|
}) => {
|
||||||
const styles = useStyles();
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const clickableProps = useClickableTableRow({
|
const clickableProps = useClickableTableRow({
|
||||||
@ -40,17 +38,14 @@ export const VersionRow: React.FC<VersionRowProps> = ({
|
|||||||
<TimelineEntry
|
<TimelineEntry
|
||||||
data-testid={`version-${version.id}`}
|
data-testid={`version-${version.id}`}
|
||||||
{...clickableProps}
|
{...clickableProps}
|
||||||
className={combineClasses({
|
css={[styles.row, isActive && styles.active]}
|
||||||
[clickableProps.className]: true,
|
className={clickableProps.className}
|
||||||
[styles.row]: true,
|
|
||||||
[styles.active]: isActive,
|
|
||||||
})}
|
|
||||||
>
|
>
|
||||||
<TableCell className={styles.versionCell}>
|
<TableCell css={styles.versionCell}>
|
||||||
<Stack
|
<Stack
|
||||||
direction="row"
|
direction="row"
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
className={styles.versionWrapper}
|
css={styles.versionWrapper}
|
||||||
justifyContent="space-between"
|
justifyContent="space-between"
|
||||||
>
|
>
|
||||||
<Stack direction="row" alignItems="center">
|
<Stack direction="row" alignItems="center">
|
||||||
@ -59,7 +54,7 @@ export const VersionRow: React.FC<VersionRowProps> = ({
|
|||||||
avatarURL={version.created_by.avatar_url}
|
avatarURL={version.created_by.avatar_url}
|
||||||
/>
|
/>
|
||||||
<Stack
|
<Stack
|
||||||
className={styles.versionSummary}
|
css={styles.versionSummary}
|
||||||
direction="row"
|
direction="row"
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
spacing={1}
|
spacing={1}
|
||||||
@ -73,7 +68,7 @@ export const VersionRow: React.FC<VersionRowProps> = ({
|
|||||||
<InfoTooltip title="Message" message={version.message} />
|
<InfoTooltip title="Message" message={version.message} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<span className={styles.versionTime}>
|
<span css={styles.versionTime}>
|
||||||
{new Date(version.created_at).toLocaleTimeString()}
|
{new Date(version.created_at).toLocaleTimeString()}
|
||||||
</span>
|
</span>
|
||||||
</Stack>
|
</Stack>
|
||||||
@ -94,7 +89,7 @@ export const VersionRow: React.FC<VersionRowProps> = ({
|
|||||||
{jobStatus === "failed" && <Pill text="Failed" type="error" />}
|
{jobStatus === "failed" && <Pill text="Failed" type="error" />}
|
||||||
{jobStatus === "failed" ? (
|
{jobStatus === "failed" ? (
|
||||||
<Button
|
<Button
|
||||||
className={styles.promoteButton}
|
css={styles.promoteButton}
|
||||||
disabled={isActive || version.archived}
|
disabled={isActive || version.archived}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@ -108,7 +103,7 @@ export const VersionRow: React.FC<VersionRowProps> = ({
|
|||||||
</Button>
|
</Button>
|
||||||
) : (
|
) : (
|
||||||
<Button
|
<Button
|
||||||
className={styles.promoteButton}
|
css={styles.promoteButton}
|
||||||
disabled={isActive || jobStatus !== "succeeded"}
|
disabled={isActive || jobStatus !== "succeeded"}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@ -128,8 +123,8 @@ export const VersionRow: React.FC<VersionRowProps> = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const styles = {
|
||||||
row: {
|
row: (theme) => ({
|
||||||
"&:hover $promoteButton": {
|
"&:hover $promoteButton": {
|
||||||
color: theme.palette.text.primary,
|
color: theme.palette.text.primary,
|
||||||
borderColor: colors.gray[11],
|
borderColor: colors.gray[11],
|
||||||
@ -137,20 +132,20 @@ const useStyles = makeStyles((theme) => ({
|
|||||||
borderColor: theme.palette.text.primary,
|
borderColor: theme.palette.text.primary,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
}),
|
||||||
|
|
||||||
promoteButton: {
|
promoteButton: (theme) => ({
|
||||||
color: theme.palette.text.secondary,
|
color: theme.palette.text.secondary,
|
||||||
transition: "none",
|
transition: "none",
|
||||||
},
|
}),
|
||||||
|
|
||||||
versionWrapper: {
|
versionWrapper: (theme) => ({
|
||||||
padding: theme.spacing(2, 4),
|
padding: theme.spacing(2, 4),
|
||||||
},
|
}),
|
||||||
|
|
||||||
active: {
|
active: (theme) => ({
|
||||||
backgroundColor: theme.palette.background.paperLight,
|
backgroundColor: theme.palette.background.paperLight,
|
||||||
},
|
}),
|
||||||
|
|
||||||
versionCell: {
|
versionCell: {
|
||||||
padding: "0 !important",
|
padding: "0 !important",
|
||||||
@ -158,13 +153,13 @@ const useStyles = makeStyles((theme) => ({
|
|||||||
borderBottom: 0,
|
borderBottom: 0,
|
||||||
},
|
},
|
||||||
|
|
||||||
versionSummary: {
|
versionSummary: (theme) => ({
|
||||||
...theme.typography.body1,
|
...(theme.typography.body1 as CSSObject),
|
||||||
fontFamily: "inherit",
|
fontFamily: "inherit",
|
||||||
},
|
}),
|
||||||
|
|
||||||
versionTime: {
|
versionTime: (theme) => ({
|
||||||
color: theme.palette.text.secondary,
|
color: theme.palette.text.secondary,
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
},
|
}),
|
||||||
}));
|
} satisfies Record<string, Interpolation<Theme>>;
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
|
import { type Interpolation, type Theme } from "@emotion/react";
|
||||||
import TextField from "@mui/material/TextField";
|
import TextField from "@mui/material/TextField";
|
||||||
import { Template, UpdateTemplateMeta } from "api/typesGenerated";
|
import type { Template, UpdateTemplateMeta } from "api/typesGenerated";
|
||||||
import { FormikContextType, FormikTouched, useFormik } from "formik";
|
import { type FormikContextType, type FormikTouched, useFormik } from "formik";
|
||||||
import { FC } from "react";
|
import { type FC } from "react";
|
||||||
import {
|
import {
|
||||||
getFormHelpers,
|
getFormHelpers,
|
||||||
nameValidator,
|
nameValidator,
|
||||||
@ -23,7 +24,6 @@ import {
|
|||||||
HelpTooltip,
|
HelpTooltip,
|
||||||
HelpTooltipText,
|
HelpTooltipText,
|
||||||
} from "components/HelpTooltip/HelpTooltip";
|
} from "components/HelpTooltip/HelpTooltip";
|
||||||
import { makeStyles } from "@mui/styles";
|
|
||||||
|
|
||||||
const MAX_DESCRIPTION_CHAR_LIMIT = 128;
|
const MAX_DESCRIPTION_CHAR_LIMIT = 128;
|
||||||
|
|
||||||
@ -79,7 +79,6 @@ export const TemplateSettingsForm: FC<TemplateSettingsForm> = ({
|
|||||||
initialTouched,
|
initialTouched,
|
||||||
});
|
});
|
||||||
const getFieldHelpers = getFormHelpers(form, error);
|
const getFieldHelpers = getFormHelpers(form, error);
|
||||||
const styles = useStyles();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HorizontalForm
|
<HorizontalForm
|
||||||
@ -154,7 +153,7 @@ export const TemplateSettingsForm: FC<TemplateSettingsForm> = ({
|
|||||||
direction="row"
|
direction="row"
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
spacing={0.5}
|
spacing={0.5}
|
||||||
className={styles.optionText}
|
css={styles.optionText}
|
||||||
>
|
>
|
||||||
Allow users to cancel in-progress workspace jobs.
|
Allow users to cancel in-progress workspace jobs.
|
||||||
<HelpTooltip>
|
<HelpTooltip>
|
||||||
@ -163,7 +162,7 @@ export const TemplateSettingsForm: FC<TemplateSettingsForm> = ({
|
|||||||
</HelpTooltipText>
|
</HelpTooltipText>
|
||||||
</HelpTooltip>
|
</HelpTooltip>
|
||||||
</Stack>
|
</Stack>
|
||||||
<span className={styles.optionHelperText}>
|
<span css={styles.optionHelperText}>
|
||||||
Depending on your template, canceling builds may leave
|
Depending on your template, canceling builds may leave
|
||||||
workspaces in an unhealthy state. This option isn't
|
workspaces in an unhealthy state. This option isn't
|
||||||
recommended for most use cases.
|
recommended for most use cases.
|
||||||
@ -186,7 +185,7 @@ export const TemplateSettingsForm: FC<TemplateSettingsForm> = ({
|
|||||||
direction="row"
|
direction="row"
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
spacing={0.5}
|
spacing={0.5}
|
||||||
className={styles.optionText}
|
css={styles.optionText}
|
||||||
>
|
>
|
||||||
Require the active template version for workspace builds.
|
Require the active template version for workspace builds.
|
||||||
<HelpTooltip>
|
<HelpTooltip>
|
||||||
@ -195,7 +194,7 @@ export const TemplateSettingsForm: FC<TemplateSettingsForm> = ({
|
|||||||
</HelpTooltipText>
|
</HelpTooltipText>
|
||||||
</HelpTooltip>
|
</HelpTooltip>
|
||||||
</Stack>
|
</Stack>
|
||||||
<span className={styles.optionHelperText}>
|
<span css={styles.optionHelperText}>
|
||||||
Workspaces that are manually started or auto-started will
|
Workspaces that are manually started or auto-started will
|
||||||
use the promoted template version.
|
use the promoted template version.
|
||||||
</span>
|
</span>
|
||||||
@ -211,14 +210,14 @@ export const TemplateSettingsForm: FC<TemplateSettingsForm> = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const styles = {
|
||||||
optionText: {
|
optionText: (theme) => ({
|
||||||
fontSize: theme.spacing(2),
|
fontSize: theme.spacing(2),
|
||||||
color: theme.palette.text.primary,
|
color: theme.palette.text.primary,
|
||||||
},
|
}),
|
||||||
|
|
||||||
optionHelperText: {
|
optionHelperText: (theme) => ({
|
||||||
fontSize: theme.spacing(1.5),
|
fontSize: theme.spacing(1.5),
|
||||||
color: theme.palette.text.secondary,
|
color: theme.palette.text.secondary,
|
||||||
},
|
}),
|
||||||
}));
|
} satisfies Record<string, Interpolation<Theme>>;
|
||||||
|
@ -1,18 +1,17 @@
|
|||||||
import { makeStyles } from "@mui/styles";
|
import { createContext, type FC, Suspense, useContext } from "react";
|
||||||
import { Sidebar } from "./Sidebar";
|
|
||||||
import { Stack } from "components/Stack/Stack";
|
|
||||||
import { createContext, FC, Suspense, useContext } from "react";
|
|
||||||
import { Helmet } from "react-helmet-async";
|
import { Helmet } from "react-helmet-async";
|
||||||
|
import { useQuery } from "react-query";
|
||||||
import { pageTitle } from "utils/page";
|
import { pageTitle } from "utils/page";
|
||||||
|
import { Stack } from "components/Stack/Stack";
|
||||||
import { Loader } from "components/Loader/Loader";
|
import { Loader } from "components/Loader/Loader";
|
||||||
import { Outlet, useParams } from "react-router-dom";
|
import { Outlet, useParams } from "react-router-dom";
|
||||||
import { Margins } from "components/Margins/Margins";
|
import { Margins } from "components/Margins/Margins";
|
||||||
import { useQuery } from "react-query";
|
|
||||||
import { useOrganizationId } from "hooks/useOrganizationId";
|
import { useOrganizationId } from "hooks/useOrganizationId";
|
||||||
import { templateByName } from "api/queries/templates";
|
import { templateByName } from "api/queries/templates";
|
||||||
import { type AuthorizationResponse, type Template } from "api/typesGenerated";
|
import type { AuthorizationResponse, Template } from "api/typesGenerated";
|
||||||
import { ErrorAlert } from "components/Alert/ErrorAlert";
|
import { ErrorAlert } from "components/Alert/ErrorAlert";
|
||||||
import { checkAuthorization } from "api/queries/authCheck";
|
import { checkAuthorization } from "api/queries/authCheck";
|
||||||
|
import { Sidebar } from "./Sidebar";
|
||||||
|
|
||||||
const TemplateSettings = createContext<
|
const TemplateSettings = createContext<
|
||||||
{ template: Template; permissions: AuthorizationResponse } | undefined
|
{ template: Template; permissions: AuthorizationResponse } | undefined
|
||||||
@ -28,7 +27,6 @@ export function useTemplateSettings() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const TemplateSettingsLayout: FC = () => {
|
export const TemplateSettingsLayout: FC = () => {
|
||||||
const styles = useStyles();
|
|
||||||
const orgId = useOrganizationId();
|
const orgId = useOrganizationId();
|
||||||
const { template: templateName } = useParams() as { template: string };
|
const { template: templateName } = useParams() as { template: string };
|
||||||
const templateQuery = useQuery(templateByName(orgId, templateName));
|
const templateQuery = useQuery(templateByName(orgId, templateName));
|
||||||
@ -58,7 +56,13 @@ export const TemplateSettingsLayout: FC = () => {
|
|||||||
</Helmet>
|
</Helmet>
|
||||||
|
|
||||||
<Margins>
|
<Margins>
|
||||||
<Stack className={styles.wrapper} direction="row" spacing={10}>
|
<Stack
|
||||||
|
css={(theme) => ({
|
||||||
|
padding: theme.spacing(6, 0),
|
||||||
|
})}
|
||||||
|
direction="row"
|
||||||
|
spacing={10}
|
||||||
|
>
|
||||||
{templateQuery.isError || permissionsQuery.isError ? (
|
{templateQuery.isError || permissionsQuery.isError ? (
|
||||||
<ErrorAlert error={templateQuery.error} />
|
<ErrorAlert error={templateQuery.error} />
|
||||||
) : (
|
) : (
|
||||||
@ -70,7 +74,11 @@ export const TemplateSettingsLayout: FC = () => {
|
|||||||
>
|
>
|
||||||
<Sidebar template={templateQuery.data} />
|
<Sidebar template={templateQuery.data} />
|
||||||
<Suspense fallback={<Loader />}>
|
<Suspense fallback={<Loader />}>
|
||||||
<main className={styles.content}>
|
<main
|
||||||
|
css={{
|
||||||
|
width: "100%",
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</main>
|
</main>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
@ -81,13 +89,3 @@ export const TemplateSettingsLayout: FC = () => {
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
|
||||||
wrapper: {
|
|
||||||
padding: theme.spacing(6, 0),
|
|
||||||
},
|
|
||||||
|
|
||||||
content: {
|
|
||||||
width: "100%",
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import { makeStyles } from "@mui/styles";
|
|
||||||
import ChevronRightIcon from "@mui/icons-material/ChevronRight";
|
import ChevronRightIcon from "@mui/icons-material/ChevronRight";
|
||||||
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
|
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
|
||||||
import TreeView from "@mui/lab/TreeView";
|
import TreeView from "@mui/lab/TreeView";
|
||||||
import TreeItem from "@mui/lab/TreeItem";
|
import TreeItem from "@mui/lab/TreeItem";
|
||||||
import Menu from "@mui/material/Menu";
|
import Menu from "@mui/material/Menu";
|
||||||
import MenuItem from "@mui/material/MenuItem";
|
import MenuItem from "@mui/material/MenuItem";
|
||||||
import { CSSProperties, FC, useState } from "react";
|
import { type CSSProperties, type FC, useState } from "react";
|
||||||
|
import { css } from "@emotion/react";
|
||||||
import { FileTree } from "utils/filetree";
|
import { FileTree } from "utils/filetree";
|
||||||
import { DockerIcon } from "components/Icons/DockerIcon";
|
import { DockerIcon } from "components/Icons/DockerIcon";
|
||||||
import { colors } from "theme/colors";
|
import { colors } from "theme/colors";
|
||||||
@ -35,7 +35,6 @@ export const FileTreeView: FC<{
|
|||||||
fileTree: FileTree;
|
fileTree: FileTree;
|
||||||
activePath?: string;
|
activePath?: string;
|
||||||
}> = ({ fileTree, activePath, onDelete, onRename, onSelect }) => {
|
}> = ({ fileTree, activePath, onDelete, onRename, onSelect }) => {
|
||||||
const styles = useStyles();
|
|
||||||
const [contextMenu, setContextMenu] = useState<ContextMenu | undefined>();
|
const [contextMenu, setContextMenu] = useState<ContextMenu | undefined>();
|
||||||
|
|
||||||
const buildTreeItems = (
|
const buildTreeItems = (
|
||||||
@ -60,9 +59,54 @@ export const FileTreeView: FC<{
|
|||||||
nodeId={currentPath}
|
nodeId={currentPath}
|
||||||
key={currentPath}
|
key={currentPath}
|
||||||
label={filename}
|
label={filename}
|
||||||
className={`${styles.fileTreeItem} ${
|
css={(theme) => css`
|
||||||
currentPath === activePath ? "active" : ""
|
overflow: hidden;
|
||||||
}`}
|
user-select: none;
|
||||||
|
|
||||||
|
&:focus:not(.active) > .MuiTreeItem-content {
|
||||||
|
background: ${theme.palette.action.hover};
|
||||||
|
color: ${theme.palette.text.primary};
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(:focus):not(.active) > .MuiTreeItem-content:hover {
|
||||||
|
background: ${theme.palette.action.hover};
|
||||||
|
color: ${theme.palette.text.primary};
|
||||||
|
}
|
||||||
|
|
||||||
|
& > .MuiTreeItem-content {
|
||||||
|
padding: ${theme.spacing(0.25, 2)};
|
||||||
|
color: ${theme.palette.text.secondary};
|
||||||
|
|
||||||
|
& svg {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > .MuiTreeItem-label {
|
||||||
|
margin-left: 4px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
& > .MuiTreeItem-content {
|
||||||
|
color: ${theme.palette.text.primary};
|
||||||
|
background: ${colors.gray[13]};
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& .MuiTreeItem-group {
|
||||||
|
margin-left: 0;
|
||||||
|
|
||||||
|
// We need to find a better way to recursive padding here
|
||||||
|
& .MuiTreeItem-content {
|
||||||
|
padding-left: calc(var(--level) * ${theme.spacing(5)});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
className={currentPath === activePath ? "active" : ""}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onSelect(currentPath);
|
onSelect(currentPath);
|
||||||
}}
|
}}
|
||||||
@ -161,60 +205,6 @@ export const FileTreeView: FC<{
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
|
||||||
fileTreeItem: {
|
|
||||||
overflow: "hidden",
|
|
||||||
userSelect: "none",
|
|
||||||
|
|
||||||
"&:focus:not(.active) > .MuiTreeItem-content": {
|
|
||||||
background: theme.palette.action.hover,
|
|
||||||
color: theme.palette.text.primary,
|
|
||||||
},
|
|
||||||
|
|
||||||
"&:not(:focus):not(.active) > .MuiTreeItem-content:hover": {
|
|
||||||
background: theme.palette.action.hover,
|
|
||||||
color: theme.palette.text.primary,
|
|
||||||
},
|
|
||||||
|
|
||||||
"& > .MuiTreeItem-content": {
|
|
||||||
padding: theme.spacing(0.25, 2),
|
|
||||||
color: theme.palette.text.secondary,
|
|
||||||
|
|
||||||
"& svg": {
|
|
||||||
width: 16,
|
|
||||||
height: 16,
|
|
||||||
},
|
|
||||||
|
|
||||||
"& > .MuiTreeItem-label": {
|
|
||||||
marginLeft: 4,
|
|
||||||
fontSize: 13,
|
|
||||||
color: "inherit",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
"&.active": {
|
|
||||||
"& > .MuiTreeItem-content": {
|
|
||||||
color: theme.palette.text.primary,
|
|
||||||
background: colors.gray[13],
|
|
||||||
pointerEvents: "none",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
"& .MuiTreeItem-group": {
|
|
||||||
marginLeft: 0,
|
|
||||||
|
|
||||||
// We need to find a better way to recursive padding here
|
|
||||||
"& .MuiTreeItem-content": {
|
|
||||||
paddingLeft: `calc(var(--level) * ${theme.spacing(5)})`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
editor: {
|
|
||||||
flex: 1,
|
|
||||||
},
|
|
||||||
preview: {},
|
|
||||||
}));
|
|
||||||
|
|
||||||
const FileTypeTerraform = () => (
|
const FileTypeTerraform = () => (
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" fill="#813cf3">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" fill="#813cf3">
|
||||||
<title>file_type_terraform</title>
|
<title>file_type_terraform</title>
|
||||||
|
@ -1,12 +1,16 @@
|
|||||||
import { makeStyles } from "@mui/styles";
|
import { css } from "@emotion/css";
|
||||||
|
import { useTheme, type Interpolation, type Theme } from "@emotion/react";
|
||||||
import Dialog from "@mui/material/Dialog";
|
import Dialog from "@mui/material/Dialog";
|
||||||
import DialogContent from "@mui/material/DialogContent";
|
import DialogContent from "@mui/material/DialogContent";
|
||||||
import DialogContentText from "@mui/material/DialogContentText";
|
import DialogContentText from "@mui/material/DialogContentText";
|
||||||
import DialogTitle from "@mui/material/DialogTitle";
|
import DialogTitle from "@mui/material/DialogTitle";
|
||||||
import { DialogProps } from "components/Dialogs/Dialog";
|
import { type DialogProps } from "components/Dialogs/Dialog";
|
||||||
import { FC, useEffect, useState } from "react";
|
import { type FC, useEffect, useState } from "react";
|
||||||
import { FormFields, VerticalForm } from "components/Form/Form";
|
import { FormFields, VerticalForm } from "components/Form/Form";
|
||||||
import { TemplateVersionVariable, VariableValue } from "api/typesGenerated";
|
import type {
|
||||||
|
TemplateVersionVariable,
|
||||||
|
VariableValue,
|
||||||
|
} from "api/typesGenerated";
|
||||||
import DialogActions from "@mui/material/DialogActions";
|
import DialogActions from "@mui/material/DialogActions";
|
||||||
import Button from "@mui/material/Button";
|
import Button from "@mui/material/Button";
|
||||||
import { VariableInput } from "pages/CreateTemplatePage/VariableInput";
|
import { VariableInput } from "pages/CreateTemplatePage/VariableInput";
|
||||||
@ -24,7 +28,7 @@ export type MissingTemplateVariablesDialogProps = Omit<
|
|||||||
export const MissingTemplateVariablesDialog: FC<
|
export const MissingTemplateVariablesDialog: FC<
|
||||||
MissingTemplateVariablesDialogProps
|
MissingTemplateVariablesDialogProps
|
||||||
> = ({ missingVariables, onSubmit, ...dialogProps }) => {
|
> = ({ missingVariables, onSubmit, ...dialogProps }) => {
|
||||||
const styles = useStyles();
|
const theme = useTheme();
|
||||||
const [variableValues, setVariableValues] = useState<VariableValue[]>([]);
|
const [variableValues, setVariableValues] = useState<VariableValue[]>([]);
|
||||||
|
|
||||||
// Pre-fill the form with the default values when missing variables are loaded
|
// Pre-fill the form with the default values when missing variables are loaded
|
||||||
@ -47,16 +51,25 @@ export const MissingTemplateVariablesDialog: FC<
|
|||||||
>
|
>
|
||||||
<DialogTitle
|
<DialogTitle
|
||||||
id="update-build-parameters-title"
|
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;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
Template variables
|
Template variables
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogContent className={styles.content}>
|
<DialogContent css={styles.content}>
|
||||||
<DialogContentText className={styles.info}>
|
<DialogContentText css={styles.info}>
|
||||||
There are a few missing template variable values. Please fill them in.
|
There are a few missing template variable values. Please fill them in.
|
||||||
</DialogContentText>
|
</DialogContentText>
|
||||||
<VerticalForm
|
<VerticalForm
|
||||||
className={styles.form}
|
css={styles.form}
|
||||||
id="updateVariables"
|
id="updateVariables"
|
||||||
onSubmit={(e) => {
|
onSubmit={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@ -89,7 +102,7 @@ export const MissingTemplateVariablesDialog: FC<
|
|||||||
)}
|
)}
|
||||||
</VerticalForm>
|
</VerticalForm>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions disableSpacing className={styles.dialogActions}>
|
<DialogActions disableSpacing css={styles.dialogActions}>
|
||||||
<Button color="primary" fullWidth type="submit" form="updateVariables">
|
<Button color="primary" fullWidth type="submit" form="updateVariables">
|
||||||
Submit
|
Submit
|
||||||
</Button>
|
</Button>
|
||||||
@ -101,43 +114,22 @@ export const MissingTemplateVariablesDialog: FC<
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const styles = {
|
||||||
title: {
|
content: (theme) => ({
|
||||||
padding: theme.spacing(3, 5),
|
padding: theme.spacing(0, 5),
|
||||||
|
}),
|
||||||
"& h2": {
|
|
||||||
fontSize: theme.spacing(2.5),
|
|
||||||
fontWeight: 400,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
content: {
|
|
||||||
padding: theme.spacing(0, 5, 0, 5),
|
|
||||||
},
|
|
||||||
|
|
||||||
info: {
|
info: {
|
||||||
margin: 0,
|
margin: 0,
|
||||||
},
|
},
|
||||||
|
|
||||||
form: {
|
form: (theme) => ({
|
||||||
paddingTop: theme.spacing(4),
|
paddingTop: theme.spacing(4),
|
||||||
},
|
}),
|
||||||
|
|
||||||
infoTitle: {
|
dialogActions: (theme) => ({
|
||||||
fontSize: theme.spacing(2),
|
|
||||||
fontWeight: 600,
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
gap: theme.spacing(1),
|
|
||||||
},
|
|
||||||
|
|
||||||
formFooter: {
|
|
||||||
flexDirection: "column",
|
|
||||||
},
|
|
||||||
|
|
||||||
dialogActions: {
|
|
||||||
padding: theme.spacing(5),
|
padding: theme.spacing(5),
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
gap: theme.spacing(1),
|
gap: theme.spacing(1),
|
||||||
},
|
}),
|
||||||
}));
|
} satisfies Record<string, Interpolation<Theme>>;
|
||||||
|
@ -5,7 +5,6 @@ import FormGroup from "@mui/material/FormGroup";
|
|||||||
import FormHelperText from "@mui/material/FormHelperText";
|
import FormHelperText from "@mui/material/FormHelperText";
|
||||||
import FormLabel from "@mui/material/FormLabel";
|
import FormLabel from "@mui/material/FormLabel";
|
||||||
import MenuItem from "@mui/material/MenuItem";
|
import MenuItem from "@mui/material/MenuItem";
|
||||||
import makeStyles from "@mui/styles/makeStyles";
|
|
||||||
import Switch from "@mui/material/Switch";
|
import Switch from "@mui/material/Switch";
|
||||||
import TextField from "@mui/material/TextField";
|
import TextField from "@mui/material/TextField";
|
||||||
import {
|
import {
|
||||||
@ -200,8 +199,6 @@ export const WorkspaceScheduleForm: FC<
|
|||||||
enableAutoStop,
|
enableAutoStop,
|
||||||
enableAutoStart,
|
enableAutoStart,
|
||||||
}) => {
|
}) => {
|
||||||
const styles = useStyles();
|
|
||||||
|
|
||||||
const form = useFormik<WorkspaceScheduleFormValues>({
|
const form = useFormik<WorkspaceScheduleFormValues>({
|
||||||
initialValues,
|
initialValues,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
@ -340,11 +337,23 @@ export const WorkspaceScheduleForm: FC<
|
|||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
<FormControl component="fieldset" error={Boolean(form.errors.monday)}>
|
<FormControl component="fieldset" error={Boolean(form.errors.monday)}>
|
||||||
<FormLabel className={styles.daysOfWeekLabel} component="legend">
|
<FormLabel
|
||||||
|
css={{
|
||||||
|
fontSize: 12,
|
||||||
|
}}
|
||||||
|
component="legend"
|
||||||
|
>
|
||||||
{Language.daysOfWeekLabel}
|
{Language.daysOfWeekLabel}
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
|
|
||||||
<FormGroup className={styles.daysOfWeekOptions}>
|
<FormGroup
|
||||||
|
css={(theme) => ({
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "row",
|
||||||
|
flexWrap: "wrap",
|
||||||
|
paddingTop: theme.spacing(0.5),
|
||||||
|
})}
|
||||||
|
>
|
||||||
{checkboxes.map((checkbox) => (
|
{checkboxes.map((checkbox) => (
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
control={
|
control={
|
||||||
@ -422,15 +431,3 @@ export const ttlShutdownAt = (formTTL: number): string => {
|
|||||||
.humanize()} ${Language.ttlCausesShutdownAfterStart}.`;
|
.humanize()} ${Language.ttlCausesShutdownAfterStart}.`;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
|
||||||
daysOfWeekLabel: {
|
|
||||||
fontSize: 12,
|
|
||||||
},
|
|
||||||
daysOfWeekOptions: {
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "row",
|
|
||||||
flexWrap: "wrap",
|
|
||||||
paddingTop: theme.spacing(0.5),
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
Reference in New Issue
Block a user