Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export function PageHeaderExtended({
{loadingToast}

{(tipBoxEntity || summaryCards) && (
<Stack gap="18px" sx={{ mt: "18px", mb: summaryCards ? "16px" : 0 }}>
<Stack gap="18px" sx={{ mt: "18px" }}>
{tipBoxEntity && <TipBox entityName={tipBoxEntity} />}
{summaryCards && (
<Box
Expand All @@ -80,7 +80,7 @@ export function PageHeaderExtended({
)}
</Stack>
)}
<Stack gap="16px">
<Stack gap="16px" sx={{ mt: "16px" }}>
{children}
</Stack>
</>
Expand Down
17 changes: 17 additions & 0 deletions Clients/src/presentation/components/UserGuide/WhatsNewSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,23 @@ interface ChangelogEntry {
}

const CHANGELOG: ChangelogEntry[] = [
{
version: 'v2.2',
date: 'April 2, 2026',
title: 'AI Gateway, Policy Radar, and CI/CD scanning',
summary:
'Major release introducing the AI Gateway for centralized LLM traffic control with guardrails, spend tracking, and virtual keys. Adds Policy Radar for automated vendor policy monitoring, incremental AI detection scans, GitHub webhook CI/CD integration, super admin management, and intake form builder for use case registration.',
items: [
'AI Gateway — proxy all LLM traffic through a single control plane with spend dashboards, request/response logging, and per-endpoint analytics',
'AI Gateway guardrails — PII detection (Presidio) and content filtering with block/mask actions on any endpoint',
'AI Gateway virtual keys — scoped API keys with model/provider access controls and budget limits',
'AI Gateway prompt library — save, version, and reuse prompt templates across endpoints',
'Incremental AI detection scans — scan only changed files between two commits, carrying forward baseline findings',
'GitHub webhook CI/CD integration — trigger AI detection scans automatically on push events with status reporting',
'Super admin panel — manage organizations, users, and platform settings across all tenants',
'Intake form builder — create custom use case registration forms with public submission links',
],
},
{
version: 'v2.1',
date: 'February 19, 2026',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import {
Bot,
Database,
FileSearch,
FolderGit2,
Search,
History,
Eye,
Expand Down Expand Up @@ -124,6 +125,7 @@ export const routeMapping: Record<string, string> = {
"/ai-detection/scan": "Scan repository",
"/ai-detection/history": "Scan history",
"/ai-detection/scans": "Scan history",
"/ai-detection/repositories": "Repositories",
"/ai-detection/settings": "Settings",

// AI Gateway
Expand Down Expand Up @@ -270,6 +272,7 @@ export const routeIconMapping: Record<string, () => React.ReactNode> = {
"/ai-detection/scan": () => React.createElement(Search, { size: 14, strokeWidth: 1.5 }),
"/ai-detection/history": () => React.createElement(History, { size: 14, strokeWidth: 1.5 }),
"/ai-detection/scans": () => React.createElement(History, { size: 14, strokeWidth: 1.5 }),
"/ai-detection/repositories": () => React.createElement(FolderGit2, { size: 14, strokeWidth: 1.5 }),
"/ai-detection/settings": () => React.createElement(Settings, { size: 14, strokeWidth: 1.5 }),

// Intake forms
Expand Down
96 changes: 80 additions & 16 deletions Clients/src/presentation/pages/AIGateway/Logs/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ import { sectionTitleSx, useCardSx, formatEntityType } from "../shared";

const AUTO_REFRESH_INTERVAL_MS = 10_000;
const SEARCH_DEBOUNCE_MS = 300;
const GR_PAGE_SIZE = 50;
const GR_DEFAULT_PAGE_SIZE = 25;

type StatusFilter = "all" | "success" | "error";
type SourceFilter = "all" | "playground" | "virtual-key";
Expand Down Expand Up @@ -162,12 +162,15 @@ export default function LogsPage() {
const [grLogs, setGrLogs] = useState<any[]>([]);
const [grTotal, setGrTotal] = useState(0);
const [grPage, setGrPage] = useState(0);
const [grRowsPerPage, setGrRowsPerPage] = useState(() =>
getPaginationRowCount("aiGatewayGuardrailLogs", GR_DEFAULT_PAGE_SIZE)
);
const [grLoading, setGrLoading] = useState(false);

const loadGuardrailLogs = useCallback(async () => {
setGrLoading(true);
try {
const res = await apiServices.get<Record<string, any>>(`/ai-gateway/guardrails/logs?limit=${GR_PAGE_SIZE}&offset=${grPage * GR_PAGE_SIZE}`);
const res = await apiServices.get<Record<string, any>>(`/ai-gateway/guardrails/logs?limit=${grRowsPerPage}&offset=${grPage * grRowsPerPage}`);
const data = res?.data || {};
setGrLogs(data.logs || data?.data?.logs || []);
setGrTotal(data.total || data.logs?.length || 0);
Expand All @@ -176,7 +179,7 @@ export default function LogsPage() {
} finally {
setGrLoading(false);
}
}, [grPage]);
}, [grPage, grRowsPerPage]);

useEffect(() => {
if (activeLogTab === "guardrails") loadGuardrailLogs();
Expand Down Expand Up @@ -308,9 +311,9 @@ export default function LogsPage() {
</Typography>
</Stack>
<CustomizableButton
text={loading ? "Loading..." : "Refresh"}
text={(activeLogTab === "requests" ? loading : grLoading) ? "Loading..." : "Refresh"}
icon={<RefreshCw size={14} strokeWidth={1.5} />}
onClick={() => loadLogs(page, rowsPerPage)}
onClick={() => activeLogTab === "requests" ? loadLogs(page, rowsPerPage) : loadGuardrailLogs()}
/>
</Stack>
}
Expand Down Expand Up @@ -776,17 +779,78 @@ export default function LogsPage() {
))}

{/* Pagination */}
{grTotal > GR_PAGE_SIZE && (
<Stack direction="row" justifyContent="space-between" alignItems="center" sx={{ pt: "12px" }}>
<Typography sx={{ fontSize: 12, color: palette.text.tertiary }}>
Page {grPage + 1} of {Math.ceil(grTotal / GR_PAGE_SIZE)} ({grTotal} total)
</Typography>
<Stack direction="row" gap="8px">
<CustomizableButton text="Previous" variant="outlined" onClick={() => setGrPage(Math.max(0, grPage - 1))} isDisabled={grPage === 0} />
<CustomizableButton text="Next" variant="outlined" onClick={() => setGrPage(grPage + 1)} isDisabled={(grPage + 1) * GR_PAGE_SIZE >= grTotal} />
</Stack>
</Stack>
)}
<Table>
<TableBody>
<TableRow>
<TablePagination
count={grTotal}
page={grPage}
onPageChange={(_, newPage) => setGrPage(newPage)}
rowsPerPage={grRowsPerPage}
rowsPerPageOptions={[10, 25, 50]}
onRowsPerPageChange={(e) => {
const rpp = parseInt(e.target.value, 10);
setGrRowsPerPage(rpp);
setGrPage(0);
setPaginationRowCount("aiGatewayGuardrailLogs", rpp);
}}
ActionsComponent={(props) => <TablePaginationActions {...props} />}
labelRowsPerPage="Rows per page"
labelDisplayedRows={({ page, count }) =>
`Page ${page + 1} of ${Math.max(1, Math.ceil(count / grRowsPerPage))}`
}
slotProps={{
select: {
MenuProps: {
keepMounted: true,
PaperProps: {
className: "pagination-dropdown",
sx: { mt: 0, mb: theme.spacing(2) },
},
transformOrigin: { vertical: "bottom", horizontal: "left" },
anchorOrigin: { vertical: "top", horizontal: "left" },
sx: { mt: theme.spacing(-2) },
},
inputProps: { id: "gr-pagination-dropdown" },
IconComponent: () => <ChevronsUpDown size={16} />,
sx: {
ml: theme.spacing(4),
mr: theme.spacing(12),
minWidth: theme.spacing(20),
textAlign: "left",
"&.Mui-focused > div": {
backgroundColor: theme.palette.background.main,
},
},
},
}}
sx={{
backgroundColor: theme.palette.grey[50],
border: `1px solid ${theme.palette.border.light}`,
borderTop: "none",
borderRadius: `0 0 ${theme.shape.borderRadius}px ${theme.shape.borderRadius}px`,
color: theme.palette.text.secondary,
height: "50px",
minHeight: "50px",
"& .MuiTablePagination-toolbar": {
minHeight: "50px",
paddingTop: "4px",
paddingBottom: "4px",
},
"& .MuiSelect-icon": {
width: "24px",
height: "fit-content",
},
"& .MuiSelect-select": {
width: theme.spacing(10),
borderRadius: theme.shape.borderRadius,
border: `1px solid ${theme.palette.border.light}`,
},
}}
/>
</TableRow>
</TableBody>
</Table>
</Stack>
)}
</Stack>
Expand Down
Loading