|
|
@@ -1,8 +1,8 @@
|
|
|
import React from "react";
|
|
|
-import { KeyValueType } from "main/home/cluster-dashboard/env-groups/EnvGroupArray";
|
|
|
+import { type KeyValueType } from "main/home/cluster-dashboard/env-groups/EnvGroupArray";
|
|
|
import Tooltip from "components/porter/Tooltip";
|
|
|
import { Controller, useFormContext } from "react-hook-form";
|
|
|
-import { PorterAppFormData } from "lib/porter-apps";
|
|
|
+import { type PorterAppFormData } from "lib/porter-apps";
|
|
|
import Spacer from "components/porter/Spacer";
|
|
|
import Text from "components/porter/Text";
|
|
|
import styled from "styled-components";
|
|
|
@@ -12,15 +12,31 @@ type Props = {
|
|
|
index: number;
|
|
|
remove: () => void;
|
|
|
isKeyOverriding: (key: string) => boolean;
|
|
|
+ invalidKey: (key: string) => boolean;
|
|
|
}
|
|
|
const EnvVarRow: React.FC<Props> = ({
|
|
|
entry,
|
|
|
index,
|
|
|
remove,
|
|
|
isKeyOverriding,
|
|
|
+ invalidKey,
|
|
|
}) => {
|
|
|
- const { control: appControl, watch } = useFormContext<PorterAppFormData>();
|
|
|
+ const { control: appControl, watch, setError, clearErrors, formState: { errors } } = useFormContext<PorterAppFormData>();
|
|
|
const hidden = watch(`app.env.${index}.hidden`);
|
|
|
+ const keys = watch(`app.env.${index}.key`);
|
|
|
+
|
|
|
+ const validateKey = (key: string): boolean => {
|
|
|
+ const isValid = /^[A-Za-z]/.test(key);
|
|
|
+ if (!isValid) {
|
|
|
+ setError(`app.env.${index}.key`, {
|
|
|
+ type: "manual",
|
|
|
+ message: "Key must begin with a letter",
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ clearErrors(`app.env.${index}.key`);
|
|
|
+ }
|
|
|
+ return isValid;
|
|
|
+ };
|
|
|
|
|
|
return (
|
|
|
<InputWrapper>
|
|
|
@@ -42,15 +58,21 @@ const EnvVarRow: React.FC<Props> = ({
|
|
|
<Controller
|
|
|
name={`app.env.${index}.key`}
|
|
|
control={appControl}
|
|
|
- render={({ field: { value, onChange } }) => (
|
|
|
- <Input
|
|
|
- placeholder="ex: key"
|
|
|
- width="270px"
|
|
|
- value={value}
|
|
|
- onChange={(e) => onChange(e.target.value)}
|
|
|
- spellCheck={false}
|
|
|
- override={isKeyOverriding(value)}
|
|
|
- />
|
|
|
+ render={({ field: { value, onChange }, fieldState: { error } }) => (
|
|
|
+ <>
|
|
|
+ <Input
|
|
|
+ placeholder="ex: key"
|
|
|
+ width="270px"
|
|
|
+ value={value}
|
|
|
+ onChange={(e) => {
|
|
|
+ validateKey(e.target.value);
|
|
|
+ onChange(e.target.value);
|
|
|
+ }}
|
|
|
+ spellCheck={false}
|
|
|
+ override={isKeyOverriding(value)}
|
|
|
+ style={error ? { borderColor: '#fbc902' } : {}}
|
|
|
+ />
|
|
|
+ </>
|
|
|
)}
|
|
|
/>
|
|
|
)}
|
|
|
@@ -80,7 +102,7 @@ const EnvVarRow: React.FC<Props> = ({
|
|
|
placeholder="ex: value"
|
|
|
width="270px"
|
|
|
value={value}
|
|
|
- onChange={(e) => onChange(e.target.value)}
|
|
|
+ onChange={(e) => { onChange(e.target.value); }}
|
|
|
type={"password"}
|
|
|
spellCheck={false}
|
|
|
override={isKeyOverriding(entry.key)}
|
|
|
@@ -97,7 +119,7 @@ const EnvVarRow: React.FC<Props> = ({
|
|
|
placeholder="ex: value"
|
|
|
width="270px"
|
|
|
value={value}
|
|
|
- onChange={(e) => onChange(e.target.value)}
|
|
|
+ onChange={(e) => { onChange(e.target.value); }}
|
|
|
rows={value?.split("\n").length}
|
|
|
spellCheck={false}
|
|
|
override={isKeyOverriding(entry.key)}
|
|
|
@@ -148,6 +170,12 @@ const EnvVarRow: React.FC<Props> = ({
|
|
|
>
|
|
|
<i className="material-icons">cancel</i>
|
|
|
</DeleteButton>
|
|
|
+ {!invalidKey(keys) && (
|
|
|
+ <>
|
|
|
+ <Spacer x={1} inline />
|
|
|
+ <Text color={'#fbc902'}>Key must begin with a letter</Text>
|
|
|
+ </>
|
|
|
+ )}
|
|
|
{isKeyOverriding(entry.key) && (
|
|
|
<>
|
|
|
<Spacer x={1} inline />
|
|
|
@@ -159,12 +187,13 @@ const EnvVarRow: React.FC<Props> = ({
|
|
|
};
|
|
|
export default EnvVarRow;
|
|
|
|
|
|
+
|
|
|
const InputWrapper = styled.div`
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- margin-top: 5px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ margin-top: 5px;
|
|
|
|
|
|
-`;
|
|
|
+ `;
|
|
|
|
|
|
type InputProps = {
|
|
|
disabled?: boolean;
|
|
|
@@ -173,91 +202,91 @@ type InputProps = {
|
|
|
};
|
|
|
|
|
|
const Input = styled.input<InputProps>`
|
|
|
- outline: none;
|
|
|
- border: none;
|
|
|
- margin-bottom: 5px;
|
|
|
- font-size: 13px;
|
|
|
- background: #ffffff11;
|
|
|
- border: ${(props) => (props.override ? '2px solid #6b74d6' : ' 1px solid #ffffff55')};
|
|
|
- border-radius: 3px;
|
|
|
- width: ${(props) => props.width ? props.width : "270px"};
|
|
|
- color: ${(props) => props.disabled ? "#ffffff44" : "white"};
|
|
|
- padding: 5px 10px;
|
|
|
- height: 35px;
|
|
|
- `;
|
|
|
+ outline: none;
|
|
|
+ border: none;
|
|
|
+ margin-bottom: 5px;
|
|
|
+ font-size: 13px;
|
|
|
+ background: #ffffff11;
|
|
|
+ border: ${(props) => (props.override ? '2px solid #6b74d6' : ' 1px solid #ffffff55')};
|
|
|
+ border-radius: 3px;
|
|
|
+ width: ${(props) => props.width ? props.width : "270px"};
|
|
|
+ color: ${(props) => props.disabled ? "#ffffff44" : "white"};
|
|
|
+ padding: 5px 10px;
|
|
|
+ height: 35px;
|
|
|
+ `;
|
|
|
|
|
|
const MultiLineInputer = styled.textarea<InputProps>`
|
|
|
- outline: none;
|
|
|
- border: none;
|
|
|
- margin-bottom: 5px;
|
|
|
- font-size: 13px;
|
|
|
- background: #ffffff11;
|
|
|
- border: ${(props) => (props.override ? '2px solid #6b74d6' : ' 1px solid #ffffff55')};
|
|
|
- border-radius: 3px;
|
|
|
- min-width: ${(props) => (props.width ? props.width : "270px")};
|
|
|
- max-width: ${(props) => (props.width ? props.width : "270px")};
|
|
|
- color: ${(props) => (props.disabled ? "#ffffff44" : "white")};
|
|
|
- padding: 8px 10px 5px 10px;
|
|
|
- min-height: 35px;
|
|
|
- max-height: 100px;
|
|
|
- white-space: nowrap;
|
|
|
+ outline: none;
|
|
|
+ border: none;
|
|
|
+ margin-bottom: 5px;
|
|
|
+ font-size: 13px;
|
|
|
+ background: #ffffff11;
|
|
|
+ border: ${(props) => (props.override ? '2px solid #6b74d6' : ' 1px solid #ffffff55')};
|
|
|
+ border-radius: 3px;
|
|
|
+ min-width: ${(props) => (props.width ? props.width : "270px")};
|
|
|
+ max-width: ${(props) => (props.width ? props.width : "270px")};
|
|
|
+ color: ${(props) => (props.disabled ? "#ffffff44" : "white")};
|
|
|
+ padding: 8px 10px 5px 10px;
|
|
|
+ min-height: 35px;
|
|
|
+ max-height: 100px;
|
|
|
+ white-space: nowrap;
|
|
|
|
|
|
- ::-webkit-scrollbar {
|
|
|
- width: 8px;
|
|
|
- :horizontal {
|
|
|
- height: 8px;
|
|
|
+ ::-webkit-scrollbar {
|
|
|
+ width: 8px;
|
|
|
+ :horizontal {
|
|
|
+ height: 8px;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- ::-webkit-scrollbar-corner {
|
|
|
- width: 10px;
|
|
|
- background: #ffffff11;
|
|
|
- color: white;
|
|
|
+ ::-webkit-scrollbar-corner {
|
|
|
+ width: 10px;
|
|
|
+ background: #ffffff11;
|
|
|
+ color: white;
|
|
|
}
|
|
|
|
|
|
- ::-webkit-scrollbar-track {
|
|
|
- width: 10px;
|
|
|
- -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
|
|
|
- box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
|
|
|
+ ::-webkit-scrollbar-track {
|
|
|
+ width: 10px;
|
|
|
+ -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
|
|
|
+ box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
|
|
|
}
|
|
|
|
|
|
- ::-webkit-scrollbar-thumb {
|
|
|
- background-color: darkgrey;
|
|
|
- outline: 1px solid slategrey;
|
|
|
+ ::-webkit-scrollbar-thumb {
|
|
|
+ background - color: darkgrey;
|
|
|
+ outline: 1px solid slategrey;
|
|
|
}
|
|
|
-`;
|
|
|
+ `;
|
|
|
|
|
|
const DeleteButton = styled.div`
|
|
|
- width: 15px;
|
|
|
- height: 15px;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- margin-left: 8px;
|
|
|
- margin-top: -3px;
|
|
|
- justify-content: center;
|
|
|
+ width: 15px;
|
|
|
+ height: 15px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ margin-left: 8px;
|
|
|
+ margin-top: -3px;
|
|
|
+ justify-content: center;
|
|
|
|
|
|
> i {
|
|
|
- font-size: 17px;
|
|
|
- color: #ffffff44;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
- cursor: pointer;
|
|
|
- :hover {
|
|
|
- color: #ffffff88;
|
|
|
+ font - size: 17px;
|
|
|
+ color: #ffffff44;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ cursor: pointer;
|
|
|
+ :hover {
|
|
|
+ color: #ffffff88;
|
|
|
}
|
|
|
}
|
|
|
-`;
|
|
|
+ `;
|
|
|
|
|
|
const HideButton = styled(DeleteButton)`
|
|
|
- margin-top: -5px;
|
|
|
+ margin-top: -5px;
|
|
|
> i {
|
|
|
- font-size: 19px;
|
|
|
- cursor: ${(props: { disabled: boolean }) =>
|
|
|
+ font - size: 19px;
|
|
|
+ cursor: ${(props: { disabled: boolean }) =>
|
|
|
props.disabled ? "default" : "pointer"};
|
|
|
- :hover {
|
|
|
- color: ${(props: { disabled: boolean }) =>
|
|
|
+ :hover {
|
|
|
+ color: ${(props: { disabled: boolean }) =>
|
|
|
props.disabled ? "#ffffff44" : "#ffffff88"};
|
|
|
}
|
|
|
}
|
|
|
-`;
|
|
|
+ `;
|