mirror of
https://github.com/coder/coder.git
synced 2025-07-13 21:36:50 +00:00
feat: add UI for autostart workspace days (#10263)
* feat: add ui for selecting auto start days
This commit is contained in:
@ -610,6 +610,7 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) {
|
|||||||
req.DefaultTTLMillis == time.Duration(template.DefaultTTL).Milliseconds() &&
|
req.DefaultTTLMillis == time.Duration(template.DefaultTTL).Milliseconds() &&
|
||||||
req.MaxTTLMillis == time.Duration(template.MaxTTL).Milliseconds() &&
|
req.MaxTTLMillis == time.Duration(template.MaxTTL).Milliseconds() &&
|
||||||
autostopRequirementDaysOfWeekParsed == scheduleOpts.AutostopRequirement.DaysOfWeek &&
|
autostopRequirementDaysOfWeekParsed == scheduleOpts.AutostopRequirement.DaysOfWeek &&
|
||||||
|
autostartRequirementDaysOfWeekParsed == scheduleOpts.AutostartRequirement.DaysOfWeek &&
|
||||||
req.AutostopRequirement.Weeks == scheduleOpts.AutostopRequirement.Weeks &&
|
req.AutostopRequirement.Weeks == scheduleOpts.AutostopRequirement.Weeks &&
|
||||||
req.FailureTTLMillis == time.Duration(template.FailureTTL).Milliseconds() &&
|
req.FailureTTLMillis == time.Duration(template.FailureTTL).Milliseconds() &&
|
||||||
req.TimeTilDormantMillis == time.Duration(template.TimeTilDormant).Milliseconds() &&
|
req.TimeTilDormantMillis == time.Duration(template.TimeTilDormant).Milliseconds() &&
|
||||||
|
@ -0,0 +1,137 @@
|
|||||||
|
import { FC } from "react";
|
||||||
|
import { TemplateAutostartRequirementDaysValue } from "utils/schedule";
|
||||||
|
import Button from "@mui/material/Button";
|
||||||
|
import { Stack } from "components/Stack/Stack";
|
||||||
|
import FormHelperText from "@mui/material/FormHelperText";
|
||||||
|
|
||||||
|
export interface TemplateScheduleAutostartProps {
|
||||||
|
allow_user_autostart?: boolean;
|
||||||
|
autostart_requirement_days_of_week: TemplateAutostartRequirementDaysValue[];
|
||||||
|
isSubmitting: boolean;
|
||||||
|
onChange: (newDaysOfWeek: TemplateAutostartRequirementDaysValue[]) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TemplateScheduleAutostart: FC<
|
||||||
|
React.PropsWithChildren<TemplateScheduleAutostartProps>
|
||||||
|
> = ({
|
||||||
|
autostart_requirement_days_of_week,
|
||||||
|
isSubmitting,
|
||||||
|
allow_user_autostart,
|
||||||
|
onChange,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<Stack
|
||||||
|
direction="column"
|
||||||
|
width="100%"
|
||||||
|
alignItems="center"
|
||||||
|
css={{
|
||||||
|
marginBottom: "20px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Stack
|
||||||
|
direction="row"
|
||||||
|
css={{
|
||||||
|
width: "100%",
|
||||||
|
}}
|
||||||
|
spacing={0}
|
||||||
|
alignItems="baseline"
|
||||||
|
justifyContent="center"
|
||||||
|
>
|
||||||
|
{(
|
||||||
|
[
|
||||||
|
{ value: "monday", key: "Mon" },
|
||||||
|
{ value: "tuesday", key: "Tue" },
|
||||||
|
{ value: "wednesday", key: "Wed" },
|
||||||
|
{ value: "thursday", key: "Thu" },
|
||||||
|
{ value: "friday", key: "Fri" },
|
||||||
|
{ value: "saturday", key: "Sat" },
|
||||||
|
{ value: "sunday", key: "Sun" },
|
||||||
|
] as {
|
||||||
|
value: TemplateAutostartRequirementDaysValue;
|
||||||
|
key: string;
|
||||||
|
}[]
|
||||||
|
).map((day) => (
|
||||||
|
<Button
|
||||||
|
key={day.key}
|
||||||
|
css={{
|
||||||
|
borderRadius: "0px",
|
||||||
|
}}
|
||||||
|
// TODO: Adding a background color would also help
|
||||||
|
color={
|
||||||
|
autostart_requirement_days_of_week.includes(day.value)
|
||||||
|
? "primary"
|
||||||
|
: "secondary"
|
||||||
|
}
|
||||||
|
disabled={isSubmitting || !allow_user_autostart}
|
||||||
|
onClick={() => {
|
||||||
|
if (!autostart_requirement_days_of_week.includes(day.value)) {
|
||||||
|
onChange(autostart_requirement_days_of_week.concat(day.value));
|
||||||
|
} else {
|
||||||
|
onChange(
|
||||||
|
autostart_requirement_days_of_week.filter(
|
||||||
|
(obj) => obj !== day.value,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{day.key}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
<FormHelperText>
|
||||||
|
<AutostartRequirementDaysHelperText
|
||||||
|
allowed={allow_user_autostart}
|
||||||
|
days={autostart_requirement_days_of_week}
|
||||||
|
/>
|
||||||
|
</FormHelperText>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const sortedDays = [
|
||||||
|
"monday",
|
||||||
|
"tuesday",
|
||||||
|
"wednesday",
|
||||||
|
"thursday",
|
||||||
|
"friday",
|
||||||
|
"saturday",
|
||||||
|
"sunday",
|
||||||
|
] as TemplateAutostartRequirementDaysValue[];
|
||||||
|
|
||||||
|
const AutostartRequirementDaysHelperText: FC<{
|
||||||
|
allowed?: boolean;
|
||||||
|
days: TemplateAutostartRequirementDaysValue[];
|
||||||
|
}> = ({ allowed, days: unsortedDays }) => {
|
||||||
|
if (!allowed) {
|
||||||
|
return <span>Workspaces are not allowed to auto start.</span>;
|
||||||
|
}
|
||||||
|
// Sort the days
|
||||||
|
const days = unsortedDays.sort(
|
||||||
|
(a, b) => sortedDays.indexOf(a) - sortedDays.indexOf(b),
|
||||||
|
);
|
||||||
|
|
||||||
|
let daymsg = `Workspaces can autostart on ${days.join(", ")}.`;
|
||||||
|
if (days.length === 7) {
|
||||||
|
// If every day is allowed, no more explaining is needed.
|
||||||
|
return <span>Workspaces are allowed to auto start on any day.</span>;
|
||||||
|
}
|
||||||
|
if (days.length === 0) {
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
Workspaces will never auto start. This is effectively the same as
|
||||||
|
disabling autostart.
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
days.length === 5 &&
|
||||||
|
!days.includes("saturday") &&
|
||||||
|
!days.includes("sunday")
|
||||||
|
) {
|
||||||
|
daymsg = "Workspaces will never auto start on the weekends.";
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<span>{daymsg} These days are relative to the user's timezone.</span>
|
||||||
|
);
|
||||||
|
};
|
@ -42,7 +42,14 @@ import {
|
|||||||
AutostopRequirementWeeksHelperText,
|
AutostopRequirementWeeksHelperText,
|
||||||
} from "pages/TemplateSettingsPage/TemplateSchedulePage/AutostopRequirementHelperText";
|
} from "pages/TemplateSettingsPage/TemplateSchedulePage/AutostopRequirementHelperText";
|
||||||
import MenuItem from "@mui/material/MenuItem";
|
import MenuItem from "@mui/material/MenuItem";
|
||||||
import { TemplateAutostopRequirementDaysValue } from "utils/schedule";
|
import {
|
||||||
|
TemplateAutostartRequirementDaysValue,
|
||||||
|
TemplateAutostopRequirementDaysValue,
|
||||||
|
} from "utils/schedule";
|
||||||
|
import {
|
||||||
|
TemplateScheduleAutostart,
|
||||||
|
sortedDays,
|
||||||
|
} from "components/TemplateScheduleAutostart/TemplateScheduleAutostart";
|
||||||
|
|
||||||
const MAX_DESCRIPTION_CHAR_LIMIT = 128;
|
const MAX_DESCRIPTION_CHAR_LIMIT = 128;
|
||||||
const MAX_TTL_DAYS = 30;
|
const MAX_TTL_DAYS = 30;
|
||||||
@ -54,6 +61,7 @@ export interface CreateTemplateData {
|
|||||||
icon: string;
|
icon: string;
|
||||||
default_ttl_hours: number;
|
default_ttl_hours: number;
|
||||||
max_ttl_hours: number;
|
max_ttl_hours: number;
|
||||||
|
autostart_requirement_days_of_week: TemplateAutostartRequirementDaysValue[];
|
||||||
autostop_requirement_days_of_week: TemplateAutostopRequirementDaysValue;
|
autostop_requirement_days_of_week: TemplateAutostopRequirementDaysValue;
|
||||||
autostop_requirement_weeks: number;
|
autostop_requirement_weeks: number;
|
||||||
allow_user_autostart: boolean;
|
allow_user_autostart: boolean;
|
||||||
@ -88,6 +96,7 @@ const validationSchema = Yup.object({
|
|||||||
),
|
),
|
||||||
autostop_requirement_days_of_week: Yup.string().required(),
|
autostop_requirement_days_of_week: Yup.string().required(),
|
||||||
autostop_requirement_weeks: Yup.number().required().min(1).max(16),
|
autostop_requirement_weeks: Yup.number().required().min(1).max(16),
|
||||||
|
autostart_requirement_days_of_week: Yup.array().of(Yup.string()).required(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const defaultInitialValues: CreateTemplateData = {
|
const defaultInitialValues: CreateTemplateData = {
|
||||||
@ -110,6 +119,7 @@ const defaultInitialValues: CreateTemplateData = {
|
|||||||
// user's timezone.
|
// user's timezone.
|
||||||
autostop_requirement_days_of_week: "sunday",
|
autostop_requirement_days_of_week: "sunday",
|
||||||
autostop_requirement_weeks: 1,
|
autostop_requirement_weeks: 1,
|
||||||
|
autostart_requirement_days_of_week: sortedDays,
|
||||||
allow_user_cancel_workspace_jobs: false,
|
allow_user_cancel_workspace_jobs: false,
|
||||||
allow_user_autostart: false,
|
allow_user_autostart: false,
|
||||||
allow_user_autostop: false,
|
allow_user_autostop: false,
|
||||||
@ -434,6 +444,25 @@ export const CreateTemplateForm: FC<CreateTemplateFormProps> = (props) => {
|
|||||||
</strong>
|
</strong>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
|
{allowAdvancedScheduling && (
|
||||||
|
<TemplateScheduleAutostart
|
||||||
|
allow_user_autostart={form.values.allow_user_autostart}
|
||||||
|
autostart_requirement_days_of_week={
|
||||||
|
form.values.autostart_requirement_days_of_week
|
||||||
|
}
|
||||||
|
isSubmitting={isSubmitting}
|
||||||
|
onChange={async (
|
||||||
|
newDaysOfWeek: TemplateAutostartRequirementDaysValue[],
|
||||||
|
) => {
|
||||||
|
await form.setFieldValue(
|
||||||
|
"autostart_requirement_days_of_week",
|
||||||
|
newDaysOfWeek,
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<Stack direction="row" alignItems="center">
|
<Stack direction="row" alignItems="center">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
id="allow-user-autostop"
|
id="allow-user-autostop"
|
||||||
|
@ -17,6 +17,7 @@ export const newTemplate = (formData: CreateTemplateData) => {
|
|||||||
max_ttl_hours,
|
max_ttl_hours,
|
||||||
parameter_values_by_name,
|
parameter_values_by_name,
|
||||||
allow_everyone_group_access,
|
allow_everyone_group_access,
|
||||||
|
autostart_requirement_days_of_week,
|
||||||
autostop_requirement_days_of_week,
|
autostop_requirement_days_of_week,
|
||||||
autostop_requirement_weeks,
|
autostop_requirement_weeks,
|
||||||
...safeTemplateData
|
...safeTemplateData
|
||||||
@ -33,6 +34,9 @@ export const newTemplate = (formData: CreateTemplateData) => {
|
|||||||
),
|
),
|
||||||
weeks: formData.autostop_requirement_weeks,
|
weeks: formData.autostop_requirement_weeks,
|
||||||
},
|
},
|
||||||
|
autostart_requirement: {
|
||||||
|
days_of_week: autostart_requirement_days_of_week,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -9,7 +9,10 @@ import { FC, ChangeEvent, useState, useEffect } from "react";
|
|||||||
import { Template, UpdateTemplateMeta } from "api/typesGenerated";
|
import { Template, UpdateTemplateMeta } from "api/typesGenerated";
|
||||||
import { getFormHelpers } from "utils/formUtils";
|
import { getFormHelpers } from "utils/formUtils";
|
||||||
import { docs } from "utils/docs";
|
import { docs } from "utils/docs";
|
||||||
import { calculateAutostopRequirementDaysValue } from "utils/schedule";
|
import {
|
||||||
|
TemplateAutostartRequirementDaysValue,
|
||||||
|
calculateAutostopRequirementDaysValue,
|
||||||
|
} from "utils/schedule";
|
||||||
import {
|
import {
|
||||||
FormSection,
|
FormSection,
|
||||||
HorizontalForm,
|
HorizontalForm,
|
||||||
@ -36,6 +39,7 @@ import {
|
|||||||
convertAutostopRequirementDaysValue,
|
convertAutostopRequirementDaysValue,
|
||||||
} from "./AutostopRequirementHelperText";
|
} from "./AutostopRequirementHelperText";
|
||||||
import { useTheme } from "@emotion/react";
|
import { useTheme } from "@emotion/react";
|
||||||
|
import { TemplateScheduleAutostart } from "components/TemplateScheduleAutostart/TemplateScheduleAutostart";
|
||||||
|
|
||||||
const MS_HOUR_CONVERSION = 3600000;
|
const MS_HOUR_CONVERSION = 3600000;
|
||||||
const MS_DAY_CONVERSION = 86400000;
|
const MS_DAY_CONVERSION = 86400000;
|
||||||
@ -95,6 +99,8 @@ export const TemplateScheduleForm: FC<TemplateScheduleForm> = ({
|
|||||||
? template.autostop_requirement.weeks
|
? template.autostop_requirement.weeks
|
||||||
: 1
|
: 1
|
||||||
: 1,
|
: 1,
|
||||||
|
autostart_requirement_days_of_week: template.autostart_requirement
|
||||||
|
.days_of_week as TemplateAutostartRequirementDaysValue[],
|
||||||
|
|
||||||
allow_user_autostart: template.allow_user_autostart,
|
allow_user_autostart: template.allow_user_autostart,
|
||||||
allow_user_autostop: template.allow_user_autostop,
|
allow_user_autostop: template.allow_user_autostop,
|
||||||
@ -215,6 +221,9 @@ export const TemplateScheduleForm: FC<TemplateScheduleForm> = ({
|
|||||||
),
|
),
|
||||||
weeks: autostop_requirement_weeks,
|
weeks: autostop_requirement_weeks,
|
||||||
},
|
},
|
||||||
|
autostart_requirement: {
|
||||||
|
days_of_week: form.values.autostart_requirement_days_of_week,
|
||||||
|
},
|
||||||
|
|
||||||
allow_user_autostart: form.values.allow_user_autostart,
|
allow_user_autostart: form.values.allow_user_autostart,
|
||||||
allow_user_autostop: form.values.allow_user_autostop,
|
allow_user_autostop: form.values.allow_user_autostop,
|
||||||
@ -430,6 +439,24 @@ export const TemplateScheduleForm: FC<TemplateScheduleForm> = ({
|
|||||||
</strong>
|
</strong>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
{allowAdvancedScheduling && (
|
||||||
|
<TemplateScheduleAutostart
|
||||||
|
allow_user_autostart={form.values.allow_user_autostart}
|
||||||
|
autostart_requirement_days_of_week={
|
||||||
|
form.values.autostart_requirement_days_of_week
|
||||||
|
}
|
||||||
|
isSubmitting={isSubmitting}
|
||||||
|
onChange={async (
|
||||||
|
newDaysOfWeek: TemplateAutostartRequirementDaysValue[],
|
||||||
|
) => {
|
||||||
|
await form.setFieldValue(
|
||||||
|
"autostart_requirement_days_of_week",
|
||||||
|
newDaysOfWeek,
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<Stack direction="row" alignItems="center">
|
<Stack direction="row" alignItems="center">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
id="allow-user-autostop"
|
id="allow-user-autostop"
|
||||||
@ -623,4 +650,7 @@ const styles = {
|
|||||||
ttlFields: {
|
ttlFields: {
|
||||||
width: "100%",
|
width: "100%",
|
||||||
},
|
},
|
||||||
|
dayButtons: {
|
||||||
|
borderRadius: "0px",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
@ -26,6 +26,15 @@ const validFormValues: TemplateScheduleFormValues = {
|
|||||||
failure_cleanup_enabled: false,
|
failure_cleanup_enabled: false,
|
||||||
inactivity_cleanup_enabled: false,
|
inactivity_cleanup_enabled: false,
|
||||||
dormant_autodeletion_cleanup_enabled: false,
|
dormant_autodeletion_cleanup_enabled: false,
|
||||||
|
autostart_requirement_days_of_week: [
|
||||||
|
"monday",
|
||||||
|
"tuesday",
|
||||||
|
"wednesday",
|
||||||
|
"thursday",
|
||||||
|
"friday",
|
||||||
|
"saturday",
|
||||||
|
"sunday",
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderTemplateSchedulePage = async () => {
|
const renderTemplateSchedulePage = async () => {
|
||||||
|
@ -1,9 +1,16 @@
|
|||||||
import { UpdateTemplateMeta } from "api/typesGenerated";
|
import { UpdateTemplateMeta } from "api/typesGenerated";
|
||||||
import { TemplateAutostopRequirementDaysValue } from "utils/schedule";
|
import {
|
||||||
|
TemplateAutostartRequirementDaysValue,
|
||||||
|
TemplateAutostopRequirementDaysValue,
|
||||||
|
} from "utils/schedule";
|
||||||
import * as Yup from "yup";
|
import * as Yup from "yup";
|
||||||
|
|
||||||
export interface TemplateScheduleFormValues
|
export interface TemplateScheduleFormValues
|
||||||
extends Omit<UpdateTemplateMeta, "autostop_requirement"> {
|
extends Omit<
|
||||||
|
UpdateTemplateMeta,
|
||||||
|
"autostop_requirement" | "autostart_requirement"
|
||||||
|
> {
|
||||||
|
autostart_requirement_days_of_week: TemplateAutostartRequirementDaysValue[];
|
||||||
autostop_requirement_days_of_week: TemplateAutostopRequirementDaysValue;
|
autostop_requirement_days_of_week: TemplateAutostopRequirementDaysValue;
|
||||||
autostop_requirement_weeks: number;
|
autostop_requirement_weeks: number;
|
||||||
failure_cleanup_enabled: boolean;
|
failure_cleanup_enabled: boolean;
|
||||||
@ -75,5 +82,6 @@ export const getValidationSchema = (): Yup.AnyObjectSchema =>
|
|||||||
allow_user_autostop: Yup.boolean(),
|
allow_user_autostop: Yup.boolean(),
|
||||||
|
|
||||||
autostop_requirement_days_of_week: Yup.string().required(),
|
autostop_requirement_days_of_week: Yup.string().required(),
|
||||||
|
autostart_requirement_days_of_week: Yup.array().of(Yup.string()).required(),
|
||||||
autostop_requirement_weeks: Yup.number().required().min(1).max(16),
|
autostop_requirement_weeks: Yup.number().required().min(1).max(16),
|
||||||
});
|
});
|
||||||
|
@ -211,6 +211,15 @@ export const quietHoursDisplay = (
|
|||||||
return display;
|
return display;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TemplateAutostartRequirementDaysValue =
|
||||||
|
| "monday"
|
||||||
|
| "tuesday"
|
||||||
|
| "wednesday"
|
||||||
|
| "thursday"
|
||||||
|
| "friday"
|
||||||
|
| "saturday"
|
||||||
|
| "sunday";
|
||||||
|
|
||||||
export type TemplateAutostopRequirementDaysValue =
|
export type TemplateAutostopRequirementDaysValue =
|
||||||
| "off"
|
| "off"
|
||||||
| "daily"
|
| "daily"
|
||||||
|
Reference in New Issue
Block a user