Skip to content
33 changes: 29 additions & 4 deletions src/api/services/views.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,16 +70,16 @@ export const getAllViews = (
/**
* Get the data for a view by its id.
*/
export const getViewDataById = async (
viewId: string,
const fetchViewData = async (
path: string,
variables?: Record<string, string>,
headers?: Record<string, string>
): Promise<ViewResult> => {
const body: { variables?: Record<string, string> } = {
variables: variables
variables
};

const response = await fetch(`/api/view/${viewId}`, {
const response = await fetch(path, {
method: "POST",
credentials: "include",
headers: {
Expand All @@ -99,6 +99,31 @@ export const getViewDataById = async (
return response.json();
};

export const getViewDataById = async (
viewId: string,
variables?: Record<string, string>,
headers?: Record<string, string>
): Promise<ViewResult> => {
return fetchViewData(
`/api/view/${encodeURIComponent(viewId)}`,
variables,
headers
);
};

export const getViewDataByNamespace = async (
namespace: string,
name: string,
variables?: Record<string, string>,
headers?: Record<string, string>
): Promise<ViewResult> => {
return fetchViewData(
`/api/view/${encodeURIComponent(namespace)}/${encodeURIComponent(name)}`,
variables,
headers
);
};

/**
* Get display plugin variables for a view based on a config
*/
Expand Down
191 changes: 99 additions & 92 deletions src/pages/audit-report/components/View/View.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import GlobalFilters from "./GlobalFilters";
import GlobalFiltersForm from "./GlobalFiltersForm";
import { usePrefixedSearchParams } from "../../../../hooks/usePrefixedSearchParams";
import ViewCardsDisplay from "./ViewCardsDisplay";
import { VIEW_VAR_PREFIX } from "@flanksource-ui/pages/views/constants";

interface ViewProps {
title?: string;
Expand All @@ -43,6 +44,7 @@ interface ViewProps {
};
requestFingerprint: string;
currentVariables?: Record<string, string>;
hideVariables?: boolean;
}

const View: React.FC<ViewProps> = ({
Expand All @@ -55,7 +57,8 @@ const View: React.FC<ViewProps> = ({
variables,
card,
requestFingerprint,
currentVariables
currentVariables,
hideVariables
}) => {
const { pageSize } = useReactTablePaginationState();

Expand All @@ -66,8 +69,8 @@ const View: React.FC<ViewProps> = ({
// Separate display mode state (frontend only, not sent to backend)
const [searchParams, setSearchParams] = useSearchParams();

// Create unique prefix for global filters
const globalVarPrefix = "viewvar";
// Create unique prefix for global filters (same as ViewSection uses)
const globalVarPrefix = VIEW_VAR_PREFIX;
const hasDataTable = columns && columns.length > 0;

// Detect if card mode is available (supports both new and old cardPosition field)
Expand Down Expand Up @@ -161,94 +164,96 @@ const View: React.FC<ViewProps> = ({

return (
<>
{title !== "" && (
<h3 className="mb-4 flex items-center text-xl font-semibold">
<Box className="mr-2 text-teal-600" size={20} />
{title}
</h3>
)}

{variables && variables.length > 0 && (
<GlobalFiltersForm
variables={variables}
globalVarPrefix={globalVarPrefix}
currentVariables={currentVariables}
>
<GlobalFilters variables={variables} />
</GlobalFiltersForm>
)}

{variables && variables.length > 0 && (
<hr className="my-4 border-gray-200" />
)}
<div className="flex-none">
{title !== "" && (
<h3 className="mb-4 flex items-center text-xl font-semibold">
<Box className="mr-2 text-teal-600" size={20} />
{title}
</h3>
)}

<div className="mb-4 space-y-6">
{panels && panels.length > 0 && (
<div
className="grid gap-4"
style={{
gridTemplateColumns: "repeat(auto-fit, minmax(280px, 1fr))",
gridAutoRows: "minmax(auto, 250px)"
}}
{!hideVariables && variables && variables.length > 0 && (
<GlobalFiltersForm
variables={variables}
globalVarPrefix={globalVarPrefix}
currentVariables={currentVariables}
>
{groupAndRenderPanels(panels)}
</div>
<GlobalFilters variables={variables} />
</GlobalFiltersForm>
)}
</div>

<ViewTableFilterForm
filterFields={filterFields}
defaultFieldValues={{}}
tablePrefix={tablePrefix}
>
{hasDataTable && (
<div className="mb-2">
<div className="flex flex-wrap items-center justify-between gap-2">
<div className="flex flex-wrap items-center gap-2">
{filterableColumns.map(({ column, options }) => (
<ViewColumnDropdown
key={column.name}
label={formatDisplayLabel(column.name)}
paramsKey={column.name}
options={options}
isLabelsColumn={column.type === "labels"}
/>
))}
</div>
{!hideVariables && variables && variables.length > 0 && (
<hr className="my-4 border-gray-200" />
)}

<div className="mb-4 space-y-6">
{panels && panels.length > 0 && (
<div
className="grid gap-4"
style={{
gridTemplateColumns: "repeat(auto-fit, minmax(280px, 1fr))",
gridAutoRows: "minmax(auto, 250px)"
}}
>
{groupAndRenderPanels(panels)}
</div>
)}
</div>

{/* Display Mode Toggle */}
{hasCardMode && (
<div className="flex items-center gap-1 rounded-lg border border-gray-300 bg-white p-1">
<button
onClick={() => setDisplayMode("table")}
className={`flex items-center gap-1.5 rounded px-3 py-1.5 text-sm font-medium transition-colors ${
displayMode === "table"
? "bg-blue-100 text-blue-700"
: "text-gray-600 hover:bg-gray-100"
}`}
title="Table view"
>
<Table2 size={16} />
Table
</button>
<button
onClick={() => setDisplayMode("cards")}
className={`flex items-center gap-1.5 rounded px-3 py-1.5 text-sm font-medium transition-colors ${
displayMode === "cards"
? "bg-blue-100 text-blue-700"
: "text-gray-600 hover:bg-gray-100"
}`}
title="Card view"
>
<LayoutGrid size={16} />
Cards
</button>
<ViewTableFilterForm
filterFields={filterFields}
defaultFieldValues={{}}
tablePrefix={tablePrefix}
>
{hasDataTable && (
<div className="mb-2">
<div className="flex flex-wrap items-center justify-between gap-2">
<div className="flex flex-wrap items-center gap-2">
{filterableColumns.map(({ column, options }) => (
<ViewColumnDropdown
key={column.name}
label={formatDisplayLabel(column.name)}
paramsKey={column.name}
options={options}
isLabelsColumn={column.type === "labels"}
/>
))}
</div>
)}

{/* Display Mode Toggle */}
{hasCardMode && (
<div className="flex items-center gap-1 rounded-lg border border-gray-300 bg-white p-1">
<button
onClick={() => setDisplayMode("table")}
className={`flex items-center gap-1.5 rounded px-3 py-1.5 text-sm font-medium transition-colors ${
displayMode === "table"
? "bg-blue-100 text-blue-700"
: "text-gray-600 hover:bg-gray-100"
}`}
title="Table view"
>
<Table2 size={16} />
Table
</button>
<button
onClick={() => setDisplayMode("cards")}
className={`flex items-center gap-1.5 rounded px-3 py-1.5 text-sm font-medium transition-colors ${
displayMode === "cards"
? "bg-blue-100 text-blue-700"
: "text-gray-600 hover:bg-gray-100"
}`}
title="Card view"
>
<LayoutGrid size={16} />
Cards
</button>
</div>
)}
</div>
</div>
</div>
)}
</ViewTableFilterForm>
)}
</ViewTableFilterForm>
</div>

{tableError && (
<div className="text-center text-red-500">
Expand All @@ -270,14 +275,16 @@ const View: React.FC<ViewProps> = ({
totalRowCount={totalEntries}
/>
) : (
<DynamicDataTable
columns={columns}
isLoading={isLoading}
rows={rows || []}
pageCount={totalEntries ? Math.ceil(totalEntries / pageSize) : 1}
totalRowCount={totalEntries}
tablePrefix={tablePrefix}
/>
<div className="min-h-0 flex-1">
<DynamicDataTable
columns={columns}
isLoading={isLoading}
rows={rows || []}
pageCount={totalEntries ? Math.ceil(totalEntries / pageSize) : 1}
totalRowCount={totalEntries}
tablePrefix={tablePrefix}
/>
</div>
))}
</>
);
Expand Down
12 changes: 12 additions & 0 deletions src/pages/audit-report/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,17 @@ export interface ColumnFilterOptions {
labels?: Record<string, string[]>;
}

export interface ViewRef {
namespace: string;
name: string;
}

export interface ViewSection {
title: string;
icon?: string;
viewRef: ViewRef;
}

export interface ViewResult {
title?: string;
icon?: string;
Expand All @@ -390,6 +401,7 @@ export interface ViewResult {
variables?: ViewVariable[];
card?: DisplayCard;
requestFingerprint: string;
sections?: ViewSection[];
}

export interface GaugeThreshold {
Expand Down
Loading
Loading