Compare commits
8 Commits
chore/ms23
...
fix/missio
| Author | SHA1 | Date | |
|---|---|---|---|
| a0cc6c3ec7 | |||
| 51d6302401 | |||
| cf490510bf | |||
| 3d91334df7 | |||
| e80b624ca6 | |||
| 65536fcb75 | |||
| 53915dc621 | |||
| 398ee06920 |
@@ -146,7 +146,7 @@ export class AgentsController {
|
||||
* Return recent orchestrator events for non-streaming consumers.
|
||||
*/
|
||||
@Get("events/recent")
|
||||
@Throttle({ status: { limit: 200, ttl: 60000 } })
|
||||
@Throttle({ default: { limit: 1000, ttl: 60000 } })
|
||||
getRecentEvents(@Query("limit") limit?: string): {
|
||||
events: ReturnType<AgentEventsService["getRecentEvents"]>;
|
||||
} {
|
||||
|
||||
@@ -4,6 +4,6 @@ import { AuthGuard } from "./guards/auth.guard";
|
||||
|
||||
@Module({
|
||||
providers: [OrchestratorApiKeyGuard, AuthGuard],
|
||||
exports: [AuthGuard],
|
||||
exports: [OrchestratorApiKeyGuard, AuthGuard],
|
||||
})
|
||||
export class AuthModule {}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Outfit, Fira_Code } from "next/font/google";
|
||||
import { AuthProvider } from "@/lib/auth/auth-context";
|
||||
import { ErrorBoundary } from "@/components/error-boundary";
|
||||
import { ThemeProvider } from "@/providers/ThemeProvider";
|
||||
import { ReactQueryProvider } from "@/providers/ReactQueryProvider";
|
||||
import "./globals.css";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
@@ -56,9 +57,11 @@ export default function RootLayout({ children }: { children: ReactNode }): React
|
||||
</head>
|
||||
<body>
|
||||
<ThemeProvider>
|
||||
<ErrorBoundary>
|
||||
<AuthProvider>{children}</AuthProvider>
|
||||
</ErrorBoundary>
|
||||
<ReactQueryProvider>
|
||||
<ErrorBoundary>
|
||||
<AuthProvider>{children}</AuthProvider>
|
||||
</ErrorBoundary>
|
||||
</ReactQueryProvider>
|
||||
</ThemeProvider>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -44,6 +44,25 @@ interface AuditLogResponse {
|
||||
total: number;
|
||||
page: number;
|
||||
pages: number;
|
||||
notice?: string;
|
||||
}
|
||||
|
||||
function createEmptyAuditLogResponse(page: number, notice?: string): AuditLogResponse {
|
||||
return {
|
||||
items: [],
|
||||
total: 0,
|
||||
page,
|
||||
pages: 0,
|
||||
...(notice !== undefined ? { notice } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
function isRateLimitError(error: unknown): boolean {
|
||||
if (!(error instanceof Error)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return /429|rate limit|too many requests/i.test(error.message);
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
@@ -138,7 +157,15 @@ async function fetchAuditLog(
|
||||
params.set("sessionId", normalizedSessionId);
|
||||
}
|
||||
|
||||
return apiGet<AuditLogResponse>(`/api/mission-control/audit-log?${params.toString()}`);
|
||||
try {
|
||||
return await apiGet<AuditLogResponse>(`/api/mission-control/audit-log?${params.toString()}`);
|
||||
} catch (error) {
|
||||
if (isRateLimitError(error)) {
|
||||
return createEmptyAuditLogResponse(page, "Rate limited - retrying...");
|
||||
}
|
||||
|
||||
return createEmptyAuditLogResponse(page);
|
||||
}
|
||||
}
|
||||
|
||||
export function AuditLogDrawer({ sessionId, trigger }: AuditLogDrawerProps): React.JSX.Element {
|
||||
@@ -180,11 +207,10 @@ export function AuditLogDrawer({ sessionId, trigger }: AuditLogDrawerProps): Rea
|
||||
const totalItems = auditLogQuery.data?.total ?? 0;
|
||||
const totalPages = auditLogQuery.data?.pages ?? 0;
|
||||
const items = auditLogQuery.data?.items ?? [];
|
||||
const notice = auditLogQuery.data?.notice;
|
||||
|
||||
const canGoPrevious = page > 1;
|
||||
const canGoNext = totalPages > 0 && page < totalPages;
|
||||
const errorMessage =
|
||||
auditLogQuery.error instanceof Error ? auditLogQuery.error.message : "Failed to load audit log";
|
||||
|
||||
return (
|
||||
<Sheet open={open} onOpenChange={setOpen}>
|
||||
@@ -237,10 +263,13 @@ export function AuditLogDrawer({ sessionId, trigger }: AuditLogDrawerProps): Rea
|
||||
Loading audit log...
|
||||
</td>
|
||||
</tr>
|
||||
) : auditLogQuery.error ? (
|
||||
) : notice ? (
|
||||
<tr>
|
||||
<td colSpan={5} className="px-3 py-6 text-center text-sm text-red-500">
|
||||
{errorMessage}
|
||||
<td
|
||||
colSpan={5}
|
||||
className="px-3 py-6 text-center text-sm text-muted-foreground"
|
||||
>
|
||||
{notice}
|
||||
</td>
|
||||
</tr>
|
||||
) : items.length === 0 ? (
|
||||
|
||||
28
apps/web/src/providers/ReactQueryProvider.tsx
Normal file
28
apps/web/src/providers/ReactQueryProvider.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
"use client";
|
||||
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { useState, type ReactNode } from "react";
|
||||
|
||||
interface ReactQueryProviderProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export function ReactQueryProvider({ children }: ReactQueryProviderProps): React.JSX.Element {
|
||||
// Create a stable QueryClient per component mount (one per app session)
|
||||
const [queryClient] = useState(
|
||||
() =>
|
||||
new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
// Don't refetch on window focus in a dashboard context
|
||||
refetchOnWindowFocus: false,
|
||||
// Stale time of 30s — short enough for live data, avoids hammering
|
||||
staleTime: 30_000,
|
||||
retry: 1,
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
return <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>;
|
||||
}
|
||||
Reference in New Issue
Block a user