mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-13 07:29:26 +00:00
Merge branch 'main' into feat/multipleApprovalEnvs
This commit is contained in:
@@ -6,7 +6,6 @@ import { twMerge } from "tailwind-merge";
|
||||
|
||||
import { useOrganization, useWorkspace } from "@app/context";
|
||||
import { useToggle } from "@app/hooks";
|
||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||
|
||||
import { createNotification } from "../notifications";
|
||||
import { IconButton, Select, SelectItem, Tooltip } from "../v2";
|
||||
@@ -93,7 +92,7 @@ export default function NavHeader({
|
||||
<FontAwesomeIcon icon={faAngleRight} className="ml-3 mr-3 text-sm text-gray-400" />
|
||||
{pageName === "Secrets" ? (
|
||||
<Link
|
||||
to="/projects/$projectId/secret-manager/overview"
|
||||
to="/projects/secret-management/$projectId/overview"
|
||||
params={{ projectId: currentWorkspace.id }}
|
||||
className="text-sm font-semibold text-primary/80 hover:text-primary"
|
||||
>
|
||||
@@ -129,7 +128,7 @@ export default function NavHeader({
|
||||
<div className="flex items-center space-x-3">
|
||||
<FontAwesomeIcon icon={faAngleRight} className="ml-3 mr-1.5 text-xs text-gray-400" />
|
||||
<Link
|
||||
to={`/projects/$projectId/${ProjectType.SecretManager}/secrets/$envSlug` as const}
|
||||
to="/projects/secret-management/$projectId/secrets/$envSlug"
|
||||
params={{ projectId: currentWorkspace.id, envSlug: routerEnvSlug }}
|
||||
className="text-sm font-semibold text-primary/80 hover:text-primary"
|
||||
>
|
||||
@@ -191,7 +190,7 @@ export default function NavHeader({
|
||||
</div>
|
||||
) : (
|
||||
<Link
|
||||
to={`/projects/$projectId/${ProjectType.SecretManager}/secrets/$envSlug` as const}
|
||||
to="/projects/secret-management/$projectId/secrets/$envSlug"
|
||||
params={{
|
||||
projectId: currentWorkspace.id,
|
||||
envSlug: routerEnvSlug || ""
|
||||
|
||||
@@ -4,7 +4,6 @@ import { Link } from "@tanstack/react-router";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import { useTimedReset } from "@app/hooks";
|
||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||
|
||||
import { createNotification } from "../notifications";
|
||||
import { IconButton, Tooltip } from "../v2";
|
||||
@@ -70,7 +69,7 @@ export const SecretDashboardPathBreadcrumb = ({
|
||||
</div>
|
||||
) : (
|
||||
<Link
|
||||
to={`/projects/$projectId/${ProjectType.SecretManager}/secrets/$envSlug` as const}
|
||||
to="/projects/secret-management/$projectId/secrets/$envSlug"
|
||||
params={{
|
||||
projectId,
|
||||
envSlug: environmentSlug
|
||||
|
||||
@@ -5,14 +5,12 @@ import { z } from "zod";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||
import { Button, FormControl, Input, Select, SelectItem, TextArea } from "@app/components/v2";
|
||||
import { Button, FormControl, Input, TextArea } from "@app/components/v2";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
|
||||
import { useUpdateProject } from "@app/hooks/api";
|
||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||
|
||||
const baseFormSchema = z.object({
|
||||
name: z.string().min(1, "Required").max(64, "Too long, maximum length is 64 characters"),
|
||||
defaultProduct: z.nativeEnum(ProjectType).default(ProjectType.SecretManager),
|
||||
description: z
|
||||
.string()
|
||||
.trim()
|
||||
@@ -52,7 +50,6 @@ export const ProjectOverviewChangeSection = ({ showSlugField = false }: Props) =
|
||||
reset({
|
||||
name: currentWorkspace.name,
|
||||
description: currentWorkspace.description ?? "",
|
||||
defaultProduct: currentWorkspace.defaultProduct,
|
||||
...(showSlugField && { slug: currentWorkspace.slug })
|
||||
});
|
||||
}
|
||||
@@ -66,7 +63,6 @@ export const ProjectOverviewChangeSection = ({ showSlugField = false }: Props) =
|
||||
projectID: currentWorkspace.id,
|
||||
newProjectName: data.name,
|
||||
newProjectDescription: data.description,
|
||||
defaultProduct: data.defaultProduct,
|
||||
...(showSlugField &&
|
||||
"slug" in data && {
|
||||
newSlug: data.slug !== currentWorkspace.slug ? data.slug : undefined
|
||||
@@ -216,31 +212,6 @@ export const ProjectOverviewChangeSection = ({ showSlugField = false }: Props) =
|
||||
</ProjectPermissionCan>
|
||||
</div>
|
||||
</div>
|
||||
<Controller
|
||||
control={control}
|
||||
name="defaultProduct"
|
||||
defaultValue={ProjectType.SecretManager}
|
||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Default Product"
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error)}
|
||||
>
|
||||
<Select
|
||||
defaultValue={field.value}
|
||||
{...field}
|
||||
onValueChange={(e) => onChange(e)}
|
||||
className="w-full max-w-md bg-mineshaft-800"
|
||||
>
|
||||
<SelectItem value={ProjectType.SecretManager}>Secrets Manager</SelectItem>
|
||||
<SelectItem value={ProjectType.CertificateManager}>PKI Manager</SelectItem>
|
||||
<SelectItem value={ProjectType.KMS}>KMS</SelectItem>
|
||||
<SelectItem value={ProjectType.SSH}>SSH</SelectItem>
|
||||
<SelectItem value={ProjectType.SecretScanning}>Secret Scanning</SelectItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<div>
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Edit}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { faInfoCircle } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useNavigate } from "@tanstack/react-router";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import z from "zod";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
@@ -16,6 +17,7 @@ import {
|
||||
Button,
|
||||
FormControl,
|
||||
Input,
|
||||
Lottie,
|
||||
Modal,
|
||||
ModalClose,
|
||||
ModalContent,
|
||||
@@ -31,10 +33,11 @@ import {
|
||||
useSubscription,
|
||||
useUser
|
||||
} from "@app/context";
|
||||
import { getProjectHomePage } from "@app/helpers/project";
|
||||
import { getProjectHomePage, getProjectLottieIcon } from "@app/helpers/project";
|
||||
import { useCreateWorkspace, useGetExternalKmsList, useGetUserWorkspaces } from "@app/hooks/api";
|
||||
import { INTERNAL_KMS_KEY_ID } from "@app/hooks/api/kms/types";
|
||||
import { InfisicalProjectTemplate, useListProjectTemplates } from "@app/hooks/api/projectTemplates";
|
||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||
|
||||
const formSchema = z.object({
|
||||
name: z.string().trim().min(1, "Required").max(64, "Too long, maximum length is 64 characters"),
|
||||
@@ -43,6 +46,7 @@ const formSchema = z.object({
|
||||
.trim()
|
||||
.max(256, "Description too long, max length is 256 characters")
|
||||
.optional(),
|
||||
type: z.nativeEnum(ProjectType),
|
||||
kmsKeyId: z.string(),
|
||||
template: z.string()
|
||||
});
|
||||
@@ -56,6 +60,29 @@ interface NewProjectModalProps {
|
||||
|
||||
type NewProjectFormProps = Pick<NewProjectModalProps, "onOpenChange">;
|
||||
|
||||
const PROJECT_TYPE_MENU_ITEMS = [
|
||||
{
|
||||
label: "Secrets Management",
|
||||
value: ProjectType.SecretManager
|
||||
},
|
||||
{
|
||||
label: "Certificates Management",
|
||||
value: ProjectType.CertificateManager
|
||||
},
|
||||
{
|
||||
label: "KMS",
|
||||
value: ProjectType.KMS
|
||||
},
|
||||
{
|
||||
label: "SSH",
|
||||
value: ProjectType.SSH
|
||||
},
|
||||
{
|
||||
label: "Secret Scanning",
|
||||
value: ProjectType.SecretScanning
|
||||
}
|
||||
];
|
||||
|
||||
const NewProjectForm = ({ onOpenChange }: NewProjectFormProps) => {
|
||||
const navigate = useNavigate();
|
||||
const { currentOrg } = useOrganization();
|
||||
@@ -70,18 +97,11 @@ const NewProjectForm = ({ onOpenChange }: NewProjectFormProps) => {
|
||||
OrgPermissionSubjects.ProjectTemplates
|
||||
);
|
||||
|
||||
const { data: projectTemplates = [] } = useListProjectTemplates({
|
||||
enabled: Boolean(canReadProjectTemplates && subscription?.projectTemplates)
|
||||
});
|
||||
|
||||
const { data: externalKmsList } = useGetExternalKmsList(currentOrg.id, {
|
||||
enabled: permission.can(OrgPermissionActions.Read, OrgPermissionSubjects.Kms)
|
||||
});
|
||||
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
reset,
|
||||
watch,
|
||||
formState: { isSubmitting, errors }
|
||||
} = useForm<TAddProjectFormData>({
|
||||
resolver: zodResolver(formSchema),
|
||||
@@ -91,6 +111,16 @@ const NewProjectForm = ({ onOpenChange }: NewProjectFormProps) => {
|
||||
}
|
||||
});
|
||||
|
||||
const selectedProjectType = watch("type");
|
||||
const { data: projectTemplates = [] } = useListProjectTemplates({
|
||||
enabled: Boolean(canReadProjectTemplates && subscription?.projectTemplates),
|
||||
select: (template) => template.filter((el) => el.type === selectedProjectType)
|
||||
});
|
||||
|
||||
const { data: externalKmsList } = useGetExternalKmsList(currentOrg.id, {
|
||||
enabled: permission.can(OrgPermissionActions.Read, OrgPermissionSubjects.Kms)
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (Object.keys(errors).length > 0) {
|
||||
console.log("Current form errors:", errors);
|
||||
@@ -101,7 +131,8 @@ const NewProjectForm = ({ onOpenChange }: NewProjectFormProps) => {
|
||||
name,
|
||||
description,
|
||||
kmsKeyId,
|
||||
template
|
||||
template,
|
||||
type
|
||||
}: TAddProjectFormData) => {
|
||||
// type check
|
||||
if (!currentOrg) return;
|
||||
@@ -113,7 +144,8 @@ const NewProjectForm = ({ onOpenChange }: NewProjectFormProps) => {
|
||||
projectName: name,
|
||||
projectDescription: description,
|
||||
kmsKeyId: kmsKeyId !== INTERNAL_KMS_KEY_ID ? kmsKeyId : undefined,
|
||||
template
|
||||
template,
|
||||
type
|
||||
});
|
||||
await refetchWorkspaces();
|
||||
|
||||
@@ -121,7 +153,7 @@ const NewProjectForm = ({ onOpenChange }: NewProjectFormProps) => {
|
||||
reset();
|
||||
onOpenChange(false);
|
||||
navigate({
|
||||
to: getProjectHomePage(project.defaultProduct),
|
||||
to: getProjectHomePage(project.type),
|
||||
params: { projectId: project.id }
|
||||
});
|
||||
} catch (err) {
|
||||
@@ -150,6 +182,42 @@ const NewProjectForm = ({ onOpenChange }: NewProjectFormProps) => {
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name="type"
|
||||
defaultValue={ProjectType.SecretManager}
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Project Type"
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
className="flex-1"
|
||||
>
|
||||
<div className="mt-2 grid grid-cols-5 gap-4">
|
||||
{PROJECT_TYPE_MENU_ITEMS.map((el) => (
|
||||
<div
|
||||
key={el.value}
|
||||
className={twMerge(
|
||||
"flex cursor-pointer flex-col items-center gap-2 rounded border border-mineshaft-600 p-4 opacity-75 transition-all hover:border-primary-400 hover:bg-mineshaft-600",
|
||||
field.value === el.value && "border-primary-400 bg-mineshaft-600 opacity-100"
|
||||
)}
|
||||
onClick={() => field.onChange(el.value)}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
field.onChange(el.value);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Lottie icon={getProjectLottieIcon(el.value)} className="h-8 w-8" />
|
||||
<div className="text-center text-xs">{el.label}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name="description"
|
||||
@@ -171,6 +239,7 @@ const NewProjectForm = ({ onOpenChange }: NewProjectFormProps) => {
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Controller
|
||||
control={control}
|
||||
name="template"
|
||||
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
import { Controller, useFormContext, useWatch } from "react-hook-form";
|
||||
import { SingleValue } from "react-select";
|
||||
|
||||
import { SecretSyncConnectionField } from "@app/components/secret-syncs/forms/SecretSyncConnectionField";
|
||||
import { FilterableSelect, FormControl } from "@app/components/v2";
|
||||
import {
|
||||
TBitbucketEnvironment,
|
||||
TBitbucketRepo,
|
||||
TBitbucketWorkspace,
|
||||
useBitbucketConnectionListEnvironments,
|
||||
useBitbucketConnectionListRepositories,
|
||||
useBitbucketConnectionListWorkspaces
|
||||
} from "@app/hooks/api/appConnections/bitbucket";
|
||||
import { SecretSync } from "@app/hooks/api/secretSyncs";
|
||||
|
||||
import { TSecretSyncForm } from "../schemas";
|
||||
|
||||
export const BitbucketSyncFields = () => {
|
||||
const { control, setValue } = useFormContext<
|
||||
TSecretSyncForm & { destination: SecretSync.Bitbucket }
|
||||
>();
|
||||
|
||||
const connectionId = useWatch({ name: "connection.id", control });
|
||||
const workspace = useWatch({ name: "destinationConfig.workspaceSlug", control });
|
||||
const repository = useWatch({ name: "destinationConfig.repositorySlug", control });
|
||||
|
||||
const { data: workspaces = [], isPending: isWorkspacesLoading } =
|
||||
useBitbucketConnectionListWorkspaces(connectionId, {
|
||||
enabled: Boolean(connectionId)
|
||||
});
|
||||
|
||||
const { data: repositories = [], isPending: isRepositoriesLoading } =
|
||||
useBitbucketConnectionListRepositories(connectionId, workspace ?? "", {
|
||||
enabled: Boolean(connectionId) && Boolean(workspace)
|
||||
});
|
||||
|
||||
const { data: environments = [], isPending: isEnvironmentsLoading } =
|
||||
useBitbucketConnectionListEnvironments(connectionId, workspace ?? "", repository ?? "", {
|
||||
enabled: Boolean(connectionId) && Boolean(workspace) && Boolean(repository)
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<SecretSyncConnectionField
|
||||
onChange={() => {
|
||||
setValue("destinationConfig.workspaceSlug", "");
|
||||
setValue("destinationConfig.repositorySlug", "");
|
||||
setValue("destinationConfig.environmentId", "");
|
||||
}}
|
||||
/>
|
||||
|
||||
<Controller
|
||||
name="destinationConfig.workspaceSlug"
|
||||
control={control}
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
label="Bitbucket Workspace"
|
||||
tooltipClassName="max-w-md"
|
||||
>
|
||||
<FilterableSelect
|
||||
isLoading={isWorkspacesLoading && Boolean(connectionId)}
|
||||
isDisabled={!connectionId}
|
||||
value={workspaces.find((w) => w.slug === value) ?? null}
|
||||
onChange={(option) => {
|
||||
const v = option as SingleValue<TBitbucketWorkspace>;
|
||||
onChange(v?.slug ?? "");
|
||||
// Clear downstream selections
|
||||
setValue("destinationConfig.repositorySlug", "");
|
||||
setValue("destinationConfig.environmentId", "");
|
||||
}}
|
||||
options={workspaces}
|
||||
placeholder="Select workspace..."
|
||||
getOptionLabel={(option) => option.slug}
|
||||
getOptionValue={(option) => option.slug}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Controller
|
||||
name="destinationConfig.repositorySlug"
|
||||
control={control}
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
label="Bitbucket Repository"
|
||||
tooltipClassName="max-w-md"
|
||||
>
|
||||
<FilterableSelect
|
||||
isLoading={isRepositoriesLoading && Boolean(workspace)}
|
||||
isDisabled={!workspace}
|
||||
value={repositories.find((r) => r.slug === value) ?? null}
|
||||
onChange={(option) => {
|
||||
const v = option as SingleValue<TBitbucketRepo>;
|
||||
onChange(v?.slug ?? "");
|
||||
// Clear downstream selections
|
||||
setValue("destinationConfig.environmentId", "");
|
||||
}}
|
||||
options={repositories}
|
||||
placeholder="Select repository..."
|
||||
getOptionLabel={(option) => option.full_name}
|
||||
getOptionValue={(option) => option.slug}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Controller
|
||||
name="destinationConfig.environmentId"
|
||||
control={control}
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
isOptional
|
||||
label="Bitbucket Deployment Environment"
|
||||
tooltipClassName="max-w-md"
|
||||
>
|
||||
<FilterableSelect
|
||||
isLoading={isEnvironmentsLoading && Boolean(repository)}
|
||||
isDisabled={!repository}
|
||||
value={environments.find((e) => e.uuid === value) ?? null}
|
||||
onChange={(option) => {
|
||||
const v = option as SingleValue<TBitbucketEnvironment>;
|
||||
onChange(v?.uuid ?? "");
|
||||
}}
|
||||
options={environments}
|
||||
placeholder="Select environment..."
|
||||
getOptionLabel={(option) => option.name}
|
||||
getOptionValue={(option) => option.uuid}
|
||||
isClearable
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
import { Controller, useFormContext, useWatch } from "react-hook-form";
|
||||
import { SingleValue } from "react-select";
|
||||
|
||||
import { SecretSyncConnectionField } from "@app/components/secret-syncs/forms/SecretSyncConnectionField";
|
||||
import { FilterableSelect, FormControl } from "@app/components/v2";
|
||||
import { useDigitalOceanConnectionListApps } from "@app/hooks/api/appConnections/digital-ocean";
|
||||
import { TDigitalOceanApp } from "@app/hooks/api/appConnections/digital-ocean/types";
|
||||
import { SecretSync } from "@app/hooks/api/secretSyncs";
|
||||
|
||||
import { TSecretSyncForm } from "../schemas";
|
||||
|
||||
export const DigitalOceanAppPlatformSyncFields = () => {
|
||||
const { control, setValue } = useFormContext<
|
||||
TSecretSyncForm & { destination: SecretSync.DigitalOceanAppPlatform }
|
||||
>();
|
||||
|
||||
const connectionId = useWatch({ name: "connection.id", control });
|
||||
|
||||
const { data: apps = [], isPending: isAccountsLoading } = useDigitalOceanConnectionListApps(
|
||||
connectionId,
|
||||
{
|
||||
enabled: Boolean(connectionId)
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<SecretSyncConnectionField
|
||||
onChange={() => {
|
||||
setValue("destinationConfig.appId", "");
|
||||
setValue("destinationConfig.appName", "");
|
||||
}}
|
||||
/>
|
||||
<Controller
|
||||
name="destinationConfig.appId"
|
||||
control={control}
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
label="Select an app"
|
||||
tooltipClassName="max-w-md"
|
||||
>
|
||||
<FilterableSelect
|
||||
isLoading={isAccountsLoading && Boolean(connectionId)}
|
||||
isDisabled={!connectionId}
|
||||
value={apps.find((p) => p.id === value) ?? null}
|
||||
onChange={(option) => {
|
||||
const v = option as SingleValue<TDigitalOceanApp>;
|
||||
onChange(v?.id ?? null);
|
||||
setValue("destinationConfig.appName", v?.spec.name ?? "");
|
||||
}}
|
||||
options={apps}
|
||||
placeholder="Select an app..."
|
||||
getOptionLabel={(option) => option.spec.name}
|
||||
getOptionValue={(option) => option.id}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
+6
@@ -9,11 +9,13 @@ import { AwsSecretsManagerSyncFields } from "./AwsSecretsManagerSyncFields";
|
||||
import { AzureAppConfigurationSyncFields } from "./AzureAppConfigurationSyncFields";
|
||||
import { AzureDevOpsSyncFields } from "./AzureDevOpsSyncFields";
|
||||
import { AzureKeyVaultSyncFields } from "./AzureKeyVaultSyncFields";
|
||||
import { BitbucketSyncFields } from "./BitbucketSyncFields";
|
||||
import { CamundaSyncFields } from "./CamundaSyncFields";
|
||||
import { ChecklySyncFields } from "./ChecklySyncFields";
|
||||
import { CloudflarePagesSyncFields } from "./CloudflarePagesSyncFields";
|
||||
import { CloudflareWorkersSyncFields } from "./CloudflareWorkersSyncFields";
|
||||
import { DatabricksSyncFields } from "./DatabricksSyncFields";
|
||||
import { DigitalOceanAppPlatformSyncFields } from "./DigitalOceanAppPlatformSyncFields";
|
||||
import { FlyioSyncFields } from "./FlyioSyncFields";
|
||||
import { GcpSyncFields } from "./GcpSyncFields";
|
||||
import { GitHubSyncFields } from "./GitHubSyncFields";
|
||||
@@ -91,6 +93,10 @@ export const SecretSyncDestinationFields = () => {
|
||||
return <ChecklySyncFields />;
|
||||
case SecretSync.Supabase:
|
||||
return <SupabaseSyncFields />;
|
||||
case SecretSync.DigitalOceanAppPlatform:
|
||||
return <DigitalOceanAppPlatformSyncFields />;
|
||||
case SecretSync.Bitbucket:
|
||||
return <BitbucketSyncFields />;
|
||||
default:
|
||||
throw new Error(`Unhandled Destination Config Field: ${destination}`);
|
||||
}
|
||||
|
||||
+2
@@ -63,6 +63,8 @@ export const SecretSyncOptionsFields = ({ hideInitialSync }: Props) => {
|
||||
case SecretSync.Railway:
|
||||
case SecretSync.Checkly:
|
||||
case SecretSync.Supabase:
|
||||
case SecretSync.DigitalOceanAppPlatform:
|
||||
case SecretSync.Bitbucket:
|
||||
AdditionalSyncOptionsFieldsComponent = null;
|
||||
break;
|
||||
default:
|
||||
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
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 BitbucketSyncReviewFields = () => {
|
||||
const { watch } = useFormContext<TSecretSyncForm & { destination: SecretSync.Bitbucket }>();
|
||||
const repository = watch("destinationConfig.repositorySlug");
|
||||
const environment = watch("destinationConfig.environmentId");
|
||||
const workspace = watch("destinationConfig.workspaceSlug");
|
||||
|
||||
return (
|
||||
<>
|
||||
<GenericFieldLabel label="Repository">{repository}</GenericFieldLabel>
|
||||
<GenericFieldLabel label="Environment">{environment}</GenericFieldLabel>
|
||||
<GenericFieldLabel label="Workspace">{workspace}</GenericFieldLabel>
|
||||
</>
|
||||
);
|
||||
};
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
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 DigitalOceanAppPlatformSyncReviewFields = () => {
|
||||
const { watch } = useFormContext<
|
||||
TSecretSyncForm & { destination: SecretSync.DigitalOceanAppPlatform }
|
||||
>();
|
||||
const appName = watch("destinationConfig.appName");
|
||||
|
||||
return <GenericFieldLabel label="App">{appName}</GenericFieldLabel>;
|
||||
};
|
||||
+8
@@ -18,11 +18,13 @@ import {
|
||||
import { AzureAppConfigurationSyncReviewFields } from "./AzureAppConfigurationSyncReviewFields";
|
||||
import { AzureDevOpsSyncReviewFields } from "./AzureDevOpsSyncReviewFields";
|
||||
import { AzureKeyVaultSyncReviewFields } from "./AzureKeyVaultSyncReviewFields";
|
||||
import { BitbucketSyncReviewFields } from "./BitbucketSyncReviewFields";
|
||||
import { CamundaSyncReviewFields } from "./CamundaSyncReviewFields";
|
||||
import { ChecklySyncReviewFields } from "./ChecklySyncReviewFields";
|
||||
import { CloudflarePagesSyncReviewFields } from "./CloudflarePagesReviewFields";
|
||||
import { CloudflareWorkersSyncReviewFields } from "./CloudflareWorkersReviewFields";
|
||||
import { DatabricksSyncReviewFields } from "./DatabricksSyncReviewFields";
|
||||
import { DigitalOceanAppPlatformSyncReviewFields } from "./DigitalOceanAppPlatformSyncReviewFields";
|
||||
import { FlyioSyncReviewFields } from "./FlyioSyncReviewFields";
|
||||
import { GcpSyncReviewFields } from "./GcpSyncReviewFields";
|
||||
import { GitHubSyncReviewFields } from "./GitHubSyncReviewFields";
|
||||
@@ -144,6 +146,12 @@ export const SecretSyncReviewFields = () => {
|
||||
case SecretSync.Supabase:
|
||||
DestinationFieldsComponent = <SupabaseSyncReviewFields />;
|
||||
break;
|
||||
case SecretSync.DigitalOceanAppPlatform:
|
||||
DestinationFieldsComponent = <DigitalOceanAppPlatformSyncReviewFields />;
|
||||
break;
|
||||
case SecretSync.Bitbucket:
|
||||
DestinationFieldsComponent = <BitbucketSyncReviewFields />;
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unhandled Destination Review Fields: ${destination}`);
|
||||
}
|
||||
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
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 BitbucketSyncDestinationSchema = BaseSecretSyncSchema().merge(
|
||||
z.object({
|
||||
destination: z.literal(SecretSync.Bitbucket),
|
||||
destinationConfig: z.object({
|
||||
repositorySlug: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1, "Repository slug required")
|
||||
.describe("Repository slug"),
|
||||
environmentId: z.string().trim().optional().describe("Deployment environment uuid"),
|
||||
workspaceSlug: z.string().trim().min(1, "Workspace slug required").describe("Workspace slug")
|
||||
})
|
||||
})
|
||||
);
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
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 DigitalOceanAppPlatformSyncDestinationSchema = BaseSecretSyncSchema().merge(
|
||||
z.object({
|
||||
destination: z.literal(SecretSync.DigitalOceanAppPlatform),
|
||||
destinationConfig: z.object({
|
||||
appId: z
|
||||
.string()
|
||||
.min(1, "App ID is required")
|
||||
.max(255, "App ID must be less than 255 characters"),
|
||||
appName: z
|
||||
.string()
|
||||
.min(1, "Account Name is required")
|
||||
.max(255, "App Name must be less than 255 characters")
|
||||
})
|
||||
})
|
||||
);
|
||||
@@ -6,11 +6,13 @@ import { AwsSecretsManagerSyncDestinationSchema } from "./aws-secrets-manager-sy
|
||||
import { AzureAppConfigurationSyncDestinationSchema } from "./azure-app-configuration-sync-destination-schema";
|
||||
import { AzureDevOpsSyncDestinationSchema } from "./azure-devops-sync-destination-schema";
|
||||
import { AzureKeyVaultSyncDestinationSchema } from "./azure-key-vault-sync-destination-schema";
|
||||
import { BitbucketSyncDestinationSchema } from "./bitbucket-sync-destination-schema";
|
||||
import { CamundaSyncDestinationSchema } from "./camunda-sync-destination-schema";
|
||||
import { ChecklySyncDestinationSchema } from "./checkly-sync-destination-schema";
|
||||
import { CloudflarePagesSyncDestinationSchema } from "./cloudflare-pages-sync-destination-schema";
|
||||
import { CloudflareWorkersSyncDestinationSchema } from "./cloudflare-workers-sync-destination-schema";
|
||||
import { DatabricksSyncDestinationSchema } from "./databricks-sync-destination-schema";
|
||||
import { DigitalOceanAppPlatformSyncDestinationSchema } from "./digital-ocean-app-platform-sync-destination-schema";
|
||||
import { FlyioSyncDestinationSchema } from "./flyio-sync-destination-schema";
|
||||
import { GcpSyncDestinationSchema } from "./gcp-sync-destination-schema";
|
||||
import { GitHubSyncDestinationSchema } from "./github-sync-destination-schema";
|
||||
@@ -55,7 +57,9 @@ const SecretSyncUnionSchema = z.discriminatedUnion("destination", [
|
||||
SupabaseSyncDestinationSchema,
|
||||
ZabbixSyncDestinationSchema,
|
||||
RailwaySyncDestinationSchema,
|
||||
ChecklySyncDestinationSchema
|
||||
ChecklySyncDestinationSchema,
|
||||
DigitalOceanAppPlatformSyncDestinationSchema,
|
||||
BitbucketSyncDestinationSchema
|
||||
]);
|
||||
|
||||
export const SecretSyncFormSchema = SecretSyncUnionSchema;
|
||||
|
||||
@@ -20,6 +20,7 @@ export type DatePickerProps = Omit<DayPickerProps, "selected"> & {
|
||||
popUpProps: PopoverProps;
|
||||
popUpContentProps: PopoverContentProps;
|
||||
dateFormat?: "PPP" | "PP" | "P"; // extend as needed
|
||||
hideTime?: boolean;
|
||||
buttonClassName?: string;
|
||||
timezone?: Timezone;
|
||||
};
|
||||
@@ -55,6 +56,7 @@ export const DatePicker = ({
|
||||
popUpProps,
|
||||
popUpContentProps,
|
||||
dateFormat = "PPP",
|
||||
hideTime = false,
|
||||
buttonClassName,
|
||||
timezone,
|
||||
...props
|
||||
@@ -124,14 +126,16 @@ export const DatePicker = ({
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="mx-4 my-4">
|
||||
<Input
|
||||
type="time"
|
||||
value={displayUtc ? localTimeToUTC(timeValue) : timeValue}
|
||||
onChange={handleTimeChange}
|
||||
className="bg-mineshaft-700 text-white [color-scheme:dark]"
|
||||
/>
|
||||
</div>
|
||||
{!hideTime && (
|
||||
<div className="mx-4 my-4">
|
||||
<Input
|
||||
type="time"
|
||||
value={displayUtc ? localTimeToUTC(timeValue) : timeValue}
|
||||
onChange={handleTimeChange}
|
||||
className="bg-mineshaft-700 text-white [color-scheme:dark]"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
|
||||
+132
-132
@@ -70,276 +70,276 @@ export const ROUTE_PATHS = Object.freeze({
|
||||
},
|
||||
SecretManager: {
|
||||
ApprovalPage: setRoute(
|
||||
"/projects/$projectId/secret-manager/approval",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/approval"
|
||||
"/projects/secret-management/$projectId/approval",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/approval"
|
||||
),
|
||||
SecretDashboardPage: setRoute(
|
||||
"/projects/$projectId/secret-manager/secrets/$envSlug",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/secrets/$envSlug"
|
||||
"/projects/secret-management/$projectId/secrets/$envSlug",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/secrets/$envSlug"
|
||||
),
|
||||
RollbackPreviewPage: setRoute(
|
||||
"/projects/$projectId/secret-manager/commits/$environment/$folderId/$commitId/restore",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/commits/$environment/$folderId/$commitId/restore"
|
||||
"/projects/secret-management/$projectId/commits/$environment/$folderId/$commitId/restore",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/commits/$environment/$folderId/$commitId/restore"
|
||||
),
|
||||
CommitDetailsPage: setRoute(
|
||||
"/projects/$projectId/secret-manager/commits/$environment/$folderId/$commitId",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/commits/$environment/$folderId/$commitId"
|
||||
"/projects/secret-management/$projectId/commits/$environment/$folderId/$commitId",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/commits/$environment/$folderId/$commitId"
|
||||
),
|
||||
CommitsPage: setRoute(
|
||||
"/projects/$projectId/secret-manager/commits/$environment/$folderId",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/commits/$environment/$folderId"
|
||||
"/projects/secret-management/$projectId/commits/$environment/$folderId",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/commits/$environment/$folderId"
|
||||
),
|
||||
OverviewPage: setRoute(
|
||||
"/projects/$projectId/secret-manager/overview",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/overview"
|
||||
"/projects/secret-management/$projectId/overview",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/overview"
|
||||
),
|
||||
IntegrationsListPage: setRoute(
|
||||
"/projects/$projectId/secret-manager/integrations",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/"
|
||||
"/projects/secret-management/$projectId/integrations",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/"
|
||||
),
|
||||
IntegrationDetailsByIDPage: setRoute(
|
||||
"/projects/$projectId/secret-manager/integrations/$integrationId",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/$integrationId"
|
||||
"/projects/secret-management/$projectId/integrations/$integrationId",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/$integrationId"
|
||||
),
|
||||
SecretSyncDetailsByIDPage: setRoute(
|
||||
"/projects/$projectId/secret-manager/integrations/secret-syncs/$destination/$syncId",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/secret-syncs/$destination/$syncId"
|
||||
"/projects/secret-management/$projectId/integrations/secret-syncs/$destination/$syncId",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/secret-syncs/$destination/$syncId"
|
||||
),
|
||||
Integratons: {
|
||||
SelectIntegrationAuth: setRoute(
|
||||
"/projects/$projectId/secret-manager/integrations/select-integration-auth",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/select-integration-auth"
|
||||
"/projects/secret-management/$projectId/integrations/select-integration-auth",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/select-integration-auth"
|
||||
),
|
||||
HerokuOauthCallbackPage: setRoute(
|
||||
"/projects/$projectId/secret-manager/integrations/heroku/oauth2/callback",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/heroku/oauth2/callback"
|
||||
"/projects/secret-management/$projectId/integrations/heroku/oauth2/callback",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/heroku/oauth2/callback"
|
||||
),
|
||||
HerokuConfigurePage: setRoute(
|
||||
"/projects/$projectId/secret-manager/integrations/heroku/create",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/heroku/create"
|
||||
"/projects/secret-management/$projectId/integrations/heroku/create",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/heroku/create"
|
||||
),
|
||||
AwsParameterStoreConfigurePage: setRoute(
|
||||
"/projects/$projectId/secret-manager/integrations/aws-parameter-store/create",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/aws-parameter-store/create"
|
||||
"/projects/secret-management/$projectId/integrations/aws-parameter-store/create",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/aws-parameter-store/create"
|
||||
),
|
||||
AwsSecretManagerConfigurePage: setRoute(
|
||||
"/projects/$projectId/secret-manager/integrations/aws-secret-manager/create",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/aws-secret-manager/create"
|
||||
"/projects/secret-management/$projectId/integrations/aws-secret-manager/create",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/aws-secret-manager/create"
|
||||
),
|
||||
AzureAppConfigurationsOauthCallbackPage: setRoute(
|
||||
"/projects/$projectId/secret-manager/integrations/azure-app-configuration/oauth2/callback",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/azure-app-configuration/oauth2/callback"
|
||||
"/projects/secret-management/$projectId/integrations/azure-app-configuration/oauth2/callback",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/azure-app-configuration/oauth2/callback"
|
||||
),
|
||||
AzureAppConfigurationsConfigurePage: setRoute(
|
||||
"/projects/$projectId/secret-manager/integrations/azure-app-configuration/create",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/azure-app-configuration/create"
|
||||
"/projects/secret-management/$projectId/integrations/azure-app-configuration/create",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/azure-app-configuration/create"
|
||||
),
|
||||
AzureDevopsConfigurePage: setRoute(
|
||||
"/projects/$projectId/secret-manager/integrations/azure-devops/create",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/azure-devops/create"
|
||||
"/projects/secret-management/$projectId/integrations/azure-devops/create",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/azure-devops/create"
|
||||
),
|
||||
AzureKeyVaultAuthorizePage: setRoute(
|
||||
"/projects/$projectId/secret-manager/integrations/azure-key-vault/authorize",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/azure-key-vault/authorize"
|
||||
"/projects/secret-management/$projectId/integrations/azure-key-vault/authorize",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/azure-key-vault/authorize"
|
||||
),
|
||||
AzureKeyVaultOauthCallbackPage: setRoute(
|
||||
"/projects/$projectId/secret-manager/integrations/azure-key-vault/oauth2/callback",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/azure-key-vault/oauth2/callback"
|
||||
"/projects/secret-management/$projectId/integrations/azure-key-vault/oauth2/callback",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/azure-key-vault/oauth2/callback"
|
||||
),
|
||||
AzureKeyVaultConfigurePage: setRoute(
|
||||
"/projects/$projectId/secret-manager/integrations/azure-key-vault/create",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/azure-key-vault/create"
|
||||
"/projects/secret-management/$projectId/integrations/azure-key-vault/create",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/azure-key-vault/create"
|
||||
),
|
||||
BitbucketOauthCallbackPage: setRoute(
|
||||
"/projects/$projectId/secret-manager/integrations/bitbucket/oauth2/callback",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/bitbucket/oauth2/callback"
|
||||
"/projects/secret-management/$projectId/integrations/bitbucket/oauth2/callback",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/bitbucket/oauth2/callback"
|
||||
),
|
||||
BitbucketConfigurePage: setRoute(
|
||||
"/projects/$projectId/secret-manager/integrations/bitbucket/create",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/bitbucket/create"
|
||||
"/projects/secret-management/$projectId/integrations/bitbucket/create",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/bitbucket/create"
|
||||
),
|
||||
ChecklyConfigurePage: setRoute(
|
||||
"/projects/$projectId/secret-manager/integrations/checkly/create",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/checkly/create"
|
||||
"/projects/secret-management/$projectId/integrations/checkly/create",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/checkly/create"
|
||||
),
|
||||
CircleConfigurePage: setRoute(
|
||||
"/projects/$projectId/secret-manager/integrations/circleci/create",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/circleci/create"
|
||||
"/projects/secret-management/$projectId/integrations/circleci/create",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/circleci/create"
|
||||
),
|
||||
CloudflarePagesConfigurePage: setRoute(
|
||||
"/projects/$projectId/secret-manager/integrations/cloudflare-pages/create",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/cloudflare-pages/create"
|
||||
"/projects/secret-management/$projectId/integrations/cloudflare-pages/create",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/cloudflare-pages/create"
|
||||
),
|
||||
DigitalOceanAppPlatformConfigurePage: setRoute(
|
||||
"/projects/$projectId/secret-manager/integrations/digital-ocean-app-platform/create",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/digital-ocean-app-platform/create"
|
||||
"/projects/secret-management/$projectId/integrations/digital-ocean-app-platform/create",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/digital-ocean-app-platform/create"
|
||||
),
|
||||
CloudflareWorkersConfigurePage: setRoute(
|
||||
"/projects/$projectId/secret-manager/integrations/cloudflare-workers/create",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/cloudflare-workers/create"
|
||||
"/projects/secret-management/$projectId/integrations/cloudflare-workers/create",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/cloudflare-workers/create"
|
||||
),
|
||||
CodefreshConfigurePage: setRoute(
|
||||
"/projects/$projectId/secret-manager/integrations/codefresh/create",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/codefresh/create"
|
||||
"/projects/secret-management/$projectId/integrations/codefresh/create",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/codefresh/create"
|
||||
),
|
||||
GcpSecretManagerConfigurePage: setRoute(
|
||||
"/projects/$projectId/secret-manager/integrations/gcp-secret-manager/create",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/gcp-secret-manager/create"
|
||||
"/projects/secret-management/$projectId/integrations/gcp-secret-manager/create",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/gcp-secret-manager/create"
|
||||
),
|
||||
GcpSecretManagerOauthCallbackPage: setRoute(
|
||||
"/projects/$projectId/secret-manager/integrations/gcp-secret-manager/oauth2/callback",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/gcp-secret-manager/oauth2/callback"
|
||||
"/projects/secret-management/$projectId/integrations/gcp-secret-manager/oauth2/callback",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/gcp-secret-manager/oauth2/callback"
|
||||
),
|
||||
GithubConfigurePage: setRoute(
|
||||
"/projects/$projectId/secret-manager/integrations/github/create",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/github/create"
|
||||
"/projects/secret-management/$projectId/integrations/github/create",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/github/create"
|
||||
),
|
||||
GithubOauthCallbackPage: setRoute(
|
||||
"/projects/$projectId/secret-manager/integrations/github/oauth2/callback",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/github/oauth2/callback"
|
||||
"/projects/secret-management/$projectId/integrations/github/oauth2/callback",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/github/oauth2/callback"
|
||||
),
|
||||
GitlabConfigurePage: setRoute(
|
||||
"/projects/$projectId/secret-manager/integrations/gitlab/create",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/gitlab/create"
|
||||
"/projects/secret-management/$projectId/integrations/gitlab/create",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/gitlab/create"
|
||||
),
|
||||
GitlabOauthCallbackPage: setRoute(
|
||||
"/projects/$projectId/secret-manager/integrations/gitlab/oauth2/callback",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/gitlab/oauth2/callback"
|
||||
"/projects/secret-management/$projectId/integrations/gitlab/oauth2/callback",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/gitlab/oauth2/callback"
|
||||
),
|
||||
VercelOauthCallbackPage: setRoute(
|
||||
"/projects/$projectId/secret-manager/integrations/vercel/oauth2/callback",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/vercel/oauth2/callback"
|
||||
"/projects/secret-management/$projectId/integrations/vercel/oauth2/callback",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/vercel/oauth2/callback"
|
||||
),
|
||||
VercelConfigurePage: setRoute(
|
||||
"/projects/$projectId/secret-manager/integrations/vercel/create",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/vercel/create"
|
||||
"/projects/secret-management/$projectId/integrations/vercel/create",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/vercel/create"
|
||||
),
|
||||
FlyioConfigurePage: setRoute(
|
||||
"/projects/$projectId/secret-manager/integrations/flyio/create",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/flyio/create"
|
||||
"/projects/secret-management/$projectId/integrations/flyio/create",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/flyio/create"
|
||||
),
|
||||
HashicorpVaultConfigurePage: setRoute(
|
||||
"/projects/$projectId/secret-manager/integrations/hashicorp-vault/create",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/hashicorp-vault/create"
|
||||
"/projects/secret-management/$projectId/integrations/hashicorp-vault/create",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/hashicorp-vault/create"
|
||||
),
|
||||
HasuraCloudConfigurePage: setRoute(
|
||||
"/projects/$projectId/secret-manager/integrations/hasura-cloud/create",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/hasura-cloud/create"
|
||||
"/projects/secret-management/$projectId/integrations/hasura-cloud/create",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/hasura-cloud/create"
|
||||
),
|
||||
LaravelForgeConfigurePage: setRoute(
|
||||
"/projects/$projectId/secret-manager/integrations/laravel-forge/create",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/laravel-forge/create"
|
||||
"/projects/secret-management/$projectId/integrations/laravel-forge/create",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/laravel-forge/create"
|
||||
),
|
||||
NorthflankConfigurePage: setRoute(
|
||||
"/projects/$projectId/secret-manager/integrations/northflank/create",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/northflank/create"
|
||||
"/projects/secret-management/$projectId/integrations/northflank/create",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/northflank/create"
|
||||
),
|
||||
RailwayConfigurePage: setRoute(
|
||||
"/projects/$projectId/secret-manager/integrations/railway/create",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/railway/create"
|
||||
"/projects/secret-management/$projectId/integrations/railway/create",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/railway/create"
|
||||
),
|
||||
RenderConfigurePage: setRoute(
|
||||
"/projects/$projectId/secret-manager/integrations/render/create",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/render/create"
|
||||
"/projects/secret-management/$projectId/integrations/render/create",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/render/create"
|
||||
),
|
||||
RundeckConfigurePage: setRoute(
|
||||
"/projects/$projectId/secret-manager/integrations/rundeck/create",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/rundeck/create"
|
||||
"/projects/secret-management/$projectId/integrations/rundeck/create",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/rundeck/create"
|
||||
),
|
||||
WindmillConfigurePage: setRoute(
|
||||
"/projects/$projectId/secret-manager/integrations/windmill/create",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/windmill/create"
|
||||
"/projects/secret-management/$projectId/integrations/windmill/create",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/windmill/create"
|
||||
),
|
||||
TravisCIConfigurePage: setRoute(
|
||||
"/projects/$projectId/secret-manager/integrations/travisci/create",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/travisci/create"
|
||||
"/projects/secret-management/$projectId/integrations/travisci/create",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/travisci/create"
|
||||
),
|
||||
TerraformCloudConfigurePage: setRoute(
|
||||
"/projects/$projectId/secret-manager/integrations/terraform-cloud/create",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/terraform-cloud/create"
|
||||
"/projects/secret-management/$projectId/integrations/terraform-cloud/create",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/terraform-cloud/create"
|
||||
),
|
||||
TeamcityConfigurePage: setRoute(
|
||||
"/projects/$projectId/secret-manager/integrations/teamcity/create",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/teamcity/create"
|
||||
"/projects/secret-management/$projectId/integrations/teamcity/create",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/teamcity/create"
|
||||
),
|
||||
SupabaseConfigurePage: setRoute(
|
||||
"/projects/$projectId/secret-manager/integrations/supabase/create",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/supabase/create"
|
||||
"/projects/secret-management/$projectId/integrations/supabase/create",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/supabase/create"
|
||||
),
|
||||
OctopusDeployCloudConfigurePage: setRoute(
|
||||
"/projects/$projectId/secret-manager/integrations/octopus-deploy/create",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/octopus-deploy/create"
|
||||
"/projects/secret-management/$projectId/integrations/octopus-deploy/create",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/octopus-deploy/create"
|
||||
),
|
||||
DatabricksConfigurePage: setRoute(
|
||||
"/projects/$projectId/secret-manager/integrations/databricks/create",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/databricks/create"
|
||||
"/projects/secret-management/$projectId/integrations/databricks/create",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/databricks/create"
|
||||
),
|
||||
QoveryConfigurePage: setRoute(
|
||||
"/projects/$projectId/secret-manager/integrations/qovery/create",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/qovery/create"
|
||||
"/projects/secret-management/$projectId/integrations/qovery/create",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/qovery/create"
|
||||
),
|
||||
Cloud66ConfigurePage: setRoute(
|
||||
"/projects/$projectId/secret-manager/integrations/cloud-66/create",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/cloud-66/create"
|
||||
"/projects/secret-management/$projectId/integrations/cloud-66/create",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/cloud-66/create"
|
||||
),
|
||||
NetlifyConfigurePage: setRoute(
|
||||
"/projects/$projectId/secret-manager/integrations/netlify/create",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/netlify/create"
|
||||
"/projects/secret-management/$projectId/integrations/netlify/create",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/netlify/create"
|
||||
),
|
||||
NetlifyOuathCallbackPage: setRoute(
|
||||
"/projects/$projectId/secret-manager/integrations/netlify/oauth2/callback",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/netlify/oauth2/callback"
|
||||
"/projects/secret-management/$projectId/integrations/netlify/oauth2/callback",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/netlify/oauth2/callback"
|
||||
)
|
||||
}
|
||||
},
|
||||
CertManager: {
|
||||
CertAuthDetailsByIDPage: setRoute(
|
||||
"/projects/$projectId/cert-manager/ca/$caName",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/cert-manager/_cert-manager-layout/ca/$caName"
|
||||
"/projects/cert-management/$projectId/ca/$caName",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/ca/$caName"
|
||||
),
|
||||
SubscribersPage: setRoute(
|
||||
"/projects/$projectId/cert-manager/subscribers",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/cert-manager/_cert-manager-layout/subscribers"
|
||||
"/projects/cert-management/$projectId/subscribers",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/subscribers"
|
||||
),
|
||||
CertificatesPage: setRoute(
|
||||
"/projects/$projectId/cert-manager/certificates",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/cert-manager/_cert-manager-layout/certificates"
|
||||
"/projects/cert-management/$projectId/certificates",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/certificates"
|
||||
),
|
||||
CertificateAuthoritiesPage: setRoute(
|
||||
"/projects/$projectId/cert-manager/certificate-authorities",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/cert-manager/_cert-manager-layout/certificate-authorities"
|
||||
"/projects/cert-management/$projectId/certificate-authorities",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/certificate-authorities"
|
||||
),
|
||||
AlertingPage: setRoute(
|
||||
"/projects/$projectId/cert-manager/alerting",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/cert-manager/_cert-manager-layout/alerting"
|
||||
"/projects/cert-management/$projectId/alerting",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/alerting"
|
||||
),
|
||||
PkiCollectionDetailsByIDPage: setRoute(
|
||||
"/projects/$projectId/cert-manager/pki-collections/$collectionId",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/cert-manager/_cert-manager-layout/pki-collections/$collectionId"
|
||||
"/projects/cert-management/$projectId/pki-collections/$collectionId",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/pki-collections/$collectionId"
|
||||
),
|
||||
PkiSubscriberDetailsByIDPage: setRoute(
|
||||
"/projects/$projectId/cert-manager/subscribers/$subscriberName",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/cert-manager/_cert-manager-layout/subscribers/$subscriberName"
|
||||
"/projects/cert-management/$projectId/subscribers/$subscriberName",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/subscribers/$subscriberName"
|
||||
)
|
||||
},
|
||||
Ssh: {
|
||||
SshCaByIDPage: setRoute(
|
||||
"/projects/$projectId/ssh/ca/$caId",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/ssh/_ssh-layout/ca/$caId"
|
||||
"/projects/ssh/$projectId/ca/$caId",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/ssh/$projectId/_ssh-layout/ca/$caId"
|
||||
),
|
||||
SshHostGroupDetailsByIDPage: setRoute(
|
||||
"/projects/$projectId/ssh/ssh-host-groups/$sshHostGroupId",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/ssh/_ssh-layout/ssh-host-groups/$sshHostGroupId"
|
||||
"/projects/ssh/$projectId/ssh-host-groups/$sshHostGroupId",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/ssh/$projectId/_ssh-layout/ssh-host-groups/$sshHostGroupId"
|
||||
)
|
||||
},
|
||||
SecretScanning: {
|
||||
DataSourceByIdPage: setRoute(
|
||||
"/projects/$projectId/secret-scanning/data-sources/$type/$dataSourceId",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-scanning/_secret-scanning-layout/data-sources/$type/$dataSourceId"
|
||||
"/projects/secret-scanning/$projectId/data-sources/$type/$dataSourceId",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout/data-sources/$type/$dataSourceId"
|
||||
),
|
||||
FindingsPage: setRoute(
|
||||
"/projects/$projectId/secret-scanning/findings",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-scanning/_secret-scanning-layout/findings"
|
||||
"/projects/secret-scanning/$projectId/findings",
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout/findings"
|
||||
)
|
||||
},
|
||||
Public: {
|
||||
|
||||
@@ -17,9 +17,7 @@ import {
|
||||
AzureClientSecretsConnectionMethod,
|
||||
AzureDevOpsConnectionMethod,
|
||||
AzureKeyVaultConnectionMethod,
|
||||
BitbucketConnectionMethod,
|
||||
CamundaConnectionMethod,
|
||||
ChecklyConnectionMethod,
|
||||
CloudflareConnectionMethod,
|
||||
DatabricksConnectionMethod,
|
||||
FlyioConnectionMethod,
|
||||
@@ -28,19 +26,14 @@ import {
|
||||
GitHubRadarConnectionMethod,
|
||||
GitLabConnectionMethod,
|
||||
HCVaultConnectionMethod,
|
||||
HerokuConnectionMethod,
|
||||
HumanitecConnectionMethod,
|
||||
LdapConnectionMethod,
|
||||
MsSqlConnectionMethod,
|
||||
MySqlConnectionMethod,
|
||||
OCIConnectionMethod,
|
||||
OktaConnectionMethod,
|
||||
OnePassConnectionMethod,
|
||||
OracleDBConnectionMethod,
|
||||
PostgresConnectionMethod,
|
||||
RailwayConnectionMethod,
|
||||
RenderConnectionMethod,
|
||||
SupabaseConnectionMethod,
|
||||
TAppConnection,
|
||||
TeamCityConnectionMethod,
|
||||
TerraformCloudConnectionMethod,
|
||||
@@ -48,6 +41,14 @@ import {
|
||||
WindmillConnectionMethod,
|
||||
ZabbixConnectionMethod
|
||||
} from "@app/hooks/api/appConnections/types";
|
||||
import { BitbucketConnectionMethod } from "@app/hooks/api/appConnections/types/bitbucket-connection";
|
||||
import { ChecklyConnectionMethod } from "@app/hooks/api/appConnections/types/checkly-connection";
|
||||
import { DigitalOceanConnectionMethod } from "@app/hooks/api/appConnections/types/digital-ocean";
|
||||
import { HerokuConnectionMethod } from "@app/hooks/api/appConnections/types/heroku-connection";
|
||||
import { OCIConnectionMethod } from "@app/hooks/api/appConnections/types/oci-connection";
|
||||
import { RailwayConnectionMethod } from "@app/hooks/api/appConnections/types/railway-connection";
|
||||
import { RenderConnectionMethod } from "@app/hooks/api/appConnections/types/render-connection";
|
||||
import { SupabaseConnectionMethod } from "@app/hooks/api/appConnections/types/supabase-connection";
|
||||
|
||||
export const APP_CONNECTION_MAP: Record<
|
||||
AppConnection,
|
||||
@@ -100,6 +101,10 @@ export const APP_CONNECTION_MAP: Record<
|
||||
[AppConnection.Bitbucket]: { name: "Bitbucket", image: "Bitbucket.png" },
|
||||
[AppConnection.Checkly]: { name: "Checkly", image: "Checkly.png" },
|
||||
[AppConnection.Supabase]: { name: "Supabase", image: "Supabase.png" },
|
||||
[AppConnection.DigitalOcean]: {
|
||||
name: "Digital Ocean",
|
||||
image: "Digital Ocean.png"
|
||||
},
|
||||
[AppConnection.Okta]: { name: "Okta", image: "Okta.png" }
|
||||
};
|
||||
|
||||
@@ -134,6 +139,7 @@ export const getAppConnectionMethodDetails = (method: TAppConnection["method"])
|
||||
case CloudflareConnectionMethod.ApiToken:
|
||||
case BitbucketConnectionMethod.ApiToken:
|
||||
case ZabbixConnectionMethod.ApiToken:
|
||||
case DigitalOceanConnectionMethod.ApiToken:
|
||||
case OktaConnectionMethod.ApiToken:
|
||||
return { name: "API Token", icon: faKey };
|
||||
case PostgresConnectionMethod.UsernameAndPassword:
|
||||
|
||||
@@ -42,7 +42,8 @@ export const initProjectHelper = async ({ projectName }: { projectName: string }
|
||||
const {
|
||||
data: { project }
|
||||
} = await createWorkspace({
|
||||
projectName
|
||||
projectName,
|
||||
type: ProjectType.SecretManager
|
||||
});
|
||||
|
||||
try {
|
||||
@@ -59,20 +60,34 @@ export const initProjectHelper = async ({ projectName }: { projectName: string }
|
||||
|
||||
return project;
|
||||
};
|
||||
|
||||
export const getProjectBaseURL = (type: ProjectType) => {
|
||||
switch (type) {
|
||||
case ProjectType.SecretManager:
|
||||
return "/projects/secret-management/$projectId";
|
||||
case ProjectType.CertificateManager:
|
||||
return "/projects/cert-management/$projectId";
|
||||
default:
|
||||
return `/projects/${type}/$projectId` as const;
|
||||
}
|
||||
};
|
||||
|
||||
export const getProjectHomePage = (type: ProjectType) => {
|
||||
switch (type) {
|
||||
case ProjectType.SecretManager:
|
||||
return "/projects/secret-management/$projectId/overview";
|
||||
case ProjectType.CertificateManager:
|
||||
return `/projects/$projectId/${type}/subscribers` as const;
|
||||
return "/projects/cert-management/$projectId/subscribers";
|
||||
case ProjectType.SecretScanning:
|
||||
return `/projects/$projectId/${type}/data-sources` as const;
|
||||
return `/projects/${type}/$projectId/data-sources` as const;
|
||||
default:
|
||||
return `/projects/$projectId/${type}/overview` as const;
|
||||
return `/projects/${type}/$projectId/overview` as const;
|
||||
}
|
||||
};
|
||||
|
||||
export const getProjectTitle = (type: ProjectType) => {
|
||||
const titleConvert = {
|
||||
[ProjectType.SecretManager]: "Secret Management",
|
||||
[ProjectType.SecretManager]: "Secrets Management",
|
||||
[ProjectType.KMS]: "Key Management",
|
||||
[ProjectType.CertificateManager]: "Cert Management",
|
||||
[ProjectType.SSH]: "SSH",
|
||||
@@ -81,7 +96,13 @@ export const getProjectTitle = (type: ProjectType) => {
|
||||
return titleConvert[type];
|
||||
};
|
||||
|
||||
export const getCurrentProductFromUrl = (location: string) => {
|
||||
const type = Object.values(ProjectType).find((el) => location.includes(`/${el}`));
|
||||
return type;
|
||||
export const getProjectLottieIcon = (type: ProjectType) => {
|
||||
const titleConvert = {
|
||||
[ProjectType.SecretManager]: "vault",
|
||||
[ProjectType.KMS]: "unlock",
|
||||
[ProjectType.CertificateManager]: "note",
|
||||
[ProjectType.SSH]: "terminal",
|
||||
[ProjectType.SecretScanning]: "secret-scan"
|
||||
};
|
||||
return titleConvert[type];
|
||||
};
|
||||
|
||||
@@ -101,6 +101,14 @@ export const SECRET_SYNC_MAP: Record<SecretSync, { name: string; image: string }
|
||||
[SecretSync.Supabase]: {
|
||||
name: "Supabase",
|
||||
image: "Supabase.png"
|
||||
},
|
||||
[SecretSync.DigitalOceanAppPlatform]: {
|
||||
name: "Digital Ocean App Platform",
|
||||
image: "Digital Ocean.png"
|
||||
},
|
||||
[SecretSync.Bitbucket]: {
|
||||
name: "Bitbucket",
|
||||
image: "Bitbucket.png"
|
||||
}
|
||||
};
|
||||
|
||||
@@ -131,7 +139,9 @@ export const SECRET_SYNC_CONNECTION_MAP: Record<SecretSync, AppConnection> = {
|
||||
[SecretSync.Supabase]: AppConnection.Supabase,
|
||||
[SecretSync.Zabbix]: AppConnection.Zabbix,
|
||||
[SecretSync.Railway]: AppConnection.Railway,
|
||||
[SecretSync.Checkly]: AppConnection.Checkly
|
||||
[SecretSync.Checkly]: AppConnection.Checkly,
|
||||
[SecretSync.DigitalOceanAppPlatform]: AppConnection.DigitalOcean,
|
||||
[SecretSync.Bitbucket]: AppConnection.Bitbucket
|
||||
};
|
||||
|
||||
export const SECRET_SYNC_INITIAL_SYNC_BEHAVIOR_MAP: Record<
|
||||
|
||||
@@ -4,8 +4,10 @@ import { apiRequest } from "@app/config/request";
|
||||
|
||||
import { appConnectionKeys } from "../queries";
|
||||
import {
|
||||
TBitbucketConnectionListEnvironmentsResponse,
|
||||
TBitbucketConnectionListRepositoriesResponse,
|
||||
TBitbucketConnectionListWorkspacesResponse,
|
||||
TBitbucketEnvironment,
|
||||
TBitbucketRepo,
|
||||
TBitbucketWorkspace
|
||||
} from "./types";
|
||||
@@ -15,7 +17,9 @@ const bitbucketConnectionKeys = {
|
||||
listRepos: (connectionId: string, workspaceSlug: string) =>
|
||||
[...bitbucketConnectionKeys.all, "repos", connectionId, workspaceSlug] as const,
|
||||
listWorkspaces: (connectionId: string) =>
|
||||
[...bitbucketConnectionKeys.all, "workspaces", connectionId] as const
|
||||
[...bitbucketConnectionKeys.all, "workspaces", connectionId] as const,
|
||||
listEnvironments: (connectionId: string, workspaceSlug: string, repoSlug: string) =>
|
||||
[...bitbucketConnectionKeys.all, "environments", connectionId, workspaceSlug, repoSlug] as const
|
||||
};
|
||||
|
||||
export const useBitbucketConnectionListWorkspaces = (
|
||||
@@ -68,3 +72,30 @@ export const useBitbucketConnectionListRepositories = (
|
||||
...options
|
||||
});
|
||||
};
|
||||
|
||||
export const useBitbucketConnectionListEnvironments = (
|
||||
connectionId: string,
|
||||
workspaceSlug: string,
|
||||
repoSlug: string,
|
||||
options?: Omit<
|
||||
UseQueryOptions<
|
||||
TBitbucketEnvironment[],
|
||||
unknown,
|
||||
TBitbucketEnvironment[],
|
||||
ReturnType<typeof bitbucketConnectionKeys.listEnvironments>
|
||||
>,
|
||||
"queryKey" | "queryFn"
|
||||
>
|
||||
) => {
|
||||
return useQuery({
|
||||
queryKey: bitbucketConnectionKeys.listEnvironments(connectionId, workspaceSlug, repoSlug),
|
||||
queryFn: async () => {
|
||||
const { data } = await apiRequest.get<TBitbucketConnectionListEnvironmentsResponse>(
|
||||
`/api/v1/app-connections/bitbucket/${connectionId}/environments?workspaceSlug=${encodeURIComponent(workspaceSlug)}&repositorySlug=${encodeURIComponent(repoSlug)}`
|
||||
);
|
||||
|
||||
return data.environments;
|
||||
},
|
||||
...options
|
||||
});
|
||||
};
|
||||
|
||||
@@ -15,3 +15,13 @@ export type TBitbucketConnectionListWorkspacesResponse = {
|
||||
export type TBitbucketConnectionListRepositoriesResponse = {
|
||||
repositories: TBitbucketRepo[];
|
||||
};
|
||||
|
||||
export type TBitbucketEnvironment = {
|
||||
uuid: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
};
|
||||
|
||||
export type TBitbucketConnectionListEnvironmentsResponse = {
|
||||
environments: TBitbucketEnvironment[];
|
||||
};
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./queries";
|
||||
@@ -0,0 +1,37 @@
|
||||
import { useQuery, UseQueryOptions } from "@tanstack/react-query";
|
||||
|
||||
import { apiRequest } from "@app/config/request";
|
||||
import { appConnectionKeys } from "@app/hooks/api/appConnections";
|
||||
|
||||
import { TDigitalOceanApp } from "./types";
|
||||
|
||||
const digitalOceanAppPlatformConnectionKeys = {
|
||||
all: [...appConnectionKeys.all, "digitalOceanAppPlatform"] as const,
|
||||
listAccounts: (connectionId: string) =>
|
||||
[...digitalOceanAppPlatformConnectionKeys.all, "workspace-scopes", connectionId] as const
|
||||
};
|
||||
|
||||
export const useDigitalOceanConnectionListApps = (
|
||||
connectionId: string,
|
||||
options?: Omit<
|
||||
UseQueryOptions<
|
||||
TDigitalOceanApp[],
|
||||
unknown,
|
||||
TDigitalOceanApp[],
|
||||
ReturnType<typeof digitalOceanAppPlatformConnectionKeys.listAccounts>
|
||||
>,
|
||||
"queryKey" | "queryFn"
|
||||
>
|
||||
) => {
|
||||
return useQuery({
|
||||
queryKey: digitalOceanAppPlatformConnectionKeys.listAccounts(connectionId),
|
||||
queryFn: async () => {
|
||||
const { data } = await apiRequest.get<{ apps: TDigitalOceanApp[] }>(
|
||||
`/api/v1/app-connections/digital-ocean/${connectionId}/apps`
|
||||
);
|
||||
|
||||
return data.apps;
|
||||
},
|
||||
...options
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
export type TDigitalOceanAppPlatformVariable = {
|
||||
key: string;
|
||||
value: string;
|
||||
type: "SECRET" | "GENERAL";
|
||||
};
|
||||
|
||||
export type TDigitalOceanApp = {
|
||||
id: string;
|
||||
spec: {
|
||||
name: string;
|
||||
services: Array<{
|
||||
name: string;
|
||||
}>;
|
||||
envs?: TDigitalOceanAppPlatformVariable[];
|
||||
};
|
||||
};
|
||||
@@ -33,5 +33,6 @@ export enum AppConnection {
|
||||
Railway = "railway",
|
||||
Checkly = "checkly",
|
||||
Supabase = "supabase",
|
||||
DigitalOcean = "digital-ocean",
|
||||
Okta = "okta"
|
||||
}
|
||||
|
||||
@@ -152,6 +152,10 @@ export type TSupabaseConnectionOption = TAppConnectionOptionBase & {
|
||||
app: AppConnection.Supabase;
|
||||
};
|
||||
|
||||
export type TDigitalOceanConnectionOption = TAppConnectionOptionBase & {
|
||||
app: AppConnection.DigitalOcean;
|
||||
};
|
||||
|
||||
export type TOktaConnectionOption = TAppConnectionOptionBase & {
|
||||
app: AppConnection.Okta;
|
||||
};
|
||||
@@ -188,6 +192,7 @@ export type TAppConnectionOption =
|
||||
| TZabbixConnectionOption
|
||||
| TRailwayConnectionOption
|
||||
| TChecklyConnectionOption
|
||||
| TDigitalOceanConnectionOption
|
||||
| TOktaConnectionOption;
|
||||
|
||||
export type TAppConnectionOptionMap = {
|
||||
@@ -225,5 +230,6 @@ export type TAppConnectionOptionMap = {
|
||||
[AppConnection.Railway]: TRailwayConnectionOption;
|
||||
[AppConnection.Checkly]: TChecklyConnectionOption;
|
||||
[AppConnection.Supabase]: TSupabaseConnectionOption;
|
||||
[AppConnection.DigitalOcean]: TDigitalOceanConnectionOption;
|
||||
[AppConnection.Okta]: TOktaConnectionOption;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { AppConnection } from "@app/hooks/api/appConnections/enums";
|
||||
import { TRootAppConnection } from "@app/hooks/api/appConnections/types/root-connection";
|
||||
|
||||
export enum DigitalOceanConnectionMethod {
|
||||
ApiToken = "api-token"
|
||||
}
|
||||
|
||||
export type TDigitalOceanConnection = TRootAppConnection & {
|
||||
app: AppConnection.DigitalOcean;
|
||||
method: DigitalOceanConnectionMethod.ApiToken;
|
||||
credentials: {
|
||||
apiToken: string;
|
||||
};
|
||||
};
|
||||
@@ -12,6 +12,7 @@ import { TCamundaConnection } from "./camunda-connection";
|
||||
import { TChecklyConnection } from "./checkly-connection";
|
||||
import { TCloudflareConnection } from "./cloudflare-connection";
|
||||
import { TDatabricksConnection } from "./databricks-connection";
|
||||
import { TDigitalOceanConnection } from "./digital-ocean";
|
||||
import { TFlyioConnection } from "./flyio-connection";
|
||||
import { TGcpConnection } from "./gcp-connection";
|
||||
import { TGitHubConnection } from "./github-connection";
|
||||
@@ -107,6 +108,7 @@ export type TAppConnection =
|
||||
| TRailwayConnection
|
||||
| TChecklyConnection
|
||||
| TSupabaseConnection
|
||||
| TDigitalOceanConnection
|
||||
| TOktaConnection;
|
||||
|
||||
export type TAvailableAppConnection = Pick<TAppConnection, "name" | "id">;
|
||||
@@ -178,5 +180,6 @@ export type TAppConnectionMap = {
|
||||
[AppConnection.Railway]: TRailwayConnection;
|
||||
[AppConnection.Checkly]: TChecklyConnection;
|
||||
[AppConnection.Supabase]: TSupabaseConnection;
|
||||
[AppConnection.DigitalOcean]: TDigitalOceanConnection;
|
||||
[AppConnection.Okta]: TOktaConnection;
|
||||
};
|
||||
|
||||
@@ -51,7 +51,7 @@ export type IdentityMembershipOrg = {
|
||||
export type IdentityMembership = {
|
||||
id: string;
|
||||
identity: Identity;
|
||||
project: Pick<Workspace, "id" | "name" | "defaultProduct">;
|
||||
project: Pick<Workspace, "id" | "name" | "type">;
|
||||
roles: Array<
|
||||
{
|
||||
id: string;
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { TProjectRole } from "@app/hooks/api/roles/types";
|
||||
|
||||
import { ProjectType } from "../workspace/types";
|
||||
|
||||
export type TProjectTemplate = {
|
||||
id: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
type: ProjectType;
|
||||
roles: Pick<TProjectRole, "slug" | "name" | "permissions">[];
|
||||
environments?: { name: string; slug: string; position: number }[] | null;
|
||||
createdAt: string;
|
||||
@@ -16,6 +19,7 @@ export type TProjectTemplateResponse = { projectTemplate: TProjectTemplate };
|
||||
export type TCreateProjectTemplateDTO = {
|
||||
name: string;
|
||||
description?: string;
|
||||
type?: ProjectType;
|
||||
};
|
||||
|
||||
export type TUpdateProjectTemplateDTO = Partial<
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export { useCreateReminder, useDeleteReminder, useGetReminder } from "./queries";
|
||||
@@ -0,0 +1,60 @@
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
import { apiRequest } from "@app/config/request";
|
||||
|
||||
import { CreateReminderDTO, DeleteReminderDTO, Reminder } from "./types";
|
||||
|
||||
export const reminderKeys = {
|
||||
getReminder: (secretId: string) => ["get-reminder", secretId] as const
|
||||
};
|
||||
|
||||
export const useCreateReminder = (secretId: string) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<Reminder, object, CreateReminderDTO>({
|
||||
mutationFn: async ({ message, repeatDays, nextReminderDate, recipients }) => {
|
||||
const { data } = await apiRequest.post<{ reminder: Reminder }>(
|
||||
`/api/v1/reminders/secrets/${secretId}`,
|
||||
{
|
||||
message,
|
||||
repeatDays,
|
||||
nextReminderDate,
|
||||
recipients
|
||||
}
|
||||
);
|
||||
return data.reminder;
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: reminderKeys.getReminder(secretId) });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const useDeleteReminder = (secretId: string) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<Reminder, object, DeleteReminderDTO>({
|
||||
mutationFn: async () => {
|
||||
const { data } = await apiRequest.delete<{ reminder: Reminder }>(
|
||||
`/api/v1/reminders/secrets/${secretId}`
|
||||
);
|
||||
return data.reminder;
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: reminderKeys.getReminder(secretId) });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetReminder = (secretId: string) => {
|
||||
return useQuery({
|
||||
queryKey: reminderKeys.getReminder(secretId),
|
||||
queryFn: async () => {
|
||||
const { data } = await apiRequest.get<{ reminder: Reminder }>(
|
||||
`/api/v1/reminders/secrets/${secretId}`
|
||||
);
|
||||
return data.reminder;
|
||||
},
|
||||
enabled: Boolean(secretId)
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
export type CreateReminderDTO = {
|
||||
message?: string | null;
|
||||
repeatDays?: number | null;
|
||||
nextReminderDate?: Date | null;
|
||||
secretId: string;
|
||||
recipients?: string[];
|
||||
};
|
||||
|
||||
export type DeleteReminderDTO = {
|
||||
secretId: string;
|
||||
reminderId: string;
|
||||
};
|
||||
|
||||
export type Reminder = { id: string } & CreateReminderDTO;
|
||||
@@ -25,7 +25,9 @@ export enum SecretSync {
|
||||
Supabase = "supabase",
|
||||
Zabbix = "zabbix",
|
||||
Railway = "railway",
|
||||
Checkly = "checkly"
|
||||
Checkly = "checkly",
|
||||
DigitalOceanAppPlatform = "digital-ocean-app-platform",
|
||||
Bitbucket = "bitbucket"
|
||||
}
|
||||
|
||||
export enum SecretSyncStatus {
|
||||
|
||||
@@ -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 TBitbucketSync = TRootSecretSync & {
|
||||
destination: SecretSync.Bitbucket;
|
||||
destinationConfig: {
|
||||
workspaceSlug: string;
|
||||
repositorySlug: string;
|
||||
environmentId?: string;
|
||||
};
|
||||
connection: {
|
||||
app: AppConnection.Bitbucket;
|
||||
name: string;
|
||||
id: string;
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
/* eslint-disable @typescript-eslint/no-empty-object-type */
|
||||
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 TDigitalOceanAppPlatformSync = TRootSecretSync & {
|
||||
destination: SecretSync.DigitalOceanAppPlatform;
|
||||
destinationConfig: {
|
||||
appId: string;
|
||||
appName: string;
|
||||
};
|
||||
connection: {
|
||||
app: AppConnection.DigitalOcean;
|
||||
name: string;
|
||||
id: string;
|
||||
};
|
||||
};
|
||||
@@ -8,11 +8,13 @@ import { TAwsSecretsManagerSync } from "./aws-secrets-manager-sync";
|
||||
import { TAzureAppConfigurationSync } from "./azure-app-configuration-sync";
|
||||
import { TAzureDevOpsSync } from "./azure-devops-sync";
|
||||
import { TAzureKeyVaultSync } from "./azure-key-vault-sync";
|
||||
import { TBitbucketSync } from "./bitbucket-sync";
|
||||
import { TCamundaSync } from "./camunda-sync";
|
||||
import { TChecklySync } from "./checkly-sync";
|
||||
import { TCloudflarePagesSync } from "./cloudflare-pages-sync";
|
||||
import { TCloudflareWorkersSync } from "./cloudflare-workers-sync";
|
||||
import { TDatabricksSync } from "./databricks-sync";
|
||||
import { TDigitalOceanAppPlatformSync } from "./digital-ocean-app-platform-sync";
|
||||
import { TFlyioSync } from "./flyio-sync";
|
||||
import { TGcpSync } from "./gcp-sync";
|
||||
import { TGitHubSync } from "./github-sync";
|
||||
@@ -63,7 +65,9 @@ export type TSecretSync =
|
||||
| TZabbixSync
|
||||
| TRailwaySync
|
||||
| TChecklySync
|
||||
| TSupabaseSync;
|
||||
| TSupabaseSync
|
||||
| TDigitalOceanAppPlatformSync
|
||||
| TBitbucketSync;
|
||||
|
||||
export type TListSecretSyncs = { secretSyncs: TSecretSync[] };
|
||||
|
||||
|
||||
@@ -255,12 +255,13 @@ export const useCreateWorkspace = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<{ data: { project: Workspace } }, object, CreateWorkspaceDTO>({
|
||||
mutationFn: async ({ projectName, projectDescription, kmsKeyId, template }) =>
|
||||
mutationFn: async ({ projectName, projectDescription, kmsKeyId, template, type }) =>
|
||||
createWorkspace({
|
||||
projectName,
|
||||
projectDescription,
|
||||
kmsKeyId,
|
||||
template
|
||||
template,
|
||||
type
|
||||
}),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({
|
||||
@@ -280,8 +281,7 @@ export const useUpdateProject = () => {
|
||||
newProjectDescription,
|
||||
newSlug,
|
||||
secretSharing,
|
||||
showSnapshotsLegacy,
|
||||
defaultProduct
|
||||
showSnapshotsLegacy
|
||||
}) => {
|
||||
const { data } = await apiRequest.patch<{ workspace: Workspace }>(
|
||||
`/api/v1/workspace/${projectID}`,
|
||||
@@ -289,7 +289,6 @@ export const useUpdateProject = () => {
|
||||
name: newProjectName,
|
||||
description: newProjectDescription,
|
||||
slug: newSlug,
|
||||
defaultProduct,
|
||||
secretSharing,
|
||||
showSnapshotsLegacy
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ export type Workspace = {
|
||||
__v: number;
|
||||
id: string;
|
||||
name: string;
|
||||
defaultProduct: ProjectType;
|
||||
type: ProjectType;
|
||||
description?: string;
|
||||
orgId: string;
|
||||
version: ProjectVersion;
|
||||
@@ -68,6 +68,7 @@ export type TGetUpgradeProjectStatusDTO = {
|
||||
// mutation dto
|
||||
export type CreateWorkspaceDTO = {
|
||||
projectName: string;
|
||||
type: ProjectType;
|
||||
projectDescription?: string;
|
||||
kmsKeyId?: string;
|
||||
template?: string;
|
||||
@@ -80,7 +81,6 @@ export type UpdateProjectDTO = {
|
||||
newSlug?: string;
|
||||
secretSharing?: boolean;
|
||||
showSnapshotsLegacy?: boolean;
|
||||
defaultProduct?: ProjectType;
|
||||
};
|
||||
|
||||
export type UpdatePitVersionLimitDTO = { projectSlug: string; pitVersionLimit: number };
|
||||
@@ -184,6 +184,7 @@ export type TSearchProjectsDTO = {
|
||||
name?: string;
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
type?: ProjectType;
|
||||
options?: { enabled?: boolean };
|
||||
orderBy?: ProjectIdentityOrderBy;
|
||||
orderDirection?: OrderByDirection;
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
import { faCog, faCube, faHome, faLock, faUsers } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { Link, Outlet } from "@tanstack/react-router";
|
||||
import { motion } from "framer-motion";
|
||||
|
||||
import { Menu, MenuItem } from "@app/components/v2";
|
||||
import { useWorkspace } from "@app/context";
|
||||
import { Lottie, Menu, MenuGroup, MenuItem } from "@app/components/v2";
|
||||
import { useProjectPermission, useWorkspace } from "@app/context";
|
||||
|
||||
import { AssumePrivilegeModeBanner } from "../ProjectLayout/components/AssumePrivilegeModeBanner";
|
||||
|
||||
export const KmsLayout = () => {
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const { assumedPrivilegeDetails } = useProjectPermission();
|
||||
|
||||
return (
|
||||
<div className="dark hidden h-full w-full flex-col overflow-x-hidden md:flex">
|
||||
@@ -19,40 +24,106 @@ export const KmsLayout = () => {
|
||||
className="dark w-full border-r border-mineshaft-600 bg-gradient-to-tr from-mineshaft-700 via-mineshaft-800 to-mineshaft-900 md:w-60"
|
||||
>
|
||||
<nav className="items-between flex h-full flex-col overflow-y-auto dark:[color-scheme:dark]">
|
||||
<div className="border-b border-mineshaft-600 px-4 py-3.5 text-lg text-white">KMS</div>
|
||||
<div className="flex items-center gap-3 border-b border-mineshaft-600 px-4 py-3.5 text-lg text-white">
|
||||
<Lottie className="inline-block h-5 w-5 shrink-0" icon="unlock" />
|
||||
KMS
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<Menu>
|
||||
<Link
|
||||
to="/projects/$projectId/kms/overview"
|
||||
params={{
|
||||
projectId: currentWorkspace.id
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => <MenuItem isSelected={isActive}>Overview</MenuItem>}
|
||||
<MenuGroup title="Resources">
|
||||
<Link
|
||||
to="/projects/kms/$projectId/overview"
|
||||
params={{
|
||||
projectId: currentWorkspace.id
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
<div className="mx-1 flex gap-2">
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faCube} />
|
||||
</div>
|
||||
Overview
|
||||
</div>
|
||||
</MenuItem>
|
||||
)}
|
||||
</Link>
|
||||
<Link
|
||||
to="/projects/kms/$projectId/kmip"
|
||||
params={{
|
||||
projectId: currentWorkspace.id
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
<div className="mx-1 flex gap-2">
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faLock} />
|
||||
</div>
|
||||
KMIP
|
||||
</div>
|
||||
</MenuItem>
|
||||
)}
|
||||
</Link>
|
||||
</MenuGroup>
|
||||
<MenuGroup title="Others">
|
||||
<Link
|
||||
to="/projects/kms/$projectId/access-management"
|
||||
params={{
|
||||
projectId: currentWorkspace.id
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
<div className="mx-1 flex gap-2">
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faUsers} />
|
||||
</div>
|
||||
Access Management
|
||||
</div>
|
||||
</MenuItem>
|
||||
)}
|
||||
</Link>
|
||||
<Link
|
||||
to="/projects/kms/$projectId/settings"
|
||||
params={{
|
||||
projectId: currentWorkspace.id
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
<div className="mx-1 flex gap-2">
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faCog} />
|
||||
</div>
|
||||
Settings
|
||||
</div>
|
||||
</MenuItem>
|
||||
)}
|
||||
</Link>
|
||||
</MenuGroup>
|
||||
</Menu>
|
||||
</div>
|
||||
<div>
|
||||
<Menu>
|
||||
<Link to="/organization/projects">
|
||||
<MenuItem
|
||||
className="relative flex items-center gap-2 overflow-hidden text-sm text-mineshaft-400 hover:text-mineshaft-300"
|
||||
leftIcon={
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon className="mx-1 inline-block shrink-0" icon={faHome} />
|
||||
</div>
|
||||
}
|
||||
>
|
||||
Organization Home
|
||||
</MenuItem>
|
||||
</Link>
|
||||
<Link
|
||||
to="/projects/$projectId/kms/kmip"
|
||||
params={{
|
||||
projectId: currentWorkspace.id
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => <MenuItem isSelected={isActive}>KMIP</MenuItem>}
|
||||
</Link>
|
||||
{
|
||||
// <Link
|
||||
// to="/projects/$projectId/kms/settings"
|
||||
// params={{
|
||||
// projectId: currentWorkspace.id
|
||||
// }}
|
||||
// >
|
||||
// {({ isActive }) => <MenuItem isSelected={isActive}>Settings</MenuItem>}
|
||||
// </Link>
|
||||
}
|
||||
</Menu>
|
||||
</div>
|
||||
</nav>
|
||||
</motion.div>
|
||||
<div className="flex-1 overflow-y-auto overflow-x-hidden bg-bunker-800 p-4 pt-8">
|
||||
{assumedPrivilegeDetails && <AssumePrivilegeModeBanner />}
|
||||
<Outlet />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import {
|
||||
faBook,
|
||||
faCog,
|
||||
faCubes,
|
||||
faDoorClosed,
|
||||
faInfinity,
|
||||
faMoneyBill,
|
||||
faPlug,
|
||||
faShare,
|
||||
faTable,
|
||||
faUserCog,
|
||||
faUsers,
|
||||
faUserTie
|
||||
@@ -56,7 +56,7 @@ export const OrgSidebar = ({ isHidden }: Props) => {
|
||||
<MenuItem isSelected={isActive}>
|
||||
<div className="mx-1 flex gap-2">
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faCubes} />
|
||||
<FontAwesomeIcon icon={faTable} />
|
||||
</div>
|
||||
Projects
|
||||
</div>
|
||||
|
||||
@@ -1,14 +1,27 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { faMobile } from "@fortawesome/free-solid-svg-icons";
|
||||
import {
|
||||
faBell,
|
||||
faCertificate,
|
||||
faCog,
|
||||
faFileLines,
|
||||
faHome,
|
||||
faMobile,
|
||||
faSitemap,
|
||||
faStamp,
|
||||
faUsers
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { Link, Outlet } from "@tanstack/react-router";
|
||||
import { motion } from "framer-motion";
|
||||
|
||||
import { Menu, MenuItem } from "@app/components/v2";
|
||||
import { useWorkspace } from "@app/context";
|
||||
import { Lottie, Menu, MenuGroup, MenuItem } from "@app/components/v2";
|
||||
import { useProjectPermission, useWorkspace } from "@app/context";
|
||||
|
||||
import { AssumePrivilegeModeBanner } from "../ProjectLayout/components/AssumePrivilegeModeBanner";
|
||||
|
||||
export const PkiManagerLayout = () => {
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const { assumedPrivilegeDetails } = useProjectPermission();
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
@@ -24,70 +37,157 @@ export const PkiManagerLayout = () => {
|
||||
className="dark w-full border-r border-mineshaft-600 bg-gradient-to-tr from-mineshaft-700 via-mineshaft-800 to-mineshaft-900 md:w-60"
|
||||
>
|
||||
<nav className="items-between flex h-full flex-col overflow-y-auto dark:[color-scheme:dark]">
|
||||
<div className="border-b border-mineshaft-600 px-4 py-3.5 text-lg text-white">
|
||||
<div className="flex items-center gap-3 border-b border-mineshaft-600 px-4 py-3.5 text-lg text-white">
|
||||
<Lottie className="inline-block h-5 w-5 shrink-0" icon="note" />
|
||||
PKI Manager
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<Menu>
|
||||
<Link
|
||||
to="/projects/$projectId/cert-manager/subscribers"
|
||||
params={{
|
||||
projectId: currentWorkspace.id
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => <MenuItem isSelected={isActive}>Subscribers</MenuItem>}
|
||||
<MenuGroup title="Resources">
|
||||
<Link
|
||||
to="/projects/cert-management/$projectId/subscribers"
|
||||
params={{
|
||||
projectId: currentWorkspace.id
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
<div className="mx-1 flex gap-2">
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faSitemap} />
|
||||
</div>
|
||||
Subscribers
|
||||
</div>
|
||||
</MenuItem>
|
||||
)}
|
||||
</Link>
|
||||
<Link
|
||||
to="/projects/cert-management/$projectId/certificate-templates"
|
||||
params={{
|
||||
projectId: currentWorkspace.id
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
<div className="mx-1 flex gap-2">
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faFileLines} />
|
||||
</div>
|
||||
Certificate Templates
|
||||
</div>
|
||||
</MenuItem>
|
||||
)}
|
||||
</Link>
|
||||
<Link
|
||||
to="/projects/cert-management/$projectId/certificates"
|
||||
params={{
|
||||
projectId: currentWorkspace.id
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
<div className="mx-1 flex gap-2">
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faCertificate} />
|
||||
</div>
|
||||
Certificates
|
||||
</div>
|
||||
</MenuItem>
|
||||
)}
|
||||
</Link>
|
||||
<Link
|
||||
to="/projects/cert-management/$projectId/certificate-authorities"
|
||||
params={{
|
||||
projectId: currentWorkspace.id
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
<div className="mx-1 flex gap-2">
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faStamp} />
|
||||
</div>
|
||||
Certificates Authority
|
||||
</div>
|
||||
</MenuItem>
|
||||
)}
|
||||
</Link>
|
||||
<Link
|
||||
to="/projects/cert-management/$projectId/alerting"
|
||||
params={{
|
||||
projectId: currentWorkspace.id
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
<div className="mx-1 flex gap-2">
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faBell} />
|
||||
</div>
|
||||
Alerting
|
||||
</div>
|
||||
</MenuItem>
|
||||
)}
|
||||
</Link>
|
||||
</MenuGroup>
|
||||
<MenuGroup title="Others">
|
||||
<Link
|
||||
to="/projects/cert-management/$projectId/access-management"
|
||||
params={{
|
||||
projectId: currentWorkspace.id
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
<div className="mx-1 flex gap-2">
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faUsers} />
|
||||
</div>
|
||||
Access Management
|
||||
</div>
|
||||
</MenuItem>
|
||||
)}
|
||||
</Link>
|
||||
<Link
|
||||
to="/projects/cert-management/$projectId/settings"
|
||||
params={{
|
||||
projectId: currentWorkspace.id
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
<div className="mx-1 flex gap-2">
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faCog} />
|
||||
</div>
|
||||
Settings
|
||||
</div>
|
||||
</MenuItem>
|
||||
)}
|
||||
</Link>
|
||||
</MenuGroup>
|
||||
</Menu>
|
||||
</div>
|
||||
<div>
|
||||
<Menu>
|
||||
<Link to="/organization/projects">
|
||||
<MenuItem
|
||||
className="relative flex items-center gap-2 overflow-hidden text-sm text-mineshaft-400 hover:text-mineshaft-300"
|
||||
leftIcon={
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon className="mx-1 inline-block shrink-0" icon={faHome} />
|
||||
</div>
|
||||
}
|
||||
>
|
||||
Organization Home
|
||||
</MenuItem>
|
||||
</Link>
|
||||
<Link
|
||||
to="/projects/$projectId/cert-manager/certificate-templates"
|
||||
params={{
|
||||
projectId: currentWorkspace.id
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}> Certificate Templates</MenuItem>
|
||||
)}
|
||||
</Link>
|
||||
<Link
|
||||
to="/projects/$projectId/cert-manager/certificates"
|
||||
params={{
|
||||
projectId: currentWorkspace.id
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => <MenuItem isSelected={isActive}> Certificates</MenuItem>}
|
||||
</Link>
|
||||
<Link
|
||||
to="/projects/$projectId/cert-manager/certificate-authorities"
|
||||
params={{
|
||||
projectId: currentWorkspace.id
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}> Certificates Authorities</MenuItem>
|
||||
)}
|
||||
</Link>
|
||||
<Link
|
||||
to="/projects/$projectId/cert-manager/alerting"
|
||||
params={{
|
||||
projectId: currentWorkspace.id
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => <MenuItem isSelected={isActive}>Alerting</MenuItem>}
|
||||
</Link>
|
||||
{
|
||||
// <Link
|
||||
// to="/projects/$projectId/cert-manager/settings"
|
||||
// params={{
|
||||
// projectId: currentWorkspace.id
|
||||
// }}
|
||||
// >
|
||||
// {({ isActive }) => <MenuItem isSelected={isActive}>Settings</MenuItem>}
|
||||
// </Link>
|
||||
}
|
||||
</Menu>
|
||||
</div>
|
||||
</nav>
|
||||
</motion.div>
|
||||
<div className="flex-1 overflow-y-auto overflow-x-hidden bg-bunker-800 p-4 pt-8">
|
||||
{assumedPrivilegeDetails && <AssumePrivilegeModeBanner />}
|
||||
<Outlet />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
import { Link, Outlet } from "@tanstack/react-router";
|
||||
import { motion } from "framer-motion";
|
||||
|
||||
import { Menu, MenuItem } from "@app/components/v2";
|
||||
import { useWorkspace } from "@app/context";
|
||||
|
||||
export const ProjectGeneralLayout = () => {
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
|
||||
return (
|
||||
<div className="dark hidden h-full w-full flex-col overflow-x-hidden md:flex">
|
||||
<div className="flex flex-grow flex-col overflow-y-hidden md:flex-row">
|
||||
<motion.div
|
||||
key="menu-project-items"
|
||||
initial={{ x: -150 }}
|
||||
animate={{ x: 0 }}
|
||||
exit={{ x: -150 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
className="dark w-full border-r border-mineshaft-600 bg-gradient-to-tr from-mineshaft-700 via-mineshaft-800 to-mineshaft-900 md:w-60"
|
||||
>
|
||||
<nav className="items-between flex h-full flex-col overflow-y-auto dark:[color-scheme:dark]">
|
||||
<div className="border-b border-mineshaft-600 px-4 py-3.5 text-lg text-white">
|
||||
Project Overview
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<Menu>
|
||||
<Link
|
||||
to="/projects/$projectId/access-management"
|
||||
params={{
|
||||
projectId: currentWorkspace.id
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => <MenuItem isSelected={isActive}>Access Control</MenuItem>}
|
||||
</Link>
|
||||
<Link
|
||||
to="/projects/$projectId/settings"
|
||||
params={{
|
||||
projectId: currentWorkspace.id
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => <MenuItem isSelected={isActive}>Project Settings</MenuItem>}
|
||||
</Link>
|
||||
</Menu>
|
||||
</div>
|
||||
</nav>
|
||||
</motion.div>
|
||||
<div className="flex-1 overflow-y-auto overflow-x-hidden bg-bunker-800 p-4 pt-8">
|
||||
<Outlet />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
export { ProjectGeneralLayout } from "./ProjectGeneralLayout";
|
||||
@@ -1,327 +0,0 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { faDotCircle, faMobile, faWindowMaximize } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { Link, Outlet, useLocation } from "@tanstack/react-router";
|
||||
import { motion } from "framer-motion";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import { ShouldWrap } from "@app/components/utilities/ShouldWrapComponent";
|
||||
import {
|
||||
Divider,
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
Lottie,
|
||||
Menu,
|
||||
MenuItem,
|
||||
Tooltip
|
||||
} from "@app/components/v2";
|
||||
import { useProjectPermission, useWorkspace } from "@app/context";
|
||||
import { getCurrentProductFromUrl } from "@app/helpers/project";
|
||||
import { useLocalStorageState } from "@app/hooks";
|
||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||
|
||||
import { AssumePrivilegeModeBanner } from "./components/AssumePrivilegeModeBanner";
|
||||
|
||||
enum SidebarStyle {
|
||||
Expanded = "expanded",
|
||||
Collapsed = "collapsed",
|
||||
ExpandOnHover = "expand-on-hover"
|
||||
}
|
||||
const MIN_SIDEBAR_SIZE = "55px";
|
||||
const MAX_SIDEBAR_SIZE = "220px";
|
||||
// This is a generic layout shared by all types of projects.
|
||||
// If the product layout differs significantly, create a new layout as needed.
|
||||
export const ProjectLayout = () => {
|
||||
const location = useLocation();
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const [sidebarStyle, setSidebarStyle] = useLocalStorageState(
|
||||
"project-sidebar-style",
|
||||
SidebarStyle.ExpandOnHover
|
||||
);
|
||||
|
||||
const { t } = useTranslation();
|
||||
const { assumedPrivilegeDetails } = useProjectPermission();
|
||||
|
||||
const minSidebarWidth =
|
||||
sidebarStyle === SidebarStyle.Expanded ? MAX_SIDEBAR_SIZE : MIN_SIDEBAR_SIZE;
|
||||
const maxSidebarWidth =
|
||||
sidebarStyle === SidebarStyle.Collapsed ? MIN_SIDEBAR_SIZE : MAX_SIDEBAR_SIZE;
|
||||
|
||||
const currentProductType = getCurrentProductFromUrl(location.pathname);
|
||||
const isSecretManager = currentProductType === ProjectType.SecretManager;
|
||||
const isPki = currentProductType === ProjectType.CertificateManager;
|
||||
const isKms = currentProductType === ProjectType.KMS;
|
||||
const isSsh = currentProductType === ProjectType.SSH;
|
||||
const isSecretScanning = currentProductType === ProjectType.SecretScanning;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className="dark relative hidden w-full flex-col overflow-x-hidden md:flex"
|
||||
style={{
|
||||
height: "calc(100vh - 3rem)",
|
||||
paddingLeft: !(sidebarStyle === SidebarStyle.Expanded) ? MIN_SIDEBAR_SIZE : "0px"
|
||||
}}
|
||||
>
|
||||
<div className="flex flex-grow flex-col overflow-y-auto overflow-x-hidden md:flex-row">
|
||||
<motion.div
|
||||
key="menu-project-items"
|
||||
initial={{ x: -150 }}
|
||||
animate={{ x: 0 }}
|
||||
exit={{ x: -150 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
style={{ width: minSidebarWidth, height: "calc(100vh - 3rem)" }}
|
||||
whileHover={{
|
||||
width: maxSidebarWidth
|
||||
}}
|
||||
className={twMerge(
|
||||
"dark group z-10 w-full overflow-hidden border-r border-mineshaft-600 bg-gradient-to-tr from-mineshaft-700 via-mineshaft-800 to-mineshaft-900 md:w-60",
|
||||
!(sidebarStyle === SidebarStyle.Expanded) && "absolute bottom-0 left-0"
|
||||
)}
|
||||
>
|
||||
<nav className="items-between flex h-full flex-col justify-between">
|
||||
<Menu>
|
||||
<ShouldWrap
|
||||
wrapper={Tooltip}
|
||||
isWrapped={sidebarStyle === SidebarStyle.Collapsed}
|
||||
content="Secret Manager"
|
||||
position="right"
|
||||
>
|
||||
<Link
|
||||
to="/projects/$projectId/secret-manager/overview"
|
||||
params={{ projectId: currentWorkspace.id }}
|
||||
>
|
||||
<MenuItem
|
||||
className="relative flex items-center gap-2 overflow-hidden rounded-none"
|
||||
isSelected={isSecretManager}
|
||||
leftIcon={<Lottie className="inline-block h-6 w-6 shrink-0" icon="vault" />}
|
||||
>
|
||||
{isSecretManager && (
|
||||
<div className="absolute left-0 top-0 h-full w-0.5 bg-primary" />
|
||||
)}
|
||||
Secret Manager
|
||||
</MenuItem>
|
||||
</Link>
|
||||
</ShouldWrap>
|
||||
<ShouldWrap
|
||||
wrapper={Tooltip}
|
||||
isWrapped={sidebarStyle === SidebarStyle.Collapsed}
|
||||
content="PKI Manager"
|
||||
position="right"
|
||||
>
|
||||
<Link
|
||||
to="/projects/$projectId/cert-manager/subscribers"
|
||||
params={{ projectId: currentWorkspace.id }}
|
||||
>
|
||||
<MenuItem
|
||||
className="relative flex items-center gap-2 overflow-hidden rounded-none"
|
||||
isSelected={isPki}
|
||||
leftIcon={<Lottie className="inline-block h-6 w-6 shrink-0" icon="note" />}
|
||||
>
|
||||
{isPki && <div className="absolute left-0 top-0 h-full w-0.5 bg-primary" />}
|
||||
PKI Manager
|
||||
</MenuItem>
|
||||
</Link>
|
||||
</ShouldWrap>
|
||||
<ShouldWrap
|
||||
wrapper={Tooltip}
|
||||
isWrapped={sidebarStyle === SidebarStyle.Collapsed}
|
||||
content="KMS"
|
||||
position="right"
|
||||
>
|
||||
<Link
|
||||
to="/projects/$projectId/kms/overview"
|
||||
params={{ projectId: currentWorkspace.id }}
|
||||
>
|
||||
<MenuItem
|
||||
className="relative flex items-center gap-2 overflow-hidden rounded-none"
|
||||
isSelected={isKms}
|
||||
leftIcon={<Lottie className="inline-block h-6 w-6 shrink-0" icon="unlock" />}
|
||||
>
|
||||
{isKms && <div className="absolute left-0 top-0 h-full w-0.5 bg-primary" />}
|
||||
KMS
|
||||
</MenuItem>
|
||||
</Link>
|
||||
</ShouldWrap>
|
||||
<ShouldWrap
|
||||
wrapper={Tooltip}
|
||||
isWrapped={sidebarStyle === SidebarStyle.Collapsed}
|
||||
content="SSH"
|
||||
position="right"
|
||||
>
|
||||
<Link
|
||||
to="/projects/$projectId/ssh/overview"
|
||||
params={{ projectId: currentWorkspace.id }}
|
||||
>
|
||||
<MenuItem
|
||||
className="relative flex items-center gap-2 overflow-hidden rounded-none"
|
||||
isSelected={isSsh}
|
||||
leftIcon={
|
||||
<Lottie className="inline-block h-6 w-6 shrink-0" icon="terminal" />
|
||||
}
|
||||
>
|
||||
{isSsh && <div className="absolute left-0 top-0 h-full w-0.5 bg-primary" />}
|
||||
SSH
|
||||
</MenuItem>
|
||||
</Link>
|
||||
</ShouldWrap>
|
||||
<ShouldWrap
|
||||
wrapper={Tooltip}
|
||||
isWrapped={sidebarStyle === SidebarStyle.Collapsed}
|
||||
content="Secret Scanning"
|
||||
position="right"
|
||||
>
|
||||
<Link
|
||||
to="/projects/$projectId/secret-scanning/data-sources"
|
||||
params={{ projectId: currentWorkspace.id }}
|
||||
>
|
||||
<MenuItem
|
||||
className="relative flex items-center gap-2 overflow-hidden rounded-none"
|
||||
isSelected={isSecretScanning}
|
||||
leftIcon={
|
||||
<Lottie className="inline-block h-6 w-6 shrink-0" icon="secret-scan" />
|
||||
}
|
||||
>
|
||||
{isSecretScanning && (
|
||||
<div className="absolute left-0 top-0 h-full w-0.5 bg-primary" />
|
||||
)}
|
||||
Secret Scanning
|
||||
</MenuItem>
|
||||
</Link>
|
||||
</ShouldWrap>
|
||||
</Menu>
|
||||
<Divider />
|
||||
<Menu>
|
||||
<ShouldWrap
|
||||
wrapper={Tooltip}
|
||||
isWrapped={sidebarStyle === SidebarStyle.Collapsed}
|
||||
content="Access Control"
|
||||
position="right"
|
||||
>
|
||||
<Link
|
||||
to="/projects/$projectId/access-management"
|
||||
params={{ projectId: currentWorkspace.id }}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<MenuItem
|
||||
className="relative flex items-center gap-2 overflow-hidden rounded-none"
|
||||
isSelected={isActive}
|
||||
leftIcon={
|
||||
<Lottie className="inline-block h-6 w-6 shrink-0" icon="groups" />
|
||||
}
|
||||
>
|
||||
{isActive && (
|
||||
<div className="absolute left-0 top-0 h-full w-0.5 bg-primary" />
|
||||
)}
|
||||
Access Control
|
||||
</MenuItem>
|
||||
)}
|
||||
</Link>
|
||||
</ShouldWrap>
|
||||
<ShouldWrap
|
||||
wrapper={Tooltip}
|
||||
isWrapped={sidebarStyle === SidebarStyle.Collapsed}
|
||||
content="Project Settings"
|
||||
position="right"
|
||||
>
|
||||
<Link
|
||||
to="/projects/$projectId/settings"
|
||||
params={{ projectId: currentWorkspace.id }}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<MenuItem
|
||||
className="relative flex items-center gap-2 overflow-hidden rounded-none"
|
||||
isSelected={isActive}
|
||||
leftIcon={
|
||||
<Lottie className="inline-block h-6 w-6 shrink-0" icon="settings-cog" />
|
||||
}
|
||||
>
|
||||
{isActive && (
|
||||
<div className="absolute left-0 top-0 h-full w-0.5 bg-primary" />
|
||||
)}
|
||||
Project Settings
|
||||
</MenuItem>
|
||||
)}
|
||||
</Link>
|
||||
</ShouldWrap>
|
||||
</Menu>
|
||||
<div className="flex-grow" />
|
||||
<Menu>
|
||||
<DropdownMenu>
|
||||
<ShouldWrap
|
||||
wrapper={Tooltip}
|
||||
isWrapped={sidebarStyle === SidebarStyle.Collapsed}
|
||||
content="Sidebar Control"
|
||||
position="right"
|
||||
>
|
||||
<DropdownMenuTrigger className="w-full">
|
||||
<MenuItem
|
||||
className="relative flex items-center gap-2 overflow-hidden text-sm text-mineshaft-400 hover:text-mineshaft-300"
|
||||
leftIcon={
|
||||
<FontAwesomeIcon
|
||||
className="mx-1 inline-block shrink-0"
|
||||
icon={faWindowMaximize}
|
||||
flip="vertical"
|
||||
/>
|
||||
}
|
||||
>
|
||||
Sidebar Control
|
||||
</MenuItem>
|
||||
</DropdownMenuTrigger>
|
||||
</ShouldWrap>
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuItem
|
||||
iconPos="right"
|
||||
icon={
|
||||
sidebarStyle === SidebarStyle.Expanded && (
|
||||
<FontAwesomeIcon icon={faDotCircle} size="sm" />
|
||||
)
|
||||
}
|
||||
onClick={() => setSidebarStyle(SidebarStyle.Expanded)}
|
||||
>
|
||||
Expanded
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
iconPos="right"
|
||||
icon={
|
||||
sidebarStyle === SidebarStyle.Collapsed && (
|
||||
<FontAwesomeIcon icon={faDotCircle} size="sm" />
|
||||
)
|
||||
}
|
||||
onClick={() => setSidebarStyle(SidebarStyle.Collapsed)}
|
||||
>
|
||||
Collapsed
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
iconPos="right"
|
||||
icon={
|
||||
sidebarStyle === SidebarStyle.ExpandOnHover && (
|
||||
<FontAwesomeIcon icon={faDotCircle} size="sm" />
|
||||
)
|
||||
}
|
||||
onClick={() => setSidebarStyle(SidebarStyle.ExpandOnHover)}
|
||||
>
|
||||
Expand on hover
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</Menu>
|
||||
</nav>
|
||||
</motion.div>
|
||||
<div className="flex-1 overflow-y-auto overflow-x-hidden bg-bunker-800">
|
||||
{assumedPrivilegeDetails && <AssumePrivilegeModeBanner />}
|
||||
<Outlet />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="z-[200] flex h-screen w-screen flex-col items-center justify-center bg-bunker-800 md:hidden">
|
||||
<FontAwesomeIcon icon={faMobile} className="mb-8 text-7xl text-gray-300" />
|
||||
<p className="max-w-sm px-6 text-center text-lg text-gray-200">
|
||||
{` ${t("common.no-mobile")} `}
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
+2
-6
@@ -3,10 +3,9 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import { Button } from "@app/components/v2";
|
||||
import { useProjectPermission, useWorkspace } from "@app/context";
|
||||
import { getCurrentProductFromUrl, getProjectHomePage } from "@app/helpers/project";
|
||||
import { getProjectHomePage } from "@app/helpers/project";
|
||||
import { useRemoveAssumeProjectPrivilege } from "@app/hooks/api";
|
||||
import { ActorType } from "@app/hooks/api/auditLogs/enums";
|
||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||
|
||||
export const AssumePrivilegeModeBanner = () => {
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
@@ -37,10 +36,7 @@ export const AssumePrivilegeModeBanner = () => {
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
const url = getProjectHomePage(
|
||||
getCurrentProductFromUrl(window.location.href) || ProjectType.SecretManager
|
||||
);
|
||||
|
||||
const url = getProjectHomePage(currentWorkspace.type);
|
||||
window.location.href = url.replace("$projectId", currentWorkspace.id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
import { ComponentPropsWithRef, ElementType, useRef } from "react";
|
||||
import { DotLottie, DotLottieReact } from "@lottiefiles/dotlottie-react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import { MenuItemProps } from "@app/components/v2";
|
||||
|
||||
export const MenuIconButton = <T extends ElementType = "button">({
|
||||
children,
|
||||
icon,
|
||||
className,
|
||||
isDisabled,
|
||||
isSelected,
|
||||
as: Item = "div",
|
||||
description,
|
||||
// wrapping in forward ref with generic component causes the loss of ts definitions on props
|
||||
inputRef,
|
||||
lottieIconMode = "forward",
|
||||
...props
|
||||
}: MenuItemProps<T> &
|
||||
ComponentPropsWithRef<T> & { lottieIconMode?: "reverse" | "forward" }): JSX.Element => {
|
||||
const iconRef = useRef<DotLottie | null>(null);
|
||||
return (
|
||||
<div className="px-1">
|
||||
<Item
|
||||
type="button"
|
||||
role="menuitem"
|
||||
className={twMerge(
|
||||
"group relative my-1 flex w-full cursor-pointer flex-col items-center justify-center rounded p-2 font-inter text-sm text-bunker-100 transition-all duration-150 hover:bg-mineshaft-700",
|
||||
isSelected && "bg-bunker-8g0 hover:bg-mineshaft-500",
|
||||
isDisabled && "cursor-not-allowed hover:bg-transparent",
|
||||
className
|
||||
)}
|
||||
onMouseEnter={() => iconRef.current?.play()}
|
||||
onMouseLeave={() => iconRef.current?.stop()}
|
||||
ref={inputRef}
|
||||
{...props}
|
||||
>
|
||||
<div
|
||||
className={`${isSelected ? "opacity-100" : "opacity-0"} absolute left-0 h-full w-0.5 rounded-bl rounded-tl bg-primary transition-all duration-150`}
|
||||
/>
|
||||
{icon && (
|
||||
<div className="my-auto mb-2 h-6 w-6">
|
||||
<DotLottieReact
|
||||
dotLottieRefCallback={(el) => {
|
||||
iconRef.current = el;
|
||||
}}
|
||||
src={`/lotties/${icon}.json`}
|
||||
loop
|
||||
className="h-full w-full"
|
||||
mode={lottieIconMode}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className="flex-grow justify-center break-words text-center"
|
||||
style={{ fontSize: "10px" }}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</Item>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// export const MenuIconButton = <T extends ElementType = "button">({
|
||||
// children,
|
||||
// icon,
|
||||
// className,
|
||||
// isDisabled,
|
||||
// isSelected,
|
||||
// as: Item = "div",
|
||||
// description,
|
||||
// // wrapping in forward ref with generic component causes the loss of ts definitions on props
|
||||
// inputRef,
|
||||
// lottieIconMode = "forward",
|
||||
// ...props
|
||||
// }: MenuItemProps<T> &
|
||||
// ComponentPropsWithRef<T> & { lottieIconMode?: "reverse" | "forward" }): JSX.Element => {
|
||||
// const iconRef = useRef<DotLottie | null>(null);
|
||||
// return (
|
||||
// <div className="px-1">
|
||||
// <Item
|
||||
// type="button"
|
||||
// role="menuitem"
|
||||
// className={twMerge(
|
||||
// "group relative my-1 flex w-full cursor-pointer flex-col items-center justify-center rounded p-2 font-inter text-sm text-bunker-100 transition-all duration-150 hover:bg-mineshaft-700",
|
||||
// isSelected && "bg-mineshaft-600 hover:bg-mineshaft-700",
|
||||
// isDisabled && "cursor-not-allowed hover:bg-transparent",
|
||||
// className
|
||||
// )}
|
||||
// onMouseEnter={() => iconRef.current?.play()}
|
||||
// onMouseLeave={() => iconRef.current?.stop()}
|
||||
// ref={inputRef}
|
||||
// {...props}
|
||||
// >
|
||||
// <div
|
||||
// className={`${"opacity-0"} absolute left-0 h-full w-1 bg-primary transition-all duration-150`}
|
||||
// />
|
||||
// {icon && (
|
||||
// <div className="my-auto mb-2 h-6 w-6">
|
||||
// <DotLottieReact
|
||||
// dotLottieRefCallback={(el) => {
|
||||
// iconRef.current = el;
|
||||
// }}
|
||||
// src={`/lotties/${icon}.json`}
|
||||
// loop
|
||||
// className="h-full w-full"
|
||||
// mode={lottieIconMode}
|
||||
// />
|
||||
// </div>
|
||||
// )}
|
||||
// <div
|
||||
// className="flex-grow justify-center break-words text-center"
|
||||
// style={{ fontSize: "10px" }}
|
||||
// >
|
||||
// {children}
|
||||
// </div>
|
||||
// </Item>
|
||||
// </div>
|
||||
// );
|
||||
// };
|
||||
@@ -1,65 +0,0 @@
|
||||
import { ComponentPropsWithRef, ElementType, useRef } from "react";
|
||||
import { DotLottie, DotLottieReact } from "@lottiefiles/dotlottie-react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import { MenuItemProps } from "@app/components/v2";
|
||||
|
||||
export const MenuIconButton = <T extends ElementType = "button">({
|
||||
children,
|
||||
icon,
|
||||
className,
|
||||
isDisabled,
|
||||
isSelected,
|
||||
as: Item = "div",
|
||||
description,
|
||||
// wrapping in forward ref with generic component causes the loss of ts definitions on props
|
||||
inputRef,
|
||||
lottieIconMode = "forward",
|
||||
...props
|
||||
}: MenuItemProps<T> &
|
||||
ComponentPropsWithRef<T> & { lottieIconMode?: "reverse" | "forward" }): JSX.Element => {
|
||||
const iconRef = useRef<DotLottie | null>(null);
|
||||
return (
|
||||
<div>
|
||||
<Item
|
||||
type="button"
|
||||
role="menuitem"
|
||||
className={twMerge(
|
||||
"group relative flex w-full cursor-pointer flex-col items-center justify-center p-2 font-inter text-sm text-bunker-100 transition-all duration-150 hover:bg-mineshaft-700",
|
||||
isSelected && "rounded-none bg-bunker-800 hover:bg-mineshaft-600",
|
||||
isDisabled && "cursor-not-allowed hover:bg-transparent",
|
||||
className
|
||||
)}
|
||||
onMouseEnter={() => iconRef.current?.play()}
|
||||
onMouseLeave={() => iconRef.current?.stop()}
|
||||
ref={inputRef}
|
||||
{...props}
|
||||
>
|
||||
<div
|
||||
className={`${
|
||||
isSelected ? "opacity-100" : "opacity-0"
|
||||
} absolute left-0 h-full w-0.5 bg-primary transition-all duration-150`}
|
||||
/>
|
||||
{icon && (
|
||||
<div className="my-auto mb-2 h-6 w-6">
|
||||
<DotLottieReact
|
||||
dotLottieRefCallback={(el) => {
|
||||
iconRef.current = el;
|
||||
}}
|
||||
src={`/lotties/${icon}.json`}
|
||||
loop
|
||||
className="h-full w-full"
|
||||
mode={lottieIconMode}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className="flex-grow justify-center break-words text-center"
|
||||
style={{ fontSize: "10px" }}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</Item>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
export { MenuIconButton } from "./MenuIconButton";
|
||||
@@ -31,14 +31,13 @@ import {
|
||||
useSubscription,
|
||||
useWorkspace
|
||||
} from "@app/context";
|
||||
import { getCurrentProductFromUrl, getProjectHomePage } from "@app/helpers/project";
|
||||
import { getProjectHomePage } from "@app/helpers/project";
|
||||
import { usePopUp } from "@app/hooks";
|
||||
import { useGetUserWorkspaces } from "@app/hooks/api";
|
||||
import { useUpdateUserProjectFavorites } from "@app/hooks/api/users/mutation";
|
||||
import { useGetUserProjectFavorites } from "@app/hooks/api/users/queries";
|
||||
import { Workspace } from "@app/hooks/api/workspace/types";
|
||||
|
||||
// TODO(pta): add search to project select
|
||||
export const ProjectSelect = () => {
|
||||
const [searchProject, setSearchProject] = useState("");
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
@@ -102,9 +101,7 @@ export const ProjectSelect = () => {
|
||||
<div className="-mr-2 flex w-full items-center gap-1">
|
||||
<DropdownMenu modal={false}>
|
||||
<Link
|
||||
to={getProjectHomePage(
|
||||
getCurrentProductFromUrl(window.location.href) || currentWorkspace.defaultProduct
|
||||
)}
|
||||
to={getProjectHomePage(currentWorkspace.type)}
|
||||
params={{
|
||||
projectId: currentWorkspace.id
|
||||
}}
|
||||
@@ -113,7 +110,7 @@ export const ProjectSelect = () => {
|
||||
<div>
|
||||
<FontAwesomeIcon icon={faTable} className="text-xs text-bunker-300" />
|
||||
</div>
|
||||
<Tooltip content={currentWorkspace.name} className="max-w-96">
|
||||
<Tooltip content={currentWorkspace.name} className="max-w-96 break-words">
|
||||
<div className="max-w-44 overflow-hidden text-ellipsis whitespace-nowrap">
|
||||
{currentWorkspace?.name}
|
||||
</div>
|
||||
@@ -161,7 +158,7 @@ export const ProjectSelect = () => {
|
||||
// to reproduce change this back to router.push and switch between two projects with different env count
|
||||
// look into this on dashboard revamp
|
||||
const url = linkOptions({
|
||||
to: getProjectHomePage(workspace.defaultProduct),
|
||||
to: getProjectHomePage(workspace.type),
|
||||
params: {
|
||||
projectId: workspace.id
|
||||
}
|
||||
|
||||
@@ -1,179 +0,0 @@
|
||||
import {
|
||||
faAngleDown,
|
||||
faArrowLeft,
|
||||
faArrowUpRightFromSquare,
|
||||
faCheck
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { Link, useNavigate } from "@tanstack/react-router";
|
||||
|
||||
import {
|
||||
Button,
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger
|
||||
} from "@app/components/v2";
|
||||
import { useOrganization, useUser } from "@app/context";
|
||||
import { useGetOrganizations, useLogoutUser } from "@app/hooks/api";
|
||||
import { AuthMethod } from "@app/hooks/api/users/types";
|
||||
|
||||
type Prop = {
|
||||
onChangeOrg: (orgId: string) => void;
|
||||
};
|
||||
|
||||
export const SidebarHeader = ({ onChangeOrg }: Prop) => {
|
||||
const { currentOrg } = useOrganization();
|
||||
const { user } = useUser();
|
||||
const navigate = useNavigate();
|
||||
const { data: orgs } = useGetOrganizations();
|
||||
|
||||
const logout = useLogoutUser();
|
||||
const logOutUser = async () => {
|
||||
try {
|
||||
console.log("Logging out...");
|
||||
await logout.mutateAsync();
|
||||
navigate({ to: "/login" });
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex h-12 cursor-default items-center px-3 pt-6">
|
||||
<Link to="/organization/projects">
|
||||
<div className="pl-1 pr-2 text-mineshaft-400 duration-200 hover:text-mineshaft-100">
|
||||
<FontAwesomeIcon icon={faArrowLeft} />
|
||||
</div>
|
||||
</Link>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild className="max-w-[160px] data-[state=open]:bg-mineshaft-600">
|
||||
<div className="mr-auto flex items-center rounded-md py-1.5 pl-1.5 pr-2 hover:bg-mineshaft-600">
|
||||
<div className="flex h-5 w-5 min-w-[20px] items-center justify-center rounded-md bg-primary text-sm">
|
||||
{currentOrg?.name.charAt(0)}
|
||||
</div>
|
||||
<div
|
||||
className="overflow-hidden truncate text-ellipsis pl-2 text-sm text-mineshaft-100"
|
||||
style={{ maxWidth: "140px" }}
|
||||
>
|
||||
{currentOrg?.name}
|
||||
</div>
|
||||
<FontAwesomeIcon icon={faAngleDown} className="pl-1 pt-1 text-xs text-mineshaft-300" />
|
||||
</div>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start" className="p-1">
|
||||
<div className="px-2 py-1 text-xs text-mineshaft-400">{user?.username}</div>
|
||||
{orgs?.map((org) => {
|
||||
return (
|
||||
<DropdownMenuItem key={org.id}>
|
||||
<Button
|
||||
onClick={async () => {
|
||||
if (currentOrg?.id === org.id) return;
|
||||
|
||||
if (org.authEnforced) {
|
||||
// org has an org-level auth method enabled (e.g. SAML)
|
||||
// -> logout + redirect to SAML SSO
|
||||
|
||||
await logout.mutateAsync();
|
||||
if (org.orgAuthMethod === AuthMethod.OIDC) {
|
||||
window.open(`/api/v1/sso/oidc/login?orgSlug=${org.slug}`);
|
||||
} else {
|
||||
window.open(`/api/v1/sso/redirect/saml2/organizations/${org.slug}`);
|
||||
}
|
||||
window.close();
|
||||
return;
|
||||
}
|
||||
|
||||
onChangeOrg(org?.id);
|
||||
}}
|
||||
variant="plain"
|
||||
colorSchema="secondary"
|
||||
size="xs"
|
||||
className="flex w-full items-center justify-start p-0 font-normal"
|
||||
leftIcon={
|
||||
currentOrg?.id === org.id && (
|
||||
<FontAwesomeIcon icon={faCheck} className="mr-3 text-primary" />
|
||||
)
|
||||
}
|
||||
>
|
||||
<div className="flex w-full max-w-[150px] items-center justify-between truncate">
|
||||
{org.name}
|
||||
</div>
|
||||
</Button>
|
||||
</DropdownMenuItem>
|
||||
);
|
||||
})}
|
||||
|
||||
<div className="mt-1 h-1 border-t border-mineshaft-600" />
|
||||
<button type="button" onClick={logOutUser} className="w-full">
|
||||
<DropdownMenuItem>Log Out</DropdownMenuItem>
|
||||
</button>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger
|
||||
asChild
|
||||
className="p-1 hover:bg-primary-400 hover:text-black data-[state=open]:bg-primary-400 data-[state=open]:text-black"
|
||||
>
|
||||
<div
|
||||
className="child flex items-center justify-center rounded-full bg-mineshaft pr-1 text-mineshaft-300 hover:bg-mineshaft-500"
|
||||
style={{ fontSize: "11px", width: "26px", height: "26px" }}
|
||||
>
|
||||
{user?.firstName?.charAt(0)}
|
||||
{user?.lastName && user?.lastName?.charAt(0)}
|
||||
</div>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start" className="p-1">
|
||||
<div className="px-2 py-1 text-xs text-mineshaft-400">{user?.username}</div>
|
||||
<Link to="/personal-settings">
|
||||
<DropdownMenuItem>Personal Settings</DropdownMenuItem>
|
||||
</Link>
|
||||
<a
|
||||
href="https://infisical.com/docs/documentation/getting-started/introduction"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="mt-3 w-full text-sm font-normal leading-[1.2rem] text-mineshaft-300 hover:text-mineshaft-100"
|
||||
>
|
||||
<DropdownMenuItem>
|
||||
Documentation
|
||||
<FontAwesomeIcon
|
||||
icon={faArrowUpRightFromSquare}
|
||||
className="mb-[0.06rem] pl-1.5 text-xxs"
|
||||
/>
|
||||
</DropdownMenuItem>
|
||||
</a>
|
||||
<a
|
||||
href="https://infisical.com/slack"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="mt-3 w-full text-sm font-normal leading-[1.2rem] text-mineshaft-300 hover:text-mineshaft-100"
|
||||
>
|
||||
<DropdownMenuItem>
|
||||
Join Slack Community
|
||||
<FontAwesomeIcon
|
||||
icon={faArrowUpRightFromSquare}
|
||||
className="mb-[0.06rem] pl-1.5 text-xxs"
|
||||
/>
|
||||
</DropdownMenuItem>
|
||||
</a>
|
||||
{user?.superAdmin && (
|
||||
<Link to="/admin">
|
||||
<DropdownMenuItem className="mt-1 border-t border-mineshaft-600">
|
||||
Server Admin Console
|
||||
</DropdownMenuItem>
|
||||
</Link>
|
||||
)}
|
||||
<Link to="/organization/admin">
|
||||
<DropdownMenuItem className="mt-1 border-t border-mineshaft-600">
|
||||
Organization Admin Console
|
||||
</DropdownMenuItem>
|
||||
</Link>
|
||||
<div className="mt-1 h-1 border-t border-mineshaft-600" />
|
||||
<button type="button" onClick={logOutUser} className="w-full">
|
||||
<DropdownMenuItem>Log Out</DropdownMenuItem>
|
||||
</button>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
export { SidebarHeader } from "./SidebarHeader";
|
||||
@@ -1 +0,0 @@
|
||||
export { ProjectLayout } from "./ProjectLayout";
|
||||
@@ -1,19 +1,31 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { faMobile } from "@fortawesome/free-solid-svg-icons";
|
||||
import {
|
||||
faArrowsSpin,
|
||||
faCheckToSlot,
|
||||
faCog,
|
||||
faHome,
|
||||
faMobile,
|
||||
faPuzzlePiece,
|
||||
faUsers,
|
||||
faVault
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { Link, Outlet } from "@tanstack/react-router";
|
||||
import { motion } from "framer-motion";
|
||||
|
||||
import { Badge, Menu, MenuItem } from "@app/components/v2";
|
||||
import { useWorkspace } from "@app/context";
|
||||
import { Badge, Lottie, Menu, MenuGroup, MenuItem } from "@app/components/v2";
|
||||
import { useProjectPermission, useWorkspace } from "@app/context";
|
||||
import {
|
||||
useGetAccessRequestsCount,
|
||||
useGetSecretApprovalRequestCount,
|
||||
useGetSecretRotations
|
||||
} from "@app/hooks/api";
|
||||
|
||||
import { AssumePrivilegeModeBanner } from "../ProjectLayout/components/AssumePrivilegeModeBanner";
|
||||
|
||||
export const SecretManagerLayout = () => {
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const { assumedPrivilegeDetails } = useProjectPermission();
|
||||
|
||||
const { t } = useTranslation();
|
||||
const workspaceId = currentWorkspace?.id || "";
|
||||
@@ -50,73 +62,150 @@ export const SecretManagerLayout = () => {
|
||||
className="dark w-full border-r border-mineshaft-600 bg-gradient-to-tr from-mineshaft-700 via-mineshaft-800 to-mineshaft-900 md:w-60"
|
||||
>
|
||||
<nav className="items-between flex h-full flex-col overflow-y-auto dark:[color-scheme:dark]">
|
||||
<div className="border-b border-mineshaft-600 px-4 py-3.5 text-lg text-white">
|
||||
<div className="flex items-center gap-3 border-b border-mineshaft-600 px-4 py-3.5 text-lg text-white">
|
||||
<Lottie className="inline-block h-5 w-5 shrink-0" icon="vault" />
|
||||
Secrets Manager
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<Menu>
|
||||
<Link
|
||||
to="/projects/$projectId/secret-manager/overview"
|
||||
params={{
|
||||
projectId: currentWorkspace.id
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>{t("nav.menu.secrets")}</MenuItem>
|
||||
)}
|
||||
</Link>
|
||||
<Link
|
||||
to="/projects/$projectId/secret-manager/integrations"
|
||||
params={{
|
||||
projectId: currentWorkspace.id
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>{t("nav.menu.integrations")}</MenuItem>
|
||||
)}
|
||||
</Link>
|
||||
{Boolean(secretRotations?.length) && (
|
||||
<MenuGroup title="Resources">
|
||||
<Link
|
||||
to="/projects/$projectId/secret-manager/secret-rotation"
|
||||
to="/projects/secret-management/$projectId/overview"
|
||||
params={{
|
||||
projectId: currentWorkspace.id
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => <MenuItem isSelected={isActive}>Secret Rotation</MenuItem>}
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
<div className="mx-1 flex gap-2">
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faVault} />
|
||||
</div>
|
||||
Secrets
|
||||
</div>
|
||||
</MenuItem>
|
||||
)}
|
||||
</Link>
|
||||
)}
|
||||
<Link
|
||||
to="/projects/$projectId/secret-manager/approval"
|
||||
params={{
|
||||
projectId: currentWorkspace.id
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
Approvals
|
||||
{Boolean(
|
||||
secretApprovalReqCount?.open || accessApprovalRequestCount?.pendingCount
|
||||
) && (
|
||||
<Badge variant="primary" className="ml-1.5">
|
||||
{pendingRequestsCount}
|
||||
</Badge>
|
||||
<Link
|
||||
to="/projects/secret-management/$projectId/integrations"
|
||||
params={{
|
||||
projectId: currentWorkspace.id
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
<div className="mx-1 flex gap-2">
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faPuzzlePiece} />
|
||||
</div>
|
||||
Integrations
|
||||
</div>
|
||||
</MenuItem>
|
||||
)}
|
||||
</Link>
|
||||
{Boolean(secretRotations?.length) && (
|
||||
<Link
|
||||
to="/projects/secret-management/$projectId/secret-rotation"
|
||||
params={{
|
||||
projectId: currentWorkspace.id
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
<div className="mx-1 flex gap-2">
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faArrowsSpin} />
|
||||
</div>
|
||||
Secret Rotations
|
||||
</div>
|
||||
</MenuItem>
|
||||
)}
|
||||
</MenuItem>
|
||||
</Link>
|
||||
)}
|
||||
</Link>
|
||||
<Link
|
||||
to="/projects/$projectId/secret-manager/settings"
|
||||
params={{
|
||||
projectId: currentWorkspace.id
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => <MenuItem isSelected={isActive}>Settings</MenuItem>}
|
||||
<Link
|
||||
to="/projects/secret-management/$projectId/approval"
|
||||
params={{
|
||||
projectId: currentWorkspace.id
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
<div className="mx-1 flex gap-2">
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faCheckToSlot} />
|
||||
</div>
|
||||
Approvals
|
||||
{Boolean(
|
||||
secretApprovalReqCount?.open ||
|
||||
accessApprovalRequestCount?.pendingCount
|
||||
) && (
|
||||
<Badge variant="primary" className="ml-1.5">
|
||||
{pendingRequestsCount}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</MenuItem>
|
||||
)}
|
||||
</Link>
|
||||
</MenuGroup>
|
||||
<MenuGroup title="Others">
|
||||
<Link
|
||||
to="/projects/secret-management/$projectId/access-management"
|
||||
params={{
|
||||
projectId: currentWorkspace.id
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
<div className="mx-1 flex gap-2">
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faUsers} />
|
||||
</div>
|
||||
Access Management
|
||||
</div>
|
||||
</MenuItem>
|
||||
)}
|
||||
</Link>
|
||||
<Link
|
||||
to="/projects/secret-management/$projectId/settings"
|
||||
params={{
|
||||
projectId: currentWorkspace.id
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
<div className="mx-1 flex gap-2">
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faCog} />
|
||||
</div>
|
||||
Settings
|
||||
</div>
|
||||
</MenuItem>
|
||||
)}
|
||||
</Link>
|
||||
</MenuGroup>
|
||||
</Menu>
|
||||
</div>
|
||||
<div>
|
||||
<Menu>
|
||||
<Link to="/organization/projects">
|
||||
<MenuItem
|
||||
className="relative flex items-center gap-2 overflow-hidden text-sm text-mineshaft-400 hover:text-mineshaft-300"
|
||||
leftIcon={
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon className="mx-1 inline-block shrink-0" icon={faHome} />
|
||||
</div>
|
||||
}
|
||||
>
|
||||
Organization Home
|
||||
</MenuItem>
|
||||
</Link>
|
||||
</Menu>
|
||||
</div>
|
||||
</nav>
|
||||
</motion.div>
|
||||
<div className="flex-1 overflow-y-auto overflow-x-hidden bg-bunker-800 p-4 pt-8">
|
||||
{assumedPrivilegeDetails && <AssumePrivilegeModeBanner />}
|
||||
<Outlet />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,11 +1,22 @@
|
||||
import {
|
||||
faCog,
|
||||
faDatabase,
|
||||
faHome,
|
||||
faMagnifyingGlass,
|
||||
faUsers
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { Link, Outlet } from "@tanstack/react-router";
|
||||
import { motion } from "framer-motion";
|
||||
|
||||
import { Menu, MenuItem } from "@app/components/v2";
|
||||
import { useWorkspace } from "@app/context";
|
||||
import { Lottie, Menu, MenuGroup, MenuItem } from "@app/components/v2";
|
||||
import { useProjectPermission, useWorkspace } from "@app/context";
|
||||
|
||||
import { AssumePrivilegeModeBanner } from "../ProjectLayout/components/AssumePrivilegeModeBanner";
|
||||
|
||||
export const SecretScanningLayout = () => {
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const { assumedPrivilegeDetails } = useProjectPermission();
|
||||
|
||||
return (
|
||||
<div className="dark hidden h-full w-full flex-col overflow-x-hidden md:flex">
|
||||
@@ -19,40 +30,106 @@ export const SecretScanningLayout = () => {
|
||||
className="dark w-full border-r border-mineshaft-600 bg-gradient-to-tr from-mineshaft-700 via-mineshaft-800 to-mineshaft-900 md:w-60"
|
||||
>
|
||||
<nav className="items-between flex h-full flex-col overflow-y-auto dark:[color-scheme:dark]">
|
||||
<div className="border-b border-mineshaft-600 px-4 py-3.5 text-lg text-white">
|
||||
<div className="flex items-center gap-3 border-b border-mineshaft-600 px-4 py-3.5 text-lg text-white">
|
||||
<Lottie className="inline-block h-5 w-5 shrink-0" icon="secret-scan" />
|
||||
Secret Scanning
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<Menu>
|
||||
<Link
|
||||
to="/projects/$projectId/secret-scanning/data-sources"
|
||||
params={{
|
||||
projectId: currentWorkspace.id
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => <MenuItem isSelected={isActive}> Data Sources</MenuItem>}
|
||||
</Link>
|
||||
<Link
|
||||
to="/projects/$projectId/secret-scanning/findings"
|
||||
params={{
|
||||
projectId: currentWorkspace.id
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => <MenuItem isSelected={isActive}>Findings</MenuItem>}
|
||||
</Link>
|
||||
<Link
|
||||
to="/projects/$projectId/secret-scanning/settings"
|
||||
params={{
|
||||
projectId: currentWorkspace.id
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => <MenuItem isSelected={isActive}>Settings</MenuItem>}
|
||||
<MenuGroup title="Resources">
|
||||
<Link
|
||||
to="/projects/secret-scanning/$projectId/data-sources"
|
||||
params={{
|
||||
projectId: currentWorkspace.id
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
<div className="mx-1 flex gap-2">
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faDatabase} />
|
||||
</div>
|
||||
Data Sources
|
||||
</div>
|
||||
</MenuItem>
|
||||
)}
|
||||
</Link>
|
||||
<Link
|
||||
to="/projects/secret-scanning/$projectId/findings"
|
||||
params={{
|
||||
projectId: currentWorkspace.id
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
<div className="mx-1 flex gap-2">
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faMagnifyingGlass} />
|
||||
</div>
|
||||
Findings
|
||||
</div>
|
||||
</MenuItem>
|
||||
)}
|
||||
</Link>
|
||||
</MenuGroup>
|
||||
<MenuGroup title="Others">
|
||||
<Link
|
||||
to="/projects/secret-scanning/$projectId/access-management"
|
||||
params={{
|
||||
projectId: currentWorkspace.id
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
<div className="mx-1 flex gap-2">
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faUsers} />
|
||||
</div>
|
||||
Access Management
|
||||
</div>
|
||||
</MenuItem>
|
||||
)}
|
||||
</Link>
|
||||
<Link
|
||||
to="/projects/secret-scanning/$projectId/settings"
|
||||
params={{
|
||||
projectId: currentWorkspace.id
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
<div className="mx-1 flex gap-2">
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faCog} />
|
||||
</div>
|
||||
Settings
|
||||
</div>
|
||||
</MenuItem>
|
||||
)}
|
||||
</Link>
|
||||
</MenuGroup>
|
||||
</Menu>
|
||||
</div>
|
||||
<div>
|
||||
<Menu>
|
||||
<Link to="/organization/projects">
|
||||
<MenuItem
|
||||
className="relative flex items-center gap-2 overflow-hidden text-sm text-mineshaft-400 hover:text-mineshaft-300"
|
||||
leftIcon={
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon className="mx-1 inline-block shrink-0" icon={faHome} />
|
||||
</div>
|
||||
}
|
||||
>
|
||||
Organization Home
|
||||
</MenuItem>
|
||||
</Link>
|
||||
</Menu>
|
||||
</div>
|
||||
</nav>
|
||||
</motion.div>
|
||||
<div className="flex-1 overflow-y-auto overflow-x-hidden bg-bunker-800 p-4 pt-8">
|
||||
{assumedPrivilegeDetails && <AssumePrivilegeModeBanner />}
|
||||
<Outlet />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,12 +1,22 @@
|
||||
import { faCog, faHome, faServer, faStamp, faUsers } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { Link, Outlet } from "@tanstack/react-router";
|
||||
import { motion } from "framer-motion";
|
||||
|
||||
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||
import { Menu, MenuItem } from "@app/components/v2";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
|
||||
import { Lottie, Menu, MenuGroup, MenuItem } from "@app/components/v2";
|
||||
import {
|
||||
ProjectPermissionActions,
|
||||
ProjectPermissionSub,
|
||||
useProjectPermission,
|
||||
useWorkspace
|
||||
} from "@app/context";
|
||||
|
||||
import { AssumePrivilegeModeBanner } from "../ProjectLayout/components/AssumePrivilegeModeBanner";
|
||||
|
||||
export const SshLayout = () => {
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const { assumedPrivilegeDetails } = useProjectPermission();
|
||||
|
||||
return (
|
||||
<div className="dark hidden h-full w-full flex-col overflow-x-hidden md:flex">
|
||||
@@ -20,49 +30,116 @@ export const SshLayout = () => {
|
||||
className="dark w-full border-r border-mineshaft-600 bg-gradient-to-tr from-mineshaft-700 via-mineshaft-800 to-mineshaft-900 md:w-60"
|
||||
>
|
||||
<nav className="items-between flex h-full flex-col overflow-y-auto dark:[color-scheme:dark]">
|
||||
<div className="border-b border-mineshaft-600 px-4 py-3.5 text-lg text-white">SSH</div>
|
||||
<div className="flex items-center gap-3 border-b border-mineshaft-600 px-4 py-3.5 text-lg text-white">
|
||||
<Lottie className="inline-block h-5 w-5 shrink-0" icon="terminal" />
|
||||
SSH
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<Menu>
|
||||
<Link
|
||||
to="/projects/$projectId/ssh/overview"
|
||||
params={{
|
||||
projectId: currentWorkspace.id
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => <MenuItem isSelected={isActive}>Hosts</MenuItem>}
|
||||
</Link>
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Read}
|
||||
a={ProjectPermissionSub.SshCertificateAuthorities}
|
||||
>
|
||||
{(isAllowed) =>
|
||||
isAllowed && (
|
||||
<Link
|
||||
to="/projects/$projectId/ssh/cas"
|
||||
params={{
|
||||
projectId: currentWorkspace.id
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>Certificate Authorities</MenuItem>
|
||||
)}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
</ProjectPermissionCan>
|
||||
<Link
|
||||
to="/projects/$projectId/ssh/settings"
|
||||
params={{
|
||||
projectId: currentWorkspace.id
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => <MenuItem isSelected={isActive}>Settings</MenuItem>}
|
||||
<MenuGroup title="Resources">
|
||||
<Link
|
||||
to="/projects/ssh/$projectId/overview"
|
||||
params={{
|
||||
projectId: currentWorkspace.id
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
<div className="mx-1 flex gap-2">
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faServer} />
|
||||
</div>
|
||||
Hosts
|
||||
</div>
|
||||
</MenuItem>
|
||||
)}
|
||||
</Link>
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Read}
|
||||
a={ProjectPermissionSub.SshCertificateAuthorities}
|
||||
>
|
||||
{(isAllowed) =>
|
||||
isAllowed && (
|
||||
<Link
|
||||
to="/projects/ssh/$projectId/cas"
|
||||
params={{
|
||||
projectId: currentWorkspace.id
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
<div className="mx-1 flex gap-2">
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faStamp} />
|
||||
</div>
|
||||
Certificates Authority
|
||||
</div>
|
||||
</MenuItem>
|
||||
)}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
</ProjectPermissionCan>
|
||||
</MenuGroup>
|
||||
|
||||
<MenuGroup title="Others">
|
||||
<Link
|
||||
to="/projects/ssh/$projectId/access-management"
|
||||
params={{
|
||||
projectId: currentWorkspace.id
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
<div className="mx-1 flex gap-2">
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faUsers} />
|
||||
</div>
|
||||
Access Management
|
||||
</div>
|
||||
</MenuItem>
|
||||
)}
|
||||
</Link>
|
||||
<Link
|
||||
to="/projects/ssh/$projectId/settings"
|
||||
params={{
|
||||
projectId: currentWorkspace.id
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
<div className="mx-1 flex gap-2">
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faCog} />
|
||||
</div>
|
||||
Settings
|
||||
</div>
|
||||
</MenuItem>
|
||||
)}
|
||||
</Link>
|
||||
</MenuGroup>
|
||||
</Menu>
|
||||
</div>
|
||||
<div>
|
||||
<Menu>
|
||||
<Link to="/organization/projects">
|
||||
<MenuItem
|
||||
className="relative flex items-center gap-2 overflow-hidden text-sm text-mineshaft-400 hover:text-mineshaft-300"
|
||||
leftIcon={
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon className="mx-1 inline-block shrink-0" icon={faHome} />
|
||||
</div>
|
||||
}
|
||||
>
|
||||
Organization Home
|
||||
</MenuItem>
|
||||
</Link>
|
||||
</Menu>
|
||||
</div>
|
||||
</nav>
|
||||
</motion.div>
|
||||
<div className="flex-1 overflow-y-auto overflow-x-hidden bg-bunker-800 p-4 pt-8">
|
||||
{assumedPrivilegeDetails && <AssumePrivilegeModeBanner />}
|
||||
<Outlet />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -82,7 +82,7 @@ export const PkiCollectionModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
});
|
||||
|
||||
navigate({
|
||||
to: "/projects/$projectId/cert-manager/pki-collections/$collectionId",
|
||||
to: "/projects/cert-management/$projectId/pki-collections/$collectionId",
|
||||
params: {
|
||||
projectId,
|
||||
collectionId
|
||||
|
||||
@@ -21,7 +21,6 @@ import {
|
||||
} from "@app/components/v2";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
|
||||
import { useListWorkspacePkiCollections } from "@app/hooks/api";
|
||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||
|
||||
type Props = {
|
||||
@@ -61,7 +60,7 @@ export const PkiCollectionTable = ({ handlePopUpOpen }: Props) => {
|
||||
key={`pki-collection-${pkiCollection.id}`}
|
||||
onClick={() =>
|
||||
navigate({
|
||||
to: `/projects/$projectId/${ProjectType.CertificateManager}/pki-collections/$collectionId` as const,
|
||||
to: "/projects/cert-management/$projectId/pki-collections/$collectionId",
|
||||
params: {
|
||||
projectId,
|
||||
collectionId: pkiCollection.id
|
||||
|
||||
@@ -3,7 +3,7 @@ import { createFileRoute } from "@tanstack/react-router";
|
||||
import { AlertingPage } from "./AlertingPage";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/cert-manager/_cert-manager-layout/alerting"
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/alerting"
|
||||
)({
|
||||
component: AlertingPage,
|
||||
beforeLoad: ({ context }) => {
|
||||
|
||||
@@ -18,7 +18,6 @@ import { ROUTE_PATHS } from "@app/const/routes";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
|
||||
import { CaType, useDeleteCa, useGetCa } from "@app/hooks/api";
|
||||
import { TInternalCertificateAuthority } from "@app/hooks/api/ca/types";
|
||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||
import { usePopUp } from "@app/hooks/usePopUp";
|
||||
|
||||
import { CaInstallCertModal } from "../CertificateAuthoritiesPage/components/CaInstallCertModal";
|
||||
@@ -71,7 +70,7 @@ const Page = () => {
|
||||
|
||||
handlePopUpClose("deleteCa");
|
||||
navigate({
|
||||
to: `/projects/$projectId/${ProjectType.CertificateManager}/certificate-authorities` as const,
|
||||
to: "/projects/cert-management/$projectId/certificate-authorities",
|
||||
params: {
|
||||
projectId
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||
import { CertAuthDetailsByIDPage } from "./CertAuthDetailsByIDPage";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/cert-manager/_cert-manager-layout/ca/$caName"
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/ca/$caName"
|
||||
)({
|
||||
component: CertAuthDetailsByIDPage,
|
||||
beforeLoad: ({ context, params }) => {
|
||||
@@ -13,7 +13,7 @@ export const Route = createFileRoute(
|
||||
{
|
||||
label: "Certificate Authorities",
|
||||
link: linkOptions({
|
||||
to: "/projects/$projectId/cert-manager/certificate-authorities",
|
||||
to: "/projects/cert-management/$projectId/certificate-authorities",
|
||||
params: {
|
||||
projectId: params.projectId
|
||||
}
|
||||
|
||||
@@ -30,7 +30,6 @@ import {
|
||||
getCaStatusBadgeVariant
|
||||
} from "@app/hooks/api/ca/constants";
|
||||
import { TInternalCertificateAuthority } from "@app/hooks/api/ca/types";
|
||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||
|
||||
type Props = {
|
||||
@@ -79,7 +78,7 @@ export const CaTable = ({ handlePopUpOpen }: Props) => {
|
||||
key={`ca-${ca.id}`}
|
||||
onClick={() =>
|
||||
navigate({
|
||||
to: `/projects/$projectId/${ProjectType.CertificateManager}/ca/$caName` as const,
|
||||
to: "/projects/cert-management/$projectId/ca/$caName",
|
||||
params: {
|
||||
projectId: currentWorkspace.id,
|
||||
caName: ca.name
|
||||
|
||||
@@ -3,7 +3,7 @@ import { createFileRoute } from "@tanstack/react-router";
|
||||
import { CertificateAuthoritiesPage } from "./CertificateAuthoritiesPage";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/cert-manager/_cert-manager-layout/certificate-authorities"
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/certificate-authorities"
|
||||
)({
|
||||
component: CertificateAuthoritiesPage,
|
||||
beforeLoad: ({ context }) => {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { createFileRoute } from "@tanstack/react-router";
|
||||
import { CertificatesPage } from "./CertificatesPage";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/cert-manager/_cert-manager-layout/certificates"
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/certificates"
|
||||
)({
|
||||
component: CertificatesPage,
|
||||
beforeLoad: ({ context }) => {
|
||||
|
||||
+1
-2
@@ -19,7 +19,6 @@ import { ROUTE_PATHS } from "@app/const/routes";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
|
||||
import { useDeletePkiCollection, useGetPkiCollectionById } from "@app/hooks/api";
|
||||
import { PkiItemType } from "@app/hooks/api/pkiCollections/constants";
|
||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||
import { usePopUp } from "@app/hooks/usePopUp";
|
||||
|
||||
import { PkiCollectionModal } from "../AlertingPage/components/PkiCollectionModal";
|
||||
@@ -57,7 +56,7 @@ export const PkiCollectionPage = () => {
|
||||
});
|
||||
handlePopUpClose("deletePkiCollection");
|
||||
navigate({
|
||||
to: `/projects/$projectId/${ProjectType.CertificateManager}/certificates` as const,
|
||||
to: "/projects/cert-management/$projectId/certificates",
|
||||
params: {
|
||||
projectId
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||
import { PkiCollectionDetailsByIDPage } from "./PkiCollectionDetailsByIDPage";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/cert-manager/_cert-manager-layout/pki-collections/$collectionId"
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/pki-collections/$collectionId"
|
||||
)({
|
||||
component: PkiCollectionDetailsByIDPage,
|
||||
beforeLoad: ({ context, params }) => {
|
||||
@@ -13,7 +13,7 @@ export const Route = createFileRoute(
|
||||
{
|
||||
label: "Certificate Collections",
|
||||
link: linkOptions({
|
||||
to: "/projects/$projectId/cert-manager/certificates",
|
||||
to: "/projects/cert-management/$projectId/certificates",
|
||||
params: {
|
||||
projectId: params.projectId
|
||||
}
|
||||
|
||||
+1
-2
@@ -22,7 +22,6 @@ import {
|
||||
useWorkspace
|
||||
} from "@app/context";
|
||||
import { useDeletePkiSubscriber, useGetPkiSubscriber } from "@app/hooks/api";
|
||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||
import { usePopUp } from "@app/hooks/usePopUp";
|
||||
|
||||
import { PkiSubscriberModal } from "../PkiSubscribersPage/components/PkiSubscriberModal";
|
||||
@@ -61,7 +60,7 @@ const Page = () => {
|
||||
|
||||
handlePopUpClose("deletePkiSubscriber");
|
||||
navigate({
|
||||
to: `/projects/$projectId/${ProjectType.CertificateManager}/subscribers` as const,
|
||||
to: "/projects/cert-management/$projectId/subscribers",
|
||||
params: {
|
||||
projectId
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||
import { PkiSubscriberDetailsByIDPage } from "./PkiSubscriberDetailsByIDPage";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/cert-manager/_cert-manager-layout/subscribers/$subscriberName"
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/subscribers/$subscriberName"
|
||||
)({
|
||||
component: PkiSubscriberDetailsByIDPage,
|
||||
beforeLoad: ({ context, params }) => {
|
||||
@@ -13,7 +13,7 @@ export const Route = createFileRoute(
|
||||
{
|
||||
label: "Subscribers",
|
||||
link: linkOptions({
|
||||
to: "/projects/$projectId/cert-manager/subscribers",
|
||||
to: "/projects/cert-management/$projectId/subscribers",
|
||||
params: {
|
||||
projectId: params.projectId
|
||||
}
|
||||
|
||||
+1
-2
@@ -38,7 +38,6 @@ import {
|
||||
PkiSubscriberStatus,
|
||||
pkiSubscriberStatusToNameMap
|
||||
} from "@app/hooks/api/pkiSubscriber/constants";
|
||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||
|
||||
type Props = {
|
||||
@@ -76,7 +75,7 @@ export const PkiSubscribersTable = ({ handlePopUpOpen }: Props) => {
|
||||
key={`pki-subscriber-${subscriber.id}`}
|
||||
onClick={() =>
|
||||
navigate({
|
||||
to: `/projects/$projectId/${ProjectType.CertificateManager}/subscribers/$subscriberName` as const,
|
||||
to: "/projects/cert-management/$projectId/subscribers/$subscriberName",
|
||||
params: {
|
||||
projectId: currentWorkspace.id,
|
||||
subscriberName: subscriber.name
|
||||
|
||||
@@ -3,7 +3,7 @@ import { createFileRoute } from "@tanstack/react-router";
|
||||
import { PkiSubscribersPage } from "./PkiSubscribersPage";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/cert-manager/_cert-manager-layout/subscribers/"
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/subscribers/"
|
||||
)({
|
||||
component: PkiSubscribersPage,
|
||||
beforeLoad: ({ context }) => {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { createFileRoute } from "@tanstack/react-router";
|
||||
import { PkiTemplateListPage } from "./PkiTemplateListPage";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/cert-manager/_cert-manager-layout/certificate-templates/"
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/certificate-templates/"
|
||||
)({
|
||||
component: PkiTemplateListPage,
|
||||
beforeLoad: ({ context }) => {
|
||||
|
||||
@@ -2,12 +2,13 @@ import { Helmet } from "react-helmet";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { PageHeader, Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
|
||||
import { ProjectGeneralTab } from "@app/pages/project/SettingsPage/components/ProjectGeneralTab";
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
name: "General",
|
||||
key: "tab-project-general",
|
||||
Component: () => <div className="text-white">Coming soon</div>
|
||||
Component: ProjectGeneralTab
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { createFileRoute } from "@tanstack/react-router";
|
||||
import { SettingsPage } from "./SettingsPage";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/cert-manager/_cert-manager-layout/settings"
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/settings"
|
||||
)({
|
||||
component: SettingsPage,
|
||||
beforeLoad: ({ context }) => {
|
||||
|
||||
@@ -1,9 +1,37 @@
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
|
||||
import { BreadcrumbTypes } from "@app/components/v2";
|
||||
import { workspaceKeys } from "@app/hooks/api";
|
||||
import { fetchUserProjectPermissions, roleQueryKeys } from "@app/hooks/api/roles/queries";
|
||||
import { fetchWorkspaceById } from "@app/hooks/api/workspace/queries";
|
||||
import { PkiManagerLayout } from "@app/layouts/PkiManagerLayout";
|
||||
import { ProjectSelect } from "@app/layouts/ProjectLayout/components/ProjectSelect";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/cert-manager/_cert-manager-layout"
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout"
|
||||
)({
|
||||
component: PkiManagerLayout
|
||||
component: PkiManagerLayout,
|
||||
beforeLoad: async ({ params, context }) => {
|
||||
const project = await context.queryClient.ensureQueryData({
|
||||
queryKey: workspaceKeys.getWorkspaceById(params.projectId),
|
||||
queryFn: () => fetchWorkspaceById(params.projectId)
|
||||
});
|
||||
|
||||
await context.queryClient.ensureQueryData({
|
||||
queryKey: roleQueryKeys.getUserProjectPermissions({
|
||||
workspaceId: params.projectId
|
||||
}),
|
||||
queryFn: () => fetchUserProjectPermissions({ workspaceId: params.projectId })
|
||||
});
|
||||
|
||||
return {
|
||||
project,
|
||||
breadcrumbs: [
|
||||
{
|
||||
type: BreadcrumbTypes.Component,
|
||||
component: ProjectSelect
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
@@ -3,7 +3,7 @@ import { createFileRoute } from "@tanstack/react-router";
|
||||
import { KmipPage } from "./KmipPage";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/kms/_kms-layout/kmip"
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/kms/$projectId/_kms-layout/kmip"
|
||||
)({
|
||||
component: KmipPage,
|
||||
beforeLoad: ({ context }) => {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { createFileRoute } from "@tanstack/react-router";
|
||||
import { OverviewPage } from "./OverviewPage";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/kms/_kms-layout/overview"
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/kms/$projectId/_kms-layout/overview"
|
||||
)({
|
||||
component: OverviewPage,
|
||||
beforeLoad: ({ context }) => {
|
||||
|
||||
@@ -2,12 +2,13 @@ import { Helmet } from "react-helmet";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { PageHeader, Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
|
||||
import { ProjectGeneralTab } from "@app/pages/project/SettingsPage/components/ProjectGeneralTab";
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
name: "General",
|
||||
key: "tab-project-general",
|
||||
Component: () => <div className="text-white">Coming soon...</div>
|
||||
Component: ProjectGeneralTab
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { createFileRoute } from "@tanstack/react-router";
|
||||
import { SettingsPage } from "./SettingsPage";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/kms/_kms-layout/settings"
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/kms/$projectId/_kms-layout/settings"
|
||||
)({
|
||||
component: SettingsPage,
|
||||
beforeLoad: ({ context }) => {
|
||||
|
||||
@@ -1,9 +1,37 @@
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
|
||||
import { BreadcrumbTypes } from "@app/components/v2";
|
||||
import { workspaceKeys } from "@app/hooks/api";
|
||||
import { fetchUserProjectPermissions, roleQueryKeys } from "@app/hooks/api/roles/queries";
|
||||
import { fetchWorkspaceById } from "@app/hooks/api/workspace/queries";
|
||||
import { KmsLayout } from "@app/layouts/KmsLayout";
|
||||
import { ProjectSelect } from "@app/layouts/ProjectLayout/components/ProjectSelect";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/kms/_kms-layout"
|
||||
"/_authenticate/_inject-org-details/_org-layout/projects/kms/$projectId/_kms-layout"
|
||||
)({
|
||||
component: KmsLayout
|
||||
component: KmsLayout,
|
||||
beforeLoad: async ({ params, context }) => {
|
||||
const project = await context.queryClient.ensureQueryData({
|
||||
queryKey: workspaceKeys.getWorkspaceById(params.projectId),
|
||||
queryFn: () => fetchWorkspaceById(params.projectId)
|
||||
});
|
||||
|
||||
await context.queryClient.ensureQueryData({
|
||||
queryKey: roleQueryKeys.getUserProjectPermissions({
|
||||
workspaceId: params.projectId
|
||||
}),
|
||||
queryFn: () => fetchUserProjectPermissions({ workspaceId: params.projectId })
|
||||
});
|
||||
|
||||
return {
|
||||
project,
|
||||
breadcrumbs: [
|
||||
{
|
||||
type: BreadcrumbTypes.Component,
|
||||
component: ProjectSelect
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
+2
-2
@@ -105,7 +105,7 @@ export const OrgAdminProjects = withPermission(
|
||||
<TBody>
|
||||
{isProjectsLoading && <TableSkeleton columns={4} innerKey="projects" />}
|
||||
{!isProjectsLoading &&
|
||||
projects?.map(({ name, slug, createdAt, id, defaultProduct }) => (
|
||||
projects?.map(({ name, slug, createdAt, id, type }) => (
|
||||
<Tr key={`project-${id}`} className="group w-full">
|
||||
<Td>{name}</Td>
|
||||
<Td>{slug}</Td>
|
||||
@@ -126,7 +126,7 @@ export const OrgAdminProjects = withPermission(
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
handleAccessProject(defaultProduct, id);
|
||||
handleAccessProject(type, id);
|
||||
}}
|
||||
icon={<FontAwesomeIcon icon={faSignIn} />}
|
||||
disabled={
|
||||
|
||||
+5
@@ -21,6 +21,7 @@ import { CamundaConnectionForm } from "./CamundaConnectionForm";
|
||||
import { ChecklyConnectionForm } from "./ChecklyConnectionForm";
|
||||
import { CloudflareConnectionForm } from "./CloudflareConnectionForm";
|
||||
import { DatabricksConnectionForm } from "./DatabricksConnectionForm";
|
||||
import { DigitalOceanConnectionForm } from "./DigitalOceanConnectionForm";
|
||||
import { FlyioConnectionForm } from "./FlyioConnectionForm";
|
||||
import { GcpConnectionForm } from "./GcpConnectionForm";
|
||||
import { GitHubConnectionForm } from "./GitHubConnectionForm";
|
||||
@@ -150,6 +151,8 @@ const CreateForm = ({ app, onComplete }: CreateFormProps) => {
|
||||
return <ChecklyConnectionForm onSubmit={onSubmit} />;
|
||||
case AppConnection.Supabase:
|
||||
return <SupabaseConnectionForm onSubmit={onSubmit} />;
|
||||
case AppConnection.DigitalOcean:
|
||||
return <DigitalOceanConnectionForm onSubmit={onSubmit} />;
|
||||
case AppConnection.Okta:
|
||||
return <OktaConnectionForm onSubmit={onSubmit} />;
|
||||
default:
|
||||
@@ -256,6 +259,8 @@ const UpdateForm = ({ appConnection, onComplete }: UpdateFormProps) => {
|
||||
return <ChecklyConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
|
||||
case AppConnection.Supabase:
|
||||
return <SupabaseConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
|
||||
case AppConnection.DigitalOcean:
|
||||
return <DigitalOceanConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
|
||||
case AppConnection.Okta:
|
||||
return <OktaConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
|
||||
default:
|
||||
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
import { Controller, FormProvider, useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import {
|
||||
Button,
|
||||
FormControl,
|
||||
ModalClose,
|
||||
SecretInput,
|
||||
Select,
|
||||
SelectItem
|
||||
} from "@app/components/v2";
|
||||
import { APP_CONNECTION_MAP, getAppConnectionMethodDetails } from "@app/helpers/appConnections";
|
||||
import { AppConnection } from "@app/hooks/api/appConnections/enums";
|
||||
import {
|
||||
DigitalOceanConnectionMethod,
|
||||
TDigitalOceanConnection
|
||||
} from "@app/hooks/api/appConnections/types/digital-ocean";
|
||||
|
||||
import {
|
||||
genericAppConnectionFieldsSchema,
|
||||
GenericAppConnectionsFields
|
||||
} from "./GenericAppConnectionFields";
|
||||
|
||||
type Props = {
|
||||
appConnection?: TDigitalOceanConnection;
|
||||
onSubmit: (formData: FormData) => void;
|
||||
};
|
||||
|
||||
const rootSchema = genericAppConnectionFieldsSchema.extend({
|
||||
app: z.literal(AppConnection.DigitalOcean)
|
||||
});
|
||||
|
||||
const formSchema = z.discriminatedUnion("method", [
|
||||
rootSchema.extend({
|
||||
method: z.literal(DigitalOceanConnectionMethod.ApiToken),
|
||||
credentials: z.object({
|
||||
apiToken: z.string().trim().min(1, "API Token required")
|
||||
})
|
||||
})
|
||||
]);
|
||||
|
||||
type FormData = z.infer<typeof formSchema>;
|
||||
|
||||
export const DigitalOceanConnectionForm = ({ appConnection, onSubmit }: Props) => {
|
||||
const isUpdate = Boolean(appConnection);
|
||||
|
||||
const form = useForm<FormData>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: appConnection ?? {
|
||||
app: AppConnection.DigitalOcean,
|
||||
method: DigitalOceanConnectionMethod.ApiToken
|
||||
}
|
||||
});
|
||||
|
||||
const {
|
||||
handleSubmit,
|
||||
control,
|
||||
formState: { isSubmitting, isDirty }
|
||||
} = form;
|
||||
|
||||
return (
|
||||
<FormProvider {...form}>
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
{!isUpdate && <GenericAppConnectionsFields />}
|
||||
<Controller
|
||||
name="method"
|
||||
control={control}
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
tooltipText={`The type of token you would like to use to connect with ${
|
||||
APP_CONNECTION_MAP[AppConnection.DigitalOcean].name
|
||||
}. This field cannot be changed after creation.`}
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error?.message)}
|
||||
label="Token Type"
|
||||
>
|
||||
<Select
|
||||
isDisabled={isUpdate}
|
||||
value={value}
|
||||
onValueChange={(val) => onChange(val)}
|
||||
className="w-full border border-mineshaft-500"
|
||||
position="popper"
|
||||
dropdownContainerClassName="max-w-none"
|
||||
>
|
||||
{Object.values(DigitalOceanConnectionMethod).map((method) => {
|
||||
return (
|
||||
<SelectItem value={method} key={method}>
|
||||
{getAppConnectionMethodDetails(method).name}{" "}
|
||||
</SelectItem>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="credentials.apiToken"
|
||||
control={control}
|
||||
shouldUnregister
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error?.message)}
|
||||
label="API Key Value"
|
||||
>
|
||||
<SecretInput
|
||||
containerClassName="text-gray-400 group-focus-within:!border-primary-400/50 border border-mineshaft-500 bg-mineshaft-900 px-2.5 py-1.5"
|
||||
value={value}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className="mt-8 flex items-center">
|
||||
<Button
|
||||
className="mr-4"
|
||||
size="sm"
|
||||
type="submit"
|
||||
colorSchema="secondary"
|
||||
isLoading={isSubmitting}
|
||||
isDisabled={isSubmitting || !isDirty}
|
||||
>
|
||||
{isUpdate ? "Update Credentials" : "Connect to DigitalOcean"}
|
||||
</Button>
|
||||
<ModalClose asChild>
|
||||
<Button colorSchema="secondary" variant="plain">
|
||||
Cancel
|
||||
</Button>
|
||||
</ModalClose>
|
||||
</div>
|
||||
</form>
|
||||
</FormProvider>
|
||||
);
|
||||
};
|
||||
@@ -300,10 +300,10 @@ export const LogsFilter = ({ presets, setFilter, filter }: Props) => {
|
||||
onChange(e);
|
||||
}}
|
||||
placeholder="All projects"
|
||||
options={workspacesInOrg.map(({ name, id, defaultProduct }) => ({
|
||||
options={workspacesInOrg.map(({ name, id, type }) => ({
|
||||
name,
|
||||
id,
|
||||
type: defaultProduct
|
||||
type
|
||||
}))}
|
||||
getOptionValue={(option) => option.id}
|
||||
getOptionLabel={(option) => option.name}
|
||||
|
||||
+2
-1
@@ -6,6 +6,7 @@ import { format } from "date-fns";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { IconButton, Td, Tooltip, Tr } from "@app/components/v2";
|
||||
import { getProjectBaseURL } from "@app/helpers/project";
|
||||
import { formatProjectRoleName } from "@app/helpers/roles";
|
||||
import { useGetUserWorkspaces } from "@app/hooks/api";
|
||||
import { IdentityMembership } from "@app/hooks/api/identities/types";
|
||||
@@ -50,7 +51,7 @@ export const IdentityProjectRow = ({
|
||||
onClick={() => {
|
||||
if (isAccessible) {
|
||||
navigate({
|
||||
to: "/projects/$projectId/access-management",
|
||||
to: `${getProjectBaseURL(project.type)}/access-management` as const,
|
||||
params: {
|
||||
projectId: project.id
|
||||
},
|
||||
|
||||
@@ -38,7 +38,8 @@ export const ProjectsPage = () => {
|
||||
"upgradePlan"
|
||||
] as const);
|
||||
|
||||
const { data: serverDetails } = useFetchServerStatus();
|
||||
const { data: serverDetails, isLoading } = useFetchServerStatus();
|
||||
|
||||
const { subscription } = useSubscription();
|
||||
|
||||
const isAddingProjectsAllowed = subscription?.workspaceLimit
|
||||
@@ -51,7 +52,8 @@ export const ProjectsPage = () => {
|
||||
<title>{t("common.head-title", { title: t("settings.members.title") })}</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
</Helmet>
|
||||
{!serverDetails?.redisConfigured && (
|
||||
|
||||
{!isLoading && !serverDetails?.redisConfigured && (
|
||||
<div className="mb-4 flex flex-col items-start justify-start text-3xl">
|
||||
<p className="mb-4 mr-4 font-semibold text-white">Announcements</p>
|
||||
<div className="flex w-full items-center rounded-md border border-blue-400/70 bg-blue-900/70 p-2 text-base text-mineshaft-100">
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
faArrowDownAZ,
|
||||
faBorderAll,
|
||||
faCheck,
|
||||
faCheckCircle,
|
||||
faFolderOpen,
|
||||
faList,
|
||||
faMagnifyingGlass,
|
||||
@@ -16,10 +17,17 @@ import { twMerge } from "tailwind-merge";
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { OrgPermissionCan } from "@app/components/permissions";
|
||||
import {
|
||||
Badge,
|
||||
Button,
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuTrigger,
|
||||
FormControl,
|
||||
IconButton,
|
||||
Input,
|
||||
Lottie,
|
||||
Modal,
|
||||
ModalContent,
|
||||
Pagination,
|
||||
@@ -27,7 +35,7 @@ import {
|
||||
Tooltip
|
||||
} from "@app/components/v2";
|
||||
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context";
|
||||
import { getProjectHomePage } from "@app/helpers/project";
|
||||
import { getProjectHomePage, getProjectLottieIcon, getProjectTitle } from "@app/helpers/project";
|
||||
import {
|
||||
getUserTablePreference,
|
||||
PreferenceKey,
|
||||
@@ -35,7 +43,7 @@ import {
|
||||
} from "@app/helpers/userTablePreferences";
|
||||
import { useDebounce, usePagination, usePopUp, useResetPageHelper } from "@app/hooks";
|
||||
import { useRequestProjectAccess, useSearchProjects } from "@app/hooks/api";
|
||||
import { Workspace } from "@app/hooks/api/workspace/types";
|
||||
import { ProjectType, Workspace } from "@app/hooks/api/workspace/types";
|
||||
|
||||
type Props = {
|
||||
onAddNewProject: () => void;
|
||||
@@ -98,6 +106,7 @@ export const AllProjectView = ({
|
||||
const navigate = useNavigate();
|
||||
const [searchFilter, setSearchFilter] = useState("");
|
||||
const [debouncedSearch] = useDebounce(searchFilter);
|
||||
const [projectTypeFilter, setProjectTypeFilter] = useState<ProjectType>();
|
||||
const {
|
||||
setPage,
|
||||
perPage,
|
||||
@@ -124,7 +133,8 @@ export const AllProjectView = ({
|
||||
limit,
|
||||
offset,
|
||||
name: debouncedSearch || undefined,
|
||||
orderDirection
|
||||
orderDirection,
|
||||
type: projectTypeFilter
|
||||
});
|
||||
|
||||
useResetPageHelper({
|
||||
@@ -134,6 +144,9 @@ export const AllProjectView = ({
|
||||
});
|
||||
const requestedWorkspaceDetails = (popUp.requestAccessConfirmation.data || {}) as Workspace;
|
||||
|
||||
const handleToggleFilterByProjectType = (el: ProjectType) =>
|
||||
setProjectTypeFilter((state) => (state === el ? undefined : el));
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex w-full flex-row">
|
||||
@@ -160,16 +173,62 @@ export const AllProjectView = ({
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<div
|
||||
className={twMerge(
|
||||
"ml-2 flex rounded-md border border-mineshaft-600 bg-mineshaft-800 p-1",
|
||||
projectTypeFilter && "border-primary-400 text-primary-400"
|
||||
)}
|
||||
>
|
||||
<Tooltip content="Choose visible project type" className="mb-2">
|
||||
<IconButton
|
||||
ariaLabel="project-types"
|
||||
className={twMerge(
|
||||
"min-w-[2.4rem] border-none hover:bg-mineshaft-600",
|
||||
projectTypeFilter && "text-primary-400"
|
||||
)}
|
||||
variant="plain"
|
||||
size="xs"
|
||||
colorSchema="secondary"
|
||||
>
|
||||
<FontAwesomeIcon icon={faList} />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="thin-scrollbar overflow-y-auto" align="end">
|
||||
<DropdownMenuLabel>Filter By Project Type</DropdownMenuLabel>
|
||||
{Object.values(ProjectType).map((el) => (
|
||||
<DropdownMenuItem
|
||||
key={`filter-item-${el}`}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
handleToggleFilterByProjectType(el);
|
||||
}}
|
||||
icon={projectTypeFilter === el && <FontAwesomeIcon icon={faCheckCircle} />}
|
||||
iconPos="right"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<span>{getProjectTitle(el)}</span>
|
||||
</div>
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<div className="ml-2 flex rounded-md border border-mineshaft-600 bg-mineshaft-800 p-1">
|
||||
<Tooltip content="Disabled across All Project view.">
|
||||
<IconButton
|
||||
variant="outline_bg"
|
||||
ariaLabel="grid"
|
||||
size="xs"
|
||||
className="min-w-[2.4rem] border-none bg-transparent hover:bg-mineshaft-600"
|
||||
>
|
||||
<FontAwesomeIcon icon={faBorderAll} />
|
||||
</IconButton>
|
||||
<div className="flex cursor-not-allowed items-center justify-center">
|
||||
<IconButton
|
||||
variant="outline_bg"
|
||||
ariaLabel="grid"
|
||||
size="xs"
|
||||
isDisabled
|
||||
className="pointer-events-none min-w-[2.4rem] border-none bg-transparent hover:bg-mineshaft-600"
|
||||
>
|
||||
<FontAwesomeIcon icon={faBorderAll} />
|
||||
</IconButton>
|
||||
</div>
|
||||
</Tooltip>
|
||||
<IconButton
|
||||
variant="outline_bg"
|
||||
@@ -184,7 +243,7 @@ export const AllProjectView = ({
|
||||
{(isAllowed) => (
|
||||
<Button
|
||||
isDisabled={!isAllowed}
|
||||
colorSchema="primary"
|
||||
colorSchema="secondary"
|
||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||
onClick={() => {
|
||||
if (isAddingProjectsAllowed) {
|
||||
@@ -222,7 +281,7 @@ export const AllProjectView = ({
|
||||
onKeyDown={(evt) => {
|
||||
if (evt.key === "Enter" && workspace.isMember) {
|
||||
navigate({
|
||||
to: getProjectHomePage(workspace.defaultProduct),
|
||||
to: getProjectHomePage(workspace.type),
|
||||
params: {
|
||||
projectId: workspace.id
|
||||
}
|
||||
@@ -232,7 +291,7 @@ export const AllProjectView = ({
|
||||
onClick={() => {
|
||||
if (workspace.isMember) {
|
||||
navigate({
|
||||
to: getProjectHomePage(workspace.defaultProduct),
|
||||
to: getProjectHomePage(workspace.type),
|
||||
params: {
|
||||
projectId: workspace.id
|
||||
}
|
||||
@@ -241,38 +300,41 @@ export const AllProjectView = ({
|
||||
}}
|
||||
key={workspace.id}
|
||||
className={twMerge(
|
||||
"group flex min-w-72 grid-cols-6 items-center justify-center border-l border-r border-t border-mineshaft-600 bg-mineshaft-800 px-6 py-3 first:rounded-t-md",
|
||||
"group flex min-w-72 items-center justify-center border-l border-r border-t border-mineshaft-600 bg-mineshaft-800 px-6 py-3 first:rounded-t-md",
|
||||
workspace.isMember ? "cursor-pointer hover:bg-mineshaft-700" : "cursor-default"
|
||||
)}
|
||||
>
|
||||
<div className="w-full items-center">
|
||||
<div className="flex flex-grow items-center">
|
||||
<div className="flex-grow truncate text-sm text-mineshaft-100">
|
||||
{workspace.name}
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
{workspace.isMember ? (
|
||||
<div className="flex items-center text-center text-sm text-primary">
|
||||
<FontAwesomeIcon icon={faCheck} className="mr-2" />
|
||||
Joined
|
||||
</div>
|
||||
) : (
|
||||
<div className="opacity-0 transition-all group-hover:opacity-100">
|
||||
<Button
|
||||
size="xs"
|
||||
variant="outline_bg"
|
||||
onClick={() => handlePopUpOpen("requestAccessConfirmation", workspace)}
|
||||
>
|
||||
Request Access
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="mr-3 flex min-w-0 flex-1 items-center gap-3">
|
||||
<div className="rounded border border-mineshaft-500 bg-mineshaft-600 p-1 shadow-inner">
|
||||
<Lottie
|
||||
className="h-[1.35rem] w-[1.35rem] shrink-0"
|
||||
icon={getProjectLottieIcon(workspace.type)}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-1 max-w-lg overflow-hidden text-ellipsis whitespace-nowrap text-xs text-mineshaft-300">
|
||||
{workspace.description}
|
||||
<div className="-mt-0.5 flex min-w-0 flex-col">
|
||||
<p className="truncate text-sm text-mineshaft-100">{workspace.name}</p>
|
||||
<p className="truncate text-xs leading-4 text-mineshaft-300">
|
||||
{getProjectTitle(workspace.type)}{" "}
|
||||
{workspace.description ? `- ${workspace.description}` : ""}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{workspace.isMember ? (
|
||||
<Badge className="flex items-center" variant="success">
|
||||
<FontAwesomeIcon icon={faCheck} className="mr-1" />
|
||||
<span>Joined</span>
|
||||
</Badge>
|
||||
) : (
|
||||
<div className="opacity-0 transition-all group-hover:opacity-100">
|
||||
<Button
|
||||
size="xs"
|
||||
variant="outline_bg"
|
||||
onClick={() => handlePopUpOpen("requestAccessConfirmation", workspace)}
|
||||
>
|
||||
Request Access
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -2,9 +2,9 @@ import { ReactNode, useMemo, useState } from "react";
|
||||
import { faFolderOpen, faStar } from "@fortawesome/free-regular-svg-icons";
|
||||
import {
|
||||
faArrowDownAZ,
|
||||
faArrowRight,
|
||||
faArrowUpZA,
|
||||
faBorderAll,
|
||||
faCheckCircle,
|
||||
faList,
|
||||
faMagnifyingGlass,
|
||||
faPlus,
|
||||
@@ -13,12 +13,26 @@ import {
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { useNavigate } from "@tanstack/react-router";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { OrgPermissionCan } from "@app/components/permissions";
|
||||
import { Button, IconButton, Input, Pagination, Skeleton, Tooltip } from "@app/components/v2";
|
||||
import {
|
||||
Button,
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuTrigger,
|
||||
IconButton,
|
||||
Input,
|
||||
Lottie,
|
||||
Pagination,
|
||||
Skeleton,
|
||||
Tooltip
|
||||
} from "@app/components/v2";
|
||||
import { OrgPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context";
|
||||
import { getProjectHomePage } from "@app/helpers/project";
|
||||
import { getProjectHomePage, getProjectLottieIcon, getProjectTitle } from "@app/helpers/project";
|
||||
import {
|
||||
getUserTablePreference,
|
||||
PreferenceKey,
|
||||
@@ -29,7 +43,7 @@ import { useGetUserWorkspaces } from "@app/hooks/api";
|
||||
import { OrderByDirection } from "@app/hooks/api/generic/types";
|
||||
import { useUpdateUserProjectFavorites } from "@app/hooks/api/users/mutation";
|
||||
import { useGetUserProjectFavorites } from "@app/hooks/api/users/queries";
|
||||
import { Workspace } from "@app/hooks/api/workspace/types";
|
||||
import { ProjectType, Workspace } from "@app/hooks/api/workspace/types";
|
||||
|
||||
type Props = {
|
||||
onAddNewProject: () => void;
|
||||
@@ -53,6 +67,9 @@ export const MyProjectView = ({
|
||||
}: Props) => {
|
||||
const navigate = useNavigate();
|
||||
const { currentOrg } = useOrganization();
|
||||
const [projectTypeFilter, setProjectTypeFilter] = useState<Partial<Record<ProjectType, boolean>>>(
|
||||
{}
|
||||
);
|
||||
|
||||
const { data: workspaces = [], isPending: isWorkspaceLoading } = useGetUserWorkspaces();
|
||||
const {
|
||||
@@ -67,6 +84,7 @@ export const MyProjectView = ({
|
||||
} = usePagination(ProjectOrderBy.Name, {
|
||||
initPerPage: getUserTablePreference("myProjectsTable", PreferenceKey.PerPage, 24)
|
||||
});
|
||||
const isTableFilteredByType = Boolean(Object.values(projectTypeFilter).some((el) => el));
|
||||
|
||||
const handlePerPageChange = (newPerPage: number) => {
|
||||
setPerPage(newPerPage);
|
||||
@@ -88,13 +106,18 @@ export const MyProjectView = ({
|
||||
const filteredWorkspaces = useMemo(
|
||||
() =>
|
||||
workspaces
|
||||
.filter((ws) => ws?.name?.toLowerCase().includes(searchFilter.toLowerCase()))
|
||||
.filter((ws) => {
|
||||
if (isTableFilteredByType && !projectTypeFilter?.[ws.type]) {
|
||||
return false;
|
||||
}
|
||||
return ws?.name?.toLowerCase().includes(searchFilter.toLowerCase());
|
||||
})
|
||||
.sort((a, b) =>
|
||||
orderDirection === OrderByDirection.ASC
|
||||
? a.name.toLowerCase().localeCompare(b.name.toLowerCase())
|
||||
: b.name.toLowerCase().localeCompare(a.name.toLowerCase())
|
||||
),
|
||||
[searchFilter, orderDirection, workspaces]
|
||||
[searchFilter, orderDirection, workspaces, projectTypeFilter]
|
||||
);
|
||||
|
||||
useResetPageHelper({
|
||||
@@ -117,6 +140,15 @@ export const MyProjectView = ({
|
||||
};
|
||||
}, [filteredWorkspaces, projectFavorites, offset, limit, page]);
|
||||
|
||||
const handleToggleFilterByProjectType = (type: ProjectType) => {
|
||||
setProjectTypeFilter((state) => {
|
||||
return {
|
||||
...(state || {}),
|
||||
[type]: !state?.[type]
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const addProjectToFavorites = async (projectId: string) => {
|
||||
try {
|
||||
if (currentOrg?.id) {
|
||||
@@ -153,61 +185,53 @@ export const MyProjectView = ({
|
||||
<div
|
||||
onClick={() => {
|
||||
navigate({
|
||||
to: getProjectHomePage(workspace.defaultProduct),
|
||||
to: getProjectHomePage(workspace.type),
|
||||
params: {
|
||||
projectId: workspace.id
|
||||
}
|
||||
});
|
||||
}}
|
||||
key={workspace.id}
|
||||
className="flex h-40 min-w-72 cursor-pointer flex-col rounded-md border border-mineshaft-600 bg-mineshaft-800 p-4"
|
||||
className="cursor-pointer overflow-clip rounded border border-l-[4px] border-mineshaft-600 border-l-mineshaft-400 bg-mineshaft-800 p-4 transition-transform duration-100 hover:scale-[103%] hover:border-l-primary hover:bg-mineshaft-700"
|
||||
>
|
||||
<div className="flex flex-row justify-between">
|
||||
<div className="mt-0 truncate text-lg text-mineshaft-100">{workspace.name}</div>
|
||||
{isFavorite ? (
|
||||
<FontAwesomeIcon
|
||||
icon={faSolidStar}
|
||||
className="text-sm text-yellow-600 hover:text-mineshaft-400"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
removeProjectFromFavorites(workspace.id);
|
||||
}}
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="rounded border border-mineshaft-500 bg-mineshaft-600 p-1.5 shadow-inner">
|
||||
<Lottie
|
||||
className="h-[1.75rem] w-[1.75rem] shrink-0"
|
||||
icon={getProjectLottieIcon(workspace.type)}
|
||||
/>
|
||||
) : (
|
||||
<FontAwesomeIcon
|
||||
icon={faStar}
|
||||
className="text-sm text-mineshaft-400 hover:text-mineshaft-300"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
addProjectToFavorites(workspace.id);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="mb-2.5 mt-1 grow text-sm text-mineshaft-300"
|
||||
style={{
|
||||
overflow: "hidden",
|
||||
display: "-webkit-box",
|
||||
WebkitBoxOrient: "vertical",
|
||||
WebkitLineClamp: 2
|
||||
}}
|
||||
>
|
||||
{workspace.description}
|
||||
</div>
|
||||
|
||||
<div className="flex w-full flex-row items-end justify-between place-self-end">
|
||||
<button type="button">
|
||||
<div className="group ml-auto w-max cursor-pointer rounded-full border border-mineshaft-600 bg-mineshaft-900 px-4 py-2 text-sm text-mineshaft-300 transition-all hover:border-primary-500/80 hover:bg-primary-800/20 hover:text-mineshaft-200">
|
||||
Explore{" "}
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="truncate text-lg font-semibold text-mineshaft-100">{workspace.name}</p>
|
||||
<p className="truncate text-sm leading-4 text-mineshaft-300">
|
||||
{getProjectTitle(workspace.type)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="mt-0.5 self-start">
|
||||
{isFavorite ? (
|
||||
<FontAwesomeIcon
|
||||
icon={faArrowRight}
|
||||
className="pl-1.5 pr-0.5 duration-200 hover:pl-2 hover:pr-0"
|
||||
icon={faSolidStar}
|
||||
className="text-sm text-yellow-600 hover:text-mineshaft-400"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
removeProjectFromFavorites(workspace.id);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</button>
|
||||
) : (
|
||||
<FontAwesomeIcon
|
||||
icon={faStar}
|
||||
className="text-sm text-mineshaft-400 hover:text-mineshaft-300"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
addProjectToFavorites(workspace.id);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<p className="mt-4 truncate text-sm text-mineshaft-400">
|
||||
{workspace.description || "No description"}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
const renderProjectListItem = (workspace: Workspace, isFavorite: boolean, index: number) => (
|
||||
@@ -215,21 +239,33 @@ export const MyProjectView = ({
|
||||
<div
|
||||
onClick={() => {
|
||||
navigate({
|
||||
to: getProjectHomePage(workspace.defaultProduct),
|
||||
to: getProjectHomePage(workspace.type),
|
||||
params: {
|
||||
projectId: workspace.id
|
||||
}
|
||||
});
|
||||
}}
|
||||
key={workspace.id}
|
||||
className={`group grid h-14 min-w-72 cursor-pointer grid-cols-6 border-l border-r border-t border-mineshaft-600 bg-mineshaft-800 px-6 hover:bg-mineshaft-700 ${
|
||||
className={`group flex min-w-72 cursor-pointer border-l border-r border-t border-mineshaft-600 bg-mineshaft-800 px-6 py-3 hover:bg-mineshaft-700 ${
|
||||
index === 0 && "rounded-t-md"
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center sm:col-span-3 lg:col-span-4">
|
||||
<div className="truncate text-sm text-mineshaft-100">{workspace.name}</div>
|
||||
<div className="flex min-w-0 flex-1 items-center gap-3">
|
||||
<div className="rounded border border-mineshaft-500 bg-mineshaft-600 p-1 shadow-inner">
|
||||
<Lottie
|
||||
className="h-[1.35rem] w-[1.35rem] shrink-0"
|
||||
icon={getProjectLottieIcon(workspace.type)}
|
||||
/>
|
||||
</div>
|
||||
<div className="-mt-0.5 flex min-w-0 flex-col">
|
||||
<p className="truncate text-sm text-mineshaft-100">{workspace.name}</p>
|
||||
<p className="truncate text-xs leading-4 text-mineshaft-300">
|
||||
{getProjectTitle(workspace.type)}{" "}
|
||||
{workspace.description ? `- ${workspace.description}` : ""}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-end sm:col-span-3 lg:col-span-2">
|
||||
<div className="flex items-center justify-end">
|
||||
{isFavorite ? (
|
||||
<FontAwesomeIcon
|
||||
icon={faSolidStar}
|
||||
@@ -258,7 +294,7 @@ export const MyProjectView = ({
|
||||
switch (projectsViewMode) {
|
||||
case ProjectsViewMode.GRID:
|
||||
projectsComponents = (
|
||||
<div className="mt-4 grid w-full grid-cols-1 gap-4 lg:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4">
|
||||
<div className="mt-4 grid w-full grid-cols-1 gap-5 lg:grid-cols-2 xl:grid-cols-3">
|
||||
{isProjectViewLoading &&
|
||||
Array.apply(0, Array(3)).map((_x, i) => (
|
||||
<div
|
||||
@@ -320,7 +356,18 @@ export const MyProjectView = ({
|
||||
<div className="text-center font-light">No projects match search...</div>
|
||||
</div>
|
||||
);
|
||||
} else if (filteredWorkspaces.length === 0 && isTableFilteredByType) {
|
||||
projectsComponents = (
|
||||
<div className="mt-4 w-full rounded-md border border-mineshaft-700 bg-mineshaft-800 px-4 py-6 text-base text-mineshaft-300">
|
||||
<FontAwesomeIcon
|
||||
icon={faSearch}
|
||||
className="mb-4 mt-2 w-full text-center text-5xl text-mineshaft-400"
|
||||
/>
|
||||
<div className="text-center font-light">No projects match filters...</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex w-full flex-row">
|
||||
@@ -351,6 +398,49 @@ export const MyProjectView = ({
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<div
|
||||
className={twMerge(
|
||||
"ml-2 flex rounded-md border border-mineshaft-600 bg-mineshaft-800 p-1",
|
||||
isTableFilteredByType && "border-primary-400 text-primary-400"
|
||||
)}
|
||||
>
|
||||
<Tooltip content="Choose visible project type" className="mb-2">
|
||||
<IconButton
|
||||
ariaLabel="project-types"
|
||||
className={twMerge(
|
||||
"min-w-[2.4rem] border-none hover:bg-mineshaft-600",
|
||||
isTableFilteredByType && "text-primary-400"
|
||||
)}
|
||||
variant="plain"
|
||||
size="xs"
|
||||
colorSchema="secondary"
|
||||
>
|
||||
<FontAwesomeIcon icon={faList} />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="thin-scrollbar overflow-y-auto" align="end">
|
||||
<DropdownMenuLabel>Filter By Project Type</DropdownMenuLabel>
|
||||
{Object.values(ProjectType).map((el) => (
|
||||
<DropdownMenuItem
|
||||
key={`filter-item-${el}`}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
handleToggleFilterByProjectType(el);
|
||||
}}
|
||||
icon={projectTypeFilter?.[el] && <FontAwesomeIcon icon={faCheckCircle} />}
|
||||
iconPos="right"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<span>{getProjectTitle(el)}</span>
|
||||
</div>
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<div className="ml-2 flex rounded-md border border-mineshaft-600 bg-mineshaft-800 p-1">
|
||||
<IconButton
|
||||
variant="outline_bg"
|
||||
@@ -385,7 +475,7 @@ export const MyProjectView = ({
|
||||
{(isAllowed) => (
|
||||
<Button
|
||||
isDisabled={!isAllowed}
|
||||
colorSchema="primary"
|
||||
colorSchema="secondary"
|
||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||
onClick={() => {
|
||||
if (isAddingProjectsAllowed) {
|
||||
@@ -417,7 +507,7 @@ export const MyProjectView = ({
|
||||
onChangePerPage={handlePerPageChange}
|
||||
/>
|
||||
)}
|
||||
{isWorkspaceEmpty && (
|
||||
{isWorkspaceEmpty && !isTableFilteredByType && (
|
||||
<div className="mt-4 w-full rounded-md border border-mineshaft-700 bg-mineshaft-800 px-4 py-6 text-base text-mineshaft-300">
|
||||
<FontAwesomeIcon
|
||||
icon={faFolderOpen}
|
||||
|
||||
@@ -27,7 +27,7 @@ export const ProjectListToggle = ({ value, onChange }: Props) => {
|
||||
value={value}
|
||||
onValueChange={onChange}
|
||||
className="absolute left-0 top-0 h-full w-full cursor-pointer opacity-0"
|
||||
dropdownContainerClassName="mt-10 ml-5"
|
||||
position="popper"
|
||||
>
|
||||
<SelectItem value={ProjectListView.MyProjects}>My Projects</SelectItem>
|
||||
<SelectItem value={ProjectListView.AllProjects}>All Projects</SelectItem>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Helmet } from "react-helmet";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { faCopy, faEllipsisV } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { useNavigate, useParams } from "@tanstack/react-router";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { OrgPermissionCan } from "@app/components/permissions";
|
||||
@@ -12,8 +13,7 @@ import {
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
PageHeader,
|
||||
Tooltip
|
||||
PageHeader
|
||||
} from "@app/components/v2";
|
||||
import { ROUTE_PATHS } from "@app/const/routes";
|
||||
import { OrgPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context";
|
||||
@@ -22,7 +22,7 @@ import { usePopUp } from "@app/hooks/usePopUp";
|
||||
import { DuplicateOrgRoleModal } from "@app/pages/organization/RoleByIDPage/components/DuplicateOrgRoleModal";
|
||||
import { OrgAccessControlTabSections } from "@app/types/org";
|
||||
|
||||
import { RoleDetailsSection, RoleModal, RolePermissionsSection } from "./components";
|
||||
import { RoleModal, RolePermissionsSection } from "./components";
|
||||
|
||||
export const Page = () => {
|
||||
const navigate = useNavigate();
|
||||
@@ -80,29 +80,64 @@ export const Page = () => {
|
||||
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white">
|
||||
{data && (
|
||||
<div className="mx-auto mb-6 w-full max-w-7xl">
|
||||
<PageHeader title={data.name}>
|
||||
<PageHeader
|
||||
title={
|
||||
<div className="flex flex-col">
|
||||
<div>
|
||||
<span>{data.name}</span>
|
||||
<p className="text-sm font-[400] normal-case leading-3 text-mineshaft-400">
|
||||
{data.slug} {data.description && `- ${data.description}`}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
{isCustomRole && (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild className="rounded-lg">
|
||||
<div className="hover:text-primary-400 data-[state=open]:text-primary-400">
|
||||
<Tooltip content="More options">
|
||||
<Button variant="outline_bg">More</Button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
colorSchema="secondary"
|
||||
rightIcon={<FontAwesomeIcon icon={faEllipsisV} className="ml-2" />}
|
||||
>
|
||||
Options
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start" className="p-1">
|
||||
<DropdownMenuContent align="end" sideOffset={2} className="p-1">
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(data.id);
|
||||
|
||||
createNotification({
|
||||
text: "Copied ID to clipboard",
|
||||
type: "info"
|
||||
});
|
||||
}}
|
||||
icon={<FontAwesomeIcon icon={faCopy} />}
|
||||
>
|
||||
Copy ID
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(data.slug);
|
||||
|
||||
createNotification({
|
||||
text: "Copied slug to clipboard",
|
||||
type: "info"
|
||||
});
|
||||
}}
|
||||
icon={<FontAwesomeIcon icon={faCopy} />}
|
||||
>
|
||||
Copy Slug
|
||||
</DropdownMenuItem>
|
||||
<OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Role}>
|
||||
{(isAllowed) => (
|
||||
<DropdownMenuItem
|
||||
className={twMerge(
|
||||
!isAllowed && "pointer-events-none cursor-not-allowed opacity-50"
|
||||
)}
|
||||
onClick={async () => {
|
||||
onClick={() => {
|
||||
handlePopUpOpen("role", {
|
||||
roleId
|
||||
});
|
||||
}}
|
||||
disabled={!isAllowed}
|
||||
isDisabled={!isAllowed}
|
||||
>
|
||||
Edit Role
|
||||
</DropdownMenuItem>
|
||||
@@ -111,13 +146,10 @@ export const Page = () => {
|
||||
<OrgPermissionCan I={OrgPermissionActions.Create} a={OrgPermissionSubjects.Role}>
|
||||
{(isAllowed) => (
|
||||
<DropdownMenuItem
|
||||
className={twMerge(
|
||||
!isAllowed && "pointer-events-none cursor-not-allowed opacity-50"
|
||||
)}
|
||||
onClick={() => {
|
||||
handlePopUpOpen("duplicateRole");
|
||||
}}
|
||||
disabled={!isAllowed}
|
||||
isDisabled={!isAllowed}
|
||||
>
|
||||
Duplicate Role
|
||||
</DropdownMenuItem>
|
||||
@@ -126,15 +158,10 @@ export const Page = () => {
|
||||
<OrgPermissionCan I={OrgPermissionActions.Delete} a={OrgPermissionSubjects.Role}>
|
||||
{(isAllowed) => (
|
||||
<DropdownMenuItem
|
||||
className={twMerge(
|
||||
isAllowed
|
||||
? "hover:!bg-red-500 hover:!text-white"
|
||||
: "pointer-events-none cursor-not-allowed opacity-50"
|
||||
)}
|
||||
onClick={async () => {
|
||||
onClick={() => {
|
||||
handlePopUpOpen("deleteOrgRole");
|
||||
}}
|
||||
disabled={!isAllowed}
|
||||
isDisabled={!isAllowed}
|
||||
>
|
||||
Delete Role
|
||||
</DropdownMenuItem>
|
||||
@@ -144,12 +171,7 @@ export const Page = () => {
|
||||
</DropdownMenu>
|
||||
)}
|
||||
</PageHeader>
|
||||
<div className="flex">
|
||||
<div className="mr-4 w-96">
|
||||
<RoleDetailsSection roleId={roleId} handlePopUpOpen={handlePopUpOpen} />
|
||||
</div>
|
||||
<RolePermissionsSection roleId={roleId} />
|
||||
</div>
|
||||
<RolePermissionsSection roleId={roleId} />
|
||||
</div>
|
||||
)}
|
||||
<RoleModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
import { faCheck, faCopy, faPencil } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import { OrgPermissionCan } from "@app/components/permissions";
|
||||
import { IconButton, Tooltip } from "@app/components/v2";
|
||||
import { OrgPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context";
|
||||
import { useTimedReset } from "@app/hooks";
|
||||
import { useGetOrgRole } from "@app/hooks/api";
|
||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||
|
||||
type Props = {
|
||||
roleId: string;
|
||||
handlePopUpOpen: (popUpName: keyof UsePopUpState<["role"]>, data?: object) => void;
|
||||
};
|
||||
|
||||
export const RoleDetailsSection = ({ roleId, handlePopUpOpen }: Props) => {
|
||||
const [copyTextId, isCopyingId, setCopyTextId] = useTimedReset<string>({
|
||||
initialState: "Copy ID to clipboard"
|
||||
});
|
||||
|
||||
const { currentOrg } = useOrganization();
|
||||
const orgId = currentOrg?.id || "";
|
||||
const { data } = useGetOrgRole(orgId, roleId);
|
||||
const isCustomRole = !["admin", "member", "no-access"].includes(data?.slug ?? "");
|
||||
|
||||
return data ? (
|
||||
<div className="rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||
<div className="flex items-center justify-between border-b border-mineshaft-400 pb-4">
|
||||
<h3 className="text-lg font-semibold text-mineshaft-100">Org Role Details</h3>
|
||||
{isCustomRole && (
|
||||
<OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Role}>
|
||||
{(isAllowed) => {
|
||||
return (
|
||||
<Tooltip content="Edit Role">
|
||||
<IconButton
|
||||
isDisabled={!isAllowed}
|
||||
ariaLabel="copy icon"
|
||||
variant="plain"
|
||||
className="group relative"
|
||||
onClick={() =>
|
||||
handlePopUpOpen("role", {
|
||||
roleId
|
||||
})
|
||||
}
|
||||
>
|
||||
<FontAwesomeIcon icon={faPencil} />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
);
|
||||
}}
|
||||
</OrgPermissionCan>
|
||||
)}
|
||||
</div>
|
||||
<div className="pt-4">
|
||||
<div className="mb-4">
|
||||
<p className="text-sm font-semibold text-mineshaft-300">Role ID</p>
|
||||
<div className="group flex align-top">
|
||||
<p className="text-sm text-mineshaft-300">{roleId}</p>
|
||||
<div className="opacity-0 transition-opacity duration-300 group-hover:opacity-100">
|
||||
<Tooltip content={copyTextId}>
|
||||
<IconButton
|
||||
ariaLabel="copy icon"
|
||||
variant="plain"
|
||||
className="group relative ml-2"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(roleId);
|
||||
setCopyTextId("Copied");
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon icon={isCopyingId ? faCheck : faCopy} />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-4">
|
||||
<p className="text-sm font-semibold text-mineshaft-300">Name</p>
|
||||
<p className="text-sm text-mineshaft-300">{data.name}</p>
|
||||
</div>
|
||||
<div className="mb-4">
|
||||
<p className="text-sm font-semibold text-mineshaft-300">Slug</p>
|
||||
<p className="text-sm text-mineshaft-300">{data.slug}</p>
|
||||
</div>
|
||||
<div className="mb-4">
|
||||
<p className="text-sm font-semibold text-mineshaft-300">Description</p>
|
||||
<p className="text-sm text-mineshaft-300">
|
||||
{data.description?.length ? data.description : "-"}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div />
|
||||
);
|
||||
};
|
||||
+8
-10
@@ -76,17 +76,18 @@ export const OrgPermissionAdminConsoleRow = ({ isEditable, control, setValue }:
|
||||
className="h-10 cursor-pointer transition-colors duration-100 hover:bg-mineshaft-700"
|
||||
onClick={() => setIsRowExpanded.toggle()}
|
||||
>
|
||||
<Td>
|
||||
<FontAwesomeIcon icon={isRowExpanded ? faChevronDown : faChevronRight} />
|
||||
<Td className="w-4">
|
||||
<FontAwesomeIcon className="w-4" icon={isRowExpanded ? faChevronDown : faChevronRight} />
|
||||
</Td>
|
||||
<Td>Organization Admin Console</Td>
|
||||
<Td className="w-full select-none">Organization Admin Console</Td>
|
||||
<Td>
|
||||
<Select
|
||||
value={selectedPermissionCategory}
|
||||
className="w-40 bg-mineshaft-600"
|
||||
dropdownContainerClassName="border border-mineshaft-600 bg-mineshaft-800"
|
||||
className="h-8 w-40 bg-mineshaft-700"
|
||||
dropdownContainerClassName="border text-left border-mineshaft-600 bg-mineshaft-800"
|
||||
onValueChange={handlePermissionChange}
|
||||
isDisabled={!isEditable}
|
||||
position="popper"
|
||||
>
|
||||
<SelectItem value={Permission.NoAccess}>No Access</SelectItem>
|
||||
<SelectItem value={Permission.Custom}>Custom</SelectItem>
|
||||
@@ -95,11 +96,8 @@ export const OrgPermissionAdminConsoleRow = ({ isEditable, control, setValue }:
|
||||
</Tr>
|
||||
{isRowExpanded && (
|
||||
<Tr>
|
||||
<Td
|
||||
colSpan={3}
|
||||
className={`bg-bunker-600 px-0 py-0 ${isRowExpanded && "border-mineshaft-500 p-8"}`}
|
||||
>
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<Td colSpan={3} className="border-mineshaft-500 bg-mineshaft-900 p-8">
|
||||
<div className="flex flex-grow flex-wrap justify-start gap-x-8 gap-y-4">
|
||||
{PERMISSION_ACTIONS.map(({ action, label }) => {
|
||||
return (
|
||||
<Controller
|
||||
|
||||
+8
-10
@@ -124,17 +124,18 @@ export const OrgPermissionAppConnectionRow = ({ isEditable, control, setValue }:
|
||||
className="h-10 cursor-pointer transition-colors duration-100 hover:bg-mineshaft-700"
|
||||
onClick={() => setIsRowExpanded.toggle()}
|
||||
>
|
||||
<Td>
|
||||
<FontAwesomeIcon icon={isRowExpanded ? faChevronDown : faChevronRight} />
|
||||
<Td className="w-4">
|
||||
<FontAwesomeIcon className="w-4" icon={isRowExpanded ? faChevronDown : faChevronRight} />
|
||||
</Td>
|
||||
<Td>App Connections</Td>
|
||||
<Td className="w-full select-none">App Connections</Td>
|
||||
<Td>
|
||||
<Select
|
||||
value={selectedPermissionCategory}
|
||||
className="w-40 bg-mineshaft-600"
|
||||
dropdownContainerClassName="border border-mineshaft-600 bg-mineshaft-800"
|
||||
className="h-8 w-40 bg-mineshaft-700"
|
||||
dropdownContainerClassName="border text-left border-mineshaft-600 bg-mineshaft-800"
|
||||
onValueChange={handlePermissionChange}
|
||||
isDisabled={!isEditable}
|
||||
position="popper"
|
||||
>
|
||||
<SelectItem value={Permission.NoAccess}>No Access</SelectItem>
|
||||
<SelectItem value={Permission.ReadOnly}>Read Only</SelectItem>
|
||||
@@ -145,11 +146,8 @@ export const OrgPermissionAppConnectionRow = ({ isEditable, control, setValue }:
|
||||
</Tr>
|
||||
{isRowExpanded && (
|
||||
<Tr>
|
||||
<Td
|
||||
colSpan={3}
|
||||
className={`bg-bunker-600 px-0 py-0 ${isRowExpanded && "border-mineshaft-500 p-8"}`}
|
||||
>
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<Td colSpan={3} className="border-mineshaft-500 bg-mineshaft-900 p-8">
|
||||
<div className="flex flex-grow flex-wrap justify-start gap-x-8 gap-y-4">
|
||||
{PERMISSION_ACTIONS.map(({ action, label }) => {
|
||||
return (
|
||||
<Controller
|
||||
|
||||
+8
-10
@@ -112,17 +112,18 @@ export const OrgPermissionBillingRow = ({ isEditable, control, setValue }: Props
|
||||
className="h-10 cursor-pointer transition-colors duration-100 hover:bg-mineshaft-700"
|
||||
onClick={() => setIsRowExpanded.toggle()}
|
||||
>
|
||||
<Td>
|
||||
<FontAwesomeIcon icon={isRowExpanded ? faChevronDown : faChevronRight} />
|
||||
<Td className="w-4">
|
||||
<FontAwesomeIcon className="w-4" icon={isRowExpanded ? faChevronDown : faChevronRight} />
|
||||
</Td>
|
||||
<Td>Billing</Td>
|
||||
<Td className="w-full select-none">Billing</Td>
|
||||
<Td>
|
||||
<Select
|
||||
value={selectedPermissionCategory}
|
||||
className="w-40 bg-mineshaft-600"
|
||||
dropdownContainerClassName="border border-mineshaft-600 bg-mineshaft-800"
|
||||
className="h-8 w-40 bg-mineshaft-700"
|
||||
dropdownContainerClassName="border text-left border-mineshaft-600 bg-mineshaft-800"
|
||||
onValueChange={handlePermissionChange}
|
||||
isDisabled={!isEditable}
|
||||
position="popper"
|
||||
>
|
||||
<SelectItem value={Permission.NoAccess}>No Access</SelectItem>
|
||||
<SelectItem value={Permission.ReadOnly}>Read Only</SelectItem>
|
||||
@@ -133,11 +134,8 @@ export const OrgPermissionBillingRow = ({ isEditable, control, setValue }: Props
|
||||
</Tr>
|
||||
{isRowExpanded && (
|
||||
<Tr>
|
||||
<Td
|
||||
colSpan={3}
|
||||
className={`bg-bunker-600 px-0 py-0 ${isRowExpanded && "border-mineshaft-500 p-8"}`}
|
||||
>
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<Td colSpan={3} className="border-mineshaft-500 bg-mineshaft-900 p-8">
|
||||
<div className="flex flex-grow flex-wrap justify-start gap-x-8 gap-y-4">
|
||||
{PERMISSION_ACTIONS.map(({ action, label }) => {
|
||||
return (
|
||||
<Controller
|
||||
|
||||
+8
-10
@@ -121,17 +121,18 @@ export const OrgGatewayPermissionRow = ({ isEditable, control, setValue }: Props
|
||||
className="h-10 cursor-pointer transition-colors duration-100 hover:bg-mineshaft-700"
|
||||
onClick={() => setIsRowExpanded.toggle()}
|
||||
>
|
||||
<Td>
|
||||
<FontAwesomeIcon icon={isRowExpanded ? faChevronDown : faChevronRight} />
|
||||
<Td className="w-4">
|
||||
<FontAwesomeIcon className="w-4" icon={isRowExpanded ? faChevronDown : faChevronRight} />
|
||||
</Td>
|
||||
<Td>Gateways</Td>
|
||||
<Td className="w-full select-none">Gateways</Td>
|
||||
<Td>
|
||||
<Select
|
||||
value={selectedPermissionCategory}
|
||||
className="w-40 bg-mineshaft-600"
|
||||
dropdownContainerClassName="border border-mineshaft-600 bg-mineshaft-800"
|
||||
className="h-8 w-40 bg-mineshaft-700"
|
||||
dropdownContainerClassName="border text-left border-mineshaft-600 bg-mineshaft-800"
|
||||
onValueChange={handlePermissionChange}
|
||||
isDisabled={!isEditable}
|
||||
position="popper"
|
||||
>
|
||||
<SelectItem value={Permission.NoAccess}>No Access</SelectItem>
|
||||
<SelectItem value={Permission.ReadOnly}>Read Only</SelectItem>
|
||||
@@ -142,11 +143,8 @@ export const OrgGatewayPermissionRow = ({ isEditable, control, setValue }: Props
|
||||
</Tr>
|
||||
{isRowExpanded && (
|
||||
<Tr>
|
||||
<Td
|
||||
colSpan={3}
|
||||
className={`bg-bunker-600 px-0 py-0 ${isRowExpanded && "border-mineshaft-500 p-8"}`}
|
||||
>
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<Td colSpan={3} className="border-mineshaft-500 bg-mineshaft-900 p-8">
|
||||
<div className="flex flex-grow flex-wrap justify-start gap-x-8 gap-y-4">
|
||||
{PERMISSION_ACTIONS.map(({ action, label }) => {
|
||||
return (
|
||||
<Controller
|
||||
|
||||
+8
-10
@@ -145,17 +145,18 @@ export const OrgPermissionGroupRow = ({ isEditable, control, setValue }: Props)
|
||||
className="h-10 cursor-pointer transition-colors duration-100 hover:bg-mineshaft-700"
|
||||
onClick={() => setIsRowExpanded.toggle()}
|
||||
>
|
||||
<Td>
|
||||
<FontAwesomeIcon icon={isRowExpanded ? faChevronDown : faChevronRight} />
|
||||
<Td className="w-4">
|
||||
<FontAwesomeIcon className="w-4" icon={isRowExpanded ? faChevronDown : faChevronRight} />
|
||||
</Td>
|
||||
<Td>Group Management</Td>
|
||||
<Td className="w-full select-none">Group Management</Td>
|
||||
<Td>
|
||||
<Select
|
||||
value={selectedPermissionCategory}
|
||||
className="w-40 bg-mineshaft-600"
|
||||
dropdownContainerClassName="border border-mineshaft-600 bg-mineshaft-800"
|
||||
className="h-8 w-40 bg-mineshaft-700"
|
||||
dropdownContainerClassName="border text-left border-mineshaft-600 bg-mineshaft-800"
|
||||
onValueChange={handlePermissionChange}
|
||||
isDisabled={!isEditable}
|
||||
position="popper"
|
||||
>
|
||||
<SelectItem value={Permission.NoAccess}>No Access</SelectItem>
|
||||
<SelectItem value={Permission.ReadOnly}>Read Only</SelectItem>
|
||||
@@ -166,11 +167,8 @@ export const OrgPermissionGroupRow = ({ isEditable, control, setValue }: Props)
|
||||
</Tr>
|
||||
{isRowExpanded && (
|
||||
<Tr>
|
||||
<Td
|
||||
colSpan={3}
|
||||
className={`bg-bunker-600 px-0 py-0 ${isRowExpanded && "border-mineshaft-500 p-8"}`}
|
||||
>
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<Td colSpan={3} className="border-mineshaft-500 bg-mineshaft-900 p-8">
|
||||
<div className="flex flex-grow flex-wrap justify-start gap-x-8 gap-y-4">
|
||||
{PERMISSION_ACTIONS.map(({ action, label }) => {
|
||||
return (
|
||||
<Controller
|
||||
|
||||
+8
-10
@@ -155,17 +155,18 @@ export const OrgPermissionIdentityRow = ({ isEditable, control, setValue }: Prop
|
||||
className="h-10 cursor-pointer transition-colors duration-100 hover:bg-mineshaft-700"
|
||||
onClick={() => setIsRowExpanded.toggle()}
|
||||
>
|
||||
<Td>
|
||||
<FontAwesomeIcon icon={isRowExpanded ? faChevronDown : faChevronRight} />
|
||||
<Td className="w-4">
|
||||
<FontAwesomeIcon className="w-4" icon={isRowExpanded ? faChevronDown : faChevronRight} />
|
||||
</Td>
|
||||
<Td>Machine Identity Management</Td>
|
||||
<Td className="w-full select-none">Machine Identity Management</Td>
|
||||
<Td>
|
||||
<Select
|
||||
value={selectedPermissionCategory}
|
||||
className="w-40 bg-mineshaft-600"
|
||||
dropdownContainerClassName="border border-mineshaft-600 bg-mineshaft-800"
|
||||
className="h-8 w-40 bg-mineshaft-700"
|
||||
dropdownContainerClassName="border text-left border-mineshaft-600 bg-mineshaft-800"
|
||||
onValueChange={handlePermissionChange}
|
||||
isDisabled={!isEditable}
|
||||
position="popper"
|
||||
>
|
||||
<SelectItem value={Permission.NoAccess}>No Access</SelectItem>
|
||||
<SelectItem value={Permission.ReadOnly}>Read Only</SelectItem>
|
||||
@@ -176,11 +177,8 @@ export const OrgPermissionIdentityRow = ({ isEditable, control, setValue }: Prop
|
||||
</Tr>
|
||||
{isRowExpanded && (
|
||||
<Tr>
|
||||
<Td
|
||||
colSpan={3}
|
||||
className={`bg-bunker-600 px-0 py-0 ${isRowExpanded && "border-mineshaft-500 p-8"}`}
|
||||
>
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<Td colSpan={3} className="border-mineshaft-500 bg-mineshaft-900 p-8">
|
||||
<div className="flex flex-grow flex-wrap justify-start gap-x-8 gap-y-4">
|
||||
{PERMISSION_ACTIONS.map(({ action, label }) => {
|
||||
return (
|
||||
<Controller
|
||||
|
||||
+8
-10
@@ -73,17 +73,18 @@ export const OrgPermissionKmipRow = ({ isEditable, control, setValue }: Props) =
|
||||
className="h-10 cursor-pointer transition-colors duration-100 hover:bg-mineshaft-700"
|
||||
onClick={() => setIsRowExpanded.toggle()}
|
||||
>
|
||||
<Td>
|
||||
<FontAwesomeIcon icon={isRowExpanded ? faChevronDown : faChevronRight} />
|
||||
<Td className="w-4">
|
||||
<FontAwesomeIcon className="w-4" icon={isRowExpanded ? faChevronDown : faChevronRight} />
|
||||
</Td>
|
||||
<Td>KMIP</Td>
|
||||
<Td className="w-full select-none">KMIP</Td>
|
||||
<Td>
|
||||
<Select
|
||||
value={selectedPermissionCategory}
|
||||
className="w-40 bg-mineshaft-600"
|
||||
dropdownContainerClassName="border border-mineshaft-600 bg-mineshaft-800"
|
||||
className="h-8 w-40 bg-mineshaft-700"
|
||||
dropdownContainerClassName="border text-left border-mineshaft-600 bg-mineshaft-800"
|
||||
onValueChange={handlePermissionChange}
|
||||
isDisabled={!isEditable}
|
||||
position="popper"
|
||||
>
|
||||
<SelectItem value={Permission.NoAccess}>No Access</SelectItem>
|
||||
<SelectItem value={Permission.Custom}>Custom</SelectItem>
|
||||
@@ -92,11 +93,8 @@ export const OrgPermissionKmipRow = ({ isEditable, control, setValue }: Props) =
|
||||
</Tr>
|
||||
{isRowExpanded && (
|
||||
<Tr>
|
||||
<Td
|
||||
colSpan={3}
|
||||
className={`bg-bunker-600 px-0 py-0 ${isRowExpanded && "border-mineshaft-500 p-8"}`}
|
||||
>
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<Td colSpan={3} className="border-mineshaft-500 bg-mineshaft-900 p-8">
|
||||
<div className="flex flex-grow flex-wrap justify-start gap-x-8 gap-y-4">
|
||||
{PERMISSION_ACTIONS.map(({ action, label }) => {
|
||||
return (
|
||||
<Controller
|
||||
|
||||
+8
-10
@@ -70,17 +70,18 @@ export const OrgPermissionSecretShareRow = ({ isEditable, control, setValue }: P
|
||||
className="h-10 cursor-pointer transition-colors duration-100 hover:bg-mineshaft-700"
|
||||
onClick={() => setIsRowExpanded.toggle()}
|
||||
>
|
||||
<Td>
|
||||
<FontAwesomeIcon icon={isRowExpanded ? faChevronDown : faChevronRight} />
|
||||
<Td className="w-4">
|
||||
<FontAwesomeIcon className="w-4" icon={isRowExpanded ? faChevronDown : faChevronRight} />
|
||||
</Td>
|
||||
<Td>Secret Share</Td>
|
||||
<Td className="w-full select-none">Secret Share</Td>
|
||||
<Td>
|
||||
<Select
|
||||
value={selectedPermissionCategory}
|
||||
className="w-40 bg-mineshaft-600"
|
||||
dropdownContainerClassName="border border-mineshaft-600 bg-mineshaft-800"
|
||||
className="h-8 w-40 bg-mineshaft-700"
|
||||
dropdownContainerClassName="border text-left border-mineshaft-600 bg-mineshaft-800"
|
||||
onValueChange={handlePermissionChange}
|
||||
isDisabled={!isEditable}
|
||||
position="popper"
|
||||
>
|
||||
<SelectItem value={Permission.NoAccess}>No Access</SelectItem>
|
||||
<SelectItem value={Permission.Custom}>Custom</SelectItem>
|
||||
@@ -89,11 +90,8 @@ export const OrgPermissionSecretShareRow = ({ isEditable, control, setValue }: P
|
||||
</Tr>
|
||||
{isRowExpanded && (
|
||||
<Tr>
|
||||
<Td
|
||||
colSpan={3}
|
||||
className={`bg-bunker-600 px-0 py-0 ${isRowExpanded && "border-mineshaft-500 p-8"}`}
|
||||
>
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<Td colSpan={3} className="border-mineshaft-500 bg-mineshaft-900 p-8">
|
||||
<div className="flex flex-grow flex-wrap justify-start gap-x-8 gap-y-4">
|
||||
{PERMISSION_ACTIONS.map(({ action, label }) => {
|
||||
return (
|
||||
<Controller
|
||||
|
||||
+8
-10
@@ -70,17 +70,18 @@ export const OrgRoleWorkspaceRow = ({ isEditable, control, setValue }: Props) =>
|
||||
className="h-10 cursor-pointer transition-colors duration-100 hover:bg-mineshaft-700"
|
||||
onClick={() => setIsRowExpanded.toggle()}
|
||||
>
|
||||
<Td>
|
||||
<FontAwesomeIcon icon={isRowExpanded ? faChevronDown : faChevronRight} />
|
||||
<Td className="w-4">
|
||||
<FontAwesomeIcon className="w-4" icon={isRowExpanded ? faChevronDown : faChevronRight} />
|
||||
</Td>
|
||||
<Td>Project</Td>
|
||||
<Td className="w-full select-none">Project</Td>
|
||||
<Td>
|
||||
<Select
|
||||
value={selectedPermissionCategory}
|
||||
className="w-40 bg-mineshaft-600"
|
||||
dropdownContainerClassName="border border-mineshaft-600 bg-mineshaft-800"
|
||||
className="h-8 w-40 bg-mineshaft-700"
|
||||
dropdownContainerClassName="border text-left border-mineshaft-600 bg-mineshaft-800"
|
||||
onValueChange={handlePermissionChange}
|
||||
isDisabled={!isEditable}
|
||||
position="popper"
|
||||
>
|
||||
<SelectItem value={Permission.NoAccess}>No Access</SelectItem>
|
||||
<SelectItem value={Permission.Custom}>Custom</SelectItem>
|
||||
@@ -89,11 +90,8 @@ export const OrgRoleWorkspaceRow = ({ isEditable, control, setValue }: Props) =>
|
||||
</Tr>
|
||||
{isRowExpanded && (
|
||||
<Tr>
|
||||
<Td
|
||||
colSpan={3}
|
||||
className={`bg-bunker-600 px-0 py-0 ${isRowExpanded && "border-mineshaft-500 p-8"}`}
|
||||
>
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<Td colSpan={3} className="border-mineshaft-500 bg-mineshaft-900 p-8">
|
||||
<div className="flex flex-grow flex-wrap justify-start gap-x-8 gap-y-4">
|
||||
{PERMISSION_ACTIONS.map(({ action, label }) => {
|
||||
return (
|
||||
<Controller
|
||||
|
||||
+8
-10
@@ -158,17 +158,18 @@ export const RolePermissionRow = ({ isEditable, title, formName, control, setVal
|
||||
className="h-10 cursor-pointer transition-colors duration-100 hover:bg-mineshaft-700"
|
||||
onClick={() => setIsRowExpanded.toggle()}
|
||||
>
|
||||
<Td>
|
||||
<FontAwesomeIcon icon={isRowExpanded ? faChevronDown : faChevronRight} />
|
||||
<Td className="w-4">
|
||||
<FontAwesomeIcon className="w-4" icon={isRowExpanded ? faChevronDown : faChevronRight} />
|
||||
</Td>
|
||||
<Td>{title}</Td>
|
||||
<Td className="w-full select-none">{title}</Td>
|
||||
<Td>
|
||||
<Select
|
||||
value={selectedPermissionCategory}
|
||||
className="w-40 bg-mineshaft-600"
|
||||
dropdownContainerClassName="border border-mineshaft-600 bg-mineshaft-800"
|
||||
className="h-8 w-40 bg-mineshaft-700"
|
||||
dropdownContainerClassName="border text-left border-mineshaft-600 bg-mineshaft-800"
|
||||
onValueChange={handlePermissionChange}
|
||||
isDisabled={!isEditable}
|
||||
position="popper"
|
||||
>
|
||||
<SelectItem value={Permission.NoAccess}>No Access</SelectItem>
|
||||
<SelectItem value={Permission.ReadOnly}>Read Only</SelectItem>
|
||||
@@ -179,11 +180,8 @@ export const RolePermissionRow = ({ isEditable, title, formName, control, setVal
|
||||
</Tr>
|
||||
{isRowExpanded && (
|
||||
<Tr>
|
||||
<Td
|
||||
colSpan={3}
|
||||
className={`bg-bunker-600 px-0 py-0 ${isRowExpanded && "border-mineshaft-500 p-8"}`}
|
||||
>
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<Td colSpan={3} className="border-mineshaft-500 bg-mineshaft-900 p-8">
|
||||
<div className="flex flex-grow flex-wrap justify-start gap-x-8 gap-y-4">
|
||||
{getPermissionList(formName).map(({ action, label }) => {
|
||||
return (
|
||||
<Controller
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user