mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 20:27:12 +00:00
Feat: Integration UI improvements
This commit is contained in:
@@ -4,7 +4,7 @@ import { IntegrationsSchema } from "@app/db/schemas";
|
|||||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
import { INTEGRATION } from "@app/lib/api-docs";
|
import { INTEGRATION } from "@app/lib/api-docs";
|
||||||
import { removeTrailingSlash, shake } from "@app/lib/fn";
|
import { removeTrailingSlash, shake } from "@app/lib/fn";
|
||||||
import { writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
|
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
@@ -154,6 +154,48 @@ export const registerIntegrationRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/:integrationId",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
description: "Get an integration by integration id",
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
params: z.object({
|
||||||
|
integrationId: z.string().trim().describe(INTEGRATION.UPDATE.integrationId)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
integration: IntegrationsSchema.extend({
|
||||||
|
environment: z.object({
|
||||||
|
slug: z.string().trim(),
|
||||||
|
name: z.string().trim(),
|
||||||
|
id: z.string().trim()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const integration = await server.services.integration.getIntegration({
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
id: req.params.integrationId
|
||||||
|
});
|
||||||
|
|
||||||
|
return { integration };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
url: "/:integrationId",
|
url: "/:integrationId",
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { ForbiddenError, subject } from "@casl/ability";
|
|||||||
|
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
import { TProjectPermission } from "@app/lib/types";
|
import { TProjectPermission } from "@app/lib/types";
|
||||||
|
|
||||||
import { TIntegrationAuthDALFactory } from "../integration-auth/integration-auth-dal";
|
import { TIntegrationAuthDALFactory } from "../integration-auth/integration-auth-dal";
|
||||||
@@ -19,6 +19,7 @@ import { TIntegrationDALFactory } from "./integration-dal";
|
|||||||
import {
|
import {
|
||||||
TCreateIntegrationDTO,
|
TCreateIntegrationDTO,
|
||||||
TDeleteIntegrationDTO,
|
TDeleteIntegrationDTO,
|
||||||
|
TGetIntegrationDTO,
|
||||||
TSyncIntegrationDTO,
|
TSyncIntegrationDTO,
|
||||||
TUpdateIntegrationDTO
|
TUpdateIntegrationDTO
|
||||||
} from "./integration-types";
|
} from "./integration-types";
|
||||||
@@ -180,6 +181,27 @@ export const integrationServiceFactory = ({
|
|||||||
return updatedIntegration;
|
return updatedIntegration;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getIntegration = async ({ id, actor, actorAuthMethod, actorId, actorOrgId }: TGetIntegrationDTO) => {
|
||||||
|
const integration = await integrationDAL.findById(id);
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
integration?.projectId || "",
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
|
||||||
|
|
||||||
|
if (!integration) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: "Integration not found"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return { ...integration, envId: integration.environment.id };
|
||||||
|
};
|
||||||
|
|
||||||
const deleteIntegration = async ({
|
const deleteIntegration = async ({
|
||||||
actorId,
|
actorId,
|
||||||
id,
|
id,
|
||||||
@@ -289,6 +311,7 @@ export const integrationServiceFactory = ({
|
|||||||
updateIntegration,
|
updateIntegration,
|
||||||
deleteIntegration,
|
deleteIntegration,
|
||||||
listIntegrationByProject,
|
listIntegrationByProject,
|
||||||
|
getIntegration,
|
||||||
syncIntegration
|
syncIntegration
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -39,6 +39,10 @@ export type TCreateIntegrationDTO = {
|
|||||||
};
|
};
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
|
export type TGetIntegrationDTO = {
|
||||||
|
id: string;
|
||||||
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
export type TUpdateIntegrationDTO = {
|
export type TUpdateIntegrationDTO = {
|
||||||
id: string;
|
id: string;
|
||||||
app?: string;
|
app?: string;
|
||||||
|
|||||||
@@ -1 +1,6 @@
|
|||||||
export { useCreateIntegration, useDeleteIntegration, useGetCloudIntegrations } from "./queries";
|
export {
|
||||||
|
useCreateIntegration,
|
||||||
|
useDeleteIntegration,
|
||||||
|
useGetCloudIntegrations,
|
||||||
|
useGetIntegration
|
||||||
|
} from "./queries";
|
||||||
|
|||||||
@@ -5,9 +5,11 @@ import { apiRequest } from "@app/config/request";
|
|||||||
|
|
||||||
import { workspaceKeys } from "../workspace";
|
import { workspaceKeys } from "../workspace";
|
||||||
import { TCloudIntegration } from "./types";
|
import { TCloudIntegration } from "./types";
|
||||||
|
import { TCloudIntegration, TIntegrationWithEnv } from "./types";
|
||||||
|
|
||||||
export const integrationQueryKeys = {
|
export const integrationQueryKeys = {
|
||||||
getIntegrations: () => ["integrations"] as const
|
getIntegrations: () => ["integrations"] as const,
|
||||||
|
getIntegration: (id: string) => ["integration", id] as const
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchIntegrations = async () => {
|
const fetchIntegrations = async () => {
|
||||||
@@ -18,6 +20,14 @@ const fetchIntegrations = async () => {
|
|||||||
return data.integrationOptions;
|
return data.integrationOptions;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const fetchIntegration = async (id: string) => {
|
||||||
|
const { data } = await apiRequest.get<{ integration: TIntegrationWithEnv }>(
|
||||||
|
`/api/v1/integration/${id}`
|
||||||
|
);
|
||||||
|
|
||||||
|
return data.integration;
|
||||||
|
};
|
||||||
|
|
||||||
export const useGetCloudIntegrations = () =>
|
export const useGetCloudIntegrations = () =>
|
||||||
useQuery({
|
useQuery({
|
||||||
queryKey: integrationQueryKeys.getIntegrations(),
|
queryKey: integrationQueryKeys.getIntegrations(),
|
||||||
@@ -128,6 +138,14 @@ export const useDeleteIntegration = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useGetIntegration = (integrationId: string) => {
|
||||||
|
return useQuery({
|
||||||
|
enabled: Boolean(integrationId),
|
||||||
|
queryKey: integrationQueryKeys.getIntegration(integrationId),
|
||||||
|
queryFn: () => fetchIntegration(integrationId)
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export const useSyncIntegration = () => {
|
export const useSyncIntegration = () => {
|
||||||
return useMutation<{}, {}, { id: string; workspaceId: string; lastUsed: string }>({
|
return useMutation<{}, {}, { id: string; workspaceId: string; lastUsed: string }>({
|
||||||
mutationFn: ({ id }) => apiRequest.post(`/api/v1/integration/${id}/sync`),
|
mutationFn: ({ id }) => apiRequest.post(`/api/v1/integration/${id}/sync`),
|
||||||
|
|||||||
@@ -47,6 +47,14 @@ export type TIntegration = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TIntegrationWithEnv = TIntegration & {
|
||||||
|
environment: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
slug: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export enum IntegrationSyncBehavior {
|
export enum IntegrationSyncBehavior {
|
||||||
OVERWRITE_TARGET = "overwrite-target",
|
OVERWRITE_TARGET = "overwrite-target",
|
||||||
PREFER_TARGET = "prefer-target",
|
PREFER_TARGET = "prefer-target",
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import Head from "next/head";
|
||||||
|
|
||||||
|
import { IntegrationDetails } from "@app/views/IntegrationsPage/DetailsPage/DetailsPage";
|
||||||
|
|
||||||
|
export default function IntegrationsDetailsPage() {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Head>
|
||||||
|
<title>Integration Details | Infisical</title>
|
||||||
|
<link rel="icon" href="/infisical.ico" />
|
||||||
|
<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} />
|
||||||
|
</Head>
|
||||||
|
<IntegrationDetails />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
IntegrationsDetailsPage.requireAuth = true;
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
|
import { useRouter } from "next/router";
|
||||||
|
import { faChevronLeft, faEllipsis, faRefresh, faTrash } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { integrationSlugNameMapping } from "public/data/frequentConstants";
|
||||||
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
|
import { createNotification } from "@app/components/notifications";
|
||||||
|
import { OrgPermissionCan } from "@app/components/permissions";
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
Tooltip
|
||||||
|
} from "@app/components/v2";
|
||||||
|
import { OrgPermissionActions, OrgPermissionSubjects, useUser, useWorkspace } from "@app/context";
|
||||||
|
import { useGetIntegration } from "@app/hooks/api";
|
||||||
|
import { useSyncIntegration } from "@app/hooks/api/integrations/queries";
|
||||||
|
import { usePopUp } from "@app/hooks/usePopUp";
|
||||||
|
|
||||||
|
import { IntegrationDetailsSection } from "./components/IntegrationDetailsSection";
|
||||||
|
|
||||||
|
// import { UserDetailsSection, UserOrgMembershipModal, UserProjectsSection } from "./components";
|
||||||
|
|
||||||
|
export const IntegrationDetails = () => {
|
||||||
|
const router = useRouter();
|
||||||
|
const integrationId = router.query.integrationId as string;
|
||||||
|
|
||||||
|
const { data: integration } = useGetIntegration(integrationId);
|
||||||
|
|
||||||
|
const projectId = useWorkspace().currentWorkspace?.id;
|
||||||
|
const { mutateAsync: syncIntegration } = useSyncIntegration();
|
||||||
|
|
||||||
|
const { user } = useUser();
|
||||||
|
|
||||||
|
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
|
||||||
|
"removeMember",
|
||||||
|
"orgMembership",
|
||||||
|
"deactivateMember",
|
||||||
|
"upgradePlan"
|
||||||
|
] as const);
|
||||||
|
|
||||||
|
return integration ? (
|
||||||
|
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white">
|
||||||
|
<div className="mx-auto mb-6 w-full max-w-7xl py-6 px-6">
|
||||||
|
<Button
|
||||||
|
variant="link"
|
||||||
|
type="submit"
|
||||||
|
leftIcon={<FontAwesomeIcon icon={faChevronLeft} />}
|
||||||
|
onClick={() => {
|
||||||
|
router.push(`/integrations/${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">
|
||||||
|
<div className="mr-4 w-96">
|
||||||
|
<IntegrationDetailsSection integration={integration} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : null;
|
||||||
|
};
|
||||||
+241
@@ -0,0 +1,241 @@
|
|||||||
|
import { faCheckCircle, faCircleXmark } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { integrationSlugNameMapping } from "public/data/frequentConstants";
|
||||||
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
|
import { FormLabel } from "@app/components/v2";
|
||||||
|
import { IntegrationMappingBehavior, TIntegrationWithEnv } from "@app/hooks/api/integrations/types";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
integration: TIntegrationWithEnv;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const IntegrationDetailsSection = ({ 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":
|
||||||
|
case "terraform-cloud":
|
||||||
|
return "Project";
|
||||||
|
case "aws-secret-manager":
|
||||||
|
return "Secret";
|
||||||
|
case "aws-parameter-store":
|
||||||
|
case "rundeck":
|
||||||
|
return "Path";
|
||||||
|
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}aaaaa`;
|
||||||
|
|
||||||
|
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 (
|
||||||
|
["vercel", "netlify", "railway", "gitlab", "teamcity", "bitbucket"].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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
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>
|
||||||
|
<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 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>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
+7
-1
@@ -1,3 +1,6 @@
|
|||||||
|
/* eslint-disable jsx-a11y/click-events-have-key-events */
|
||||||
|
/* eslint-disable jsx-a11y/no-static-element-interactions */
|
||||||
|
import { useRouter } from "next/router";
|
||||||
import {
|
import {
|
||||||
faArrowRight,
|
faArrowRight,
|
||||||
faCalendarCheck,
|
faCalendarCheck,
|
||||||
@@ -28,9 +31,12 @@ export const ConfiguredIntegrationItem = ({
|
|||||||
onRemoveIntegration,
|
onRemoveIntegration,
|
||||||
onManualSyncIntegration
|
onManualSyncIntegration
|
||||||
}: IProps) => {
|
}: IProps) => {
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="max-w-8xl flex justify-between rounded-md border border-mineshaft-600 bg-mineshaft-800 p-3"
|
onClick={() => router.push(`/integrations/details/${integration.id}`)}
|
||||||
|
className="max-w-8xl flex cursor-pointer justify-between rounded-md border border-mineshaft-600 bg-mineshaft-800 p-3 transition-all hover:bg-mineshaft-700"
|
||||||
key={`integration-${integration?.id.toString()}`}
|
key={`integration-${integration?.id.toString()}`}
|
||||||
>
|
>
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
|
|||||||
Reference in New Issue
Block a user