diff --git a/frontend/src/pages/integrations/details/[integrationId].tsx b/frontend/src/pages/integrations/details/[integrationId].tsx index d437dabd4..f0db57472 100644 --- a/frontend/src/pages/integrations/details/[integrationId].tsx +++ b/frontend/src/pages/integrations/details/[integrationId].tsx @@ -1,7 +1,7 @@ import { useTranslation } from "react-i18next"; import Head from "next/head"; -import { IntegrationDetails } from "@app/views/IntegrationsPage/DetailsPage/DetailsPage"; +import { IntegrationDetailsPage } from "@app/views/IntegrationsPage/IntegrationDetailsPage"; export default function IntegrationsDetailsPage() { const { t } = useTranslation(); @@ -15,7 +15,7 @@ export default function IntegrationsDetailsPage() { - + ); } diff --git a/frontend/src/views/IntegrationsPage/DetailsPage/index.tsx b/frontend/src/views/IntegrationsPage/DetailsPage/index.tsx deleted file mode 100644 index e69de29bb..000000000 diff --git a/frontend/src/views/IntegrationsPage/DetailsPage/DetailsPage.tsx b/frontend/src/views/IntegrationsPage/IntegrationDetailsPage/IntegrationDetailsPage.tsx similarity index 80% rename from frontend/src/views/IntegrationsPage/DetailsPage/DetailsPage.tsx rename to frontend/src/views/IntegrationsPage/IntegrationDetailsPage/IntegrationDetailsPage.tsx index c645f0f39..6fe2a1562 100644 --- a/frontend/src/views/IntegrationsPage/DetailsPage/DetailsPage.tsx +++ b/frontend/src/views/IntegrationsPage/IntegrationDetailsPage/IntegrationDetailsPage.tsx @@ -5,7 +5,6 @@ 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, @@ -15,32 +14,32 @@ import { DropdownMenuTrigger, Tooltip } 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 { 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 { IntegrationSettingsSection } from "./components/IntegrationSettingsSection"; -// import { UserDetailsSection, UserOrgMembershipModal, UserProjectsSection } from "./components"; - -export const IntegrationDetails = () => { +export const IntegrationDetailsPage = () => { const router = useRouter(); 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 { mutateAsync: syncIntegration } = useSyncIntegration(); - - const { user } = useUser(); - - const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([ - "removeMember", - "orgMembership", - "deactivateMember", - "upgradePlan" - ] as const); + const { currentOrg } = useOrganization(); return integration ? (
@@ -104,9 +103,15 @@ export const IntegrationDetails = () => {
-
+ +
+ +
+
+ +
diff --git a/frontend/src/views/IntegrationsPage/IntegrationDetailsPage/components/IntegrationAuditLogsSection.tsx b/frontend/src/views/IntegrationsPage/IntegrationDetailsPage/components/IntegrationAuditLogsSection.tsx new file mode 100644 index 000000000..2d1c64db5 --- /dev/null +++ b/frontend/src/views/IntegrationsPage/IntegrationDetailsPage/components/IntegrationAuditLogsSection.tsx @@ -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 ? ( +
+
+

Integration Logs

+

+ View integration logs from the past {auditLogsRetentionDays} days +

+
+ +
+ ) : !isLoading ? ( +
+
+

Integration Logs

+
+ +

+ Please{" "} + + + upgrade your subscription + + {" "} + to view integration logs +

+
+ } + /> + + ) : null; +}; diff --git a/frontend/src/views/IntegrationsPage/DetailsPage/components/IntegrationDetailsSection.tsx b/frontend/src/views/IntegrationsPage/IntegrationDetailsPage/components/IntegrationConnectionSection.tsx similarity index 59% rename from frontend/src/views/IntegrationsPage/DetailsPage/components/IntegrationDetailsSection.tsx rename to frontend/src/views/IntegrationsPage/IntegrationDetailsPage/components/IntegrationConnectionSection.tsx index c7ab28e0c..7aa862593 100644 --- a/frontend/src/views/IntegrationsPage/DetailsPage/components/IntegrationDetailsSection.tsx +++ b/frontend/src/views/IntegrationsPage/IntegrationDetailsPage/components/IntegrationConnectionSection.tsx @@ -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 { twMerge } from "tailwind-merge"; import { FormLabel } from "@app/components/v2"; import { IntegrationMappingBehavior, TIntegrationWithEnv } from "@app/hooks/api/integrations/types"; @@ -10,7 +7,7 @@ type Props = { integration: TIntegrationWithEnv; }; -export const IntegrationDetailsSection = ({ integration }: Props) => { +export const IntegrationConnectionSection = ({ integration }: Props) => { const specifcQoveryDetails = () => { if (integration.integration !== "qovery") return null; @@ -79,7 +76,7 @@ export const IntegrationDetailsSection = ({ integration }: Props) => { case "aws-parameter-store": case "rundeck": - return `${integration.path}aaaaa`; + return `${integration.path}`; default: return `${integration.app}`; @@ -160,80 +157,36 @@ export const IntegrationDetailsSection = ({ integration }: Props) => { }; return ( -
-
-
-

Integration Details

-
-
-
-
-

Name

-

- {integrationSlugNameMapping[integration.integration]} -

-
-
-

Sync Status

-
-

- {integration.isSynced ? "Synced" : "Not Synced"} -

- -
-
-
- {!integration.isSynced && integration.syncMessage && ( - <> -

Latest sync error

-

{integration.syncMessage}

- - )} -
-
-
+
+
+

Connection

-
-
-

Connection

+
+ + +
+
+ +
{integration.environment.name}
+
+
+ +
{integration.secretPath}
+
-
- - -
-
- -
{integration.environment.name}
-
-
- -
{integration.secretPath}
-
+ +
+ +
+ {integrationSlugNameMapping[integration.integration]}
- -
- -
- {integrationSlugNameMapping[integration.integration]} -
- - {specifcQoveryDetails()} - {isNotAwsManagerOneToOneDetails()} - {targetEnvironmentDetails()} - {generalIntegrationSpecificDetails()} -
+ {specifcQoveryDetails()} + {isNotAwsManagerOneToOneDetails()} + {targetEnvironmentDetails()} + {generalIntegrationSpecificDetails()}
diff --git a/frontend/src/views/IntegrationsPage/IntegrationDetailsPage/components/IntegrationDetailsSection.tsx b/frontend/src/views/IntegrationsPage/IntegrationDetailsPage/components/IntegrationDetailsSection.tsx new file mode 100644 index 000000000..d2ffeb906 --- /dev/null +++ b/frontend/src/views/IntegrationsPage/IntegrationDetailsPage/components/IntegrationDetailsSection.tsx @@ -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 ( +
+
+
+

Integration Details

+
+
+
+
+

Name

+

+ {integrationSlugNameMapping[integration.integration]} +

+
+
+

Sync Status

+
+

+ {integration.isSynced ? "Synced" : "Not Synced"} +

+ +
+
+ {integration.lastUsed && ( +
+

Latest Successful Sync

+
+ {format(new Date(integration.lastUsed), "yyyy-MM-dd, hh:mm aaa")} + +
+
+ )} + +
+ {!integration.isSynced && integration.syncMessage && ( + <> +

Latest Sync Error

+

{integration.syncMessage}

+ + )} +
+
+
+
+
+ ); +}; diff --git a/frontend/src/views/IntegrationsPage/IntegrationDetailsPage/components/IntegrationSettingsSection.tsx b/frontend/src/views/IntegrationsPage/IntegrationDetailsPage/components/IntegrationSettingsSection.tsx new file mode 100644 index 000000000..50c638b66 --- /dev/null +++ b/frontend/src/views/IntegrationsPage/IntegrationDetailsPage/components/IntegrationSettingsSection.tsx @@ -0,0 +1,89 @@ +import { TIntegrationWithEnv } from "@app/hooks/api/integrations/types"; + +type Props = { + integration: TIntegrationWithEnv; +}; + +type Metadata = NonNullable; +type MetadataKey = keyof Metadata; +type MetadataValue = Metadata[K]; + +const metadataMappings: Record, 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 = (key: K, value: MetadataValue) => { + 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 }) => ( +

+ {tagKey}={tagValue} +

+ )); + } + + 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 ( +
+
+

Integration Settings

+
+
+ {integration.metadata && + Object.entries(integration.metadata).map(([key, value]) => ( +
+

+ {metadataMappings[key as keyof typeof metadataMappings]} +

+

{renderValue(key as MetadataKey, value)}

+
+ ))} +
+
+ ); +}; diff --git a/frontend/src/views/IntegrationsPage/IntegrationDetailsPage/index.tsx b/frontend/src/views/IntegrationsPage/IntegrationDetailsPage/index.tsx new file mode 100644 index 000000000..145a6e2e0 --- /dev/null +++ b/frontend/src/views/IntegrationsPage/IntegrationDetailsPage/index.tsx @@ -0,0 +1 @@ +export { IntegrationDetailsPage } from "./IntegrationDetailsPage";