mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 15:27:27 +00:00
chore: minor ui improvements
This commit is contained in:
+62
-41
@@ -21,6 +21,7 @@ import {
|
|||||||
faTrash
|
faTrash
|
||||||
} from "@fortawesome/free-solid-svg-icons";
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { AxiosError } from "axios";
|
||||||
import FileSaver from "file-saver";
|
import FileSaver from "file-saver";
|
||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
@@ -54,7 +55,7 @@ import {
|
|||||||
import { usePopUp } from "@app/hooks";
|
import { usePopUp } from "@app/hooks";
|
||||||
import { useCreateFolder, useDeleteSecretBatch, useMoveSecrets } from "@app/hooks/api";
|
import { useCreateFolder, useDeleteSecretBatch, useMoveSecrets } from "@app/hooks/api";
|
||||||
import { fetchProjectSecrets } from "@app/hooks/api/secrets/queries";
|
import { fetchProjectSecrets } from "@app/hooks/api/secrets/queries";
|
||||||
import { SecretType, WsTag } from "@app/hooks/api/types";
|
import { ApiErrorTypes, SecretType, TApiErrors, WsTag } from "@app/hooks/api/types";
|
||||||
import { SecretSearchInput } from "@app/pages/secret-manager/OverviewPage/components/SecretSearchInput";
|
import { SecretSearchInput } from "@app/pages/secret-manager/OverviewPage/components/SecretSearchInput";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -152,51 +153,71 @@ export const ActionBar = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleSecretDownload = async () => {
|
const handleSecretDownload = async () => {
|
||||||
const { secrets: localSecrets, imports: localImportedSecrets } = await fetchProjectSecrets({
|
try {
|
||||||
workspaceId,
|
const { secrets: localSecrets, imports: localImportedSecrets } = await fetchProjectSecrets({
|
||||||
expandSecretReferences: true,
|
workspaceId,
|
||||||
includeImports: true,
|
expandSecretReferences: true,
|
||||||
environment,
|
includeImports: true,
|
||||||
secretPath
|
environment,
|
||||||
});
|
secretPath
|
||||||
const secretsPicked = new Set<string>();
|
});
|
||||||
const secretsToDownload: { key: string; value?: string; comment?: string }[] = [];
|
const secretsPicked = new Set<string>();
|
||||||
localSecrets.forEach((el) => {
|
const secretsToDownload: { key: string; value?: string; comment?: string }[] = [];
|
||||||
secretsPicked.add(el.secretKey);
|
localSecrets.forEach((el) => {
|
||||||
secretsToDownload.push({
|
secretsPicked.add(el.secretKey);
|
||||||
key: el.secretKey,
|
secretsToDownload.push({
|
||||||
value: el.secretValue,
|
key: el.secretKey,
|
||||||
comment: el.secretComment
|
value: el.secretValue,
|
||||||
|
comment: el.secretComment
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
for (let i = localImportedSecrets.length - 1; i >= 0; i -= 1) {
|
for (let i = localImportedSecrets.length - 1; i >= 0; i -= 1) {
|
||||||
for (let j = localImportedSecrets[i].secrets.length - 1; j >= 0; j -= 1) {
|
for (let j = localImportedSecrets[i].secrets.length - 1; j >= 0; j -= 1) {
|
||||||
const secret = localImportedSecrets[i].secrets[j];
|
const secret = localImportedSecrets[i].secrets[j];
|
||||||
if (!secretsPicked.has(secret.secretKey)) {
|
if (!secretsPicked.has(secret.secretKey)) {
|
||||||
secretsToDownload.push({
|
secretsToDownload.push({
|
||||||
key: secret.secretKey,
|
key: secret.secretKey,
|
||||||
value: secret.secretValue,
|
value: secret.secretValue,
|
||||||
comment: secret.secretComment
|
comment: secret.secretComment
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
secretsPicked.add(secret.secretKey);
|
||||||
}
|
}
|
||||||
secretsPicked.add(secret.secretKey);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const file = secretsToDownload
|
||||||
|
.sort((a, b) => a.key.toLowerCase().localeCompare(b.key.toLowerCase()))
|
||||||
|
.reduce(
|
||||||
|
(prev, { key, comment, value }, index) =>
|
||||||
|
prev +
|
||||||
|
(comment
|
||||||
|
? `${index === 0 ? "#" : "\n#"} ${comment}\n${key}=${value}\n`
|
||||||
|
: `${key}=${value}\n`),
|
||||||
|
""
|
||||||
|
);
|
||||||
|
|
||||||
|
const blob = new Blob([file], { type: "text/plain;charset=utf-8" });
|
||||||
|
FileSaver.saveAs(blob, `${environment}.env`);
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof AxiosError) {
|
||||||
|
const error = err?.response?.data as TApiErrors;
|
||||||
|
|
||||||
|
if (error?.error === ApiErrorTypes.ForbiddenError && error.message.includes("readValue")) {
|
||||||
|
createNotification({
|
||||||
|
title: "You don't have permission to download secrets",
|
||||||
|
text: "You don't have permission to view one or more of the secrets in the current folder. Please contact your administrator.",
|
||||||
|
type: "error"
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
createNotification({
|
||||||
|
title: "Failed to download secrets",
|
||||||
|
text: "Please try again later.",
|
||||||
|
type: "error"
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const file = secretsToDownload
|
|
||||||
.sort((a, b) => a.key.toLowerCase().localeCompare(b.key.toLowerCase()))
|
|
||||||
.reduce(
|
|
||||||
(prev, { key, comment, value }, index) =>
|
|
||||||
prev +
|
|
||||||
(comment
|
|
||||||
? `${index === 0 ? "#" : "\n#"} ${comment}\n${key}=${value}\n`
|
|
||||||
: `${key}=${value}\n`),
|
|
||||||
""
|
|
||||||
);
|
|
||||||
|
|
||||||
const blob = new Blob([file], { type: "text/plain;charset=utf-8" });
|
|
||||||
FileSaver.saveAs(blob, `${environment}.env`);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSecretBulkDelete = async () => {
|
const handleSecretBulkDelete = async () => {
|
||||||
|
|||||||
+18
-12
@@ -314,19 +314,25 @@ export const SecretDetailSidebar = ({
|
|||||||
{...field}
|
{...field}
|
||||||
autoFocus={false}
|
autoFocus={false}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Tooltip
|
||||||
className="px-2 py-[0.43rem] font-normal"
|
content="You don't have permission to view the secret value."
|
||||||
variant="outline_bg"
|
isDisabled={!secret?.secretValueHidden}
|
||||||
leftIcon={<FontAwesomeIcon icon={faShare} />}
|
|
||||||
onClick={() => {
|
|
||||||
const value = secret?.valueOverride ?? secret?.value;
|
|
||||||
if (value) {
|
|
||||||
handleSecretShare(value);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
Share
|
<Button
|
||||||
</Button>
|
isDisabled={secret?.secretValueHidden}
|
||||||
|
className="px-2 py-[0.43rem] font-normal"
|
||||||
|
variant="outline_bg"
|
||||||
|
leftIcon={<FontAwesomeIcon icon={faShare} />}
|
||||||
|
onClick={() => {
|
||||||
|
const value = secret?.valueOverride ?? secret?.value;
|
||||||
|
if (value) {
|
||||||
|
handleSecretShare(value);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Share
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+2
@@ -307,6 +307,7 @@ export const SecretItem = memo(
|
|||||||
<div key="actions" className="flex h-8 flex-shrink-0 self-start transition-all">
|
<div key="actions" className="flex h-8 flex-shrink-0 self-start transition-all">
|
||||||
<Tooltip content="Copy secret">
|
<Tooltip content="Copy secret">
|
||||||
<IconButton
|
<IconButton
|
||||||
|
isDisabled={secret.secretValueHidden}
|
||||||
ariaLabel="copy-value"
|
ariaLabel="copy-value"
|
||||||
variant="plain"
|
variant="plain"
|
||||||
size="sm"
|
size="sm"
|
||||||
@@ -514,6 +515,7 @@ export const SecretItem = memo(
|
|||||||
)}
|
)}
|
||||||
</ProjectPermissionCan>
|
</ProjectPermissionCan>
|
||||||
<IconButton
|
<IconButton
|
||||||
|
isDisabled={secret.secretValueHidden}
|
||||||
className="w-0 overflow-hidden p-0 group-hover:mr-2 group-hover:w-5 data-[state=open]:w-6"
|
className="w-0 overflow-hidden p-0 group-hover:mr-2 group-hover:w-5 data-[state=open]:w-6"
|
||||||
variant="plain"
|
variant="plain"
|
||||||
size="md"
|
size="md"
|
||||||
|
|||||||
Reference in New Issue
Block a user