diff --git a/frontend/components/dashboard/DropZone.tsx b/frontend/components/dashboard/DropZone.tsx index 66be964cc..38e293f4e 100644 --- a/frontend/components/dashboard/DropZone.tsx +++ b/frontend/components/dashboard/DropZone.tsx @@ -3,10 +3,11 @@ import Image from "next/image"; import { useTranslation } from "next-i18next"; import { faUpload } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { parseDocument, Scalar, YAMLMap } from 'yaml'; import Button from "../basic/buttons/Button"; import Error from "../basic/Error"; -import parse from "../utilities/file"; +import { parseDotEnv } from '../utilities/parseDotEnv'; import guidGenerator from "../utilities/randomId"; interface DropZoneProps { @@ -51,6 +52,53 @@ const DropZone = ({ const [loading, setLoading] = useState(false); + const getSecrets = (file: ArrayBuffer, fileType: string) => { + let secrets; + switch (fileType) { + case 'env': { + const keyPairs = parseDotEnv(file); + secrets = Object.keys(keyPairs).map((key, index) => { + return { + id: guidGenerator(), + pos: numCurrentRows + index, + key: key, + value: keyPairs[key as keyof typeof keyPairs].value, + comment: keyPairs[key as keyof typeof keyPairs].comments.join('\n'), + type: 'shared', + }; + }); + break; + } + case 'yml': { + const parsedFile = parseDocument(file.toString()); + const keyPairs = parsedFile.contents!.toJSON(); + + secrets = Object.keys(keyPairs).map((key, index) => { + const fileContent = parsedFile.contents as YAMLMap; + const comment = + fileContent!.items + .find((item) => item.key.value === key) + ?.key?.commentBefore?.split('\n') + .map((comment) => comment.trim()) + .join('\n') ?? ''; + return { + id: guidGenerator(), + pos: numCurrentRows + index, + key: key, + value: keyPairs[key as keyof typeof keyPairs]?.toString() ?? '', + comment, + type: 'shared', + }; + }); + break; + } + default: + secrets = ''; + break; + } + return secrets; + }; + // This function function immediately parses the file after it is dropped const handleDrop = async (e: DragEvent) => { setLoading(true); @@ -61,20 +109,12 @@ const DropZone = ({ const file = e.dataTransfer.files[0]; const reader = new FileReader(); + const fileType = file.name.split('.')[1]; reader.onload = (event) => { if (event.target === null || event.target.result === null) return; // parse function's argument looks like to be ArrayBuffer - const keyPairs = parse(event.target.result as Buffer); - const newData = Object.keys(keyPairs).map((key, index) => { - return { - id: guidGenerator(), - pos: numCurrentRows + index, - key: key, - value: keyPairs[key as keyof typeof keyPairs], - type: "shared", - }; - }); + const newData = getSecrets(event.target.result as ArrayBuffer, fileType); setData(newData); setButtonReady(true); }; @@ -95,25 +135,14 @@ const DropZone = ({ setTimeout(() => setLoading(false), 5000); if (e.currentTarget.files === null) return; const file = e.currentTarget.files[0]; + const fileType = file.name.split('.')[1]; const reader = new FileReader(); reader.onload = (event) => { if (event.target === null || event.target.result === null) return; const { result } = event.target; - if (typeof result === "string") { - const newData = result - .split("\n") - .map((line: string, index: number) => { - return { - id: guidGenerator(), - pos: numCurrentRows + index, - key: line.split("=")[0], - value: line.split("=").slice(1, line.split("=").length).join("="), - type: "shared", - }; - }); - setData(newData); - setButtonReady(true); - } + const newData = getSecrets(result as ArrayBuffer, fileType); + setData(newData); + setButtonReady(true); }; reader.readAsText(file); }; @@ -139,7 +168,7 @@ const DropZone = ({ id="fileSelect" type="file" className="opacity-0 absolute w-full h-full" - accept=".txt,.env" + accept=".txt,.env,.yml" onChange={handleFileSelect} /> {errorDragAndDrop ? ( @@ -176,7 +205,7 @@ const DropZone = ({ id="fileSelect" type="file" className="opacity-0 absolute w-full h-full" - accept=".txt,.env" + accept=".txt,.env,.yml" onChange={handleFileSelect} />
diff --git a/frontend/components/utilities/file.ts b/frontend/components/utilities/file.ts deleted file mode 100644 index 3784405f9..000000000 --- a/frontend/components/utilities/file.ts +++ /dev/null @@ -1,47 +0,0 @@ -const LINE = - /(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/gm; - -/** - * Return text that is the buffer parsed - * @param {Buffer} src - source buffer - * @returns {String} text - text of buffer - */ -function parse(src: Buffer) { - const obj: Record = {}; - - // Convert buffer to string - let lines = src.toString(); - - // Convert line breaks to same format - lines = lines.replace(/\r\n?/gm, '\n'); - - let match; - while ((match = LINE.exec(lines)) != null) { - const key = match[1]; - - // Default undefined or null to empty string - let value = match[2] || ''; - - // Remove whitespace - value = value.trim(); - - // Check if double quoted - const maybeQuote = value[0]; - - // Remove surrounding quotes - value = value.replace(/^(['"`])([\s\S]*)\1$/gm, '$2'); - - // Expand newlines if double quoted - if (maybeQuote === '"') { - value = value.replace(/\\n/g, '\n'); - value = value.replace(/\\r/g, '\r'); - } - - // Add to object - obj[key] = value; - } - - return obj; -} - -export default parse; diff --git a/frontend/components/utilities/parseDotEnv.ts b/frontend/components/utilities/parseDotEnv.ts new file mode 100644 index 000000000..56fe3e349 --- /dev/null +++ b/frontend/components/utilities/parseDotEnv.ts @@ -0,0 +1,66 @@ +const LINE = + /(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/gm; + +/** + * Return text that is the buffer parsed + * @param {ArrayBuffer} src - source buffer + * @returns {String} text - text of buffer + */ +export function parseDotEnv(src: ArrayBuffer) { + const object: { + [key: string]: { value: string; comments: string[] }; + } = {}; + + // Convert buffer to string + let lines = src.toString(); + + // Convert line breaks to same format + lines = lines.replace(/\r\n?/gm, '\n'); + + let comments: string[] = []; + + lines + .split('\n') + .map((line) => { + // collect comments of each env variable + if (line.startsWith('#')) { + comments.push(line.replace('#', '').trim()); + } else if (line) { + let match; + let item: [string, string, string[]] | [] = []; + + while ((match = LINE.exec(line)) !== null) { + const key = match[1]; + + // Default undefined or null to empty string + let value = match[2] || ''; + + // Remove whitespace + value = value.trim(); + + // Check if double quoted + const maybeQuote = value[0]; + + // Remove surrounding quotes + value = value.replace(/^(['"`])([\s\S]*)\1$/gm, '$2'); + + // Expand newlines if double quoted + if (maybeQuote === '"') { + value = value.replace(/\\n/g, '\n'); + value = value.replace(/\\r/g, '\r'); + } + item = [key, value, comments]; + } + comments = []; + return item; + } + return []; + }) + .filter((line) => line.length > 1) + .forEach((line) => { + const [key, value, comments] = line; + object[key as string] = { value, comments }; + }); + + return object; +}