Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 84 additions & 15 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import {
schemaResourceTypes
} from "./components/SchemaResourcePage/resourceTypes";
import { ConfigPageContextProvider } from "./context/ConfigPageContext";
import { ConfigDetailsBaseRouteProvider } from "./components/Configs/ConfigDetailsBaseRouteContext";
import { useFeatureFlagsContext } from "./context/FeatureFlagsContext";
import { HealthPageContextProvider } from "./context/HealthPageContext";
import { IncidentPageContextProvider } from "./context/IncidentPageContext";
Expand All @@ -61,12 +62,6 @@ const TopologyPage = dynamic(
import("@flanksource-ui/pages/TopologyPage").then((mod) => mod.TopologyPage)
);

const TopologyCardPage = dynamic(
import("@flanksource-ui/pages/TopologyCard").then(
(mod) => mod.TopologyCardPage
)
);

const IncidentDetailsPage = dynamic(
import("@flanksource-ui/pages/incident/IncidentDetails").then(
(mod) => mod.IncidentDetailsPage
Expand Down Expand Up @@ -495,10 +490,7 @@ export function HealthRoutes({ sidebar }: { sidebar: ReactNode }) {
export function IncidentManagerRoutes({ sidebar }: { sidebar: ReactNode }) {
const { featureFlagsLoaded } = useFeatureFlagsContext();

if (
!featureFlagsLoaded &&
!window.location.pathname.startsWith("/view/topology")
) {
if (!featureFlagsLoaded) {
return <FullPageSkeletonLoader />;
}

Expand All @@ -508,17 +500,94 @@ export function IncidentManagerRoutes({ sidebar }: { sidebar: ReactNode }) {
<Route index element={<HomepageRedirect />} />
</Route>

<Route path="/embed/health" element={<HealthPage url={CANARY_API} />} />

{/* Config embed routes — no sidebar */}
<Route
path="/embed/config/:id"
element={
<ConfigDetailsBaseRouteProvider baseRoute="/embed/config" embedded>
{withAuthorizationAccessCheck(
<ConfigDetailsPage />,
tables.database,
"read",
true
)}
</ConfigDetailsBaseRouteProvider>
}
/>
<Route
path="/embed/config/:id/changes"
element={
<ConfigDetailsBaseRouteProvider baseRoute="/embed/config" embedded>
{withAuthorizationAccessCheck(
<ConfigDetailsChangesPage />,
tables.database,
"read",
true
)}
</ConfigDetailsBaseRouteProvider>
}
/>
<Route
path="/embed/config/:id/relationships"
element={
<ConfigDetailsBaseRouteProvider baseRoute="/embed/config" embedded>
{withAuthorizationAccessCheck(
<ConfigDetailsRelationshipsPage />,
tables.database,
"read",
true
)}
</ConfigDetailsBaseRouteProvider>
}
/>
<Route
path="/embed/config/:id/access"
element={
<ConfigDetailsBaseRouteProvider baseRoute="/embed/config" embedded>
{withAuthorizationAccessCheck(
<ConfigDetailsAccessPage />,
tables.database,
"read",
true
)}
</ConfigDetailsBaseRouteProvider>
}
/>
<Route
path="/embed/config/:id/playbooks"
element={
<ConfigDetailsBaseRouteProvider baseRoute="/embed/config" embedded>
{withAuthorizationAccessCheck(
<ConfigDetailsPlaybooksPage />,
tables.database,
"read",
true
)}
</ConfigDetailsBaseRouteProvider>
}
/>
Comment on lines +505 to +570
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

rg -n "path:" src/components/Configs/ConfigTabsLinks.tsx

Repository: flanksource/flanksource-ui

Length of output: 434


🏁 Script executed:

# Check if there are more embed/config routes beyond what's shown in the snippet (lines 505-570)
rg -n "path=\"/embed/config" src/App.tsx

Repository: flanksource/flanksource-ui

Length of output: 293


🏁 Script executed:

# Check if ConfigTabsLinks has any conditional logic for embed mode
cat -n src/components/Configs/ConfigTabsLinks.tsx | head -160

Repository: flanksource/flanksource-ui

Length of output: 5234


Add missing embed config routes for insights, checks, access-logs, and view tabs.

The ConfigTabsLinks hook generates tabs for Insights, Checks, Access Logs, and View tabs regardless of the base route. When baseRoute="/embed/config", these tabs are rendered but lack corresponding /embed/config/:id/insights, /embed/config/:id/checks, /embed/config/:id/access-logs, and /embed/config/:id/view/:viewId routes, causing navigation breakage or context loss when users click them.

Implement the missing embed routes to maintain embed context consistency.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/App.tsx` around lines 505 - 570, Add missing embed routes for the config
tabs so embedded tab links don't break: register Route entries for
path="/embed/config/:id/insights", "/embed/config/:id/checks",
"/embed/config/:id/access-logs", and "/embed/config/:id/view/:viewId" using the
same pattern as the existing embed routes—wrap each element in
<ConfigDetailsBaseRouteProvider baseRoute="/embed/config" embedded> and pass the
corresponding page components (e.g., ConfigDetailsInsightsPage,
ConfigDetailsChecksPage, ConfigDetailsAccessLogsPage, ConfigDetailsViewPage)
through withAuthorizationAccessCheck with tables.database, "read", true to
preserve auth and embed context. Ensure component names match the imported page
components used elsewhere.


{/* Views embed routes — no sidebar */}
<Route
path="/view/topology/:id"
path="/embed/views/:namespace/:name"
element={withAuthorizationAccessCheck(
<TopologyCardPage />,
tables.topologies,
<ViewPage />,
tables.views,
"read",
true
)}
/>
<Route
path="/embed/views/:id"
element={withAuthorizationAccessCheck(
<ViewPage />,
tables.views,
"read",
true
)}
/>

<Route path="/view/health" element={<HealthPage url={CANARY_API} />} />

<Route path="topology" element={sidebar}>
<Route
Expand Down
32 changes: 32 additions & 0 deletions src/components/Configs/ConfigDetailsBaseRouteContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { createContext, ReactNode, useContext } from "react";

type ConfigDetailsContext = { baseRoute: string; embedded: boolean };

const ConfigDetailsBaseRouteContext = createContext<ConfigDetailsContext>({
baseRoute: "/catalog",
embedded: false
});

export function useConfigDetailsBaseRoute() {
return useContext(ConfigDetailsBaseRouteContext).baseRoute;
}

export function useConfigDetailsEmbedded() {
return useContext(ConfigDetailsBaseRouteContext).embedded;
}

export function ConfigDetailsBaseRouteProvider({
baseRoute,
embedded = false,
children
}: {
baseRoute: string;
embedded?: boolean;
children: ReactNode;
}) {
return (
<ConfigDetailsBaseRouteContext.Provider value={{ baseRoute, embedded }}>
{children}
</ConfigDetailsBaseRouteContext.Provider>
);
}
18 changes: 16 additions & 2 deletions src/components/Configs/ConfigDetailsTabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ import PlaybooksDropdownMenu from "../Playbooks/Runs/Submit/PlaybooksDropdownMen
import { ErrorBoundary } from "../ErrorBoundary";
import { useConfigDetailsTabs } from "./ConfigTabsLinks";
import ConfigSidebar from "./Sidebar/ConfigSidebar";
import {
useConfigDetailsBaseRoute,
useConfigDetailsEmbedded
} from "./ConfigDetailsBaseRouteContext";

type ConfigDetailsTabsProps = {
refetch?: () => void;
Expand All @@ -30,6 +34,8 @@ type ConfigDetailsTabsProps = {
| string; // Views
className?: string;
extra?: ReactNode;
/** Override the base route for tab links. Defaults to context value (usually "/catalog"). */
baseRoute?: string;
};

export function ConfigDetailsTabs({
Expand All @@ -39,8 +45,12 @@ export function ConfigDetailsTabs({
pageTitlePrefix,
activeTabName = "Spec",
className = "p-2",
extra
extra,
baseRoute
}: ConfigDetailsTabsProps) {
const contextBaseRoute = useConfigDetailsBaseRoute();
const embedded = useConfigDetailsEmbedded();
const resolvedBaseRoute = baseRoute ?? contextBaseRoute;
const { id } = useParams();

const [, setRefreshButtonClickedTrigger] = useAtom(
Expand All @@ -50,7 +60,10 @@ export function ConfigDetailsTabs({
const { data: configItem, isLoading: isLoadingConfig } =
useGetConfigByIdQuery(id!);

const { tabs: configTabList } = useConfigDetailsTabs(configItem?.summary);
const { tabs: configTabList } = useConfigDetailsTabs(
configItem?.summary,
resolvedBaseRoute
);

const playbooksButton = id ? (
<PlaybooksDropdownMenu config_id={id} containerClassName="my-0" />
Expand Down Expand Up @@ -87,6 +100,7 @@ export function ConfigDetailsTabs({
loading={isLoading}
extra={layoutExtra}
contentClass="p-0 h-full overflow-y-hidden"
hideChrome={embedded}
>
<div className="flex min-h-0 min-w-0 flex-1 flex-row overflow-y-hidden">
<div className="flex min-h-0 min-w-0 flex-1 flex-col">
Expand Down
25 changes: 15 additions & 10 deletions src/components/Configs/ConfigTabsLinks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ type ConfigDetailsTab = {
search?: string;
};

export function useConfigDetailsTabs(countSummary?: ConfigItem["summary"]): {
export function useConfigDetailsTabs(
countSummary?: ConfigItem["summary"],
baseRoute = "/catalog"
): {
isLoading: boolean;
isError: boolean;
tabs: ConfigDetailsTab[];
Expand All @@ -41,8 +44,10 @@ export function useConfigDetailsTabs(countSummary?: ConfigItem["summary"]): {
const accessLogsCount =
accessLogsData?.totalEntries ?? accessLogsData?.data?.length ?? 0;

const base = `${baseRoute}/${id}`;

const staticTabs: ConfigDetailsTab[] = [
{ label: "Spec", key: "Spec", path: `/catalog/${id}/spec` },
{ label: "Spec", key: "Spec", path: `${base}/spec` },
{
label: (
<>
Expand All @@ -51,7 +56,7 @@ export function useConfigDetailsTabs(countSummary?: ConfigItem["summary"]): {
</>
),
key: "Changes",
path: `/catalog/${id}/changes`
path: `${base}/changes`
},
{
label: (
Expand All @@ -61,7 +66,7 @@ export function useConfigDetailsTabs(countSummary?: ConfigItem["summary"]): {
</>
),
key: "Insights",
path: `/catalog/${id}/insights`
path: `${base}/insights`
},
{
label: (
Expand All @@ -71,7 +76,7 @@ export function useConfigDetailsTabs(countSummary?: ConfigItem["summary"]): {
</>
),
key: "Relationships",
path: `/catalog/${id}/relationships`
path: `${base}/relationships`
},
{
label: (
Expand All @@ -81,7 +86,7 @@ export function useConfigDetailsTabs(countSummary?: ConfigItem["summary"]): {
</>
),
key: "Playbooks",
path: `/catalog/${id}/playbooks`
path: `${base}/playbooks`
},
{
label: (
Expand All @@ -91,7 +96,7 @@ export function useConfigDetailsTabs(countSummary?: ConfigItem["summary"]): {
</>
),
key: "Checks",
path: `/catalog/${id}/checks`
path: `${base}/checks`
}
];

Expand All @@ -104,7 +109,7 @@ export function useConfigDetailsTabs(countSummary?: ConfigItem["summary"]): {
</>
),
key: "Access",
path: `/catalog/${id}/access`
path: `${base}/access`
});
}

Expand All @@ -117,7 +122,7 @@ export function useConfigDetailsTabs(countSummary?: ConfigItem["summary"]): {
</>
),
key: "Access Logs",
path: `/catalog/${id}/access-logs`
path: `${base}/access-logs`
});
}

Expand All @@ -142,7 +147,7 @@ export function useConfigDetailsTabs(countSummary?: ConfigItem["summary"]): {
const viewTabs: ConfigDetailsTab[] = orderedViews.map((view) => ({
label: view.title || view.name,
key: view.id,
path: `/catalog/${id}/view/${view.id}`,
path: `${base}/view/${view.id}`,
icon: <Icon name={view.icon || "workflow"} />
}));

Expand Down
34 changes: 0 additions & 34 deletions src/pages/TopologyCard.tsx

This file was deleted.

7 changes: 7 additions & 0 deletions src/pages/views/components/SingleView.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, { useEffect, useState } from "react";
import { useLocation } from "react-router-dom";
import Age from "../../../ui/Age/Age";
import ViewLayout from "./ViewLayout";
import ViewWithSections from "./ViewWithSections";
Expand All @@ -17,6 +18,8 @@ interface SingleViewProps {
}

const SingleView: React.FC<SingleViewProps> = ({ id }) => {
const location = useLocation();
const hideChrome = location.pathname.startsWith("/embed/views/");
const {
viewResult,
isLoading,
Expand Down Expand Up @@ -45,6 +48,7 @@ const SingleView: React.FC<SingleViewProps> = ({ id }) => {
icon="workflow"
onRefresh={handleForceRefresh}
centered
hideChrome={hideChrome}
>
<ErrorViewer error={error} className="mx-auto max-w-3xl" />
</ViewLayout>
Expand All @@ -58,6 +62,7 @@ const SingleView: React.FC<SingleViewProps> = ({ id }) => {
icon="workflow"
onRefresh={handleForceRefresh}
centered
hideChrome={hideChrome}
>
<div className="text-center">
<div className="mx-auto mb-4 h-12 w-12 animate-spin rounded-full border-b-2 border-blue-600"></div>
Expand All @@ -74,6 +79,7 @@ const SingleView: React.FC<SingleViewProps> = ({ id }) => {
icon="workflow"
onRefresh={handleForceRefresh}
centered
hideChrome={hideChrome}
>
<ErrorViewer
error="The requested view could not be found."
Expand Down Expand Up @@ -106,6 +112,7 @@ const SingleView: React.FC<SingleViewProps> = ({ id }) => {
icon={icon || "workflow"}
onRefresh={handleForceRefresh}
loading={isFetching}
hideChrome={hideChrome}
extra={
viewResult.lastRefreshedAt && (
<p className="text-sm text-gray-500">
Expand Down
Loading
Loading