chore: minor ui improvements

This commit is contained in:
Daniel Hougaard
2025-03-05 22:47:40 +04:00
parent 4d040706a9
commit 2a292455ef
3 changed files with 82 additions and 53 deletions
@@ -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 () => {
@@ -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>
@@ -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"