mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
refactor: Move schedule info to the sidebar (#1665)
This commit is contained in:
@@ -8,6 +8,7 @@ import { BuildsTable } from "../BuildsTable/BuildsTable"
|
||||
import { Resources } from "../Resources/Resources"
|
||||
import { Stack } from "../Stack/Stack"
|
||||
import { WorkspaceActions } from "../WorkspaceActions/WorkspaceActions"
|
||||
import { WorkspaceSchedule } from "../WorkspaceSchedule/WorkspaceSchedule"
|
||||
import { WorkspaceSection } from "../WorkspaceSection/WorkspaceSection"
|
||||
import { WorkspaceStats } from "../WorkspaceStats/WorkspaceStats"
|
||||
|
||||
@@ -64,12 +65,18 @@ export const Workspace: React.FC<WorkspaceProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Stack spacing={3}>
|
||||
<WorkspaceStats workspace={workspace} />
|
||||
<Resources resources={resources} getResourcesError={getResourcesError} />
|
||||
<WorkspaceSection title="Timeline" contentsProps={{ className: styles.timelineContents }}>
|
||||
<BuildsTable builds={builds} className={styles.timelineTable} />
|
||||
</WorkspaceSection>
|
||||
<Stack direction="row" spacing={3} className={styles.layout}>
|
||||
<Stack spacing={3} className={styles.main}>
|
||||
<WorkspaceStats workspace={workspace} />
|
||||
<Resources resources={resources} getResourcesError={getResourcesError} />
|
||||
<WorkspaceSection title="Timeline" contentsProps={{ className: styles.timelineContents }}>
|
||||
<BuildsTable builds={builds} className={styles.timelineTable} />
|
||||
</WorkspaceSection>
|
||||
</Stack>
|
||||
|
||||
<Stack spacing={3} className={styles.sidebar}>
|
||||
<WorkspaceSchedule workspace={workspace} />
|
||||
</Stack>
|
||||
</Stack>
|
||||
</div>
|
||||
)
|
||||
@@ -99,6 +106,16 @@ export const useStyles = makeStyles((theme) => {
|
||||
fontFamily: "inherit",
|
||||
marginTop: theme.spacing(0.5),
|
||||
},
|
||||
layout: {
|
||||
alignItems: "flex-start",
|
||||
},
|
||||
main: {
|
||||
width: "100%",
|
||||
},
|
||||
sidebar: {
|
||||
width: theme.spacing(32),
|
||||
flexShrink: 0,
|
||||
},
|
||||
timelineContents: {
|
||||
margin: 0,
|
||||
},
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Story } from "@storybook/react"
|
||||
import React from "react"
|
||||
import { MockWorkspace } from "../../testHelpers/renderHelpers"
|
||||
import { WorkspaceSchedule, WorkspaceScheduleProps } from "./WorkspaceSchedule"
|
||||
|
||||
export default {
|
||||
title: "components/WorkspaceSchedule",
|
||||
component: WorkspaceSchedule,
|
||||
argTypes: {},
|
||||
}
|
||||
|
||||
const Template: Story<WorkspaceScheduleProps> = (args) => <WorkspaceSchedule {...args} />
|
||||
|
||||
export const Example = Template.bind({})
|
||||
Example.args = {
|
||||
workspace: MockWorkspace,
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
import Link from "@material-ui/core/Link"
|
||||
import { makeStyles } from "@material-ui/core/styles"
|
||||
import Typography from "@material-ui/core/Typography"
|
||||
import ScheduleIcon from "@material-ui/icons/Schedule"
|
||||
import cronstrue from "cronstrue"
|
||||
import dayjs from "dayjs"
|
||||
import duration from "dayjs/plugin/duration"
|
||||
import relativeTime from "dayjs/plugin/relativeTime"
|
||||
import React from "react"
|
||||
import { Workspace } from "../../api/typesGenerated"
|
||||
import { MONOSPACE_FONT_FAMILY } from "../../theme/constants"
|
||||
import { extractTimezone, stripTimezone } from "../../util/schedule"
|
||||
import { Stack } from "../Stack/Stack"
|
||||
|
||||
dayjs.extend(duration)
|
||||
dayjs.extend(relativeTime)
|
||||
|
||||
const autoStartLabel = (schedule: string): string => {
|
||||
const prefix = "Start"
|
||||
|
||||
if (schedule) {
|
||||
return `${prefix} (${extractTimezone(schedule)})`
|
||||
} else {
|
||||
return prefix
|
||||
}
|
||||
}
|
||||
|
||||
const autoStartDisplay = (schedule: string): string => {
|
||||
if (schedule) {
|
||||
return cronstrue.toString(stripTimezone(schedule), { throwExceptionOnParseError: false })
|
||||
}
|
||||
return "Manual"
|
||||
}
|
||||
|
||||
const autoStopDisplay = (workspace: Workspace): string => {
|
||||
const latest = workspace.latest_build
|
||||
|
||||
if (!workspace.ttl || workspace.ttl < 1) {
|
||||
return "Manual"
|
||||
}
|
||||
|
||||
if (latest.transition === "start") {
|
||||
const now = dayjs()
|
||||
const updatedAt = dayjs(latest.updated_at)
|
||||
const deadline = updatedAt.add(workspace.ttl / 1_000_000, "ms")
|
||||
if (now.isAfter(deadline)) {
|
||||
return "Workspace is shutting down now"
|
||||
}
|
||||
return now.to(deadline)
|
||||
}
|
||||
|
||||
const duration = dayjs.duration(workspace.ttl / 1_000_000, "milliseconds")
|
||||
return `${duration.humanize()} after start`
|
||||
}
|
||||
|
||||
export interface WorkspaceScheduleProps {
|
||||
workspace: Workspace
|
||||
}
|
||||
|
||||
export const WorkspaceSchedule: React.FC<WorkspaceScheduleProps> = ({ workspace }) => {
|
||||
const styles = useStyles()
|
||||
|
||||
return (
|
||||
<div className={styles.schedule}>
|
||||
<Stack spacing={2}>
|
||||
<Typography variant="body1" className={styles.title}>
|
||||
<ScheduleIcon className={styles.scheduleIcon} />
|
||||
Schedule
|
||||
</Typography>
|
||||
<div>
|
||||
<span className={styles.scheduleLabel}>{autoStartLabel(workspace.autostart_schedule)}</span>
|
||||
<span className={styles.scheduleValue}>{autoStartDisplay(workspace.autostart_schedule)}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className={styles.scheduleLabel}>Shutdown</span>
|
||||
<span className={styles.scheduleValue}>{autoStopDisplay(workspace)}</span>
|
||||
</div>
|
||||
<div>
|
||||
<Link className={styles.scheduleAction}>Edit schedule</Link>
|
||||
</div>
|
||||
</Stack>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
schedule: {
|
||||
fontFamily: MONOSPACE_FONT_FAMILY,
|
||||
},
|
||||
title: {
|
||||
fontWeight: 600,
|
||||
|
||||
fontFamily: "inherit",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
},
|
||||
scheduleIcon: {
|
||||
width: 16,
|
||||
height: 16,
|
||||
marginRight: theme.spacing(1),
|
||||
},
|
||||
scheduleLabel: {
|
||||
fontSize: 12,
|
||||
textTransform: "uppercase",
|
||||
display: "block",
|
||||
fontWeight: 600,
|
||||
color: theme.palette.text.secondary,
|
||||
},
|
||||
scheduleValue: {
|
||||
fontSize: 16,
|
||||
marginTop: theme.spacing(0.25),
|
||||
display: "inline-block",
|
||||
color: theme.palette.text.secondary,
|
||||
},
|
||||
scheduleAction: {
|
||||
cursor: "pointer",
|
||||
},
|
||||
}))
|
||||
@@ -1,58 +1,13 @@
|
||||
import Link from "@material-ui/core/Link"
|
||||
import { makeStyles, useTheme } from "@material-ui/core/styles"
|
||||
import cronstrue from "cronstrue"
|
||||
import dayjs from "dayjs"
|
||||
import duration from "dayjs/plugin/duration"
|
||||
import relativeTime from "dayjs/plugin/relativeTime"
|
||||
import React from "react"
|
||||
import { Link as RouterLink } from "react-router-dom"
|
||||
import { Workspace } from "../../api/typesGenerated"
|
||||
import { CardRadius, MONOSPACE_FONT_FAMILY } from "../../theme/constants"
|
||||
import { combineClasses } from "../../util/combineClasses"
|
||||
import { extractTimezone, stripTimezone } from "../../util/schedule"
|
||||
import { getDisplayStatus } from "../../util/workspace"
|
||||
|
||||
dayjs.extend(duration)
|
||||
dayjs.extend(relativeTime)
|
||||
|
||||
const autoStartLabel = (schedule: string): string => {
|
||||
const prefix = "Start"
|
||||
|
||||
if (schedule) {
|
||||
return `${prefix} (${extractTimezone(schedule)})`
|
||||
} else {
|
||||
return prefix
|
||||
}
|
||||
}
|
||||
|
||||
const autoStartDisplay = (schedule: string): string => {
|
||||
if (schedule) {
|
||||
return cronstrue.toString(stripTimezone(schedule), { throwExceptionOnParseError: false })
|
||||
}
|
||||
return "Manual"
|
||||
}
|
||||
|
||||
const autoStopDisplay = (workspace: Workspace): string => {
|
||||
const latest = workspace.latest_build
|
||||
|
||||
if (!workspace.ttl || workspace.ttl < 1) {
|
||||
return "Manual"
|
||||
}
|
||||
|
||||
if (latest.transition === "start") {
|
||||
const now = dayjs()
|
||||
const updatedAt = dayjs(latest.updated_at)
|
||||
const deadline = updatedAt.add(workspace.ttl / 1_000_000, "ms")
|
||||
if (now.isAfter(deadline)) {
|
||||
return "workspace is shutting down now"
|
||||
}
|
||||
return now.to(deadline)
|
||||
}
|
||||
|
||||
const duration = dayjs.duration(workspace.ttl / 1_000_000, "milliseconds")
|
||||
return `${duration.humanize()} after start`
|
||||
}
|
||||
|
||||
export interface WorkspaceStatsProps {
|
||||
workspace: Workspace
|
||||
}
|
||||
@@ -99,16 +54,6 @@ export const WorkspaceStats: React.FC<WorkspaceStatsProps> = ({ workspace }) =>
|
||||
<span className={styles.statsLabel}>Last Built</span>
|
||||
<span className={styles.statsValue}>{dayjs().to(dayjs(workspace.latest_build.created_at))}</span>
|
||||
</div>
|
||||
<div className={styles.statsDivider} />
|
||||
<div className={styles.statItem}>
|
||||
<span className={styles.statsLabel}>{autoStartLabel(workspace.autostart_schedule)}</span>
|
||||
<span className={styles.statsValue}>{autoStartDisplay(workspace.autostart_schedule)}</span>
|
||||
</div>
|
||||
<div className={styles.statsDivider} />
|
||||
<div className={styles.statItem}>
|
||||
<span className={styles.statsLabel}>Shutdown</span>
|
||||
<span className={styles.statsValue}>{autoStopDisplay(workspace)}</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user