Merge branch 'main' into feat/zabbixSyncIntegration

This commit is contained in:
Carlos Monastyrski
2025-07-02 09:25:19 -03:00
651 changed files with 12487 additions and 13813 deletions
@@ -7,7 +7,6 @@ import {
ProjectPermissionCommitsActions,
ProjectPermissionSub
} from "@app/context/ProjectPermissionContext/types";
import { ProjectType } from "@app/hooks/api/workspace/types";
import { CommitDetailsTab } from "./components/CommitDetailsTab";
@@ -35,7 +34,7 @@ export const CommitDetailsPage = () => {
const handleGoBackToHistory = () => {
navigate({
to: `/${ProjectType.SecretManager}/$projectId/commits/$environment/$folderId` as const,
to: "/projects/$projectId/secret-manager/commits/$environment/$folderId",
params: {
projectId: currentWorkspace.id,
folderId,
@@ -50,7 +49,7 @@ export const CommitDetailsPage = () => {
const handleGoToRollbackPreview = () => {
navigate({
to: `/${ProjectType.SecretManager}/$projectId/commits/$environment/$folderId/$commitId/restore` as const,
to: "/projects/$projectId/secret-manager/commits/$environment/$folderId/$commitId/restore",
params: {
projectId: currentWorkspace.id,
folderId,
@@ -24,7 +24,6 @@ import {
} from "@app/context/ProjectPermissionContext/types";
import { usePopUp } from "@app/hooks";
import { useCommitRollback, useGetRollbackPreview } from "@app/hooks/api/folderCommits/queries";
import { ProjectType } from "@app/hooks/api/workspace/types";
import { SecretVersionDiffView } from "../SecretVersionDiffView";
@@ -103,7 +102,7 @@ export const RollbackPreviewTab = (): JSX.Element => {
const goBackToHistory = () => {
navigate({
to: `/${ProjectType.SecretManager}/$projectId/commits/$environment/$folderId` as const,
to: "/projects/$projectId/secret-manager/commits/$environment/$folderId",
params: {
projectId: currentWorkspace.id,
folderId,
@@ -12,7 +12,7 @@ const RollbackPreviewTabQueryParamsSchema = z.object({
});
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/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"
)({
component: RollbackPreviewTab,
validateSearch: zodValidator(RollbackPreviewTabQueryParamsSchema),
@@ -33,7 +33,7 @@ export const Route = createFileRoute(
links: context.project.environments.map((el) => ({
label: el.name,
link: linkOptions({
to: "/secret-manager/$projectId/secrets/$envSlug",
to: "/projects/$projectId/secret-manager/secrets/$envSlug",
params: {
projectId: params.projectId,
envSlug: el.slug
@@ -56,7 +56,7 @@ export const Route = createFileRoute(
{
label: "Commits",
link: linkOptions({
to: "/secret-manager/$projectId/commits/$environment/$folderId",
to: "/projects/$projectId/secret-manager/commits/$environment/$folderId",
params: {
projectId: params.projectId,
environment: params.environment,
@@ -70,7 +70,7 @@ export const Route = createFileRoute(
{
label: params.commitId,
link: linkOptions({
to: "/secret-manager/$projectId/commits/$environment/$folderId/$commitId",
to: "/projects/$projectId/secret-manager/commits/$environment/$folderId/$commitId",
params: {
projectId: params.projectId,
environment: params.environment,
@@ -12,7 +12,7 @@ const CommitDetailsPageQueryParamsSchema = z.object({
});
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/commits/$environment/$folderId/$commitId/"
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/commits/$environment/$folderId/$commitId/"
)({
component: CommitDetailsPage,
validateSearch: zodValidator(CommitDetailsPageQueryParamsSchema),
@@ -33,7 +33,7 @@ export const Route = createFileRoute(
links: context.project.environments.map((el) => ({
label: el.name,
link: linkOptions({
to: "/secret-manager/$projectId/secrets/$envSlug",
to: "/projects/$projectId/secret-manager/secrets/$envSlug",
params: {
projectId: params.projectId,
envSlug: el.slug
@@ -56,7 +56,7 @@ export const Route = createFileRoute(
{
label: "Commits",
link: linkOptions({
to: "/secret-manager/$projectId/commits/$environment/$folderId",
to: "/projects/$projectId/secret-manager/commits/$environment/$folderId",
params: {
projectId: params.projectId,
environment: params.environment,
@@ -9,7 +9,6 @@ import {
ProjectPermissionCommitsActions,
ProjectPermissionSub
} from "@app/context/ProjectPermissionContext/types";
import { ProjectType } from "@app/hooks/api/workspace/types";
import { CommitHistoryTab } from "./components/CommitHistoryTab";
@@ -32,7 +31,7 @@ export const CommitsPage = () => {
const handleSelectCommit = (commitId: string) => {
navigate({
to: `/${ProjectType.SecretManager}/$projectId/commits/$environment/$folderId/$commitId` as const,
to: "/projects/$projectId/secret-manager/commits/$environment/$folderId/$commitId",
params: {
projectId: currentWorkspace.id,
folderId,
@@ -13,7 +13,7 @@ const CommitsPageQueryParamsSchema = z.object({
});
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/commits/$environment/$folderId/"
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/commits/$environment/$folderId/"
)({
component: CommitsPage,
validateSearch: zodValidator(CommitsPageQueryParamsSchema),
@@ -34,7 +34,7 @@ export const Route = createFileRoute(
links: context.project.environments.map((el) => ({
label: el.name,
link: linkOptions({
to: "/secret-manager/$projectId/secrets/$envSlug",
to: "/projects/$projectId/secret-manager/secrets/$envSlug",
params: {
projectId: params.projectId,
envSlug: el.slug
@@ -57,7 +57,7 @@ export const Route = createFileRoute(
{
label: "Commits",
link: linkOptions({
to: "/secret-manager/$projectId/commits/$environment/$folderId",
to: "/projects/$projectId/secret-manager/commits/$environment/$folderId",
params: {
projectId: params.projectId,
environment: params.environment,
@@ -3,7 +3,7 @@ import { createFileRoute } from "@tanstack/react-router";
import { IPAllowListPage } from "./IPAllowlistPage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/allowlist"
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/allowlist"
)({
component: () => IPAllowListPage
});
@@ -5,7 +5,7 @@ import { IntegrationsListPageTabs } from "@app/types/integrations";
import { IntegrationDetailsByIDPage } from "./IntegrationsDetailsByIDPage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/$integrationId"
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/$integrationId"
)({
component: IntegrationDetailsByIDPage,
beforeLoad: ({ context, params }) => {
@@ -15,7 +15,7 @@ export const Route = createFileRoute(
{
label: "Integrations",
link: linkOptions({
to: "/secret-manager/$projectId/integrations",
to: "/projects/$projectId/secret-manager/integrations",
params,
search: {
selectedTab: IntegrationsListPageTabs.NativeIntegrations
@@ -69,7 +69,7 @@ export const redirectForProviderAuth = (
switch (integrationOption.slug) {
case "gcp-secret-manager":
navigate({
to: "/secret-manager/$projectId/integrations/gcp-secret-manager/authorize",
to: "/projects/$projectId/secret-manager/integrations/gcp-secret-manager/authorize",
params: {
projectId
}
@@ -81,7 +81,7 @@ export const redirectForProviderAuth = (
return;
}
navigate({
to: "/secret-manager/$projectId/integrations/azure-key-vault/authorize",
to: "/projects/$projectId/secret-manager/integrations/azure-key-vault/authorize",
params: {
projectId
},
@@ -103,7 +103,7 @@ export const redirectForProviderAuth = (
}
case "aws-parameter-store":
navigate({
to: "/secret-manager/$projectId/integrations/aws-parameter-store/authorize",
to: "/projects/$projectId/secret-manager/integrations/aws-parameter-store/authorize",
params: {
projectId
}
@@ -111,7 +111,7 @@ export const redirectForProviderAuth = (
break;
case "aws-secret-manager":
navigate({
to: "/secret-manager/$projectId/integrations/aws-secret-manager/authorize",
to: "/projects/$projectId/secret-manager/integrations/aws-secret-manager/authorize",
params: {
projectId
}
@@ -147,7 +147,7 @@ export const redirectForProviderAuth = (
}
case "github":
navigate({
to: "/secret-manager/$projectId/integrations/github/auth-mode-selection",
to: "/projects/$projectId/secret-manager/integrations/github/auth-mode-selection",
params: {
projectId
}
@@ -155,7 +155,7 @@ export const redirectForProviderAuth = (
break;
case "gitlab":
navigate({
to: "/secret-manager/$projectId/integrations/gitlab/authorize",
to: "/projects/$projectId/secret-manager/integrations/gitlab/authorize",
params: {
projectId
}
@@ -163,7 +163,7 @@ export const redirectForProviderAuth = (
break;
case "render":
navigate({
to: "/secret-manager/$projectId/integrations/render/authorize",
to: "/projects/$projectId/secret-manager/integrations/render/authorize",
params: {
projectId
}
@@ -171,7 +171,7 @@ export const redirectForProviderAuth = (
break;
case "flyio":
navigate({
to: "/secret-manager/$projectId/integrations/flyio/authorize",
to: "/projects/$projectId/secret-manager/integrations/flyio/authorize",
params: {
projectId
}
@@ -179,7 +179,7 @@ export const redirectForProviderAuth = (
break;
case "circleci":
navigate({
to: "/secret-manager/$projectId/integrations/circleci/authorize",
to: "/projects/$projectId/secret-manager/integrations/circleci/authorize",
params: {
projectId
}
@@ -187,7 +187,7 @@ export const redirectForProviderAuth = (
break;
case "databricks":
navigate({
to: "/secret-manager/$projectId/integrations/databricks/authorize",
to: "/projects/$projectId/secret-manager/integrations/databricks/authorize",
params: {
projectId
}
@@ -195,7 +195,7 @@ export const redirectForProviderAuth = (
break;
case "laravel-forge":
navigate({
to: "/secret-manager/$projectId/integrations/laravel-forge/authorize",
to: "/projects/$projectId/secret-manager/integrations/laravel-forge/authorize",
params: {
projectId
}
@@ -203,7 +203,7 @@ export const redirectForProviderAuth = (
break;
case "travisci":
navigate({
to: "/secret-manager/$projectId/integrations/travisci/authorize",
to: "/projects/$projectId/secret-manager/integrations/travisci/authorize",
params: {
projectId
}
@@ -211,7 +211,7 @@ export const redirectForProviderAuth = (
break;
case "supabase":
navigate({
to: "/secret-manager/$projectId/integrations/supabase/authorize",
to: "/projects/$projectId/secret-manager/integrations/supabase/authorize",
params: {
projectId
}
@@ -219,7 +219,7 @@ export const redirectForProviderAuth = (
break;
case "checkly":
navigate({
to: "/secret-manager/$projectId/integrations/checkly/authorize",
to: "/projects/$projectId/secret-manager/integrations/checkly/authorize",
params: {
projectId
}
@@ -227,7 +227,7 @@ export const redirectForProviderAuth = (
break;
case "qovery":
navigate({
to: "/secret-manager/$projectId/integrations/qovery/authorize",
to: "/projects/$projectId/secret-manager/integrations/qovery/authorize",
params: {
projectId
}
@@ -235,7 +235,7 @@ export const redirectForProviderAuth = (
break;
case "railway":
navigate({
to: "/secret-manager/$projectId/integrations/railway/authorize",
to: "/projects/$projectId/secret-manager/integrations/railway/authorize",
params: {
projectId
}
@@ -243,7 +243,7 @@ export const redirectForProviderAuth = (
break;
case "terraform-cloud":
navigate({
to: "/secret-manager/$projectId/integrations/terraform-cloud/authorize",
to: "/projects/$projectId/secret-manager/integrations/terraform-cloud/authorize",
params: {
projectId
}
@@ -251,7 +251,7 @@ export const redirectForProviderAuth = (
break;
case "hashicorp-vault":
navigate({
to: "/secret-manager/$projectId/integrations/hashicorp-vault/authorize",
to: "/projects/$projectId/secret-manager/integrations/hashicorp-vault/authorize",
params: {
projectId
}
@@ -259,7 +259,7 @@ export const redirectForProviderAuth = (
break;
case "cloudflare-pages":
navigate({
to: "/secret-manager/$projectId/integrations/cloudflare-pages/authorize",
to: "/projects/$projectId/secret-manager/integrations/cloudflare-pages/authorize",
params: {
projectId
}
@@ -267,7 +267,7 @@ export const redirectForProviderAuth = (
break;
case "cloudflare-workers":
navigate({
to: "/secret-manager/$projectId/integrations/cloudflare-workers/authorize",
to: "/projects/$projectId/secret-manager/integrations/cloudflare-workers/authorize",
params: {
projectId
}
@@ -284,7 +284,7 @@ export const redirectForProviderAuth = (
}
case "codefresh":
navigate({
to: "/secret-manager/$projectId/integrations/codefresh/authorize",
to: "/projects/$projectId/secret-manager/integrations/codefresh/authorize",
params: {
projectId
}
@@ -292,7 +292,7 @@ export const redirectForProviderAuth = (
break;
case "digital-ocean-app-platform":
navigate({
to: "/secret-manager/$projectId/integrations/digital-ocean-app-platform/authorize",
to: "/projects/$projectId/secret-manager/integrations/digital-ocean-app-platform/authorize",
params: {
projectId
}
@@ -300,7 +300,7 @@ export const redirectForProviderAuth = (
break;
case "cloud-66":
navigate({
to: "/secret-manager/$projectId/integrations/cloud-66/authorize",
to: "/projects/$projectId/secret-manager/integrations/cloud-66/authorize",
params: {
projectId
}
@@ -308,7 +308,7 @@ export const redirectForProviderAuth = (
break;
case "northflank":
navigate({
to: "/secret-manager/$projectId/integrations/northflank/authorize",
to: "/projects/$projectId/secret-manager/integrations/northflank/authorize",
params: {
projectId
}
@@ -316,7 +316,7 @@ export const redirectForProviderAuth = (
break;
case "windmill":
navigate({
to: "/secret-manager/$projectId/integrations/windmill/authorize",
to: "/projects/$projectId/secret-manager/integrations/windmill/authorize",
params: {
projectId
}
@@ -324,7 +324,7 @@ export const redirectForProviderAuth = (
break;
case "teamcity":
navigate({
to: "/secret-manager/$projectId/integrations/teamcity/authorize",
to: "/projects/$projectId/secret-manager/integrations/teamcity/authorize",
params: {
projectId
}
@@ -332,7 +332,7 @@ export const redirectForProviderAuth = (
break;
case "hasura-cloud":
navigate({
to: "/secret-manager/$projectId/integrations/hasura-cloud/authorize",
to: "/projects/$projectId/secret-manager/integrations/hasura-cloud/authorize",
params: {
projectId
}
@@ -340,7 +340,7 @@ export const redirectForProviderAuth = (
break;
case "rundeck":
navigate({
to: "/secret-manager/$projectId/integrations/rundeck/authorize",
to: "/projects/$projectId/secret-manager/integrations/rundeck/authorize",
params: {
projectId
}
@@ -348,7 +348,7 @@ export const redirectForProviderAuth = (
break;
case "azure-devops":
navigate({
to: "/secret-manager/$projectId/integrations/azure-devops/authorize",
to: "/projects/$projectId/secret-manager/integrations/azure-devops/authorize",
params: {
projectId
}
@@ -356,7 +356,7 @@ export const redirectForProviderAuth = (
break;
case "octopus-deploy":
navigate({
to: "/secret-manager/$projectId/integrations/octopus-deploy/authorize",
to: "/projects/$projectId/secret-manager/integrations/octopus-deploy/authorize",
params: {
projectId
}
@@ -59,7 +59,7 @@ export const IntegrationRow = ({
<Tr
onClick={() =>
navigate({
to: "/secret-manager/$projectId/integrations/$integrationId",
to: "/projects/$projectId/secret-manager/integrations/$integrationId",
params: {
integrationId: integration.id,
projectId: currentWorkspace.id
@@ -21,7 +21,7 @@ const IntegrationsListPageQuerySchema = z.object({
});
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/"
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/"
)({
component: IntegrationsListPage,
validateSearch: zodValidator(IntegrationsListPageQuerySchema),
@@ -36,7 +36,7 @@ export const Route = createFileRoute(
});
} catch {
throw redirect({
to: "/secret-manager/$projectId/integrations",
to: "/projects/$projectId/secret-manager/integrations",
params: {
projectId
},
@@ -46,7 +46,7 @@ export const Route = createFileRoute(
if (secretSyncs.length) {
throw redirect({
to: "/secret-manager/$projectId/integrations",
to: "/projects/$projectId/secret-manager/integrations",
params: {
projectId
},
@@ -62,7 +62,7 @@ export const Route = createFileRoute(
});
} catch {
throw redirect({
to: "/secret-manager/$projectId/integrations",
to: "/projects/$projectId/secret-manager/integrations",
params: {
projectId
},
@@ -72,7 +72,7 @@ export const Route = createFileRoute(
if (integrations.length) {
throw redirect({
to: "/secret-manager/$projectId/integrations",
to: "/projects/$projectId/secret-manager/integrations",
params: {
projectId
},
@@ -81,7 +81,7 @@ export const Route = createFileRoute(
}
throw redirect({
to: "/secret-manager/$projectId/integrations",
to: "/projects/$projectId/secret-manager/integrations",
params: {
projectId
},
@@ -42,6 +42,7 @@ import {
DropdownMenuTrigger,
EmptyState,
IconButton,
Lottie,
Modal,
ModalContent,
PageHeader,
@@ -88,7 +89,7 @@ import { useUpdateFolderBatch } from "@app/hooks/api/secretFolders/queries";
import { TUpdateFolderBatchDTO } from "@app/hooks/api/secretFolders/types";
import { TSecretRotationV2 } from "@app/hooks/api/secretRotationsV2";
import { SecretType, SecretV3RawSanitized, TSecretFolder } from "@app/hooks/api/types";
import { ProjectType, ProjectVersion } from "@app/hooks/api/workspace/types";
import { ProjectVersion } from "@app/hooks/api/workspace/types";
import {
useDynamicSecretOverview,
useFolderOverview,
@@ -655,7 +656,7 @@ export const OverviewPage = () => {
const envIndex = visibleEnvs.findIndex((el) => slug === el.slug);
if (envIndex !== -1) {
navigate({
to: `/${ProjectType.SecretManager}/$projectId/secrets/$envSlug` as const,
to: "/projects/$projectId/secret-manager/secrets/$envSlug",
params: {
projectId: workspaceId,
envSlug: slug
@@ -863,14 +864,7 @@ export const OverviewPage = () => {
if (isProjectV3 && visibleEnvs.length > 0 && isOverviewLoading) {
return (
<div className="container mx-auto flex h-screen w-full items-center justify-center px-8 text-mineshaft-50 dark:[color-scheme:dark]">
<img
src="/images/loading/loading.gif"
height={70}
width={120}
alt="loading animation"
decoding="async"
loading="lazy"
/>
<Lottie icon="infisical_loading" className="h-32 w-32" />
</div>
);
}
@@ -1371,7 +1365,7 @@ export const OverviewPage = () => {
iconSize="3x"
>
<Link
to={`/${ProjectType.SecretManager}/$projectId/settings` as const}
to="/projects/$projectId/secret-manager/settings"
params={{
projectId: workspaceId
}}
@@ -9,7 +9,7 @@ type Props = {
export const FolderBreadCrumbs = ({ secretPath = "/", onResetSearch }: Props) => {
const navigate = useNavigate({
from: "/secret-manager/$projectId/overview"
from: "/projects/$projectId/secret-manager/overview"
});
const onFolderCrumbClick = (index: number) => {
@@ -17,7 +17,7 @@ export const QuickSearchDynamicSecretItem = ({
onClose
}: Props) => {
const navigate = useNavigate({
from: "/secret-manager/$projectId/overview"
from: "/projects/$projectId/secret-manager/overview"
});
const [groupDynamicSecret] = dynamicSecretGroup;
@@ -13,7 +13,7 @@ type Props = {
export const QuickSearchFolderItem = ({ folderGroup, onClose }: Props) => {
const navigate = useNavigate({
from: "/secret-manager/$projectId/overview"
from: "/projects/$projectId/secret-manager/overview"
});
const [groupFolder] = folderGroup;
@@ -46,7 +46,7 @@ export const QuickSearchSecretItem = ({
isSingleEnv,
search
}: Props) => {
const navigate = useNavigate({ from: "/secret-manager/$projectId/overview" });
const navigate = useNavigate({ from: "/projects/$projectId/secret-manager/overview" });
const envSlugMap = new Map(environments.map((env) => [env.slug, env]));
const [isUrlCopied, , setIsUrlCopied] = useTimedReset<boolean>({
initialState: false
@@ -13,7 +13,7 @@ type Props = {
export const QuickSearchSecretRotationItem = ({ secretRotationGroup, onClose }: Props) => {
const navigate = useNavigate({
from: "/secret-manager/$projectId/overview"
from: "/projects/$projectId/secret-manager/overview"
});
const [groupSecretRotation] = secretRotationGroup;
@@ -12,7 +12,7 @@ import { useProjectPermission, useWorkspace } from "@app/context";
import { usePopUp } from "@app/hooks";
import { useGetWorkspaceById, useMigrateProjectToV3, workspaceKeys } from "@app/hooks/api";
import { ProjectMembershipRole } from "@app/hooks/api/roles/types";
import { ProjectType, ProjectVersion } from "@app/hooks/api/workspace/types";
import { ProjectVersion } from "@app/hooks/api/workspace/types";
enum ProjectUpgradeStatus {
InProgress = "IN_PROGRESS",
@@ -54,7 +54,7 @@ export const SecretV2MigrationSection = () => {
createNotification({ type: "success", text: "Project upgrade completed successfully" });
migrateProjectToV3.reset();
queryClient.invalidateQueries({
queryKey: workspaceKeys.getAllUserWorkspace(ProjectType.SecretManager)
queryKey: workspaceKeys.getAllUserWorkspace()
});
}
}, [isProjectUpgraded, Boolean(migrateProjectToV3.data)]);
@@ -1,4 +1,4 @@
import { createFileRoute, stripSearchParams } from "@tanstack/react-router";
import { createFileRoute, linkOptions, stripSearchParams } from "@tanstack/react-router";
import { zodValidator } from "@tanstack/zod-adapter";
import { z } from "zod";
@@ -10,11 +10,24 @@ const SecretOverviewPageQuerySchema = z.object({
});
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/overview"
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/overview"
)({
component: OverviewPage,
validateSearch: zodValidator(SecretOverviewPageQuerySchema),
search: {
middlewares: [stripSearchParams({ secretPath: "/", search: "" })]
}
},
beforeLoad: ({ context, params }) => ({
...context,
breadcrumbs: [
...context.breadcrumbs,
{
label: "Secrets",
link: linkOptions({
to: "/projects/$projectId/secret-manager/overview",
params
})
}
]
})
});
@@ -247,9 +247,7 @@ export const AccessApprovalRequest = ({
};
else if (userReviewStatus === ApprovalStatus.APPROVED) {
displayData = {
label: `Pending ${request.policy.approvals - request.reviewers.length} review${
request.policy.approvals - request.reviewers.length > 1 ? "s" : ""
}`,
label: "Pending Additional Reviews",
type: "primary",
icon: faClipboardCheck
};
@@ -1,10 +1,9 @@
import { useCallback, useMemo, useState } from "react";
import { ReactNode, useCallback, useMemo, useState } from "react";
import {
faCheckCircle,
faCircle,
faTriangleExclamation,
faUsers,
faXmarkCircle
faBan,
faCheck,
faHourglass,
faTriangleExclamation
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import ms from "ms";
@@ -15,12 +14,10 @@ import {
Button,
Checkbox,
FormControl,
GenericFieldLabel,
Input,
Modal,
ModalContent,
Popover,
PopoverContent,
PopoverTrigger,
Tooltip
} from "@app/components/v2";
import { Badge } from "@app/components/v2/Badge";
@@ -38,10 +35,22 @@ import { groupBy } from "@app/lib/fn/array";
const getReviewedStatusSymbol = (status?: ApprovalStatus) => {
if (status === ApprovalStatus.APPROVED)
return <FontAwesomeIcon icon={faCheckCircle} size="xs" style={{ color: "#15803d" }} />;
return (
<Badge variant="success" className="flex h-4 items-center justify-center">
<FontAwesomeIcon icon={faCheck} size="xs" />
</Badge>
);
if (status === ApprovalStatus.REJECTED)
return <FontAwesomeIcon icon={faXmarkCircle} size="xs" style={{ color: "#b91c1c" }} />;
return <FontAwesomeIcon icon={faCircle} size="xs" style={{ color: "#c2410c" }} />;
return (
<Badge variant="danger" className="flex h-4 items-center justify-center">
<FontAwesomeIcon icon={faBan} size="xs" />
</Badge>
);
return (
<Badge variant="primary" className="flex h-4 items-center justify-center">
<FontAwesomeIcon icon={faHourglass} size="xs" />
</Badge>
);
};
export const ReviewAccessRequestModal = ({
@@ -267,139 +276,160 @@ export const ReviewAccessRequestModal = ({
</div>
<div className="">
<div className="mb-2 mt-4 text-mineshaft-200">
<div className="grid grid-cols-2 gap-4">
<div>
<div className="mb-1 text-xs font-semibold uppercase">Environment</div>
<div>{accessDetails.env || "-"}</div>
</div>
<div>
<div className="mb-1 text-xs font-semibold uppercase">Secret Path</div>
<div>{accessDetails.secretPath || "-"}</div>
</div>
<div>
<div className="mb-1 text-xs font-semibold uppercase">Access Type</div>
<div>{getAccessLabel()}</div>
</div>
<div>
<div className="mb-1 text-xs font-semibold uppercase">Permission</div>
<div>{requestedAccess}</div>
</div>
<div className="col-span-2">
<div className="mb-1 text-xs font-semibold uppercase">Note</div>
<div>{request.note || "-"}</div>
</div>
<div className="flex flex-wrap gap-8">
<GenericFieldLabel label="Environment">{accessDetails.env}</GenericFieldLabel>
<GenericFieldLabel truncate label="Secret Path">
{accessDetails.secretPath}
</GenericFieldLabel>
<GenericFieldLabel label="Access Type">{getAccessLabel()}</GenericFieldLabel>
<GenericFieldLabel label="Permission">{requestedAccess}</GenericFieldLabel>
{request.note && (
<GenericFieldLabel className="col-span-full" label="Note">
{request.note}
</GenericFieldLabel>
)}
</div>
</div>
<div className="mb-4 border-b-2 border-mineshaft-500 py-2 text-lg">Approvers</div>
<div className="thin-scrollbar max-h-64 overflow-y-auto rounded p-2">
{approverSequence?.approvers?.map((approver, index) => (
<div
key={`approval-list-${index + 1}`}
className={twMerge(
"relative mb-2 flex items-center rounded border border-mineshaft-500 bg-mineshaft-700 p-4",
approverSequence?.currentSequence !== approver.sequence &&
!hasApproved &&
"text-mineshaft-400"
)}
>
<div>
<div className="mt-4 flex items-center justify-between border-b-2 border-mineshaft-500 py-2">
<span>Approvers</span>
{approverSequence.isMyReviewInThisSequence &&
request.status === ApprovalStatus.PENDING && (
<Badge variant="primary" className="h-min">
Awaiting Your Review
</Badge>
)}
</div>
<div className="thin-scrollbar max-h-[40vh] overflow-y-auto rounded py-2">
{approverSequence?.approvers &&
approverSequence.approvers.map((approver, index) => {
const isInactive =
approverSequence?.currentSequence <
(approver.sequence ?? approverSequence.approvers!.length);
const isPending = approverSequence?.currentSequence === approver.sequence;
let StepComponent: ReactNode;
let BadgeComponent: ReactNode = null;
if (approver.hasRejected) {
StepComponent = (
<Badge
variant="danger"
className="flex h-6 min-w-6 items-center justify-center"
>
<FontAwesomeIcon icon={faBan} />
</Badge>
);
BadgeComponent = <Badge variant="danger">Rejected</Badge>;
} else if (approver.hasApproved) {
StepComponent = (
<Badge
variant="success"
className="flex h-6 min-w-6 items-center justify-center"
>
<FontAwesomeIcon icon={faCheck} />
</Badge>
);
BadgeComponent = <Badge variant="success">Approved</Badge>;
} else if (isPending) {
StepComponent = (
<Badge
variant="primary"
className="flex h-6 min-w-6 items-center justify-center"
>
<FontAwesomeIcon icon={faHourglass} />
</Badge>
);
BadgeComponent = <Badge variant="primary">Pending</Badge>;
} else {
StepComponent = (
<Badge
className={
isInactive
? "py-auto my-auto flex h-6 min-w-6 items-center justify-center gap-1.5 whitespace-nowrap bg-mineshaft-400/50 text-center text-bunker-200"
: ""
}
>
<span>{index + 1}</span>
</Badge>
);
}
return (
<div
className={twMerge(
"mr-8 flex h-8 w-8 items-center justify-center text-3xl font-medium",
approver.hasApproved && "border-green-400 text-green-400",
approver.hasRejected && "border-red-500 text-red-500"
key={`approval-list-${index + 1}`}
className={twMerge("flex", isInactive && "opacity-50")}
>
{approverSequence.approvers!.length > 1 && (
<div className="flex w-12 flex-col items-center gap-2 pr-4">
<div
className={twMerge(
"flex-grow border-mineshaft-600",
index !== 0 && "border-r"
)}
/>
{StepComponent}
<div
className={twMerge(
"flex-grow border-mineshaft-600",
index < approverSequence.approvers!.length - 1 && "border-r"
)}
/>
</div>
)}
>
{index + 1}
</div>
{index !== (approverSequence?.approvers?.length || 0) - 1 && (
<div
className={twMerge(
"absolute bottom-0 left-8 h-5 border-r-2 border-gray-400",
approver.hasApproved && "border-green-400",
approver.hasRejected && "border-red-500"
)}
/>
)}
{index !== 0 && (
<div
className={twMerge(
"absolute left-8 top-0 h-5 border-r-2 border-gray-400",
approver.hasApproved && "border-green-400",
approver.hasRejected && "border-red-500"
)}
/>
)}
</div>
<div className="grid flex-grow grid-cols-3">
<div>
<div className="mb-1 text-xs font-semibold uppercase">Users</div>
<div>
{approver?.user
?.map(
(el) => approverSequence?.membersGroupById?.[el.id]?.[0]?.user?.username
)
.join(",") || "-"}
</div>
</div>
<div>
<div className="mb-1 text-xs font-semibold uppercase">Groups</div>
<div>
{approver?.group
?.map(
(el) =>
approverSequence?.projectGroupsGroupById?.[el.id]?.[0]?.group?.name
)
.join(",") || "-"}
</div>
</div>
<div className="flex items-center">
<div>
<div className="mb-1 text-xs font-semibold uppercase">Approvals Required</div>
<div>{approver.approvals || "-"}</div>
</div>
<div className="ml-16">
<Popover>
<PopoverTrigger>
<FontAwesomeIcon icon={faUsers} />
</PopoverTrigger>
<PopoverContent hideCloseBtn className="pt-3">
<div>
<div className="mb-1 text-sm text-bunker-300">Reviewers</div>
<div className="thin-scrollbar flex max-h-64 flex-col gap-1 overflow-y-auto rounded">
{approver.reviewers.map((el, idx) => (
<div
key={`reviewer-${idx + 1}`}
className="flex items-center gap-2 bg-mineshaft-700 p-1 text-sm"
>
<div className="flex-grow">{el.username}</div>
<Tooltip
content={`Status: ${el?.status || ApprovalStatus.PENDING}`}
>
{getReviewedStatusSymbol(el?.status as ApprovalStatus)}
</Tooltip>
<div className="grid flex-1 grid-cols-5 border-b border-mineshaft-600 p-4">
<GenericFieldLabel className="col-span-2" label="Users">
{approver?.user
?.map(
(el) => approverSequence?.membersGroupById?.[el.id]?.[0]?.user?.username
)
.join(", ")}
</GenericFieldLabel>
<GenericFieldLabel className="col-span-2" label="Groups">
{approver?.group
?.map(
(el) =>
approverSequence?.projectGroupsGroupById?.[el.id]?.[0]?.group?.name
)
.join(", ")}
</GenericFieldLabel>
<GenericFieldLabel label="Approvals Required">
<div className="flex items-center">
<span className="mr-2">{approver.approvals}</span>
{BadgeComponent && (
<Tooltip
className="max-w-lg"
content={
<div>
<div className="mb-1 text-sm text-bunker-300">Reviewers</div>
<div className="thin-scrollbar flex max-h-64 flex-col divide-y divide-mineshaft-500 overflow-y-auto rounded">
{approver.reviewers.map((el, idx) => (
<div
key={`reviewer-${idx + 1}`}
className="flex items-center gap-2 px-2 py-2 text-sm"
>
<div className="flex-1">{el.username}</div>
{getReviewedStatusSymbol(el?.status as ApprovalStatus)}
</div>
))}
</div>
</div>
))}
</div>
</div>
</PopoverContent>
</Popover>
}
>
<div>{BadgeComponent}</div>
</Tooltip>
)}
</div>
</GenericFieldLabel>
</div>
</div>
</div>
</div>
))}
);
})}
</div>
{approverSequence.isMyReviewInThisSequence &&
request.status === ApprovalStatus.PENDING && (
<div className="mb-4 rounded-r border-l-2 border-l-primary-400 bg-mineshaft-300/5 px-4 py-2.5 text-sm">
Awaiting review from you.
</div>
)}
{shouldBlockRequestActions ? (
<div
className={twMerge(
"mb-4 rounded-r border-l-2 border-l-red-500 bg-mineshaft-300/5 px-4 py-2.5 text-sm",
"mt-2 rounded-r border-l-2 border-l-red-500 bg-mineshaft-300/5 px-4 py-2.5 text-sm",
isReviewedByMe && "border-l-green-400",
!approverSequence.isMyReviewInThisSequence && "border-l-primary-400",
hasRejected && "border-l-red-500"
@@ -409,6 +439,35 @@ export const ReviewAccessRequestModal = ({
</div>
) : (
<>
{isSoftEnforcement && request.isRequestedByCurrentUser && canBypass && (
<div className="mt-2 flex flex-col space-y-2">
<Checkbox
onCheckedChange={(checked) => setBypassApproval(checked === true)}
isChecked={bypassApproval}
id="byPassApproval"
className={twMerge("mr-2", bypassApproval ? "!border-red/30 !bg-red/10" : "")}
>
<span className="text-xs text-red">
Approve without waiting for requirements to be met (bypass policy protection)
</span>
</Checkbox>
{bypassApproval && (
<FormControl
label="Reason for bypass"
className="mt-2"
isRequired
tooltipText="Enter a reason for bypassing the policy"
>
<Input
value={bypassReason}
onChange={(e) => setBypassReason(e.currentTarget.value)}
placeholder="Enter reason for bypass (min 10 chars)"
leftIcon={<FontAwesomeIcon icon={faTriangleExclamation} />}
/>
</FormControl>
)}
</div>
)}
<div className="space-x-2">
<Button
isLoading={isLoading === "approved"}
@@ -423,6 +482,7 @@ export const ReviewAccessRequestModal = ({
onClick={() => handleReview("approved")}
className="mt-4"
size="sm"
variant="outline_bg"
colorSchema={!request.isApprover && isSoftEnforcement ? "danger" : "primary"}
>
Approve Request
@@ -444,43 +504,6 @@ export const ReviewAccessRequestModal = ({
Reject Request
</Button>
</div>
{isSoftEnforcement &&
request.isRequestedByCurrentUser &&
!(request.isApprover && request.isSelfApproveAllowed) &&
canBypass && (
<div className="mt-2 flex flex-col space-y-2">
<Checkbox
onCheckedChange={(checked) => setBypassApproval(checked === true)}
isChecked={bypassApproval}
id="byPassApproval"
checkIndicatorBg="text-white"
className={twMerge(
"mr-2",
bypassApproval ? "border-red bg-red hover:bg-red-600" : ""
)}
>
<span className="text-xs text-red">
Approve without waiting for requirements to be met (bypass policy
protection)
</span>
</Checkbox>
{bypassApproval && (
<FormControl
label="Reason for bypass"
className="mt-2"
isRequired
tooltipText="Enter a reason for bypassing the secret change policy"
>
<Input
value={bypassReason}
onChange={(e) => setBypassReason(e.currentTarget.value)}
placeholder="Enter reason for bypass (min 10 chars)"
leftIcon={<FontAwesomeIcon icon={faTriangleExclamation} />}
/>
</FormControl>
)}
</div>
)}
</>
)}
</div>
@@ -16,11 +16,9 @@ import { AnimatePresence, motion } from "framer-motion";
import { twMerge } from "tailwind-merge";
import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
import { createNotification } from "@app/components/notifications";
import { ProjectPermissionCan } from "@app/components/permissions";
import {
Button,
DeleteActionModal,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
@@ -54,8 +52,6 @@ import {
} from "@app/helpers/userTablePreferences";
import { usePagination, usePopUp, useResetPageHelper } from "@app/hooks";
import {
useDeleteAccessApprovalPolicy,
useDeleteSecretApprovalPolicy,
useGetSecretApprovalPolicies,
useGetWorkspaceUsers,
useListWorkspaceGroups
@@ -67,6 +63,7 @@ import { TAccessApprovalPolicy, Workspace } from "@app/hooks/api/types";
import { AccessPolicyForm } from "./components/AccessPolicyModal";
import { ApprovalPolicyRow } from "./components/ApprovalPolicyRow";
import { RemoveApprovalPolicyModal } from "./components/RemoveApprovalPolicyModal";
interface IProps {
workspaceId: string;
@@ -122,7 +119,7 @@ const useApprovalPolicies = (permission: TProjectPermission, currentWorkspace?:
};
export const ApprovalPolicyList = ({ workspaceId }: IProps) => {
const { handlePopUpToggle, handlePopUpOpen, handlePopUpClose, popUp } = usePopUp([
const { handlePopUpToggle, handlePopUpOpen, popUp } = usePopUp([
"policyForm",
"deletePolicy",
"upgradePlan"
@@ -213,39 +210,6 @@ export const ApprovalPolicyList = ({ workspaceId }: IProps) => {
setPage
});
const { mutateAsync: deleteSecretApprovalPolicy } = useDeleteSecretApprovalPolicy();
const { mutateAsync: deleteAccessApprovalPolicy } = useDeleteAccessApprovalPolicy();
const handleDeletePolicy = async () => {
const { id, policyType } = popUp.deletePolicy.data as TAccessApprovalPolicy;
if (!currentWorkspace?.slug) return;
try {
if (policyType === PolicyType.ChangePolicy) {
await deleteSecretApprovalPolicy({
workspaceId,
id
});
} else {
await deleteAccessApprovalPolicy({
projectSlug: currentWorkspace?.slug,
id
});
}
createNotification({
type: "success",
text: "Successfully deleted policy"
});
handlePopUpClose("deletePolicy");
} catch (err) {
console.log(err);
createNotification({
type: "error",
text: "Failed to delete policy"
});
}
};
const isTableFiltered = filters.type !== null || Boolean(filters.environmentIds.length);
const handleSort = (column: PolicyOrderBy) => {
@@ -528,13 +492,14 @@ export const ApprovalPolicyList = ({ workspaceId }: IProps) => {
members={members}
editValues={popUp.policyForm.data as TAccessApprovalPolicy}
/>
<DeleteActionModal
isOpen={popUp.deletePolicy.isOpen}
deleteKey="remove"
title="Do you want to remove this policy?"
onChange={(isOpen) => handlePopUpToggle("deletePolicy", isOpen)}
onDeleteApproved={handleDeletePolicy}
/>
{popUp.deletePolicy.data && (
<RemoveApprovalPolicyModal
isOpen={popUp.deletePolicy.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle("deletePolicy", isOpen)}
policyType={popUp.deletePolicy.data.policyType}
policyId={popUp.deletePolicy.data.id}
/>
)}
<UpgradePlanModal
isOpen={popUp.upgradePlan.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
@@ -400,7 +400,7 @@ const Form = ({
isError={Boolean(error)}
tooltipText="Change policies govern secret changes within a given environment and secret path. Access policies allow underprivileged user to request access to environment/secret path."
errorText={error?.message}
className="flex-grow"
className="flex-1"
>
<Select
isDisabled={isEditMode}
@@ -419,6 +419,20 @@ const Form = ({
</FormControl>
)}
/>
<Controller
control={control}
name="name"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Policy Name"
isError={Boolean(error)}
errorText={error?.message}
className="flex-1"
>
<Input {...field} value={field.value || ""} />
</FormControl>
)}
/>
{!isAccessPolicyType && (
<Controller
control={control}
@@ -429,7 +443,7 @@ const Form = ({
label="Min. Approvals Required"
isError={Boolean(error)}
errorText={error?.message}
className="flex-grow"
className="flex-shrink"
>
<Input
{...field}
@@ -443,20 +457,6 @@ const Form = ({
)}
</div>
<div className="flex items-center gap-x-3">
<Controller
control={control}
name="name"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Policy Name"
isError={Boolean(error)}
errorText={error?.message}
className="flex-grow"
>
<Input {...field} value={field.value || ""} />
</FormControl>
)}
/>
<Controller
control={control}
name="secretPath"
@@ -467,35 +467,36 @@ const Form = ({
label="Secret Path"
isError={Boolean(error)}
errorText={error?.message}
className="flex-grow"
className="flex-1"
>
<Input {...field} value={field.value || ""} />
</FormControl>
)}
/>
<Controller
control={control}
name="environment"
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
label="Environment"
isRequired
isError={Boolean(error)}
errorText={error?.message}
className="flex-1"
>
<FilterableSelect
isDisabled={isEditMode}
value={value}
onChange={onChange}
placeholder="Select environment..."
options={environments}
getOptionValue={(option) => option.slug}
getOptionLabel={(option) => option.name}
/>
</FormControl>
)}
/>
</div>
<Controller
control={control}
name="environment"
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
label="Environment"
isRequired
isError={Boolean(error)}
errorText={error?.message}
>
<FilterableSelect
isDisabled={isEditMode}
value={value}
onChange={onChange}
placeholder="Select environment..."
options={environments}
getOptionValue={(option) => option.slug}
getOptionLabel={(option) => option.name}
/>
</FormControl>
)}
/>
<div className="mb-2">
<p>Approvers</p>
<p className="font-inter text-xs text-mineshaft-300 opacity-90">
@@ -504,11 +505,11 @@ const Form = ({
</div>
{isAccessPolicyType ? (
<>
<div className="thin-scrollbar max-h-64 space-y-2 overflow-y-auto rounded">
<div className="thin-scrollbar max-h-64 space-y-2 overflow-y-auto rounded border border-mineshaft-600 bg-mineshaft-900 p-2">
{sequenceApproversFieldArray.fields.map((el, index) => (
<div
className={twMerge(
"rounded border border-mineshaft-500 bg-mineshaft-700 p-3 pb-0",
"rounded border border-mineshaft-500 bg-mineshaft-700 p-3 pb-0 shadow-inner",
dragOverItem === index ? "border-2 border-blue-400" : "",
draggedItem === index ? "opacity-50" : ""
)}
@@ -567,7 +568,7 @@ const Form = ({
label="User Approvers"
isError={Boolean(error)}
errorText={error?.message}
className="flex-grow"
className="flex-1"
>
<FilterableSelect
menuPortalTarget={modalContainer.current}
@@ -597,7 +598,7 @@ const Form = ({
label="Group Approvers"
isError={Boolean(error)}
errorText={error?.message}
className="flex-grow"
className="flex-1"
>
<FilterableSelect
menuPortalTarget={modalContainer.current}
@@ -800,10 +801,15 @@ const Form = ({
</>
)}
<div className="mt-8 flex items-center space-x-4">
<Button type="submit" isLoading={isSubmitting} isDisabled={isSubmitting}>
<Button
type="submit"
colorSchema="secondary"
isLoading={isSubmitting}
isDisabled={isSubmitting}
>
Save
</Button>
<Button onClick={() => onToggle(false)} variant="outline_bg">
<Button onClick={() => onToggle(false)} colorSchema="secondary" variant="plain">
Close
</Button>
</div>
@@ -1,5 +1,12 @@
import { useMemo } from "react";
import { faEdit, faEllipsisV, faTrash } from "@fortawesome/free-solid-svg-icons";
import {
faClipboardCheck,
faEdit,
faEllipsisV,
faTrash,
faUser,
faUserGroup
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { twMerge } from "tailwind-merge";
@@ -179,26 +186,40 @@ export const ApprovalPolicyRow = ({
}`}
>
<div className="p-4">
<div className="mb-4 border-b-2 border-mineshaft-500 pb-2">Approvers</div>
<div className="border-b-2 border-mineshaft-500 pb-2">Approvers</div>
{labels?.map((el, index) => (
<div
key={`approval-list-${index + 1}`}
className="relative mb-2 flex rounded border border-mineshaft-500 bg-mineshaft-800 p-4"
>
<div className="my-auto mr-8 flex h-8 w-8 items-center justify-center rounded border border-mineshaft-400 bg-bunker-500/50 text-white">
<div>{index + 1}</div>
</div>
{index !== labels.length - 1 && (
<div className="absolute bottom-0 left-8 h-[1.25rem] border-r border-mineshaft-400" />
<div key={`approval-list-${index + 1}`} className="flex">
{labels.length > 1 && (
<div className="flex w-12 flex-col items-center gap-2 pr-4">
<div
className={twMerge(
"flex-grow border-mineshaft-600",
index !== 0 && "border-r"
)}
/>
{labels.length > 1 && (
<Badge className="my-auto flex h-5 w-min min-w-5 items-center justify-center gap-1.5 whitespace-nowrap bg-mineshaft-400/50 text-center text-bunker-200">
<span>{index + 1}</span>
</Badge>
)}
<div
className={twMerge(
"flex-grow border-mineshaft-600",
index < labels.length - 1 && "border-r"
)}
/>
</div>
)}
{index !== 0 && (
<div className="absolute left-8 top-0 h-[1.25rem] border-r border-mineshaft-400" />
)}
<div className="grid flex-grow grid-cols-3">
<GenericFieldLabel label="Users">{el.userLabels}</GenericFieldLabel>
<GenericFieldLabel label="Groups">{el.groupLabels}</GenericFieldLabel>
<GenericFieldLabel label="Approvals Required">{el.approvals}</GenericFieldLabel>
<div className="grid flex-1 grid-cols-5 border-b border-mineshaft-600 p-4">
<GenericFieldLabel className="col-span-2" icon={faUser} label="Users">
{el.userLabels}
</GenericFieldLabel>
<GenericFieldLabel className="col-span-2" icon={faUserGroup} label="Groups">
{el.groupLabels}
</GenericFieldLabel>
<GenericFieldLabel icon={faClipboardCheck} label="Approvals Required">
{el.approvals}
</GenericFieldLabel>
</div>
</div>
))}
@@ -0,0 +1,135 @@
import { faCheck, faWarning } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { twMerge } from "tailwind-merge";
import { createNotification } from "@app/components/notifications";
import { DeleteActionModal, Spinner } from "@app/components/v2";
import { useWorkspace } from "@app/context";
import {
useDeleteAccessApprovalPolicy,
useDeleteSecretApprovalPolicy,
useGetAccessRequestsCount,
useGetSecretApprovalRequestCount
} from "@app/hooks/api";
import { PolicyType } from "@app/hooks/api/policies/enums";
type Props = {
policyId: string;
policyType: PolicyType;
isOpen: boolean;
onOpenChange: (isOpen: boolean) => void;
};
export const RemoveApprovalPolicyModal = ({
policyId,
policyType,
isOpen,
onOpenChange
}: Props) => {
const { mutateAsync: deleteSecretApprovalPolicy } = useDeleteSecretApprovalPolicy();
const { mutateAsync: deleteAccessApprovalPolicy } = useDeleteAccessApprovalPolicy();
const { currentWorkspace } = useWorkspace();
const handleDeletePolicy = async () => {
try {
if (policyType === PolicyType.ChangePolicy) {
await deleteSecretApprovalPolicy({
workspaceId: currentWorkspace.id,
id: policyId
});
} else {
await deleteAccessApprovalPolicy({
projectSlug: currentWorkspace.slug,
id: policyId
});
}
createNotification({
type: "success",
text: "Successfully deleted policy"
});
onOpenChange(false);
} catch {
createNotification({
type: "error",
text: "Failed to delete policy"
});
}
};
const deleteSecretApprovalData = useGetSecretApprovalRequestCount({
policyId,
workspaceId: currentWorkspace.id,
options: {
enabled: Boolean(policyId) && policyType === PolicyType.ChangePolicy
}
});
const deleteAccessApprovalData = useGetAccessRequestsCount({
projectSlug: currentWorkspace.slug,
policyId,
options: {
enabled: Boolean(policyId) && policyType === PolicyType.AccessPolicy
}
});
let openCount: number | undefined;
let isPending: boolean;
if (policyType === PolicyType.ChangePolicy) {
openCount = deleteSecretApprovalData.data?.open;
isPending = deleteSecretApprovalData.isPending;
} else {
openCount = deleteAccessApprovalData.data?.pendingCount;
isPending = deleteAccessApprovalData.isPending;
}
return (
<DeleteActionModal
isOpen={isOpen}
deleteKey="remove"
title="Do you want to remove this policy?"
onChange={onOpenChange}
onDeleteApproved={handleDeletePolicy}
isDisabled={isPending}
>
{isPending ? (
<div className="mt-4 flex w-full items-center gap-2 p-2">
<Spinner size="xs" className="text-mineshaft-600" />
<span className="text-sm text-mineshaft-400">Checking for open requests...</span>
</div>
) : (
<div
className={twMerge(
"mt-4 flex w-full items-start gap-2 rounded border p-2 text-sm",
(openCount ?? 0) > 0
? "border-yellow/20 bg-yellow/10 text-yellow"
: "border-green/20 bg-green/10 text-green"
)}
>
{(openCount ?? 0) > 0 ? (
<>
<FontAwesomeIcon className="mt-1" icon={faWarning} />
<div className="flex flex-col">
<span>
This policy has {openCount} open request
{(openCount ?? 0) > 1 ? "s" : ""}
</span>
<p className="text-xs text-mineshaft-200">
Removing this policy will close all open requests.
</p>
</div>
</>
) : (
<>
<FontAwesomeIcon className="mt-1" icon={faCheck} />
<div className="flex flex-col">
<span>This policy has no open requests</span>
<p className="text-xs text-mineshaft-200">This policy is safe to remove.</p>
</div>
</>
)}
</div>
)}
</DeleteActionModal>
);
};
@@ -102,43 +102,73 @@ export const SecretApprovalRequestAction = ({
if (!hasMerged && status === "open") {
return (
<div className="flex w-full flex-col items-start justify-between py-4 transition-all">
<div className="flex items-center space-x-4 px-4">
<div
className={`flex items-center justify-center rounded-full ${isMergable ? "h-10 w-10 bg-green" : "h-11 w-11 bg-red-600"}`}
>
<FontAwesomeIcon
icon={isMergable ? faCheck : faXmark}
className={isMergable ? "text-lg text-black" : "text-2xl text-white"}
/>
<div className="flex w-full flex-col items-start justify-between py-4 text-mineshaft-100 transition-all">
<div className="flex w-full flex-col justify-between xl:flex-row xl:items-center">
<div className="mr-auto flex items-center space-x-4 px-4">
<div
className={`flex items-center justify-center rounded-full ${isMergable ? "h-8 w-8 bg-green" : "h-10 w-10 bg-red-600"}`}
>
<FontAwesomeIcon
icon={isMergable ? faCheck : faXmark}
className={isMergable ? "text-lg text-white" : "text-2xl text-white"}
/>
</div>
<span className="flex flex-col">
<p className={`text-md font-medium ${isMergable && "text-lg"}`}>
{isMergable ? "Good to merge" : "Merging is blocked"}
</p>
{!isMergable && (
<span className="inline-block text-xs text-mineshaft-300">
At least {approvals} approving review{`${approvals > 1 ? "s" : ""}`} required by
eligible reviewers.
{Boolean(statusChangeByEmail) && `. Reopened by ${statusChangeByEmail}`}
</span>
)}
</span>
</div>
<span className="flex flex-col">
<p className={`text-md font-medium ${isMergable && "text-lg"}`}>
{isMergable ? "Good to merge" : "Merging is blocked"}
</p>
{!isMergable && (
<span className="inline-block text-xs text-bunker-200">
At least {approvals} approving review{`${approvals > 1 ? "s" : ""}`} required by
eligible reviewers.
{Boolean(statusChangeByEmail) && `. Reopened by ${statusChangeByEmail}`}
</span>
<div className="mt-4 flex items-center justify-end space-x-2 px-4 xl:mt-0">
{canApprove || isSoftEnforcement ? (
<div className="flex items-center space-x-4">
<Button
onClick={() => handleSecretApprovalStatusChange("close")}
isLoading={isStatusChanging}
variant="outline_bg"
colorSchema="primary"
leftIcon={<FontAwesomeIcon icon={faClose} />}
className="hover:border-red/60 hover:bg-red/10"
>
Close request
</Button>
<Button
leftIcon={<FontAwesomeIcon icon={!canApprove ? faLandMineOn : faCheck} />}
isDisabled={
!(
(isMergable && canApprove) ||
(isSoftEnforcement && byPassApproval && isValidBypassReason(bypassReason))
)
}
isLoading={isMerging}
onClick={handleSecretApprovalRequestMerge}
colorSchema={isSoftEnforcement && !canApprove ? "danger" : "primary"}
variant="outline_bg"
>
Merge
</Button>
</div>
) : (
<div className="text-sm text-mineshaft-400">Only approvers can merge</div>
)}
</span>
</div>
</div>
{isSoftEnforcement && !isMergable && isBypasser && (
<div
className={`mt-4 w-full border-mineshaft-600 px-5 ${isMergable ? "border-t pb-2" : "border-y pb-4"}`}
>
<div className="mt-4 w-full border-t border-mineshaft-600 px-5">
<div className="mt-2 flex flex-col space-y-2 pt-2">
<Checkbox
onCheckedChange={(checked) => setByPassApproval(checked === true)}
isChecked={byPassApproval}
id="byPassApproval"
checkIndicatorBg="text-white"
className={twMerge(
"mr-2",
byPassApproval ? "border-red bg-red hover:bg-red-600" : ""
)}
className={twMerge("mr-2", byPassApproval ? "!border-red/30 !bg-red/10" : "")}
>
<span className="text-sm">
Merge without waiting for approval (bypass secret change policy)
@@ -162,51 +192,18 @@ export const SecretApprovalRequestAction = ({
</div>
</div>
)}
<div className="mt-2 flex w-full items-center justify-end space-x-2 px-4">
{canApprove || isSoftEnforcement ? (
<div className="flex items-center space-x-4">
<Button
onClick={() => handleSecretApprovalStatusChange("close")}
isLoading={isStatusChanging}
variant="outline_bg"
colorSchema="primary"
leftIcon={<FontAwesomeIcon icon={faClose} />}
className="hover:border-red/60 hover:bg-red/10"
>
Close request
</Button>
<Button
leftIcon={<FontAwesomeIcon icon={!canApprove ? faLandMineOn : faCheck} />}
isDisabled={
!(
(isMergable && canApprove) ||
(isSoftEnforcement && byPassApproval && isValidBypassReason(bypassReason))
)
}
isLoading={isMerging}
onClick={handleSecretApprovalRequestMerge}
colorSchema={isSoftEnforcement && !canApprove ? "danger" : "primary"}
variant="solid"
>
Merge
</Button>
</div>
) : (
<div>Only approvers can merge</div>
)}
</div>
</div>
);
}
if (hasMerged && status === "close")
return (
<div className="flex w-full items-center justify-between rounded-md border border-primary/60 bg-primary/10">
<div className="flex items-start space-x-4 p-4">
<FontAwesomeIcon icon={faCheck} className="pt-1 text-2xl text-primary" />
<div className="flex w-full items-center justify-between rounded-md border border-green/60 bg-green/10">
<div className="flex items-start space-x-2 p-4">
<FontAwesomeIcon icon={faCheck} className="mt-0.5 text-xl text-green" />
<span className="flex flex-col">
Change request merged
<span className="inline-block text-xs text-bunker-200">
<span className="inline-block text-xs text-mineshaft-300">
Merged by {statusChangeByEmail}.
</span>
</span>
@@ -215,26 +212,26 @@ export const SecretApprovalRequestAction = ({
);
return (
<div className="flex w-full items-center justify-between">
<div className="flex items-start space-x-4">
<FontAwesomeIcon icon={faUserLock} className="pt-1 text-2xl text-primary" />
<div className="flex w-full items-center justify-between rounded-md border border-yellow/60 bg-yellow/10">
<div className="flex items-start space-x-2 p-4">
<FontAwesomeIcon icon={faUserLock} className="mt-0.5 text-xl text-yellow" />
<span className="flex flex-col">
Secret approval has been closed
<span className="inline-block text-xs text-bunker-200">
<span className="inline-block text-xs text-mineshaft-300">
Closed by {statusChangeByEmail}
</span>
</span>
</div>
<div className="flex items-center space-x-6">
<Button
onClick={() => handleSecretApprovalStatusChange("open")}
isLoading={isStatusChanging}
variant="outline_bg"
leftIcon={<FontAwesomeIcon icon={faLockOpen} />}
>
Reopen request
</Button>
</div>
<Button
onClick={() => handleSecretApprovalStatusChange("open")}
isLoading={isStatusChanging}
variant="plain"
colorSchema="secondary"
className="mr-4 text-yellow/60 hover:text-yellow"
leftIcon={<FontAwesomeIcon icon={faLockOpen} />}
>
Reopen request
</Button>
</div>
);
};
@@ -3,11 +3,12 @@
/* eslint-disable no-nested-ternary */
import { useState } from "react";
import {
faCircleCheck,
faCircleXmark,
faExclamationTriangle,
faEye,
faEyeSlash,
faInfo,
faInfoCircle,
faKey
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
@@ -29,14 +30,14 @@ export type Props = {
};
const generateItemTitle = (op: CommitType) => {
let text = { label: "", color: "" };
if (op === CommitType.CREATE) text = { label: "create", color: "#60DD00" };
else if (op === CommitType.UPDATE) text = { label: "change", color: "#F8EB30" };
else text = { label: "deletion", color: "#F83030" };
let text = { label: "", className: "" };
if (op === CommitType.CREATE) text = { label: "create", className: "text-green-600" };
else if (op === CommitType.UPDATE) text = { label: "change", className: "text-yellow-600" };
else text = { label: "deletion", className: "text-red-600" };
return (
<div className="text-md pb-2 font-medium">
Request for <span style={{ color: text.color }}>secret {text.label}</span>
Request for <span className={text.className}>secret {text.label}</span>
</div>
);
};
@@ -68,15 +69,15 @@ export const SecretApprovalRequestChangeItem = ({
<div className="flex items-center px-1 py-1">
<div className="flex-grow">{generateItemTitle(op)}</div>
{!hasMerged && isStale && (
<div className="flex items-center">
<FontAwesomeIcon icon={faInfo} className="text-sm text-primary-600" />
<span className="ml-2 text-xs">Secret has been changed(stale)</span>
<div className="flex items-center text-mineshaft-300">
<FontAwesomeIcon icon={faInfoCircle} className="text-xs" />
<span className="ml-1 text-xs">Secret has been changed (stale)</span>
</div>
)}
{hasMerged && hasConflict && (
<div className="flex items-center space-x-2 text-sm text-bunker-300">
<div className="flex items-center space-x-1 text-xs text-bunker-300">
<Tooltip content="Merge Conflict">
<FontAwesomeIcon icon={faExclamationTriangle} className="text-red-700" />
<FontAwesomeIcon icon={faExclamationTriangle} className="text-xs text-red" />
</Tooltip>
<div>{generateConflictText(op)}</div>
</div>
@@ -95,7 +96,7 @@ export const SecretApprovalRequestChangeItem = ({
</div>
<div className="mb-2">
<div className="text-sm font-medium text-mineshaft-300">Key</div>
<div className="text-sm">{secretVersion?.secretKey} </div>
<p className="max-w-lg break-words text-sm">{secretVersion?.secretKey}</p>
</div>
<div className="mb-2">
<div className="text-sm font-medium text-mineshaft-300">Value</div>
@@ -147,7 +148,7 @@ export const SecretApprovalRequestChangeItem = ({
</div>
<div className="mb-2">
<div className="text-sm font-medium text-mineshaft-300">Comment</div>
<div className="max-h-[5rem] overflow-y-auto text-sm">
<div className="thin-scrollbar max-h-[5rem] max-w-[34rem] overflow-y-auto break-words text-sm xl:max-w-[28rem]">
{secretVersion?.secretComment || (
<span className="text-sm text-mineshaft-300">-</span>
)}{" "}
@@ -186,15 +187,27 @@ export const SecretApprovalRequestChangeItem = ({
className="mr-0 flex items-center rounded-r-none border border-mineshaft-500"
>
<FontAwesomeIcon icon={faKey} size="xs" className="mr-1" />
<div>{el.key}</div>
<Tooltip
className="max-w-lg whitespace-normal break-words"
content={el.key}
>
<div className="max-w-[125px] overflow-hidden text-ellipsis whitespace-nowrap">
{el.key}
</div>
</Tooltip>
</Tag>
<Tag
size="xs"
className="flex items-center rounded-l-none border border-mineshaft-500 bg-mineshaft-900 pl-1"
>
<div className="max-w-[150px] overflow-hidden text-ellipsis whitespace-nowrap">
{el.value}
</div>
<Tooltip
className="max-w-lg whitespace-normal break-words"
content={el.value}
>
<div className="max-w-[125px] overflow-hidden text-ellipsis whitespace-nowrap">
{el.value}
</div>
</Tooltip>
</Tag>
</div>
))}
@@ -215,13 +228,13 @@ export const SecretApprovalRequestChangeItem = ({
<div className="mb-4 flex flex-row justify-between">
<span className="text-md font-medium">New Secret</span>
<div className="rounded-full bg-green-600 px-2 pb-[0.14rem] pt-[0.2rem] text-xs font-medium">
<FontAwesomeIcon icon={faCircleXmark} className="pr-1 text-white" />
<FontAwesomeIcon icon={faCircleCheck} className="pr-1 text-white" />
New
</div>
</div>
<div className="mb-2">
<div className="text-sm font-medium text-mineshaft-300">Key</div>
<div className="text-sm">{newVersion?.secretKey} </div>
<div className="max-w-md break-words text-sm">{newVersion?.secretKey} </div>
</div>
<div className="mb-2">
<div className="text-sm font-medium text-mineshaft-300">Value</div>
@@ -273,7 +286,7 @@ export const SecretApprovalRequestChangeItem = ({
</div>
<div className="mb-2">
<div className="text-sm font-medium text-mineshaft-300">Comment</div>
<div className="max-h-[5rem] overflow-y-auto text-sm">
<div className="thin-scrollbar max-h-[5rem] max-w-[34rem] overflow-y-auto break-words text-sm xl:max-w-[28rem]">
{newVersion?.secretComment || (
<span className="text-sm text-mineshaft-300">-</span>
)}{" "}
@@ -281,15 +294,15 @@ export const SecretApprovalRequestChangeItem = ({
</div>
<div className="mb-2">
<div className="text-sm font-medium text-mineshaft-300">Tags</div>
<div className="flex flex-wrap gap-2">
<div className="flex flex-wrap gap-y-2">
{(newVersion?.tags?.length ?? 0) ? (
newVersion?.tags?.map(({ slug, id: tagId, color }) => (
<Tag
className="flex w-min items-center space-x-2"
className="flex w-min items-center space-x-1.5 border border-mineshaft-500 bg-mineshaft-800"
key={`${newVersion.id}-${tagId}`}
>
<div
className="h-3 w-3 rounded-full"
className="h-2.5 w-2.5 rounded-full"
style={{ backgroundColor: color || "#bec2c8" }}
/>
<div className="text-sm">{slug}</div>
@@ -311,15 +324,27 @@ export const SecretApprovalRequestChangeItem = ({
className="mr-0 flex items-center rounded-r-none border border-mineshaft-500"
>
<FontAwesomeIcon icon={faKey} size="xs" className="mr-1" />
<div>{el.key}</div>
<Tooltip
className="max-w-lg whitespace-normal break-words"
content={el.key}
>
<div className="max-w-[125px] overflow-hidden text-ellipsis whitespace-nowrap">
{el.key}
</div>
</Tooltip>
</Tag>
<Tag
size="xs"
className="flex items-center rounded-l-none border border-mineshaft-500 bg-mineshaft-900 pl-1"
>
<div className="max-w-[150px] overflow-hidden text-ellipsis whitespace-nowrap">
{el.value}
</div>
<Tooltip
className="max-w-lg whitespace-normal break-words"
content={el.value}
>
<div className="max-w-[125px] overflow-hidden text-ellipsis whitespace-nowrap">
{el.value}
</div>
</Tooltip>
</Tag>
</div>
))}
@@ -3,12 +3,12 @@ import { Controller, useForm } from "react-hook-form";
import {
faAngleDown,
faArrowLeft,
faCheckCircle,
faCircle,
faBan,
faCheck,
faCodeBranch,
faComment,
faFolder,
faXmarkCircle
faHourglass
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { zodResolver } from "@hookform/resolvers/zod";
@@ -26,6 +26,7 @@ import {
DropdownMenuTrigger,
EmptyState,
FormControl,
GenericFieldLabel,
IconButton,
TextArea,
Tooltip
@@ -81,10 +82,10 @@ export const generateCommitText = (commits: { op: CommitType }[] = [], isReplica
const getReviewedStatusSymbol = (status?: ApprovalStatus) => {
if (status === ApprovalStatus.APPROVED)
return <FontAwesomeIcon icon={faCheckCircle} size="xs" style={{ color: "#15803d" }} />;
return <FontAwesomeIcon icon={faCheck} size="xs" className="text-green" />;
if (status === ApprovalStatus.REJECTED)
return <FontAwesomeIcon icon={faXmarkCircle} size="xs" style={{ color: "#b91c1c" }} />;
return <FontAwesomeIcon icon={faCircle} size="xs" style={{ color: "#c2410c" }} />;
return <FontAwesomeIcon icon={faBan} size="xs" className="text-red" />;
return <FontAwesomeIcon icon={faHourglass} size="xs" className="text-yellow" />;
};
type Props = {
@@ -223,8 +224,8 @@ export const SecretApprovalRequestChanges = ({
const hasMerged = secretApprovalRequestDetails?.hasMerged;
return (
<div className="flex space-x-6">
<div className="flex-grow">
<div className="flex flex-col space-x-6 lg:flex-row">
<div className="flex-1 lg:max-w-[calc(100%-17rem)]">
<div className="sticky top-0 z-20 flex items-center space-x-4 bg-bunker-800 pb-6 pt-2">
<IconButton variant="outline_bg" ariaLabel="go-back" onClick={onGoBack}>
<FontAwesomeIcon icon={faArrowLeft} />
@@ -242,17 +243,17 @@ export const SecretApprovalRequestChanges = ({
: secretApprovalRequestDetails.status}
</span>
</div>
<div className="flex-grow flex-col">
<div className="-mt-0.5 flex-grow flex-col">
<div className="text-xl">
{generateCommitText(
secretApprovalRequestDetails.commits,
secretApprovalRequestDetails.isReplicated
)}
</div>
<div className="flex items-center space-x-2 text-xs text-gray-400">
<span className="-mt-1 flex items-center space-x-2 text-xs text-gray-400">
By {secretApprovalRequestDetails?.committerUser?.firstName} (
{secretApprovalRequestDetails?.committerUser?.email})
</div>
</span>
</div>
{!hasMerged &&
secretApprovalRequestDetails.status === "open" &&
@@ -262,7 +263,10 @@ export const SecretApprovalRequestChanges = ({
onOpenChange={(isOpen) => handlePopUpToggle("reviewChanges", isOpen)}
>
<DropdownMenuTrigger asChild>
<Button rightIcon={<FontAwesomeIcon className="ml-2" icon={faAngleDown} />}>
<Button
colorSchema="secondary"
rightIcon={<FontAwesomeIcon className="ml-2" icon={faAngleDown} />}
>
Review
</Button>
</DropdownMenuTrigger>
@@ -279,82 +283,87 @@ export const SecretApprovalRequestChanges = ({
{...field}
placeholder="Leave a comment..."
reSize="none"
className="text-md mt-2 h-40 border border-mineshaft-600 bg-bunker-800"
className="text-md mt-2 h-40 border border-mineshaft-600 bg-mineshaft-800 placeholder:text-mineshaft-400"
/>
</FormControl>
)}
/>
<Controller
control={control}
name="status"
defaultValue={ApprovalStatus.APPROVED}
render={({ field, fieldState: { error } }) => (
<FormControl errorText={error?.message} isError={Boolean(error)}>
<RadioGroup
value={field.value}
onValueChange={field.onChange}
className="mb-4 space-y-2"
aria-label="Status"
<div className="flex justify-between">
<Controller
control={control}
name="status"
defaultValue={ApprovalStatus.APPROVED}
render={({ field, fieldState: { error } }) => (
<FormControl
className="mb-0"
errorText={error?.message}
isError={Boolean(error)}
>
<div className="flex items-center gap-2">
<RadioGroupItem
id="approve"
className="h-4 w-4 rounded-full border border-gray-300 text-primary focus:ring-2 focus:ring-mineshaft-500"
value={ApprovalStatus.APPROVED}
aria-labelledby="approve-label"
>
<RadioGroupIndicator className="flex h-full w-full items-center justify-center after:h-2 after:w-2 after:rounded-full after:bg-current" />
</RadioGroupItem>
<span
id="approve-label"
className="cursor-pointer"
onClick={() => field.onChange(ApprovalStatus.APPROVED)}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
field.onChange(ApprovalStatus.APPROVED);
}
}}
tabIndex={0}
role="button"
>
Approve
</span>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem
id="reject"
className="h-4 w-4 rounded-full border border-gray-300 text-red focus:ring-2 focus:ring-mineshaft-500"
value={ApprovalStatus.REJECTED}
aria-labelledby="reject-label"
>
<RadioGroupIndicator className="flex h-full w-full items-center justify-center after:h-2 after:w-2 after:rounded-full after:bg-current" />
</RadioGroupItem>
<span
id="reject-label"
className="cursor-pointer"
onClick={() => field.onChange(ApprovalStatus.REJECTED)}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
field.onChange(ApprovalStatus.REJECTED);
}
}}
tabIndex={0}
role="button"
>
Reject
</span>
</div>
</RadioGroup>
</FormControl>
)}
/>
<div className="flex justify-end">
<RadioGroup
value={field.value}
onValueChange={field.onChange}
className="space-y-2"
aria-label="Status"
>
<div className="flex items-center gap-2">
<RadioGroupItem
id="approve"
className="h-4 w-4 rounded-full border border-gray-400 text-green focus:ring-2 focus:ring-mineshaft-500"
value={ApprovalStatus.APPROVED}
aria-labelledby="approve-label"
>
<RadioGroupIndicator className="flex h-full w-full items-center justify-center after:h-2 after:w-2 after:rounded-full after:bg-current" />
</RadioGroupItem>
<span
id="approve-label"
className="cursor-pointer"
onClick={() => field.onChange(ApprovalStatus.APPROVED)}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
field.onChange(ApprovalStatus.APPROVED);
}
}}
tabIndex={0}
role="button"
>
Approve
</span>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem
id="reject"
className="h-4 w-4 rounded-full border border-gray-400 text-red focus:ring-2 focus:ring-mineshaft-500"
value={ApprovalStatus.REJECTED}
aria-labelledby="reject-label"
>
<RadioGroupIndicator className="flex h-full w-full items-center justify-center after:h-2 after:w-2 after:rounded-full after:bg-current" />
</RadioGroupItem>
<span
id="reject-label"
className="cursor-pointer"
onClick={() => field.onChange(ApprovalStatus.REJECTED)}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
field.onChange(ApprovalStatus.REJECTED);
}
}}
tabIndex={0}
role="button"
>
Reject
</span>
</div>
</RadioGroup>
</FormControl>
)}
/>
<Button
type="submit"
isLoading={isApproving || isRejecting || isSubmitting}
variant="outline_bg"
className="mt-auto h-min"
>
Submit Review
</Button>
@@ -371,14 +380,14 @@ export const SecretApprovalRequestChanges = ({
<div className="text-sm text-bunker-300">
A secret import in
<p
className="mx-1 inline rounded bg-primary-600/40 text-primary-300"
className="mx-1 inline rounded bg-mineshaft-600/80 text-mineshaft-300"
style={{ padding: "2px 4px" }}
>
{secretApprovalRequestDetails?.environment}
</p>
<div className="mr-2 inline-flex w-min items-center rounded border border-mineshaft-500 pl-1 pr-2">
<p className="cursor-default border-r border-mineshaft-500 pr-1">
<FontAwesomeIcon icon={faFolder} className="text-primary" size="sm" />
<div className="mr-1 inline-flex w-min items-center rounded border border-mineshaft-500 pl-1.5 pr-2">
<p className="cursor-default border-r border-mineshaft-500 pr-1.5">
<FontAwesomeIcon icon={faFolder} className="text-yellow" size="sm" />
</p>
<Tooltip content={approvalSecretPath}>
<p
@@ -391,14 +400,14 @@ export const SecretApprovalRequestChanges = ({
</div>
has pending changes to be accepted from its source at{" "}
<p
className="mx-1 inline rounded bg-primary-600/40 text-primary-300"
className="mx-1 inline rounded bg-mineshaft-600/80 text-mineshaft-300"
style={{ padding: "2px 4px" }}
>
{replicatedImport?.importEnv?.slug}
</p>
<div className="inline-flex w-min items-center rounded border border-mineshaft-500 pl-1 pr-2">
<p className="cursor-default border-r border-mineshaft-500 pr-1">
<FontAwesomeIcon icon={faFolder} className="text-primary" size="sm" />
<div className="mr-1 inline-flex w-min items-center rounded border border-mineshaft-500 pl-1.5 pr-2">
<p className="cursor-default border-r border-mineshaft-500 pr-1.5">
<FontAwesomeIcon icon={faFolder} className="text-yellow" size="sm" />
</p>
<Tooltip content={replicatedImport?.importPath}>
<p
@@ -415,14 +424,14 @@ export const SecretApprovalRequestChanges = ({
<div className="text-sm text-bunker-300">
<p className="inline">Secret(s) in</p>
<p
className="mx-1 inline rounded bg-primary-600/40 text-primary-300"
className="mx-1 inline rounded bg-mineshaft-600/80 text-mineshaft-300"
style={{ padding: "2px 4px" }}
>
{secretApprovalRequestDetails?.environment}
</p>
<div className="mr-1 inline-flex w-min items-center rounded border border-mineshaft-500 pl-1 pr-2">
<p className="cursor-default border-r border-mineshaft-500 pr-1">
<FontAwesomeIcon icon={faFolder} className="text-primary" size="sm" />
<div className="mr-1 inline-flex w-min items-center rounded border border-mineshaft-500 pl-1.5 pr-2">
<p className="cursor-default border-r border-mineshaft-500 pr-1.5">
<FontAwesomeIcon icon={faFolder} className="text-yellow" size="sm" />
</p>
<Tooltip content={formatReservedPaths(secretApprovalRequestDetails.secretPath)}>
<p
@@ -463,7 +472,7 @@ export const SecretApprovalRequestChanges = ({
const reviewer = reviewedUsers?.[requiredApprover.userId];
return (
<div
className="flex w-full flex-col rounded-md bg-mineshaft-800 p-4"
className="flex w-full flex-col rounded-md bg-mineshaft-800 p-4 text-sm text-mineshaft-100"
key={`required-approver-${requiredApprover.userId}`}
>
<div>
@@ -477,14 +486,16 @@ export const SecretApprovalRequestChanges = ({
{reviewer?.status === ApprovalStatus.APPROVED ? "approved" : "rejected"}
</span>{" "}
the request on{" "}
{format(new Date(secretApprovalRequestDetails.createdAt), "PPpp zzz")}.
{format(
new Date(secretApprovalRequestDetails.createdAt),
"MM/dd/yyyy h:mm:ss aa"
)}
.
</div>
{reviewer?.comment && (
<FormControl label="Comment" className="mb-0 mt-4">
<TextArea value={reviewer.comment} isDisabled reSize="none">
{reviewer?.comment && reviewer.comment}
</TextArea>
</FormControl>
<GenericFieldLabel label="Comment" className="mt-2 max-w-4xl break-words">
{reviewer?.comment && reviewer.comment}
</GenericFieldLabel>
)}
</div>
);
@@ -505,7 +516,7 @@ export const SecretApprovalRequestChanges = ({
/>
</div>
</div>
<div className="sticky top-0 w-1/5 cursor-default pt-4" style={{ minWidth: "240px" }}>
<div className="sticky top-0 z-[51] w-1/5 cursor-default pt-2" style={{ minWidth: "240px" }}>
<div className="text-sm text-bunker-300">Reviewers</div>
<div className="mt-2 flex flex-col space-y-2 text-sm">
{secretApprovalRequestDetails?.policy?.approvers
@@ -526,17 +537,17 @@ export const SecretApprovalRequestChanges = ({
requiredApprover.lastName || ""
}`}
>
<span>{requiredApprover?.email} </span>
<span>{requiredApprover?.email}</span>
</Tooltip>
<span className="text-red">*</span>
</div>
<div>
{reviewer?.comment && (
<Tooltip content={reviewer.comment}>
<Tooltip className="max-w-lg break-words" content={reviewer.comment}>
<FontAwesomeIcon
icon={faComment}
size="xs"
className="mr-1 text-mineshaft-300"
className="mr-1.5 text-mineshaft-300"
/>
</Tooltip>
)}
@@ -9,7 +9,7 @@ const SecretApprovalPageQueryParams = z.object({
});
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/approval"
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/approval"
)({
component: SecretApprovalsPage,
validateSearch: zodValidator(SecretApprovalPageQueryParams),
@@ -46,7 +46,6 @@ import { useGetProjectSecretsDetails } from "@app/hooks/api/dashboard";
import { DashboardSecretsOrderBy } from "@app/hooks/api/dashboard/types";
import { useGetFolderCommitsCount } from "@app/hooks/api/folderCommits";
import { OrderByDirection } from "@app/hooks/api/generic/types";
import { ProjectType } from "@app/hooks/api/workspace/types";
import { hasSecretReadValueOrDescribePermission } from "@app/lib/fn/permission";
import { SecretRotationListView } from "@app/pages/secret-manager/SecretDashboardPage/components/SecretRotationListView";
@@ -191,7 +190,7 @@ const Page = () => {
type: "error"
});
navigate({
to: `/${ProjectType.SecretManager}/$projectId/overview` as const,
to: "/projects/$projectId/secret-manager/overview",
params: {
projectId: workspaceId
}
@@ -317,7 +316,7 @@ const Page = () => {
const handleOnClickRollbackMode = () => {
if (isPITEnabled) {
navigate({
to: `/${ProjectType.SecretManager}/$projectId/commits/$environment/$folderId` as const,
to: "/projects/$projectId/secret-manager/commits/$environment/$folderId",
params: {
projectId: workspaceId,
folderId,
@@ -10,7 +10,7 @@ import { createNotification } from "@app/components/notifications";
import { ProjectPermissionCan } from "@app/components/permissions";
// TODO:(akhilmhdh) convert all the util functions like this into a lib folder grouped by functionality
import { parseDotEnv, parseJson } from "@app/components/utilities/parseSecrets";
import { Button, Modal, ModalContent } from "@app/components/v2";
import { Button, Lottie, Modal, ModalContent } from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
import { usePopUp, useToggle } from "@app/hooks";
import { useCreateSecretBatch, useUpdateSecretBatch } from "@app/hooks/api";
@@ -253,14 +253,7 @@ export const SecretDropzone = ({
>
{isLoading ? (
<div className="mb-16 flex items-center justify-center pt-16">
<img
src="/images/loading/loading.gif"
height={70}
width={120}
decoding="async"
loading="lazy"
alt="loading animation"
/>
<Lottie icon="infisical_loading" className="h-32 w-32" />
</div>
) : (
<div className="flex flex-col items-center justify-center space-y-2">
@@ -990,9 +990,7 @@ export const SecretDetailSidebar = ({
className="z-[100] capitalize"
>
<Link
to={
`/${ProjectType.SecretManager}/$projectId/members/$membershipId` as const
}
to="/projects/$projectId/members/$membershipId"
params={{
projectId: currentWorkspace.id,
membershipId: user.membershipId
@@ -1023,9 +1021,7 @@ export const SecretDetailSidebar = ({
className="z-[100]"
>
<Link
to={
`/${ProjectType.SecretManager}/$projectId/identities/$identityId` as const
}
to="/projects/$projectId/identities/$identityId"
params={{
projectId: currentWorkspace.id,
identityId: identity.id
@@ -13,7 +13,7 @@ const SecretDashboardPageQueryParamsSchema = z.object({
tags: z.string().catch("")
});
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/secrets/$envSlug"
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/secrets/$envSlug"
)({
component: SecretDashboardPage,
validateSearch: zodValidator(SecretDashboardPageQueryParamsSchema),
@@ -25,6 +25,13 @@ export const Route = createFileRoute(
return {
breadcrumbs: [
...context.breadcrumbs,
{
label: "Secrets",
link: linkOptions({
to: "/projects/$projectId/secret-manager/overview",
params
})
},
{
type: BreadcrumbTypes.Dropdown,
label: context.project.environments.find((el) => el.slug === params.envSlug)?.name || "",
@@ -32,7 +39,7 @@ export const Route = createFileRoute(
links: context.project.environments.map((el) => ({
label: el.name,
link: linkOptions({
to: "/secret-manager/$projectId/secrets/$envSlug",
to: "/projects/$projectId/secret-manager/secrets/$envSlug",
params: {
projectId: params.projectId,
envSlug: el.slug
@@ -53,7 +53,6 @@ import {
useRestartSecretRotation
} from "@app/hooks/api";
import { TSecretRotationProviderTemplate } from "@app/hooks/api/secretRotation/types";
import { ProjectType } from "@app/hooks/api/workspace/types";
import { CreateRotationForm } from "@app/pages/secret-manager/SecretRotationPage/components/CreateRotationForm";
const Page = () => {
@@ -173,7 +172,7 @@ const Page = () => {
PostgreSQL and Microsoft SQL Server Rotations can now be created from the{" "}
<Link
className="text-mineshaft-100 underline decoration-primary underline-offset-2 hover:text-mineshaft-200"
to={`/${ProjectType.SecretManager}/$projectId/overview` as const}
to="/projects/$projectId/secret-manager/overview"
params={{ projectId: currentWorkspace.id }}
>
Secret Manager Dashboard
@@ -408,7 +407,7 @@ const Page = () => {
Infisical is revamping its Secret Rotation experience. Navigate to the{" "}
<Link
className="text-mineshaft-100 underline decoration-primary underline-offset-2 hover:text-mineshaft-200"
to={`/${ProjectType.SecretManager}/$projectId/overview` as const}
to="/projects/$projectId/secret-manager/overview"
params={{ projectId: currentWorkspace.id }}
>
Secret Manager Dashboard
@@ -425,7 +424,7 @@ const Page = () => {
<Button
onClick={() =>
navigate({
to: `/${ProjectType.SecretManager}/$projectId/overview` as const,
to: "/projects/$projectId/secret-manager/overview",
params: { projectId: currentWorkspace.id }
})
}
@@ -3,7 +3,7 @@ import { createFileRoute } from "@tanstack/react-router";
import { SecretRotationPage } from "./SecretRotationPage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/secret-rotation"
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/secret-rotation"
)({
component: SecretRotationPage,
beforeLoad: ({ context }) => {
@@ -7,8 +7,13 @@ type Props = {
export const OnePassSyncDestinationSection = ({ secretSync }: Props) => {
const {
destinationConfig: { vaultId }
destinationConfig: { vaultId, valueLabel }
} = secretSync;
return <GenericFieldLabel label="Vault ID">{vaultId}</GenericFieldLabel>;
return (
<>
<GenericFieldLabel label="Vault ID">{vaultId}</GenericFieldLabel>
<GenericFieldLabel label="Value Key">{valueLabel || "value"}</GenericFieldLabel>
</>
);
};
@@ -5,7 +5,7 @@ import { IntegrationsListPageTabs } from "@app/types/integrations";
import { SecretSyncDetailsByIDPage } from "./SecretSyncDetailsByIDPage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/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"
)({
component: SecretSyncDetailsByIDPage,
beforeLoad: ({ context, params }) => {
@@ -15,7 +15,7 @@ export const Route = createFileRoute(
{
label: "Integrations",
link: linkOptions({
to: "/secret-manager/$projectId/integrations",
to: "/projects/$projectId/secret-manager/integrations",
params,
search: {
selectedTab: IntegrationsListPageTabs.SecretSyncs
@@ -3,7 +3,7 @@ import { useTranslation } from "react-i18next";
import { PageHeader, Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
import { useWorkspace } from "@app/context";
import { ProjectType, ProjectVersion } from "@app/hooks/api/workspace/types";
import { ProjectVersion } from "@app/hooks/api/workspace/types";
import { EncryptionTab } from "./components/EncryptionTab";
import { ProjectGeneralTab } from "./components/ProjectGeneralTab";
@@ -18,22 +18,17 @@ export const SettingsPage = () => {
{
name: "Encryption",
key: "tab-project-encryption",
isHidden:
currentWorkspace?.version !== ProjectVersion.V3 ||
currentWorkspace?.type !== ProjectType.SecretManager,
isHidden: currentWorkspace?.version !== ProjectVersion.V3,
Component: EncryptionTab
},
{
name: "Workflow Integrations",
key: "tab-workflow-integrations",
isHidden: currentWorkspace?.type !== ProjectType.SecretManager,
Component: WorkflowIntegrationTab
},
{
name: "Webhooks",
key: "tab-project-webhooks",
isHidden: currentWorkspace?.type !== ProjectType.SecretManager,
Component: WebhooksTab
}
];
@@ -44,7 +39,10 @@ export const SettingsPage = () => {
<title>{t("common.head-title", { title: t("settings.project.title") })}</title>
</Helmet>
<div className="w-full max-w-7xl">
<PageHeader title={t("settings.project.title")} />
<PageHeader
title="Settings"
description="Configure your secret manager's encryption, environments, webhooks and other configurations."
/>
<Tabs defaultValue={tabs[0].key}>
<TabList>
{tabs
@@ -1,129 +0,0 @@
import { Controller, useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
import { createNotification } from "@app/components/notifications";
import { Button, FormControl, Input } from "@app/components/v2";
import { useProjectPermission, useSubscription, useWorkspace } from "@app/context";
import { usePopUp } from "@app/hooks";
import { ProjectMembershipRole } from "@app/hooks/api/roles/types";
import { useUpdateWorkspaceAuditLogsRetention } from "@app/hooks/api/workspace/queries";
const formSchema = z.object({
auditLogsRetentionDays: z.coerce.number().min(0)
});
type TForm = z.infer<typeof formSchema>;
export const AuditLogsRetentionSection = () => {
const { mutateAsync: updateAuditLogsRetention } = useUpdateWorkspaceAuditLogsRetention();
const { currentWorkspace } = useWorkspace();
const { membership } = useProjectPermission();
const { subscription } = useSubscription();
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["upgradePlan"] as const);
const {
control,
formState: { isSubmitting, isDirty },
handleSubmit
} = useForm<TForm>({
resolver: zodResolver(formSchema),
values: {
auditLogsRetentionDays:
currentWorkspace?.auditLogsRetentionDays ?? subscription?.auditLogsRetentionDays ?? 0
}
});
if (!currentWorkspace) return null;
const handleAuditLogsRetentionSubmit = async ({ auditLogsRetentionDays }: TForm) => {
try {
if (!subscription?.auditLogs) {
handlePopUpOpen("upgradePlan", {
description: "You can only configure audit logs retention if you upgrade your plan."
});
return;
}
if (subscription && auditLogsRetentionDays > subscription?.auditLogsRetentionDays) {
handlePopUpOpen("upgradePlan", {
description:
"To update your audit logs retention period to a higher value, upgrade your plan."
});
return;
}
await updateAuditLogsRetention({
auditLogsRetentionDays,
projectSlug: currentWorkspace.slug
});
createNotification({
text: "Successfully updated audit logs retention period",
type: "success"
});
} catch {
createNotification({
text: "Failed updating audit logs retention period",
type: "error"
});
}
};
// render only for dedicated/self-hosted instances of Infisical
if (
window.location.origin.includes("https://app.infisical.com") ||
window.location.origin.includes("https://gamma.infisical.com")
) {
return null;
}
const isAdmin = membership.roles.includes(ProjectMembershipRole.Admin);
return (
<>
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<div className="flex w-full items-center justify-between">
<p className="text-xl font-semibold">Audit Logs Retention</p>
</div>
<p className="mb-4 mt-2 max-w-2xl text-sm text-gray-400">
Set the number of days to keep your project audit logs.
</p>
<form onSubmit={handleSubmit(handleAuditLogsRetentionSubmit)} autoComplete="off">
<div className="max-w-xs">
<Controller
control={control}
defaultValue={0}
name="auditLogsRetentionDays"
render={({ field, fieldState: { error } }) => (
<FormControl
isError={Boolean(error)}
errorText={error?.message}
label="Number of days"
>
<Input {...field} type="number" min={1} step={1} isDisabled={!isAdmin} />
</FormControl>
)}
/>
</div>
<Button
colorSchema="secondary"
type="submit"
isLoading={isSubmitting}
disabled={!isAdmin || !isDirty}
>
Save
</Button>
</form>
</div>
<UpgradePlanModal
isOpen={popUp.upgradePlan.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
text={(popUp.upgradePlan?.data as { description: string })?.description}
/>
</>
);
};
@@ -1 +0,0 @@
export { AuditLogsRetentionSection } from "./AuditLogsRetentionSection";
@@ -1,56 +0,0 @@
import { createNotification } from "@app/components/notifications";
import { ProjectPermissionCan } from "@app/components/permissions";
import { Checkbox } from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
import { useToggleDeleteProjectProtection } from "@app/hooks/api/workspace/queries";
export const DeleteProjectProtection = () => {
const { currentWorkspace } = useWorkspace();
const { mutateAsync } = useToggleDeleteProjectProtection();
const handleToggleDeleteProjectProtection = async (state: boolean) => {
try {
if (!currentWorkspace?.id) return;
await mutateAsync({
workspaceID: currentWorkspace.id,
state
});
const text = `Successfully ${state ? "enabled" : "disabled"} delete protection`;
createNotification({
text,
type: "success"
});
} catch (err) {
console.error(err);
createNotification({
text: "Failed to update delete protection",
type: "error"
});
}
};
return (
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<p className="mb-3 text-xl font-semibold">Delete Protection</p>
<ProjectPermissionCan I={ProjectPermissionActions.Edit} a={ProjectPermissionSub.Settings}>
{(isAllowed) => (
<div className="w-max">
<Checkbox
id="hasDeleteProtection"
isDisabled={!isAllowed}
isChecked={currentWorkspace?.hasDeleteProtection ?? false}
onCheckedChange={(state) => {
handleToggleDeleteProjectProtection(state as boolean);
}}
>
Protects the project from being deleted accidentally. While this option is enabled,
you can&apos;t delete the project.
</Checkbox>
</div>
)}
</ProjectPermissionCan>
</div>
);
};
@@ -1 +0,0 @@
export { DeleteProjectProtection } from "./DeleteProjectProtection";
@@ -1,190 +0,0 @@
import { useMemo } from "react";
import { useNavigate } from "@tanstack/react-router";
import { createNotification } from "@app/components/notifications";
import { ProjectPermissionCan } from "@app/components/permissions";
import { Button, DeleteActionModal, Tooltip } from "@app/components/v2";
import { LeaveProjectModal } from "@app/components/v2/LeaveProjectModal";
import {
ProjectPermissionActions,
ProjectPermissionSub,
useOrganization,
useProjectPermission,
useWorkspace
} from "@app/context";
import { useToggle } from "@app/hooks";
import { useDeleteWorkspace, useGetWorkspaceUsers, useLeaveProject } from "@app/hooks/api";
import { ProjectType } from "@app/hooks/api/workspace/types";
import { usePopUp } from "@app/hooks/usePopUp";
export const DeleteProjectSection = () => {
const navigate = useNavigate();
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
"deleteWorkspace",
"leaveWorkspace"
] as const);
const { currentOrg } = useOrganization();
const { hasProjectRole, membership } = useProjectPermission();
const { currentWorkspace } = useWorkspace();
const [isDeleting, setIsDeleting] = useToggle();
const [isLeaving, setIsLeaving] = useToggle();
const deleteWorkspace = useDeleteWorkspace();
const leaveProject = useLeaveProject();
const { data: members, isPending: isMembersLoading } = useGetWorkspaceUsers(
currentWorkspace?.id || ""
);
// If isNoAccessMember is true, then the user can't read the workspace members. So we need to handle this case separately.
const isNoAccessMember = hasProjectRole("no-access");
const isOnlyAdminMember = useMemo(() => {
if (!members || !membership || !hasProjectRole("admin")) return false;
const adminMembers = members.filter(
(member) => member.roles.map((r) => r.role).includes("admin") && member.id !== membership.id // exclude the current user
);
return !adminMembers.length;
}, [members, membership]);
const handleDeleteWorkspaceSubmit = async () => {
setIsDeleting.on();
try {
if (!currentWorkspace?.id) return;
await deleteWorkspace.mutateAsync({
workspaceID: currentWorkspace?.id
});
createNotification({
text: "Successfully deleted project",
type: "success"
});
navigate({
to: `/organization/${ProjectType.SecretManager}/overview` as const
});
handlePopUpClose("deleteWorkspace");
} catch (err) {
console.error(err);
createNotification({
text: "Failed to delete project",
type: "error"
});
} finally {
setIsDeleting.off();
}
};
const handleLeaveWorkspaceSubmit = async () => {
try {
setIsLeaving.on();
if (!currentWorkspace?.id || !currentOrg?.id) return;
// If there's no members, and the user has access to read members, something went wrong.
if (!members && !isNoAccessMember) return;
// If the user has elevated permissions and can read members:
if (!isNoAccessMember) {
if (!members) return;
if (members.length < 2) {
createNotification({
text: "You can't leave the project as you are the only member",
type: "error"
});
return;
}
// If the user has access to read members, and there's less than 1 admin member excluding the current user, they can't leave the project.
if (isOnlyAdminMember) {
createNotification({
text: "You can't leave a project with no admin members left. Promote another member to admin first.",
type: "error"
});
return;
}
}
// If it's actually a no-access member, then we don't really care about the members.
await leaveProject.mutateAsync({
workspaceId: currentWorkspace.id
});
navigate({
to: `/organization/${ProjectType.SecretManager}/overview` as const
});
} catch (err) {
console.error(err);
createNotification({
text: "Failed to leave project",
type: "error"
});
} finally {
setIsLeaving.off();
}
};
return (
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<p className="mb-4 text-xl font-semibold text-mineshaft-100">Danger Zone</p>
<div className="space-x-4">
<ProjectPermissionCan I={ProjectPermissionActions.Delete} a={ProjectPermissionSub.Project}>
{(isAllowed) => (
<Tooltip
className="max-w-sm"
content="This project is protected from deletion. To delete it, disable delete protection first."
isDisabled={!currentWorkspace?.hasDeleteProtection}
>
<Button
isLoading={isDeleting}
isDisabled={!isAllowed || isDeleting || currentWorkspace?.hasDeleteProtection}
colorSchema="danger"
variant="outline_bg"
type="submit"
onClick={() => handlePopUpOpen("deleteWorkspace")}
>
{`Delete ${currentWorkspace?.name}`}
</Button>
</Tooltip>
)}
</ProjectPermissionCan>
{!isOnlyAdminMember && (
<Button
disabled={isMembersLoading || (members && members?.length < 2)}
isLoading={isLeaving}
colorSchema="danger"
variant="outline_bg"
type="submit"
onClick={() => handlePopUpOpen("leaveWorkspace")}
>
{`Leave ${currentWorkspace?.name}`}
</Button>
)}
</div>
<DeleteActionModal
isOpen={popUp.deleteWorkspace.isOpen}
title="Are you sure you want to delete this project?"
subTitle={`Permanently delete ${currentWorkspace?.name} and all of its data. This action is not reversible, so please be careful.`}
onChange={(isOpen) => handlePopUpToggle("deleteWorkspace", isOpen)}
deleteKey="confirm"
buttonText="Delete Project"
onDeleteApproved={handleDeleteWorkspaceSubmit}
/>
<LeaveProjectModal
isOpen={popUp.leaveWorkspace.isOpen}
title="Are you sure you want to leave this project?"
subTitle={`If you leave ${currentWorkspace?.name} you will lose access to the project and its contents.`}
onChange={(isOpen) => handlePopUpToggle("leaveWorkspace", isOpen)}
deleteKey="confirm"
buttonText="Leave Project"
onLeaveApproved={handleLeaveWorkspaceSubmit}
/>
</div>
);
};
@@ -1 +0,0 @@
export { DeleteProjectSection } from "./DeleteProjectSection";
@@ -1,12 +1,8 @@
import { ProjectOverviewChangeSection } from "@app/components/project/ProjectOverviewChangeSection";
import { useWorkspace } from "@app/context";
import { ProjectType, ProjectVersion } from "@app/hooks/api/workspace/types";
import { ProjectVersion } from "@app/hooks/api/workspace/types";
import { AuditLogsRetentionSection } from "../AuditLogsRetentionSection";
import { AutoCapitalizationSection } from "../AutoCapitalizationSection";
import { BackfillSecretReferenceSecretion } from "../BackfillSecretReferenceSection";
import { DeleteProjectProtection } from "../DeleteProjectProtection";
import { DeleteProjectSection } from "../DeleteProjectSection";
import { EnvironmentSection } from "../EnvironmentSection";
import { PointInTimeVersionLimitSection } from "../PointInTimeVersionLimitSection";
import { RebuildSecretIndicesSection } from "../RebuildSecretIndicesSection/RebuildSecretIndicesSection";
@@ -16,24 +12,17 @@ import { SecretTagsSection } from "../SecretTagsSection";
export const ProjectGeneralTab = () => {
const { currentWorkspace } = useWorkspace();
const isSecretManager = currentWorkspace?.type === ProjectType.SecretManager;
return (
<div>
<ProjectOverviewChangeSection showSlugField />
{isSecretManager && <EnvironmentSection />}
{isSecretManager && <SecretTagsSection />}
{isSecretManager && <AutoCapitalizationSection />}
{isSecretManager && <SecretSharingSection />}
{isSecretManager && <SecretSnapshotsLegacySection />}
{isSecretManager && <PointInTimeVersionLimitSection />}
<AuditLogsRetentionSection />
{isSecretManager && <BackfillSecretReferenceSecretion />}
{currentWorkspace?.version !== ProjectVersion.V3 && isSecretManager && (
<RebuildSecretIndicesSection />
)}
<DeleteProjectProtection />
<DeleteProjectSection />
<EnvironmentSection />
<SecretTagsSection />
<AutoCapitalizationSection />
<SecretSharingSection />
<SecretSnapshotsLegacySection />
<PointInTimeVersionLimitSection />
<BackfillSecretReferenceSecretion />
{currentWorkspace?.version !== ProjectVersion.V3 && <RebuildSecretIndicesSection />}
</div>
);
};
@@ -1,5 +1,4 @@
export { AutoCapitalizationSection } from "./AutoCapitalizationSection";
export { BackfillSecretReferenceSecretion } from "./BackfillSecretReferenceSection";
export { DeleteProjectSection } from "./DeleteProjectSection";
export { EnvironmentSection } from "./EnvironmentSection";
export { SecretTagsSection } from "./SecretTagsSection";
@@ -3,7 +3,7 @@ import { createFileRoute } from "@tanstack/react-router";
import { SettingsPage } from "./SettingsPage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/settings"
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/settings"
)({
component: SettingsPage,
beforeLoad: ({ context }) => {
@@ -68,7 +68,7 @@ export const AWSParameterStoreAuthorizeIntegrationPage = () => {
});
navigate({
to: "/secret-manager/$projectId/integrations/aws-parameter-store/create",
to: "/projects/$projectId/secret-manager/integrations/aws-parameter-store/create",
params: {
projectId: currentWorkspace.id
},
@@ -5,7 +5,7 @@ import { IntegrationsListPageTabs } from "@app/types/integrations";
import { AWSParameterStoreAuthorizeIntegrationPage } from "./AwsParameterStoreAuthorizePage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/aws-parameter-store/authorize"
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/aws-parameter-store/authorize"
)({
component: AWSParameterStoreAuthorizeIntegrationPage,
beforeLoad: ({ context, params }) => {
@@ -15,7 +15,7 @@ export const Route = createFileRoute(
{
label: "Integrations",
link: linkOptions({
to: "/secret-manager/$projectId/integrations",
to: "/projects/$projectId/secret-manager/integrations",
params,
search: {
selectedTab: IntegrationsListPageTabs.NativeIntegrations
@@ -156,7 +156,7 @@ export const AWSParameterStoreConfigurePage = () => {
setPathErrorText("");
navigate({
to: "/secret-manager/$projectId/integrations",
to: "/projects/$projectId/secret-manager/integrations",
params: {
projectId: currentWorkspace.id
},
@@ -11,7 +11,7 @@ const AwsParameterStoreConfigurePageQueryParamsSchema = z.object({
});
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/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"
)({
component: AWSParameterStoreConfigurePage,
validateSearch: zodValidator(AwsParameterStoreConfigurePageQueryParamsSchema),
@@ -22,7 +22,7 @@ export const Route = createFileRoute(
{
label: "Integrations",
link: linkOptions({
to: "/secret-manager/$projectId/integrations",
to: "/projects/$projectId/secret-manager/integrations",
params,
search: {
selectedTab: IntegrationsListPageTabs.NativeIntegrations
@@ -66,7 +66,7 @@ export const AWSSecretManagerAuthorizePage = () => {
})
});
navigate({
to: "/secret-manager/$projectId/integrations/aws-secret-manager/create",
to: "/projects/$projectId/secret-manager/integrations/aws-secret-manager/create",
params: {
projectId: currentWorkspace.id
},
@@ -5,7 +5,7 @@ import { IntegrationsListPageTabs } from "@app/types/integrations";
import { AWSSecretManagerAuthorizePage } from "./AwsSecretManagerAuthorizePage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/aws-secret-manager/authorize"
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/aws-secret-manager/authorize"
)({
component: AWSSecretManagerAuthorizePage,
beforeLoad: ({ context, params }) => {
@@ -15,7 +15,7 @@ export const Route = createFileRoute(
{
label: "Integrations",
link: linkOptions({
to: "/secret-manager/$projectId/integrations",
to: "/projects/$projectId/secret-manager/integrations",
params,
search: {
selectedTab: IntegrationsListPageTabs.NativeIntegrations
@@ -204,7 +204,7 @@ export const AwsSecretManagerConfigurePage = () => {
});
navigate({
to: "/secret-manager/$projectId/integrations",
to: "/projects/$projectId/secret-manager/integrations",
params: {
projectId: currentWorkspace.id
},
@@ -11,7 +11,7 @@ const AwsSecretManagerConfigurePageQueryParamsSchema = z.object({
});
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/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"
)({
component: AwsSecretManagerConfigurePage,
validateSearch: zodValidator(AwsSecretManagerConfigurePageQueryParamsSchema),
@@ -22,7 +22,7 @@ export const Route = createFileRoute(
{
label: "Integrations",
link: linkOptions({
to: "/secret-manager/$projectId/integrations",
to: "/projects/$projectId/secret-manager/integrations",
params,
search: {
selectedTab: IntegrationsListPageTabs.NativeIntegrations
@@ -131,7 +131,7 @@ export const AzureAppConfigurationConfigurePage = () => {
});
navigate({
to: "/secret-manager/$projectId/integrations",
to: "/projects/$projectId/secret-manager/integrations",
params: {
projectId: currentWorkspace.id
},
@@ -11,7 +11,7 @@ const AzureAppConfigurationPageQueryParamsSchema = z.object({
});
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/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"
)({
component: AzureAppConfigurationConfigurePage,
validateSearch: zodValidator(AzureAppConfigurationPageQueryParamsSchema),
@@ -22,7 +22,7 @@ export const Route = createFileRoute(
{
label: "Integrations",
link: linkOptions({
to: "/secret-manager/$projectId/integrations",
to: "/projects/$projectId/secret-manager/integrations",
params,
search: {
selectedTab: IntegrationsListPageTabs.NativeIntegrations
@@ -28,7 +28,7 @@ export const AzureAppConfigurationOauthCallbackPage = () => {
});
navigate({
to: "/secret-manager/$projectId/integrations/azure-app-configuration/create",
to: "/projects/$projectId/secret-manager/integrations/azure-app-configuration/create",
params: {
projectId: currentWorkspace.id
},
@@ -12,7 +12,7 @@ export const AzureAppConfigurationOauthCallbackPageQueryParamsSchema = z.object(
});
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/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"
)({
component: AzureAppConfigurationOauthCallbackPage,
validateSearch: zodValidator(AzureAppConfigurationOauthCallbackPageQueryParamsSchema),
@@ -23,7 +23,7 @@ export const Route = createFileRoute(
{
label: "Integrations",
link: linkOptions({
to: "/secret-manager/$projectId/integrations",
to: "/projects/$projectId/secret-manager/integrations",
params,
search: {
selectedTab: IntegrationsListPageTabs.NativeIntegrations
@@ -39,7 +39,7 @@ export const AzureDevopsAuthorizePage = () => {
setIsLoading(false);
navigate({
to: "/secret-manager/$projectId/integrations/azure-devops/create",
to: "/projects/$projectId/secret-manager/integrations/azure-devops/create",
params: {
projectId: currentWorkspace.id
},
@@ -5,7 +5,7 @@ import { IntegrationsListPageTabs } from "@app/types/integrations";
import { AzureDevopsAuthorizePage } from "./AzureDevopsAuthorizePage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/azure-devops/authorize"
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/azure-devops/authorize"
)({
component: AzureDevopsAuthorizePage,
beforeLoad: ({ context, params }) => {
@@ -15,7 +15,7 @@ export const Route = createFileRoute(
{
label: "Integrations",
link: linkOptions({
to: "/secret-manager/$projectId/integrations",
to: "/projects/$projectId/secret-manager/integrations",
params,
search: {
selectedTab: IntegrationsListPageTabs.NativeIntegrations
@@ -80,7 +80,7 @@ export const AzureDevopsConfigurePage = () => {
setIsLoading(false);
navigate({
to: "/secret-manager/$projectId/integrations",
to: "/projects/$projectId/secret-manager/integrations",
params: {
projectId: currentWorkspace.id
},
@@ -11,7 +11,7 @@ const AzureDevopsConfigurePageQueryParamsSchema = z.object({
});
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/azure-devops/create"
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/azure-devops/create"
)({
component: AzureDevopsConfigurePage,
validateSearch: zodValidator(AzureDevopsConfigurePageQueryParamsSchema),
@@ -22,7 +22,7 @@ export const Route = createFileRoute(
{
label: "Integrations",
link: linkOptions({
to: "/secret-manager/$projectId/integrations",
to: "/projects/$projectId/secret-manager/integrations",
params,
search: {
selectedTab: IntegrationsListPageTabs.NativeIntegrations
@@ -11,7 +11,7 @@ const PageQueryParamsSchema = z.object({
});
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/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"
)({
component: AzureKeyVaultAuthorizePage,
validateSearch: PageQueryParamsSchema,
@@ -22,7 +22,7 @@ export const Route = createFileRoute(
{
label: "Integrations",
link: linkOptions({
to: "/secret-manager/$projectId/integrations",
to: "/projects/$projectId/secret-manager/integrations",
params,
search: {
selectedTab: IntegrationsListPageTabs.NativeIntegrations
@@ -88,7 +88,7 @@ export const AzureKeyVaultConfigurePage = () => {
setIsLoading(false);
navigate({
to: "/secret-manager/$projectId/integrations",
to: "/projects/$projectId/secret-manager/integrations",
params: {
projectId: currentWorkspace.id
},
@@ -11,7 +11,7 @@ const AzureKeyVaultConfigurePageQueryParamsSchema = z.object({
});
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/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"
)({
component: AzureKeyVaultConfigurePage,
validateSearch: zodValidator(AzureKeyVaultConfigurePageQueryParamsSchema),
@@ -22,7 +22,7 @@ export const Route = createFileRoute(
{
label: "Integrations",
link: linkOptions({
to: "/secret-manager/$projectId/integrations",
to: "/projects/$projectId/secret-manager/integrations",
params,
search: {
selectedTab: IntegrationsListPageTabs.NativeIntegrations
@@ -28,7 +28,7 @@ export const AzureKeyVaultOauthCallbackPage = () => {
});
navigate({
to: "/secret-manager/$projectId/integrations/azure-key-vault/create",
to: "/projects/$projectId/secret-manager/integrations/azure-key-vault/create",
params: {
projectId: currentWorkspace.id
},
@@ -12,7 +12,7 @@ export const AzureKeyVaultOauthCallbackQueryParamsSchema = z.object({
});
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/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"
)({
component: AzureKeyVaultOauthCallbackPage,
validateSearch: zodValidator(AzureKeyVaultOauthCallbackQueryParamsSchema),
@@ -23,7 +23,7 @@ export const Route = createFileRoute(
{
label: "Integrations",
link: linkOptions({
to: "/secret-manager/$projectId/integrations",
to: "/projects/$projectId/secret-manager/integrations",
params,
search: {
selectedTab: IntegrationsListPageTabs.NativeIntegrations
@@ -148,7 +148,7 @@ export const BitbucketConfigurePage = () => {
text: "Successfully created integration"
});
navigate({
to: "/secret-manager/$projectId/integrations",
to: "/projects/$projectId/secret-manager/integrations",
params: {
projectId: currentWorkspace.id
},
@@ -11,7 +11,7 @@ const BitbucketConfigurePageQueryParamsSchema = z.object({
});
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/bitbucket/create"
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/bitbucket/create"
)({
component: BitbucketConfigurePage,
validateSearch: zodValidator(BitbucketConfigurePageQueryParamsSchema),
@@ -22,7 +22,7 @@ export const Route = createFileRoute(
{
label: "Integrations",
link: linkOptions({
to: "/secret-manager/$projectId/integrations",
to: "/projects/$projectId/secret-manager/integrations",
params,
search: {
selectedTab: IntegrationsListPageTabs.NativeIntegrations
@@ -27,7 +27,7 @@ export const BitbucketOauthCallbackPage = () => {
});
navigate({
to: "/secret-manager/$projectId/integrations/bitbucket/create",
to: "/projects/$projectId/secret-manager/integrations/bitbucket/create",
params: {
projectId: currentWorkspace.id
},
@@ -12,7 +12,7 @@ export const BitbucketOauthCallbackQueryParamsSchema = z.object({
});
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/bitbucket/oauth2/callback"
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/bitbucket/oauth2/callback"
)({
component: BitbucketOauthCallbackPage,
validateSearch: zodValidator(BitbucketOauthCallbackQueryParamsSchema),
@@ -23,7 +23,7 @@ export const Route = createFileRoute(
{
label: "Integrations",
link: linkOptions({
to: "/secret-manager/$projectId/integrations",
to: "/projects/$projectId/secret-manager/integrations",
params,
search: {
selectedTab: IntegrationsListPageTabs.NativeIntegrations
@@ -36,7 +36,7 @@ export const ChecklyAuthorizePage = () => {
setIsLoading(false);
navigate({
to: "/secret-manager/$projectId/integrations/checkly/create",
to: "/projects/$projectId/secret-manager/integrations/checkly/create",
params: {
projectId: currentWorkspace.id
},
@@ -5,7 +5,7 @@ import { IntegrationsListPageTabs } from "@app/types/integrations";
import { ChecklyAuthorizePage } from "./ChecklyAuthorizePage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/checkly/authorize"
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/checkly/authorize"
)({
component: ChecklyAuthorizePage,
beforeLoad: ({ context, params }) => {
@@ -15,7 +15,7 @@ export const Route = createFileRoute(
{
label: "Integrations",
link: linkOptions({
to: "/secret-manager/$projectId/integrations",
to: "/projects/$projectId/secret-manager/integrations",
params,
search: {
selectedTab: IntegrationsListPageTabs.NativeIntegrations
@@ -113,7 +113,7 @@ export const ChecklyConfigurePage = () => {
setIsLoading(false);
navigate({
to: "/secret-manager/$projectId/integrations",
to: "/projects/$projectId/secret-manager/integrations",
params: {
projectId: currentWorkspace.id
},
@@ -11,7 +11,7 @@ const ChecklyConfigurePageQueryParamsSchema = z.object({
});
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/checkly/create"
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/checkly/create"
)({
component: ChecklyConfigurePage,
validateSearch: zodValidator(ChecklyConfigurePageQueryParamsSchema),
@@ -22,7 +22,7 @@ export const Route = createFileRoute(
{
label: "Integrations",
link: linkOptions({
to: "/secret-manager/$projectId/integrations",
to: "/projects/$projectId/secret-manager/integrations",
params,
search: {
selectedTab: IntegrationsListPageTabs.NativeIntegrations
@@ -36,7 +36,7 @@ export const CircleCIAuthorizePage = () => {
setIsLoading(false);
navigate({
to: "/secret-manager/$projectId/integrations/circleci/create",
to: "/projects/$projectId/secret-manager/integrations/circleci/create",
params: {
projectId: currentWorkspace.id
},
@@ -5,7 +5,7 @@ import { IntegrationsListPageTabs } from "@app/types/integrations";
import { CircleCIAuthorizePage } from "./CircleCIAuthorizePage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/circleci/authorize"
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/circleci/authorize"
)({
component: CircleCIAuthorizePage,
beforeLoad: ({ context, params }) => {
@@ -15,7 +15,7 @@ export const Route = createFileRoute(
{
label: "Integrations",
link: linkOptions({
to: "/secret-manager/$projectId/integrations",
to: "/projects/$projectId/secret-manager/integrations",
params,
search: {
selectedTab: IntegrationsListPageTabs.NativeIntegrations
@@ -103,7 +103,7 @@ export const CircleCIConfigurePage = () => {
text: "Successfully created integration"
});
navigate({
to: "/secret-manager/$projectId/integrations",
to: "/projects/$projectId/secret-manager/integrations",
params: {
projectId: currentWorkspace.id
},
@@ -11,7 +11,7 @@ const CircleCIConfigurePageQueryParamsSchema = z.object({
});
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/circleci/create"
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/circleci/create"
)({
component: CircleCIConfigurePage,
validateSearch: zodValidator(CircleCIConfigurePageQueryParamsSchema),
@@ -22,7 +22,7 @@ export const Route = createFileRoute(
{
label: "Integrations",
link: linkOptions({
to: "/secret-manager/$projectId/integrations",
to: "/projects/$projectId/secret-manager/integrations",
params,
search: {
selectedTab: IntegrationsListPageTabs.NativeIntegrations
@@ -34,7 +34,7 @@ export const Cloud66AuthorizePage = () => {
setIsLoading(false);
navigate({
to: "/secret-manager/$projectId/integrations/cloud-66/create",
to: "/projects/$projectId/secret-manager/integrations/cloud-66/create",
params: {
projectId: currentWorkspace.id
},
@@ -5,7 +5,7 @@ import { IntegrationsListPageTabs } from "@app/types/integrations";
import { Cloud66AuthorizePage } from "./Cloud66AuthorizePage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/cloud-66/authorize"
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/cloud-66/authorize"
)({
component: Cloud66AuthorizePage,
beforeLoad: ({ context, params }) => {
@@ -15,7 +15,7 @@ export const Route = createFileRoute(
{
label: "Integrations",
link: linkOptions({
to: "/secret-manager/$projectId/integrations",
to: "/projects/$projectId/secret-manager/integrations",
params,
search: {
selectedTab: IntegrationsListPageTabs.NativeIntegrations
@@ -75,7 +75,7 @@ export const Cloud66ConfigurePage = () => {
setIsLoading(false);
navigate({
to: "/secret-manager/$projectId/integrations",
to: "/projects/$projectId/secret-manager/integrations",
params: {
projectId: currentWorkspace.id
},
@@ -11,7 +11,7 @@ const Cloud66ConfigurePageQueryParamsSchema = z.object({
});
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/cloud-66/create"
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/cloud-66/create"
)({
component: Cloud66ConfigurePage,
validateSearch: zodValidator(Cloud66ConfigurePageQueryParamsSchema),
@@ -22,7 +22,7 @@ export const Route = createFileRoute(
{
label: "Integrations",
link: linkOptions({
to: "/secret-manager/$projectId/integrations",
to: "/projects/$projectId/secret-manager/integrations",
params,
search: {
selectedTab: IntegrationsListPageTabs.NativeIntegrations
@@ -41,7 +41,7 @@ export const CloudflarePagesAuthorizePage = () => {
setIsLoading(false);
navigate({
to: "/secret-manager/$projectId/integrations/cloudflare-pages/create",
to: "/projects/$projectId/secret-manager/integrations/cloudflare-pages/create",
params: {
projectId: currentWorkspace.id
},
@@ -5,7 +5,7 @@ import { IntegrationsListPageTabs } from "@app/types/integrations";
import { CloudflarePagesAuthorizePage } from "./CloudflarePagesAuthorizePage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/cloudflare-pages/authorize"
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/cloudflare-pages/authorize"
)({
component: CloudflarePagesAuthorizePage,
beforeLoad: ({ context, params }) => {
@@ -15,7 +15,7 @@ export const Route = createFileRoute(
{
label: "Integrations",
link: linkOptions({
to: "/secret-manager/$projectId/integrations",
to: "/projects/$projectId/secret-manager/integrations",
params,
search: {
selectedTab: IntegrationsListPageTabs.NativeIntegrations
@@ -94,7 +94,7 @@ export const CloudflarePagesConfigurePage = () => {
setIsLoading(false);
navigate({
to: "/secret-manager/$projectId/integrations",
to: "/projects/$projectId/secret-manager/integrations",
params: {
projectId: currentWorkspace.id
},
@@ -11,7 +11,7 @@ const CloudflarePagesConfigurePageQueryParamsSchema = z.object({
});
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/cloudflare-pages/create"
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/cloudflare-pages/create"
)({
component: CloudflarePagesConfigurePage,
validateSearch: zodValidator(CloudflarePagesConfigurePageQueryParamsSchema),
@@ -22,7 +22,7 @@ export const Route = createFileRoute(
{
label: "Integrations",
link: linkOptions({
to: "/secret-manager/$projectId/integrations",
to: "/projects/$projectId/secret-manager/integrations",
params,
search: {
selectedTab: IntegrationsListPageTabs.NativeIntegrations
@@ -41,7 +41,7 @@ export const CloudflareWorkersAuthorizePage = () => {
setIsLoading(false);
navigate({
to: "/secret-manager/$projectId/integrations/cloudflare-workers/create",
to: "/projects/$projectId/secret-manager/integrations/cloudflare-workers/create",
params: {
projectId: currentWorkspace.id
},
@@ -5,7 +5,7 @@ import { IntegrationsListPageTabs } from "@app/types/integrations";
import { CloudflareWorkersAuthorizePage } from "./CloudflareWorkersAuthorizePage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/cloudflare-workers/authorize"
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/cloudflare-workers/authorize"
)({
component: CloudflareWorkersAuthorizePage,
beforeLoad: ({ context, params }) => {
@@ -15,7 +15,7 @@ export const Route = createFileRoute(
{
label: "Integrations",
link: linkOptions({
to: "/secret-manager/$projectId/integrations",
to: "/projects/$projectId/secret-manager/integrations",
params,
search: {
selectedTab: IntegrationsListPageTabs.NativeIntegrations
@@ -67,7 +67,7 @@ export const CloudflareWorkersConfigurePage = () => {
setIsLoading(false);
navigate({
to: "/secret-manager/$projectId/integrations",
to: "/projects/$projectId/secret-manager/integrations",
params: {
projectId: currentWorkspace.id
},
@@ -11,7 +11,7 @@ const CloudflareWorkersConfigurePageQueryParamsSchema = z.object({
});
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/cloudflare-workers/create"
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/cloudflare-workers/create"
)({
component: CloudflareWorkersConfigurePage,
validateSearch: zodValidator(CloudflareWorkersConfigurePageQueryParamsSchema),
@@ -22,7 +22,7 @@ export const Route = createFileRoute(
{
label: "Integrations",
link: linkOptions({
to: "/secret-manager/$projectId/integrations",
to: "/projects/$projectId/secret-manager/integrations",
params,
search: {
selectedTab: IntegrationsListPageTabs.NativeIntegrations
@@ -33,7 +33,7 @@ export const CodefreshAuthorizePage = () => {
setIsLoading(false);
navigate({
to: "/secret-manager/$projectId/integrations/codefresh/create",
to: "/projects/$projectId/secret-manager/integrations/codefresh/create",
params: {
projectId: currentWorkspace.id
},
@@ -5,7 +5,7 @@ import { IntegrationsListPageTabs } from "@app/types/integrations";
import { CodefreshAuthorizePage } from "./CodefreshAuthorizePage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/codefresh/authorize"
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/codefresh/authorize"
)({
component: CodefreshAuthorizePage,
beforeLoad: ({ context, params }) => {
@@ -15,7 +15,7 @@ export const Route = createFileRoute(
{
label: "Integrations",
link: linkOptions({
to: "/secret-manager/$projectId/integrations",
to: "/projects/$projectId/secret-manager/integrations",
params,
search: {
selectedTab: IntegrationsListPageTabs.NativeIntegrations
@@ -71,7 +71,7 @@ export const CodefreshConfigurePage = () => {
setIsLoading(false);
navigate({
to: "/secret-manager/$projectId/integrations",
to: "/projects/$projectId/secret-manager/integrations",
params: {
projectId: currentWorkspace.id
},
@@ -11,7 +11,7 @@ const CodefreshConfigurePageQueryParamsSchema = z.object({
});
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/codefresh/create"
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/codefresh/create"
)({
component: CodefreshConfigurePage,
validateSearch: zodValidator(CodefreshConfigurePageQueryParamsSchema),
@@ -22,7 +22,7 @@ export const Route = createFileRoute(
{
label: "Integrations",
link: linkOptions({
to: "/secret-manager/$projectId/integrations",
to: "/projects/$projectId/secret-manager/integrations",
params,
search: {
selectedTab: IntegrationsListPageTabs.NativeIntegrations
@@ -40,7 +40,7 @@ export const DatabricksAuthorizePage = () => {
});
navigate({
to: "/secret-manager/$projectId/integrations/databricks/create",
to: "/projects/$projectId/secret-manager/integrations/databricks/create",
params: {
projectId: currentWorkspace.id
},
@@ -5,7 +5,7 @@ import { IntegrationsListPageTabs } from "@app/types/integrations";
import { DatabricksAuthorizePage } from "./DatabricksAuthorizePage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/databricks/authorize"
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/databricks/authorize"
)({
component: DatabricksAuthorizePage,
beforeLoad: ({ context, params }) => {
@@ -15,7 +15,7 @@ export const Route = createFileRoute(
{
label: "Integrations",
link: linkOptions({
to: "/secret-manager/$projectId/integrations",
to: "/projects/$projectId/secret-manager/integrations",
params,
search: {
selectedTab: IntegrationsListPageTabs.NativeIntegrations
@@ -87,7 +87,7 @@ export const DatabricksConfigurePage = () => {
});
navigate({
to: "/secret-manager/$projectId/integrations",
to: "/projects/$projectId/secret-manager/integrations",
params: {
projectId: currentWorkspace.id
},
@@ -11,7 +11,7 @@ const DatabricksConfigurePageQueryParamsSchema = z.object({
});
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/databricks/create"
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/databricks/create"
)({
component: DatabricksConfigurePage,
validateSearch: zodValidator(DatabricksConfigurePageQueryParamsSchema),
@@ -22,7 +22,7 @@ export const Route = createFileRoute(
{
label: "Integrations",
link: linkOptions({
to: "/secret-manager/$projectId/integrations",
to: "/projects/$projectId/secret-manager/integrations",
params,
search: {
selectedTab: IntegrationsListPageTabs.NativeIntegrations

Some files were not shown because too many files have changed in this diff Show More