mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 11:27:32 +00:00
feat: integration details page
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import Head from "next/head";
|
import Head from "next/head";
|
||||||
|
|
||||||
import { IntegrationDetails } from "@app/views/IntegrationsPage/DetailsPage/DetailsPage";
|
import { IntegrationDetailsPage } from "@app/views/IntegrationsPage/IntegrationDetailsPage";
|
||||||
|
|
||||||
export default function IntegrationsDetailsPage() {
|
export default function IntegrationsDetailsPage() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -15,7 +15,7 @@ export default function IntegrationsDetailsPage() {
|
|||||||
<meta property="og:title" content="Manage your .env files in seconds" />
|
<meta property="og:title" content="Manage your .env files in seconds" />
|
||||||
<meta name="og:description" content={t("integrations.description") as string} />
|
<meta name="og:description" content={t("integrations.description") as string} />
|
||||||
</Head>
|
</Head>
|
||||||
<IntegrationDetails />
|
<IntegrationDetailsPage />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+22
-17
@@ -5,7 +5,6 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|||||||
import { integrationSlugNameMapping } from "public/data/frequentConstants";
|
import { integrationSlugNameMapping } from "public/data/frequentConstants";
|
||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
import { createNotification } from "@app/components/notifications";
|
|
||||||
import { OrgPermissionCan } from "@app/components/permissions";
|
import { OrgPermissionCan } from "@app/components/permissions";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
@@ -15,32 +14,32 @@ import {
|
|||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
Tooltip
|
Tooltip
|
||||||
} from "@app/components/v2";
|
} from "@app/components/v2";
|
||||||
import { OrgPermissionActions, OrgPermissionSubjects, useUser, useWorkspace } from "@app/context";
|
import {
|
||||||
|
OrgPermissionActions,
|
||||||
|
OrgPermissionSubjects,
|
||||||
|
useOrganization,
|
||||||
|
useUser,
|
||||||
|
useWorkspace
|
||||||
|
} from "@app/context";
|
||||||
import { useGetIntegration } from "@app/hooks/api";
|
import { useGetIntegration } from "@app/hooks/api";
|
||||||
import { useSyncIntegration } from "@app/hooks/api/integrations/queries";
|
import { useSyncIntegration } from "@app/hooks/api/integrations/queries";
|
||||||
import { usePopUp } from "@app/hooks/usePopUp";
|
|
||||||
|
|
||||||
|
import { IntegrationAuditLogsSection } from "./components/IntegrationAuditLogsSection";
|
||||||
|
import { IntegrationConnectionSection } from "./components/IntegrationConnectionSection";
|
||||||
import { IntegrationDetailsSection } from "./components/IntegrationDetailsSection";
|
import { IntegrationDetailsSection } from "./components/IntegrationDetailsSection";
|
||||||
|
import { IntegrationSettingsSection } from "./components/IntegrationSettingsSection";
|
||||||
|
|
||||||
// import { UserDetailsSection, UserOrgMembershipModal, UserProjectsSection } from "./components";
|
export const IntegrationDetailsPage = () => {
|
||||||
|
|
||||||
export const IntegrationDetails = () => {
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const integrationId = router.query.integrationId as string;
|
const integrationId = router.query.integrationId as string;
|
||||||
|
|
||||||
const { data: integration } = useGetIntegration(integrationId);
|
const { data: integration } = useGetIntegration(integrationId, {
|
||||||
|
refetchInterval: 4000
|
||||||
|
});
|
||||||
|
|
||||||
const projectId = useWorkspace().currentWorkspace?.id;
|
const projectId = useWorkspace().currentWorkspace?.id;
|
||||||
const { mutateAsync: syncIntegration } = useSyncIntegration();
|
const { mutateAsync: syncIntegration } = useSyncIntegration();
|
||||||
|
const { currentOrg } = useOrganization();
|
||||||
const { user } = useUser();
|
|
||||||
|
|
||||||
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
|
|
||||||
"removeMember",
|
|
||||||
"orgMembership",
|
|
||||||
"deactivateMember",
|
|
||||||
"upgradePlan"
|
|
||||||
] as const);
|
|
||||||
|
|
||||||
return integration ? (
|
return integration ? (
|
||||||
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white">
|
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white">
|
||||||
@@ -104,9 +103,15 @@ export const IntegrationDetails = () => {
|
|||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex">
|
|
||||||
|
<div className="flex justify-center">
|
||||||
<div className="mr-4 w-96">
|
<div className="mr-4 w-96">
|
||||||
<IntegrationDetailsSection integration={integration} />
|
<IntegrationDetailsSection integration={integration} />
|
||||||
|
<IntegrationConnectionSection integration={integration} />
|
||||||
|
</div>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<IntegrationSettingsSection integration={integration} />
|
||||||
|
<IntegrationAuditLogsSection orgId={currentOrg?.id || ""} integration={integration} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
+78
@@ -0,0 +1,78 @@
|
|||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
|
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/views/Project/AuditLogsPage/components";
|
||||||
|
|
||||||
|
// Add more events if needed
|
||||||
|
const INTEGRATION_EVENTS = [EventType.INTEGRATION_SYNCED];
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
integration: TIntegrationWithEnv;
|
||||||
|
orgId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const IntegrationAuditLogsSection = ({ integration, orgId }: Props) => {
|
||||||
|
const { subscription, isLoading } = useSubscription();
|
||||||
|
|
||||||
|
const auditLogsRetentionDays = subscription?.auditLogsRetentionDays ?? 30;
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-nested-ternary
|
||||||
|
return subscription?.auditLogs ? (
|
||||||
|
<div className="h-full 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 Logs</p>
|
||||||
|
<p className="text-xs text-gray-400">
|
||||||
|
View integration logs from the past {auditLogsRetentionDays} days
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<LogsSection
|
||||||
|
refetchInterval={4000}
|
||||||
|
remappedHeaders={{
|
||||||
|
Metadata: "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>
|
||||||
|
) : !isLoading ? (
|
||||||
|
<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 Logs</p>
|
||||||
|
</div>
|
||||||
|
<EmptyState
|
||||||
|
className="rounded-lg"
|
||||||
|
title={
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
Please{" "}
|
||||||
|
<Link
|
||||||
|
href={
|
||||||
|
subscription && subscription.slug !== null
|
||||||
|
? `/org${orgId}/billing`
|
||||||
|
: "https://infisical.com/scheduledemo"
|
||||||
|
}
|
||||||
|
passHref
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
className="cursor-pointer font-medium text-primary-500 transition-all hover:text-primary-600"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
upgrade your subscription
|
||||||
|
</a>
|
||||||
|
</Link>{" "}
|
||||||
|
to view integration logs
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : null;
|
||||||
|
};
|
||||||
+26
-73
@@ -1,7 +1,4 @@
|
|||||||
import { faCheckCircle, faCircleXmark } from "@fortawesome/free-solid-svg-icons";
|
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
||||||
import { integrationSlugNameMapping } from "public/data/frequentConstants";
|
import { integrationSlugNameMapping } from "public/data/frequentConstants";
|
||||||
import { twMerge } from "tailwind-merge";
|
|
||||||
|
|
||||||
import { FormLabel } from "@app/components/v2";
|
import { FormLabel } from "@app/components/v2";
|
||||||
import { IntegrationMappingBehavior, TIntegrationWithEnv } from "@app/hooks/api/integrations/types";
|
import { IntegrationMappingBehavior, TIntegrationWithEnv } from "@app/hooks/api/integrations/types";
|
||||||
@@ -10,7 +7,7 @@ type Props = {
|
|||||||
integration: TIntegrationWithEnv;
|
integration: TIntegrationWithEnv;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const IntegrationDetailsSection = ({ integration }: Props) => {
|
export const IntegrationConnectionSection = ({ integration }: Props) => {
|
||||||
const specifcQoveryDetails = () => {
|
const specifcQoveryDetails = () => {
|
||||||
if (integration.integration !== "qovery") return null;
|
if (integration.integration !== "qovery") return null;
|
||||||
|
|
||||||
@@ -79,7 +76,7 @@ export const IntegrationDetailsSection = ({ integration }: Props) => {
|
|||||||
|
|
||||||
case "aws-parameter-store":
|
case "aws-parameter-store":
|
||||||
case "rundeck":
|
case "rundeck":
|
||||||
return `${integration.path}aaaaa`;
|
return `${integration.path}`;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return `${integration.app}`;
|
return `${integration.app}`;
|
||||||
@@ -160,80 +157,36 @@ export const IntegrationDetailsSection = ({ integration }: Props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="mt-4 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||||
<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">
|
||||||
<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>
|
||||||
<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>
|
||||||
|
|
||||||
<div className="mt-4 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
<div className="mt-4">
|
||||||
<div className="flex items-center justify-between border-b border-mineshaft-400 pb-4">
|
<FormLabel className="my-2" label="Source" />
|
||||||
<h3 className="text-lg font-semibold text-mineshaft-100">Connection</h3>
|
|
||||||
|
<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>
|
</div>
|
||||||
|
|
||||||
<div className="mt-4">
|
<FormLabel className="my-2" label="Destination" />
|
||||||
<FormLabel className="my-2" label="Source" />
|
<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="space-y-2 rounded-lg border border-mineshaft-700 bg-mineshaft-800 p-2">
|
<div className="text-sm text-mineshaft-300">
|
||||||
<div className="flex flex-col">
|
{integrationSlugNameMapping[integration.integration]}
|
||||||
<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>
|
</div>
|
||||||
|
|
||||||
<FormLabel className="my-2" label="Destination" />
|
{specifcQoveryDetails()}
|
||||||
<div className="space-y-2 rounded-lg border border-mineshaft-700 bg-mineshaft-800 p-2">
|
{isNotAwsManagerOneToOneDetails()}
|
||||||
<FormLabel className="text-sm font-semibold text-mineshaft-300" label="Platform" />
|
{targetEnvironmentDetails()}
|
||||||
<div className="text-sm text-mineshaft-300">
|
{generalIntegrationSpecificDetails()}
|
||||||
{integrationSlugNameMapping[integration.integration]}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{specifcQoveryDetails()}
|
|
||||||
{isNotAwsManagerOneToOneDetails()}
|
|
||||||
{targetEnvironmentDetails()}
|
|
||||||
{generalIntegrationSpecificDetails()}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
+69
@@ -0,0 +1,69 @@
|
|||||||
|
import { faCalendarCheck, faCheckCircle, faCircleXmark } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { format } from "date-fns";
|
||||||
|
import { integrationSlugNameMapping } from "public/data/frequentConstants";
|
||||||
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
|
import { TIntegrationWithEnv } from "@app/hooks/api/integrations/types";
|
||||||
|
|
||||||
|
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="pt-0.5 pr-2 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>
|
||||||
|
);
|
||||||
|
};
|
||||||
+89
@@ -0,0 +1,89 @@
|
|||||||
|
import { TIntegrationWithEnv } from "@app/hooks/api/integrations/types";
|
||||||
|
|
||||||
|
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",
|
||||||
|
kmsKeyId: "AWS KMS Key ID",
|
||||||
|
secretSuffix: "Secret Suffix",
|
||||||
|
secretPrefix: "Secret Prefix",
|
||||||
|
syncBehavior: "Secrets 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"
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const IntegrationSettingsSection = ({ integration }: Props) => {
|
||||||
|
const renderValue = <K extends MetadataKey>(key: K, value: MetadataValue<K>) => {
|
||||||
|
if (!value) return null;
|
||||||
|
|
||||||
|
// 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.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">
|
||||||
|
{metadataMappings[key as keyof typeof metadataMappings]}
|
||||||
|
</p>
|
||||||
|
<p className="text-sm text-gray-200">{renderValue(key as MetadataKey, value)}</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { IntegrationDetailsPage } from "./IntegrationDetailsPage";
|
||||||
Reference in New Issue
Block a user