mirror of
https://github.com/garethgeorge/backrest.git
synced 2026-09-23 16:35:38 +00:00
fix: slightly improve schedule layout and minor ui bug fixes
Release Please / release-please (push) Has been cancelled
Release Preview / call-reusable-release (push) Has been cancelled
Test / test-nix (push) Has been cancelled
Test / test-win (push) Has been cancelled
Update Restic / update-restic-version (push) Has been cancelled
Release Please / release-please (push) Has been cancelled
Release Preview / call-reusable-release (push) Has been cancelled
Test / test-nix (push) Has been cancelled
Test / test-win (push) Has been cancelled
Update Restic / update-restic-version (push) Has been cancelled
This commit is contained in:
@@ -623,7 +623,7 @@ const BackupOperationStatus = ({
|
||||
console.error("GOT UNEXPECTED STATUS: ", status);
|
||||
return (
|
||||
<>
|
||||
{m.op_row_unexpected_status} {status}
|
||||
{m.op_row_unexpected_status() + JSON.stringify(status)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import {
|
||||
Checkbox,
|
||||
Flex,
|
||||
Form,
|
||||
InputNumber,
|
||||
Radio,
|
||||
Row,
|
||||
Tooltip,
|
||||
Typography,
|
||||
} from "antd";
|
||||
@@ -169,8 +169,8 @@ export const ScheduleFormItem = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Row>
|
||||
<Flex vertical gap="small">
|
||||
<div>
|
||||
<Radio.Group
|
||||
value={mode}
|
||||
onChange={(e) => {
|
||||
@@ -217,26 +217,25 @@ export const ScheduleFormItem = ({
|
||||
</Tooltip>
|
||||
</Radio.Button>
|
||||
</Radio.Group>
|
||||
<Typography.Text style={{ marginLeft: "1em", marginRight: "1em" }}>
|
||||
Clock for schedule:{" "}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
<Flex align="center" gap="small">
|
||||
<Typography.Text>Clock for schedule:</Typography.Text>
|
||||
<Tooltip
|
||||
title={
|
||||
<>
|
||||
Clock provides the time that the schedule is evaluated relative
|
||||
to.
|
||||
Clock provides the time that the schedule is evaluated relative to.
|
||||
<ul>
|
||||
<li>Local - current time in the local timezone.</li>
|
||||
<li>UTC - current time in the UTC timezone.</li>
|
||||
<li>
|
||||
Last Run Time - relative to the last time the task ran. Good
|
||||
for devices that aren't always powered on e.g. laptops.
|
||||
Last Run Time - relative to the last time the task ran. Good for
|
||||
devices that aren't always powered on e.g. laptops.
|
||||
</li>
|
||||
</ul>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<Form.Item name={name.concat("clock")}>
|
||||
<Form.Item name={name.concat("clock")} noStyle>
|
||||
<Radio.Group>
|
||||
<Radio.Button
|
||||
value={clockEnumValueToString(Schedule_Clock.LOCAL)}
|
||||
@@ -254,12 +253,13 @@ export const ScheduleFormItem = ({
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
</Tooltip>
|
||||
</Row>
|
||||
<div style={{ height: "0.5em" }} />
|
||||
<Row>
|
||||
<Form.Item>{elem}</Form.Item>
|
||||
</Row>
|
||||
</>
|
||||
</Flex>
|
||||
{elem && (
|
||||
<div style={{ marginTop: "8px" }}>
|
||||
<Form.Item noStyle>{elem}</Form.Item>
|
||||
</div>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
+138
-215
@@ -13,6 +13,7 @@ import {
|
||||
Collapse,
|
||||
Checkbox,
|
||||
AutoComplete,
|
||||
Flex,
|
||||
} from "antd";
|
||||
import React, { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useShowModal } from "../components/ModalManager";
|
||||
@@ -53,112 +54,7 @@ import * as m from "../paraglide/messages";
|
||||
const { TextArea } = Input;
|
||||
const sep = isWindows ? "\\" : "/";
|
||||
|
||||
const PathsTextArea = ({ value, onChange, ...props }: any) => {
|
||||
const [options, setOptions] = useState<{ value: string }[]>([]);
|
||||
const [currentLine, setCurrentLine] = useState("");
|
||||
const [cursorPosition, setCursorPosition] = useState(0);
|
||||
// selectingRef acts as a lock to prevent the AutoComplete's default behavior (replacing the entire value)
|
||||
// from overwriting the multi-line merge logic in onSelect.
|
||||
const selectingRef = useRef(false);
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const handleSearch = useMemo(
|
||||
() =>
|
||||
debounce((searchValue: string) => {
|
||||
if (!searchValue) {
|
||||
setOptions([]);
|
||||
return;
|
||||
}
|
||||
|
||||
const lastSlash = searchValue.lastIndexOf(sep);
|
||||
let searchPath = searchValue;
|
||||
if (lastSlash !== -1) {
|
||||
searchPath = searchValue.substring(0, lastSlash);
|
||||
}
|
||||
|
||||
backrestService
|
||||
.pathAutocomplete({ value: searchPath + sep })
|
||||
.then((res: StringList) => {
|
||||
if (!res.values) {
|
||||
return;
|
||||
}
|
||||
const vals = res.values.map((v) => {
|
||||
return {
|
||||
value: searchPath + sep + v,
|
||||
};
|
||||
});
|
||||
setOptions(vals.filter((o) => o.value.indexOf(searchValue) !== -1));
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log("Path autocomplete error: ", e);
|
||||
});
|
||||
}, 200),
|
||||
[]
|
||||
);
|
||||
|
||||
const handleTextAreaChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
// If we are currently handling a selection, ignore the change event that AutoComplete triggers
|
||||
// to replace the text with the selected item.
|
||||
if (selectingRef.current) {
|
||||
selectingRef.current = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const newValue = e.target.value;
|
||||
const cursorPos = e.target.selectionStart || 0;
|
||||
|
||||
// Find the current line based on cursor position
|
||||
const lines = newValue.substring(0, cursorPos).split("\n");
|
||||
const currentLineValue = lines[lines.length - 1];
|
||||
|
||||
setCurrentLine(currentLineValue);
|
||||
setCursorPosition(cursorPos);
|
||||
|
||||
// Trigger autocomplete for the current line
|
||||
handleSearch(currentLineValue);
|
||||
|
||||
// Update the form value
|
||||
if (onChange) {
|
||||
onChange(newValue);
|
||||
}
|
||||
};
|
||||
|
||||
const onSelect = (selectedValue: string) => {
|
||||
// Set the flag to ignore the next onChange event (which contains the raw selected value)
|
||||
selectingRef.current = true;
|
||||
|
||||
const lines = (value || "").split("\n");
|
||||
const beforeCursor = (value || "").substring(0, cursorPosition);
|
||||
const afterCursor = (value || "").substring(cursorPosition);
|
||||
const linesBeforeCursor = beforeCursor.split("\n");
|
||||
|
||||
// Replace the current line with the selected value
|
||||
linesBeforeCursor[linesBeforeCursor.length - 1] = selectedValue;
|
||||
const newValue = linesBeforeCursor.join("\n") + afterCursor;
|
||||
|
||||
if (onChange) {
|
||||
onChange(newValue);
|
||||
}
|
||||
setOptions([]);
|
||||
};
|
||||
|
||||
return (
|
||||
<AutoComplete
|
||||
options={options}
|
||||
onSelect={onSelect}
|
||||
onSearch={() => {}} // We handle search in textarea change
|
||||
{...props}
|
||||
>
|
||||
<TextArea
|
||||
value={value}
|
||||
onChange={handleTextAreaChange}
|
||||
placeholder={m.add_plan_modal_field_paths_placeholder()}
|
||||
style={{ minHeight: 100 }}
|
||||
autoSize={{ minRows: 3, maxRows: 10 }}
|
||||
/>
|
||||
</AutoComplete>
|
||||
);
|
||||
};
|
||||
|
||||
const planDefaults = create(PlanSchema, {
|
||||
schedule: {
|
||||
@@ -191,15 +87,7 @@ export const AddPlanModal = ({ template }: { template: Plan | null }) => {
|
||||
? toJson(PlanSchema, template, { alwaysEmitImplicit: true })
|
||||
: toJson(PlanSchema, planDefaults, { alwaysEmitImplicit: true });
|
||||
|
||||
// Convert paths array to newline-separated string for the textarea
|
||||
const formDataObj = formData as any;
|
||||
if (formDataObj?.paths && Array.isArray(formDataObj.paths)) {
|
||||
formDataObj.pathsText = formDataObj.paths.join("\n");
|
||||
} else {
|
||||
formDataObj.pathsText = "";
|
||||
}
|
||||
|
||||
form.setFieldsValue(formDataObj);
|
||||
form.setFieldsValue(formData);
|
||||
}, [template]);
|
||||
|
||||
if (!config) {
|
||||
@@ -244,17 +132,6 @@ export const AddPlanModal = ({ template }: { template: Plan | null }) => {
|
||||
try {
|
||||
let planFormData = await validateForm(form);
|
||||
|
||||
// Convert pathsText back to paths array
|
||||
if (planFormData.pathsText) {
|
||||
planFormData.paths = planFormData.pathsText
|
||||
.split("\n")
|
||||
.map((path: string) => path.trim())
|
||||
.filter((path: string) => path.length > 0);
|
||||
delete planFormData.pathsText;
|
||||
} else {
|
||||
planFormData.paths = [];
|
||||
}
|
||||
|
||||
const plan = fromJson(PlanSchema, planFormData, {
|
||||
ignoreUnknownFields: false,
|
||||
});
|
||||
@@ -342,8 +219,8 @@ export const AddPlanModal = ({ template }: { template: Plan | null }) => {
|
||||
<Form
|
||||
autoComplete="off"
|
||||
form={form}
|
||||
labelCol={{ span: 6 }}
|
||||
wrapperCol={{ span: 16 }}
|
||||
labelCol={{ flex: "160px" }}
|
||||
wrapperCol={{ flex: "auto" }}
|
||||
disabled={confirmLoading}
|
||||
>
|
||||
{/* Plan.id */}
|
||||
@@ -406,30 +283,73 @@ export const AddPlanModal = ({ template }: { template: Plan | null }) => {
|
||||
|
||||
{/* Plan.paths */}
|
||||
<Form.Item
|
||||
name="pathsText"
|
||||
label={m.add_plan_modal_field_paths()}
|
||||
required={true}
|
||||
tooltip={m.add_plan_modal_field_paths_tooltip()}
|
||||
rules={[
|
||||
{
|
||||
validator: async (_, value) => {
|
||||
if (!value || !value.trim()) {
|
||||
throw new Error(m.add_plan_modal_validation_paths_required());
|
||||
}
|
||||
const paths = value
|
||||
.split("\n")
|
||||
.map((p: string) => p.trim())
|
||||
.filter((p: string) => p.length > 0);
|
||||
if (paths.length === 0) {
|
||||
throw new Error(
|
||||
m.add_plan_modal_validation_paths_valid_required()
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
]}
|
||||
>
|
||||
<PathsTextArea />
|
||||
<Form.List
|
||||
name="paths"
|
||||
rules={[
|
||||
{
|
||||
validator: async (_, paths) => {
|
||||
if (!paths || paths.length === 0) {
|
||||
throw new Error(
|
||||
m.add_plan_modal_validation_paths_required()
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
]}
|
||||
initialValue={template ? template.paths : []}
|
||||
>
|
||||
{(fields, { add, remove }, { errors }) => (
|
||||
<>
|
||||
{fields.map((field, index) => {
|
||||
const { key, ...restField } = field;
|
||||
return (
|
||||
<Form.Item required={false} key={field.key}>
|
||||
<Flex gap="small" align="center">
|
||||
<Form.Item
|
||||
{...restField}
|
||||
validateTrigger={["onChange", "onBlur"]}
|
||||
initialValue={""}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message:
|
||||
m.add_plan_modal_validation_paths_valid_required(),
|
||||
},
|
||||
]}
|
||||
noStyle
|
||||
>
|
||||
<URIAutocomplete
|
||||
style={{ flex: 1 }}
|
||||
onBlur={() => form.validateFields()}
|
||||
globAllowed={true}
|
||||
/>
|
||||
</Form.Item>
|
||||
<MinusCircleOutlined
|
||||
className="dynamic-delete-button"
|
||||
onClick={() => remove(field.name)}
|
||||
/>
|
||||
</Flex>
|
||||
</Form.Item>
|
||||
);
|
||||
})}
|
||||
<Form.Item>
|
||||
<Button
|
||||
type="dashed"
|
||||
onClick={() => add()}
|
||||
block
|
||||
icon={<PlusOutlined />}
|
||||
>
|
||||
Add Path
|
||||
</Button>
|
||||
<Form.ErrorList errors={errors} />
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
</Form.List>
|
||||
</Form.Item>
|
||||
|
||||
{/* Plan.excludes */}
|
||||
@@ -460,28 +380,29 @@ export const AddPlanModal = ({ template }: { template: Plan | null }) => {
|
||||
const { key, ...restField } = field;
|
||||
return (
|
||||
<Form.Item required={false} key={field.key}>
|
||||
<Form.Item
|
||||
{...restField}
|
||||
validateTrigger={["onChange", "onBlur"]}
|
||||
initialValue={""}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
},
|
||||
]}
|
||||
noStyle
|
||||
>
|
||||
<URIAutocomplete
|
||||
style={{ width: "90%" }}
|
||||
onBlur={() => form.validateFields()}
|
||||
globAllowed={true}
|
||||
<Flex gap="small" align="center">
|
||||
<Form.Item
|
||||
{...restField}
|
||||
validateTrigger={["onChange", "onBlur"]}
|
||||
initialValue={""}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
},
|
||||
]}
|
||||
noStyle
|
||||
>
|
||||
<URIAutocomplete
|
||||
style={{ flex: 1 }}
|
||||
onBlur={() => form.validateFields()}
|
||||
globAllowed={true}
|
||||
/>
|
||||
</Form.Item>
|
||||
<MinusCircleOutlined
|
||||
className="dynamic-delete-button"
|
||||
onClick={() => remove(field.name)}
|
||||
/>
|
||||
</Form.Item>
|
||||
<MinusCircleOutlined
|
||||
className="dynamic-delete-button"
|
||||
onClick={() => remove(field.name)}
|
||||
style={{ paddingLeft: "5px" }}
|
||||
/>
|
||||
</Flex>
|
||||
</Form.Item>
|
||||
);
|
||||
})}
|
||||
@@ -489,7 +410,7 @@ export const AddPlanModal = ({ template }: { template: Plan | null }) => {
|
||||
<Button
|
||||
type="dashed"
|
||||
onClick={() => add()}
|
||||
style={{ width: "90%" }}
|
||||
block
|
||||
icon={<PlusOutlined />}
|
||||
>
|
||||
{m.add_plan_modal_field_excludes_add()}
|
||||
@@ -529,28 +450,29 @@ export const AddPlanModal = ({ template }: { template: Plan | null }) => {
|
||||
const { key, ...restField } = field;
|
||||
return (
|
||||
<Form.Item required={false} key={field.key}>
|
||||
<Form.Item
|
||||
{...restField}
|
||||
validateTrigger={["onChange", "onBlur"]}
|
||||
initialValue={""}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
},
|
||||
]}
|
||||
noStyle
|
||||
>
|
||||
<URIAutocomplete
|
||||
style={{ width: "90%" }}
|
||||
onBlur={() => form.validateFields()}
|
||||
globAllowed={true}
|
||||
<Flex gap="small" align="center">
|
||||
<Form.Item
|
||||
{...restField}
|
||||
validateTrigger={["onChange", "onBlur"]}
|
||||
initialValue={""}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
},
|
||||
]}
|
||||
noStyle
|
||||
>
|
||||
<URIAutocomplete
|
||||
style={{ flex: 1 }}
|
||||
onBlur={() => form.validateFields()}
|
||||
globAllowed={true}
|
||||
/>
|
||||
</Form.Item>
|
||||
<MinusCircleOutlined
|
||||
className="dynamic-delete-button"
|
||||
onClick={() => remove(field.name)}
|
||||
/>
|
||||
</Form.Item>
|
||||
<MinusCircleOutlined
|
||||
className="dynamic-delete-button"
|
||||
onClick={() => remove(field.name)}
|
||||
style={{ paddingLeft: "5px" }}
|
||||
/>
|
||||
</Flex>
|
||||
</Form.Item>
|
||||
);
|
||||
})}
|
||||
@@ -558,7 +480,7 @@ export const AddPlanModal = ({ template }: { template: Plan | null }) => {
|
||||
<Button
|
||||
type="dashed"
|
||||
onClick={() => add()}
|
||||
style={{ width: "90%" }}
|
||||
block
|
||||
icon={<PlusOutlined />}
|
||||
>
|
||||
{m.add_plan_modal_field_iexcludes_add()}
|
||||
@@ -593,30 +515,31 @@ export const AddPlanModal = ({ template }: { template: Plan | null }) => {
|
||||
const { key, ...restField } = field;
|
||||
return (
|
||||
<Form.Item required={false} key={field.key}>
|
||||
<Form.Item
|
||||
{...restField}
|
||||
validateTrigger={["onChange", "onBlur"]}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
whitespace: true,
|
||||
pattern: /^\-\-?.*$/,
|
||||
message:
|
||||
m.add_plan_modal_validation_flag_pattern(),
|
||||
},
|
||||
]}
|
||||
noStyle
|
||||
>
|
||||
<Input
|
||||
placeholder="--flag"
|
||||
style={{ width: "90%" }}
|
||||
<Flex gap="small" align="center">
|
||||
<Form.Item
|
||||
{...restField}
|
||||
validateTrigger={["onChange", "onBlur"]}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
whitespace: true,
|
||||
pattern: /^\-\-?.*$/,
|
||||
message:
|
||||
m.add_plan_modal_validation_flag_pattern(),
|
||||
},
|
||||
]}
|
||||
noStyle
|
||||
>
|
||||
<Input
|
||||
placeholder="--flag"
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
</Form.Item>
|
||||
<MinusCircleOutlined
|
||||
className="dynamic-delete-button"
|
||||
onClick={() => remove(index)}
|
||||
/>
|
||||
</Form.Item>
|
||||
<MinusCircleOutlined
|
||||
className="dynamic-delete-button"
|
||||
onClick={() => remove(index)}
|
||||
style={{ paddingLeft: "5px" }}
|
||||
/>
|
||||
</Flex>
|
||||
</Form.Item>
|
||||
);
|
||||
})}
|
||||
@@ -624,7 +547,7 @@ export const AddPlanModal = ({ template }: { template: Plan | null }) => {
|
||||
<Button
|
||||
type="dashed"
|
||||
onClick={() => add()}
|
||||
style={{ width: "90%" }}
|
||||
block
|
||||
icon={<PlusOutlined />}
|
||||
>
|
||||
{m.add_plan_modal_field_backup_flags_add()}
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
Checkbox,
|
||||
Select,
|
||||
Space,
|
||||
Flex,
|
||||
} from "antd";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useShowModal } from "../components/ModalManager";
|
||||
@@ -243,8 +244,8 @@ export const AddRepoModal = ({ template }: { template: Repo | null }) => {
|
||||
<Form
|
||||
autoComplete="off"
|
||||
form={form}
|
||||
labelCol={{ span: 4 }}
|
||||
wrapperCol={{ span: 18 }}
|
||||
labelCol={{ flex: "160px" }}
|
||||
wrapperCol={{ flex: "auto" }}
|
||||
disabled={confirmLoading}
|
||||
>
|
||||
{/* Repo.id */}
|
||||
@@ -350,41 +351,34 @@ export const AddRepoModal = ({ template }: { template: Repo | null }) => {
|
||||
}
|
||||
>
|
||||
<Form.Item label={m.add_repo_modal_field_password()}>
|
||||
<Row>
|
||||
<Col span={16}>
|
||||
<Form.Item<Repo>
|
||||
hasFeedback
|
||||
name="password"
|
||||
validateTrigger={["onChange", "onBlur"]}
|
||||
>
|
||||
<Input disabled={!!template} />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col
|
||||
span={7}
|
||||
offset={1}
|
||||
style={{ display: "flex", justifyContent: "left" }}
|
||||
<Flex gap="small">
|
||||
<Form.Item<Repo>
|
||||
hasFeedback
|
||||
name="password"
|
||||
validateTrigger={["onChange", "onBlur"]}
|
||||
noStyle
|
||||
>
|
||||
<Button
|
||||
type="text"
|
||||
onClick={() => {
|
||||
if (template) return;
|
||||
form.setFieldsValue({
|
||||
password: cryptoRandomPassword(),
|
||||
});
|
||||
}}
|
||||
>
|
||||
{m.add_repo_modal_button_generate()}
|
||||
</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
<Input disabled={!!template} style={{ flex: 1 }} />
|
||||
</Form.Item>
|
||||
<Button
|
||||
type="text"
|
||||
onClick={() => {
|
||||
if (template) return;
|
||||
form.setFieldsValue({
|
||||
password: cryptoRandomPassword(),
|
||||
});
|
||||
}}
|
||||
>
|
||||
{m.add_repo_modal_button_generate()}
|
||||
</Button>
|
||||
</Flex>
|
||||
</Form.Item>
|
||||
</Tooltip>
|
||||
|
||||
{/* Repo.env */}
|
||||
<Tooltip
|
||||
title={
|
||||
m.add_repo_modal_field_env_vars_tooltip()
|
||||
m.add_repo_modal_field_env_vars_tooltip({ MY_FOO_VAR: "$MY_FOO_VAR" })
|
||||
}
|
||||
>
|
||||
<Form.Item label={m.add_repo_modal_field_env_vars()}>
|
||||
@@ -404,31 +398,31 @@ export const AddRepoModal = ({ template }: { template: Repo | null }) => {
|
||||
const { key, ...restField } = field;
|
||||
return (
|
||||
<Form.Item key={field.key}>
|
||||
<Form.Item
|
||||
{...restField}
|
||||
validateTrigger={["onChange", "onBlur"]}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
whitespace: true,
|
||||
pattern: /^[\w-]+=.*$/,
|
||||
message:
|
||||
m.add_repo_modal_error_env_format(),
|
||||
},
|
||||
]}
|
||||
noStyle
|
||||
>
|
||||
<Input
|
||||
placeholder="KEY=VALUE"
|
||||
onBlur={() => form.validateFields()}
|
||||
style={{ width: "90%" }}
|
||||
<Flex gap="small" align="center">
|
||||
<Form.Item
|
||||
{...restField}
|
||||
validateTrigger={["onChange", "onBlur"]}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
whitespace: true,
|
||||
pattern: /^[\w-]+=.*$/,
|
||||
message: m.add_repo_modal_error_env_format(),
|
||||
},
|
||||
]}
|
||||
noStyle
|
||||
>
|
||||
<Input
|
||||
placeholder="KEY=VALUE"
|
||||
onBlur={() => form.validateFields()}
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
</Form.Item>
|
||||
<MinusCircleOutlined
|
||||
className="dynamic-delete-button"
|
||||
onClick={() => remove(index)}
|
||||
/>
|
||||
</Form.Item>
|
||||
<MinusCircleOutlined
|
||||
className="dynamic-delete-button"
|
||||
onClick={() => remove(index)}
|
||||
style={{ paddingLeft: "5px" }}
|
||||
/>
|
||||
</Flex>
|
||||
</Form.Item>
|
||||
);
|
||||
})}
|
||||
@@ -436,7 +430,7 @@ export const AddRepoModal = ({ template }: { template: Repo | null }) => {
|
||||
<Button
|
||||
type="dashed"
|
||||
onClick={() => add("")}
|
||||
style={{ width: "90%" }}
|
||||
block
|
||||
icon={<PlusOutlined />}
|
||||
>
|
||||
{m.add_repo_modal_button_set_env()}
|
||||
@@ -458,30 +452,30 @@ export const AddRepoModal = ({ template }: { template: Repo | null }) => {
|
||||
const { key, ...restField } = field;
|
||||
return (
|
||||
<Form.Item required={false} key={field.key}>
|
||||
<Form.Item
|
||||
{...restField}
|
||||
validateTrigger={["onChange", "onBlur"]}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
whitespace: true,
|
||||
pattern: /^\-\-?.*$/,
|
||||
message:
|
||||
m.add_repo_modal_error_flag_format(),
|
||||
},
|
||||
]}
|
||||
noStyle
|
||||
>
|
||||
<Input
|
||||
placeholder="--flag"
|
||||
style={{ width: "90%" }}
|
||||
<Flex gap="small" align="center">
|
||||
<Form.Item
|
||||
{...restField}
|
||||
validateTrigger={["onChange", "onBlur"]}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
whitespace: true,
|
||||
pattern: /^\-\-?.*$/,
|
||||
message: m.add_repo_modal_error_flag_format(),
|
||||
},
|
||||
]}
|
||||
noStyle
|
||||
>
|
||||
<Input
|
||||
placeholder="--flag"
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
</Form.Item>
|
||||
<MinusCircleOutlined
|
||||
className="dynamic-delete-button"
|
||||
onClick={() => remove(index)}
|
||||
/>
|
||||
</Form.Item>
|
||||
<MinusCircleOutlined
|
||||
className="dynamic-delete-button"
|
||||
onClick={() => remove(index)}
|
||||
style={{ paddingLeft: "5px" }}
|
||||
/>
|
||||
</Flex>
|
||||
</Form.Item>
|
||||
);
|
||||
})}
|
||||
@@ -489,7 +483,7 @@ export const AddRepoModal = ({ template }: { template: Repo | null }) => {
|
||||
<Button
|
||||
type="dashed"
|
||||
onClick={() => add()}
|
||||
style={{ width: "90%" }}
|
||||
block
|
||||
icon={<PlusOutlined />}
|
||||
>
|
||||
{m.add_repo_modal_button_set_flag()}
|
||||
@@ -501,6 +495,23 @@ export const AddRepoModal = ({ template }: { template: Repo | null }) => {
|
||||
</Form.List>
|
||||
</Form.Item>
|
||||
|
||||
{/* Repo.autoUnlock */}
|
||||
<Form.Item
|
||||
label={
|
||||
<Tooltip
|
||||
title={
|
||||
m.add_repo_modal_field_auto_unlock_tooltip()
|
||||
}
|
||||
>
|
||||
{m.add_repo_modal_field_auto_unlock()}
|
||||
</Tooltip>
|
||||
}
|
||||
name="autoUnlock"
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Checkbox />
|
||||
</Form.Item>
|
||||
|
||||
{/* Repo.prunePolicy */}
|
||||
<Form.Item
|
||||
label={
|
||||
@@ -669,22 +680,6 @@ export const AddRepoModal = ({ template }: { template: Repo | null }) => {
|
||||
</Form.Item>
|
||||
)}
|
||||
|
||||
<Form.Item
|
||||
label={
|
||||
<Tooltip
|
||||
title={
|
||||
m.add_repo_modal_field_auto_unlock_tooltip()
|
||||
}
|
||||
>
|
||||
{m.add_repo_modal_field_auto_unlock()}
|
||||
</Tooltip>
|
||||
}
|
||||
name="autoUnlock"
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Checkbox />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label={<Tooltip title={hooksListTooltipText}>{m.add_plan_modal_field_hooks()}</Tooltip>}
|
||||
>
|
||||
|
||||
@@ -98,6 +98,7 @@ export const SettingsModal = () => {
|
||||
user.passwordBcrypt = hash.value;
|
||||
delete user.needsBcrypt;
|
||||
}
|
||||
delete user.isExisting;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user