mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-10 09:28:36 +00:00
requested changes
This commit is contained in:
@@ -23,12 +23,6 @@ export function throwIfMissingSecretReadValueOrDescribePermission(
|
||||
subjectFields?: SecretSubjectFields
|
||||
) {
|
||||
try {
|
||||
if (subjectFields) {
|
||||
ForbiddenError.from(permission).throwUnlessCan(action, subject(ProjectPermissionSub.Secrets, subjectFields));
|
||||
} else {
|
||||
ForbiddenError.from(permission).throwUnlessCan(action, ProjectPermissionSub.Secrets);
|
||||
}
|
||||
} catch {
|
||||
if (subjectFields) {
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionSecretActions.DescribeAndReadValue,
|
||||
@@ -40,6 +34,12 @@ export function throwIfMissingSecretReadValueOrDescribePermission(
|
||||
ProjectPermissionSub.Secrets
|
||||
);
|
||||
}
|
||||
} catch {
|
||||
if (subjectFields) {
|
||||
ForbiddenError.from(permission).throwUnlessCan(action, subject(ProjectPermissionSub.Secrets, subjectFields));
|
||||
} else {
|
||||
ForbiddenError.from(permission).throwUnlessCan(action, ProjectPermissionSub.Secrets);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -834,6 +834,51 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: "GET",
|
||||
url: "/accessible-secrets",
|
||||
config: {
|
||||
rateLimit: secretsLimit
|
||||
},
|
||||
schema: {
|
||||
querystring: z.object({
|
||||
projectId: z.string().trim(),
|
||||
environment: z.string().trim(),
|
||||
secretPath: z.string().trim().default("/").transform(removeTrailingSlash),
|
||||
filterByAction: z
|
||||
.enum([ProjectPermissionSecretActions.DescribeSecret, ProjectPermissionSecretActions.ReadValue])
|
||||
.default(ProjectPermissionSecretActions.ReadValue)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
secrets: secretRawSchema
|
||||
.extend({
|
||||
secretPath: z.string().optional()
|
||||
})
|
||||
.array()
|
||||
.optional()
|
||||
})
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
handler: async (req) => {
|
||||
const { projectId, environment, secretPath, filterByAction } = req.query;
|
||||
|
||||
const { secrets } = await server.services.secret.getAccessibleSecrets({
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
environment,
|
||||
secretPath,
|
||||
projectId,
|
||||
filterByAction
|
||||
});
|
||||
|
||||
return { secrets };
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: "GET",
|
||||
url: "/secrets-by-keys",
|
||||
|
||||
@@ -60,6 +60,7 @@ import {
|
||||
TCreateSecretDTO,
|
||||
TDeleteManySecretDTO,
|
||||
TDeleteSecretDTO,
|
||||
TGetAccessibleSecretsDTO,
|
||||
TGetASecretDTO,
|
||||
TGetSecretReferencesTreeDTO,
|
||||
TGetSecretsDTO,
|
||||
@@ -200,7 +201,7 @@ export const secretV2BridgeServiceFactory = ({
|
||||
|
||||
const referredSecretsGroupBySecretKey = groupBy(referredSecrets, (i) => i.key);
|
||||
references.forEach((el) => {
|
||||
throwIfMissingSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.ReadValue, {
|
||||
throwIfMissingSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.DescribeSecret, {
|
||||
environment: el.environment,
|
||||
secretPath: el.secretPath,
|
||||
secretName: el.secretKey,
|
||||
@@ -1291,7 +1292,7 @@ export const secretV2BridgeServiceFactory = ({
|
||||
folderDAL,
|
||||
secretImportDAL,
|
||||
decryptor: (value) => (value ? secretManagerDecryptor({ cipherTextBlob: value }).toString() : ""),
|
||||
expandSecretReferences: shouldExpandSecretReferences ? expandSecretReferences : undefined,
|
||||
expandSecretReferences: shouldExpandSecretReferences && viewSecretValue ? expandSecretReferences : undefined,
|
||||
hasSecretAccess: (expandEnvironment, expandSecretPath, expandSecretKey, expandSecretTags) => {
|
||||
return hasSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.DescribeSecret, {
|
||||
environment: expandEnvironment,
|
||||
@@ -1347,7 +1348,7 @@ export const secretV2BridgeServiceFactory = ({
|
||||
let secretValue = secret.encryptedValue
|
||||
? secretManagerDecryptor({ cipherTextBlob: secret.encryptedValue }).toString()
|
||||
: "";
|
||||
if (shouldExpandSecretReferences && secretValue) {
|
||||
if (shouldExpandSecretReferences && secretValue && viewSecretValue) {
|
||||
// eslint-disable-next-line
|
||||
const expandedSecretValue = await expandSecretReferences({
|
||||
environment,
|
||||
@@ -2613,6 +2614,86 @@ export const secretV2BridgeServiceFactory = ({
|
||||
return { tree: stackTrace, value: expandedValue };
|
||||
};
|
||||
|
||||
const getAccessibleSecrets = async ({
|
||||
projectId,
|
||||
secretPath,
|
||||
environment,
|
||||
filterByAction,
|
||||
actorId,
|
||||
actor,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
}: TGetAccessibleSecretsDTO) => {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actor,
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
});
|
||||
throwIfMissingSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.DescribeSecret, {
|
||||
environment,
|
||||
secretPath
|
||||
});
|
||||
|
||||
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
|
||||
if (!folder) return { secrets: [] };
|
||||
|
||||
const secrets = await secretDAL.findByFolderIds([folder.id]);
|
||||
|
||||
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
|
||||
type: KmsDataKey.SecretManager,
|
||||
projectId
|
||||
});
|
||||
|
||||
const decryptedSecrets = secrets
|
||||
.filter((el) => {
|
||||
if (
|
||||
!hasSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.DescribeSecret, {
|
||||
environment,
|
||||
secretPath,
|
||||
secretName: el.key,
|
||||
secretTags: el.tags.map((i) => i.slug)
|
||||
})
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (filterByAction === ProjectPermissionSecretActions.ReadValue) {
|
||||
return hasSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.ReadValue, {
|
||||
environment,
|
||||
secretPath,
|
||||
secretName: el.key,
|
||||
secretTags: el.tags.map((i) => i.slug)
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
})
|
||||
.map((secret) => {
|
||||
return reshapeBridgeSecret(
|
||||
projectId,
|
||||
environment,
|
||||
secretPath,
|
||||
{
|
||||
...secret,
|
||||
value: secret.encryptedValue
|
||||
? secretManagerDecryptor({ cipherTextBlob: secret.encryptedValue }).toString()
|
||||
: "",
|
||||
comment: secret.encryptedComment
|
||||
? secretManagerDecryptor({ cipherTextBlob: secret.encryptedComment }).toString()
|
||||
: ""
|
||||
},
|
||||
false
|
||||
);
|
||||
});
|
||||
|
||||
return {
|
||||
secrets: decryptedSecrets
|
||||
};
|
||||
};
|
||||
|
||||
return {
|
||||
createSecret,
|
||||
deleteSecret,
|
||||
@@ -2630,6 +2711,7 @@ export const secretV2BridgeServiceFactory = ({
|
||||
getSecretsMultiEnv,
|
||||
getSecretReferenceTree,
|
||||
getSecretsByFolderMappings,
|
||||
getSecretById
|
||||
getSecretById,
|
||||
getAccessibleSecrets
|
||||
};
|
||||
};
|
||||
|
||||
@@ -351,3 +351,10 @@ export type TGetSecretsRawByFolderMappingsDTO = {
|
||||
filters: TFindSecretsByFolderIdsFilter;
|
||||
filterByAction?: ProjectPermissionSecretActions.DescribeSecret | ProjectPermissionSecretActions.ReadValue;
|
||||
};
|
||||
|
||||
export type TGetAccessibleSecretsDTO = {
|
||||
environment: string;
|
||||
projectId: string;
|
||||
secretPath: string;
|
||||
filterByAction: ProjectPermissionSecretActions.DescribeSecret | ProjectPermissionSecretActions.ReadValue;
|
||||
} & TProjectPermission;
|
||||
|
||||
@@ -81,6 +81,7 @@ import {
|
||||
TDeleteManySecretRawDTO,
|
||||
TDeleteSecretDTO,
|
||||
TDeleteSecretRawDTO,
|
||||
TGetAccessibleSecretsDTO,
|
||||
TGetASecretByIdRawDTO,
|
||||
TGetASecretDTO,
|
||||
TGetASecretRawDTO,
|
||||
@@ -1312,6 +1313,39 @@ export const secretServiceFactory = ({
|
||||
return { users: usersWithAccess, identities: identitiesWithAccess, groups: groupsWithAccess };
|
||||
};
|
||||
|
||||
const getAccessibleSecrets = async ({
|
||||
projectId,
|
||||
secretPath,
|
||||
actor,
|
||||
actorId,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
environment,
|
||||
filterByAction
|
||||
}: TGetAccessibleSecretsDTO) => {
|
||||
const { shouldUseSecretV2Bridge } = await projectBotService.getBotKey(projectId);
|
||||
|
||||
if (!shouldUseSecretV2Bridge) {
|
||||
throw new BadRequestError({
|
||||
message: "Project version does not support this endpoint.",
|
||||
name: "ProjectVersionNotSupported"
|
||||
});
|
||||
}
|
||||
|
||||
const secrets = await secretV2BridgeService.getAccessibleSecrets({
|
||||
projectId,
|
||||
secretPath,
|
||||
environment,
|
||||
filterByAction,
|
||||
actor,
|
||||
actorId,
|
||||
actorOrgId,
|
||||
actorAuthMethod
|
||||
});
|
||||
|
||||
return secrets;
|
||||
};
|
||||
|
||||
const getSecretsRaw = async ({
|
||||
projectId,
|
||||
path,
|
||||
@@ -3261,6 +3295,7 @@ export const secretServiceFactory = ({
|
||||
getSecretReferenceTree,
|
||||
getSecretsRawByFolderMappings,
|
||||
getSecretAccessList,
|
||||
getSecretByIdRaw
|
||||
getSecretByIdRaw,
|
||||
getAccessibleSecrets
|
||||
};
|
||||
};
|
||||
|
||||
@@ -20,6 +20,7 @@ import { TSecretV2BridgeDALFactory } from "../secret-v2-bridge/secret-v2-bridge-
|
||||
import { SecretUpdateMode } from "../secret-v2-bridge/secret-v2-bridge-types";
|
||||
import { TSecretVersionV2DALFactory } from "../secret-v2-bridge/secret-version-dal";
|
||||
import { TSecretVersionV2TagDALFactory } from "../secret-v2-bridge/secret-version-tag-dal";
|
||||
import { ProjectPermissionSecretActions } from "@app/ee/services/permission/project-permission";
|
||||
|
||||
type TPartialSecret = Pick<TSecrets, "id" | "secretReminderRepeatDays" | "secretReminderNote">;
|
||||
|
||||
@@ -180,6 +181,12 @@ export enum SecretsOrderBy {
|
||||
Name = "name" // "key" for secrets but using name for use across resources
|
||||
}
|
||||
|
||||
export type TGetAccessibleSecretsDTO = {
|
||||
secretPath: string;
|
||||
environment: string;
|
||||
filterByAction: ProjectPermissionSecretActions.DescribeSecret | ProjectPermissionSecretActions.ReadValue;
|
||||
} & TProjectPermission;
|
||||
|
||||
export type TGetSecretsRawDTO = {
|
||||
expandSecretReferences?: boolean;
|
||||
path: string;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export {
|
||||
useGetAccessibleSecrets,
|
||||
useGetProjectSecretsDetails,
|
||||
useGetProjectSecretsOverview,
|
||||
useGetProjectSecretsQuickSearch
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
DashboardSecretsOrderBy,
|
||||
TDashboardProjectSecretsQuickSearch,
|
||||
TDashboardProjectSecretsQuickSearchResponse,
|
||||
TGetAccessibleSecretsDTO,
|
||||
TGetDashboardProjectSecretsByKeys,
|
||||
TGetDashboardProjectSecretsDetailsDTO,
|
||||
TGetDashboardProjectSecretsOverviewDTO,
|
||||
@@ -20,6 +21,8 @@ import { OrderByDirection } from "@app/hooks/api/generic/types";
|
||||
import { mergePersonalSecrets } from "@app/hooks/api/secrets/queries";
|
||||
import { groupBy, unique } from "@app/lib/fn/array";
|
||||
|
||||
import { SecretV3Raw } from "../types";
|
||||
|
||||
export const dashboardKeys = {
|
||||
all: () => ["dashboard"] as const,
|
||||
getDashboardSecrets: ({
|
||||
@@ -58,7 +61,14 @@ export const dashboardKeys = {
|
||||
...dashboardKeys.getDashboardSecrets({ projectId, secretPath }),
|
||||
"quick-search",
|
||||
params
|
||||
] as const
|
||||
] as const,
|
||||
getAccessibleSecrets: ({
|
||||
projectId,
|
||||
secretPath,
|
||||
environment,
|
||||
filterByAction
|
||||
}: TGetAccessibleSecretsDTO) =>
|
||||
[...dashboardKeys.all(), { projectId, secretPath, environment, filterByAction }] as const
|
||||
};
|
||||
|
||||
export const fetchProjectSecretsOverview = async ({
|
||||
@@ -295,6 +305,22 @@ export const fetchProjectSecretsQuickSearch = async ({
|
||||
return data;
|
||||
};
|
||||
|
||||
const fetchAccessibleSecrets = async ({
|
||||
projectId,
|
||||
secretPath,
|
||||
environment,
|
||||
filterByAction
|
||||
}: TGetAccessibleSecretsDTO) => {
|
||||
const { data } = await apiRequest.get<{ secrets: SecretV3Raw[] }>(
|
||||
"/api/v1/dashboard/accessible-secrets",
|
||||
{
|
||||
params: { projectId, secretPath, environment, filterByAction }
|
||||
}
|
||||
);
|
||||
|
||||
return data.secrets;
|
||||
};
|
||||
|
||||
export const useGetProjectSecretsQuickSearch = (
|
||||
{
|
||||
projectId,
|
||||
@@ -357,3 +383,32 @@ export const useGetProjectSecretsQuickSearch = (
|
||||
placeholderData: (previousData) => previousData
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetAccessibleSecrets = ({
|
||||
projectId,
|
||||
secretPath,
|
||||
environment,
|
||||
filterByAction,
|
||||
options
|
||||
}: TGetAccessibleSecretsDTO & {
|
||||
options?: Omit<
|
||||
UseQueryOptions<
|
||||
SecretV3Raw[],
|
||||
unknown,
|
||||
SecretV3Raw[],
|
||||
ReturnType<typeof dashboardKeys.getAccessibleSecrets>
|
||||
>,
|
||||
"queryKey" | "queryFn"
|
||||
>;
|
||||
}) => {
|
||||
return useQuery({
|
||||
...options,
|
||||
queryKey: dashboardKeys.getAccessibleSecrets({
|
||||
projectId,
|
||||
secretPath,
|
||||
environment,
|
||||
filterByAction
|
||||
}),
|
||||
queryFn: () => fetchAccessibleSecrets({ projectId, secretPath, environment, filterByAction })
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ProjectPermissionSecretActions } from "@app/context/ProjectPermissionContext/types";
|
||||
import { TDynamicSecret } from "@app/hooks/api/dynamicSecret/types";
|
||||
import { OrderByDirection } from "@app/hooks/api/generic/types";
|
||||
import { TSecretFolder } from "@app/hooks/api/secretFolders/types";
|
||||
@@ -101,3 +102,12 @@ export type TGetDashboardProjectSecretsByKeys = {
|
||||
environment: string;
|
||||
keys: string[];
|
||||
};
|
||||
|
||||
export type TGetAccessibleSecretsDTO = {
|
||||
projectId: string;
|
||||
secretPath: string;
|
||||
environment: string;
|
||||
filterByAction:
|
||||
| ProjectPermissionSecretActions.DescribeSecret
|
||||
| ProjectPermissionSecretActions.ReadValue;
|
||||
};
|
||||
|
||||
+14
-9
@@ -27,9 +27,11 @@ import {
|
||||
} from "@app/components/v2";
|
||||
import { Blur } from "@app/components/v2/Blur";
|
||||
import { InfisicalSecretInput } from "@app/components/v2/InfisicalSecretInput";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub, useProjectPermission } from "@app/context";
|
||||
import { ProjectPermissionSecretActions } from "@app/context/ProjectPermissionContext/types";
|
||||
import { useToggle } from "@app/hooks";
|
||||
import { SecretType } from "@app/hooks/api/types";
|
||||
import { hasSecretReadValueOrDescribePermission } from "@app/lib/fn/permission";
|
||||
|
||||
type Props = {
|
||||
defaultValue?: string | null;
|
||||
@@ -79,6 +81,9 @@ export const SecretEditRow = ({
|
||||
value: defaultValue || null
|
||||
}
|
||||
});
|
||||
|
||||
const { permission } = useProjectPermission();
|
||||
|
||||
const [isDeleting, setIsDeleting] = useToggle();
|
||||
const [isModalOpen, setIsModalOpen] = useState<boolean>(false);
|
||||
|
||||
@@ -120,6 +125,11 @@ export const SecretEditRow = ({
|
||||
reset({ value });
|
||||
};
|
||||
|
||||
const canReadSecretValue = hasSecretReadValueOrDescribePermission(
|
||||
permission,
|
||||
ProjectPermissionSecretActions.ReadValue
|
||||
);
|
||||
|
||||
const handleDeleteSecret = useCallback(async () => {
|
||||
setIsDeleting.on();
|
||||
setIsModalOpen(false);
|
||||
@@ -229,11 +239,7 @@ export const SecretEditRow = ({
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Read}
|
||||
a={ProjectPermissionSub.Secrets}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
|
||||
<div className="opacity-0 group-hover:opacity-100">
|
||||
<Modal>
|
||||
<ModalTrigger asChild>
|
||||
@@ -249,7 +255,7 @@ export const SecretEditRow = ({
|
||||
variant="plain"
|
||||
ariaLabel="reference-tree"
|
||||
className="h-full"
|
||||
isDisabled={!hasSecretReference(defaultValue || "") || !isAllowed}
|
||||
isDisabled={!hasSecretReference(defaultValue || "") || !canReadSecretValue}
|
||||
>
|
||||
<FontAwesomeIcon icon={faProjectDiagram} />
|
||||
</IconButton>
|
||||
@@ -269,8 +275,7 @@ export const SecretEditRow = ({
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
</div>
|
||||
)}
|
||||
</ProjectPermissionCan>
|
||||
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Delete}
|
||||
a={subject(ProjectPermissionSub.Secrets, {
|
||||
|
||||
+25
-17
@@ -20,8 +20,9 @@ import {
|
||||
} from "@app/components/v2";
|
||||
import { SecretPathInput } from "@app/components/v2/SecretPathInput";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
|
||||
import { ProjectPermissionSecretActions } from "@app/context/ProjectPermissionContext/types";
|
||||
import { useDebounce } from "@app/hooks";
|
||||
import { useGetProjectSecrets } from "@app/hooks/api";
|
||||
import { useGetAccessibleSecrets } from "@app/hooks/api/dashboard";
|
||||
|
||||
const formSchema = z.object({
|
||||
environment: z.object({ name: z.string(), slug: z.string() }),
|
||||
@@ -32,7 +33,7 @@ const formSchema = z.object({
|
||||
typeof val === "string" && val.at(-1) === "/" && val.length > 1 ? val.slice(0, -1) : val
|
||||
),
|
||||
secrets: z
|
||||
.object({ key: z.string(), value: z.string().optional() })
|
||||
.object({ secretKey: z.string(), secretValue: z.string().optional() })
|
||||
.array()
|
||||
.min(1, "Select one or more secrets to copy")
|
||||
});
|
||||
@@ -78,10 +79,14 @@ export const CopySecretsFromBoard = ({
|
||||
const selectedEnvSlug = watch("environment");
|
||||
const [debouncedEnvCopySecretPath] = useDebounce(envCopySecPath);
|
||||
|
||||
const { data: secrets, isPending: isSecretsLoading } = useGetProjectSecrets({
|
||||
workspaceId,
|
||||
environment: selectedEnvSlug.slug,
|
||||
const { data: accessibleSecrets, isPending: isAccessibleSecretsLoading } =
|
||||
useGetAccessibleSecrets({
|
||||
projectId: workspaceId,
|
||||
secretPath: debouncedEnvCopySecretPath,
|
||||
environment: selectedEnvSlug.slug,
|
||||
filterByAction: shouldIncludeValues
|
||||
? ProjectPermissionSecretActions.ReadValue
|
||||
: ProjectPermissionSecretActions.DescribeSecret,
|
||||
options: {
|
||||
enabled:
|
||||
Boolean(workspaceId) &&
|
||||
@@ -96,16 +101,16 @@ export const CopySecretsFromBoard = ({
|
||||
}, [debouncedEnvCopySecretPath, selectedEnvSlug]);
|
||||
|
||||
const handleSecSelectAll = () => {
|
||||
if (secrets) {
|
||||
setValue("secrets", secrets, { shouldDirty: true });
|
||||
if (accessibleSecrets) {
|
||||
setValue("secrets", accessibleSecrets, { shouldDirty: true });
|
||||
}
|
||||
};
|
||||
|
||||
const handleFormSubmit = async (data: TFormSchema) => {
|
||||
const secretsToBePulled: Record<string, { value: string; comments: string[] }> = {};
|
||||
data.secrets.forEach(({ key, value }) => {
|
||||
secretsToBePulled[key] = {
|
||||
value: (shouldIncludeValues && value) || "",
|
||||
data.secrets.forEach(({ secretKey, secretValue }) => {
|
||||
secretsToBePulled[secretKey] = {
|
||||
value: (shouldIncludeValues && secretValue) || "",
|
||||
comments: [""]
|
||||
};
|
||||
});
|
||||
@@ -202,19 +207,19 @@ export const CopySecretsFromBoard = ({
|
||||
<FilterableSelect
|
||||
placeholder={
|
||||
// eslint-disable-next-line no-nested-ternary
|
||||
isSecretsLoading
|
||||
isAccessibleSecretsLoading
|
||||
? "Loading secrets..."
|
||||
: secrets?.length
|
||||
: accessibleSecrets?.length
|
||||
? "Select secrets..."
|
||||
: "No secrets found..."
|
||||
}
|
||||
isLoading={isSecretsLoading}
|
||||
options={secrets}
|
||||
isLoading={isAccessibleSecretsLoading}
|
||||
options={accessibleSecrets}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
isMulti
|
||||
getOptionValue={(option) => option.key}
|
||||
getOptionLabel={(option) => option.key}
|
||||
getOptionValue={(option) => option.secretKey}
|
||||
getOptionLabel={(option) => option.secretKey}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
@@ -235,7 +240,10 @@ export const CopySecretsFromBoard = ({
|
||||
<Switch
|
||||
id="populate-include-value"
|
||||
isChecked={shouldIncludeValues}
|
||||
onCheckedChange={(isChecked) => setShouldIncludeValues(isChecked as boolean)}
|
||||
onCheckedChange={(isChecked) => {
|
||||
setValue("secrets", []);
|
||||
setShouldIncludeValues(isChecked as boolean);
|
||||
}}
|
||||
>
|
||||
Include secret values
|
||||
</Switch>
|
||||
|
||||
+8
-13
@@ -13,8 +13,8 @@ import {
|
||||
faShare,
|
||||
faTag,
|
||||
faTrash,
|
||||
faUser,
|
||||
faTriangleExclamation
|
||||
faTriangleExclamation,
|
||||
faUser
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
@@ -728,8 +728,8 @@ export const SecretDetailSidebar = ({
|
||||
<div className="mb-2 pl-1">Version History</div>
|
||||
<div className="thin-scrollbar flex h-48 flex-col space-y-2 overflow-y-auto overflow-x-hidden rounded-md border border-mineshaft-600 bg-mineshaft-900 p-4 dark:[color-scheme:dark]">
|
||||
{secretVersion?.map(
|
||||
({ createdAt, secretValue, version, id, secretValueHidden, actor }, index) => (
|
||||
<div key={`secret-version-${index + 1}`} className="flex flex-row">
|
||||
({ createdAt, secretValue, secretValueHidden, version, id, actor }) => (
|
||||
<div className="flex flex-row">
|
||||
<div key={id} className="flex w-full flex-col space-y-1">
|
||||
<div className="flex items-center">
|
||||
<div className="w-10">
|
||||
@@ -739,8 +739,6 @@ export const SecretDetailSidebar = ({
|
||||
</div>
|
||||
<div>{format(new Date(createdAt), "Pp")}</div>
|
||||
</div>
|
||||
<div>{format(new Date(createdAt), "Pp")}</div>
|
||||
</div>
|
||||
<div className="flex w-full cursor-default">
|
||||
<div className="relative w-10">
|
||||
<div className="absolute bottom-0 left-3 top-0 mt-0.5 border-l border-mineshaft-400/60" />
|
||||
@@ -750,7 +748,9 @@ export const SecretDetailSidebar = ({
|
||||
<div className="flex flex-row">
|
||||
<div className="flex w-fit flex-row text-sm">
|
||||
Modified by:
|
||||
<Tooltip content={getModifiedByName(actor.actorType, actor.name)}>
|
||||
<Tooltip
|
||||
content={getModifiedByName(actor.actorType, actor.name)}
|
||||
>
|
||||
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */}
|
||||
<div
|
||||
onClick={() =>
|
||||
@@ -824,11 +824,6 @@ export const SecretDetailSidebar = ({
|
||||
}, 3000);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Tooltip
|
||||
className="break-normal text-xs"
|
||||
content="You do not have permission to view this secret value"
|
||||
isDisabled={!secretValueHidden}
|
||||
>
|
||||
<span
|
||||
className={twMerge(
|
||||
@@ -837,7 +832,6 @@ export const SecretDetailSidebar = ({
|
||||
>
|
||||
{secretValueHidden ? "Hidden" : secretValue}
|
||||
</span>
|
||||
</Tooltip>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@@ -885,6 +879,7 @@ export const SecretDetailSidebar = ({
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={`flex items-center justify-center ${version === secretVersion.length ? "hidden" : ""}`}
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user