diff --git a/ui/changelog.d/imported-provider-provenance.added.md b/ui/changelog.d/imported-provider-provenance.added.md
new file mode 100644
index 0000000000..79999a976a
--- /dev/null
+++ b/ui/changelog.d/imported-provider-provenance.added.md
@@ -0,0 +1 @@
+Prowler Cloud indicator for providers created via Import Findings alongside every connection status
diff --git a/ui/components/providers/table/column-providers.test.tsx b/ui/components/providers/table/column-providers.test.tsx
index 89e7aea791..27e76f3b3b 100644
--- a/ui/components/providers/table/column-providers.test.tsx
+++ b/ui/components/providers/table/column-providers.test.tsx
@@ -1,6 +1,7 @@
import { render, screen } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
import type { ReactNode } from "react";
-import { describe, expect, it, vi } from "vitest";
+import { afterEach, describe, expect, it, vi } from "vitest";
import {
PROVIDERS_ROW_TYPE,
@@ -10,9 +11,18 @@ import {
import { getColumnProviders } from "./column-providers";
-vi.mock("@/components/shadcn", () => ({
- Badge: ({ children }: { children: ReactNode }) => {children},
-}));
+vi.mock("@/components/shadcn", async () => {
+ const tooltip = await vi.importActual<
+ typeof import("@/components/shadcn/tooltip")
+ >("@/components/shadcn/tooltip");
+
+ return {
+ Badge: ({ children, ...props }: { children: ReactNode }) => (
+ {children}
+ ),
+ ...tooltip,
+ };
+});
vi.mock("@/components/shadcn/checkbox/checkbox", () => ({
Checkbox: () => null,
@@ -64,6 +74,7 @@ const providerRow: ProvidersProviderRow = {
attributes: {
provider: "aws",
is_dynamic: false,
+ is_imported: false,
uid: "123456789012",
alias: "Production",
status: "completed",
@@ -115,6 +126,33 @@ function renderLastScanCell(row: ProvidersTableRow) {
render(<>{element as ReactNode}>);
}
+function renderStatusCell(row: ProvidersTableRow) {
+ const statusColumn = getColumnProviders(
+ {},
+ [],
+ [],
+ [],
+ vi.fn(),
+ vi.fn(),
+ vi.fn(),
+ ).find((column) => column.id === "status");
+
+ const cell = statusColumn?.cell;
+ if (typeof cell !== "function") {
+ throw new Error("Status column cell renderer not found");
+ }
+
+ const element = cell({
+ row: { original: row },
+ } as unknown as Parameters[0]);
+
+ render(<>{element as ReactNode}>);
+}
+
+afterEach(() => {
+ vi.unstubAllEnvs();
+});
+
describe("getColumnProviders", () => {
it("falls back to connection last_checked_at when lastScanAt is undefined", () => {
renderLastScanCell({ ...providerRow, lastScanAt: undefined });
@@ -129,4 +167,133 @@ describe("getColumnProviders", () => {
expect(screen.getByText("Never")).toBeVisible();
expect(screen.queryByText("2026-01-01T00:00:00Z")).not.toBeInTheDocument();
});
+
+ it("shows Not connected with the imported tooltip in Cloud", async () => {
+ // Given
+ vi.stubEnv("UI_CLOUD_ENABLED", "true");
+ const user = userEvent.setup();
+ const importedProvider = {
+ ...providerRow,
+ attributes: {
+ ...providerRow.attributes,
+ is_imported: true,
+ connection: {
+ connected: null,
+ last_checked_at: null,
+ },
+ },
+ relationships: {
+ ...providerRow.relationships,
+ secret: { data: null },
+ },
+ } satisfies ProvidersProviderRow;
+
+ // When
+ renderStatusCell(importedProvider);
+
+ // Then
+ expect(screen.getByText("Not connected")).toBeVisible();
+ const indicator = screen.getByLabelText("Imported provider");
+ expect(indicator).not.toHaveTextContent("Imported");
+ expect(indicator).toHaveAttribute("tabindex", "0");
+ expect(indicator.querySelector(".lucide-terminal")).toBeInTheDocument();
+ expect(indicator.parentElement).toHaveClass("items-stretch");
+ expect(screen.queryByRole("tooltip")).not.toBeInTheDocument();
+ expect(screen.queryByText("Disconnected")).not.toBeInTheDocument();
+
+ await user.hover(indicator);
+
+ expect(await screen.findByRole("tooltip")).toHaveTextContent(
+ /^This provider has findings imported with the Prowler CLI$/,
+ );
+ });
+
+ it("shows the imported tooltip beside Connected in Cloud", async () => {
+ // Given
+ vi.stubEnv("UI_CLOUD_ENABLED", "true");
+ const user = userEvent.setup();
+ const connectedImportedProvider = {
+ ...providerRow,
+ attributes: {
+ ...providerRow.attributes,
+ is_imported: true,
+ connection: {
+ connected: true,
+ last_checked_at: "2026-01-01T00:00:00Z",
+ },
+ },
+ } satisfies ProvidersProviderRow;
+
+ // When
+ renderStatusCell(connectedImportedProvider);
+
+ // Then
+ expect(screen.getByText("Connected")).toBeVisible();
+ const indicator = screen.getByLabelText("Imported provider");
+ expect(indicator).toBeVisible();
+ expect(indicator.querySelector(".lucide-terminal")).toBeInTheDocument();
+ expect(indicator.parentElement).toHaveClass("items-stretch");
+ expect(indicator).not.toHaveTextContent("Imported");
+ expect(screen.queryByRole("tooltip")).not.toBeInTheDocument();
+
+ await user.hover(indicator);
+
+ expect(await screen.findByRole("tooltip")).toHaveTextContent(
+ /^This provider has findings imported with the Prowler CLI$/,
+ );
+ });
+
+ it("keeps Connection failed for imported providers in Cloud", () => {
+ // Given
+ vi.stubEnv("UI_CLOUD_ENABLED", "true");
+ const importedProviderWithCredentials = {
+ ...providerRow,
+ attributes: {
+ ...providerRow.attributes,
+ is_imported: true,
+ connection: {
+ connected: false,
+ last_checked_at: null,
+ },
+ },
+ } satisfies ProvidersProviderRow;
+
+ // When
+ renderStatusCell(importedProviderWithCredentials);
+
+ // Then
+ expect(screen.getByText("Connection failed")).toBeVisible();
+ expect(screen.queryByText("Not connected")).not.toBeInTheDocument();
+ const indicator = screen.getByLabelText("Imported provider");
+ expect(indicator).not.toHaveTextContent("Imported");
+ });
+
+ it("does not expose imported provenance in OSS", () => {
+ // Given
+ vi.stubEnv("UI_CLOUD_ENABLED", "false");
+ const importedProvider = {
+ ...providerRow,
+ attributes: {
+ ...providerRow.attributes,
+ is_imported: true,
+ connection: {
+ connected: null,
+ last_checked_at: null,
+ },
+ },
+ relationships: {
+ ...providerRow.relationships,
+ secret: { data: null },
+ },
+ } satisfies ProvidersProviderRow;
+
+ // When
+ renderStatusCell(importedProvider);
+
+ // Then
+ expect(screen.getByText("Not connected")).toBeVisible();
+ expect(
+ screen.queryByLabelText("Imported provider"),
+ ).not.toBeInTheDocument();
+ });
});
diff --git a/ui/components/providers/table/column-providers.tsx b/ui/components/providers/table/column-providers.tsx
index c8ee2407e4..78c59ac764 100644
--- a/ui/components/providers/table/column-providers.tsx
+++ b/ui/components/providers/table/column-providers.tsx
@@ -1,13 +1,18 @@
"use client";
import { ColumnDef, Row, RowSelectionState } from "@tanstack/react-table";
-import { Building2, FolderTree } from "lucide-react";
+import { Building2, FolderTree, Terminal } from "lucide-react";
import type {
OrgWizardInitialData,
ProviderWizardInitialData,
} from "@/components/providers/wizard/types";
-import { Badge } from "@/components/shadcn";
+import {
+ Badge,
+ Tooltip,
+ TooltipContent,
+ TooltipTrigger,
+} from "@/components/shadcn";
import { Checkbox } from "@/components/shadcn/checkbox/checkbox";
import { CodeSnippet } from "@/components/shadcn/code-snippet/code-snippet";
import { DateWithTime, EntityInfo } from "@/components/shadcn/entities";
@@ -15,6 +20,7 @@ import { DataTableColumnHeader } from "@/components/shadcn/table";
import { DataTableExpandAllToggle } from "@/components/shadcn/table/data-table-expand-all-toggle";
import { DataTableExpandableCell } from "@/components/shadcn/table/data-table-expandable-cell";
import { getNodeLabel } from "@/lib/organizations";
+import { isCloud } from "@/lib/shared/env";
import {
isProvidersOrganizationRow,
PROVIDERS_GROUP_KIND,
@@ -51,28 +57,70 @@ const OrganizationIcon = ({ groupKind }: { groupKind: ProvidersGroupKind }) => {
);
};
-const ProviderStatusCell = ({ connected }: { connected: boolean | null }) => {
+interface ProviderStatusCellProps {
+ connected: boolean | null;
+ isImported: boolean;
+}
+
+const IMPORTED_PROVIDER_TOOLTIP =
+ "This provider has findings imported with the Prowler CLI";
+
+const ImportedIndicator = () => {
+ const indicator = (
+
+
+
+ );
+
+ return (
+
+ {indicator}
+ {IMPORTED_PROVIDER_TOOLTIP}
+
+ );
+};
+
+const ProviderStatusCell = ({
+ connected,
+ isImported,
+}: ProviderStatusCellProps) => {
+ let statusBadge;
+
if (connected === true) {
- return (
+ statusBadge = (
Connected
);
- }
-
- if (connected === false) {
- return (
+ } else if (connected === false) {
+ statusBadge = (
Connection failed
);
+ } else {
+ statusBadge = (
+
+ Not connected
+
+ );
}
- return (
-
- Not connected
-
- );
+ if (isImported) {
+ return (
+
+ {statusBadge}
+
+
+ );
+ }
+
+ return statusBadge;
};
function getSelectionLabel(row: Row): string | undefined {
@@ -299,6 +347,9 @@ export function getColumnProviders(
return (
);
},
diff --git a/ui/types/providers.ts b/ui/types/providers.ts
index def1f9f004..f6f898ce48 100644
--- a/ui/types/providers.ts
+++ b/ui/types/providers.ts
@@ -80,13 +80,15 @@ export interface ProviderProps {
attributes: {
provider: ProviderType;
is_dynamic: boolean;
+ /** Import Findings provenance returned only by the Prowler Cloud API. */
+ is_imported?: boolean;
uid: string;
alias: string;
status: "completed" | "pending" | "cancelled";
resources: number;
connection: {
- connected: boolean;
- last_checked_at: string;
+ connected: boolean | null;
+ last_checked_at: string | null;
};
scanner_args: {
only_logs: boolean;