mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-13 08:29:28 +00:00
Finish preliminary CRUD ops for service token v3, ServiceTokenV3Key structure
This commit is contained in:
@@ -29,7 +29,7 @@ export const ModalContent = forwardRef<HTMLDivElement, ModalContentProps>(
|
||||
<Card
|
||||
isRounded
|
||||
className={twMerge(
|
||||
"fixed top-1/2 left-1/2 z-[90] dark:[color-scheme:dark] max-h-screen overflow-y-auto thin-scrollbar max-w-lg -translate-y-2/4 -translate-x-2/4 animate-popIn border border-mineshaft-600 drop-shadow-2xl",
|
||||
"fixed top-1/2 left-1/2 z-[90] dark:[color-scheme:dark] max-h-screen overflow-y-auto thin-scrollbar max-w-xl -translate-y-2/4 -translate-x-2/4 animate-popIn border border-mineshaft-600 drop-shadow-2xl",
|
||||
className
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -2,18 +2,17 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
import { apiRequest } from "@app/config/request";
|
||||
|
||||
import { workspaceKeys } from "../workspace/queries";
|
||||
import {
|
||||
CreateServiceTokenDataV3DTO,
|
||||
CreateServiceTokenDataV3Res,
|
||||
CreateServiceTokenDTO,
|
||||
CreateServiceTokenRes,
|
||||
DeleteServiceTokenDataV3DTO,
|
||||
DeleteServiceTokenRes,
|
||||
ServiceToken,
|
||||
ServiceTokenDataV3,
|
||||
CreateServiceTokenDataV3DTO,
|
||||
CreateServiceTokenDataV3Res,
|
||||
UpdateServiceTokenDataV3DTO,
|
||||
DeleteServiceTokenDataV3DTO
|
||||
} from "./types";
|
||||
import { workspaceKeys } from "../workspace/queries";
|
||||
UpdateServiceTokenDataV3DTO} from "./types";
|
||||
|
||||
const serviceTokenKeys = {
|
||||
getAllWorkspaceServiceToken: (workspaceID: string) => [{ workspaceID }, "service-tokens"] as const
|
||||
@@ -58,7 +57,6 @@ export const useDeleteServiceToken = () => {
|
||||
|
||||
return useMutation<DeleteServiceTokenRes, {}, string>({
|
||||
mutationFn: async (serviceTokenId) => {
|
||||
console.log("useDeleteServiceToken");
|
||||
const { data } = await apiRequest.delete(`/api/v2/service-token/${serviceTokenId}`);
|
||||
return data;
|
||||
},
|
||||
@@ -87,11 +85,15 @@ export const useUpdateServiceTokenV3 = () => {
|
||||
mutationFn: async ({
|
||||
serviceTokenDataId,
|
||||
name,
|
||||
isActive
|
||||
isActive,
|
||||
scopes,
|
||||
expiresIn
|
||||
}) => {
|
||||
const { data: { serviceTokenData } } = await apiRequest.patch(`/api/v3/service-token/${serviceTokenDataId}`, {
|
||||
name,
|
||||
isActive
|
||||
isActive,
|
||||
scopes,
|
||||
expiresIn
|
||||
});
|
||||
|
||||
return serviceTokenData;
|
||||
@@ -108,13 +110,10 @@ export const useDeleteServiceTokenV3 = () => {
|
||||
mutationFn: async ({
|
||||
serviceTokenDataId
|
||||
}) => {
|
||||
console.log("useDeleteServiceTokenV3");
|
||||
const { data: { serviceTokenData } } = await apiRequest.delete(`/api/v3/service-token/${serviceTokenDataId}`);
|
||||
console.log("useDeleteServiceTokenV3 serviceTokenData: ", serviceTokenData);
|
||||
return serviceTokenData;
|
||||
},
|
||||
onSuccess: ({ workspace }) => {
|
||||
console.log("useDeleteServiceTokenV3 onSuccess: ", workspace);
|
||||
queryClient.invalidateQueries(workspaceKeys.getWorkspaceServiceTokenDataV3(workspace));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -36,22 +36,32 @@ export type DeleteServiceTokenRes = { serviceTokenData: ServiceToken };
|
||||
|
||||
// --- v3
|
||||
|
||||
export type ServiceTokenV3Scope = {
|
||||
permission: string;
|
||||
environment: string;
|
||||
secretPath: string;
|
||||
};
|
||||
|
||||
export type ServiceTokenDataV3 = {
|
||||
_id: string;
|
||||
name: string;
|
||||
workspace: string;
|
||||
isActive: boolean;
|
||||
lastUsed?: string;
|
||||
scopes: ServiceTokenV3Scope[];
|
||||
expiresAt?: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
};
|
||||
|
||||
// TODO: add scopes
|
||||
// TODO: encrypted key info
|
||||
export type CreateServiceTokenDataV3DTO = {
|
||||
name: string;
|
||||
workspaceId: string;
|
||||
publicKey: string;
|
||||
scopes: ServiceTokenV3Scope[];
|
||||
expiresIn?: number;
|
||||
encryptedKey: string;
|
||||
nonce: string;
|
||||
}
|
||||
|
||||
export type CreateServiceTokenDataV3Res = {
|
||||
@@ -61,8 +71,10 @@ export type CreateServiceTokenDataV3Res = {
|
||||
|
||||
export type UpdateServiceTokenDataV3DTO = {
|
||||
serviceTokenDataId: string;
|
||||
name?: string;
|
||||
isActive?: boolean;
|
||||
name?: string;
|
||||
scopes?: ServiceTokenV3Scope[];
|
||||
expiresIn?: number;
|
||||
}
|
||||
|
||||
export type DeleteServiceTokenDataV3DTO = {
|
||||
|
||||
+5
-2
@@ -88,7 +88,10 @@ export const AddServiceTokenModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
} = useForm<FormData>({
|
||||
resolver: yupResolver(schema),
|
||||
defaultValues: {
|
||||
scopes: [{ secretPath: "/", environment: currentWorkspace?.environments?.[0]?.slug }]
|
||||
scopes: [{
|
||||
secretPath: "/",
|
||||
environment: currentWorkspace?.environments?.[0]?.slug
|
||||
}]
|
||||
}
|
||||
});
|
||||
|
||||
@@ -133,7 +136,7 @@ export const AddServiceTokenModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
plaintext: key,
|
||||
key: randomBytes
|
||||
});
|
||||
|
||||
|
||||
const { serviceToken } = await createServiceToken.mutateAsync({
|
||||
encryptedKey: ciphertext,
|
||||
iv,
|
||||
|
||||
+1
-1
@@ -43,7 +43,7 @@ export const ServiceTokenTable = ({ handlePopUpOpen }: Props) => {
|
||||
<THead>
|
||||
<Tr>
|
||||
<Th>Token Name</Th>
|
||||
<Th>Envrionment - Secret Path</Th>
|
||||
<Th>Environment - Secret Path</Th>
|
||||
<Th>Valid Until</Th>
|
||||
<Th aria-label="button" />
|
||||
</Tr>
|
||||
|
||||
+333
-85
@@ -1,23 +1,41 @@
|
||||
import { useEffect } from "react";
|
||||
import { Controller, useFieldArray, useForm } from "react-hook-form";
|
||||
import { faPlus,faXmark } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import nacl from "tweetnacl";
|
||||
import { encodeBase64 } from "tweetnacl-util";
|
||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { useWorkspace } from "@app/context";
|
||||
import * as yup from "yup";
|
||||
|
||||
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
||||
import {
|
||||
decryptAssymmetric,
|
||||
encryptAssymmetric
|
||||
} from "@app/components/utilities/cryptography/crypto";
|
||||
import {
|
||||
Button,
|
||||
FormControl,
|
||||
IconButton,
|
||||
Input,
|
||||
Modal,
|
||||
ModalContent,
|
||||
FormControl,
|
||||
Select,
|
||||
SelectItem,
|
||||
Input,
|
||||
Button
|
||||
// Accordion,
|
||||
// AccordionItem,
|
||||
// AccordionTrigger,
|
||||
// AccordionContent
|
||||
} from "@app/components/v2";
|
||||
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import * as yup from "yup";
|
||||
import { useCreateServiceTokenV3 } from "@app/hooks/api";
|
||||
import { useWorkspace } from "@app/context";
|
||||
import {
|
||||
useCreateServiceTokenV3,
|
||||
useGetUserWsKey,
|
||||
useUpdateServiceTokenV3
|
||||
} from "@app/hooks/api";
|
||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||
|
||||
const expirations = [
|
||||
{ label: "Never", value: undefined },
|
||||
{ label: "1 day", value: "86400" },
|
||||
{ label: "7 days", value: "604800" },
|
||||
{ label: "1 month", value: "2592000" },
|
||||
@@ -25,88 +43,172 @@ const expirations = [
|
||||
{ label: "12 months", value: "31104000" }
|
||||
];
|
||||
|
||||
const permissionValues: Array<"read" | "readWrite"> = ["read", "readWrite"];
|
||||
|
||||
const schema = yup.object({
|
||||
name: yup.string().required("ST V3 name is required"),
|
||||
expiresIn: yup.string().required("ST V3 expiration window is required")
|
||||
expiresIn: yup.string(),
|
||||
scopes: yup
|
||||
.array(
|
||||
yup.object({
|
||||
permission: yup.string().oneOf(permissionValues, "Invalid permission").required().label("Permission"),
|
||||
environment: yup.string().max(50).required().label("Environment"),
|
||||
secretPath: yup
|
||||
.string()
|
||||
.required()
|
||||
.default("/")
|
||||
.label("Secret Path")
|
||||
.transform((val) =>
|
||||
typeof val === "string" && val.at(-1) === "/" && val.length > 1 ? val.slice(0, -1) : val
|
||||
)
|
||||
})
|
||||
)
|
||||
.min(1)
|
||||
.required()
|
||||
.label("Scope")
|
||||
}).required();
|
||||
|
||||
export type FormData = yup.InferType<typeof schema>;
|
||||
|
||||
type Props = {
|
||||
popUp: UsePopUpState<["createServiceTokenV3"]>;
|
||||
handlePopUpToggle: (popUpName: keyof UsePopUpState<["createServiceTokenV3"]>, state?: boolean) => void;
|
||||
popUp: UsePopUpState<["serviceTokenV3"]>;
|
||||
handlePopUpToggle: (popUpName: keyof UsePopUpState<["serviceTokenV3"]>, state?: boolean) => void;
|
||||
};
|
||||
|
||||
// Will download a JSON
|
||||
// Maybe you can set a timer at which point service token is no longer active!
|
||||
// Maybe you can also set IP allowlist for it too
|
||||
|
||||
export const AddServiceTokenV3Modal = ({
|
||||
popUp,
|
||||
handlePopUpToggle
|
||||
}: Props) => {
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const { mutateAsync } = useCreateServiceTokenV3();
|
||||
|
||||
const { data: latestFileKey } = useGetUserWsKey(currentWorkspace?._id ?? "");
|
||||
const { mutateAsync: createMutateAsync } = useCreateServiceTokenV3();
|
||||
const { mutateAsync: updateMutateAsync } = useUpdateServiceTokenV3();
|
||||
const { createNotification } = useNotificationContext();
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
reset
|
||||
reset,
|
||||
formState: { isSubmitting }
|
||||
} = useForm<FormData>({
|
||||
resolver: yupResolver(schema)
|
||||
resolver: yupResolver(schema),
|
||||
defaultValues: {
|
||||
name: "",
|
||||
scopes: [{
|
||||
permission: "read",
|
||||
environment: currentWorkspace?.environments?.[0]?.slug,
|
||||
secretPath: "/",
|
||||
}]
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
const serviceTokenData = popUp?.serviceTokenV3?.data as {
|
||||
serviceTokenDataId: string;
|
||||
name: string;
|
||||
scopes: any;
|
||||
};
|
||||
|
||||
if (serviceTokenData) {
|
||||
reset({
|
||||
name: serviceTokenData.name,
|
||||
scopes: serviceTokenData.scopes
|
||||
});
|
||||
} else {
|
||||
reset({
|
||||
name: "",
|
||||
scopes: [{
|
||||
permission: "read",
|
||||
environment: currentWorkspace?.environments?.[0]?.slug,
|
||||
secretPath: "/",
|
||||
}]
|
||||
});
|
||||
}
|
||||
}, [popUp?.serviceTokenV3?.data]);
|
||||
|
||||
const { fields: tokenScopes, append, remove } = useFieldArray({ control, name: "scopes" });
|
||||
|
||||
const onFormSubmit = async ({
|
||||
name,
|
||||
expiresIn
|
||||
expiresIn,
|
||||
scopes
|
||||
}: FormData) => {
|
||||
try {
|
||||
if (!currentWorkspace?._id) return;
|
||||
|
||||
console.log("onFormSubmit name: ", name);
|
||||
console.log("onFormSubmit expiresIn: ", expiresIn);
|
||||
|
||||
const pair = nacl.box.keyPair();
|
||||
const secretKeyUint8Array = pair.secretKey;
|
||||
const publicKeyUint8Array = pair.publicKey;
|
||||
const privateKey = encodeBase64(secretKeyUint8Array);
|
||||
const publicKey = encodeBase64(publicKeyUint8Array);
|
||||
|
||||
console.log("pair: ", pair);
|
||||
console.log("privateKey: ", privateKey);
|
||||
console.log("publicKey: ", publicKey );
|
||||
const { serviceToken } = await mutateAsync({
|
||||
name,
|
||||
workspaceId: currentWorkspace._id,
|
||||
publicKey
|
||||
});
|
||||
|
||||
const downloadData = {
|
||||
publicKey,
|
||||
privateKey,
|
||||
serviceToken
|
||||
const serviceTokenData = popUp?.serviceTokenV3?.data as {
|
||||
serviceTokenDataId: string;
|
||||
name: string;
|
||||
scopes: any;
|
||||
};
|
||||
|
||||
if (serviceTokenData) {
|
||||
// update
|
||||
await updateMutateAsync({
|
||||
serviceTokenDataId: serviceTokenData.serviceTokenDataId,
|
||||
name,
|
||||
scopes,
|
||||
expiresIn: expiresIn === "" ? undefined : Number(expiresIn)
|
||||
});
|
||||
} else {
|
||||
// create
|
||||
if (!currentWorkspace?._id) return;
|
||||
if (!latestFileKey) return;
|
||||
|
||||
const pair = nacl.box.keyPair();
|
||||
const secretKeyUint8Array = pair.secretKey;
|
||||
const publicKeyUint8Array = pair.publicKey;
|
||||
const privateKey = encodeBase64(secretKeyUint8Array);
|
||||
const publicKey = encodeBase64(publicKeyUint8Array);
|
||||
|
||||
const key = decryptAssymmetric({
|
||||
ciphertext: latestFileKey.encryptedKey,
|
||||
nonce: latestFileKey.nonce,
|
||||
publicKey: latestFileKey.sender.publicKey,
|
||||
privateKey: localStorage.getItem("PRIVATE_KEY") as string
|
||||
});
|
||||
|
||||
const { ciphertext, nonce } = encryptAssymmetric({
|
||||
plaintext: key,
|
||||
publicKey,
|
||||
privateKey: localStorage.getItem("PRIVATE_KEY") as string
|
||||
});
|
||||
|
||||
const blob = new Blob([JSON.stringify(downloadData, null, 2)], { type: 'application/json' });
|
||||
const href = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = href;
|
||||
link.download = `infisical_${name}.json`;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
const { serviceToken } = await createMutateAsync({
|
||||
name,
|
||||
workspaceId: currentWorkspace._id,
|
||||
publicKey,
|
||||
scopes,
|
||||
expiresIn: expiresIn === "" ? undefined : Number(expiresIn),
|
||||
encryptedKey: ciphertext,
|
||||
nonce
|
||||
});
|
||||
|
||||
const downloadData = {
|
||||
publicKey,
|
||||
privateKey,
|
||||
serviceToken
|
||||
};
|
||||
|
||||
const blob = new Blob([JSON.stringify(downloadData, null, 2)], { type: "application/json" });
|
||||
const href = URL.createObjectURL(blob);
|
||||
const link = document.createElement("a");
|
||||
link.href = href;
|
||||
link.download = `infisical_${name}.json`;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
}
|
||||
|
||||
createNotification({
|
||||
text: "Successfully created ST V3",
|
||||
text: `Successfully ${popUp?.serviceTokenV3?.data ? "updated" : "created"} ST V3`,
|
||||
type: "success"
|
||||
});
|
||||
|
||||
reset();
|
||||
handlePopUpToggle("createServiceTokenV3", false);
|
||||
handlePopUpToggle("serviceTokenV3", false);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
createNotification({
|
||||
text: "Failed to create ST V3",
|
||||
text: `Failed to ${popUp?.serviceTokenV3?.data ? "updated" : "created"} ST V3`,
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
@@ -114,13 +216,13 @@ export const AddServiceTokenV3Modal = ({
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={popUp?.createServiceTokenV3?.isOpen}
|
||||
isOpen={popUp?.serviceTokenV3?.isOpen}
|
||||
onOpenChange={(isOpen) => {
|
||||
handlePopUpToggle("createServiceTokenV3", isOpen);
|
||||
handlePopUpToggle("serviceTokenV3", isOpen);
|
||||
reset();
|
||||
}}
|
||||
>
|
||||
<ModalContent title="Create Service Token V3">
|
||||
<ModalContent title={`${popUp?.serviceTokenV3?.data ? "Update" : "Create"} Service Token V3`}>
|
||||
<form onSubmit={handleSubmit(onFormSubmit)}>
|
||||
<Controller
|
||||
control={control}
|
||||
@@ -139,38 +241,184 @@ export const AddServiceTokenV3Modal = ({
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name="expiresIn"
|
||||
defaultValue="15552000"
|
||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Expiration"
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error)}
|
||||
{tokenScopes.map(({ id }, index) => (
|
||||
<div className="flex items-end space-x-2 mb-3" key={id}>
|
||||
<Controller
|
||||
control={control}
|
||||
name={`scopes.${index}.permission`}
|
||||
defaultValue={currentWorkspace?.environments?.[0]?.slug}
|
||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
className="mb-0"
|
||||
label={index === 0 ? "Permission" : undefined}
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error)}
|
||||
>
|
||||
<Select
|
||||
defaultValue={field.value}
|
||||
{...field}
|
||||
onValueChange={(e) => onChange(e)}
|
||||
className="w-36"
|
||||
>
|
||||
<SelectItem value="read" key="st-v3-read">
|
||||
Read
|
||||
</SelectItem>
|
||||
<SelectItem value="readWrite" key="st-v3-write">
|
||||
Read & Write
|
||||
</SelectItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name={`scopes.${index}.environment`}
|
||||
defaultValue={currentWorkspace?.environments?.[0]?.slug}
|
||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
className="mb-0"
|
||||
label={index === 0 ? "Environment" : undefined}
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error)}
|
||||
>
|
||||
<Select
|
||||
defaultValue={field.value}
|
||||
{...field}
|
||||
onValueChange={(e) => onChange(e)}
|
||||
className="w-36"
|
||||
>
|
||||
{currentWorkspace?.environments.map(({ name, slug }) => (
|
||||
<SelectItem value={slug} key={slug}>
|
||||
{name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name={`scopes.${index}.secretPath`}
|
||||
defaultValue="/"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
className="mb-0 flex-grow"
|
||||
label={index === 0 ? "Secrets Path" : undefined}
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
>
|
||||
<Input {...field} placeholder="can be /, /nested/**, /**/deep" />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<IconButton
|
||||
onClick={() => remove(index)}
|
||||
size="lg"
|
||||
colorSchema="danger"
|
||||
variant="plain"
|
||||
ariaLabel="update"
|
||||
className="p-3"
|
||||
>
|
||||
<FontAwesomeIcon icon={faXmark} />
|
||||
</IconButton>
|
||||
</div>
|
||||
))}
|
||||
<div className="my-4 ml-1">
|
||||
<Button
|
||||
variant="outline_bg"
|
||||
onClick={() =>
|
||||
append({
|
||||
permission: "read",
|
||||
environment: currentWorkspace?.environments?.[0]?.slug || "",
|
||||
secretPath: "/"
|
||||
})
|
||||
}
|
||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||
size="xs"
|
||||
>
|
||||
<Select
|
||||
defaultValue={field.value}
|
||||
{...field}
|
||||
onValueChange={(e) => onChange(e)}
|
||||
className="w-full"
|
||||
Add Scope
|
||||
</Button>
|
||||
</div>
|
||||
<Controller
|
||||
control={control}
|
||||
name="expiresIn"
|
||||
defaultValue="15552000"
|
||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label={`${popUp?.serviceTokenV3?.data ? "Update" : ""} Expire In`}
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error)}
|
||||
className="mt-4"
|
||||
>
|
||||
{expirations.map(({ label, value }) => (
|
||||
<SelectItem value={String(value || "")} key={`api-key-expiration-${label}`}>
|
||||
{label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Select
|
||||
defaultValue={field.value}
|
||||
{...field}
|
||||
onValueChange={(e) => onChange(e)}
|
||||
className="w-full"
|
||||
>
|
||||
{expirations.map(({ label, value }) => (
|
||||
<SelectItem value={String(value || "")} key={`api-key-expiration-${label}`}>
|
||||
{label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
{/* <Accordion
|
||||
type="multiple"
|
||||
className="w-full"
|
||||
>
|
||||
<AccordionItem value="section-1">
|
||||
<AccordionTrigger>Scopes</AccordionTrigger>
|
||||
<AccordionContent>Description of Section 1</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion> */}
|
||||
{/* <h3 className="text-mineshaft-400 text-sm mb-2">Temporariness</h3>
|
||||
<Switch
|
||||
id={`enable-ephemerality`}
|
||||
onCheckedChange={(value) => setIsTemporary(value)}
|
||||
isChecked={isTemporary}
|
||||
>
|
||||
<div className="w-96 mr-4">
|
||||
<p className="text-gray-400 text-md">This token will be deactivated after your specified duration.</p>
|
||||
</div>
|
||||
</Switch>
|
||||
{isTemporary && (
|
||||
<Controller
|
||||
control={control}
|
||||
name="expiresIn"
|
||||
defaultValue="15552000"
|
||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Duration"
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error)}
|
||||
className="mt-4"
|
||||
>
|
||||
<Select
|
||||
defaultValue={field.value}
|
||||
{...field}
|
||||
onValueChange={(e) => onChange(e)}
|
||||
className="w-full"
|
||||
>
|
||||
{expirations.map(({ label, value }) => (
|
||||
<SelectItem value={String(value || "")} key={`api-key-expiration-${label}`}>
|
||||
{label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
)} */}
|
||||
<div className="mt-8 flex items-center">
|
||||
<Button
|
||||
className="mr-4"
|
||||
size="sm"
|
||||
type="submit"
|
||||
// isLoading={isLoading}
|
||||
// isDisabled={isLoading}
|
||||
isLoading={isSubmitting}
|
||||
isDisabled={isSubmitting}
|
||||
>
|
||||
Create
|
||||
</Button>
|
||||
|
||||
+55
-7
@@ -1,14 +1,47 @@
|
||||
import { faPlus } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { AddServiceTokenV3Modal } from "./AddServiceTokenV3Modal";
|
||||
import { ServiceTokenV3Table } from "./ServiceTokenV3Table";
|
||||
import { Button } from "@app/components/v2";
|
||||
|
||||
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
||||
import {
|
||||
Button,
|
||||
DeleteActionModal
|
||||
} from "@app/components/v2";
|
||||
import {
|
||||
useDeleteServiceTokenV3
|
||||
} from "@app/hooks/api";
|
||||
import { usePopUp } from "@app/hooks/usePopUp";
|
||||
|
||||
import { AddServiceTokenV3Modal } from "./AddServiceTokenV3Modal";
|
||||
import { ServiceTokenV3Table } from "./ServiceTokenV3Table";
|
||||
|
||||
export const ServiceTokenV3Section = () => {
|
||||
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp([
|
||||
"createServiceTokenV3"
|
||||
const { createNotification } = useNotificationContext();
|
||||
const { mutateAsync: deleteMutateAsync } = useDeleteServiceTokenV3();
|
||||
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
|
||||
"serviceTokenV3",
|
||||
"deleteServiceTokenV3"
|
||||
] as const);
|
||||
|
||||
const onDeleteServiceTokenDataSubmit = async (serviceTokenDataId: string) => {
|
||||
try {
|
||||
await deleteMutateAsync({
|
||||
serviceTokenDataId
|
||||
});
|
||||
createNotification({
|
||||
text: "Successfully deleted service token v3",
|
||||
type: "success"
|
||||
});
|
||||
|
||||
handlePopUpClose("deleteServiceTokenV3");
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
createNotification({
|
||||
text: "Failed to delete service token v3",
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||
<div className="flex justify-between mb-8">
|
||||
@@ -19,16 +52,31 @@ export const ServiceTokenV3Section = () => {
|
||||
colorSchema="secondary"
|
||||
type="submit"
|
||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||
onClick={() => handlePopUpOpen("createServiceTokenV3")}
|
||||
onClick={() => handlePopUpOpen("serviceTokenV3")}
|
||||
>
|
||||
Create ST V3
|
||||
</Button>
|
||||
</div>
|
||||
<ServiceTokenV3Table />
|
||||
<ServiceTokenV3Table
|
||||
handlePopUpOpen={handlePopUpOpen}
|
||||
/>
|
||||
<AddServiceTokenV3Modal
|
||||
popUp={popUp}
|
||||
handlePopUpToggle={handlePopUpToggle}
|
||||
/>
|
||||
<DeleteActionModal
|
||||
isOpen={popUp.deleteServiceTokenV3.isOpen}
|
||||
title={`Are you sure want to delete ${
|
||||
(popUp?.deleteServiceTokenV3?.data as { name: string })?.name || ""
|
||||
}?`}
|
||||
onChange={(isOpen) => handlePopUpToggle("deleteServiceTokenV3", isOpen)}
|
||||
deleteKey="confirm"
|
||||
onDeleteApproved={() =>
|
||||
onDeleteServiceTokenDataSubmit(
|
||||
(popUp?.deleteServiceTokenV3?.data as { serviceTokenDataId: string })?.serviceTokenDataId
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
+63
-45
@@ -1,6 +1,6 @@
|
||||
import { faKey, faPencil,faXmark } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faKey, faXmark, faPencil } from "@fortawesome/free-solid-svg-icons";
|
||||
import { useWorkspace } from "@app/context";
|
||||
|
||||
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
||||
import {
|
||||
EmptyState,
|
||||
@@ -15,38 +15,34 @@ import {
|
||||
THead,
|
||||
Tr
|
||||
} from "@app/components/v2";
|
||||
import { useWorkspace } from "@app/context";
|
||||
import {
|
||||
useGetWorkspaceServiceTokenDataV3,
|
||||
useUpdateServiceTokenV3,
|
||||
useDeleteServiceTokenV3
|
||||
useUpdateServiceTokenV3
|
||||
} from "@app/hooks/api";
|
||||
import {
|
||||
ServiceTokenV3Scope
|
||||
} from "@app/hooks/api/serviceTokens/types"
|
||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||
|
||||
export const ServiceTokenV3Table = () => {
|
||||
type Props = {
|
||||
handlePopUpOpen: (
|
||||
popUpName: keyof UsePopUpState<["deleteServiceTokenV3", "serviceTokenV3"]>,
|
||||
data?: {
|
||||
serviceTokenDataId?: string;
|
||||
name?: string;
|
||||
scopes?: ServiceTokenV3Scope[];
|
||||
}
|
||||
) => void;
|
||||
};
|
||||
|
||||
export const ServiceTokenV3Table = ({
|
||||
handlePopUpOpen
|
||||
}: Props) => {
|
||||
const { createNotification } = useNotificationContext();
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const { data, isLoading } = useGetWorkspaceServiceTokenDataV3(currentWorkspace?._id || "");
|
||||
const { mutateAsync: updateMutateAsync } = useUpdateServiceTokenV3();
|
||||
const { mutateAsync: deleteMutateAsync } = useDeleteServiceTokenV3();
|
||||
|
||||
console.log("data1: ", data);
|
||||
|
||||
const handleDeleteServiceTokenData = async (serviceTokenDataId: string) => {
|
||||
try {
|
||||
await deleteMutateAsync({
|
||||
serviceTokenDataId
|
||||
});
|
||||
createNotification({
|
||||
text: "Successfully deleted service token v3",
|
||||
type: "success"
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
createNotification({
|
||||
text: "Failed to delete service token v3",
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const handleToggleServiceTokenDataStatus = async ({
|
||||
serviceTokenDataId,
|
||||
@@ -92,10 +88,11 @@ export const ServiceTokenV3Table = () => {
|
||||
<Tr>
|
||||
<Th>Name</Th>
|
||||
<Th>Status</Th>
|
||||
<Th>Last Active</Th>
|
||||
<Th>Created</Th>
|
||||
<Th>Expiration</Th>
|
||||
<Th className="w-5"></Th>
|
||||
<Th>Scopes</Th>
|
||||
<Th>Last Used</Th>
|
||||
<Th>Created At</Th>
|
||||
<Th>Expires At</Th>
|
||||
<Th className="w-5" />
|
||||
</Tr>
|
||||
</THead>
|
||||
<TBody>
|
||||
@@ -108,32 +105,48 @@ export const ServiceTokenV3Table = () => {
|
||||
name,
|
||||
isActive,
|
||||
lastUsed,
|
||||
scopes,
|
||||
createdAt,
|
||||
// expiresAt
|
||||
expiresAt
|
||||
}) => {
|
||||
return (
|
||||
<Tr className="h-10" key={`st-v3-${_id}`}>
|
||||
<Td>{name}</Td>
|
||||
<Td>
|
||||
<Switch
|
||||
id="test"
|
||||
// id={`enable-${authMethodOpt.value}-auth`}
|
||||
onCheckedChange={(value) => handleToggleServiceTokenDataStatus({
|
||||
serviceTokenDataId: _id,
|
||||
isActive: value
|
||||
})}
|
||||
isChecked={isActive}
|
||||
>
|
||||
<p className="w-12 mr-4">{isActive ? "Active" : "Inactive"}</p>
|
||||
</Switch>
|
||||
<Switch
|
||||
id={`enable-service-token-${_id}`}
|
||||
onCheckedChange={(value) => handleToggleServiceTokenDataStatus({
|
||||
serviceTokenDataId: _id,
|
||||
isActive: value
|
||||
})}
|
||||
isChecked={isActive}
|
||||
>
|
||||
<p className="w-12 mr-4">{isActive ? "Active" : "Inactive"}</p>
|
||||
</Switch>
|
||||
</Td>
|
||||
<Td>
|
||||
{scopes.map((scope) => {
|
||||
return (
|
||||
<p key={`service-token-${_id}-scope-${scope.environment}-${scope.secretPath}`}>
|
||||
<span className="font-bold">
|
||||
{scope.permission}
|
||||
</span>
|
||||
{` @${scope.environment} - ${scope.secretPath}`}
|
||||
</p>
|
||||
);
|
||||
})}
|
||||
</Td>
|
||||
<Td>{lastUsed ? formatDate(lastUsed) : "-"}</Td>
|
||||
<Td>{formatDate(createdAt)}</Td>
|
||||
<Td>{formatDate(createdAt)}</Td>
|
||||
<Td>{expiresAt ? formatDate(expiresAt) : "-"}</Td>
|
||||
<Td className="flex justify-end">
|
||||
<IconButton
|
||||
onClick={async () => {
|
||||
console.log("edit");
|
||||
handlePopUpOpen("serviceTokenV3", {
|
||||
serviceTokenDataId: _id,
|
||||
name,
|
||||
scopes,
|
||||
});
|
||||
}}
|
||||
size="lg"
|
||||
colorSchema="primary"
|
||||
@@ -143,7 +156,12 @@ export const ServiceTokenV3Table = () => {
|
||||
<FontAwesomeIcon icon={faPencil} />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
onClick={() => handleDeleteServiceTokenData(_id)}
|
||||
onClick={() => {
|
||||
handlePopUpOpen("deleteServiceTokenV3", {
|
||||
serviceTokenDataId: _id,
|
||||
name
|
||||
});
|
||||
}}
|
||||
size="lg"
|
||||
colorSchema="danger"
|
||||
variant="plain"
|
||||
|
||||
Reference in New Issue
Block a user