Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit bdbc6f9

Browse files
committed
add form for template scheduling
1 parent 4ddfcb2 commit bdbc6f9

File tree

4 files changed

+218
-45
lines changed

4 files changed

+218
-45
lines changed

site/src/components/Dialogs/ConfirmDialog/ConfirmDialog.tsx

Lines changed: 154 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ import {
77
DialogActionButtonsProps,
88
} from "../Dialog"
99
import { ConfirmDialogType } from "../types"
10+
import Checkbox from "@mui/material/Checkbox"
11+
import { FormControlLabel } from "@mui/material"
12+
import { Stack } from "@mui/system"
1013

1114
interface ConfirmDialogTypeConfig {
1215
confirmText: ReactNode
@@ -61,7 +64,7 @@ const useStyles = makeStyles((theme) => ({
6164
background: theme.palette.background.paper,
6265
border: `1px solid ${theme.palette.divider}`,
6366
width: "100%",
64-
maxWidth: theme.spacing(55),
67+
maxWidth: theme.spacing(50),
6568
},
6669
"& .MuiDialogActions-spacing": {
6770
padding: `0 ${theme.spacing(5)} ${theme.spacing(5)}`,
@@ -151,3 +154,153 @@ export const ConfirmDialog: FC<PropsWithChildren<ConfirmDialogProps>> = ({
151154
</Dialog>
152155
)
153156
}
157+
158+
export interface ScheduleDialogProps extends ConfirmDialogProps {
159+
readonly inactiveWorkspaceToBeLocked: number
160+
readonly lockedWorkspacesToBeDeleted: number
161+
readonly updateLockedWorkspaces: (confirm: boolean) => void
162+
readonly updateInactiveWorkspaces: (confirm: boolean) => void
163+
}
164+
165+
export const ScheduleDialog: FC<PropsWithChildren<ScheduleDialogProps>> = ({
166+
cancelText,
167+
confirmLoading,
168+
disabled = false,
169+
hideCancel,
170+
onClose,
171+
onConfirm,
172+
type,
173+
open = false,
174+
title,
175+
inactiveWorkspaceToBeLocked,
176+
lockedWorkspacesToBeDeleted,
177+
updateLockedWorkspaces,
178+
updateInactiveWorkspaces,
179+
}) => {
180+
const styles = useScheduleStyles({ type })
181+
182+
const defaults = CONFIRM_DIALOG_DEFAULTS["delete"]
183+
184+
if (typeof hideCancel === "undefined") {
185+
hideCancel = defaults.hideCancel
186+
}
187+
188+
return (
189+
<Dialog
190+
className={styles.dialogWrapper}
191+
onClose={onClose}
192+
open={open}
193+
data-testid="dialog"
194+
>
195+
<div className={styles.dialogContent}>
196+
<h3 className={styles.dialogTitle}>{title}</h3>
197+
<>
198+
{inactiveWorkspaceToBeLocked > 0 && (
199+
<>
200+
<h4>{"Inactivity TTL"}</h4>
201+
<Stack direction={"row"} spacing={"5"}>
202+
<div
203+
className={styles.dialogDescription}
204+
>{`The current value will result in ${inactiveWorkspaceToBeLocked}+ workspaces being automatically soft deleted. To prevent this, do you want to reset the inactivity period for all template workspaces?`}</div>
205+
<FormControlLabel
206+
sx={{
207+
marginTop: 2,
208+
}}
209+
control={
210+
<Checkbox
211+
size="small"
212+
onChange={(e) => {
213+
updateInactiveWorkspaces(e.target.checked)
214+
}}
215+
/>
216+
}
217+
label="Reset"
218+
/>
219+
</Stack>
220+
</>
221+
)}
222+
223+
{lockedWorkspacesToBeDeleted > 0 && (
224+
<>
225+
<h4>{"Deletion Grace Period"}</h4>
226+
<Stack direction={"row"} spacing={5}>
227+
<div
228+
className={styles.dialogDescription}
229+
>{`The current value will result in ${lockedWorkspacesToBeDeleted}+ workspaces being permanently deleted. To prevent this, do you want to reset the soft-deletion period for all template workspaces?`}</div>
230+
<FormControlLabel
231+
sx={{
232+
marginTop: 2,
233+
}}
234+
control={
235+
<Checkbox
236+
size="small"
237+
onChange={(e) => {
238+
updateLockedWorkspaces(e.target.checked)
239+
}}
240+
/>
241+
}
242+
label="Reset"
243+
/>
244+
</Stack>
245+
</>
246+
)}
247+
</>
248+
</div>
249+
250+
<DialogActions>
251+
<DialogActionButtons
252+
cancelText={cancelText}
253+
confirmDialog
254+
confirmLoading={confirmLoading}
255+
confirmText={"Submit"}
256+
disabled={disabled}
257+
onCancel={!hideCancel ? onClose : undefined}
258+
onConfirm={onConfirm || onClose}
259+
type={"delete"}
260+
/>
261+
</DialogActions>
262+
</Dialog>
263+
)
264+
}
265+
266+
const useScheduleStyles = makeStyles((theme) => ({
267+
dialogWrapper: {
268+
"& .MuiPaper-root": {
269+
background: theme.palette.background.paper,
270+
border: `1px solid ${theme.palette.divider}`,
271+
width: "100%",
272+
maxWidth: theme.spacing(125),
273+
},
274+
"& .MuiDialogActions-spacing": {
275+
padding: `0 ${theme.spacing(5)} ${theme.spacing(5)}`,
276+
},
277+
},
278+
dialogContent: {
279+
color: theme.palette.text.secondary,
280+
padding: theme.spacing(5),
281+
},
282+
dialogTitle: {
283+
margin: 0,
284+
marginBottom: theme.spacing(2),
285+
color: theme.palette.text.primary,
286+
fontWeight: 400,
287+
fontSize: theme.spacing(2.5),
288+
},
289+
dialogDescription: {
290+
color: theme.palette.text.secondary,
291+
lineHeight: "160%",
292+
fontSize: 16,
293+
294+
"& strong": {
295+
color: theme.palette.text.primary,
296+
},
297+
298+
"& p:not(.MuiFormHelperText-root)": {
299+
margin: 0,
300+
},
301+
302+
"& > p": {
303+
margin: theme.spacing(1, 0),
304+
},
305+
},
306+
}))

site/src/components/WorkspaceDeletion/ImpendingDeletionBanner.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,13 +61,13 @@ export const LockedWorkspaceBanner = ({
6161
hasDeletionScheduledWorkspaces.deleting_at &&
6262
hasDeletionScheduledWorkspaces.locked_at
6363
) {
64-
return `This workspace has been locked since ${formatDistanceToNow(
64+
return `This workspace has been locked for ${formatDistanceToNow(
6565
Date.parse(hasDeletionScheduledWorkspaces.locked_at),
66-
)} and is scheduled to be deleted at ${formatDate(
66+
)} and is scheduled to be deleted on ${formatDate(
6767
hasDeletionScheduledWorkspaces.deleting_at,
6868
)} . To keep it you must unlock the workspace.`
6969
} else if (hasLockedWorkspaces && hasLockedWorkspaces.locked_at) {
70-
return `This workspace has been locked since ${formatDate(
70+
return `This workspace has been locked for ${formatDate(
7171
hasLockedWorkspaces.locked_at,
7272
)}
7373
and cannot be interacted

site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TemplateScheduleForm/TemplateScheduleForm.tsx

Lines changed: 50 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,15 @@ import Link from "@mui/material/Link"
1616
import Checkbox from "@mui/material/Checkbox"
1717
import FormControlLabel from "@mui/material/FormControlLabel"
1818
import Switch from "@mui/material/Switch"
19-
import { DeleteLockedDialog, InactivityDialog } from "./InactivityDialog"
19+
import { InactivityDialog } from "./InactivityDialog"
2020
import {
2121
useWorkspacesToBeLocked,
2222
useWorkspacesToBeDeleted,
2323
} from "./useWorkspacesToBeDeleted"
2424
import { TemplateScheduleFormValues, getValidationSchema } from "./formHelpers"
2525
import { TTLHelperText } from "./TTLHelperText"
2626
import { docs } from "utils/docs"
27+
import { ScheduleDialog } from "components/Dialogs/ConfirmDialog/ConfirmDialog"
2728

2829
const MS_HOUR_CONVERSION = 3600000
2930
const MS_DAY_CONVERSION = 86400000
@@ -92,18 +93,22 @@ export const TemplateScheduleForm: FC<TemplateScheduleForm> = ({
9293
},
9394
validationSchema,
9495
onSubmit: () => {
95-
if (
96+
// Determine if this form will automatically
97+
// lock workspaces upon submission.
98+
const updateWillLockWorkspaces =
9699
form.values.inactivity_cleanup_enabled &&
97100
workspacesToBeLockedToday &&
98101
workspacesToBeLockedToday.length > 0
99-
) {
100-
setIsInactivityDialogOpen(true)
101-
} else if (
102+
103+
// Determine if this form will automatically
104+
// delete locked workspaces upon submission.
105+
const updateWillDeletWorkspaces =
102106
form.values.locked_cleanup_enabled &&
103107
workspacesToBeDeletedToday &&
104108
workspacesToBeDeletedToday.length > 0
105-
) {
106-
setIsLockedDialogOpen(true)
109+
110+
if (updateWillLockWorkspaces || updateWillDeletWorkspaces) {
111+
setIsScheduleDialogOpen(true)
107112
} else {
108113
submitValues()
109114
}
@@ -126,9 +131,14 @@ export const TemplateScheduleForm: FC<TemplateScheduleForm> = ({
126131
form.values,
127132
)
128133

129-
const [isInactivityDialogOpen, setIsInactivityDialogOpen] =
134+
const showScheduleDialog =
135+
workspacesToBeLockedToday &&
136+
workspacesToBeDeletedToday &&
137+
(workspacesToBeLockedToday.length > 0 ||
138+
workspacesToBeDeletedToday.length > 0)
139+
140+
const [isScheduleDialogOpen, setIsScheduleDialogOpen] =
130141
useState<boolean>(false)
131-
const [isLockedDialogOpen, setIsLockedDialogOpen] = useState<boolean>(false)
132142

133143
const submitValues = () => {
134144
// on submit, convert from hours => ms
@@ -349,8 +359,8 @@ export const TemplateScheduleForm: FC<TemplateScheduleForm> = ({
349359
</FormFields>
350360
</FormSection>
351361
<FormSection
352-
title="Inactivity TTL"
353-
description="When enabled, Coder will lock workspaces that have not been accessed after a specified number of days."
362+
title="Inactivity Soft Deletion"
363+
description="When enabled, Coder will soft-delete workspaces that have not been accessed after a specified number of days. A soft-deleted workspace cannot be interacted with until it is recovered by the user."
354364
>
355365
<FormFields>
356366
<FormControlLabel
@@ -361,7 +371,7 @@ export const TemplateScheduleForm: FC<TemplateScheduleForm> = ({
361371
onChange={handleToggleInactivityCleanup}
362372
/>
363373
}
364-
label="Enable Inactivity TTL"
374+
label="Enable Inactivity Soft Deletion"
365375
/>
366376
<TextField
367377
{...getFieldHelpers(
@@ -382,8 +392,8 @@ export const TemplateScheduleForm: FC<TemplateScheduleForm> = ({
382392
</FormFields>
383393
</FormSection>
384394
<FormSection
385-
title="Deletion Grace Period"
386-
description="When enabled, Coder will permanently delete workspaces that have been locked for a specified number of days."
395+
title="Deletion Retention"
396+
description="When enabled, Coder will permanently delete workspaces that have been soft-deleted for a specified number of days. Once a workspace is permanently deleted it cannot be recovered."
387397
>
388398
<FormFields>
389399
<FormControlLabel
@@ -394,7 +404,7 @@ export const TemplateScheduleForm: FC<TemplateScheduleForm> = ({
394404
onChange={handleToggleLockedCleanup}
395405
/>
396406
}
397-
label="Enable Locked TTL"
407+
label="Enable Deletion Retention"
398408
/>
399409
<TextField
400410
{...getFieldHelpers(
@@ -417,17 +427,34 @@ export const TemplateScheduleForm: FC<TemplateScheduleForm> = ({
417427
{workspacesToBeLockedToday && workspacesToBeLockedToday.length > 0 && (
418428
<InactivityDialog
419429
submitValues={submitValues}
420-
isInactivityDialogOpen={isInactivityDialogOpen}
421-
setIsInactivityDialogOpen={setIsInactivityDialogOpen}
430+
isInactivityDialogOpen={isScheduleDialogOpen}
431+
setIsInactivityDialogOpen={setIsScheduleDialogOpen}
422432
workspacesToBeLockedToday={workspacesToBeLockedToday?.length ?? 0}
423433
/>
424434
)}
425-
{workspacesToBeDeletedToday && workspacesToBeDeletedToday.length > 0 && (
426-
<DeleteLockedDialog
427-
submitValues={submitValues}
428-
isLockedDialogOpen={isLockedDialogOpen}
429-
setIsLockedDialogOpen={setIsLockedDialogOpen}
430-
workspacesToBeDeletedToday={workspacesToBeDeletedToday?.length ?? 0}
435+
{showScheduleDialog && (
436+
<ScheduleDialog
437+
onConfirm={() => {
438+
submitValues()
439+
setIsScheduleDialogOpen(false)
440+
// These fields are request-scoped so they should be reset
441+
// after every submission.
442+
form.setFieldValue("update_workspace_locked_at", false)
443+
form.setFieldValue("update_workspace_last_used_at", false)
444+
}}
445+
inactiveWorkspaceToBeLocked={workspacesToBeLockedToday.length}
446+
lockedWorkspacesToBeDeleted={workspacesToBeDeletedToday.length}
447+
open={isScheduleDialogOpen}
448+
onClose={() => {
449+
setIsScheduleDialogOpen(false)
450+
}}
451+
title="Workspace Scheduling"
452+
updateLockedWorkspaces={(update: boolean) =>
453+
form.setFieldValue("update_workspace_locked_at", update)
454+
}
455+
updateInactiveWorkspaces={(update: boolean) =>
456+
form.setFieldValue("update_workspace_last_used_at", update)
457+
}
431458
/>
432459
)}
433460

site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TemplateScheduleForm/useWorkspacesToBeDeleted.ts

Lines changed: 11 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,19 @@
1-
import { useQuery } from "@tanstack/react-query"
2-
import { getWorkspaces } from "api/api"
31
import { compareAsc } from "date-fns"
42
import { Workspace, Template } from "api/typesGenerated"
53
import { TemplateScheduleFormValues } from "./formHelpers"
4+
import { useWorkspacesData } from "pages/WorkspacesPage/data"
65

76
export const useWorkspacesToBeLocked = (
87
template: Template,
98
formValues: TemplateScheduleFormValues,
109
) => {
11-
const { data: workspacesData } = useQuery({
12-
queryKey: ["workspaces"],
13-
queryFn: () =>
14-
getWorkspaces({
15-
q: "template:" + template.name,
16-
}),
17-
enabled: formValues.inactivity_cleanup_enabled,
10+
const { data } = useWorkspacesData({
11+
page: 0,
12+
limit: 0,
13+
query: "template:" + template.name,
1814
})
1915

20-
return workspacesData?.workspaces?.filter((workspace: Workspace) => {
16+
return data?.workspaces?.filter((workspace: Workspace) => {
2117
if (!formValues.inactivity_ttl_ms) {
2218
return
2319
}
@@ -41,15 +37,12 @@ export const useWorkspacesToBeDeleted = (
4137
template: Template,
4238
formValues: TemplateScheduleFormValues,
4339
) => {
44-
const { data: workspacesData } = useQuery({
45-
queryKey: ["workspaces"],
46-
queryFn: () =>
47-
getWorkspaces({
48-
q: "template:" + template.name,
49-
}),
50-
enabled: formValues.locked_cleanup_enabled,
40+
const { data } = useWorkspacesData({
41+
page: 0,
42+
limit: 0,
43+
query: "template:" + template.name + " locked_at:1970-01-01",
5144
})
52-
return workspacesData?.workspaces?.filter((workspace: Workspace) => {
45+
return data?.workspaces?.filter((workspace: Workspace) => {
5346
if (!workspace.locked_at || !formValues.locked_ttl_ms) {
5447
return false
5548
}

0 commit comments

Comments
 (0)