mirror of
https://github.com/Infisical/infisical.git
synced 2025-03-15 10:29:43 +00:00
Compare commits
1 Commits
patch-6
...
folders-fr
Author | SHA1 | Date | |
---|---|---|---|
017dedd4fa |
@ -29,7 +29,8 @@ export default function NavHeader({
|
||||
isOrganizationRelated,
|
||||
currentEnv,
|
||||
userAvailableEnvs,
|
||||
onEnvChange
|
||||
onEnvChange,
|
||||
secretsPath
|
||||
}: {
|
||||
pageName: string;
|
||||
isProjectRelated?: boolean;
|
||||
@ -37,6 +38,7 @@ export default function NavHeader({
|
||||
currentEnv?: string;
|
||||
userAvailableEnvs?: any[];
|
||||
onEnvChange?: (slug: string) => void;
|
||||
secretsPath?: string;
|
||||
}): JSX.Element {
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const { currentOrg } = useOrganization();
|
||||
@ -75,7 +77,7 @@ export default function NavHeader({
|
||||
{currentEnv && (
|
||||
<>
|
||||
<FontAwesomeIcon icon={faAngleRight} className="ml-3 mr-1.5 text-xs text-gray-400" />
|
||||
<div className="rounded-md pl-3 hover:bg-bunker-100/10">
|
||||
{(!secretsPath || secretsPath === "/") ? <div className="rounded-md pl-3 hover:bg-bunker-100/10">
|
||||
<Tooltip content="Select environment">
|
||||
<Select
|
||||
value={userAvailableEnvs?.filter((uae) => uae.name === currentEnv)[0]?.slug}
|
||||
@ -92,9 +94,23 @@ export default function NavHeader({
|
||||
))}
|
||||
</Select>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div> : <Link
|
||||
passHref
|
||||
legacyBehavior
|
||||
href={{ pathname: router.pathname, query: { id: router.query.id, env: router.query.env } }}
|
||||
>
|
||||
<a className="pl-1.5 text-sm font-semibold text-primary/80 hover:text-primary">{currentEnv}</a>
|
||||
</Link>}
|
||||
</>
|
||||
)}
|
||||
{secretsPath && secretsPath.split("/").map(folder =>
|
||||
(folder) && (
|
||||
<>
|
||||
<FontAwesomeIcon icon={faAngleRight} className="ml-3 mr-3 text-xs text-gray-400" />
|
||||
<div className="text-sm font-semibold text-bunker-300">{folder}</div>
|
||||
</>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -7,9 +7,10 @@ type Props = {
|
||||
isOpen?: boolean;
|
||||
onOpenChange?: (isOpen: boolean) => void;
|
||||
text: string;
|
||||
subText?: string;
|
||||
};
|
||||
|
||||
export const UpgradePlanModal = ({ text, isOpen, onOpenChange }: Props): JSX.Element => (
|
||||
export const UpgradePlanModal = ({ text, subText, isOpen, onOpenChange }: Props): JSX.Element => (
|
||||
<Modal isOpen={isOpen} onOpenChange={onOpenChange}>
|
||||
<ModalContent
|
||||
title="Unleash Infisical's Full Power"
|
||||
@ -28,9 +29,10 @@ export const UpgradePlanModal = ({ text, isOpen, onOpenChange }: Props): JSX.Ele
|
||||
]}
|
||||
>
|
||||
<p className="mb-4 text-bunker-300">{text}</p>
|
||||
<p className="font-medium text-bunker-300">
|
||||
{/* <p className="text-bunker-300">
|
||||
Upgrade and get access to this, as well as to other powerful enhancements.
|
||||
</p>
|
||||
</p> */}
|
||||
<p key={1} className="mt-6 text-xs text-bunker-300">{subText}</p>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
);
|
||||
|
4
frontend/src/hooks/api/secretFolders/index.ts
Normal file
4
frontend/src/hooks/api/secretFolders/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export {
|
||||
useFolderOp,
|
||||
useGetProjectFolderById,
|
||||
useGetProjectFolders} from './queries';
|
114
frontend/src/hooks/api/secretFolders/queries.tsx
Normal file
114
frontend/src/hooks/api/secretFolders/queries.tsx
Normal file
@ -0,0 +1,114 @@
|
||||
/* eslint-disable no-param-reassign */
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
|
||||
import { apiRequest } from '@app/config/request';
|
||||
|
||||
import { secretSnapshotKeys } from '../secretSnapshots/queries';
|
||||
import {
|
||||
FolderDTO,
|
||||
GetProjectFolderDTO,
|
||||
GetProjectSecretsDTO,
|
||||
} from './types';
|
||||
|
||||
export const folderKeys = {
|
||||
// this is also used in secretSnapshot part
|
||||
getProjectFolder: (workspaceId: string, env: string | string[]) => [
|
||||
{ workspaceId, env },
|
||||
'folders'
|
||||
],
|
||||
// getSecretVersion: (secretId: string) => [{ secretId }, 'secret-versions']
|
||||
};
|
||||
|
||||
const fetchProjectFolders = async (workspaceId: string, env: string | string[], secretsPath: string) => {
|
||||
if (typeof env === 'string') {
|
||||
const { data } = await apiRequest.get<{ folders: any[] }>('/api/v2/secrets', {
|
||||
params: {
|
||||
workspaceId,
|
||||
environment: env,
|
||||
secretsPath: secretsPath || '/'
|
||||
}
|
||||
});
|
||||
console.log('folders here', data.folders)
|
||||
return data.folders;
|
||||
}
|
||||
|
||||
if (typeof env === 'object') {
|
||||
let allEnvData: any = [];
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const envPoint of env) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const { data } = await apiRequest.get<{ folders: any[] }>('/api/v2/secrets', {
|
||||
params: {
|
||||
environment: envPoint,
|
||||
workspaceId,
|
||||
secretsPath: secretsPath || '/'
|
||||
}
|
||||
});
|
||||
allEnvData = allEnvData.concat(data.folders);
|
||||
}
|
||||
console.log('folders here2', allEnvData)
|
||||
|
||||
return allEnvData;
|
||||
// eslint-disable-next-line no-else-return
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const useGetProjectFolders = ({
|
||||
workspaceId,
|
||||
env,
|
||||
secretsPath,
|
||||
decryptFileKey,
|
||||
isPaused
|
||||
}: GetProjectSecretsDTO) =>
|
||||
useQuery({
|
||||
// wait for all values to be available
|
||||
enabled: Boolean(decryptFileKey && workspaceId && env) && !isPaused,
|
||||
queryKey: folderKeys.getProjectFolder(workspaceId, env),
|
||||
queryFn: () => fetchProjectFolders(workspaceId, env, secretsPath),
|
||||
select: (data) => {
|
||||
console.log('folders', data)
|
||||
return { folders: data };
|
||||
}
|
||||
});
|
||||
|
||||
const fetchProjectFolderById = async (folderId: string) => {
|
||||
const { data } = await apiRequest.get<{ folder: any[] }>(`/api/v1/folder/${folderId}`);
|
||||
return data.folder;
|
||||
};
|
||||
|
||||
|
||||
export const useGetProjectFolderById = ({
|
||||
folderId,
|
||||
workspaceId,
|
||||
env,
|
||||
isPaused
|
||||
}: GetProjectFolderDTO) =>
|
||||
useQuery({
|
||||
// wait for all values to be available
|
||||
enabled: Boolean(folderId) && !isPaused,
|
||||
queryKey: folderKeys.getProjectFolder(workspaceId, env),
|
||||
queryFn: () => fetchProjectFolderById(folderId),
|
||||
select: (data) => {
|
||||
console.log(666777, data)
|
||||
return data;
|
||||
}
|
||||
});
|
||||
|
||||
export const useFolderOp = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<{}, {}, FolderDTO>({
|
||||
mutationFn: async (dto) => {
|
||||
const { data } = await apiRequest.post('/api/v1/folder/', dto);
|
||||
return data;
|
||||
},
|
||||
onSuccess: (_, dto) => {
|
||||
queryClient.invalidateQueries(folderKeys.getProjectFolder(dto.workspaceId, dto.environment));
|
||||
queryClient.invalidateQueries(secretSnapshotKeys.list(dto.workspaceId));
|
||||
queryClient.invalidateQueries(secretSnapshotKeys.count(dto.workspaceId));
|
||||
}
|
||||
});
|
||||
};
|
120
frontend/src/hooks/api/secretFolders/types.ts
Normal file
120
frontend/src/hooks/api/secretFolders/types.ts
Normal file
@ -0,0 +1,120 @@
|
||||
import type { UserWsKeyPair } from '../keys/types';
|
||||
import type { WsTag } from '../tags/types';
|
||||
|
||||
export type EncryptedSecret = {
|
||||
_id: string;
|
||||
version: number;
|
||||
workspace: string;
|
||||
type: 'shared' | 'personal';
|
||||
environment: string;
|
||||
secretKeyCiphertext: string;
|
||||
secretKeyIV: string;
|
||||
secretKeyTag: string;
|
||||
secretValueCiphertext: string;
|
||||
secretValueIV: string;
|
||||
secretValueTag: string;
|
||||
__v: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
secretCommentCiphertext: string;
|
||||
secretCommentIV: string;
|
||||
secretCommentTag: string;
|
||||
tags: WsTag[];
|
||||
};
|
||||
|
||||
export type DecryptedSecret = {
|
||||
_id: string;
|
||||
key: string;
|
||||
value: string;
|
||||
comment: string;
|
||||
tags: WsTag[];
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
env: string;
|
||||
valueOverride?: string;
|
||||
idOverride?: string;
|
||||
overrideAction?: string;
|
||||
};
|
||||
|
||||
export type EncryptedSecretVersion = {
|
||||
_id: string;
|
||||
secret: string;
|
||||
version: number;
|
||||
workspace: string;
|
||||
type: string;
|
||||
environment: string;
|
||||
isDeleted: boolean;
|
||||
secretKeyCiphertext: string;
|
||||
secretKeyIV: string;
|
||||
secretKeyTag: string;
|
||||
secretValueCiphertext: string;
|
||||
secretValueIV: string;
|
||||
secretValueTag: string;
|
||||
tags: WsTag[];
|
||||
__v: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
};
|
||||
|
||||
// dto
|
||||
type SecretTagArg = { _id: string; name: string; slug: string };
|
||||
|
||||
export type UpdateSecretArg = {
|
||||
_id: string;
|
||||
type: 'shared' | 'personal';
|
||||
secretName: string;
|
||||
secretKeyCiphertext: string;
|
||||
secretKeyIV: string;
|
||||
secretKeyTag: string;
|
||||
secretValueCiphertext: string;
|
||||
secretValueIV: string;
|
||||
secretValueTag: string;
|
||||
secretCommentCiphertext: string;
|
||||
secretCommentIV: string;
|
||||
secretCommentTag: string;
|
||||
tags: SecretTagArg[];
|
||||
};
|
||||
|
||||
export type CreateSecretArg = Omit<UpdateSecretArg, '_id'>;
|
||||
|
||||
export type DeleteSecretArg = { _id: string };
|
||||
|
||||
export type BatchSecretDTO = {
|
||||
workspaceId: string;
|
||||
environment: string;
|
||||
requests: Array<
|
||||
| { method: 'POST'; secret: CreateSecretArg }
|
||||
| { method: 'PATCH'; secret: UpdateSecretArg }
|
||||
| { method: 'DELETE'; secret: DeleteSecretArg }
|
||||
>;
|
||||
};
|
||||
|
||||
export type FolderDTO = {
|
||||
workspaceId: string;
|
||||
environment: string;
|
||||
folderName: string;
|
||||
parentId?: string;
|
||||
}
|
||||
|
||||
export type GetProjectSecretsDTO = {
|
||||
workspaceId: string;
|
||||
env: string | string[];
|
||||
secretsPath: string;
|
||||
decryptFileKey: UserWsKeyPair;
|
||||
isPaused?: boolean;
|
||||
onSuccess?: (data: DecryptedSecret[]) => void;
|
||||
};
|
||||
|
||||
export type GetProjectFolderDTO = {
|
||||
folderId: string;
|
||||
workspaceId: string;
|
||||
env: string | string[];
|
||||
isPaused?: boolean;
|
||||
}
|
||||
|
||||
export type GetSecretVersionsDTO = {
|
||||
secretId: string;
|
||||
limit: number;
|
||||
offset: number;
|
||||
decryptFileKey: UserWsKeyPair;
|
||||
};
|
@ -19,19 +19,20 @@ import {
|
||||
|
||||
export const secretKeys = {
|
||||
// this is also used in secretSnapshot part
|
||||
getProjectSecret: (workspaceId: string, env: string | string[]) => [
|
||||
{ workspaceId, env },
|
||||
getProjectSecret: (workspaceId: string, env: string | string[], secretsPath: string) => [
|
||||
{ workspaceId, env, secretsPath },
|
||||
'secrets'
|
||||
],
|
||||
getSecretVersion: (secretId: string) => [{ secretId }, 'secret-versions']
|
||||
};
|
||||
|
||||
const fetchProjectEncryptedSecrets = async (workspaceId: string, env: string | string[]) => {
|
||||
const fetchProjectEncryptedSecrets = async (workspaceId: string, env: string | string[], secretsPath: string) => {
|
||||
if (typeof env === 'string') {
|
||||
const { data } = await apiRequest.get<{ secrets: EncryptedSecret[] }>('/api/v2/secrets', {
|
||||
params: {
|
||||
environment: env,
|
||||
workspaceId
|
||||
workspaceId,
|
||||
secretsPath: secretsPath || '/'
|
||||
}
|
||||
});
|
||||
return data.secrets;
|
||||
@ -46,7 +47,8 @@ const fetchProjectEncryptedSecrets = async (workspaceId: string, env: string | s
|
||||
const { data } = await apiRequest.get<{ secrets: EncryptedSecret[] }>('/api/v2/secrets', {
|
||||
params: {
|
||||
environment: envPoint,
|
||||
workspaceId
|
||||
workspaceId,
|
||||
secretsPath: secretsPath || '/'
|
||||
}
|
||||
});
|
||||
allEnvData = allEnvData.concat(data.secrets);
|
||||
@ -62,14 +64,15 @@ const fetchProjectEncryptedSecrets = async (workspaceId: string, env: string | s
|
||||
export const useGetProjectSecrets = ({
|
||||
workspaceId,
|
||||
env,
|
||||
secretsPath,
|
||||
decryptFileKey,
|
||||
isPaused
|
||||
}: GetProjectSecretsDTO) =>
|
||||
useQuery({
|
||||
// wait for all values to be available
|
||||
enabled: Boolean(decryptFileKey && workspaceId && env) && !isPaused,
|
||||
queryKey: secretKeys.getProjectSecret(workspaceId, env),
|
||||
queryFn: () => fetchProjectEncryptedSecrets(workspaceId, env),
|
||||
queryKey: secretKeys.getProjectSecret(workspaceId, env, secretsPath),
|
||||
queryFn: () => fetchProjectEncryptedSecrets(workspaceId, env, secretsPath),
|
||||
select: (data) => {
|
||||
const PRIVATE_KEY = localStorage.getItem('PRIVATE_KEY') as string;
|
||||
const latestKey = decryptFileKey;
|
||||
@ -145,14 +148,15 @@ export const useGetProjectSecrets = ({
|
||||
export const useGetProjectSecretsByKey = ({
|
||||
workspaceId,
|
||||
env,
|
||||
decryptFileKey,
|
||||
decryptFileKey,
|
||||
secretsPath,
|
||||
isPaused
|
||||
}: GetProjectSecretsDTO) =>
|
||||
useQuery({
|
||||
// wait for all values to be available
|
||||
enabled: Boolean(decryptFileKey && workspaceId && env) && !isPaused,
|
||||
queryKey: secretKeys.getProjectSecret(workspaceId, env),
|
||||
queryFn: () => fetchProjectEncryptedSecrets(workspaceId, env),
|
||||
queryKey: secretKeys.getProjectSecret(workspaceId, env, secretsPath),
|
||||
queryFn: () => fetchProjectEncryptedSecrets(workspaceId, env, secretsPath),
|
||||
select: (data) => {
|
||||
const PRIVATE_KEY = localStorage.getItem('PRIVATE_KEY') as string;
|
||||
const latestKey = decryptFileKey;
|
||||
@ -283,7 +287,7 @@ export const useBatchSecretsOp = () => {
|
||||
return data;
|
||||
},
|
||||
onSuccess: (_, dto) => {
|
||||
queryClient.invalidateQueries(secretKeys.getProjectSecret(dto.workspaceId, dto.environment));
|
||||
queryClient.invalidateQueries(secretKeys.getProjectSecret(dto.workspaceId, dto.environment, dto.secretsPath));
|
||||
queryClient.invalidateQueries(secretSnapshotKeys.list(dto.workspaceId));
|
||||
queryClient.invalidateQueries(secretSnapshotKeys.count(dto.workspaceId));
|
||||
}
|
||||
|
@ -73,6 +73,7 @@ export type UpdateSecretArg = {
|
||||
secretCommentIV: string;
|
||||
secretCommentTag: string;
|
||||
tags: SecretTagArg[];
|
||||
folderId: string;
|
||||
};
|
||||
|
||||
export type CreateSecretArg = Omit<UpdateSecretArg, '_id'>;
|
||||
@ -82,6 +83,7 @@ export type DeleteSecretArg = { _id: string };
|
||||
export type BatchSecretDTO = {
|
||||
workspaceId: string;
|
||||
environment: string;
|
||||
secretsPath: string;
|
||||
requests: Array<
|
||||
| { method: 'POST'; secret: CreateSecretArg }
|
||||
| { method: 'PATCH'; secret: UpdateSecretArg }
|
||||
@ -92,6 +94,7 @@ export type BatchSecretDTO = {
|
||||
export type GetProjectSecretsDTO = {
|
||||
workspaceId: string;
|
||||
env: string | string[];
|
||||
secretsPath: string;
|
||||
decryptFileKey: UserWsKeyPair;
|
||||
isPaused?: boolean;
|
||||
onSuccess?: (data: DecryptedSecret[]) => void;
|
||||
|
@ -297,34 +297,28 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
||||
<div className={`${currentWorkspace ? 'block' : 'hidden'}`}>
|
||||
<Menu>
|
||||
<Link href={`/dashboard/${currentWorkspace?._id}`} passHref>
|
||||
<a>
|
||||
<MenuItem
|
||||
isSelected={router.asPath.includes(`/dashboard/${currentWorkspace?._id}`)}
|
||||
icon="system-outline-90-lock-closed"
|
||||
>
|
||||
{t('nav:menu.secrets')}
|
||||
</MenuItem>
|
||||
</a>
|
||||
</Link>
|
||||
<Link href={`/users/${currentWorkspace?._id}`} passHref>
|
||||
<a>
|
||||
<MenuItem
|
||||
isSelected={router.asPath === `/users/${currentWorkspace?._id}`}
|
||||
icon="system-outline-96-groups"
|
||||
>
|
||||
{t('nav:menu.members')}
|
||||
</MenuItem>
|
||||
</a>
|
||||
</Link>
|
||||
<Link href={`/integrations/${currentWorkspace?._id}`} passHref>
|
||||
<a>
|
||||
<MenuItem
|
||||
isSelected={router.asPath === `/integrations/${currentWorkspace?._id}`}
|
||||
icon="system-outline-82-extension"
|
||||
>
|
||||
{t('nav:menu.integrations')}
|
||||
</MenuItem>
|
||||
</a>
|
||||
</Link>
|
||||
<Link href={`/activity/${currentWorkspace?._id}`} passHref>
|
||||
<MenuItem
|
||||
@ -336,7 +330,6 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
||||
</MenuItem>
|
||||
</Link>
|
||||
<Link href={`/settings/project/${currentWorkspace?._id}`} passHref>
|
||||
<a>
|
||||
<MenuItem
|
||||
isSelected={
|
||||
router.asPath === `/settings/project/${currentWorkspace?._id}`
|
||||
@ -345,7 +338,6 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
||||
>
|
||||
{t('nav:menu.project-settings')}
|
||||
</MenuItem>
|
||||
</a>
|
||||
</Link>
|
||||
</Menu>
|
||||
</div>
|
||||
|
@ -14,7 +14,7 @@ import {
|
||||
} from '@app/hooks/api';
|
||||
import { WorkspaceEnv } from '@app/hooks/api/types';
|
||||
|
||||
import { EnvComparisonRow } from './components/EnvComparisonRow';
|
||||
import { EnvComparisonFolder, EnvComparisonRow } from './components/EnvComparisonRow';
|
||||
import { FormData, schema } from './DashboardPage.utils';
|
||||
|
||||
export const DashboardEnvOverview = ({ onEnvChange }: { onEnvChange: any }) => {
|
||||
@ -49,6 +49,7 @@ export const DashboardEnvOverview = ({ onEnvChange }: { onEnvChange: any }) => {
|
||||
const { data: secrets, isLoading: isSecretsLoading } = useGetProjectSecretsByKey({
|
||||
workspaceId,
|
||||
env: userAvailableEnvs?.map((env) => env.slug) ?? [],
|
||||
secretsPath: "/",
|
||||
decryptFileKey: latestFileKey!,
|
||||
isPaused: false
|
||||
});
|
||||
@ -167,12 +168,14 @@ export const DashboardEnvOverview = ({ onEnvChange }: { onEnvChange: any }) => {
|
||||
<TableContainer className="border-none">
|
||||
<table className="secret-table relative w-full bg-mineshaft-900">
|
||||
<tbody className="max-h-screen overflow-y-auto">
|
||||
<EnvComparisonFolder
|
||||
folderName="FOLDER_A"
|
||||
/>
|
||||
{Object.keys(secrets?.secrets || {}).map((key, index) => (
|
||||
<EnvComparisonRow
|
||||
key={`row-${key}`}
|
||||
key={`row-${key}-${String(index)}`}
|
||||
secrets={secrets?.secrets?.[key]}
|
||||
isReadOnly={isReadOnly}
|
||||
index={index}
|
||||
isSecretValueHidden
|
||||
userAvailableEnvs={userAvailableEnvs}
|
||||
/>
|
||||
@ -204,7 +207,7 @@ export const DashboardEnvOverview = ({ onEnvChange }: { onEnvChange: any }) => {
|
||||
</div> */}
|
||||
</div>
|
||||
<div className="group mt-4 flex min-w-[60.3rem] flex-row items-center">
|
||||
<div className="flex h-10 w-10 items-center justify-center border-none px-4">
|
||||
<div className="flex h-10 w-14 items-center justify-center border-none px-1">
|
||||
<div className="w-10 text-center text-xs text-transparent">0</div>
|
||||
</div>
|
||||
<div className="flex min-w-[200px] flex-row items-center justify-between lg:min-w-[220px] xl:min-w-[250px]">
|
||||
@ -217,7 +220,7 @@ export const DashboardEnvOverview = ({ onEnvChange }: { onEnvChange: any }) => {
|
||||
return (
|
||||
<div
|
||||
key={`button-${env.slug}`}
|
||||
className="mx-2 mb-1 flex h-10 w-full min-w-[11rem] flex-row items-center justify-center border-none"
|
||||
className="mx-2 mb-1 flex h-10 w-full min-w-[10rem] flex-row items-center justify-center border-none"
|
||||
>
|
||||
<Button
|
||||
onClick={() => onEnvChange(env.slug)}
|
||||
|
@ -1,5 +1,7 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { FormProvider, useFieldArray, useForm } from 'react-hook-form';
|
||||
import {
|
||||
// Controller,
|
||||
FormProvider, useFieldArray, useForm } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useRouter } from 'next/router';
|
||||
import {
|
||||
@ -10,6 +12,7 @@ import {
|
||||
faDownload,
|
||||
faEye,
|
||||
faEyeSlash,
|
||||
faFolder,
|
||||
faMagnifyingGlass,
|
||||
faPlus
|
||||
} from '@fortawesome/free-solid-svg-icons';
|
||||
@ -21,6 +24,7 @@ import { useNotificationContext } from '@app/components/context/Notifications/No
|
||||
import NavHeader from '@app/components/navigation/NavHeader';
|
||||
import {
|
||||
Button,
|
||||
// FormControl,
|
||||
IconButton,
|
||||
Input,
|
||||
Modal,
|
||||
@ -50,6 +54,7 @@ import {
|
||||
usePerformSecretRollback,
|
||||
useRegisterUserAction
|
||||
} from '@app/hooks/api';
|
||||
import { useFolderOp, useGetProjectFolderById, useGetProjectFolders } from '@app/hooks/api/secretFolders/queries';
|
||||
import { secretKeys } from '@app/hooks/api/secrets/queries';
|
||||
import { WorkspaceEnv } from '@app/hooks/api/types';
|
||||
|
||||
@ -58,6 +63,7 @@ import { CreateTagModal } from './components/CreateTagModal';
|
||||
import { PitDrawer } from './components/PitDrawer';
|
||||
import { SecretDetailDrawer } from './components/SecretDetailDrawer';
|
||||
import { SecretDropzone } from './components/SecretDropzone';
|
||||
import { SecretFolderRow } from './components/SecretFolderRow';
|
||||
import { SecretInputRow } from './components/SecretInputRow';
|
||||
import { SecretTableHeader } from './components/SecretTableHeader';
|
||||
import {
|
||||
@ -96,7 +102,9 @@ export const DashboardPage = ({ envFromTop }: { envFromTop: string }) => {
|
||||
'addTag',
|
||||
'secretSnapshots',
|
||||
'uploadedSecOpts',
|
||||
'compareSecrets'
|
||||
'compareSecrets',
|
||||
'createUpdateFolder',
|
||||
'deleteFolder'
|
||||
] as const);
|
||||
const [isSecretValueHidden, setIsSecretValueHidden] = useToggle(true);
|
||||
const [searchFilter, setSearchFilter] = useState('');
|
||||
@ -138,13 +146,41 @@ export const DashboardPage = ({ envFromTop }: { envFromTop: string }) => {
|
||||
decryptFileKey: latestFileKey!
|
||||
});
|
||||
|
||||
let folderInfo: any;
|
||||
let isFolderInfoLoading;
|
||||
if (router.query.folder) {
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
const { data: folderInfoTemp, isLoading: isFolderInfoLoadingTemp } = useGetProjectFolderById({
|
||||
workspaceId,
|
||||
env: selectedEnv?.slug || '',
|
||||
folderId: String(router.query.folder),
|
||||
isPaused: Boolean(snapshotId)
|
||||
});
|
||||
folderInfo = folderInfoTemp;
|
||||
isFolderInfoLoading = isFolderInfoLoadingTemp;
|
||||
|
||||
console.log(98765, folderInfo, isFolderInfoLoading)
|
||||
}
|
||||
|
||||
console.log('getting secrets in', folderInfo?.path || "/")
|
||||
const { data: secrets, isLoading: isSecretsLoading } = useGetProjectSecrets({
|
||||
workspaceId,
|
||||
env: selectedEnv?.slug || '',
|
||||
secretsPath: folderInfo?.path || "/",
|
||||
decryptFileKey: latestFileKey!,
|
||||
isPaused: Boolean(snapshotId)
|
||||
});
|
||||
|
||||
const { data: folders, isLoading: isFoldersLoading } = useGetProjectFolders({
|
||||
workspaceId,
|
||||
env: selectedEnv?.slug || '',
|
||||
secretsPath: folderInfo?.path || "/",
|
||||
decryptFileKey: latestFileKey!,
|
||||
isPaused: Boolean(snapshotId)
|
||||
});
|
||||
|
||||
console.log(444, folders, isFoldersLoading)
|
||||
|
||||
const {
|
||||
data: secretSnaphots,
|
||||
fetchNextPage,
|
||||
@ -168,12 +204,14 @@ export const DashboardPage = ({ envFromTop }: { envFromTop: string }) => {
|
||||
const { data: snapshotCount, isLoading: isLoadingSnapshotCount } =
|
||||
useGetWsSnapshotCount(workspaceId);
|
||||
|
||||
console.log(45678, workspaceId)
|
||||
const { data: wsTags } = useGetWsTags(workspaceId);
|
||||
// mutation calls
|
||||
const { mutateAsync: batchSecretOp } = useBatchSecretsOp();
|
||||
const { mutateAsync: performSecretRollback } = usePerformSecretRollback();
|
||||
const { mutateAsync: registerUserAction } = useRegisterUserAction();
|
||||
const { mutateAsync: createWsTag } = useCreateWsTag();
|
||||
const { mutateAsync: addFolder } = useFolderOp();
|
||||
|
||||
const method = useForm<FormData>({
|
||||
// why any: well yup inferred ts expects other keys to defined as undefined
|
||||
@ -183,6 +221,14 @@ export const DashboardPage = ({ envFromTop }: { envFromTop: string }) => {
|
||||
resolver: yupResolver(schema)
|
||||
});
|
||||
|
||||
const methodFolders = useForm<FormData>({
|
||||
// why any: well yup inferred ts expects other keys to defined as undefined
|
||||
defaultValues: folders as any,
|
||||
values: folders as any,
|
||||
mode: 'onBlur',
|
||||
resolver: yupResolver(schema)
|
||||
});
|
||||
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
@ -191,7 +237,24 @@ export const DashboardPage = ({ envFromTop }: { envFromTop: string }) => {
|
||||
formState: { isSubmitting, isDirty },
|
||||
reset
|
||||
} = method;
|
||||
|
||||
const {
|
||||
control: controlFolders,
|
||||
// handleSubmit: handleSubmitFolders,
|
||||
// getValues: getValuesFolders,
|
||||
// setValue: setValueFolders,
|
||||
// formState: { isFoldersSubmitting, isFoldersDirty },
|
||||
// reset: resetFolders
|
||||
} = methodFolders;
|
||||
const { fields, prepend, append, remove } = useFieldArray({ control, name: 'secrets' });
|
||||
const {
|
||||
fields: fieldsFolders,
|
||||
prepend: prependFolders,
|
||||
// append: appendFolders,
|
||||
// remove: removeFolders,
|
||||
update: updateFolders
|
||||
} = useFieldArray({ control: controlFolders, name: 'folders' });
|
||||
console.log(777, fields, fieldsFolders)
|
||||
const isRollbackMode = Boolean(snapshotId);
|
||||
const isReadOnly = selectedEnv?.isWriteDenied;
|
||||
const isAddOnly = selectedEnv?.isReadDenied && !selectedEnv?.isWriteDenied;
|
||||
@ -286,7 +349,7 @@ export const DashboardPage = ({ envFromTop }: { envFromTop: string }) => {
|
||||
setValue('isSnapshotMode', false);
|
||||
setSnaphotId(null);
|
||||
queryClient.invalidateQueries(
|
||||
secretKeys.getProjectSecret(workspaceId, selectedEnv?.slug || '')
|
||||
secretKeys.getProjectSecret(workspaceId, selectedEnv?.slug || '', folderInfo?.path)
|
||||
);
|
||||
createNotification({
|
||||
text: 'Successfully rollback secrets',
|
||||
@ -314,17 +377,32 @@ export const DashboardPage = ({ envFromTop }: { envFromTop: string }) => {
|
||||
const sec = isAddOnly ? userSec.filter(({ _id }) => !_id) : userSec;
|
||||
// encrypt and format the secrets to batch api format
|
||||
// requests = [ {method:"", secret:""} ]
|
||||
fieldsFolders.filter(folder => !folder._id).map(async (folder) => {
|
||||
await addFolder({
|
||||
workspaceId,
|
||||
environment: String(selectedEnv?.slug),
|
||||
folderName: String(folder.name)
|
||||
})
|
||||
})
|
||||
const batchedSecret = transformSecretsToBatchSecretReq(
|
||||
deletedSecretIds.current,
|
||||
latestFileKey,
|
||||
sec,
|
||||
secrets?.secrets
|
||||
String(router.query.folder),
|
||||
secrets?.secrets,
|
||||
);
|
||||
// type check
|
||||
if (!selectedEnv?.slug) return;
|
||||
try {
|
||||
console.log(5, {
|
||||
requests: batchedSecret,
|
||||
secretsPath: folderInfo?.path || "/",
|
||||
workspaceId,
|
||||
environment: selectedEnv?.slug
|
||||
})
|
||||
await batchSecretOp({
|
||||
requests: batchedSecret,
|
||||
secretsPath: folderInfo?.path || "/",
|
||||
workspaceId,
|
||||
environment: selectedEnv?.slug
|
||||
});
|
||||
@ -401,6 +479,40 @@ export const DashboardPage = ({ envFromTop }: { envFromTop: string }) => {
|
||||
);
|
||||
}
|
||||
|
||||
const isFolderUpdate = Boolean(popUp?.createUpdateFolder?.data);
|
||||
// const oldFolderName = (popUp?.createUpdateFolder?.data as { slug: string })?.slug;
|
||||
const onFolderModalSubmit = async (data: any) => {
|
||||
console.log('checkcheckcheckcheck', isFolderUpdate)
|
||||
if (isFolderUpdate) {
|
||||
console.log('updating', data.id)
|
||||
// removeFolders(data.id)
|
||||
updateFolders(fieldsFolders.map(folder => folder.id).indexOf(data.id), {
|
||||
// id: data.id,
|
||||
_id: data._id,
|
||||
name: data.name,
|
||||
});
|
||||
} else {
|
||||
// await onCreate(data);
|
||||
if (secretContainer.current) {
|
||||
secretContainer.current.scroll({
|
||||
top: 0,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
}
|
||||
console.log(555666)
|
||||
prependFolders({
|
||||
_id: undefined,
|
||||
name: data.name,
|
||||
}, { shouldFocus: false });
|
||||
}
|
||||
handlePopUpClose('createUpdateFolder');
|
||||
};
|
||||
|
||||
// const onFolderDeleteSubmit = async (envSlug: string) => {
|
||||
// await onDelete(envSlug);
|
||||
// handlePopUpClose('deleteFolder');
|
||||
// };
|
||||
|
||||
// when secrets is not loading and secrets list is empty
|
||||
const isDashboardSecretEmpty = !isSecretsLoading && false;
|
||||
// when using snapshot mode and snapshot is loading and snapshot list is empty
|
||||
@ -426,6 +538,7 @@ export const DashboardPage = ({ envFromTop }: { envFromTop: string }) => {
|
||||
isProjectRelated
|
||||
userAvailableEnvs={userAvailableEnvs}
|
||||
onEnvChange={onEnvChange}
|
||||
secretsPath={folderInfo?.path || "/"}
|
||||
/>
|
||||
</div>
|
||||
{/* This is only for rollbacks */}
|
||||
@ -526,11 +639,19 @@ export const DashboardPage = ({ envFromTop }: { envFromTop: string }) => {
|
||||
{!isReadOnly && !isRollbackMode && (
|
||||
<>
|
||||
<div className='block lg:hidden'>
|
||||
<Tooltip content='Point-in-time Recovery'>
|
||||
<Tooltip content='Add Secret'>
|
||||
<IconButton
|
||||
ariaLabel="recovery"
|
||||
variant="outline_bg"
|
||||
onClick={() => setIsSecretValueHidden.toggle()}
|
||||
onClick={() => {
|
||||
if (secretContainer.current) {
|
||||
secretContainer.current.scroll({
|
||||
top: 0,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
}
|
||||
prepend(DEFAULT_SECRET_VALUE, { shouldFocus: false });
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon icon={faPlus} />
|
||||
</IconButton>
|
||||
@ -555,6 +676,20 @@ export const DashboardPage = ({ envFromTop }: { envFromTop: string }) => {
|
||||
Add Secret
|
||||
</Button>
|
||||
</div>
|
||||
<div className=''>
|
||||
<Tooltip content='Add Folder'>
|
||||
<IconButton
|
||||
ariaLabel="recovery"
|
||||
variant="outline_bg"
|
||||
onClick={() => {
|
||||
handlePopUpOpen('createUpdateFolder', undefined)
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon icon={faPlus} />
|
||||
<FontAwesomeIcon icon={faFolder} className="pl-2"/>
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<Button
|
||||
@ -579,6 +714,21 @@ export const DashboardPage = ({ envFromTop }: { envFromTop: string }) => {
|
||||
<table className="secret-table relative">
|
||||
<SecretTableHeader sortDir={sortDir} onSort={onSortSecrets} />
|
||||
<tbody className="max-h-96 overflow-y-auto">
|
||||
{/* ["Folder 1", "Folder 2", "Folder 3"] */}
|
||||
{fieldsFolders.map(({ _id, name }, index) => (
|
||||
<SecretFolderRow
|
||||
key={_id}
|
||||
index={index}
|
||||
_id={String(_id)}
|
||||
handlePopUpOpen={handlePopUpOpen}
|
||||
// index={index}
|
||||
searchTerm={searchFilter}
|
||||
name={String(name)}
|
||||
reset={reset}
|
||||
onFolderDelete={() => {}}
|
||||
// onRowExpand={() => onDrawerOpen({ id: _id as string, index })}
|
||||
/>
|
||||
))}
|
||||
{fields.map(({ id, _id }, index) => (
|
||||
<SecretInputRow
|
||||
key={id}
|
||||
@ -612,6 +762,46 @@ export const DashboardPage = ({ envFromTop }: { envFromTop: string }) => {
|
||||
</table>
|
||||
</TableContainer>
|
||||
)}
|
||||
<Modal
|
||||
isOpen={popUp?.createUpdateFolder?.isOpen}
|
||||
onOpenChange={(isOpen) => {
|
||||
handlePopUpToggle('createUpdateFolder', isOpen);
|
||||
reset();
|
||||
}}
|
||||
>
|
||||
<ModalContent title={isFolderUpdate ? 'Update folder name' : 'Create a new folder'}>
|
||||
<form onSubmit={handleSubmit(onFolderModalSubmit)}>
|
||||
{/* <Controller
|
||||
control={control}
|
||||
// defaultValue=""
|
||||
name="folders"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Folder Name"
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
)}
|
||||
/> */}
|
||||
<div className="mt-8 flex items-center">
|
||||
<Button
|
||||
className="mr-4"
|
||||
size="sm"
|
||||
type="submit"
|
||||
isLoading={isSubmitting}
|
||||
isDisabled={isSubmitting}
|
||||
>
|
||||
{isFolderUpdate ? 'Update' : 'Create'}
|
||||
</Button>
|
||||
<Button colorSchema="secondary" variant="outline" className="border-none">
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
<PitDrawer
|
||||
isDrawerOpen={popUp?.secretSnapshots?.isOpen}
|
||||
onOpenChange={(isOpen) => handlePopUpToggle('secretSnapshots', isOpen)}
|
||||
|
@ -66,9 +66,15 @@ const secretSchema = yup.object({
|
||||
valueOverride: yup.string().trim().notRequired()
|
||||
});
|
||||
|
||||
const folderSchema = yup.object({
|
||||
_id: yup.string(),
|
||||
name: yup.string().trim(),
|
||||
});
|
||||
|
||||
export const schema = yup.object({
|
||||
isSnapshotMode: yup.bool().notRequired(),
|
||||
secrets: yup.array(secretSchema)
|
||||
secrets: yup.array(secretSchema),
|
||||
folders: yup.array(folderSchema)
|
||||
});
|
||||
|
||||
export type FormData = yup.InferType<typeof schema>;
|
||||
@ -159,7 +165,8 @@ export const transformSecretsToBatchSecretReq = (
|
||||
deletedSecretIds: string[],
|
||||
latestFileKey: any,
|
||||
secrets: FormData['secrets'],
|
||||
intialValues: DecryptedSecret[] = []
|
||||
folderId: string,
|
||||
intialValues: DecryptedSecret[] = [],
|
||||
) => {
|
||||
// deleted secrets
|
||||
const secretsToBeDeleted: BatchSecretDTO['requests'] = deletedSecretIds.map((id) => ({
|
||||
@ -198,6 +205,7 @@ export const transformSecretsToBatchSecretReq = (
|
||||
type: 'personal',
|
||||
tags,
|
||||
secretName: key,
|
||||
folderId,
|
||||
...encryptASecret(randomBytes, key, valueOverride, comment)
|
||||
}
|
||||
});
|
||||
@ -210,6 +218,7 @@ export const transformSecretsToBatchSecretReq = (
|
||||
type: 'shared',
|
||||
tags,
|
||||
secretName: key,
|
||||
folderId,
|
||||
...encryptASecret(randomBytes, key, value, comment)
|
||||
}
|
||||
});
|
||||
@ -227,6 +236,7 @@ export const transformSecretsToBatchSecretReq = (
|
||||
type: 'shared',
|
||||
tags,
|
||||
secretName: key,
|
||||
folderId,
|
||||
...encryptASecret(randomBytes, key, value, comment)
|
||||
}
|
||||
});
|
||||
@ -247,6 +257,7 @@ export const transformSecretsToBatchSecretReq = (
|
||||
type: 'personal',
|
||||
tags,
|
||||
secretName: key,
|
||||
folderId,
|
||||
...encryptASecret(randomBytes, key, valueOverride, comment)
|
||||
}
|
||||
});
|
||||
|
@ -0,0 +1,43 @@
|
||||
/* eslint-disable react/jsx-no-useless-fragment */
|
||||
import { faAngleRight, faFolder } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
|
||||
type Props = {
|
||||
folderName: string;
|
||||
};
|
||||
|
||||
export const EnvComparisonFolder = ({
|
||||
folderName,
|
||||
}: Props): JSX.Element => {
|
||||
// const [isOpen, setIsOpen] = useState(true);
|
||||
|
||||
// const getSecretByEnv = useCallback(
|
||||
// (secEnv: string, secs?: any[]) => secs?.find(({ env }) => env === secEnv),
|
||||
// []
|
||||
// );
|
||||
|
||||
return (
|
||||
<tr className="group flex min-w-full flex-row items-center hover:bg-mineshaft-800">
|
||||
<td className="flex h-10 w-14 items-center justify-center border-none">
|
||||
<div className="text-center text-xs flex itesm-center flex-row pr-4">
|
||||
<FontAwesomeIcon icon={faAngleRight} className="w-3.5 h-3.5 text-bunker-400 pl-6 pt-[0.05rem]" />
|
||||
<FontAwesomeIcon icon={faFolder} className="w-4 h-4 text-yellow-400/50 pl-1" />
|
||||
</div>
|
||||
</td>
|
||||
<td className="flex h-full min-w-[200px] flex-row items-center justify-between lg:min-w-[220px] xl:min-w-[250px]">
|
||||
<div className="flex h-8 cursor-default flex-row items-center truncate">
|
||||
{folderName}
|
||||
</div>
|
||||
</td>
|
||||
{/* {userAvailableEnvs?.map(({ slug }) => (
|
||||
<DashboardInput
|
||||
isReadOnly={isReadOnly}
|
||||
key={`row-${folderName || ''}-${slug}`}
|
||||
isOverridden={false}
|
||||
secret={getSecretByEnv(slug, folderName)}
|
||||
isSecretValueHidden={areValuesHiddenThisRow && isSecretValueHidden}
|
||||
/>
|
||||
))} */}
|
||||
</tr>
|
||||
);
|
||||
};
|
@ -1,11 +1,10 @@
|
||||
/* eslint-disable react/jsx-no-useless-fragment */
|
||||
import { useCallback, useState } from 'react';
|
||||
import { faCircle, faEye, faEyeSlash } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faCircle, faEye, faEyeSlash, faKey } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
type Props = {
|
||||
index: number;
|
||||
secrets: any[] | undefined;
|
||||
// permission and external state's that decided to hide or show
|
||||
isReadOnly?: boolean;
|
||||
@ -108,7 +107,6 @@ const DashboardInput = ({
|
||||
};
|
||||
|
||||
export const EnvComparisonRow = ({
|
||||
index,
|
||||
secrets,
|
||||
isSecretValueHidden,
|
||||
isReadOnly,
|
||||
@ -123,8 +121,10 @@ export const EnvComparisonRow = ({
|
||||
|
||||
return (
|
||||
<tr className="group flex min-w-full flex-row items-center hover:bg-mineshaft-800">
|
||||
<td className="flex h-10 w-10 items-center justify-center border-none px-4">
|
||||
<div className="w-10 text-center text-xs text-bunker-400">{index + 1}</div>
|
||||
<td className="flex h-10 w-14 items-center justify-center border-none px-4">
|
||||
<div className="w-10 text-center text-xs">
|
||||
<FontAwesomeIcon icon={faKey} className="w-3 h-3 text-blue-400/50 pl-7 pt-0.5" />
|
||||
</div>
|
||||
</td>
|
||||
<td className="flex h-full min-w-[200px] flex-row items-center justify-between lg:min-w-[220px] xl:min-w-[250px]">
|
||||
<div className="flex h-8 cursor-default flex-row items-center truncate">
|
||||
|
@ -1 +1,2 @@
|
||||
export { EnvComparisonFolder } from './EnvComparisonFolder';
|
||||
export { EnvComparisonRow } from './EnvComparisonRow';
|
||||
|
@ -0,0 +1,156 @@
|
||||
/* eslint-disable react/jsx-no-useless-fragment */
|
||||
import { memo, useRef } from 'react';
|
||||
import { useFormContext, useWatch } from 'react-hook-form';
|
||||
import { useRouter } from 'next/router';
|
||||
import {
|
||||
faFolder,
|
||||
faPencil,
|
||||
faXmark
|
||||
} from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
|
||||
import {
|
||||
IconButton,
|
||||
Tooltip
|
||||
} from '@app/components/v2';
|
||||
|
||||
import { FormData } from '../../DashboardPage.utils';
|
||||
|
||||
type Props = {
|
||||
index: number;
|
||||
_id: string;
|
||||
// permission and external state's that decided to hide or show
|
||||
isReadOnly?: boolean;
|
||||
isAddOnly?: boolean;
|
||||
isRollbackMode?: boolean;
|
||||
searchTerm: string;
|
||||
// to record the ids of deleted ones
|
||||
onFolderDelete: (index: number, id?: string, overrideId?: string) => void;
|
||||
// sidebar control props
|
||||
handlePopUpOpen: (popUpName: "createUpdateFolder", data: any) => void;
|
||||
name: string;
|
||||
reset: any;
|
||||
};
|
||||
|
||||
|
||||
export const SecretFolderRow = memo(
|
||||
({
|
||||
index,
|
||||
_id,
|
||||
handlePopUpOpen,
|
||||
isReadOnly,
|
||||
isRollbackMode,
|
||||
isAddOnly,
|
||||
onFolderDelete,
|
||||
searchTerm,
|
||||
name,
|
||||
reset
|
||||
}: Props): JSX.Element => {
|
||||
const isKeySubDisabled = useRef<boolean>(false);
|
||||
const {
|
||||
// register, setValue,
|
||||
control } = useFormContext<FormData>();
|
||||
console.log(123, _id, name)
|
||||
const router = useRouter();
|
||||
|
||||
// to get details on a secret
|
||||
// const overrideAction = useWatch({ control, name: `secrets.${index}.overrideAction` });
|
||||
const idOverride = useWatch({ control, name: `secrets.${index}.idOverride` });
|
||||
const secComment = useWatch({ control, name: `secrets.${index}.comment` });
|
||||
const secKey = useWatch({
|
||||
control,
|
||||
name: `secrets.${index}.key`,
|
||||
disabled: isKeySubDisabled.current
|
||||
});
|
||||
const secId = useWatch({ control, name: `secrets.${index}._id` });
|
||||
|
||||
const tags = useWatch({ control, name: `secrets.${index}.tags`, defaultValue: [] }) || [];
|
||||
// const selectedTagIds = tags.reduce<Record<string, boolean>>(
|
||||
// (prev, curr) => ({ ...prev, [curr.slug]: true }),
|
||||
// {}
|
||||
// );
|
||||
|
||||
// const isCreatedSecret = !secId;
|
||||
// const shouldBeBlockedInAddOnly = !isCreatedSecret && isAddOnly;
|
||||
|
||||
// Why this instead of filter in parent
|
||||
// Because rhf field.map has default values so basically
|
||||
// keys are not updated there and index needs to kept so that we can monitor
|
||||
// values individually here
|
||||
if (
|
||||
!(
|
||||
secKey?.toUpperCase().includes(searchTerm?.toUpperCase()) ||
|
||||
tags
|
||||
?.map((tag) => tag.name)
|
||||
.join(' ')
|
||||
?.toUpperCase()
|
||||
.includes(searchTerm?.toUpperCase()) ||
|
||||
secComment?.toUpperCase().includes(searchTerm?.toUpperCase())
|
||||
)
|
||||
) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return (
|
||||
<tr
|
||||
className="group flex flex-row items-center cursor-default hover:bg-mineshaft-700"
|
||||
key={index}
|
||||
>
|
||||
<td className="flex h-10 w-10 items-center justify-center px-4 border-none">
|
||||
{/* <div className="w-10 text-center text-xs text-bunker-400">{index + 1}</div> */}
|
||||
<div className="w-10 text-center text-xs"><FontAwesomeIcon icon={faFolder} className="w-4 h-4 text-yellow-400/50 pl-2.5 pt-0.5" /></div>
|
||||
</td>
|
||||
<button
|
||||
type="button"
|
||||
className="w-full border-none ml-2.5 text-left cursor-default"
|
||||
onClick={async () => {
|
||||
await router.push({
|
||||
pathname: router.pathname,
|
||||
query: { ...router.query, folder: _id }
|
||||
})
|
||||
router.reload();
|
||||
}}
|
||||
>{name}</button>
|
||||
<td className="min-w-sm flex h-10 items-center">
|
||||
<div className="duration-0 ml-auto w-0 flex items-center justify-end space-x-2.5 overflow-hidden transition-all w-16 border-l border-mineshaft-600 h-10">
|
||||
{!isAddOnly && (
|
||||
<div className="opacity-0 group-hover:opacity-100">
|
||||
<Tooltip content="Settings" className="z-50">
|
||||
<IconButton
|
||||
size="md"
|
||||
colorSchema="primary"
|
||||
variant="plain"
|
||||
onClick={() => {
|
||||
console.log(888, {id: _id, name})
|
||||
handlePopUpOpen('createUpdateFolder', {id: _id, name});
|
||||
reset({id: _id, name});
|
||||
}}
|
||||
ariaLabel="expand"
|
||||
>
|
||||
<FontAwesomeIcon icon={faPencil} />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
)}
|
||||
<div className="opacity-0 group-hover:opacity-100">
|
||||
<Tooltip content="Delete" className="z-50">
|
||||
<IconButton
|
||||
size="md"
|
||||
variant="plain"
|
||||
colorSchema="danger"
|
||||
ariaLabel="delete"
|
||||
isDisabled={isReadOnly || isRollbackMode}
|
||||
onClick={() => onFolderDelete(index, secId, idOverride)}
|
||||
>
|
||||
<FontAwesomeIcon icon={faXmark} />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
SecretFolderRow.displayName = 'SecretFolderRow';
|
@ -0,0 +1 @@
|
||||
export { SecretFolderRow } from './SecretFolderRow';
|
@ -161,7 +161,7 @@ export const SecretInputRow = memo(
|
||||
<tr className="group flex flex-row items-center" key={index}>
|
||||
<td className="flex h-10 w-10 items-center justify-center px-4 border-none">
|
||||
{/* <div className="w-10 text-center text-xs text-bunker-400">{index + 1}</div> */}
|
||||
<div className="w-10 text-center text-xs text-bunker-400"><FontAwesomeIcon icon={faKey} className="w-4 h-4 text-bunker-400/60 pl-2.5 pt-0.5" /></div>
|
||||
<div className="w-10 text-center text-xs text-bunker-400"><FontAwesomeIcon icon={faKey} className="w-4 h-4 text-blue-400/50 pl-2.5 pt-0.5" /></div>
|
||||
</td>
|
||||
<Controller
|
||||
control={control}
|
||||
|
@ -216,7 +216,6 @@ export const EnvironmentSection = ({
|
||||
>
|
||||
{isEnvUpdate ? 'Update' : 'Create'}
|
||||
</Button>
|
||||
|
||||
<Button colorSchema="secondary" variant="plain">
|
||||
Cancel
|
||||
</Button>
|
||||
|
Reference in New Issue
Block a user