mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 14:27:30 +00:00
feat: completed secret manager integration list and detail page
This commit is contained in:
@@ -63,6 +63,10 @@ export const ROUTE_PATHS = Object.freeze({
|
|||||||
OverviewPage: setRoute(
|
OverviewPage: setRoute(
|
||||||
"/secret-manager/$projectId/overview",
|
"/secret-manager/$projectId/overview",
|
||||||
"/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/overview"
|
"/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/overview"
|
||||||
|
),
|
||||||
|
IntegrationDetailsByIDPage: setRoute(
|
||||||
|
"/secret-manager/$projectId/integrations/$integrationId",
|
||||||
|
"/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/$integrationId"
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
CertManager: {
|
CertManager: {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useMutation, useQuery, useQueryClient, UseQueryOptions } from "@tanstack/react-query";
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
|
|
||||||
import { apiRequest } from "@app/config/request";
|
import { apiRequest } from "@app/config/request";
|
||||||
|
import { TReactQueryOptions } from "@app/types/reactQuery";
|
||||||
|
|
||||||
import { workspaceKeys } from "../workspace";
|
import { workspaceKeys } from "../workspace";
|
||||||
import {
|
import {
|
||||||
@@ -530,11 +531,7 @@ export const useGetIntegrationAuthApps = (
|
|||||||
azureDevOpsOrgName?: string;
|
azureDevOpsOrgName?: string;
|
||||||
workspaceSlug?: string;
|
workspaceSlug?: string;
|
||||||
},
|
},
|
||||||
options?: UseQueryOptions<
|
options?: TReactQueryOptions["options"]
|
||||||
Awaited<ReturnType<typeof fetchIntegrationAuthApps>>,
|
|
||||||
unknown,
|
|
||||||
Awaited<ReturnType<typeof fetchIntegrationAuthApps>>
|
|
||||||
>
|
|
||||||
) => {
|
) => {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: integrationAuthKeys.getIntegrationAuthApps(integrationAuthId, teamId, workspaceSlug),
|
queryKey: integrationAuthKeys.getIntegrationAuthApps(integrationAuthId, teamId, workspaceSlug),
|
||||||
@@ -806,11 +803,7 @@ export const useGetIntegrationAuthOctopusDeploySpaces = (integrationAuthId: stri
|
|||||||
|
|
||||||
export const useGetIntegrationAuthOctopusDeployScopeValues = (
|
export const useGetIntegrationAuthOctopusDeployScopeValues = (
|
||||||
params: TGetIntegrationAuthOctopusDeployScopeValuesDTO,
|
params: TGetIntegrationAuthOctopusDeployScopeValuesDTO,
|
||||||
options?: UseQueryOptions<
|
options?: TReactQueryOptions["options"]
|
||||||
TOctopusDeployVariableSetScopeValues,
|
|
||||||
unknown,
|
|
||||||
TOctopusDeployVariableSetScopeValues
|
|
||||||
>
|
|
||||||
) =>
|
) =>
|
||||||
useQuery({
|
useQuery({
|
||||||
queryKey: integrationAuthKeys.getIntegrationAuthOctopusDeployScopeValues(params),
|
queryKey: integrationAuthKeys.getIntegrationAuthOctopusDeployScopeValues(params),
|
||||||
@@ -828,7 +821,7 @@ export const useGetIntegrationAuthBitBucketEnvironments = (
|
|||||||
workspaceSlug: string;
|
workspaceSlug: string;
|
||||||
repoSlug: string;
|
repoSlug: string;
|
||||||
},
|
},
|
||||||
options?: UseQueryOptions<BitBucketEnvironment[]>
|
options?: TReactQueryOptions["options"]
|
||||||
) => {
|
) => {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: integrationAuthKeys.getIntegrationAuthBitBucketEnvironments(
|
queryKey: integrationAuthKeys.getIntegrationAuthBitBucketEnvironments(
|
||||||
|
|||||||
@@ -105,6 +105,19 @@ export enum OctopusDeployScope {
|
|||||||
// tenant, variable set
|
// tenant, variable set
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type CircleCIOrganization = {
|
||||||
|
name: string;
|
||||||
|
slug: string;
|
||||||
|
projects: {
|
||||||
|
name: string;
|
||||||
|
id: string;
|
||||||
|
}[];
|
||||||
|
contexts: {
|
||||||
|
name: string;
|
||||||
|
id: string;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
|
||||||
export type TGetIntegrationAuthOctopusDeployScopeValuesDTO = {
|
export type TGetIntegrationAuthOctopusDeployScopeValuesDTO = {
|
||||||
integrationAuthId: string;
|
integrationAuthId: string;
|
||||||
spaceId: string;
|
spaceId: string;
|
||||||
@@ -125,3 +138,8 @@ export type TOctopusDeployVariableSetScopeValues = {
|
|||||||
Name: string;
|
Name: string;
|
||||||
}[];
|
}[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export enum CircleCiScope {
|
||||||
|
Context = "context",
|
||||||
|
Project = "project"
|
||||||
|
}
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ export type TIntegration = {
|
|||||||
key: string;
|
key: string;
|
||||||
value: string;
|
value: string;
|
||||||
}[];
|
}[];
|
||||||
|
azureLabel?: string;
|
||||||
|
|
||||||
kmsKeyId?: string;
|
kmsKeyId?: string;
|
||||||
secretSuffix?: string;
|
secretSuffix?: string;
|
||||||
|
|||||||
+142
@@ -0,0 +1,142 @@
|
|||||||
|
import { Helmet } from "react-helmet";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { faChevronLeft, faEllipsis, faRefresh, faTrash } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { useNavigate, useParams } from "@tanstack/react-router";
|
||||||
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
|
import { OrgPermissionCan } from "@app/components/permissions";
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
EmptyState,
|
||||||
|
Tooltip
|
||||||
|
} from "@app/components/v2";
|
||||||
|
import { ROUTE_PATHS } from "@app/const/routes";
|
||||||
|
import { OrgPermissionActions, OrgPermissionSubjects, useWorkspace } from "@app/context";
|
||||||
|
import { useGetIntegration } from "@app/hooks/api";
|
||||||
|
import { useSyncIntegration } from "@app/hooks/api/integrations/queries";
|
||||||
|
|
||||||
|
import { IntegrationAuditLogsSection } from "./components/IntegrationAuditLogsSection";
|
||||||
|
import { IntegrationConnectionSection } from "./components/IntegrationConnectionSection";
|
||||||
|
import { IntegrationDetailsSection } from "./components/IntegrationDetailsSection";
|
||||||
|
import { IntegrationSettingsSection } from "./components/IntegrationSettingsSection";
|
||||||
|
import { integrationSlugNameMapping } from "./IntegrationsDetailsByIDPage.utils";
|
||||||
|
|
||||||
|
export const IntegrationDetailsByIDPage = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const integrationId = useParams({
|
||||||
|
from: ROUTE_PATHS.SecretManager.IntegrationDetailsByIDPage.id,
|
||||||
|
select: (el) => el.integrationId
|
||||||
|
});
|
||||||
|
|
||||||
|
const { data: integration } = useGetIntegration(integrationId, {
|
||||||
|
refetchInterval: 4000
|
||||||
|
});
|
||||||
|
|
||||||
|
const { currentWorkspace } = useWorkspace();
|
||||||
|
const projectId = currentWorkspace.id;
|
||||||
|
const { mutateAsync: syncIntegration } = useSyncIntegration();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Helmet>
|
||||||
|
<title>Integration Details | Infisical</title>
|
||||||
|
<meta property="og:image" content="/images/message.png" />
|
||||||
|
<meta property="og:title" content="Manage your .env files in seconds" />
|
||||||
|
<meta name="og:description" content={t("integrations.description") as string} />
|
||||||
|
</Helmet>
|
||||||
|
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white">
|
||||||
|
{integration ? (
|
||||||
|
<div className="mx-auto mb-6 w-full max-w-7xl px-6 py-6">
|
||||||
|
<Button
|
||||||
|
variant="link"
|
||||||
|
type="submit"
|
||||||
|
leftIcon={<FontAwesomeIcon icon={faChevronLeft} />}
|
||||||
|
onClick={() => {
|
||||||
|
navigate({
|
||||||
|
to: "/secret-manager/$projectId/integrations",
|
||||||
|
params: {
|
||||||
|
projectId
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
className="mb-4"
|
||||||
|
>
|
||||||
|
Integrations
|
||||||
|
</Button>
|
||||||
|
<div className="mb-4 flex items-center justify-between">
|
||||||
|
<p className="text-3xl font-semibold text-white">
|
||||||
|
{integrationSlugNameMapping[integration.integration]} Integration
|
||||||
|
</p>
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild className="rounded-lg">
|
||||||
|
<div className="hover:text-primary-400 data-[state=open]:text-primary-400">
|
||||||
|
<Tooltip content="More options">
|
||||||
|
<FontAwesomeIcon size="sm" icon={faEllipsis} />
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="start" className="p-1">
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={async () => {
|
||||||
|
await syncIntegration({
|
||||||
|
id: integration.id,
|
||||||
|
lastUsed: integration.lastUsed!,
|
||||||
|
workspaceId: projectId!
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<FontAwesomeIcon icon={faRefresh} />
|
||||||
|
Manually Sync
|
||||||
|
</div>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<OrgPermissionCan
|
||||||
|
I={OrgPermissionActions.Delete}
|
||||||
|
a={OrgPermissionSubjects.Member}
|
||||||
|
>
|
||||||
|
{(isAllowed) => (
|
||||||
|
<DropdownMenuItem
|
||||||
|
className={twMerge(
|
||||||
|
isAllowed
|
||||||
|
? "hover:!bg-red-500 hover:!text-white"
|
||||||
|
: "pointer-events-none cursor-not-allowed opacity-50"
|
||||||
|
)}
|
||||||
|
onClick={() => {}}
|
||||||
|
disabled={!isAllowed}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<FontAwesomeIcon icon={faTrash} />
|
||||||
|
Delete Integration
|
||||||
|
</div>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
)}
|
||||||
|
</OrgPermissionCan>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-center">
|
||||||
|
<div className="mr-4 w-96">
|
||||||
|
<IntegrationDetailsSection integration={integration} />
|
||||||
|
<IntegrationConnectionSection integration={integration} />
|
||||||
|
</div>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<IntegrationSettingsSection integration={integration} />
|
||||||
|
<IntegrationAuditLogsSection integration={integration} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div>
|
||||||
|
<EmptyState title="Error: Unable to fetch integration." className="py-12" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
+37
@@ -0,0 +1,37 @@
|
|||||||
|
export const integrationSlugNameMapping: Record<string, string> = {
|
||||||
|
"azure-key-vault": "Azure Key Vault",
|
||||||
|
"aws-parameter-store": "AWS Parameter Store",
|
||||||
|
"aws-secret-manager": "AWS Secrets Manager",
|
||||||
|
heroku: "Heroku",
|
||||||
|
vercel: "Vercel",
|
||||||
|
netlify: "Netlify",
|
||||||
|
github: "GitHub",
|
||||||
|
gitlab: "GitLab",
|
||||||
|
render: "Render",
|
||||||
|
"laravel-forge": "Laravel Forge",
|
||||||
|
railway: "Railway",
|
||||||
|
flyio: "Fly.io",
|
||||||
|
circleci: "CircleCI",
|
||||||
|
databricks: "Databricks",
|
||||||
|
travisci: "TravisCI",
|
||||||
|
supabase: "Supabase",
|
||||||
|
checkly: "Checkly",
|
||||||
|
qovery: "Qovery",
|
||||||
|
"terraform-cloud": "Terraform Cloud",
|
||||||
|
teamcity: "TeamCity",
|
||||||
|
"hashicorp-vault": "Vault",
|
||||||
|
"cloudflare-pages": "Cloudflare Pages",
|
||||||
|
"cloudflare-workers": "Cloudflare Workers",
|
||||||
|
codefresh: "Codefresh",
|
||||||
|
"digital-ocean-app-platform": "Digital Ocean App Platform",
|
||||||
|
bitbucket: "Bitbucket",
|
||||||
|
"cloud-66": "Cloud 66",
|
||||||
|
northflank: "Northflank",
|
||||||
|
windmill: "Windmill",
|
||||||
|
"gcp-secret-manager": "GCP Secret Manager",
|
||||||
|
"hasura-cloud": "Hasura Cloud",
|
||||||
|
rundeck: "Rundeck",
|
||||||
|
"azure-devops": "Azure DevOps",
|
||||||
|
"azure-app-configuration": "Azure App Configuration",
|
||||||
|
"octopus-deploy": "Octopus Deploy"
|
||||||
|
};
|
||||||
+85
@@ -0,0 +1,85 @@
|
|||||||
|
import { Link } from "@tanstack/react-router";
|
||||||
|
|
||||||
|
import { EmptyState } from "@app/components/v2";
|
||||||
|
import { useSubscription } from "@app/context";
|
||||||
|
import { EventType } from "@app/hooks/api/auditLogs/enums";
|
||||||
|
import { TIntegrationWithEnv } from "@app/hooks/api/integrations/types";
|
||||||
|
import { LogsSection } from "@app/pages/organization/AuditLogsPage/components/LogsSection";
|
||||||
|
|
||||||
|
// Add more events if needed
|
||||||
|
const INTEGRATION_EVENTS = [EventType.INTEGRATION_SYNCED];
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
integration: TIntegrationWithEnv;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const IntegrationAuditLogsSection = ({ integration }: Props) => {
|
||||||
|
const { subscription } = useSubscription();
|
||||||
|
|
||||||
|
const auditLogsRetentionDays = subscription?.auditLogsRetentionDays ?? 30;
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-nested-ternary
|
||||||
|
return subscription?.auditLogs ? (
|
||||||
|
<div className="h-full w-full min-w-[51rem] rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||||
|
<div className="mb-4 flex items-center justify-between border-b border-mineshaft-400 pb-4">
|
||||||
|
<p className="text-lg font-semibold text-gray-200">Integration Logs</p>
|
||||||
|
<p className="text-xs text-gray-400">
|
||||||
|
Displaying audit logs from the last {auditLogsRetentionDays} days
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<LogsSection
|
||||||
|
refetchInterval={4000}
|
||||||
|
remappedHeaders={{
|
||||||
|
Metadata: "Sync Status"
|
||||||
|
}}
|
||||||
|
showFilters={false}
|
||||||
|
presets={{
|
||||||
|
eventMetadata: { integrationId: integration.id },
|
||||||
|
startDate: new Date(new Date().setDate(new Date().getDate() - auditLogsRetentionDays)),
|
||||||
|
eventType: INTEGRATION_EVENTS
|
||||||
|
}}
|
||||||
|
filterClassName="bg-mineshaft-900 static"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="h-full w-full min-w-[51rem] rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4 opacity-60">
|
||||||
|
<div className="mb-4 flex items-center justify-between border-b border-mineshaft-400 pb-4">
|
||||||
|
<p className="text-lg font-semibold text-gray-200">Integration Logs</p>
|
||||||
|
</div>
|
||||||
|
<EmptyState
|
||||||
|
className="rounded-lg"
|
||||||
|
title={
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
Please{" "}
|
||||||
|
{subscription && subscription.slug !== null ? (
|
||||||
|
<Link to="/organization/billing" target="_blank" rel="noopener noreferrer">
|
||||||
|
<a
|
||||||
|
className="cursor-pointer font-medium text-primary-500 transition-all hover:text-primary-600"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
upgrade your subscription
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
) : (
|
||||||
|
<a
|
||||||
|
href="https://infisical.com/scheduledemo"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
className="cursor-pointer font-medium text-primary-500 transition-all hover:text-primary-600"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
upgrade your subscription
|
||||||
|
</a>
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
to view integration logs
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
+233
@@ -0,0 +1,233 @@
|
|||||||
|
import { FormLabel } from "@app/components/v2";
|
||||||
|
import { CircleCiScope } from "@app/hooks/api/integrationAuth/types";
|
||||||
|
import { IntegrationMappingBehavior, TIntegrationWithEnv } from "@app/hooks/api/integrations/types";
|
||||||
|
|
||||||
|
import { integrationSlugNameMapping } from "../IntegrationsDetailsByIDPage.utils";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
integration: TIntegrationWithEnv;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const IntegrationConnectionSection = ({ integration }: Props) => {
|
||||||
|
const specifcQoveryDetails = () => {
|
||||||
|
if (integration.integration !== "qovery") return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-row">
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<FormLabel className="text-sm font-semibold text-mineshaft-300" label="Organization" />
|
||||||
|
<div className="text-sm text-mineshaft-300">{integration?.owner || "-"}</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<FormLabel className="text-sm font-semibold text-mineshaft-300" label="Project" />
|
||||||
|
<div className="text-sm text-mineshaft-300">{integration?.targetService || "-"}</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<FormLabel
|
||||||
|
className="text-sm font-semibold text-mineshaft-300"
|
||||||
|
label="Target Environment"
|
||||||
|
/>
|
||||||
|
<div className="text-sm text-mineshaft-300">{integration?.targetEnvironment || "-"}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const isNotAwsManagerOneToOneDetails = () => {
|
||||||
|
const isAwsSecretManagerOneToOne =
|
||||||
|
integration.integration === "aws-secret-manager" &&
|
||||||
|
integration.metadata?.mappingBehavior === IntegrationMappingBehavior.ONE_TO_ONE;
|
||||||
|
|
||||||
|
if (isAwsSecretManagerOneToOne) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const formLabel = () => {
|
||||||
|
switch (integration.integration) {
|
||||||
|
case "qovery":
|
||||||
|
return integration.scope;
|
||||||
|
case "circleci":
|
||||||
|
if (integration.scope === CircleCiScope.Context) {
|
||||||
|
return "Context";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "Project";
|
||||||
|
case "terraform-cloud":
|
||||||
|
return "Project";
|
||||||
|
case "aws-secret-manager":
|
||||||
|
return "Secret";
|
||||||
|
case "aws-parameter-store":
|
||||||
|
case "rundeck":
|
||||||
|
return "Path";
|
||||||
|
case "bitbucket":
|
||||||
|
return "Repository";
|
||||||
|
case "octopus-deploy":
|
||||||
|
return "Project";
|
||||||
|
case "github":
|
||||||
|
if (["github-env", "github-repo"].includes(integration.scope!)) {
|
||||||
|
return "Repository";
|
||||||
|
}
|
||||||
|
return "Organization";
|
||||||
|
|
||||||
|
default:
|
||||||
|
return "App";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const contents = () => {
|
||||||
|
switch (integration.integration) {
|
||||||
|
case "hashicorp-vault":
|
||||||
|
return `${integration.app} - path: ${integration.path}`;
|
||||||
|
case "github":
|
||||||
|
if (integration.scope === "github-org") {
|
||||||
|
return `${integration.owner}`;
|
||||||
|
}
|
||||||
|
return `${integration.owner}/${integration.app}`;
|
||||||
|
case "aws-parameter-store":
|
||||||
|
case "rundeck":
|
||||||
|
return `${integration.path}`;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return `${integration.app}`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<FormLabel className="text-sm font-semibold text-mineshaft-300" label={formLabel()} />
|
||||||
|
<div className="text-sm text-mineshaft-300">{contents()}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const targetEnvironmentDetails = () => {
|
||||||
|
if (integration.integration === "bitbucket") {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<FormLabel className="text-sm font-semibold text-mineshaft-300" label="Workspace" />
|
||||||
|
<div className="text-sm text-mineshaft-300">
|
||||||
|
{integration.targetEnvironment || integration.targetEnvironmentId}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (integration.integration === "octopus-deploy") {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<FormLabel className="text-sm font-semibold text-mineshaft-300" label="Space" />
|
||||||
|
<div className="text-sm text-mineshaft-300">{integration.targetEnvironment}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
["vercel", "netlify", "railway", "gitlab", "teamcity"].includes(integration.integration) ||
|
||||||
|
(integration.integration === "github" && integration.scope === "github-env")
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<FormLabel
|
||||||
|
className="text-sm font-semibold text-mineshaft-300"
|
||||||
|
label="Target Environment"
|
||||||
|
/>
|
||||||
|
<div className="text-sm text-mineshaft-300">
|
||||||
|
{integration.targetEnvironment || integration.targetEnvironmentId}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const generalIntegrationSpecificDetails = () => {
|
||||||
|
if (integration.integration === "checkly" && integration.targetService) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<FormLabel className="text-sm font-semibold text-mineshaft-300" label="Group" />
|
||||||
|
<div className="text-sm text-mineshaft-300">{integration.targetService}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (integration.integration === "circleci" && integration.owner) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<FormLabel className="text-sm font-semibold text-mineshaft-300" label="Organization" />
|
||||||
|
<div className="text-sm text-mineshaft-300">{integration.owner}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (integration.integration === "terraform-cloud" && integration.targetService) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<FormLabel className="text-sm font-semibold text-mineshaft-300" label="Category" />
|
||||||
|
<div className="text-sm text-mineshaft-300">{integration.targetService}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (integration.integration === "checkly" || integration.integration === "github") {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<FormLabel className="text-sm font-semibold text-mineshaft-300" label="Secret Suffix" />
|
||||||
|
<div className="text-sm text-mineshaft-300">
|
||||||
|
{integration?.metadata?.secretSuffix || "-"}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (integration.integration === "bitbucket" && integration.targetServiceId) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<FormLabel
|
||||||
|
className="text-sm font-semibold text-mineshaft-300"
|
||||||
|
label="Deployment Environment"
|
||||||
|
/>
|
||||||
|
<div className="text-sm text-mineshaft-300">{integration.targetService}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mt-4 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||||
|
<div className="flex items-center justify-between border-b border-mineshaft-400 pb-4">
|
||||||
|
<h3 className="text-lg font-semibold text-mineshaft-100">Connection</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-4">
|
||||||
|
<FormLabel className="my-2" label="Source" />
|
||||||
|
|
||||||
|
<div className="space-y-2 rounded-lg border border-mineshaft-700 bg-mineshaft-800 p-2">
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<FormLabel className="text-sm font-semibold text-mineshaft-300" label="Environment" />
|
||||||
|
<div className="text-sm text-mineshaft-300">{integration.environment.name}</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<FormLabel className="text-sm font-semibold text-mineshaft-300" label="Secret Path" />
|
||||||
|
<div className="text-sm text-mineshaft-300">{integration.secretPath}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<FormLabel className="my-2" label="Destination" />
|
||||||
|
<div className="space-y-2 rounded-lg border border-mineshaft-700 bg-mineshaft-800 p-2">
|
||||||
|
<FormLabel className="text-sm font-semibold text-mineshaft-300" label="Platform" />
|
||||||
|
<div className="text-sm text-mineshaft-300">
|
||||||
|
{integrationSlugNameMapping?.[integration.integration]}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{specifcQoveryDetails()}
|
||||||
|
{isNotAwsManagerOneToOneDetails()}
|
||||||
|
{targetEnvironmentDetails()}
|
||||||
|
{generalIntegrationSpecificDetails()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
+70
@@ -0,0 +1,70 @@
|
|||||||
|
import { faCalendarCheck, faCheckCircle, faCircleXmark } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { format } from "date-fns";
|
||||||
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
|
import { TIntegrationWithEnv } from "@app/hooks/api/integrations/types";
|
||||||
|
|
||||||
|
import { integrationSlugNameMapping } from "../IntegrationsDetailsByIDPage.utils";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
integration: TIntegrationWithEnv;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const IntegrationDetailsSection = ({ integration }: Props) => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="w-full rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||||
|
<div className="flex items-center justify-between border-b border-mineshaft-400 pb-4">
|
||||||
|
<h3 className="text-lg font-semibold text-mineshaft-100">Integration Details</h3>
|
||||||
|
</div>
|
||||||
|
<div className="mt-4">
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div>
|
||||||
|
<p className="text-sm font-semibold text-mineshaft-300">Name</p>
|
||||||
|
<p className="text-sm text-mineshaft-300">
|
||||||
|
{integrationSlugNameMapping[integration.integration]}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="text-sm font-semibold text-mineshaft-300">Sync Status</p>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<p
|
||||||
|
className={twMerge(
|
||||||
|
"mr-2 text-sm font-medium",
|
||||||
|
integration.isSynced ? "text-green-500" : "text-red-500"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{integration.isSynced ? "Synced" : "Not Synced"}
|
||||||
|
</p>
|
||||||
|
<FontAwesomeIcon
|
||||||
|
size="sm"
|
||||||
|
className={twMerge(integration.isSynced ? "text-green-500" : "text-red-500")}
|
||||||
|
icon={integration.isSynced ? faCheckCircle : faCircleXmark}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{integration.lastUsed && (
|
||||||
|
<div>
|
||||||
|
<p className="text-sm font-semibold text-mineshaft-300">Latest Successful Sync</p>
|
||||||
|
<div className="flex items-center gap-2 text-sm text-mineshaft-300">
|
||||||
|
{format(new Date(integration.lastUsed), "yyyy-MM-dd, hh:mm aaa")}
|
||||||
|
<FontAwesomeIcon icon={faCalendarCheck} className="pr-2 pt-0.5 text-sm" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{!integration.isSynced && integration.syncMessage && (
|
||||||
|
<>
|
||||||
|
<p className="text-sm font-semibold text-mineshaft-300">Latest Sync Error</p>
|
||||||
|
<p className="text-sm text-mineshaft-300">{integration.syncMessage}</p>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
+99
@@ -0,0 +1,99 @@
|
|||||||
|
import { TIntegrationWithEnv } from "@app/hooks/api/integrations/types";
|
||||||
|
|
||||||
|
import { OctopusDeployScopeValues } from "./OctopusDeployScopeValues";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
integration: TIntegrationWithEnv;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Metadata = NonNullable<TIntegrationWithEnv["metadata"]>;
|
||||||
|
type MetadataKey = keyof Metadata;
|
||||||
|
type MetadataValue<K extends MetadataKey> = Metadata[K];
|
||||||
|
|
||||||
|
const metadataMappings: Record<keyof NonNullable<TIntegrationWithEnv["metadata"]>, string> = {
|
||||||
|
githubVisibility: "Github Visibility",
|
||||||
|
githubVisibilityRepoIds: "Github Visibility Repo Ids",
|
||||||
|
shouldAutoRedeploy: "Auto Redeploy Target Application When Secrets Change",
|
||||||
|
secretAWSTag: "Tags For Secrets Stored In AWS",
|
||||||
|
azureLabel: "Azure Label",
|
||||||
|
kmsKeyId: "AWS KMS Key ID",
|
||||||
|
secretSuffix: "Secret Suffix",
|
||||||
|
secretPrefix: "Secret Prefix",
|
||||||
|
syncBehavior: "Secrets Sync behavior",
|
||||||
|
initialSyncBehavior: "Initial Sync Behavior",
|
||||||
|
mappingBehavior: "Secrets Mapping Behavior",
|
||||||
|
scope: "Scope",
|
||||||
|
org: "Organization",
|
||||||
|
project: "Project",
|
||||||
|
environment: "Environment",
|
||||||
|
shouldDisableDelete: "AWS Secret Deletion Disabled",
|
||||||
|
shouldMaskSecrets: "GitLab Secrets Masking Enabled",
|
||||||
|
shouldProtectSecrets: "GitLab Secret Protection Enabled",
|
||||||
|
shouldEnableDelete: "GitHub Secret Deletion Enabled",
|
||||||
|
octopusDeployScopeValues: "Octopus Deploy Scope Values",
|
||||||
|
awsIamRole: "AWS IAM Role",
|
||||||
|
region: "Region"
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const IntegrationSettingsSection = ({ integration }: Props) => {
|
||||||
|
const renderValue = <K extends MetadataKey>(key: K, value: MetadataValue<K>) => {
|
||||||
|
if (!value) return null;
|
||||||
|
|
||||||
|
if (key === "octopusDeployScopeValues")
|
||||||
|
return <OctopusDeployScopeValues integration={integration} />;
|
||||||
|
|
||||||
|
// If it's a boolean, we render a generic "Yes" or "No" response.
|
||||||
|
if (typeof value === "boolean") {
|
||||||
|
return value ? "Yes" : "No";
|
||||||
|
}
|
||||||
|
|
||||||
|
// When the value is an object or array, or array of objects, we need to handle some special cases.
|
||||||
|
if (typeof value === "object") {
|
||||||
|
if (key === "secretAWSTag") {
|
||||||
|
return (value as MetadataValue<"secretAWSTag">)!.map(({ key: tagKey, value: tagValue }) => (
|
||||||
|
<p key={tagKey} className="text-sm text-gray-200">
|
||||||
|
{tagKey}={tagValue}
|
||||||
|
</p>
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key === "githubVisibilityRepoIds") {
|
||||||
|
return (value as string[]).join(", ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value === "string") {
|
||||||
|
return value.length ? value : "N/A";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value === "number") {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!integration.metadata || Object.keys(integration.metadata).length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-nested-ternary
|
||||||
|
return (
|
||||||
|
<div className="w-full rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||||
|
<div className="mb-4 flex items-center justify-between border-b border-mineshaft-400 pb-4">
|
||||||
|
<p className="text-lg font-semibold text-gray-200">Integration Settings</p>
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
{integration.metadata &&
|
||||||
|
Object.entries(integration.metadata).map(([key, value]) => (
|
||||||
|
<div key={key} className="flex flex-col">
|
||||||
|
<p className="text-sm text-gray-400">
|
||||||
|
{!!value && metadataMappings[key as keyof typeof metadataMappings]}
|
||||||
|
</p>
|
||||||
|
<p className="text-sm text-gray-200">{renderValue(key as MetadataKey, value)}</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
+90
@@ -0,0 +1,90 @@
|
|||||||
|
import { FormLabel, Spinner } from "@app/components/v2";
|
||||||
|
import { useGetIntegrationAuthOctopusDeployScopeValues } from "@app/hooks/api/integrationAuth/queries";
|
||||||
|
import {
|
||||||
|
OctopusDeployScope,
|
||||||
|
TOctopusDeployVariableSetScopeValues
|
||||||
|
} from "@app/hooks/api/integrationAuth/types";
|
||||||
|
import { TIntegration, TOctopusDeployScopeValues } from "@app/hooks/api/integrations/types";
|
||||||
|
|
||||||
|
type OctopusDeployScopeValuesProps = {
|
||||||
|
integration: TIntegration;
|
||||||
|
};
|
||||||
|
|
||||||
|
// remove plural since Octopus Deploy can decide whether they want to use singular or plural...
|
||||||
|
const modifyKey = (key: keyof TOctopusDeployVariableSetScopeValues) => {
|
||||||
|
switch (key) {
|
||||||
|
case "Processes":
|
||||||
|
return "ProcessOwner";
|
||||||
|
default:
|
||||||
|
return key.substring(0, key.length - 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const OctopusDeployScopeValues = ({ integration }: OctopusDeployScopeValuesProps) => {
|
||||||
|
const hasScopeValues = Boolean(
|
||||||
|
Object.values(integration.metadata?.octopusDeployScopeValues ?? {}).some(
|
||||||
|
(values) => values.length > 0
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const { data: scopeValues = {}, isPending } = useGetIntegrationAuthOctopusDeployScopeValues(
|
||||||
|
{
|
||||||
|
scope: OctopusDeployScope.Project,
|
||||||
|
spaceId: integration.targetEnvironmentId!,
|
||||||
|
resourceId: integration.appId!,
|
||||||
|
integrationAuthId: integration.integrationAuthId
|
||||||
|
},
|
||||||
|
{
|
||||||
|
enabled: hasScopeValues
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!integration.metadata?.octopusDeployScopeValues)
|
||||||
|
return <span className="text-sm text-mineshaft-400">Not Configured</span>;
|
||||||
|
|
||||||
|
if (isPending) return <Spinner size="sm" className="ml-2 mt-2" />;
|
||||||
|
|
||||||
|
const scopeValuesMap = new Map(
|
||||||
|
Object.entries(scopeValues).map(([key, values]) => [
|
||||||
|
modifyKey(key as keyof TOctopusDeployVariableSetScopeValues),
|
||||||
|
new Map((values as { Name: string; Id: string }[]).map((value) => [value.Id, value.Name]))
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{Object.entries(integration.metadata.octopusDeployScopeValues).map(([key, values]) => {
|
||||||
|
if (!values.length) return null;
|
||||||
|
|
||||||
|
const getLabel = (scope: string) => {
|
||||||
|
switch (scope as keyof TOctopusDeployScopeValues) {
|
||||||
|
case "Role":
|
||||||
|
return "Target Tags";
|
||||||
|
case "Machine":
|
||||||
|
return "Targets";
|
||||||
|
case "ProcessOwner":
|
||||||
|
return "Processes";
|
||||||
|
case "Action":
|
||||||
|
return "Steps";
|
||||||
|
default:
|
||||||
|
return `${scope}s`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mt-4" key={key}>
|
||||||
|
<FormLabel className="text-sm font-semibold text-mineshaft-200" label={getLabel(key)} />
|
||||||
|
|
||||||
|
<div className="text-sm text-mineshaft-300">
|
||||||
|
{values
|
||||||
|
.map((value) => scopeValuesMap.get(key)?.get(value))
|
||||||
|
.map((name) => (
|
||||||
|
<p key={name}>{name}</p>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
|
import { IntegrationDetailsByIDPage } from "./IntegrationsDetailsByIDPage";
|
||||||
|
|
||||||
|
export const Route = createFileRoute(
|
||||||
|
"/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/$integrationId"
|
||||||
|
)({
|
||||||
|
component: IntegrationDetailsByIDPage
|
||||||
|
});
|
||||||
@@ -0,0 +1,234 @@
|
|||||||
|
import { useCallback, useEffect, useState } from "react";
|
||||||
|
import { Helmet } from "react-helmet";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { motion } from "framer-motion";
|
||||||
|
|
||||||
|
import { createNotification } from "@app/components/notifications";
|
||||||
|
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||||
|
import { ContentLoader } from "@app/components/v2";
|
||||||
|
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
|
||||||
|
import {
|
||||||
|
useDeleteIntegration,
|
||||||
|
useDeleteIntegrationAuths,
|
||||||
|
useGetCloudIntegrations,
|
||||||
|
useGetWorkspaceAuthorizations,
|
||||||
|
useGetWorkspaceIntegrations
|
||||||
|
} from "@app/hooks/api";
|
||||||
|
import { IntegrationAuth } from "@app/hooks/api/types";
|
||||||
|
|
||||||
|
import { CloudIntegrationSection } from "./components/CloudIntegrationSection";
|
||||||
|
import { FrameworkIntegrationSection } from "./components/FrameworkIntegrationSection";
|
||||||
|
import { InfrastructureIntegrationSection } from "./components/InfrastructureIntegrationSection/InfrastructureIntegrationSection";
|
||||||
|
import { IntegrationsSection } from "./components/IntegrationsSection";
|
||||||
|
import { redirectForProviderAuth } from "./IntegrationsListPage.utils";
|
||||||
|
|
||||||
|
enum IntegrationView {
|
||||||
|
List = "list",
|
||||||
|
New = "new"
|
||||||
|
}
|
||||||
|
|
||||||
|
const Page = () => {
|
||||||
|
const { currentWorkspace } = useWorkspace();
|
||||||
|
const { environments, id: workspaceId } = currentWorkspace;
|
||||||
|
const [view, setView] = useState<IntegrationView>(IntegrationView.New);
|
||||||
|
|
||||||
|
const { data: cloudIntegrations, isPending: isCloudIntegrationsLoading } =
|
||||||
|
useGetCloudIntegrations();
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: integrationAuths,
|
||||||
|
isPending: isIntegrationAuthLoading,
|
||||||
|
isFetching: isIntegrationAuthFetching
|
||||||
|
} = useGetWorkspaceAuthorizations(
|
||||||
|
workspaceId,
|
||||||
|
useCallback((data: IntegrationAuth[]) => {
|
||||||
|
const groupBy: Record<string, IntegrationAuth> = {};
|
||||||
|
data.forEach((el) => {
|
||||||
|
groupBy[el.integration] = el;
|
||||||
|
});
|
||||||
|
return groupBy;
|
||||||
|
}, [])
|
||||||
|
);
|
||||||
|
|
||||||
|
// mutation
|
||||||
|
const {
|
||||||
|
data: integrations,
|
||||||
|
isPending: isIntegrationLoading,
|
||||||
|
isFetching: isIntegrationFetching,
|
||||||
|
isFetched: isIntegrationsFetched
|
||||||
|
} = useGetWorkspaceIntegrations(workspaceId);
|
||||||
|
|
||||||
|
const { mutateAsync: deleteIntegration } = useDeleteIntegration();
|
||||||
|
const {
|
||||||
|
mutateAsync: deleteIntegrationAuths,
|
||||||
|
isSuccess: isDeleteIntegrationAuthSuccess,
|
||||||
|
reset: resetDeleteIntegrationAuths
|
||||||
|
} = useDeleteIntegrationAuths();
|
||||||
|
|
||||||
|
const isIntegrationsAuthorizedEmpty = !Object.keys(integrationAuths || {}).length;
|
||||||
|
const isIntegrationsEmpty = !integrations?.length;
|
||||||
|
// summary: this use effect is trigger when all integration auths are removed thus deactivate bot
|
||||||
|
// details: so on successfully deleting an integration auth, immediately integration list is refeteched
|
||||||
|
// After the refetch is completed check if its empty. Then set bot active and reset the submit hook for isSuccess to go back to false
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
isDeleteIntegrationAuthSuccess &&
|
||||||
|
!isIntegrationFetching &&
|
||||||
|
!isIntegrationAuthFetching &&
|
||||||
|
isIntegrationsAuthorizedEmpty &&
|
||||||
|
isIntegrationsEmpty
|
||||||
|
) {
|
||||||
|
resetDeleteIntegrationAuths();
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
isIntegrationFetching,
|
||||||
|
isDeleteIntegrationAuthSuccess,
|
||||||
|
isIntegrationAuthFetching,
|
||||||
|
isIntegrationsAuthorizedEmpty,
|
||||||
|
isIntegrationsEmpty
|
||||||
|
]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setView(integrations?.length ? IntegrationView.List : IntegrationView.New);
|
||||||
|
}, [isIntegrationsFetched]);
|
||||||
|
|
||||||
|
const handleProviderIntegration = async (provider: string) => {
|
||||||
|
const selectedCloudIntegration = cloudIntegrations?.find(({ slug }) => provider === slug);
|
||||||
|
if (!selectedCloudIntegration) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
redirectForProviderAuth(selectedCloudIntegration);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// function to strat integration for a provider
|
||||||
|
// confirmation to user passing the bot key for provider to get secret access
|
||||||
|
const handleProviderIntegrationStart = (provider: string) => {
|
||||||
|
handleProviderIntegration(provider);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleIntegrationDelete = async (
|
||||||
|
integrationId: string,
|
||||||
|
shouldDeleteIntegrationSecrets: boolean,
|
||||||
|
cb: () => void
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
await deleteIntegration({ id: integrationId, workspaceId, shouldDeleteIntegrationSecrets });
|
||||||
|
if (cb) cb();
|
||||||
|
createNotification({
|
||||||
|
type: "success",
|
||||||
|
text: "Deleted integration"
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
createNotification({
|
||||||
|
type: "error",
|
||||||
|
text: "Failed to delete integration"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleIntegrationAuthRevoke = async (provider: string, cb?: () => void) => {
|
||||||
|
const integrationAuthForProvider = integrationAuths?.[provider];
|
||||||
|
if (!integrationAuthForProvider) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await deleteIntegrationAuths({
|
||||||
|
integration: provider,
|
||||||
|
workspaceId
|
||||||
|
});
|
||||||
|
if (cb) cb();
|
||||||
|
createNotification({
|
||||||
|
type: "success",
|
||||||
|
text: "Revoked provider authentication"
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
createNotification({
|
||||||
|
type: "error",
|
||||||
|
text: "Failed to revoke provider authentication"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isIntegrationLoading || isCloudIntegrationsLoading)
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col items-center gap-2">
|
||||||
|
<ContentLoader text={["Loading integrations..."]} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="container relative mx-auto max-w-7xl pb-12 text-white">
|
||||||
|
<div className="relative">
|
||||||
|
{view === IntegrationView.List ? (
|
||||||
|
<motion.div
|
||||||
|
key="view-integrations"
|
||||||
|
transition={{ duration: 0.3 }}
|
||||||
|
initial={{ opacity: 0, translateX: 30 }}
|
||||||
|
animate={{ opacity: 1, translateX: 0 }}
|
||||||
|
exit={{ opacity: 0, translateX: 30 }}
|
||||||
|
className="w-full"
|
||||||
|
>
|
||||||
|
<IntegrationsSection
|
||||||
|
cloudIntegrations={cloudIntegrations}
|
||||||
|
onAddIntegration={() => setView(IntegrationView.New)}
|
||||||
|
isLoading={isIntegrationLoading}
|
||||||
|
integrations={integrations}
|
||||||
|
environments={environments}
|
||||||
|
onIntegrationDelete={handleIntegrationDelete}
|
||||||
|
workspaceId={workspaceId}
|
||||||
|
/>
|
||||||
|
</motion.div>
|
||||||
|
) : (
|
||||||
|
<motion.div
|
||||||
|
key="add-integration"
|
||||||
|
transition={{ duration: 0.3 }}
|
||||||
|
initial={{ opacity: 0, translateX: 30 }}
|
||||||
|
animate={{ opacity: 1, translateX: 0 }}
|
||||||
|
exit={{ opacity: 0, translateX: 30 }}
|
||||||
|
className="w-full"
|
||||||
|
>
|
||||||
|
<CloudIntegrationSection
|
||||||
|
onViewActiveIntegrations={
|
||||||
|
integrations?.length ? () => setView(IntegrationView.List) : undefined
|
||||||
|
}
|
||||||
|
isLoading={isCloudIntegrationsLoading || isIntegrationAuthLoading}
|
||||||
|
cloudIntegrations={cloudIntegrations}
|
||||||
|
integrationAuths={integrationAuths}
|
||||||
|
onIntegrationStart={handleProviderIntegrationStart}
|
||||||
|
onIntegrationRevoke={handleIntegrationAuthRevoke}
|
||||||
|
/>
|
||||||
|
<FrameworkIntegrationSection />
|
||||||
|
<InfrastructureIntegrationSection />
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const IntegrationsListPage = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Helmet>
|
||||||
|
<title>{t("common.head-title", { title: t("integrations.title") })}</title>
|
||||||
|
<meta property="og:image" content="/images/message.png" />
|
||||||
|
<meta property="og:title" content="Manage your .env files in seconds" />
|
||||||
|
<meta name="og:description" content={t("integrations.description") as string} />
|
||||||
|
</Helmet>
|
||||||
|
<ProjectPermissionCan
|
||||||
|
renderGuardBanner
|
||||||
|
passThrough={false}
|
||||||
|
I={ProjectPermissionActions.Read}
|
||||||
|
a={ProjectPermissionSub.Integrations}
|
||||||
|
>
|
||||||
|
<Page />
|
||||||
|
</ProjectPermissionCan>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
+205
@@ -0,0 +1,205 @@
|
|||||||
|
import crypto from "crypto";
|
||||||
|
|
||||||
|
import { createNotification } from "@app/components/notifications";
|
||||||
|
import {
|
||||||
|
decryptAssymmetric,
|
||||||
|
encryptAssymmetric
|
||||||
|
} from "@app/components/utilities/cryptography/crypto";
|
||||||
|
import { TCloudIntegration, UserWsKeyPair } from "@app/hooks/api/types";
|
||||||
|
|
||||||
|
export const generateBotKey = (botPublicKey: string, latestKey: UserWsKeyPair) => {
|
||||||
|
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY");
|
||||||
|
|
||||||
|
if (!PRIVATE_KEY) {
|
||||||
|
throw new Error("Private Key missing");
|
||||||
|
}
|
||||||
|
|
||||||
|
const WORKSPACE_KEY = decryptAssymmetric({
|
||||||
|
ciphertext: latestKey.encryptedKey,
|
||||||
|
nonce: latestKey.nonce,
|
||||||
|
publicKey: latestKey.sender.publicKey,
|
||||||
|
privateKey: PRIVATE_KEY
|
||||||
|
});
|
||||||
|
|
||||||
|
const { ciphertext, nonce } = encryptAssymmetric({
|
||||||
|
plaintext: WORKSPACE_KEY,
|
||||||
|
publicKey: botPublicKey,
|
||||||
|
privateKey: PRIVATE_KEY
|
||||||
|
});
|
||||||
|
|
||||||
|
return { encryptedKey: ciphertext, nonce };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createIntegrationMissingEnvVarsNotification = (
|
||||||
|
slug: string,
|
||||||
|
type: "cloud" | "cicd" = "cloud",
|
||||||
|
hashtag?: string
|
||||||
|
) =>
|
||||||
|
createNotification({
|
||||||
|
type: "error",
|
||||||
|
text: (
|
||||||
|
<a
|
||||||
|
href={`https://infisical.com/docs/integrations/${type}/${slug}${
|
||||||
|
hashtag ? `#${hashtag}` : ""
|
||||||
|
}`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="underline"
|
||||||
|
>
|
||||||
|
Click here to view docs
|
||||||
|
</a>
|
||||||
|
),
|
||||||
|
title: "Missing Environment Variables"
|
||||||
|
});
|
||||||
|
|
||||||
|
export const redirectForProviderAuth = (integrationOption: TCloudIntegration) => {
|
||||||
|
try {
|
||||||
|
// generate CSRF token for OAuth2 code-token exchange integrations
|
||||||
|
const state = crypto.randomBytes(16).toString("hex");
|
||||||
|
localStorage.setItem("latestCSRFToken", state);
|
||||||
|
|
||||||
|
let link = "";
|
||||||
|
switch (integrationOption.slug) {
|
||||||
|
case "gcp-secret-manager":
|
||||||
|
link = `${window.location.origin}/integrations/gcp-secret-manager/authorize`;
|
||||||
|
break;
|
||||||
|
case "azure-key-vault":
|
||||||
|
if (!integrationOption.clientId) {
|
||||||
|
createIntegrationMissingEnvVarsNotification(integrationOption.slug);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
link = `https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=${integrationOption.clientId}&response_type=code&redirect_uri=${window.location.origin}/integrations/azure-key-vault/oauth2/callback&response_mode=query&scope=https://vault.azure.net/.default openid offline_access&state=${state}`;
|
||||||
|
break;
|
||||||
|
case "azure-app-configuration":
|
||||||
|
if (!integrationOption.clientId) {
|
||||||
|
createIntegrationMissingEnvVarsNotification(integrationOption.slug);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
link = `https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=${integrationOption.clientId}&response_type=code&redirect_uri=${window.location.origin}/integrations/azure-app-configuration/oauth2/callback&response_mode=query&scope=https://azconfig.io/.default openid offline_access&state=${state}`;
|
||||||
|
break;
|
||||||
|
case "aws-parameter-store":
|
||||||
|
link = `${window.location.origin}/integrations/aws-parameter-store/authorize`;
|
||||||
|
break;
|
||||||
|
case "aws-secret-manager":
|
||||||
|
link = `${window.location.origin}/integrations/aws-secret-manager/authorize`;
|
||||||
|
break;
|
||||||
|
case "heroku":
|
||||||
|
if (!integrationOption.clientId) {
|
||||||
|
createIntegrationMissingEnvVarsNotification(integrationOption.slug);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
link = `https://id.heroku.com/oauth/authorize?client_id=${integrationOption.clientId}&response_type=code&scope=write-protected&state=${state}`;
|
||||||
|
break;
|
||||||
|
case "vercel":
|
||||||
|
if (!integrationOption.clientSlug) {
|
||||||
|
createIntegrationMissingEnvVarsNotification(integrationOption.slug);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
link = `https://vercel.com/integrations/${integrationOption.clientSlug}/new?state=${state}`;
|
||||||
|
break;
|
||||||
|
case "netlify":
|
||||||
|
if (!integrationOption.clientId) {
|
||||||
|
createIntegrationMissingEnvVarsNotification(integrationOption.slug);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
link = `https://app.netlify.com/authorize?client_id=${integrationOption.clientId}&response_type=code&state=${state}&redirect_uri=${window.location.origin}/integrations/netlify/oauth2/callback`;
|
||||||
|
break;
|
||||||
|
case "github":
|
||||||
|
link = `${window.location.origin}/integrations/github/auth-mode-selection`;
|
||||||
|
break;
|
||||||
|
case "gitlab":
|
||||||
|
link = `${window.location.origin}/integrations/gitlab/authorize`;
|
||||||
|
break;
|
||||||
|
case "render":
|
||||||
|
link = `${window.location.origin}/integrations/render/authorize`;
|
||||||
|
break;
|
||||||
|
case "flyio":
|
||||||
|
link = `${window.location.origin}/integrations/flyio/authorize`;
|
||||||
|
break;
|
||||||
|
case "circleci":
|
||||||
|
link = `${window.location.origin}/integrations/circleci/authorize`;
|
||||||
|
break;
|
||||||
|
case "databricks":
|
||||||
|
link = `${window.location.origin}/integrations/databricks/authorize`;
|
||||||
|
break;
|
||||||
|
case "laravel-forge":
|
||||||
|
link = `${window.location.origin}/integrations/laravel-forge/authorize`;
|
||||||
|
break;
|
||||||
|
case "travisci":
|
||||||
|
link = `${window.location.origin}/integrations/travisci/authorize`;
|
||||||
|
break;
|
||||||
|
case "supabase":
|
||||||
|
link = `${window.location.origin}/integrations/supabase/authorize`;
|
||||||
|
break;
|
||||||
|
case "checkly":
|
||||||
|
link = `${window.location.origin}/integrations/checkly/authorize`;
|
||||||
|
break;
|
||||||
|
case "qovery":
|
||||||
|
link = `${window.location.origin}/integrations/qovery/authorize`;
|
||||||
|
break;
|
||||||
|
case "railway":
|
||||||
|
link = `${window.location.origin}/integrations/railway/authorize`;
|
||||||
|
break;
|
||||||
|
case "terraform-cloud":
|
||||||
|
link = `${window.location.origin}/integrations/terraform-cloud/authorize`;
|
||||||
|
break;
|
||||||
|
case "hashicorp-vault":
|
||||||
|
link = `${window.location.origin}/integrations/hashicorp-vault/authorize`;
|
||||||
|
break;
|
||||||
|
case "cloudflare-pages":
|
||||||
|
link = `${window.location.origin}/integrations/cloudflare-pages/authorize`;
|
||||||
|
break;
|
||||||
|
case "cloudflare-workers":
|
||||||
|
link = `${window.location.origin}/integrations/cloudflare-workers/authorize`;
|
||||||
|
break;
|
||||||
|
case "bitbucket":
|
||||||
|
if (!integrationOption.clientId) {
|
||||||
|
createIntegrationMissingEnvVarsNotification(integrationOption.slug, "cicd");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
link = `https://bitbucket.org/site/oauth2/authorize?client_id=${integrationOption.clientId}&response_type=code&redirect_uri=${window.location.origin}/integrations/bitbucket/oauth2/callback&state=${state}`;
|
||||||
|
break;
|
||||||
|
case "codefresh":
|
||||||
|
link = `${window.location.origin}/integrations/codefresh/authorize`;
|
||||||
|
break;
|
||||||
|
case "digital-ocean-app-platform":
|
||||||
|
link = `${window.location.origin}/integrations/digital-ocean-app-platform/authorize`;
|
||||||
|
break;
|
||||||
|
case "cloud-66":
|
||||||
|
link = `${window.location.origin}/integrations/cloud-66/authorize`;
|
||||||
|
break;
|
||||||
|
case "northflank":
|
||||||
|
link = `${window.location.origin}/integrations/northflank/authorize`;
|
||||||
|
break;
|
||||||
|
case "windmill":
|
||||||
|
link = `${window.location.origin}/integrations/windmill/authorize`;
|
||||||
|
break;
|
||||||
|
case "teamcity":
|
||||||
|
link = `${window.location.origin}/integrations/teamcity/authorize`;
|
||||||
|
break;
|
||||||
|
case "hasura-cloud":
|
||||||
|
link = `${window.location.origin}/integrations/hasura-cloud/authorize`;
|
||||||
|
break;
|
||||||
|
case "rundeck":
|
||||||
|
link = `${window.location.origin}/integrations/rundeck/authorize`;
|
||||||
|
break;
|
||||||
|
case "azure-devops":
|
||||||
|
link = `${window.location.origin}/integrations/azure-devops/authorize`;
|
||||||
|
break;
|
||||||
|
case "octopus-deploy":
|
||||||
|
link = `${window.location.origin}/integrations/octopus-deploy/authorize`;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (link !== "") {
|
||||||
|
window.location.assign(link);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const redirectToIntegrationAppConfigScreen = (provider: string, integrationAuthId: string) =>
|
||||||
|
`/integrations/${provider}/create?integrationAuthId=${integrationAuthId}`;
|
||||||
+216
@@ -0,0 +1,216 @@
|
|||||||
|
import { useMemo, useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import {
|
||||||
|
faCheck,
|
||||||
|
faChevronLeft,
|
||||||
|
faMagnifyingGlass,
|
||||||
|
faSearch,
|
||||||
|
faXmark
|
||||||
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
|
import { NoEnvironmentsBanner } from "@app/components/integrations/NoEnvironmentsBanner";
|
||||||
|
import { createNotification } from "@app/components/notifications";
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
DeleteActionModal,
|
||||||
|
EmptyState,
|
||||||
|
Input,
|
||||||
|
Skeleton,
|
||||||
|
Tooltip
|
||||||
|
} from "@app/components/v2";
|
||||||
|
import {
|
||||||
|
ProjectPermissionActions,
|
||||||
|
ProjectPermissionSub,
|
||||||
|
useProjectPermission,
|
||||||
|
useWorkspace
|
||||||
|
} from "@app/context";
|
||||||
|
import { usePopUp } from "@app/hooks";
|
||||||
|
import { IntegrationAuth, TCloudIntegration } from "@app/hooks/api/types";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
isLoading?: boolean;
|
||||||
|
integrationAuths?: Record<string, IntegrationAuth>;
|
||||||
|
cloudIntegrations?: TCloudIntegration[];
|
||||||
|
onIntegrationStart: (slug: string) => void;
|
||||||
|
// cb: handle popUpClose child->parent communication pattern
|
||||||
|
onIntegrationRevoke: (slug: string, cb: () => void) => void;
|
||||||
|
onViewActiveIntegrations?: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
type TRevokeIntegrationPopUp = { provider: string };
|
||||||
|
|
||||||
|
export const CloudIntegrationSection = ({
|
||||||
|
isLoading,
|
||||||
|
cloudIntegrations = [],
|
||||||
|
integrationAuths = {},
|
||||||
|
onIntegrationStart,
|
||||||
|
onIntegrationRevoke,
|
||||||
|
onViewActiveIntegrations
|
||||||
|
}: Props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
|
||||||
|
"deleteConfirmation"
|
||||||
|
] as const);
|
||||||
|
const { permission } = useProjectPermission();
|
||||||
|
const { currentWorkspace } = useWorkspace();
|
||||||
|
|
||||||
|
const isEmpty = !isLoading && !cloudIntegrations?.length;
|
||||||
|
|
||||||
|
const sortedCloudIntegrations = useMemo(() => {
|
||||||
|
const sortedIntegrations = cloudIntegrations.sort((a, b) => a.name.localeCompare(b.name));
|
||||||
|
|
||||||
|
if (currentWorkspace?.environments.length === 0) {
|
||||||
|
return sortedIntegrations.map((integration) => ({ ...integration, isAvailable: false }));
|
||||||
|
}
|
||||||
|
|
||||||
|
return sortedIntegrations;
|
||||||
|
}, [cloudIntegrations, currentWorkspace?.environments]);
|
||||||
|
|
||||||
|
const [search, setSearch] = useState("");
|
||||||
|
|
||||||
|
const filteredIntegrations = sortedCloudIntegrations?.filter((cloudIntegration) =>
|
||||||
|
cloudIntegration.name.toLowerCase().includes(search.toLowerCase().trim())
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="px-5">
|
||||||
|
{currentWorkspace?.environments.length === 0 && (
|
||||||
|
<NoEnvironmentsBanner projectId={currentWorkspace.id} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="m-4 mt-7 flex flex-col items-start justify-between px-2 text-xl">
|
||||||
|
{onViewActiveIntegrations && (
|
||||||
|
<Button
|
||||||
|
variant="link"
|
||||||
|
onClick={onViewActiveIntegrations}
|
||||||
|
leftIcon={<FontAwesomeIcon icon={faChevronLeft} />}
|
||||||
|
>
|
||||||
|
Back to Integrations
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<div className="flex w-full flex-col justify-between gap-4 whitespace-nowrap lg:flex-row lg:items-end lg:gap-8">
|
||||||
|
<div className="flex-1">
|
||||||
|
<h1 className="text-3xl font-semibold">{t("integrations.cloud-integrations")}</h1>
|
||||||
|
<p className="text-base text-gray-400">{t("integrations.click-to-start")}</p>
|
||||||
|
</div>
|
||||||
|
<Input
|
||||||
|
value={search}
|
||||||
|
onChange={(e) => setSearch(e.target.value)}
|
||||||
|
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
|
||||||
|
placeholder="Search cloud integrations..."
|
||||||
|
containerClassName="flex-1 h-min text-base"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mx-6 grid grid-cols-3 gap-4 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 2xl:grid-cols-7">
|
||||||
|
{isLoading &&
|
||||||
|
Array.from({ length: 12 }).map((_, index) => (
|
||||||
|
<Skeleton className="h-32" key={`cloud-integration-skeleton-${index + 1}`} />
|
||||||
|
))}
|
||||||
|
|
||||||
|
{!isLoading && filteredIntegrations.length ? (
|
||||||
|
filteredIntegrations.map((cloudIntegration) => (
|
||||||
|
<div
|
||||||
|
onKeyDown={() => null}
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
|
className={`group relative ${
|
||||||
|
cloudIntegration.isAvailable
|
||||||
|
? "cursor-pointer duration-200 hover:bg-mineshaft-700"
|
||||||
|
: "opacity-50"
|
||||||
|
} flex h-32 flex-col items-center justify-center rounded-md border border-mineshaft-600 bg-mineshaft-800 p-4`}
|
||||||
|
onClick={() => {
|
||||||
|
if (!cloudIntegration.isAvailable) return;
|
||||||
|
if (
|
||||||
|
permission.cannot(
|
||||||
|
ProjectPermissionActions.Create,
|
||||||
|
ProjectPermissionSub.Integrations
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
createNotification({
|
||||||
|
type: "error",
|
||||||
|
text: "You do not have permission to create an integration"
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
onIntegrationStart(cloudIntegration.slug);
|
||||||
|
}}
|
||||||
|
key={cloudIntegration.slug}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={`/images/integrations/${cloudIntegration.image}`}
|
||||||
|
height={60}
|
||||||
|
width={60}
|
||||||
|
className="mt-auto"
|
||||||
|
alt="integration logo"
|
||||||
|
/>
|
||||||
|
<div className="mt-auto max-w-xs text-center text-sm font-semibold text-gray-300 duration-200 group-hover:text-gray-200">
|
||||||
|
{cloudIntegration.name}
|
||||||
|
</div>
|
||||||
|
{cloudIntegration.isAvailable &&
|
||||||
|
Boolean(integrationAuths?.[cloudIntegration.slug]) && (
|
||||||
|
<div className="absolute right-0 top-0 z-30 h-full">
|
||||||
|
<div className="relative h-full">
|
||||||
|
<div className="absolute right-0 top-0 w-24 flex-row items-center overflow-hidden whitespace-nowrap rounded-bl-md rounded-tr-md bg-primary px-2 py-0.5 text-xs text-black opacity-80 transition-all duration-300 group-hover:w-0 group-hover:p-0">
|
||||||
|
<FontAwesomeIcon icon={faCheck} className="mr-2 text-xs" />
|
||||||
|
Authorized
|
||||||
|
</div>
|
||||||
|
<Tooltip content="Revoke Access">
|
||||||
|
<div
|
||||||
|
onKeyDown={() => null}
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
|
onClick={async (event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
handlePopUpOpen("deleteConfirmation", {
|
||||||
|
provider: cloudIntegration.slug
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
className="absolute right-0 top-0 flex h-0 w-12 cursor-pointer items-center justify-center overflow-hidden rounded-r-md bg-red text-xs opacity-50 transition-all duration-300 hover:opacity-100 group-hover:h-full"
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faXmark} size="xl" />
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<EmptyState
|
||||||
|
className="col-span-full h-32 w-full rounded-md bg-transparent pt-14"
|
||||||
|
title="No cloud integrations match search..."
|
||||||
|
icon={faSearch}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{isEmpty && (
|
||||||
|
<div className="mx-6 grid max-w-5xl grid-cols-4 grid-rows-2 gap-4">
|
||||||
|
{Array.from({ length: 16 }).map((_, index) => (
|
||||||
|
<div
|
||||||
|
key={`dummy-cloud-integration-${index + 1}`}
|
||||||
|
className="h-32 animate-pulse rounded-md border border-mineshaft-600 bg-mineshaft-800"
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<DeleteActionModal
|
||||||
|
isOpen={popUp.deleteConfirmation.isOpen}
|
||||||
|
title={`Are you sure want to revoke access ${
|
||||||
|
(popUp?.deleteConfirmation.data as TRevokeIntegrationPopUp)?.provider || " "
|
||||||
|
}?`}
|
||||||
|
subTitle="This will remove all the secret integration of this provider!!!"
|
||||||
|
onChange={(isOpen) => handlePopUpToggle("deleteConfirmation", isOpen)}
|
||||||
|
deleteKey={(popUp?.deleteConfirmation?.data as TRevokeIntegrationPopUp)?.provider || ""}
|
||||||
|
onDeleteApproved={async () => {
|
||||||
|
onIntegrationRevoke(
|
||||||
|
(popUp.deleteConfirmation.data as TRevokeIntegrationPopUp)?.provider,
|
||||||
|
() => handlePopUpClose("deleteConfirmation")
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export { CloudIntegrationSection } from "./CloudIntegrationSection";
|
||||||
+71
@@ -0,0 +1,71 @@
|
|||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { faKeyboard } from "@fortawesome/free-regular-svg-icons";
|
||||||
|
import { faComputer } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
|
import frameworks from "../json/frameworkIntegrations.json";
|
||||||
|
|
||||||
|
export const FrameworkIntegrationSection = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const sortedFrameworks = frameworks.sort((a, b) => a.name.localeCompare(b.name));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="mx-4 mb-4 mt-12 flex flex-col items-start justify-between px-2 text-xl">
|
||||||
|
<h1 className="text-3xl font-semibold">{t("integrations.framework-integrations")}</h1>
|
||||||
|
<p className="text-base text-gray-400">{t("integrations.click-to-setup")}</p>
|
||||||
|
</div>
|
||||||
|
<div className="mx-6 mt-4 grid grid-cols-3 gap-4 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 2xl:grid-cols-7">
|
||||||
|
{sortedFrameworks.map((framework) => (
|
||||||
|
<a
|
||||||
|
key={`framework-integration-${framework.slug}`}
|
||||||
|
href={framework.docsLink}
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
target="_blank"
|
||||||
|
className="relative flex h-32 cursor-pointer flex-col items-center justify-center rounded-md border border-mineshaft-600 bg-mineshaft-800 p-4 duration-200 hover:bg-mineshaft-700"
|
||||||
|
>
|
||||||
|
{framework?.image && (
|
||||||
|
<img
|
||||||
|
src={`/images/integrations/${framework.image}.png`}
|
||||||
|
height={60}
|
||||||
|
width={60}
|
||||||
|
className="mt-auto"
|
||||||
|
alt="integration logo"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{framework?.name && (
|
||||||
|
<div className="mt-auto max-w-xs text-center text-sm font-semibold text-gray-300 duration-200 group-hover:text-gray-200">
|
||||||
|
{framework.name}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</a>
|
||||||
|
))}
|
||||||
|
<a
|
||||||
|
key="framework-integration-more"
|
||||||
|
href="https://infisical.com/docs/cli/commands/run"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
target="_blank"
|
||||||
|
className="relative flex h-32 cursor-pointer flex-col items-center justify-center rounded-md border border-mineshaft-600 bg-mineshaft-800 p-4 duration-200 hover:bg-mineshaft-700"
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon className="mt-auto text-5xl text-white/90" icon={faKeyboard} />
|
||||||
|
<div className="mt-auto max-w-xs text-center text-sm font-semibold text-gray-300 duration-200 group-hover:text-gray-200">
|
||||||
|
CLI
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
key="framework-integration-more"
|
||||||
|
href="https://infisical.com/docs/sdks/overview"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
target="_blank"
|
||||||
|
className="relative flex h-32 cursor-pointer flex-col items-center justify-center rounded-md border border-mineshaft-600 bg-mineshaft-800 p-4 duration-200 hover:bg-mineshaft-700"
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon className="mt-auto text-5xl text-white/90" icon={faComputer} />
|
||||||
|
<div className="mt-auto max-w-xs text-center text-sm font-semibold text-gray-300 duration-200 group-hover:text-gray-200">
|
||||||
|
SDKs
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export { FrameworkIntegrationSection } from "./FrameworkIntegrationSection";
|
||||||
+45
@@ -0,0 +1,45 @@
|
|||||||
|
import integrations from "../json/infrastructureIntegrations.json";
|
||||||
|
|
||||||
|
export const InfrastructureIntegrationSection = () => {
|
||||||
|
const sortedIntegrations = integrations.sort((a, b) => a.name.localeCompare(b.name));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="mx-4 mb-4 mt-12 flex flex-col items-start justify-between px-2 text-xl">
|
||||||
|
<h1 className="text-3xl font-semibold">Infrastructure Integrations</h1>
|
||||||
|
<p className="text-base text-gray-400">
|
||||||
|
Click on of the integration to read the documentation.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="mx-6 grid grid-cols-2 gap-4 lg:grid-cols-3 2xl:grid-cols-4">
|
||||||
|
{sortedIntegrations.map((integration) => (
|
||||||
|
<a
|
||||||
|
key={`framework-integration-${integration.slug}`}
|
||||||
|
href={integration.docsLink}
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
target="_blank"
|
||||||
|
className="relative flex h-32 w-full cursor-pointer flex-row items-center justify-center rounded-md p-0.5 duration-200"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
onKeyDown={() => null}
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
|
className="group relative flex h-32 w-full cursor-pointer flex-row items-center rounded-md border border-mineshaft-600 bg-mineshaft-800 p-4 duration-200 hover:bg-mineshaft-700"
|
||||||
|
key={integration?.name}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={`/images/integrations/${integration.image}.png`}
|
||||||
|
height={integration?.name ? 60 : 90}
|
||||||
|
width={integration?.name ? 60 : 90}
|
||||||
|
alt="integration logo"
|
||||||
|
/>
|
||||||
|
<div className="ml-4 max-w-xs text-xl font-semibold text-gray-300 duration-200 group-hover:text-gray-200">
|
||||||
|
{integration?.name && integration.name}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export { InfrastructureIntegrationSection } from "./InfrastructureIntegrationSection";
|
||||||
+136
@@ -0,0 +1,136 @@
|
|||||||
|
import { faPlus } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
|
import { Button, Checkbox, DeleteActionModal } from "@app/components/v2";
|
||||||
|
import { usePopUp, useToggle } from "@app/hooks";
|
||||||
|
import { TCloudIntegration, TIntegration } from "@app/hooks/api/types";
|
||||||
|
|
||||||
|
import { IntegrationsTable } from "./components";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
environments: Array<{ name: string; slug: string; id: string }>;
|
||||||
|
integrations?: TIntegration[];
|
||||||
|
cloudIntegrations?: TCloudIntegration[];
|
||||||
|
isLoading?: boolean;
|
||||||
|
onIntegrationDelete: (
|
||||||
|
integrationId: string,
|
||||||
|
shouldDeleteIntegrationSecrets: boolean,
|
||||||
|
cb: () => void
|
||||||
|
) => Promise<void>;
|
||||||
|
workspaceId: string;
|
||||||
|
onAddIntegration: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const IntegrationsSection = ({
|
||||||
|
integrations = [],
|
||||||
|
environments = [],
|
||||||
|
isLoading,
|
||||||
|
onIntegrationDelete,
|
||||||
|
workspaceId,
|
||||||
|
onAddIntegration,
|
||||||
|
cloudIntegrations = []
|
||||||
|
}: Props) => {
|
||||||
|
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
|
||||||
|
"deleteConfirmation",
|
||||||
|
"deleteSecretsConfirmation"
|
||||||
|
] as const);
|
||||||
|
|
||||||
|
const [shouldDeleteSecrets, setShouldDeleteSecrets] = useToggle(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mx-6 mb-8">
|
||||||
|
<div className="mb-4 mt-6 flex flex-col items-start justify-between px-2 text-xl">
|
||||||
|
<h1 className="text-3xl font-semibold">Integrations</h1>
|
||||||
|
<p className="text-base text-bunker-300">Manage integrations with third-party services.</p>
|
||||||
|
</div>
|
||||||
|
<div className="w-full rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||||
|
<div className="mb-4 flex items-center justify-between">
|
||||||
|
<p className="text-xl font-semibold text-mineshaft-100">Active Integrations</p>
|
||||||
|
<Button
|
||||||
|
colorSchema="primary"
|
||||||
|
type="submit"
|
||||||
|
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||||
|
onClick={onAddIntegration}
|
||||||
|
>
|
||||||
|
Add Integration
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<IntegrationsTable
|
||||||
|
cloudIntegrations={cloudIntegrations}
|
||||||
|
integrations={integrations}
|
||||||
|
isLoading={isLoading}
|
||||||
|
workspaceId={workspaceId}
|
||||||
|
environments={environments}
|
||||||
|
onDeleteIntegration={(integration) => {
|
||||||
|
setShouldDeleteSecrets.off();
|
||||||
|
handlePopUpOpen("deleteConfirmation", integration);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<DeleteActionModal
|
||||||
|
isOpen={popUp.deleteConfirmation.isOpen}
|
||||||
|
title={`Are you sure want to remove ${
|
||||||
|
(popUp?.deleteConfirmation.data as TIntegration)?.integration || " "
|
||||||
|
} integration for ${
|
||||||
|
(popUp?.deleteConfirmation.data as TIntegration)?.app || "this project"
|
||||||
|
}?`}
|
||||||
|
onChange={(isOpen) => handlePopUpToggle("deleteConfirmation", isOpen)}
|
||||||
|
deleteKey={
|
||||||
|
((popUp?.deleteConfirmation?.data as TIntegration)?.integration ===
|
||||||
|
"azure-app-configuration" &&
|
||||||
|
(popUp?.deleteConfirmation?.data as TIntegration)?.app
|
||||||
|
?.split("//")[1]
|
||||||
|
?.split(".")[0]) ||
|
||||||
|
(popUp?.deleteConfirmation?.data as TIntegration)?.app ||
|
||||||
|
(popUp?.deleteConfirmation?.data as TIntegration)?.owner ||
|
||||||
|
(popUp?.deleteConfirmation?.data as TIntegration)?.path ||
|
||||||
|
(popUp?.deleteConfirmation?.data as TIntegration)?.integration ||
|
||||||
|
""
|
||||||
|
}
|
||||||
|
onDeleteApproved={async () => {
|
||||||
|
if (shouldDeleteSecrets) {
|
||||||
|
handlePopUpOpen("deleteSecretsConfirmation");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await onIntegrationDelete(
|
||||||
|
(popUp?.deleteConfirmation.data as TIntegration).id,
|
||||||
|
false,
|
||||||
|
() => handlePopUpClose("deleteConfirmation")
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{(popUp?.deleteConfirmation?.data as TIntegration)?.integration === "github" && (
|
||||||
|
<div className="mt-4">
|
||||||
|
<Checkbox
|
||||||
|
id="delete-integration-secrets"
|
||||||
|
checkIndicatorBg="text-white"
|
||||||
|
onCheckedChange={() => setShouldDeleteSecrets.toggle()}
|
||||||
|
>
|
||||||
|
Delete previously synced secrets from the destination
|
||||||
|
</Checkbox>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</DeleteActionModal>
|
||||||
|
<DeleteActionModal
|
||||||
|
isOpen={popUp.deleteSecretsConfirmation.isOpen}
|
||||||
|
title={`Are you sure you also want to delete secrets on ${
|
||||||
|
(popUp?.deleteConfirmation.data as TIntegration)?.integration
|
||||||
|
}?`}
|
||||||
|
subTitle="By confirming, you acknowledge that all secrets managed by this integration will be removed from the destination. This action is irreversible."
|
||||||
|
onChange={(isOpen) => handlePopUpToggle("deleteSecretsConfirmation", isOpen)}
|
||||||
|
deleteKey="confirm"
|
||||||
|
onDeleteApproved={async () => {
|
||||||
|
await onIntegrationDelete(
|
||||||
|
(popUp?.deleteConfirmation.data as TIntegration).id,
|
||||||
|
true,
|
||||||
|
() => {
|
||||||
|
handlePopUpClose("deleteSecretsConfirmation");
|
||||||
|
handlePopUpClose("deleteConfirmation");
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
+140
@@ -0,0 +1,140 @@
|
|||||||
|
import { FormLabel } from "@app/components/v2";
|
||||||
|
import { CircleCiScope } from "@app/hooks/api/integrationAuth/types";
|
||||||
|
import { IntegrationMappingBehavior, TIntegration } from "@app/hooks/api/integrations/types";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
integration: TIntegration;
|
||||||
|
};
|
||||||
|
|
||||||
|
const FIELD_CLASSNAME =
|
||||||
|
"truncate rounded-md border border-mineshaft-700 bg-mineshaft-900 px-3 py-2 font-inter text-sm text-bunker-200";
|
||||||
|
|
||||||
|
export const getIntegrationDestination = (integration: TIntegration) =>
|
||||||
|
(integration.integration === "hashicorp-vault" &&
|
||||||
|
`${integration.app} - path: ${integration.path}`) ||
|
||||||
|
(integration.scope === "github-org" && `${integration.owner}`) ||
|
||||||
|
(["aws-parameter-store", "rundeck"].includes(integration.integration) && `${integration.path}`) ||
|
||||||
|
(integration.scope?.startsWith("github-") && `${integration.owner}/${integration.app}`) ||
|
||||||
|
integration.app ||
|
||||||
|
"-";
|
||||||
|
|
||||||
|
export const IntegrationDetails = ({ integration }: Props) => {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-2 p-2">
|
||||||
|
{integration.integration === "octopus-deploy" && (
|
||||||
|
<div>
|
||||||
|
<FormLabel label="Space" />
|
||||||
|
<div className={FIELD_CLASSNAME}>
|
||||||
|
{integration.targetEnvironment || integration.targetEnvironmentId}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{integration.integration === "qovery" && (
|
||||||
|
<>
|
||||||
|
<div>
|
||||||
|
<FormLabel label="Org" />
|
||||||
|
<div className={FIELD_CLASSNAME}>{integration?.owner || "-"}</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<FormLabel label="Project" />
|
||||||
|
<div className={FIELD_CLASSNAME}>{integration?.targetService || "-"}</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<FormLabel label="Env" />
|
||||||
|
<div className={FIELD_CLASSNAME}>{integration?.targetEnvironment || "-"}</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{!(
|
||||||
|
integration.integration === "aws-secret-manager" &&
|
||||||
|
integration.metadata?.mappingBehavior === IntegrationMappingBehavior.ONE_TO_ONE
|
||||||
|
) && (
|
||||||
|
<div>
|
||||||
|
<FormLabel
|
||||||
|
label={
|
||||||
|
(integration.integration === "qovery" && integration?.scope) ||
|
||||||
|
(integration.integration === "circleci" &&
|
||||||
|
(integration.scope === CircleCiScope.Context ? "Context" : "Project")) ||
|
||||||
|
(integration.integration === "bitbucket" && "Repository") ||
|
||||||
|
(integration.integration === "octopus-deploy" && "Project") ||
|
||||||
|
(integration.integration === "aws-secret-manager" && "Secret") ||
|
||||||
|
(["aws-parameter-store", "rundeck"].includes(integration.integration) && "Path") ||
|
||||||
|
(integration?.integration === "terraform-cloud" && "Project") ||
|
||||||
|
(integration?.scope === "github-org" && "Organization") ||
|
||||||
|
(["github-repo", "github-env"].includes(integration?.scope as string) &&
|
||||||
|
"Repository") ||
|
||||||
|
"App"
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<div className={FIELD_CLASSNAME}>{getIntegrationDestination(integration)}</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{(integration.integration === "vercel" ||
|
||||||
|
integration.integration === "netlify" ||
|
||||||
|
integration.integration === "railway" ||
|
||||||
|
integration.integration === "gitlab" ||
|
||||||
|
integration.integration === "teamcity" ||
|
||||||
|
(integration.integration === "github" && integration.scope === "github-env")) && (
|
||||||
|
<div>
|
||||||
|
<FormLabel label="Target Environment" />
|
||||||
|
<div className={FIELD_CLASSNAME}>
|
||||||
|
{integration.targetEnvironment || integration.targetEnvironmentId}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{integration.integration === "bitbucket" && (
|
||||||
|
<>
|
||||||
|
{integration.targetServiceId && (
|
||||||
|
<div>
|
||||||
|
<FormLabel label="Environment" />
|
||||||
|
<div className={FIELD_CLASSNAME}>
|
||||||
|
{integration.targetService || integration.targetServiceId}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div>
|
||||||
|
<FormLabel label="Workspace" />
|
||||||
|
<div className={FIELD_CLASSNAME}>
|
||||||
|
{integration.targetEnvironment || integration.targetEnvironmentId}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{integration.integration === "checkly" && integration.targetService && (
|
||||||
|
<div>
|
||||||
|
<FormLabel label="Group" />
|
||||||
|
<div className={FIELD_CLASSNAME}>{integration.targetService}</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{integration.integration === "circleci" && integration.owner && (
|
||||||
|
<div>
|
||||||
|
<FormLabel label="Organization" />
|
||||||
|
<div className={FIELD_CLASSNAME}>{integration.owner}</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{integration.integration === "terraform-cloud" && integration.targetService && (
|
||||||
|
<div>
|
||||||
|
<FormLabel label="Category" />
|
||||||
|
<div className={FIELD_CLASSNAME}>{integration.targetService}</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{(integration.integration === "checkly" || integration.integration === "github") &&
|
||||||
|
integration?.metadata?.secretSuffix && (
|
||||||
|
<div>
|
||||||
|
<FormLabel label="Secret Suffix" />
|
||||||
|
<div className={FIELD_CLASSNAME}>{integration.metadata.secretSuffix}</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{integration.integration === "github" && integration.metadata?.githubVisibility ? (
|
||||||
|
<div className="mt-2 text-xs text-mineshaft-200">
|
||||||
|
{/* eslint-disable-next-line no-nested-ternary */}
|
||||||
|
{integration.metadata?.githubVisibility === "selected"
|
||||||
|
? "* Syncing to selected repositories in the organization. "
|
||||||
|
: integration.metadata?.githubVisibility === "private"
|
||||||
|
? "* Syncing to all private repositories in the organization"
|
||||||
|
: "* Syncing to all public and private repositories in the organization"}
|
||||||
|
</div>
|
||||||
|
) : undefined}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
+194
@@ -0,0 +1,194 @@
|
|||||||
|
import { useMemo } from "react";
|
||||||
|
import {
|
||||||
|
faCalendarCheck,
|
||||||
|
faCheck,
|
||||||
|
faInfoCircle,
|
||||||
|
faRefresh,
|
||||||
|
faTrash,
|
||||||
|
faWarning,
|
||||||
|
faXmark
|
||||||
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { useNavigate } from "@tanstack/react-router";
|
||||||
|
import { format } from "date-fns";
|
||||||
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
|
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||||
|
import { Badge, IconButton, Td, Tooltip, Tr } from "@app/components/v2";
|
||||||
|
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
|
||||||
|
import { TCloudIntegration } from "@app/hooks/api/integrations/types";
|
||||||
|
import { TIntegration } from "@app/hooks/api/types";
|
||||||
|
|
||||||
|
import { getIntegrationDestination, IntegrationDetails } from "./IntegrationDetails";
|
||||||
|
|
||||||
|
type IProps = {
|
||||||
|
integration: TIntegration;
|
||||||
|
environment?: { name: string; slug: string; id: string };
|
||||||
|
onRemoveIntegration: VoidFunction;
|
||||||
|
onManualSyncIntegration: VoidFunction;
|
||||||
|
cloudIntegration: TCloudIntegration;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const IntegrationRow = ({
|
||||||
|
integration,
|
||||||
|
environment,
|
||||||
|
onRemoveIntegration,
|
||||||
|
onManualSyncIntegration,
|
||||||
|
cloudIntegration
|
||||||
|
}: IProps) => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const { currentWorkspace } = useWorkspace();
|
||||||
|
|
||||||
|
const { id, secretPath, syncMessage, isSynced } = integration;
|
||||||
|
|
||||||
|
const failureMessage = useMemo(() => {
|
||||||
|
if (isSynced === false) {
|
||||||
|
if (syncMessage)
|
||||||
|
try {
|
||||||
|
return JSON.stringify(JSON.parse(syncMessage), null, 2);
|
||||||
|
} catch {
|
||||||
|
return syncMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "An Unknown Error Occurred.";
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}, [isSynced, syncMessage]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tr
|
||||||
|
onClick={() =>
|
||||||
|
navigate({
|
||||||
|
to: "/secret-manager/$projectId/integrations/$integrationId",
|
||||||
|
params: {
|
||||||
|
integrationId: integration.id,
|
||||||
|
projectId: currentWorkspace.id
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
className={twMerge(
|
||||||
|
"group h-10 cursor-pointer transition-colors duration-100 hover:bg-mineshaft-700",
|
||||||
|
isSynced === false && "bg-red/5 hover:bg-red/10"
|
||||||
|
)}
|
||||||
|
key={`integration-${id}`}
|
||||||
|
>
|
||||||
|
<Td>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<img
|
||||||
|
alt={`${cloudIntegration?.name} integration`}
|
||||||
|
src={`/images/integrations/${cloudIntegration?.image}`}
|
||||||
|
className="h-5 w-5"
|
||||||
|
/>
|
||||||
|
<span className="hidden lg:inline">{cloudIntegration?.name}</span>
|
||||||
|
</div>
|
||||||
|
</Td>
|
||||||
|
<Td className="!min-w-[8rem] max-w-0">
|
||||||
|
<Tooltip side="top" className="max-w-2xl break-words" content={secretPath}>
|
||||||
|
<p className="truncate">{secretPath}</p>
|
||||||
|
</Tooltip>{" "}
|
||||||
|
</Td>
|
||||||
|
<Td>{environment?.name ?? "-"}</Td>
|
||||||
|
<Td className="!min-w-[5rem] max-w-0">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<p className="truncate">{getIntegrationDestination(integration)}</p>
|
||||||
|
<Tooltip
|
||||||
|
position="left"
|
||||||
|
className="min-w-[20rem] max-w-lg"
|
||||||
|
content={<IntegrationDetails integration={integration} />}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faInfoCircle} className="text-mineshaft-400" />
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</Td>
|
||||||
|
<Td>
|
||||||
|
{" "}
|
||||||
|
{typeof integration.isSynced !== "boolean" ? (
|
||||||
|
<Badge variant="primary" key={integration.id}>
|
||||||
|
Pending Sync
|
||||||
|
</Badge>
|
||||||
|
) : (
|
||||||
|
<Tooltip
|
||||||
|
position="left"
|
||||||
|
className="max-w-sm"
|
||||||
|
content={
|
||||||
|
<div className="flex flex-col gap-2 py-1">
|
||||||
|
{integration.lastUsed && (
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
className={`mb-2 flex self-start ${!isSynced ? "text-yellow" : "text-green"}`}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={faCalendarCheck}
|
||||||
|
className="ml-1 pr-1.5 pt-0.5 text-sm"
|
||||||
|
/>
|
||||||
|
<div className="text-xs">Last Synced</div>
|
||||||
|
</div>
|
||||||
|
<div className="rounded bg-mineshaft-600 p-2 text-xs">
|
||||||
|
{format(new Date(integration.lastUsed!), "yyyy-MM-dd, hh:mm aaa")}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{failureMessage && (
|
||||||
|
<div>
|
||||||
|
<div className="mb-2 flex self-start text-red">
|
||||||
|
<FontAwesomeIcon icon={faXmark} className="ml-1 pr-1.5 pt-0.5 text-sm" />
|
||||||
|
<div className="text-xs">Failure Reason</div>
|
||||||
|
</div>
|
||||||
|
<div className="rounded bg-mineshaft-600 p-2 text-xs">{failureMessage}</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className="w-min whitespace-nowrap">
|
||||||
|
<Badge variant={integration.isSynced ? "success" : "danger"} key={integration.id}>
|
||||||
|
<div className="flex items-center space-x-1">
|
||||||
|
<FontAwesomeIcon icon={integration.isSynced ? faCheck : faWarning} />
|
||||||
|
<div>{integration.isSynced ? "Synced" : "Not Synced"}</div>
|
||||||
|
</div>
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</Td>
|
||||||
|
<Td>
|
||||||
|
<div className="flex gap-2 whitespace-nowrap">
|
||||||
|
<Tooltip className="max-w-sm text-center" content="Manually Sync">
|
||||||
|
<IconButton
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onManualSyncIntegration();
|
||||||
|
}}
|
||||||
|
ariaLabel="sync"
|
||||||
|
colorSchema="secondary"
|
||||||
|
variant="plain"
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faRefresh} />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
<ProjectPermissionCan
|
||||||
|
I={ProjectPermissionActions.Delete}
|
||||||
|
a={ProjectPermissionSub.Integrations}
|
||||||
|
>
|
||||||
|
{(isAllowed: boolean) => (
|
||||||
|
<Tooltip content="Remove Integration">
|
||||||
|
<IconButton
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onRemoveIntegration();
|
||||||
|
}}
|
||||||
|
ariaLabel="delete"
|
||||||
|
isDisabled={!isAllowed}
|
||||||
|
colorSchema="danger"
|
||||||
|
variant="plain"
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faTrash} className="px-1" />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</ProjectPermissionCan>
|
||||||
|
</div>
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
|
);
|
||||||
|
};
|
||||||
+448
@@ -0,0 +1,448 @@
|
|||||||
|
import { useEffect, useMemo, useState } from "react";
|
||||||
|
import { faCheckCircle } from "@fortawesome/free-regular-svg-icons";
|
||||||
|
import {
|
||||||
|
faArrowDown,
|
||||||
|
faArrowUp,
|
||||||
|
faCheck,
|
||||||
|
faClock,
|
||||||
|
faFilter,
|
||||||
|
faMagnifyingGlass,
|
||||||
|
faPlug,
|
||||||
|
faSearch,
|
||||||
|
faWarning
|
||||||
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuLabel,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
EmptyState,
|
||||||
|
IconButton,
|
||||||
|
Input,
|
||||||
|
Pagination,
|
||||||
|
Table,
|
||||||
|
TableContainer,
|
||||||
|
TBody,
|
||||||
|
Th,
|
||||||
|
THead,
|
||||||
|
Tooltip,
|
||||||
|
Tr
|
||||||
|
} from "@app/components/v2";
|
||||||
|
import { usePagination, useResetPageHelper } from "@app/hooks";
|
||||||
|
import { OrderByDirection } from "@app/hooks/api/generic/types";
|
||||||
|
import { useSyncIntegration } from "@app/hooks/api/integrations/queries";
|
||||||
|
import { TCloudIntegration, TIntegration } from "@app/hooks/api/integrations/types";
|
||||||
|
|
||||||
|
import { getIntegrationDestination } from "./IntegrationDetails";
|
||||||
|
import { IntegrationRow } from "./IntegrationRow";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
integrations?: TIntegration[];
|
||||||
|
cloudIntegrations?: TCloudIntegration[];
|
||||||
|
workspaceId: string;
|
||||||
|
isLoading?: boolean;
|
||||||
|
environments: Array<{ name: string; slug: string; id: string }>;
|
||||||
|
onDeleteIntegration: (integration: TIntegration) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum IntegrationsOrderBy {
|
||||||
|
App = "app",
|
||||||
|
Status = "status",
|
||||||
|
SecretPath = "secretPath",
|
||||||
|
Environment = "environment",
|
||||||
|
Destination = "destination"
|
||||||
|
}
|
||||||
|
|
||||||
|
enum IntegrationStatus {
|
||||||
|
Synced = "synced",
|
||||||
|
NotSynced = "not-synced",
|
||||||
|
PendingSync = "pending-sync"
|
||||||
|
}
|
||||||
|
|
||||||
|
type IntegrationFilters = {
|
||||||
|
environmentIds: string[];
|
||||||
|
integrations: string[];
|
||||||
|
status: IntegrationStatus[];
|
||||||
|
};
|
||||||
|
|
||||||
|
const STATUS_ICON_MAP = {
|
||||||
|
[IntegrationStatus.Synced]: { icon: faCheck, className: "text-green" },
|
||||||
|
[IntegrationStatus.NotSynced]: { icon: faWarning, className: "text-red" },
|
||||||
|
[IntegrationStatus.PendingSync]: { icon: faClock, className: "text-yellow" }
|
||||||
|
};
|
||||||
|
|
||||||
|
export const IntegrationsTable = ({
|
||||||
|
integrations = [],
|
||||||
|
cloudIntegrations = [],
|
||||||
|
workspaceId,
|
||||||
|
environments,
|
||||||
|
onDeleteIntegration,
|
||||||
|
isLoading
|
||||||
|
}: Props) => {
|
||||||
|
const { mutate: syncIntegration } = useSyncIntegration();
|
||||||
|
|
||||||
|
const initialFilters = useMemo(
|
||||||
|
() => ({
|
||||||
|
environmentIds: environments.map((env) => env.id),
|
||||||
|
integrations: [...new Set(integrations.map(({ integration }) => integration))],
|
||||||
|
status: Object.values(IntegrationStatus)
|
||||||
|
}),
|
||||||
|
[environments, integrations]
|
||||||
|
);
|
||||||
|
|
||||||
|
const [filters, setFilters] = useState<IntegrationFilters>(initialFilters);
|
||||||
|
|
||||||
|
const cloudIntegrationMap = useMemo(() => {
|
||||||
|
return new Map(
|
||||||
|
cloudIntegrations.map((cloudIntegration) => [cloudIntegration.slug, cloudIntegration])
|
||||||
|
);
|
||||||
|
}, [cloudIntegrations]);
|
||||||
|
|
||||||
|
const {
|
||||||
|
search,
|
||||||
|
setSearch,
|
||||||
|
setPage,
|
||||||
|
page,
|
||||||
|
perPage,
|
||||||
|
setPerPage,
|
||||||
|
offset,
|
||||||
|
orderDirection,
|
||||||
|
toggleOrderDirection,
|
||||||
|
orderBy,
|
||||||
|
setOrderDirection,
|
||||||
|
setOrderBy
|
||||||
|
} = usePagination<IntegrationsOrderBy>(IntegrationsOrderBy.App, { initPerPage: 20 });
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (integrations?.some((integration) => integration.isSynced === false))
|
||||||
|
setOrderBy(IntegrationsOrderBy.Status);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const environmentMap = new Map(environments.map((env) => [env.id, env]));
|
||||||
|
|
||||||
|
const filteredIntegrations = useMemo(
|
||||||
|
() =>
|
||||||
|
integrations
|
||||||
|
.filter((integration) => {
|
||||||
|
const { secretPath, envId, isSynced } = integration;
|
||||||
|
|
||||||
|
if (!filters.status.includes(IntegrationStatus.Synced) && isSynced) return false;
|
||||||
|
if (!filters.status.includes(IntegrationStatus.NotSynced) && isSynced === false)
|
||||||
|
return false;
|
||||||
|
if (
|
||||||
|
!filters.status.includes(IntegrationStatus.PendingSync) &&
|
||||||
|
typeof isSynced !== "boolean"
|
||||||
|
)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!filters.integrations.includes(integration.integration)) return false;
|
||||||
|
|
||||||
|
if (!filters.environmentIds.includes(envId)) return false;
|
||||||
|
|
||||||
|
return (
|
||||||
|
integration.integration
|
||||||
|
.replace("-", " ")
|
||||||
|
.toLowerCase()
|
||||||
|
.includes(search.trim().toLowerCase()) ||
|
||||||
|
secretPath.replace("-", " ").toLowerCase().includes(search.trim().toLowerCase()) ||
|
||||||
|
getIntegrationDestination(integration)
|
||||||
|
.toLowerCase()
|
||||||
|
.includes(search.trim().toLowerCase()) ||
|
||||||
|
environmentMap
|
||||||
|
.get(envId)
|
||||||
|
?.name.replace("-", " ")
|
||||||
|
.toLowerCase()
|
||||||
|
.includes(search.trim().toLowerCase())
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.sort((a, b) => {
|
||||||
|
const [integrationOne, integrationTwo] =
|
||||||
|
orderDirection === OrderByDirection.ASC ? [a, b] : [b, a];
|
||||||
|
|
||||||
|
switch (orderBy) {
|
||||||
|
case IntegrationsOrderBy.SecretPath:
|
||||||
|
return integrationOne.secretPath
|
||||||
|
.toLowerCase()
|
||||||
|
.localeCompare(integrationTwo.secretPath.toLowerCase());
|
||||||
|
case IntegrationsOrderBy.Environment:
|
||||||
|
return (environmentMap.get(integrationOne.envId)?.name ?? "-")
|
||||||
|
.toLowerCase()
|
||||||
|
.localeCompare(
|
||||||
|
(environmentMap.get(integrationTwo.envId)?.name ?? "-").toLowerCase()
|
||||||
|
);
|
||||||
|
case IntegrationsOrderBy.Destination:
|
||||||
|
return getIntegrationDestination(integrationOne)
|
||||||
|
.toLowerCase()
|
||||||
|
.localeCompare(getIntegrationDestination(integrationTwo).toLowerCase());
|
||||||
|
case IntegrationsOrderBy.Status:
|
||||||
|
if (typeof integrationOne.isSynced !== "boolean") return 1; // Place undefined at the end
|
||||||
|
if (typeof integrationTwo.isSynced !== "boolean") return -1;
|
||||||
|
|
||||||
|
return Number(integrationOne.isSynced) - Number(integrationTwo.isSynced);
|
||||||
|
case IntegrationsOrderBy.App:
|
||||||
|
default:
|
||||||
|
return integrationOne.integration
|
||||||
|
.toLowerCase()
|
||||||
|
.localeCompare(integrationTwo.integration.toLowerCase());
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
[integrations, orderDirection, search, orderBy, filters]
|
||||||
|
);
|
||||||
|
|
||||||
|
useResetPageHelper({
|
||||||
|
totalCount: filteredIntegrations.length,
|
||||||
|
offset,
|
||||||
|
setPage
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleSort = (column: IntegrationsOrderBy) => {
|
||||||
|
if (column === orderBy) {
|
||||||
|
toggleOrderDirection();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setOrderBy(column);
|
||||||
|
setOrderDirection(OrderByDirection.ASC);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getClassName = (col: IntegrationsOrderBy) =>
|
||||||
|
twMerge("ml-2", orderBy === col ? "" : "opacity-30");
|
||||||
|
|
||||||
|
const getColSortIcon = (col: IntegrationsOrderBy) =>
|
||||||
|
orderDirection === OrderByDirection.DESC && orderBy === col ? faArrowUp : faArrowDown;
|
||||||
|
|
||||||
|
const isTableFiltered =
|
||||||
|
filters.integrations.length !== initialFilters.integrations.length ||
|
||||||
|
filters.environmentIds.length !== initialFilters.environmentIds.length ||
|
||||||
|
filters.status.length !== initialFilters.status.length;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Input
|
||||||
|
value={search}
|
||||||
|
onChange={(e) => setSearch(e.target.value)}
|
||||||
|
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
|
||||||
|
placeholder="Search integrations..."
|
||||||
|
className="flex-1"
|
||||||
|
/>
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<IconButton
|
||||||
|
ariaLabel="Environments"
|
||||||
|
variant="plain"
|
||||||
|
size="sm"
|
||||||
|
className={twMerge(
|
||||||
|
"flex h-10 w-11 items-center justify-center overflow-hidden border border-mineshaft-600 bg-mineshaft-800 p-0 transition-all hover:border-primary/60 hover:bg-primary/10",
|
||||||
|
isTableFiltered && "border-primary/50 text-primary"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Tooltip content="Filter Integrations" className="mb-2">
|
||||||
|
<FontAwesomeIcon icon={faFilter} />
|
||||||
|
</Tooltip>
|
||||||
|
</IconButton>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent className="thin-scrollbar max-h-[70vh] overflow-y-auto" align="end">
|
||||||
|
<DropdownMenuLabel>Status</DropdownMenuLabel>
|
||||||
|
{Object.values(IntegrationStatus).map((status) => (
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setFilters((prev) => ({
|
||||||
|
...prev,
|
||||||
|
status: prev.status.includes(status)
|
||||||
|
? prev.status.filter((s) => s !== status)
|
||||||
|
: [...prev.status, status]
|
||||||
|
}));
|
||||||
|
}}
|
||||||
|
key={status}
|
||||||
|
icon={
|
||||||
|
filters.status.includes(status) && (
|
||||||
|
<FontAwesomeIcon className="text-primary" icon={faCheckCircle} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
iconPos="right"
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={STATUS_ICON_MAP[status].icon}
|
||||||
|
className={STATUS_ICON_MAP[status].className}
|
||||||
|
/>
|
||||||
|
<span className="capitalize">{status.replace("-", " ")}</span>
|
||||||
|
</div>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
))}
|
||||||
|
<DropdownMenuLabel>Integration</DropdownMenuLabel>
|
||||||
|
{[...new Set(integrations.map(({ integration }) => integration))].map((integration) => (
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setFilters((prev) => ({
|
||||||
|
...prev,
|
||||||
|
integrations: prev.integrations.includes(integration)
|
||||||
|
? prev.integrations.filter((i) => i !== integration)
|
||||||
|
: [...prev.integrations, integration]
|
||||||
|
}));
|
||||||
|
}}
|
||||||
|
key={integration}
|
||||||
|
icon={
|
||||||
|
filters.integrations.includes(integration) && (
|
||||||
|
<FontAwesomeIcon className="text-primary" icon={faCheckCircle} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
iconPos="right"
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<img
|
||||||
|
alt={`${cloudIntegrationMap.get(integration)!.name} integration`}
|
||||||
|
src={`/images/integrations/${cloudIntegrationMap.get(integration)!.image}`}
|
||||||
|
className="h-4 w-4"
|
||||||
|
/>
|
||||||
|
<span className="capitalize">{cloudIntegrationMap.get(integration)!.name}</span>
|
||||||
|
</div>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
))}
|
||||||
|
<DropdownMenuLabel>Environment</DropdownMenuLabel>
|
||||||
|
{environments.map((env) => (
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setFilters((prev) => ({
|
||||||
|
...prev,
|
||||||
|
environmentIds: prev.environmentIds.includes(env.id)
|
||||||
|
? prev.environmentIds.filter((i) => i !== env.id)
|
||||||
|
: [...prev.environmentIds, env.id]
|
||||||
|
}));
|
||||||
|
}}
|
||||||
|
key={env.id}
|
||||||
|
icon={
|
||||||
|
filters.environmentIds.includes(env.id) && (
|
||||||
|
<FontAwesomeIcon className="text-primary" icon={faCheckCircle} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
iconPos="right"
|
||||||
|
>
|
||||||
|
<span className="capitalize">{env.name}</span>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
))}
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</div>
|
||||||
|
<TableContainer className="mt-4">
|
||||||
|
<Table>
|
||||||
|
<THead>
|
||||||
|
<Tr>
|
||||||
|
<Th className="w-[25%]">
|
||||||
|
<div className="flex items-center">
|
||||||
|
Integration
|
||||||
|
<IconButton
|
||||||
|
variant="plain"
|
||||||
|
className={getClassName(IntegrationsOrderBy.App)}
|
||||||
|
ariaLabel="sort"
|
||||||
|
onClick={() => handleSort(IntegrationsOrderBy.App)}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={getColSortIcon(IntegrationsOrderBy.App)} />
|
||||||
|
</IconButton>
|
||||||
|
</div>
|
||||||
|
</Th>
|
||||||
|
<Th className="w-1/5">
|
||||||
|
<div className="flex items-center">
|
||||||
|
Source Path
|
||||||
|
<IconButton
|
||||||
|
variant="plain"
|
||||||
|
className={getClassName(IntegrationsOrderBy.SecretPath)}
|
||||||
|
ariaLabel="sort"
|
||||||
|
onClick={() => handleSort(IntegrationsOrderBy.SecretPath)}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={getColSortIcon(IntegrationsOrderBy.SecretPath)} />
|
||||||
|
</IconButton>
|
||||||
|
</div>
|
||||||
|
</Th>
|
||||||
|
<Th className="w-1/5">
|
||||||
|
<div className="flex items-center">
|
||||||
|
Source Environment
|
||||||
|
<IconButton
|
||||||
|
variant="plain"
|
||||||
|
className={getClassName(IntegrationsOrderBy.Environment)}
|
||||||
|
ariaLabel="sort"
|
||||||
|
onClick={() => handleSort(IntegrationsOrderBy.Environment)}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={getColSortIcon(IntegrationsOrderBy.Environment)} />
|
||||||
|
</IconButton>
|
||||||
|
</div>
|
||||||
|
</Th>
|
||||||
|
<Th className="w-1/5">
|
||||||
|
<div className="flex items-center">
|
||||||
|
Destination
|
||||||
|
<IconButton
|
||||||
|
variant="plain"
|
||||||
|
className={getClassName(IntegrationsOrderBy.Destination)}
|
||||||
|
ariaLabel="sort"
|
||||||
|
onClick={() => handleSort(IntegrationsOrderBy.Destination)}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={getColSortIcon(IntegrationsOrderBy.Destination)} />
|
||||||
|
</IconButton>
|
||||||
|
</div>
|
||||||
|
</Th>
|
||||||
|
<Th className="w-1/5">
|
||||||
|
<div className="flex items-center">
|
||||||
|
Status
|
||||||
|
<IconButton
|
||||||
|
variant="plain"
|
||||||
|
className={getClassName(IntegrationsOrderBy.Status)}
|
||||||
|
ariaLabel="sort"
|
||||||
|
onClick={() => handleSort(IntegrationsOrderBy.Status)}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={getColSortIcon(IntegrationsOrderBy.Status)} />
|
||||||
|
</IconButton>
|
||||||
|
</div>
|
||||||
|
</Th>
|
||||||
|
<Th className="w-5" />
|
||||||
|
</Tr>
|
||||||
|
</THead>
|
||||||
|
<TBody>
|
||||||
|
{filteredIntegrations.slice(offset, perPage * page).map((integration) => (
|
||||||
|
<IntegrationRow
|
||||||
|
cloudIntegration={cloudIntegrationMap.get(integration.integration)!}
|
||||||
|
key={`integration-${integration.id}`}
|
||||||
|
onManualSyncIntegration={() => {
|
||||||
|
syncIntegration({
|
||||||
|
workspaceId,
|
||||||
|
id: integration.id,
|
||||||
|
lastUsed: integration.lastUsed as string
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
onRemoveIntegration={() => onDeleteIntegration(integration)}
|
||||||
|
integration={integration}
|
||||||
|
environment={environmentMap.get(integration.envId)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</TBody>
|
||||||
|
</Table>
|
||||||
|
{Boolean(filteredIntegrations.length) && (
|
||||||
|
<Pagination
|
||||||
|
count={filteredIntegrations.length}
|
||||||
|
page={page}
|
||||||
|
perPage={perPage}
|
||||||
|
onChangePage={setPage}
|
||||||
|
onChangePerPage={setPerPage}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{!isLoading && !filteredIntegrations?.length && (
|
||||||
|
<EmptyState
|
||||||
|
title={
|
||||||
|
integrations.length
|
||||||
|
? "No integrations match search..."
|
||||||
|
: "This project has no integrations configured"
|
||||||
|
}
|
||||||
|
icon={integrations.length ? faSearch : faPlug}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</TableContainer>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export * from "./IntegrationsTable";
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export { IntegrationsSection } from "./IntegrationsSection";
|
||||||
+92
@@ -0,0 +1,92 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "React",
|
||||||
|
"slug": "react",
|
||||||
|
"image": "React",
|
||||||
|
"docsLink": "https://infisical.com/docs/integrations/frameworks/react"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Vue",
|
||||||
|
"slug": "vue",
|
||||||
|
"image": "Vue",
|
||||||
|
"docsLink": "https://infisical.com/docs/integrations/frameworks/vue"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Express",
|
||||||
|
"slug": "express",
|
||||||
|
"image": "Express",
|
||||||
|
"docsLink": "https://infisical.com/docs/integrations/frameworks/express"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Next.js",
|
||||||
|
"slug": "nextjs",
|
||||||
|
"image": "Next.js",
|
||||||
|
"docsLink": "https://infisical.com/docs/integrations/frameworks/nextjs"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "SvelteKit",
|
||||||
|
"slug": "sveltekit",
|
||||||
|
"image": "SvelteKit",
|
||||||
|
"docsLink": "https://infisical.com/docs/integrations/frameworks/sveltekit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Django",
|
||||||
|
"slug": "django",
|
||||||
|
"image": "Django",
|
||||||
|
"docsLink": "https://infisical.com/docs/integrations/frameworks/django"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "NestJS",
|
||||||
|
"slug": "nestjs",
|
||||||
|
"image": "NestJS",
|
||||||
|
"docsLink": "https://infisical.com/docs/integrations/frameworks/nestjs"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Nuxt",
|
||||||
|
"slug": "nuxt",
|
||||||
|
"image": "Nuxt",
|
||||||
|
"docsLink": "https://infisical.com/docs/integrations/frameworks/nuxt"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Gatsby",
|
||||||
|
"slug": "gatsby",
|
||||||
|
"image": "Gatsby",
|
||||||
|
"docsLink": "https://infisical.com/docs/integrations/frameworks/gatsby"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Remix",
|
||||||
|
"slug": "remix",
|
||||||
|
"image": "Remix",
|
||||||
|
"docsLink": "https://infisical.com/docs/integrations/frameworks/remix"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Vite",
|
||||||
|
"slug": "vite",
|
||||||
|
"image": "Vite",
|
||||||
|
"docsLink": "https://infisical.com/docs/integrations/frameworks/vite"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Fiber",
|
||||||
|
"slug": "fiber",
|
||||||
|
"image": "Fiber",
|
||||||
|
"docsLink": "https://infisical.com/docs/integrations/frameworks/fiber"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Flask",
|
||||||
|
"slug": "flask",
|
||||||
|
"image": "Flask",
|
||||||
|
"docsLink": "https://infisical.com/docs/integrations/frameworks/flask"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Laravel",
|
||||||
|
"slug": "laravel",
|
||||||
|
"image": "Laravel",
|
||||||
|
"docsLink": "https://infisical.com/docs/integrations/frameworks/laravel"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Rails",
|
||||||
|
"slug": "rails",
|
||||||
|
"image": "Rails",
|
||||||
|
"docsLink": "https://infisical.com/docs/integrations/frameworks/rails"
|
||||||
|
}
|
||||||
|
]
|
||||||
+50
@@ -0,0 +1,50 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "Docker",
|
||||||
|
"slug": "docker",
|
||||||
|
"image": "Docker",
|
||||||
|
"docsLink": "https://infisical.com/docs/integrations/platforms/docker"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Docker Compose",
|
||||||
|
"slug": "docker-compose",
|
||||||
|
"image": "Docker Compose",
|
||||||
|
"docsLink": "https://infisical.com/docs/integrations/platforms/docker-compose"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Kubernetes",
|
||||||
|
"slug": "kubernetes",
|
||||||
|
"image": "Kubernetes",
|
||||||
|
"docsLink": "https://infisical.com/docs/integrations/platforms/kubernetes"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Terraform",
|
||||||
|
"slug": "terraform",
|
||||||
|
"image": "Terraform",
|
||||||
|
"docsLink": "https://infisical.com/docs/integrations/frameworks/terraform"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Jenkins",
|
||||||
|
"slug": "jenkins",
|
||||||
|
"image": "Jenkins",
|
||||||
|
"docsLink": "https://infisical.com/docs/integrations/cicd/jenkins"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Infisical Agent",
|
||||||
|
"slug": "agent",
|
||||||
|
"image": "Agent",
|
||||||
|
"docsLink": "https://infisical.com/docs/integrations/platforms/infisical-agent"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Amazon ECS",
|
||||||
|
"slug": "ecs",
|
||||||
|
"image": "ECS",
|
||||||
|
"docsLink": "https://infisical.com/docs/integrations/platforms/ecs-with-agent"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Ansible",
|
||||||
|
"slug": "ansible",
|
||||||
|
"image": "Ansible",
|
||||||
|
"docsLink": "https://infisical.com/docs/integrations/platforms/ansible"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { createFileRoute } from '@tanstack/react-router'
|
||||||
|
|
||||||
|
import { IntegrationsListPage } from './IntegrationsListPage'
|
||||||
|
|
||||||
|
export const Route = createFileRoute(
|
||||||
|
'/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/',
|
||||||
|
)({
|
||||||
|
component: IntegrationsListPage,
|
||||||
|
})
|
||||||
@@ -89,7 +89,9 @@ import { Route as projectMemberDetailsByIDPageRouteCertManagerImport } from './p
|
|||||||
import { Route as projectIdentityDetailsByIDPageRouteCertManagerImport } from './pages/project/IdentityDetailsByIDPage/route-cert-manager'
|
import { Route as projectIdentityDetailsByIDPageRouteCertManagerImport } from './pages/project/IdentityDetailsByIDPage/route-cert-manager'
|
||||||
import { Route as sshSshCaByIDPageRouteImport } from './pages/ssh/SshCaByIDPage/route'
|
import { Route as sshSshCaByIDPageRouteImport } from './pages/ssh/SshCaByIDPage/route'
|
||||||
import { Route as secretManagerSecretDashboardPageRouteImport } from './pages/secret-manager/SecretDashboardPage/route'
|
import { Route as secretManagerSecretDashboardPageRouteImport } from './pages/secret-manager/SecretDashboardPage/route'
|
||||||
|
import { Route as secretManagerIntegrationsDetailsByIDPageRouteImport } from './pages/secret-manager/IntegrationsDetailsByIDPage/route'
|
||||||
import { Route as certManagerCertAuthDetailsByIDPageRouteImport } from './pages/cert-manager/CertAuthDetailsByIDPage/route'
|
import { Route as certManagerCertAuthDetailsByIDPageRouteImport } from './pages/cert-manager/CertAuthDetailsByIDPage/route'
|
||||||
|
import { Route as secretManagerIntegrationsListPageRouteImport } from './pages/secret-manager/IntegrationsListPage/route'
|
||||||
import { Route as organizationAppConnectionsGithubOauthCallbackPageRouteImport } from './pages/organization/AppConnections/GithubOauthCallbackPage/route'
|
import { Route as organizationAppConnectionsGithubOauthCallbackPageRouteImport } from './pages/organization/AppConnections/GithubOauthCallbackPage/route'
|
||||||
|
|
||||||
// Create Virtual Routes
|
// Create Virtual Routes
|
||||||
@@ -120,6 +122,10 @@ const AuthenticateInjectOrgDetailsKmsProjectIdImport = createFileRoute(
|
|||||||
const AuthenticateInjectOrgDetailsCertManagerProjectIdImport = createFileRoute(
|
const AuthenticateInjectOrgDetailsCertManagerProjectIdImport = createFileRoute(
|
||||||
'/_authenticate/_inject-org-details/cert-manager/$projectId',
|
'/_authenticate/_inject-org-details/cert-manager/$projectId',
|
||||||
)()
|
)()
|
||||||
|
const AuthenticateInjectOrgDetailsSecretManagerProjectIdSecretManagerLayoutIntegrationsImport =
|
||||||
|
createFileRoute(
|
||||||
|
'/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations',
|
||||||
|
)()
|
||||||
|
|
||||||
// Create/Update Routes
|
// Create/Update Routes
|
||||||
|
|
||||||
@@ -431,6 +437,15 @@ const projectAccessControlPageRouteSshRoute =
|
|||||||
getParentRoute: () => sshLayoutRoute,
|
getParentRoute: () => sshLayoutRoute,
|
||||||
} as any)
|
} as any)
|
||||||
|
|
||||||
|
const AuthenticateInjectOrgDetailsSecretManagerProjectIdSecretManagerLayoutIntegrationsRoute =
|
||||||
|
AuthenticateInjectOrgDetailsSecretManagerProjectIdSecretManagerLayoutIntegrationsImport.update(
|
||||||
|
{
|
||||||
|
id: '/integrations',
|
||||||
|
path: '/integrations',
|
||||||
|
getParentRoute: () => secretManagerLayoutRoute,
|
||||||
|
} as any,
|
||||||
|
)
|
||||||
|
|
||||||
const projectAccessControlPageRouteSecretManagerRoute =
|
const projectAccessControlPageRouteSecretManagerRoute =
|
||||||
projectAccessControlPageRouteSecretManagerImport.update({
|
projectAccessControlPageRouteSecretManagerImport.update({
|
||||||
id: '/access-management',
|
id: '/access-management',
|
||||||
@@ -685,6 +700,14 @@ const secretManagerSecretDashboardPageRouteRoute =
|
|||||||
getParentRoute: () => secretManagerLayoutRoute,
|
getParentRoute: () => secretManagerLayoutRoute,
|
||||||
} as any)
|
} as any)
|
||||||
|
|
||||||
|
const secretManagerIntegrationsDetailsByIDPageRouteRoute =
|
||||||
|
secretManagerIntegrationsDetailsByIDPageRouteImport.update({
|
||||||
|
id: '/$integrationId',
|
||||||
|
path: '/$integrationId',
|
||||||
|
getParentRoute: () =>
|
||||||
|
AuthenticateInjectOrgDetailsSecretManagerProjectIdSecretManagerLayoutIntegrationsRoute,
|
||||||
|
} as any)
|
||||||
|
|
||||||
const certManagerCertAuthDetailsByIDPageRouteRoute =
|
const certManagerCertAuthDetailsByIDPageRouteRoute =
|
||||||
certManagerCertAuthDetailsByIDPageRouteImport.update({
|
certManagerCertAuthDetailsByIDPageRouteImport.update({
|
||||||
id: '/ca/$caId',
|
id: '/ca/$caId',
|
||||||
@@ -692,6 +715,14 @@ const certManagerCertAuthDetailsByIDPageRouteRoute =
|
|||||||
getParentRoute: () => certManagerLayoutRoute,
|
getParentRoute: () => certManagerLayoutRoute,
|
||||||
} as any)
|
} as any)
|
||||||
|
|
||||||
|
const secretManagerIntegrationsListPageRouteRoute =
|
||||||
|
secretManagerIntegrationsListPageRouteImport.update({
|
||||||
|
id: '/',
|
||||||
|
path: '/',
|
||||||
|
getParentRoute: () =>
|
||||||
|
AuthenticateInjectOrgDetailsSecretManagerProjectIdSecretManagerLayoutIntegrationsRoute,
|
||||||
|
} as any)
|
||||||
|
|
||||||
const organizationAppConnectionsGithubOauthCallbackPageRouteRoute =
|
const organizationAppConnectionsGithubOauthCallbackPageRouteRoute =
|
||||||
organizationAppConnectionsGithubOauthCallbackPageRouteImport.update({
|
organizationAppConnectionsGithubOauthCallbackPageRouteImport.update({
|
||||||
id: '/app-connections/github/oauth/callback',
|
id: '/app-connections/github/oauth/callback',
|
||||||
@@ -1186,6 +1217,13 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof projectAccessControlPageRouteSecretManagerImport
|
preLoaderRoute: typeof projectAccessControlPageRouteSecretManagerImport
|
||||||
parentRoute: typeof secretManagerLayoutImport
|
parentRoute: typeof secretManagerLayoutImport
|
||||||
}
|
}
|
||||||
|
'/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations': {
|
||||||
|
id: '/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations'
|
||||||
|
path: '/integrations'
|
||||||
|
fullPath: '/secret-manager/$projectId/integrations'
|
||||||
|
preLoaderRoute: typeof AuthenticateInjectOrgDetailsSecretManagerProjectIdSecretManagerLayoutIntegrationsImport
|
||||||
|
parentRoute: typeof secretManagerLayoutImport
|
||||||
|
}
|
||||||
'/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/access-management': {
|
'/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/access-management': {
|
||||||
id: '/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/access-management'
|
id: '/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/access-management'
|
||||||
path: '/access-management'
|
path: '/access-management'
|
||||||
@@ -1193,6 +1231,13 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof projectAccessControlPageRouteSshImport
|
preLoaderRoute: typeof projectAccessControlPageRouteSshImport
|
||||||
parentRoute: typeof sshLayoutImport
|
parentRoute: typeof sshLayoutImport
|
||||||
}
|
}
|
||||||
|
'/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/': {
|
||||||
|
id: '/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/'
|
||||||
|
path: '/'
|
||||||
|
fullPath: '/secret-manager/$projectId/integrations/'
|
||||||
|
preLoaderRoute: typeof secretManagerIntegrationsListPageRouteImport
|
||||||
|
parentRoute: typeof AuthenticateInjectOrgDetailsSecretManagerProjectIdSecretManagerLayoutIntegrationsImport
|
||||||
|
}
|
||||||
'/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/ca/$caId': {
|
'/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/ca/$caId': {
|
||||||
id: '/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/ca/$caId'
|
id: '/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/ca/$caId'
|
||||||
path: '/ca/$caId'
|
path: '/ca/$caId'
|
||||||
@@ -1200,6 +1245,13 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof certManagerCertAuthDetailsByIDPageRouteImport
|
preLoaderRoute: typeof certManagerCertAuthDetailsByIDPageRouteImport
|
||||||
parentRoute: typeof certManagerLayoutImport
|
parentRoute: typeof certManagerLayoutImport
|
||||||
}
|
}
|
||||||
|
'/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/$integrationId': {
|
||||||
|
id: '/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/$integrationId'
|
||||||
|
path: '/$integrationId'
|
||||||
|
fullPath: '/secret-manager/$projectId/integrations/$integrationId'
|
||||||
|
preLoaderRoute: typeof secretManagerIntegrationsDetailsByIDPageRouteImport
|
||||||
|
parentRoute: typeof AuthenticateInjectOrgDetailsSecretManagerProjectIdSecretManagerLayoutIntegrationsImport
|
||||||
|
}
|
||||||
'/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/secrets/$envSlug': {
|
'/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/secrets/$envSlug': {
|
||||||
id: '/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/secrets/$envSlug'
|
id: '/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/secrets/$envSlug'
|
||||||
path: '/secrets/$envSlug'
|
path: '/secrets/$envSlug'
|
||||||
@@ -1467,6 +1519,24 @@ const AuthenticateInjectOrgDetailsKmsProjectIdRouteWithChildren =
|
|||||||
AuthenticateInjectOrgDetailsKmsProjectIdRouteChildren,
|
AuthenticateInjectOrgDetailsKmsProjectIdRouteChildren,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
interface AuthenticateInjectOrgDetailsSecretManagerProjectIdSecretManagerLayoutIntegrationsRouteChildren {
|
||||||
|
secretManagerIntegrationsListPageRouteRoute: typeof secretManagerIntegrationsListPageRouteRoute
|
||||||
|
secretManagerIntegrationsDetailsByIDPageRouteRoute: typeof secretManagerIntegrationsDetailsByIDPageRouteRoute
|
||||||
|
}
|
||||||
|
|
||||||
|
const AuthenticateInjectOrgDetailsSecretManagerProjectIdSecretManagerLayoutIntegrationsRouteChildren: AuthenticateInjectOrgDetailsSecretManagerProjectIdSecretManagerLayoutIntegrationsRouteChildren =
|
||||||
|
{
|
||||||
|
secretManagerIntegrationsListPageRouteRoute:
|
||||||
|
secretManagerIntegrationsListPageRouteRoute,
|
||||||
|
secretManagerIntegrationsDetailsByIDPageRouteRoute:
|
||||||
|
secretManagerIntegrationsDetailsByIDPageRouteRoute,
|
||||||
|
}
|
||||||
|
|
||||||
|
const AuthenticateInjectOrgDetailsSecretManagerProjectIdSecretManagerLayoutIntegrationsRouteWithChildren =
|
||||||
|
AuthenticateInjectOrgDetailsSecretManagerProjectIdSecretManagerLayoutIntegrationsRoute._addFileChildren(
|
||||||
|
AuthenticateInjectOrgDetailsSecretManagerProjectIdSecretManagerLayoutIntegrationsRouteChildren,
|
||||||
|
)
|
||||||
|
|
||||||
interface secretManagerLayoutRouteChildren {
|
interface secretManagerLayoutRouteChildren {
|
||||||
secretManagerIPAllowlistPageRouteRoute: typeof secretManagerIPAllowlistPageRouteRoute
|
secretManagerIPAllowlistPageRouteRoute: typeof secretManagerIPAllowlistPageRouteRoute
|
||||||
secretManagerSecretApprovalsPageRouteRoute: typeof secretManagerSecretApprovalsPageRouteRoute
|
secretManagerSecretApprovalsPageRouteRoute: typeof secretManagerSecretApprovalsPageRouteRoute
|
||||||
@@ -1474,6 +1544,7 @@ interface secretManagerLayoutRouteChildren {
|
|||||||
secretManagerSecretRotationPageRouteRoute: typeof secretManagerSecretRotationPageRouteRoute
|
secretManagerSecretRotationPageRouteRoute: typeof secretManagerSecretRotationPageRouteRoute
|
||||||
secretManagerSettingsPageRouteRoute: typeof secretManagerSettingsPageRouteRoute
|
secretManagerSettingsPageRouteRoute: typeof secretManagerSettingsPageRouteRoute
|
||||||
projectAccessControlPageRouteSecretManagerRoute: typeof projectAccessControlPageRouteSecretManagerRoute
|
projectAccessControlPageRouteSecretManagerRoute: typeof projectAccessControlPageRouteSecretManagerRoute
|
||||||
|
AuthenticateInjectOrgDetailsSecretManagerProjectIdSecretManagerLayoutIntegrationsRoute: typeof AuthenticateInjectOrgDetailsSecretManagerProjectIdSecretManagerLayoutIntegrationsRouteWithChildren
|
||||||
secretManagerSecretDashboardPageRouteRoute: typeof secretManagerSecretDashboardPageRouteRoute
|
secretManagerSecretDashboardPageRouteRoute: typeof secretManagerSecretDashboardPageRouteRoute
|
||||||
projectIdentityDetailsByIDPageRouteSecretManagerRoute: typeof projectIdentityDetailsByIDPageRouteSecretManagerRoute
|
projectIdentityDetailsByIDPageRouteSecretManagerRoute: typeof projectIdentityDetailsByIDPageRouteSecretManagerRoute
|
||||||
projectMemberDetailsByIDPageRouteSecretManagerRoute: typeof projectMemberDetailsByIDPageRouteSecretManagerRoute
|
projectMemberDetailsByIDPageRouteSecretManagerRoute: typeof projectMemberDetailsByIDPageRouteSecretManagerRoute
|
||||||
@@ -1491,6 +1562,8 @@ const secretManagerLayoutRouteChildren: secretManagerLayoutRouteChildren = {
|
|||||||
secretManagerSettingsPageRouteRoute: secretManagerSettingsPageRouteRoute,
|
secretManagerSettingsPageRouteRoute: secretManagerSettingsPageRouteRoute,
|
||||||
projectAccessControlPageRouteSecretManagerRoute:
|
projectAccessControlPageRouteSecretManagerRoute:
|
||||||
projectAccessControlPageRouteSecretManagerRoute,
|
projectAccessControlPageRouteSecretManagerRoute,
|
||||||
|
AuthenticateInjectOrgDetailsSecretManagerProjectIdSecretManagerLayoutIntegrationsRoute:
|
||||||
|
AuthenticateInjectOrgDetailsSecretManagerProjectIdSecretManagerLayoutIntegrationsRouteWithChildren,
|
||||||
secretManagerSecretDashboardPageRouteRoute:
|
secretManagerSecretDashboardPageRouteRoute:
|
||||||
secretManagerSecretDashboardPageRouteRoute,
|
secretManagerSecretDashboardPageRouteRoute,
|
||||||
projectIdentityDetailsByIDPageRouteSecretManagerRoute:
|
projectIdentityDetailsByIDPageRouteSecretManagerRoute:
|
||||||
@@ -1786,8 +1859,11 @@ export interface FileRoutesByFullPath {
|
|||||||
'/cert-manager/$projectId/access-management': typeof projectAccessControlPageRouteCertManagerRoute
|
'/cert-manager/$projectId/access-management': typeof projectAccessControlPageRouteCertManagerRoute
|
||||||
'/kms/$projectId/access-management': typeof projectAccessControlPageRouteKmsRoute
|
'/kms/$projectId/access-management': typeof projectAccessControlPageRouteKmsRoute
|
||||||
'/secret-manager/$projectId/access-management': typeof projectAccessControlPageRouteSecretManagerRoute
|
'/secret-manager/$projectId/access-management': typeof projectAccessControlPageRouteSecretManagerRoute
|
||||||
|
'/secret-manager/$projectId/integrations': typeof AuthenticateInjectOrgDetailsSecretManagerProjectIdSecretManagerLayoutIntegrationsRouteWithChildren
|
||||||
'/ssh/$projectId/access-management': typeof projectAccessControlPageRouteSshRoute
|
'/ssh/$projectId/access-management': typeof projectAccessControlPageRouteSshRoute
|
||||||
|
'/secret-manager/$projectId/integrations/': typeof secretManagerIntegrationsListPageRouteRoute
|
||||||
'/cert-manager/$projectId/ca/$caId': typeof certManagerCertAuthDetailsByIDPageRouteRoute
|
'/cert-manager/$projectId/ca/$caId': typeof certManagerCertAuthDetailsByIDPageRouteRoute
|
||||||
|
'/secret-manager/$projectId/integrations/$integrationId': typeof secretManagerIntegrationsDetailsByIDPageRouteRoute
|
||||||
'/secret-manager/$projectId/secrets/$envSlug': typeof secretManagerSecretDashboardPageRouteRoute
|
'/secret-manager/$projectId/secrets/$envSlug': typeof secretManagerSecretDashboardPageRouteRoute
|
||||||
'/ssh/$projectId/ca/$caId': typeof sshSshCaByIDPageRouteRoute
|
'/ssh/$projectId/ca/$caId': typeof sshSshCaByIDPageRouteRoute
|
||||||
'/cert-manager/$projectId/identities/$identityId': typeof projectIdentityDetailsByIDPageRouteCertManagerRoute
|
'/cert-manager/$projectId/identities/$identityId': typeof projectIdentityDetailsByIDPageRouteCertManagerRoute
|
||||||
@@ -1864,7 +1940,9 @@ export interface FileRoutesByTo {
|
|||||||
'/kms/$projectId/access-management': typeof projectAccessControlPageRouteKmsRoute
|
'/kms/$projectId/access-management': typeof projectAccessControlPageRouteKmsRoute
|
||||||
'/secret-manager/$projectId/access-management': typeof projectAccessControlPageRouteSecretManagerRoute
|
'/secret-manager/$projectId/access-management': typeof projectAccessControlPageRouteSecretManagerRoute
|
||||||
'/ssh/$projectId/access-management': typeof projectAccessControlPageRouteSshRoute
|
'/ssh/$projectId/access-management': typeof projectAccessControlPageRouteSshRoute
|
||||||
|
'/secret-manager/$projectId/integrations': typeof secretManagerIntegrationsListPageRouteRoute
|
||||||
'/cert-manager/$projectId/ca/$caId': typeof certManagerCertAuthDetailsByIDPageRouteRoute
|
'/cert-manager/$projectId/ca/$caId': typeof certManagerCertAuthDetailsByIDPageRouteRoute
|
||||||
|
'/secret-manager/$projectId/integrations/$integrationId': typeof secretManagerIntegrationsDetailsByIDPageRouteRoute
|
||||||
'/secret-manager/$projectId/secrets/$envSlug': typeof secretManagerSecretDashboardPageRouteRoute
|
'/secret-manager/$projectId/secrets/$envSlug': typeof secretManagerSecretDashboardPageRouteRoute
|
||||||
'/ssh/$projectId/ca/$caId': typeof sshSshCaByIDPageRouteRoute
|
'/ssh/$projectId/ca/$caId': typeof sshSshCaByIDPageRouteRoute
|
||||||
'/cert-manager/$projectId/identities/$identityId': typeof projectIdentityDetailsByIDPageRouteCertManagerRoute
|
'/cert-manager/$projectId/identities/$identityId': typeof projectIdentityDetailsByIDPageRouteCertManagerRoute
|
||||||
@@ -1954,8 +2032,11 @@ export interface FileRoutesById {
|
|||||||
'/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/access-management': typeof projectAccessControlPageRouteCertManagerRoute
|
'/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/access-management': typeof projectAccessControlPageRouteCertManagerRoute
|
||||||
'/_authenticate/_inject-org-details/kms/$projectId/_kms-layout/access-management': typeof projectAccessControlPageRouteKmsRoute
|
'/_authenticate/_inject-org-details/kms/$projectId/_kms-layout/access-management': typeof projectAccessControlPageRouteKmsRoute
|
||||||
'/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/access-management': typeof projectAccessControlPageRouteSecretManagerRoute
|
'/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/access-management': typeof projectAccessControlPageRouteSecretManagerRoute
|
||||||
|
'/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations': typeof AuthenticateInjectOrgDetailsSecretManagerProjectIdSecretManagerLayoutIntegrationsRouteWithChildren
|
||||||
'/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/access-management': typeof projectAccessControlPageRouteSshRoute
|
'/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/access-management': typeof projectAccessControlPageRouteSshRoute
|
||||||
|
'/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/': typeof secretManagerIntegrationsListPageRouteRoute
|
||||||
'/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/ca/$caId': typeof certManagerCertAuthDetailsByIDPageRouteRoute
|
'/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/ca/$caId': typeof certManagerCertAuthDetailsByIDPageRouteRoute
|
||||||
|
'/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/$integrationId': typeof secretManagerIntegrationsDetailsByIDPageRouteRoute
|
||||||
'/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/secrets/$envSlug': typeof secretManagerSecretDashboardPageRouteRoute
|
'/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/secrets/$envSlug': typeof secretManagerSecretDashboardPageRouteRoute
|
||||||
'/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/ca/$caId': typeof sshSshCaByIDPageRouteRoute
|
'/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/ca/$caId': typeof sshSshCaByIDPageRouteRoute
|
||||||
'/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/identities/$identityId': typeof projectIdentityDetailsByIDPageRouteCertManagerRoute
|
'/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/identities/$identityId': typeof projectIdentityDetailsByIDPageRouteCertManagerRoute
|
||||||
@@ -2037,8 +2118,11 @@ export interface FileRouteTypes {
|
|||||||
| '/cert-manager/$projectId/access-management'
|
| '/cert-manager/$projectId/access-management'
|
||||||
| '/kms/$projectId/access-management'
|
| '/kms/$projectId/access-management'
|
||||||
| '/secret-manager/$projectId/access-management'
|
| '/secret-manager/$projectId/access-management'
|
||||||
|
| '/secret-manager/$projectId/integrations'
|
||||||
| '/ssh/$projectId/access-management'
|
| '/ssh/$projectId/access-management'
|
||||||
|
| '/secret-manager/$projectId/integrations/'
|
||||||
| '/cert-manager/$projectId/ca/$caId'
|
| '/cert-manager/$projectId/ca/$caId'
|
||||||
|
| '/secret-manager/$projectId/integrations/$integrationId'
|
||||||
| '/secret-manager/$projectId/secrets/$envSlug'
|
| '/secret-manager/$projectId/secrets/$envSlug'
|
||||||
| '/ssh/$projectId/ca/$caId'
|
| '/ssh/$projectId/ca/$caId'
|
||||||
| '/cert-manager/$projectId/identities/$identityId'
|
| '/cert-manager/$projectId/identities/$identityId'
|
||||||
@@ -2114,7 +2198,9 @@ export interface FileRouteTypes {
|
|||||||
| '/kms/$projectId/access-management'
|
| '/kms/$projectId/access-management'
|
||||||
| '/secret-manager/$projectId/access-management'
|
| '/secret-manager/$projectId/access-management'
|
||||||
| '/ssh/$projectId/access-management'
|
| '/ssh/$projectId/access-management'
|
||||||
|
| '/secret-manager/$projectId/integrations'
|
||||||
| '/cert-manager/$projectId/ca/$caId'
|
| '/cert-manager/$projectId/ca/$caId'
|
||||||
|
| '/secret-manager/$projectId/integrations/$integrationId'
|
||||||
| '/secret-manager/$projectId/secrets/$envSlug'
|
| '/secret-manager/$projectId/secrets/$envSlug'
|
||||||
| '/ssh/$projectId/ca/$caId'
|
| '/ssh/$projectId/ca/$caId'
|
||||||
| '/cert-manager/$projectId/identities/$identityId'
|
| '/cert-manager/$projectId/identities/$identityId'
|
||||||
@@ -2202,8 +2288,11 @@ export interface FileRouteTypes {
|
|||||||
| '/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/access-management'
|
| '/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/access-management'
|
||||||
| '/_authenticate/_inject-org-details/kms/$projectId/_kms-layout/access-management'
|
| '/_authenticate/_inject-org-details/kms/$projectId/_kms-layout/access-management'
|
||||||
| '/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/access-management'
|
| '/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/access-management'
|
||||||
|
| '/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations'
|
||||||
| '/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/access-management'
|
| '/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/access-management'
|
||||||
|
| '/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/'
|
||||||
| '/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/ca/$caId'
|
| '/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/ca/$caId'
|
||||||
|
| '/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/$integrationId'
|
||||||
| '/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/secrets/$envSlug'
|
| '/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/secrets/$envSlug'
|
||||||
| '/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/ca/$caId'
|
| '/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/ca/$caId'
|
||||||
| '/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/identities/$identityId'
|
| '/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/identities/$identityId'
|
||||||
@@ -2542,6 +2631,7 @@ export const routeTree = rootRoute
|
|||||||
"/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/secret-rotation",
|
"/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/secret-rotation",
|
||||||
"/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/settings",
|
"/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/settings",
|
||||||
"/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/access-management",
|
"/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/access-management",
|
||||||
|
"/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations",
|
||||||
"/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/secrets/$envSlug",
|
"/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/secrets/$envSlug",
|
||||||
"/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/identities/$identityId",
|
"/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/identities/$identityId",
|
||||||
"/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/members/$membershipId",
|
"/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/members/$membershipId",
|
||||||
@@ -2649,14 +2739,30 @@ export const routeTree = rootRoute
|
|||||||
"filePath": "project/AccessControlPage/route-secret-manager.tsx",
|
"filePath": "project/AccessControlPage/route-secret-manager.tsx",
|
||||||
"parent": "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout"
|
"parent": "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout"
|
||||||
},
|
},
|
||||||
|
"/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations": {
|
||||||
|
"filePath": "",
|
||||||
|
"parent": "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout",
|
||||||
|
"children": [
|
||||||
|
"/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/",
|
||||||
|
"/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/$integrationId"
|
||||||
|
]
|
||||||
|
},
|
||||||
"/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/access-management": {
|
"/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/access-management": {
|
||||||
"filePath": "project/AccessControlPage/route-ssh.tsx",
|
"filePath": "project/AccessControlPage/route-ssh.tsx",
|
||||||
"parent": "/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout"
|
"parent": "/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout"
|
||||||
},
|
},
|
||||||
|
"/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/": {
|
||||||
|
"filePath": "secret-manager/IntegrationsListPage/route.tsx",
|
||||||
|
"parent": "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations"
|
||||||
|
},
|
||||||
"/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/ca/$caId": {
|
"/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/ca/$caId": {
|
||||||
"filePath": "cert-manager/CertAuthDetailsByIDPage/route.tsx",
|
"filePath": "cert-manager/CertAuthDetailsByIDPage/route.tsx",
|
||||||
"parent": "/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout"
|
"parent": "/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout"
|
||||||
},
|
},
|
||||||
|
"/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/$integrationId": {
|
||||||
|
"filePath": "secret-manager/IntegrationsDetailsByIDPage/route.tsx",
|
||||||
|
"parent": "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations"
|
||||||
|
},
|
||||||
"/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/secrets/$envSlug": {
|
"/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/secrets/$envSlug": {
|
||||||
"filePath": "secret-manager/SecretDashboardPage/route.tsx",
|
"filePath": "secret-manager/SecretDashboardPage/route.tsx",
|
||||||
"parent": "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout"
|
"parent": "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout"
|
||||||
|
|||||||
@@ -40,6 +40,10 @@ const secretManagerRoutes = route("/secret-manager/$projectId", [
|
|||||||
route("/approval", "secret-manager/SecretApprovalsPage/route.tsx"),
|
route("/approval", "secret-manager/SecretApprovalsPage/route.tsx"),
|
||||||
route("/secret-rotation", "secret-manager/SecretRotationPage/route.tsx"),
|
route("/secret-rotation", "secret-manager/SecretRotationPage/route.tsx"),
|
||||||
route("/settings", "secret-manager/SettingsPage/route.tsx"),
|
route("/settings", "secret-manager/SettingsPage/route.tsx"),
|
||||||
|
route("/integrations", [
|
||||||
|
index("secret-manager/IntegrationsListPage/route.tsx"),
|
||||||
|
route("/$integrationId", "secret-manager/IntegrationsDetailsByIDPage/route.tsx")
|
||||||
|
]),
|
||||||
route("/access-management", "project/AccessControlPage/route-secret-manager.tsx"),
|
route("/access-management", "project/AccessControlPage/route-secret-manager.tsx"),
|
||||||
route("/roles/$roleSlug", "project/RoleDetailsBySlugPage/route-secret-manager.tsx"),
|
route("/roles/$roleSlug", "project/RoleDetailsBySlugPage/route-secret-manager.tsx"),
|
||||||
route("/identities/$identityId", "project/IdentityDetailsByIDPage/route-secret-manager.tsx"),
|
route("/identities/$identityId", "project/IdentityDetailsByIDPage/route-secret-manager.tsx"),
|
||||||
|
|||||||
Reference in New Issue
Block a user