mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-15 02:51:51 +00:00
Merge branch 'main' into feat/point-in-time-revamp
This commit is contained in:
@@ -72,7 +72,7 @@ const NewProjectForm = ({ onOpenChange, projectType }: NewProjectFormProps) => {
|
||||
OrgPermissionSubjects.ProjectTemplates
|
||||
);
|
||||
|
||||
const { data: projectTemplates = [] } = useListProjectTemplates({
|
||||
const { data: projectTemplates = [] } = useListProjectTemplates(projectType, {
|
||||
enabled: Boolean(canReadProjectTemplates && subscription?.projectTemplates)
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import { useState } from "react";
|
||||
|
||||
import { Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
|
||||
|
||||
import { ProjectTemplatesTab } from "./components";
|
||||
|
||||
const tabs = [
|
||||
{ name: "Project Templates", key: "project-templates", component: ProjectTemplatesTab }
|
||||
];
|
||||
|
||||
export const ProjectSettings = () => {
|
||||
const [selectedTab, setSelectedTab] = useState(tabs[0].key);
|
||||
|
||||
return (
|
||||
<Tabs value={selectedTab} onValueChange={setSelectedTab}>
|
||||
<TabList>
|
||||
{tabs.map((tab) => (
|
||||
<Tab value={tab.key} key={tab.key}>
|
||||
{tab.name}
|
||||
</Tab>
|
||||
))}
|
||||
</TabList>
|
||||
{tabs.map(({ key, component: Component }) => (
|
||||
<TabPanel value={key} key={`tab-panel-${key}`}>
|
||||
<Component />
|
||||
</TabPanel>
|
||||
))}
|
||||
</Tabs>
|
||||
);
|
||||
};
|
||||
+8
-5
@@ -7,6 +7,7 @@ import { Button, DeleteActionModal } from "@app/components/v2";
|
||||
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context";
|
||||
import { usePopUp } from "@app/hooks";
|
||||
import { TProjectTemplate, useDeleteProjectTemplate } from "@app/hooks/api/projectTemplates";
|
||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||
|
||||
import { ProjectTemplateDetailsModal } from "../../ProjectTemplateDetailsModal";
|
||||
import { ProjectTemplateEnvironmentsForm } from "./ProjectTemplateEnvironmentsForm";
|
||||
@@ -24,7 +25,7 @@ export const EditProjectTemplate = ({ isInfisicalTemplate, projectTemplate, onBa
|
||||
"editDetails"
|
||||
] as const);
|
||||
|
||||
const { id: templateId, name, description } = projectTemplate;
|
||||
const { id: templateId, name, description, type } = projectTemplate;
|
||||
|
||||
const deleteProjectTemplate = useDeleteProjectTemplate();
|
||||
|
||||
@@ -94,10 +95,12 @@ export const EditProjectTemplate = ({ isInfisicalTemplate, projectTemplate, onBa
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<ProjectTemplateEnvironmentsForm
|
||||
isInfisicalTemplate={isInfisicalTemplate}
|
||||
projectTemplate={projectTemplate}
|
||||
/>
|
||||
{type === ProjectType.SecretManager && (
|
||||
<ProjectTemplateEnvironmentsForm
|
||||
isInfisicalTemplate={isInfisicalTemplate}
|
||||
projectTemplate={projectTemplate}
|
||||
/>
|
||||
)}
|
||||
<ProjectTemplateRolesSection
|
||||
isInfisicalTemplate={isInfisicalTemplate}
|
||||
projectTemplate={projectTemplate}
|
||||
+8
-28
@@ -1,19 +1,18 @@
|
||||
import { Controller, FormProvider, useForm } from "react-hook-form";
|
||||
import { faChevronLeft, faPlus, faSave } from "@fortawesome/free-solid-svg-icons";
|
||||
import { faChevronLeft, faSave } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { z } from "zod";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { Button, FormControl, Input, Modal, ModalContent, ModalTrigger } from "@app/components/v2";
|
||||
import { Button, FormControl, Input } from "@app/components/v2";
|
||||
import { ProjectPermissionSub } from "@app/context";
|
||||
import { isCustomProjectRole } from "@app/helpers/roles";
|
||||
import { usePopUp } from "@app/hooks";
|
||||
import { TProjectTemplate, useUpdateProjectTemplate } from "@app/hooks/api/projectTemplates";
|
||||
import { slugSchema } from "@app/lib/schemas";
|
||||
import { AddPoliciesButton } from "@app/pages/project/RoleDetailsBySlugPage/components/AddPoliciesButton";
|
||||
import { GeneralPermissionPolicies } from "@app/pages/project/RoleDetailsBySlugPage/components/GeneralPermissionPolicies";
|
||||
import { NewPermissionRule } from "@app/pages/project/RoleDetailsBySlugPage/components/NewPermissionRule";
|
||||
import { PermissionEmptyState } from "@app/pages/project/RoleDetailsBySlugPage/components/PermissionEmptyState";
|
||||
import {
|
||||
formRolePermission2API,
|
||||
@@ -44,8 +43,6 @@ export const ProjectTemplateEditRoleForm = ({
|
||||
role,
|
||||
isDisabled
|
||||
}: Props) => {
|
||||
const { popUp, handlePopUpToggle } = usePopUp(["createPolicy"] as const);
|
||||
|
||||
const formMethods = useForm<TFormSchema>({
|
||||
values: role ? { ...role, permissions: rolePermission2Form(role.permissions) } : undefined,
|
||||
resolver: zodResolver(formSchema)
|
||||
@@ -119,34 +116,17 @@ export const ProjectTemplateEditRoleForm = ({
|
||||
<Button
|
||||
variant="outline_bg"
|
||||
type="submit"
|
||||
className={twMerge("h-10 rounded-r-none", isDirty && "bg-primary text-black")}
|
||||
className={twMerge(
|
||||
"mr-4 h-10 border border-primary",
|
||||
isDirty && "bg-primary text-black"
|
||||
)}
|
||||
isDisabled={isSubmitting || !isDirty || isDisabled}
|
||||
isLoading={isSubmitting}
|
||||
leftIcon={<FontAwesomeIcon icon={faSave} />}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
<Modal
|
||||
isOpen={popUp.createPolicy.isOpen}
|
||||
onOpenChange={(isOpen) => handlePopUpToggle("createPolicy", isOpen)}
|
||||
>
|
||||
<ModalTrigger asChild>
|
||||
<Button
|
||||
className="h-10 rounded-l-none"
|
||||
variant="outline_bg"
|
||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||
isDisabled={isDisabled}
|
||||
>
|
||||
New Policy
|
||||
</Button>
|
||||
</ModalTrigger>
|
||||
<ModalContent
|
||||
title="New Policy"
|
||||
subTitle="Policies grant additional permissions."
|
||||
>
|
||||
<NewPermissionRule onClose={() => handlePopUpToggle("createPolicy")} />
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
<AddPoliciesButton isDisabled={isDisabled} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
+11
-3
@@ -19,7 +19,7 @@ import {
|
||||
THead,
|
||||
Tr
|
||||
} from "@app/components/v2";
|
||||
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context";
|
||||
import { OrgPermissionActions, OrgPermissionSubjects, useSubscription } from "@app/context";
|
||||
import { TProjectTemplate, useUpdateProjectTemplate } from "@app/hooks/api/projectTemplates";
|
||||
import { slugSchema } from "@app/lib/schemas";
|
||||
|
||||
@@ -35,6 +35,7 @@ const formSchema = z.object({
|
||||
slug: slugSchema({ min: 1, max: 32 })
|
||||
})
|
||||
.array()
|
||||
.nullish()
|
||||
});
|
||||
|
||||
type TFormSchema = z.infer<typeof formSchema>;
|
||||
@@ -55,6 +56,8 @@ export const ProjectTemplateEnvironmentsForm = ({
|
||||
resolver: zodResolver(formSchema)
|
||||
});
|
||||
|
||||
const { subscription } = useSubscription();
|
||||
|
||||
const {
|
||||
fields: environments,
|
||||
move,
|
||||
@@ -67,7 +70,7 @@ export const ProjectTemplateEnvironmentsForm = ({
|
||||
const onFormSubmit = async (form: TFormSchema) => {
|
||||
try {
|
||||
const { environments: updatedEnvs } = await updateProjectTemplate.mutateAsync({
|
||||
environments: form.environments.map((env, index) => ({
|
||||
environments: form.environments?.map((env, index) => ({
|
||||
...env,
|
||||
position: index + 1
|
||||
})),
|
||||
@@ -89,6 +92,9 @@ export const ProjectTemplateEnvironmentsForm = ({
|
||||
}
|
||||
};
|
||||
|
||||
const isEnvironmentLimitExceeded =
|
||||
Boolean(subscription.environmentLimit) && environments.length >= subscription.environmentLimit;
|
||||
|
||||
return (
|
||||
<form
|
||||
onSubmit={handleSubmit(onFormSubmit)}
|
||||
@@ -138,6 +144,8 @@ export const ProjectTemplateEnvironmentsForm = ({
|
||||
<OrgPermissionCan
|
||||
I={OrgPermissionActions.Edit}
|
||||
a={OrgPermissionSubjects.ProjectTemplates}
|
||||
renderTooltip={isEnvironmentLimitExceeded ? true : undefined}
|
||||
allowedLabel={`Plan environment limit of ${subscription.environmentLimit} exceeded. Contact Infisical to increase limit.`}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<Button
|
||||
@@ -147,7 +155,7 @@ export const ProjectTemplateEnvironmentsForm = ({
|
||||
variant="solid"
|
||||
size="xs"
|
||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||
isDisabled={!isAllowed}
|
||||
isDisabled={!isAllowed || isEnvironmentLimitExceeded}
|
||||
>
|
||||
Add Environment
|
||||
</Button>
|
||||
+11
-1
@@ -12,6 +12,7 @@ import {
|
||||
ModalContent,
|
||||
TextArea
|
||||
} from "@app/components/v2";
|
||||
import { useGetProjectTypeFromRoute } from "@app/hooks";
|
||||
import {
|
||||
TProjectTemplate,
|
||||
useCreateProjectTemplate,
|
||||
@@ -41,6 +42,7 @@ type FormProps = {
|
||||
const ProjectTemplateForm = ({ onComplete, projectTemplate }: FormProps) => {
|
||||
const createProjectTemplate = useCreateProjectTemplate();
|
||||
const updateProjectTemplate = useUpdateProjectTemplate();
|
||||
const projectType = useGetProjectTypeFromRoute();
|
||||
|
||||
const {
|
||||
handleSubmit,
|
||||
@@ -55,9 +57,17 @@ const ProjectTemplateForm = ({ onComplete, projectTemplate }: FormProps) => {
|
||||
});
|
||||
|
||||
const onFormSubmit = async (data: FormData) => {
|
||||
if (!projectType) {
|
||||
createNotification({
|
||||
text: "Failed to determine project type",
|
||||
type: "error"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const mutation = projectTemplate
|
||||
? updateProjectTemplate.mutateAsync({ templateId: projectTemplate.id, ...data })
|
||||
: createProjectTemplate.mutateAsync(data);
|
||||
: createProjectTemplate.mutateAsync({ ...data, type: projectType });
|
||||
try {
|
||||
const template = await mutation;
|
||||
createNotification({
|
||||
+1
-1
@@ -50,7 +50,7 @@ export const ProjectTemplatesSection = () => {
|
||||
className="absolute min-h-[10rem] w-full"
|
||||
>
|
||||
<div>
|
||||
<p className="mb-6 text-bunker-300">
|
||||
<p className="mb-6 font-inter text-bunker-300">
|
||||
Create and configure templates with predefined roles and environments to streamline
|
||||
project setup
|
||||
</p>
|
||||
+42
-31
@@ -23,8 +23,9 @@ import {
|
||||
Tr
|
||||
} from "@app/components/v2";
|
||||
import { OrgPermissionActions, OrgPermissionSubjects, useSubscription } from "@app/context";
|
||||
import { usePopUp } from "@app/hooks";
|
||||
import { useGetProjectTypeFromRoute, usePopUp } from "@app/hooks";
|
||||
import { TProjectTemplate, useListProjectTemplates } from "@app/hooks/api/projectTemplates";
|
||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||
|
||||
import { DeleteProjectTemplateModal } from "./DeleteProjectTemplateModal";
|
||||
|
||||
@@ -35,9 +36,12 @@ type Props = {
|
||||
export const ProjectTemplatesTable = ({ onEdit }: Props) => {
|
||||
const { subscription } = useSubscription();
|
||||
|
||||
const { isPending, data: projectTemplates = [] } = useListProjectTemplates({
|
||||
enabled: subscription?.projectTemplates
|
||||
const projectType = useGetProjectTypeFromRoute();
|
||||
|
||||
const { isPending, data: projectTemplates = [] } = useListProjectTemplates(projectType, {
|
||||
enabled: subscription?.projectTemplates && Boolean(projectType)
|
||||
});
|
||||
|
||||
const [search, setSearch] = useState("");
|
||||
|
||||
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["deleteTemplate"] as const);
|
||||
@@ -50,6 +54,10 @@ export const ProjectTemplatesTable = ({ onEdit }: Props) => {
|
||||
[search, projectTemplates]
|
||||
);
|
||||
|
||||
const isSecretManagerTemplates = projectType === ProjectType.SecretManager;
|
||||
|
||||
const colSpan = isSecretManagerTemplates ? 4 : 3;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Input
|
||||
@@ -64,20 +72,20 @@ export const ProjectTemplatesTable = ({ onEdit }: Props) => {
|
||||
<Tr>
|
||||
<Th>Name</Th>
|
||||
<Th>Roles</Th>
|
||||
<Th>Environments</Th>
|
||||
{isSecretManagerTemplates && <Th>Environments</Th>}
|
||||
<Th />
|
||||
</Tr>
|
||||
</THead>
|
||||
<TBody>
|
||||
{isPending && (
|
||||
{subscription?.projectTemplates && isPending && (
|
||||
<TableSkeleton
|
||||
innerKey="project-templates-table"
|
||||
columns={4}
|
||||
columns={colSpan}
|
||||
key="project-templates"
|
||||
/>
|
||||
)}
|
||||
{filteredTemplates.map((template) => {
|
||||
const { id, name, roles, environments, description } = template;
|
||||
const { id, name, roles, environments = [], description } = template;
|
||||
return (
|
||||
<Tr
|
||||
onClick={() => onEdit(template)}
|
||||
@@ -116,28 +124,30 @@ export const ProjectTemplatesTable = ({ onEdit }: Props) => {
|
||||
</Tooltip>
|
||||
)}
|
||||
</Td>
|
||||
<Td className="pl-14">
|
||||
{environments.length}
|
||||
{environments.length > 0 && (
|
||||
<Tooltip
|
||||
content={
|
||||
<ul className="ml-2 list-disc">
|
||||
{environments
|
||||
.sort((a, b) => (a.position > b.position ? 1 : -1))
|
||||
.map((env) => (
|
||||
<li key={env.slug}>{env.name}</li>
|
||||
))}
|
||||
</ul>
|
||||
}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
size="sm"
|
||||
className="ml-2 text-mineshaft-400"
|
||||
icon={faCircleInfo}
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Td>
|
||||
{isSecretManagerTemplates && environments && (
|
||||
<Td className="pl-14">
|
||||
{environments.length}
|
||||
{environments.length > 0 && (
|
||||
<Tooltip
|
||||
content={
|
||||
<ul className="ml-2 list-disc">
|
||||
{environments
|
||||
.sort((a, b) => (a.position > b.position ? 1 : -1))
|
||||
.map((env) => (
|
||||
<li key={env.slug}>{env.name}</li>
|
||||
))}
|
||||
</ul>
|
||||
}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
size="sm"
|
||||
className="ml-2 text-mineshaft-400"
|
||||
icon={faCircleInfo}
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Td>
|
||||
)}
|
||||
<Td className="w-5">
|
||||
{name !== "default" && (
|
||||
<OrgPermissionCan
|
||||
@@ -164,9 +174,10 @@ export const ProjectTemplatesTable = ({ onEdit }: Props) => {
|
||||
</Tr>
|
||||
);
|
||||
})}
|
||||
{!isPending && filteredTemplates?.length === 0 && (
|
||||
{(!subscription?.projectTemplates ||
|
||||
(!isPending && filteredTemplates?.length === 0)) && (
|
||||
<Tr>
|
||||
<Td colSpan={5}>
|
||||
<Td colSpan={colSpan}>
|
||||
<EmptyState
|
||||
title={
|
||||
search.trim()
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./ProjectTemplatesTab";
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./ProjectSettings";
|
||||
@@ -44,7 +44,9 @@ const Content = ({ secretSync, onComplete }: ContentProps) => {
|
||||
handleSubmit,
|
||||
control,
|
||||
formState: { isSubmitting, isDirty }
|
||||
} = useForm<TFormData>({ resolver: zodResolver(FormSchema) });
|
||||
} = useForm<TFormData>({
|
||||
resolver: zodResolver(FormSchema)
|
||||
});
|
||||
|
||||
const triggerImportSecrets = useTriggerSecretSyncImportSecrets();
|
||||
|
||||
|
||||
@@ -40,7 +40,14 @@ export const SecretSyncStatusBadge = ({ status }: Props) => {
|
||||
|
||||
return (
|
||||
<Badge className="flex h-5 w-min items-center gap-1.5 whitespace-nowrap" variant={variant}>
|
||||
<FontAwesomeIcon icon={icon} />
|
||||
<FontAwesomeIcon
|
||||
icon={icon}
|
||||
className={
|
||||
[SecretSyncStatus.Pending, SecretSyncStatus.Running].includes(status)
|
||||
? "animate-spin"
|
||||
: ""
|
||||
}
|
||||
/>
|
||||
<span>{text}</span>
|
||||
</Badge>
|
||||
);
|
||||
|
||||
+175
@@ -0,0 +1,175 @@
|
||||
import { Controller, useFormContext, useWatch } from "react-hook-form";
|
||||
import { SingleValue } from "react-select";
|
||||
import { faCircleInfo } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import { SecretSyncConnectionField } from "@app/components/secret-syncs/forms/SecretSyncConnectionField";
|
||||
import { FilterableSelect, FormControl, Tooltip } from "@app/components/v2";
|
||||
import {
|
||||
useOCIConnectionListCompartments,
|
||||
useOCIConnectionListVaultKeys,
|
||||
useOCIConnectionListVaults
|
||||
} from "@app/hooks/api/appConnections/oci";
|
||||
import { SecretSync } from "@app/hooks/api/secretSyncs";
|
||||
|
||||
import { TSecretSyncForm } from "../schemas";
|
||||
|
||||
export const OCIVaultSyncFields = () => {
|
||||
const { control, setValue } = useFormContext<
|
||||
TSecretSyncForm & { destination: SecretSync.OCIVault }
|
||||
>();
|
||||
|
||||
const connectionId = useWatch({ name: "connection.id", control });
|
||||
|
||||
// Compartments
|
||||
const { data: compartments, isLoading: isCompartmentsLoading } = useOCIConnectionListCompartments(
|
||||
connectionId,
|
||||
{
|
||||
enabled: Boolean(connectionId)
|
||||
}
|
||||
);
|
||||
|
||||
// Vaults
|
||||
const selectedCompartment = useWatch({ name: "destinationConfig.compartmentOcid", control });
|
||||
const { data: vaults, isLoading: isVaultsLoading } = useOCIConnectionListVaults(
|
||||
{ connectionId, compartmentOcid: selectedCompartment },
|
||||
{
|
||||
enabled: Boolean(connectionId && selectedCompartment)
|
||||
}
|
||||
);
|
||||
|
||||
// Keys
|
||||
const selectedVault = useWatch({ name: "destinationConfig.vaultOcid", control });
|
||||
const { data: keys, isLoading: isKeysLoading } = useOCIConnectionListVaultKeys(
|
||||
{ connectionId, compartmentOcid: selectedCompartment, vaultOcid: selectedVault },
|
||||
{
|
||||
enabled: Boolean(connectionId && selectedCompartment && selectedVault)
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<SecretSyncConnectionField
|
||||
onChange={() => {
|
||||
setValue("destinationConfig.compartmentOcid", "");
|
||||
setValue("destinationConfig.vaultOcid", "");
|
||||
setValue("destinationConfig.keyOcid", "");
|
||||
}}
|
||||
/>
|
||||
|
||||
<Controller
|
||||
name="destinationConfig.compartmentOcid"
|
||||
control={control}
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
label="Compartment"
|
||||
helperText={
|
||||
<Tooltip
|
||||
className="max-w-md"
|
||||
content="Ensure the compartment exists and that the connection has permission to view it."
|
||||
>
|
||||
<div>
|
||||
<span>Don't see the compartment you're looking for?</span>{" "}
|
||||
<FontAwesomeIcon icon={faCircleInfo} className="text-mineshaft-400" />
|
||||
</div>
|
||||
</Tooltip>
|
||||
}
|
||||
>
|
||||
<FilterableSelect
|
||||
menuPlacement="top"
|
||||
isLoading={isCompartmentsLoading && Boolean(connectionId)}
|
||||
isDisabled={!connectionId}
|
||||
value={compartments?.find((c) => c.id === value) ?? null}
|
||||
onChange={(option) => {
|
||||
onChange((option as SingleValue<{ id: string }>)?.id ?? null);
|
||||
setValue("destinationConfig.vaultOcid", "");
|
||||
setValue("destinationConfig.keyOcid", "");
|
||||
}}
|
||||
options={compartments}
|
||||
placeholder="Select a compartment..."
|
||||
getOptionLabel={(option) => option.name}
|
||||
getOptionValue={(option) => option.id}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Controller
|
||||
name="destinationConfig.vaultOcid"
|
||||
control={control}
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
label="Vault"
|
||||
helperText={
|
||||
<Tooltip
|
||||
className="max-w-md"
|
||||
content="Ensure the vault exists in the selected compartment and that the connection has permission to view it."
|
||||
>
|
||||
<div>
|
||||
<span>Don't see the vault you're looking for?</span>{" "}
|
||||
<FontAwesomeIcon icon={faCircleInfo} className="text-mineshaft-400" />
|
||||
</div>
|
||||
</Tooltip>
|
||||
}
|
||||
>
|
||||
<FilterableSelect
|
||||
menuPlacement="top"
|
||||
isLoading={isVaultsLoading && Boolean(connectionId)}
|
||||
isDisabled={!connectionId || !selectedCompartment}
|
||||
value={vaults?.find((v) => v.id === value) ?? null}
|
||||
onChange={(option) => {
|
||||
onChange((option as SingleValue<{ id: string }>)?.id ?? null);
|
||||
setValue("destinationConfig.keyOcid", "");
|
||||
}}
|
||||
options={vaults}
|
||||
placeholder="Select a vault..."
|
||||
getOptionLabel={(option) => option.displayName}
|
||||
getOptionValue={(option) => option.id}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Controller
|
||||
name="destinationConfig.keyOcid"
|
||||
control={control}
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
label="Encryption Key"
|
||||
helperText={
|
||||
<Tooltip
|
||||
className="max-w-md"
|
||||
content="Ensure the key exists in the selected vault and that the connection has permission to view it."
|
||||
>
|
||||
<div>
|
||||
<span>Don't see the key you're looking for?</span>{" "}
|
||||
<FontAwesomeIcon icon={faCircleInfo} className="text-mineshaft-400" />
|
||||
</div>
|
||||
</Tooltip>
|
||||
}
|
||||
>
|
||||
<FilterableSelect
|
||||
menuPlacement="top"
|
||||
isLoading={isKeysLoading && Boolean(connectionId)}
|
||||
isDisabled={!connectionId || !selectedCompartment || !selectedVault}
|
||||
value={keys?.find((v) => v.id === value) ?? null}
|
||||
onChange={(option) => {
|
||||
onChange((option as SingleValue<{ id: string }>)?.id ?? null);
|
||||
}}
|
||||
options={keys}
|
||||
placeholder="Select a key..."
|
||||
getOptionLabel={(option) => option.displayName}
|
||||
getOptionValue={(option) => option.id}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
+3
@@ -13,6 +13,7 @@ import { GcpSyncFields } from "./GcpSyncFields";
|
||||
import { GitHubSyncFields } from "./GitHubSyncFields";
|
||||
import { HCVaultSyncFields } from "./HCVaultSyncFields";
|
||||
import { HumanitecSyncFields } from "./HumanitecSyncFields";
|
||||
import { OCIVaultSyncFields } from "./OCIVaultSyncFields";
|
||||
import { TeamCitySyncFields } from "./TeamCitySyncFields";
|
||||
import { TerraformCloudSyncFields } from "./TerraformCloudSyncFields";
|
||||
import { VercelSyncFields } from "./VercelSyncFields";
|
||||
@@ -52,6 +53,8 @@ export const SecretSyncDestinationFields = () => {
|
||||
return <HCVaultSyncFields />;
|
||||
case SecretSync.TeamCity:
|
||||
return <TeamCitySyncFields />;
|
||||
case SecretSync.OCIVault:
|
||||
return <OCIVaultSyncFields />;
|
||||
default:
|
||||
throw new Error(`Unhandled Destination Config Field: ${destination}`);
|
||||
}
|
||||
|
||||
+47
-30
@@ -1,9 +1,13 @@
|
||||
import { ReactNode } from "react";
|
||||
import { Controller, useFormContext } from "react-hook-form";
|
||||
import { faQuestionCircle, faTriangleExclamation } from "@fortawesome/free-solid-svg-icons";
|
||||
import {
|
||||
faCircleInfo,
|
||||
faQuestionCircle,
|
||||
faTriangleExclamation
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import { FormControl, Select, SelectItem, Switch, Tooltip } from "@app/components/v2";
|
||||
import { FormControl, Input, Select, SelectItem, Switch, Tooltip } from "@app/components/v2";
|
||||
import { SECRET_SYNC_INITIAL_SYNC_BEHAVIOR_MAP, SECRET_SYNC_MAP } from "@app/helpers/secretSyncs";
|
||||
import { SecretSync, useSecretSyncOption } from "@app/hooks/api/secretSyncs";
|
||||
|
||||
@@ -45,6 +49,7 @@ export const SecretSyncOptionsFields = ({ hideInitialSync }: Props) => {
|
||||
case SecretSync.Windmill:
|
||||
case SecretSync.HCVault:
|
||||
case SecretSync.TeamCity:
|
||||
case SecretSync.OCIVault:
|
||||
AdditionalSyncOptionsFieldsComponent = null;
|
||||
break;
|
||||
default:
|
||||
@@ -121,6 +126,46 @@ export const SecretSyncOptionsFields = ({ hideInitialSync }: Props) => {
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<Controller
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
tooltipClassName="max-w-md"
|
||||
tooltipText="When a secret is synced, its key will be injected into the key schema before it reaches the destination. This is useful for organization."
|
||||
isError={Boolean(error)}
|
||||
isOptional
|
||||
errorText={error?.message}
|
||||
label="Key Schema"
|
||||
helperText={
|
||||
<Tooltip
|
||||
className="max-w-md"
|
||||
content={
|
||||
<span>
|
||||
We highly recommend using a{" "}
|
||||
<a
|
||||
href="https://infisical.com/docs/integrations/secret-syncs/overview#key-schemas"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Key Schema
|
||||
</a>{" "}
|
||||
to ensure that Infisical only manages the specific keys you intend, keeping
|
||||
everything else untouched.
|
||||
</span>
|
||||
}
|
||||
>
|
||||
<div>
|
||||
<span>Infisical strongly advises setting a Key Schema</span>{" "}
|
||||
<FontAwesomeIcon icon={faCircleInfo} className="text-mineshaft-400" />
|
||||
</div>
|
||||
</Tooltip>
|
||||
}
|
||||
>
|
||||
<Input value={value} onChange={onChange} placeholder="INFISICAL_{{secretKey}}" />
|
||||
</FormControl>
|
||||
)}
|
||||
control={control}
|
||||
name="syncOptions.keySchema"
|
||||
/>
|
||||
{AdditionalSyncOptionsFieldsComponent}
|
||||
<Controller
|
||||
control={control}
|
||||
@@ -160,34 +205,6 @@ export const SecretSyncOptionsFields = ({ hideInitialSync }: Props) => {
|
||||
);
|
||||
}}
|
||||
/>
|
||||
{/* <Controller
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
isError={Boolean(error)}
|
||||
isOptional
|
||||
errorText={error?.message}
|
||||
label="Prepend Prefix"
|
||||
>
|
||||
<Input className="uppercase" value={value} onChange={onChange} placeholder="INF_" />
|
||||
</FormControl>
|
||||
)}
|
||||
control={control}
|
||||
name="syncOptions.prependPrefix"
|
||||
/>
|
||||
<Controller
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
isError={Boolean(error)}
|
||||
isOptional
|
||||
errorText={error?.message}
|
||||
label="Append Suffix"
|
||||
>
|
||||
<Input className="uppercase" value={value} onChange={onChange} placeholder="_INF" />
|
||||
</FormControl>
|
||||
)}
|
||||
control={control}
|
||||
name="syncOptions.appendSuffix"
|
||||
/> */}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
import { useFormContext } from "react-hook-form";
|
||||
|
||||
import { TSecretSyncForm } from "@app/components/secret-syncs/forms/schemas";
|
||||
import { GenericFieldLabel } from "@app/components/v2";
|
||||
import { SecretSync } from "@app/hooks/api/secretSyncs";
|
||||
|
||||
export const OCIVaultSyncReviewFields = () => {
|
||||
const { watch } = useFormContext<TSecretSyncForm & { destination: SecretSync.OCIVault }>();
|
||||
const compartmentOcid = watch("destinationConfig.compartmentOcid");
|
||||
const vaultOcid = watch("destinationConfig.vaultOcid");
|
||||
const keyOcid = watch("destinationConfig.keyOcid");
|
||||
|
||||
return (
|
||||
<>
|
||||
<GenericFieldLabel label="Compartment OCID" truncate>
|
||||
{compartmentOcid}
|
||||
</GenericFieldLabel>
|
||||
<GenericFieldLabel label="Vault OCID" truncate>
|
||||
{vaultOcid}
|
||||
</GenericFieldLabel>
|
||||
<GenericFieldLabel label="Key OCID" truncate>
|
||||
{keyOcid}
|
||||
</GenericFieldLabel>
|
||||
</>
|
||||
);
|
||||
};
|
||||
+6
-7
@@ -23,6 +23,7 @@ import { GcpSyncReviewFields } from "./GcpSyncReviewFields";
|
||||
import { GitHubSyncReviewFields } from "./GitHubSyncReviewFields";
|
||||
import { HCVaultSyncReviewFields } from "./HCVaultSyncReviewFields";
|
||||
import { HumanitecSyncReviewFields } from "./HumanitecSyncReviewFields";
|
||||
import { OCIVaultSyncReviewFields } from "./OCIVaultSyncReviewFields";
|
||||
import { TeamCitySyncReviewFields } from "./TeamCitySyncReviewFields";
|
||||
import { TerraformCloudSyncReviewFields } from "./TerraformCloudSyncReviewFields";
|
||||
import { VercelSyncReviewFields } from "./VercelSyncReviewFields";
|
||||
@@ -40,11 +41,7 @@ export const SecretSyncReviewFields = () => {
|
||||
connection,
|
||||
environment,
|
||||
secretPath,
|
||||
syncOptions: {
|
||||
// appendSuffix, prependPrefix,
|
||||
disableSecretDeletion,
|
||||
initialSyncBehavior
|
||||
},
|
||||
syncOptions: { disableSecretDeletion, initialSyncBehavior, keySchema },
|
||||
destination,
|
||||
isAutoSyncEnabled
|
||||
} = watch();
|
||||
@@ -96,6 +93,9 @@ export const SecretSyncReviewFields = () => {
|
||||
case SecretSync.TeamCity:
|
||||
DestinationFieldsComponent = <TeamCitySyncReviewFields />;
|
||||
break;
|
||||
case SecretSync.OCIVault:
|
||||
DestinationFieldsComponent = <OCIVaultSyncReviewFields />;
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unhandled Destination Review Fields: ${destination}`);
|
||||
}
|
||||
@@ -133,8 +133,7 @@ export const SecretSyncReviewFields = () => {
|
||||
<GenericFieldLabel label="Initial Sync Behavior">
|
||||
{SECRET_SYNC_INITIAL_SYNC_BEHAVIOR_MAP[initialSyncBehavior](destinationName).name}
|
||||
</GenericFieldLabel>
|
||||
{/* <SecretSyncLabel label="Prepend Prefix">{prependPrefix}</SecretSyncLabel>
|
||||
<SecretSyncLabel label="Append Suffix">{appendSuffix}</SecretSyncLabel> */}
|
||||
<GenericFieldLabel label="Key Schema">{keySchema}</GenericFieldLabel>
|
||||
{AdditionalSyncOptionsFieldsComponent}
|
||||
{disableSecretDeletion && (
|
||||
<GenericFieldLabel label="Secret Deletion">
|
||||
|
||||
@@ -8,18 +8,18 @@ export const BaseSecretSyncSchema = <T extends AnyZodObject | undefined = undefi
|
||||
) => {
|
||||
const baseSyncOptionsSchema = z.object({
|
||||
initialSyncBehavior: z.nativeEnum(SecretSyncInitialSyncBehavior),
|
||||
disableSecretDeletion: z.boolean().optional().default(false)
|
||||
// scott: removed temporarily for evaluation of template formatting
|
||||
// prependPrefix: z
|
||||
// .string()
|
||||
// .trim()
|
||||
// .transform((str) => str.toUpperCase())
|
||||
// .optional(),
|
||||
// appendSuffix: z
|
||||
// .string()
|
||||
// .trim()
|
||||
// .transform((str) => str.toUpperCase())
|
||||
// .optional()
|
||||
disableSecretDeletion: z.boolean().optional().default(false),
|
||||
keySchema: z
|
||||
.string()
|
||||
.optional()
|
||||
.refine(
|
||||
(val) =>
|
||||
!val || /^(?:[a-zA-Z0-9_\-/]*)(?:\{\{secretKey\}\})(?:[a-zA-Z0-9_\-/]*)$/.test(val),
|
||||
{
|
||||
message:
|
||||
"Key schema must include one {{secretKey}} and only contain letters, numbers, dashes, underscores, slashes, and the {{secretKey}} placeholder."
|
||||
}
|
||||
)
|
||||
});
|
||||
|
||||
const syncOptionsSchema = additionalSyncOptions
|
||||
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { BaseSecretSyncSchema } from "@app/components/secret-syncs/forms/schemas/base-secret-sync-schema";
|
||||
import { SecretSync } from "@app/hooks/api/secretSyncs";
|
||||
|
||||
export const OCIVaultSyncDestinationSchema = BaseSecretSyncSchema().merge(
|
||||
z.object({
|
||||
destination: z.literal(SecretSync.OCIVault),
|
||||
destinationConfig: z.object({
|
||||
compartmentOcid: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1, "Compartment OCID required")
|
||||
.regex(
|
||||
/^ocid1\.(tenancy|compartment)\.oc1\..+$/,
|
||||
"Invalid Compartment OCID format. Must start with ocid1.tenancy.oc1. or ocid1.compartment.oc1."
|
||||
),
|
||||
vaultOcid: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1, "Vault OCID required")
|
||||
.regex(
|
||||
/^ocid1\.vault\.oc1\..+$/,
|
||||
"Invalid Vault OCID format. Must start with ocid1.vault.oc1."
|
||||
),
|
||||
keyOcid: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1, "Key OCID required")
|
||||
.regex(/^ocid1\.key\.oc1\..+$/, "Invalid Key OCID format. Must start with ocid1.key.oc1.")
|
||||
})
|
||||
})
|
||||
);
|
||||
@@ -10,6 +10,7 @@ import { GcpSyncDestinationSchema } from "./gcp-sync-destination-schema";
|
||||
import { GitHubSyncDestinationSchema } from "./github-sync-destination-schema";
|
||||
import { HCVaultSyncDestinationSchema } from "./hc-vault-sync-destination-schema";
|
||||
import { HumanitecSyncDestinationSchema } from "./humanitec-sync-destination-schema";
|
||||
import { OCIVaultSyncDestinationSchema } from "./oci-vault-sync-destination-schema";
|
||||
import { TeamCitySyncDestinationSchema } from "./teamcity-sync-destination-schema";
|
||||
import { TerraformCloudSyncDestinationSchema } from "./terraform-cloud-destination-schema";
|
||||
import { VercelSyncDestinationSchema } from "./vercel-sync-destination-schema";
|
||||
@@ -29,7 +30,8 @@ const SecretSyncUnionSchema = z.discriminatedUnion("destination", [
|
||||
VercelSyncDestinationSchema,
|
||||
WindmillSyncDestinationSchema,
|
||||
HCVaultSyncDestinationSchema,
|
||||
TeamCitySyncDestinationSchema
|
||||
TeamCitySyncDestinationSchema,
|
||||
OCIVaultSyncDestinationSchema
|
||||
]);
|
||||
|
||||
export const SecretSyncFormSchema = SecretSyncUnionSchema;
|
||||
|
||||
@@ -6,14 +6,21 @@ type Props = {
|
||||
children?: ReactNode;
|
||||
className?: string;
|
||||
labelClassName?: string;
|
||||
truncate?: boolean;
|
||||
};
|
||||
|
||||
export const GenericFieldLabel = ({ label, children, className, labelClassName }: Props) => {
|
||||
export const GenericFieldLabel = ({
|
||||
label,
|
||||
children,
|
||||
className,
|
||||
labelClassName,
|
||||
truncate
|
||||
}: Props) => {
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className={twMerge("min-w-0", className)}>
|
||||
<p className={twMerge("text-xs font-medium text-mineshaft-400", labelClassName)}>{label}</p>
|
||||
{children ? (
|
||||
<p className="text-sm text-mineshaft-100">{children}</p>
|
||||
<p className={twMerge("text-sm text-mineshaft-100", truncate && "truncate")}>{children}</p>
|
||||
) : (
|
||||
<p className="text-sm italic text-mineshaft-400/50">None</p>
|
||||
)}
|
||||
|
||||
@@ -7,9 +7,10 @@ type Props = {
|
||||
title: string;
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
titleClassName?: string;
|
||||
};
|
||||
|
||||
export const NoticeBannerV2 = ({ title, children, className }: Props) => {
|
||||
export const NoticeBannerV2 = ({ title, children, className, titleClassName }: Props) => {
|
||||
return (
|
||||
<div
|
||||
className={twMerge(
|
||||
@@ -17,7 +18,7 @@ export const NoticeBannerV2 = ({ title, children, className }: Props) => {
|
||||
className
|
||||
)}
|
||||
>
|
||||
<div className="mb-1 flex items-center text-sm">
|
||||
<div className={twMerge("mb-1 flex items-center text-sm", titleClassName)}>
|
||||
<FontAwesomeIcon icon={faInfoCircle} size="sm" className="mr-1.5 text-primary" />
|
||||
{title}
|
||||
</div>
|
||||
|
||||
@@ -123,6 +123,7 @@ export const SelectItem = forwardRef<HTMLDivElement, SelectItemProps>(
|
||||
return (
|
||||
<SelectPrimitive.Item
|
||||
{...props}
|
||||
disabled={isDisabled}
|
||||
className={twMerge(
|
||||
"relative mb-0.5 cursor-pointer select-none items-center overflow-hidden truncate rounded-md py-2 pl-10 pr-4 text-sm outline-none transition-all hover:bg-mineshaft-500 data-[highlighted]:bg-mineshaft-700/80",
|
||||
isSelected && "bg-primary",
|
||||
|
||||
@@ -23,6 +23,10 @@ export const ROUTE_PATHS = Object.freeze({
|
||||
"/_authenticate/_inject-org-details/_org-layout/organization/settings/oauth/callback"
|
||||
)
|
||||
},
|
||||
SsoPage: setRoute(
|
||||
"/organization/sso",
|
||||
"/_authenticate/_inject-org-details/_org-layout/organization/sso"
|
||||
),
|
||||
SecretScanning: setRoute(
|
||||
"/organization/secret-scanning",
|
||||
"/_authenticate/_inject-org-details/_org-layout/organization/secret-scanning"
|
||||
@@ -295,9 +299,13 @@ export const ROUTE_PATHS = Object.freeze({
|
||||
"/cert-manager/$projectId/ca/$caId",
|
||||
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/ca/$caId"
|
||||
),
|
||||
OverviewPage: setRoute(
|
||||
"/cert-manager/$projectId/overview",
|
||||
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/overview"
|
||||
SubscribersPage: setRoute(
|
||||
"/cert-manager/$projectId/subscribers",
|
||||
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers"
|
||||
),
|
||||
CertificatesPage: setRoute(
|
||||
"/cert-manager/$projectId/certificates",
|
||||
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/certificates"
|
||||
),
|
||||
CertificateAuthoritiesPage: setRoute(
|
||||
"/cert-manager/$projectId/certificate-authorities",
|
||||
@@ -310,6 +318,10 @@ export const ROUTE_PATHS = Object.freeze({
|
||||
PkiCollectionDetailsByIDPage: setRoute(
|
||||
"/cert-manager/$projectId/pki-collections/$collectionId",
|
||||
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/pki-collections/$collectionId"
|
||||
),
|
||||
PkiSubscriberDetailsByIDPage: setRoute(
|
||||
"/cert-manager/$projectId/subscribers/$subscriberName",
|
||||
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers/$subscriberName"
|
||||
)
|
||||
},
|
||||
Ssh: {
|
||||
|
||||
@@ -12,7 +12,8 @@ export enum OrgGatewayPermissionActions {
|
||||
CreateGateways = "create-gateways",
|
||||
ListGateways = "list-gateways",
|
||||
EditGateways = "edit-gateways",
|
||||
DeleteGateways = "delete-gateways"
|
||||
DeleteGateways = "delete-gateways",
|
||||
AttachGateways = "attach-gateways"
|
||||
}
|
||||
|
||||
export enum OrgPermissionSubjects {
|
||||
|
||||
@@ -2,11 +2,14 @@ export { useProjectPermission } from "./ProjectPermissionContext";
|
||||
export type { ProjectPermissionSet, TProjectPermission } from "./types";
|
||||
export {
|
||||
ProjectPermissionActions,
|
||||
ProjectPermissionCertificateActions,
|
||||
ProjectPermissionCmekActions,
|
||||
ProjectPermissionDynamicSecretActions,
|
||||
ProjectPermissionGroupActions,
|
||||
ProjectPermissionIdentityActions,
|
||||
ProjectPermissionKmipActions,
|
||||
ProjectPermissionMemberActions,
|
||||
ProjectPermissionPkiSubscriberActions,
|
||||
ProjectPermissionSshHostActions,
|
||||
ProjectPermissionSub
|
||||
} from "./types";
|
||||
|
||||
@@ -7,6 +7,14 @@ export enum ProjectPermissionActions {
|
||||
Delete = "delete"
|
||||
}
|
||||
|
||||
export enum ProjectPermissionCertificateActions {
|
||||
Read = "read",
|
||||
Create = "create",
|
||||
Edit = "edit",
|
||||
Delete = "delete",
|
||||
ReadPrivateKey = "read-private-key"
|
||||
}
|
||||
|
||||
export enum ProjectPermissionSecretActions {
|
||||
DescribeAndReadValue = "read",
|
||||
DescribeSecret = "describeSecret",
|
||||
@@ -87,6 +95,15 @@ export enum ProjectPermissionSshHostActions {
|
||||
IssueHostCert = "issue-host-cert"
|
||||
}
|
||||
|
||||
export enum ProjectPermissionPkiSubscriberActions {
|
||||
Read = "read",
|
||||
Create = "create",
|
||||
Edit = "edit",
|
||||
Delete = "delete",
|
||||
IssueCert = "issue-cert",
|
||||
ListCerts = "list-certs"
|
||||
}
|
||||
|
||||
export enum ProjectPermissionSecretRotationActions {
|
||||
Read = "read",
|
||||
ReadGeneratedCredentials = "read-generated-credentials",
|
||||
@@ -183,6 +200,7 @@ export enum ProjectPermissionSub {
|
||||
SshHostGroups = "ssh-host-groups",
|
||||
PkiAlerts = "pki-alerts",
|
||||
PkiCollections = "pki-collections",
|
||||
PkiSubscribers = "pki-subscribers",
|
||||
Kms = "kms",
|
||||
Cmek = "cmek",
|
||||
SecretSyncs = "secret-syncs",
|
||||
@@ -218,6 +236,14 @@ export type SecretRotationSubjectFields = {
|
||||
secretPath: string;
|
||||
};
|
||||
|
||||
export type SshHostSubjectFields = {
|
||||
hostname: string;
|
||||
};
|
||||
|
||||
export type PkiSubscriberSubjectFields = {
|
||||
name: string;
|
||||
};
|
||||
|
||||
export type ProjectPermissionSet =
|
||||
| [
|
||||
ProjectPermissionSecretActions,
|
||||
@@ -274,13 +300,26 @@ export type ProjectPermissionSet =
|
||||
)
|
||||
]
|
||||
| [ProjectPermissionActions, ProjectPermissionSub.CertificateAuthorities]
|
||||
| [ProjectPermissionActions, ProjectPermissionSub.Certificates]
|
||||
| [ProjectPermissionCertificateActions, ProjectPermissionSub.Certificates]
|
||||
| [ProjectPermissionActions, ProjectPermissionSub.CertificateTemplates]
|
||||
| [ProjectPermissionActions, ProjectPermissionSub.SshCertificateAuthorities]
|
||||
| [ProjectPermissionActions, ProjectPermissionSub.SshCertificateTemplates]
|
||||
| [ProjectPermissionActions, ProjectPermissionSub.SshCertificates]
|
||||
| [ProjectPermissionActions, ProjectPermissionSub.SshHostGroups]
|
||||
| [ProjectPermissionSshHostActions, ProjectPermissionSub.SshHosts]
|
||||
| [
|
||||
ProjectPermissionSshHostActions,
|
||||
(
|
||||
| ProjectPermissionSub.SshHosts
|
||||
| (ForcedSubject<ProjectPermissionSub.SshHosts> & SshHostSubjectFields)
|
||||
)
|
||||
]
|
||||
| [
|
||||
ProjectPermissionPkiSubscriberActions,
|
||||
(
|
||||
| ProjectPermissionSub.PkiSubscribers
|
||||
| (ForcedSubject<ProjectPermissionSub.PkiSubscribers> & PkiSubscriberSubjectFields)
|
||||
)
|
||||
]
|
||||
| [ProjectPermissionActions, ProjectPermissionSub.PkiAlerts]
|
||||
| [ProjectPermissionActions, ProjectPermissionSub.PkiCollections]
|
||||
| [ProjectPermissionSecretSyncActions, ProjectPermissionSub.SecretSyncs]
|
||||
|
||||
@@ -10,12 +10,15 @@ export {
|
||||
export type { TProjectPermission } from "./ProjectPermissionContext";
|
||||
export {
|
||||
ProjectPermissionActions,
|
||||
ProjectPermissionCertificateActions,
|
||||
ProjectPermissionCmekActions,
|
||||
ProjectPermissionDynamicSecretActions,
|
||||
ProjectPermissionGroupActions,
|
||||
ProjectPermissionIdentityActions,
|
||||
ProjectPermissionKmipActions,
|
||||
ProjectPermissionMemberActions,
|
||||
ProjectPermissionPkiSubscriberActions,
|
||||
ProjectPermissionSshHostActions,
|
||||
ProjectPermissionSub,
|
||||
useProjectPermission
|
||||
} from "./ProjectPermissionContext";
|
||||
|
||||
@@ -30,6 +30,7 @@ import {
|
||||
VercelConnectionMethod,
|
||||
WindmillConnectionMethod
|
||||
} from "@app/hooks/api/appConnections/types";
|
||||
import { OCIConnectionMethod } from "@app/hooks/api/appConnections/types/oci-connection";
|
||||
|
||||
export const APP_CONNECTION_MAP: Record<
|
||||
AppConnection,
|
||||
@@ -61,7 +62,8 @@ export const APP_CONNECTION_MAP: Record<
|
||||
[AppConnection.Auth0]: { name: "Auth0", image: "Auth0.png", size: 40 },
|
||||
[AppConnection.HCVault]: { name: "Hashicorp Vault", image: "Vault.png", size: 65 },
|
||||
[AppConnection.LDAP]: { name: "LDAP", image: "LDAP.png", size: 65 },
|
||||
[AppConnection.TeamCity]: { name: "TeamCity", image: "TeamCity.png" }
|
||||
[AppConnection.TeamCity]: { name: "TeamCity", image: "TeamCity.png" },
|
||||
[AppConnection.OCI]: { name: "OCI", image: "Oracle.png" }
|
||||
};
|
||||
|
||||
export const getAppConnectionMethodDetails = (method: TAppConnection["method"]) => {
|
||||
@@ -74,6 +76,7 @@ export const getAppConnectionMethodDetails = (method: TAppConnection["method"])
|
||||
case GitHubConnectionMethod.OAuth:
|
||||
return { name: "OAuth", icon: faPassport };
|
||||
case AwsConnectionMethod.AccessKey:
|
||||
case OCIConnectionMethod.AccessKey:
|
||||
return { name: "Access Key", icon: faKey };
|
||||
case AwsConnectionMethod.AssumeRole:
|
||||
return { name: "Assume Role", icon: faUser };
|
||||
|
||||
@@ -61,6 +61,9 @@ export const initProjectHelper = async ({ projectName }: { projectName: string }
|
||||
return project;
|
||||
};
|
||||
export const getProjectHomePage = (workspace: Workspace) => {
|
||||
if (workspace.type === ProjectType.CertificateManager) {
|
||||
return `/${workspace.type}/$projectId/subscribers` as const;
|
||||
}
|
||||
return `/${workspace.type}/$projectId/overview` as const;
|
||||
};
|
||||
|
||||
|
||||
@@ -6,21 +6,9 @@ export enum OrgMembershipRole {
|
||||
NoAccess = "no-access"
|
||||
}
|
||||
|
||||
enum ProjectMemberRole {
|
||||
Admin = "admin",
|
||||
Member = "member",
|
||||
Viewer = "viewer",
|
||||
NoAccess = "no-access"
|
||||
}
|
||||
|
||||
export const isCustomOrgRole = (slug: string) =>
|
||||
!Object.values(OrgMembershipRole).includes(slug as OrgMembershipRole);
|
||||
|
||||
export const formatProjectRoleName = (name: string) => {
|
||||
if (name === ProjectMemberRole.Member) return "developer";
|
||||
return name;
|
||||
};
|
||||
|
||||
export const isCustomProjectRole = (slug: string) =>
|
||||
!Object.values(ProjectMembershipRole).includes(slug as ProjectMembershipRole);
|
||||
|
||||
@@ -28,3 +16,24 @@ export const findOrgMembershipRole = (roles: TOrgRole[], roleIdOrSlug: string) =
|
||||
isCustomOrgRole(roleIdOrSlug)
|
||||
? roles.find((r) => r.id === roleIdOrSlug)
|
||||
: roles.find((r) => r.slug === roleIdOrSlug);
|
||||
|
||||
export const formatProjectRoleName = (role: string, customRoleName?: string) => {
|
||||
switch (role) {
|
||||
case ProjectMembershipRole.Admin:
|
||||
return "Admin";
|
||||
case ProjectMembershipRole.Member:
|
||||
return "Developer";
|
||||
case ProjectMembershipRole.Viewer:
|
||||
return "Viewer";
|
||||
case ProjectMembershipRole.NoAccess:
|
||||
return "No Access";
|
||||
case ProjectMembershipRole.Custom:
|
||||
return customRoleName ?? role;
|
||||
case ProjectMembershipRole.SshHostBootstrapper:
|
||||
return "SSH Host Bootstrapper";
|
||||
case ProjectMembershipRole.KmsCryptographicOperator:
|
||||
return "Cryptographic Operator";
|
||||
default:
|
||||
return role;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -47,6 +47,10 @@ export const SECRET_SYNC_MAP: Record<SecretSync, { name: string; image: string }
|
||||
[SecretSync.TeamCity]: {
|
||||
name: "TeamCity",
|
||||
image: "TeamCity.png"
|
||||
},
|
||||
[SecretSync.OCIVault]: {
|
||||
name: "OCI Vault",
|
||||
image: "Oracle.png"
|
||||
}
|
||||
};
|
||||
|
||||
@@ -64,7 +68,8 @@ export const SECRET_SYNC_CONNECTION_MAP: Record<SecretSync, AppConnection> = {
|
||||
[SecretSync.Vercel]: AppConnection.Vercel,
|
||||
[SecretSync.Windmill]: AppConnection.Windmill,
|
||||
[SecretSync.HCVault]: AppConnection.HCVault,
|
||||
[SecretSync.TeamCity]: AppConnection.TeamCity
|
||||
[SecretSync.TeamCity]: AppConnection.TeamCity,
|
||||
[SecretSync.OCIVault]: AppConnection.OCI
|
||||
};
|
||||
|
||||
export const SECRET_SYNC_INITIAL_SYNC_BEHAVIOR_MAP: Record<
|
||||
|
||||
@@ -3,6 +3,7 @@ export {
|
||||
useAdminGrantServerAdminAccess,
|
||||
useAdminRemoveIdentitySuperAdminAccess,
|
||||
useCreateAdminUser,
|
||||
useInvalidateCache,
|
||||
useRemoveUserServerAdminAccess,
|
||||
useUpdateServerConfig,
|
||||
useUpdateServerEncryptionStrategy
|
||||
|
||||
@@ -8,6 +8,7 @@ import { adminQueryKeys, adminStandaloneKeys } from "./queries";
|
||||
import {
|
||||
RootKeyEncryptionStrategy,
|
||||
TCreateAdminUserDTO,
|
||||
TInvalidateCacheDTO,
|
||||
TServerConfig,
|
||||
TUpdateServerConfigDTO
|
||||
} from "./types";
|
||||
@@ -126,3 +127,15 @@ export const useUpdateServerEncryptionStrategy = () => {
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const useInvalidateCache = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation<void, object, TInvalidateCacheDTO>({
|
||||
mutationFn: async (dto) => {
|
||||
await apiRequest.post("/api/v1/admin/invalidate-cache", dto);
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: adminQueryKeys.getInvalidateCache() });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
AdminGetIdentitiesFilters,
|
||||
AdminGetUsersFilters,
|
||||
AdminIntegrationsConfig,
|
||||
TGetInvalidatingCacheStatus,
|
||||
TGetServerRootKmsEncryptionDetails,
|
||||
TServerConfig
|
||||
} from "./types";
|
||||
@@ -22,8 +23,10 @@ export const adminQueryKeys = {
|
||||
getUsers: (filters: AdminGetUsersFilters) => [adminStandaloneKeys.getUsers, { filters }] as const,
|
||||
getIdentities: (filters: AdminGetIdentitiesFilters) =>
|
||||
[adminStandaloneKeys.getIdentities, { filters }] as const,
|
||||
getAdminIntegrationsConfig: () => ["admin-integrations-config"] as const,
|
||||
getServerEncryptionStrategies: () => ["server-encryption-strategies"] as const
|
||||
getAdminSlackConfig: () => ["admin-slack-config"] as const,
|
||||
getServerEncryptionStrategies: () => ["server-encryption-strategies"] as const,
|
||||
getInvalidateCache: () => ["admin-invalidate-cache"] as const,
|
||||
getAdminIntegrationsConfig: () => ["admin-integrations-config"] as const
|
||||
};
|
||||
|
||||
export const fetchServerConfig = async () => {
|
||||
@@ -118,3 +121,18 @@ export const useGetServerRootKmsEncryptionDetails = () => {
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetInvalidatingCacheStatus = (enabled = true) => {
|
||||
return useQuery({
|
||||
queryKey: adminQueryKeys.getInvalidateCache(),
|
||||
queryFn: async () => {
|
||||
const { data } = await apiRequest.get<TGetInvalidatingCacheStatus>(
|
||||
"/api/v1/admin/invalidating-cache-status"
|
||||
);
|
||||
|
||||
return data.invalidating;
|
||||
},
|
||||
enabled,
|
||||
refetchInterval: (data) => (data ? 3000 : false)
|
||||
});
|
||||
};
|
||||
|
||||
@@ -24,6 +24,7 @@ export type TServerConfig = {
|
||||
enabledLoginMethods: LoginMethod[];
|
||||
authConsentContent?: string;
|
||||
pageFrameContent?: string;
|
||||
invalidatingCache: boolean;
|
||||
};
|
||||
|
||||
export type TUpdateServerConfigDTO = {
|
||||
@@ -84,3 +85,16 @@ export enum RootKeyEncryptionStrategy {
|
||||
Software = "SOFTWARE",
|
||||
HSM = "HSM"
|
||||
}
|
||||
|
||||
export enum CacheType {
|
||||
ALL = "all",
|
||||
SECRETS = "secrets"
|
||||
}
|
||||
|
||||
export type TInvalidateCacheDTO = {
|
||||
type: CacheType;
|
||||
};
|
||||
|
||||
export type TGetInvalidatingCacheStatus = {
|
||||
invalidating: boolean;
|
||||
};
|
||||
|
||||
@@ -16,5 +16,6 @@ export enum AppConnection {
|
||||
Auth0 = "auth0",
|
||||
HCVault = "hashicorp-vault",
|
||||
LDAP = "ldap",
|
||||
TeamCity = "teamcity"
|
||||
TeamCity = "teamcity",
|
||||
OCI = "oci"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from "./queries";
|
||||
export * from "./types";
|
||||
@@ -0,0 +1,108 @@
|
||||
import { useQuery, UseQueryOptions } from "@tanstack/react-query";
|
||||
|
||||
import { apiRequest } from "@app/config/request";
|
||||
|
||||
import { appConnectionKeys } from "../queries";
|
||||
import {
|
||||
TListOCIVaultKeys,
|
||||
TListOCIVaults,
|
||||
TOCICompartment,
|
||||
TOCIVault,
|
||||
TOCIVaultKey
|
||||
} from "./types";
|
||||
|
||||
const ociConnectionKeys = {
|
||||
all: [...appConnectionKeys.all, "oci"] as const,
|
||||
listCompartments: (connectionId: string) =>
|
||||
[...ociConnectionKeys.all, "compartments", connectionId] as const,
|
||||
listVaults: (connectionId: string, compartmentOcid: string) =>
|
||||
[...ociConnectionKeys.all, "vaults", connectionId, compartmentOcid] as const,
|
||||
listVaultKeys: (connectionId: string, compartmentOcid: string, vaultOcid: string) =>
|
||||
[...ociConnectionKeys.all, "keys", connectionId, compartmentOcid, vaultOcid] as const
|
||||
};
|
||||
|
||||
export const useOCIConnectionListCompartments = (
|
||||
connectionId: string,
|
||||
options?: Omit<
|
||||
UseQueryOptions<
|
||||
TOCICompartment[],
|
||||
unknown,
|
||||
TOCICompartment[],
|
||||
ReturnType<typeof ociConnectionKeys.listCompartments>
|
||||
>,
|
||||
"queryKey" | "queryFn"
|
||||
>
|
||||
) => {
|
||||
return useQuery({
|
||||
queryKey: ociConnectionKeys.listCompartments(connectionId),
|
||||
queryFn: async () => {
|
||||
const { data } = await apiRequest.get<TOCICompartment[]>(
|
||||
`/api/v1/app-connections/oci/${connectionId}/compartments`
|
||||
);
|
||||
|
||||
return data;
|
||||
},
|
||||
...options
|
||||
});
|
||||
};
|
||||
|
||||
export const useOCIConnectionListVaults = (
|
||||
{ connectionId, compartmentOcid }: TListOCIVaults,
|
||||
options?: Omit<
|
||||
UseQueryOptions<
|
||||
TOCIVault[],
|
||||
unknown,
|
||||
TOCIVault[],
|
||||
ReturnType<typeof ociConnectionKeys.listVaults>
|
||||
>,
|
||||
"queryKey" | "queryFn"
|
||||
>
|
||||
) => {
|
||||
return useQuery({
|
||||
queryKey: ociConnectionKeys.listVaults(connectionId, compartmentOcid),
|
||||
queryFn: async () => {
|
||||
const { data } = await apiRequest.get<TOCIVault[]>(
|
||||
`/api/v1/app-connections/oci/${connectionId}/vaults`,
|
||||
{
|
||||
params: {
|
||||
compartmentOcid
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return data;
|
||||
},
|
||||
...options
|
||||
});
|
||||
};
|
||||
|
||||
export const useOCIConnectionListVaultKeys = (
|
||||
{ connectionId, compartmentOcid, vaultOcid }: TListOCIVaultKeys,
|
||||
options?: Omit<
|
||||
UseQueryOptions<
|
||||
TOCIVaultKey[],
|
||||
unknown,
|
||||
TOCIVaultKey[],
|
||||
ReturnType<typeof ociConnectionKeys.listVaultKeys>
|
||||
>,
|
||||
"queryKey" | "queryFn"
|
||||
>
|
||||
) => {
|
||||
return useQuery({
|
||||
queryKey: ociConnectionKeys.listVaultKeys(connectionId, compartmentOcid, vaultOcid),
|
||||
queryFn: async () => {
|
||||
const { data } = await apiRequest.get<TOCIVaultKey[]>(
|
||||
`/api/v1/app-connections/oci/${connectionId}/vault-keys`,
|
||||
{
|
||||
params: {
|
||||
compartmentOcid,
|
||||
vaultOcid
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return data;
|
||||
},
|
||||
...options
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
// Response types
|
||||
export type TOCICompartment = {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
export type TOCIVault = {
|
||||
id: string;
|
||||
displayName: string;
|
||||
};
|
||||
|
||||
export type TOCIVaultKey = {
|
||||
id: string;
|
||||
displayName: string;
|
||||
};
|
||||
|
||||
// Param types
|
||||
export type TListOCIVaults = {
|
||||
connectionId: string;
|
||||
compartmentOcid: string;
|
||||
};
|
||||
|
||||
export type TListOCIVaultKeys = {
|
||||
connectionId: string;
|
||||
compartmentOcid: string;
|
||||
vaultOcid: string;
|
||||
};
|
||||
@@ -84,6 +84,10 @@ export type TTeamCityConnectionOption = TAppConnectionOptionBase & {
|
||||
app: AppConnection.TeamCity;
|
||||
};
|
||||
|
||||
export type TOCIConnectionOption = TAppConnectionOptionBase & {
|
||||
app: AppConnection.OCI;
|
||||
};
|
||||
|
||||
export type TAppConnectionOption =
|
||||
| TAwsConnectionOption
|
||||
| TGitHubConnectionOption
|
||||
@@ -101,7 +105,8 @@ export type TAppConnectionOption =
|
||||
| TWindmillConnectionOption
|
||||
| TAuth0ConnectionOption
|
||||
| THCVaultConnectionOption
|
||||
| TTeamCityConnectionOption;
|
||||
| TTeamCityConnectionOption
|
||||
| TOCIConnectionOption;
|
||||
|
||||
export type TAppConnectionOptionMap = {
|
||||
[AppConnection.AWS]: TAwsConnectionOption;
|
||||
@@ -122,4 +127,5 @@ export type TAppConnectionOptionMap = {
|
||||
[AppConnection.HCVault]: THCVaultConnectionOption;
|
||||
[AppConnection.LDAP]: TLdapConnectionOption;
|
||||
[AppConnection.TeamCity]: TTeamCityConnectionOption;
|
||||
[AppConnection.OCI]: TOCIConnectionOption;
|
||||
};
|
||||
|
||||
@@ -13,6 +13,7 @@ import { THCVaultConnection } from "./hc-vault-connection";
|
||||
import { THumanitecConnection } from "./humanitec-connection";
|
||||
import { TLdapConnection } from "./ldap-connection";
|
||||
import { TMsSqlConnection } from "./mssql-connection";
|
||||
import { TOCIConnection } from "./oci-connection";
|
||||
import { TPostgresConnection } from "./postgres-connection";
|
||||
import { TTeamCityConnection } from "./teamcity-connection";
|
||||
import { TTerraformCloudConnection } from "./terraform-cloud-connection";
|
||||
@@ -32,6 +33,7 @@ export * from "./hc-vault-connection";
|
||||
export * from "./humanitec-connection";
|
||||
export * from "./ldap-connection";
|
||||
export * from "./mssql-connection";
|
||||
export * from "./oci-connection";
|
||||
export * from "./postgres-connection";
|
||||
export * from "./teamcity-connection";
|
||||
export * from "./terraform-cloud-connection";
|
||||
@@ -56,7 +58,8 @@ export type TAppConnection =
|
||||
| TAuth0Connection
|
||||
| THCVaultConnection
|
||||
| TLdapConnection
|
||||
| TTeamCityConnection;
|
||||
| TTeamCityConnection
|
||||
| TOCIConnection;
|
||||
|
||||
export type TAvailableAppConnection = Pick<TAppConnection, "name" | "id">;
|
||||
|
||||
@@ -102,4 +105,5 @@ export type TAppConnectionMap = {
|
||||
[AppConnection.HCVault]: THCVaultConnection;
|
||||
[AppConnection.LDAP]: TLdapConnection;
|
||||
[AppConnection.TeamCity]: TTeamCityConnection;
|
||||
[AppConnection.OCI]: TOCIConnection;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { AppConnection } from "@app/hooks/api/appConnections/enums";
|
||||
import { TRootAppConnection } from "@app/hooks/api/appConnections/types/root-connection";
|
||||
|
||||
export enum OCIConnectionMethod {
|
||||
AccessKey = "access-key"
|
||||
}
|
||||
|
||||
export type TOCIConnection = TRootAppConnection & { app: AppConnection.OCI } & {
|
||||
method: OCIConnectionMethod.AccessKey;
|
||||
credentials: {
|
||||
userOcid: string;
|
||||
tenancyOcid: string;
|
||||
region: string;
|
||||
fingerprint: string;
|
||||
privateKey: string;
|
||||
};
|
||||
};
|
||||
@@ -72,6 +72,8 @@ export const eventToNameMap: { [K in EventType]: string } = {
|
||||
[EventType.DELETE_CERT]: "Delete certificate",
|
||||
[EventType.REVOKE_CERT]: "Revoke certificate",
|
||||
[EventType.GET_CERT_BODY]: "Get certificate body",
|
||||
[EventType.GET_CERT_PRIVATE_KEY]: "Get certificate private key",
|
||||
[EventType.GET_CERT_BUNDLE]: "Get certificate bundle",
|
||||
[EventType.CREATE_PKI_ALERT]: "Create PKI alert",
|
||||
[EventType.GET_PKI_ALERT]: "Get PKI alert",
|
||||
[EventType.UPDATE_PKI_ALERT]: "Update PKI alert",
|
||||
@@ -180,10 +182,17 @@ export const eventToNameMap: { [K in EventType]: string } = {
|
||||
"Microsoft Teams Workflow Integration Check Installation Status",
|
||||
[EventType.MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_GET_TEAMS]: "Get Microsoft Teams tenant teams",
|
||||
[EventType.MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_GET]: "Get Microsoft Teams Workflow Integration",
|
||||
[EventType.MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_LIST]: "List Microsoft Teams Workflow Integration"
|
||||
[EventType.MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_LIST]:
|
||||
"List Microsoft Teams Workflow Integration",
|
||||
|
||||
[EventType.LOGIN_IDENTITY_LDAP_AUTH]: "Identity login via LDAP Auth",
|
||||
[EventType.ADD_IDENTITY_LDAP_AUTH]: "Attached LDAP Auth to identity",
|
||||
[EventType.UPDATE_IDENTITY_LDAP_AUTH]: "Updated LDAP Auth for identity",
|
||||
[EventType.GET_IDENTITY_LDAP_AUTH]: "Retrieved LDAP Auth for identity",
|
||||
[EventType.REVOKE_IDENTITY_LDAP_AUTH]: "Revoked LDAP Auth for identity"
|
||||
};
|
||||
|
||||
export const userAgentTTypeoNameMap: { [K in UserAgentType]: string } = {
|
||||
export const userAgentTypeToNameMap: { [K in UserAgentType]: string } = {
|
||||
[UserAgentType.WEB]: "Web",
|
||||
[UserAgentType.CLI]: "CLI",
|
||||
[UserAgentType.K8_OPERATOR]: "K8s operator",
|
||||
|
||||
@@ -47,6 +47,13 @@ export enum EventType {
|
||||
CREATE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET = "create-identity-universal-auth-client-secret",
|
||||
REVOKE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET = "revoke-identity-universal-auth-client-secret",
|
||||
GET_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRETS = "get-identity-universal-auth-client-secret",
|
||||
|
||||
LOGIN_IDENTITY_LDAP_AUTH = "login-identity-ldap-auth",
|
||||
ADD_IDENTITY_LDAP_AUTH = "add-identity-ldap-auth",
|
||||
UPDATE_IDENTITY_LDAP_AUTH = "update-identity-ldap-auth",
|
||||
GET_IDENTITY_LDAP_AUTH = "get-identity-ldap-auth",
|
||||
REVOKE_IDENTITY_LDAP_AUTH = "revoke-identity-ldap-auth",
|
||||
|
||||
CREATE_ENVIRONMENT = "create-environment",
|
||||
UPDATE_ENVIRONMENT = "update-environment",
|
||||
DELETE_ENVIRONMENT = "delete-environment",
|
||||
@@ -78,6 +85,8 @@ export enum EventType {
|
||||
DELETE_CERT = "delete-cert",
|
||||
REVOKE_CERT = "revoke-cert",
|
||||
GET_CERT_BODY = "get-cert-body",
|
||||
GET_CERT_PRIVATE_KEY = "get-cert-private-key",
|
||||
GET_CERT_BUNDLE = "get-cert-bundle",
|
||||
CREATE_PKI_ALERT = "create-pki-alert",
|
||||
GET_PKI_ALERT = "get-pki-alert",
|
||||
UPDATE_PKI_ALERT = "update-pki-alert",
|
||||
|
||||
@@ -620,6 +620,24 @@ interface GetCertBody {
|
||||
};
|
||||
}
|
||||
|
||||
interface GetCertPrivateKey {
|
||||
type: EventType.GET_CERT_PRIVATE_KEY;
|
||||
metadata: {
|
||||
certId: string;
|
||||
cn: string;
|
||||
serialNumber: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface GetCertBundle {
|
||||
type: EventType.GET_CERT_BUNDLE;
|
||||
metadata: {
|
||||
certId: string;
|
||||
cn: string;
|
||||
serialNumber: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface CreatePkiAlert {
|
||||
type: EventType.CREATE_PKI_ALERT;
|
||||
metadata: {
|
||||
@@ -881,6 +899,8 @@ export type Event =
|
||||
| DeleteCert
|
||||
| RevokeCert
|
||||
| GetCertBody
|
||||
| GetCertPrivateKey
|
||||
| GetCertBundle
|
||||
| CreatePkiAlert
|
||||
| GetPkiAlert
|
||||
| UpdatePkiAlert
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
import { apiRequest } from "@app/config/request";
|
||||
|
||||
import { pkiSubscriberKeys } from "../pkiSubscriber/queries";
|
||||
import { workspaceKeys } from "../workspace";
|
||||
import { TCertificate, TDeleteCertDTO, TRevokeCertDTO } from "./types";
|
||||
|
||||
@@ -42,6 +43,9 @@ export const useRevokeCert = () => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: workspaceKeys.forWorkspaceCertificates(projectSlug)
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: pkiSubscriberKeys.allPkiSubscriberCertificates()
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -6,7 +6,8 @@ import { TCertificate } from "./types";
|
||||
|
||||
export const certKeys = {
|
||||
getCertById: (serialNumber: string) => [{ serialNumber }, "cert"],
|
||||
getCertBody: (serialNumber: string) => [{ serialNumber }, "certBody"]
|
||||
getCertBody: (serialNumber: string) => [{ serialNumber }, "certBody"],
|
||||
getCertBundle: (serialNumber: string) => [{ serialNumber }, "certBundle"]
|
||||
};
|
||||
|
||||
export const useGetCert = (serialNumber: string) => {
|
||||
@@ -38,3 +39,19 @@ export const useGetCertBody = (serialNumber: string) => {
|
||||
enabled: Boolean(serialNumber)
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetCertBundle = (serialNumber: string) => {
|
||||
return useQuery({
|
||||
queryKey: certKeys.getCertBundle(serialNumber),
|
||||
queryFn: async () => {
|
||||
const { data } = await apiRequest.get<{
|
||||
certificate: string;
|
||||
certificateChain: string;
|
||||
serialNumber: string;
|
||||
privateKey: string | null;
|
||||
}>(`/api/v1/pki/certificates/${serialNumber}/bundle`);
|
||||
return data;
|
||||
},
|
||||
enabled: Boolean(serialNumber)
|
||||
});
|
||||
};
|
||||
|
||||
@@ -20,8 +20,8 @@ export const useDeleteGatewayById = () => {
|
||||
export const useUpdateGatewayById = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: ({ id, name, projectIds }: TUpdateGatewayDTO) => {
|
||||
return apiRequest.patch(`/api/v1/gateways/${id}`, { name, projectIds });
|
||||
mutationFn: ({ id, name }: TUpdateGatewayDTO) => {
|
||||
return apiRequest.patch(`/api/v1/gateways/${id}`, { name });
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries(gatewaysQueryKeys.list());
|
||||
|
||||
@@ -2,7 +2,7 @@ import { queryOptions } from "@tanstack/react-query";
|
||||
|
||||
import { apiRequest } from "@app/config/request";
|
||||
|
||||
import { TGateway, TListProjectGatewayDTO, TProjectGateway } from "./types";
|
||||
import { TGateway } from "./types";
|
||||
|
||||
export const gatewaysQueryKeys = {
|
||||
allKey: () => ["gateways"],
|
||||
@@ -14,20 +14,5 @@ export const gatewaysQueryKeys = {
|
||||
const { data } = await apiRequest.get<{ gateways: TGateway[] }>("/api/v1/gateways");
|
||||
return data.gateways;
|
||||
}
|
||||
}),
|
||||
listProjectGatewayKey: ({ projectId }: TListProjectGatewayDTO) => [
|
||||
...gatewaysQueryKeys.allKey(),
|
||||
"list",
|
||||
{ projectId }
|
||||
],
|
||||
listProjectGateways: ({ projectId }: TListProjectGatewayDTO) =>
|
||||
queryOptions({
|
||||
queryKey: gatewaysQueryKeys.listProjectGatewayKey({ projectId }),
|
||||
queryFn: async () => {
|
||||
const { data } = await apiRequest.get<{ gateways: TProjectGateway[] }>(
|
||||
`/api/v1/gateways/projects/${projectId}`
|
||||
);
|
||||
return data.gateways;
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
@@ -11,39 +11,13 @@ export type TGateway = {
|
||||
name: string;
|
||||
id: string;
|
||||
};
|
||||
projects: {
|
||||
name: string;
|
||||
id: string;
|
||||
slug: string;
|
||||
}[];
|
||||
};
|
||||
|
||||
export type TProjectGateway = {
|
||||
id: string;
|
||||
identityId: string;
|
||||
name: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
issuedAt: string;
|
||||
serialNumber: string;
|
||||
heartbeat: string;
|
||||
projectGatewayId: string;
|
||||
identity: {
|
||||
name: string;
|
||||
id: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type TUpdateGatewayDTO = {
|
||||
id: string;
|
||||
name?: string;
|
||||
projectIds?: string[];
|
||||
};
|
||||
|
||||
export type TDeleteGatewayDTO = {
|
||||
id: string;
|
||||
};
|
||||
|
||||
export type TListProjectGatewayDTO = {
|
||||
projectId: string;
|
||||
};
|
||||
|
||||
@@ -7,6 +7,8 @@ export const identityAuthToNameMap: { [I in IdentityAuthMethod]: string } = {
|
||||
[IdentityAuthMethod.GCP_AUTH]: "GCP Auth",
|
||||
[IdentityAuthMethod.AWS_AUTH]: "AWS Auth",
|
||||
[IdentityAuthMethod.AZURE_AUTH]: "Azure Auth",
|
||||
[IdentityAuthMethod.OCI_AUTH]: "OCI Auth",
|
||||
[IdentityAuthMethod.OIDC_AUTH]: "OIDC Auth",
|
||||
[IdentityAuthMethod.LDAP_AUTH]: "LDAP Auth",
|
||||
[IdentityAuthMethod.JWT_AUTH]: "JWT Auth"
|
||||
};
|
||||
|
||||
@@ -5,7 +5,9 @@ export enum IdentityAuthMethod {
|
||||
GCP_AUTH = "gcp-auth",
|
||||
AWS_AUTH = "aws-auth",
|
||||
AZURE_AUTH = "azure-auth",
|
||||
OCI_AUTH = "oci-auth",
|
||||
OIDC_AUTH = "oidc-auth",
|
||||
LDAP_AUTH = "ldap-auth",
|
||||
JWT_AUTH = "jwt-auth"
|
||||
}
|
||||
|
||||
|
||||
@@ -1,51 +1,4 @@
|
||||
export { identityAuthToNameMap } from "./constants";
|
||||
export { IdentityAuthMethod } from "./enums";
|
||||
export {
|
||||
useAddIdentityAwsAuth,
|
||||
useAddIdentityAzureAuth,
|
||||
useAddIdentityGcpAuth,
|
||||
useAddIdentityJwtAuth,
|
||||
useAddIdentityKubernetesAuth,
|
||||
useAddIdentityOidcAuth,
|
||||
useAddIdentityTokenAuth,
|
||||
useAddIdentityUniversalAuth,
|
||||
useCreateIdentity,
|
||||
useCreateIdentityUniversalAuthClientSecret,
|
||||
useCreateTokenIdentityTokenAuth,
|
||||
useDeleteIdentity,
|
||||
useDeleteIdentityAwsAuth,
|
||||
useDeleteIdentityAzureAuth,
|
||||
useDeleteIdentityGcpAuth,
|
||||
useDeleteIdentityJwtAuth,
|
||||
useDeleteIdentityKubernetesAuth,
|
||||
useDeleteIdentityOidcAuth,
|
||||
useDeleteIdentityTokenAuth,
|
||||
useDeleteIdentityUniversalAuth,
|
||||
useRevokeIdentityTokenAuthToken,
|
||||
useRevokeIdentityUniversalAuthClientSecret,
|
||||
useUpdateIdentity,
|
||||
useUpdateIdentityAwsAuth,
|
||||
useUpdateIdentityAzureAuth,
|
||||
useUpdateIdentityGcpAuth,
|
||||
useUpdateIdentityJwtAuth,
|
||||
useUpdateIdentityKubernetesAuth,
|
||||
useUpdateIdentityOidcAuth,
|
||||
useUpdateIdentityTokenAuth,
|
||||
useUpdateIdentityTokenAuthToken,
|
||||
useUpdateIdentityUniversalAuth
|
||||
} from "./mutations";
|
||||
export {
|
||||
useGetIdentityAwsAuth,
|
||||
useGetIdentityAzureAuth,
|
||||
useGetIdentityById,
|
||||
useGetIdentityGcpAuth,
|
||||
useGetIdentityJwtAuth,
|
||||
useGetIdentityKubernetesAuth,
|
||||
useGetIdentityOidcAuth,
|
||||
useGetIdentityProjectMemberships,
|
||||
useGetIdentityTokenAuth,
|
||||
useGetIdentityTokensTokenAuth,
|
||||
useGetIdentityUniversalAuth,
|
||||
useGetIdentityUniversalAuthClientSecrets,
|
||||
useSearchIdentities
|
||||
} from "./queries";
|
||||
export * from "./mutations";
|
||||
export * from "./queries";
|
||||
|
||||
@@ -10,6 +10,8 @@ import {
|
||||
AddIdentityGcpAuthDTO,
|
||||
AddIdentityJwtAuthDTO,
|
||||
AddIdentityKubernetesAuthDTO,
|
||||
AddIdentityLdapAuthDTO,
|
||||
AddIdentityOciAuthDTO,
|
||||
AddIdentityOidcAuthDTO,
|
||||
AddIdentityTokenAuthDTO,
|
||||
AddIdentityUniversalAuthDTO,
|
||||
@@ -25,6 +27,8 @@ import {
|
||||
DeleteIdentityGcpAuthDTO,
|
||||
DeleteIdentityJwtAuthDTO,
|
||||
DeleteIdentityKubernetesAuthDTO,
|
||||
DeleteIdentityLdapAuthDTO,
|
||||
DeleteIdentityOciAuthDTO,
|
||||
DeleteIdentityOidcAuthDTO,
|
||||
DeleteIdentityTokenAuthDTO,
|
||||
DeleteIdentityUniversalAuthClientSecretDTO,
|
||||
@@ -36,6 +40,8 @@ import {
|
||||
IdentityGcpAuth,
|
||||
IdentityJwtAuth,
|
||||
IdentityKubernetesAuth,
|
||||
IdentityLdapAuth,
|
||||
IdentityOciAuth,
|
||||
IdentityOidcAuth,
|
||||
IdentityTokenAuth,
|
||||
IdentityUniversalAuth,
|
||||
@@ -47,6 +53,8 @@ import {
|
||||
UpdateIdentityGcpAuthDTO,
|
||||
UpdateIdentityJwtAuthDTO,
|
||||
UpdateIdentityKubernetesAuthDTO,
|
||||
UpdateIdentityLdapAuthDTO,
|
||||
UpdateIdentityOciAuthDTO,
|
||||
UpdateIdentityOidcAuthDTO,
|
||||
UpdateIdentityTokenAuthDTO,
|
||||
UpdateIdentityUniversalAuthDTO,
|
||||
@@ -448,6 +456,101 @@ export const useDeleteIdentityAwsAuth = () => {
|
||||
});
|
||||
};
|
||||
|
||||
export const useAddIdentityOciAuth = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation<IdentityOciAuth, object, AddIdentityOciAuthDTO>({
|
||||
mutationFn: async ({
|
||||
identityId,
|
||||
tenancyOcid,
|
||||
allowedUsernames,
|
||||
accessTokenTTL,
|
||||
accessTokenMaxTTL,
|
||||
accessTokenNumUsesLimit,
|
||||
accessTokenTrustedIps
|
||||
}) => {
|
||||
const {
|
||||
data: { identityOciAuth }
|
||||
} = await apiRequest.post<{ identityOciAuth: IdentityOciAuth }>(
|
||||
`/api/v1/auth/oci-auth/identities/${identityId}`,
|
||||
{
|
||||
tenancyOcid,
|
||||
allowedUsernames,
|
||||
accessTokenTTL,
|
||||
accessTokenMaxTTL,
|
||||
accessTokenNumUsesLimit,
|
||||
accessTokenTrustedIps
|
||||
}
|
||||
);
|
||||
|
||||
return identityOciAuth;
|
||||
},
|
||||
onSuccess: (_, { identityId, organizationId }) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
|
||||
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityOciAuth(identityId) });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const useUpdateIdentityOciAuth = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation<IdentityOciAuth, object, UpdateIdentityOciAuthDTO>({
|
||||
mutationFn: async ({
|
||||
identityId,
|
||||
tenancyOcid,
|
||||
allowedUsernames,
|
||||
accessTokenTTL,
|
||||
accessTokenMaxTTL,
|
||||
accessTokenNumUsesLimit,
|
||||
accessTokenTrustedIps
|
||||
}) => {
|
||||
const {
|
||||
data: { identityOciAuth }
|
||||
} = await apiRequest.patch<{ identityOciAuth: IdentityOciAuth }>(
|
||||
`/api/v1/auth/oci-auth/identities/${identityId}`,
|
||||
{
|
||||
tenancyOcid,
|
||||
allowedUsernames,
|
||||
accessTokenTTL,
|
||||
accessTokenMaxTTL,
|
||||
accessTokenNumUsesLimit,
|
||||
accessTokenTrustedIps
|
||||
}
|
||||
);
|
||||
|
||||
return identityOciAuth;
|
||||
},
|
||||
onSuccess: (_, { identityId, organizationId }) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
|
||||
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityOciAuth(identityId) });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const useDeleteIdentityOciAuth = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation<IdentityOciAuth, object, DeleteIdentityOciAuthDTO>({
|
||||
mutationFn: async ({ identityId }) => {
|
||||
const {
|
||||
data: { identityOciAuth }
|
||||
} = await apiRequest.delete(`/api/v1/auth/oci-auth/identities/${identityId}`);
|
||||
return identityOciAuth;
|
||||
},
|
||||
onSuccess: (_, { organizationId, identityId }) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
|
||||
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityOciAuth(identityId) });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const useUpdateIdentityOidcAuth = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation<IdentityOidcAuth, object, UpdateIdentityOidcAuthDTO>({
|
||||
@@ -737,7 +840,8 @@ export const useAddIdentityKubernetesAuth = () => {
|
||||
accessTokenTTL,
|
||||
accessTokenMaxTTL,
|
||||
accessTokenNumUsesLimit,
|
||||
accessTokenTrustedIps
|
||||
accessTokenTrustedIps,
|
||||
gatewayId
|
||||
}) => {
|
||||
const {
|
||||
data: { identityKubernetesAuth }
|
||||
@@ -753,7 +857,8 @@ export const useAddIdentityKubernetesAuth = () => {
|
||||
accessTokenTTL,
|
||||
accessTokenMaxTTL,
|
||||
accessTokenNumUsesLimit,
|
||||
accessTokenTrustedIps
|
||||
accessTokenTrustedIps,
|
||||
gatewayId
|
||||
}
|
||||
);
|
||||
|
||||
@@ -842,7 +947,8 @@ export const useUpdateIdentityKubernetesAuth = () => {
|
||||
accessTokenTTL,
|
||||
accessTokenMaxTTL,
|
||||
accessTokenNumUsesLimit,
|
||||
accessTokenTrustedIps
|
||||
accessTokenTrustedIps,
|
||||
gatewayId
|
||||
}) => {
|
||||
const {
|
||||
data: { identityKubernetesAuth }
|
||||
@@ -858,7 +964,8 @@ export const useUpdateIdentityKubernetesAuth = () => {
|
||||
accessTokenTTL,
|
||||
accessTokenMaxTTL,
|
||||
accessTokenNumUsesLimit,
|
||||
accessTokenTrustedIps
|
||||
accessTokenTrustedIps,
|
||||
gatewayId
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1049,3 +1156,116 @@ export const useRevokeIdentityTokenAuthToken = () => {
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const useAddIdentityLdapAuth = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation<IdentityLdapAuth, object, AddIdentityLdapAuthDTO>({
|
||||
mutationFn: async ({
|
||||
identityId,
|
||||
url,
|
||||
bindDN,
|
||||
bindPass,
|
||||
searchBase,
|
||||
searchFilter,
|
||||
ldapCaCertificate,
|
||||
allowedFields,
|
||||
accessTokenTTL,
|
||||
accessTokenMaxTTL,
|
||||
accessTokenNumUsesLimit,
|
||||
accessTokenTrustedIps
|
||||
}) => {
|
||||
const { data } = await apiRequest.post<{ identityLdapAuth: IdentityLdapAuth }>(
|
||||
`/api/v1/auth/ldap-auth/identities/${identityId}`,
|
||||
{
|
||||
url,
|
||||
bindDN,
|
||||
bindPass,
|
||||
searchBase,
|
||||
searchFilter,
|
||||
ldapCaCertificate,
|
||||
allowedFields,
|
||||
accessTokenTTL,
|
||||
accessTokenMaxTTL,
|
||||
accessTokenNumUsesLimit,
|
||||
accessTokenTrustedIps
|
||||
}
|
||||
);
|
||||
return data.identityLdapAuth;
|
||||
},
|
||||
onSuccess: (_, { identityId, organizationId }) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: identitiesKeys.getIdentityLdapAuth(identityId)
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const useUpdateIdentityLdapAuth = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation<IdentityLdapAuth, object, UpdateIdentityLdapAuthDTO>({
|
||||
mutationFn: async ({
|
||||
identityId,
|
||||
url,
|
||||
bindDN,
|
||||
bindPass,
|
||||
searchBase,
|
||||
searchFilter,
|
||||
ldapCaCertificate,
|
||||
allowedFields,
|
||||
accessTokenTTL,
|
||||
accessTokenMaxTTL,
|
||||
accessTokenNumUsesLimit,
|
||||
accessTokenTrustedIps
|
||||
}) => {
|
||||
const { data } = await apiRequest.patch<{ identityLdapAuth: IdentityLdapAuth }>(
|
||||
`/api/v1/auth/ldap-auth/identities/${identityId}`,
|
||||
{
|
||||
url,
|
||||
bindDN,
|
||||
bindPass,
|
||||
searchBase,
|
||||
searchFilter,
|
||||
ldapCaCertificate,
|
||||
allowedFields,
|
||||
accessTokenTTL,
|
||||
accessTokenMaxTTL,
|
||||
accessTokenNumUsesLimit,
|
||||
accessTokenTrustedIps
|
||||
}
|
||||
);
|
||||
return data.identityLdapAuth;
|
||||
},
|
||||
onSuccess: (_, { identityId, organizationId }) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: identitiesKeys.getIdentityLdapAuth(identityId)
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const useDeleteIdentityLdapAuth = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation<IdentityLdapAuth, object, DeleteIdentityLdapAuthDTO>({
|
||||
mutationFn: async ({ identityId }) => {
|
||||
const { data } = await apiRequest.delete(`/api/v1/auth/ldap-auth/identities/${identityId}`);
|
||||
return data.identityLdapAuth;
|
||||
},
|
||||
onSuccess: (_, { organizationId, identityId }) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: identitiesKeys.getIdentityLdapAuth(identityId)
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -11,8 +11,10 @@ import {
|
||||
IdentityGcpAuth,
|
||||
IdentityJwtAuth,
|
||||
IdentityKubernetesAuth,
|
||||
IdentityLdapAuth,
|
||||
IdentityMembership,
|
||||
IdentityMembershipOrg,
|
||||
IdentityOciAuth,
|
||||
IdentityOidcAuth,
|
||||
IdentityTokenAuth,
|
||||
IdentityUniversalAuth,
|
||||
@@ -31,9 +33,11 @@ export const identitiesKeys = {
|
||||
getIdentityGcpAuth: (identityId: string) => [{ identityId }, "identity-gcp-auth"] as const,
|
||||
getIdentityOidcAuth: (identityId: string) => [{ identityId }, "identity-oidc-auth"] as const,
|
||||
getIdentityAwsAuth: (identityId: string) => [{ identityId }, "identity-aws-auth"] as const,
|
||||
getIdentityOciAuth: (identityId: string) => [{ identityId }, "identity-oci-auth"] as const,
|
||||
getIdentityAzureAuth: (identityId: string) => [{ identityId }, "identity-azure-auth"] as const,
|
||||
getIdentityTokenAuth: (identityId: string) => [{ identityId }, "identity-token-auth"] as const,
|
||||
getIdentityJwtAuth: (identityId: string) => [{ identityId }, "identity-jwt-auth"] as const,
|
||||
getIdentityLdapAuth: (identityId: string) => [{ identityId }, "identity-ldap-auth"] as const,
|
||||
getIdentityTokensTokenAuth: (identityId: string) =>
|
||||
[{ identityId }, "identity-tokens-token-auth"] as const,
|
||||
getIdentityProjectMemberships: (identityId: string) =>
|
||||
@@ -168,6 +172,27 @@ export const useGetIdentityAwsAuth = (
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetIdentityOciAuth = (
|
||||
identityId: string,
|
||||
options?: TReactQueryOptions["options"]
|
||||
) => {
|
||||
return useQuery({
|
||||
queryKey: identitiesKeys.getIdentityOciAuth(identityId),
|
||||
queryFn: async () => {
|
||||
const {
|
||||
data: { identityOciAuth }
|
||||
} = await apiRequest.get<{ identityOciAuth: IdentityOciAuth }>(
|
||||
`/api/v1/auth/oci-auth/identities/${identityId}`
|
||||
);
|
||||
return identityOciAuth;
|
||||
},
|
||||
staleTime: 0,
|
||||
gcTime: 0,
|
||||
...options,
|
||||
enabled: Boolean(identityId) && (options?.enabled ?? true)
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetIdentityAzureAuth = (
|
||||
identityId: string,
|
||||
options?: TReactQueryOptions["options"]
|
||||
@@ -231,6 +256,27 @@ export const useGetIdentityTokenAuth = (
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetIdentityLdapAuth = (
|
||||
identityId: string,
|
||||
options?: TReactQueryOptions["options"]
|
||||
) => {
|
||||
return useQuery({
|
||||
queryKey: identitiesKeys.getIdentityLdapAuth(identityId),
|
||||
queryFn: async () => {
|
||||
const {
|
||||
data: { identityLdapAuth }
|
||||
} = await apiRequest.get<{ identityLdapAuth: IdentityLdapAuth }>(
|
||||
`/api/v1/auth/ldap-auth/identities/${identityId}`
|
||||
);
|
||||
return identityLdapAuth;
|
||||
},
|
||||
staleTime: 0,
|
||||
gcTime: 0,
|
||||
...options,
|
||||
enabled: Boolean(identityId) && (options?.enabled ?? true)
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetIdentityTokensTokenAuth = (identityId: string) => {
|
||||
return useQuery({
|
||||
enabled: Boolean(identityId),
|
||||
|
||||
@@ -290,6 +290,48 @@ export type DeleteIdentityAwsAuthDTO = {
|
||||
identityId: string;
|
||||
};
|
||||
|
||||
export type IdentityOciAuth = {
|
||||
identityId: string;
|
||||
type: "iam";
|
||||
tenancyOcid: string;
|
||||
allowedUsernames?: string | null;
|
||||
accessTokenTTL: number;
|
||||
accessTokenMaxTTL: number;
|
||||
accessTokenNumUsesLimit: number;
|
||||
accessTokenTrustedIps: IdentityTrustedIp[];
|
||||
};
|
||||
|
||||
export type AddIdentityOciAuthDTO = {
|
||||
organizationId: string;
|
||||
identityId: string;
|
||||
tenancyOcid: string;
|
||||
allowedUsernames?: string | null;
|
||||
accessTokenTTL: number;
|
||||
accessTokenMaxTTL: number;
|
||||
accessTokenNumUsesLimit: number;
|
||||
accessTokenTrustedIps: {
|
||||
ipAddress: string;
|
||||
}[];
|
||||
};
|
||||
|
||||
export type UpdateIdentityOciAuthDTO = {
|
||||
organizationId: string;
|
||||
identityId: string;
|
||||
tenancyOcid?: string;
|
||||
allowedUsernames?: string | null;
|
||||
accessTokenTTL?: number;
|
||||
accessTokenMaxTTL?: number;
|
||||
accessTokenNumUsesLimit?: number;
|
||||
accessTokenTrustedIps?: {
|
||||
ipAddress: string;
|
||||
}[];
|
||||
};
|
||||
|
||||
export type DeleteIdentityOciAuthDTO = {
|
||||
organizationId: string;
|
||||
identityId: string;
|
||||
};
|
||||
|
||||
export type IdentityAzureAuth = {
|
||||
identityId: string;
|
||||
tenantId: string;
|
||||
@@ -346,6 +388,7 @@ export type IdentityKubernetesAuth = {
|
||||
accessTokenMaxTTL: number;
|
||||
accessTokenNumUsesLimit: number;
|
||||
accessTokenTrustedIps: IdentityTrustedIp[];
|
||||
gatewayId?: string | null;
|
||||
};
|
||||
|
||||
export type AddIdentityKubernetesAuthDTO = {
|
||||
@@ -356,6 +399,7 @@ export type AddIdentityKubernetesAuthDTO = {
|
||||
allowedNamespaces: string;
|
||||
allowedNames: string;
|
||||
allowedAudience: string;
|
||||
gatewayId?: string | null;
|
||||
caCert: string;
|
||||
accessTokenTTL: number;
|
||||
accessTokenMaxTTL: number;
|
||||
@@ -373,6 +417,7 @@ export type UpdateIdentityKubernetesAuthDTO = {
|
||||
allowedNamespaces?: string;
|
||||
allowedNames?: string;
|
||||
allowedAudience?: string;
|
||||
gatewayId?: string | null;
|
||||
caCert?: string;
|
||||
accessTokenTTL?: number;
|
||||
accessTokenMaxTTL?: number;
|
||||
@@ -425,6 +470,72 @@ export type IdentityTokenAuth = {
|
||||
accessTokenTrustedIps: IdentityTrustedIp[];
|
||||
};
|
||||
|
||||
export type AddIdentityLdapAuthDTO = {
|
||||
organizationId: string;
|
||||
identityId: string;
|
||||
url: string;
|
||||
bindDN: string;
|
||||
bindPass: string;
|
||||
searchBase: string;
|
||||
searchFilter: string;
|
||||
ldapCaCertificate?: string;
|
||||
allowedFields?: {
|
||||
key: string;
|
||||
value: string;
|
||||
}[];
|
||||
accessTokenTTL: number;
|
||||
accessTokenMaxTTL: number;
|
||||
accessTokenNumUsesLimit: number;
|
||||
accessTokenTrustedIps: {
|
||||
ipAddress: string;
|
||||
}[];
|
||||
};
|
||||
|
||||
export type UpdateIdentityLdapAuthDTO = {
|
||||
identityId: string;
|
||||
organizationId: string;
|
||||
url?: string;
|
||||
bindDN?: string;
|
||||
bindPass?: string;
|
||||
searchBase?: string;
|
||||
searchFilter?: string;
|
||||
ldapCaCertificate?: string;
|
||||
allowedFields?: {
|
||||
key: string;
|
||||
value: string;
|
||||
}[];
|
||||
accessTokenTTL?: number;
|
||||
accessTokenMaxTTL?: number;
|
||||
accessTokenNumUsesLimit?: number;
|
||||
accessTokenTrustedIps?: {
|
||||
ipAddress: string;
|
||||
}[];
|
||||
};
|
||||
|
||||
export type DeleteIdentityLdapAuthDTO = {
|
||||
organizationId: string;
|
||||
identityId: string;
|
||||
};
|
||||
|
||||
export type IdentityLdapAuth = {
|
||||
url: string;
|
||||
bindDN: string;
|
||||
bindPass: string;
|
||||
searchBase: string;
|
||||
searchFilter: string;
|
||||
ldapCaCertificate?: string;
|
||||
allowedFields?: {
|
||||
key: string;
|
||||
value: string;
|
||||
}[];
|
||||
|
||||
identityId: string;
|
||||
accessTokenTTL: number;
|
||||
accessTokenMaxTTL: number;
|
||||
accessTokenNumUsesLimit: number;
|
||||
accessTokenTrustedIps: IdentityTrustedIp[];
|
||||
};
|
||||
|
||||
export type AddIdentityTokenAuthDTO = {
|
||||
organizationId: string;
|
||||
identityId: string;
|
||||
|
||||
@@ -27,6 +27,7 @@ export * from "./orgAdmin";
|
||||
export * from "./organization";
|
||||
export * from "./pkiAlerts";
|
||||
export * from "./pkiCollections";
|
||||
export * from "./pkiSubscriber";
|
||||
export * from "./projectUserAdditionalPrivilege";
|
||||
export * from "./rateLimit";
|
||||
export * from "./roles";
|
||||
|
||||
@@ -112,7 +112,15 @@ export const useUpdateOrg = () => {
|
||||
selectedMfaMethod,
|
||||
allowSecretSharingOutsideOrganization,
|
||||
bypassOrgAuthEnabled,
|
||||
userTokenExpiration
|
||||
userTokenExpiration,
|
||||
secretsProductEnabled,
|
||||
pkiProductEnabled,
|
||||
kmsProductEnabled,
|
||||
sshProductEnabled,
|
||||
scannerProductEnabled,
|
||||
shareSecretsProductEnabled,
|
||||
maxSharedSecretLifetime,
|
||||
maxSharedSecretViewLimit
|
||||
}) => {
|
||||
return apiRequest.patch(`/api/v1/organization/${orgId}`, {
|
||||
name,
|
||||
@@ -124,7 +132,15 @@ export const useUpdateOrg = () => {
|
||||
selectedMfaMethod,
|
||||
allowSecretSharingOutsideOrganization,
|
||||
bypassOrgAuthEnabled,
|
||||
userTokenExpiration
|
||||
userTokenExpiration,
|
||||
secretsProductEnabled,
|
||||
pkiProductEnabled,
|
||||
kmsProductEnabled,
|
||||
sshProductEnabled,
|
||||
scannerProductEnabled,
|
||||
shareSecretsProductEnabled,
|
||||
maxSharedSecretLifetime,
|
||||
maxSharedSecretViewLimit
|
||||
});
|
||||
},
|
||||
onSuccess: () => {
|
||||
|
||||
@@ -20,6 +20,14 @@ export type Organization = {
|
||||
allowSecretSharingOutsideOrganization?: boolean;
|
||||
userTokenExpiration?: string;
|
||||
userRole: string;
|
||||
secretsProductEnabled: boolean;
|
||||
pkiProductEnabled: boolean;
|
||||
kmsProductEnabled: boolean;
|
||||
sshProductEnabled: boolean;
|
||||
scannerProductEnabled: boolean;
|
||||
shareSecretsProductEnabled: boolean;
|
||||
maxSharedSecretLifetime: number;
|
||||
maxSharedSecretViewLimit: number | null;
|
||||
};
|
||||
|
||||
export type UpdateOrgDTO = {
|
||||
@@ -34,6 +42,14 @@ export type UpdateOrgDTO = {
|
||||
allowSecretSharingOutsideOrganization?: boolean;
|
||||
bypassOrgAuthEnabled?: boolean;
|
||||
userTokenExpiration?: string;
|
||||
secretsProductEnabled?: boolean;
|
||||
pkiProductEnabled?: boolean;
|
||||
kmsProductEnabled?: boolean;
|
||||
sshProductEnabled?: boolean;
|
||||
scannerProductEnabled?: boolean;
|
||||
shareSecretsProductEnabled?: boolean;
|
||||
maxSharedSecretViewLimit?: number | null;
|
||||
maxSharedSecretLifetime?: number;
|
||||
};
|
||||
|
||||
export type BillingDetails = {
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
export enum PkiSubscriberStatus {
|
||||
ACTIVE = "active",
|
||||
DISABLED = "disabled"
|
||||
}
|
||||
|
||||
export const pkiSubscriberStatusToNameMap: { [K in PkiSubscriberStatus]: string } = {
|
||||
[PkiSubscriberStatus.ACTIVE]: "Active",
|
||||
[PkiSubscriberStatus.DISABLED]: "Disabled"
|
||||
};
|
||||
|
||||
export const getPkiSubscriberStatusBadgeVariant = (status: PkiSubscriberStatus) => {
|
||||
switch (status) {
|
||||
case PkiSubscriberStatus.ACTIVE:
|
||||
return "success";
|
||||
case PkiSubscriberStatus.DISABLED:
|
||||
return "danger";
|
||||
default:
|
||||
return "primary";
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
export {
|
||||
useCreatePkiSubscriber,
|
||||
useDeletePkiSubscriber,
|
||||
useIssuePkiSubscriberCert,
|
||||
useUpdatePkiSubscriber
|
||||
} from "./mutations";
|
||||
export { useGetPkiSubscriber, useGetPkiSubscriberCertificates } from "./queries";
|
||||
@@ -0,0 +1,110 @@
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
import { apiRequest } from "@app/config/request";
|
||||
|
||||
import { TCreateCertificateResponse } from "../ca/types";
|
||||
import { workspaceKeys } from "../workspace/query-keys";
|
||||
import { pkiSubscriberKeys } from "./queries";
|
||||
import {
|
||||
TCreatePkiSubscriberDTO,
|
||||
TDeletePkiSubscriberDTO,
|
||||
TIssuePkiSubscriberCertDTO,
|
||||
TPkiSubscriber,
|
||||
TUpdatePkiSubscriberDTO
|
||||
} from "./types";
|
||||
|
||||
export const useCreatePkiSubscriber = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation<TPkiSubscriber, object, TCreatePkiSubscriberDTO>({
|
||||
mutationFn: async (body) => {
|
||||
const { data: subscriber } = await apiRequest.post("/api/v1/pki/subscribers", body);
|
||||
return subscriber;
|
||||
},
|
||||
onSuccess: ({ projectId, name }) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: workspaceKeys.getWorkspacePkiSubscribers(projectId)
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: pkiSubscriberKeys.getPkiSubscriber({
|
||||
subscriberName: name,
|
||||
projectId
|
||||
})
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const useUpdatePkiSubscriber = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation<TPkiSubscriber, object, TUpdatePkiSubscriberDTO>({
|
||||
mutationFn: async ({ subscriberName, ...body }) => {
|
||||
const { data: subscriber } = await apiRequest.patch(
|
||||
`/api/v1/pki/subscribers/${subscriberName}`,
|
||||
body
|
||||
);
|
||||
return subscriber;
|
||||
},
|
||||
onSuccess: ({ projectId, name }) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: workspaceKeys.getWorkspacePkiSubscribers(projectId)
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: pkiSubscriberKeys.getPkiSubscriber({
|
||||
subscriberName: name,
|
||||
projectId
|
||||
})
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const useDeletePkiSubscriber = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation<TPkiSubscriber, object, TDeletePkiSubscriberDTO>({
|
||||
mutationFn: async ({ subscriberName, projectId }) => {
|
||||
const { data: subscriber } = await apiRequest.delete(
|
||||
`/api/v1/pki/subscribers/${subscriberName}`,
|
||||
{
|
||||
data: {
|
||||
projectId
|
||||
}
|
||||
}
|
||||
);
|
||||
return subscriber;
|
||||
},
|
||||
onSuccess: ({ name, projectId }) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: workspaceKeys.getWorkspacePkiSubscribers(projectId)
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: pkiSubscriberKeys.getPkiSubscriber({
|
||||
subscriberName: name,
|
||||
projectId
|
||||
})
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const useIssuePkiSubscriberCert = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation<TCreateCertificateResponse, object, TIssuePkiSubscriberCertDTO>({
|
||||
mutationFn: async ({ subscriberName, projectId }) => {
|
||||
const { data } = await apiRequest.post(
|
||||
`/api/v1/pki/subscribers/${subscriberName}/issue-certificate`,
|
||||
{
|
||||
projectId
|
||||
}
|
||||
);
|
||||
return data;
|
||||
},
|
||||
onSuccess: (_, { subscriberName, projectId }) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: pkiSubscriberKeys.forPkiSubscriberCertificates({
|
||||
subscriberName,
|
||||
projectId
|
||||
})
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,102 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
|
||||
import { apiRequest } from "@app/config/request";
|
||||
|
||||
import { TCertificate } from "../certificates/types";
|
||||
import { TPkiSubscriber } from "./types";
|
||||
|
||||
export const pkiSubscriberKeys = {
|
||||
getPkiSubscriber: ({
|
||||
subscriberName,
|
||||
projectId
|
||||
}: {
|
||||
subscriberName: string;
|
||||
projectId: string;
|
||||
}) => [{ subscriberName, projectId }, "pki-subscriber"] as const,
|
||||
allPkiSubscriberCertificates: () => ["pki-subscriber-certificates"] as const,
|
||||
forPkiSubscriberCertificates: ({
|
||||
subscriberName,
|
||||
projectId
|
||||
}: {
|
||||
subscriberName: string;
|
||||
projectId: string;
|
||||
}) => [...pkiSubscriberKeys.allPkiSubscriberCertificates(), subscriberName, projectId] as const,
|
||||
specificPkiSubscriberCertificates: ({
|
||||
subscriberName,
|
||||
projectId,
|
||||
offset,
|
||||
limit
|
||||
}: {
|
||||
subscriberName: string;
|
||||
projectId: string;
|
||||
offset: number;
|
||||
limit: number;
|
||||
}) =>
|
||||
[
|
||||
...pkiSubscriberKeys.forPkiSubscriberCertificates({ subscriberName, projectId }),
|
||||
{ offset, limit, projectId }
|
||||
] as const
|
||||
};
|
||||
|
||||
export const useGetPkiSubscriber = ({
|
||||
subscriberName,
|
||||
projectId
|
||||
}: {
|
||||
subscriberName: string;
|
||||
projectId: string;
|
||||
}) => {
|
||||
return useQuery({
|
||||
queryKey: pkiSubscriberKeys.getPkiSubscriber({ subscriberName, projectId }),
|
||||
queryFn: async () => {
|
||||
const { data: pkiSubscriber } = await apiRequest.get<TPkiSubscriber>(
|
||||
`/api/v1/pki/subscribers/${subscriberName}`,
|
||||
{
|
||||
params: {
|
||||
projectId
|
||||
}
|
||||
}
|
||||
);
|
||||
return pkiSubscriber;
|
||||
},
|
||||
enabled: Boolean(subscriberName) && Boolean(projectId)
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetPkiSubscriberCertificates = ({
|
||||
subscriberName,
|
||||
projectId,
|
||||
offset,
|
||||
limit
|
||||
}: {
|
||||
subscriberName: string;
|
||||
projectId: string;
|
||||
offset: number;
|
||||
limit: number;
|
||||
}) => {
|
||||
return useQuery({
|
||||
queryKey: pkiSubscriberKeys.specificPkiSubscriberCertificates({
|
||||
subscriberName,
|
||||
projectId,
|
||||
offset,
|
||||
limit
|
||||
}),
|
||||
queryFn: async () => {
|
||||
const params = new URLSearchParams({
|
||||
offset: String(offset),
|
||||
limit: String(limit),
|
||||
projectId
|
||||
});
|
||||
|
||||
const {
|
||||
data: { certificates, totalCount }
|
||||
} = await apiRequest.get<{ certificates: TCertificate[]; totalCount: number }>(
|
||||
`/api/v1/pki/subscribers/${subscriberName}/certificates`,
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
return { certificates, totalCount };
|
||||
},
|
||||
enabled: Boolean(subscriberName) && Boolean(projectId)
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,53 @@
|
||||
import { CertExtendedKeyUsage, CertKeyUsage } from "../certificates/enums";
|
||||
|
||||
export enum PkiSubscriberStatus {
|
||||
ACTIVE = "active",
|
||||
DISABLED = "disabled"
|
||||
}
|
||||
|
||||
export type TPkiSubscriber = {
|
||||
id: string;
|
||||
projectId: string;
|
||||
caId: string;
|
||||
name: string;
|
||||
commonName: string;
|
||||
status: PkiSubscriberStatus;
|
||||
ttl: string;
|
||||
subjectAlternativeNames: string[];
|
||||
keyUsages: CertKeyUsage[];
|
||||
extendedKeyUsages: CertExtendedKeyUsage[];
|
||||
};
|
||||
|
||||
export type TCreatePkiSubscriberDTO = {
|
||||
projectId: string;
|
||||
caId: string;
|
||||
name: string;
|
||||
commonName: string;
|
||||
ttl: string;
|
||||
subjectAlternativeNames: string[];
|
||||
keyUsages: CertKeyUsage[];
|
||||
extendedKeyUsages: CertExtendedKeyUsage[];
|
||||
};
|
||||
|
||||
export type TUpdatePkiSubscriberDTO = {
|
||||
subscriberName: string;
|
||||
projectId: string;
|
||||
caId?: string;
|
||||
name?: string;
|
||||
commonName?: string;
|
||||
status?: PkiSubscriberStatus;
|
||||
ttl?: string;
|
||||
subjectAlternativeNames?: string[];
|
||||
keyUsages?: CertKeyUsage[];
|
||||
extendedKeyUsages?: CertExtendedKeyUsage[];
|
||||
};
|
||||
|
||||
export type TDeletePkiSubscriberDTO = {
|
||||
subscriberName: string;
|
||||
projectId: string;
|
||||
};
|
||||
|
||||
export type TIssuePkiSubscriberCertDTO = {
|
||||
subscriberName: string;
|
||||
projectId: string;
|
||||
};
|
||||
@@ -6,14 +6,17 @@ import {
|
||||
TProjectTemplate,
|
||||
TProjectTemplateResponse
|
||||
} from "@app/hooks/api/projectTemplates/types";
|
||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||
|
||||
export const projectTemplateKeys = {
|
||||
all: ["project-template"] as const,
|
||||
list: () => [...projectTemplateKeys.all, "list"] as const,
|
||||
list: (projectType?: ProjectType) =>
|
||||
[...projectTemplateKeys.all, "list", ...(projectType ? [projectType] : [])] as const,
|
||||
byId: (templateId: string) => [...projectTemplateKeys.all, templateId] as const
|
||||
};
|
||||
|
||||
export const useListProjectTemplates = (
|
||||
type?: ProjectType,
|
||||
options?: Omit<
|
||||
UseQueryOptions<
|
||||
TProjectTemplate[],
|
||||
@@ -25,9 +28,11 @@ export const useListProjectTemplates = (
|
||||
>
|
||||
) => {
|
||||
return useQuery({
|
||||
queryKey: projectTemplateKeys.list(),
|
||||
queryKey: projectTemplateKeys.list(type),
|
||||
queryFn: async () => {
|
||||
const { data } = await apiRequest.get<TListProjectTemplates>("/api/v1/project-templates");
|
||||
const { data } = await apiRequest.get<TListProjectTemplates>("/api/v1/project-templates", {
|
||||
params: { type }
|
||||
});
|
||||
|
||||
return data.projectTemplates;
|
||||
},
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import { TProjectRole } from "@app/hooks/api/roles/types";
|
||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||
|
||||
export type TProjectTemplate = {
|
||||
id: string;
|
||||
name: string;
|
||||
type: ProjectType;
|
||||
description?: string;
|
||||
roles: Pick<TProjectRole, "slug" | "name" | "permissions">[];
|
||||
environments: { name: string; slug: string; position: number }[];
|
||||
environments?: { name: string; slug: string; position: number }[] | null;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
};
|
||||
@@ -14,6 +16,7 @@ export type TListProjectTemplates = { projectTemplates: TProjectTemplate[] };
|
||||
export type TProjectTemplateResponse = { projectTemplate: TProjectTemplate };
|
||||
|
||||
export type TCreateProjectTemplateDTO = {
|
||||
type: ProjectType;
|
||||
name: string;
|
||||
description?: string;
|
||||
};
|
||||
|
||||
@@ -3,7 +3,9 @@ export enum ProjectMembershipRole {
|
||||
Member = "member",
|
||||
Custom = "custom",
|
||||
Viewer = "viewer",
|
||||
NoAccess = "no-access"
|
||||
NoAccess = "no-access",
|
||||
SshHostBootstrapper = "ssh-host-bootstrapper",
|
||||
KmsCryptographicOperator = "cryptographic-operator"
|
||||
}
|
||||
|
||||
export type TGetProjectRolesDTO = {
|
||||
@@ -17,7 +19,7 @@ export type TProjectRole = {
|
||||
id: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
description?: string;
|
||||
description?: string | null;
|
||||
permissions: TProjectPermission[];
|
||||
};
|
||||
|
||||
@@ -74,7 +76,7 @@ export type TDeleteOrgRoleDTO = {
|
||||
export type TCreateProjectRoleDTO = {
|
||||
projectId: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
description?: string | null;
|
||||
slug: string;
|
||||
permissions: TProjectPermission[];
|
||||
};
|
||||
|
||||
@@ -10,15 +10,17 @@ import {
|
||||
} from "./types";
|
||||
|
||||
export const useCreateNewInstallationSession = () => {
|
||||
return useMutation<{ sessionId: string }, object, { organizationId: string }>({
|
||||
mutationFn: async (opt) => {
|
||||
const { data } = await apiRequest.post(
|
||||
"/api/v1/secret-scanning/create-installation-session/organization",
|
||||
opt
|
||||
);
|
||||
return data;
|
||||
return useMutation<{ sessionId: string; gitAppSlug: string }, object, { organizationId: string }>(
|
||||
{
|
||||
mutationFn: async (opt) => {
|
||||
const { data } = await apiRequest.post(
|
||||
"/api/v1/secret-scanning/create-installation-session/organization",
|
||||
opt
|
||||
);
|
||||
return data;
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
};
|
||||
|
||||
export const useUpdateRiskStatus = () => {
|
||||
|
||||
@@ -11,10 +11,13 @@ export const secretSharingKeys = {
|
||||
allSecretRequests: () => ["secretRequests"] as const,
|
||||
specificSecretRequests: ({ offset, limit }: { offset: number; limit: number }) =>
|
||||
[...secretSharingKeys.allSecretRequests(), { offset, limit }] as const,
|
||||
getSecretById: (arg: { id: string; hashedHex: string | null; password?: string }) => [
|
||||
"shared-secret",
|
||||
arg
|
||||
],
|
||||
getSecretById: (arg: {
|
||||
id: string;
|
||||
hashedHex: string | null;
|
||||
password?: string;
|
||||
email?: string;
|
||||
hash?: string;
|
||||
}) => ["shared-secret", arg],
|
||||
getSecretRequestById: (arg: { id: string }) => ["secret-request", arg] as const
|
||||
};
|
||||
|
||||
@@ -70,20 +73,34 @@ export const useGetSecretRequests = ({
|
||||
export const useGetActiveSharedSecretById = ({
|
||||
sharedSecretId,
|
||||
hashedHex,
|
||||
password
|
||||
password,
|
||||
email,
|
||||
hash
|
||||
}: {
|
||||
sharedSecretId: string;
|
||||
hashedHex: string | null;
|
||||
password?: string;
|
||||
|
||||
// For secrets shared to specific emails (optional)
|
||||
email?: string;
|
||||
hash?: string;
|
||||
}) => {
|
||||
return useQuery({
|
||||
queryKey: secretSharingKeys.getSecretById({ id: sharedSecretId, hashedHex, password }),
|
||||
queryKey: secretSharingKeys.getSecretById({
|
||||
id: sharedSecretId,
|
||||
hashedHex,
|
||||
password,
|
||||
email,
|
||||
hash
|
||||
}),
|
||||
queryFn: async () => {
|
||||
const { data } = await apiRequest.post<TViewSharedSecretResponse>(
|
||||
`/api/v1/secret-sharing/shared/public/${sharedSecretId}`,
|
||||
{
|
||||
...(hashedHex && { hashedHex }),
|
||||
password
|
||||
password,
|
||||
email,
|
||||
hash
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ export type TCreateSharedSecretRequest = {
|
||||
expiresAt: Date;
|
||||
expiresAfterViews?: number;
|
||||
accessType?: SecretSharingAccessType;
|
||||
emails?: string[];
|
||||
};
|
||||
|
||||
export type TCreateSecretRequestRequestDTO = {
|
||||
|
||||
@@ -12,7 +12,8 @@ export enum SecretSync {
|
||||
Vercel = "vercel",
|
||||
Windmill = "windmill",
|
||||
HCVault = "hashicorp-vault",
|
||||
TeamCity = "teamcity"
|
||||
TeamCity = "teamcity",
|
||||
OCIVault = "oci-vault"
|
||||
}
|
||||
|
||||
export enum SecretSyncStatus {
|
||||
|
||||
@@ -11,6 +11,7 @@ import { TGcpSync } from "./gcp-sync";
|
||||
import { TGitHubSync } from "./github-sync";
|
||||
import { THCVaultSync } from "./hc-vault-sync";
|
||||
import { THumanitecSync } from "./humanitec-sync";
|
||||
import { TOCIVaultSync } from "./oci-vault-sync";
|
||||
import { TTeamCitySync } from "./teamcity-sync";
|
||||
import { TTerraformCloudSync } from "./terraform-cloud-sync";
|
||||
import { TVercelSync } from "./vercel-sync";
|
||||
@@ -36,7 +37,8 @@ export type TSecretSync =
|
||||
| TVercelSync
|
||||
| TWindmillSync
|
||||
| THCVaultSync
|
||||
| TTeamCitySync;
|
||||
| TTeamCitySync
|
||||
| TOCIVaultSync;
|
||||
|
||||
export type TListSecretSyncs = { secretSyncs: TSecretSync[] };
|
||||
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { AppConnection } from "@app/hooks/api/appConnections/enums";
|
||||
import { SecretSync } from "@app/hooks/api/secretSyncs";
|
||||
import { TRootSecretSync } from "@app/hooks/api/secretSyncs/types/root-sync";
|
||||
|
||||
export type TOCIVaultSync = TRootSecretSync & {
|
||||
destination: SecretSync.OCIVault;
|
||||
destinationConfig: {
|
||||
compartmentOcid: string;
|
||||
vaultOcid: string;
|
||||
keyOcid: string;
|
||||
};
|
||||
connection: {
|
||||
app: AppConnection.OCI;
|
||||
name: string;
|
||||
id: string;
|
||||
};
|
||||
};
|
||||
@@ -4,8 +4,7 @@ import { SecretSyncInitialSyncBehavior, SecretSyncStatus } from "@app/hooks/api/
|
||||
export type RootSyncOptions = {
|
||||
initialSyncBehavior: SecretSyncInitialSyncBehavior;
|
||||
disableSecretDeletion?: boolean;
|
||||
// prependPrefix?: string;
|
||||
// appendSuffix?: string;
|
||||
keySchema?: string;
|
||||
};
|
||||
|
||||
export type TRootSecretSync = {
|
||||
|
||||
@@ -6,7 +6,8 @@ export enum LoginMappingSource {
|
||||
export type TLoginMapping = {
|
||||
loginUser: string;
|
||||
allowedPrincipals: {
|
||||
usernames: string[];
|
||||
usernames?: string[];
|
||||
groups?: string[];
|
||||
};
|
||||
source: LoginMappingSource;
|
||||
};
|
||||
@@ -20,6 +21,7 @@ export type TSshHost = {
|
||||
hostCertTtl: string;
|
||||
loginMappings: TLoginMapping[];
|
||||
};
|
||||
|
||||
export type TCreateSshHostDTO = {
|
||||
projectId: string;
|
||||
hostname: string;
|
||||
|
||||
@@ -35,6 +35,7 @@ export {
|
||||
useListWorkspaceGroups,
|
||||
useListWorkspacePkiAlerts,
|
||||
useListWorkspacePkiCollections,
|
||||
useListWorkspacePkiSubscribers,
|
||||
useListWorkspaceSshCas,
|
||||
useListWorkspaceSshCertificates,
|
||||
useListWorkspaceSshCertificateTemplates,
|
||||
|
||||
@@ -14,6 +14,7 @@ import { IntegrationAuth } from "../integrationAuth/types";
|
||||
import { TIntegration } from "../integrations/types";
|
||||
import { TPkiAlert } from "../pkiAlerts/types";
|
||||
import { TPkiCollection } from "../pkiCollections/types";
|
||||
import { TPkiSubscriber } from "../pkiSubscriber/types";
|
||||
import { EncryptedSecret } from "../secrets/types";
|
||||
import { TSshCertificate, TSshCertificateAuthority } from "../sshCa/types";
|
||||
import { TSshCertificateTemplate } from "../sshCertificateTemplates/types";
|
||||
@@ -276,13 +277,20 @@ export const useUpdateProject = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<Workspace, object, UpdateProjectDTO>({
|
||||
mutationFn: async ({ projectID, newProjectName, newProjectDescription, newSlug }) => {
|
||||
mutationFn: async ({
|
||||
projectID,
|
||||
newProjectName,
|
||||
newProjectDescription,
|
||||
newSlug,
|
||||
secretSharing
|
||||
}) => {
|
||||
const { data } = await apiRequest.patch<{ workspace: Workspace }>(
|
||||
`/api/v1/workspace/${projectID}`,
|
||||
{
|
||||
name: newProjectName,
|
||||
description: newProjectDescription,
|
||||
slug: newSlug
|
||||
slug: newSlug,
|
||||
secretSharing
|
||||
}
|
||||
);
|
||||
return data.workspace;
|
||||
@@ -874,6 +882,21 @@ export const useListWorkspaceSshHosts = (projectId: string) => {
|
||||
});
|
||||
};
|
||||
|
||||
export const useListWorkspacePkiSubscribers = (projectId: string) => {
|
||||
return useQuery({
|
||||
queryKey: workspaceKeys.getWorkspacePkiSubscribers(projectId),
|
||||
queryFn: async () => {
|
||||
const {
|
||||
data: { subscribers }
|
||||
} = await apiRequest.get<{ subscribers: TPkiSubscriber[] }>(
|
||||
`/api/v2/workspace/${projectId}/pki-subscribers`
|
||||
);
|
||||
return subscribers;
|
||||
},
|
||||
enabled: Boolean(projectId)
|
||||
});
|
||||
};
|
||||
|
||||
export const useListWorkspaceSshHostGroups = (projectId: string) => {
|
||||
return useQuery({
|
||||
queryKey: workspaceKeys.getWorkspaceSshHostGroups(projectId),
|
||||
|
||||
@@ -16,8 +16,11 @@ export const workspaceKeys = {
|
||||
type ? ["workspaces", { type }] : (["workspaces"] as const),
|
||||
getWorkspaceAuditLogs: (workspaceId: string) =>
|
||||
[{ workspaceId }, "workspace-audit-logs"] as const,
|
||||
getWorkspaceUsers: (workspaceId: string, includeGroupMembers?: boolean, roles?: string[]) =>
|
||||
[{ workspaceId, includeGroupMembers, roles }, "workspace-users"] as const,
|
||||
getWorkspaceUsers: (
|
||||
workspaceId: string,
|
||||
includeGroupMembers: boolean = false,
|
||||
roles: string[] = []
|
||||
) => [{ workspaceId, includeGroupMembers, roles }, "workspace-users"] as const,
|
||||
getWorkspaceUserDetails: (workspaceId: string, membershipId: string) =>
|
||||
[{ workspaceId, membershipId }, "workspace-user-details"] as const,
|
||||
getWorkspaceIdentityMemberships: (workspaceId: string) =>
|
||||
@@ -51,6 +54,8 @@ export const workspaceKeys = {
|
||||
}) => [...workspaceKeys.forWorkspaceCertificates(slug), { offset, limit }] as const,
|
||||
getWorkspacePkiAlerts: (workspaceId: string) =>
|
||||
[{ workspaceId }, "workspace-pki-alerts"] as const,
|
||||
getWorkspacePkiSubscribers: (projectId: string) =>
|
||||
[{ projectId }, "workspace-pki-subscribers"] as const,
|
||||
getWorkspacePkiCollections: (workspaceId: string) =>
|
||||
[{ workspaceId }, "workspace-pki-collections"] as const,
|
||||
getWorkspaceCertificateTemplates: (workspaceId: string) =>
|
||||
|
||||
@@ -37,6 +37,7 @@ export type Workspace = {
|
||||
createdAt: string;
|
||||
roles?: TProjectRole[];
|
||||
hasDeleteProtection: boolean;
|
||||
secretSharing: boolean;
|
||||
};
|
||||
|
||||
export type WorkspaceEnv = {
|
||||
@@ -73,9 +74,10 @@ export type CreateWorkspaceDTO = {
|
||||
|
||||
export type UpdateProjectDTO = {
|
||||
projectID: string;
|
||||
newProjectName: string;
|
||||
newProjectName?: string;
|
||||
newProjectDescription?: string;
|
||||
newSlug?: string;
|
||||
secretSharing?: boolean;
|
||||
};
|
||||
|
||||
export type UpdatePitVersionLimitDTO = { projectSlug: string; pitVersionLimit: number };
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export { useDebounce } from "./useDebounce";
|
||||
export * from "./useGetProjectTypeFromRoute";
|
||||
export { usePagination } from "./usePagination";
|
||||
export { usePersistentState } from "./usePersistentState";
|
||||
export { usePopUp } from "./usePopUp";
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import { useMemo } from "react";
|
||||
import { useRouterState } from "@tanstack/react-router";
|
||||
|
||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||
|
||||
export const useGetProjectTypeFromRoute = () => {
|
||||
const { location } = useRouterState();
|
||||
|
||||
return useMemo(() => {
|
||||
const segments = location.pathname.split("/");
|
||||
|
||||
let type: ProjectType | undefined;
|
||||
|
||||
// location of project type can vary in router path, so we need to check all possible values
|
||||
segments.forEach((segment) => {
|
||||
if (Object.values(ProjectType).includes(segment as ProjectType))
|
||||
type = segment as ProjectType;
|
||||
});
|
||||
|
||||
return type;
|
||||
}, [location]);
|
||||
};
|
||||
@@ -11,11 +11,12 @@ import { BreadcrumbContainer, TBreadcrumbFormat } from "@app/components/v2";
|
||||
import { OrgPermissionSubjects, useOrgPermission, useServerConfig } from "@app/context";
|
||||
import { OrgPermissionSecretShareAction } from "@app/context/OrgPermissionContext/types";
|
||||
import { usePopUp } from "@app/hooks";
|
||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||
|
||||
import { InsecureConnectionBanner } from "./components/InsecureConnectionBanner";
|
||||
import { MinimizedOrgSidebar } from "./components/MinimizedOrgSidebar";
|
||||
import { SidebarHeader } from "./components/SidebarHeader";
|
||||
import { DefaultSideBar, SecretSharingSideBar } from "./ProductsSideBar";
|
||||
import { DefaultSideBar, ProjectOverviewSideBar, SecretSharingSideBar } from "./ProductsSideBar";
|
||||
|
||||
export const OrganizationLayout = () => {
|
||||
const matches = useRouterState({ select: (s) => s.matches.at(-1)?.context });
|
||||
@@ -45,21 +46,38 @@ export const OrganizationLayout = () => {
|
||||
] as string[]
|
||||
).includes(location.pathname);
|
||||
|
||||
const isProjectOverviewOrSettingsPage = (
|
||||
[
|
||||
linkOptions({ to: "/organization/secret-manager/overview" }).to,
|
||||
linkOptions({ to: "/organization/secret-manager/settings" }).to,
|
||||
linkOptions({ to: "/organization/cert-manager/overview" }).to,
|
||||
linkOptions({ to: "/organization/cert-manager/settings" }).to,
|
||||
linkOptions({ to: "/organization/kms/overview" }).to,
|
||||
linkOptions({ to: "/organization/kms/settings" }).to,
|
||||
linkOptions({ to: "/organization/ssh/overview" }).to,
|
||||
linkOptions({ to: "/organization/ssh/settings" }).to
|
||||
] as string[]
|
||||
).includes(location.pathname);
|
||||
|
||||
const shouldShowOrgSidebar =
|
||||
location.pathname.startsWith("/organization") &&
|
||||
(!isSecretSharingPage || shouldShowProductsSidebar) &&
|
||||
!(
|
||||
[
|
||||
linkOptions({ to: "/organization/secret-manager/overview" }).to,
|
||||
linkOptions({ to: "/organization/cert-manager/overview" }).to,
|
||||
linkOptions({ to: "/organization/ssh/overview" }).to,
|
||||
linkOptions({ to: "/organization/kms/overview" }).to,
|
||||
linkOptions({ to: "/organization/secret-scanning" }).to
|
||||
] as string[]
|
||||
).includes(location.pathname);
|
||||
!([linkOptions({ to: "/organization/secret-scanning" }).to] as string[]).includes(
|
||||
location.pathname
|
||||
);
|
||||
|
||||
const containerHeight = config.pageFrameContent ? "h-[94vh]" : "h-screen";
|
||||
|
||||
let SideBarComponent = <DefaultSideBar />;
|
||||
|
||||
if (isSecretSharingPage) {
|
||||
SideBarComponent = <SecretSharingSideBar />;
|
||||
} else if (isProjectOverviewOrSettingsPage) {
|
||||
SideBarComponent = (
|
||||
<ProjectOverviewSideBar type={location.pathname.split("/")[2] as ProjectType} />
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Banner />
|
||||
@@ -80,10 +98,12 @@ export const OrganizationLayout = () => {
|
||||
className="dark w-60 overflow-hidden border-r border-mineshaft-600 bg-gradient-to-tr from-mineshaft-700 via-mineshaft-800 to-mineshaft-900"
|
||||
>
|
||||
<nav className="items-between flex h-full flex-col overflow-y-auto dark:[color-scheme:dark]">
|
||||
<div className="p-2 pt-3">
|
||||
<SidebarHeader />
|
||||
</div>
|
||||
{isSecretSharingPage ? <SecretSharingSideBar /> : <DefaultSideBar />}
|
||||
{!isProjectOverviewOrSettingsPage && (
|
||||
<div className="p-2 pt-3">
|
||||
<SidebarHeader />
|
||||
</div>
|
||||
)}
|
||||
{SideBarComponent}
|
||||
</nav>
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
@@ -5,22 +5,6 @@ import { Menu, MenuGroup, MenuItem } from "@app/components/v2";
|
||||
export const DefaultSideBar = () => (
|
||||
<Menu>
|
||||
<MenuGroup title="Organization Control">
|
||||
<Link to="/organization/audit-logs">
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive} icon="moving-block">
|
||||
Audit Logs
|
||||
</MenuItem>
|
||||
)}
|
||||
</Link>
|
||||
<Link to="/organization/billing">
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive} icon="spinning-coin">
|
||||
Usage & Billing
|
||||
</MenuItem>
|
||||
)}
|
||||
</Link>
|
||||
</MenuGroup>
|
||||
<MenuGroup title="Other">
|
||||
<Link to="/organization/access-management">
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive} icon="groups">
|
||||
@@ -42,6 +26,29 @@ export const DefaultSideBar = () => (
|
||||
</MenuItem>
|
||||
)}
|
||||
</Link>
|
||||
<Link to="/organization/sso">
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive} icon="check">
|
||||
Single Sign-On (SSO)
|
||||
</MenuItem>
|
||||
)}
|
||||
</Link>
|
||||
</MenuGroup>
|
||||
<MenuGroup title="Other">
|
||||
<Link to="/organization/audit-logs">
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive} icon="moving-block">
|
||||
Audit Logs
|
||||
</MenuItem>
|
||||
)}
|
||||
</Link>
|
||||
<Link to="/organization/billing">
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive} icon="spinning-coin">
|
||||
Usage & Billing
|
||||
</MenuItem>
|
||||
)}
|
||||
</Link>
|
||||
<Link to="/organization/settings">
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive} icon="toggle-settings">
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
import { faArrowUpRightFromSquare } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { Link, useMatchRoute } from "@tanstack/react-router";
|
||||
|
||||
import { Menu, MenuGroup, MenuItem } from "@app/components/v2";
|
||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||
|
||||
type TProjectOverviewSideBarProps = {
|
||||
type: ProjectType;
|
||||
};
|
||||
|
||||
export const ProjectOverviewSideBar = ({ type }: TProjectOverviewSideBarProps) => {
|
||||
const matchRoute = useMatchRoute();
|
||||
|
||||
const isOverviewActive = !!matchRoute({
|
||||
to: `/organization/${type}/overview`,
|
||||
fuzzy: false
|
||||
});
|
||||
|
||||
let label: string;
|
||||
let icon: string;
|
||||
let link: string;
|
||||
|
||||
switch (type) {
|
||||
case ProjectType.CertificateManager:
|
||||
label = "Cert Management";
|
||||
icon = "note";
|
||||
link = "https://infisical.com/docs/documentation/platform/pki/overview";
|
||||
break;
|
||||
case ProjectType.SecretManager:
|
||||
label = "Secret Management";
|
||||
icon = "sliding-carousel";
|
||||
link = "https://infisical.com/docs/documentation/getting-started/introduction";
|
||||
break;
|
||||
case ProjectType.KMS:
|
||||
label = "KMS";
|
||||
icon = "unlock";
|
||||
link = "https://infisical.com/docs/documentation/platform/kms/overview";
|
||||
break;
|
||||
case ProjectType.SSH:
|
||||
label = "SSH";
|
||||
icon = "verified";
|
||||
link = "https://infisical.com/docs/documentation/platform/ssh/overview";
|
||||
break;
|
||||
default:
|
||||
throw new Error("Unknown project type");
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="p-2 pt-3">
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href={link}
|
||||
className="flex w-full items-center rounded-md border border-mineshaft-600 p-2 pl-3 transition-all duration-150 hover:bg-mineshaft-700"
|
||||
>
|
||||
<div className="mr-2 flex h-6 w-6 items-center justify-center rounded-md bg-mineshaft-500 p-4">
|
||||
<FontAwesomeIcon
|
||||
icon={faArrowUpRightFromSquare}
|
||||
className="h-3.5 w-3.5 text-mineshaft-300"
|
||||
/>
|
||||
</div>
|
||||
<div className="-mt-1 flex flex-grow flex-col text-white">
|
||||
<div className="max-w-36 truncate text-ellipsis text-sm font-medium capitalize">
|
||||
Infisical Docs
|
||||
</div>
|
||||
<div className="text-xs leading-[10px] text-mineshaft-400">
|
||||
{type === ProjectType.SecretManager ? "Get Started" : `${label} Overview`}
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<Menu>
|
||||
<MenuGroup title="Overview">
|
||||
<Link to={`/organization/${type}/overview`}>
|
||||
<MenuItem isSelected={isOverviewActive} icon={icon}>
|
||||
{label}
|
||||
</MenuItem>
|
||||
</Link>
|
||||
</MenuGroup>
|
||||
<MenuGroup title="Other">
|
||||
<Link to={`/organization/${type}/settings`}>
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive} icon="toggle-settings">
|
||||
Settings
|
||||
</MenuItem>
|
||||
)}
|
||||
</Link>
|
||||
</MenuGroup>
|
||||
</Menu>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from "./DefaultSideBar";
|
||||
export * from "./ProjectOverviewSideBar";
|
||||
export * from "./SecretSharingSideBar";
|
||||
|
||||
+96
-65
@@ -4,6 +4,7 @@ import {
|
||||
faArrowUpRightFromSquare,
|
||||
faBook,
|
||||
faCheck,
|
||||
faCheckCircle,
|
||||
faCog,
|
||||
faDoorClosed,
|
||||
faEnvelope,
|
||||
@@ -118,6 +119,9 @@ export const MinimizedOrgSidebar = () => {
|
||||
[
|
||||
linkOptions({ to: "/organization/access-management" }).to,
|
||||
linkOptions({ to: "/organization/app-connections" }).to,
|
||||
linkOptions({ to: "/organization/billing" }).to,
|
||||
linkOptions({ to: "/organization/sso" }).to,
|
||||
linkOptions({ to: "/organization/gateways" }).to,
|
||||
linkOptions({ to: "/organization/settings" }).to,
|
||||
linkOptions({ to: "/organization/audit-logs" }).to
|
||||
] as string[]
|
||||
@@ -264,71 +268,91 @@ export const MinimizedOrgSidebar = () => {
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<Link to="/organization/secret-manager/overview">
|
||||
{({ isActive }) => (
|
||||
<MenuIconButton
|
||||
isSelected={
|
||||
isActive ||
|
||||
window.location.pathname.startsWith(`/${ProjectType.SecretManager}`)
|
||||
}
|
||||
icon="sliding-carousel"
|
||||
>
|
||||
Secrets
|
||||
</MenuIconButton>
|
||||
)}
|
||||
</Link>
|
||||
<Link to="/organization/cert-manager/overview">
|
||||
{({ isActive }) => (
|
||||
<MenuIconButton
|
||||
isSelected={
|
||||
isActive ||
|
||||
window.location.pathname.startsWith(`/${ProjectType.CertificateManager}`)
|
||||
}
|
||||
icon="note"
|
||||
>
|
||||
PKI
|
||||
</MenuIconButton>
|
||||
)}
|
||||
</Link>
|
||||
<Link to="/organization/kms/overview">
|
||||
{({ isActive }) => (
|
||||
<MenuIconButton
|
||||
isSelected={
|
||||
isActive || window.location.pathname.startsWith(`/${ProjectType.KMS}`)
|
||||
}
|
||||
icon="unlock"
|
||||
>
|
||||
KMS
|
||||
</MenuIconButton>
|
||||
)}
|
||||
</Link>
|
||||
<Link to="/organization/ssh/overview">
|
||||
{({ isActive }) => (
|
||||
<MenuIconButton
|
||||
isSelected={
|
||||
isActive || window.location.pathname.startsWith(`/${ProjectType.SSH}`)
|
||||
}
|
||||
icon="verified"
|
||||
>
|
||||
SSH
|
||||
</MenuIconButton>
|
||||
)}
|
||||
</Link>
|
||||
<div className="w-full bg-mineshaft-500" style={{ height: "1px" }} />
|
||||
<Link to="/organization/secret-scanning">
|
||||
{({ isActive }) => (
|
||||
<MenuIconButton isSelected={isActive} icon="secret-scan">
|
||||
Scanner
|
||||
</MenuIconButton>
|
||||
)}
|
||||
</Link>
|
||||
<Link to="/organization/secret-sharing">
|
||||
{({ isActive }) => (
|
||||
<MenuIconButton isSelected={isActive} icon="lock-closed">
|
||||
Share
|
||||
</MenuIconButton>
|
||||
)}
|
||||
</Link>
|
||||
{currentOrg.secretsProductEnabled && (
|
||||
<Link to="/organization/secret-manager/overview">
|
||||
{({ isActive }) => (
|
||||
<MenuIconButton
|
||||
isSelected={
|
||||
isActive ||
|
||||
window.location.pathname.startsWith(
|
||||
`/organization/${ProjectType.SecretManager}`
|
||||
)
|
||||
}
|
||||
icon="sliding-carousel"
|
||||
>
|
||||
Secrets
|
||||
</MenuIconButton>
|
||||
)}
|
||||
</Link>
|
||||
)}
|
||||
{currentOrg.pkiProductEnabled && (
|
||||
<Link to="/organization/cert-manager/overview">
|
||||
{({ isActive }) => (
|
||||
<MenuIconButton
|
||||
isSelected={
|
||||
isActive ||
|
||||
window.location.pathname.startsWith(
|
||||
`/organization/${ProjectType.CertificateManager}`
|
||||
)
|
||||
}
|
||||
icon="note"
|
||||
>
|
||||
PKI
|
||||
</MenuIconButton>
|
||||
)}
|
||||
</Link>
|
||||
)}
|
||||
{currentOrg.kmsProductEnabled && (
|
||||
<Link to="/organization/kms/overview">
|
||||
{({ isActive }) => (
|
||||
<MenuIconButton
|
||||
isSelected={
|
||||
isActive ||
|
||||
window.location.pathname.startsWith(`/organization/${ProjectType.KMS}`)
|
||||
}
|
||||
icon="unlock"
|
||||
>
|
||||
KMS
|
||||
</MenuIconButton>
|
||||
)}
|
||||
</Link>
|
||||
)}
|
||||
{currentOrg.sshProductEnabled && (
|
||||
<Link to="/organization/ssh/overview">
|
||||
{({ isActive }) => (
|
||||
<MenuIconButton
|
||||
isSelected={
|
||||
isActive ||
|
||||
window.location.pathname.startsWith(`/organization/${ProjectType.SSH}`)
|
||||
}
|
||||
icon="verified"
|
||||
>
|
||||
SSH
|
||||
</MenuIconButton>
|
||||
)}
|
||||
</Link>
|
||||
)}
|
||||
{(currentOrg.scannerProductEnabled || currentOrg.shareSecretsProductEnabled) && (
|
||||
<div className="w-full bg-mineshaft-500" style={{ height: "1px" }} />
|
||||
)}
|
||||
{currentOrg.scannerProductEnabled && (
|
||||
<Link to="/organization/secret-scanning">
|
||||
{({ isActive }) => (
|
||||
<MenuIconButton isSelected={isActive} icon="secret-scan">
|
||||
Scanner
|
||||
</MenuIconButton>
|
||||
)}
|
||||
</Link>
|
||||
)}
|
||||
{currentOrg.shareSecretsProductEnabled && (
|
||||
<Link to="/organization/secret-sharing">
|
||||
{({ isActive }) => (
|
||||
<MenuIconButton isSelected={isActive} icon="lock-closed">
|
||||
Share
|
||||
</MenuIconButton>
|
||||
)}
|
||||
</Link>
|
||||
)}
|
||||
<div className="my-1 w-full bg-mineshaft-500" style={{ height: "1px" }} />
|
||||
<DropdownMenu open={open} onOpenChange={setOpen} modal={false}>
|
||||
<DropdownMenuTrigger
|
||||
@@ -381,6 +405,13 @@ export const MinimizedOrgSidebar = () => {
|
||||
Audit Logs
|
||||
</DropdownMenuItem>
|
||||
</Link>
|
||||
<Link to="/organization/sso">
|
||||
<DropdownMenuItem
|
||||
icon={<FontAwesomeIcon className="w-3" icon={faCheckCircle} />}
|
||||
>
|
||||
SSO Settings
|
||||
</DropdownMenuItem>
|
||||
</Link>
|
||||
<Link to="/organization/settings">
|
||||
<DropdownMenuItem icon={<FontAwesomeIcon className="w-3" icon={faCog} />}>
|
||||
Organization Settings
|
||||
|
||||
@@ -104,7 +104,23 @@ export const ProjectLayout = () => {
|
||||
{isCertManager && (
|
||||
<>
|
||||
<Link
|
||||
to={`/${ProjectType.CertificateManager}/$projectId/overview` as const}
|
||||
to={
|
||||
`/${ProjectType.CertificateManager}/$projectId/subscribers` as const
|
||||
}
|
||||
params={{
|
||||
projectId: currentWorkspace.id
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive} icon="pki-subscriber">
|
||||
Subscribers
|
||||
</MenuItem>
|
||||
)}
|
||||
</Link>
|
||||
<Link
|
||||
to={
|
||||
`/${ProjectType.CertificateManager}/$projectId/certificates` as const
|
||||
}
|
||||
params={{
|
||||
projectId: currentWorkspace.id
|
||||
}}
|
||||
|
||||
@@ -31,6 +31,7 @@ import {
|
||||
import { IdentityPanel } from "@app/pages/admin/OverviewPage/components/IdentityPanel";
|
||||
|
||||
import { AuthPanel } from "./components/AuthPanel";
|
||||
import { CachingPanel } from "./components/CachingPanel";
|
||||
import { EncryptionPanel } from "./components/EncryptionPanel";
|
||||
import { IntegrationPanel } from "./components/IntegrationPanel";
|
||||
import { UserPanel } from "./components/UserPanel";
|
||||
@@ -42,7 +43,8 @@ enum TabSections {
|
||||
Integrations = "integrations",
|
||||
Users = "users",
|
||||
Identities = "identities",
|
||||
Kmip = "kmip"
|
||||
Kmip = "kmip",
|
||||
Caching = "caching"
|
||||
}
|
||||
|
||||
enum SignUpModes {
|
||||
@@ -164,6 +166,7 @@ export const OverviewPage = () => {
|
||||
<Tab value={TabSections.Integrations}>Integrations</Tab>
|
||||
<Tab value={TabSections.Users}>User Identities</Tab>
|
||||
<Tab value={TabSections.Identities}>Machine Identities</Tab>
|
||||
<Tab value={TabSections.Caching}>Caching</Tab>
|
||||
</div>
|
||||
</TabList>
|
||||
<TabPanel value={TabSections.Settings}>
|
||||
@@ -408,6 +411,9 @@ export const OverviewPage = () => {
|
||||
<TabPanel value={TabSections.Identities}>
|
||||
<IdentityPanel />
|
||||
</TabPanel>
|
||||
<TabPanel value={TabSections.Caching}>
|
||||
<CachingPanel />
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { faRotate } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { Badge, Button, DeleteActionModal } from "@app/components/v2";
|
||||
import { useUser } from "@app/context";
|
||||
import { usePopUp } from "@app/hooks";
|
||||
import { useInvalidateCache } from "@app/hooks/api";
|
||||
import { useGetInvalidatingCacheStatus } from "@app/hooks/api/admin/queries";
|
||||
import { CacheType } from "@app/hooks/api/admin/types";
|
||||
|
||||
export const CachingPanel = () => {
|
||||
const { mutateAsync: invalidateCache } = useInvalidateCache();
|
||||
const { user } = useUser();
|
||||
|
||||
const [type, setType] = useState<CacheType | null>(null);
|
||||
const [shouldPoll, setShouldPoll] = useState(false);
|
||||
|
||||
const {
|
||||
data: invalidationStatus,
|
||||
isFetching,
|
||||
refetch
|
||||
} = useGetInvalidatingCacheStatus(shouldPoll);
|
||||
const isInvalidating = Boolean(shouldPoll && (isFetching || invalidationStatus));
|
||||
|
||||
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
|
||||
"invalidateCache"
|
||||
] as const);
|
||||
|
||||
const handleInvalidateCacheSubmit = async () => {
|
||||
if (!type || isInvalidating) return;
|
||||
|
||||
try {
|
||||
await invalidateCache({ type });
|
||||
createNotification({ text: `Began invalidating ${type} cache`, type: "success" });
|
||||
setShouldPoll(true);
|
||||
handlePopUpClose("invalidateCache");
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
createNotification({ text: `Failed to invalidate ${type} cache`, type: "error" });
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (isInvalidating) return;
|
||||
|
||||
if (shouldPoll) {
|
||||
setShouldPoll(false);
|
||||
createNotification({ text: "Successfully invalidated cache", type: "success" });
|
||||
}
|
||||
}, [isInvalidating, shouldPoll]);
|
||||
|
||||
useEffect(() => {
|
||||
refetch().then((v) => setShouldPoll(v.data || false));
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="mb-6 flex flex-wrap items-end justify-between gap-4 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||
<div className="flex flex-col">
|
||||
<div className="mb-2 flex items-center gap-3">
|
||||
<span className="text-xl font-semibold text-mineshaft-100">Secrets Cache</span>
|
||||
{isInvalidating && (
|
||||
<Badge
|
||||
variant="danger"
|
||||
className="flex h-5 w-min items-center gap-1.5 whitespace-nowrap"
|
||||
>
|
||||
<FontAwesomeIcon icon={faRotate} className="animate-spin" />
|
||||
Invalidating Cache
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
<span className="max-w-xl text-sm text-mineshaft-400">
|
||||
The encrypted secrets cache encompasses all secrets stored within the system and
|
||||
provides a temporary, secure storage location for frequently accessed credentials.
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
colorSchema="danger"
|
||||
onClick={() => {
|
||||
setType(CacheType.SECRETS);
|
||||
handlePopUpOpen("invalidateCache");
|
||||
}}
|
||||
isDisabled={!user.superAdmin || isInvalidating}
|
||||
>
|
||||
Invalidate Secrets Cache
|
||||
</Button>
|
||||
</div>
|
||||
<DeleteActionModal
|
||||
isOpen={popUp.invalidateCache.isOpen}
|
||||
title={`Are you sure you want to invalidate ${type} cache?`}
|
||||
subTitle="This action is permanent and irreversible. The cache invalidation process may take several minutes to complete."
|
||||
onChange={(isOpen) => handlePopUpToggle("invalidateCache", isOpen)}
|
||||
deleteKey="confirm"
|
||||
onDeleteApproved={handleInvalidateCacheSubmit}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -52,7 +52,7 @@ export const MicrosoftTeamsIntegrationForm = ({ adminIntegrationsConfig }: Props
|
||||
});
|
||||
|
||||
createNotification({
|
||||
text: "Updated admin Microsoft Teams configuration",
|
||||
text: "Updated admin Microsoft Teams configuration. It can take up to 5 minutes to take effect.",
|
||||
type: "success"
|
||||
});
|
||||
};
|
||||
|
||||
@@ -10,7 +10,7 @@ import { useNavigateToSelectOrganization } from "./Login.utils";
|
||||
|
||||
export const LoginPage = ({ isAdmin }: { isAdmin?: boolean }) => {
|
||||
const { t } = useTranslation();
|
||||
const [step, setStep] = useState(0);
|
||||
const [step, setStep] = useState<number | null>(null);
|
||||
const [email, setEmail] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const { navigateToSelectOrganization } = useNavigateToSelectOrganization();
|
||||
@@ -36,6 +36,8 @@ export const LoginPage = ({ isAdmin }: { isAdmin?: boolean }) => {
|
||||
|
||||
if (isLoggedIn()) {
|
||||
handleRedirects();
|
||||
} else {
|
||||
setStep(0);
|
||||
}
|
||||
}, []);
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user