Refactoring: merge WorkflowParameterEditPanel and WorkflowParameterAddPanel (#3750)
This commit is contained in:
committed by
GitHub
parent
a2cef7985d
commit
117b2469e4
@@ -1,615 +0,0 @@
|
|||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { Checkbox } from "@/components/ui/checkbox";
|
|
||||||
import { Input } from "@/components/ui/input";
|
|
||||||
import { Label } from "@/components/ui/label";
|
|
||||||
import { ScrollArea, ScrollAreaViewport } from "@/components/ui/scroll-area";
|
|
||||||
import {
|
|
||||||
Select,
|
|
||||||
SelectContent,
|
|
||||||
SelectGroup,
|
|
||||||
SelectItem,
|
|
||||||
SelectTrigger,
|
|
||||||
SelectValue,
|
|
||||||
} from "@/components/ui/select";
|
|
||||||
import { toast } from "@/components/ui/use-toast";
|
|
||||||
import CloudContext from "@/store/CloudContext";
|
|
||||||
import { Cross2Icon } from "@radix-ui/react-icons";
|
|
||||||
import { useContext, useState } from "react";
|
|
||||||
import { CredentialParameterSourceSelector } from "../../components/CredentialParameterSourceSelector";
|
|
||||||
import { SourceParameterKeySelector } from "../../components/SourceParameterKeySelector";
|
|
||||||
import {
|
|
||||||
WorkflowEditorParameterType,
|
|
||||||
WorkflowParameterValueType,
|
|
||||||
} from "../../types/workflowTypes";
|
|
||||||
import { WorkflowParameterInput } from "../../WorkflowParameterInput";
|
|
||||||
import { ParametersState } from "../types";
|
|
||||||
import { getDefaultValueForParameterType } from "../workflowEditorUtils";
|
|
||||||
import { validateBitwardenLoginCredential } from "./util";
|
|
||||||
import { HelpTooltip } from "@/components/HelpTooltip";
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
type: WorkflowEditorParameterType;
|
|
||||||
onClose: () => void;
|
|
||||||
onSave: (value: ParametersState[number]) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
const workflowParameterTypeOptions = [
|
|
||||||
{ label: "string", value: WorkflowParameterValueType.String },
|
|
||||||
{ label: "float", value: WorkflowParameterValueType.Float },
|
|
||||||
{ label: "integer", value: WorkflowParameterValueType.Integer },
|
|
||||||
{ label: "boolean", value: WorkflowParameterValueType.Boolean },
|
|
||||||
{ label: "file", value: WorkflowParameterValueType.FileURL },
|
|
||||||
{ label: "JSON", value: WorkflowParameterValueType.JSON },
|
|
||||||
];
|
|
||||||
|
|
||||||
function header(type: WorkflowEditorParameterType) {
|
|
||||||
if (type === "workflow") {
|
|
||||||
return "Add Input Parameter";
|
|
||||||
}
|
|
||||||
if (type === "credential") {
|
|
||||||
return "Add Credential Parameter";
|
|
||||||
}
|
|
||||||
if (type === "secret") {
|
|
||||||
return "Add Secret Parameter";
|
|
||||||
}
|
|
||||||
if (type === "creditCardData") {
|
|
||||||
return "Add Credit Card Parameter";
|
|
||||||
}
|
|
||||||
return "Add Context Parameter";
|
|
||||||
}
|
|
||||||
|
|
||||||
function WorkflowParameterAddPanel({ type, onClose, onSave }: Props) {
|
|
||||||
const reservedKeys = ["current_item", "current_value", "current_index"];
|
|
||||||
const isCloud = useContext(CloudContext);
|
|
||||||
const [key, setKey] = useState("");
|
|
||||||
const hasWhitespace = /\s/.test(key);
|
|
||||||
const [urlParameterKey, setUrlParameterKey] = useState("");
|
|
||||||
const [description, setDescription] = useState("");
|
|
||||||
const [bitwardenCollectionId, setBitwardenCollectionId] = useState("");
|
|
||||||
const [bitwardenLoginCredentialItemId, setBitwardenLoginCredentialItemId] =
|
|
||||||
useState("");
|
|
||||||
const [parameterType, setParameterType] =
|
|
||||||
useState<WorkflowParameterValueType>("string");
|
|
||||||
const [defaultValueState, setDefaultValueState] = useState<{
|
|
||||||
hasDefaultValue: boolean;
|
|
||||||
defaultValue: unknown;
|
|
||||||
}>({
|
|
||||||
hasDefaultValue: false,
|
|
||||||
defaultValue: null,
|
|
||||||
});
|
|
||||||
const [sourceParameterKey, setSourceParameterKey] = useState<
|
|
||||||
string | undefined
|
|
||||||
>(undefined);
|
|
||||||
|
|
||||||
const [credentialType, setCredentialType] = useState<
|
|
||||||
"bitwarden" | "skyvern" | "onepassword" | "azurevault"
|
|
||||||
>("skyvern");
|
|
||||||
const [vaultId, setVaultId] = useState("");
|
|
||||||
const [itemId, setItemId] = useState("");
|
|
||||||
|
|
||||||
const [identityKey, setIdentityKey] = useState("");
|
|
||||||
const [identityFields, setIdentityFields] = useState("");
|
|
||||||
const [sensitiveInformationItemId, setSensitiveInformationItemId] =
|
|
||||||
useState("");
|
|
||||||
|
|
||||||
const [credentialId, setCredentialId] = useState("");
|
|
||||||
|
|
||||||
const [azureVaultName, setAzureVaultName] = useState("");
|
|
||||||
const [azureUsernameKey, setAzureUsernameKey] = useState("");
|
|
||||||
const [azurePasswordKey, setAzurePasswordKey] = useState("");
|
|
||||||
const [azureTotpSecretKey, setAzureTotpKey] = useState("");
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ScrollArea>
|
|
||||||
<ScrollAreaViewport className="max-h-[500px]">
|
|
||||||
<div className="space-y-4 p-1 px-4">
|
|
||||||
<header className="flex items-center justify-between">
|
|
||||||
<span>{header(type)}</span>
|
|
||||||
<Cross2Icon className="h-6 w-6 cursor-pointer" onClick={onClose} />
|
|
||||||
</header>
|
|
||||||
<div className="space-y-1">
|
|
||||||
<Label className="text-xs text-slate-300">Key</Label>
|
|
||||||
<Input value={key} onChange={(e) => setKey(e.target.value)} />
|
|
||||||
{hasWhitespace && (
|
|
||||||
<p className="text-xs text-destructive">
|
|
||||||
Spaces are not allowed, consider using _
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="space-y-1">
|
|
||||||
<Label className="text-xs text-slate-300">Description</Label>
|
|
||||||
<Input
|
|
||||||
value={description}
|
|
||||||
onChange={(e) => setDescription(e.target.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{type === "workflow" && (
|
|
||||||
<>
|
|
||||||
<div className="space-y-1">
|
|
||||||
<Label className="text-xs">Value Type</Label>
|
|
||||||
<Select
|
|
||||||
value={parameterType}
|
|
||||||
onValueChange={(value) => {
|
|
||||||
setParameterType(value as WorkflowParameterValueType);
|
|
||||||
setDefaultValueState((state) => {
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
defaultValue: getDefaultValueForParameterType(
|
|
||||||
value as WorkflowParameterValueType,
|
|
||||||
),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<SelectTrigger className="w-full">
|
|
||||||
<SelectValue placeholder="Select a type" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectGroup>
|
|
||||||
{workflowParameterTypeOptions.map((option) => (
|
|
||||||
<SelectItem key={option.value} value={option.value}>
|
|
||||||
{option.label}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectGroup>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Checkbox
|
|
||||||
checked={defaultValueState.hasDefaultValue}
|
|
||||||
onCheckedChange={(checked) => {
|
|
||||||
if (!checked) {
|
|
||||||
setDefaultValueState({
|
|
||||||
hasDefaultValue: false,
|
|
||||||
defaultValue: null,
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setDefaultValueState({
|
|
||||||
hasDefaultValue: true,
|
|
||||||
defaultValue:
|
|
||||||
getDefaultValueForParameterType(parameterType),
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Label className="text-xs text-slate-300">
|
|
||||||
Use Default Value
|
|
||||||
</Label>
|
|
||||||
</div>
|
|
||||||
{defaultValueState.hasDefaultValue && (
|
|
||||||
<WorkflowParameterInput
|
|
||||||
onChange={(value) => {
|
|
||||||
if (
|
|
||||||
parameterType === "file_url" &&
|
|
||||||
typeof value === "object" &&
|
|
||||||
value &&
|
|
||||||
"s3uri" in value
|
|
||||||
) {
|
|
||||||
setDefaultValueState((state) => {
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
defaultValue: value.s3uri,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setDefaultValueState((state) => {
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
defaultValue: value,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
type={parameterType}
|
|
||||||
value={defaultValueState.defaultValue}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{type === "credential" && (
|
|
||||||
<>
|
|
||||||
<div className="space-y-1">
|
|
||||||
<Label className="text-xs text-slate-300">
|
|
||||||
Credential Type
|
|
||||||
</Label>
|
|
||||||
<Select
|
|
||||||
value={credentialType}
|
|
||||||
onValueChange={(value) => {
|
|
||||||
setCredentialType(
|
|
||||||
value as
|
|
||||||
| "bitwarden"
|
|
||||||
| "skyvern"
|
|
||||||
| "onepassword"
|
|
||||||
| "azurevault",
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<SelectTrigger className="w-full">
|
|
||||||
<SelectValue placeholder="Select a type" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectGroup>
|
|
||||||
<SelectItem value="skyvern">Skyvern</SelectItem>
|
|
||||||
<SelectItem value="bitwarden">Bitwarden</SelectItem>
|
|
||||||
<SelectItem value="onepassword">1Password</SelectItem>
|
|
||||||
<SelectItem value="azurevault">Azure Vault</SelectItem>
|
|
||||||
</SelectGroup>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{type === "credential" && credentialType === "bitwarden" && (
|
|
||||||
<>
|
|
||||||
<div className="space-y-1">
|
|
||||||
<Label className="text-xs text-slate-300">
|
|
||||||
URL Parameter Key
|
|
||||||
</Label>
|
|
||||||
<Input
|
|
||||||
value={urlParameterKey}
|
|
||||||
onChange={(e) => setUrlParameterKey(e.target.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-1">
|
|
||||||
<Label className="text-xs text-slate-300">Collection ID</Label>
|
|
||||||
<Input
|
|
||||||
value={bitwardenCollectionId}
|
|
||||||
onChange={(e) => setBitwardenCollectionId(e.target.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-1">
|
|
||||||
<Label className="text-xs text-slate-300">Item ID</Label>
|
|
||||||
<Input
|
|
||||||
value={bitwardenLoginCredentialItemId}
|
|
||||||
onChange={(e) =>
|
|
||||||
setBitwardenLoginCredentialItemId(e.target.value)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{type === "credential" && credentialType === "onepassword" && (
|
|
||||||
<>
|
|
||||||
<div className="space-y-1">
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<Label className="text-xs text-slate-300">Vault ID</Label>
|
|
||||||
<HelpTooltip content="You can find the Vault ID and Item ID in the URL when viewing the item in 1Password on the web." />
|
|
||||||
</div>
|
|
||||||
<Input
|
|
||||||
value={vaultId}
|
|
||||||
onChange={(e) => setVaultId(e.target.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-1">
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<Label className="text-xs text-slate-300">Item ID</Label>
|
|
||||||
<HelpTooltip content="Supports all 1Password item types: Logins, Passwords, Credit Cards, Secure Notes, and more." />
|
|
||||||
</div>
|
|
||||||
<Input
|
|
||||||
value={itemId}
|
|
||||||
onChange={(e) => setItemId(e.target.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="rounded-md bg-slate-800 p-2">
|
|
||||||
<div className="space-y-1 text-xs text-slate-400">
|
|
||||||
* Credit Cards: Due to a 1Password limitation, add the
|
|
||||||
expiration date as a separate text field named "Expire Date"
|
|
||||||
in the format MM/YYYY (e.g. 09/2027).
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{type === "credential" && credentialType === "azurevault" && (
|
|
||||||
<>
|
|
||||||
<div className="space-y-1">
|
|
||||||
<Label className="text-xs text-slate-300">Vault Name</Label>
|
|
||||||
<Input
|
|
||||||
value={azureVaultName}
|
|
||||||
onChange={(e) => setAzureVaultName(e.target.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-1">
|
|
||||||
<Label className="text-xs text-slate-300">Username Key</Label>
|
|
||||||
<Input
|
|
||||||
autoComplete="off"
|
|
||||||
value={azureUsernameKey}
|
|
||||||
onChange={(e) => setAzureUsernameKey(e.target.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-1">
|
|
||||||
<Label className="text-xs text-slate-300">Password Key</Label>
|
|
||||||
<Input
|
|
||||||
value={azurePasswordKey}
|
|
||||||
onChange={(e) => setAzurePasswordKey(e.target.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-1">
|
|
||||||
<Label className="text-xs text-slate-300">
|
|
||||||
TOTP Secret Key
|
|
||||||
</Label>
|
|
||||||
<Input
|
|
||||||
value={azureTotpSecretKey}
|
|
||||||
onChange={(e) => setAzureTotpKey(e.target.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{type === "context" && (
|
|
||||||
<div className="space-y-1">
|
|
||||||
<Label className="text-xs text-slate-300">Source Parameter</Label>
|
|
||||||
<SourceParameterKeySelector
|
|
||||||
value={sourceParameterKey}
|
|
||||||
onChange={setSourceParameterKey}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{type === "secret" && (
|
|
||||||
<>
|
|
||||||
<div className="space-y-1">
|
|
||||||
<Label className="text-xs text-slate-300">Identity Key</Label>
|
|
||||||
<Input
|
|
||||||
value={identityKey}
|
|
||||||
onChange={(e) => setIdentityKey(e.target.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-1">
|
|
||||||
<Label className="text-xs text-slate-300">
|
|
||||||
Identity Fields
|
|
||||||
</Label>
|
|
||||||
<Input
|
|
||||||
value={identityFields}
|
|
||||||
onChange={(e) => setIdentityFields(e.target.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-1">
|
|
||||||
<Label className="text-xs text-slate-300">Collection ID</Label>
|
|
||||||
<Input
|
|
||||||
value={bitwardenCollectionId}
|
|
||||||
onChange={(e) => setBitwardenCollectionId(e.target.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{type === "creditCardData" && (
|
|
||||||
<>
|
|
||||||
<div className="space-y-1">
|
|
||||||
<Label className="text-xs text-slate-300">Collection ID</Label>
|
|
||||||
<Input
|
|
||||||
value={bitwardenCollectionId}
|
|
||||||
onChange={(e) => setBitwardenCollectionId(e.target.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-1">
|
|
||||||
<Label className="text-xs text-slate-300">Item ID</Label>
|
|
||||||
<Input
|
|
||||||
value={sensitiveInformationItemId}
|
|
||||||
onChange={(e) =>
|
|
||||||
setSensitiveInformationItemId(e.target.value)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{
|
|
||||||
// temporarily cloud only
|
|
||||||
type === "credential" &&
|
|
||||||
credentialType === "skyvern" &&
|
|
||||||
isCloud && (
|
|
||||||
<div className="space-y-1">
|
|
||||||
<Label className="text-xs text-slate-300">Credential</Label>
|
|
||||||
<CredentialParameterSourceSelector
|
|
||||||
value={credentialId}
|
|
||||||
onChange={(value) => setCredentialId(value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
<div className="flex justify-end">
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
if (!key) {
|
|
||||||
toast({
|
|
||||||
variant: "destructive",
|
|
||||||
title: "Failed to add parameter",
|
|
||||||
description: "Key is required",
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (hasWhitespace) {
|
|
||||||
toast({
|
|
||||||
variant: "destructive",
|
|
||||||
title: "Failed to add parameter",
|
|
||||||
description: "Key cannot contain whitespaces",
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (reservedKeys.includes(key)) {
|
|
||||||
toast({
|
|
||||||
variant: "destructive",
|
|
||||||
title: "Failed to add parameter",
|
|
||||||
description: `${key} is reserved, please use another key`,
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (type === "workflow") {
|
|
||||||
if (
|
|
||||||
parameterType === "json" &&
|
|
||||||
typeof defaultValueState.defaultValue === "string"
|
|
||||||
) {
|
|
||||||
try {
|
|
||||||
JSON.parse(defaultValueState.defaultValue);
|
|
||||||
} catch (e) {
|
|
||||||
toast({
|
|
||||||
variant: "destructive",
|
|
||||||
title: "Failed to add parameter",
|
|
||||||
description: "Invalid JSON for default value",
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const defaultValue =
|
|
||||||
parameterType === "json" &&
|
|
||||||
typeof defaultValueState.defaultValue === "string"
|
|
||||||
? JSON.parse(defaultValueState.defaultValue)
|
|
||||||
: defaultValueState.defaultValue;
|
|
||||||
onSave({
|
|
||||||
key,
|
|
||||||
parameterType: "workflow",
|
|
||||||
dataType: parameterType,
|
|
||||||
description,
|
|
||||||
defaultValue: defaultValueState.hasDefaultValue
|
|
||||||
? defaultValue
|
|
||||||
: null,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (type === "credential" && credentialType === "bitwarden") {
|
|
||||||
const errorMessage = validateBitwardenLoginCredential(
|
|
||||||
bitwardenCollectionId,
|
|
||||||
bitwardenLoginCredentialItemId,
|
|
||||||
urlParameterKey,
|
|
||||||
);
|
|
||||||
if (errorMessage) {
|
|
||||||
toast({
|
|
||||||
variant: "destructive",
|
|
||||||
title: "Failed to save parameter",
|
|
||||||
description: errorMessage,
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
onSave({
|
|
||||||
key,
|
|
||||||
parameterType: "credential",
|
|
||||||
collectionId:
|
|
||||||
bitwardenCollectionId === ""
|
|
||||||
? null
|
|
||||||
: bitwardenCollectionId,
|
|
||||||
itemId:
|
|
||||||
bitwardenLoginCredentialItemId === ""
|
|
||||||
? null
|
|
||||||
: bitwardenLoginCredentialItemId,
|
|
||||||
urlParameterKey:
|
|
||||||
urlParameterKey === "" ? null : urlParameterKey,
|
|
||||||
description,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (type === "credential" && credentialType === "onepassword") {
|
|
||||||
if (vaultId.trim() === "" || itemId.trim() === "") {
|
|
||||||
toast({
|
|
||||||
variant: "destructive",
|
|
||||||
title: "Failed to add parameter",
|
|
||||||
description: "Vault ID and Item ID are required",
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
onSave({
|
|
||||||
key,
|
|
||||||
parameterType: "onepassword",
|
|
||||||
vaultId,
|
|
||||||
itemId,
|
|
||||||
description,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (type === "credential" && credentialType === "azurevault") {
|
|
||||||
if (
|
|
||||||
azureVaultName.trim() === "" ||
|
|
||||||
azureUsernameKey.trim() === "" ||
|
|
||||||
azurePasswordKey.trim() === ""
|
|
||||||
) {
|
|
||||||
toast({
|
|
||||||
variant: "destructive",
|
|
||||||
title: "Failed to add parameter",
|
|
||||||
description:
|
|
||||||
"Azure Vault Name, Username Key and Password Key are required",
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
onSave({
|
|
||||||
key,
|
|
||||||
parameterType: "credential",
|
|
||||||
vaultName: azureVaultName,
|
|
||||||
usernameKey: azureUsernameKey,
|
|
||||||
passwordKey: azurePasswordKey,
|
|
||||||
totpSecretKey:
|
|
||||||
azureTotpSecretKey === "" ? null : azureTotpSecretKey,
|
|
||||||
description: description,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (type === "secret" || type === "creditCardData") {
|
|
||||||
if (!bitwardenCollectionId) {
|
|
||||||
toast({
|
|
||||||
variant: "destructive",
|
|
||||||
title: "Failed to save parameter",
|
|
||||||
description: "Collection ID is required",
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (type === "secret") {
|
|
||||||
onSave({
|
|
||||||
key,
|
|
||||||
parameterType: "secret",
|
|
||||||
collectionId: bitwardenCollectionId,
|
|
||||||
identityFields: identityFields
|
|
||||||
.split(",")
|
|
||||||
.filter((s) => s.length > 0)
|
|
||||||
.map((field) => field.trim()),
|
|
||||||
identityKey,
|
|
||||||
description,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (type === "creditCardData") {
|
|
||||||
onSave({
|
|
||||||
key,
|
|
||||||
parameterType: "creditCardData",
|
|
||||||
collectionId: bitwardenCollectionId,
|
|
||||||
itemId: sensitiveInformationItemId,
|
|
||||||
description,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (type === "context") {
|
|
||||||
if (!sourceParameterKey) {
|
|
||||||
toast({
|
|
||||||
variant: "destructive",
|
|
||||||
title: "Failed to add parameter",
|
|
||||||
description: "Source parameter key is required",
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
onSave({
|
|
||||||
key,
|
|
||||||
parameterType: "context",
|
|
||||||
sourceParameterKey,
|
|
||||||
description,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (type === "credential" && credentialType === "skyvern") {
|
|
||||||
if (!credentialId) {
|
|
||||||
toast({
|
|
||||||
variant: "destructive",
|
|
||||||
title: "Failed to add parameter",
|
|
||||||
description: "Credential is required",
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
onSave({
|
|
||||||
key,
|
|
||||||
parameterType: "credential",
|
|
||||||
credentialId,
|
|
||||||
description,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Save
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</ScrollAreaViewport>
|
|
||||||
</ScrollArea>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export { WorkflowParameterAddPanel };
|
|
||||||
@@ -37,7 +37,7 @@ type Props = {
|
|||||||
type: WorkflowEditorParameterType;
|
type: WorkflowEditorParameterType;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onSave: (value: ParametersState[number]) => void;
|
onSave: (value: ParametersState[number]) => void;
|
||||||
initialValues: ParametersState[number];
|
initialValues?: ParametersState[number];
|
||||||
};
|
};
|
||||||
|
|
||||||
const workflowParameterTypeOptions = [
|
const workflowParameterTypeOptions = [
|
||||||
@@ -50,20 +50,21 @@ const workflowParameterTypeOptions = [
|
|||||||
{ label: "JSON", value: WorkflowParameterValueType.JSON },
|
{ label: "JSON", value: WorkflowParameterValueType.JSON },
|
||||||
];
|
];
|
||||||
|
|
||||||
function header(type: WorkflowEditorParameterType) {
|
function header(type: WorkflowEditorParameterType, isEdit: boolean) {
|
||||||
|
const prefix = isEdit ? "Edit" : "Add";
|
||||||
if (type === "workflow") {
|
if (type === "workflow") {
|
||||||
return "Edit Input Parameter";
|
return `${prefix} Input Parameter`;
|
||||||
}
|
}
|
||||||
if (type === "credential") {
|
if (type === "credential") {
|
||||||
return "Edit Credential Parameter";
|
return `${prefix} Credential Parameter`;
|
||||||
}
|
}
|
||||||
if (type === "secret") {
|
if (type === "secret") {
|
||||||
return "Edit Secret Parameter";
|
return `${prefix} Secret Parameter`;
|
||||||
}
|
}
|
||||||
if (type === "creditCardData") {
|
if (type === "creditCardData") {
|
||||||
return "Edit Credit Card Parameter";
|
return `${prefix} Credit Card Parameter`;
|
||||||
}
|
}
|
||||||
return "Edit Context Parameter";
|
return `${prefix} Context Parameter`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function WorkflowParameterEditPanel({
|
function WorkflowParameterEditPanel({
|
||||||
@@ -72,20 +73,22 @@ function WorkflowParameterEditPanel({
|
|||||||
onSave,
|
onSave,
|
||||||
initialValues,
|
initialValues,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
|
const reservedKeys = ["current_item", "current_value", "current_index"];
|
||||||
const isCloud = useContext(CloudContext);
|
const isCloud = useContext(CloudContext);
|
||||||
const [key, setKey] = useState(initialValues.key);
|
const isEditMode = !!initialValues;
|
||||||
|
const [key, setKey] = useState(initialValues?.key ?? "");
|
||||||
const hasWhitespace = /\s/.test(key);
|
const hasWhitespace = /\s/.test(key);
|
||||||
const isBitwardenCredential =
|
const isBitwardenCredential =
|
||||||
initialValues.parameterType === "credential" &&
|
initialValues?.parameterType === "credential" &&
|
||||||
parameterIsBitwardenCredential(initialValues);
|
parameterIsBitwardenCredential(initialValues);
|
||||||
const isSkyvernCredential =
|
const isSkyvernCredential =
|
||||||
initialValues.parameterType === "credential" &&
|
initialValues?.parameterType === "credential" &&
|
||||||
parameterIsSkyvernCredential(initialValues);
|
parameterIsSkyvernCredential(initialValues);
|
||||||
const isOnePasswordCredential =
|
const isOnePasswordCredential =
|
||||||
initialValues.parameterType === "onepassword" &&
|
initialValues?.parameterType === "onepassword" &&
|
||||||
parameterIsOnePasswordCredential(initialValues);
|
parameterIsOnePasswordCredential(initialValues);
|
||||||
const isAzureVaultCredential =
|
const isAzureVaultCredential =
|
||||||
initialValues.parameterType === "credential" &&
|
initialValues?.parameterType === "credential" &&
|
||||||
parameterIsAzureVaultCredential(initialValues);
|
parameterIsAzureVaultCredential(initialValues);
|
||||||
const [credentialType, setCredentialType] = useState<
|
const [credentialType, setCredentialType] = useState<
|
||||||
"bitwarden" | "skyvern" | "onepassword" | "azurevault"
|
"bitwarden" | "skyvern" | "onepassword" | "azurevault"
|
||||||
@@ -99,21 +102,21 @@ function WorkflowParameterEditPanel({
|
|||||||
: "skyvern",
|
: "skyvern",
|
||||||
);
|
);
|
||||||
const [urlParameterKey, setUrlParameterKey] = useState(
|
const [urlParameterKey, setUrlParameterKey] = useState(
|
||||||
isBitwardenCredential ? initialValues.urlParameterKey ?? "" : "",
|
isBitwardenCredential ? initialValues?.urlParameterKey ?? "" : "",
|
||||||
);
|
);
|
||||||
const [description, setDescription] = useState(
|
const [description, setDescription] = useState(
|
||||||
initialValues.description ?? "",
|
initialValues?.description ?? "",
|
||||||
);
|
);
|
||||||
const [collectionId, setCollectionId] = useState(
|
const [bitwardenCollectionId, setBitwardenCollectionId] = useState(
|
||||||
isBitwardenCredential ||
|
isBitwardenCredential ||
|
||||||
initialValues.parameterType === "secret" ||
|
initialValues?.parameterType === "secret" ||
|
||||||
initialValues.parameterType === "creditCardData"
|
initialValues?.parameterType === "creditCardData"
|
||||||
? initialValues.collectionId ?? ""
|
? initialValues?.collectionId ?? ""
|
||||||
: "",
|
: "",
|
||||||
);
|
);
|
||||||
const [parameterType, setParameterType] =
|
const [parameterType, setParameterType] =
|
||||||
useState<WorkflowParameterValueType>(
|
useState<WorkflowParameterValueType>(
|
||||||
initialValues.parameterType === "workflow"
|
initialValues?.parameterType === "workflow"
|
||||||
? initialValues.dataType
|
? initialValues.dataType
|
||||||
: "string",
|
: "string",
|
||||||
);
|
);
|
||||||
@@ -122,7 +125,7 @@ function WorkflowParameterEditPanel({
|
|||||||
hasDefaultValue: boolean;
|
hasDefaultValue: boolean;
|
||||||
defaultValue: unknown;
|
defaultValue: unknown;
|
||||||
}>(
|
}>(
|
||||||
initialValues.parameterType === "workflow"
|
initialValues?.parameterType === "workflow"
|
||||||
? {
|
? {
|
||||||
hasDefaultValue: initialValues.defaultValue !== null,
|
hasDefaultValue: initialValues.defaultValue !== null,
|
||||||
defaultValue: initialValues.defaultValue ?? null,
|
defaultValue: initialValues.defaultValue ?? null,
|
||||||
@@ -136,23 +139,23 @@ function WorkflowParameterEditPanel({
|
|||||||
const [sourceParameterKey, setSourceParameterKey] = useState<
|
const [sourceParameterKey, setSourceParameterKey] = useState<
|
||||||
string | undefined
|
string | undefined
|
||||||
>(
|
>(
|
||||||
initialValues.parameterType === "context"
|
initialValues?.parameterType === "context"
|
||||||
? initialValues.sourceParameterKey
|
? initialValues.sourceParameterKey
|
||||||
: undefined,
|
: undefined,
|
||||||
);
|
);
|
||||||
|
|
||||||
const [identityKey, setIdentityKey] = useState(
|
const [identityKey, setIdentityKey] = useState(
|
||||||
initialValues.parameterType === "secret" ? initialValues.identityKey : "",
|
initialValues?.parameterType === "secret" ? initialValues.identityKey : "",
|
||||||
);
|
);
|
||||||
|
|
||||||
const [identityFields, setIdentityFields] = useState(
|
const [identityFields, setIdentityFields] = useState(
|
||||||
initialValues.parameterType === "secret"
|
initialValues?.parameterType === "secret"
|
||||||
? initialValues.identityFields.join(", ")
|
? initialValues.identityFields.join(", ")
|
||||||
: "",
|
: "",
|
||||||
);
|
);
|
||||||
|
|
||||||
const [itemId, setItemId] = useState(
|
const [sensitiveInformationItemId, setSensitiveInformationItemId] = useState(
|
||||||
initialValues.parameterType === "creditCardData"
|
initialValues?.parameterType === "creditCardData"
|
||||||
? initialValues.itemId
|
? initialValues.itemId
|
||||||
: "",
|
: "",
|
||||||
);
|
);
|
||||||
@@ -160,7 +163,7 @@ function WorkflowParameterEditPanel({
|
|||||||
const [credentialId, setCredentialId] = useState(
|
const [credentialId, setCredentialId] = useState(
|
||||||
isSkyvernCredential ? initialValues.credentialId : "",
|
isSkyvernCredential ? initialValues.credentialId : "",
|
||||||
);
|
);
|
||||||
const [vaultId, setVaultId] = useState(
|
const [opVaultId, setOpVaultId] = useState(
|
||||||
isOnePasswordCredential ? initialValues.vaultId : "",
|
isOnePasswordCredential ? initialValues.vaultId : "",
|
||||||
);
|
);
|
||||||
const [opItemId, setOpItemId] = useState(
|
const [opItemId, setOpItemId] = useState(
|
||||||
@@ -168,7 +171,7 @@ function WorkflowParameterEditPanel({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const [bitwardenLoginCredentialItemId, setBitwardenLoginCredentialItemId] =
|
const [bitwardenLoginCredentialItemId, setBitwardenLoginCredentialItemId] =
|
||||||
useState(isBitwardenCredential ? initialValues.itemId ?? "" : "");
|
useState(isBitwardenCredential ? initialValues?.itemId ?? "" : "");
|
||||||
|
|
||||||
const [azureVaultName, setAzureVaultName] = useState(
|
const [azureVaultName, setAzureVaultName] = useState(
|
||||||
isAzureVaultCredential ? initialValues.vaultName : "",
|
isAzureVaultCredential ? initialValues.vaultName : "",
|
||||||
@@ -188,7 +191,7 @@ function WorkflowParameterEditPanel({
|
|||||||
<ScrollAreaViewport className="max-h-[500px]">
|
<ScrollAreaViewport className="max-h-[500px]">
|
||||||
<div className="space-y-4 p-1 px-4">
|
<div className="space-y-4 p-1 px-4">
|
||||||
<header className="flex items-center justify-between">
|
<header className="flex items-center justify-between">
|
||||||
<span>{header(type)}</span>
|
<span>{header(type, isEditMode)}</span>
|
||||||
<Cross2Icon className="h-6 w-6 cursor-pointer" onClick={onClose} />
|
<Cross2Icon className="h-6 w-6 cursor-pointer" onClick={onClose} />
|
||||||
</header>
|
</header>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
@@ -274,7 +277,7 @@ function WorkflowParameterEditPanel({
|
|||||||
setDefaultValueState((state) => {
|
setDefaultValueState((state) => {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
defaultValue: value,
|
defaultValue: value.s3uri,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
@@ -340,8 +343,8 @@ function WorkflowParameterEditPanel({
|
|||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<Label className="text-xs text-slate-300">Collection ID</Label>
|
<Label className="text-xs text-slate-300">Collection ID</Label>
|
||||||
<Input
|
<Input
|
||||||
value={collectionId}
|
value={bitwardenCollectionId}
|
||||||
onChange={(e) => setCollectionId(e.target.value)}
|
onChange={(e) => setBitwardenCollectionId(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
@@ -363,8 +366,8 @@ function WorkflowParameterEditPanel({
|
|||||||
<HelpTooltip content="You can find the Vault ID and Item ID in the URL when viewing the item in 1Password on the web." />
|
<HelpTooltip content="You can find the Vault ID and Item ID in the URL when viewing the item in 1Password on the web." />
|
||||||
</div>
|
</div>
|
||||||
<Input
|
<Input
|
||||||
value={vaultId}
|
value={opVaultId}
|
||||||
onChange={(e) => setVaultId(e.target.value)}
|
onChange={(e) => setOpVaultId(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
@@ -451,8 +454,8 @@ function WorkflowParameterEditPanel({
|
|||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<Label className="text-xs text-slate-300">Collection ID</Label>
|
<Label className="text-xs text-slate-300">Collection ID</Label>
|
||||||
<Input
|
<Input
|
||||||
value={collectionId}
|
value={bitwardenCollectionId}
|
||||||
onChange={(e) => setCollectionId(e.target.value)}
|
onChange={(e) => setBitwardenCollectionId(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
@@ -462,15 +465,17 @@ function WorkflowParameterEditPanel({
|
|||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<Label className="text-xs text-slate-300">Collection ID</Label>
|
<Label className="text-xs text-slate-300">Collection ID</Label>
|
||||||
<Input
|
<Input
|
||||||
value={collectionId}
|
value={bitwardenCollectionId}
|
||||||
onChange={(e) => setCollectionId(e.target.value)}
|
onChange={(e) => setBitwardenCollectionId(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<Label className="text-xs text-slate-300">Item ID</Label>
|
<Label className="text-xs text-slate-300">Item ID</Label>
|
||||||
<Input
|
<Input
|
||||||
value={itemId}
|
value={sensitiveInformationItemId}
|
||||||
onChange={(e) => setItemId(e.target.value)}
|
onChange={(e) =>
|
||||||
|
setSensitiveInformationItemId(e.target.value)
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
@@ -509,6 +514,14 @@ function WorkflowParameterEditPanel({
|
|||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!isEditMode && reservedKeys.includes(key)) {
|
||||||
|
toast({
|
||||||
|
variant: "destructive",
|
||||||
|
title: "Failed to add parameter",
|
||||||
|
description: `${key} is reserved, please use another key`,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (type === "workflow") {
|
if (type === "workflow") {
|
||||||
if (
|
if (
|
||||||
parameterType === "json" &&
|
parameterType === "json" &&
|
||||||
@@ -542,7 +555,7 @@ function WorkflowParameterEditPanel({
|
|||||||
}
|
}
|
||||||
if (type === "credential" && credentialType === "bitwarden") {
|
if (type === "credential" && credentialType === "bitwarden") {
|
||||||
const errorMessage = validateBitwardenLoginCredential(
|
const errorMessage = validateBitwardenLoginCredential(
|
||||||
collectionId,
|
bitwardenCollectionId,
|
||||||
bitwardenLoginCredentialItemId,
|
bitwardenLoginCredentialItemId,
|
||||||
urlParameterKey,
|
urlParameterKey,
|
||||||
);
|
);
|
||||||
@@ -563,12 +576,15 @@ function WorkflowParameterEditPanel({
|
|||||||
: bitwardenLoginCredentialItemId,
|
: bitwardenLoginCredentialItemId,
|
||||||
urlParameterKey:
|
urlParameterKey:
|
||||||
urlParameterKey === "" ? null : urlParameterKey,
|
urlParameterKey === "" ? null : urlParameterKey,
|
||||||
collectionId: collectionId === "" ? null : collectionId,
|
collectionId:
|
||||||
|
bitwardenCollectionId === ""
|
||||||
|
? null
|
||||||
|
: bitwardenCollectionId,
|
||||||
description,
|
description,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (type === "credential" && credentialType === "onepassword") {
|
if (type === "credential" && credentialType === "onepassword") {
|
||||||
if (vaultId.trim() === "" || opItemId.trim() === "") {
|
if (opVaultId.trim() === "" || opItemId.trim() === "") {
|
||||||
toast({
|
toast({
|
||||||
variant: "destructive",
|
variant: "destructive",
|
||||||
title: "Failed to save parameter",
|
title: "Failed to save parameter",
|
||||||
@@ -579,7 +595,7 @@ function WorkflowParameterEditPanel({
|
|||||||
onSave({
|
onSave({
|
||||||
key,
|
key,
|
||||||
parameterType: "onepassword",
|
parameterType: "onepassword",
|
||||||
vaultId,
|
vaultId: opVaultId,
|
||||||
itemId: opItemId,
|
itemId: opItemId,
|
||||||
description,
|
description,
|
||||||
});
|
});
|
||||||
@@ -610,7 +626,7 @@ function WorkflowParameterEditPanel({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (type === "secret" || type === "creditCardData") {
|
if (type === "secret" || type === "creditCardData") {
|
||||||
if (!collectionId) {
|
if (!bitwardenCollectionId) {
|
||||||
toast({
|
toast({
|
||||||
variant: "destructive",
|
variant: "destructive",
|
||||||
title: "Failed to save parameter",
|
title: "Failed to save parameter",
|
||||||
@@ -623,7 +639,7 @@ function WorkflowParameterEditPanel({
|
|||||||
onSave({
|
onSave({
|
||||||
key,
|
key,
|
||||||
parameterType: "secret",
|
parameterType: "secret",
|
||||||
collectionId,
|
collectionId: bitwardenCollectionId,
|
||||||
identityFields: identityFields
|
identityFields: identityFields
|
||||||
.split(",")
|
.split(",")
|
||||||
.filter((s) => s.length > 0)
|
.filter((s) => s.length > 0)
|
||||||
@@ -636,8 +652,8 @@ function WorkflowParameterEditPanel({
|
|||||||
onSave({
|
onSave({
|
||||||
key,
|
key,
|
||||||
parameterType: "creditCardData",
|
parameterType: "creditCardData",
|
||||||
collectionId,
|
collectionId: bitwardenCollectionId,
|
||||||
itemId,
|
itemId: sensitiveInformationItemId,
|
||||||
description,
|
description,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { WorkflowParameterAddPanel } from "./WorkflowParameterAddPanel";
|
|
||||||
import { ParametersState } from "../types";
|
import { ParametersState } from "../types";
|
||||||
import { WorkflowParameterEditPanel } from "./WorkflowParameterEditPanel";
|
import { WorkflowParameterEditPanel } from "./WorkflowParameterEditPanel";
|
||||||
import { MixerVerticalIcon, PlusIcon } from "@radix-ui/react-icons";
|
import { MixerVerticalIcon, PlusIcon } from "@radix-ui/react-icons";
|
||||||
@@ -242,7 +241,7 @@ function WorkflowParametersPanel({ onMouseDownCapture }: Props) {
|
|||||||
>
|
>
|
||||||
{operationPanelState.operation === "add" && (
|
{operationPanelState.operation === "add" && (
|
||||||
<div className="w-80 rounded-xl border border-slate-700 bg-slate-950 p-5 px-2 shadow-xl">
|
<div className="w-80 rounded-xl border border-slate-700 bg-slate-950 p-5 px-2 shadow-xl">
|
||||||
<WorkflowParameterAddPanel
|
<WorkflowParameterEditPanel
|
||||||
type={operationPanelState.type}
|
type={operationPanelState.type}
|
||||||
onSave={(parameter) => {
|
onSave={(parameter) => {
|
||||||
setWorkflowParameters([...workflowParameters, parameter]);
|
setWorkflowParameters([...workflowParameters, parameter]);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { defineConfig } from 'vitest/config'
|
import { defineConfig } from "vitest/config";
|
||||||
import react from '@vitejs/plugin-react-swc'
|
import react from "@vitejs/plugin-react-swc";
|
||||||
import path from 'path'
|
import path from "path";
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [react()],
|
plugins: [react()],
|
||||||
@@ -9,7 +9,7 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
'@': path.resolve(__dirname, './src'),
|
"@": path.resolve(__dirname, "./src"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user