diff --git a/src/App.tsx b/src/App.tsx index 1eb219a13..ea02d27a2 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -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"; @@ -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 @@ -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 ; } @@ -508,17 +500,94 @@ export function IncidentManagerRoutes({ sidebar }: { sidebar: ReactNode }) { } /> + } /> + + {/* Config embed routes — no sidebar */} + + {withAuthorizationAccessCheck( + , + tables.database, + "read", + true + )} + + } + /> + + {withAuthorizationAccessCheck( + , + tables.database, + "read", + true + )} + + } + /> + + {withAuthorizationAccessCheck( + , + tables.database, + "read", + true + )} + + } + /> + + {withAuthorizationAccessCheck( + , + tables.database, + "read", + true + )} + + } + /> + + {withAuthorizationAccessCheck( + , + tables.database, + "read", + true + )} + + } + /> + + {/* Views embed routes — no sidebar */} , - tables.topologies, + , + tables.views, + "read", + true + )} + /> + , + tables.views, "read", true )} /> - - } /> ({ + 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 ( + + {children} + + ); +} diff --git a/src/components/Configs/ConfigDetailsTabs.tsx b/src/components/Configs/ConfigDetailsTabs.tsx index 6afef4d3e..f5f3187f8 100644 --- a/src/components/Configs/ConfigDetailsTabs.tsx +++ b/src/components/Configs/ConfigDetailsTabs.tsx @@ -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; @@ -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({ @@ -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( @@ -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 ? ( @@ -87,6 +100,7 @@ export function ConfigDetailsTabs({ loading={isLoading} extra={layoutExtra} contentClass="p-0 h-full overflow-y-hidden" + hideChrome={embedded} >
diff --git a/src/components/Configs/ConfigTabsLinks.tsx b/src/components/Configs/ConfigTabsLinks.tsx index 5bc31ac4f..d1bfcc157 100644 --- a/src/components/Configs/ConfigTabsLinks.tsx +++ b/src/components/Configs/ConfigTabsLinks.tsx @@ -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[]; @@ -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: ( <> @@ -51,7 +56,7 @@ export function useConfigDetailsTabs(countSummary?: ConfigItem["summary"]): { ), key: "Changes", - path: `/catalog/${id}/changes` + path: `${base}/changes` }, { label: ( @@ -61,7 +66,7 @@ export function useConfigDetailsTabs(countSummary?: ConfigItem["summary"]): { ), key: "Insights", - path: `/catalog/${id}/insights` + path: `${base}/insights` }, { label: ( @@ -71,7 +76,7 @@ export function useConfigDetailsTabs(countSummary?: ConfigItem["summary"]): { ), key: "Relationships", - path: `/catalog/${id}/relationships` + path: `${base}/relationships` }, { label: ( @@ -81,7 +86,7 @@ export function useConfigDetailsTabs(countSummary?: ConfigItem["summary"]): { ), key: "Playbooks", - path: `/catalog/${id}/playbooks` + path: `${base}/playbooks` }, { label: ( @@ -91,7 +96,7 @@ export function useConfigDetailsTabs(countSummary?: ConfigItem["summary"]): { ), key: "Checks", - path: `/catalog/${id}/checks` + path: `${base}/checks` } ]; @@ -104,7 +109,7 @@ export function useConfigDetailsTabs(countSummary?: ConfigItem["summary"]): { ), key: "Access", - path: `/catalog/${id}/access` + path: `${base}/access` }); } @@ -117,7 +122,7 @@ export function useConfigDetailsTabs(countSummary?: ConfigItem["summary"]): { ), key: "Access Logs", - path: `/catalog/${id}/access-logs` + path: `${base}/access-logs` }); } @@ -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: })); diff --git a/src/pages/TopologyCard.tsx b/src/pages/TopologyCard.tsx deleted file mode 100644 index aba1c0338..000000000 --- a/src/pages/TopologyCard.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { useQuery } from "@tanstack/react-query"; -import { useParams } from "react-router-dom"; -import { getTopology } from "../api/services/topology"; -import { InfoMessage } from "../components/InfoMessage"; -import { TopologyCard } from "../components/Topology/TopologyCard"; -import { Head } from "../ui/Head"; - -export function TopologyCardPage() { - const { id, size } = useParams(); - - const { data } = useQuery({ - queryKey: ["topology", id], - queryFn: () => getTopology({ id: id }) - }); - if (!data || !data.components || data.components?.length === 0) { - return ; - } - - const topology = data.components[0]; - return ( - <> - - -
- -
- - ); -} diff --git a/src/pages/views/components/SingleView.tsx b/src/pages/views/components/SingleView.tsx index a9a122655..d7b8c9ea0 100644 --- a/src/pages/views/components/SingleView.tsx +++ b/src/pages/views/components/SingleView.tsx @@ -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"; @@ -17,6 +18,8 @@ interface SingleViewProps { } const SingleView: React.FC = ({ id }) => { + const location = useLocation(); + const hideChrome = location.pathname.startsWith("/embed/views/"); const { viewResult, isLoading, @@ -45,6 +48,7 @@ const SingleView: React.FC = ({ id }) => { icon="workflow" onRefresh={handleForceRefresh} centered + hideChrome={hideChrome} > @@ -58,6 +62,7 @@ const SingleView: React.FC = ({ id }) => { icon="workflow" onRefresh={handleForceRefresh} centered + hideChrome={hideChrome} >
@@ -74,6 +79,7 @@ const SingleView: React.FC = ({ id }) => { icon="workflow" onRefresh={handleForceRefresh} centered + hideChrome={hideChrome} > = ({ id }) => { icon={icon || "workflow"} onRefresh={handleForceRefresh} loading={isFetching} + hideChrome={hideChrome} extra={ viewResult.lastRefreshedAt && (

diff --git a/src/pages/views/components/ViewLayout.tsx b/src/pages/views/components/ViewLayout.tsx index 803dbcea8..e296bac27 100644 --- a/src/pages/views/components/ViewLayout.tsx +++ b/src/pages/views/components/ViewLayout.tsx @@ -12,6 +12,7 @@ interface ViewLayoutProps { extra?: React.ReactNode; children: React.ReactNode; centered?: boolean; + hideChrome?: boolean; } const ViewLayout: React.FC = ({ @@ -21,7 +22,8 @@ const ViewLayout: React.FC = ({ loading, extra, children, - centered = false + centered = false, + hideChrome = false }) => ( <> @@ -40,6 +42,7 @@ const ViewLayout: React.FC = ({ contentClass="p-0 h-full" loading={loading} extra={extra} + hideChrome={hideChrome} > {centered ? (

diff --git a/src/ui/Layout/SearchLayout.tsx b/src/ui/Layout/SearchLayout.tsx index 9d38ce26f..1c8d5edf1 100644 --- a/src/ui/Layout/SearchLayout.tsx +++ b/src/ui/Layout/SearchLayout.tsx @@ -37,6 +37,7 @@ interface IProps { loading?: boolean; extra?: React.ReactNode; extraClassName?: string; + hideChrome?: boolean; } function SearchLayoutInner({ @@ -46,7 +47,8 @@ function SearchLayoutInner({ title, extra, loading, - onRefresh + onRefresh, + hideChrome = false }: IProps) { const [topologyCardSize, setTopologyCardSize] = useAtom(cardPreferenceAtom); const { requestAiFeatures, aiLoaded } = useAiFeatureLoader(); @@ -72,64 +74,76 @@ function SearchLayoutInner({ className={`ml-4 flex items-center gap-3 md:ml-6 ${extraClassName}`} > {extra} -
- {onRefresh && ( -
- + {hideChrome ? ( + onRefresh && ( +
+
+ +
+
+ ) + ) : ( + <> +
+ {onRefresh && ( +
+ +
+ )} +
+ +
- )} -
- -
-
-
- - {!isAiDisabled && - (aiLoaded ? ( - + + {!isAiDisabled && + (aiLoaded ? ( + + + + } + > + + + ) : ( - } - > - - - ) : ( - - ))} - - - -
- -
-
- + + +
+ +
+
+ + + )}