+ {integration.integration === "octopus-deploy" && (
+
+
+
+ {integration.targetEnvironment || integration.targetEnvironmentId}
+
+
+ )}
+ {integration.integration === "qovery" && (
+ <>
+
+
+
{integration?.owner || "-"}
+
+
+
+
{integration?.targetService || "-"}
+
+
+
+
{integration?.targetEnvironment || "-"}
+
+ >
+ )}
+ {!(
+ integration.integration === "aws-secret-manager" &&
+ integration.metadata?.mappingBehavior === IntegrationMappingBehavior.ONE_TO_ONE
+ ) && (
+
+
+
+ {(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}
+
+
+ )}
+ {(integration.integration === "vercel" ||
+ integration.integration === "netlify" ||
+ integration.integration === "railway" ||
+ integration.integration === "gitlab" ||
+ integration.integration === "teamcity" ||
+ (integration.integration === "github" && integration.scope === "github-env")) && (
+
+
+
+ {integration.targetEnvironment || integration.targetEnvironmentId}
+
+
+ )}
+ {integration.integration === "bitbucket" && (
+ <>
+ {integration.targetServiceId && (
+
+
+
+ {integration.targetService || integration.targetServiceId}
+
+
+ )}
+
+
+
+ {integration.targetEnvironment || integration.targetEnvironmentId}
+
+
+ >
+ )}
+ {integration.integration === "checkly" && integration.targetService && (
+
+
+
{integration.targetService}
+
+ )}
+ {integration.integration === "circleci" && integration.owner && (
+
+
+
{integration.owner}
+
+ )}
+ {integration.integration === "terraform-cloud" && integration.targetService && (
+
+
+
{integration.targetService}
+
+ )}
+ {(integration.integration === "checkly" || integration.integration === "github") && (
+
+
+
{integration?.metadata?.secretSuffix || "-"}
+
+ )}
+ {integration.integration === "github" && integration.metadata?.githubVisibility ? (
+
+ {/* 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"}
+
+ ) : undefined}
+
+ );
+};
diff --git a/frontend/src/views/IntegrationsPage/components/IntegrationsSection/components/IntegrationRow.tsx b/frontend/src/views/IntegrationsPage/components/IntegrationsSection/components/IntegrationRow.tsx
new file mode 100644
index 000000000..5579c7f76
--- /dev/null
+++ b/frontend/src/views/IntegrationsPage/components/IntegrationsSection/components/IntegrationRow.tsx
@@ -0,0 +1,191 @@
+import { useMemo } from "react";
+import { useRouter } from "next/router";
+import {
+ faCalendarCheck,
+ faCheck,
+ faInfoCircle,
+ faRefresh,
+ faTrash,
+ faWarning,
+ faXmark
+} from "@fortawesome/free-solid-svg-icons";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { format } from "date-fns";
+
+import { ProjectPermissionCan } from "@app/components/permissions";
+import { Badge, IconButton, Td, Tooltip, Tr } from "@app/components/v2";
+import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
+import { TCloudIntegration } from "@app/hooks/api/integrations/types";
+import { TIntegration } from "@app/hooks/api/types";
+
+import { 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 router = useRouter();
+
+ const { id, secretPath, syncMessage, isSynced } = integration;
+
+ const failureMessage = useMemo(() => {
+ if (isSynced === false) {
+ if (syncMessage)
+ try {
+ // format if json
+ return JSON.stringify(JSON.parse(syncMessage), null, 2);
+ } catch (e) {
+ return syncMessage;
+ }
+
+ return "An Unknown Error Occurred.";
+ }
+ return null;
+ }, [isSynced, syncMessage]);
+
+ return (
+ router.push(`/integrations/details/${integration.id}`)}
+ className="group h-10 cursor-pointer transition-colors duration-100 hover:bg-mineshaft-700"
+ key={`integration-${id}`}
+ >
+
+
+ 
+ {cloudIntegration?.name}
+
+ |
+
+
+ {secretPath}
+ {" "}
+ |
+ {environment?.name ?? "-"} |
+
+
+
+ {(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}
+
+ }
+ >
+
+
+
+ |
+
+ {" "}
+ {typeof integration.isSynced !== "boolean" ? (
+
+ Pending Sync
+
+ ) : (
+
+ {integration.lastUsed && (
+
+
+
+ {format(new Date(integration.lastUsed!), "yyyy-MM-dd, hh:mm aaa")}
+
+
+ )}
+ {failureMessage && (
+
+ )}
+
+ }
+ >
+
+
+
+
+ {integration.isSynced ? "Synced" : "Not Synced"}
+
+
+
+
+ )}
+ |
+
+
+
+ {
+ e.stopPropagation();
+ onManualSyncIntegration();
+ }}
+ ariaLabel="sync"
+ colorSchema="secondary"
+ variant="plain"
+ >
+
+
+
+
+ {(isAllowed: boolean) => (
+
+ {
+ e.stopPropagation();
+ onRemoveIntegration();
+ }}
+ ariaLabel="delete"
+ isDisabled={!isAllowed}
+ colorSchema="danger"
+ variant="plain"
+ >
+
+
+
+ )}
+
+
+ |
+
+ );
+};
diff --git a/frontend/src/views/IntegrationsPage/components/IntegrationsSection/components/IntegrationsTable.tsx b/frontend/src/views/IntegrationsPage/components/IntegrationsSection/components/IntegrationsTable.tsx
new file mode 100644
index 000000000..f61624451
--- /dev/null
+++ b/frontend/src/views/IntegrationsPage/components/IntegrationsSection/components/IntegrationsTable.tsx
@@ -0,0 +1,415 @@
+import { 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 { 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"
+}
+
+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