diff --git a/ui/app/(prowler)/scans/page.test.ts b/ui/app/(prowler)/scans/page.test.ts
index 08c36aaf46..1f90105920 100644
--- a/ui/app/(prowler)/scans/page.test.ts
+++ b/ui/app/(prowler)/scans/page.test.ts
@@ -9,11 +9,18 @@ describe("scans page onboarding", () => {
const pagePath = path.join(currentDir, "page.tsx");
const source = readFileSync(pagePath, "utf8");
- it("redirects the scan tour replay to add-provider when providers are missing or disconnected", () => {
- expect(source).toContain('redirect("/providers?onboarding=add-provider")');
- expect(source).toContain(
- 'resolvedSearchParams.onboarding === "view-first-scan"',
+ it("never blocks or redirects away from Scans when providers are missing or disconnected", () => {
+ // Imported scans belong to a never-connected provider, so the page must render the
+ // table regardless of provider connection status instead of gating/redirecting.
+ expect(source).not.toContain(
+ 'redirect("/providers?onboarding=add-provider")',
);
+ expect(source).not.toContain("missingScanPrerequisite");
+ });
+
+ it("always renders the scans table shell", () => {
+ expect(source).toContain(" {
diff --git a/ui/app/(prowler)/scans/page.tsx b/ui/app/(prowler)/scans/page.tsx
index f307920e52..5bbfbdf213 100644
--- a/ui/app/(prowler)/scans/page.tsx
+++ b/ui/app/(prowler)/scans/page.tsx
@@ -1,4 +1,3 @@
-import { redirect } from "next/navigation";
import { Suspense } from "react";
import { getAllProviderGroups } from "@/actions/manage-groups/manage-groups";
@@ -10,7 +9,6 @@ import {
} from "@/actions/scans/scans-filters";
import { getSchedules, getSchedulesPage } from "@/actions/schedules";
import { auth } from "@/auth.config";
-import { PageReady } from "@/components/onboarding";
import {
appendPendingScheduleRowsToPage,
buildScheduledTabRows,
@@ -21,7 +19,6 @@ import {
pickScheduleProviderFilters,
} from "@/components/scans/scans.utils";
import { ScansPageShell } from "@/components/scans/scans-page-shell";
-import { ScansProvidersEmptyState } from "@/components/scans/scans-providers-empty-state";
import { SkeletonTableScans } from "@/components/scans/table";
import { ScanJobsTable } from "@/components/scans/table/scan-jobs-table";
import { ContentLayout } from "@/components/shadcn/content-layout";
@@ -188,36 +185,22 @@ export default async function Scans({
const providers = providersData?.data ?? [];
const providerGroups = providerGroupsData?.data ?? [];
- const connectedProviders = providers.filter(
+ const hasConnectedProvider = providers.some(
(provider: ProviderProps) =>
provider.attributes.connection.connected === true,
);
- const thereIsNoProviders = providers.length === 0;
- const thereIsNoProvidersConnected =
- !thereIsNoProviders && connectedProviders.length === 0;
- const missingScanPrerequisite =
- thereIsNoProviders || thereIsNoProvidersConnected;
-
- if (
- missingScanPrerequisite &&
- resolvedSearchParams.onboarding === "view-first-scan"
- ) {
- redirect("/providers?onboarding=add-provider");
- }
const hasManageScansPermission = Boolean(
session?.user?.permissions?.manage_scans,
);
- const activeScanCount = missingScanPrerequisite
- ? 0
- : await getActiveScanCount(resolvedSearchParams);
- const onboardingAction = missingScanPrerequisite
- ? {
+ const activeScanCount = await getActiveScanCount(resolvedSearchParams);
+ const onboardingAction = hasConnectedProvider
+ ? { flowId: "view-first-scan" }
+ : {
flowId: "view-first-scan",
fallbackFlowId: "add-provider",
useFallback: true,
- }
- : { flowId: "view-first-scan" };
+ };
return (
- {missingScanPrerequisite ? (
- <>
- {/* The populated branch mounts inside ScansPageShell to
- enable the navbar tour icon. The empty branch must mark the route
- ready too, otherwise the icon (which falls back to the add-provider
- flow here) stays hidden for users with no connected provider. */}
-
-
- >
- ) : (
-
-
- }
- >
-
+
-
-
- )}
+ }
+ >
+
+
+
);
}
diff --git a/ui/changelog.d/display-imported-scans.fixed.md b/ui/changelog.d/display-imported-scans.fixed.md
new file mode 100644
index 0000000000..9b8e01fdb0
--- /dev/null
+++ b/ui/changelog.d/display-imported-scans.fixed.md
@@ -0,0 +1 @@
+Imported scans now appear on the Scans page even when no provider is connected
diff --git a/ui/components/scans/no-providers-connected.tsx b/ui/components/scans/no-providers-connected.tsx
index d2cd428987..b2e85d19ab 100644
--- a/ui/components/scans/no-providers-connected.tsx
+++ b/ui/components/scans/no-providers-connected.tsx
@@ -18,12 +18,8 @@ export const NoProvidersConnected = () => {
- No providers are currently connected. Connecting a provider is
- required to launch on-demand scans.
-
-
- Once the providers are correctly configured, this message will
- disappear, and on-demand scans can be launched.
+ None of your providers are connected yet. Connect one to launch
+ on-demand scans — imported scans still appear below.
diff --git a/ui/components/scans/scans-page-shell.test.tsx b/ui/components/scans/scans-page-shell.test.tsx
index c142c2e3ac..689fe51846 100644
--- a/ui/components/scans/scans-page-shell.test.tsx
+++ b/ui/components/scans/scans-page-shell.test.tsx
@@ -144,6 +144,19 @@ const providers: ProviderProps[] = [
},
];
+const disconnectedProviders: ProviderProps[] = [
+ {
+ ...providers[0],
+ attributes: {
+ ...providers[0].attributes,
+ connection: {
+ ...providers[0].attributes.connection,
+ connected: false,
+ },
+ },
+ },
+];
+
describe("ScansPageShell", () => {
afterEach(() => {
vi.unstubAllEnvs();
@@ -416,4 +429,51 @@ describe("ScansPageShell", () => {
expect(calledUrl).toContain("tab=scheduled");
expect(calledUrl).not.toContain("filter%5Btrigger%5D");
});
+
+ it("shows a non-blocking hint when no provider is connected, while still rendering the table", () => {
+ vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "false");
+
+ render(
+
+ Scans table
+ ,
+ );
+
+ expect(screen.getByText("No Connected Providers")).toBeInTheDocument();
+ // The table (and therefore imported scans) must still render below the hint.
+ expect(screen.getByText("Scans table")).toBeInTheDocument();
+ });
+
+ it("shows the no-providers hint when there are no providers, while still rendering the table", () => {
+ vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "false");
+
+ render(
+
+ Scans table
+ ,
+ );
+
+ expect(screen.getByText("No Providers Configured")).toBeInTheDocument();
+ expect(screen.getByText("Scans table")).toBeInTheDocument();
+ });
+
+ it("does not show the providers hint when a provider is connected", () => {
+ vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "false");
+
+ render(
+
+ Scans table
+ ,
+ );
+
+ expect(
+ screen.queryByText("No Connected Providers"),
+ ).not.toBeInTheDocument();
+ expect(
+ screen.queryByText("No Providers Configured"),
+ ).not.toBeInTheDocument();
+ });
});
diff --git a/ui/components/scans/scans-page-shell.tsx b/ui/components/scans/scans-page-shell.tsx
index b553d224b5..739a96753f 100644
--- a/ui/components/scans/scans-page-shell.tsx
+++ b/ui/components/scans/scans-page-shell.tsx
@@ -30,6 +30,7 @@ const viewFirstScanFlow = getFlowById("view-first-scan")!;
import { CliImportBanner } from "./cli-import-banner";
import { LaunchScanModal } from "./launch-scan-modal";
import { ScansFilterBar } from "./scans-filter-bar";
+import { ScansProvidersEmptyState } from "./scans-providers-empty-state";
import { useScansFilters } from "./use-scans-filters";
interface ScansPageShellProps {
@@ -68,6 +69,10 @@ export function ScansPageShell({
const hasConnectedProviders = providers.some(
(provider) => provider.attributes.connection.connected === true,
);
+ const thereAreNoProviders = providers.length === 0;
+ // Non-blocking onboarding hint: shown when scans can't be launched (no provider, or
+ // none connected). The table still renders below, so imported scans stay visible.
+ const showProvidersHint = thereAreNoProviders || !hasConnectedProviders;
const isCloudEnvironment = isCloud();
const launchDisabled = !hasManageScansPermission || !hasConnectedProviders;
const launchOpen = isLaunchScanModalOpen || urlLaunchOpen;
@@ -113,6 +118,12 @@ export function ScansPageShell({
{/* Signals the navbar that this route's data has loaded (enables the replay icon). */}
+ {showProvidersHint && (
+
+ )}
- {thereIsNoProviders ? (
-
- ) : (
-
- )}
- >
+ return thereIsNoProviders ? (
+
+ ) : (
+
);
}