mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
refactor: Refactor build page (#4815)
* refactor: Improve build page * Add build translation * Add build avatar better props * Get build number from build
This commit is contained in:
@@ -25,20 +25,26 @@ const StyledBadge = withStyles((theme) => ({
|
||||
},
|
||||
}))(Badge)
|
||||
|
||||
interface StyledAvatarProps {
|
||||
size?: number
|
||||
}
|
||||
|
||||
const StyledAvatar = withStyles((theme) => ({
|
||||
root: {
|
||||
background: theme.palette.divider,
|
||||
color: theme.palette.text.primary,
|
||||
border: `2px solid ${theme.palette.divider}`,
|
||||
width: ({ size }: StyledAvatarProps) => size,
|
||||
height: ({ size }: StyledAvatarProps) => size,
|
||||
|
||||
"& svg": {
|
||||
width: 18,
|
||||
height: 18,
|
||||
width: ({ size }: StyledAvatarProps) => (size ? size / 2 : 18),
|
||||
height: ({ size }: StyledAvatarProps) => (size ? size / 2 : 18),
|
||||
},
|
||||
},
|
||||
}))(Avatar)
|
||||
|
||||
export type BuildAvatarProps = {
|
||||
export interface BuildAvatarProps extends StyledAvatarProps {
|
||||
build: WorkspaceBuild
|
||||
}
|
||||
|
||||
@@ -48,7 +54,7 @@ const iconByTransition: Record<WorkspaceTransition, JSX.Element> = {
|
||||
delete: <DeleteOutlined />,
|
||||
}
|
||||
|
||||
export const BuildAvatar: FC<BuildAvatarProps> = ({ build }) => {
|
||||
export const BuildAvatar: FC<BuildAvatarProps> = ({ build, size }) => {
|
||||
const theme = useTheme<Theme>()
|
||||
const displayBuildStatus = getDisplayWorkspaceBuildStatus(theme, build)
|
||||
|
||||
@@ -65,7 +71,9 @@ export const BuildAvatar: FC<BuildAvatarProps> = ({ build }) => {
|
||||
}}
|
||||
badgeContent={<div></div>}
|
||||
>
|
||||
<StyledAvatar>{iconByTransition[build.transition]}</StyledAvatar>
|
||||
<StyledAvatar size={size}>
|
||||
{iconByTransition[build.transition]}
|
||||
</StyledAvatar>
|
||||
</StyledBadge>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -57,5 +57,6 @@ const useStyles = makeStyles((theme) => ({
|
||||
userSelect: "none",
|
||||
width: theme.spacing(12.5),
|
||||
display: "inline-block",
|
||||
color: theme.palette.text.secondary,
|
||||
},
|
||||
}))
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
import { makeStyles } from "@material-ui/core/styles"
|
||||
import { ComponentProps, FC, PropsWithChildren } from "react"
|
||||
|
||||
export const Stats: FC<PropsWithChildren<ComponentProps<"div">>> = (props) => {
|
||||
const styles = useStyles()
|
||||
return <div className={styles.stats} {...props} />
|
||||
}
|
||||
|
||||
export const StatsItem: FC<{
|
||||
label: string
|
||||
value: string | number | JSX.Element
|
||||
}> = ({ label, value }) => {
|
||||
const styles = useStyles()
|
||||
|
||||
return (
|
||||
<div className={styles.statItem}>
|
||||
<span className={styles.statsLabel}>{label}:</span>
|
||||
<span className={styles.statsValue}>{value}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
stats: {
|
||||
paddingLeft: theme.spacing(2),
|
||||
paddingRight: theme.spacing(2),
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
border: `1px solid ${theme.palette.divider}`,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
color: theme.palette.text.secondary,
|
||||
margin: "0px",
|
||||
[theme.breakpoints.down("sm")]: {
|
||||
display: "block",
|
||||
},
|
||||
},
|
||||
|
||||
statItem: {
|
||||
padding: theme.spacing(2),
|
||||
paddingTop: theme.spacing(1.75),
|
||||
display: "flex",
|
||||
alignItems: "baseline",
|
||||
gap: theme.spacing(1),
|
||||
},
|
||||
|
||||
statsLabel: {
|
||||
display: "block",
|
||||
wordWrap: "break-word",
|
||||
},
|
||||
|
||||
statsValue: {
|
||||
marginTop: theme.spacing(0.25),
|
||||
display: "block",
|
||||
wordWrap: "break-word",
|
||||
color: theme.palette.text.primary,
|
||||
|
||||
"& a": {
|
||||
color: theme.palette.text.primary,
|
||||
textDecoration: "none",
|
||||
fontWeight: 600,
|
||||
|
||||
"&:hover": {
|
||||
textDecoration: "underline",
|
||||
},
|
||||
},
|
||||
},
|
||||
}))
|
||||
@@ -1,6 +1,6 @@
|
||||
import { makeStyles } from "@material-ui/core/styles"
|
||||
import dayjs from "dayjs"
|
||||
import { FC } from "react"
|
||||
import { FC, Fragment } from "react"
|
||||
import { ProvisionerJobLog } from "../../api/typesGenerated"
|
||||
import { MONOSPACE_FONT_FAMILY } from "../../theme/constants"
|
||||
import { Logs } from "../Logs/Logs"
|
||||
@@ -59,7 +59,7 @@ export const WorkspaceBuildLogs: FC<WorkspaceBuildLogsProps> = ({ logs }) => {
|
||||
const shouldDisplayDuration = duration !== undefined
|
||||
|
||||
return (
|
||||
<div key={stage}>
|
||||
<Fragment key={stage}>
|
||||
<div className={styles.header}>
|
||||
<div>{stage}</div>
|
||||
{shouldDisplayDuration && (
|
||||
@@ -69,7 +69,7 @@ export const WorkspaceBuildLogs: FC<WorkspaceBuildLogsProps> = ({ logs }) => {
|
||||
)}
|
||||
</div>
|
||||
{!isEmpty && <Logs lines={lines} className={styles.codeBlock} />}
|
||||
</div>
|
||||
</Fragment>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
@@ -84,7 +84,7 @@ const useStyles = makeStyles((theme) => ({
|
||||
},
|
||||
|
||||
header: {
|
||||
fontSize: theme.typography.body1.fontSize,
|
||||
fontSize: 14,
|
||||
padding: theme.spacing(2),
|
||||
paddingLeft: theme.spacing(4),
|
||||
paddingRight: theme.spacing(4),
|
||||
@@ -92,6 +92,19 @@ const useStyles = makeStyles((theme) => ({
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
fontFamily: "Inter",
|
||||
|
||||
"&:first-child": {
|
||||
borderTopLeftRadius: theme.shape.borderRadius,
|
||||
borderTopRightRadius: theme.shape.borderRadius,
|
||||
},
|
||||
|
||||
"&:last-child": {
|
||||
borderBottom: 0,
|
||||
borderTop: `1px solid ${theme.palette.divider}`,
|
||||
borderBottomLeftRadius: theme.shape.borderRadius,
|
||||
borderBottomRightRadius: theme.shape.borderRadius,
|
||||
},
|
||||
},
|
||||
|
||||
duration: {
|
||||
|
||||
@@ -1,15 +1,9 @@
|
||||
import Link from "@material-ui/core/Link"
|
||||
import { makeStyles, useTheme } from "@material-ui/core/styles"
|
||||
import { Stats, StatsItem } from "components/Stats/Stats"
|
||||
import { FC } from "react"
|
||||
import { Link as RouterLink } from "react-router-dom"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { Link } from "react-router-dom"
|
||||
import { WorkspaceBuild } from "../../api/typesGenerated"
|
||||
import { MONOSPACE_FONT_FAMILY } from "../../theme/constants"
|
||||
import { combineClasses } from "../../util/combineClasses"
|
||||
import {
|
||||
displayWorkspaceBuildDuration,
|
||||
getDisplayWorkspaceBuildInitiatedBy,
|
||||
getDisplayWorkspaceBuildStatus,
|
||||
} from "../../util/workspace"
|
||||
import { displayWorkspaceBuildDuration } from "../../util/workspace"
|
||||
|
||||
export interface WorkspaceBuildStatsProps {
|
||||
build: WorkspaceBuild
|
||||
@@ -18,116 +12,27 @@ export interface WorkspaceBuildStatsProps {
|
||||
export const WorkspaceBuildStats: FC<WorkspaceBuildStatsProps> = ({
|
||||
build,
|
||||
}) => {
|
||||
const styles = useStyles()
|
||||
const theme = useTheme()
|
||||
const status = getDisplayWorkspaceBuildStatus(theme, build)
|
||||
const initiatedBy = getDisplayWorkspaceBuildInitiatedBy(build)
|
||||
const { t } = useTranslation("buildPage")
|
||||
|
||||
return (
|
||||
<div className={styles.stats}>
|
||||
<div className={styles.statItem}>
|
||||
<span className={styles.statsLabel}>Workspace Name</span>
|
||||
<Link
|
||||
component={RouterLink}
|
||||
to={`/@${build.workspace_owner_name}/${build.workspace_name}`}
|
||||
className={combineClasses([styles.statsValue, styles.link])}
|
||||
>
|
||||
{build.workspace_name}
|
||||
</Link>
|
||||
</div>
|
||||
<div className={styles.statsDivider} />
|
||||
|
||||
<div className={styles.statItem}>
|
||||
<span className={styles.statsLabel}>Duration</span>
|
||||
<span className={styles.statsValue}>
|
||||
{displayWorkspaceBuildDuration(build)}
|
||||
</span>
|
||||
</div>
|
||||
<div className={styles.statsDivider} />
|
||||
<div className={styles.statItem}>
|
||||
<span className={styles.statsLabel}>Started at</span>
|
||||
<span className={styles.statsValue}>
|
||||
{new Date(build.created_at).toLocaleString()}
|
||||
</span>
|
||||
</div>
|
||||
<div className={styles.statsDivider} />
|
||||
<div className={styles.statItem}>
|
||||
<span className={styles.statsLabel}>Status</span>
|
||||
<span className={styles.statsValue}>
|
||||
<span style={{ color: status.color }}>{status.status}</span>
|
||||
</span>
|
||||
</div>
|
||||
<div className={styles.statsDivider} />
|
||||
<div className={styles.statItem}>
|
||||
<span className={styles.statsLabel}>Action</span>
|
||||
<span
|
||||
className={combineClasses([styles.statsValue, styles.capitalize])}
|
||||
>
|
||||
{build.transition}
|
||||
</span>
|
||||
</div>
|
||||
<div className={styles.statsDivider} />
|
||||
<div className={styles.statItem}>
|
||||
<span className={styles.statsLabel}>Initiated by</span>
|
||||
<span className={styles.statsValue}>{initiatedBy}</span>
|
||||
</div>
|
||||
</div>
|
||||
<Stats>
|
||||
<StatsItem
|
||||
label={t("stats.workspace")}
|
||||
value={
|
||||
<Link to={`/@${build.workspace_owner_name}/${build.workspace_name}`}>
|
||||
{build.workspace_name}
|
||||
</Link>
|
||||
}
|
||||
/>
|
||||
<StatsItem
|
||||
label={t("stats.duration")}
|
||||
value={displayWorkspaceBuildDuration(build)}
|
||||
/>
|
||||
<StatsItem
|
||||
label={t("stats.startedAt")}
|
||||
value={new Date(build.created_at).toLocaleString()}
|
||||
/>
|
||||
<StatsItem label={t("stats.action")} value={build.transition} />
|
||||
</Stats>
|
||||
)
|
||||
}
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
stats: {
|
||||
paddingLeft: theme.spacing(2),
|
||||
paddingRight: theme.spacing(2),
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
color: theme.palette.text.secondary,
|
||||
fontFamily: MONOSPACE_FONT_FAMILY,
|
||||
border: `1px solid ${theme.palette.divider}`,
|
||||
[theme.breakpoints.down("sm")]: {
|
||||
display: "block",
|
||||
},
|
||||
},
|
||||
|
||||
statItem: {
|
||||
minWidth: "13%",
|
||||
padding: theme.spacing(2),
|
||||
paddingTop: theme.spacing(1.75),
|
||||
},
|
||||
|
||||
statsLabel: {
|
||||
fontSize: 12,
|
||||
textTransform: "uppercase",
|
||||
display: "block",
|
||||
fontWeight: 600,
|
||||
wordWrap: "break-word",
|
||||
},
|
||||
|
||||
statsValue: {
|
||||
fontSize: 16,
|
||||
marginTop: theme.spacing(0.25),
|
||||
display: "block",
|
||||
wordWrap: "break-word",
|
||||
},
|
||||
|
||||
statsDivider: {
|
||||
width: 1,
|
||||
height: theme.spacing(5),
|
||||
backgroundColor: theme.palette.divider,
|
||||
marginRight: theme.spacing(2),
|
||||
[theme.breakpoints.down("sm")]: {
|
||||
display: "none",
|
||||
},
|
||||
},
|
||||
|
||||
capitalize: {
|
||||
textTransform: "capitalize",
|
||||
},
|
||||
|
||||
link: {
|
||||
color: theme.palette.text.primary,
|
||||
fontWeight: 600,
|
||||
},
|
||||
}))
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"stats": {
|
||||
"workspace": "Workspace",
|
||||
"duration": "Duration",
|
||||
"startedAt": "Started at",
|
||||
"action": "Action"
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import templatePage from "./templatePage.json"
|
||||
import templatesPage from "./templatesPage.json"
|
||||
import workspacePage from "./workspacePage.json"
|
||||
import agent from "./agent.json"
|
||||
import buildPage from "./buildPage.json"
|
||||
|
||||
export const en = {
|
||||
common,
|
||||
@@ -14,4 +15,5 @@ export const en = {
|
||||
templatesPage,
|
||||
createWorkspacePage,
|
||||
agent,
|
||||
buildPage,
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { BuildAvatar } from "components/BuildsTable/BuildAvatar"
|
||||
import { FC } from "react"
|
||||
import { ProvisionerJobLog, WorkspaceBuild } from "../../api/typesGenerated"
|
||||
import { Loader } from "../../components/Loader/Loader"
|
||||
import { Margins } from "../../components/Margins/Margins"
|
||||
import {
|
||||
PageHeader,
|
||||
PageHeaderSubtitle,
|
||||
PageHeaderTitle,
|
||||
} from "../../components/PageHeader/PageHeader"
|
||||
import { Stack } from "../../components/Stack/Stack"
|
||||
@@ -29,11 +31,21 @@ export const WorkspaceBuildPageView: FC<WorkspaceBuildPageViewProps> = ({
|
||||
}) => {
|
||||
return (
|
||||
<Margins>
|
||||
<PageHeader>
|
||||
<PageHeaderTitle>Logs</PageHeaderTitle>
|
||||
</PageHeader>
|
||||
{build && (
|
||||
<PageHeader>
|
||||
<Stack direction="row" alignItems="center" spacing={3}>
|
||||
<BuildAvatar build={build} size={48} />
|
||||
<div>
|
||||
<PageHeaderTitle>Build #{build.build_number}</PageHeaderTitle>
|
||||
<PageHeaderSubtitle condensed>
|
||||
{build.initiator_name}
|
||||
</PageHeaderSubtitle>
|
||||
</div>
|
||||
</Stack>
|
||||
</PageHeader>
|
||||
)}
|
||||
|
||||
<Stack>
|
||||
<Stack spacing={4}>
|
||||
{build &&
|
||||
build.transition === "delete" &&
|
||||
build.job.status === "failed" && (
|
||||
|
||||
Reference in New Issue
Block a user