diff --git a/site/src/pages/CreateTemplatePage/CreateTemplateForm.tsx b/site/src/pages/CreateTemplatePage/CreateTemplateForm.tsx index 4d2122b2cb2a5..b3ff3b60e955e 100644 --- a/site/src/pages/CreateTemplatePage/CreateTemplateForm.tsx +++ b/site/src/pages/CreateTemplatePage/CreateTemplateForm.tsx @@ -105,6 +105,8 @@ const defaultInitialValues: CreateTemplateData = { // you are not licensed. We hide the form value based on entitlements. max_ttl_hours: 24 * 7, allow_user_cancel_workspace_jobs: false, + allow_user_autostart: false, + allow_user_autostop: false, } type GetInitialValuesParams = { @@ -112,19 +114,19 @@ type GetInitialValuesParams = { fromCopy?: Template parameters?: ParameterSchema[] variables?: TemplateVersionVariable[] - canSetMaxTTL: boolean + allowAdvancedScheduling: boolean } const getInitialValues = ({ fromExample, fromCopy, - canSetMaxTTL, + allowAdvancedScheduling, variables, parameters, }: GetInitialValuesParams) => { let initialValues = defaultInitialValues - if (!canSetMaxTTL) { + if (!allowAdvancedScheduling) { initialValues = { ...initialValues, max_ttl_hours: 0, @@ -188,7 +190,7 @@ export interface CreateTemplateFormProps { error?: unknown jobError?: string logs?: ProvisionerJobLog[] - canSetMaxTTL: boolean + allowAdvancedScheduling: boolean copiedTemplate?: Template } @@ -204,12 +206,12 @@ export const CreateTemplateForm: FC = ({ error, jobError, logs, - canSetMaxTTL, + allowAdvancedScheduling, }) => { const styles = useStyles() const form = useFormik({ initialValues: getInitialValues({ - canSetMaxTTL, + allowAdvancedScheduling, fromExample: starterTemplate, fromCopy: copiedTemplate, variables, @@ -319,7 +321,7 @@ export const CreateTemplateForm: FC = ({ = ({ ), )} - disabled={isSubmitting || !canSetMaxTTL} + disabled={isSubmitting || !allowAdvancedScheduling} fullWidth label={t("form.fields.maxTTL")} variant="outlined" type="number" /> + + + { + await form.setFieldValue( + "allow_user_autostart", + !form.values.allow_user_autostart, + ) + }} + name="allow_user_autostart" + checked={form.values.allow_user_autostart} + /> + + + Allow users to autostart workspaces on a schedule. + + + + + { + await form.setFieldValue( + "allow_user_autostop", + !form.values.allow_user_autostop, + ) + }} + name="allow-user-autostop" + checked={form.values.allow_user_autostop} + /> + + + Allow users to customize autostop duration for workspaces. + + + Workspaces will always use the default TTL if this is set. + Regardless of this setting, workspaces can only stay on for + the max TTL. + + + + diff --git a/site/src/pages/CreateTemplatePage/CreateTemplatePage.tsx b/site/src/pages/CreateTemplatePage/CreateTemplatePage.tsx index e6443f48f3bf2..8819cdb5d5a01 100644 --- a/site/src/pages/CreateTemplatePage/CreateTemplatePage.tsx +++ b/site/src/pages/CreateTemplatePage/CreateTemplatePage.tsx @@ -44,7 +44,7 @@ const CreateTemplatePage: FC = () => { } = state.context const shouldDisplayForm = !state.hasTag("loading") const { entitlements } = useDashboard() - const canSetMaxTTL = + const allowAdvancedScheduling = entitlements.features["advanced_template_scheduling"].enabled const onCancel = () => { @@ -70,7 +70,7 @@ const CreateTemplatePage: FC = () => { {shouldDisplayForm && ( 24 * MAX_TTL_DAYS /* 7 days in hours */, i18next.t("maxTTLMaxError", { ns: "templateSettingsPage" }), ), + allow_user_autostart: Yup.boolean(), + allow_user_autostop: Yup.boolean(), }) export interface TemplateScheduleForm { @@ -56,7 +59,7 @@ export interface TemplateScheduleForm { onCancel: () => void isSubmitting: boolean error?: unknown - canSetMaxTTL: boolean + allowAdvancedScheduling: boolean // Helpful to show field errors on Storybook initialTouched?: FormikTouched } @@ -66,35 +69,40 @@ export const TemplateScheduleForm: FC = ({ onSubmit, onCancel, error, - canSetMaxTTL, + allowAdvancedScheduling, isSubmitting, initialTouched, }) => { const { t: commonT } = useTranslation("common") const validationSchema = getValidationSchema() - const form: FormikContextType = - useFormik({ - initialValues: { - // on display, convert from ms => hours - default_ttl_ms: template.default_ttl_ms / MS_HOUR_CONVERSION, - // the API ignores this value, but to avoid tripping up validation set - // it to zero if the user can't set the field. - max_ttl_ms: canSetMaxTTL ? template.max_ttl_ms / MS_HOUR_CONVERSION : 0, - }, - validationSchema, - onSubmit: (formData) => { - // on submit, convert from hours => ms - onSubmit({ - default_ttl_ms: formData.default_ttl_ms - ? formData.default_ttl_ms * MS_HOUR_CONVERSION - : undefined, - max_ttl_ms: formData.max_ttl_ms - ? formData.max_ttl_ms * MS_HOUR_CONVERSION - : undefined, - }) - }, - initialTouched, - }) + const form = useFormik({ + initialValues: { + // on display, convert from ms => hours + default_ttl_ms: template.default_ttl_ms / MS_HOUR_CONVERSION, + // the API ignores this value, but to avoid tripping up validation set + // it to zero if the user can't set the field. + max_ttl_ms: allowAdvancedScheduling + ? template.max_ttl_ms / MS_HOUR_CONVERSION + : 0, + allow_user_autostart: template.allow_user_autostart, + allow_user_autostop: template.allow_user_autostop, + }, + validationSchema, + onSubmit: (formData) => { + // on submit, convert from hours => ms + onSubmit({ + default_ttl_ms: formData.default_ttl_ms + ? formData.default_ttl_ms * MS_HOUR_CONVERSION + : undefined, + max_ttl_ms: formData.max_ttl_ms + ? formData.max_ttl_ms * MS_HOUR_CONVERSION + : undefined, + allow_user_autostart: formData.allow_user_autostart, + allow_user_autostop: formData.allow_user_autostop, + }) + }, + initialTouched, + }) const getFieldHelpers = getFormHelpers(form, error) const { t } = useTranslation("templateSettingsPage") const styles = useStyles() @@ -128,7 +136,7 @@ export const TemplateScheduleForm: FC = ({ = ({ ), )} - disabled={isSubmitting || !canSetMaxTTL} + disabled={isSubmitting || !allowAdvancedScheduling} fullWidth inputProps={{ min: 0, step: 1 }} label={t("maxTtlLabel")} @@ -153,13 +161,72 @@ export const TemplateScheduleForm: FC = ({ + + + + { + await form.setFieldValue( + "allow_user_autostart", + !form.values.allow_user_autostart, + ) + }} + name="allow_user_autostart" + checked={form.values.allow_user_autostart} + /> + + + Allow users to autostart workspaces on a schedule. + + + + + { + await form.setFieldValue( + "allow_user_autostop", + !form.values.allow_user_autostop, + ) + }} + name="allow_user_autostop" + checked={form.values.allow_user_autostop} + /> + + + Allow users to customize autostop duration for workspaces. + + + Workspaces will always use the default TTL if this is set. + Regardless of this setting, workspaces can only stay on for the + max lifetime. + + + + + + ) } -const useStyles = makeStyles(() => ({ +const useStyles = makeStyles((theme) => ({ ttlFields: { width: "100%", }, + optionDescription: { + fontSize: 12, + color: theme.palette.text.secondary, + }, })) diff --git a/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TemplateSchedulePage.tsx b/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TemplateSchedulePage.tsx index c9e1e33c5b3b5..18075e0cd1795 100644 --- a/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TemplateSchedulePage.tsx +++ b/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TemplateSchedulePage.tsx @@ -15,7 +15,7 @@ const TemplateSchedulePage: FC = () => { const navigate = useNavigate() const { template } = useTemplateSettingsContext() const { entitlements } = useDashboard() - const canSetMaxTTL = + const allowAdvancedScheduling = entitlements.features["advanced_template_scheduling"].enabled const { mutate: updateTemplate, @@ -36,7 +36,7 @@ const TemplateSchedulePage: FC = () => { {pageTitle([template.name, "Schedule"])} ["initialTouched"] - canSetMaxTTL: boolean + allowAdvancedScheduling: boolean } export const TemplateSchedulePageView: FC = ({ @@ -19,7 +19,7 @@ export const TemplateSchedulePageView: FC = ({ onCancel, onSubmit, isSubmitting, - canSetMaxTTL, + allowAdvancedScheduling, submitError, initialTouched, }) => { @@ -32,7 +32,7 @@ export const TemplateSchedulePageView: FC = ({ user_variable_values?: VariableValue[]