mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-08 18:28:12 +00:00
feat(ui): added api hooks and state hooks for dashboard
This commit is contained in:
@@ -2,6 +2,8 @@ export * from './auth';
|
|||||||
export * from './incidentContacts';
|
export * from './incidentContacts';
|
||||||
export * from './keys';
|
export * from './keys';
|
||||||
export * from './organization';
|
export * from './organization';
|
||||||
|
export * from './secrets';
|
||||||
|
export * from './secretSnapshots';
|
||||||
export * from './serviceTokens';
|
export * from './serviceTokens';
|
||||||
export * from './subscriptions';
|
export * from './subscriptions';
|
||||||
export * from './tags';
|
export * from './tags';
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
export {
|
||||||
|
useGetSnapshotSecrets,
|
||||||
|
useGetWorkspaceSecretSnapshots,
|
||||||
|
useGetWsSnapshotCount,
|
||||||
|
usePerformSecretRollback
|
||||||
|
} from './queries';
|
||||||
@@ -0,0 +1,154 @@
|
|||||||
|
/* eslint-disable no-param-reassign */
|
||||||
|
import { useInfiniteQuery, useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import {
|
||||||
|
decryptAssymmetric,
|
||||||
|
decryptSymmetric
|
||||||
|
} from '@app/components/utilities/cryptography/crypto';
|
||||||
|
import { apiRequest } from '@app/config/request';
|
||||||
|
|
||||||
|
import { DecryptedSecret } from '../secrets/types';
|
||||||
|
import {
|
||||||
|
GetWorkspaceSecretSnapshotsDTO,
|
||||||
|
TSecretRollbackDTO,
|
||||||
|
TSnapshotSecret,
|
||||||
|
TSnapshotSecretProps,
|
||||||
|
TWorkspaceSecretSnapshot
|
||||||
|
} from './types';
|
||||||
|
|
||||||
|
export const secretSnapshotKeys = {
|
||||||
|
list: (workspaceId: string) => [{ workspaceId }, 'secret-snapshot'] as const,
|
||||||
|
snapshotSecrets: (snapshotId: string) => [{ snapshotId }, 'secret-snapshot'] as const,
|
||||||
|
count: (workspaceId: string) => [{ workspaceId }, 'count', 'secret-snapshot']
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchWorkspaceSecretSnaphots = async (workspaceId: string, limit = 10, offset = 0) => {
|
||||||
|
const res = await apiRequest.get<{ secretSnapshots: TWorkspaceSecretSnapshot[] }>(
|
||||||
|
`/api/v1/workspace/${workspaceId}/secret-snapshots`,
|
||||||
|
{
|
||||||
|
params: {
|
||||||
|
limit,
|
||||||
|
offset
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return res.data.secretSnapshots;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useGetWorkspaceSecretSnapshots = (dto: GetWorkspaceSecretSnapshotsDTO) =>
|
||||||
|
useInfiniteQuery({
|
||||||
|
enabled: Boolean(dto.workspaceId),
|
||||||
|
queryKey: secretSnapshotKeys.list(dto.workspaceId),
|
||||||
|
queryFn: ({ pageParam }) => fetchWorkspaceSecretSnaphots(dto.workspaceId, dto.limit, pageParam),
|
||||||
|
getNextPageParam: (lastPage, pages) =>
|
||||||
|
lastPage.length !== 0 ? pages.length * dto.limit : undefined
|
||||||
|
});
|
||||||
|
|
||||||
|
const fetchSnapshotEncSecrets = async (snapshotId: string) => {
|
||||||
|
const res = await apiRequest.get<{ secretSnapshot: TSnapshotSecret }>(
|
||||||
|
`/api/v1/secret-snapshot/${snapshotId}`
|
||||||
|
);
|
||||||
|
return res.data.secretSnapshot;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useGetSnapshotSecrets = ({ decryptFileKey, env, snapshotId }: TSnapshotSecretProps) =>
|
||||||
|
useQuery({
|
||||||
|
queryKey: secretSnapshotKeys.snapshotSecrets(snapshotId),
|
||||||
|
enabled: Boolean(snapshotId && decryptFileKey),
|
||||||
|
queryFn: () => fetchSnapshotEncSecrets(snapshotId),
|
||||||
|
select: (data) => {
|
||||||
|
const PRIVATE_KEY = localStorage.getItem('PRIVATE_KEY') as string;
|
||||||
|
const latestKey = decryptFileKey;
|
||||||
|
const key = decryptAssymmetric({
|
||||||
|
ciphertext: latestKey.encryptedKey,
|
||||||
|
nonce: latestKey.nonce,
|
||||||
|
publicKey: latestKey.sender.publicKey,
|
||||||
|
privateKey: PRIVATE_KEY
|
||||||
|
});
|
||||||
|
|
||||||
|
const sharedSecrets: DecryptedSecret[] = [];
|
||||||
|
const personalSecrets: Record<string, { id: string; value: string }> = {};
|
||||||
|
data.secretVersions
|
||||||
|
.filter(({ environment }) => environment === env)
|
||||||
|
.forEach((encSecret) => {
|
||||||
|
const secretKey = decryptSymmetric({
|
||||||
|
ciphertext: encSecret.secretKeyCiphertext,
|
||||||
|
iv: encSecret.secretKeyIV,
|
||||||
|
tag: encSecret.secretKeyTag,
|
||||||
|
key
|
||||||
|
});
|
||||||
|
|
||||||
|
const secretValue = decryptSymmetric({
|
||||||
|
ciphertext: encSecret.secretValueCiphertext,
|
||||||
|
iv: encSecret.secretValueIV,
|
||||||
|
tag: encSecret.secretValueTag,
|
||||||
|
key
|
||||||
|
});
|
||||||
|
|
||||||
|
const secretComment = '';
|
||||||
|
|
||||||
|
const decryptedSecret = {
|
||||||
|
_id: encSecret.secret,
|
||||||
|
env: encSecret.environment,
|
||||||
|
key: secretKey,
|
||||||
|
value: secretValue,
|
||||||
|
tags: encSecret.tags,
|
||||||
|
comment: secretComment,
|
||||||
|
createdAt: encSecret.createdAt,
|
||||||
|
updatedAt: encSecret.updatedAt,
|
||||||
|
type: 'modified'
|
||||||
|
};
|
||||||
|
|
||||||
|
if (encSecret.type === 'personal') {
|
||||||
|
personalSecrets[decryptedSecret.key] = { id: encSecret.secret, value: secretValue };
|
||||||
|
} else {
|
||||||
|
sharedSecrets.push(decryptedSecret);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
sharedSecrets.forEach((val) => {
|
||||||
|
if (personalSecrets?.[val.key]) {
|
||||||
|
val.idOverride = personalSecrets[val.key].id;
|
||||||
|
val.valueOverride = personalSecrets[val.key].value;
|
||||||
|
val.overrideAction = 'modified';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return { version: data.version, secrets: sharedSecrets, createdAt: data.createdAt };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const fetchWorkspaceSecretSnaphotCount = async (workspaceId: string) => {
|
||||||
|
const res = await apiRequest.get<{ count: number }>(
|
||||||
|
`/api/v1/workspace/${workspaceId}/secret-snapshots/count`
|
||||||
|
);
|
||||||
|
return res.data.count;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useGetWsSnapshotCount = (workspaceId: string) =>
|
||||||
|
useQuery({
|
||||||
|
enabled: Boolean(workspaceId),
|
||||||
|
queryKey: secretSnapshotKeys.count(workspaceId),
|
||||||
|
queryFn: () => fetchWorkspaceSecretSnaphotCount(workspaceId)
|
||||||
|
});
|
||||||
|
|
||||||
|
export const usePerformSecretRollback = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
return useMutation<{}, {}, TSecretRollbackDTO>({
|
||||||
|
mutationFn: async (dto) => {
|
||||||
|
const { data } = await apiRequest.post(
|
||||||
|
`/api/v1/workspace/${dto.workspaceId}/secret-snapshots/rollback`,
|
||||||
|
{
|
||||||
|
version: dto.version
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
onSuccess: (_, dto) => {
|
||||||
|
queryClient.invalidateQueries(secretSnapshotKeys.list(dto.workspaceId));
|
||||||
|
queryClient.invalidateQueries(secretSnapshotKeys.count(dto.workspaceId));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import { UserWsKeyPair } from '../keys/types';
|
||||||
|
import { EncryptedSecretVersion } from '../secrets/types';
|
||||||
|
|
||||||
|
export type TWorkspaceSecretSnapshot = {
|
||||||
|
_id: string;
|
||||||
|
workspace: string;
|
||||||
|
version: number;
|
||||||
|
secretVersions: string[];
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
__v: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TSnapshotSecret = Omit<TWorkspaceSecretSnapshot, 'secretVersions'> & {
|
||||||
|
secretVersions: EncryptedSecretVersion[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TSnapshotSecretProps = {
|
||||||
|
snapshotId: string;
|
||||||
|
env: string;
|
||||||
|
decryptFileKey: UserWsKeyPair;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type GetWorkspaceSecretSnapshotsDTO = {
|
||||||
|
workspaceId: string;
|
||||||
|
limit: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TSecretRollbackDTO = {
|
||||||
|
workspaceId: string;
|
||||||
|
version: number;
|
||||||
|
};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { useBatchSecretsOp, useGetProjectSecrets, useGetSecretVersion } from './queries';
|
||||||
@@ -0,0 +1,173 @@
|
|||||||
|
/* eslint-disable no-param-reassign */
|
||||||
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import {
|
||||||
|
decryptAssymmetric,
|
||||||
|
decryptSymmetric
|
||||||
|
} from '@app/components/utilities/cryptography/crypto';
|
||||||
|
import { apiRequest } from '@app/config/request';
|
||||||
|
|
||||||
|
import { secretSnapshotKeys } from '../secretSnapshots/queries';
|
||||||
|
import {
|
||||||
|
BatchSecretDTO,
|
||||||
|
DecryptedSecret,
|
||||||
|
EncryptedSecret,
|
||||||
|
EncryptedSecretVersion,
|
||||||
|
GetProjectSecretsDTO,
|
||||||
|
GetSecretVersionsDTO
|
||||||
|
} from './types';
|
||||||
|
|
||||||
|
export const secretKeys = {
|
||||||
|
// this is also used in secretSnapshot part
|
||||||
|
getProjectSecret: (workspaceId: string, env: string) => [{ workspaceId, env }, 'secrets'],
|
||||||
|
getSecretVersion: (secretId: string) => [{ secretId }, 'secret-versions']
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchProjectEncryptedSecrets = async (workspaceId: string, env: string) => {
|
||||||
|
const { data } = await apiRequest.get<{ secrets: EncryptedSecret[] }>('/api/v2/secrets', {
|
||||||
|
params: {
|
||||||
|
environment: env,
|
||||||
|
workspaceId
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return data.secrets;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useGetProjectSecrets = ({
|
||||||
|
workspaceId,
|
||||||
|
env,
|
||||||
|
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),
|
||||||
|
select: (data) => {
|
||||||
|
const PRIVATE_KEY = localStorage.getItem('PRIVATE_KEY') as string;
|
||||||
|
const latestKey = decryptFileKey;
|
||||||
|
const key = decryptAssymmetric({
|
||||||
|
ciphertext: latestKey.encryptedKey,
|
||||||
|
nonce: latestKey.nonce,
|
||||||
|
publicKey: latestKey.sender.publicKey,
|
||||||
|
privateKey: PRIVATE_KEY
|
||||||
|
});
|
||||||
|
|
||||||
|
const sharedSecrets: DecryptedSecret[] = [];
|
||||||
|
const personalSecrets: Record<string, { id: string; value: string }> = {};
|
||||||
|
// this used for add-only mode in dashboard
|
||||||
|
// type won't be there thus only one key is shown
|
||||||
|
const duplicateSecretKey: Record<string, boolean> = {};
|
||||||
|
data.forEach((encSecret) => {
|
||||||
|
const secretKey = decryptSymmetric({
|
||||||
|
ciphertext: encSecret.secretKeyCiphertext,
|
||||||
|
iv: encSecret.secretKeyIV,
|
||||||
|
tag: encSecret.secretKeyTag,
|
||||||
|
key
|
||||||
|
});
|
||||||
|
|
||||||
|
const secretValue = decryptSymmetric({
|
||||||
|
ciphertext: encSecret.secretValueCiphertext,
|
||||||
|
iv: encSecret.secretValueIV,
|
||||||
|
tag: encSecret.secretValueTag,
|
||||||
|
key
|
||||||
|
});
|
||||||
|
|
||||||
|
const secretComment = decryptSymmetric({
|
||||||
|
ciphertext: encSecret.secretCommentCiphertext,
|
||||||
|
iv: encSecret.secretCommentIV,
|
||||||
|
tag: encSecret.secretCommentTag,
|
||||||
|
key
|
||||||
|
});
|
||||||
|
|
||||||
|
const decryptedSecret = {
|
||||||
|
_id: encSecret._id,
|
||||||
|
env: encSecret.environment,
|
||||||
|
key: secretKey,
|
||||||
|
value: secretValue,
|
||||||
|
tags: encSecret.tags,
|
||||||
|
comment: secretComment,
|
||||||
|
createdAt: encSecret.createdAt,
|
||||||
|
updatedAt: encSecret.updatedAt
|
||||||
|
};
|
||||||
|
|
||||||
|
if (encSecret.type === 'personal') {
|
||||||
|
personalSecrets[decryptedSecret.key] = { id: encSecret._id, value: secretValue };
|
||||||
|
} else {
|
||||||
|
if (!duplicateSecretKey?.[decryptedSecret.key]) {
|
||||||
|
sharedSecrets.push(decryptedSecret);
|
||||||
|
}
|
||||||
|
duplicateSecretKey[decryptedSecret.key] = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
sharedSecrets.forEach((val) => {
|
||||||
|
if (personalSecrets?.[val.key]) {
|
||||||
|
val.idOverride = personalSecrets[val.key].id;
|
||||||
|
val.valueOverride = personalSecrets[val.key].value;
|
||||||
|
val.overrideAction = 'modified';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return { secrets: sharedSecrets };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const fetchEncryptedSecretVersion = async (secretId: string, offset: number, limit: number) => {
|
||||||
|
const { data } = await apiRequest.get<{ secretVersions: EncryptedSecretVersion[] }>(
|
||||||
|
`/api/v1/secret/${secretId}/secret-versions`,
|
||||||
|
{
|
||||||
|
params: {
|
||||||
|
limit,
|
||||||
|
offset
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return data.secretVersions;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useGetSecretVersion = (dto: GetSecretVersionsDTO) =>
|
||||||
|
useQuery({
|
||||||
|
enabled: Boolean(dto.secretId && dto.decryptFileKey),
|
||||||
|
queryKey: secretKeys.getSecretVersion(dto.secretId),
|
||||||
|
queryFn: () => fetchEncryptedSecretVersion(dto.secretId, dto.offset, dto.limit),
|
||||||
|
select: (data) => {
|
||||||
|
const PRIVATE_KEY = localStorage.getItem('PRIVATE_KEY') as string;
|
||||||
|
const latestKey = dto.decryptFileKey;
|
||||||
|
const key = decryptAssymmetric({
|
||||||
|
ciphertext: latestKey.encryptedKey,
|
||||||
|
nonce: latestKey.nonce,
|
||||||
|
publicKey: latestKey.sender.publicKey,
|
||||||
|
privateKey: PRIVATE_KEY
|
||||||
|
});
|
||||||
|
|
||||||
|
return data
|
||||||
|
.map((el) => ({
|
||||||
|
createdAt: el.createdAt,
|
||||||
|
id: el._id,
|
||||||
|
value: decryptSymmetric({
|
||||||
|
ciphertext: el.secretValueCiphertext,
|
||||||
|
iv: el.secretValueIV,
|
||||||
|
tag: el.secretValueTag,
|
||||||
|
key
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const useBatchSecretsOp = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
return useMutation<{}, {}, BatchSecretDTO>({
|
||||||
|
mutationFn: async (dto) => {
|
||||||
|
const { data } = await apiRequest.post('/api/v2/secrets/batch', dto);
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
onSuccess: (_, dto) => {
|
||||||
|
queryClient.invalidateQueries(secretKeys.getProjectSecret(dto.workspaceId, dto.environment));
|
||||||
|
queryClient.invalidateQueries(secretSnapshotKeys.list(dto.workspaceId));
|
||||||
|
queryClient.invalidateQueries(secretSnapshotKeys.count(dto.workspaceId));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
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';
|
||||||
|
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 GetProjectSecretsDTO = {
|
||||||
|
workspaceId: string;
|
||||||
|
env: string;
|
||||||
|
decryptFileKey: UserWsKeyPair;
|
||||||
|
isPaused?: boolean;
|
||||||
|
onSuccess?: (data: DecryptedSecret[]) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type GetSecretVersionsDTO = {
|
||||||
|
secretId: string;
|
||||||
|
limit: number;
|
||||||
|
offset: number;
|
||||||
|
decryptFileKey: UserWsKeyPair;
|
||||||
|
};
|
||||||
@@ -4,6 +4,7 @@ export type { UserWsKeyPair } from './keys/types';
|
|||||||
export type { Organization } from './organization/types';
|
export type { Organization } from './organization/types';
|
||||||
export type { CreateServiceTokenDTO, ServiceToken } from './serviceTokens/types';
|
export type { CreateServiceTokenDTO, ServiceToken } from './serviceTokens/types';
|
||||||
export type { GetSubscriptionPlan, SubscriptionPlan } from './subscriptions/types';
|
export type { GetSubscriptionPlan, SubscriptionPlan } from './subscriptions/types';
|
||||||
|
export type { WsTag } from './tags/types';
|
||||||
export type { AddUserToWsDTO, AddUserToWsRes, OrgUser, User } from './users/types';
|
export type { AddUserToWsDTO, AddUserToWsRes, OrgUser, User } from './users/types';
|
||||||
export type {
|
export type {
|
||||||
CreateEnvironmentDTO,
|
CreateEnvironmentDTO,
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ export {
|
|||||||
useDeleteOrgMembership,
|
useDeleteOrgMembership,
|
||||||
useGetOrgUsers,
|
useGetOrgUsers,
|
||||||
useGetUser,
|
useGetUser,
|
||||||
|
useGetUserAction,
|
||||||
useLogoutUser,
|
useLogoutUser,
|
||||||
|
useRegisterUserAction,
|
||||||
useUpdateOrgUserRole
|
useUpdateOrgUserRole
|
||||||
} from './queries';
|
} from './queries';
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import {
|
|||||||
|
|
||||||
const userKeys = {
|
const userKeys = {
|
||||||
getUser: ['user'] as const,
|
getUser: ['user'] as const,
|
||||||
|
userAction: ['user-action'] as const,
|
||||||
getOrgUsers: (orgId: string) => [{ orgId }, 'user']
|
getOrgUsers: (orgId: string) => [{ orgId }, 'user']
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -31,6 +32,21 @@ const fetchUserDetails = async () => {
|
|||||||
|
|
||||||
export const useGetUser = () => useQuery(userKeys.getUser, fetchUserDetails);
|
export const useGetUser = () => useQuery(userKeys.getUser, fetchUserDetails);
|
||||||
|
|
||||||
|
const fetchUserAction = async (action: string) => {
|
||||||
|
const { data } = await apiRequest.get<{ userAction: string }>('/api/v1/user-action', {
|
||||||
|
params: {
|
||||||
|
action
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return data.userAction;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useGetUserAction = (action: string) =>
|
||||||
|
useQuery({
|
||||||
|
queryKey: userKeys.userAction,
|
||||||
|
queryFn: () => fetchUserAction(action)
|
||||||
|
});
|
||||||
|
|
||||||
export const fetchOrgUsers = async (orgId: string) => {
|
export const fetchOrgUsers = async (orgId: string) => {
|
||||||
const { data } = await apiRequest.get<{ users: OrgUser[] }>(
|
const { data } = await apiRequest.get<{ users: OrgUser[] }>(
|
||||||
`/api/v1/organization/${orgId}/users`
|
`/api/v1/organization/${orgId}/users`
|
||||||
@@ -128,6 +144,16 @@ export const useUpdateOrgUserRole = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useRegisterUserAction = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
return useMutation<{}, {}, string>({
|
||||||
|
mutationFn: (action) => apiRequest.post('/api/v1/user-action', { action }),
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries(userKeys.userAction);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export const useLogoutUser = () =>
|
export const useLogoutUser = () =>
|
||||||
useMutation({
|
useMutation({
|
||||||
mutationFn: () => apiRequest.post('/api/v1/auth/logout'),
|
mutationFn: () => apiRequest.post('/api/v1/auth/logout'),
|
||||||
|
|||||||
@@ -5,7 +5,9 @@ export {
|
|||||||
useDeleteWsEnvironment,
|
useDeleteWsEnvironment,
|
||||||
useGetUserWorkspaceMemberships,
|
useGetUserWorkspaceMemberships,
|
||||||
useGetUserWorkspaces,
|
useGetUserWorkspaces,
|
||||||
|
useGetUserWsEnvironments,
|
||||||
useGetWorkspaceById,
|
useGetWorkspaceById,
|
||||||
useRenameWorkspace,
|
useRenameWorkspace,
|
||||||
useToggleAutoCapitalization,
|
useToggleAutoCapitalization,
|
||||||
useUpdateWsEnvironment} from './queries';
|
useUpdateWsEnvironment
|
||||||
|
} from './queries';
|
||||||
|
|||||||
@@ -7,16 +7,19 @@ import {
|
|||||||
CreateWorkspaceDTO,
|
CreateWorkspaceDTO,
|
||||||
DeleteEnvironmentDTO,
|
DeleteEnvironmentDTO,
|
||||||
DeleteWorkspaceDTO,
|
DeleteWorkspaceDTO,
|
||||||
|
GetWsEnvironmentDTO,
|
||||||
RenameWorkspaceDTO,
|
RenameWorkspaceDTO,
|
||||||
ToggleAutoCapitalizationDTO,
|
ToggleAutoCapitalizationDTO,
|
||||||
UpdateEnvironmentDTO,
|
UpdateEnvironmentDTO,
|
||||||
Workspace
|
Workspace,
|
||||||
|
WorkspaceEnv
|
||||||
} from './types';
|
} from './types';
|
||||||
|
|
||||||
const workspaceKeys = {
|
const workspaceKeys = {
|
||||||
getWorkspaceById: (workspaceId: string) => [{ workspaceId }, 'workspace'] as const,
|
getWorkspaceById: (workspaceId: string) => [{ workspaceId }, 'workspace'] as const,
|
||||||
getWorkspaceMemberships: (orgId: string) => [{ orgId }, 'workspace-memberships'],
|
getWorkspaceMemberships: (orgId: string) => [{ orgId }, 'workspace-memberships'],
|
||||||
getAllUserWorkspace: ['workspaces'] as const
|
getAllUserWorkspace: ['workspaces'] as const,
|
||||||
|
getUserWsEnvironments: (workspaceId: string) => ['workspace-env', { workspaceId }] as const
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchWorkspaceById = async (workspaceId: string) => {
|
const fetchWorkspaceById = async (workspaceId: string) => {
|
||||||
@@ -49,6 +52,21 @@ const fetchUserWorkspaceMemberships = async (orgId: string) => {
|
|||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const fetchUserWsEnvironments = async (workspaceId: string) => {
|
||||||
|
const { data } = await apiRequest.get<{ accessibleEnvironments: WorkspaceEnv[] }>(
|
||||||
|
`/api/v2/workspace/${workspaceId}/environments`
|
||||||
|
);
|
||||||
|
return data.accessibleEnvironments;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useGetUserWsEnvironments = ({ workspaceId, onSuccess }: GetWsEnvironmentDTO) =>
|
||||||
|
useQuery({
|
||||||
|
enabled: Boolean(workspaceId),
|
||||||
|
onSuccess,
|
||||||
|
queryKey: workspaceKeys.getUserWsEnvironments(workspaceId),
|
||||||
|
queryFn: () => fetchUserWsEnvironments(workspaceId)
|
||||||
|
});
|
||||||
|
|
||||||
// to get all userids in an org with the workspace they are part of
|
// to get all userids in an org with the workspace they are part of
|
||||||
export const useGetUserWorkspaceMemberships = (orgId: string) =>
|
export const useGetUserWorkspaceMemberships = (orgId: string) =>
|
||||||
useQuery({
|
useQuery({
|
||||||
|
|||||||
@@ -7,7 +7,13 @@ export type Workspace = {
|
|||||||
environments: WorkspaceEnv[];
|
environments: WorkspaceEnv[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type WorkspaceEnv = { name: string; slug: string };
|
export type WorkspaceEnv = {
|
||||||
|
name: string;
|
||||||
|
slug: string;
|
||||||
|
isReadDenied: boolean;
|
||||||
|
isWriteDenied: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
export type WorkspaceTag = { _id: string; name: string; slug: string };
|
export type WorkspaceTag = { _id: string; name: string; slug: string };
|
||||||
|
|
||||||
// mutation dto
|
// mutation dto
|
||||||
@@ -16,6 +22,11 @@ export type CreateWorkspaceDTO = {
|
|||||||
organizationId: string;
|
organizationId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type GetWsEnvironmentDTO = {
|
||||||
|
workspaceId: string;
|
||||||
|
onSuccess?: (data: WorkspaceEnv[]) => void;
|
||||||
|
};
|
||||||
|
|
||||||
export type RenameWorkspaceDTO = { workspaceID: string; newWorkspaceName: string };
|
export type RenameWorkspaceDTO = { workspaceID: string; newWorkspaceName: string };
|
||||||
export type ToggleAutoCapitalizationDTO = { workspaceID: string; state: boolean };
|
export type ToggleAutoCapitalizationDTO = { workspaceID: string; state: boolean };
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
export { useLeaveConfirm } from './useLeaveConfirm';
|
export { useLeaveConfirm } from './useLeaveConfirm';
|
||||||
|
export { usePersistentState } from './usePersistentState';
|
||||||
export { usePopUp } from './usePopUp';
|
export { usePopUp } from './usePopUp';
|
||||||
export { useToggle } from './useToggle';
|
export { useToggle } from './useToggle';
|
||||||
|
|||||||
@@ -4,52 +4,56 @@ import { useRouter } from 'next/router';
|
|||||||
import { leaveConfirmDefaultMessage } from '@app/const';
|
import { leaveConfirmDefaultMessage } from '@app/const';
|
||||||
|
|
||||||
type LeaveConfirmProps = {
|
type LeaveConfirmProps = {
|
||||||
initialValue: boolean,
|
initialValue: boolean;
|
||||||
message?: string
|
message?: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
interface LeaveConfirmReturn {
|
interface LeaveConfirmReturn {
|
||||||
hasUnsavedChanges: boolean,
|
hasUnsavedChanges: boolean;
|
||||||
setHasUnsavedChanges: Dispatch<SetStateAction<boolean>>,
|
setHasUnsavedChanges: Dispatch<SetStateAction<boolean>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useLeaveConfirm({
|
export function useLeaveConfirm({
|
||||||
initialValue,
|
initialValue,
|
||||||
message = leaveConfirmDefaultMessage,
|
message = leaveConfirmDefaultMessage
|
||||||
}: LeaveConfirmProps): LeaveConfirmReturn {
|
}: LeaveConfirmProps): LeaveConfirmReturn {
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
const [hasUnsavedChanges, setHasUnsavedChanges] = useState<boolean>(initialValue);
|
const [hasUnsavedChanges, setHasUnsavedChanges] = useState<boolean>(initialValue);
|
||||||
|
|
||||||
const onRouteChangeStart = useCallback(() => {
|
const onRouteChangeStart = useCallback(() => {
|
||||||
if (hasUnsavedChanges) {
|
if (hasUnsavedChanges) {
|
||||||
|
// eslint-disable-next-line no-alert
|
||||||
if (window.confirm(message)) {
|
if (window.confirm(message)) {
|
||||||
return true
|
return true;
|
||||||
}
|
}
|
||||||
throw new Error("Abort route change by user's confirmation.")
|
throw new Error("Abort route change by user's confirmation.");
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}, [hasUnsavedChanges])
|
}, [hasUnsavedChanges]);
|
||||||
|
|
||||||
const handleWindowClose = useCallback((e: any) => {
|
const handleWindowClose = useCallback(
|
||||||
if (!hasUnsavedChanges) {
|
(e: any) => {
|
||||||
return;
|
if (!hasUnsavedChanges) {
|
||||||
}
|
return;
|
||||||
e.preventDefault();
|
}
|
||||||
e.returnValue = message;
|
e.preventDefault();
|
||||||
}, []);
|
e.returnValue = message;
|
||||||
|
},
|
||||||
|
[hasUnsavedChanges]
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
router.events.on("routeChangeStart", onRouteChangeStart);
|
router.events.on('routeChangeStart', onRouteChangeStart);
|
||||||
window.addEventListener('beforeunload', handleWindowClose);
|
window.addEventListener('beforeunload', handleWindowClose);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
router.events.off("routeChangeStart", onRouteChangeStart);
|
router.events.off('routeChangeStart', onRouteChangeStart);
|
||||||
window.removeEventListener('beforeunload', handleWindowClose);
|
window.removeEventListener('beforeunload', handleWindowClose);
|
||||||
}
|
};
|
||||||
}, [onRouteChangeStart, handleWindowClose]);
|
}, [onRouteChangeStart, handleWindowClose]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
hasUnsavedChanges,
|
hasUnsavedChanges,
|
||||||
setHasUnsavedChanges,
|
setHasUnsavedChanges
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
type TPersisntentStateReturn<T extends unknown> = [T, (val: T) => void];
|
||||||
|
|
||||||
|
export const usePersistentState = <T extends unknown>(
|
||||||
|
initialValue: T,
|
||||||
|
persistenceKey: string
|
||||||
|
): TPersisntentStateReturn<T> => {
|
||||||
|
const [val, setVal] = useState<T>(initialValue);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const temp = localStorage.getItem(persistenceKey);
|
||||||
|
if (temp) {
|
||||||
|
const { key } = JSON.parse(temp);
|
||||||
|
setVal(key);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const setState = (state: T) => {
|
||||||
|
localStorage.setItem(persistenceKey, JSON.stringify({ key: state }));
|
||||||
|
setVal(state);
|
||||||
|
};
|
||||||
|
|
||||||
|
return [val, setState];
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user